diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f470a6eb..915bb0512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.91.200.0] - 2026-10-25 + +**/cso doctor no longer calls a Go repository "stack: python".** A repository with a root `go.mod` and no Node, Bun, Rails or JVM build matched no supported stack, so preparation fell back to Python and doctor reported `stack: python` with `MISSING_METADATA requirements.txt`, which sent Go users looking for a Python file they never needed (#3263). Go runtime support itself is not planned for now (Garry, 2026-10-24), so /cso keeps running static assessment on Go repositories; the runtime catalog and the /cso release contract are unchanged. + +### Fixed +- **`lib/cso/preparation.ts`**: a root `go.mod` with no other supported stack is reported as `stack: go` with `UNSUPPORTED_STACK` ("Go (go.mod) is not a supported runtime stack yet; supported runtime stacks are Node, Bun, Python, and Rails."), the way a Maven or Gradle build is already reported as Java. Python helper metadata beside `go.mod` (a docs `requirements.txt`, say) no longer turns the repository into Python. A `go.mod` beside a Node, Bun or Rails lockfile leaves that stack's detection as it was. + +### Tests +- `test/cso-preparation.test.ts` checks that a Go module is `go` with exactly that prerequisite and never mentions `requirements.txt`, and that a hashed `requirements.txt` beside `go.mod` stays Go. Both fail on main. Controls: `go.mod` beside a Node lockfile stays a ready Node plan, a Python project without `go.mod` stays Python, and a repository with no stack metadata keeps the Python fallback. +- `test/helpers/cso-runtime-catalog.ts` leaves `go` out of its qualified-runtime fixture list, as it already did `java`. + +### Gate integrity +- No gate edit. No threshold, budget, timeout, trial count or case kind changed. + ## [1.91.199.0] - 2026-10-25 **When /sync-gbrain refuses because a gbrain autopilot is running, it now says which process it found and what to do.** The guard against syncing while `gbrain autopilot` runs is machine-wide on purpose: two syncs sharing one brain or working tree can race and delete files (#1734). But the refusal only said `(process:gbrain autopilot)`, so someone whose autopilot served a different brain could not tell what was blocking them (#3360, part 1). Garry decided on 2026-10-24 to keep the check machine-wide and improve the message. diff --git a/VERSION b/VERSION index dbc24fac0..122611d8e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.199.0 +1.91.200.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 198416a67..c968125c0 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.199.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.200.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/lib/cso/preparation.ts b/lib/cso/preparation.ts index 26a68bc73..35612e9ee 100644 --- a/lib/cso/preparation.ts +++ b/lib/cso/preparation.ts @@ -5,8 +5,8 @@ import { createHash } from 'node:crypto'; import { lstatSync, readFileSync, realpathSync } from 'node:fs'; import { isAbsolute, relative, resolve, sep } from 'node:path'; -/** 'java' (Maven or Gradle) is detected so it is reported as itself; no runtime prepares it. */ -export type CsoStack = 'node' | 'bun' | 'python' | 'rails' | 'java'; +/** 'java' (Maven or Gradle) and 'go' (go.mod) are detected so they are reported as themselves; no runtime prepares them. */ +export type CsoStack = 'node' | 'bun' | 'python' | 'rails' | 'java' | 'go'; export interface PreparationPrerequisite { code: string; message: string; @@ -1205,12 +1205,15 @@ export function inspectPreparation(snapshotPath: string, stack?: CsoStack): Prep detectedStacks.push('node'); if (read(root, 'Gemfile.lock', true) !== undefined || read(root, 'Gemfile', true) !== undefined) detectedStacks.push('rails'); - // A root Maven or Gradle build names the repository's language; Python - // metadata beside it is helper tooling, not a second application stack. + // A root Maven or Gradle build, or a root go.mod with no other supported + // stack, names the repository's language; Python metadata beside it is + // helper tooling, not a second application stack. const jvmBuild = ['pom.xml', 'build.gradle', 'build.gradle.kts'].find( (file) => read(root, file, true) !== undefined, ); if (jvmBuild) detectedStacks.push('java'); + else if (detectedStacks.length === 0 && read(root, 'go.mod', true) !== undefined) + detectedStacks.push('go'); else if ( read(root, 'uv.lock', true) !== undefined || read(root, 'requirements.txt', true) !== undefined || @@ -1231,10 +1234,16 @@ export function inspectPreparation(snapshotPath: string, stack?: CsoStack): Prep `Java (${jvmBuild ?? 'Maven or Gradle'}) is not a supported runtime stack yet; supported runtime stacks are Node, Bun, Python, and Rails.`, jvmBuild, ); + if (detected === 'go') + fail( + 'UNSUPPORTED_STACK', + 'Go (go.mod) is not a supported runtime stack yet; supported runtime stacks are Node, Bun, Python, and Rails.', + 'go.mod', + ); if (!['node', 'bun', 'python', 'rails'].includes(detected)) fail('UNSUPPORTED_STACK', 'Supported runtime stacks are Node, Bun, Python, and Rails.'); ({ node: inspectNode, bun: inspectBun, python: inspectPython, rails: inspectRails })[ - detected as Exclude + detected as Exclude ](root, plan); } catch (error) { plan.status = 'prerequisites'; diff --git a/package.json b/package.json index 6ec1304ac..41f3b6340 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.91.199", + "version": "1.91.200", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/cso-preparation.test.ts b/test/cso-preparation.test.ts index 6addab47f..174ebd8b3 100644 --- a/test/cso-preparation.test.ts +++ b/test/cso-preparation.test.ts @@ -170,6 +170,32 @@ describe('CSO inert Python preparation', () => { }); }); +describe('CSO preparation names a Go module as Go (#3263)', () => { + const goFiles = { + 'go.mod': 'module example.com/app\n\ngo 1.23\n\nrequire github.com/google/uuid v1.6.0\n', + 'go.sum': 'github.com/google/uuid v1.6.0 h1:abc=\n', + 'main.go': 'package main\n\nfunc main() {}\n', + }; + const GO_UNSUPPORTED = { code: 'UNSUPPORTED_STACK', message: 'Go (go.mod) is not a supported runtime stack yet; supported runtime stacks are Node, Bun, Python, and Rails.', path: 'go.mod' }; + test('a go.mod repository is Go, unsupported, and never asks for requirements.txt', () => { + const plan = inspectPreparation(fixture(goFiles)); + expect([plan.stack, plan.runtimeProfile, plan.status]).toEqual(['go', 'go', 'prerequisites']); + expect(plan.prerequisites).toEqual([GO_UNSUPPORTED]); + expect(JSON.stringify(plan)).not.toContain('requirements.txt'); + }); + test('Python helper metadata beside go.mod stays Go', () => { + const plan = inspectPreparation(fixture({ ...goFiles, 'requirements.txt': `mkdocs==1.6.1 --hash=sha256:${hash}\n` })); + expect(plan.stack).toBe('go'); + expect(plan.prerequisites).toEqual([GO_UNSUPPORTED]); + }); + test('controls: go.mod beside a Node lockfile stays Node; Python without go.mod stays Python', () => { + const node = inspectPreparation(fixture({ ...goFiles, ...nodeFiles() })); + expect([node.stack, node.status]).toEqual(['node', 'ready']); + expect(inspectPreparation(fixture({ 'requirements.txt': `Flask==3.1.0 --hash=sha256:${hash}\n` })).stack).toBe('python'); + expect(inspectPreparation(fixture({ 'go.sum': '', 'main.go': '' })).stack).toBe('python'); + }); +}); + describe('CSO inert Rails preparation', () => { test('does not silently choose Node in a multi-stack repository',()=>{const plan=inspectPreparation(fixture({...nodeFiles(),'Gemfile.lock':gemLock,'Gemfile':''}));expect(plan.status).toBe('prerequisites');expect(plan.prerequisites[0].code).toBe('MULTIPLE_STACKS');expect(inspectPreparation(fixture({...nodeFiles(),'Gemfile.lock':gemLock,'Gemfile':''}),'rails').stack).toBe('rails');}); test('fetches exact gems without evaluating Gemfiles or building native extensions', () => { diff --git a/test/helpers/cso-runtime-catalog.ts b/test/helpers/cso-runtime-catalog.ts index 1e55877d1..10c5a414d 100644 --- a/test/helpers/cso-runtime-catalog.ts +++ b/test/helpers/cso-runtime-catalog.ts @@ -1,6 +1,6 @@ import type { CsoStack } from '../../lib/cso/preparation'; -type RuntimeStack = Exclude; +type RuntimeStack = Exclude; import { canonical, sha256 } from '../../lib/cso/contracts'; import { ISOLATION_POLICY_HASH } from '../../lib/cso/docker'; import {