From d3571d47816532f5e6cf24a08b3fc1e4c197df65 Mon Sep 17 00:00:00 2001 From: edward lugovtsov Date: Fri, 31 Jul 2026 12:41:19 +0300 Subject: [PATCH 1/2] Clear what CI found the first time it ran MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things, all surfaced by gates added last sprint and none of them visible before. govulncheck reported 22 reachable vulnerabilities across 8 modules — reachable meaning it found a call path from this code, not merely a version in go.mod. Ten were in x/crypto, which the server uses for password hashing and SSH. Upgrading x/crypto, x/text, go-git, go-billy, lego, goldmark, circl and grpc takes it to zero. Two remain in modules that are required but never called, which govulncheck reports separately and does not fail on. The gate found these on its first run. They were there the whole time. Two Windows failures, both in tests I wrote rather than in the code: TestTheFirstRunIsSilent left the background refresh running and returned, so the goroutine wrote its state file into a directory the test framework was already removing. Windows reports that as "the directory is not empty"; Unix hides it. The same mistake was fixed in the neighbouring test last sprint and missed here. TestAKeyRoundTripsBackToItsPath asserted that a backslash survives a round trip through an S3 key. It does not, and should not: CleanPath treats a backslash as a separator on every OS so 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. The case encoded a Unix-only assumption. Removed, with the reasoning, rather than made conditional. Co-Authored-By: Claude Opus 5 --- go.mod | 39 +++++++------- go.sum | 84 ++++++++++++++----------------- internal/selfupdate/check_test.go | 4 ++ internal/storage/s3_keys_test.go | 8 ++- 4 files changed, 67 insertions(+), 68 deletions(-) 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/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/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) From 044c6c20293f22b3493b52226373d036b4e7943f Mon Sep 17 00:00:00 2001 From: edward lugovtsov Date: Fri, 31 Jul 2026 12:55:19 +0300 Subject: [PATCH 2/2] Make the linter and the Go toolchain match what the code targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CI failures, neither about the code. golangci-lint was pinned to v1.62, whose binary is built with Go 1.23. It refuses to load a module targeting 1.25 and says so plainly — "the Go language version used to build golangci-lint is lower than the targeted Go version" — so the gate added last sprint could never have passed, and the local run that convinced me otherwise was a newer binary than the one CI installs. v2 with the v2 config schema. The migration is not a rename. In v2 staticcheck absorbs stylecheck, so checks: ["all"] would pull in naming and comment-formatting opinions; the setting is dropped and the defaults used, because a gate that argues about taste is a gate somebody turns off. The five findings the new default reports are fixed rather than excluded: De Morgan on a character-class test, four WriteString(Sprintf) that are Fprintf, and a bare 502 that is http.StatusBadGateway. The control plane pinned Go with go-version-file: go.mod, which setup-go honours literally — go.mod says 1.25.0, and the standard-library CVEs its vulnerability check reports are fixed in 1.25.10 through 1.25.12. The gate was telling the truth about a toolchain nobody meant to pin. It now takes the newest patch of that line, the way contextd already did. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 8 +++++-- .golangci.yml | 39 +++++++++++++++++-------------- internal/acme/acme.go | 20 ++++++++-------- internal/audit/logger.go | 12 +++++----- internal/bench/bench.go | 2 +- internal/cli/tui_login.go | 2 +- internal/entrypoint/entrypoint.go | 32 ++++++++++++------------- internal/export/chatgpt.go | 8 +++---- internal/graph/render.go | 4 ++-- internal/hooks/secrets.go | 2 +- internal/plugins/inject.go | 10 ++++---- internal/plugins/mechanisms.go | 12 +++++----- internal/plugins/plugins_test.go | 1 - internal/ratelimit/limiter.go | 12 +++++----- internal/server/ui_handlers.go | 2 +- internal/space/space.go | 10 ++++---- internal/tui/app.go | 22 ++++++++--------- 17 files changed, 103 insertions(+), 95 deletions(-) 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/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/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/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.")