Merge branch 'next' into codex/gsd-onboard

This commit is contained in:
Tom Boucher
2026-07-06 22:43:32 -04:00
committed by GitHub
149 changed files with 6229 additions and 983 deletions

View File

@@ -32,8 +32,8 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const RUNTIMES = Object.keys(registry.runtimes);
test('declarative adapter: kind === "declarative" + runtime echoed, for all 15 runtimes', () => {
assert.ok(RUNTIMES.length >= 15, `expected ≥15 runtimes in registry, got ${RUNTIMES.length}`);
test('declarative adapter: kind === "declarative" + runtime echoed, for every registry runtime', () => {
assert.ok(RUNTIMES.length > 0, `expected at least one runtime in registry, got ${RUNTIMES.length}`);
for (const r of RUNTIMES) {
const adapter = createDeclarativeAdapter({ runtime: r });
assert.strictEqual(adapter.kind, 'declarative', `${r}: kind must be 'declarative'`);

View File

@@ -25,7 +25,7 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const RUNTIMES = Object.keys(registry.runtimes);
test('imperative adapter: kind === "imperative" + runtime echoed + registry present, for all 15 runtimes', () => {
test('imperative adapter: kind === "imperative" + runtime echoed + registry present, for every registry runtime', () => {
for (const r of RUNTIMES) {
const adapter = createImperativeAdapter({ runtime: r });
assert.strictEqual(adapter.kind, 'imperative', `${r}: kind must be 'imperative'`);

View File

@@ -1,6 +1,6 @@
{
"gsd-advisor-researcher.md": 4693,
"gsd-ai-researcher.md": 5909,
"gsd-advisor-researcher.md": 4727,
"gsd-ai-researcher.md": 5943,
"gsd-assumptions-analyzer.md": 4646,
"gsd-code-fixer.md": 36640,
"gsd-code-reviewer.md": 16870,
@@ -11,26 +11,26 @@
"gsd-doc-synthesizer.md": 9782,
"gsd-doc-verifier.md": 12403,
"gsd-doc-writer.md": 38924,
"gsd-domain-researcher.md": 6998,
"gsd-domain-researcher.md": 7032,
"gsd-eval-auditor.md": 12496,
"gsd-eval-planner.md": 7008,
"gsd-executor.md": 43578,
"gsd-executor.md": 43607,
"gsd-framework-selector.md": 6778,
"gsd-integration-checker.md": 15238,
"gsd-intel-updater.md": 18166,
"gsd-mempalace-curator.md": 4325,
"gsd-nyquist-auditor.md": 7345,
"gsd-pattern-mapper.md": 12487,
"gsd-phase-researcher.md": 40832,
"gsd-phase-researcher.md": 40866,
"gsd-plan-checker.md": 44780,
"gsd-planner.md": 48157,
"gsd-project-researcher.md": 22208,
"gsd-planner.md": 48191,
"gsd-project-researcher.md": 22242,
"gsd-research-synthesizer.md": 13847,
"gsd-roadmapper.md": 22273,
"gsd-security-auditor.md": 8981,
"gsd-ui-auditor.md": 17249,
"gsd-ui-checker.md": 11178,
"gsd-ui-researcher.md": 19466,
"gsd-ui-researcher.md": 19500,
"gsd-user-profiler.md": 8516,
"gsd-verifier.md": 49147
}

View File

@@ -3186,11 +3186,9 @@ const {
// file is an installed artifact captured by golden-install-parity (#1943).
const VALID_EXTENSION_EVENTS = capValidatorModule.VALID_EXTENSION_EVENTS;
const RUNTIME_IDS = [
'claude', 'codex', 'antigravity', 'cursor', 'opencode',
'kilo', 'copilot', 'augment', 'trae', 'qwen', 'hermes',
'codebuddy', 'cline', 'kimi', 'windsurf',
];
// Runtime ids are derived from the built registry (the single source of truth)
// so this file stays fluid when a runtime descriptor is added or removed.
const RUNTIME_IDS = Object.keys(buildRegistry(loadAndValidate(new Set()).capMap).runtimes);
// Helper: build a minimal valid runtime capability object for fixture-based tests
function makeRuntimeCap(overrides) {
@@ -3232,18 +3230,31 @@ function makeRuntimeCap(overrides) {
};
}
// ── 24a. All 15 runtime ids appear in the runtimes index ─────────────────────
// ── 24a. Every role:runtime capability appears in the runtimes index ─────────
// Count-agnostic: the built registry's runtime set must exactly equal the set
// of capability folders whose descriptor declares role:"runtime". Adding a
// runtime descriptor extends coverage with zero edits here.
describe('ADR-1016 phase 5a: all 15 runtimes in registry index', () => {
describe('ADR-1016 phase 5a: every role:runtime capability is in the registry index', () => {
let registry;
test('loadAndValidate + buildRegistry produces runtimes index with 15 entries', () => {
test('loadAndValidate + buildRegistry indexes exactly the role:runtime capabilities on disk', () => {
const { capMap, errors } = loadAndValidate(new Set());
const hardErrors = errors.filter((e) => !e.includes('pending-migration'));
assert.deepEqual(hardErrors, [], 'Expected no hard errors: ' + JSON.stringify(hardErrors));
registry = buildRegistry(capMap);
// Derive the expected runtime id set from the loaded capability map — not a
// hand-pinned count. This is the contract: every role:runtime descriptor on
// disk becomes a runtimes-index entry, no more, no less.
const expectedRuntimeIds = [...capMap.entries()]
.filter(([, cap]) => cap && cap.role === 'runtime')
.map(([id]) => id)
.sort();
const runtimeKeys = Object.keys(registry.runtimes).sort();
assert.strictEqual(runtimeKeys.length, 15, 'Expected 15 runtime entries, got: ' + runtimeKeys.join(', '));
assert.ok(expectedRuntimeIds.length > 0, 'expected at least one role:runtime capability on disk');
assert.deepEqual(runtimeKeys, expectedRuntimeIds,
'runtimes index must exactly equal the role:runtime capabilities on disk. got: ' + runtimeKeys.join(', '));
for (const id of RUNTIME_IDS) {
assert.ok(
Object.prototype.hasOwnProperty.call(registry.runtimes, id),
@@ -4072,17 +4083,17 @@ describe('ADR-857 phase 5e: validateArtifactKindEntry — ConverterName enum (FA
assert.deepEqual(converterErrors, [], 'converter: null must always be accepted, got: ' + JSON.stringify(converterErrors));
});
// Parity: all 15 runtime descriptors must have converters in the valid set (or null)
test('all 15 real runtime descriptors have converters in VALID_CONVERTER_NAMES or null', () => {
// Parity: every role:runtime descriptor's converters must be in the valid set (or null).
// Count-agnostic: iterates the real capability map rather than a hand-pinned runtime list.
test('every real runtime descriptor has converters in VALID_CONVERTER_NAMES or null', () => {
const { capMap, errors } = loadAndValidate(new Set());
const hardErrors = errors.filter((e) => !e.includes('pending-migration'));
assert.deepEqual(hardErrors, [], 'Expected no hard errors from real capabilities, got: ' + JSON.stringify(hardErrors));
const runtimeIds = [
'claude', 'codex', 'antigravity', 'cursor', 'opencode',
'kilo', 'copilot', 'augment', 'trae', 'qwen', 'hermes',
'codebuddy', 'cline', 'kimi', 'windsurf',
];
const runtimeIds = [...capMap.entries()]
.filter(([, cap]) => cap && cap.role === 'runtime')
.map(([id]) => id);
assert.ok(runtimeIds.length > 0, 'expected at least one role:runtime capability');
for (const id of runtimeIds) {
const cap = capMap.get(id);
assert.ok(cap, 'capMap must contain "' + id + '"');
@@ -6406,45 +6417,43 @@ const {
resolveRuntimeConfigIntent,
ALLOWED_CONFIG_RUNTIMES,
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-config-adapter-registry.cjs'));
const enh1055Registry = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'capability-registry.cjs'));
// ---------------------------------------------------------------------------
// Frozen expected table (pre-change behavior — the contract being pinned)
// Expected table — DERIVED from the capability registry descriptors. The
// contract being pinned is the PROJECTION (descriptor fields → intent), not a
// frozen per-runtime value snapshot. Adding a runtime descriptor extends
// coverage with zero edits here.
// ---------------------------------------------------------------------------
const EXPECTED = [
{ runtime: 'claude', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'antigravity', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'augment', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'qwen', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'hermes', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'codebuddy', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'opencode', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: 'opencode' },
{ runtime: 'kilo', installSurface: 'settings-json', writesSharedSettings: false, finishPermissionWriter: 'kilo' },
{ runtime: 'codex', installSurface: 'codex-toml', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'copilot', installSurface: 'copilot-instructions', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'cline', installSurface: 'cline-rules', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'cursor', installSurface: 'cursor-hooks-json', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'windsurf', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'trae', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'kimi', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
];
const EXPECTED = Object.keys(enh1055Registry.runtimes).map((id) => {
const r = enh1055Registry.runtimes[id].runtime;
const pw = r.permissionWriter;
return {
runtime: id,
installSurface: r.installSurface,
writesSharedSettings: r.writesSharedSettings,
finishPermissionWriter: pw == null ? null : pw,
};
});
// ---------------------------------------------------------------------------
// Test 1: Golden master — all 15 runtimes resolve to expected values
// Test 1: Projection contract — every registry runtime resolves to its
// descriptor-derived intent (count-agnostic).
// ---------------------------------------------------------------------------
describe('enh-1055 descriptor-drive: resolveRuntimeConfigIntent golden master', () => {
for (const row of EXPECTED) {
test(`${row.runtime} resolves to expected intent`, () => {
const intent = resolveRuntimeConfigIntent(row.runtime);
assert.deepStrictEqual(intent, {
runtime: row.runtime,
installSurface: row.installSurface,
writesSharedSettings: row.writesSharedSettings,
describe('enh-1055 descriptor-drive: resolveRuntimeConfigIntent projection contract', () => {
test('every registry runtime resolves to its descriptor-derived intent', () => {
assert.ok(EXPECTED.length > 0, 'registry must contain at least one runtime');
for (const row of EXPECTED) {
assert.deepStrictEqual(resolveRuntimeConfigIntent(row.runtime), {
runtime: row.runtime,
installSurface: row.installSurface,
writesSharedSettings: row.writesSharedSettings,
finishPermissionWriter: row.finishPermissionWriter,
});
});
}
}, `resolveRuntimeConfigIntent('${row.runtime}') must match the descriptor projection`);
}
});
});
// ---------------------------------------------------------------------------
@@ -6470,21 +6479,18 @@ describe('enh-1055 descriptor-drive: unknown runtime throws TypeError', () => {
});
// ---------------------------------------------------------------------------
// Test 4: ALLOWED_CONFIG_RUNTIMES contains all 15 expected runtimes
// Test 4: ALLOWED_CONFIG_RUNTIMES equals the registry runtimes that declare an
// installSurface (count-agnostic; derived from the same source as production).
// ---------------------------------------------------------------------------
describe('enh-1055 descriptor-drive: ALLOWED_CONFIG_RUNTIMES completeness', () => {
const EXPECTED_15 = new Set([
'claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
'opencode', 'kilo', 'codex', 'copilot', 'cline', 'cursor', 'windsurf', 'trae', 'kimi',
]);
test('contains exactly the 15 expected runtimes', () => {
assert.deepStrictEqual(new Set(ALLOWED_CONFIG_RUNTIMES), EXPECTED_15);
});
test('has exactly 15 entries', () => {
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 15);
test('equals the registry runtimes that declare an installSurface', () => {
const descriptorAllowed = new Set(
Object.entries(enh1055Registry.runtimes)
.filter(([, cap]) => cap && cap.runtime && typeof cap.runtime.installSurface === 'string')
.map(([id]) => id),
);
assert.deepStrictEqual(new Set(ALLOWED_CONFIG_RUNTIMES), descriptorAllowed);
});
});

View File

@@ -22,6 +22,7 @@ const {
isCapabilityActive,
_isSafePropKey,
_loadInstalledSkillsManifest,
_loadFlatCommandsGsdManifest,
_resolveManifest,
} = require('../gsd-core/bin/lib/capability-state.cjs');
@@ -823,6 +824,223 @@ describe('cmdCapabilityState — end-to-end via gsd-tools CLI', () => {
// FAIL before the fix and PASS after, regardless of whether commands/gsd
// happens to exist in the current checkout.
describe('regressions: flat commands/gsd-<stem>.md layout (#1858)', () => {
// Flat command layout (Claude local project install shape): skills live at
// <repo>/commands/gsd-<stem>.md — the gsd- prefix is baked into the filename
// and there is NO commands/gsd/ subdir. _resolveManifest must detect this
// layout, strip the gsd- prefix, and produce the same stems the nested
// loader (commands/gsd/<stem>.md) would, or every skill-bearing capability
// is silently reported surfaced:false / enabled:false / active:false.
function makeFlatCommandMd(stem, requires) {
const req = requires ? `requires: [${requires.join(', ')}]` : 'requires: [phase]';
return [
'---',
`name: gsd:${stem}`,
`description: ${stem} skill`,
'argument-hint: "[phase number]"',
'allowed-tools:',
' - Read',
req,
'---',
'Execute end-to-end.',
].join('\n') + '\n';
}
// ── Unit tests for _loadFlatCommandsGsdManifest ─────────────────────────────
test('_loadFlatCommandsGsdManifest: returns empty map when parent dir absent', () => {
const missing = path.join(os.tmpdir(), 'cap-flat-missing-' + Date.now());
const manifest = _loadFlatCommandsGsdManifest(missing);
assert.ok(manifest instanceof Map, 'should return a Map');
assert.strictEqual(manifest.size, 0, 'should be empty when parent dir absent');
});
test('_loadFlatCommandsGsdManifest: scans gsd-<stem>.md and strips the gsd- prefix', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-flat-scan-'));
try {
fs.writeFileSync(path.join(tmpDir, 'gsd-validate-phase.md'), makeFlatCommandMd('validate-phase'), 'utf8');
fs.writeFileSync(path.join(tmpDir, 'gsd-secure-phase.md'), makeFlatCommandMd('secure-phase'), 'utf8');
// Non-gsd file must be ignored
fs.writeFileSync(path.join(tmpDir, 'random-doc.md'), '# not a skill\n', 'utf8');
// Non-markdown gsd file must be ignored
fs.writeFileSync(path.join(tmpDir, 'gsd-notskill.txt'), 'nope\n', 'utf8');
const manifest = _loadFlatCommandsGsdManifest(tmpDir);
assert.ok(manifest.has('validate-phase'), 'flat gsd-validate-phase.md -> stem validate-phase');
assert.ok(manifest.has('secure-phase'), 'flat gsd-secure-phase.md -> stem secure-phase');
assert.ok(!manifest.has('gsd-validate-phase'), 'must NOT keep the gsd- prefix on the stem');
assert.ok(!manifest.has('random-doc'), 'non-gsd file must be ignored');
assert.ok(!manifest.has('notskill'), 'non-.md gsd file must be ignored');
} finally {
cleanup(tmpDir);
}
});
test('_loadFlatCommandsGsdManifest: parses requires via shared parseRequires (no drift)', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-flat-req-'));
try {
fs.writeFileSync(
path.join(tmpDir, 'gsd-my-skill.md'),
makeFlatCommandMd('my-skill', ['dep-a', 'dep-b']),
'utf8',
);
const manifest = _loadFlatCommandsGsdManifest(tmpDir);
assert.deepStrictEqual(manifest.get('my-skill'), ['dep-a', 'dep-b']);
} finally {
cleanup(tmpDir);
}
});
test('_loadFlatCommandsGsdManifest: companion _calls_agents_<stem> key present (parity with nested loader)', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-flat-agents-'));
try {
fs.writeFileSync(path.join(tmpDir, 'gsd-validate-phase.md'), makeFlatCommandMd('validate-phase'), 'utf8');
const manifest = _loadFlatCommandsGsdManifest(tmpDir);
assert.ok(manifest.has('_calls_agents_validate-phase'),
'flat loader must emit the companion _calls_agents_ key (same Map shape as loadSkillsManifest)');
} finally {
cleanup(tmpDir);
}
});
// Low-1 (review): boundary — empty stem (gsd-.md) skipped, single-char stem kept.
test('_loadFlatCommandsGsdManifest: skips gsd-.md (empty stem) and keeps single-char stem (slice boundary)', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-flat-edge-'));
try {
fs.writeFileSync(path.join(tmpDir, 'gsd-.md'), makeFlatCommandMd(''), 'utf8');
fs.writeFileSync(path.join(tmpDir, 'gsd-x.md'), makeFlatCommandMd('x'), 'utf8');
const manifest = _loadFlatCommandsGsdManifest(tmpDir);
assert.ok(!manifest.has(''), 'gsd-.md must NOT register an empty-string stem');
assert.ok(!manifest.has('_calls_agents_'), 'no companion key for an empty stem');
assert.ok(manifest.has('x'), 'gsd-x.md -> single-char stem "x" (slice(4,-3) boundary)');
} finally {
cleanup(tmpDir);
}
});
// Low-2 (review): unreadable file degrades both keys to [] (parity with nested
// loader's catch). POSIX-only AND must not run as root — root bypasses POSIX
// read permission bits, so chmod 0o000 would NOT make the file unreadable and
// the test would assert [] against the real parsed deps (false failure).
// Skip on win32 (DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT) and when getuid()==0.
const _skipUnreadable = process.platform === 'win32' || (typeof process.getuid === 'function' && process.getuid() === 0);
test('_loadFlatCommandsGsdManifest: unreadable file degrades to empty deps + agents (parity)', { skip: _skipUnreadable }, () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-flat-unread-'));
let madeUnreadable = false;
try {
const skillPath = path.join(tmpDir, 'gsd-secure.md');
fs.writeFileSync(skillPath, '---\nname: gsd:secure\nrequires: [phase]\n---\nbody\n', { mode: 0o644 });
fs.chmodSync(skillPath, 0o000);
madeUnreadable = true;
const manifest = _loadFlatCommandsGsdManifest(tmpDir);
assert.deepStrictEqual(manifest.get('secure'), [], 'unreadable file -> empty requires (parity with nested catch)');
assert.deepStrictEqual(manifest.get('_calls_agents_secure'), [], 'unreadable file -> empty agents (parity with nested catch)');
} finally {
// Restore writability so cleanup() can rm the tmp tree.
if (madeUnreadable) {
try { fs.chmodSync(path.join(tmpDir, 'gsd-secure.md'), 0o644); } catch { /* best effort */ }
}
cleanup(tmpDir);
}
});
// ── _resolveManifest picks the flat branch when nested is absent ────────────
test('_resolveManifest: detects flat commands/gsd-<stem>.md layout when nested commands/gsd/ is absent (#1858)', () => {
const tmpRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rm-flat-repo-'));
try {
// Flat source layout: <repo>/commands/gsd-<stem>.md
// NO commands/gsd/ subdir, NO skills/ dir.
const commandsDir = path.join(tmpRepo, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
fs.writeFileSync(path.join(commandsDir, 'gsd-validate-phase.md'), makeFlatCommandMd('validate-phase'), 'utf8');
fs.writeFileSync(path.join(commandsDir, 'gsd-secure-phase.md'), makeFlatCommandMd('secure-phase'), 'utf8');
// commandsGsdDir = <repo>/commands/gsd (nested — does NOT exist).
// dirname(commandsGsdDir) = <repo>/commands (where the flat files live).
const commandsGsdDir = path.join(commandsDir, 'gsd');
// configDir = a separate empty tmp dir (no skills/ → installed fallback empty).
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rm-flat-cfg-'));
try {
const manifest = _resolveManifest(commandsGsdDir, configDir);
assert.ok(manifest.has('validate-phase'),
'flat layout must populate validate-phase stem (was empty pre-fix → all skill caps unsurfaced)');
assert.ok(manifest.has('secure-phase'), 'flat layout must populate secure-phase stem');
assert.ok(!manifest.has('gsd-validate-phase'), 'stem must have gsd- prefix stripped');
} finally {
cleanup(configDir);
}
} finally {
cleanup(tmpRepo);
}
});
test('_resolveManifest: flat branch does NOT shadow a populated installed skills dir when no flat files exist', () => {
// Precedence: nested > flat-source > installed. If the flat parent dir has
// NO gsd-*.md files, the flat loader returns an empty Map and _resolveManifest
// must fall through to the installed-skills branch (not return empty).
const tmpRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rm-precedence-'));
try {
const commandsDir = path.join(tmpRepo, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
// No gsd-*.md files in commands/ — flat loader yields empty.
// Installed skills present under configDir:
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rm-precedence-cfg-'));
try {
const secureDir = path.join(configDir, 'skills', 'gsd-secure-phase');
fs.mkdirSync(secureDir, { recursive: true });
fs.writeFileSync(path.join(secureDir, 'SKILL.md'),
'---\nname: gsd:secure-phase\nrequires: [phase]\n---\nbody\n', 'utf8');
const commandsGsdDir = path.join(commandsDir, 'gsd'); // nested absent
const manifest = _resolveManifest(commandsGsdDir, configDir);
assert.ok(manifest.has('secure-phase'),
'when flat dir is empty, installed-skills fallback must still work (precedence flat > installed only when flat non-empty)');
} finally {
cleanup(configDir);
}
} finally {
cleanup(tmpRepo);
}
});
// ── Parity: flat loader produces the same stems as the nested loader ────────
// (DEFECT.GENERATIVE-FIX — guards against silent divergence between the two
// parallel manifest-builders.)
test('flat loader and nested loader produce identical stems for the same command set (parity)', () => {
const realCommandsGsdDir = path.resolve(__dirname, '..', 'commands', 'gsd');
if (!fs.existsSync(realCommandsGsdDir)) return; // skip outside a repo checkout
// Build a flat mirror of the real nested commands/gsd/<stem>.md as
// commands/gsd-<stem>.md in a temp dir, then compare stem sets.
const tmpFlat = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-parity-flat-'));
try {
const nested = require('../gsd-core/bin/lib/install-profiles.cjs').loadSkillsManifest(realCommandsGsdDir);
for (const [stem] of nested) {
if (stem.startsWith('_calls_agents_')) continue;
const src = path.join(realCommandsGsdDir, stem + '.md');
if (!fs.existsSync(src)) continue;
fs.writeFileSync(path.join(tmpFlat, 'gsd-' + stem + '.md'), fs.readFileSync(src, 'utf8'), 'utf8');
}
const flat = _loadFlatCommandsGsdManifest(tmpFlat);
const nestedStems = [...nested.keys()].filter((k) => !k.startsWith('_calls_agents_')).sort();
const flatStems = [...flat.keys()].filter((k) => !k.startsWith('_calls_agents_')).sort();
assert.deepStrictEqual(flatStems, nestedStems,
'flat loader stem set must match nested loader stem set for the real command tree');
// Nit-2 (review): also compare _calls_agents_<stem> VALUES, not just the
// stem set — proves the shared parseCallsAgents output is identical.
for (const stem of flatStems) {
assert.deepStrictEqual(flat.get(`_calls_agents_${stem}`), nested.get(`_calls_agents_${stem}`),
`agent refs for stem "${stem}" must match between flat and nested loaders`);
assert.deepStrictEqual(flat.get(stem), nested.get(stem),
`requires for stem "${stem}" must match between flat and nested loaders`);
}
} finally {
cleanup(tmpFlat);
}
});
});
describe('regressions: installed-runtime capability surface (#1160)', () => {
// Minimal valid SKILL.md content (frontmatter only — matches what install emits)
function makeSkillMd(stem) {

View File

@@ -493,7 +493,7 @@ describe('resolveLoopHooks plan:post — pure function against real registry', (
assert.strictEqual(result.activeHooks[0].capId, 'gap-analysis');
});
test('[happy] real registry byLoopPoint plan:post has 1 step (mempalace), 1 contribution (external-job planner fragment), and 1 gate (gap-analysis)', () => {
test('[happy] real registry byLoopPoint plan:post has 1 step (mempalace), 2 contributions (external-job planner + claude-orchestration ultraplan ownership), and 1 gate (gap-analysis)', () => {
const entry = realRegistry.byLoopPoint['plan:post'];
assert.ok(entry, 'plan:post must exist in byLoopPoint');
assert.ok(Array.isArray(entry.steps), 'steps must be an array');
@@ -501,8 +501,12 @@ describe('resolveLoopHooks plan:post — pure function against real registry', (
assert.ok(Array.isArray(entry.gates), 'gates must be an array');
assert.strictEqual(entry.steps.length, 1, 'plan:post must have 1 step (mempalace capture)');
assert.strictEqual(entry.steps[0].capId, 'mempalace', 'plan:post step must be from mempalace');
assert.strictEqual(entry.contributions.length, 1, 'plan:post must have 1 contribution (external-job planner runtime-budget fragment)');
assert.strictEqual(entry.contributions[0].capId, 'external-job', 'plan:post contribution must be from external-job');
// #1143: claude-orchestration registers a plan:post contribution declaring
// ultraplan plan-offload ownership under its runtime gate (default-off).
assert.strictEqual(entry.contributions.length, 2, 'plan:post must have 2 contributions (external-job planner + claude-orchestration ultraplan ownership)');
const capIds = entry.contributions.map(c => c.capId).sort();
assert.deepStrictEqual(capIds, ['claude-orchestration', 'external-job'],
`plan:post contributions must be external-job + claude-orchestration; got ${capIds.join(',')}`);
assert.strictEqual(entry.gates.length, 1, 'plan:post must have exactly one gate');
assert.strictEqual(entry.gates[0].capId, 'gap-analysis');
});

View File

@@ -0,0 +1,169 @@
'use strict';
/**
* claude-orchestration-command-router.test.cjs — end-to-end tests for the
* `gsd-tools claude-orchestration` command surface (#1143).
*
* Exercises the full dispatch path: gsd-tools → dispatchCapabilityCommand →
* routeClaudeOrchestrationCommand → the pure detect/emitter functions.
*
* NOTE: runGsdTools() returns { success, output, exitCode, error } — it does
* NOT throw on a non-zero exit (helpers.cjs). Tests assert `.success` and parse
* `.output`, and check `.error` on the failure path.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
// ─── Fixtures ─────────────────────────────────────────────────────────────────
const WAVES_MANIFEST = {
waves: [
{
id: 'w1',
plans: [
{ id: 'p1', brief: 'Implement the foo module', files_modified: ['src/foo.cts'] },
{ id: 'p2', brief: 'Wire the bar seam', files_modified: ['src/bar.cts'] },
],
},
],
};
function writeManifest(tmpDir) {
const manifestPath = path.join(tmpDir, 'waves.json');
fs.writeFileSync(manifestPath, JSON.stringify(WAVES_MANIFEST), 'utf8');
return manifestPath;
}
/** Run the command and assert it succeeded, returning the parsed JSON output. */
function runAndParse(args, cwd) {
const res = runGsdTools(args, cwd);
assert.strictEqual(res.success, true, 'command should succeed; stderr: ' + (res.error || ''));
assert.ok(res.output.length > 0, 'command should emit output');
return JSON.parse(res.output);
}
// ─── emit-workflow ────────────────────────────────────────────────────────────
describe('claude-orchestration emit-workflow (CLI)', () => {
test('emits a Workflow script with parallel barriers, gsd-executor + worktree, and resumeFromRunId', () => {
const tmp = createTempProject('claw-emit-');
try {
const manifestPath = writeManifest(tmp);
const parsed = runAndParse([
'claude-orchestration', 'emit-workflow',
'--waves', manifestPath,
'--run-id', 'run-cli-1143',
'--phase-dir', '.planning/phases/01-foo',
], tmp);
assert.ok(typeof parsed.script === 'string' && parsed.script.length > 0);
assert.ok(parsed.script.includes('parallel('), 'parallel() barrier emitted');
assert.ok(parsed.script.includes('gsd-executor'), 'gsd-executor agentType');
assert.ok(parsed.script.includes('worktree'), 'worktree isolation');
assert.ok(parsed.script.includes('resumeFromRunId'), 'resumeFromRunId wired');
assert.ok(parsed.script.includes('run-cli-1143'), 'carries the run id');
assert.strictEqual(parsed.summary.resumeRunId, 'run-cli-1143');
assert.strictEqual(parsed.summary.waves, 1);
assert.strictEqual(parsed.summary.plans, 2);
} finally {
cleanup(tmp);
}
});
test('budget flag threads a shared token pool into the script', () => {
const tmp = createTempProject('claw-budget-');
try {
const manifestPath = writeManifest(tmp);
const parsed = runAndParse([
'claude-orchestration', 'emit-workflow',
'--waves', manifestPath,
'--run-id', 'r',
'--budget', '750000',
], tmp);
assert.ok(parsed.script.includes('budget('), 'budget() pool emitted');
assert.ok(parsed.script.includes('750000'));
} finally {
cleanup(tmp);
}
});
test('missing --waves -> non-zero exit with a diagnostic', () => {
const tmp = createTempProject('claw-noargs-');
try {
const res = runGsdTools([
'claude-orchestration', 'emit-workflow', '--run-id', 'r',
], tmp);
assert.strictEqual(res.success, false, 'missing --waves must fail');
assert.ok(res.exitCode !== 0, 'non-zero exit');
assert.match(res.error || '', /--waves/);
} finally {
cleanup(tmp);
}
});
});
// ─── detect-backend ───────────────────────────────────────────────────────────
describe('claude-orchestration detect-backend (CLI)', () => {
test('default config (capability off) -> inline, even on Claude', () => {
const tmp = createTempProject('claw-detect-');
try {
const parsed = runAndParse([
'claude-orchestration', 'detect-backend',
'--runtime', 'claude',
'--agent-sdk-version', '1.0.0',
], tmp);
assert.strictEqual(parsed.backend, 'inline');
assert.strictEqual(parsed.available, false);
assert.match(parsed.reason, /disabled/);
} finally {
cleanup(tmp);
}
});
test('enabled + claude + capable + new-enough SDK -> workflow', () => {
const tmp = createTempProject('claw-detect-on-');
try {
fs.mkdirSync(path.join(tmp, '.planning'), { recursive: true });
fs.writeFileSync(
path.join(tmp, '.planning', 'config.json'),
JSON.stringify({ claude_orchestration: { enabled: true, execution_backend: 'auto' } }),
'utf8',
);
const parsed = runAndParse([
'claude-orchestration', 'detect-backend',
'--runtime', 'claude',
'--agent-sdk-version', '1.2.0',
], tmp);
assert.strictEqual(parsed.backend, 'workflow');
assert.strictEqual(parsed.available, true);
} finally {
cleanup(tmp);
}
});
test('non-Claude runtime -> inline (criterion 6)', () => {
const tmp = createTempProject('claw-detect-nonclaude-');
try {
fs.mkdirSync(path.join(tmp, '.planning'), { recursive: true });
fs.writeFileSync(
path.join(tmp, '.planning', 'config.json'),
JSON.stringify({ claude_orchestration: { enabled: true } }),
'utf8',
);
const parsed = runAndParse([
'claude-orchestration', 'detect-backend',
'--runtime', 'codex',
'--agent-sdk-version', '1.0.0',
], tmp);
assert.strictEqual(parsed.backend, 'inline');
assert.match(parsed.reason, /claude/i);
} finally {
cleanup(tmp);
}
});
});

View File

@@ -0,0 +1,615 @@
'use strict';
/**
* claude-orchestration.test.cjs — Behavioral tests for the Claude orchestration
* capability (#1143): Workflow-tool backend detection, Workflow-script emission,
* capability-declaration validation, registry integration, and inline-fallback parity.
*
* The capability is default-off + BETA + claude-only. On any runtime lacking the
* Workflow tool it must be a byte-identical no-op. These tests encode that contract.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const fc = require('fast-check');
const {
detectWorkflowBackend,
emitWorkflowScript,
WORKFLOW_TOOL_FLOOR_VERSION,
BACKEND_VALUES,
compareSemver,
} = require('../gsd-core/bin/lib/claude-orchestration.cjs');
const {
validateCapability,
validateAgainstContract,
loadAndValidate,
buildRegistry,
serializeRegistry,
normalizeLineEndings,
stripGeneratedComment,
} = require('../scripts/gen-capability-registry.cjs');
const ROOT = path.resolve(__dirname, '..');
const CAP_PATH = path.join(ROOT, 'capabilities', 'claude-orchestration', 'capability.json');
const REGISTRY_PATH = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'capability-registry.cjs');
// ─── Fixtures ─────────────────────────────────────────────────────────────────
/** A host-integration descriptor whose dispatch axis signals Workflow-tool capability. */
const CAPABLE_HOST = {
dispatch: { namedDispatch: true, nested: true, background: true, backgroundDispatch: false },
};
/** Read the real capability declaration (data file — not a source grep). */
function loadCap() {
return JSON.parse(fs.readFileSync(CAP_PATH, 'utf8'));
}
/** A minimal single-plan wave manifest. */
function singleWaveManifest() {
return {
phaseDir: '.planning/phases/01-foo',
runId: 'run-abc-1143',
waves: [
{
id: 'w1',
plans: [
{ id: 'p1', brief: 'Implement the foo module', files_modified: ['src/foo.cts'] },
],
},
],
};
}
/** Two plans in one wave that DO NOT overlap (parallel-safe in a single stage). */
function nonOverlappingManifest() {
return {
phaseDir: '.planning/phases/01-foo',
runId: 'run-abc-1143',
waves: [
{
id: 'w1',
plans: [
{ id: 'p1', brief: 'Plan A', files_modified: ['src/a.cts'] },
{ id: 'p2', brief: 'Plan B', files_modified: ['src/b.cts'] },
],
},
],
};
}
/** Two plans in one wave that DO overlap on files_modified (must split into stages). */
function overlappingManifest() {
return {
phaseDir: '.planning/phases/01-foo',
runId: 'run-abc-1143',
waves: [
{
id: 'w1',
plans: [
{ id: 'p1', brief: 'Plan A', files_modified: ['src/shared.cts', 'src/a.cts'] },
{ id: 'p2', brief: 'Plan B', files_modified: ['src/shared.cts', 'src/b.cts'] },
],
},
],
};
}
// ─── 1. detectWorkflowBackend ─────────────────────────────────────────────────
describe('detectWorkflowBackend', () => {
test('capability disabled (default-off) -> inline, even on Claude with the tool', () => {
const r = detectWorkflowBackend({
runtimeId: 'claude',
hostIntegration: CAPABLE_HOST,
agentSdkVersion: '1.0.0',
config: { 'claude_orchestration.enabled': false },
});
assert.strictEqual(r.available, false);
assert.strictEqual(r.backend, 'inline');
assert.match(r.reason, /disabled/);
});
test('non-Claude runtime -> inline (criterion 6: no change to non-Claude loop)', () => {
for (const runtimeId of ['codex', 'cursor', 'opencode', 'copilot', ' Windsurf'.trim()]) {
const r = detectWorkflowBackend({
runtimeId,
hostIntegration: CAPABLE_HOST,
agentSdkVersion: '1.0.0',
config: { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'workflow' },
});
assert.strictEqual(r.backend, 'inline', runtimeId + ' should be inline');
assert.strictEqual(r.available, false, runtimeId + ' should be unavailable');
assert.match(r.reason, /claude/i, runtimeId + ' reason should mention claude');
}
});
test('Claude + auto + capable host + new-enough SDK -> workflow', () => {
const r = detectWorkflowBackend({
runtimeId: 'claude',
hostIntegration: CAPABLE_HOST,
agentSdkVersion: '1.2.0',
config: { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'auto' },
});
assert.strictEqual(r.backend, 'workflow');
assert.strictEqual(r.available, true);
});
test('Claude + execution_backend:"workflow" forces workflow when tool is capable', () => {
const r = detectWorkflowBackend({
runtimeId: 'claude',
hostIntegration: CAPABLE_HOST,
agentSdkVersion: '1.0.0',
config: { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'workflow' },
});
assert.strictEqual(r.backend, 'workflow');
assert.strictEqual(r.available, true);
});
test('Claude + execution_backend:"inline" -> inline even when tool is capable', () => {
const r = detectWorkflowBackend({
runtimeId: 'claude',
hostIntegration: CAPABLE_HOST,
agentSdkVersion: '1.0.0',
config: { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'inline' },
});
assert.strictEqual(r.backend, 'inline');
assert.match(r.reason, /inline/);
});
test('Claude + auto + host lacking nested dispatch -> inline (fail-closed)', () => {
const r = detectWorkflowBackend({
runtimeId: 'claude',
hostIntegration: { dispatch: { nested: false, background: true } },
agentSdkVersion: '1.0.0',
config: { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'auto' },
});
assert.strictEqual(r.backend, 'inline');
assert.strictEqual(r.available, false);
});
test('Claude + unknown agentSdkVersion -> inline fail-closed (criterion 3 fallback)', () => {
const r = detectWorkflowBackend({
runtimeId: 'claude',
hostIntegration: CAPABLE_HOST,
agentSdkVersion: undefined,
config: { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'auto' },
});
assert.strictEqual(r.backend, 'inline');
assert.strictEqual(r.available, false);
assert.match(r.reason, /version|sdk|unknown/i);
});
test('agent SDK version boundary: floor-1 -> inline, floor -> workflow, floor+patch -> workflow', () => {
const floor = WORKFLOW_TOOL_FLOOR_VERSION;
const [maj, min, pat] = floor.split('.').map((n) => parseInt(n, 10));
// Robust "below" derivation with full borrow chain (works for .0.0 floors too).
let below;
if (pat > 0) below = `${maj}.${min}.${pat - 1}`;
else if (min > 0) below = `${maj}.${min - 1}.999`;
else if (maj > 0) below = `${maj - 1}.999.999`;
else { assert.ok(false, 'cannot derive below for 0.0.0 floor'); return; }
const above = `${maj}.${min}.${pat + 1}`;
// Sanity: confirm below really is below per the comparator under test.
assert.ok(compareSemver(below, floor) < 0, below + ' must compare below ' + floor);
const cfg = { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'auto' };
const rBelow = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: below, config: cfg });
assert.strictEqual(rBelow.backend, 'inline', below + ' (floor-1) must be inline');
const rAt = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: floor, config: cfg });
assert.strictEqual(rAt.backend, 'workflow', floor + ' (exact floor) must be workflow');
const rAbove = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: above, config: cfg });
assert.strictEqual(rAbove.backend, 'workflow', above + ' (floor+patch) must be workflow');
});
test('config-level min_agent_sdk_version override raises/lowers the floor', () => {
const cfg = {
'claude_orchestration.enabled': true,
'claude_orchestration.execution_backend': 'auto',
'claude_orchestration.min_agent_sdk_version': '2.0.0',
};
const r1 = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: '1.9.9', config: cfg });
assert.strictEqual(r1.backend, 'inline', 'below raised floor -> inline');
const r2 = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: '2.0.0', config: cfg });
assert.strictEqual(r2.backend, 'workflow', 'at raised floor -> workflow');
});
test('execution_backend:"workflow" + SDK below floor -> inline (M-1: floor applies in both modes)', () => {
const cfg = {
'claude_orchestration.enabled': true,
'claude_orchestration.execution_backend': 'workflow',
};
const r = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: '0.3.0', config: cfg });
assert.strictEqual(r.backend, 'inline', 'workflow mode must still honor the SDK floor (fail-closed)');
assert.strictEqual(r.available, false);
assert.match(r.reason, /floor|version/);
});
test('pre-release of the floor (0.3.149-rc.1) -> inline (pre-release < GA per SemVer)', () => {
const cfg = { 'claude_orchestration.enabled': true, 'claude_orchestration.execution_backend': 'auto' };
// Explicitly assert the precedence rule: a pre-release tag is below the GA release.
assert.ok(compareSemver('0.3.149-rc.1', '0.3.149') < 0, 'pre-release must compare below GA');
const r = detectWorkflowBackend({ runtimeId: 'claude', hostIntegration: CAPABLE_HOST, agentSdkVersion: '0.3.149-rc.1', config: cfg });
assert.strictEqual(r.backend, 'inline', 'pre-release of the floor must not activate the BETA backend');
assert.strictEqual(r.available, false);
});
test('two pre-releases of the same triple order by their identifiers (SemVer §11)', () => {
assert.ok(compareSemver('0.3.149-rc.0', '0.3.149-rc.1') < 0, 'rc.0 < rc.1');
assert.ok(compareSemver('1.0.0-alpha.1', '1.0.0-alpha.2') < 0, 'alpha.1 < alpha.2');
assert.ok(compareSemver('1.0.0-rc.1', '1.0.0-rc.2') < 0, 'rc.1 < rc.2');
// numeric < alphanumeric at the same position
assert.ok(compareSemver('1.0.0-1', '1.0.0-alpha') < 0, 'numeric identifier < alphanumeric');
});
test('missing/empty input -> inline, never throws (Postel: liberal-in-input)', () => {
assert.strictEqual(detectWorkflowBackend({}).backend, 'inline');
assert.strictEqual(detectWorkflowBackend(null).backend, 'inline');
assert.strictEqual(detectWorkflowBackend(undefined).backend, 'inline');
assert.strictEqual(detectWorkflowBackend({ runtimeId: 'claude' }).backend, 'inline');
});
test('BACKEND_VALUES exposes the closed enum', () => {
assert.deepStrictEqual([...BACKEND_VALUES].sort(), ['auto', 'inline', 'workflow']);
});
test('property: pure & deterministic (same input -> same output)', () => {
fc.assert(fc.property(
fc.record({
runtimeId: fc.constantFrom('claude', 'codex', 'cursor', 'opencode'),
sdk: fc.option(fc.string({ minLength: 1, maxLength: 8 }).filter((s) => /^\d/.test(s)), { nil: undefined }),
backend: fc.constantFrom('auto', 'workflow', 'inline'),
enabled: fc.boolean(),
}),
(input) => {
const cfg = {
'claude_orchestration.enabled': input.enabled,
'claude_orchestration.execution_backend': input.backend,
};
const a = detectWorkflowBackend({ runtimeId: input.runtimeId, hostIntegration: CAPABLE_HOST, agentSdkVersion: input.sdk, config: cfg });
const b = detectWorkflowBackend({ runtimeId: input.runtimeId, hostIntegration: CAPABLE_HOST, agentSdkVersion: input.sdk, config: cfg });
assert.deepStrictEqual(a, b);
assert.ok(['workflow', 'inline'].includes(a.backend));
},
));
});
});
// ─── 2. compareSemver helper ──────────────────────────────────────────────────
describe('compareSemver', () => {
test('ordering', () => {
assert.ok(compareSemver('1.0.0', '0.9.9') > 0);
assert.ok(compareSemver('1.0.0', '1.0.0') === 0);
assert.ok(compareSemver('1.0.0', '1.0.1') < 0);
assert.ok(compareSemver('2.0.0', '1.9.9') > 0);
});
test('garbage versions compare as -1 (fail-closed)', () => {
assert.strictEqual(compareSemver('garbage', '1.0.0'), -1);
assert.strictEqual(compareSemver('1.0.0', ''), -1);
});
});
// ─── 3. emitWorkflowScript ────────────────────────────────────────────────────
describe('emitWorkflowScript', () => {
test('single-wave single-plan -> one parallel barrier, one agent, executor+worktree', () => {
const { ok, script, summary } = emitWorkflowScript(singleWaveManifest());
assert.strictEqual(ok, true);
assert.ok(typeof script === 'string' && script.length > 0);
const parallelCount = (script.match(/parallel\s*\(/g) || []).length;
assert.ok(parallelCount >= 1, 'at least one parallel() barrier');
assert.ok(script.includes('agent('), 'agent() call per plan');
assert.ok(script.includes('gsd-executor'), 'uses gsd-executor agentType');
assert.ok(script.includes('worktree'), 'uses worktree isolation');
assert.ok(script.includes('SUMMARY.md'), 'produces SUMMARY.md (same artifact as inline path)');
assert.deepStrictEqual(summary.waves, 1);
assert.deepStrictEqual(summary.plans, 1);
});
test('multi-wave -> one parallel() barrier per wave (sequential barriers)', () => {
const r = emitWorkflowScript({
phaseDir: '.planning/phases/01-foo',
runId: 'run-multi',
waves: [
{ id: 'w1', plans: [{ id: 'p1', brief: 'A', files_modified: ['src/a.cts'] }] },
{ id: 'w2', plans: [{ id: 'p2', brief: 'B', files_modified: ['src/b.cts'] }] },
{ id: 'w3', plans: [{ id: 'p3', brief: 'C', files_modified: ['src/c.cts'] }] },
],
});
assert.strictEqual(r.ok, true);
const parallelCount = (r.script.match(/parallel\s*\(/g) || []).length;
assert.strictEqual(parallelCount, 3, 'one parallel() per wave');
assert.strictEqual(r.summary.waves, 3);
assert.strictEqual(r.summary.plans, 3);
});
test('overlapping files_modified -> plans split into separate sequential stages (criterion 2)', () => {
const r = emitWorkflowScript(overlappingManifest());
assert.strictEqual(r.ok, true);
// Two plans sharing src/shared.cts must NOT be in the same stage.
const stages = r.summary.stagesByWave[0]; // wave w1
assert.ok(Array.isArray(stages), 'stagesByWave present');
assert.strictEqual(stages.length, 2, 'overlapping plans split into 2 stages');
const stagePlanSets = stages.map((s) => s.slice().sort());
const allPlans = stagePlanSets.flat().sort();
assert.deepStrictEqual(allPlans, ['p1', 'p2']);
// p1 and p2 must be in different stages
assert.ok(stages[0].length === 1 && stages[1].length === 1, 'one plan per stage when they overlap');
});
test('non-overlapping plans -> coalesced into a single parallel stage', () => {
const r = emitWorkflowScript(nonOverlappingManifest());
assert.strictEqual(r.ok, true);
const stages = r.summary.stagesByWave[0];
assert.strictEqual(stages.length, 1, 'non-overlapping plans share one stage');
assert.deepStrictEqual(stages[0].slice().sort(), ['p1', 'p2']);
});
test('resumeFromRunId wired to the provided runId (criterion 4)', () => {
const r = emitWorkflowScript(singleWaveManifest());
assert.ok(r.script.includes('resumeFromRunId'), 'references resumeFromRunId');
assert.ok(r.script.includes('run-abc-1143'), 'carries the run id');
assert.strictEqual(r.summary.resumeRunId, 'run-abc-1143');
});
test('shared budget pool emitted when budgetTokens provided', () => {
const r = emitWorkflowScript({ ...singleWaveManifest(), budgetTokens: 500000 });
assert.ok(r.script.includes('budget('), 'emits budget() pool');
assert.ok(r.script.includes('500000'));
});
test('no budget() emitted when budgetTokens omitted', () => {
const r = emitWorkflowScript(singleWaveManifest());
assert.ok(!r.script.includes('budget('), 'no budget() when unset');
});
test('invalid input -> ok:false with a reason, never throws', () => {
const empty = emitWorkflowScript({ phaseDir: '.p', runId: 'r', waves: [] });
assert.strictEqual(empty.ok, false);
assert.ok(typeof empty.reason === 'string' && empty.reason.length > 0);
const noRun = emitWorkflowScript({ phaseDir: '.p', runId: '', waves: singleWaveManifest().waves });
assert.strictEqual(noRun.ok, false);
const noPhase = emitWorkflowScript({ phaseDir: '', runId: 'r', waves: singleWaveManifest().waves });
assert.strictEqual(noPhase.ok, false);
const badWave = emitWorkflowScript({ phaseDir: '.p', runId: 'r', waves: [{ id: 'w1', plans: [] }] });
assert.strictEqual(badWave.ok, false);
});
test('SECURITY: runId/phaseDir/wave.id/plan.id with injection chars -> ok:false (never reach the script)', () => {
// runId is interpolated inside resumeFromRunId("...") — a quote/backslash/newline
// could break out of the call. Identifier validation must reject it.
const injectRun = emitWorkflowScript({ phaseDir: '.p', runId: 'x");evil("y', waves: singleWaveManifest().waves });
assert.strictEqual(injectRun.ok, false);
assert.match(injectRun.reason, /runId/i);
const newlineRun = emitWorkflowScript({ phaseDir: '.p', runId: 'r\nbreakout', waves: singleWaveManifest().waves });
assert.strictEqual(newlineRun.ok, false);
const injectPhase = emitWorkflowScript({ phaseDir: '.p"; drop table', runId: 'r', waves: singleWaveManifest().waves });
assert.strictEqual(injectPhase.ok, false);
const injectWave = emitWorkflowScript({
phaseDir: '.p', runId: 'r',
waves: [{ id: 'w1\nagent("evil")', plans: [{ id: 'p1', brief: 'b', files_modified: ['a.cts'] }] }],
});
assert.strictEqual(injectWave.ok, false);
const injectPlan = emitWorkflowScript({
phaseDir: '.p', runId: 'r',
waves: [{ id: 'w1', plans: [{ id: 'p1";x("y', brief: 'b', files_modified: ['a.cts'] }] }],
});
assert.strictEqual(injectPlan.ok, false);
});
test('SECURITY: a brief containing quotes/backslash/newlines is neutralised (never breaks the string literal)', () => {
const r = emitWorkflowScript({
phaseDir: '.p', runId: 'r',
waves: [{ id: 'w1', plans: [{ id: 'p1', brief: 'he said "hi" \\ then \n newline', files_modified: ['a.cts'] }] }],
});
assert.strictEqual(r.ok, true);
// The emitted script must not contain a raw unescaped quote that closes the
// agent() string literal, nor a raw newline inside the brief.
assert.ok(!r.script.includes('he said "hi" \\\\'), 'no unescaped breakout');
// The full brief text never appears verbatim with its dangerous chars intact.
assert.ok(!r.script.includes('"hi"'), 'the inner quote must be JSON-escaped, not raw');
});
test('duplicate plan id within a wave -> ok:false (L-5: no silent brief loss)', () => {
const r = emitWorkflowScript({
phaseDir: '.p', runId: 'r',
waves: [{ id: 'w1', plans: [
{ id: 'p1', brief: 'first', files_modified: ['a.cts'] },
{ id: 'p1', brief: 'second', files_modified: ['b.cts'] },
] }],
});
assert.strictEqual(r.ok, false);
assert.match(r.reason, /duplicate/i);
});
test('non-string files_modified entries -> ok:false (L-7: strict element typing)', () => {
const r = emitWorkflowScript({
phaseDir: '.p', runId: 'r',
waves: [{ id: 'w1', plans: [{ id: 'p1', brief: 'b', files_modified: ['ok.cts', 42, { path: 'x' }] }] }],
});
assert.strictEqual(r.ok, false);
assert.match(r.reason, /files_modified/);
});
test('property: deterministic (same input -> identical script)', () => {
fc.assert(fc.property(
fc.record({
runId: fc.string({ minLength: 1, maxLength: 12 }).filter((s) => /^[a-zA-Z0-9-]+$/.test(s)),
nPlans: fc.integer({ min: 1, max: 5 }),
}),
({ runId, nPlans }) => {
const waves = [{
id: 'w1',
plans: Array.from({ length: nPlans }, (_, i) => ({
id: 'p' + i,
brief: 'brief ' + i,
files_modified: ['src/file' + i + '.cts'],
})),
}];
const a = emitWorkflowScript({ phaseDir: '.planning/phases/01-x', runId, waves });
const b = emitWorkflowScript({ phaseDir: '.planning/phases/01-x', runId, waves });
assert.strictEqual(a.script, b.script);
assert.deepStrictEqual(a.summary, b.summary);
},
));
});
});
// ─── 4. Capability declaration validation ─────────────────────────────────────
describe('capability declaration (capabilities/claude-orchestration/capability.json)', () => {
test('file exists and parses', () => {
const cap = loadCap();
assert.strictEqual(cap.id, 'claude-orchestration');
});
test('passes per-file validateCapability', () => {
const errors = validateCapability(loadCap(), 'claude-orchestration');
assert.deepEqual(errors, [], 'Expected no validation errors: ' + JSON.stringify(errors));
});
test('passes contract validation (contribution.into roles, when references)', () => {
const errors = validateAgainstContract(loadCap(), 'claude-orchestration');
assert.deepEqual(errors, [], 'Expected no contract errors: ' + JSON.stringify(errors));
});
test('default-off: activationKey default is false and points at the enabled key', () => {
const cap = loadCap();
assert.strictEqual(cap.activationKey, 'claude_orchestration.enabled');
assert.strictEqual(cap.config['claude_orchestration.enabled'].default, false);
assert.strictEqual(cap.config['claude_orchestration.enabled'].type, 'boolean');
});
test('runtimeCompat is claude-only (criterion 6)', () => {
const cap = loadCap();
assert.deepStrictEqual(cap.runtimeCompat.supported, ['claude']);
assert.deepStrictEqual(cap.runtimeCompat.unsupported, []);
});
test('BETA posture: tier full, role feature', () => {
const cap = loadCap();
assert.strictEqual(cap.role, 'feature');
assert.strictEqual(cap.tier, 'full');
});
test('execution_backend is an enum with auto|workflow|inline defaulting to auto', () => {
const slice = loadCap().config['claude_orchestration.execution_backend'];
assert.strictEqual(slice.type, 'enum');
assert.deepStrictEqual(slice.values, ['auto', 'workflow', 'inline']);
assert.strictEqual(slice.default, 'auto');
});
test('registers at WIRED points only (execute:wave:post, plan:post)', () => {
const cap = loadCap();
const points = cap.contributions.map((c) => c.point);
for (const p of points) {
assert.ok(
['discuss:pre', 'discuss:post', 'plan:pre', 'plan:post', 'execute:post', 'execute:wave:post', 'verify:post', 'ship:pre', 'ship:post'].includes(p),
'contribution point ' + p + ' must be a wired point',
);
}
assert.ok(points.includes('execute:wave:post'), 'registers the execute wave hook');
assert.ok(points.includes('plan:post'), 'declares plan:* ownership for ultraplan (criterion 5)');
});
test('all contributions gated by the enabled key + onError:skip (default-resilient)', () => {
const cap = loadCap();
for (const c of cap.contributions) {
assert.strictEqual(c.when, 'claude_orchestration.enabled', 'every contribution gated by enabled');
assert.strictEqual(c.onError, 'skip', 'every contribution onError:skip');
}
});
});
// ─── 5. Registry integration ──────────────────────────────────────────────────
describe('registry integration', () => {
test('loadAndValidate includes claude-orchestration with no errors', () => {
const { capMap, errors } = loadAndValidate(new Set()); // empty central keys = no collision noise
// Filter errors to only those touching our capability.
const ours = errors.filter((e) => e.includes('claude-orchestration'));
assert.deepEqual(ours, [], 'our capability produced errors: ' + JSON.stringify(ours));
assert.ok(capMap.has('claude-orchestration'), 'capMap includes claude-orchestration');
});
test('buildRegistry surfaces the federated config keys in configSchema', () => {
const { capMap } = loadAndValidate(new Set());
const registry = buildRegistry(capMap);
assert.ok(registry.configSchema['claude_orchestration.enabled'], 'enabled key federated');
assert.ok(registry.configSchema['claude_orchestration.execution_backend'], 'execution_backend key federated');
assert.strictEqual(registry.configSchema['claude_orchestration.enabled'].owner, 'claude-orchestration');
assert.strictEqual(registry.configSchema['claude_orchestration.execution_backend'].default, 'auto');
});
test('byLoopPoint[execute:wave:post].contributions includes our capability', () => {
const { capMap } = loadAndValidate(new Set());
const registry = buildRegistry(capMap);
const contribs = registry.byLoopPoint['execute:wave:post'].contributions;
const ours = contribs.find((c) => c.capId === 'claude-orchestration');
assert.ok(ours, 'our execute:wave:post contribution is registered');
assert.strictEqual(ours.into, 'executor');
});
test('committed registry is in sync (gen-capability-registry --check)', () => {
const { capMap } = loadAndValidate(new Set());
const registry = buildRegistry(capMap);
const live = serializeRegistry(registry, capMap);
const committed = fs.readFileSync(REGISTRY_PATH, 'utf8');
assert.strictEqual(
normalizeLineEndings(stripGeneratedComment(committed)),
normalizeLineEndings(stripGeneratedComment(live)),
'registry is stale — run: node scripts/gen-capability-registry.cjs --write',
);
});
});
// ─── 6. Inline-fallback parity (criterion 3 + 6) ──────────────────────────────
describe('inline-fallback parity', () => {
test('default config (capability off) -> inline on every runtime, including Claude', () => {
// The capability ships default-off; with no user opt-in the backend is always inline.
const defaultCfg = {}; // nothing set
for (const runtimeId of ['claude', 'codex', 'cursor', 'opencode']) {
const r = detectWorkflowBackend({
runtimeId,
hostIntegration: CAPABLE_HOST,
agentSdkVersion: '1.0.0',
config: defaultCfg,
});
assert.strictEqual(r.backend, 'inline', runtimeId + ' default must be inline');
assert.strictEqual(r.available, false, runtimeId + ' default must be unavailable');
}
});
test('generated Workflow script preserves the inline-path contract (same agent + isolation + artifact)', () => {
// Criterion 2: the emitted Workflow composes the SAME gsd-executor agent and worktree
// isolation the inline path uses, and produces the same SUMMARY.md artifact.
const r = emitWorkflowScript(singleWaveManifest());
assert.ok(r.script.includes('gsd-executor'), 'same executor agent as inline dispatch');
assert.ok(r.script.includes('worktree'), 'same worktree isolation as inline dispatch');
assert.ok(r.script.includes('SUMMARY.md'), 'same SUMMARY.md artifact as inline dispatch');
});
});

View File

@@ -0,0 +1,35 @@
'use strict';
// Regression guard for #2017 — agent `mcp__context7__*` grants only match a
// standalone context7 MCP server; the Claude Code plugin-marketplace install
// (`context7@claude-plugins-official`) names tools `mcp__plugin_context7_context7__*`,
// so every agent that uses context7 must grant BOTH forms or silently lose
// documentation lookup (falls back to WebSearch with no error).
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
function agentFiles() {
return fs.readdirSync(AGENTS_DIR).filter((f) => f.endsWith('.md')).map((f) => path.join(AGENTS_DIR, f));
}
describe('#2017 — context7 plugin-marketplace grant parity', () => {
const STANDALONE = 'mcp__context7__*';
const PLUGIN = 'mcp__plugin_context7_context7__*';
for (const file of agentFiles()) {
const name = path.basename(file);
const content = fs.readFileSync(file, 'utf8');
const toolsLine = (content.split(/\r?\n/).find((l) => /^tools:/.test(l)) || '');
test(`${name}: standalone context7 grant implies the plugin-marketplace grant`, () => {
if (!toolsLine.includes(STANDALONE)) return; // agent doesn't use context7 — skip
assert.ok(toolsLine.includes(PLUGIN),
`${name} grants ${STANDALONE} but not ${PLUGIN} (plugin-marketplace installs silently lose doc lookup — #2017)`);
});
}
});

View File

@@ -0,0 +1,340 @@
'use strict';
/**
* Tests for gsd-core/bin/ensure-runtime-build.cjs — the self-healing runtime
* build that compiles the gitignored ./lib/*.cjs artifacts on demand when the
* CLI is run from a channel (Claude Code plugin marketplace) that never ran
* `npm run build:lib`. See #2002.
*
* The unit tests inject `spawn` so no real tsc runs; the final integration test
* drives the REAL module against a real (tiny) tsc build in a temp package to
* prove the end-to-end heal. IO failures are injected by monkeypatching the fs
* method (root-independent, OS-independent), never via chmod.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const { createTempDir, cleanup } = require('./helpers.cjs');
const {
ensureRuntimeBuild,
resolveTscScript,
isBuilt,
RuntimeBuildError,
SENTINEL,
} = require('../gsd-core/bin/ensure-runtime-build.cjs');
const REPO_ROOT = path.resolve(__dirname, '..');
/** Fresh isolated package dir (auto-cleaned) with a libDir and optional tsconfig. */
function makeEnv(t, { withTsconfig = true, prebuilt = false } = {}) {
const dir = createTempDir('gsd-erb-');
t.after(() => cleanup(dir));
const libDir = path.join(dir, 'gsd-core', 'bin', 'lib');
fs.mkdirSync(libDir, { recursive: true });
if (withTsconfig) fs.writeFileSync(path.join(dir, 'tsconfig.build.json'), '{}');
if (prebuilt) fs.writeFileSync(path.join(libDir, SENTINEL), '// built\n');
return { dir, packageRoot: dir, libDir };
}
/** A spawn stub that "builds" by writing the sentinel, then reports success. */
function buildingSpawn(libDir, calls) {
return (cmd, args, options) => {
calls.push({ cmd, args, options });
fs.writeFileSync(path.join(libDir, SENTINEL), '// built by stub\n');
return { status: 0, stdout: '', stderr: '' };
};
}
const silent = () => {};
test('fast path: already-built lib is a no-op, never spawns tsc', (t) => {
const { libDir, packageRoot } = makeEnv(t, { prebuilt: true });
const calls = [];
const res = ensureRuntimeBuild({
libDir,
packageRoot,
spawn: (...a) => { calls.push(a); return { status: 0 }; },
log: silent,
});
assert.deepEqual(res, { built: true, healed: false });
assert.equal(calls.length, 0, 'must not spawn when already built');
});
test('heal path: missing lib is compiled once with the correct tsc invocation', (t) => {
const { libDir, packageRoot } = makeEnv(t);
const calls = [];
const res = ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/typescript/bin/tsc',
spawn: buildingSpawn(libDir, calls),
log: silent,
});
assert.equal(res.built, true);
assert.equal(res.healed, true);
assert.equal(calls.length, 1, 'compiles exactly once');
const { cmd, args, options } = calls[0];
assert.equal(cmd, process.execPath, 'runs `node <tsc>` (portable, no shell/.cmd)');
assert.deepEqual(args, [
'/fake/typescript/bin/tsc',
'-p',
path.join(packageRoot, 'tsconfig.build.json'),
]);
assert.equal(options.cwd, packageRoot);
assert.ok(isBuilt(libDir), 'sentinel exists after heal');
});
test('missing tsconfig.build.json throws an actionable RuntimeBuildError', (t) => {
const { libDir, packageRoot } = makeEnv(t, { withTsconfig: false });
assert.throws(
() => ensureRuntimeBuild({ libDir, packageRoot, tscScript: '/fake/tsc', log: silent }),
(err) => {
assert.ok(err instanceof RuntimeBuildError);
assert.match(err.message, /tsconfig\.build\.json not found/);
assert.match(err.message, /npm run build:lib/);
return true;
},
);
});
test('absent TypeScript throws with install guidance and does not spawn', (t) => {
const { libDir, packageRoot } = makeEnv(t);
const calls = [];
assert.throws(
() => ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: null, // TypeScript not resolvable
spawn: (...a) => { calls.push(a); return { status: 0 }; },
log: silent,
}),
(err) => {
assert.ok(err instanceof RuntimeBuildError);
assert.match(err.message, /TypeScript is unavailable/);
assert.match(err.message, /npm install && npm run build:lib/);
return true;
},
);
assert.equal(calls.length, 0, 'never spawns when tsc is absent');
});
test('non-zero tsc exit surfaces the exit code and captured output', (t) => {
const { libDir, packageRoot } = makeEnv(t);
assert.throws(
() => ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: () => ({ status: 2, stdout: '', stderr: 'src/x.cts(1,1): error TS1005' }),
log: silent,
}),
(err) => {
assert.ok(err instanceof RuntimeBuildError);
assert.match(err.message, /tsc exit 2/);
assert.match(err.message, /error TS1005/);
return true;
},
);
});
test('tsc reports success but sentinel is still missing → throws', (t) => {
const { libDir, packageRoot } = makeEnv(t);
assert.throws(
() => ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: () => ({ status: 0 }), // "succeeds" but writes nothing
log: silent,
}),
(err) => {
assert.ok(err instanceof RuntimeBuildError);
assert.match(err.message, /still missing/);
return true;
},
);
});
test('the build lock releases after a successful heal', (t) => {
const { libDir, packageRoot } = makeEnv(t);
ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: buildingSpawn(libDir, []),
log: silent,
});
assert.ok(!fs.existsSync(path.join(libDir, '.build.lock')), 'lock removed in finally');
});
test('lock contention: waits for a peer build instead of racing tsc', (t) => {
const { libDir, packageRoot } = makeEnv(t);
fs.mkdirSync(path.join(libDir, '.build.lock')); // a peer "holds" the lock
const calls = [];
const res = ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: (...a) => { calls.push(a); return { status: 0 }; },
log: silent,
pollMs: 1,
waitTimeoutMs: 1000,
// Peer finishes the build between polls.
onPoll: () => fs.writeFileSync(path.join(libDir, SENTINEL), '// peer built\n'),
});
assert.equal(res.healed, true);
assert.equal(res.waited, true);
assert.equal(calls.length, 0, 'never launches a competing tsc');
});
test('lock takeover: a crashed peer (stale lock) is taken over and the build runs', (t) => {
const { libDir, packageRoot } = makeEnv(t);
const lockDir = path.join(libDir, '.build.lock');
fs.mkdirSync(lockDir);
const calls = [];
const res = ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: buildingSpawn(libDir, calls),
log: silent,
pollMs: 1,
waitTimeoutMs: 0, // give up waiting immediately
// Peer vanished without finishing — free the stale lock so we can take over.
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- simulating a peer releasing its lock, not temp-dir teardown
onPoll: () => fs.rmSync(lockDir, { recursive: true, force: true }),
});
assert.equal(res.healed, true);
assert.equal(calls.length, 1, 'takes over and builds once');
});
test('lock never frees before timeout → throws a lock-held RuntimeBuildError', (t) => {
const { libDir, packageRoot } = makeEnv(t);
fs.mkdirSync(path.join(libDir, '.build.lock')); // stuck peer, never released
assert.throws(
() => ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: buildingSpawn(libDir, []),
log: silent,
pollMs: 1,
waitTimeoutMs: 0,
}),
(err) => {
assert.ok(err instanceof RuntimeBuildError);
assert.match(err.message, /did not complete within 0ms/);
return true;
},
);
});
test('IO fault on lock acquisition propagates (injected via fs monkeypatch, not chmod)', (t) => {
const { libDir, packageRoot } = makeEnv(t);
const lockDir = path.join(libDir, '.build.lock');
const orig = fs.mkdirSync;
fs.mkdirSync = (target, opts) => {
if (target === lockDir) {
const e = new Error('injected EPERM');
e.code = 'EPERM';
throw e;
}
return orig(target, opts);
};
try {
assert.throws(
() => ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: buildingSpawn(libDir, []),
log: silent,
}),
(err) => {
// Non-EEXIST lock errors must NOT be swallowed as "peer holds lock".
assert.equal(err.code, 'EPERM');
return true;
},
);
} finally {
fs.mkdirSync = orig;
}
});
test('heal clears a stale tsc incremental cache before compiling (forces full emit)', (t) => {
const { libDir, packageRoot } = makeEnv(t);
const buildInfo = path.join(packageRoot, 'tsconfig.build.tsbuildinfo');
fs.writeFileSync(buildInfo, '{"stale":true}'); // leftover from a partial build
ensureRuntimeBuild({
libDir,
packageRoot,
tscScript: '/fake/tsc',
spawn: buildingSpawn(libDir, []),
log: silent,
});
assert.ok(!fs.existsSync(buildInfo), 'stale .tsbuildinfo removed so tsc re-emits');
});
test('resolveTscScript finds the real TypeScript under the repo root', () => {
const tsc = resolveTscScript(REPO_ROOT);
assert.ok(tsc && tsc.includes('typescript'), 'resolves the installed typescript/bin/tsc');
assert.ok(fs.existsSync(tsc), 'resolved tsc script exists on disk');
});
test('integration: real tsc heals a missing lib end-to-end (subprocess)', (t) => {
// Build a tiny package that mirrors the real layout: a src/*.cts that tsc
// compiles to gsd-core/bin/lib/cli-exit.cjs. Then invoke the REAL module from
// a subprocess with lib absent and assert it self-heals and the require works.
const dir = createTempDir('gsd-erb-int-');
t.after(() => cleanup(dir));
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
fs.mkdirSync(path.join(dir, 'gsd-core', 'bin', 'lib'), { recursive: true });
fs.writeFileSync(
path.join(dir, 'tsconfig.build.json'),
JSON.stringify({
compilerOptions: {
rootDir: 'src',
outDir: 'gsd-core/bin/lib',
module: 'nodenext',
moduleResolution: 'nodenext',
target: 'ES2022',
noEmitOnError: true,
},
include: ['src/**/*.cts'],
}),
);
fs.writeFileSync(
path.join(dir, 'src', 'cli-exit.cts'),
'export const runMain = (): string => "ok";\n',
);
// Resolve the REAL tsc and pass it explicitly, rather than symlinking
// node_modules into the temp package — directory-symlink creation is a
// privileged operation on Windows and would fail on CI runners. resolveTscScript
// itself is covered by its own unit test; here we just need a real tsc to run.
const realTsc = resolveTscScript(REPO_ROOT);
assert.ok(realTsc, 'real typescript resolvable for the integration build');
const driver = path.join(dir, 'driver.cjs');
fs.writeFileSync(
driver,
`const { ensureRuntimeBuild } = require(${JSON.stringify(
path.join(REPO_ROOT, 'gsd-core', 'bin', 'ensure-runtime-build.cjs'),
)});\n` +
`const libDir = ${JSON.stringify(path.join(dir, 'gsd-core', 'bin', 'lib'))};\n` +
`const packageRoot = ${JSON.stringify(dir)};\n` +
`const tscScript = ${JSON.stringify(realTsc)};\n` +
'const r = ensureRuntimeBuild({ libDir, packageRoot, tscScript, log: () => {} });\n' +
'const { runMain } = require(require("path").join(libDir, "cli-exit.cjs"));\n' +
'process.stdout.write(JSON.stringify({ healed: r.healed, run: runMain() }));\n',
);
assert.ok(!isBuilt(path.join(dir, 'gsd-core', 'bin', 'lib')), 'lib starts empty');
const out = execFileSync(process.execPath, [driver], { encoding: 'utf8', cwd: dir });
const parsed = JSON.parse(out);
assert.equal(parsed.healed, true, 'self-healed the missing build');
assert.equal(parsed.run, 'ok', 'the freshly-compiled cli-exit.cjs loads and runs');
});

View File

@@ -629,15 +629,17 @@ describe('F. Real registry execute:wave:post shape — guard against accidental
`ui.safety-gate onError must be 'halt'; got ${uiGate.onError}`);
});
test('[happy] real registry: execute:wave:post has no steps and 2 contributions (mempalace capture-problems + external-job executor fragment)', () => {
test('[happy] real registry: execute:wave:post has no steps and 3 contributions (claude-orchestration executor + external-job executor + mempalace capture-problems)', () => {
const point = realRegistry.byLoopPoint['execute:wave:post'];
assert.strictEqual(point.steps.length, 0,
`execute:wave:post steps must be empty; got ${point.steps.length}`);
assert.strictEqual(point.contributions.length, 2,
`execute:wave:post must have 2 contributions (mempalace + external-job); got ${point.contributions.length}`);
// #1143: claude-orchestration registers an execute:wave:post contribution
// providing the Workflow-tool backend guidance (default-off, claude-only).
assert.strictEqual(point.contributions.length, 3,
`execute:wave:post must have 3 contributions (claude-orchestration + external-job + mempalace); got ${point.contributions.length}`);
const capIds = point.contributions.map(c => c.capId).sort();
assert.deepStrictEqual(capIds, ['external-job', 'mempalace'],
`execute:wave:post contributions must be mempalace + external-job; got ${capIds.join(',')}`);
assert.deepStrictEqual(capIds, ['claude-orchestration', 'external-job', 'mempalace'],
`execute:wave:post contributions must be claude-orchestration + external-job + mempalace; got ${capIds.join(',')}`);
});
});

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "1db46cac3f4d9889",
"agents/gsd-eval-auditor.md": "1b8391f1aafb067f",
"agents/gsd-eval-planner.md": "3d10fd11147f6857",
"agents/gsd-executor.md": "e3e159690812a81e",
"agents/gsd-executor.md": "152ca51a10b99ff8",
"agents/gsd-framework-selector.md": "daa62c79619c76bf",
"agents/gsd-integration-checker.md": "0643cd2d779b131c",
"agents/gsd-intel-updater.md": "26c1f1e028c6346a",
@@ -37,11 +37,12 @@
"agents/gsd-verifier.md": "224e8df2d8fd1d95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74",
"gsd-core/bin/gsd-tools.cjs": "747defe620cb8ae6",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -107,7 +108,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "8ce19741d18507f7",
"gsd-core/references/planning-config.md": "49fbbfdac6b8ced4",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -276,7 +277,7 @@
"gsd-core/workflows/profile-user.md": "355af92ac285567f",
"gsd-core/workflows/progress.md": "79a11ce798082054",
"gsd-core/workflows/quick.md": "a6aa362881f4e5da",
"gsd-core/workflows/reapply-patches.md": "825e37a55a992892",
"gsd-core/workflows/reapply-patches.md": "4dcd6117d0a507ca",
"gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e",
"gsd-core/workflows/remove-workspace.md": "d0bd7e0601138798",
"gsd-core/workflows/resume-project.md": "98e2cf8908e73a52",
@@ -305,7 +306,7 @@
"gsd-core/workflows/update.md": "2c58df5e21c41c31",
"gsd-core/workflows/validate-phase.md": "6c0ab739d15709fa",
"gsd-core/workflows/verify-phase.md": "8070a098ab538b33",
"gsd-core/workflows/verify-work.md": "d8e44ebee36b01e5",
"gsd-core/workflows/verify-work.md": "be699ed7920f61b0",
"hooks/gsd-check-update-worker.js": "fa301e6366270d5f",
"hooks/gsd-check-update.js": "4617a98bf529e4c3",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "671c9ea949889c4a",
"agents/gsd-eval-auditor.md": "fcaec7b00f94c435",
"agents/gsd-eval-planner.md": "a4a5b4b3f7828ba3",
"agents/gsd-executor.md": "88b1108114a2fa2e",
"agents/gsd-executor.md": "46f911cd211034b0",
"agents/gsd-framework-selector.md": "4b77eebbe9288d80",
"agents/gsd-integration-checker.md": "fa53e2d78be1de74",
"agents/gsd-intel-updater.md": "fa40e685d7441ace",
@@ -108,11 +108,12 @@
"commands/gsd-workstreams.md": "52ab9c585d3a00f3",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -178,7 +179,7 @@
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "1c3a3aae2ae89e83",
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -347,7 +348,7 @@
"gsd-core/workflows/profile-user.md": "14263db831230142",
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
"gsd-core/workflows/quick.md": "3bafc0a59cc90d26",
"gsd-core/workflows/reapply-patches.md": "7915e40411490bbe",
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
@@ -376,7 +377,7 @@
"gsd-core/workflows/update.md": "fd160e13f8b7e83c",
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
"gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c",
"gsd-core/workflows/verify-work.md": "5ed471293dc6835f",
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
"hooks/gsd-check-update-worker.js": "cc1ef5f840f9dfc9",
"hooks/gsd-check-update.js": "7b3a7983d5f1f5d3",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",

View File

@@ -1,7 +1,7 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
"agents/gsd-advisor-researcher.md": "7cdfe98e2baf606e",
"agents/gsd-ai-researcher.md": "fa68db1fb2c6ee8a",
"agents/gsd-advisor-researcher.md": "6f1f17e206e2c0c4",
"agents/gsd-ai-researcher.md": "e2236ee7b717c925",
"agents/gsd-assumptions-analyzer.md": "f94fdd67f70b4a5d",
"agents/gsd-code-fixer.md": "3d5f67cfd24ac452",
"agents/gsd-code-reviewer.md": "d626a828e8de3648",
@@ -12,35 +12,36 @@
"agents/gsd-doc-synthesizer.md": "135bbdfb9053cbd8",
"agents/gsd-doc-verifier.md": "4232dcf9076e3566",
"agents/gsd-doc-writer.md": "8cb62907c6458111",
"agents/gsd-domain-researcher.md": "f8be56555689a970",
"agents/gsd-domain-researcher.md": "5f7d366251b957fe",
"agents/gsd-eval-auditor.md": "fea2759beff0a642",
"agents/gsd-eval-planner.md": "112f6730f23854e3",
"agents/gsd-executor.md": "5e8219d0a9ff9485",
"agents/gsd-executor.md": "22fc2f17098a4d14",
"agents/gsd-framework-selector.md": "c350ee693cb1aa4e",
"agents/gsd-integration-checker.md": "c8b4e65dee89c8ea",
"agents/gsd-intel-updater.md": "5b41e05f90ce89d9",
"agents/gsd-mempalace-curator.md": "77b53f1b155242b4",
"agents/gsd-nyquist-auditor.md": "0bc0cdad7b3a0013",
"agents/gsd-pattern-mapper.md": "b45b5e106775bec1",
"agents/gsd-phase-researcher.md": "529a4573230b104e",
"agents/gsd-phase-researcher.md": "85217c69c1ed2ac6",
"agents/gsd-plan-checker.md": "c70134c61b969589",
"agents/gsd-planner.md": "d42aa2d1bc51edb4",
"agents/gsd-project-researcher.md": "e572c285002ba02e",
"agents/gsd-planner.md": "aa68af11f852ecfd",
"agents/gsd-project-researcher.md": "f468e96f8339d1e0",
"agents/gsd-research-synthesizer.md": "7be02e47f4fd901b",
"agents/gsd-roadmapper.md": "8a7f1f1256a6aed5",
"agents/gsd-security-auditor.md": "4f9fc3f654af4944",
"agents/gsd-ui-auditor.md": "40c0dcc15bcfb9fb",
"agents/gsd-ui-checker.md": "6e94b43fd459e10e",
"agents/gsd-ui-researcher.md": "48bc161ca5a858b9",
"agents/gsd-ui-researcher.md": "5e561130434efdb2",
"agents/gsd-user-profiler.md": "003276f85792cfda",
"agents/gsd-verifier.md": "2271174b5aa20e31",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -106,7 +107,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "4435dfdc3381233f",
"gsd-core/references/planning-config.md": "35aa39fef2641311",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -275,7 +276,7 @@
"gsd-core/workflows/profile-user.md": "ff3820a27731ceb8",
"gsd-core/workflows/progress.md": "bd1ecf9207331bda",
"gsd-core/workflows/quick.md": "bd56a6313d5ed43c",
"gsd-core/workflows/reapply-patches.md": "2d7dada9edec108b",
"gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041",
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
"gsd-core/workflows/remove-workspace.md": "f3ab3a88a7e9e1ed",
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
@@ -304,7 +305,7 @@
"gsd-core/workflows/update.md": "f9e7d8a760d0d3c8",
"gsd-core/workflows/validate-phase.md": "2ac231dc541441c2",
"gsd-core/workflows/verify-phase.md": "0d4ffabc1caa473a",
"gsd-core/workflows/verify-work.md": "2b49359ed055f9c4",
"gsd-core/workflows/verify-work.md": "efe57bdbbb3af03f",
"hooks/gsd-check-update-worker.js": "a530efdb5fdc0da3",
"hooks/gsd-check-update.js": "25cde66a12d6b886",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",

View File

@@ -19,7 +19,7 @@
"agents/gsd-domain-researcher.md": "0fecdaea86466a56",
"agents/gsd-eval-auditor.md": "36c44303085df2f8",
"agents/gsd-eval-planner.md": "3ddea88a69b4da3f",
"agents/gsd-executor.md": "0a955e3fc57b22b9",
"agents/gsd-executor.md": "441e624e9685e505",
"agents/gsd-framework-selector.md": "564669d479433f15",
"agents/gsd-integration-checker.md": "1bbbdd3d420b994e",
"agents/gsd-intel-updater.md": "42c40fffbc720d0b",
@@ -40,11 +40,12 @@
"agents/gsd-verifier.md": "75912e9cadd83eb3",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "476aa24e8c4f03cf",
"gsd-core/bin/gsd-tools.cjs": "13640341aa311ae3",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -110,7 +111,7 @@
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "c386ac9e804f862e",
"gsd-core/references/planning-config.md": "15a5fc63268379d4",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -279,7 +280,7 @@
"gsd-core/workflows/profile-user.md": "26f74db0a7fcd268",
"gsd-core/workflows/progress.md": "9f326d63afb4b76b",
"gsd-core/workflows/quick.md": "10051a9b41d5d210",
"gsd-core/workflows/reapply-patches.md": "4cfdb59f5d8e727e",
"gsd-core/workflows/reapply-patches.md": "eb4272145a117904",
"gsd-core/workflows/remove-phase.md": "e336350f8113a328",
"gsd-core/workflows/remove-workspace.md": "e685dfbd736dfd90",
"gsd-core/workflows/resume-project.md": "e23981178fa37b3d",
@@ -308,7 +309,7 @@
"gsd-core/workflows/update.md": "165beec33490bd28",
"gsd-core/workflows/validate-phase.md": "5b4ae14c87859bd2",
"gsd-core/workflows/verify-phase.md": "20e90407e1e531fc",
"gsd-core/workflows/verify-work.md": "343a1c8615c06c2b",
"gsd-core/workflows/verify-work.md": "6fa216623778c541",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "1c1a800108a2b225",
"agents/gsd-eval-auditor.md": "99012004b14ea602",
"agents/gsd-eval-planner.md": "4ebdd7fe9cbb0cfe",
"agents/gsd-executor.md": "89fe6f42f9b4003a",
"agents/gsd-executor.md": "d0bfc88c01a63181",
"agents/gsd-framework-selector.md": "7726fccc86bfeb50",
"agents/gsd-integration-checker.md": "2d8339790bbb2dc3",
"agents/gsd-intel-updater.md": "c51339956197cbd3",
@@ -108,11 +108,12 @@
"commands/gsd-workstreams.md": "884b6c8d648422c7",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -178,7 +179,7 @@
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "1c3a3aae2ae89e83",
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -347,7 +348,7 @@
"gsd-core/workflows/profile-user.md": "4fa910d15dea5695",
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
"gsd-core/workflows/quick.md": "5e7dcdd864513b87",
"gsd-core/workflows/reapply-patches.md": "7915e40411490bbe",
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
@@ -376,7 +377,7 @@
"gsd-core/workflows/update.md": "5ff1f77222977648",
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
"gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c",
"gsd-core/workflows/verify-work.md": "5ed471293dc6835f",
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
"hooks/gsd-check-update-worker.js": "bdc9324a2f080ddd",
"hooks/gsd-check-update.js": "b7669f605631e506",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",

View File

@@ -1,9 +1,9 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"agents/gsd-advisor-researcher.md": "3995e081531dd043",
"agents/gsd-advisor-researcher.md": "eea6d1604aaf305c",
"agents/gsd-advisor-researcher.toml": "144f037c79aa9c26",
"agents/gsd-ai-researcher.md": "9deaf29ae170faff",
"agents/gsd-ai-researcher.md": "303be751c91da4c8",
"agents/gsd-ai-researcher.toml": "5a16e952628fdeb0",
"agents/gsd-assumptions-analyzer.md": "21ee3cd4095f7cf8",
"agents/gsd-assumptions-analyzer.toml": "9460360826f33ab1",
@@ -25,14 +25,14 @@
"agents/gsd-doc-verifier.toml": "c0b7ac150730b954",
"agents/gsd-doc-writer.md": "0a3557268f169722",
"agents/gsd-doc-writer.toml": "d21d67f9ba074156",
"agents/gsd-domain-researcher.md": "bafef7d698ccb928",
"agents/gsd-domain-researcher.md": "fbd2f916f265c106",
"agents/gsd-domain-researcher.toml": "a49ddec9005b4bf5",
"agents/gsd-eval-auditor.md": "2810917e02e5547c",
"agents/gsd-eval-auditor.toml": "9b81d61b3c5f722d",
"agents/gsd-eval-planner.md": "73f2ad2ff2797a51",
"agents/gsd-eval-planner.toml": "09468ad1a34ac468",
"agents/gsd-executor.md": "c5e340b1a279eb2e",
"agents/gsd-executor.toml": "e583836b4db56565",
"agents/gsd-executor.md": "93a05c1436bc4458",
"agents/gsd-executor.toml": "f5a55d4eacd8613c",
"agents/gsd-framework-selector.md": "ebae32430887d2e0",
"agents/gsd-framework-selector.toml": "637e4e021b7ec380",
"agents/gsd-integration-checker.md": "9cc875676cf7d741",
@@ -45,13 +45,13 @@
"agents/gsd-nyquist-auditor.toml": "b3d7214b42fb67fa",
"agents/gsd-pattern-mapper.md": "efbcfa7c5de4027f",
"agents/gsd-pattern-mapper.toml": "48a17baa3d7d142c",
"agents/gsd-phase-researcher.md": "a8876e9ca2e40d2f",
"agents/gsd-phase-researcher.md": "87d74324e7708ea0",
"agents/gsd-phase-researcher.toml": "44a3d510cd0ce3bd",
"agents/gsd-plan-checker.md": "e7f02c10ea788aee",
"agents/gsd-plan-checker.toml": "6f8ceb421d0ad721",
"agents/gsd-planner.md": "0e92e8d74894a56b",
"agents/gsd-planner.md": "5d638a2e37b63731",
"agents/gsd-planner.toml": "50cec2af6b45a61c",
"agents/gsd-project-researcher.md": "787697faf68ad1a5",
"agents/gsd-project-researcher.md": "959f2e57c3d69ed8",
"agents/gsd-project-researcher.toml": "f395e8e8c4baf1ed",
"agents/gsd-research-synthesizer.md": "497f85adf53259ef",
"agents/gsd-research-synthesizer.toml": "053616d4941a1458",
@@ -63,7 +63,7 @@
"agents/gsd-ui-auditor.toml": "5e9dd62a12a16a1e",
"agents/gsd-ui-checker.md": "bf7bf8cc301c65a1",
"agents/gsd-ui-checker.toml": "d0a084e82a9ae52d",
"agents/gsd-ui-researcher.md": "cab148fcbfff7e4d",
"agents/gsd-ui-researcher.md": "2befca69af55e44a",
"agents/gsd-ui-researcher.toml": "b55270666066e501",
"agents/gsd-user-profiler.md": "1bf5033c929181c1",
"agents/gsd-user-profiler.toml": "b9c244bb8fbf8140",
@@ -72,11 +72,12 @@
"config.toml": "a34316b9ac61a620",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -142,7 +143,7 @@
"gsd-core/references/planner-reviews.md": "7889bfa28e82156b",
"gsd-core/references/planner-revision.md": "2ebf1a714d1ec4bf",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "0767f44905c9a65e",
"gsd-core/references/planning-config.md": "8593e6610784a339",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -311,7 +312,7 @@
"gsd-core/workflows/profile-user.md": "05828c8cc61ef384",
"gsd-core/workflows/progress.md": "7bedc431bb55edb8",
"gsd-core/workflows/quick.md": "85fb48e52f0b3725",
"gsd-core/workflows/reapply-patches.md": "fba79b3c55c3b2e7",
"gsd-core/workflows/reapply-patches.md": "26297b84736e66a4",
"gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4",
"gsd-core/workflows/remove-workspace.md": "19d7465aaa50cb62",
"gsd-core/workflows/resume-project.md": "9965f87eb278f7f8",
@@ -340,7 +341,7 @@
"gsd-core/workflows/update.md": "5c35c0ec0f462ea6",
"gsd-core/workflows/validate-phase.md": "020201a41049679f",
"gsd-core/workflows/verify-phase.md": "b9f70235e777aa81",
"gsd-core/workflows/verify-work.md": "d24a21519c50b1f8",
"gsd-core/workflows/verify-work.md": "69e27f6f419d0bba",
"hooks/gsd-check-update.js": "ef48957eb6ac6a10",
"hooks/gsd-context-monitor.js": "76fecaaa2babd6c1",
"scripts/changeset/README.md": "86ff89331dfd94b2",

View File

@@ -1,8 +1,8 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"agents/gsd-advisor-researcher.agent.md": "ecd72019cd5634c4",
"agents/gsd-ai-researcher.agent.md": "65c846c74f17a74b",
"agents/gsd-advisor-researcher.agent.md": "1fb10242ff7fb1c9",
"agents/gsd-ai-researcher.agent.md": "8e9c3a2ddd179bb1",
"agents/gsd-assumptions-analyzer.agent.md": "cdabed4777a842fe",
"agents/gsd-code-fixer.agent.md": "8f39aacb760aab57",
"agents/gsd-code-reviewer.agent.md": "fa1e7c421c78eae1",
@@ -13,36 +13,37 @@
"agents/gsd-doc-synthesizer.agent.md": "16cebf04d8998356",
"agents/gsd-doc-verifier.agent.md": "0230208ae7ab1a08",
"agents/gsd-doc-writer.agent.md": "37ea4932a4b80355",
"agents/gsd-domain-researcher.agent.md": "b06738b093df1cb9",
"agents/gsd-domain-researcher.agent.md": "d603239b3e9fe428",
"agents/gsd-eval-auditor.agent.md": "3c03009564de55c8",
"agents/gsd-eval-planner.agent.md": "14751876fc2b5f16",
"agents/gsd-executor.agent.md": "1c42c925b9113c19",
"agents/gsd-executor.agent.md": "0058082fef5a4985",
"agents/gsd-framework-selector.agent.md": "cafeec0b3489be45",
"agents/gsd-integration-checker.agent.md": "30439b804927acc7",
"agents/gsd-intel-updater.agent.md": "238c1a886f35a25c",
"agents/gsd-mempalace-curator.agent.md": "9e5290ea4c4ac211",
"agents/gsd-nyquist-auditor.agent.md": "6463a50d90ada125",
"agents/gsd-pattern-mapper.agent.md": "b1f488b0fa6a2395",
"agents/gsd-phase-researcher.agent.md": "fc6062a07152951b",
"agents/gsd-phase-researcher.agent.md": "03cfb510a766fe93",
"agents/gsd-plan-checker.agent.md": "c50a5b008ddcbfad",
"agents/gsd-planner.agent.md": "5a1d56ee3dae82a9",
"agents/gsd-project-researcher.agent.md": "39007b6fcb854032",
"agents/gsd-planner.agent.md": "d40d16eced1c6463",
"agents/gsd-project-researcher.agent.md": "d73bdbe986ffa8a6",
"agents/gsd-research-synthesizer.agent.md": "f03eed4aa89e47c5",
"agents/gsd-roadmapper.agent.md": "322048cf8ddcb4e5",
"agents/gsd-security-auditor.agent.md": "6f6a88b35dc2a24b",
"agents/gsd-ui-auditor.agent.md": "92f50549e84ef482",
"agents/gsd-ui-checker.agent.md": "c9958f2587b24156",
"agents/gsd-ui-researcher.agent.md": "a9c6a2f0e9975df6",
"agents/gsd-ui-researcher.agent.md": "d24301cc182be33e",
"agents/gsd-user-profiler.agent.md": "ae16a248e18dd42b",
"agents/gsd-verifier.agent.md": "87a8e3238e838a39",
"copilot-instructions.md": "1fb04111759f1645",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74",
"gsd-core/bin/gsd-tools.cjs": "747defe620cb8ae6",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -108,7 +109,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "ea950944302fba67",
"gsd-core/references/planning-config.md": "091d7541826f8cb0",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -277,7 +278,7 @@
"gsd-core/workflows/profile-user.md": "5cc032206c99ef71",
"gsd-core/workflows/progress.md": "6b9a84a43dc55af5",
"gsd-core/workflows/quick.md": "89c34e07f1bf6f53",
"gsd-core/workflows/reapply-patches.md": "7e1d16d9a9ccbb03",
"gsd-core/workflows/reapply-patches.md": "8fd59e24b486f180",
"gsd-core/workflows/remove-phase.md": "e262654e319d1bc4",
"gsd-core/workflows/remove-workspace.md": "ceddfeef5f2d6754",
"gsd-core/workflows/resume-project.md": "40db7f350f5866d8",
@@ -306,7 +307,7 @@
"gsd-core/workflows/update.md": "f444a7cfcd246cfb",
"gsd-core/workflows/validate-phase.md": "2f705775a4b76d42",
"gsd-core/workflows/verify-phase.md": "59d1046b3a2bc481",
"gsd-core/workflows/verify-work.md": "74d024e92d8ca2b0",
"gsd-core/workflows/verify-work.md": "58e9b1b16f773b53",
"hooks/gsd-session.json": "0a462834f2a28fee",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "56395dbdabf076f6",
"agents/gsd-eval-auditor.md": "ad2840fd5cd76172",
"agents/gsd-eval-planner.md": "2049dac060d00eda",
"agents/gsd-executor.md": "058936492c4f2814",
"agents/gsd-executor.md": "1fb2c778178cfa1c",
"agents/gsd-framework-selector.md": "4b77eebbe9288d80",
"agents/gsd-integration-checker.md": "5da30584d06b878c",
"agents/gsd-intel-updater.md": "b8971c5d96e63b38",
@@ -108,11 +108,12 @@
"commands/gsd-workstreams.md": "112660ceb663c750",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/ensure-runtime-build.cjs": "2525f1ae8b086828",
"gsd-core/bin/gsd-tools.cjs": "887e2cb9ebdb609d",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -178,7 +179,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "e2330447f33f6609",
"gsd-core/references/planning-config.md": "a7b41174b754b215",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -347,7 +348,7 @@
"gsd-core/workflows/profile-user.md": "8c943983241260b5",
"gsd-core/workflows/progress.md": "65aabee5e8a6dd82",
"gsd-core/workflows/quick.md": "db98fdbe79d68ffc",
"gsd-core/workflows/reapply-patches.md": "2d7dada9edec108b",
"gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041",
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
"gsd-core/workflows/remove-workspace.md": "433affcd1a200826",
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
@@ -376,7 +377,7 @@
"gsd-core/workflows/update.md": "23e294ba707c3580",
"gsd-core/workflows/validate-phase.md": "2df0c6e298a5f249",
"gsd-core/workflows/verify-phase.md": "0d4ffabc1caa473a",
"gsd-core/workflows/verify-work.md": "74f5f7349ea9f757",
"gsd-core/workflows/verify-work.md": "145596b2542c457a",
"hooks/gsd-cursor-post-tool.js": "019d503aee8b4a3f",
"hooks/gsd-cursor-session-start.js": "c6e04ed597ea7020",
"scripts/changeset/README.md": "86ff89331dfd94b2",

View File

@@ -1,8 +1,8 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"agents/gsd-advisor-researcher.md": "f3f0a55854689045",
"agents/gsd-ai-researcher.md": "72f67082f35c9da1",
"agents/gsd-advisor-researcher.md": "f1c4a47d3d8cea72",
"agents/gsd-ai-researcher.md": "08847faf5494ff08",
"agents/gsd-assumptions-analyzer.md": "f11a80795163bb5d",
"agents/gsd-code-fixer.md": "94628312f25decb5",
"agents/gsd-code-reviewer.md": "5ef947ca5baed775",
@@ -13,35 +13,36 @@
"agents/gsd-doc-synthesizer.md": "8e6fb8220f777022",
"agents/gsd-doc-verifier.md": "d3d8457bdd44bf10",
"agents/gsd-doc-writer.md": "2858a8335170e85c",
"agents/gsd-domain-researcher.md": "d4c07520f650ffd5",
"agents/gsd-domain-researcher.md": "412cdbb05ba252ea",
"agents/gsd-eval-auditor.md": "4ffb265063c318e5",
"agents/gsd-eval-planner.md": "03448fc9c5774b56",
"agents/gsd-executor.md": "aed0ea22f9322b2c",
"agents/gsd-executor.md": "18365948be7c2dff",
"agents/gsd-framework-selector.md": "ea9981d65d6b3429",
"agents/gsd-integration-checker.md": "35b4f2969d279871",
"agents/gsd-intel-updater.md": "5fe5edfae2719cb8",
"agents/gsd-mempalace-curator.md": "399e90e473b33814",
"agents/gsd-nyquist-auditor.md": "8bfde4a9a36318aa",
"agents/gsd-pattern-mapper.md": "cea092600aeb3978",
"agents/gsd-phase-researcher.md": "1c73b638d891363e",
"agents/gsd-phase-researcher.md": "2bd0402f33d757ca",
"agents/gsd-plan-checker.md": "4b4e2b475bf5b5c3",
"agents/gsd-planner.md": "08e85102869630b4",
"agents/gsd-project-researcher.md": "97711cda03fdc9c0",
"agents/gsd-planner.md": "018542e335a983fa",
"agents/gsd-project-researcher.md": "425a7df7f37a5c06",
"agents/gsd-research-synthesizer.md": "9d31c87fc2c87ffa",
"agents/gsd-roadmapper.md": "64dce5d5f9fa5654",
"agents/gsd-security-auditor.md": "e4d35ada4ea67d7f",
"agents/gsd-ui-auditor.md": "86797e85f718dfac",
"agents/gsd-ui-checker.md": "c56fa63fb94ded5f",
"agents/gsd-ui-researcher.md": "daa5ef704ff55753",
"agents/gsd-ui-researcher.md": "25ffa7fa83c32a69",
"agents/gsd-user-profiler.md": "ca3bf75581f211a0",
"agents/gsd-verifier.md": "82d3e9015ed04017",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "3a3409215044af9f",
"gsd-core/bin/gsd-tools.cjs": "cbdd5da1b973c6ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -107,7 +108,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "9b5f8ed49024cdbf",
"gsd-core/references/planning-config.md": "b5a719401e701e12",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -276,7 +277,7 @@
"gsd-core/workflows/profile-user.md": "de5030437226cf2c",
"gsd-core/workflows/progress.md": "f18db000584d9cb1",
"gsd-core/workflows/quick.md": "66b26d6fe2bd4d71",
"gsd-core/workflows/reapply-patches.md": "7af45bb3f2fdd1b8",
"gsd-core/workflows/reapply-patches.md": "158083a310859594",
"gsd-core/workflows/remove-phase.md": "fce799aae3ab2715",
"gsd-core/workflows/remove-workspace.md": "8facde381657dd71",
"gsd-core/workflows/resume-project.md": "a0443839f1f83c2d",
@@ -305,7 +306,7 @@
"gsd-core/workflows/update.md": "7499bb4cb2a3ce6f",
"gsd-core/workflows/validate-phase.md": "75e8971d3981d06b",
"gsd-core/workflows/verify-phase.md": "84e00bd685006870",
"gsd-core/workflows/verify-work.md": "f7750a6cce6d5391",
"gsd-core/workflows/verify-work.md": "e56e07475d5eb51e",
"hooks/gsd-check-update-worker.js": "7989cc2bedd1138d",
"hooks/gsd-check-update.js": "25f5ad726f76fc11",
"hooks/gsd-config-reload.js": "880b696458e85e9b",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "a3874d80bcbc7380",
"agents/gsd-eval-auditor.md": "630d4cd3bd6ea195",
"agents/gsd-eval-planner.md": "3db12cde12aeb2c1",
"agents/gsd-executor.md": "3ee15ac212214106",
"agents/gsd-executor.md": "c9aca9129bd4cab3",
"agents/gsd-framework-selector.md": "ad5f2c6b9bec6270",
"agents/gsd-integration-checker.md": "c503e2f4a3d8ec05",
"agents/gsd-intel-updater.md": "231393da62a45b2e",
@@ -108,11 +108,12 @@
"command/gsd-workstreams.md": "5e57eed1881c3891",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -178,7 +179,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "4acefb3c71169c84",
"gsd-core/references/planning-config.md": "af0a9694b3c39557",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -347,7 +348,7 @@
"gsd-core/workflows/profile-user.md": "203ebe3f8f3876a8",
"gsd-core/workflows/progress.md": "9381c59676ccb937",
"gsd-core/workflows/quick.md": "11ba83f6350462fb",
"gsd-core/workflows/reapply-patches.md": "2c44426624047ed0",
"gsd-core/workflows/reapply-patches.md": "becf9728cdb124c4",
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
"gsd-core/workflows/remove-workspace.md": "fc83f362a2d0a1b7",
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
@@ -376,7 +377,7 @@
"gsd-core/workflows/update.md": "07dc2fba78ad1865",
"gsd-core/workflows/validate-phase.md": "557e3251e3b9349a",
"gsd-core/workflows/verify-phase.md": "0d4ffabc1caa473a",
"gsd-core/workflows/verify-work.md": "41a198eb414e051e",
"gsd-core/workflows/verify-work.md": "b68ac37f6301a3b5",
"hooks/gsd-check-update-worker.js": "c992bbad91d0e994",
"hooks/gsd-check-update.js": "fdd77abe7ef26a2d",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",

View File

@@ -33,7 +33,7 @@
"agents/subagents/gsd-eval-auditor.yaml": "e3d868bd5fefe938",
"agents/subagents/gsd-eval-planner.md": "70f8c5727bfb9876",
"agents/subagents/gsd-eval-planner.yaml": "df8499f7af297ec2",
"agents/subagents/gsd-executor.md": "9b7607df31335054",
"agents/subagents/gsd-executor.md": "278a569a7306244c",
"agents/subagents/gsd-executor.yaml": "e29422986636fd64",
"agents/subagents/gsd-framework-selector.md": "a15b7aa1e0576e16",
"agents/subagents/gsd-framework-selector.yaml": "fb52c31cde27b0e3",
@@ -73,11 +73,12 @@
"agents/subagents/gsd-verifier.yaml": "2d2bd6b37626f382",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -143,7 +144,7 @@
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "1c3a3aae2ae89e83",
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -312,7 +313,7 @@
"gsd-core/workflows/profile-user.md": "5abfae83739fa978",
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
"gsd-core/workflows/quick.md": "d722803b72217377",
"gsd-core/workflows/reapply-patches.md": "7915e40411490bbe",
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
@@ -341,7 +342,7 @@
"gsd-core/workflows/update.md": "6718e0632bba26ca",
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
"gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c",
"gsd-core/workflows/verify-work.md": "5ed471293dc6835f",
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "71250e759ca9e723",
"agents/gsd-eval-auditor.md": "c88890105f32ace6",
"agents/gsd-eval-planner.md": "60bddb70a937f796",
"agents/gsd-executor.md": "d14a44fd6ae0935b",
"agents/gsd-executor.md": "b091b0126895ae03",
"agents/gsd-framework-selector.md": "1c0a10355e787675",
"agents/gsd-integration-checker.md": "a9de5928e5a5c649",
"agents/gsd-intel-updater.md": "493e07482fa6198a",
@@ -108,11 +108,12 @@
"command/gsd-workstreams.md": "5e57eed1881c3891",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -178,7 +179,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "4acefb3c71169c84",
"gsd-core/references/planning-config.md": "af0a9694b3c39557",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -347,7 +348,7 @@
"gsd-core/workflows/profile-user.md": "248d59a31948e0ed",
"gsd-core/workflows/progress.md": "8fc3404087f50b95",
"gsd-core/workflows/quick.md": "3866a5259e8912d8",
"gsd-core/workflows/reapply-patches.md": "a6cfec7e2a0e7ee2",
"gsd-core/workflows/reapply-patches.md": "a0e9b53f90abceb2",
"gsd-core/workflows/remove-phase.md": "dea4661e8f89596f",
"gsd-core/workflows/remove-workspace.md": "446847e71aa52504",
"gsd-core/workflows/resume-project.md": "ad9f06a10bab8cc0",
@@ -376,7 +377,7 @@
"gsd-core/workflows/update.md": "71b6cd852f38b4bc",
"gsd-core/workflows/validate-phase.md": "abcdbc1b56780565",
"gsd-core/workflows/verify-phase.md": "5bd04b040cd071dd",
"gsd-core/workflows/verify-work.md": "f7dad67215e6b730",
"gsd-core/workflows/verify-work.md": "f0d205568abfaf74",
"hooks/gsd-check-update-worker.js": "385fb7c67810baf6",
"hooks/gsd-check-update.js": "4549451414ffa7d7",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",

View File

@@ -1,8 +1,8 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"agents/gsd-advisor-researcher.md": "f3f0a55854689045",
"agents/gsd-ai-researcher.md": "72f67082f35c9da1",
"agents/gsd-advisor-researcher.md": "f1c4a47d3d8cea72",
"agents/gsd-ai-researcher.md": "08847faf5494ff08",
"agents/gsd-assumptions-analyzer.md": "f11a80795163bb5d",
"agents/gsd-code-fixer.md": "5660b5d7a912dbac",
"agents/gsd-code-reviewer.md": "f70ee630a166fec8",
@@ -13,35 +13,36 @@
"agents/gsd-doc-synthesizer.md": "8e6fb8220f777022",
"agents/gsd-doc-verifier.md": "3767850df78f4a36",
"agents/gsd-doc-writer.md": "10962b6bd4212379",
"agents/gsd-domain-researcher.md": "d4c07520f650ffd5",
"agents/gsd-domain-researcher.md": "412cdbb05ba252ea",
"agents/gsd-eval-auditor.md": "2d121dbec93fd4ce",
"agents/gsd-eval-planner.md": "03448fc9c5774b56",
"agents/gsd-executor.md": "748d8595f8c530bc",
"agents/gsd-executor.md": "0735dcf053be7c76",
"agents/gsd-framework-selector.md": "ea9981d65d6b3429",
"agents/gsd-integration-checker.md": "33fd537b862eb944",
"agents/gsd-intel-updater.md": "699dacdc0a1f2478",
"agents/gsd-mempalace-curator.md": "399e90e473b33814",
"agents/gsd-nyquist-auditor.md": "7fb0ecf639c1cdfe",
"agents/gsd-pattern-mapper.md": "9ab071c3ffc1cf46",
"agents/gsd-phase-researcher.md": "b064bc7bb4881443",
"agents/gsd-phase-researcher.md": "ecb365e5f62aaafc",
"agents/gsd-plan-checker.md": "ca47fdf6d4a36849",
"agents/gsd-planner.md": "358edac30c2edf06",
"agents/gsd-project-researcher.md": "b0f23dcd0f383907",
"agents/gsd-planner.md": "7cb18e515bab3c05",
"agents/gsd-project-researcher.md": "d38509cab69224d9",
"agents/gsd-research-synthesizer.md": "6d64fce8b06b7a62",
"agents/gsd-roadmapper.md": "604b25c6687811f4",
"agents/gsd-security-auditor.md": "f85447e5b5300feb",
"agents/gsd-ui-auditor.md": "cc46d104cbc49079",
"agents/gsd-ui-checker.md": "8f140c80fc193e13",
"agents/gsd-ui-researcher.md": "c2e93ad96d0a93e1",
"agents/gsd-ui-researcher.md": "623cb59aa6dbe146",
"agents/gsd-user-profiler.md": "ca3bf75581f211a0",
"agents/gsd-verifier.md": "45c450b6d7cc5f0d",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/ensure-runtime-build.cjs": "6e98d76e955e35a2",
"gsd-core/bin/gsd-tools.cjs": "1118403709188168",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -107,7 +108,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "32430b855023bfdb",
"gsd-core/references/planning-config.md": "e8087cf6aae8d56f",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -276,7 +277,7 @@
"gsd-core/workflows/profile-user.md": "263c0693563d98da",
"gsd-core/workflows/progress.md": "3b1b2142a74af85c",
"gsd-core/workflows/quick.md": "518740e30dcd4da3",
"gsd-core/workflows/reapply-patches.md": "4a93b90cbe70a2f1",
"gsd-core/workflows/reapply-patches.md": "de0ee8acfe7245b2",
"gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0",
"gsd-core/workflows/remove-workspace.md": "4ac64de862dc650e",
"gsd-core/workflows/resume-project.md": "7f20769f302e5427",
@@ -305,7 +306,7 @@
"gsd-core/workflows/update.md": "b34cb866152d4de3",
"gsd-core/workflows/validate-phase.md": "e989cbaa4228564c",
"gsd-core/workflows/verify-phase.md": "a8679ba1d84145c5",
"gsd-core/workflows/verify-work.md": "5d523d416fef66f0",
"gsd-core/workflows/verify-work.md": "b5afb65fdf311301",
"hooks/gsd-check-update-worker.js": "4bb354044e0dff91",
"hooks/gsd-check-update.js": "d2065cb3e725a42a",
"hooks/gsd-config-reload.js": "4f52b8a0120bb1b8",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "b80f76874c04e515",
"agents/gsd-eval-auditor.md": "470bf16303ec4d2e",
"agents/gsd-eval-planner.md": "22334fde85723c9d",
"agents/gsd-executor.md": "381f4a44532d8b1b",
"agents/gsd-executor.md": "45eeb443a6e6dbe8",
"agents/gsd-framework-selector.md": "7726fccc86bfeb50",
"agents/gsd-integration-checker.md": "7cd2072984411c7f",
"agents/gsd-intel-updater.md": "83de6ba9172891c3",
@@ -37,11 +37,12 @@
"agents/gsd-verifier.md": "99e95e235aacffbf",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/ensure-runtime-build.cjs": "de4627dff103d527",
"gsd-core/bin/gsd-tools.cjs": "fa4db2ce1f77ef99",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -107,7 +108,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "970c47f816acdb30",
"gsd-core/references/planning-config.md": "33c5b0685be54e96",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -276,7 +277,7 @@
"gsd-core/workflows/profile-user.md": "672821e6b1266645",
"gsd-core/workflows/progress.md": "94768f835b0b8908",
"gsd-core/workflows/quick.md": "8ac5f3919f754499",
"gsd-core/workflows/reapply-patches.md": "ea66e63f56e7deb3",
"gsd-core/workflows/reapply-patches.md": "21b38c374f19fd78",
"gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86",
"gsd-core/workflows/remove-workspace.md": "ae0e1c6d4438d663",
"gsd-core/workflows/resume-project.md": "f242e4c8aba18ea2",
@@ -305,7 +306,7 @@
"gsd-core/workflows/update.md": "1f935251fca1f276",
"gsd-core/workflows/validate-phase.md": "1c0ebe56d96a14d1",
"gsd-core/workflows/verify-phase.md": "53cbdce394dcdac0",
"gsd-core/workflows/verify-work.md": "17b2f2591c3f642e",
"gsd-core/workflows/verify-work.md": "d3a0970205acc6a5",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -16,7 +16,7 @@
"agents/gsd-domain-researcher.md": "56395dbdabf076f6",
"agents/gsd-eval-auditor.md": "fb64fc5acf359747",
"agents/gsd-eval-planner.md": "2049dac060d00eda",
"agents/gsd-executor.md": "704ea6e1d44da9c1",
"agents/gsd-executor.md": "406fdcc0597bea77",
"agents/gsd-framework-selector.md": "4b77eebbe9288d80",
"agents/gsd-integration-checker.md": "4ffb37fb230c2b90",
"agents/gsd-intel-updater.md": "a81d77c143c02108",
@@ -37,11 +37,12 @@
"agents/gsd-verifier.md": "a07b00b9c5b7b338",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/ensure-runtime-build.cjs": "5636ca0b726871b2",
"gsd-core/bin/gsd-tools.cjs": "92e0f7b7eb55af51",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -107,7 +108,7 @@
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "742e11db47c5c0aa",
"gsd-core/references/planning-config.md": "062db9fe16ec76f9",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
@@ -276,7 +277,7 @@
"gsd-core/workflows/profile-user.md": "c4313672b81b5bcd",
"gsd-core/workflows/progress.md": "18813a345bd2343a",
"gsd-core/workflows/quick.md": "0fcad72e7dbe99c9",
"gsd-core/workflows/reapply-patches.md": "04885b2129cbb457",
"gsd-core/workflows/reapply-patches.md": "d449a23d3acf6379",
"gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b",
"gsd-core/workflows/remove-workspace.md": "b5e60fbb33b3e33a",
"gsd-core/workflows/resume-project.md": "82cfe1b8cb17c085",
@@ -305,7 +306,7 @@
"gsd-core/workflows/update.md": "79aaf4b8f1f83045",
"gsd-core/workflows/validate-phase.md": "2db47bf5547d7b9d",
"gsd-core/workflows/verify-phase.md": "ea7f8a705b297e27",
"gsd-core/workflows/verify-work.md": "bbf699e62d93d05b",
"gsd-core/workflows/verify-work.md": "5ad63a5edfb6acac",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -0,0 +1,484 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"agents/gsd-advisor-researcher.md": "f1c4a47d3d8cea72",
"agents/gsd-ai-researcher.md": "2f2da4b89d445860",
"agents/gsd-assumptions-analyzer.md": "f11a80795163bb5d",
"agents/gsd-code-fixer.md": "78549833f0411b0c",
"agents/gsd-code-reviewer.md": "7d94fe8bfa6661aa",
"agents/gsd-codebase-mapper.md": "7cc9d387f29c46e1",
"agents/gsd-debug-session-manager.md": "30651d0b4a465263",
"agents/gsd-debugger.md": "fc76a617817be610",
"agents/gsd-doc-classifier.md": "6c33418bf370ec44",
"agents/gsd-doc-synthesizer.md": "c2cd65ae13aa33fa",
"agents/gsd-doc-verifier.md": "7ed4d9611f678a95",
"agents/gsd-doc-writer.md": "3ed3dc255d7eca8d",
"agents/gsd-domain-researcher.md": "049f588663814fa2",
"agents/gsd-eval-auditor.md": "54870d3b07433525",
"agents/gsd-eval-planner.md": "552e9fa164c51ce8",
"agents/gsd-executor.md": "7bc50a045cb23811",
"agents/gsd-framework-selector.md": "8a795f230436ad2e",
"agents/gsd-integration-checker.md": "c1760a0bbd4f7bf5",
"agents/gsd-intel-updater.md": "944f1d903e2e9e09",
"agents/gsd-mempalace-curator.md": "399e90e473b33814",
"agents/gsd-nyquist-auditor.md": "f2232a1be0835709",
"agents/gsd-pattern-mapper.md": "68ecefd60811a669",
"agents/gsd-phase-researcher.md": "2235f61764d8e969",
"agents/gsd-plan-checker.md": "bb38f345d3d41edc",
"agents/gsd-planner.md": "262cbc06269277d1",
"agents/gsd-project-researcher.md": "f572892f138734ff",
"agents/gsd-research-synthesizer.md": "29949bf3f049a8f1",
"agents/gsd-roadmapper.md": "840ac933e3b094f9",
"agents/gsd-security-auditor.md": "b7202c44366697dd",
"agents/gsd-ui-auditor.md": "76f446c50edf81f8",
"agents/gsd-ui-checker.md": "63e5c862bf7bd8ba",
"agents/gsd-ui-researcher.md": "2a1dda374b1958aa",
"agents/gsd-user-profiler.md": "ca3bf75581f211a0",
"agents/gsd-verifier.md": "8282761ba471c022",
"commands/gsd-add-tests.md": "51542808862d4aa7",
"commands/gsd-ai-integration-phase.md": "22b1e98786c2a43e",
"commands/gsd-audit-fix.md": "24d7e5478c271584",
"commands/gsd-audit-milestone.md": "192c2d4979ef6a5e",
"commands/gsd-audit-uat.md": "a7ce7c63bf2db68e",
"commands/gsd-autonomous.md": "35ccce06299833cf",
"commands/gsd-capture.md": "b8494f0d0b1aff6a",
"commands/gsd-cleanup.md": "e7b7644ddcefdf01",
"commands/gsd-code-review.md": "1687eabcdf03f643",
"commands/gsd-complete-milestone.md": "0b9066c97cf17898",
"commands/gsd-config.md": "f6532a6cff144abe",
"commands/gsd-debug.md": "9b42a48f5ae6836a",
"commands/gsd-discuss-phase.md": "5a29df7d87aeb5cc",
"commands/gsd-docs-update.md": "4992630ef84a9bfe",
"commands/gsd-eval-review.md": "56a5842110d51254",
"commands/gsd-execute-phase.md": "6bc473253c54343c",
"commands/gsd-explore.md": "99d92deebce81126",
"commands/gsd-extract-learnings.md": "b74c866e71283e49",
"commands/gsd-fast.md": "5ac666f183c840ae",
"commands/gsd-forensics.md": "fd672efe51d65a1a",
"commands/gsd-graphify.md": "43e6dc6f9a38a043",
"commands/gsd-health.md": "5339a925c497c35b",
"commands/gsd-help.md": "9101ee73c245037e",
"commands/gsd-import.md": "8a81293674937978",
"commands/gsd-inbox.md": "da55c1408a6ee6cc",
"commands/gsd-ingest-docs.md": "7a3c21ae5d5d133a",
"commands/gsd-manager.md": "9f54653a7c589b00",
"commands/gsd-map-codebase.md": "8b605921868c03a3",
"commands/gsd-mempalace-capture.md": "5d4556e444acbb90",
"commands/gsd-mempalace-recall.md": "5ac67fa4b7f2b03b",
"commands/gsd-milestone-summary.md": "f8fa9c357c08b74b",
"commands/gsd-mvp-phase.md": "cba67bcb55250e5f",
"commands/gsd-new-milestone.md": "53cb82c66bbc808c",
"commands/gsd-new-project.md": "952d7601b800ad8d",
"commands/gsd-next.md": "7d3a2cb0bd12043a",
"commands/gsd-ns-context.md": "011c44e7aa46e64a",
"commands/gsd-ns-ideate.md": "edc5e543512dd48a",
"commands/gsd-ns-manage.md": "0409d810e499357f",
"commands/gsd-ns-project.md": "8dfd1b9a2ffe86ac",
"commands/gsd-ns-review.md": "3766ed10827882a0",
"commands/gsd-ns-workflow.md": "c3b3c046a74ec0ee",
"commands/gsd-pause-work.md": "40a953fcddbedb5d",
"commands/gsd-phase.md": "5dd3d40e3461a973",
"commands/gsd-plan-phase.md": "2fb6cc9c4caa6a36",
"commands/gsd-plan-review-convergence.md": "f8e7ea9c70e59035",
"commands/gsd-pr-branch.md": "ab1fcffe92129061",
"commands/gsd-profile-user.md": "37c9ef202669bfd3",
"commands/gsd-progress.md": "13d7e5ba8acc2605",
"commands/gsd-quick.md": "3b9762ba2616ac69",
"commands/gsd-resume-work.md": "ece9c3a5f64e21a6",
"commands/gsd-review-backlog.md": "c5b557d4cb1b223d",
"commands/gsd-review.md": "3e8908603a3c2b28",
"commands/gsd-secure-phase.md": "34f8fb0720708cd1",
"commands/gsd-settings.md": "6f95c7d98a02f13a",
"commands/gsd-ship.md": "141c690957330961",
"commands/gsd-sketch.md": "770df13e2f848d0e",
"commands/gsd-spec-phase.md": "c0e592ef19cc6721",
"commands/gsd-spike.md": "5c85bade6e6b804f",
"commands/gsd-stats.md": "bea8a4f0e6f2ddf3",
"commands/gsd-surface.md": "b1ab5c2127dbf856",
"commands/gsd-thread.md": "56036040a95fc60b",
"commands/gsd-ui-phase.md": "b48b780198ec7a2e",
"commands/gsd-ui-review.md": "2ed61b5729f6e492",
"commands/gsd-ultraplan-phase.md": "08e75b083f5fe3f2",
"commands/gsd-undo.md": "479c579d2bb7c141",
"commands/gsd-update.md": "6f59562ac2d6bee5",
"commands/gsd-validate-phase.md": "22ad3181017f1b0e",
"commands/gsd-verify-work.md": "71aad09ecfd3f844",
"commands/gsd-workspace.md": "611002e4941d5654",
"commands/gsd-workstreams.md": "57a8ef0af8c085d7",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "770e0b1c1f08ef47",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "c55b99e5d82f69fb",
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
"gsd-core/contexts/review.md": "dc578fdd74bbea11",
"gsd-core/references/agent-contracts.md": "ff65e633c656c0d2",
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-philosophy.md": "0466f95a3d6bfcd3",
"gsd-core/references/decimal-phase-calculation.md": "46b5ba045852c474",
"gsd-core/references/doc-conflict-engine.md": "67d019d23e17f934",
"gsd-core/references/domain-probes.md": "62d23ed1992c48a9",
"gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json": "72d1e29cedc854ec",
"gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json": "fbc1b355d8625eeb",
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json": "fad67dcc8294f6da",
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json": "30a78ee9ce3473ea",
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json": "66dd60957fee45f0",
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json": "47fca61f076835fa",
"gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json": "72d1e29cedc854ec",
"gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json": "80f04f5c04fb24cf",
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json": "fad67dcc8294f6da",
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json": "d38147adb0e5b342",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json": "bc552c01939bf4f8",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json": "30a78ee9ce3473ea",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json": "688ec62c13e08afe",
"gsd-core/references/edge-probe.md": "5687eba25a078561",
"gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc",
"gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e",
"gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242",
"gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8",
"gsd-core/references/executor-examples.md": "ba59243ed45c8ab1",
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
"gsd-core/references/model-profile-resolution.md": "f32bb05102839767",
"gsd-core/references/model-profiles.md": "8484ad9799b7f680",
"gsd-core/references/mvp-concepts.md": "72f7e8b1f8ae7118",
"gsd-core/references/phase-argument-parsing.md": "e5bbb985f3bc3e34",
"gsd-core/references/planner-antipatterns.md": "7ed54ec1e2cc54ac",
"gsd-core/references/planner-chunked.md": "79fe674221e738e6",
"gsd-core/references/planner-gap-closure.md": "76bee257911413e7",
"gsd-core/references/planner-graphify-auto-update.md": "6aeffd9097e25e22",
"gsd-core/references/planner-guidance.md": "96486cac2f7885e6",
"gsd-core/references/planner-human-verify-mode.md": "56d05e841630b3f4",
"gsd-core/references/planner-interface-context.md": "b28fa3da6ae739a8",
"gsd-core/references/planner-load-graph-context.md": "ca7a7af3f35ae61b",
"gsd-core/references/planner-mvp-mode.md": "ec33050db81101a8",
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "7fe958e09e74b6c8",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
"gsd-core/references/prohibition-probe.md": "605dc3f5a118ff3b",
"gsd-core/references/project-skills-discovery.md": "c155e03dce8dc3c2",
"gsd-core/references/questioning.md": "a8c988cab05f4651",
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
"gsd-core/references/skeleton-template.md": "528691d1f0efa878",
"gsd-core/references/sketch-interactivity.md": "7d982fe877e1e1cc",
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
"gsd-core/references/tdd.md": "e4708ede157478b6",
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
"gsd-core/references/thinking-models-execution.md": "dcc650a8b5f3e049",
"gsd-core/references/thinking-models-planning.md": "7e19462313fa028f",
"gsd-core/references/thinking-models-research.md": "5f6bf3f3b889c6e4",
"gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d",
"gsd-core/references/thinking-partner.md": "41069529ef776e39",
"gsd-core/references/ui-brand.md": "48717bcfcd63bd27",
"gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01",
"gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a",
"gsd-core/references/user-profiling.md": "b50416fe57c1b321",
"gsd-core/references/user-story-template.md": "0cc50e06a144ff8a",
"gsd-core/references/verification-overrides.md": "8213de9bd62283b6",
"gsd-core/references/verification-patterns.md": "72f4d4814fdae9a8",
"gsd-core/references/verify-mvp-mode.md": "534bdc7f2432903a",
"gsd-core/references/workstream-flag.md": "ca99ca79e716f0f5",
"gsd-core/references/worktree-branch-check.md": "21d9c31bf6542b93",
"gsd-core/references/worktree-path-safety.md": "3c8d74756f9b16a8",
"gsd-core/templates/AI-SPEC.md": "efa1f8354bd3a24b",
"gsd-core/templates/DEBUG.md": "a13470b82b1935e7",
"gsd-core/templates/README.md": "93d3426fc64e2c12",
"gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f",
"gsd-core/templates/UAT.md": "9e296471b97ebcec",
"gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01",
"gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e",
"gsd-core/templates/claude-md.md": "d1d333e4b963c0d2",
"gsd-core/templates/codebase/architecture.md": "6be88214162fdd89",
"gsd-core/templates/codebase/concerns.md": "efa26d1fb5132f25",
"gsd-core/templates/codebase/conventions.md": "c2e07698dad6b364",
"gsd-core/templates/codebase/integrations.md": "39bd23c71eedd564",
"gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec",
"gsd-core/templates/codebase/structure.md": "222997133232a6f5",
"gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea",
"gsd-core/templates/config.json": "1473dff11272d3c4",
"gsd-core/templates/context.md": "69b01e7909ea3f66",
"gsd-core/templates/continue-here.md": "f522a51b6895fba8",
"gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea",
"gsd-core/templates/debug-subagent-prompt.md": "920656683dedb869",
"gsd-core/templates/dev-preferences.md": "88d0a65ec0993a3a",
"gsd-core/templates/discovery.md": "9a0e0935cc825dbc",
"gsd-core/templates/discussion-log.md": "cac1b48ec0f4dcb8",
"gsd-core/templates/milestone-archive.md": "591b6decdc0c0e51",
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
"gsd-core/templates/phase-prompt.md": "b811bf951092df2f",
"gsd-core/templates/planner-subagent-prompt.md": "ebf29dbb27042370",
"gsd-core/templates/project.md": "ab4af6becca2bb23",
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
"gsd-core/templates/research-project/PITFALLS.md": "3ef75fa768422eec",
"gsd-core/templates/research-project/STACK.md": "82c85799ac4dd344",
"gsd-core/templates/research-project/SUMMARY.md": "dceb2f346388839d",
"gsd-core/templates/research.md": "88ce0920417091d0",
"gsd-core/templates/retrospective.md": "03981e30dd760103",
"gsd-core/templates/roadmap.md": "e4e35a9eb5dd4d4f",
"gsd-core/templates/spec.md": "26d55bce940f0288",
"gsd-core/templates/state.md": "4d123aa6cea167fe",
"gsd-core/templates/summary-complex.md": "a5e40574fd8894dc",
"gsd-core/templates/summary-minimal.md": "7d09b5e709e2e67c",
"gsd-core/templates/summary-standard.md": "e8d9cf4a8377cdff",
"gsd-core/templates/summary.md": "23c40f6503b3ea98",
"gsd-core/templates/user-profile.md": "20749f23e4c413fc",
"gsd-core/templates/user-setup.md": "78b7d718b6e8d67c",
"gsd-core/templates/verification-report.md": "dd5faa6254183731",
"gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335",
"gsd-core/workflows/add-backlog.md": "1bc7377b105194fc",
"gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a",
"gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2",
"gsd-core/workflows/add-todo.md": "cc0efe270004c8fb",
"gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f",
"gsd-core/workflows/analyze-dependencies.md": "52942af10f140717",
"gsd-core/workflows/audit-fix.md": "514d02c730349a35",
"gsd-core/workflows/audit-milestone.md": "6866ce260980c21e",
"gsd-core/workflows/audit-uat.md": "fcfbec501620b564",
"gsd-core/workflows/autonomous.md": "7e5683728ef33707",
"gsd-core/workflows/check-todos.md": "bdeaf43f9c61e0cc",
"gsd-core/workflows/cleanup.md": "0daa2f2720c111f1",
"gsd-core/workflows/code-review-fix.md": "c323f7098b671bd6",
"gsd-core/workflows/code-review.md": "ad450c6ef8459dad",
"gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2",
"gsd-core/workflows/debug.md": "849d7e5b9c12dcae",
"gsd-core/workflows/diagnose-issues.md": "aa8d787db8f3c46c",
"gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761",
"gsd-core/workflows/discuss-phase-assumptions.md": "1e229f6d21831d60",
"gsd-core/workflows/discuss-phase-power.md": "0841f7dc6e9a054a",
"gsd-core/workflows/discuss-phase.md": "d0a67b6f77b4d339",
"gsd-core/workflows/discuss-phase/modes/advisor.md": "db488d74d080c653",
"gsd-core/workflows/discuss-phase/modes/all.md": "fa70d79066562e54",
"gsd-core/workflows/discuss-phase/modes/analyze.md": "da0788f3be7f8105",
"gsd-core/workflows/discuss-phase/modes/auto.md": "d06f0bea2ba0b240",
"gsd-core/workflows/discuss-phase/modes/batch.md": "6946597770e2d448",
"gsd-core/workflows/discuss-phase/modes/chain.md": "5502a67de7776853",
"gsd-core/workflows/discuss-phase/modes/default.md": "67d1b67f61f03966",
"gsd-core/workflows/discuss-phase/modes/power.md": "fdc7a728eaaa2261",
"gsd-core/workflows/discuss-phase/modes/text.md": "da6d45207da4a988",
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
"gsd-core/workflows/discuss-phase/templates/context.md": "6cd929e989fe2b0f",
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "1bbd7703f11128e1",
"gsd-core/workflows/do.md": "8f460af710c8f6a4",
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
"gsd-core/workflows/execute-phase.md": "49f793a605e4665e",
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da",
"gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e",
"gsd-core/workflows/execute-plan.md": "ed874410972d32b7",
"gsd-core/workflows/explore.md": "6c04f2e658d93261",
"gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4",
"gsd-core/workflows/fast.md": "94136fb570d20a9d",
"gsd-core/workflows/forensics.md": "0d500a3f5ab26913",
"gsd-core/workflows/graduation.md": "47f1594c88c08501",
"gsd-core/workflows/health.md": "107e3c72e76d9535",
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "cc8ef97de634c995",
"gsd-core/workflows/help/modes/full.md": "96abbac7f3f10e32",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "3d3fa603ceb8bc9f",
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
"gsd-core/workflows/ingest-docs.md": "c859921c811ac11b",
"gsd-core/workflows/insert-phase.md": "08dca838d831bb5b",
"gsd-core/workflows/list-phase-assumptions.md": "53dd4b69536c1bc3",
"gsd-core/workflows/list-seeds.md": "b687f2a843032d65",
"gsd-core/workflows/list-workspaces.md": "7f94e18b5c549453",
"gsd-core/workflows/manager.md": "3ba7a3e8213c4b23",
"gsd-core/workflows/map-codebase.md": "83d750aade709983",
"gsd-core/workflows/milestone-summary.md": "e0cdfddbd39a9043",
"gsd-core/workflows/mvp-phase.md": "344dd300e0cc1e74",
"gsd-core/workflows/new-milestone.md": "a616efb0f82d1a11",
"gsd-core/workflows/new-project.md": "fdb53bee6b4178ad",
"gsd-core/workflows/new-workspace.md": "017688423110ed66",
"gsd-core/workflows/next.md": "13fb800f2472d970",
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
"gsd-core/workflows/note.md": "5a99eb396c744619",
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
"gsd-core/workflows/plan-phase.md": "a886ba9bdf4d0baf",
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
"gsd-core/workflows/plan-review-convergence.md": "c635ecb8590a724e",
"gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede",
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
"gsd-core/workflows/profile-user.md": "e23bea0a69c0bb4b",
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
"gsd-core/workflows/quick.md": "1bc59c4d97fed876",
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "f3c2e894941c9943",
"gsd-core/workflows/scan.md": "003883d71c37da7d",
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "b57c947edee905bc",
"gsd-core/workflows/settings-integrations.md": "70515c5838fb9826",
"gsd-core/workflows/settings.md": "b2d39d972650f5f3",
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
"gsd-core/workflows/sketch-wrap-up.md": "86db87b16548117e",
"gsd-core/workflows/sketch.md": "e96f1866d3e60cab",
"gsd-core/workflows/smart-entry.md": "449238eb94abe187",
"gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f",
"gsd-core/workflows/spike-wrap-up.md": "d643447e16e95753",
"gsd-core/workflows/spike.md": "265e5c5a13ea0deb",
"gsd-core/workflows/stats.md": "76a42cbeaf6007c2",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "927e7eeefd2fcf5c",
"gsd-core/workflows/transition.md": "cb8ec5affb7ebba1",
"gsd-core/workflows/ui-phase.md": "49fa69e892423486",
"gsd-core/workflows/ui-review.md": "7acfc485526d064b",
"gsd-core/workflows/ultraplan-phase.md": "0fb8291153e3937d",
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "dc580dee13f881a6",
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
"gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c",
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
"hooks/gsd-check-update-worker.js": "6660bcf03ed849a0",
"hooks/gsd-check-update.js": "901ea3bd75fd5bbc",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "9f969c172d1614a9",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "84e6b584da9805c4",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "7d0344c1ef6dd0ab",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "c1056568acf6dfd2",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "635cbdc5cde5277b",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "49f1e3ea9e332097",
"package.json": "dbf8353f77358bc1",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
"scripts/changeset/lint.cjs": "0066faed159154f0",
"scripts/changeset/new.cjs": "4991e21fd17f5541",
"scripts/changeset/parse.cjs": "f9a949cbcab56445",
"scripts/changeset/render.cjs": "e47bc3e1587c3cae",
"scripts/changeset/serialize.cjs": "ac0b8fe6f87cdb0e",
"scripts/fix-slash-commands.cjs": "0519742531ff3529",
"scripts/gen-capability-registry.cjs": "c52201ff4d1c2cd7",
"scripts/gen-loop-host-contract.cjs": "c7f15237234811a0",
"scripts/lib/allowlist-ratchet.cjs": "ffaceaac3efc2660",
"scripts/lib/cli-exit.cjs": "612d0c372c75b7e7",
"skills/gsd-ns-context/SKILL.md": "654c71262d91650c",
"skills/gsd-ns-context/skills/docs-update/SKILL.md": "8fb602920a333d73",
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "6de7eeaaf4939e5d",
"skills/gsd-ns-context/skills/graphify/SKILL.md": "d54c2b44e8059299",
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "ce7f2382e4086b68",
"skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "40a31ad049374297",
"skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "c3f412d5a64f3921",
"skills/gsd-ns-ideate/SKILL.md": "4a0d2691054b23e5",
"skills/gsd-ns-ideate/skills/capture/SKILL.md": "c848255c134a860f",
"skills/gsd-ns-ideate/skills/explore/SKILL.md": "168a5b8d52127624",
"skills/gsd-ns-ideate/skills/sketch/SKILL.md": "11ef7d436090ae09",
"skills/gsd-ns-ideate/skills/spec-phase/SKILL.md": "052cfb21eb0ee3d4",
"skills/gsd-ns-ideate/skills/spike/SKILL.md": "3b5e6ea4bf8d81cc",
"skills/gsd-ns-manage/SKILL.md": "b6a1480f20b33bfa",
"skills/gsd-ns-manage/skills/cleanup/SKILL.md": "7579f085459dc393",
"skills/gsd-ns-manage/skills/config/SKILL.md": "2ae550cacdfc08a4",
"skills/gsd-ns-manage/skills/health/SKILL.md": "f766339e1de08cb6",
"skills/gsd-ns-manage/skills/help/SKILL.md": "93b961bb1f1b322b",
"skills/gsd-ns-manage/skills/inbox/SKILL.md": "0b5f4ce8ee98ac3c",
"skills/gsd-ns-manage/skills/manager/SKILL.md": "2f6228d719073d19",
"skills/gsd-ns-manage/skills/pause-work/SKILL.md": "c5454473ffeba05e",
"skills/gsd-ns-manage/skills/pr-branch/SKILL.md": "2976577caa538486",
"skills/gsd-ns-manage/skills/resume-work/SKILL.md": "b0d8628cc6d578ab",
"skills/gsd-ns-manage/skills/settings/SKILL.md": "8fcd8b04bb607ca7",
"skills/gsd-ns-manage/skills/ship/SKILL.md": "eb23c16b4893b035",
"skills/gsd-ns-manage/skills/stats/SKILL.md": "af60a0fed275a90b",
"skills/gsd-ns-manage/skills/surface/SKILL.md": "9bc5790f601615cf",
"skills/gsd-ns-manage/skills/thread/SKILL.md": "59e0ea339e45a105",
"skills/gsd-ns-manage/skills/undo/SKILL.md": "22d4462c987b7b66",
"skills/gsd-ns-manage/skills/update/SKILL.md": "01ca0efbdb8f6efa",
"skills/gsd-ns-manage/skills/workspace/SKILL.md": "c6c07625a373e6a3",
"skills/gsd-ns-manage/skills/workstreams/SKILL.md": "f4e54cb9b1ca0442",
"skills/gsd-ns-project/SKILL.md": "a58b3170197968be",
"skills/gsd-ns-project/skills/audit-milestone/SKILL.md": "a6eb7edcfd23c7e4",
"skills/gsd-ns-project/skills/complete-milestone/SKILL.md": "d7f6202222d1b26e",
"skills/gsd-ns-project/skills/import/SKILL.md": "0a734021e0b86481",
"skills/gsd-ns-project/skills/ingest-docs/SKILL.md": "0fadb4f9c69aa61d",
"skills/gsd-ns-project/skills/milestone-summary/SKILL.md": "c77f6c8ce7c57db9",
"skills/gsd-ns-project/skills/new-milestone/SKILL.md": "3aad804c48d77474",
"skills/gsd-ns-project/skills/new-project/SKILL.md": "e3cfd1fdbc2c3406",
"skills/gsd-ns-project/skills/profile-user/SKILL.md": "f57ad5e5c5899100",
"skills/gsd-ns-project/skills/review-backlog/SKILL.md": "1708aab6cb919223",
"skills/gsd-ns-review/SKILL.md": "7badceb8627d8154",
"skills/gsd-ns-review/skills/audit-fix/SKILL.md": "e005e777f3b607ef",
"skills/gsd-ns-review/skills/audit-uat/SKILL.md": "cb5f445d66e7f9c4",
"skills/gsd-ns-review/skills/code-review/SKILL.md": "8d3bc8093ac9a676",
"skills/gsd-ns-review/skills/debug/SKILL.md": "1b2864b06019554b",
"skills/gsd-ns-review/skills/eval-review/SKILL.md": "00b35a36a2f16f2f",
"skills/gsd-ns-review/skills/forensics/SKILL.md": "0169426d0dcda2db",
"skills/gsd-ns-review/skills/review/SKILL.md": "d80db8762bec97de",
"skills/gsd-ns-review/skills/secure-phase/SKILL.md": "56ee4246050ba8b5",
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "fabc841fb1f364cc",
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "444a4366f19899c3",
"skills/gsd-ns-review/skills/validate-phase/SKILL.md": "9e1c7909c089ee46",
"skills/gsd-ns-workflow/SKILL.md": "5a65f9fb93a71cec",
"skills/gsd-ns-workflow/skills/add-tests/SKILL.md": "303c01b7dd9c7614",
"skills/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "d30c04aeb1d09bb7",
"skills/gsd-ns-workflow/skills/autonomous/SKILL.md": "f1c614fed8f4d0a5",
"skills/gsd-ns-workflow/skills/discuss-phase/SKILL.md": "997ae1063e8c3703",
"skills/gsd-ns-workflow/skills/execute-phase/SKILL.md": "b2627b774725a381",
"skills/gsd-ns-workflow/skills/fast/SKILL.md": "0dc6926e16dcc1cb",
"skills/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "3455ea7562a447a2",
"skills/gsd-ns-workflow/skills/next/SKILL.md": "63659cd48a0276f9",
"skills/gsd-ns-workflow/skills/phase/SKILL.md": "b377b03d13db8573",
"skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "a453c632234623d1",
"skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "ded70bf0252c6307",
"skills/gsd-ns-workflow/skills/progress/SKILL.md": "fb24eab4a0e5f6a3",
"skills/gsd-ns-workflow/skills/quick/SKILL.md": "48629fef2b9ac5b0",
"skills/gsd-ns-workflow/skills/spec-phase/SKILL.md": "052cfb21eb0ee3d4",
"skills/gsd-ns-workflow/skills/ultraplan-phase/SKILL.md": "032c3412fddd02b4",
"skills/gsd-ns-workflow/skills/verify-work/SKILL.md": "a0609b334f4cb9aa"
}

View File

@@ -22,8 +22,6 @@ const fc = require('fast-check');
const {
evaluatePredicate,
evaluateCommandExitZero,
interpolate,
COMMAND_EXIT_ZERO_DEFAULT_TIMEOUT_MS,
COMMAND_MAX_OUTPUT_CHARS,
COMMAND_MAX_LENGTH,

View File

@@ -45,6 +45,8 @@ const {
getProjectInstructionFile,
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-name-policy.cjs'));
const registry = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'capability-registry.cjs'));
const { convertClaudeAgentToAntigravityAgent } = require('../bin/install.js');
// Run the installer as a subprocess with an isolated HOME so no install can
@@ -162,10 +164,15 @@ describe('#1928 gemini removed from every runtime-name-policy surface', () => {
assert.strictEqual(getRuntimeNewProjectCommand('gemini'), '/gsd-new-project', 'new-project override removed → default');
});
test('runtimeFlags has exactly 14 non-claude runtimes and no isGemini', () => {
test('runtimeFlags has no isGemini and covers exactly the non-claude registry runtimes (count-agnostic)', () => {
const flags = runtimeFlags('claude');
assert.ok(!('isGemini' in flags), 'isGemini flag must be gone');
assert.strictEqual(Object.keys(flags).length, 14, 'flag count drops 15 → 14 with gemini removed');
// The flag set tracks the non-claude registry runtimes (one is<Runtime> per
// id), so adding a runtime updates the count automatically — no hand-pinned
// number that would break on the next runtime addition.
const expectedNonClaudeCount = Object.keys(registry.runtimes).filter((id) => id !== 'claude').length;
assert.strictEqual(Object.keys(flags).length, expectedNonClaudeCount,
'flag count must equal the non-claude registry runtime count');
});
test('gemini no longer maps to GEMINI.md (defaults to AGENTS.md)', () => {

View File

@@ -1,17 +1,20 @@
'use strict';
/**
* Drift-guard: getDirName must be derived from the capability registry.
* Verifies:
* 1. For every known runtime id, getDirName(id) equals the hardcoded golden
* expected map — a pinned oracle that catches BOTH formula bugs AND
* unintended registry drift (adding/removing a runtime or changing its
* localConfigDir forces a deliberate golden-map update here).
* 2. getDirName('unknown') and getDirName('') fall back to '.claude'.
* 3. Every registry runtime entry has a non-empty dot-dir localConfigDir string —
* cross-check from a different angle than the production derivation formula.
* Property test: getDirName is a pure projection of each runtime descriptor's
* `localConfigDir`. Because 1.7.0 (ADR-1016 / ADR-1239) makes runtimes
* pluggable data descriptors, this test asserts the DERIVATION CONTRACT —
* `getDirName(id) === registry.runtimes[id].runtime.localConfigDir` — for
* EVERY runtime currently in the registry, rather than pinning a frozen
* per-runtime golden snapshot that would have to be hand-edited every time a
* runtime is added or removed. Adding a new runtime descriptor requires zero
* changes here; if the derivation breaks for any runtime, this fails loudly.
*
* ADR-1239 Phase B (#1679).
* Behavioral tests only: assert on returned values, no source-grep.
* Also covers:
* - the fail-closed fallback (`getDirName('unknown')` / `getDirName('')` → '.claude');
* - a structural cross-check that every descriptor's localConfigDir is a
* non-empty dot-dir string.
*
* ADR-1239 Phase B (#1679). Behavioral tests only: assert on returned values.
*/
const { test } = require('node:test');
@@ -21,59 +24,26 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const { getDirName } = runtimeNamePolicy;
// Golden oracle: hardcoded expected map of all 15 runtime ids to their local config dir.
// A pinned expected value in a TEST is correct — the test IS the oracle (non-circular).
// Only PRODUCTION code should derive dynamically from the registry.
// If this map diverges from getDirName output, either the formula is wrong
// OR the registry changed — both require a deliberate golden-map update here.
const GOLDEN_DIR_MAP = {
claude: '.claude',
copilot: '.github',
opencode: '.opencode',
kilo: '.kilo',
codex: '.codex',
antigravity: '.agents',
cursor: '.cursor',
windsurf: '.windsurf',
augment: '.augment',
trae: '.trae',
qwen: '.qwen',
hermes: '.hermes',
kimi: '.kimi-code',
codebuddy: '.codebuddy',
cline: '.cline',
};
const RUNTIME_IDS = Object.keys(registry.runtimes);
test('getDirName: golden map matches for all 15 known runtime ids', () => {
for (const [id, expected] of Object.entries(GOLDEN_DIR_MAP)) {
const actual = getDirName(id);
test('getDirName(id) projects each descriptor runtime.localConfigDir (derivation contract, count-agnostic)', () => {
assert.ok(RUNTIME_IDS.length > 0, 'registry must contain at least one runtime');
for (const id of RUNTIME_IDS) {
const desc = registry.runtimes[id] && registry.runtimes[id].runtime;
const expected = desc && desc.localConfigDir;
assert.ok(typeof expected === 'string' && expected.length > 0,
`registry.runtimes['${id}'].runtime.localConfigDir must be a non-empty string`);
assert.strictEqual(
actual,
getDirName(id),
expected,
`getDirName('${id}') diverged from golden.\n` +
` actual: ${JSON.stringify(actual)}\n` +
` expected: ${JSON.stringify(expected)}`,
);
`getDirName('${id}') must equal the descriptor localConfigDir '${expected}'`);
}
});
test('drift guard: registry runtime id set EXACTLY equals the golden map (adding/removing a runtime forces a golden update)', () => {
// Without this, a newly-added runtime would pass (its value never checked) and
// removing `claude` could pass via the .claude fallback. Pin the set both ways.
const registryIds = Object.keys(registry.runtimes).sort();
const goldenIds = Object.keys(GOLDEN_DIR_MAP).sort();
assert.deepEqual(registryIds, goldenIds,
'registry.runtimes id set must exactly match GOLDEN_DIR_MAP — update the golden map when adding/removing a runtime');
});
test('getDirName fallback: unknown runtime returns ".claude"', () => {
assert.strictEqual(getDirName('unknown'), '.claude',
'getDirName("unknown") must return ".claude" (default fallback)');
});
test('getDirName fallback: empty string returns ".claude"', () => {
assert.strictEqual(getDirName(''), '.claude',
'getDirName("") must return ".claude" (empty-input fallback)');
test('getDirName fallback: unknown / empty runtime returns ".claude" (fail-closed)', () => {
assert.strictEqual(getDirName('unknown'), '.claude');
assert.strictEqual(getDirName(''), '.claude');
assert.strictEqual(getDirName('__nonexistent_runtime__'), '.claude');
});
test('registry cross-check: every runtimes[id].runtime.localConfigDir is a non-empty dot-dir string', () => {

View File

@@ -46,6 +46,7 @@ const GOLDEN_FRAGMENT_MAP = {
codebuddy: "'.codebuddy'",
cline: "'.cline'",
kimi: "'.config', 'agents'",
zcode: "'.zcode'",
};
// Runtimes intentionally NOT in the table: claude is the default; antigravity is

View File

@@ -423,6 +423,7 @@ function resetRuntimeWarningCaches() {
const modelResolver = require('../gsd-core/bin/lib/model-resolver.cjs');
configLoader._resetRuntimeWarningCacheForTests();
modelResolver._resetModelPolicyWarningCacheForTests();
modelResolver._resetModelOverrideWarningCacheForTests();
}
module.exports = { runGsdTools, createTempDir, createTempProject, createTempGitProject, cleanup, parseFrontmatter, isUsageOutput, captureConsole, toPosixPath, runNpm, isolatedNpmEnv, withIsolatedProcessState, delay, waitFor, resetRuntimeWarningCaches, TOOLS_PATH };

View File

@@ -56,6 +56,7 @@ const RUNTIME_META = {
qwen: { localDir: '.qwen', globalSuffix: '.qwen' },
trae: { localDir: '.trae', globalSuffix: '.trae' },
windsurf: { localDir: '.windsurf', globalSuffix: path.join('.codeium', 'windsurf') },
zcode: { localDir: '.zcode', globalSuffix: '.zcode' },
};
// Runtimes that emit per-skill files under skills/ (not rules-based or commands-based)

View File

@@ -1,7 +1,7 @@
'use strict';
/**
* ADR-1239 Phase A: Descriptor tests — validate that all 15 role:runtime
* ADR-1239 Phase A: Descriptor tests — validate that all 16 role:runtime
* capability descriptors have correct hostIntegration axes, pass the validator,
* and negotiate correctly via the host-integration module.
*
@@ -31,16 +31,15 @@ const SCALAR_AXES = ['embeddingMode', 'commandSurface', 'modelMode', 'hookBus',
// All 6 dispatch sub-keys (includes backgroundDispatch added in feat/1679-dispatch-flatten)
const DISPATCH_KEYS = ['namedDispatch', 'nested', 'maxDepth', 'background', 'subagentToolkit', 'backgroundDispatch'];
// All 15 runtime IDs (ordered alphabetically)
const RUNTIME_IDS = [
'antigravity', 'augment', 'claude', 'cline', 'codebuddy',
'codex', 'copilot', 'cursor', 'hermes',
'kilo', 'kimi', 'opencode', 'qwen', 'trae', 'windsurf',
];
// Runtime ids are derived from the registry (the single source of truth) so the
// suite stays fluid when a runtime descriptor is added or removed. The profile
// and flatten maps below remain CURATED pins — they catch an accidental axis
// flip (e.g. a descriptor changing embeddingMode silently moves its profile).
const RUNTIME_IDS = Object.keys(registry.runtimes);
// Contract-pinned profile split (derived from .host-cli-final.json):
// programmatic-cli: claude, cline, cursor, hermes, kilo, kimi, opencode, qwen, trae (9)
// declarative-cli: antigravity, augment, codebuddy, codex, copilot, windsurf (6)
// declarative-cli: antigravity, augment, codebuddy, codex, copilot, windsurf, zcode (7)
// ide: 0
const EXPECTED_PROFILES = {
claude: 'programmatic-cli',
@@ -58,22 +57,26 @@ const EXPECTED_PROFILES = {
codex: 'declarative-cli',
copilot: 'declarative-cli',
windsurf: 'declarative-cli',
zcode: 'declarative-cli',
};
describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
// ─── Registry shape ──────────────────────────────────────────────────────────
test('registry.runtimes contains all 15 expected runtime ids', () => {
test('registry.runtimes exactly equals the curated RUNTIME_IDS set (count-agnostic)', () => {
// RUNTIME_IDS is derived from the registry above, so this asserts internal
// consistency: the curated profile/flatten maps cover every registry runtime
// exactly once, no matter how many exist.
for (const id of RUNTIME_IDS) {
assert.ok(
Object.prototype.hasOwnProperty.call(registry.runtimes, id),
'registry.runtimes must contain "' + id + '"',
);
}
assert.strictEqual(
Object.keys(registry.runtimes).length,
15,
'registry.runtimes must have exactly 15 entries',
assert.deepStrictEqual(
Object.keys(registry.runtimes).sort(),
[...RUNTIME_IDS].sort(),
'registry.runtimes key set must match RUNTIME_IDS',
);
});
@@ -208,7 +211,10 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
// ─── Contract-pin profile split ───────────────────────────────────────────────
test('contract-pin: exactly 9 programmatic-cli, 6 declarative-cli, 0 ide', () => {
test('contract-pin: profile split is internally consistent with EXPECTED_PROFILES (count-agnostic)', () => {
// The counts are DERIVED from the curated EXPECTED_PROFILES map rather than
// hand-pinned, so adding a runtime + its profile entry updates the counts
// automatically. ide must remain 0 (no installed ide-profile host yet).
const counts = { 'programmatic-cli': 0, 'declarative-cli': 0, 'ide': 0 };
for (const id of RUNTIME_IDS) {
const cap = registry.runtimes[id];
@@ -216,13 +222,20 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
assert.ok(hi, id + ': hostIntegration must exist for profile count');
const profile = profileOf(hi);
assert.ok(profile !== null, id + ': profileOf must be non-null');
assert.strictEqual(profile, EXPECTED_PROFILES[id],
id + ': profileOf must match EXPECTED_PROFILES (an axis may have flipped)');
if (counts[profile] !== undefined) {
counts[profile]++;
}
}
assert.strictEqual(counts['programmatic-cli'], 9, 'Must have exactly 9 programmatic-cli runtimes');
assert.strictEqual(counts['declarative-cli'], 6, 'Must have exactly 6 declarative-cli runtimes');
assert.strictEqual(counts['ide'], 0, 'Must have exactly 0 ide runtimes');
// Derived expected counts from the curated map itself.
const expectedCounts = { 'programmatic-cli': 0, 'declarative-cli': 0, 'ide': 0 };
for (const p of Object.values(EXPECTED_PROFILES)) {
if (expectedCounts[p] !== undefined) expectedCounts[p]++;
}
assert.strictEqual(counts['programmatic-cli'], expectedCounts['programmatic-cli']);
assert.strictEqual(counts['declarative-cli'], expectedCounts['declarative-cli']);
assert.strictEqual(counts['ide'], 0, 'no installed host may carry the ide profile yet');
});
// ─── backgroundDispatch presence ─────────────────────────────────────────────
@@ -249,7 +262,7 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
// ─── shouldFlattenDispatch per-host (#853 discriminator) ─────────────────────
// Expected: false (may background) for codex and cursor ONLY; true (must inline) for the other 14.
// Expected: false (may background) for codex and cursor ONLY; true (must inline) for the other 15.
const EXPECTED_FLATTEN = {
antigravity: true,
augment: true,
@@ -266,6 +279,7 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
qwen: true,
trae: true,
windsurf: true,
zcode: true,
};
for (const id of RUNTIME_IDS) {
@@ -282,17 +296,20 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
});
}
test('contract-pin: exactly 2 hosts are background-eligible (shouldFlattenDispatch === false): codex and cursor', () => {
test('contract-pin: background-eligible set matches EXPECTED_FLATTEN (count-agnostic)', () => {
// The eligible set is DERIVED from the curated EXPECTED_FLATTEN map rather
// than hand-pinned to a fixed pair, so a runtime whose dispatch axes change
// updates the expectation automatically.
const expectedEligible = RUNTIME_IDS
.filter((id) => EXPECTED_FLATTEN[id] === false)
.sort();
const eligible = RUNTIME_IDS.filter((id) => {
const cap = registry.runtimes[id];
const dispatch = cap && cap.runtime && cap.runtime.hostIntegration && cap.runtime.hostIntegration.dispatch;
return dispatch && shouldFlattenDispatch(dispatch) === false;
});
assert.deepEqual(
eligible.slice().sort(),
['codex', 'cursor'],
'Exactly codex and cursor must be background-eligible, got: ' + JSON.stringify(eligible.sort()),
);
}).sort();
assert.deepEqual(eligible, expectedEligible,
'background-eligible set must match EXPECTED_FLATTEN (a dispatch axis may have flipped)');
});
test('contract-pin: spot-check claude→programmatic-cli, codex→declarative-cli, opencode→programmatic-cli, windsurf→declarative-cli', () => {

View File

@@ -40,6 +40,7 @@ const RUNTIME_INSTALL_CONTRACTS = {
qwen: { surface: 'flat-skills', settings: true, packageJson: true },
trae: { surface: 'flat-skills', settings: false, packageJson: false },
windsurf: { surface: 'global-artifacts-noop', settings: false, packageJson: false },
zcode: { surface: 'flat-skills', settings: false, packageJson: true },
};
function sha256(content) {

View File

@@ -0,0 +1,176 @@
'use strict';
// #1575 — Golden-parity harness (ADR-1235 §0).
//
// Asserts that the surface path (applySurface) produces byte-for-byte identical
// agent output to the install path (installRuntimeArtifacts) for every
// descriptor-driven runtime. Both paths run against the SAME configDir so
// pathPrefix, attribution, and converter outputs match.
//
// The harness:
// 1. installRuntimeArtifacts(runtime, configDir, 'global', profile, resolveAttribution)
// 2. Snapshot every gsd-* agent file in configDir/agents/
// 3. applySurface(configDir, layout, manifest, ..., opts)
// 4. Compare every agent file byte-for-byte: snapshot === current
process.env.GSD_TEST_MODE = '1';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const ROOT = path.join(__dirname, '..');
const COMMANDS_GSD = path.join(ROOT, 'commands', 'gsd');
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
const { applySurface } = require('../gsd-core/bin/lib/surface.cjs');
const { loadSkillsManifest, resolveProfile } = require('../gsd-core/bin/lib/install-profiles.cjs');
const { resolveRuntimeArtifactLayout } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs');
const { cleanup } = require('./helpers.cjs');
// The 7 descriptor-driven agent runtimes (cline deferred per code comment:
// rules-only local branch + local/global complication).
const DESCRIPTOR_RUNTIMES = [
'cursor',
'windsurf',
'augment',
'trae',
'codebuddy',
'copilot',
'antigravity',
];
function snapshotAgents(agentsDir) {
const snap = new Map();
if (!fs.existsSync(agentsDir)) return snap;
for (const name of fs.readdirSync(agentsDir)) {
if (!name.startsWith('gsd-')) continue;
if (!name.endsWith('.md') && !name.endsWith('.agent.md')) continue;
snap.set(name, fs.readFileSync(path.join(agentsDir, name), 'utf8'));
}
return snap;
}
// Shared manifest + profile so both paths see the same source agents.
const manifest = loadSkillsManifest(COMMANDS_GSD);
const profile = resolveProfile({ modes: ['full'], manifest });
// Same attribution resolver for both paths (undefined → no Co-Authored-By mutation).
const resolveAttribution = () => undefined;
describe('#1575 — golden-parity: surface path matches install path for descriptor-driven agents', () => {
for (const runtime of DESCRIPTOR_RUNTIMES) {
test(`${runtime}: surface agents byte-identical to install agents`, (t) => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-1575-${runtime}-`));
t.after(() => { try { cleanup(configDir); } catch { /* best-effort */ } });
// Step 1: install path writes agents
installRuntimeArtifacts(runtime, configDir, 'global', profile, resolveAttribution);
// Step 2: snapshot agent files
const agentsDir = path.join(configDir, 'agents');
const installSnap = snapshotAgents(agentsDir);
assert.ok(installSnap.size > 0, `${runtime}: install must produce at least one gsd-* agent`);
// Step 3: surface path re-materializes into the SAME configDir
const layout = resolveRuntimeArtifactLayout(runtime, configDir, 'global');
applySurface(configDir, layout, manifest, undefined, undefined, { resolveAttribution });
// Step 4: compare byte-for-byte
const surfaceSnap = snapshotAgents(agentsDir);
// File lists must match
const installFiles = [...installSnap.keys()].sort();
const surfaceFiles = [...surfaceSnap.keys()].sort();
assert.deepEqual(
surfaceFiles,
installFiles,
`${runtime}: file lists must match after surface. Install: [${installFiles.join(', ')}] Surface: [${surfaceFiles.join(', ')}]`,
);
// Content must match byte-for-byte
for (const [fileName, installContent] of installSnap) {
const surfaceContent = surfaceSnap.get(fileName);
assert.strictEqual(
surfaceContent,
installContent,
`${runtime}/${fileName}: surface content must be byte-identical to install content`,
);
}
test('cursor with non-undefined attribution: surface agents byte-identical to install agents (M2 coverage)', (t) => {
// M2 regression guard: verify parity holds when resolveAttribution returns
// a real value. Source agents don't carry Co-Authored-By, so processAttribution
// is a no-op (it replaces existing lines, doesn't add new ones). But this test
// proves the agentCtx threading is correct for both paths regardless.
const attrResolver = () => 'Test Bot <test@example.com>';
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1575-attr-'));
t.after(() => { try { cleanup(configDir); } catch { /* best-effort */ } });
installRuntimeArtifacts('cursor', configDir, 'global', profile, attrResolver);
const agentsDir = path.join(configDir, 'agents');
const installSnap = snapshotAgents(agentsDir);
assert.ok(installSnap.size > 0, 'install must produce agents');
const layout = resolveRuntimeArtifactLayout('cursor', configDir, 'global');
applySurface(configDir, layout, manifest, undefined, undefined, { resolveAttribution: attrResolver });
const surfaceSnap = snapshotAgents(agentsDir);
for (const [fileName, installContent] of installSnap) {
assert.strictEqual(surfaceSnap.get(fileName), installContent,
`cursor/${fileName}: content must be byte-identical with non-undefined attribution`);
}
});
});
}
test('copilot: agents installed as .agent.md (filename rename parity)', (t) => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1575-copilot-rename-'));
t.after(() => { try { cleanup(configDir); } catch { /* best-effort */ } });
installRuntimeArtifacts('copilot', configDir, 'global', profile, resolveAttribution);
const agentsDir = path.join(configDir, 'agents');
assert.ok(fs.existsSync(agentsDir), 'copilot agents dir must exist');
const agentFiles = fs.readdirSync(agentsDir).filter((f) => f.startsWith('gsd-'));
assert.ok(agentFiles.length > 0, 'copilot must have installed agents');
assert.ok(
agentFiles.every((f) => f.endsWith('.agent.md')),
`copilot agents must be .agent.md, got: [${agentFiles.slice(0, 3).join(', ')}]`,
);
});
});
describe('#1575 — surface path: no prune data-loss over pre-existing legacy agents', () => {
test('pre-existing gsd-* agents not in staged set are pruned; user agents preserved', (t) => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1575-prune-'));
t.after(() => { try { cleanup(configDir); } catch { /* best-effort */ } });
// Seed a pre-existing legacy .agent.md (simulating a prior install)
const agentsDir = path.join(configDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
fs.writeFileSync(path.join(agentsDir, 'gsd-old-defunct.agent.md'), '# Old\n');
fs.writeFileSync(path.join(agentsDir, 'user-custom.md'), '# User\n');
// Install (should prune stale gsd-*, preserve user agents)
installRuntimeArtifacts('copilot', configDir, 'global', profile, resolveAttribution);
const afterInstall = fs.readdirSync(agentsDir);
assert.ok(!afterInstall.includes('gsd-old-defunct.agent.md'), 'stale gsd-* agent must be pruned');
assert.ok(afterInstall.includes('user-custom.md'), 'user agent must be preserved');
// Now surface over the install — must converge to the same state
const layout = resolveRuntimeArtifactLayout('copilot', configDir, 'global');
applySurface(configDir, layout, manifest, undefined, undefined, { resolveAttribution });
const afterSurface = fs.readdirSync(agentsDir);
// Same set of agent files as after install
const installAgents = afterInstall.filter((f) => f.startsWith('gsd-')).sort();
const surfaceAgents = afterSurface.filter((f) => f.startsWith('gsd-')).sort();
assert.deepEqual(surfaceAgents, installAgents, 'surface must converge to same agent set as install');
assert.ok(afterSurface.includes('user-custom.md'), 'user agent still preserved after surface');
});
});

View File

@@ -3770,6 +3770,190 @@ describe('#49 resolveModelPolicy: prototype-pollution guards', () => {
});
});
// ─── #2041: model_overrides Claude full ID → Agent-tool alias on claude runtime ─
//
// Mirrors the #1133 model_policy alias-mapping tests (above) for the
// model_overrides path. Bug: a full Claude model ID in model_overrides
// (e.g. "claude-sonnet-5") was returned VERBATIM on the claude runtime and
// handed to the Claude Agent tool, whose typed `model` parameter documents only
// tier aliases (opus/sonnet/haiku/fable). The model_policy path already maps
// full IDs → aliases via CLAUDE_POLICY_ID_TO_ALIAS (#1144); model_overrides
// skipped that mapping entirely. The fix mirrors #1144 on the override path.
// Non-Claude runtimes and non-Claude values pass through verbatim (parity).
describe('#2041 model_overrides: Claude full ID → alias on claude runtime', () => {
let tmpDir;
beforeEach(() => {
tmpDir = makeTmp('2041');
resetRuntimeWarningCaches();
});
afterEach(() => {
rmr(tmpDir);
resetRuntimeWarningCaches();
});
// AC1 + AC2: mappable Claude full IDs resolve to their aliases on claude runtime
test('model_overrides claude-sonnet-5 → "sonnet" on runtime:claude (resolveModelInternal)', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-executor': 'claude-sonnet-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-executor'), 'sonnet');
});
test('model_overrides claude-opus-4-8 → "opus" on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-planner': 'claude-opus-4-8' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'opus');
});
test('model_overrides claude-haiku-4-5 → "haiku" on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-codebase-mapper': 'claude-haiku-4-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-codebase-mapper'), 'haiku');
});
test('model_overrides claude-fable-5 → "fable" on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-planner': 'claude-fable-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'fable');
});
// AC3: bare aliases pass through verbatim
test('model_overrides bare "sonnet" alias passes through verbatim on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-executor': 'sonnet' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-executor'), 'sonnet');
});
test('model_overrides bare "fable" alias passes through verbatim on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-planner': 'fable' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'fable');
});
// AC1 (implicit claude): mapping fires when runtime key is absent (defaults to claude)
test('model_overrides claude-sonnet-5 → "sonnet" with implicit claude runtime (no runtime key)', () => {
writeConfig(tmpDir, {
model_overrides: { 'gsd-executor': 'claude-sonnet-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-executor'), 'sonnet');
});
// AC4: non-claude runtimes keep full IDs verbatim (parity with model_policy path)
test('model_overrides claude-sonnet-5 → verbatim ID on non-claude runtime (opencode)', () => {
writeConfig(tmpDir, {
runtime: 'opencode',
model_overrides: { 'gsd-executor': 'claude-sonnet-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-executor'), 'claude-sonnet-5');
});
// AC5: unmappable Claude full ID warns once + falls through to tier alias
test('model_overrides unmappable claude ID (claude-opus-4-5) falls through to tier alias on claude', () => {
resetRuntimeWarningCaches();
writeConfig(tmpDir, {
runtime: 'claude',
model_profile: 'balanced',
model_overrides: { 'gsd-planner': 'claude-opus-4-5' },
});
// gsd-planner balanced → opus tier; claude-opus-4-5 has no alias → warn + fall through → 'opus'
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'opus');
});
test('model_overrides unmappable claude ID emits a stderr warning exactly once (dedupe)', () => {
resetRuntimeWarningCaches();
writeConfig(tmpDir, {
runtime: 'claude',
model_profile: 'balanced',
model_overrides: { 'gsd-planner': 'claude-opus-4-5' },
});
const writes = [];
const original = process.stderr.write.bind(process.stderr);
process.stderr.write = (chunk) => { writes.push(String(chunk)); return true; };
try {
resolveModelInternal(tmpDir, 'gsd-planner');
resolveModelInternal(tmpDir, 'gsd-planner'); // second call — dedupe must suppress
} finally {
process.stderr.write = original;
}
const warnings = writes.filter((w) => w.includes('model_overrides') && w.includes('claude-opus-4-5'));
assert.strictEqual(warnings.length, 1,
`expected exactly one override warning, got ${warnings.length}: ${JSON.stringify(writes)}`);
});
// AC6: resolveModelForTier (escalation / --attempt path) maps the same way
test('resolveModelForTier maps claude-sonnet-5 → "sonnet" on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-executor': 'claude-sonnet-5' },
});
assert.strictEqual(resolveModelForTier(tmpDir, 'gsd-executor', 0), 'sonnet');
});
test('resolveModelForTier keeps full ID verbatim on non-claude runtime', () => {
writeConfig(tmpDir, {
runtime: 'opencode',
model_overrides: { 'gsd-executor': 'claude-sonnet-5' },
});
assert.strictEqual(resolveModelForTier(tmpDir, 'gsd-executor', 0), 'claude-sonnet-5');
});
// MEDIUM-1 (review): exercise the unmappable-override fall-through branch in
// resolveModelForTier (closes the mutation-score gap — a future refactor that
// accidentally returned the verbatim override instead of falling through
// would otherwise survive the suite).
test('resolveModelForTier unmappable claude ID falls through to tier alias on claude', () => {
resetRuntimeWarningCaches();
writeConfig(tmpDir, {
runtime: 'claude',
model_profile: 'balanced',
model_overrides: { 'gsd-planner': 'claude-opus-4-5' },
});
// unmappable override → fall through → no dynamic_routing → resolveModelInternal → 'opus'
assert.strictEqual(resolveModelForTier(tmpDir, 'gsd-planner', 0), 'opus');
});
// LOW-2 (review): pin the case-sensitive contract — a case-variant like
// "Claude-Sonnet-5" is NOT mapped (alias keys are case-sensitive, matching
// the model_policy path and the Claude API).
test('model_overrides case-variant "Claude-Sonnet-5" passes through verbatim (case-sensitive contract)', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-executor': 'Claude-Sonnet-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-executor'), 'Claude-Sonnet-5');
});
// Regression guard: non-Claude custom / vendor values still pass through verbatim
// on the claude runtime (the fix must NOT touch values that aren't Claude IDs).
test('model_overrides non-Claude custom model passes through verbatim on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-planner': 'my-custom-model' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'my-custom-model');
});
test('model_overrides non-Claude vendor ID (openai/gpt-5) passes through verbatim on runtime:claude', () => {
writeConfig(tmpDir, {
runtime: 'claude',
model_overrides: { 'gsd-executor': 'openai/gpt-5' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-executor'), 'openai/gpt-5');
});
});
// ─── resolveModelForTier: model_policy beats dynamic_routing ─────────────────
describe('#49 resolveModelForTier: model_policy beats dynamic_routing', () => {

View File

@@ -13,6 +13,8 @@
* removed from GSD (Antigravity CLI is the successor). The runtime option
* numbering below is renumbered accordingly — option 9 (formerly gemini) is
* now hermes, and the "All" shortcut moved from 17 to 16.
*
* #1925: ZCode (Z.ai) added as option 16; the "All" shortcut moved from 16 to 17.
*/
process.env.GSD_TEST_MODE = '1';
@@ -86,23 +88,27 @@ describe('multi-runtime selection parsing', () => {
assert.deepStrictEqual(parseRuntimeInput('15'), ['windsurf']);
});
test('single choice for zcode', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), ['zcode']);
});
test('single choice for kimi', () => {
assert.deepStrictEqual(parseRuntimeInput('10'), ['kimi']);
});
test('choice 16 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), allRuntimes);
test('choice 17 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
});
test('choice 16 returns all runtimes when mixed with separators or other tokens', () => {
// CR feedback: tokenized inputs that include 16 (e.g. trailing comma, or
test('choice 17 returns all runtimes when mixed with separators or other tokens', () => {
// CR feedback: tokenized inputs that include 17 (e.g. trailing comma, or
// alongside other choices) must still expand to all-runtimes — previously
// only the bare all-runtimes option matched, so "16," or "16 1" silently installed a
// only the bare all-runtimes option matched, so "17," or "17 1" silently installed a
// subset.
assert.deepStrictEqual(parseRuntimeInput('16,'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('16 1'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('1,16'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput(' 16 '), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('17,'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('17 1'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('1,17'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput(' 17 '), allRuntimes);
});
test('empty input defaults to claude', () => {
@@ -111,13 +117,13 @@ describe('multi-runtime selection parsing', () => {
});
test('invalid choices are ignored, falls back to claude if all invalid', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('18'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('0'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('abc'), ['claude']);
});
test('invalid choices mixed with valid are filtered out', () => {
assert.deepStrictEqual(parseRuntimeInput('1,17,7'), ['claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('1,18,7'), ['claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('abc 3 xyz'), ['augment']);
});
@@ -149,11 +155,12 @@ describe('install.js exports multi-select runtime metadata', () => {
'13': 'qwen',
'14': 'trae',
'15': 'windsurf',
'16': 'zcode',
};
const expectedRuntimes = [
'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex',
'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode',
'qwen', 'trae', 'windsurf',
'qwen', 'trae', 'windsurf', 'zcode',
];
test('runtimeMap exports every option key bound to the right runtime', () => {
@@ -170,14 +177,18 @@ describe('install.js exports multi-select runtime metadata', () => {
'allRuntimes has no duplicates');
});
test('"All" shortcut (option 16) selects every runtime', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), allRuntimes);
test('"All" shortcut (option 17) selects every runtime', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
});
test('--kimi flag selects Kimi without interactive prompt', () => {
assert.deepStrictEqual(selectRuntimesFromArgs(['--kimi']), ['kimi']);
});
test('--zcode flag selects ZCode without interactive prompt', () => {
assert.deepStrictEqual(selectRuntimesFromArgs(['--zcode']), ['zcode']);
});
test('--all flag includes Kimi exactly once', () => {
const selected = selectRuntimesFromArgs(['--all']);
assert.ok(selected.includes('kimi'), '--all includes kimi');
@@ -185,7 +196,14 @@ describe('install.js exports multi-select runtime metadata', () => {
'--all includes kimi exactly once');
});
test('prompt lists Hermes Agent (9), Kimi (10), Qwen Code (13), Trae (14), and All (16)', () => {
test('--all flag includes ZCode exactly once', () => {
const selected = selectRuntimesFromArgs(['--all']);
assert.ok(selected.includes('zcode'), '--all includes zcode');
assert.strictEqual(selected.filter((runtime) => runtime === 'zcode').length, 1,
'--all includes zcode exactly once');
});
test('prompt lists ZCode (16), and All (17)', () => {
const prompt = stripAnsi(buildRuntimePromptText());
assert.ok(/\b9\)\s*Hermes Agent\b/.test(prompt),
'prompt lists Hermes Agent as option 9');
@@ -197,8 +215,10 @@ describe('install.js exports multi-select runtime metadata', () => {
'prompt lists Qwen Code as option 13');
assert.ok(/\b14\)\s*Trae\b/.test(prompt),
'prompt lists Trae as option 14');
assert.ok(/\b16\)\s*All\b/.test(prompt),
'prompt lists All as option 16');
assert.ok(/\b16\)\s*ZCode\b/.test(prompt),
'prompt lists ZCode as option 16');
assert.ok(/\b17\)\s*All\b/.test(prompt),
'prompt lists All as option 17');
});
test('prompt does not list Gemini (removed #1928)', () => {

View File

@@ -0,0 +1,54 @@
'use strict';
// Regression guard for #2020 — dead SDK file references (sdk/src/..., sdk/dist/...)
// in runtime-loaded markdown cause AI runtimes to `find` them; on Git Bash for
// Windows `find /` traverses the whole drive (14h+, orphaned find.exe, 4M+ handles).
// The SDK package was retired (ADR-0174), so these paths never resolve.
//
// Scans the markdown a runtime loads + tries to locate references in
// (agents/, workflows/, references/) and fails on any sdk/(src|dist|handlers)
// file-path reference. Code-comment mentions in *.cjs (historical prose, not
// locatable file refs) are out of scope.
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
// Runtime-loaded markdown surfaces (the issue is about references a runtime
// tries to LOCATE as files — agents/workflows/references, not source comments).
const SCAN_DIRS = ['agents', 'gsd-core/workflows', 'gsd-core/references'];
// A dead SDK file-path reference: sdk/src|sdk/dist|sdk/handlers followed by a path.
const DEAD_SDK_REF = /sdk\/(?:src|dist|handlers)\//;
function walkMd(dir, out = []) {
let entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
catch { return out; }
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) walkMd(full, out);
else if (e.isFile() && e.name.endsWith('.md')) out.push(full);
}
return out;
}
describe('#2020 — no dead SDK file references in runtime-loaded markdown', () => {
const offenders = [];
for (const rel of SCAN_DIRS) {
const absDir = path.join(ROOT, rel);
for (const file of walkMd(absDir)) {
const content = fs.readFileSync(file, 'utf8');
const lines = content.split(/\r?\n/);
lines.forEach((line, i) => {
if (DEAD_SDK_REF.test(line)) offenders.push(`${path.relative(ROOT, file)}:${i + 1}`);
});
}
}
test('agents/workflows/references contain no sdk/src|sdk/dist|sdk/handlers references', () => {
assert.deepEqual(offenders, [],
`Dead SDK file references found (runtimes \`find\` these → #2020 Windows find.exe storm):\n${offenders.join('\n')}`);
});
});

View File

@@ -27,14 +27,13 @@ const { getDirName } = runtimeNamePolicy;
// Golden oracle: hardcoded sorted known-good list of all non-Claude runtimes.
// A pinned expected value in a TEST is correct — the test IS the oracle.
// Only PRODUCTION code should derive dynamically from the registry.
// If this list diverges from NON_CLAUDE_RUNTIMES, either the formula is wrong
// OR the registry changed — both require a deliberate golden-list update here.
const EXPECTED = [
'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot',
'cursor', 'hermes', 'kilo', 'kimi', 'opencode', 'qwen',
'trae', 'windsurf',
];
// EXPECTED is DERIVED from the capability registry (the single source of truth)
// so this guard stays fluid when a runtime is added or removed. The contract
// being pinned is the DERIVATION (NON_CLAUDE_RUNTIMES === registry runtimes −
// claude), not a frozen per-runtime snapshot.
const EXPECTED = Object.keys(registry.runtimes)
.filter((id) => id !== 'claude')
.sort();
test('NON_CLAUDE_RUNTIMES matches the golden expected set (sorted)', () => {
assert.deepEqual(

View File

@@ -2530,6 +2530,53 @@ describe('phase complete command', () => {
assert.ok(roadmap.includes('completed'), 'completion date should be added');
});
test('#2012 — Progress row updated even when an earlier phase-numbered table precedes ## Progress', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
`# Roadmap
- [ ] Phase 3: Build
### Phase 3: Build
**Goal:** Build stuff
## Requirements Coverage
| Phase | Requirements | Count |
|-------|-------------|-------|
| 3. Build | R-01 | 5 |
## Progress
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 3. Build | v1.0 | 0/1 | Planned | - |
`
);
fs.writeFileSync(
path.join(tmpDir, '.planning', 'STATE.md'),
`# State\n\n**Current Phase:** 03\n**Status:** In progress\n**Last Activity:** 2025-01-01\n`
);
const p3 = path.join(tmpDir, '.planning', 'phases', '03-build');
fs.mkdirSync(p3, { recursive: true });
fs.writeFileSync(path.join(p3, '03-01-PLAN.md'), '# Plan');
fs.writeFileSync(path.join(p3, '03-01-SUMMARY.md'), '# Summary');
const result = runVerifiedPhaseComplete('phase complete 3', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
// The Requirements coverage table row must be UNCHANGED (3 columns, not a Progress row).
const reqRow = roadmap.match(/^\| 3\. Build \| R-01 \| 5 \|$/m);
assert.ok(reqRow, 'Requirements coverage row must be untouched');
// The Progress row must be updated to Complete with a date.
const progressRow = roadmap.match(/^\| 3\. Build \| v1\.0 \| 1\/1 \| Complete\s+\| \d{4}-\d{2}-\d{2} \|/m);
assert.ok(progressRow, 'Progress row must be updated to Complete with a date');
});
test('detects last phase in milestone', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),

View File

@@ -20,22 +20,36 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const ROOT = path.resolve(__dirname, '..');
test('repo-layout: root AGENTS.md is absent — no ad-hoc AI instruction file committed alongside CONTEXT.md', () => {
const agentsMdPath = path.join(ROOT, 'AGENTS.md');
test('repo-layout: root AGENTS.md is not git-tracked — no ad-hoc AI instruction file committed alongside CONTEXT.md', () => {
// The installer legitimately writes AGENTS.md to process.cwd() when
// `gsd install copilot` runs inside a repo checkout (issue #786). The file
// may exist on disk — that's expected after a local install. What must NOT
// happen is committing it to git, where editors and AI tools would silently
// pick up the installer-generated stub instead of CONTEXT.md.
let tracked;
try {
execFileSync('git', ['ls-files', '--error-unmatch', 'AGENTS.md'], {
cwd: ROOT, encoding: 'utf8', stdio: 'pipe',
});
tracked = true;
} catch {
tracked = false;
}
assert.equal(
fs.existsSync(agentsMdPath),
tracked,
false,
[
'root AGENTS.md must not be committed.',
'root AGENTS.md must not be git-tracked.',
'This file is written by `gsd install copilot` (bin/install.js, local Copilot path, issue #786)',
'when the installer runs inside a repo checkout.',
'when the installer runs inside a repo checkout — its presence on disk is fine,',
'but it must never be committed.',
'The repository source of truth for architecture and contributor guidance is',
'CONTEXT.md and docs/adr/ — not an installer-generated instruction stub.',
'Run `gsd uninstall copilot` to remove the artefact, then verify it is gitignored',
'before re-running the install in this checkout.',
'Run `git rm --cached AGENTS.md` to untrack it if accidentally staged.',
].join(' '),
);
});

View File

@@ -814,6 +814,80 @@ describe('roadmap update-plan-progress command', () => {
assert.ok(!/\[x\] \*\*Phase 30/.test(roadmapContent), 'phase checkbox must not be checked');
});
test('#2022 — all summaries present but verification NOT passed → checkbox NOT checked', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
`# Roadmap
- [ ] **Phase 1: Test** - description
### Phase 1: Test
**Goal:** Test goal
**Plans:** TBD
## Progress
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 1. Test | v1.0 | 0/1 | Planned | - |
`
);
// 1 plan + 1 summary (all summaries present) but NO VERIFICATION.md → the
// verification gate (#2022) must prevent the checkbox from being checked.
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
fs.mkdirSync(p1, { recursive: true });
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan 1');
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary 1');
const result = runGsdTools('roadmap update-plan-progress 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.complete, false, 'must NOT be complete without verification');
assert.strictEqual(output.status, 'In Progress', 'status should be In Progress (not Complete)');
const roadmapContent = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
assert.ok(roadmapContent.includes('[ ] **Phase 1'), 'phase checkbox must remain unchecked');
assert.ok(!/\[x\] \*\*Phase 1/.test(roadmapContent), 'phase checkbox must NOT be checked without verification');
assert.ok(roadmapContent.includes('1/1'), 'plan count should still be updated');
});
test('#2022 — verification status NOT passed → checkbox NOT checked', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
`# Roadmap
- [ ] **Phase 1: Test**
### Phase 1: Test
**Goal:** Test goal
## Progress
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 1. Test | v1.0 | 0/1 | Planned | - |
`
);
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
fs.mkdirSync(p1, { recursive: true });
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan 1');
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary 1');
// Verification exists but status is gaps_found (not passed)
fs.writeFileSync(path.join(p1, '01-VERIFICATION.md'), '---\nstatus: gaps_found\n---\n# Verification\n');
const result = runGsdTools('roadmap update-plan-progress 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.complete, false, 'must NOT be complete (verification gaps_found)');
assert.strictEqual(output.status, 'In Progress');
const roadmapContent = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
assert.ok(!roadmapContent.includes('[x]'), 'checkbox must NOT be checked (verification not passed)');
});
test('updates progress and checks checkbox on completion', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
@@ -833,11 +907,12 @@ describe('roadmap update-plan-progress command', () => {
`
);
// Create phase dir with 1 plan, 1 summary (complete)
// Create phase dir with 1 plan, 1 summary (complete) + verification passed (#2022 gate)
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
fs.mkdirSync(p1, { recursive: true });
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan 1');
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary 1');
fs.writeFileSync(path.join(p1, '01-VERIFICATION.md'), '---\nstatus: passed\n---\n# Verification\n');
const result = runGsdTools('roadmap update-plan-progress 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
@@ -878,6 +953,7 @@ describe('roadmap update-plan-progress command', () => {
fs.mkdirSync(p3, { recursive: true });
fs.writeFileSync(path.join(p3, '03-01-PLAN.md'), '# Plan 1');
fs.writeFileSync(path.join(p3, '03-01-SUMMARY.md'), '# Summary 1');
fs.writeFileSync(path.join(p3, '03-VERIFICATION.md'), '---\nstatus: passed\n---\n# Verification\n');
const result = runGsdTools('roadmap update-plan-progress 03', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
@@ -959,6 +1035,7 @@ describe('roadmap update-plan-progress command', () => {
fs.mkdirSync(p50, { recursive: true });
fs.writeFileSync(path.join(p50, '50-01-PLAN.md'), '# Plan');
fs.writeFileSync(path.join(p50, '50-01-SUMMARY.md'), '# Summary');
fs.writeFileSync(path.join(p50, '50-VERIFICATION.md'), '---\nstatus: passed\n---\n# Verification\n');
const result = runGsdTools('roadmap update-plan-progress 50', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

View File

@@ -83,20 +83,26 @@ const GOLDEN = {
// ── copilot ──────────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
// #1575: agents kind added (copilot cutover — .agent.md rename handled by _copyStaged).
'copilot/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'copilot/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── antigravity ──────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
// #1575: agents kind added (antigravity cutover — scope-aware converter).
'antigravity/global': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
'antigravity/local': [
{ kind: 'skills', destSubpath: 'skills', prefix: 'gsd-' },
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── windsurf ─────────────────────────────────────────────────────────────────

View File

@@ -108,11 +108,16 @@ describe('resolveRuntimeArtifactLayout — copilot', () => {
const layout = resolveRuntimeArtifactLayout('copilot', FAKE_DIR);
assert.strictEqual(layout.runtime, 'copilot');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 1);
// #1575: agents kind added (copilot cutover)
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
});
@@ -121,11 +126,16 @@ describe('resolveRuntimeArtifactLayout — antigravity', () => {
const layout = resolveRuntimeArtifactLayout('antigravity', FAKE_DIR);
assert.strictEqual(layout.runtime, 'antigravity');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 1);
// #1575: agents kind added (antigravity cutover)
assert.strictEqual(layout.kinds.length, 2);
assert.strictEqual(layout.kinds[0].kind, 'skills');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
assert.strictEqual(layout.kinds[1].kind, 'agents');
assert.strictEqual(layout.kinds[1].destSubpath, 'agents');
assert.strictEqual(layout.kinds[1].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[1].stage, 'function');
});
});

View File

@@ -1,7 +1,16 @@
'use strict';
// Tests for runtime-config-adapter-registry.cjs (issue #60).
// TDD: this file is written BEFORE the implementation to establish the red state.
//
// 1.7.0 (ADR-1016 / ADR-1239) makes runtimes pluggable data descriptors, and
// resolveRuntimeConfigIntent / resolveInstallPlan are PURE PROJECTIONS of those
// descriptors. So this file asserts the PROJECTION CONTRACT — that each
// function maps descriptor fields to the intent/plan shape correctly (right
// field names, right null-handling, right types) — for EVERY runtime in the
// registry, rather than pinning a frozen per-runtime value snapshot that would
// have to be hand-edited on every runtime addition. The EXPECTED_TABLE below is
// DERIVED from the capability registry at load time; adding a runtime descriptor
// requires zero changes here.
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
@@ -15,45 +24,44 @@ const {
ALLOWED_CONFIG_RUNTIMES,
INSTALL_SURFACES,
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-config-adapter-registry.cjs'));
const registry = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'capability-registry.cjs'));
// ---------------------------------------------------------------------------
// Source-of-truth table (mirrors the intent table in the brief exactly)
// Source-of-truth table — DERIVED from the capability registry descriptors.
// Each row is the descriptor projection of one runtime's config intent. This is
// deliberately non-circular: production reads the descriptor too, and this
// asserts the mapping (field names, null-coalescing of permissionWriter, etc.)
// is correct for every present and future runtime.
// ---------------------------------------------------------------------------
const EXPECTED_TABLE = [
{ runtime: 'claude', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'antigravity', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'augment', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'qwen', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'hermes', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'codebuddy', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'opencode', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: 'opencode' },
{ runtime: 'kilo', installSurface: 'settings-json', writesSharedSettings: false, finishPermissionWriter: 'kilo' },
{ runtime: 'codex', installSurface: 'codex-toml', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'copilot', installSurface: 'copilot-instructions', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'cline', installSurface: 'cline-rules', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'cursor', installSurface: 'cursor-hooks-json', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'windsurf', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'trae', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
{ runtime: 'kimi', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
];
const EXPECTED_TABLE = Object.keys(registry.runtimes).map((id) => {
const r = registry.runtimes[id].runtime;
const pw = r.permissionWriter;
return {
runtime: id,
installSurface: r.installSurface,
writesSharedSettings: r.writesSharedSettings,
finishPermissionWriter: pw == null ? null : pw,
};
});
// ---------------------------------------------------------------------------
// Test 1: Table-lock — every row in EXPECTED_TABLE must match exactly
// Test 1: Projection contract — every registry runtime resolves to its
// descriptor-derived intent (count-agnostic).
// ---------------------------------------------------------------------------
describe('resolveRuntimeConfigIntent — table-lock', () => {
for (const row of EXPECTED_TABLE) {
test(`${row.runtime} resolves to expected intent`, () => {
const intent = resolveRuntimeConfigIntent(row.runtime);
assert.deepStrictEqual(intent, {
runtime: row.runtime,
installSurface: row.installSurface,
writesSharedSettings: row.writesSharedSettings,
describe('resolveRuntimeConfigIntent — projection contract', () => {
test('every registry runtime resolves to its descriptor-derived intent', () => {
assert.ok(EXPECTED_TABLE.length > 0, 'registry must contain at least one runtime');
for (const row of EXPECTED_TABLE) {
assert.deepStrictEqual(resolveRuntimeConfigIntent(row.runtime), {
runtime: row.runtime,
installSurface: row.installSurface,
writesSharedSettings: row.writesSharedSettings,
finishPermissionWriter: row.finishPermissionWriter,
});
});
}
}, `resolveRuntimeConfigIntent('${row.runtime}') must match the descriptor projection`);
}
});
});
// ---------------------------------------------------------------------------
@@ -95,31 +103,28 @@ describe('resolveRuntimeConfigIntent — unknown runtime throws TypeError', () =
});
// ---------------------------------------------------------------------------
// Test 3: writesSharedSettings exclusion equivalence
// Test 3: writesSharedSettings — derived equivalence (count-agnostic).
// The runtimes resolving to false are exactly those whose descriptor declares
// writesSharedSettings===false.
// ---------------------------------------------------------------------------
describe('writesSharedSettings exclusion equivalence', () => {
const EXPECTED_FALSE_SET = new Set(['codex', 'copilot', 'kilo', 'cursor', 'windsurf', 'trae', 'cline', 'kimi']);
test('runtimes with writesSharedSettings===false are exactly the exclusion set', () => {
describe('writesSharedSettings — descriptor-driven equivalence', () => {
test('runtimes resolving writesSharedSettings===false are exactly the descriptor-declared false set', () => {
const falseRuntimes = EXPECTED_TABLE
.filter(r => r.writesSharedSettings === false)
.map(r => r.runtime);
assert.deepStrictEqual(new Set(falseRuntimes), EXPECTED_FALSE_SET);
});
test('all other supported runtimes have writesSharedSettings===true', () => {
const trueRuntimes = EXPECTED_TABLE
.filter(r => r.writesSharedSettings === true)
.map(r => r.runtime);
for (const runtime of trueRuntimes) {
assert.ok(!EXPECTED_FALSE_SET.has(runtime), `${runtime} should have writesSharedSettings true`);
}
.map(r => r.runtime)
.sort();
const descriptorFalse = Object.keys(registry.runtimes)
.filter((id) => registry.runtimes[id].runtime.writesSharedSettings === false)
.sort();
assert.deepStrictEqual(falseRuntimes, descriptorFalse);
});
});
// ---------------------------------------------------------------------------
// Test 4: finishPermissionWriter correctness
// Test 4: finishPermissionWriter — opencode/kilo are non-null, the rest null.
// Spot-check the two non-null writers (stable curated values) plus the
// descriptor-derived null set.
// ---------------------------------------------------------------------------
describe('finishPermissionWriter', () => {
@@ -131,60 +136,39 @@ describe('finishPermissionWriter', () => {
assert.strictEqual(resolveRuntimeConfigIntent('kilo').finishPermissionWriter, 'kilo');
});
test('every other supported runtime -> null', () => {
const nullExpected = EXPECTED_TABLE
.filter(r => r.finishPermissionWriter === null)
.map(r => r.runtime);
for (const runtime of nullExpected) {
test('every registry runtime whose descriptor permissionWriter is null/absent resolves to null', () => {
for (const row of EXPECTED_TABLE.filter((r) => r.finishPermissionWriter === null)) {
assert.strictEqual(
resolveRuntimeConfigIntent(runtime).finishPermissionWriter,
resolveRuntimeConfigIntent(row.runtime).finishPermissionWriter,
null,
`${runtime} should have finishPermissionWriter null`,
`${row.runtime} should have finishPermissionWriter null`,
);
}
});
});
// ---------------------------------------------------------------------------
// Test 5: Distinct dedicated surfaces
// Test 5: installSurface — spot-check the stable dedicated surfaces, plus a
// descriptor-driven assertion that every runtime resolves to its declared
// surface (count-agnostic).
// ---------------------------------------------------------------------------
describe('installSurface correctness', () => {
test('codex -> "codex-toml"', () => {
test('dedicated surfaces are stable (spot-check)', () => {
assert.strictEqual(resolveRuntimeConfigIntent('codex').installSurface, 'codex-toml');
});
test('copilot -> "copilot-instructions"', () => {
assert.strictEqual(resolveRuntimeConfigIntent('copilot').installSurface, 'copilot-instructions');
});
test('cline -> "cline-rules"', () => {
assert.strictEqual(resolveRuntimeConfigIntent('cline').installSurface, 'cline-rules');
});
test('cursor -> "cursor-hooks-json"', () => {
assert.strictEqual(resolveRuntimeConfigIntent('cursor').installSurface, 'cursor-hooks-json');
});
test('windsurf -> "profile-marker-only"', () => {
assert.strictEqual(resolveRuntimeConfigIntent('windsurf').installSurface, 'profile-marker-only');
});
test('trae -> "profile-marker-only"', () => {
assert.strictEqual(resolveRuntimeConfigIntent('trae').installSurface, 'profile-marker-only');
});
test('kimi -> "profile-marker-only"', () => {
assert.strictEqual(resolveRuntimeConfigIntent('kimi').installSurface, 'profile-marker-only');
});
test('the 6 passthroughs + opencode + kilo -> "settings-json"', () => {
const settingsJsonRuntimes = ['claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy', 'opencode', 'kilo'];
for (const runtime of settingsJsonRuntimes) {
test('every registry runtime resolves to its descriptor-declared installSurface', () => {
for (const row of EXPECTED_TABLE) {
assert.strictEqual(
resolveRuntimeConfigIntent(runtime).installSurface,
'settings-json',
`${runtime} should have installSurface "settings-json"`,
resolveRuntimeConfigIntent(row.runtime).installSurface,
row.installSurface,
`${row.runtime} must resolve its descriptor installSurface`,
);
}
});
@@ -207,19 +191,19 @@ describe('resolveRuntimeConfigIntent — fresh object each call', () => {
});
// ---------------------------------------------------------------------------
// Test 7: Completeness (AC#4 table-driven) — ALLOWED_CONFIG_RUNTIMES
// Test 7: Completeness — ALLOWED_CONFIG_RUNTIMES equals the set of registry
// runtimes that declare an installSurface (count-agnostic; derived from the
// same source as the production Set).
// ---------------------------------------------------------------------------
describe('ALLOWED_CONFIG_RUNTIMES completeness', () => {
const EXPECTED_15 = new Set([
'claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
'opencode', 'kilo', 'codex', 'copilot', 'cline', 'cursor', 'windsurf', 'trae',
'kimi',
]);
test('ALLOWED_CONFIG_RUNTIMES contains exactly the 15 expected runtimes', () => {
const runtimeSet = new Set(ALLOWED_CONFIG_RUNTIMES);
assert.deepStrictEqual(runtimeSet, EXPECTED_15);
test('ALLOWED_CONFIG_RUNTIMES equals the registry runtimes that declare an installSurface', () => {
const descriptorAllowed = new Set(
Object.entries(registry.runtimes)
.filter(([, cap]) => cap && cap.runtime && typeof cap.runtime.installSurface === 'string')
.map(([id]) => id),
);
assert.deepStrictEqual(new Set(ALLOWED_CONFIG_RUNTIMES), descriptorAllowed);
});
test('every member of ALLOWED_CONFIG_RUNTIMES resolves without throwing', () => {
@@ -227,10 +211,6 @@ describe('ALLOWED_CONFIG_RUNTIMES completeness', () => {
assert.doesNotThrow(() => resolveRuntimeConfigIntent(runtime), `${runtime} should resolve without throwing`);
}
});
test('ALLOWED_CONFIG_RUNTIMES has exactly 15 entries', () => {
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 15);
});
});
// ---------------------------------------------------------------------------
@@ -304,240 +284,79 @@ describe('resolveInstallPlan — hooksSurface is descriptor-owned', () => {
});
});
// ────────────────────────────────────────────────────────────────────────
// Folded from tests/enh-1082-install-plan-capstone.test.cjs — consolidation epic #1969 (B3 #1972)
// ────────────────────────────────────────────────────────────────────────
{
const { describe: __foldDescribe } = require('node:test');
__foldDescribe("folded:enh-1082-install-plan-capstone (consolidation epic #1969 B3 #1972)", () => {
'use strict';
/**
* Golden-master test for resolveInstallPlan — ADR-857 phase 5g capstone.
*
* Pins the exact InstallPlan shape for all 15 runtimes to guard against
* descriptor drift. Derived from actual resolveInstallPlan output at the time
* the seam was introduced (2026-06-11). Behavioral: calls the exported
* function and asserts on typed fields — no source-grep.
*/
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
// ---------------------------------------------------------------------------
// Frozen golden-master table — derived from actual resolveInstallPlan output
// resolveInstallPlan — descriptor-projection contract (replaces the frozen
// per-runtime golden master). Asserts that for EVERY registry runtime,
// resolveInstallPlan(id) deep-equals the plan built directly from that runtime's
// descriptor fields. Count-agnostic: adding a runtime descriptor extends
// coverage with zero edits here. Folded from enh-1082 (consolidation epic #1969).
// ---------------------------------------------------------------------------
const EXPECTED = {
claude: {
runtime: 'claude',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged'],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
codex: {
runtime: 'codex',
installSurface: 'codex-toml',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'codex-hooks-json',
sandboxTier: 'codex-agent-sandbox',
},
antigravity: {
runtime: 'antigravity',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'gemini',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
cursor: {
runtime: 'cursor',
installSurface: 'cursor-hooks-json',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'cursor-hooks-json',
sandboxTier: 'none',
},
opencode: {
runtime: 'opencode',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: 'opencode',
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
kilo: {
runtime: 'kilo',
installSurface: 'settings-json',
writesSharedSettings: false,
finishPermissionWriter: 'kilo',
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
copilot: {
runtime: 'copilot',
installSurface: 'copilot-instructions',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'copilot-inline',
sandboxTier: 'none',
},
augment: {
runtime: 'augment',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
trae: {
runtime: 'trae',
installSurface: 'profile-marker-only',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
qwen: {
runtime: 'qwen',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact'],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
hermes: {
runtime: 'hermes',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
codebuddy: {
runtime: 'codebuddy',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
cline: {
runtime: 'cline',
installSurface: 'cline-rules',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'cline-rules',
sandboxTier: 'none',
},
kimi: {
runtime: 'kimi',
installSurface: 'profile-marker-only',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
windsurf: {
runtime: 'windsurf',
installSurface: 'profile-marker-only',
writesSharedSettings: false,
finishPermissionWriter: null,
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
};
describe('resolveInstallPlan — descriptor-projection contract (count-agnostic)', () => {
const RUNTIME_IDS = Object.keys(registry.runtimes);
const ALL_RUNTIMES = Object.keys(EXPECTED);
describe('resolveInstallPlan — ADR-857 phase 5g golden master', () => {
it('covers exactly 15 runtimes', () => {
assert.strictEqual(ALL_RUNTIMES.length, 15);
test('covers every registry runtime', () => {
assert.ok(RUNTIME_IDS.length > 0, 'registry must contain at least one runtime');
});
for (const runtime of ALL_RUNTIMES) {
it(`resolveInstallPlan('${runtime}') matches frozen plan`, () => {
const actual = resolveInstallPlan(runtime);
assert.deepStrictEqual(actual, EXPECTED[runtime],
`InstallPlan for '${runtime}' drifted from golden master`);
// Build the expected plan directly from each descriptor — the same mapping
// resolveInstallPlan performs, asserted rather than trusted.
function expectedPlanFromDescriptor(id) {
const desc = registry.runtimes[id].runtime;
const pw = desc.permissionWriter;
return {
runtime: id,
installSurface: desc.installSurface,
writesSharedSettings: desc.writesSharedSettings,
finishPermissionWriter: pw == null ? null : pw,
hookEvents: desc.hookEvents,
extendedHookEvents: Array.isArray(desc.extendedHookEvents) ? [...desc.extendedHookEvents] : [],
hooksSurface: desc.hooksSurface,
sandboxTier: desc.sandboxTier,
};
}
for (const id of RUNTIME_IDS) {
test(`resolveInstallPlan('${id}') matches the descriptor projection`, () => {
assert.deepStrictEqual(
resolveInstallPlan(id),
expectedPlanFromDescriptor(id),
`InstallPlan for '${id}' diverged from its descriptor projection`,
);
});
}
it('resolveInstallPlan throws TypeError for unknown runtime', () => {
test('resolveInstallPlan throws TypeError for unknown runtime', () => {
assert.throws(
() => resolveInstallPlan('bogus'),
(err) => err instanceof TypeError && /bogus/.test(err.message),
);
});
it('extendedHookEvents is always an array for every runtime', () => {
for (const runtime of ALL_RUNTIMES) {
const plan = resolveInstallPlan(runtime);
assert.ok(Array.isArray(plan.extendedHookEvents),
`${runtime}: extendedHookEvents should be an array`);
test('extendedHookEvents is always an array for every runtime', () => {
for (const id of RUNTIME_IDS) {
assert.ok(Array.isArray(resolveInstallPlan(id).extendedHookEvents),
`${id}: extendedHookEvents should be an array`);
}
});
it('hooksSurface is always a non-empty string for every runtime', () => {
for (const runtime of ALL_RUNTIMES) {
const plan = resolveInstallPlan(runtime);
assert.strictEqual(typeof plan.hooksSurface, 'string',
`${runtime}: hooksSurface should be a string`);
assert.ok(plan.hooksSurface.length > 0,
`${runtime}: hooksSurface should not be empty`);
test('hooksSurface is always a non-empty string for every runtime', () => {
for (const id of RUNTIME_IDS) {
const plan = resolveInstallPlan(id);
assert.strictEqual(typeof plan.hooksSurface, 'string', `${id}: hooksSurface should be a string`);
assert.ok(plan.hooksSurface.length > 0, `${id}: hooksSurface should not be empty`);
}
});
it('parity: resolveInstallPlan config-intent fields match resolveRuntimeConfigIntent', () => {
test('parity: resolveInstallPlan config-intent fields match resolveRuntimeConfigIntent', () => {
// Guard that resolveInstallPlan composes resolveRuntimeConfigIntent correctly —
// any drift between the two would silently break install().
const { resolveRuntimeConfigIntent } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
for (const runtime of ALL_RUNTIMES) {
const plan = resolveInstallPlan(runtime);
const intent = resolveRuntimeConfigIntent(runtime);
assert.strictEqual(plan.installSurface, intent.installSurface,
`${runtime}: installSurface mismatch between plan and intent`);
assert.strictEqual(plan.writesSharedSettings, intent.writesSharedSettings,
`${runtime}: writesSharedSettings mismatch`);
assert.strictEqual(plan.finishPermissionWriter, intent.finishPermissionWriter,
`${runtime}: finishPermissionWriter mismatch`);
for (const id of RUNTIME_IDS) {
const plan = resolveInstallPlan(id);
const intent = resolveRuntimeConfigIntent(id);
assert.strictEqual(plan.installSurface, intent.installSurface, `${id}: installSurface mismatch`);
assert.strictEqual(plan.writesSharedSettings, intent.writesSharedSettings, `${id}: writesSharedSettings mismatch`);
assert.strictEqual(plan.finishPermissionWriter, intent.finishPermissionWriter, `${id}: finishPermissionWriter mismatch`);
}
});
});
});
}

View File

@@ -14,7 +14,7 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const EXPECTED_FLAGS = [
'isOpencode', 'isKilo', 'isCodex', 'isCopilot', 'isAntigravity',
'isCursor', 'isWindsurf', 'isAugment', 'isTrae', 'isQwen', 'isHermes',
'isCodebuddy', 'isCline', 'isKimi',
'isCodebuddy', 'isCline', 'isKimi', 'isZcode',
];
test('runtimeFlags: every known non-claude runtime sets exactly its own flag true', () => {
@@ -35,12 +35,12 @@ test('runtimeFlags: claude / unknown / empty → all flags false (fail-closed)',
}
});
test('runtimeFlags: all 14 flags present + boolean + the object is frozen', () => {
test('runtimeFlags: all 15 flags present + boolean + the object is frozen', () => {
const flags = runtimeFlags('opencode');
for (const f of EXPECTED_FLAGS) {
assert.strictEqual(typeof flags[f], 'boolean', `${f} must be boolean`);
}
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 14 flags');
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 15 flags');
assert.ok(Object.isFrozen(flags), 'flags object must be frozen');
});

View File

@@ -101,6 +101,7 @@ const GOLDEN_DEFAULTS = {
cline: path.join(HOME, '.cline'),
opencode: path.join(HOME, '.config', 'opencode'),
kilo: path.join(HOME, '.config', 'kilo'),
zcode: path.join(HOME, '.zcode'),
};
// ── GOLDEN DEFAULTS ────────────────────────────────────────────────────────────
@@ -839,7 +840,7 @@ describe('descriptor-driven global skills base', () => {
});
});
// ── GOLDEN PARITY: getGlobalConfigDir via process.env for all 15 registry runtimes ──
// ── GOLDEN PARITY: getGlobalConfigDir via process.env for every non-probe registry runtime ──
describe('descriptor-driven parity: 13 non-probe registry runtimes × no-env-vars = golden defaults', () => {
// This is the hardest assertion: it drives getGlobalConfigDir() (which calls
@@ -890,7 +891,7 @@ describe('descriptor-driven parity: 13 non-probe registry runtimes × no-env-var
// [agent-skills] WARNING: Global skill not found at "~/.cursor/skills/X/SKILL.md" — skipping
//
// Fix introduces gsd-core/bin/lib/runtime-homes.cjs with first-class
// support for all 15 supported runtimes, including:
// support for every supported runtime, including:
// - hermes: nested skills/gsd/<skillName>/ layout (#2841)
// - cline: rules-based, returns null (no skills directory)
// - CLAUDE_CONFIG_DIR env var for Claude (was missing)

View File

@@ -1,31 +1,30 @@
'use strict';
/**
* Drift-guard + collapse: getRuntimeLabel must be the SINGLE source of truth for
* the install/uninstall console display label, replacing the two duplicated
* `runtimeLabel` assignment chains that previously lived in bin/install.js
* (uninstall() and install()) — the add-a-host tax ADR-1239 Phase B (#1679)
* eliminates.
* getRuntimeLabel is the SINGLE source of truth for the short install/uninstall
* console display label, replacing the two duplicated `runtimeLabel` assignment
* chains that previously lived in bin/install.js (uninstall() and install()) —
* the add-a-host tax ADR-1239 Phase B (#1679) eliminates.
*
* Verifies:
* 1. For every known runtime id, getRuntimeLabel(id) equals the hardcoded
* golden expected map — a pinned oracle that catches BOTH table bugs AND
* unintended drift (adding/removing a runtime forces a deliberate
* golden-map update here).
* 2. getRuntimeLabel('unknown') and getRuntimeLabel('') fall back to
* 'Claude Code' (the always-safe default).
* 3. The golden id set EXACTLY equals the capability-registry runtime id set
* (adding/removing a runtime forces a golden update here).
* Because 1.7.0 (ADR-1016 / ADR-1239) makes runtimes pluggable data, the label
* table is CURATED (a runtime id → short label mapping that cannot be derived
* from the id alone, e.g. "Claude Code", "Qwen Code", "ZCode"). This test
* enforces the COVERAGE CONTRACT rather than a frozen per-runtime snapshot:
*
* Voice: these are the SHORT UI labels used in the install/uninstall console
* output, intentionally distinct from the descriptor `title` (the long product
* name, e.g. "OpenAI Codex CLI", "GitHub Copilot") which serves
* documentation/registry display. Two prior-chain inconsistencies are resolved
* by this canonical map (both move toward the majority + descriptor value):
* - kimi: install said 'Kimi', uninstall said 'Kimi CLI' → canonical 'Kimi CLI'
* - cline: install said 'Cline', uninstall omitted it (→ 'Claude Code') → canonical 'Cline'
* - every runtime in the capability registry MUST resolve to a distinct,
* non-default curated label (a newly-added runtime that forgets to add a
* label entry silently falls through to "Claude Code" and fails here);
* - the fail-closed fallback returns "Claude Code" for unknown / empty / alias
* inputs (raw-id match only — aliases are NOT auto-expanded).
*
* ADR-1239 Phase B (#1679).
* Behavioral tests only: assert on returned values, no source-grep.
* Adding a runtime descriptor requires adding its label to RUNTIME_LABELS (the
* deliberate curation step); it does NOT require editing a count or golden
* snapshot here.
*
* Voice: these SHORT UI labels are intentionally distinct from the descriptor
* `title` (the long product name). Two prior-chain inconsistencies are resolved
* by the canonical map: kimi → 'Kimi CLI'; cline → 'Cline'.
*
* ADR-1239 Phase B (#1679). Behavioral tests only: assert on returned values.
*/
const { test } = require('node:test');
@@ -35,70 +34,36 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const { getRuntimeLabel, getRuntimeNewProjectCommand } = runtimeNamePolicy;
// Golden oracle: hardcoded expected map of all 15 runtime ids to their short
// install/uninstall display label. A pinned expected value in a TEST is correct
// — the test IS the oracle (non-circular). Only PRODUCTION code should derive
// dynamically. If this map diverges from getRuntimeLabel output, either the
// table is wrong OR the registry changed — both require a deliberate golden-map
// update here.
const GOLDEN_LABEL_MAP = {
claude: 'Claude Code',
opencode: 'OpenCode',
kilo: 'Kilo',
codex: 'Codex',
copilot: 'Copilot',
antigravity: 'Antigravity',
cursor: 'Cursor',
windsurf: 'Windsurf',
augment: 'Augment',
trae: 'Trae',
qwen: 'Qwen Code',
hermes: 'Hermes Agent',
kimi: 'Kimi CLI',
codebuddy: 'CodeBuddy',
cline: 'Cline',
};
const FALLBACK = 'Claude Code';
const RUNTIME_IDS = Object.keys(registry.runtimes);
test('getRuntimeLabel: golden map matches for all 15 known runtime ids', () => {
for (const [id, expected] of Object.entries(GOLDEN_LABEL_MAP)) {
const actual = getRuntimeLabel(id);
assert.strictEqual(
actual,
expected,
`getRuntimeLabel('${id}') diverged from golden.\n` +
` actual: ${JSON.stringify(actual)}\n` +
` expected: ${JSON.stringify(expected)}`,
);
test('getRuntimeLabel: every registry runtime resolves to a non-empty curated label (coverage contract, count-agnostic)', () => {
assert.ok(RUNTIME_IDS.length > 0, 'registry must contain at least one runtime');
for (const id of RUNTIME_IDS) {
const label = getRuntimeLabel(id);
assert.strictEqual(typeof label, 'string', `getRuntimeLabel('${id}') must be a string`);
assert.ok(label.length > 0, `getRuntimeLabel('${id}') must be non-empty`);
}
});
test('drift guard: registry runtime id set EXACTLY equals the golden map (adding/removing a runtime forces a golden update)', () => {
// Without this, a newly-added runtime would pass (its label never checked) and
// removing `claude` could pass via the 'Claude Code' fallback. Pin the set
// both ways — mirroring the getDirName drift guard.
const registryIds = Object.keys(registry.runtimes).sort();
const goldenIds = Object.keys(GOLDEN_LABEL_MAP).sort();
assert.deepEqual(registryIds, goldenIds,
'registry.runtimes id set must exactly match GOLDEN_LABEL_MAP — update the golden map when adding/removing a runtime');
test('getRuntimeLabel drift guard: no registry runtime except claude falls through to the default (forces a deliberate label per runtime)', () => {
// claude's curated label IS the fallback string, so it is exempt. Every other
// registry runtime must resolve to a DISTINCT label — otherwise it was added
// without a RUNTIME_LABELS entry and is silently masking as "Claude Code".
for (const id of RUNTIME_IDS) {
if (id === 'claude') continue;
assert.notStrictEqual(
getRuntimeLabel(id),
FALLBACK,
`registry runtime '${id}' resolved to the fallback "${FALLBACK}" — add a distinct entry to RUNTIME_LABELS in src/runtime-name-policy.cts`);
}
});
test('getRuntimeLabel fallback: unknown runtime returns "Claude Code"', () => {
assert.strictEqual(getRuntimeLabel('unknown'), 'Claude Code',
'getRuntimeLabel("unknown") must return "Claude Code" (default fallback)');
});
test('getRuntimeLabel fallback: empty string returns "Claude Code"', () => {
assert.strictEqual(getRuntimeLabel(''), 'Claude Code',
'getRuntimeLabel("") must return "Claude Code" (empty-input fallback)');
});
test('getRuntimeLabel fallback: alias is NOT auto-expanded (raw id match only)', () => {
// getRuntimeLabel is a raw-id lookup, not alias-aware (unlike canonicalizeRuntimeName).
// Callers pass an already-canonicalized runtime id. An alias must fall back to
// the default rather than silently matching — this keeps the label surface
// explicit and prevents a future alias from changing console output by accident.
assert.strictEqual(getRuntimeLabel('claude-code'), 'Claude Code',
'getRuntimeLabel("claude-code") must return the default "Claude Code" (raw-id match only; aliases are not expanded)');
test('getRuntimeLabel fallback: unknown / empty / alias inputs return "Claude Code" (fail-closed, raw-id match only)', () => {
assert.strictEqual(getRuntimeLabel('unknown'), FALLBACK);
assert.strictEqual(getRuntimeLabel(''), FALLBACK);
assert.strictEqual(getRuntimeLabel('claude-code'), FALLBACK,
'getRuntimeLabel("claude-code") must return the default (raw-id match only; aliases are not expanded)');
});
// ---------------------------------------------------------------------------
@@ -106,23 +71,30 @@ test('getRuntimeLabel fallback: alias is NOT auto-expanded (raw id match only)',
// /gsd-new-project invocation syntax for the post-install next-step message.
// ---------------------------------------------------------------------------
const GOLDEN_COMMAND_MAP = {
// 3 real overrides (the rest use the default):
// CURATED override table — runtimes whose /gsd-new-project invocation differs
// from the default. All other registry runtimes resolve to the default.
const NEW_PROJECT_OVERRIDES = {
codex: '$gsd-new-project',
cursor: 'gsd-new-project (mention the skill name)',
kimi: '/skill:gsd-new-project',
};
const DEFAULT_CMD = '/gsd-new-project';
test('getRuntimeNewProjectCommand: the 3 overrides + the default for the other 12 runtimes', () => {
for (const [id, expected] of Object.entries(GOLDEN_COMMAND_MAP)) {
assert.strictEqual(getRuntimeLabel ? getRuntimeNewProjectCommand(id) : null, expected, `override ${id}`);
test('getRuntimeNewProjectCommand: each override runtime resolves to its curated command', () => {
for (const [id, expected] of Object.entries(NEW_PROJECT_OVERRIDES)) {
assert.strictEqual(getRuntimeNewProjectCommand(id), expected, `override ${id}`);
}
// sanity: getRuntimeNewProjectCommand is imported alongside getRuntimeLabel above
});
test('getRuntimeNewProjectCommand: claude/unknown/empty + the 12 non-override runtimes → default', () => {
for (const id of ['claude', 'opencode', 'kilo', 'copilot', 'antigravity', 'windsurf', 'augment', 'trae', 'cline', 'qwen', 'hermes', 'codebuddy', 'unknown', '']) {
assert.strictEqual(getRuntimeNewProjectCommand(id), DEFAULT_CMD, `runtime '${id}' must return the default command`);
test('getRuntimeNewProjectCommand: every registry runtime not in the override table resolves to the default (count-agnostic)', () => {
for (const id of RUNTIME_IDS) {
if (Object.prototype.hasOwnProperty.call(NEW_PROJECT_OVERRIDES, id)) continue;
assert.strictEqual(getRuntimeNewProjectCommand(id), DEFAULT_CMD,
`runtime '${id}' must return the default command (add to NEW_PROJECT_OVERRIDES if it needs a non-default form)`);
}
});
test('getRuntimeNewProjectCommand: unknown / empty → default (fail-closed)', () => {
assert.strictEqual(getRuntimeNewProjectCommand('unknown'), DEFAULT_CMD);
assert.strictEqual(getRuntimeNewProjectCommand(''), DEFAULT_CMD);
});

View File

@@ -0,0 +1,68 @@
'use strict';
// Regression guard for #2018 — applySurface over an empty/unresolvable manifest
// silently deleted every gsd-* agent because the agent-prune loop in _syncGsdDir
// didn't check manifest membership (unlike pruneSkillDirs which conservatively
// preserves everything when the manifest is empty). The fix skips the agent-prune
// loop when the manifest is empty/absent so agents are never bulk-deleted.
const { describe, test, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { _syncGsdDir } = require('../gsd-core/bin/lib/surface.cjs');
const { cleanup } = require('./helpers.cjs');
function tmp() { return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-surface-empty-')); }
describe('#2018 — empty manifest must not delete gsd-* agents', () => {
let dest, staged;
beforeEach(() => {
dest = tmp(); staged = tmp();
// Simulate existing agents in dest (what a real install has)
for (const name of ['gsd-executor.md', 'gsd-planner.md', 'gsd-verifier.md']) {
fs.writeFileSync(path.join(dest, name), `# ${name}\n`);
}
// Staged dir is EMPTY (unresolvable manifest → nothing staged)
});
afterEach(() => { cleanup(dest); cleanup(staged); });
test('empty manifest → agents preserved (not deleted)', () => {
_syncGsdDir(staged, dest, 'agents', new Map());
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('gsd-'));
assert.deepEqual(remaining.sort(), ['gsd-executor.md', 'gsd-planner.md', 'gsd-verifier.md'],
'all gsd-* agents must survive an empty manifest');
});
test('undefined manifest → agents preserved', () => {
_syncGsdDir(staged, dest, 'agents', undefined);
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('gsd-'));
assert.ok(remaining.length === 3, 'all agents preserved with undefined manifest');
});
test('non-empty manifest + empty staged → agents still preserved (staged set empty but manifest exists)', () => {
// A populated manifest (even with a different set) is trusted; but staged is empty.
// The manifest gate only fires on EMPTY manifest. With a non-empty manifest but
// empty staged, the prune loop SHOULD run (agents not in staged get pruned).
// This test documents the boundary: non-empty manifest → prune happens.
const manifest = new Map([['gsd-old-agent', []]]);
_syncGsdDir(staged, dest, 'agents', manifest);
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('gsd-'));
// With a non-empty manifest, staged is empty → all agents pruned (this is the
// existing behavior for a genuine "all agents were un-surfaced" scenario, NOT
// the empty-manifest bug).
assert.deepEqual(remaining, [], 'non-empty manifest + empty staged → agents pruned (expected behavior)');
});
test('empty manifest but new agents staged → new agents added, existing preserved', () => {
// Stage a new agent
fs.writeFileSync(path.join(staged, 'gsd-new-agent.md'), '# New\n');
_syncGsdDir(staged, dest, 'agents', new Map());
const remaining = fs.readdirSync(dest).filter((f) => f.startsWith('gsd-'));
assert.ok(remaining.includes('gsd-new-agent.md'), 'new staged agent must be copied');
assert.ok(remaining.includes('gsd-executor.md'), 'existing agent must be preserved');
assert.ok(remaining.length === 4, '3 existing + 1 new = 4 agents');
});
});

View File

@@ -60,7 +60,7 @@
"profile-user.md": 21246,
"progress.md": 30599,
"quick.md": 49183,
"reapply-patches.md": 20393,
"reapply-patches.md": 20312,
"remove-phase.md": 8513,
"remove-workspace.md": 7551,
"resume-project.md": 17270,
@@ -89,5 +89,5 @@
"update.md": 20914,
"validate-phase.md": 10789,
"verify-phase.md": 40772,
"verify-work.md": 35215
"verify-work.md": 38267
}