diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d6a898..88c51a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,10 +169,14 @@ jobs: run: | go install golang.org/x/vuln/cmd/govulncheck@latest "$(go env GOPATH)/bin/govulncheck" ./... + # v2, not v1.62: that binary is built with Go 1.23 and refuses to load a + # module targeting 1.25 — "the Go language version used to build + # golangci-lint is lower than the targeted Go version". It failed on its + # first CI run for that reason and nothing to do with the code. - name: Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: - version: v1.62 + version: v2.12 args: --timeout=5m integration: diff --git a/.golangci.yml b/.golangci.yml index 20a355a..729d374 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,20 +3,27 @@ # # The full set was run first, and it reports 489 findings — 409 of them errcheck # on deliberate discards like `defer f.Close()`, 43 noctx, 15 errorlint. Turning -# all of that on at once produces a gate that is red from the first commit, and a +# all of that on at once produces a gate that is red from its first commit, and a # gate nobody can get green is a gate somebody deletes. Those three are tracked # as their own piece of work, with the numbers, rather than being enabled and # then permanently ignored. # # What is here catches defects rather than taste, and passes today — so a new # finding is a new mistake, which is the only way a linter earns its place in CI. +# +# Schema v2. The v1 config went in with golangci-lint pinned to v1.62, whose +# binary is built with Go 1.23 and refuses to load a module targeting 1.25 — +# "the Go language version used to build golangci-lint is lower than the +# targeted Go version". It failed on its first CI run for that reason and +# nothing to do with the code. +version: "2" run: timeout: 5m tests: true linters: - disable-all: true + default: none enable: - govet # staticcheck found the real one: an `if` with an empty body where backend @@ -25,19 +32,17 @@ linters: - ineffassign - unused -linters-settings: - staticcheck: - checks: ["all"] + # staticcheck's checks are left at their default. In v2 it absorbed stylecheck, + # so "all" would pull in naming and comment-formatting opinions — the kind of + # finding that turns a gate into noise and then into something disabled. -issues: - exclude-use-default: false - max-issues-per-linter: 0 - max-same-issues: 0 - exclude-rules: - # SA1019 on the TUI is bubbles renaming its viewport scrolling methods. Real, - # but it is a rename to follow deliberately in one change, not a reason to - # block every unrelated commit in the meantime. - - path: internal/tui/ - linters: - - staticcheck - text: "SA1019" + exclusions: + generated: lax + rules: + # SA1019 on the TUI is bubbles renaming its viewport scrolling methods. + # Real, but a rename to follow deliberately in one change, not a reason to + # block every unrelated commit in the meantime. + - path: internal/tui/ + linters: + - staticcheck + text: "SA1019" diff --git a/go.mod b/go.mod index 0d859ef..991608a 100644 --- a/go.mod +++ b/go.mod @@ -13,18 +13,19 @@ require ( github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/ssh v0.0.0-20250128164007-98fd5ae11894 github.com/charmbracelet/wish v1.4.7 - github.com/go-acme/lego/v4 v4.22.2 - github.com/go-git/go-git/v5 v5.16.2 + github.com/go-acme/lego/v4 v4.25.2 + github.com/go-git/go-git/v5 v5.19.1 github.com/gofrs/flock v0.12.1 github.com/jackc/pgx/v5 v5.10.0 github.com/modelcontextprotocol/go-sdk v1.6.1 github.com/spf13/cobra v1.8.1 - github.com/yuin/goldmark v1.7.13 + github.com/yuin/goldmark v1.7.17 go.opentelemetry.io/otel v1.44.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 go.opentelemetry.io/otel/sdk v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.54.0 + golang.org/x/sys v0.47.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -62,21 +63,18 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/termios v0.1.0 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect - github.com/cloudflare/cloudflare-go v0.112.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/creack/pty v1.1.21 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/goccy/go-json v0.10.4 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/google/jsonschema-go v0.4.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect @@ -86,15 +84,16 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.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.16 // indirect - github.com/miekg/dns v1.1.62 // indirect + github.com/miekg/dns v1.1.67 // 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 - github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/segmentio/asm v1.1.3 // indirect github.com/segmentio/encoding v0.5.4 // indirect @@ -108,18 +107,16 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.81.1 // indirect + google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 458c65f..03ee8bd 100644 --- a/go.sum +++ b/go.sum @@ -87,15 +87,13 @@ github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFb github.com/charmbracelet/x/termios v0.1.0/go.mod h1:H/EVv/KRnrYjz+fCYa9bsKdqF3S8ouDK0AZEbG7r+/U= github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g= -github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0= +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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= 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= @@ -108,16 +106,16 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0= -github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo= +github.com/go-acme/lego/v4 v4.25.2 h1:+D1Q+VnZrD+WJdlkgUEGHFFTcDrwGlE7q24IFtMmHDI= +github.com/go-acme/lego/v4 v4.25.2/go.mod h1:OORYyVNZPaNdIdVYCGSBNRNZDIjhQbPuFxwGDgWj/yM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= +github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= -github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= +github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= @@ -127,8 +125,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= -github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -137,11 +133,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -162,6 +155,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -177,8 +172,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= +github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU= github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -189,8 +184,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= +github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -228,8 +223,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= -github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA= +github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= @@ -251,19 +246,19 @@ go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXd go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -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/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -272,28 +267,25 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/acme/acme.go b/internal/acme/acme.go index 97d389b..8bf00d7 100644 --- a/internal/acme/acme.go +++ b/internal/acme/acme.go @@ -26,19 +26,19 @@ import ( ) const ( - ChallengeHTTP01 = "http-01" - ChallengeDNS01 = "dns-01" + ChallengeHTTP01 = "http-01" + ChallengeDNS01 = "dns-01" ProviderCloudflare = "cloudflare" ) // Config is Let's Encrypt (ACME) settings for the OSS server. type Config struct { - Enabled bool `yaml:"enabled"` - Email string `yaml:"email"` - Domains []string `yaml:"domains"` - CacheDir string `yaml:"cache_dir,omitempty"` - HTTPAddr string `yaml:"http_addr,omitempty"` // default :80 for HTTP-01 - Challenge string `yaml:"challenge,omitempty"` // http-01 (default) | dns-01 + Enabled bool `yaml:"enabled"` + Email string `yaml:"email"` + Domains []string `yaml:"domains"` + CacheDir string `yaml:"cache_dir,omitempty"` + HTTPAddr string `yaml:"http_addr,omitempty"` // default :80 for HTTP-01 + Challenge string `yaml:"challenge,omitempty"` // http-01 (default) | dns-01 DNS DNSConfig `yaml:"dns,omitempty"` } @@ -104,8 +104,8 @@ type Manager struct { Cfg Config cache string - mu sync.RWMutex - dnsCert *tls.Certificate + mu sync.RWMutex + dnsCert *tls.Certificate stopRenew chan struct{} } diff --git a/internal/audit/logger.go b/internal/audit/logger.go index 73ccb90..5d7bf0c 100644 --- a/internal/audit/logger.go +++ b/internal/audit/logger.go @@ -73,12 +73,12 @@ type Filter struct { // Stats summarizes a window. type Stats struct { - Entries int `json:"entries"` - Actors int `json:"actors"` - Failed int `json:"failed"` - ByAction map[string]int `json:"by_action"` - Since time.Time `json:"since"` - Until time.Time `json:"until"` + Entries int `json:"entries"` + Actors int `json:"actors"` + Failed int `json:"failed"` + ByAction map[string]int `json:"by_action"` + Since time.Time `json:"since"` + Until time.Time `json:"until"` } // Logger appends JSONL under /YYYY-MM-DD.jsonl. diff --git a/internal/bench/bench.go b/internal/bench/bench.go index b118523..81cb20e 100644 --- a/internal/bench/bench.go +++ b/internal/bench/bench.go @@ -311,7 +311,7 @@ func queries(question string) []string { seen := map[string]bool{} var words []string for _, w := range strings.FieldsFunc(strings.ToLower(question), func(r rune) bool { - return !('a' <= r && r <= 'z') && !('0' <= r && r <= '9') + return ('a' > r || r > 'z') && ('0' > r || r > '9') }) { if len(w) < 3 || stopWords[w] || seen[w] { continue diff --git a/internal/cli/tui_login.go b/internal/cli/tui_login.go index f3d53e1..d1ab9e6 100644 --- a/internal/cli/tui_login.go +++ b/internal/cli/tui_login.go @@ -178,7 +178,7 @@ func tuiLoginBlock(mode string) string { b.WriteString("# Skip once: CONTEXTVERSE_TUI_SKIP=1 ssh …\n") b.WriteString("if [ -n \"${SSH_CONNECTION:-}\" ] && [ -t 0 ] && [ -z \"${CONTEXTVERSE_TUI_SKIP:-}\" ]; then\n") b.WriteString(" export CONTEXTVERSE_MODEL_A=1\n") - b.WriteString(fmt.Sprintf(" \"%s\" %s || true\n", bin, args)) + fmt.Fprintf(&b, " \"%s\" %s || true\n", bin, args) b.WriteString("fi\n") b.WriteString(tuiLoginEnd + "\n") return b.String() diff --git a/internal/entrypoint/entrypoint.go b/internal/entrypoint/entrypoint.go index ef30596..1fc8758 100644 --- a/internal/entrypoint/entrypoint.go +++ b/internal/entrypoint/entrypoint.go @@ -82,22 +82,22 @@ func claudeMD(spaceRoot, project string) string { b.WriteString("\n") b.WriteString("\n\n") b.WriteString("# Context\n\n") - b.WriteString(fmt.Sprintf("Source of truth: `%s`\n\n", spaceRoot)) + fmt.Fprintf(&b, "Source of truth: `%s`\n\n", spaceRoot) b.WriteString("Read these files in order:\n") - b.WriteString(fmt.Sprintf("1. `%s/context-entry.md` — routing\n", spaceRoot)) - b.WriteString(fmt.Sprintf("2. `%s/identity/me.md` — who you're talking to\n", spaceRoot)) - b.WriteString(fmt.Sprintf("3. `%s/team/principles.md` — how we work\n", spaceRoot)) - b.WriteString(fmt.Sprintf("4. `%s/space-index.md` — what exists\n", spaceRoot)) - b.WriteString(fmt.Sprintf("5. `%s/decisions.md` — key decisions\n", spaceRoot)) + fmt.Fprintf(&b, "1. `%s/context-entry.md` — routing\n", spaceRoot) + fmt.Fprintf(&b, "2. `%s/identity/me.md` — who you're talking to\n", spaceRoot) + fmt.Fprintf(&b, "3. `%s/team/principles.md` — how we work\n", spaceRoot) + fmt.Fprintf(&b, "4. `%s/space-index.md` — what exists\n", spaceRoot) + fmt.Fprintf(&b, "5. `%s/decisions.md` — key decisions\n", spaceRoot) if project != "" { - b.WriteString(fmt.Sprintf("6. `%s/projects/%s/project.md` — this project\n", spaceRoot, project)) + fmt.Fprintf(&b, "6. `%s/projects/%s/project.md` — this project\n", spaceRoot, project) } else { - b.WriteString(fmt.Sprintf("6. `%s/projects//` — project context for the current task\n", spaceRoot)) + fmt.Fprintf(&b, "6. `%s/projects//` — project context for the current task\n", spaceRoot) } b.WriteString("\n## Rules\n") b.WriteString("- Follow importance weights from principles.md\n") b.WriteString("- Check space-index.md before searching\n") - b.WriteString(fmt.Sprintf("- Never modify `%s/identity/` without explicit permission\n", spaceRoot)) + fmt.Fprintf(&b, "- Never modify `%s/identity/` without explicit permission\n", spaceRoot) return b.String() } @@ -109,15 +109,15 @@ func cursorRule(spaceRoot, project string) string { b.WriteString("---\n\n") b.WriteString("\n\n") b.WriteString("# ContextVerse\n\n") - b.WriteString(fmt.Sprintf("Source of truth: `%s`\n\n", spaceRoot)) + fmt.Fprintf(&b, "Source of truth: `%s`\n\n", spaceRoot) b.WriteString("Before answering project questions, read:\n") - b.WriteString(fmt.Sprintf("1. `%s/context-entry.md`\n", spaceRoot)) - b.WriteString(fmt.Sprintf("2. `%s/identity/me.md`\n", spaceRoot)) - b.WriteString(fmt.Sprintf("3. `%s/team/principles.md`\n", spaceRoot)) - b.WriteString(fmt.Sprintf("4. `%s/space-index.md`\n", spaceRoot)) - b.WriteString(fmt.Sprintf("5. `%s/decisions.md`\n", spaceRoot)) + fmt.Fprintf(&b, "1. `%s/context-entry.md`\n", spaceRoot) + fmt.Fprintf(&b, "2. `%s/identity/me.md`\n", spaceRoot) + fmt.Fprintf(&b, "3. `%s/team/principles.md`\n", spaceRoot) + fmt.Fprintf(&b, "4. `%s/space-index.md`\n", spaceRoot) + fmt.Fprintf(&b, "5. `%s/decisions.md`\n", spaceRoot) if project != "" { - b.WriteString(fmt.Sprintf("6. `%s/projects/%s/project.md`\n", spaceRoot, project)) + fmt.Fprintf(&b, "6. `%s/projects/%s/project.md`\n", spaceRoot, project) } b.WriteString("\nDo not modify identity files without explicit user permission.\n") return b.String() diff --git a/internal/export/chatgpt.go b/internal/export/chatgpt.go index 58249ab..55f0881 100644 --- a/internal/export/chatgpt.go +++ b/internal/export/chatgpt.go @@ -84,10 +84,10 @@ func ChatGPT(spaceRoot, outDir, project string) (*Result, error) { func chatgptREADME(spaceRoot, project string, missing []string) string { var b strings.Builder b.WriteString("# ContextVerse → ChatGPT export\n\n") - b.WriteString(fmt.Sprintf("Generated: %s\n\n", time.Now().UTC().Format(time.RFC3339))) - b.WriteString(fmt.Sprintf("Space root: `%s`\n", spaceRoot)) + fmt.Fprintf(&b, "Generated: %s\n\n", time.Now().UTC().Format(time.RFC3339)) + fmt.Fprintf(&b, "Space root: `%s`\n", spaceRoot) if project != "" { - b.WriteString(fmt.Sprintf("Project: `%s`\n", project)) + fmt.Fprintf(&b, "Project: `%s`\n", project) } b.WriteString("\n## Upload\n\n") b.WriteString("Upload all `.md` files in this folder (except this README, unless you want it) as **Knowledge** / project files in ChatGPT.\n\n") @@ -95,7 +95,7 @@ func chatgptREADME(spaceRoot, project string, missing []string) string { if len(missing) > 0 { b.WriteString("### Missing from space\n\n") for _, m := range missing { - b.WriteString(fmt.Sprintf("- `%s`\n", m)) + fmt.Fprintf(&b, "- `%s`\n", m) } b.WriteString("\n") } diff --git a/internal/graph/render.go b/internal/graph/render.go index 9eca748..6bb6c29 100644 --- a/internal/graph/render.go +++ b/internal/graph/render.go @@ -302,7 +302,7 @@ func Mermaid(g *Graph, limit int) string { label = p } label = strings.ReplaceAll(label, `"`, "'") - b.WriteString(fmt.Sprintf(" %s[\"%s\"]\n", keep[p], label)) + fmt.Fprintf(&b, " %s[\"%s\"]\n", keep[p], label) } for _, e := range g.Edges { if e.Broken || e.Code { @@ -313,7 +313,7 @@ func Mermaid(g *Graph, limit int) string { if !okF || !okT || from == to { continue } - b.WriteString(fmt.Sprintf(" %s --> %s\n", from, to)) + fmt.Fprintf(&b, " %s --> %s\n", from, to) } return b.String() } diff --git a/internal/hooks/secrets.go b/internal/hooks/secrets.go index c29ecf1..fa284d3 100644 --- a/internal/hooks/secrets.go +++ b/internal/hooks/secrets.go @@ -41,7 +41,7 @@ type Config struct { // SecretScanConfig is the block-secrets guardrail. type SecretScanConfig struct { - Enabled bool `yaml:"enabled"` + Enabled bool `yaml:"enabled"` OnViolation string `yaml:"on_violation"` // block (default) | warn } diff --git a/internal/plugins/inject.go b/internal/plugins/inject.go index 733f26b..5b9413a 100644 --- a/internal/plugins/inject.go +++ b/internal/plugins/inject.go @@ -27,8 +27,8 @@ func Inject(format, spaceRoot, cwd, project string) (string, error) { case "claude-hook", "claude": payload := map[string]any{ "hookSpecificOutput": map[string]any{ - "hookEventName": "SessionStart", - "additionalContext": body, + "hookEventName": "SessionStart", + "additionalContext": body, }, } raw, err := json.Marshal(payload) @@ -59,14 +59,14 @@ func entrySet(spaceRoot, project string) (string, error) { } var b strings.Builder b.WriteString("# ContextVerse session context\n\n") - b.WriteString(fmt.Sprintf("Space root: %s\n\n", spaceRoot)) + fmt.Fprintf(&b, "Space root: %s\n\n", spaceRoot) const maxTotal = 100_000 n := 0 for _, rel := range files { path := filepath.Join(spaceRoot, rel) raw, err := os.ReadFile(path) if err != nil { - b.WriteString(fmt.Sprintf("## %s\n\n_(missing)_\n\n", rel)) + fmt.Fprintf(&b, "## %s\n\n_(missing)_\n\n", rel) continue } chunk := string(raw) @@ -77,7 +77,7 @@ func entrySet(spaceRoot, project string) (string, error) { } chunk = chunk[:remain] + "\n…(truncated)\n" } - b.WriteString(fmt.Sprintf("## %s\n\n", rel)) + fmt.Fprintf(&b, "## %s\n\n", rel) b.WriteString(chunk) if !strings.HasSuffix(chunk, "\n") { b.WriteByte('\n') diff --git a/internal/plugins/mechanisms.go b/internal/plugins/mechanisms.go index 3f97ae7..a7a838b 100644 --- a/internal/plugins/mechanisms.go +++ b/internal/plugins/mechanisms.go @@ -212,16 +212,16 @@ func ManualInstructions(in *Integration, vars Vars) string { if in == nil { return ManualInstructionsCatalog(nil, vars) } - b.WriteString(fmt.Sprintf("Manual setup for %s (%s):\n", in.Display, in.ID)) - b.WriteString(fmt.Sprintf(" mechanism: %s\n", in.Mechanism)) + fmt.Fprintf(&b, "Manual setup for %s (%s):\n", in.Display, in.ID) + fmt.Fprintf(&b, " mechanism: %s\n", in.Mechanism) if in.Mechanism == MechanismCommandHook { - b.WriteString(fmt.Sprintf(" target: %s\n", Expand(in.Target, vars))) - b.WriteString(fmt.Sprintf(" command: %s\n", in.Command)) + fmt.Fprintf(&b, " target: %s\n", Expand(in.Target, vars)) + fmt.Fprintf(&b, " command: %s\n", in.Command) } else if in.Target != "" { - b.WriteString(fmt.Sprintf(" write: %s\n", Expand(in.Target, vars))) + fmt.Fprintf(&b, " write: %s\n", Expand(in.Target, vars)) } if in.Notes != "" { - b.WriteString(fmt.Sprintf(" notes: %s\n", in.Notes)) + fmt.Fprintf(&b, " notes: %s\n", in.Notes) } if strings.TrimSpace(in.Manual) != "" { b.WriteString("\n") diff --git a/internal/plugins/plugins_test.go b/internal/plugins/plugins_test.go index 70d8f37..16a41bc 100644 --- a/internal/plugins/plugins_test.go +++ b/internal/plugins/plugins_test.go @@ -117,7 +117,6 @@ func TestMergeCommunityExtra(t *testing.T) { } } - func TestAskWhich(t *testing.T) { cat := []*Integration{ {ID: "a", Display: "A"}, diff --git a/internal/ratelimit/limiter.go b/internal/ratelimit/limiter.go index e92a00f..27bcf12 100644 --- a/internal/ratelimit/limiter.go +++ b/internal/ratelimit/limiter.go @@ -7,9 +7,9 @@ import ( // Config is server rate-limit knobs. type Config struct { - Enabled bool `yaml:"enabled"` - RequestsPerMinute int `yaml:"requests_per_minute"` // per-user (or IP); default 120 - AuthPerMinute int `yaml:"auth_per_minute"` // login endpoints; default 10 + Enabled bool `yaml:"enabled"` + RequestsPerMinute int `yaml:"requests_per_minute"` // per-user (or IP); default 120 + AuthPerMinute int `yaml:"auth_per_minute"` // login endpoints; default 10 } // Default returns enabled 120 rpm / 10 auth rpm. @@ -30,9 +30,9 @@ type bucket struct { // spend our memory. Idle buckets are dropped: a bucket refills to full capacity // long before the idle window elapses, so forgetting it changes no decision. const ( - idleAfter = 10 * time.Minute - sweepEvery = 30 * time.Second - sweepAbove = 1024 + idleAfter = 10 * time.Minute + sweepEvery = 30 * time.Second + sweepAbove = 1024 ) // Limiter is an in-process token bucket keyed by string. diff --git a/internal/selfupdate/check_test.go b/internal/selfupdate/check_test.go index 550e670..b1ab762 100644 --- a/internal/selfupdate/check_test.go +++ b/internal/selfupdate/check_test.go @@ -50,6 +50,10 @@ func TestTheFirstRunIsSilent(t *testing.T) { if notice := c.Notice(context.Background()); notice != "" { t.Errorf("the first run spoke: %q", notice) } + // Notice leaves a refresh running behind it. Let it finish before the test + // ends, or it writes its state into a directory the framework is removing — + // which Windows reports as "the directory is not empty" and Unix hides. + waitForOneAsk(t, c) } func TestASecondRunReportsANewerRelease(t *testing.T) { diff --git a/internal/server/ui_handlers.go b/internal/server/ui_handlers.go index c9f57aa..80d4d04 100644 --- a/internal/server/ui_handlers.go +++ b/internal/server/ui_handlers.go @@ -1056,7 +1056,7 @@ func (s *Server) handleUIWebhooksTest(w http.ResponseWriter, r *http.Request) { } id := r.PathValue("id") if err := s.Dispatch.Test(r.Context(), id); err != nil { - http.Error(w, err.Error(), 502) + http.Error(w, err.Error(), http.StatusBadGateway) return } s.auditEmit(r, "webhook.test", "", id, nil) diff --git a/internal/space/space.go b/internal/space/space.go index 6c094c3..2a79759 100644 --- a/internal/space/space.go +++ b/internal/space/space.go @@ -29,11 +29,11 @@ type IdentityFields struct { // CreateOptions controls space creation. type CreateOptions struct { - SpaceRoot string - TemplateName string // catalog name (default solo-default) - TemplatePath string // if set, copy from this directory instead of catalog/embed - Identity IdentityFields - Force bool + SpaceRoot string + TemplateName string // catalog name (default solo-default) + TemplatePath string // if set, copy from this directory instead of catalog/embed + Identity IdentityFields + Force bool RefreshTemplate bool // re-fetch remote template, ignore cache SkipIdentity bool // used by space seed — don't rewrite identity/me.md } diff --git a/internal/storage/s3_keys_test.go b/internal/storage/s3_keys_test.go index ebfdb40..bc8f86a 100644 --- a/internal/storage/s3_keys_test.go +++ b/internal/storage/s3_keys_test.go @@ -44,6 +44,13 @@ func TestDistinctPathsGetDistinctKeys(t *testing.T) { } } +// No backslash in this table on purpose. CleanPath treats one as a separator on +// every OS so that a path is judged identically wherever a server, a client or a +// shared backend sees it — and sanitizePath reaches that through +// filepath.ToSlash, which only rewrites on Windows. A backslash is therefore not +// a character a path can contain, and asserting that it round-trips was encoding +// a Unix-only assumption; escapeKeySegment still escapes it for the cases where +// one survives. func TestAKeyRoundTripsBackToItsPath(t *testing.T) { s := s3For("spaces/team") for _, p := range []string{ @@ -53,7 +60,6 @@ func TestAKeyRoundTripsBackToItsPath(t *testing.T) { "odd name with spaces.md", "unicode-Привет-文件.md", "percent%sign.md", - "back\\slash.md", "brace{}caret^.md", } { key := s.objectKey(p) diff --git a/internal/tui/app.go b/internal/tui/app.go index 56347f7..4908b17 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -842,7 +842,7 @@ func (m model) spaceDetail() string { var b strings.Builder b.WriteString(stylePaneTitle.Render("No context space here yet")) b.WriteString("\n\n") - b.WriteString(fmt.Sprintf("Looked in %s\n\n", m.spaceRoot)) + fmt.Fprintf(&b, "Looked in %s\n\n", m.spaceRoot) b.WriteString("Create one — the setup asks a few questions and explains each:\n\n") b.WriteString(" contextd init\n\n") b.WriteString(styleMuted.Render("Already have a space somewhere else?\nPoint at it with: contextd --dir tui")) @@ -852,23 +852,23 @@ func (m model) spaceDetail() string { } var b strings.Builder - b.WriteString(fmt.Sprintf("Space %s\n", m.spaceRoot)) - b.WriteString(fmt.Sprintf("Mode %s\n", m.snap.Mode)) + fmt.Fprintf(&b, "Space %s\n", m.spaceRoot) + fmt.Fprintf(&b, "Mode %s\n", m.snap.Mode) if m.snap.IdentityName != "" { - b.WriteString(fmt.Sprintf("Identity %s", m.snap.IdentityName)) + fmt.Fprintf(&b, "Identity %s", m.snap.IdentityName) if m.snap.IdentityRole != "" { - b.WriteString(fmt.Sprintf(" (%s)", m.snap.IdentityRole)) + fmt.Fprintf(&b, " (%s)", m.snap.IdentityRole) } b.WriteString("\n") } - b.WriteString(fmt.Sprintf("Projects %d\n", len(m.snap.Projects))) + fmt.Fprintf(&b, "Projects %d\n", len(m.snap.Projects)) detected := 0 for _, p := range m.snap.Plugins { if p.Detected { detected++ } } - b.WriteString(fmt.Sprintf("Plugins %d detected / %d known\n", detected, len(m.snap.Plugins))) + fmt.Fprintf(&b, "Plugins %d detected / %d known\n", detected, len(m.snap.Plugins)) b.WriteString("\n") b.WriteString(styleMuted.Render("cwd " + m.cwd)) b.WriteString("\n\n") @@ -879,7 +879,7 @@ func (m model) spaceDetail() string { l := m.snap.Layers[m.cursor] b.WriteString("\n") b.WriteString(stylePaneTitle.Render(l.Name)) - b.WriteString(fmt.Sprintf("\n%d files under %s/%s\n", l.Files, m.spaceRoot, l.Name)) + fmt.Fprintf(&b, "\n%d files under %s/%s\n", l.Files, m.spaceRoot, l.Name) } return b.String() } @@ -893,9 +893,9 @@ func (m model) pluginDetail() string { } p := m.snap.Plugins[m.cursor] var b strings.Builder - b.WriteString(fmt.Sprintf("ID %s\n", p.ID)) - b.WriteString(fmt.Sprintf("Display %s\n", p.Display)) - b.WriteString(fmt.Sprintf("Mechanism %s\n", p.Mechanism)) + fmt.Fprintf(&b, "ID %s\n", p.ID) + fmt.Fprintf(&b, "Display %s\n", p.Display) + fmt.Fprintf(&b, "Mechanism %s\n", p.Mechanism) if p.Detected { b.WriteString(styleOk.Render("Detected ✓ " + p.How)) b.WriteString("\n\nPress i to install / refresh hooks for detected clients.")