fix(cso): report a go.mod repository as Go (unsupported), not Python (#3263) (#3378)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-10-25 07:03:51 +00:00
1 parent 688f0729f5
commit f2ddc495dd
7 files changed
+58 -9

No files matched your search

+14
View File
@@ -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.
+1 -1
View File
@@ -1 +1 @@
1.91.199.0
1.91.200.0
+1 -1
View File
@@ -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,
+14 -5
View File
@@ -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<CsoStack, 'java'>
detected as Exclude<CsoStack, 'java' | 'go'>
](root, plan);
} catch (error) {
plan.status = 'prerequisites';
+1 -1
View File
@@ -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",
+26
View File
@@ -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', () => {
+1 -1
View File
@@ -1,6 +1,6 @@
import type { CsoStack } from '../../lib/cso/preparation';
type RuntimeStack = Exclude<CsoStack, 'java'>;
type RuntimeStack = Exclude<CsoStack, 'java' | 'go'>;
import { canonical, sha256 } from '../../lib/cso/contracts';
import { ISOLATION_POLICY_HASH } from '../../lib/cso/docker';
import {