Merge branch 'next' into fix/3174-quick-verification-status-query

This commit is contained in:
Tom Boucher
2026-08-08 10:47:06 -04:00
committed by GitHub
39 changed files with 2564 additions and 381 deletions

View File

@@ -0,0 +1,6 @@
{
"version": 1,
"paths": {
"next.md": "#3184 (epic #3180 Phase 2): Route 0 (resume_incomplete_phase) grows by one branch that reads the new `scope` field `roadmap analyze` emits. Before this, Route 0 treated a well-formed `{phases: []}` as a clean scan and fell through to routing as though the incomplete-phase invariant (#160) had been checked and passed — the silent disarm #3165 reports, where a milestone window truncated by a closed-milestone heading yields an empty phase list with exit 0 and no error. The added `elif [ \"$ROADMAP_SCOPE\" != \"complete\" ]` arm routes that case into the SAME warn-and-fall-through branch the existing `roadmap.analyze failed` arm already uses, so a non-answer is no longer indistinguishable from a genuinely empty scan. The growth is the new arm's three warning lines, its explanatory comment, the `ROADMAP_SCOPE` assignment, and one success-criteria bullet. Adding this to the workflow rather than only to the CLI is deliberate: a spec review of this phase found that emitting `scope` without a consumer left #3165's actual symptom reproducing, so the field would have been a diagnostic nobody reads."
}
}

View File

@@ -210,7 +210,17 @@ describe('#2658: end-to-end --trae install never emits the malformed path (accep
test('local install: no emitted .md/.js/.cjs file contains the malformed strings; the rules file is concrete', () => {
const { configDir, root } = runMinimalInstall({ runtime: 'trae', scope: 'local' });
try {
const files = walk(configDir).filter((f) => /\.(md|js|cjs)$/.test(f));
const files = walk(configDir)
.filter((f) => /\.(md|js|cjs)$/.test(f))
// gsd-core/CHANGELOG.md is excluded by exact relative path (not a blanket
// .md skip — the emitted agent/command/workflow markdown this gate exists
// to guard stays fully scanned). CHANGELOG.md legitimately QUOTES the
// malformed `.claude/.trae/rules` / `.trae/.trae/rules` strings while
// documenting the #2658 fix itself (#3006) — that historical-value
// citation is not a regression of the installer's actual output. Verified
// empirically: excluding only this one file drops the hit count to zero
// across all 620 other emitted files.
.filter((f) => f.split(path.sep).join('/').indexOf('gsd-core/CHANGELOG.md') === -1);
assert.ok(files.length > 0, 'expected at least one emitted .md/.js/.cjs file');
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');

View File

@@ -415,6 +415,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -485,6 +485,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-ns-context/SKILL.md",
"skills/gsd-ns-context/skills/docs-update/SKILL.md",
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md",

View File

@@ -483,5 +483,6 @@
"scripts/gen-capability-registry.cjs",
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs"
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs"
]

View File

@@ -413,6 +413,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -385,6 +385,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-ns-context/SKILL.md",
"skills/gsd-ns-context/skills/docs-update/SKILL.md",
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md",

View File

@@ -485,6 +485,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -492,5 +492,6 @@
"scripts/gen-capability-registry.cjs",
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs"
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs"
]

View File

@@ -384,6 +384,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -391,6 +391,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -414,6 +414,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd/DESCRIPTION.md",
"skills/gsd/gsd-ns-context/SKILL.md",
"skills/gsd/gsd-ns-context/skills/docs-update/SKILL.md",

View File

@@ -488,6 +488,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -414,6 +414,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -450,6 +450,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -488,6 +488,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-add-tests/SKILL.md",
"skills/gsd-ai-integration-phase/SKILL.md",
"skills/gsd-audit-fix/SKILL.md",

View File

@@ -381,5 +381,6 @@
"scripts/gen-capability-registry.cjs",
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs"
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs"
]

View File

@@ -414,6 +414,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-ns-context/SKILL.md",
"skills/gsd-ns-context/skills/docs-update/SKILL.md",
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md",

View File

@@ -382,6 +382,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-ns-context/SKILL.md",
"skills/gsd-ns-context/skills/docs-update/SKILL.md",
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md",

View File

@@ -384,5 +384,6 @@
"scripts/gen-capability-registry.cjs",
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs"
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs"
]

