From 34b0c4b5dd7352b3f78c8ca7ea03420a3c61b25c Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 15 Mar 2026 15:28:59 +0000 Subject: [PATCH 01/10] chore: sync go.mod dependencies Co-Authored-By: Virgil --- go.mod | 50 +++++++++++------------ go.sum | 125 ++++++++++++++++++++++++++------------------------------- 2 files changed, 82 insertions(+), 93 deletions(-) diff --git a/go.mod b/go.mod index 662b0d9..c99c176 100644 --- a/go.mod +++ b/go.mod @@ -4,35 +4,34 @@ go 1.26.0 require ( code.gitea.io/sdk/gitea v0.23.2 - forge.lthn.ai/core/agent v0.1.0 - forge.lthn.ai/core/cli v0.3.0 - forge.lthn.ai/core/go v0.3.0 - forge.lthn.ai/core/go-container v0.1.0 - forge.lthn.ai/core/go-i18n v0.1.0 - forge.lthn.ai/core/go-io v0.1.0 - forge.lthn.ai/core/go-log v0.0.1 - forge.lthn.ai/core/go-scm v0.2.0 + forge.lthn.ai/core/agent v0.3.0 + forge.lthn.ai/core/cli v0.3.1 + forge.lthn.ai/core/go v0.3.1 + forge.lthn.ai/core/go-container v0.1.3 + forge.lthn.ai/core/go-i18n v0.1.4 + forge.lthn.ai/core/go-io v0.1.2 + forge.lthn.ai/core/go-log v0.0.4 + forge.lthn.ai/core/go-scm v0.3.1 github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 github.com/stretchr/testify v1.11.1 - golang.org/x/term v0.40.0 - golang.org/x/text v0.34.0 + golang.org/x/term v0.41.0 + golang.org/x/text v0.35.0 gopkg.in/yaml.v3 v3.0.1 ) require ( codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 // indirect - forge.lthn.ai/core/config v0.1.0 // indirect - forge.lthn.ai/core/go-config v0.1.0 // indirect - forge.lthn.ai/core/go-crypt v0.1.0 // indirect - forge.lthn.ai/core/go-inference v0.1.0 // indirect - forge.lthn.ai/core/go-process v0.1.2 // indirect - forge.lthn.ai/core/go-store v0.1.3 // indirect + forge.lthn.ai/core/config v0.1.3 // indirect + forge.lthn.ai/core/go-crypt v0.1.7 // indirect + forge.lthn.ai/core/go-inference v0.1.4 // indirect + forge.lthn.ai/core/go-process v0.2.3 // indirect + forge.lthn.ai/core/go-store v0.1.6 // indirect github.com/42wim/httpsig v1.2.3 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/ProtonMail/go-crypto v1.4.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect - github.com/charmbracelet/colorprofile v0.4.2 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect @@ -48,14 +47,14 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/gofrs/flock v0.12.1 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect @@ -66,7 +65,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -76,11 +75,10 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - modernc.org/libc v1.68.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.46.1 // indirect diff --git a/go.sum b/go.sum index 634707c..d1fea1a 100644 --- a/go.sum +++ b/go.sum @@ -2,38 +2,36 @@ code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI= codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= -forge.lthn.ai/core/agent v0.1.0 h1:bO2wqkhfxaNi2GEXZLBTgiOpYuHIZKqFeej8eC54Wv8= -forge.lthn.ai/core/agent v0.1.0/go.mod h1:FVJdDnpxqQnyJn1sAYFG76UZISw1uM8sOnti9zlpAb4= -forge.lthn.ai/core/cli v0.3.0 h1:FpP1Wp4GwhOd+ZHWrjKZUCEnGyWoXOVDTwhytFb6hrA= -forge.lthn.ai/core/cli v0.3.0/go.mod h1:pocya1fKLbIKnNJ9rmfUDqBsH5bg02P426JvDBomcJo= -forge.lthn.ai/core/config v0.1.0 h1:qj14x/dnOWcsXMBQWAT3FtA+/sy6Qd+1NFTg5Xoil1I= -forge.lthn.ai/core/config v0.1.0/go.mod h1:8HYA29drAWlX+bO4VI1JhmKUgGU66E2Xge8D3tKd3Dg= -forge.lthn.ai/core/go v0.3.0 h1:mOG97ApMprwx9Ked62FdWVwXTGSF6JO6m0DrVpoH2Q4= -forge.lthn.ai/core/go v0.3.0/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= -forge.lthn.ai/core/go-config v0.1.0 h1:bQnlt8MvFvgPisl//jw4IMHMoCcaIt5FLurwYWqlMx0= -forge.lthn.ai/core/go-config v0.1.0/go.mod h1:jsCzg3BykHqlHZs13PDhP/dq8yTZjsiEyZ35q6jA3Aw= -forge.lthn.ai/core/go-container v0.1.0 h1:ucuo+FQ28+QdkCqAYXAPnzp65c12PvQMUDKE4GbP0yw= -forge.lthn.ai/core/go-container v0.1.0/go.mod h1:FNmvFY94J5rmt1z++WXILKvPStYNdB8AUvQl12LGcjE= -forge.lthn.ai/core/go-crypt v0.1.0 h1:92gwdQi7iAwktpvZhL/8Cu+QS6xKCtGP4FJfyInPGnw= -forge.lthn.ai/core/go-crypt v0.1.0/go.mod h1:zVAgx6ZiGtC+dbX4R/VKvEPqsEqjyuLl4gQZH9SXBUw= -forge.lthn.ai/core/go-i18n v0.1.0 h1:F7JVSoVkZtzx9JfhpntM9z3iQm1vnuMUi/Zklhz8PCI= -forge.lthn.ai/core/go-i18n v0.1.0/go.mod h1:Q4xsrxuNCl/6NfMv1daria7t1RSiyy8ml+6jiPtUcBs= -forge.lthn.ai/core/go-inference v0.1.0 h1:pO7etYgqV8LMKFdpW8/2RWncuECZJCIcf8nnezeZ5R4= -forge.lthn.ai/core/go-inference v0.1.0/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= -forge.lthn.ai/core/go-io v0.1.0 h1:aYNvmbU2VVsjXnut0WQ4DfVxcFdheziahJB32mfeJ7g= -forge.lthn.ai/core/go-io v0.1.0/go.mod h1:ZlU9OQpsvNFNmTJoaHbFIkisZyc0eCq0p8znVWQLRf0= -forge.lthn.ai/core/go-log v0.0.1 h1:x/E6EfF9vixzqiLHQOl2KT25HyBcMc9qiBkomqVlpPg= -forge.lthn.ai/core/go-log v0.0.1/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= -forge.lthn.ai/core/go-process v0.1.2 h1:0fdLJq/DPssilN9E5yude/xHNfZRKHghIjo++b5aXgc= -forge.lthn.ai/core/go-process v0.1.2/go.mod h1:9oxVALrZaZCqFe8YDdheIS5bRUV1SBz4tVW/MflAtxM= -forge.lthn.ai/core/go-scm v0.2.0 h1:TvDyCzw0HWzXjmqe6uPc46nPaRzc7MPGswmwZt0CmXo= -forge.lthn.ai/core/go-scm v0.2.0/go.mod h1:Q/PV2FbqDlWnAOsXAd1pgSiHOlRCPW4HcPmOt8Z9H+E= -forge.lthn.ai/core/go-store v0.1.3 h1:CSVTRdsOXm2pl+FCs12fHOc9eM88DcZRY6HghN98w/I= -forge.lthn.ai/core/go-store v0.1.3/go.mod h1:op+ftjAqYskPv4OGvHZQf7/DLiRnFIdT0XCQTKR/GjE= +forge.lthn.ai/core/agent v0.3.0 h1:81D/DH7USgpYqCXK2Wl6+UFzxClOtc/TfE1j8Vp/V/U= +forge.lthn.ai/core/agent v0.3.0/go.mod h1:VfITjQzV5qI5qDorO7OkVhTeWUx4c24miVGH5cikiqs= +forge.lthn.ai/core/cli v0.3.1 h1:ZpHhaDrdbaV98JDxj/f0E5nytYk9tTMRu3qohGyK4M0= +forge.lthn.ai/core/cli v0.3.1/go.mod h1:28cOl9eK0H033Otkjrv9f/QCmtHcJl+IIx4om8JskOg= +forge.lthn.ai/core/config v0.1.3 h1:mq02v7LFf9jHSqJakO08qYQnPP8oVMbJHlOxNARXBa8= +forge.lthn.ai/core/config v0.1.3/go.mod h1:4+/ytojOSaPoAQ1uub1+GeOM8OoYdR9xqMtVA3SZ8Qk= +forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM= +forge.lthn.ai/core/go v0.3.1/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= +forge.lthn.ai/core/go-container v0.1.3 h1:Pb/latnVFBgyI4zDyYxAiRRqKrOYIAxL6om+k2YS1q8= +forge.lthn.ai/core/go-container v0.1.3/go.mod h1:wIlly3pAluVQnQ+DLnZ15pENOFkJicWRRke6msCudLI= +forge.lthn.ai/core/go-crypt v0.1.7 h1:tyDFnXjEksHFQpkFwCpEn+x7zvwh4LnaU+/fP3WmqZc= +forge.lthn.ai/core/go-crypt v0.1.7/go.mod h1:mQdr6K8lWOcyHmSEW24vZPTThQF8fteVgZi8CO+Ko3Y= +forge.lthn.ai/core/go-i18n v0.1.4 h1:zOHUUJDgRo88/3tj++kN+VELg/buyZ4T2OSdG3HBbLQ= +forge.lthn.ai/core/go-i18n v0.1.4/go.mod h1:aDyAfz7MMgWYgLkZCptfFmZ7jJg3ocwjEJ1WkJSvv4U= +forge.lthn.ai/core/go-inference v0.1.4 h1:fuAgWbqsEDajHniqAKyvHYbRcBrkGEiGSqR2pfTMRY0= +forge.lthn.ai/core/go-inference v0.1.4/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= +forge.lthn.ai/core/go-io v0.1.2 h1:q8hj2jtOFqAgHlBr5wsUAOXtaFkxy9gqGrQT/il0WYA= +forge.lthn.ai/core/go-io v0.1.2/go.mod h1:PbNKW1Q25ywSOoQXeGdQHbV5aiIrTXvHIQ5uhplA//g= +forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= +forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= +forge.lthn.ai/core/go-process v0.2.3 h1:/ERqRYHgCNZjNT9NMinAAJJGJWSsHuCTiHFNEm6nTPY= +forge.lthn.ai/core/go-process v0.2.3/go.mod h1:gVTbxL16ccUIexlFcyDtCy7LfYvD8Rtyzfo8bnXAXrU= +forge.lthn.ai/core/go-scm v0.3.1 h1:G+DqVJLT+UjgUzu2Hnseyl2szhb0wB+DB8VYhq/bLOI= +forge.lthn.ai/core/go-scm v0.3.1/go.mod h1:ER9fQBs8nnlJZQ6+ALnwv+boK/xiwg8jEc9VP6DMijk= +forge.lthn.ai/core/go-store v0.1.6 h1:7T+K5cciXOaWRxge0WnGkt0PcK3epliWBa1G2FLEuac= +forge.lthn.ai/core/go-store v0.1.6/go.mod h1:/2vqaAn+HgGU14N29B+vIfhjIsBzy7RC+AluI6BIUKI= github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= -github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= -github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ= +github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -44,8 +42,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= -github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= @@ -61,8 +59,6 @@ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= @@ -83,8 +79,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= @@ -111,8 +107,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= -github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -123,7 +119,6 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= @@ -137,8 +132,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -150,8 +145,6 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -167,54 +160,52 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so= -modernc.org/ccgo/v4 v4.30.2/go.mod h1:yZMnhWEdW0qw3EtCndG1+ldRrVGS+bIwyWmAWzS0XEw= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= -modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= From 7aa8c7f944588405309b3bc9dcbab0dfa0b95fa4 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 15 Mar 2026 15:43:36 +0000 Subject: [PATCH 02/10] chore: update dependencies Co-Authored-By: Virgil --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index c99c176..0593da2 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.26.0 require ( code.gitea.io/sdk/gitea v0.23.2 - forge.lthn.ai/core/agent v0.3.0 - forge.lthn.ai/core/cli v0.3.1 + forge.lthn.ai/core/agent v0.3.1 + forge.lthn.ai/core/cli v0.3.2 forge.lthn.ai/core/go v0.3.1 forge.lthn.ai/core/go-container v0.1.3 forge.lthn.ai/core/go-i18n v0.1.4 diff --git a/go.sum b/go.sum index d1fea1a..82f021b 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,12 @@ codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jv codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= forge.lthn.ai/core/agent v0.3.0 h1:81D/DH7USgpYqCXK2Wl6+UFzxClOtc/TfE1j8Vp/V/U= forge.lthn.ai/core/agent v0.3.0/go.mod h1:VfITjQzV5qI5qDorO7OkVhTeWUx4c24miVGH5cikiqs= +forge.lthn.ai/core/agent v0.3.1 h1:Q6lkSg9nr2c1oj2Pr/s3LN7xItIyOmSRgLSfMaaNuyQ= +forge.lthn.ai/core/agent v0.3.1/go.mod h1:FfHS10AkPcxnc+ms93QzNJtZ7dgcET0LvMcJAzY2h+w= forge.lthn.ai/core/cli v0.3.1 h1:ZpHhaDrdbaV98JDxj/f0E5nytYk9tTMRu3qohGyK4M0= forge.lthn.ai/core/cli v0.3.1/go.mod h1:28cOl9eK0H033Otkjrv9f/QCmtHcJl+IIx4om8JskOg= +forge.lthn.ai/core/cli v0.3.2 h1:EZNVG/W+YDAS77pw4uyDcj9+ntyl4JE287mrRX6MVGQ= +forge.lthn.ai/core/cli v0.3.2/go.mod h1:LfWXT4JldTuyDeSj5+SQGv79LSzs+yi9HUCq1diGDQ0= forge.lthn.ai/core/config v0.1.3 h1:mq02v7LFf9jHSqJakO08qYQnPP8oVMbJHlOxNARXBa8= forge.lthn.ai/core/config v0.1.3/go.mod h1:4+/ytojOSaPoAQ1uub1+GeOM8OoYdR9xqMtVA3SZ8Qk= forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM= From c364b3083c8597d626268511f89de0dcadaa002b Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 15 Mar 2026 15:44:45 +0000 Subject: [PATCH 03/10] chore: sync workspace dependencies Co-Authored-By: Virgil --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index 82f021b..4262d5e 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,8 @@ code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI= codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= -forge.lthn.ai/core/agent v0.3.0 h1:81D/DH7USgpYqCXK2Wl6+UFzxClOtc/TfE1j8Vp/V/U= -forge.lthn.ai/core/agent v0.3.0/go.mod h1:VfITjQzV5qI5qDorO7OkVhTeWUx4c24miVGH5cikiqs= forge.lthn.ai/core/agent v0.3.1 h1:Q6lkSg9nr2c1oj2Pr/s3LN7xItIyOmSRgLSfMaaNuyQ= forge.lthn.ai/core/agent v0.3.1/go.mod h1:FfHS10AkPcxnc+ms93QzNJtZ7dgcET0LvMcJAzY2h+w= -forge.lthn.ai/core/cli v0.3.1 h1:ZpHhaDrdbaV98JDxj/f0E5nytYk9tTMRu3qohGyK4M0= -forge.lthn.ai/core/cli v0.3.1/go.mod h1:28cOl9eK0H033Otkjrv9f/QCmtHcJl+IIx4om8JskOg= forge.lthn.ai/core/cli v0.3.2 h1:EZNVG/W+YDAS77pw4uyDcj9+ntyl4JE287mrRX6MVGQ= forge.lthn.ai/core/cli v0.3.2/go.mod h1:LfWXT4JldTuyDeSj5+SQGv79LSzs+yi9HUCq1diGDQ0= forge.lthn.ai/core/config v0.1.3 h1:mq02v7LFf9jHSqJakO08qYQnPP8oVMbJHlOxNARXBa8= From cea8624497fe4006377c09add4c69ef6a5d1f380 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 15 Mar 2026 17:24:30 +0000 Subject: [PATCH 04/10] feat(dev): add core dev tag command Reads repos.yaml dependency graph via TopologicalOrder(), bumps patch version bottom-up, runs GOWORK=off go get -u ./... and go mod tidy per repo, commits go.mod/go.sum, creates annotated tags, and pushes. Supports --dry-run to preview the plan and --force to skip confirmation. Co-Authored-By: Virgil --- cmd/dev/cmd_dev.go | 1 + cmd/dev/cmd_tag.go | 309 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 cmd/dev/cmd_tag.go diff --git a/cmd/dev/cmd_dev.go b/cmd/dev/cmd_dev.go index 5fa39e6..c33f7dc 100644 --- a/cmd/dev/cmd_dev.go +++ b/cmd/dev/cmd_dev.go @@ -74,6 +74,7 @@ func AddDevCommands(root *cli.Command) { AddCommitCommand(devCmd) AddPushCommand(devCmd) AddPullCommand(devCmd) + AddTagCommand(devCmd) // Safe git operations for AI agents (also available under 'core git') AddFileSyncCommand(devCmd) diff --git a/cmd/dev/cmd_tag.go b/cmd/dev/cmd_tag.go new file mode 100644 index 0000000..af2d2ff --- /dev/null +++ b/cmd/dev/cmd_tag.go @@ -0,0 +1,309 @@ +package dev + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n" +) + +// Tag command flags +var ( + tagRegistryPath string + tagDryRun bool + tagForce bool +) + +// AddTagCommand adds the 'tag' command to the given parent command. +func AddTagCommand(parent *cli.Command) { + tagCmd := &cli.Command{ + Use: "tag", + Short: i18n.T("cmd.dev.tag.short"), + Long: i18n.T("cmd.dev.tag.long"), + RunE: func(cmd *cli.Command, args []string) error { + return runTag(tagRegistryPath, tagDryRun, tagForce) + }, + } + + tagCmd.Flags().StringVar(&tagRegistryPath, "registry", "", i18n.T("common.flag.registry")) + tagCmd.Flags().BoolVar(&tagDryRun, "dry-run", false, i18n.T("cmd.dev.tag.flag.dry_run")) + tagCmd.Flags().BoolVarP(&tagForce, "force", "f", false, i18n.T("cmd.dev.tag.flag.force")) + + parent.AddCommand(tagCmd) +} + +// tagPlan holds the version bump plan for a single repo. +type tagPlan struct { + Name string + Path string + Current string // current tag (e.g. "v0.3.1") + Next string // next tag (e.g. "v0.3.2") + IsGoMod bool // whether the repo has a go.mod +} + +func runTag(registryPath string, dryRun, force bool) error { + ctx := context.Background() + + // Load registry + reg, _, err := loadRegistryWithConfig(registryPath) + if err != nil { + return err + } + + // Get topological order (dependencies first) + ordered, err := reg.TopologicalOrder() + if err != nil { + return cli.Wrap(err, "failed to compute dependency order") + } + + // Build version bump plan + var plans []tagPlan + + for _, repo := range ordered { + if !repo.Exists() || !repo.IsGitRepo() { + continue + } + + current, err := latestTag(ctx, repo.Path) + if err != nil || current == "" { + current = "v0.0.0" + } + + next, err := bumpPatch(current) + if err != nil { + return fmt.Errorf("%s: failed to bump version %s: %w", repo.Name, current, err) + } + + hasGoMod := fileExists(filepath.Join(repo.Path, "go.mod")) + + plans = append(plans, tagPlan{ + Name: repo.Name, + Path: repo.Path, + Current: current, + Next: next, + IsGoMod: hasGoMod, + }) + } + + if len(plans) == 0 { + cli.Text(i18n.T("cmd.dev.no_git_repos")) + return nil + } + + // Show plan + cli.Print("\n%s\n\n", cli.TitleStyle.Render("Tag plan (dependency order)")) + + nameWidth := 4 + for _, p := range plans { + if len(p.Name) > nameWidth { + nameWidth = len(p.Name) + } + } + + for _, p := range plans { + paddedName := cli.Sprintf("%-*s", nameWidth, p.Name) + cli.Print(" %s %s → %s\n", + repoNameStyle.Render(paddedName), + dimStyle.Render(p.Current), + aheadStyle.Render(p.Next), + ) + } + + if dryRun { + cli.Blank() + cli.Text("Dry run — no changes made.") + return nil + } + + // Confirm unless --force + if !force { + cli.Blank() + if !cli.Confirm(fmt.Sprintf("Tag and push %d repos?", len(plans))) { + cli.Text(i18n.T("cli.aborted")) + return nil + } + } + + cli.Blank() + + // Execute: for each repo in dependency order + var succeeded, failed int + + for _, p := range plans { + cli.Print("%s %s → %s\n", dimStyle.Render("▸"), repoNameStyle.Render(p.Name), aheadStyle.Render(p.Next)) + + if p.IsGoMod { + // Step 1: GOWORK=off go get -u ./... + if err := goGetUpdate(ctx, p.Path); err != nil { + cli.Print(" %s go get -u: %s\n", errorStyle.Render("x"), err) + failed++ + continue + } + + // Step 2: GOWORK=off go mod tidy + if err := goModTidy(ctx, p.Path); err != nil { + cli.Print(" %s go mod tidy: %s\n", errorStyle.Render("x"), err) + failed++ + continue + } + + // Step 3: Commit go.mod/go.sum if changed + if err := commitGoMod(ctx, p.Path, p.Next); err != nil { + cli.Print(" %s commit: %s\n", errorStyle.Render("x"), err) + failed++ + continue + } + } + + // Step 4: Create annotated tag + if err := createTag(ctx, p.Path, p.Next); err != nil { + cli.Print(" %s tag: %s\n", errorStyle.Render("x"), err) + failed++ + continue + } + + // Step 5: Push commits and tags + if err := pushWithTags(ctx, p.Path); err != nil { + cli.Print(" %s push: %s\n", errorStyle.Render("x"), err) + failed++ + continue + } + + cli.Print(" %s %s\n", successStyle.Render("v"), p.Next) + succeeded++ + } + + // Summary + cli.Blank() + cli.Print("%s", successStyle.Render(fmt.Sprintf("%d tagged", succeeded))) + if failed > 0 { + cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]any{"Count": failed}))) + } + cli.Blank() + + return nil +} + +// latestTag returns the latest semver tag in the repo. +func latestTag(ctx context.Context, repoPath string) (string, error) { + cmd := exec.CommandContext(ctx, "git", "describe", "--tags", "--abbrev=0", "--match", "v*") + cmd.Dir = repoPath + out, err := cmd.Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(out)), nil +} + +// bumpPatch increments the patch version of a semver tag. +// "v0.3.1" → "v0.3.2" +func bumpPatch(tag string) (string, error) { + v := strings.TrimPrefix(tag, "v") + parts := strings.Split(v, ".") + if len(parts) != 3 { + return "", fmt.Errorf("invalid semver: %s", tag) + } + patch, err := strconv.Atoi(parts[2]) + if err != nil { + return "", fmt.Errorf("invalid patch version: %s", parts[2]) + } + return fmt.Sprintf("v%s.%s.%d", parts[0], parts[1], patch+1), nil +} + +// goGetUpdate runs GOWORK=off go get -u ./... in the repo. +func goGetUpdate(ctx context.Context, repoPath string) error { + cmd := exec.CommandContext(ctx, "go", "get", "-u", "./...") + cmd.Dir = repoPath + cmd.Env = append(os.Environ(), "GOWORK=off") + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %w", strings.TrimSpace(string(out)), err) + } + return nil +} + +// goModTidy runs GOWORK=off go mod tidy in the repo. +func goModTidy(ctx context.Context, repoPath string) error { + cmd := exec.CommandContext(ctx, "go", "mod", "tidy") + cmd.Dir = repoPath + cmd.Env = append(os.Environ(), "GOWORK=off") + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %w", strings.TrimSpace(string(out)), err) + } + return nil +} + +// commitGoMod stages and commits go.mod and go.sum if they have changes. +func commitGoMod(ctx context.Context, repoPath, version string) error { + // Check if go.mod or go.sum changed (staged or unstaged) + diffCmd := exec.CommandContext(ctx, "git", "diff", "--quiet", "go.mod", "go.sum") + diffCmd.Dir = repoPath + modChanged := diffCmd.Run() != nil + + // Also check for untracked go.sum + lsCmd := exec.CommandContext(ctx, "git", "ls-files", "--others", "--exclude-standard", "go.sum") + lsCmd.Dir = repoPath + lsOut, _ := lsCmd.Output() + untrackedSum := strings.TrimSpace(string(lsOut)) != "" + + if !modChanged && !untrackedSum { + return nil // No changes + } + + // Stage go.mod and go.sum + addCmd := exec.CommandContext(ctx, "git", "add", "go.mod", "go.sum") + addCmd.Dir = repoPath + if out, err := addCmd.CombinedOutput(); err != nil { + return fmt.Errorf("git add: %s: %w", strings.TrimSpace(string(out)), err) + } + + // Check if anything is actually staged + stagedCmd := exec.CommandContext(ctx, "git", "diff", "--cached", "--quiet") + stagedCmd.Dir = repoPath + if stagedCmd.Run() == nil { + return nil // Nothing staged + } + + // Commit + msg := fmt.Sprintf("chore: sync dependencies for %s\n\nCo-Authored-By: Virgil ", version) + commitCmd := exec.CommandContext(ctx, "git", "commit", "-m", msg) + commitCmd.Dir = repoPath + if out, err := commitCmd.CombinedOutput(); err != nil { + return fmt.Errorf("git commit: %s: %w", strings.TrimSpace(string(out)), err) + } + return nil +} + +// createTag creates an annotated tag. +func createTag(ctx context.Context, repoPath, tag string) error { + cmd := exec.CommandContext(ctx, "git", "tag", "-a", tag, "-m", tag) + cmd.Dir = repoPath + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("%s: %w", strings.TrimSpace(string(out)), err) + } + return nil +} + +// pushWithTags pushes commits and tags to the remote. +// Uses interactive mode to support SSH passphrase prompts. +func pushWithTags(ctx context.Context, repoPath string) error { + cmd := exec.CommandContext(ctx, "git", "push", "--follow-tags") + cmd.Dir = repoPath + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + return cmd.Run() +} + +// fileExists checks if a file exists at the given path. +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} From 4c5e12c9f86762c85deebf6c9d46e256ddcb4faf Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 16 Mar 2026 19:09:50 +0000 Subject: [PATCH 05/10] refactor: replace os.ReadFile/WriteFile and fmt.Errorf/errors.New with framework equivalents Replace os.ReadFile with coreio.Local.Read for consistent filesystem abstraction. Replace fmt.Errorf/errors.New with log.E() from go-log for structured error context. Co-Authored-By: Virgil --- cmd/deploy/cmd_deploy.go | 3 ++- cmd/dev/cmd_impact.go | 6 +++--- cmd/dev/cmd_tag.go | 17 +++++++++-------- cmd/dev/cmd_vm.go | 4 ++-- cmd/dev/forge_client.go | 10 +++++----- cmd/setup/cmd_bootstrap.go | 15 ++++++++------- cmd/setup/cmd_github.go | 10 +++++----- cmd/setup/cmd_registry.go | 13 +++++++------ cmd/setup/cmd_repo.go | 5 +++-- cmd/setup/github_config.go | 21 +++++++++++---------- cmd/setup/github_protection.go | 5 +++-- cmd/setup/github_security.go | 11 ++++++----- cmd/setup/github_webhooks.go | 7 ++++--- deploy/coolify/client.go | 15 ++++++++------- snapshot/snapshot.go | 5 +++-- 15 files changed, 79 insertions(+), 68 deletions(-) diff --git a/cmd/deploy/cmd_deploy.go b/cmd/deploy/cmd_deploy.go index b6c63fd..7b962dd 100644 --- a/cmd/deploy/cmd_deploy.go +++ b/cmd/deploy/cmd_deploy.go @@ -9,6 +9,7 @@ import ( "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go-devops/deploy/coolify" "forge.lthn.ai/core/go-i18n" + log "forge.lthn.ai/core/go-log" ) var ( @@ -266,7 +267,7 @@ func runCall(cmd *cli.Command, args []string) error { var params map[string]any if len(args) > 1 { if err := json.Unmarshal([]byte(args[1]), ¶ms); err != nil { - return fmt.Errorf("invalid JSON params: %w", err) + return log.E("deploy", "invalid JSON params", err) } } diff --git a/cmd/dev/cmd_impact.go b/cmd/dev/cmd_impact.go index f3de823..2515e40 100644 --- a/cmd/dev/cmd_impact.go +++ b/cmd/dev/cmd_impact.go @@ -1,12 +1,12 @@ package dev import ( - "errors" "slices" "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go-i18n" "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/go-scm/repos" ) @@ -55,14 +55,14 @@ func runImpact(registryPath string, repoName string) error { return cli.Wrap(err, "failed to load registry") } } else { - return errors.New(i18n.T("cmd.dev.impact.requires_registry")) + return log.E("dev.impact", i18n.T("cmd.dev.impact.requires_registry"), nil) } } // Check repo exists repo, exists := reg.Get(repoName) if !exists { - return errors.New(i18n.T("error.repo_not_found", map[string]any{"Name": repoName})) + return log.E("dev.impact", i18n.T("error.repo_not_found", map[string]any{"Name": repoName}), nil) } // Build reverse dependency graph diff --git a/cmd/dev/cmd_tag.go b/cmd/dev/cmd_tag.go index af2d2ff..7096632 100644 --- a/cmd/dev/cmd_tag.go +++ b/cmd/dev/cmd_tag.go @@ -11,6 +11,7 @@ import ( "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go-i18n" + log "forge.lthn.ai/core/go-log" ) // Tag command flags @@ -77,7 +78,7 @@ func runTag(registryPath string, dryRun, force bool) error { next, err := bumpPatch(current) if err != nil { - return fmt.Errorf("%s: failed to bump version %s: %w", repo.Name, current, err) + return log.E("dev.tag", fmt.Sprintf("%s: failed to bump version %s", repo.Name, current), err) } hasGoMod := fileExists(filepath.Join(repo.Path, "go.mod")) @@ -207,11 +208,11 @@ func bumpPatch(tag string) (string, error) { v := strings.TrimPrefix(tag, "v") parts := strings.Split(v, ".") if len(parts) != 3 { - return "", fmt.Errorf("invalid semver: %s", tag) + return "", log.E("dev.tag", fmt.Sprintf("invalid semver: %s", tag), nil) } patch, err := strconv.Atoi(parts[2]) if err != nil { - return "", fmt.Errorf("invalid patch version: %s", parts[2]) + return "", log.E("dev.tag", fmt.Sprintf("invalid patch version: %s", parts[2]), nil) } return fmt.Sprintf("v%s.%s.%d", parts[0], parts[1], patch+1), nil } @@ -223,7 +224,7 @@ func goGetUpdate(ctx context.Context, repoPath string) error { cmd.Env = append(os.Environ(), "GOWORK=off") out, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("%s: %w", strings.TrimSpace(string(out)), err) + return log.E("dev.tag", strings.TrimSpace(string(out)), err) } return nil } @@ -235,7 +236,7 @@ func goModTidy(ctx context.Context, repoPath string) error { cmd.Env = append(os.Environ(), "GOWORK=off") out, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("%s: %w", strings.TrimSpace(string(out)), err) + return log.E("dev.tag", strings.TrimSpace(string(out)), err) } return nil } @@ -261,7 +262,7 @@ func commitGoMod(ctx context.Context, repoPath, version string) error { addCmd := exec.CommandContext(ctx, "git", "add", "go.mod", "go.sum") addCmd.Dir = repoPath if out, err := addCmd.CombinedOutput(); err != nil { - return fmt.Errorf("git add: %s: %w", strings.TrimSpace(string(out)), err) + return log.E("dev.tag", "git add: "+strings.TrimSpace(string(out)), err) } // Check if anything is actually staged @@ -276,7 +277,7 @@ func commitGoMod(ctx context.Context, repoPath, version string) error { commitCmd := exec.CommandContext(ctx, "git", "commit", "-m", msg) commitCmd.Dir = repoPath if out, err := commitCmd.CombinedOutput(); err != nil { - return fmt.Errorf("git commit: %s: %w", strings.TrimSpace(string(out)), err) + return log.E("dev.tag", "git commit: "+strings.TrimSpace(string(out)), err) } return nil } @@ -286,7 +287,7 @@ func createTag(ctx context.Context, repoPath, tag string) error { cmd := exec.CommandContext(ctx, "git", "tag", "-a", tag, "-m", tag) cmd.Dir = repoPath if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("%s: %w", strings.TrimSpace(string(out)), err) + return log.E("dev.tag", strings.TrimSpace(string(out)), err) } return nil } diff --git a/cmd/dev/cmd_vm.go b/cmd/dev/cmd_vm.go index 7fb002f..d33df36 100644 --- a/cmd/dev/cmd_vm.go +++ b/cmd/dev/cmd_vm.go @@ -2,7 +2,6 @@ package dev import ( "context" - "errors" "os" "time" @@ -10,6 +9,7 @@ import ( "forge.lthn.ai/core/go-container/devenv" "forge.lthn.ai/core/go-i18n" "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" ) // addVMCommands adds the dev environment VM commands to the dev parent command. @@ -119,7 +119,7 @@ func runVMBoot(memory, cpus int, fresh bool) error { } if !d.IsInstalled() { - return errors.New(i18n.T("cmd.dev.vm.not_installed")) + return log.E("dev.vm", i18n.T("cmd.dev.vm.not_installed"), nil) } opts := devenv.DefaultBootOptions() diff --git a/cmd/dev/forge_client.go b/cmd/dev/forge_client.go index 7571802..649aa97 100644 --- a/cmd/dev/forge_client.go +++ b/cmd/dev/forge_client.go @@ -1,13 +1,13 @@ package dev import ( - "fmt" - "os" "path/filepath" "strings" "code.gitea.io/sdk/gitea" + coreio "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/go-scm/forge" ) @@ -19,7 +19,7 @@ func forgeAPIClient() (*gitea.Client, error) { return nil, err } if token == "" { - return nil, fmt.Errorf("no Forge API token configured (set FORGE_TOKEN or run: core forge config --token TOKEN)") + return nil, log.E("dev.forge", "no Forge API token configured (set FORGE_TOKEN or run: core forge config --token TOKEN)", nil) } return gitea.NewClient(forgeURL, gitea.SetToken(token)) } @@ -28,12 +28,12 @@ func forgeAPIClient() (*gitea.Client, error) { // Falls back to fallbackOrg/repoName if no forge.lthn.ai remote is found. func forgeRepoIdentity(repoPath, fallbackOrg, repoName string) (owner, repo string) { configPath := filepath.Join(repoPath, ".git", "config") - content, err := os.ReadFile(configPath) + content, err := coreio.Local.Read(configPath) if err != nil { return fallbackOrg, repoName } - for _, line := range strings.Split(string(content), "\n") { + for _, line := range strings.Split(content, "\n") { line = strings.TrimSpace(line) if !strings.HasPrefix(line, "url = ") { continue diff --git a/cmd/setup/cmd_bootstrap.go b/cmd/setup/cmd_bootstrap.go index 7ead9ba..cfacb93 100644 --- a/cmd/setup/cmd_bootstrap.go +++ b/cmd/setup/cmd_bootstrap.go @@ -16,6 +16,7 @@ import ( "forge.lthn.ai/core/agent/cmd/workspace" "forge.lthn.ai/core/go-i18n" coreio "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/go-scm/repos" ) @@ -46,7 +47,7 @@ func runSetupOrchestrator(registryPath, only string, dryRun, all bool, projectNa func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectName string, runBuild bool) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return log.E("setup.bootstrap", "failed to get working directory", err) } fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.bootstrap_mode")) @@ -56,7 +57,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam // Check if current directory is empty empty, err := isDirEmpty(cwd) if err != nil { - return fmt.Errorf("failed to check directory: %w", err) + return log.E("setup.bootstrap", "failed to check directory", err) } if empty { @@ -71,7 +72,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam // Offer choice: setup working directory or create package choice, err := promptSetupChoice() if err != nil { - return fmt.Errorf("failed to get choice: %w", err) + return log.E("setup.bootstrap", "failed to get choice", err) } if choice == "setup" { @@ -88,7 +89,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam } else { projectName, err = promptProjectName(defaultOrg) if err != nil { - return fmt.Errorf("failed to get project name: %w", err) + return log.E("setup.bootstrap", "failed to get project name", err) } } } @@ -98,7 +99,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam if !dryRun { if err := coreio.Local.EnsureDir(targetDir); err != nil { - return fmt.Errorf("failed to create directory: %w", err) + return log.E("setup.bootstrap", "failed to create directory", err) } } } @@ -110,7 +111,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam if !dryRun { if err := gitClone(ctx, defaultOrg, devopsRepo, devopsPath); err != nil { - return fmt.Errorf("failed to clone %s: %w", devopsRepo, err) + return log.E("setup.bootstrap", fmt.Sprintf("failed to clone %s", devopsRepo), err) } fmt.Printf("%s %s %s\n", successStyle.Render(">>"), devopsRepo, i18n.T("cmd.setup.cloned")) } else { @@ -130,7 +131,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam reg, err := repos.LoadRegistry(coreio.Local, registryPath) if err != nil { - return fmt.Errorf("failed to load registry from %s: %w", devopsRepo, err) + return log.E("setup.bootstrap", fmt.Sprintf("failed to load registry from %s", devopsRepo), err) } // Override base path to target directory diff --git a/cmd/setup/cmd_github.go b/cmd/setup/cmd_github.go index 2664e11..3d5779e 100644 --- a/cmd/setup/cmd_github.go +++ b/cmd/setup/cmd_github.go @@ -18,13 +18,13 @@ package setup import ( - "errors" "os/exec" "path/filepath" "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go-i18n" coreio "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/go-scm/repos" ) @@ -69,12 +69,12 @@ func addGitHubCommand(parent *cli.Command) { func runGitHubSetup() error { // Check gh is available if _, err := exec.LookPath("gh"); err != nil { - return errors.New(i18n.T("error.gh_not_found")) + return log.E("setup.github", i18n.T("error.gh_not_found"), nil) } // Check gh is authenticated if !cli.GhAuthenticated() { - return errors.New(i18n.T("cmd.setup.github.error.not_authenticated")) + return log.E("setup.github", i18n.T("cmd.setup.github.error.not_authenticated"), nil) } // Find registry @@ -118,14 +118,14 @@ func runGitHubSetup() error { // Reject conflicting flags if ghRepo != "" && ghAll { - return errors.New(i18n.T("cmd.setup.github.error.conflicting_flags")) + return log.E("setup.github", i18n.T("cmd.setup.github.error.conflicting_flags"), nil) } if ghRepo != "" { // Single repo mode repo, ok := reg.Get(ghRepo) if !ok { - return errors.New(i18n.T("error.repo_not_found", map[string]any{"Name": ghRepo})) + return log.E("setup.github", i18n.T("error.repo_not_found", map[string]any{"Name": ghRepo}), nil) } reposToProcess = []*repos.Repo{repo} } else if ghAll { diff --git a/cmd/setup/cmd_registry.go b/cmd/setup/cmd_registry.go index d62dca3..acf8b59 100644 --- a/cmd/setup/cmd_registry.go +++ b/cmd/setup/cmd_registry.go @@ -17,6 +17,7 @@ import ( "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go-i18n" coreio "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/go-scm/repos" ) @@ -24,7 +25,7 @@ import ( func runRegistrySetup(ctx context.Context, registryPath, only string, dryRun, all, runBuild bool) error { reg, err := repos.LoadRegistry(coreio.Local, registryPath) if err != nil { - return fmt.Errorf("failed to load registry: %w", err) + return log.E("setup.registry", "failed to load registry", err) } // Check workspace config for default_only if no filter specified @@ -82,7 +83,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP // Ensure base path exists if !dryRun { if err := coreio.Local.EnsureDir(basePath); err != nil { - return fmt.Errorf("failed to create packages directory: %w", err) + return log.E("setup.registry", "failed to create packages directory", err) } } @@ -99,7 +100,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP if useWizard { selected, err := runPackageWizard(reg, typeFilter) if err != nil { - return fmt.Errorf("wizard error: %w", err) + return log.E("setup.registry", "wizard error", err) } // Build set of selected repos @@ -227,7 +228,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP buildCmd.Stdout = os.Stdout buildCmd.Stderr = os.Stderr if err := buildCmd.Run(); err != nil { - return fmt.Errorf("%s: %w", i18n.T("i18n.fail.run", "build"), err) + return log.E("setup.registry", i18n.T("i18n.fail.run", "build"), err) } } @@ -249,7 +250,7 @@ func gitClone(ctx context.Context, org, repo, path string) error { // Only fall through to SSH if it's an auth error if !strings.Contains(errStr, "Permission denied") && !strings.Contains(errStr, "could not read") { - return fmt.Errorf("%s", errStr) + return log.E("setup.registry", errStr, nil) } } @@ -258,7 +259,7 @@ func gitClone(ctx context.Context, org, repo, path string) error { cmd := exec.CommandContext(ctx, "git", "clone", url, path) output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("%s", strings.TrimSpace(string(output))) + return log.E("setup.registry", strings.TrimSpace(string(output)), nil) } return nil } diff --git a/cmd/setup/cmd_repo.go b/cmd/setup/cmd_repo.go index 780e733..dfc7b44 100644 --- a/cmd/setup/cmd_repo.go +++ b/cmd/setup/cmd_repo.go @@ -14,6 +14,7 @@ import ( "forge.lthn.ai/core/go-i18n" coreio "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" ) // runRepoSetup sets up the current repository with .core/ configuration. @@ -28,7 +29,7 @@ func runRepoSetup(repoPath string, dryRun bool) error { coreDir := filepath.Join(repoPath, ".core") if !dryRun { if err := coreio.Local.EnsureDir(coreDir); err != nil { - return fmt.Errorf("failed to create .core directory: %w", err) + return log.E("setup.repo", "failed to create .core directory", err) } } @@ -55,7 +56,7 @@ func runRepoSetup(repoPath string, dryRun bool) error { for filename, content := range configs { configPath := filepath.Join(coreDir, filename) if err := coreio.Local.Write(configPath, content); err != nil { - return fmt.Errorf("failed to write %s: %w", filename, err) + return log.E("setup.repo", fmt.Sprintf("failed to write %s", filename), err) } fmt.Printf("%s %s %s\n", successStyle.Render(">>"), i18n.T("cmd.setup.repo.created"), configPath) } diff --git a/cmd/setup/github_config.go b/cmd/setup/github_config.go index 01f547c..3ccaf01 100644 --- a/cmd/setup/github_config.go +++ b/cmd/setup/github_config.go @@ -13,6 +13,7 @@ import ( "strings" coreio "forge.lthn.ai/core/go-io" + log "forge.lthn.ai/core/go-log" "gopkg.in/yaml.v3" ) @@ -67,7 +68,7 @@ type SecurityConfig struct { func LoadGitHubConfig(path string) (*GitHubConfig, error) { data, err := coreio.Local.Read(path) if err != nil { - return nil, fmt.Errorf("failed to read config file: %w", err) + return nil, log.E("setup.github_config", "failed to read config file", err) } // Expand environment variables before parsing @@ -75,7 +76,7 @@ func LoadGitHubConfig(path string) (*GitHubConfig, error) { var config GitHubConfig if err := yaml.Unmarshal([]byte(expanded), &config); err != nil { - return nil, fmt.Errorf("failed to parse config file: %w", err) + return nil, log.E("setup.github_config", "failed to parse config file", err) } // Set defaults @@ -131,7 +132,7 @@ func FindGitHubConfig(registryDir, specifiedPath string) (string, error) { if coreio.Local.IsFile(specifiedPath) { return specifiedPath, nil } - return "", fmt.Errorf("config file not found: %s", specifiedPath) + return "", log.E("setup.github_config", fmt.Sprintf("config file not found: %s", specifiedPath), nil) } // Search in common locations (using filepath.Join for OS-portable paths) @@ -146,26 +147,26 @@ func FindGitHubConfig(registryDir, specifiedPath string) (string, error) { } } - return "", fmt.Errorf("github.yaml not found in %s/.core/ or %s/", registryDir, registryDir) + return "", log.E("setup.github_config", fmt.Sprintf("github.yaml not found in %s/.core/ or %s/", registryDir, registryDir), nil) } // Validate checks the configuration for errors. func (c *GitHubConfig) Validate() error { if c.Version != 1 { - return fmt.Errorf("unsupported config version: %d (expected 1)", c.Version) + return log.E("setup.github_config", fmt.Sprintf("unsupported config version: %d (expected 1)", c.Version), nil) } // Validate labels for i, label := range c.Labels { if label.Name == "" { - return fmt.Errorf("label %d: name is required", i+1) + return log.E("setup.github_config", fmt.Sprintf("label %d: name is required", i+1), nil) } if label.Color == "" { - return fmt.Errorf("label %q: color is required", label.Name) + return log.E("setup.github_config", fmt.Sprintf("label %q: color is required", label.Name), nil) } // Validate color format (hex without #) if !isValidHexColor(label.Color) { - return fmt.Errorf("label %q: invalid color %q (expected 6-digit hex without #)", label.Name, label.Color) + return log.E("setup.github_config", fmt.Sprintf("label %q: invalid color %q (expected 6-digit hex without #)", label.Name, label.Color), nil) } } @@ -176,14 +177,14 @@ func (c *GitHubConfig) Validate() error { continue } if len(wh.Events) == 0 { - return fmt.Errorf("webhook %q: at least one event is required", name) + return log.E("setup.github_config", fmt.Sprintf("webhook %q: at least one event is required", name), nil) } } // Validate branch protection for i, bp := range c.BranchProtection { if bp.Branch == "" { - return fmt.Errorf("branch_protection %d: branch is required", i+1) + return log.E("setup.github_config", fmt.Sprintf("branch_protection %d: branch is required", i+1), nil) } } diff --git a/cmd/setup/github_protection.go b/cmd/setup/github_protection.go index 56622e4..98b0243 100644 --- a/cmd/setup/github_protection.go +++ b/cmd/setup/github_protection.go @@ -13,6 +13,7 @@ import ( "strings" "forge.lthn.ai/core/cli/pkg/cli" + log "forge.lthn.ai/core/go-log" ) // GitHubBranchProtection represents branch protection rules from the GitHub API. @@ -68,7 +69,7 @@ type RequiredConversationResolution struct { func GetBranchProtection(repoFullName, branch string) (*GitHubBranchProtection, error) { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return nil, fmt.Errorf("invalid repo format: %s", repoFullName) + return nil, log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } endpoint := fmt.Sprintf("repos/%s/%s/branches/%s/protection", parts[0], parts[1], branch) @@ -101,7 +102,7 @@ func GetBranchProtection(repoFullName, branch string) (*GitHubBranchProtection, func SetBranchProtection(repoFullName, branch string, config BranchProtectionConfig) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } // Build the protection payload diff --git a/cmd/setup/github_security.go b/cmd/setup/github_security.go index 3a37d95..4353664 100644 --- a/cmd/setup/github_security.go +++ b/cmd/setup/github_security.go @@ -15,6 +15,7 @@ import ( "strings" "forge.lthn.ai/core/cli/pkg/cli" + log "forge.lthn.ai/core/go-log" ) // GitHubSecurityStatus represents the security settings status of a repository. @@ -46,7 +47,7 @@ type SecurityFeature struct { func GetSecuritySettings(repoFullName string) (*GitHubSecurityStatus, error) { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return nil, fmt.Errorf("invalid repo format: %s", repoFullName) + return nil, log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } status := &GitHubSecurityStatus{} @@ -102,7 +103,7 @@ func GetSecuritySettings(repoFullName string) (*GitHubSecurityStatus, error) { func EnableDependabotAlerts(repoFullName string) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } endpoint := fmt.Sprintf("repos/%s/%s/vulnerability-alerts", parts[0], parts[1]) @@ -118,7 +119,7 @@ func EnableDependabotAlerts(repoFullName string) error { func EnableDependabotSecurityUpdates(repoFullName string) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } endpoint := fmt.Sprintf("repos/%s/%s/automated-security-fixes", parts[0], parts[1]) @@ -134,7 +135,7 @@ func EnableDependabotSecurityUpdates(repoFullName string) error { func DisableDependabotSecurityUpdates(repoFullName string) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } endpoint := fmt.Sprintf("repos/%s/%s/automated-security-fixes", parts[0], parts[1]) @@ -150,7 +151,7 @@ func DisableDependabotSecurityUpdates(repoFullName string) error { func UpdateSecurityAndAnalysis(repoFullName string, secretScanning, pushProtection bool) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } // Build the payload diff --git a/cmd/setup/github_webhooks.go b/cmd/setup/github_webhooks.go index a530ef3..c448591 100644 --- a/cmd/setup/github_webhooks.go +++ b/cmd/setup/github_webhooks.go @@ -13,6 +13,7 @@ import ( "strings" "forge.lthn.ai/core/cli/pkg/cli" + log "forge.lthn.ai/core/go-log" ) // GitHubWebhook represents a webhook as returned by the GitHub API. @@ -35,7 +36,7 @@ type GitHubWebhookConfig struct { func ListWebhooks(repoFullName string) ([]GitHubWebhook, error) { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return nil, fmt.Errorf("invalid repo format: %s", repoFullName) + return nil, log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } endpoint := fmt.Sprintf("repos/%s/%s/hooks", parts[0], parts[1]) @@ -65,7 +66,7 @@ func ListWebhooks(repoFullName string) ([]GitHubWebhook, error) { func CreateWebhook(repoFullName string, name string, config WebhookConfig) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } // Build the webhook payload @@ -108,7 +109,7 @@ func CreateWebhook(repoFullName string, name string, config WebhookConfig) error func UpdateWebhook(repoFullName string, hookID int, config WebhookConfig) error { parts := strings.Split(repoFullName, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: %s", repoFullName) + return log.E("setup.github", fmt.Sprintf("invalid repo format: %s", repoFullName), nil) } payload := map[string]any{ diff --git a/deploy/coolify/client.go b/deploy/coolify/client.go index 1663561..5ae236a 100644 --- a/deploy/coolify/client.go +++ b/deploy/coolify/client.go @@ -3,11 +3,12 @@ package coolify import ( "context" "encoding/json" - "errors" "fmt" "os" "sync" + log "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-devops/deploy/python" ) @@ -42,15 +43,15 @@ func DefaultConfig() Config { // NewClient creates a new Coolify client. func NewClient(cfg Config) (*Client, error) { if cfg.BaseURL == "" { - return nil, errors.New("COOLIFY_URL not set") + return nil, log.E("coolify", "COOLIFY_URL not set", nil) } if cfg.APIToken == "" { - return nil, errors.New("COOLIFY_TOKEN not set") + return nil, log.E("coolify", "COOLIFY_TOKEN not set", nil) } // Initialize Python runtime if err := python.Init(); err != nil { - return nil, fmt.Errorf("failed to initialize Python: %w", err) + return nil, log.E("coolify", "failed to initialize Python", err) } return &Client{ @@ -73,11 +74,11 @@ func (c *Client) Call(ctx context.Context, operationID string, params map[string // Generate and run Python script script, err := python.CoolifyScript(c.baseURL, c.apiToken, operationID, params) if err != nil { - return nil, fmt.Errorf("failed to generate script: %w", err) + return nil, log.E("coolify", "failed to generate script", err) } output, err := python.RunScript(ctx, script) if err != nil { - return nil, fmt.Errorf("API call %s failed: %w", operationID, err) + return nil, log.E("coolify", fmt.Sprintf("API call %s failed", operationID), err) } // Parse JSON result @@ -88,7 +89,7 @@ func (c *Client) Call(ctx context.Context, operationID string, params map[string if err2 := json.Unmarshal([]byte(output), &arrResult); err2 == nil { return map[string]any{"result": arrResult}, nil } - return nil, fmt.Errorf("failed to parse response: %w (output: %s)", err, output) + return nil, log.E("coolify", fmt.Sprintf("failed to parse response (output: %s)", output), err) } return result, nil diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 05c5125..51e6164 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -3,9 +3,10 @@ package snapshot import ( "encoding/json" - "errors" "time" + log "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-scm/manifest" ) @@ -35,7 +36,7 @@ func Generate(m *manifest.Manifest, commit, tag string) ([]byte, error) { // GenerateAt creates a core.json snapshot with an explicit build timestamp. func GenerateAt(m *manifest.Manifest, commit, tag string, built time.Time) ([]byte, error) { if m == nil { - return nil, errors.New("snapshot: manifest is nil") + return nil, log.E("snapshot", "manifest is nil", nil) } snap := Snapshot{ From 2ef3e48b11a5182a5bad8cf54d0cb8084c900295 Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 16 Mar 2026 22:20:21 +0000 Subject: [PATCH 06/10] chore: sync dependencies for v0.1.11 Co-Authored-By: Virgil --- go.mod | 24 ++++++++++-------------- go.sum | 48 ++++++++++++++++++++---------------------------- 2 files changed, 30 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 0593da2..c75452c 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.26.0 require ( code.gitea.io/sdk/gitea v0.23.2 - forge.lthn.ai/core/agent v0.3.1 - forge.lthn.ai/core/cli v0.3.2 + forge.lthn.ai/core/agent v0.3.2 + forge.lthn.ai/core/cli v0.3.5 forge.lthn.ai/core/go v0.3.1 - forge.lthn.ai/core/go-container v0.1.3 - forge.lthn.ai/core/go-i18n v0.1.4 - forge.lthn.ai/core/go-io v0.1.2 + forge.lthn.ai/core/go-container v0.1.5 + forge.lthn.ai/core/go-i18n v0.1.6 + forge.lthn.ai/core/go-io v0.1.5 forge.lthn.ai/core/go-log v0.0.4 - forge.lthn.ai/core/go-scm v0.3.1 + forge.lthn.ai/core/go-scm v0.3.4 github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 github.com/stretchr/testify v1.11.1 golang.org/x/term v0.41.0 @@ -21,13 +21,10 @@ require ( require ( codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 // indirect - forge.lthn.ai/core/config v0.1.3 // indirect - forge.lthn.ai/core/go-crypt v0.1.7 // indirect - forge.lthn.ai/core/go-inference v0.1.4 // indirect - forge.lthn.ai/core/go-process v0.2.3 // indirect - forge.lthn.ai/core/go-store v0.1.6 // indirect + forge.lthn.ai/core/config v0.1.6 // indirect + forge.lthn.ai/core/go-inference v0.1.5 // indirect + forge.lthn.ai/core/go-store v0.1.8 // indirect github.com/42wim/httpsig v1.2.3 // indirect - github.com/ProtonMail/go-crypto v1.4.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect @@ -38,7 +35,6 @@ require ( github.com/charmbracelet/x/term v0.2.2 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/cloudflare/circl v1.6.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -81,5 +77,5 @@ require ( modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.46.1 // indirect + modernc.org/sqlite v1.46.2 // indirect ) diff --git a/go.sum b/go.sum index 4262d5e..03c5d20 100644 --- a/go.sum +++ b/go.sum @@ -2,36 +2,30 @@ code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI= codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= -forge.lthn.ai/core/agent v0.3.1 h1:Q6lkSg9nr2c1oj2Pr/s3LN7xItIyOmSRgLSfMaaNuyQ= -forge.lthn.ai/core/agent v0.3.1/go.mod h1:FfHS10AkPcxnc+ms93QzNJtZ7dgcET0LvMcJAzY2h+w= -forge.lthn.ai/core/cli v0.3.2 h1:EZNVG/W+YDAS77pw4uyDcj9+ntyl4JE287mrRX6MVGQ= -forge.lthn.ai/core/cli v0.3.2/go.mod h1:LfWXT4JldTuyDeSj5+SQGv79LSzs+yi9HUCq1diGDQ0= -forge.lthn.ai/core/config v0.1.3 h1:mq02v7LFf9jHSqJakO08qYQnPP8oVMbJHlOxNARXBa8= -forge.lthn.ai/core/config v0.1.3/go.mod h1:4+/ytojOSaPoAQ1uub1+GeOM8OoYdR9xqMtVA3SZ8Qk= +forge.lthn.ai/core/agent v0.3.2 h1:KMtHpQl28PGaulrsoysS6szEiklKK8aDiGzHMWAI1ms= +forge.lthn.ai/core/agent v0.3.2/go.mod h1:Yf7m03wD0mogHouV7tDEZVHbq4jGJOf67tNjbhd6P6M= +forge.lthn.ai/core/cli v0.3.5 h1:P7yK0DmSA1QnUMFuCjJZf/fk/akKPIxopQ6OwD8Sar8= +forge.lthn.ai/core/cli v0.3.5/go.mod h1:SeArHx+hbpX5iZqgASCD7Q1EDoc6uaaGiGBotmNzIx4= +forge.lthn.ai/core/config v0.1.6 h1:TSynnKJpoXpvsS//TSnwVH31GyMJF/lbT/oIiMaODNk= +forge.lthn.ai/core/config v0.1.6/go.mod h1:AIm7VlO/h4s1LmGSn0HZb+RqAbhmZFJppVGivcsJmGE= forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM= forge.lthn.ai/core/go v0.3.1/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= -forge.lthn.ai/core/go-container v0.1.3 h1:Pb/latnVFBgyI4zDyYxAiRRqKrOYIAxL6om+k2YS1q8= -forge.lthn.ai/core/go-container v0.1.3/go.mod h1:wIlly3pAluVQnQ+DLnZ15pENOFkJicWRRke6msCudLI= -forge.lthn.ai/core/go-crypt v0.1.7 h1:tyDFnXjEksHFQpkFwCpEn+x7zvwh4LnaU+/fP3WmqZc= -forge.lthn.ai/core/go-crypt v0.1.7/go.mod h1:mQdr6K8lWOcyHmSEW24vZPTThQF8fteVgZi8CO+Ko3Y= -forge.lthn.ai/core/go-i18n v0.1.4 h1:zOHUUJDgRo88/3tj++kN+VELg/buyZ4T2OSdG3HBbLQ= -forge.lthn.ai/core/go-i18n v0.1.4/go.mod h1:aDyAfz7MMgWYgLkZCptfFmZ7jJg3ocwjEJ1WkJSvv4U= -forge.lthn.ai/core/go-inference v0.1.4 h1:fuAgWbqsEDajHniqAKyvHYbRcBrkGEiGSqR2pfTMRY0= -forge.lthn.ai/core/go-inference v0.1.4/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= -forge.lthn.ai/core/go-io v0.1.2 h1:q8hj2jtOFqAgHlBr5wsUAOXtaFkxy9gqGrQT/il0WYA= -forge.lthn.ai/core/go-io v0.1.2/go.mod h1:PbNKW1Q25ywSOoQXeGdQHbV5aiIrTXvHIQ5uhplA//g= +forge.lthn.ai/core/go-container v0.1.5 h1:EWSg0nlMeeGV+KQenRn8XSqJBu2VHdafELIuvFwregc= +forge.lthn.ai/core/go-container v0.1.5/go.mod h1:WdG6PMdscBhUj1AV/Sur4Zc+VakQRN2ejOmLCpaggcg= +forge.lthn.ai/core/go-i18n v0.1.6 h1:Z9h6sEZsgJmWlkkq3ZPZyfgWipeeqN5lDCpzltpamHU= +forge.lthn.ai/core/go-i18n v0.1.6/go.mod h1:C6CbwdN7sejTx/lbutBPrxm77b8paMHBO6uHVLHOdqQ= +forge.lthn.ai/core/go-inference v0.1.5 h1:Az/Euv1DusJQJz/Eca0Ey7sVXQkFLPHW0TBrs9g+Qwg= +forge.lthn.ai/core/go-inference v0.1.5/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= +forge.lthn.ai/core/go-io v0.1.5 h1:+XJ1YhaGGFLGtcNbPtVlndTjk+pO0Ydi2hRDj5/cHOM= +forge.lthn.ai/core/go-io v0.1.5/go.mod h1:FRtXSsi8W+U9vewCU+LBAqqbIj3wjXA4dBdSv3SAtWI= forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= -forge.lthn.ai/core/go-process v0.2.3 h1:/ERqRYHgCNZjNT9NMinAAJJGJWSsHuCTiHFNEm6nTPY= -forge.lthn.ai/core/go-process v0.2.3/go.mod h1:gVTbxL16ccUIexlFcyDtCy7LfYvD8Rtyzfo8bnXAXrU= -forge.lthn.ai/core/go-scm v0.3.1 h1:G+DqVJLT+UjgUzu2Hnseyl2szhb0wB+DB8VYhq/bLOI= -forge.lthn.ai/core/go-scm v0.3.1/go.mod h1:ER9fQBs8nnlJZQ6+ALnwv+boK/xiwg8jEc9VP6DMijk= -forge.lthn.ai/core/go-store v0.1.6 h1:7T+K5cciXOaWRxge0WnGkt0PcK3epliWBa1G2FLEuac= -forge.lthn.ai/core/go-store v0.1.6/go.mod h1:/2vqaAn+HgGU14N29B+vIfhjIsBzy7RC+AluI6BIUKI= +forge.lthn.ai/core/go-scm v0.3.4 h1:McZvp2gI3wEPCF/jim8O4F1+Vp477N81TUiiklTq5hw= +forge.lthn.ai/core/go-scm v0.3.4/go.mod h1:AOrx4CEmV8/Q73Cvd2LkbFniYGpk46mticpYmK5MnJA= +forge.lthn.ai/core/go-store v0.1.8 h1:jeFqxilifa/hXtQqCeXX/+Vwy6M/XZE7uCP8XQ0ercw= +forge.lthn.ai/core/go-store v0.1.8/go.mod h1:DJocTeTCjFBPn5ppQT/IDheFJhOfwlHeoxEUtDH07zE= github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= -github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ= -github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -56,8 +50,6 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= -github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -214,8 +206,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= -modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE= +modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 764f290b34c9705cf49b3d487682e4c927d78675 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 00:34:55 +0000 Subject: [PATCH 07/10] feat: add en-GB locale file for dev/deploy/docs/setup commands 281 translation keys covering dev (health, work, commit, push, pull, tag, impact, issues, reviews, ci, apply, workflow, vm), deploy, docs, git, and setup commands. Co-Authored-By: Virgil --- locales/en.json | 493 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 locales/en.json diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..c77bc36 --- /dev/null +++ b/locales/en.json @@ -0,0 +1,493 @@ +{ + "cmd": { + "deploy": { + "short": "Manage deployments via Coolify", + "long": "Manage deployments, servers, applications and services via the Coolify API." + }, + "dev": { + "short": "Multi-repo development workflows", + "long": "Development workflow commands for managing multiple repositories.\n\nIncludes git operations, forge integration, CI status, and dev environment management.", + "api": { + "short": "API synchronisation tools" + }, + "health": { + "short": "Quick health check across all repos", + "long": "Show a one-line summary of repository health: total repos, dirty count, push/pull status and errors.", + "repos": "repos", + "to_push": "to push", + "to_pull": "to pull", + "dirty_label": "Dirty:", + "ahead_label": "Ahead:", + "behind_label": "Behind:", + "errors": "errors", + "errors_label": "Errors:", + "more": "(+{{.Count}} more)", + "flag": { + "verbose": "Show per-repo details" + } + }, + "work": { + "short": "Combined status, commit and push workflow", + "long": "Show status table for all repos, optionally commit dirty repos and push unpushed commits in a single workflow.", + "flag": { + "status": "Show status table only (no commit or push)", + "commit": "Auto-commit dirty repos before pushing" + }, + "table_modified": "Modified", + "table_untracked": "Untracked", + "table_staged": "Staged", + "table_ahead": "Ahead", + "all_up_to_date": "All repos are up to date.", + "use_commit_flag": "Tip: use --commit to auto-commit dirty repos", + "error_prefix": "error:" + }, + "commit": { + "short": "Claude-assisted commit for dirty repos", + "long": "Generate commit messages with Claude for repositories that have uncommitted changes.\n\nRuns in single-repo mode when inside a git repo, or multi-repo mode with a registry.", + "flag": { + "all": "Commit all dirty repos without prompting" + }, + "committing": "Committing..." + }, + "push": { + "short": "Push repos with unpushed commits", + "long": "Push all repositories that have commits ahead of remote.\n\nHandles diverged branches by offering to pull and retry.", + "flag": { + "force": "Push without confirmation" + }, + "confirm": "Push?", + "confirm_push": "Push {{.Commits}} commit(s) across {{.Repos}} repo(s)?", + "all_up_to_date": "All repos are up to date.", + "done_pushed": "{{.Count}} pushed", + "diverged": "branch has diverged", + "diverged_help": "Some repos have diverged from remote. This usually means someone pushed while you were working.", + "pull_and_retry": "Pull and retry push?", + "uncommitted_changes_commit": "Uncommitted changes found. Commit first?" + }, + "pull": { + "short": "Pull repos that are behind remote", + "long": "Pull latest changes for repositories that are behind their remote tracking branch.", + "flag": { + "all": "Pull all repos, not just those behind" + }, + "pulling": "pulling", + "pulling_repos": "Pulling {{.Count}} repo(s)...", + "all_up_to_date": "All repos are up to date.", + "done_pulled": "{{.Count}} pulled", + "repos_behind": "{{.Count}} repo(s) behind remote", + "commits_behind": "{{.Count}} commit(s) behind" + }, + "tag": { + "short": "Tag and push all repos in dependency order", + "long": "Bump patch version, update Go dependencies, tag and push all repos in topological order.", + "flag": { + "dry_run": "Preview the tag plan without making changes", + "force": "Tag without confirmation" + } + }, + "impact": { + "short": "Analyse dependency impact of a repo", + "long": "Show which repositories are affected when a given repo changes, including direct and transitive dependents.", + "analysis_for": "Impact analysis for", + "direct_dependents": "{{.Count}} direct dependent(s)", + "transitive_dependents": "{{.Count}} transitive dependent(s)", + "no_dependents": "{{.Name}} has no dependents", + "changes_affect": "Changes to {{.Repo}} affect {{.Affected}} of {{.Total}} repos", + "requires_registry": "Impact analysis requires a repos.yaml registry" + }, + "issues": { + "short": "List open issues across repos", + "long": "Fetch and display open issues from the Forge API across all registered repositories.", + "flag": { + "assignee": "Filter by assignee username", + "limit": "Maximum issues per repo" + }, + "open_issues": "{{.Count}} open issue(s)", + "no_issues": "No open issues found." + }, + "reviews": { + "short": "List pull requests needing review", + "long": "Show open pull requests with review status across all registered repositories.", + "flag": { + "all": "Include draft PRs", + "author": "Filter by PR author" + }, + "open_prs": "{{.Count}} open PR(s)", + "no_prs": "No open pull requests found.", + "approved": "{{.Count}} approved", + "changes_requested": "{{.Count}} changes requested", + "draft": "[draft]", + "status_approved": "approved", + "status_changes": "changes requested", + "status_pending": "pending review" + }, + "ci": { + "short": "Check CI workflow status", + "long": "Show CI/CD pipeline status for all repos, with pass/fail counts and optional branch filtering.", + "flag": { + "branch": "Branch to check (default: main)", + "failed": "Show only failed workflows" + }, + "passing": "{{.Count}} passing", + "failing": "{{.Count}} failing", + "repos_checked": "{{.Count}} repos checked", + "no_ci": "{{.Count}} no CI" + }, + "apply": { + "short": "Run a command across repos", + "long": "Execute a shell command or script in every repo directory, optionally committing and pushing changes.\n\nDesigned for safe batch operations by AI agents.", + "action": "Action", + "cancelled": "Cancelled.", + "confirm": "Proceed?", + "dry_run_mode": "[dry-run] No changes will be made", + "no_changes": "no changes", + "summary": "Summary", + "targets": "Targets", + "warning": "This will run the command in each repo directory.", + "flag": { + "command": "Shell command to run in each repo", + "script": "Script file to run in each repo", + "repos": "Comma-separated list of repo names to target", + "commit": "Commit changes after running", + "message": "Commit message (required with --commit)", + "push": "Push after committing", + "co_author": "Co-author for commits", + "dry_run": "Preview without making changes", + "yes": "Skip confirmation prompt", + "continue": "Continue on error instead of stopping" + }, + "error": { + "no_command": "Either --command or --script is required", + "both_command_script": "Cannot use both --command and --script", + "no_repos": "No target repos found", + "command_failed": "Command failed — stopping (use --continue to skip failures)", + "commit_needs_message": "--commit requires --message" + } + }, + "file_sync": { + "short": "Sync a file or directory to repos", + "long": "Copy a file or directory to matching repos, optionally committing and pushing the changes.\n\nDesigned for safe file distribution by AI agents.", + "source": "Source", + "targets": "Targets", + "dry_run_mode": "[dry-run] No changes will be made", + "no_changes": "no changes", + "summary": "Summary", + "flag": { + "to": "Target repo pattern (e.g. core-*)", + "message": "Commit message (omit to leave uncommitted)", + "push": "Push after committing", + "co_author": "Co-author for commits", + "dry_run": "Preview without making changes" + }, + "error": { + "source_not_found": "Source not found: {{.Path}}", + "no_targets": "No target repos matched the pattern" + } + }, + "sync": { + "short": "Synchronise public API surfaces", + "long": "Scan internal service packages and regenerate public API wrappers to keep them in sync." + }, + "workflow": { + "short": "Manage CI workflow files", + "long": "List and synchronise CI/CD workflow files across all registered repositories.", + "no_workflows": "No workflow files found in any repo.", + "synced": "synced", + "up_to_date": "up to date", + "would_sync": "would sync", + "dry_run_mode": "[dry-run] No changes will be made", + "run_without_dry_run": "Run without --dry-run to apply changes.", + "template_not_found": "Template workflow not found: {{.File}}", + "read_template_error": "Failed to read template workflow", + "failed_count": "{{.Count}} failed", + "skipped_count": "{{.Count}} skipped", + "synced_count": "{{.Count}} synced", + "would_sync_count": "{{.Count}} would sync", + "header": { + "repo": "Repo" + }, + "list": { + "short": "Show workflow matrix across repos", + "long": "Display a table showing which CI/CD workflow files exist in each repository." + }, + "sync": { + "short": "Copy a workflow template to all repos", + "long": "Synchronise a workflow template file into every registered repository's .github/workflows/ directory.", + "flag": { + "dry_run": "Preview sync without writing files" + } + } + }, + "scanning_label": "Scanning...", + "no_git_repos": "No git repositories found.", + "no_changes": "No changes to commit.", + "repos_with_changes": "{{.Count}} repo(s) with changes", + "modified": "{{.Count}} modified", + "untracked": "{{.Count}} untracked", + "staged": "{{.Count}} staged", + "committed": "committed", + "committing": "Committing", + "confirm_claude_commit": "Commit with Claude?", + "done_succeeded": "{{.Count}} succeeded", + "status": { + "clean": "clean" + }, + "vm": { + "boot": { + "short": "Start the dev environment VM", + "long": "Boot the Parallels dev environment virtual machine with configurable memory and CPU.", + "flag": { + "memory": "Memory in MB (default: auto)", + "cpus": "Number of CPUs (default: auto)", + "fresh": "Discard existing state and boot fresh" + } + }, + "install": { + "short": "Download the dev environment image", + "long": "Download and install the Parallels dev environment VM image." + }, + "serve": { + "short": "Mount project and start dev server", + "long": "Mount the current project directory into the VM and start the development server.", + "flag": { + "port": "Port to expose (default: auto)", + "path": "Project path to mount" + } + }, + "shell": { + "short": "Open a shell in the dev VM", + "long": "Open an interactive shell session inside the running dev environment VM.", + "flag": { + "console": "Use serial console instead of SSH" + } + }, + "stop": { + "short": "Stop the dev environment VM", + "long": "Gracefully shut down the running Parallels dev environment VM." + }, + "update": { + "short": "Check for and apply VM updates", + "long": "Check if a newer dev environment image is available and optionally download it.", + "flag": { + "apply": "Download and apply the update" + } + }, + "status": { + "short": "Check dev VM status", + "long": "Show installation and runtime status of the Parallels dev environment VM." + }, + "claude": { + "short": "Start sandboxed Claude session in VM", + "long": "Launch a Claude Code session inside the dev VM with project files mounted and optional authentication.", + "flag": { + "no_auth": "Skip authentication forwarding", + "model": "Model to use for the Claude session", + "auth": "Authentication flags to pass through" + } + }, + "test": { + "short": "Run tests in the dev environment", + "long": "Execute the test suite inside the dev environment VM with the current project mounted.", + "flag": { + "name": "Run only tests matching this name" + } + }, + "already_installed": "Dev environment already installed.", + "check_updates": "Check for updates with {{.Command}}", + "downloading": "Downloading dev environment image...", + "progress_label": "Progress:", + "installed_in": "Installed in {{.Duration}}", + "start_with": "Start with {{.Command}}", + "not_installed": "Dev environment not installed. Run 'core dev install' first.", + "config_label": "Config:", + "config_value": "{{.Memory}}MB RAM, {{.CPUs}} CPUs", + "booting": "Booting dev environment...", + "running": "Dev environment running", + "connect_with": "Connect with {{.Command}}", + "ssh_port": "SSH port:", + "not_running": "Dev environment is not running.", + "stopping": "Stopping dev environment...", + "status_title": "Dev Environment Status", + "installed_label": "Installed:", + "installed_yes": "Yes", + "installed_no": "No", + "install_with": "Install with {{.Command}}", + "container_label": "Container:", + "memory_label": "Memory:", + "cpus_label": "CPUs:", + "uptime_label": "Uptime:", + "latest_label": "Latest:", + "up_to_date": "Dev environment is up to date.", + "update_available": "Update available!", + "run_to_update": "Run {{.Command}} to update.", + "stopping_current": "Stopping current VM...", + "downloading_update": "Downloading update...", + "updated_in": "Updated in {{.Duration}}" + } + }, + "docs": { + "short": "Documentation management", + "long": "List, scan and synchronise documentation across all registered repositories.", + "list": { + "short": "List documentation coverage", + "long": "Show a table of documentation files (README, CLAUDE.md, CHANGELOG, docs/) for each repo.", + "coverage_summary": "{{.WithDocs}} with docs, {{.WithoutDocs}} without", + "header": { + "readme": "README", + "claude": "CLAUDE", + "changelog": "CHANGELOG", + "docs": "Docs" + } + }, + "sync": { + "short": "Sync docs to a central location", + "long": "Copy documentation from each repo's docs/ directory into a central output directory.", + "flag": { + "dry_run": "Preview sync without writing files", + "output": "Output directory for synced docs" + }, + "confirm": "Sync documentation?", + "dry_run_notice": "Dry run — no files written.", + "files_count": "({{.Count}} file(s))", + "repos_with_docs": "{{.Count}} repo(s) with docs", + "synced_packages": "{{.Count}} package(s) synced", + "total_summary": "{{.Files}} files from {{.Repos}} repos → {{.Output}}", + "no_docs_found": "No documentation found in any repo." + } + }, + "git": { + "short": "Git workflow commands", + "long": "Git operations for single or multi-repo workflows.\n\nIncludes status, commit, push, pull, and safe batch operations for AI agents." + }, + "setup": { + "short": "Set up workspace and clone packages", + "long": "Bootstrap a new workspace or clone packages from a repos.yaml registry.\n\nIn bootstrap mode (no registry), clones the devops repo first, then offers a package wizard.\nIn registry mode, clones all or selected packages into the packages directory.", + "flag": { + "all": "Clone all packages without prompting", + "build": "Run build after cloning", + "dry_run": "Preview what would be cloned", + "name": "Project directory name (bootstrap mode)", + "only": "Filter by repo type (e.g. foundation,module,product)", + "registry": "Path to repos.yaml" + }, + "bootstrap_mode": "No registry found — entering bootstrap mode", + "cloning_current_dir": "Cloning into current directory", + "creating_project_dir": "Creating project directory", + "cloned": "cloned", + "would_clone": "Would clone", + "already_exists": "already exists", + "would_load_registry": "Would load registry from", + "org_label": "Organisation:", + "to_clone": "{{.Count}} to clone", + "exist": "{{.Count}} exist", + "nothing_to_clone": "Nothing to clone — all repos already exist.", + "would_clone_list": "Would clone:", + "cancelled": "Cancelled.", + "done": "done", + "cloned_count": "{{.Count}} cloned", + "already_exist_count": "{{.Count}} already exist", + "wizard": { + "git_repo_title": "Git Repository Detected", + "what_to_do": "This directory is already a git repository. What would you like to do?", + "project_name_title": "Project Name", + "project_name_desc": "Enter a name for the project directory", + "package_selection": "Package Selection", + "selection_hint": "Use space to select, enter to confirm", + "select_packages": "Select packages to clone", + "confirm_clone": "Clone {{.Count}} package(s) to {{.Target}}?" + }, + "github": { + "short": "Configure GitHub repo settings", + "long": "Apply standardised GitHub settings (labels, webhooks, branch protection, security) to repos.", + "flag": { + "repo": "Target a specific repo", + "all": "Apply all settings", + "labels": "Sync issue labels", + "webhooks": "Sync webhooks", + "protection": "Sync branch protection rules", + "security": "Sync security settings", + "check": "Check current settings (dry run)", + "config": "Path to GitHub config file" + }, + "error": { + "not_authenticated": "GitHub CLI (gh) is not authenticated. Run 'gh auth login' first.", + "config_not_found": "GitHub config file not found", + "conflicting_flags": "Cannot use --check with modification flags" + }, + "dry_run_mode": "[dry-run] Checking current settings", + "no_repos_specified": "No repos specified.", + "usage_hint": "Use --repo= or --all to target repos.", + "run_without_check": "Run without --check to apply changes.", + "no_changes": "No changes needed", + "repos_checked": "Repos checked", + "all_up_to_date": "All repos are up to date", + "repos_with_changes": "Repos with changes", + "to_create": "To create", + "to_update": "To update", + "to_delete": "To delete" + }, + "repo": { + "setting_up": "Setting up repo", + "detected_type": "Detected project type", + "would_create": "Would create", + "created": "Created" + } + } + }, + "common": { + "flag": { + "registry": "Path to repos.yaml registry", + "verbose": "Show detailed output" + }, + "count": { + "commits": "{{.Count}} commit(s)", + "failed": "{{.Count}} failed", + "files": "{{.Count}} file(s)", + "pending": "{{.Count}} pending", + "repos_unpushed": "{{.Count}} repo(s) with unpushed commits", + "skipped": "{{.Count}} skipped", + "succeeded": "{{.Count}} succeeded" + }, + "status": { + "clean": "clean", + "dirty": "dirty", + "synced": "synced", + "up_to_date": "up to date", + "cloning": "Cloning", + "running": "Running", + "stopped": "Stopped" + }, + "prompt": { + "abort": "Aborted." + }, + "progress": { + "checking_updates": "Checking for updates...", + "checking": "Checking..." + } + }, + "cli": { + "aborted": "Aborted." + }, + "error": { + "gh_not_found": "GitHub CLI (gh) not found. Install it from https://cli.github.com/", + "registry_not_found": "Registry (repos.yaml) not found. Run from a workspace directory or use --registry.", + "repo_not_found": "Repository not found: {{.Name}}" + }, + "i18n": { + "count": { + "failed": "{{.Count}} failed" + }, + "done": { + "sync": "Sync complete." + }, + "fail": { + "load": "Failed to load {{.Name}}", + "run": "Failed to run {{.Name}}", + "scan": "Failed to scan {{.Name}}" + }, + "progress": { + "check": "Checking", + "fetch": "Fetching" + } + } +} From b9d9994a36d0b79bb2abf21e3a9a78425be9fee8 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 00:45:15 +0000 Subject: [PATCH 08/10] feat: embed and load locale translations on init Locales auto-load when cmd/dev is imported via init(). Co-Authored-By: Virgil --- cmd/dev/cmd_dev.go | 3 +++ locales/embed.go | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 locales/embed.go diff --git a/cmd/dev/cmd_dev.go b/cmd/dev/cmd_dev.go index c33f7dc..3c33401 100644 --- a/cmd/dev/cmd_dev.go +++ b/cmd/dev/cmd_dev.go @@ -34,10 +34,13 @@ package dev import ( "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-devops/locales" "forge.lthn.ai/core/go-i18n" ) func init() { + // Load go-devops translations (cmd.dev.*, cmd.deploy.*, cmd.docs.*, etc.) + i18n.LoadFS(locales.FS, ".") cli.RegisterCommands(AddDevCommands) } diff --git a/locales/embed.go b/locales/embed.go new file mode 100644 index 0000000..19a6c73 --- /dev/null +++ b/locales/embed.go @@ -0,0 +1,7 @@ +// Package locales embeds translation files for the go-devops module. +package locales + +import "embed" + +//go:embed *.json +var FS embed.FS From b429736097d1df9773391ccedd5e381f2c8365be Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 01:35:18 +0000 Subject: [PATCH 09/10] refactor: pass locales via RegisterCommands instead of direct i18n call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clean init(): cli.RegisterCommands(AddDevCommands, locales.FS) No more i18n.AddLoader — CLI handles locale loading automatically. Co-Authored-By: Virgil --- cmd/dev/cmd_dev.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/dev/cmd_dev.go b/cmd/dev/cmd_dev.go index 3c33401..f063c68 100644 --- a/cmd/dev/cmd_dev.go +++ b/cmd/dev/cmd_dev.go @@ -39,9 +39,7 @@ import ( ) func init() { - // Load go-devops translations (cmd.dev.*, cmd.deploy.*, cmd.docs.*, etc.) - i18n.LoadFS(locales.FS, ".") - cli.RegisterCommands(AddDevCommands) + cli.RegisterCommands(AddDevCommands, locales.FS) } // Style aliases from shared package From 0f50f98a95519ff3be27e873bae630cc3da7057e Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 02:21:52 +0000 Subject: [PATCH 10/10] fix: defer i18n.T() for package-level command vars Package-level var declarations run at import time, before i18n is initialised. Move Short/Long assignment to AddCommands functions which run after Core startup. Co-Authored-By: Virgil --- cmd/deploy/cmd_commands.go | 4 +++- cmd/deploy/cmd_deploy.go | 9 ++++++--- cmd/docs/cmd_commands.go | 8 ++++++-- cmd/docs/cmd_docs.go | 13 ++++++++++--- cmd/docs/cmd_list.go | 4 +--- cmd/docs/cmd_sync.go | 4 +--- cmd/setup/cmd_commands.go | 6 +++++- cmd/setup/cmd_setup.go | 4 +--- 8 files changed, 33 insertions(+), 19 deletions(-) diff --git a/cmd/deploy/cmd_commands.go b/cmd/deploy/cmd_commands.go index 193865f..f4fb5d6 100644 --- a/cmd/deploy/cmd_commands.go +++ b/cmd/deploy/cmd_commands.go @@ -2,13 +2,15 @@ package deploy import ( "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-devops/locales" ) func init() { - cli.RegisterCommands(AddDeployCommands) + cli.RegisterCommands(AddDeployCommands, locales.FS) } // AddDeployCommands registers the 'deploy' command and all subcommands. func AddDeployCommands(root *cli.Command) { + setDeployI18n() root.AddCommand(Cmd) } diff --git a/cmd/deploy/cmd_deploy.go b/cmd/deploy/cmd_deploy.go index 7b962dd..7e26800 100644 --- a/cmd/deploy/cmd_deploy.go +++ b/cmd/deploy/cmd_deploy.go @@ -20,9 +20,12 @@ var ( // Cmd is the root deploy command. var Cmd = &cli.Command{ - Use: "deploy", - Short: i18n.T("cmd.deploy.short"), - Long: i18n.T("cmd.deploy.long"), + Use: "deploy", +} + +func setDeployI18n() { + Cmd.Short = i18n.T("cmd.deploy.short") + Cmd.Long = i18n.T("cmd.deploy.long") } var serversCmd = &cli.Command{ diff --git a/cmd/docs/cmd_commands.go b/cmd/docs/cmd_commands.go index 4254325..31d469e 100644 --- a/cmd/docs/cmd_commands.go +++ b/cmd/docs/cmd_commands.go @@ -8,13 +8,17 @@ // to a central location for unified documentation builds. package docs -import "forge.lthn.ai/core/cli/pkg/cli" +import ( + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-devops/locales" +) func init() { - cli.RegisterCommands(AddDocsCommands) + cli.RegisterCommands(AddDocsCommands, locales.FS) } // AddDocsCommands registers the 'docs' command and all subcommands. func AddDocsCommands(root *cli.Command) { + setDocsI18n() root.AddCommand(docsCmd) } diff --git a/cmd/docs/cmd_docs.go b/cmd/docs/cmd_docs.go index 04c6c51..fa9a709 100644 --- a/cmd/docs/cmd_docs.go +++ b/cmd/docs/cmd_docs.go @@ -19,9 +19,16 @@ var ( ) var docsCmd = &cli.Command{ - Use: "docs", - Short: i18n.T("cmd.docs.short"), - Long: i18n.T("cmd.docs.long"), + Use: "docs", +} + +func setDocsI18n() { + docsCmd.Short = i18n.T("cmd.docs.short") + docsCmd.Long = i18n.T("cmd.docs.long") + docsListCmd.Short = i18n.T("cmd.docs.list.short") + docsListCmd.Long = i18n.T("cmd.docs.list.long") + docsSyncCmd.Short = i18n.T("cmd.docs.sync.short") + docsSyncCmd.Long = i18n.T("cmd.docs.sync.long") } func init() { diff --git a/cmd/docs/cmd_list.go b/cmd/docs/cmd_list.go index a384c98..b4d565f 100644 --- a/cmd/docs/cmd_list.go +++ b/cmd/docs/cmd_list.go @@ -11,9 +11,7 @@ import ( var docsListRegistryPath string var docsListCmd = &cli.Command{ - Use: "list", - Short: i18n.T("cmd.docs.list.short"), - Long: i18n.T("cmd.docs.list.long"), + Use: "list", RunE: func(cmd *cli.Command, args []string) error { return runDocsList(docsListRegistryPath) }, diff --git a/cmd/docs/cmd_sync.go b/cmd/docs/cmd_sync.go index 5bcaa54..0b0c94e 100644 --- a/cmd/docs/cmd_sync.go +++ b/cmd/docs/cmd_sync.go @@ -21,9 +21,7 @@ var ( ) var docsSyncCmd = &cli.Command{ - Use: "sync", - Short: i18n.T("cmd.docs.sync.short"), - Long: i18n.T("cmd.docs.sync.long"), + Use: "sync", RunE: func(cmd *cli.Command, args []string) error { return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun, docsSyncTarget) }, diff --git a/cmd/setup/cmd_commands.go b/cmd/setup/cmd_commands.go index 0f61918..3c095c8 100644 --- a/cmd/setup/cmd_commands.go +++ b/cmd/setup/cmd_commands.go @@ -25,13 +25,17 @@ package setup import ( "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-devops/locales" + "forge.lthn.ai/core/go-i18n" ) func init() { - cli.RegisterCommands(AddSetupCommands) + cli.RegisterCommands(AddSetupCommands, locales.FS) } // AddSetupCommands registers the 'setup' command and all subcommands. func AddSetupCommands(root *cli.Command) { + setupCmd.Short = i18n.T("cmd.setup.short") + setupCmd.Long = i18n.T("cmd.setup.long") AddSetupCommand(root) } diff --git a/cmd/setup/cmd_setup.go b/cmd/setup/cmd_setup.go index d933cb4..4cb81f9 100644 --- a/cmd/setup/cmd_setup.go +++ b/cmd/setup/cmd_setup.go @@ -33,9 +33,7 @@ var ( ) var setupCmd = &cli.Command{ - Use: "setup", - Short: i18n.T("cmd.setup.short"), - Long: i18n.T("cmd.setup.long"), + Use: "setup", RunE: func(cmd *cli.Command, args []string) error { return runSetupOrchestrator(registryPath, only, dryRun, all, name, build) },