fix(release): version-bump write regenerates the version-stamped agents digest

agents-digest/gstack-AGENTS.md embeds VERSION in its first line and is
byte-freshness-gated (test/agents-digest.test.ts + Skill Docs Freshness CI),
but nothing in the release path regenerated it — every version-bumping ship
of this repo would land red. write now spawns the repo's own generator when
present (agentsDigest true/false/null in the output JSON), and ship's
evidence gate allow-lists the digest alongside VERSION/package.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-29 05:18:34 +00:00
co-authored by Claude Fable 5
parent 87e64f69ba
commit e40faaea75
4 changed files with 102 additions and 12 deletions
+35 -3
View File
@@ -31,9 +31,13 @@
// file. No bump. Validates the VERSION pattern first.
//
// Contract: classify NEVER writes. write/repair mutate VERSION + the manifest
// + npm lockfiles (package-lock.json / npm-shrinkwrap.json, when present)
// only. No git mutation, no network. Mirrors gstack-next-version's
// reader/writer split so /ship composes them.
// + npm lockfiles (package-lock.json / npm-shrinkwrap.json, when present)
// plus, in the gstack repo only, the committed agents digest (whose first
// line embeds VERSION and is byte-freshness-gated by test/agents-digest.test.ts
// and the Skill Docs Freshness CI check, so the write that changes VERSION
// must regenerate it or every release commit goes red). No git mutation, no
// network. Mirrors gstack-next-version's reader/writer split so /ship
// composes them.
//
// Manifest resolution (all three subcommands accept --package-json-path):
// --package-json-path <p> → .gstack/package-json-path → ./package.json
@@ -335,6 +339,31 @@ function cmdClassify(args: string[], cwd: string): void {
// the only exit-2 cases.)
}
/**
* gstack-repo-only side effect: agents-digest/gstack-AGENTS.md embeds VERSION
* in its first line and is freshness-gated (committed bytes must equal the
* generated bytes), so the bump that changes VERSION regenerates it in the
* same write. Any repo without the generator + committed digest skips this.
* Regen failure warns rather than failing the bump — the deterministic
* freshness test stays red until it's rerun, so nothing rots silently.
*/
function regenAgentsDigest(versionPath: string): boolean | null {
const root = dirname(versionPath);
const gen = join(root, "scripts", "gen-agents-digest.ts");
if (!existsSync(gen) || !existsSync(join(root, "agents-digest", "gstack-AGENTS.md"))) return null;
try {
execFileSync("bun", [gen], { cwd: root, stdio: "pipe", timeout: 30_000 });
return true;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
process.stderr.write(
`write: VERSION updated but the agents digest regen failed (${msg.slice(0, 200)}). ` +
"Run 'bun scripts/gen-agents-digest.ts' manually — test/agents-digest.test.ts stays red until then.\n",
);
return false;
}
}
function cmdWrite(args: string[], cwd: string): void {
const version = argVal(args, "--version");
if (!version) fail("write requires --version <X.Y.Z.W>", 2);
@@ -398,6 +427,7 @@ function cmdWrite(args: string[], cwd: string): void {
const pkgPath = resolvePkgPath(cwd, argVal(args, "--package-json-path"));
const hasPkg = existsSync(pkgPath);
writeFileSync(versionPath, version + "\n");
const digestRegen = regenAgentsDigest(versionPath);
let lockSynced: string[] = [];
// Decision 11: the manifest (and its lockfiles) carry the npm-valid
// 3-digit translation — npm rejects a fourth component, so mirroring the
@@ -432,6 +462,8 @@ function cmdWrite(args: string[], cwd: string): void {
packageJsonPath: hasPkg ? relative(cwd, pkgPath) : null,
packageJsonVersion: hasPkg ? manifestV : null,
packageLock: lockSynced.length > 0,
// null = not the gstack repo (no digest to regen); true/false = regen outcome
agentsDigest: digestRegen,
}) + "\n",
);
}