View File

@@ -453,6 +453,7 @@
"scripts/gen-loop-host-contract.cjs",
"scripts/lib/allowlist-ratchet.cjs",
"scripts/lib/cli-exit.cjs",
"scripts/lib/drift-scan.cjs",
"skills/gsd-ns-context/SKILL.md",
"skills/gsd-ns-context/skills/docs-update/SKILL.md",
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md",

View File

@@ -49,6 +49,8 @@ const {
configureKiloPermissions,
selectRuntimesFromArgs,
normalizeNodePath,
GSD_CHANGESET_FILES,
GSD_SCRIPTS_LIB_FILES,
} = require('../bin/install.js');
const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs');
@@ -11107,3 +11109,48 @@ describe('#3026: installer --help documents every accepted runtime flag', () =>
`--help must document every accepted runtime flag; missing: ${missing.join(', ')}`);
});
});
// ─── #3184: scripts/lib/ and scripts/changeset/ install/uninstall parity ────
//
// install() copies both source directories WHOLESALE (every file present —
// see bin/install.js's "and any future lib helpers" comment), but uninstall()
// removes files via an EXPLICIT hardcoded enumeration (GSD_SCRIPTS_LIB_FILES /
// GSD_CHANGESET_FILES). Nothing keeps the two in sync: a file added to either
// source directory ships to every install and then orphans on uninstall (it
// survives removal, keeps the target dir non-empty, and blocks its rmdir).
// This guard fails the moment the real directory outgrows the enumeration.
/**
* Pure comparison: every file actually present in a source dir must be named
* in the corresponding uninstall enumeration. Decoupled from fs so it can be
* exercised directly with doctored input (see the probe in the PR report).
*/
function findUnenumeratedFiles(actualFiles, enumeratedFiles) {
const enumerated = new Set(enumeratedFiles);
return actualFiles.filter(f => !enumerated.has(f));
}
describe('#3184: scripts/lib/ and scripts/changeset/ install/uninstall parity', () => {
const REPO_ROOT = path.join(__dirname, '..');
function realFilesIn(relativeDir) {
const dir = path.join(REPO_ROOT, relativeDir);
return fs.readdirSync(dir).filter(entry => fs.statSync(path.join(dir, entry)).isFile());
}
test('every file in scripts/lib/ is enumerated in GSD_SCRIPTS_LIB_FILES (bin/install.js)', () => {
const unenumerated = findUnenumeratedFiles(realFilesIn(path.join('scripts', 'lib')), GSD_SCRIPTS_LIB_FILES);
assert.deepEqual(unenumerated, [],
`scripts/lib/${unenumerated.join(', scripts/lib/')} exist on disk but are missing from ` +
`GSD_SCRIPTS_LIB_FILES in bin/install.js — add ${unenumerated.length === 1 ? 'it' : 'them'} to that ` +
'array so uninstall() removes it (otherwise it ships to every install and orphans on uninstall).');
});
test('every file in scripts/changeset/ is enumerated in GSD_CHANGESET_FILES (bin/install.js)', () => {
const unenumerated = findUnenumeratedFiles(realFilesIn(path.join('scripts', 'changeset')), GSD_CHANGESET_FILES);
assert.deepEqual(unenumerated, [],
`scripts/changeset/${unenumerated.join(', scripts/changeset/')} exist on disk but are missing from ` +
`GSD_CHANGESET_FILES in bin/install.js — add ${unenumerated.length === 1 ? 'it' : 'them'} to that ` +
'array so uninstall() removes it (otherwise it ships to every install and orphans on uninstall).');
});
});

File diff suppressed because it is too large Load Diff