Merge branch 'next' into feat/1820-specless-predicate-rail
This commit is contained in:
@@ -15,13 +15,15 @@ const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
const { cleanup, runGsdTools } = require('./helpers.cjs');
|
||||
|
||||
const {
|
||||
resolveCapabilityState,
|
||||
resolveCapabilityRuntimeState,
|
||||
isCapabilityActive,
|
||||
_isSafePropKey,
|
||||
_loadInstalledSkillsManifest,
|
||||
_loadFlatCommandsGsdManifest,
|
||||
_resolveManifest,
|
||||
} = require('../gsd-core/bin/lib/capability-state.cjs');
|
||||
|
||||
@@ -823,6 +825,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) {
|
||||
@@ -1810,3 +2029,176 @@ describe('#1459 IC-04: capability-state threads gsdHome to the overlay loader',
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('regressions: --runtime override bypasses persisted runtime (#2003)', () => {
|
||||
// #2003: `capability state` and `loop render-hooks` parsed only --config-dir,
|
||||
// never --runtime. resolveCapabilityRuntimeState derived the config dir from
|
||||
// resolveRuntime(cwd) (GSD_RUNTIME → config.runtime → 'claude'), so a repo
|
||||
// with persisted runtime:"codex" resolved the config dir to ~/.codex — where
|
||||
// the Claude skill isn't installed → surfaced:false. Fix: thread an explicit
|
||||
// --runtime override through both commands into resolveCapabilityRuntimeState
|
||||
// so it bypasses the persisted-runtime fallback (mirrors the update-context /
|
||||
// effort sync precedent).
|
||||
|
||||
function writePersistedRuntime(tmpDir, runtime) {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'config.json'),
|
||||
JSON.stringify({ runtime }),
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
|
||||
test('resolveCapabilityRuntimeState: runtimeOverride="claude" bypasses persisted config.runtime:"codex"', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-override-'));
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const runtimeHomes = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
const expectedClaudeDir = runtimeHomes.getGlobalConfigDir('claude');
|
||||
const expectedCodexDir = runtimeHomes.getGlobalConfigDir('codex');
|
||||
// Persisted runtime is codex; explicit override is claude. The override
|
||||
// MUST win (resolveRuntime is never consulted when an override is given,
|
||||
// so GSD_RUNTIME env cannot interfere either).
|
||||
const result = resolveCapabilityRuntimeState(tmpDir, undefined, undefined, 'claude');
|
||||
assert.strictEqual(result.runtimeConfigDir, expectedClaudeDir,
|
||||
'--runtime claude must override persisted runtime:"codex"');
|
||||
assert.notStrictEqual(result.runtimeConfigDir, expectedCodexDir,
|
||||
'must NOT resolve to the codex config dir when --runtime claude is explicit');
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('resolveCapabilityRuntimeState: no override still honours persisted config.runtime (unchanged, regression guard)', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-no-override-'));
|
||||
// Control GSD_RUNTIME so resolveRuntime deterministically reads config.runtime.
|
||||
const savedGsdRuntime = process.env.GSD_RUNTIME;
|
||||
delete process.env.GSD_RUNTIME;
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const runtimeHomes = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
const expectedCodexDir = runtimeHomes.getGlobalConfigDir('codex');
|
||||
// No override → persisted codex wins (existing behavior preserved).
|
||||
const result = resolveCapabilityRuntimeState(tmpDir, undefined, undefined, undefined);
|
||||
assert.strictEqual(result.runtimeConfigDir, expectedCodexDir,
|
||||
'without --runtime, persisted config.runtime:"codex" still drives resolution (unchanged)');
|
||||
} finally {
|
||||
if (savedGsdRuntime === undefined) delete process.env.GSD_RUNTIME;
|
||||
else process.env.GSD_RUNTIME = savedGsdRuntime;
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('resolveCapabilityRuntimeState: runtimeOverride canonicalizes aliases (codex-app -> codex)', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-alias-'));
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'claude');
|
||||
const runtimeHomes = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
const expectedCodexDir = runtimeHomes.getGlobalConfigDir('codex');
|
||||
// Alias "codex-app" canonicalizes to "codex" via runtime-name-policy.
|
||||
const result = resolveCapabilityRuntimeState(tmpDir, undefined, undefined, 'codex-app');
|
||||
assert.strictEqual(result.runtimeConfigDir, expectedCodexDir,
|
||||
'--runtime codex-app (alias) must canonicalize to codex');
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('CLI: `capability state --runtime claude` reports the Claude config dir despite persisted runtime:"codex"', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-cli-'));
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const result = runGsdTools('capability state --runtime claude --raw', tmpDir);
|
||||
assert.ok(result.success, `capability state --runtime should succeed: ${result.error || ''}`);
|
||||
const parsed = JSON.parse(result.output);
|
||||
const runtimeHomes = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
assert.strictEqual(parsed.runtimeConfigDir, runtimeHomes.getGlobalConfigDir('claude'),
|
||||
'`capability state --runtime claude` must resolve to the Claude config dir, not the persisted codex dir');
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
// L-2 (review): end-to-end CLI test for loop render-hooks --runtime — the exact
|
||||
// command the bug report calls out as silently no-op'ing. Guards the copy-pasted
|
||||
// arg parsing in gsd-tools.cjs from diverging from the capability-state branch.
|
||||
test('CLI: `loop render-hooks verify:post --runtime claude` resolves against the Claude config dir', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-loop-cli-'));
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const result = runGsdTools('loop render-hooks verify:post --runtime claude --raw', tmpDir);
|
||||
assert.ok(result.success, `loop render-hooks --runtime should succeed: ${result.error || ''}`);
|
||||
const parsed = JSON.parse(result.output);
|
||||
// The envelope carries activeHooks/rendered; the key assertion is that it
|
||||
// ran without silently no-op'ing against the codex dir. A non-empty
|
||||
// point + a rendered string (even "_No active hooks..._") proves the
|
||||
// command executed against the resolved (claude) config dir rather than
|
||||
// erroring or emitting nothing.
|
||||
assert.strictEqual(parsed.point, 'verify:post', 'render-hooks must echo the requested point');
|
||||
assert.ok(typeof parsed.rendered === 'string', 'render-hooks must produce a rendered string');
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
// M-1 + NIT-01 (review): unknown / crafted --runtime values are REJECTED by the
|
||||
// closed-vocabulary canonicalizer, warn, and fall through to the persisted
|
||||
// runtime (never embedded into a path). This is the security-load-bearing
|
||||
// contract — pin it so a future refactor can't silently break it.
|
||||
test('resolveCapabilityRuntimeState: unknown/crafted --runtime is rejected (closed vocabulary), warns, and falls through to persisted runtime', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-reject-'));
|
||||
const savedGsdRuntime = process.env.GSD_RUNTIME;
|
||||
delete process.env.GSD_RUNTIME;
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const runtimeHomes = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
const expectedCodexDir = runtimeHomes.getGlobalConfigDir('codex');
|
||||
const crafted = ['../../etc/passwd', '__proto__', 'constructor', '--config-dir', 'garbage', 'foo bar', 'cluade'];
|
||||
for (const bad of crafted) {
|
||||
const result = resolveCapabilityRuntimeState(tmpDir, undefined, undefined, bad);
|
||||
assert.strictEqual(result.runtimeConfigDir, expectedCodexDir,
|
||||
`crafted --runtime "${bad}" must fall through to persisted codex dir, not be embedded in a path`);
|
||||
assert.ok(result.warnings.some((w) => w.includes('--runtime') && w.includes(bad)),
|
||||
`crafted --runtime "${bad}" must emit a warning naming the rejected value`);
|
||||
}
|
||||
} finally {
|
||||
if (savedGsdRuntime === undefined) delete process.env.GSD_RUNTIME;
|
||||
else process.env.GSD_RUNTIME = savedGsdRuntime;
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
// N-1 (review): CLI arg-parsing boundary — --config-dir wins over --runtime
|
||||
// (most-explicit input takes precedence), and missing --runtime value errors.
|
||||
test('CLI: --config-dir takes precedence over --runtime when both are given', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-precedence-'));
|
||||
const explicitDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-explicit-cfg-'));
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const result = runGsdTools(
|
||||
`capability state --config-dir ${explicitDir} --runtime claude --raw`,
|
||||
tmpDir,
|
||||
);
|
||||
assert.ok(result.success, `capability state with both flags should succeed: ${result.error || ''}`);
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.runtimeConfigDir, explicitDir,
|
||||
'--config-dir (explicit path) must win over --runtime when both are present');
|
||||
} finally {
|
||||
cleanup(explicitDir);
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('CLI: `capability state --runtime` with no value errors with USAGE', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-rt-missing-'));
|
||||
try {
|
||||
writePersistedRuntime(tmpDir, 'codex');
|
||||
const result = runGsdTools('capability state --runtime', tmpDir);
|
||||
assert.ok(!result.success, 'missing --runtime value must produce a non-zero exit');
|
||||
assert.ok(/Missing value for --runtime/.test(result.error || ''),
|
||||
`error must name the missing --runtime value: ${result.error || ''}`);
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
169
tests/claude-orchestration-command-router.test.cjs
Normal file
169
tests/claude-orchestration-command-router.test.cjs
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
615
tests/claude-orchestration.test.cjs
Normal file
615
tests/claude-orchestration.test.cjs
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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(',')}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
76
tests/fix-1941-quick-worktree-stale-base.test.cjs
Normal file
76
tests/fix-1941-quick-worktree-stale-base.test.cjs
Normal file
@@ -0,0 +1,76 @@
|
||||
// allow-test-rule: source-text-is-the-product #1941
|
||||
// Workflow .md files are the installed AI instructions — their text IS what the runtime
|
||||
// loads. Testing text content tests the deployed contract. Per CONTRIBUTING.md exception matrix.
|
||||
|
||||
/**
|
||||
* Regression tests for bug #1941: /gsd-quick worktree executor forks from a stale base —
|
||||
* up to many commits behind, not just the one-commit gap #1265 already covers.
|
||||
*
|
||||
* Root cause: Claude Code's isolation="worktree" forks new worktrees from origin/HEAD, not
|
||||
* the live local HEAD. When prior local commits (e.g. earlier quick tasks in the same
|
||||
* session, or this task's own Step 5.6 pre-dispatch plan commit) advance local HEAD without
|
||||
* an intervening `git push`, origin/HEAD stays pinned to a stale ancestor and the executor's
|
||||
* worktree_branch_check guard halts with a base-mismatch fatal. The fix ports the
|
||||
* worktree.base-check auto-degrade pattern already used by execute-phase (#683/#1369) into
|
||||
* quick.md's single-dispatch path.
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'quick.md');
|
||||
|
||||
describe('quick: pre-dispatch worktree base re-check (#1941)', () => {
|
||||
test('workflow file exists', () => {
|
||||
assert.ok(fs.existsSync(WORKFLOW_PATH), 'workflows/quick.md should exist');
|
||||
});
|
||||
|
||||
test('Step 6 runs worktree.base-check before capturing EXPECTED_BASE', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const step6Idx = content.indexOf('**Step 6: Spawn executor**');
|
||||
const baseCheckIdx = content.indexOf('worktree.base-check', step6Idx);
|
||||
const expectedBaseIdx = content.indexOf('EXPECTED_BASE=$(git rev-parse HEAD)', step6Idx);
|
||||
assert.ok(step6Idx !== -1, '"Step 6: Spawn executor" must exist in quick.md');
|
||||
assert.ok(baseCheckIdx !== -1, 'worktree.base-check must be invoked within Step 6');
|
||||
assert.ok(expectedBaseIdx !== -1, 'EXPECTED_BASE capture must exist within Step 6');
|
||||
assert.ok(
|
||||
baseCheckIdx < expectedBaseIdx,
|
||||
'worktree.base-check must run BEFORE EXPECTED_BASE is captured so the degrade decision reflects the most current local HEAD'
|
||||
);
|
||||
});
|
||||
|
||||
test('degrade check references #1941 for traceability', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
assert.ok(content.includes('#1941'), 'quick.md must reference #1941');
|
||||
});
|
||||
|
||||
test('degrade check sets USE_WORKTREES=false when shouldDegrade is true', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const baseCheckIdx = content.indexOf('worktree.base-check');
|
||||
const block = content.slice(baseCheckIdx, baseCheckIdx + 600);
|
||||
assert.ok(
|
||||
block.includes('shouldDegrade') && block.includes('USE_WORKTREES=false'),
|
||||
'degrade check must override USE_WORKTREES=false when shouldDegrade is true'
|
||||
);
|
||||
});
|
||||
|
||||
test('degrade check guards on RUNTIME=claude (worktree isolation is Claude Code-specific)', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const baseCheckIdx = content.indexOf('worktree.base-check');
|
||||
const block = content.slice(Math.max(0, baseCheckIdx - 200), baseCheckIdx + 200);
|
||||
assert.ok(
|
||||
block.includes('RUNTIME') && (block.includes('"claude"') || block.includes("'claude'")),
|
||||
'degrade check must guard on RUNTIME=claude'
|
||||
);
|
||||
});
|
||||
|
||||
test('degrade check names origin/HEAD as the stale fork base', () => {
|
||||
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
|
||||
const step6Idx = content.indexOf('**Step 6: Spawn executor**');
|
||||
const nextSection = content.indexOf('\n---', step6Idx);
|
||||
const section = content.slice(step6Idx, nextSection === -1 ? undefined : nextSection);
|
||||
assert.ok(section.includes('origin/HEAD'), 'Step 6 must name origin/HEAD as the stale fork base');
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74",
|
||||
"gsd-core/bin/gsd-tools.cjs": "747defe620cb8ae6",
|
||||
"gsd-core/bin/gsd-tools.cjs": "f2dd7a6993dc12f1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -87,6 +87,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "9e6076a137f9e156",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "c11c191ffad86a9f",
|
||||
"gsd-core/references/honest-verifier.md": "a31a4bf42d82d5e9",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -173,7 +174,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "31f627d456ab14b0",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "e4a3b331be081e62",
|
||||
"gsd-core/templates/project.md": "fae14114f2b79b10",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -226,7 +227,7 @@
|
||||
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
|
||||
"gsd-core/workflows/discuss-phase/templates/context.md": "9804674fccada405",
|
||||
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "2b56ec2868cbddb4",
|
||||
"gsd-core/workflows/do.md": "22adc4ed8589a015",
|
||||
"gsd-core/workflows/do.md": "3eddf1cc9e04b685",
|
||||
"gsd-core/workflows/docs-update.md": "8e986e26d0e6e1a0",
|
||||
"gsd-core/workflows/edit-phase.md": "fc932e82ba1f585a",
|
||||
"gsd-core/workflows/eval-review.md": "eb4040eaa5b8497f",
|
||||
@@ -243,10 +244,10 @@
|
||||
"gsd-core/workflows/graduation.md": "a9d8f15ba81a993f",
|
||||
"gsd-core/workflows/health.md": "1ac4d567ee95acfb",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
|
||||
"gsd-core/workflows/help/modes/full.md": "0e253248c1c2e063",
|
||||
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "be05e56b2c5ee2c0",
|
||||
"gsd-core/workflows/help/modes/full.md": "c8ffd141e28f5111",
|
||||
"gsd-core/workflows/help/modes/topic.md": "0bf9ab39d7044d69",
|
||||
"gsd-core/workflows/import.md": "41c62ae199209a68",
|
||||
"gsd-core/workflows/inbox.md": "a448220c548f27bc",
|
||||
"gsd-core/workflows/ingest-docs.md": "e2707f28f67d128b",
|
||||
@@ -264,6 +265,7 @@
|
||||
"gsd-core/workflows/next.md": "8beefb4335dd7c41",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "3ce09c0aa0a20599",
|
||||
"gsd-core/workflows/onboard.md": "69d871aa53a1a859",
|
||||
"gsd-core/workflows/pause-work.md": "564de32981a24337",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "dd6a4b3a8b05ab6e",
|
||||
"gsd-core/workflows/plan-phase.md": "f3310f285ab426c6",
|
||||
@@ -275,7 +277,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "dc5598ae8accdecd",
|
||||
"gsd-core/workflows/profile-user.md": "355af92ac285567f",
|
||||
"gsd-core/workflows/progress.md": "79a11ce798082054",
|
||||
"gsd-core/workflows/quick.md": "a6aa362881f4e5da",
|
||||
"gsd-core/workflows/quick.md": "35582887917ef938",
|
||||
"gsd-core/workflows/reapply-patches.md": "4dcd6117d0a507ca",
|
||||
"gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e",
|
||||
"gsd-core/workflows/remove-workspace.md": "d0bd7e0601138798",
|
||||
@@ -368,7 +370,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "4ec0198966ac4f7d",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "f4e43968af317ed8",
|
||||
"skills/gsd-manager/SKILL.md": "6e51785860784d8c",
|
||||
"skills/gsd-map-codebase/SKILL.md": "e10431a9386c6aec",
|
||||
"skills/gsd-map-codebase/SKILL.md": "552f914f43b2181c",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "588ddf4fbf3ebe65",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "2d95e385fee26dee",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "2955ab3e3a444ca3",
|
||||
@@ -379,9 +381,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "e791bc60e4cf124e",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "b85f270e8415f595",
|
||||
"skills/gsd-ns-manage/SKILL.md": "d55c53b5b0513033",
|
||||
"skills/gsd-ns-project/SKILL.md": "64844073a6115c45",
|
||||
"skills/gsd-ns-project/SKILL.md": "905eff81dbb98b38",
|
||||
"skills/gsd-ns-review/SKILL.md": "2e30158e112d173c",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "529e21833a6a8831",
|
||||
"skills/gsd-onboard/SKILL.md": "1447665c64c8ade8",
|
||||
"skills/gsd-pause-work/SKILL.md": "9d3cc6bd70b03df1",
|
||||
"skills/gsd-phase/SKILL.md": "00676bbea61410bf",
|
||||
"skills/gsd-plan-phase/SKILL.md": "168046ccf9702532",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"commands/gsd-inbox.md": "c218298db8b36e31",
|
||||
"commands/gsd-ingest-docs.md": "6718b04c597a5428",
|
||||
"commands/gsd-manager.md": "8f4ae79774902293",
|
||||
"commands/gsd-map-codebase.md": "f3a06c4c7503f243",
|
||||
"commands/gsd-map-codebase.md": "e4e170d4ef2d46c6",
|
||||
"commands/gsd-mempalace-capture.md": "2e610f3ebe2eb5b4",
|
||||
"commands/gsd-mempalace-recall.md": "38716c0983a3ef9c",
|
||||
"commands/gsd-milestone-summary.md": "ff5e11e6b33d4b5e",
|
||||
@@ -73,9 +73,10 @@
|
||||
"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-project.md": "ff67e85bc6f7fc5a",
|
||||
"commands/gsd-ns-review.md": "3766ed10827882a0",
|
||||
"commands/gsd-ns-workflow.md": "c3b3c046a74ec0ee",
|
||||
"commands/gsd-onboard.md": "1e8acf7be31834be",
|
||||
"commands/gsd-pause-work.md": "4fb032f72238fe33",
|
||||
"commands/gsd-phase.md": "4920d15d779329eb",
|
||||
"commands/gsd-plan-phase.md": "e74f3cb7a10cbb83",
|
||||
@@ -108,7 +109,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -157,6 +158,7 @@
|
||||
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
|
||||
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -243,7 +245,7 @@
|
||||
"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/project.md": "4f311fb1b05b823b",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -296,7 +298,7 @@
|
||||
"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/do.md": "7512dd1892f118a1",
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
@@ -313,10 +315,10 @@
|
||||
"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": "3ac1c1bb851da7a5",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
|
||||
"gsd-core/workflows/help/modes/brief.md": "924860e1f07defb0",
|
||||
"gsd-core/workflows/help/modes/default.md": "08a02976c0c5cc50",
|
||||
"gsd-core/workflows/help/modes/full.md": "fc307cb77544df1d",
|
||||
"gsd-core/workflows/help/modes/topic.md": "5c160093f3cbf35d",
|
||||
"gsd-core/workflows/import.md": "3d3fa603ceb8bc9f",
|
||||
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
|
||||
"gsd-core/workflows/ingest-docs.md": "c859921c811ac11b",
|
||||
@@ -334,6 +336,7 @@
|
||||
"gsd-core/workflows/next.md": "13fb800f2472d970",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "5a99eb396c744619",
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "b693bf6fd9ec6b08",
|
||||
@@ -345,7 +348,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "14263db831230142",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "3bafc0a59cc90d26",
|
||||
"gsd-core/workflows/quick.md": "5790ceb09aa685be",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
@@ -415,7 +418,7 @@
|
||||
"skills/gsd-ns-context/skills/docs-update/SKILL.md": "e7ab14a984f7462c",
|
||||
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "48846bf26ccaf0e7",
|
||||
"skills/gsd-ns-context/skills/graphify/SKILL.md": "3d6341614add6ccd",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "51d6b970dca3c28e",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "2017007dcd69d153",
|
||||
"skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "a7261b5ead71cc58",
|
||||
"skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "646e59e76acd548f",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "d70360a5f7e8ac90",
|
||||
@@ -443,7 +446,7 @@
|
||||
"skills/gsd-ns-manage/skills/update/SKILL.md": "c8165b8085ba106a",
|
||||
"skills/gsd-ns-manage/skills/workspace/SKILL.md": "98cc03aa8c735e9d",
|
||||
"skills/gsd-ns-manage/skills/workstreams/SKILL.md": "94d00b5116523f36",
|
||||
"skills/gsd-ns-project/SKILL.md": "4d7e3c3870536dc9",
|
||||
"skills/gsd-ns-project/SKILL.md": "ae47130ec878de8c",
|
||||
"skills/gsd-ns-project/skills/audit-milestone/SKILL.md": "6d4c77390c549fc7",
|
||||
"skills/gsd-ns-project/skills/complete-milestone/SKILL.md": "0af489daf6fd3f16",
|
||||
"skills/gsd-ns-project/skills/import/SKILL.md": "a3cfa7abc5ddd26d",
|
||||
@@ -451,6 +454,7 @@
|
||||
"skills/gsd-ns-project/skills/milestone-summary/SKILL.md": "18dc0c134cb62657",
|
||||
"skills/gsd-ns-project/skills/new-milestone/SKILL.md": "a94ff34b09e17a98",
|
||||
"skills/gsd-ns-project/skills/new-project/SKILL.md": "81f681f54ca61f90",
|
||||
"skills/gsd-ns-project/skills/onboard/SKILL.md": "1f5b29c75a91be4f",
|
||||
"skills/gsd-ns-project/skills/profile-user/SKILL.md": "68b015a40d386bff",
|
||||
"skills/gsd-ns-project/skills/review-backlog/SKILL.md": "0f25310aa0cd48f6",
|
||||
"skills/gsd-ns-review/SKILL.md": "d7567fd9a75a5c21",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/claude.json
vendored
23
tests/fixtures/golden-install-parity/claude.json
vendored
@@ -37,7 +37,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -86,6 +86,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "5c70ef3203b7c9ce",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -172,7 +173,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "213ccd947451ff2b",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "1f687c7a88381080",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -225,7 +226,7 @@
|
||||
"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": "56215b98e26448bc",
|
||||
"gsd-core/workflows/do.md": "61b92fb4d0bf3975",
|
||||
"gsd-core/workflows/docs-update.md": "63082608d3ae92be",
|
||||
"gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a",
|
||||
"gsd-core/workflows/eval-review.md": "f59e8329dae1e528",
|
||||
@@ -242,10 +243,10 @@
|
||||
"gsd-core/workflows/graduation.md": "47f1594c88c08501",
|
||||
"gsd-core/workflows/health.md": "f934b1f1e9f3ae72",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
|
||||
"gsd-core/workflows/help/modes/full.md": "93cbdf7e542115a1",
|
||||
"gsd-core/workflows/help/modes/topic.md": "899d7168a485297f",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "be05e56b2c5ee2c0",
|
||||
"gsd-core/workflows/help/modes/full.md": "e2084c6766e32d6c",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6e42db16f1568be9",
|
||||
"gsd-core/workflows/import.md": "6e8acff8c3918795",
|
||||
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
|
||||
"gsd-core/workflows/ingest-docs.md": "25832c784e8812e6",
|
||||
@@ -263,6 +264,7 @@
|
||||
"gsd-core/workflows/next.md": "ef30b829a6d5c5f6",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "42b66686b2c102cb",
|
||||
"gsd-core/workflows/onboard.md": "3c50ed1f1fd07619",
|
||||
"gsd-core/workflows/pause-work.md": "5716362557f44ce4",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e",
|
||||
"gsd-core/workflows/plan-phase.md": "823254227fc8e369",
|
||||
@@ -274,7 +276,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd",
|
||||
"gsd-core/workflows/profile-user.md": "ff3820a27731ceb8",
|
||||
"gsd-core/workflows/progress.md": "bd1ecf9207331bda",
|
||||
"gsd-core/workflows/quick.md": "bd56a6313d5ed43c",
|
||||
"gsd-core/workflows/quick.md": "da83b1a15b7f1bf7",
|
||||
"gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041",
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "f3ab3a88a7e9e1ed",
|
||||
@@ -367,7 +369,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "d05a099c0be02c60",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "eb85e654917a503a",
|
||||
"skills/gsd-manager/SKILL.md": "1370d93741e47828",
|
||||
"skills/gsd-map-codebase/SKILL.md": "5009595dbde69739",
|
||||
"skills/gsd-map-codebase/SKILL.md": "cf65e61890448285",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "40a31ad049374297",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "c3f412d5a64f3921",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "33d51a325d314f3a",
|
||||
@@ -378,9 +380,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "3102e7ad9b60f182",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "c2c129408a046d22",
|
||||
"skills/gsd-ns-manage/SKILL.md": "8d786e01fa28a7bb",
|
||||
"skills/gsd-ns-project/SKILL.md": "cc0c4bd9feeab147",
|
||||
"skills/gsd-ns-project/SKILL.md": "76c77b121ad68b6c",
|
||||
"skills/gsd-ns-review/SKILL.md": "4111ea56e820479f",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "4282c40a5e6e8bbe",
|
||||
"skills/gsd-onboard/SKILL.md": "46460b479b7524bf",
|
||||
"skills/gsd-pause-work/SKILL.md": "35e8a148e44f5361",
|
||||
"skills/gsd-phase/SKILL.md": "00be96e7ae36c6f0",
|
||||
"skills/gsd-plan-phase/SKILL.md": "0c9e87da048acfb7",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/cline.json
vendored
23
tests/fixtures/golden-install-parity/cline.json
vendored
@@ -41,7 +41,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "476aa24e8c4f03cf",
|
||||
"gsd-core/bin/gsd-tools.cjs": "13640341aa311ae3",
|
||||
"gsd-core/bin/gsd-tools.cjs": "c6818560fbd9f8e1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -90,6 +90,7 @@
|
||||
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
|
||||
"gsd-core/references/git-integration.md": "f5403e1470e46e69",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "07544d9a957fc2ca",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -176,7 +177,7 @@
|
||||
"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/project.md": "4f311fb1b05b823b",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -229,7 +230,7 @@
|
||||
"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": "a35bbd09f381d940",
|
||||
"gsd-core/workflows/do.md": "045a18f30efb1202",
|
||||
"gsd-core/workflows/docs-update.md": "39f288623a8f6f32",
|
||||
"gsd-core/workflows/edit-phase.md": "9c9fadc047c61d74",
|
||||
"gsd-core/workflows/eval-review.md": "3e1d7829ed2ed494",
|
||||
@@ -246,10 +247,10 @@
|
||||
"gsd-core/workflows/graduation.md": "16fedecda36769eb",
|
||||
"gsd-core/workflows/health.md": "788fad84fa42eb9f",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
|
||||
"gsd-core/workflows/help/modes/default.md": "77c34f4a77dec02c",
|
||||
"gsd-core/workflows/help/modes/full.md": "1bf502890828ada8",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
|
||||
"gsd-core/workflows/help/modes/brief.md": "924860e1f07defb0",
|
||||
"gsd-core/workflows/help/modes/default.md": "86f7a14ad06e2f6f",
|
||||
"gsd-core/workflows/help/modes/full.md": "efb266b8dbe60f51",
|
||||
"gsd-core/workflows/help/modes/topic.md": "5c160093f3cbf35d",
|
||||
"gsd-core/workflows/import.md": "96ec687c5cfe84ac",
|
||||
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
|
||||
"gsd-core/workflows/ingest-docs.md": "7d0517356c9f2619",
|
||||
@@ -267,6 +268,7 @@
|
||||
"gsd-core/workflows/next.md": "3f8b61a35587a021",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "5a99eb396c744619",
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "3530607514b0ac00",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "bafdc6945cd2bd87",
|
||||
"gsd-core/workflows/plan-phase.md": "a08c5c99792f3086",
|
||||
@@ -278,7 +280,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "9923878a4f6a2d91",
|
||||
"gsd-core/workflows/profile-user.md": "26f74db0a7fcd268",
|
||||
"gsd-core/workflows/progress.md": "9f326d63afb4b76b",
|
||||
"gsd-core/workflows/quick.md": "10051a9b41d5d210",
|
||||
"gsd-core/workflows/quick.md": "68a9dfcd53f0859f",
|
||||
"gsd-core/workflows/reapply-patches.md": "eb4272145a117904",
|
||||
"gsd-core/workflows/remove-phase.md": "e336350f8113a328",
|
||||
"gsd-core/workflows/remove-workspace.md": "e685dfbd736dfd90",
|
||||
@@ -326,7 +328,7 @@
|
||||
"skills/gsd-ns-context/skills/docs-update/SKILL.md": "d150afd41a25ec08",
|
||||
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "d39269bea995fabc",
|
||||
"skills/gsd-ns-context/skills/graphify/SKILL.md": "030050fcd08d129d",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "19aec854da15f77b",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "fa6a37402430af7d",
|
||||
"skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "588ddf4fbf3ebe65",
|
||||
"skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "2d95e385fee26dee",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "c10342345c01c91f",
|
||||
@@ -354,7 +356,7 @@
|
||||
"skills/gsd-ns-manage/skills/update/SKILL.md": "2666ab7786b69017",
|
||||
"skills/gsd-ns-manage/skills/workspace/SKILL.md": "c2616b157ec2350b",
|
||||
"skills/gsd-ns-manage/skills/workstreams/SKILL.md": "737841783c7fdd4f",
|
||||
"skills/gsd-ns-project/SKILL.md": "5668e3a5e1d8896d",
|
||||
"skills/gsd-ns-project/SKILL.md": "fac77a9e770fd655",
|
||||
"skills/gsd-ns-project/skills/audit-milestone/SKILL.md": "739ed755ad84e58e",
|
||||
"skills/gsd-ns-project/skills/complete-milestone/SKILL.md": "a69d534b385d6aaf",
|
||||
"skills/gsd-ns-project/skills/import/SKILL.md": "bc9b615cb141d26f",
|
||||
@@ -362,6 +364,7 @@
|
||||
"skills/gsd-ns-project/skills/milestone-summary/SKILL.md": "e3636a5e9bbaab1f",
|
||||
"skills/gsd-ns-project/skills/new-milestone/SKILL.md": "bc8989d97f3161ad",
|
||||
"skills/gsd-ns-project/skills/new-project/SKILL.md": "ff0d57491df30624",
|
||||
"skills/gsd-ns-project/skills/onboard/SKILL.md": "56c34282575926d4",
|
||||
"skills/gsd-ns-project/skills/profile-user/SKILL.md": "0406eed4dbd9560c",
|
||||
"skills/gsd-ns-project/skills/review-backlog/SKILL.md": "d8a150558cc9326a",
|
||||
"skills/gsd-ns-review/SKILL.md": "c5afe33c212947dd",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"commands/gsd-inbox.md": "eecfcfff57f89975",
|
||||
"commands/gsd-ingest-docs.md": "ab3e58239c23d61d",
|
||||
"commands/gsd-manager.md": "bfe9e261fa918876",
|
||||
"commands/gsd-map-codebase.md": "9ed09a9a9ce48ffd",
|
||||
"commands/gsd-map-codebase.md": "5093093d42240325",
|
||||
"commands/gsd-mempalace-capture.md": "e9e00b0602e1d8b8",
|
||||
"commands/gsd-mempalace-recall.md": "6efef238d8c62569",
|
||||
"commands/gsd-milestone-summary.md": "e3a36fbd695a1833",
|
||||
@@ -73,9 +73,10 @@
|
||||
"commands/gsd-ns-context.md": "cc8954f405ae7916",
|
||||
"commands/gsd-ns-ideate.md": "fd1dd80d705e6e36",
|
||||
"commands/gsd-ns-manage.md": "5cc012d0be1caabb",
|
||||
"commands/gsd-ns-project.md": "52e30c8d84bb1ee5",
|
||||
"commands/gsd-ns-project.md": "76672fae61723e55",
|
||||
"commands/gsd-ns-review.md": "a28dbfd4aba5f74b",
|
||||
"commands/gsd-ns-workflow.md": "a1b874be9cf16caf",
|
||||
"commands/gsd-onboard.md": "63283b90aa671229",
|
||||
"commands/gsd-pause-work.md": "6caa75a7c2b4dd2d",
|
||||
"commands/gsd-phase.md": "e3ca4958ea20a935",
|
||||
"commands/gsd-plan-phase.md": "6de27d54ac191539",
|
||||
@@ -108,7 +109,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -157,6 +158,7 @@
|
||||
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
|
||||
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -243,7 +245,7 @@
|
||||
"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/project.md": "4f311fb1b05b823b",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -296,7 +298,7 @@
|
||||
"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/do.md": "7512dd1892f118a1",
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
@@ -313,10 +315,10 @@
|
||||
"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": "4c018bcc9ed4e55c",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
|
||||
"gsd-core/workflows/help/modes/brief.md": "924860e1f07defb0",
|
||||
"gsd-core/workflows/help/modes/default.md": "08a02976c0c5cc50",
|
||||
"gsd-core/workflows/help/modes/full.md": "3a19e96b69bcc33e",
|
||||
"gsd-core/workflows/help/modes/topic.md": "5c160093f3cbf35d",
|
||||
"gsd-core/workflows/import.md": "3d3fa603ceb8bc9f",
|
||||
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
|
||||
"gsd-core/workflows/ingest-docs.md": "c859921c811ac11b",
|
||||
@@ -334,6 +336,7 @@
|
||||
"gsd-core/workflows/next.md": "13fb800f2472d970",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "5a99eb396c744619",
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "7e7e9c1c99b83834",
|
||||
@@ -345,7 +348,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "4fa910d15dea5695",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "5e7dcdd864513b87",
|
||||
"gsd-core/workflows/quick.md": "9033dbe58443af36",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
@@ -438,7 +441,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "0eb81a1b2e1835d3",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "2df55e0ec95d57d9",
|
||||
"skills/gsd-manager/SKILL.md": "816db4d4ed618537",
|
||||
"skills/gsd-map-codebase/SKILL.md": "00622b489483ba69",
|
||||
"skills/gsd-map-codebase/SKILL.md": "b304ec25e9a95c93",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "5f10d368835e062c",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "70c8491f53f4161b",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "9eb030a1ac307fa1",
|
||||
@@ -449,9 +452,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "305b9b6248e8cc95",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "c63a1aed61888b35",
|
||||
"skills/gsd-ns-manage/SKILL.md": "6a5b711b5103c566",
|
||||
"skills/gsd-ns-project/SKILL.md": "345ea11ccffd21b0",
|
||||
"skills/gsd-ns-project/SKILL.md": "3711ce904d20ca66",
|
||||
"skills/gsd-ns-review/SKILL.md": "98f1f3b3caf1a279",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "b77682820700300b",
|
||||
"skills/gsd-onboard/SKILL.md": "6ad789a03124cd4f",
|
||||
"skills/gsd-pause-work/SKILL.md": "e2de20b7539e78d4",
|
||||
"skills/gsd-phase/SKILL.md": "f87211f779315ced",
|
||||
"skills/gsd-plan-phase/SKILL.md": "6134e837750034ab",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/codex.json
vendored
23
tests/fixtures/golden-install-parity/codex.json
vendored
@@ -73,7 +73,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -122,6 +122,7 @@
|
||||
"gsd-core/references/gates.md": "11fd2bdf27df57a5",
|
||||
"gsd-core/references/git-integration.md": "94715b69448bb818",
|
||||
"gsd-core/references/git-planning-commit.md": "5aad099c51135a0f",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -208,7 +209,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "56c67fdc5d31b556",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "a48a5a2ebd5a9f47",
|
||||
"gsd-core/templates/project.md": "034a6501f348c5a5",
|
||||
"gsd-core/templates/project.md": "c6005bd88a346a3e",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -261,7 +262,7 @@
|
||||
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
|
||||
"gsd-core/workflows/discuss-phase/templates/context.md": "9804674fccada405",
|
||||
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "2b56ec2868cbddb4",
|
||||
"gsd-core/workflows/do.md": "8f3e77a9486bc57b",
|
||||
"gsd-core/workflows/do.md": "feec00355f896a3d",
|
||||
"gsd-core/workflows/docs-update.md": "e255317df939e302",
|
||||
"gsd-core/workflows/edit-phase.md": "e592a4d85ce5380f",
|
||||
"gsd-core/workflows/eval-review.md": "63d0d0670b54c244",
|
||||
@@ -278,10 +279,10 @@
|
||||
"gsd-core/workflows/graduation.md": "47f1594c88c08501",
|
||||
"gsd-core/workflows/health.md": "5b835fc606fd3e9b",
|
||||
"gsd-core/workflows/help.md": "08e1349950c5602a",
|
||||
"gsd-core/workflows/help/modes/brief.md": "becceb85b25dca89",
|
||||
"gsd-core/workflows/help/modes/default.md": "18c7dc50066f5cd6",
|
||||
"gsd-core/workflows/help/modes/full.md": "8a7854a34df89bd8",
|
||||
"gsd-core/workflows/help/modes/topic.md": "303613ba8b123b9f",
|
||||
"gsd-core/workflows/help/modes/brief.md": "da44b130d1afe556",
|
||||
"gsd-core/workflows/help/modes/default.md": "4bb3082d28026eea",
|
||||
"gsd-core/workflows/help/modes/full.md": "01a1ec54df54da6e",
|
||||
"gsd-core/workflows/help/modes/topic.md": "b7c7e4a8800bc3ea",
|
||||
"gsd-core/workflows/import.md": "cb3c8f9d09edb434",
|
||||
"gsd-core/workflows/inbox.md": "61b8b10e7a74b2e9",
|
||||
"gsd-core/workflows/ingest-docs.md": "3cb763e0687d7ffa",
|
||||
@@ -299,6 +300,7 @@
|
||||
"gsd-core/workflows/next.md": "7097a9607a981086",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "664da466ab989f9d",
|
||||
"gsd-core/workflows/onboard.md": "83c40ba7055b8b24",
|
||||
"gsd-core/workflows/pause-work.md": "a217770ecafcb2e0",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "73d46f77c50a0690",
|
||||
"gsd-core/workflows/plan-phase.md": "34c4d4c7ad7fdf36",
|
||||
@@ -310,7 +312,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "d13e1cc81de40896",
|
||||
"gsd-core/workflows/profile-user.md": "05828c8cc61ef384",
|
||||
"gsd-core/workflows/progress.md": "7bedc431bb55edb8",
|
||||
"gsd-core/workflows/quick.md": "85fb48e52f0b3725",
|
||||
"gsd-core/workflows/quick.md": "3e7686705da2af19",
|
||||
"gsd-core/workflows/reapply-patches.md": "26297b84736e66a4",
|
||||
"gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4",
|
||||
"gsd-core/workflows/remove-workspace.md": "19d7465aaa50cb62",
|
||||
@@ -383,7 +385,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "1315033595643485",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "5bd2838bf0b6dc1b",
|
||||
"skills/gsd-manager/SKILL.md": "cb1cf56f5d3f66d6",
|
||||
"skills/gsd-map-codebase/SKILL.md": "25d0b3adfc8b75dc",
|
||||
"skills/gsd-map-codebase/SKILL.md": "cd27dc028718426b",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "1a7fac4d53f607ab",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "7205b02250e89f25",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "af84ecb400f23556",
|
||||
@@ -394,9 +396,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "9b496da79789b3f9",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "84ca1cde06110981",
|
||||
"skills/gsd-ns-manage/SKILL.md": "909dafa0cd19ba5a",
|
||||
"skills/gsd-ns-project/SKILL.md": "fb8ad6c2223be3c3",
|
||||
"skills/gsd-ns-project/SKILL.md": "936b6998d42520ac",
|
||||
"skills/gsd-ns-review/SKILL.md": "022253010db0e072",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "cba075bc819b539e",
|
||||
"skills/gsd-onboard/SKILL.md": "f42fc2edebeda671",
|
||||
"skills/gsd-pause-work/SKILL.md": "b379469eed78a196",
|
||||
"skills/gsd-phase/SKILL.md": "25477edc97a90c91",
|
||||
"skills/gsd-plan-phase/SKILL.md": "19ead1acb151a868",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74",
|
||||
"gsd-core/bin/gsd-tools.cjs": "747defe620cb8ae6",
|
||||
"gsd-core/bin/gsd-tools.cjs": "f2dd7a6993dc12f1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -88,6 +88,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "657a93c539c16cab",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "fca7e30efb0afe3f",
|
||||
"gsd-core/references/honest-verifier.md": "08b1e82f59c18078",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -174,7 +175,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "e2b07376944c84e8",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "e4a3b331be081e62",
|
||||
"gsd-core/templates/project.md": "fae14114f2b79b10",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -227,7 +228,7 @@
|
||||
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
|
||||
"gsd-core/workflows/discuss-phase/templates/context.md": "9804674fccada405",
|
||||
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "2b56ec2868cbddb4",
|
||||
"gsd-core/workflows/do.md": "988a20dab90c60c5",
|
||||
"gsd-core/workflows/do.md": "9b46119803df09a6",
|
||||
"gsd-core/workflows/docs-update.md": "9292cfa3c52c434e",
|
||||
"gsd-core/workflows/edit-phase.md": "8667c28b22b1599f",
|
||||
"gsd-core/workflows/eval-review.md": "81c8e72ba3862856",
|
||||
@@ -244,10 +245,10 @@
|
||||
"gsd-core/workflows/graduation.md": "f013efc29096faf0",
|
||||
"gsd-core/workflows/health.md": "f343fd32a0e398e4",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
|
||||
"gsd-core/workflows/help/modes/full.md": "0e9381367d6257bd",
|
||||
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "be05e56b2c5ee2c0",
|
||||
"gsd-core/workflows/help/modes/full.md": "ce88d4eb6b301e09",
|
||||
"gsd-core/workflows/help/modes/topic.md": "0bf9ab39d7044d69",
|
||||
"gsd-core/workflows/import.md": "f4fa65e332b00f7e",
|
||||
"gsd-core/workflows/inbox.md": "a448220c548f27bc",
|
||||
"gsd-core/workflows/ingest-docs.md": "52be14d1a0713731",
|
||||
@@ -265,6 +266,7 @@
|
||||
"gsd-core/workflows/next.md": "81c08abb34b2da88",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "4a5ee74cf2fc1f54",
|
||||
"gsd-core/workflows/onboard.md": "a62602c6f3fd538a",
|
||||
"gsd-core/workflows/pause-work.md": "9ce66367be6c40db",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "5cf589802d08bdf3",
|
||||
"gsd-core/workflows/plan-phase.md": "909457cfb58ce634",
|
||||
@@ -276,7 +278,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "2833905f119b5722",
|
||||
"gsd-core/workflows/profile-user.md": "5cc032206c99ef71",
|
||||
"gsd-core/workflows/progress.md": "6b9a84a43dc55af5",
|
||||
"gsd-core/workflows/quick.md": "89c34e07f1bf6f53",
|
||||
"gsd-core/workflows/quick.md": "7ddfe17f048541ec",
|
||||
"gsd-core/workflows/reapply-patches.md": "8fd59e24b486f180",
|
||||
"gsd-core/workflows/remove-phase.md": "e262654e319d1bc4",
|
||||
"gsd-core/workflows/remove-workspace.md": "ceddfeef5f2d6754",
|
||||
@@ -348,7 +350,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "cb9ed3865b67c04f",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "10500de29525f6cf",
|
||||
"skills/gsd-manager/SKILL.md": "9946975bec15e737",
|
||||
"skills/gsd-map-codebase/SKILL.md": "75806f8189d8af49",
|
||||
"skills/gsd-map-codebase/SKILL.md": "39e7fd6bcc561f9d",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "f4511f69c14befb7",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "89523db66f834b2f",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "5ca1dabfe0cd85f0",
|
||||
@@ -359,9 +361,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "d61edde87ae53105",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "ca62cdf78ebe3585",
|
||||
"skills/gsd-ns-manage/SKILL.md": "9b1b7f6d9b877fcd",
|
||||
"skills/gsd-ns-project/SKILL.md": "f1a666fb76527e6b",
|
||||
"skills/gsd-ns-project/SKILL.md": "597b8dde8702c22d",
|
||||
"skills/gsd-ns-review/SKILL.md": "6405d0b1cf3f245d",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "243287d21663ba87",
|
||||
"skills/gsd-onboard/SKILL.md": "4d325f3033df2d37",
|
||||
"skills/gsd-pause-work/SKILL.md": "4f0caa008a8001ff",
|
||||
"skills/gsd-phase/SKILL.md": "d38c7f9b1d0d2360",
|
||||
"skills/gsd-plan-phase/SKILL.md": "d257bb9b1786039d",
|
||||
|
||||
28
tests/fixtures/golden-install-parity/cursor.json
vendored
28
tests/fixtures/golden-install-parity/cursor.json
vendored
@@ -62,7 +62,7 @@
|
||||
"commands/gsd-inbox.md": "e6bcee3f863d20db",
|
||||
"commands/gsd-ingest-docs.md": "9e7f8757b9408c01",
|
||||
"commands/gsd-manager.md": "f300b708a5487c76",
|
||||
"commands/gsd-map-codebase.md": "cf50f4a600119c8a",
|
||||
"commands/gsd-map-codebase.md": "12f705917eb202fb",
|
||||
"commands/gsd-mempalace-capture.md": "b30bce634ac3136c",
|
||||
"commands/gsd-mempalace-recall.md": "08c910285469a394",
|
||||
"commands/gsd-milestone-summary.md": "adfad2cf43fc5aad",
|
||||
@@ -73,9 +73,10 @@
|
||||
"commands/gsd-ns-context.md": "b551dcc549c6a23f",
|
||||
"commands/gsd-ns-ideate.md": "32f561de74b1bc7b",
|
||||
"commands/gsd-ns-manage.md": "24445a84bd817c5b",
|
||||
"commands/gsd-ns-project.md": "b39b509ac4161d28",
|
||||
"commands/gsd-ns-project.md": "525f84d73f5c51e9",
|
||||
"commands/gsd-ns-review.md": "c8550463fbab5e76",
|
||||
"commands/gsd-ns-workflow.md": "2c1f1fde3c46bb40",
|
||||
"commands/gsd-onboard.md": "d9e52f558fc2b90f",
|
||||
"commands/gsd-pause-work.md": "59630f05f95fff68",
|
||||
"commands/gsd-phase.md": "9a073dcd0f934f90",
|
||||
"commands/gsd-plan-phase.md": "57de92131fcb16b8",
|
||||
@@ -108,7 +109,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "2525f1ae8b086828",
|
||||
"gsd-core/bin/gsd-tools.cjs": "887e2cb9ebdb609d",
|
||||
"gsd-core/bin/gsd-tools.cjs": "2493822b0ae27980",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -157,6 +158,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "9840f521ad6612b5",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -243,7 +245,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "213ccd947451ff2b",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "1f687c7a88381080",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -296,7 +298,7 @@
|
||||
"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": "cd5d01c9ef84ab73",
|
||||
"gsd-core/workflows/do.md": "dc0ddd1f8231d26b",
|
||||
"gsd-core/workflows/do.md": "9da6a73980ef2a71",
|
||||
"gsd-core/workflows/docs-update.md": "e86d7d7e2e3dac6d",
|
||||
"gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a",
|
||||
"gsd-core/workflows/eval-review.md": "a86279dd98dd5c03",
|
||||
@@ -313,10 +315,10 @@
|
||||
"gsd-core/workflows/graduation.md": "53a4a6fa3b4e6613",
|
||||
"gsd-core/workflows/health.md": "e4d770484b5e7496",
|
||||
"gsd-core/workflows/help.md": "08e1349950c5602a",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "37c5149aae232e61",
|
||||
"gsd-core/workflows/help/modes/full.md": "ed31d1afcbd3591f",
|
||||
"gsd-core/workflows/help/modes/topic.md": "177d351f7465787d",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "b88ff431fffe40ae",
|
||||
"gsd-core/workflows/help/modes/full.md": "a7a42d4340e8e00b",
|
||||
"gsd-core/workflows/help/modes/topic.md": "cee80e0adfa3b06c",
|
||||
"gsd-core/workflows/import.md": "5d5fb8e51f6a243a",
|
||||
"gsd-core/workflows/inbox.md": "797c287852eb8957",
|
||||
"gsd-core/workflows/ingest-docs.md": "c6759dbf17e79293",
|
||||
@@ -334,6 +336,7 @@
|
||||
"gsd-core/workflows/next.md": "174356dcfd7c6c32",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "1c1e466c764e3deb",
|
||||
"gsd-core/workflows/onboard.md": "20c28136423d40ac",
|
||||
"gsd-core/workflows/pause-work.md": "5716362557f44ce4",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e",
|
||||
"gsd-core/workflows/plan-phase.md": "d10fdacf2b9d850e",
|
||||
@@ -345,7 +348,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c67d90c65da47168",
|
||||
"gsd-core/workflows/profile-user.md": "8c943983241260b5",
|
||||
"gsd-core/workflows/progress.md": "65aabee5e8a6dd82",
|
||||
"gsd-core/workflows/quick.md": "db98fdbe79d68ffc",
|
||||
"gsd-core/workflows/quick.md": "762256cf6d177c06",
|
||||
"gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041",
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "433affcd1a200826",
|
||||
@@ -418,7 +421,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "f9b4ff17883ee7e4",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "01ed9183a00252ee",
|
||||
"skills/gsd-manager/SKILL.md": "62cd9f671a39e8a4",
|
||||
"skills/gsd-map-codebase/SKILL.md": "f529da27d3a30b09",
|
||||
"skills/gsd-map-codebase/SKILL.md": "383559aca64201e8",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "b308823dee547c65",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "56f3037091a8bd9a",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "c5dbbc8edb97a7f7",
|
||||
@@ -429,9 +432,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "dcf5711653d57354",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "0114f0bc5501c1e3",
|
||||
"skills/gsd-ns-manage/SKILL.md": "f3232d05d5263f5b",
|
||||
"skills/gsd-ns-project/SKILL.md": "9f737a544e8d3f31",
|
||||
"skills/gsd-ns-project/SKILL.md": "81d802dd3ee2e1ff",
|
||||
"skills/gsd-ns-review/SKILL.md": "8c686e92f293bfdd",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "c379192bd17d47d7",
|
||||
"skills/gsd-onboard/SKILL.md": "cae1f9382469fa00",
|
||||
"skills/gsd-pause-work/SKILL.md": "10e7531a2cb392ad",
|
||||
"skills/gsd-phase/SKILL.md": "1a2c9b64c1af7ffc",
|
||||
"skills/gsd-plan-phase/SKILL.md": "0784da05d41dcfe7",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/hermes.json
vendored
23
tests/fixtures/golden-install-parity/hermes.json
vendored
@@ -38,7 +38,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "3a3409215044af9f",
|
||||
"gsd-core/bin/gsd-tools.cjs": "cbdd5da1b973c6ae",
|
||||
"gsd-core/bin/gsd-tools.cjs": "3d032fe88d2dc09b",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -87,6 +87,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "12d23fcbaa7fcf06",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "769d1472c989cd54",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -173,7 +174,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "213ccd947451ff2b",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "1f687c7a88381080",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -226,7 +227,7 @@
|
||||
"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": "045e70c238db6faf",
|
||||
"gsd-core/workflows/do.md": "e37540cc874fb782",
|
||||
"gsd-core/workflows/docs-update.md": "4d6c06e611d83b6d",
|
||||
"gsd-core/workflows/edit-phase.md": "7f27003f20e88fb8",
|
||||
"gsd-core/workflows/eval-review.md": "f510e5762212dc6f",
|
||||
@@ -243,10 +244,10 @@
|
||||
"gsd-core/workflows/graduation.md": "d1fd52bbe41dcf34",
|
||||
"gsd-core/workflows/health.md": "c622a5ad0d347d30",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "bb6876c976996dbd",
|
||||
"gsd-core/workflows/help/modes/full.md": "fdab4b3690bdd11d",
|
||||
"gsd-core/workflows/help/modes/topic.md": "899d7168a485297f",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "8874dac94eb68ae6",
|
||||
"gsd-core/workflows/help/modes/full.md": "62779da88565d65a",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6e42db16f1568be9",
|
||||
"gsd-core/workflows/import.md": "a441445515bc2dd5",
|
||||
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
|
||||
"gsd-core/workflows/ingest-docs.md": "ad496a4c423f1ab7",
|
||||
@@ -264,6 +265,7 @@
|
||||
"gsd-core/workflows/next.md": "8e1fa29751d96564",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "42b66686b2c102cb",
|
||||
"gsd-core/workflows/onboard.md": "3c50ed1f1fd07619",
|
||||
"gsd-core/workflows/pause-work.md": "ae2d5789a95f70fe",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "c86cdc1964256b98",
|
||||
"gsd-core/workflows/plan-phase.md": "e30dcfb9615f19da",
|
||||
@@ -275,7 +277,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "ecabd55e4eabf229",
|
||||
"gsd-core/workflows/profile-user.md": "de5030437226cf2c",
|
||||
"gsd-core/workflows/progress.md": "f18db000584d9cb1",
|
||||
"gsd-core/workflows/quick.md": "66b26d6fe2bd4d71",
|
||||
"gsd-core/workflows/quick.md": "5044ce4e7512e174",
|
||||
"gsd-core/workflows/reapply-patches.md": "158083a310859594",
|
||||
"gsd-core/workflows/remove-phase.md": "fce799aae3ab2715",
|
||||
"gsd-core/workflows/remove-workspace.md": "8facde381657dd71",
|
||||
@@ -346,7 +348,7 @@
|
||||
"skills/gsd/gsd-ns-context/skills/docs-update/SKILL.md": "b059b3045d5daf5c",
|
||||
"skills/gsd/gsd-ns-context/skills/extract-learnings/SKILL.md": "36e59d6e514dcaf0",
|
||||
"skills/gsd/gsd-ns-context/skills/graphify/SKILL.md": "169eefd42c444cde",
|
||||
"skills/gsd/gsd-ns-context/skills/map-codebase/SKILL.md": "6062ccca1a14b827",
|
||||
"skills/gsd/gsd-ns-context/skills/map-codebase/SKILL.md": "9d36eee52586bb34",
|
||||
"skills/gsd/gsd-ns-context/skills/mempalace-capture/SKILL.md": "3fa619f73834c436",
|
||||
"skills/gsd/gsd-ns-context/skills/mempalace-recall/SKILL.md": "5d06922e0d7ab7d9",
|
||||
"skills/gsd/gsd-ns-ideate/SKILL.md": "b36bb4700ed6723a",
|
||||
@@ -374,7 +376,7 @@
|
||||
"skills/gsd/gsd-ns-manage/skills/update/SKILL.md": "433a236998c305b0",
|
||||
"skills/gsd/gsd-ns-manage/skills/workspace/SKILL.md": "d655afc6f16565ed",
|
||||
"skills/gsd/gsd-ns-manage/skills/workstreams/SKILL.md": "6893312eaf14dab0",
|
||||
"skills/gsd/gsd-ns-project/SKILL.md": "3ff43fdc0e4a6d02",
|
||||
"skills/gsd/gsd-ns-project/SKILL.md": "6b3af15e7c4441c9",
|
||||
"skills/gsd/gsd-ns-project/skills/audit-milestone/SKILL.md": "de0556f07d899e50",
|
||||
"skills/gsd/gsd-ns-project/skills/complete-milestone/SKILL.md": "180cccc09bf99444",
|
||||
"skills/gsd/gsd-ns-project/skills/import/SKILL.md": "bf781c7a0d83c2cd",
|
||||
@@ -382,6 +384,7 @@
|
||||
"skills/gsd/gsd-ns-project/skills/milestone-summary/SKILL.md": "c4bda5737f1137f8",
|
||||
"skills/gsd/gsd-ns-project/skills/new-milestone/SKILL.md": "47586c1fc8aa0645",
|
||||
"skills/gsd/gsd-ns-project/skills/new-project/SKILL.md": "8f22ba75717b3c88",
|
||||
"skills/gsd/gsd-ns-project/skills/onboard/SKILL.md": "1d8d06ac1f27dacd",
|
||||
"skills/gsd/gsd-ns-project/skills/profile-user/SKILL.md": "5a8e01bc7b873a20",
|
||||
"skills/gsd/gsd-ns-project/skills/review-backlog/SKILL.md": "f3ef97da4e9b1d4c",
|
||||
"skills/gsd/gsd-ns-review/SKILL.md": "23fbadeca640e2bd",
|
||||
|
||||
28
tests/fixtures/golden-install-parity/kilo.json
vendored
28
tests/fixtures/golden-install-parity/kilo.json
vendored
@@ -62,7 +62,7 @@
|
||||
"command/gsd-inbox.md": "1429fc7f556337c3",
|
||||
"command/gsd-ingest-docs.md": "e655ecf1d4433ba9",
|
||||
"command/gsd-manager.md": "1cdc133079833552",
|
||||
"command/gsd-map-codebase.md": "186c8f79319abc1c",
|
||||
"command/gsd-map-codebase.md": "704cb63214f51523",
|
||||
"command/gsd-mempalace-capture.md": "a94ca275cbe7134c",
|
||||
"command/gsd-mempalace-recall.md": "d358cdd89c935877",
|
||||
"command/gsd-milestone-summary.md": "4ab29b777f62f0bb",
|
||||
@@ -73,9 +73,10 @@
|
||||
"command/gsd-ns-context.md": "9a2b55b64f2e1e4b",
|
||||
"command/gsd-ns-ideate.md": "27e7fcdc69eeeb5f",
|
||||
"command/gsd-ns-manage.md": "eded59052ca4b240",
|
||||
"command/gsd-ns-project.md": "91417cf7b76581dd",
|
||||
"command/gsd-ns-project.md": "c56395edb9bc7467",
|
||||
"command/gsd-ns-review.md": "61fe33e28eb1b961",
|
||||
"command/gsd-ns-workflow.md": "13c1ab91080c0cc3",
|
||||
"command/gsd-onboard.md": "3e87a21c9c04f0d7",
|
||||
"command/gsd-pause-work.md": "04e993b1c9f8322b",
|
||||
"command/gsd-phase.md": "8f0e98dc6c223229",
|
||||
"command/gsd-plan-phase.md": "b37e9a61f946c975",
|
||||
@@ -108,7 +109,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -157,6 +158,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "fbdf814a3af9c051",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -243,7 +245,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "2e6fd96c2cc26470",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "e4a3b331be081e62",
|
||||
"gsd-core/templates/project.md": "fae14114f2b79b10",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -296,7 +298,7 @@
|
||||
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
|
||||
"gsd-core/workflows/discuss-phase/templates/context.md": "9804674fccada405",
|
||||
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "04e4feee14772275",
|
||||
"gsd-core/workflows/do.md": "1e6102574016b150",
|
||||
"gsd-core/workflows/do.md": "d9a889717e390f8d",
|
||||
"gsd-core/workflows/docs-update.md": "79afaaf19fd527cc",
|
||||
"gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a",
|
||||
"gsd-core/workflows/eval-review.md": "926eda8bbee28b23",
|
||||
@@ -313,10 +315,10 @@
|
||||
"gsd-core/workflows/graduation.md": "5cc8638dabcbfd40",
|
||||
"gsd-core/workflows/health.md": "064b3668d5d9569a",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
|
||||
"gsd-core/workflows/help/modes/full.md": "051f9633f45558b4",
|
||||
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "be05e56b2c5ee2c0",
|
||||
"gsd-core/workflows/help/modes/full.md": "775f766dfafb72f9",
|
||||
"gsd-core/workflows/help/modes/topic.md": "0bf9ab39d7044d69",
|
||||
"gsd-core/workflows/import.md": "f8bbe6f2c0e08a78",
|
||||
"gsd-core/workflows/inbox.md": "a073396097c89c01",
|
||||
"gsd-core/workflows/ingest-docs.md": "29c1fe28bc93b345",
|
||||
@@ -334,6 +336,7 @@
|
||||
"gsd-core/workflows/next.md": "888297a0b21888ea",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "f8c2842a2217f776",
|
||||
"gsd-core/workflows/onboard.md": "78b288089305dc46",
|
||||
"gsd-core/workflows/pause-work.md": "a6e5336c409fdc8b",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e",
|
||||
"gsd-core/workflows/plan-phase.md": "6f5eacc08e5d9d2a",
|
||||
@@ -345,7 +348,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd",
|
||||
"gsd-core/workflows/profile-user.md": "203ebe3f8f3876a8",
|
||||
"gsd-core/workflows/progress.md": "9381c59676ccb937",
|
||||
"gsd-core/workflows/quick.md": "11ba83f6350462fb",
|
||||
"gsd-core/workflows/quick.md": "fd7461e5a92fe450",
|
||||
"gsd-core/workflows/reapply-patches.md": "becf9728cdb124c4",
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "fc83f362a2d0a1b7",
|
||||
@@ -439,7 +442,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "34370c6138415209",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "f049f06a6ec7da43",
|
||||
"skills/gsd-manager/SKILL.md": "a37ecdbe32952262",
|
||||
"skills/gsd-map-codebase/SKILL.md": "e8c01704e8ada1d2",
|
||||
"skills/gsd-map-codebase/SKILL.md": "e06d6ac1b7632cbe",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "f960b7b1a8fdf89b",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "2091c48e6e986e28",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "da645171ca2299d8",
|
||||
@@ -450,9 +453,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "110fb2227bcde535",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "d093d59a8519decc",
|
||||
"skills/gsd-ns-manage/SKILL.md": "2756981de613022e",
|
||||
"skills/gsd-ns-project/SKILL.md": "757976d48e8b7a26",
|
||||
"skills/gsd-ns-project/SKILL.md": "56d3d8a321d267f8",
|
||||
"skills/gsd-ns-review/SKILL.md": "680bdd33c882b049",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "579b7f0dac74e1fb",
|
||||
"skills/gsd-onboard/SKILL.md": "0e3cc902f44b41b8",
|
||||
"skills/gsd-pause-work/SKILL.md": "34366b18a392a717",
|
||||
"skills/gsd-phase/SKILL.md": "64a241d4f8665aa2",
|
||||
"skills/gsd-plan-phase/SKILL.md": "c73cba04a26f0bfe",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/kimi.json
vendored
23
tests/fixtures/golden-install-parity/kimi.json
vendored
@@ -74,7 +74,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -123,6 +123,7 @@
|
||||
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
|
||||
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -209,7 +210,7 @@
|
||||
"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/project.md": "4f311fb1b05b823b",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -262,7 +263,7 @@
|
||||
"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/do.md": "7512dd1892f118a1",
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
@@ -279,10 +280,10 @@
|
||||
"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": "94c18706768e5a41",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
|
||||
"gsd-core/workflows/help/modes/brief.md": "924860e1f07defb0",
|
||||
"gsd-core/workflows/help/modes/default.md": "08a02976c0c5cc50",
|
||||
"gsd-core/workflows/help/modes/full.md": "ee47582c5d8d94a7",
|
||||
"gsd-core/workflows/help/modes/topic.md": "5c160093f3cbf35d",
|
||||
"gsd-core/workflows/import.md": "3d3fa603ceb8bc9f",
|
||||
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
|
||||
"gsd-core/workflows/ingest-docs.md": "c859921c811ac11b",
|
||||
@@ -300,6 +301,7 @@
|
||||
"gsd-core/workflows/next.md": "13fb800f2472d970",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "5a99eb396c744619",
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "bfd41e5f867bd751",
|
||||
@@ -311,7 +313,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
|
||||
"gsd-core/workflows/profile-user.md": "5abfae83739fa978",
|
||||
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
|
||||
"gsd-core/workflows/quick.md": "d722803b72217377",
|
||||
"gsd-core/workflows/quick.md": "e265e01cfe117215",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
@@ -382,7 +384,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "955779a4b1df1c33",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "6366644e623ae9e4",
|
||||
"skills/gsd-manager/SKILL.md": "8a11a1beb8e35a9c",
|
||||
"skills/gsd-map-codebase/SKILL.md": "55dade835b0f4954",
|
||||
"skills/gsd-map-codebase/SKILL.md": "73ab0afb2398de2c",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "64a2ec493092d26e",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "c6e0195e55cd934c",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "3ea93ccaa0d4d967",
|
||||
@@ -393,9 +395,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "89f5bffe6702c0c5",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "e0c0420f6878906a",
|
||||
"skills/gsd-ns-manage/SKILL.md": "b7fbe29e45e75f54",
|
||||
"skills/gsd-ns-project/SKILL.md": "1292d4a6a4280ce9",
|
||||
"skills/gsd-ns-project/SKILL.md": "fcb8a93ade9ecc72",
|
||||
"skills/gsd-ns-review/SKILL.md": "2ff62330379f947f",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "86272fd655c206bc",
|
||||
"skills/gsd-onboard/SKILL.md": "fd66b3c5b9c6a871",
|
||||
"skills/gsd-pause-work/SKILL.md": "95017c70ae9dca0d",
|
||||
"skills/gsd-phase/SKILL.md": "31578c329cc2583e",
|
||||
"skills/gsd-plan-phase/SKILL.md": "54798eddf30056b5",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"command/gsd-inbox.md": "cc850bab897aed1f",
|
||||
"command/gsd-ingest-docs.md": "b47c812c247e58a4",
|
||||
"command/gsd-manager.md": "86ece154b34b6d1b",
|
||||
"command/gsd-map-codebase.md": "1fb678f8a17a380e",
|
||||
"command/gsd-map-codebase.md": "9aba6b4eed565912",
|
||||
"command/gsd-mempalace-capture.md": "a94ca275cbe7134c",
|
||||
"command/gsd-mempalace-recall.md": "d358cdd89c935877",
|
||||
"command/gsd-milestone-summary.md": "86a827271bc647c5",
|
||||
@@ -73,9 +73,10 @@
|
||||
"command/gsd-ns-context.md": "9a2b55b64f2e1e4b",
|
||||
"command/gsd-ns-ideate.md": "27e7fcdc69eeeb5f",
|
||||
"command/gsd-ns-manage.md": "eded59052ca4b240",
|
||||
"command/gsd-ns-project.md": "91417cf7b76581dd",
|
||||
"command/gsd-ns-project.md": "c56395edb9bc7467",
|
||||
"command/gsd-ns-review.md": "61fe33e28eb1b961",
|
||||
"command/gsd-ns-workflow.md": "13c1ab91080c0cc3",
|
||||
"command/gsd-onboard.md": "3aafaeb5d3941efe",
|
||||
"command/gsd-pause-work.md": "bb5bf91a2e3e480e",
|
||||
"command/gsd-phase.md": "6bcda1539f949d5a",
|
||||
"command/gsd-plan-phase.md": "7ee44086b59862b9",
|
||||
@@ -108,7 +109,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -157,6 +158,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "fbdf814a3af9c051",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "4e5af9905e4276e4",
|
||||
"gsd-core/references/honest-verifier.md": "b67482409f896a10",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -243,7 +245,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "ee8880432b4d1566",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "e4a3b331be081e62",
|
||||
"gsd-core/templates/project.md": "fae14114f2b79b10",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -296,7 +298,7 @@
|
||||
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
|
||||
"gsd-core/workflows/discuss-phase/templates/context.md": "9804674fccada405",
|
||||
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "04e4feee14772275",
|
||||
"gsd-core/workflows/do.md": "d72e76d9050b53f3",
|
||||
"gsd-core/workflows/do.md": "85c4c8cb7aa8af87",
|
||||
"gsd-core/workflows/docs-update.md": "850366c2ef8fb780",
|
||||
"gsd-core/workflows/edit-phase.md": "1876c855fb0a0a39",
|
||||
"gsd-core/workflows/eval-review.md": "5394694d29ad7543",
|
||||
@@ -313,10 +315,10 @@
|
||||
"gsd-core/workflows/graduation.md": "13080b5c24eec27d",
|
||||
"gsd-core/workflows/health.md": "a91e0a8e5d20b2e9",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
|
||||
"gsd-core/workflows/help/modes/full.md": "604993c20f4e4b20",
|
||||
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "be05e56b2c5ee2c0",
|
||||
"gsd-core/workflows/help/modes/full.md": "35598c0ed5f7356f",
|
||||
"gsd-core/workflows/help/modes/topic.md": "0bf9ab39d7044d69",
|
||||
"gsd-core/workflows/import.md": "67fbf389ab8e57b4",
|
||||
"gsd-core/workflows/inbox.md": "a073396097c89c01",
|
||||
"gsd-core/workflows/ingest-docs.md": "82370f93c45c8015",
|
||||
@@ -334,6 +336,7 @@
|
||||
"gsd-core/workflows/next.md": "6719a37b8ce2ac4f",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "0d1374f2a2257858",
|
||||
"gsd-core/workflows/onboard.md": "102556e1715c01b9",
|
||||
"gsd-core/workflows/pause-work.md": "70c72beca55c080a",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "0a9dacd422cd9533",
|
||||
"gsd-core/workflows/plan-phase.md": "c771c9c99f4d777d",
|
||||
@@ -345,7 +348,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "929b7cb0c99c7b9e",
|
||||
"gsd-core/workflows/profile-user.md": "248d59a31948e0ed",
|
||||
"gsd-core/workflows/progress.md": "8fc3404087f50b95",
|
||||
"gsd-core/workflows/quick.md": "3866a5259e8912d8",
|
||||
"gsd-core/workflows/quick.md": "10cc8ddd4bc3f0ee",
|
||||
"gsd-core/workflows/reapply-patches.md": "a0e9b53f90abceb2",
|
||||
"gsd-core/workflows/remove-phase.md": "dea4661e8f89596f",
|
||||
"gsd-core/workflows/remove-workspace.md": "446847e71aa52504",
|
||||
@@ -440,7 +443,7 @@
|
||||
"skills/gsd-inbox/SKILL.md": "3b2dd319d91dbab4",
|
||||
"skills/gsd-ingest-docs/SKILL.md": "8f294b4894f782a5",
|
||||
"skills/gsd-manager/SKILL.md": "cf40f237b4f20aa7",
|
||||
"skills/gsd-map-codebase/SKILL.md": "6d74ea41e29ff035",
|
||||
"skills/gsd-map-codebase/SKILL.md": "73f714297815f74c",
|
||||
"skills/gsd-mempalace-capture/SKILL.md": "f960b7b1a8fdf89b",
|
||||
"skills/gsd-mempalace-recall/SKILL.md": "2091c48e6e986e28",
|
||||
"skills/gsd-milestone-summary/SKILL.md": "175366bb500b5e64",
|
||||
@@ -451,9 +454,10 @@
|
||||
"skills/gsd-ns-context/SKILL.md": "110fb2227bcde535",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "d093d59a8519decc",
|
||||
"skills/gsd-ns-manage/SKILL.md": "2756981de613022e",
|
||||
"skills/gsd-ns-project/SKILL.md": "757976d48e8b7a26",
|
||||
"skills/gsd-ns-project/SKILL.md": "56d3d8a321d267f8",
|
||||
"skills/gsd-ns-review/SKILL.md": "680bdd33c882b049",
|
||||
"skills/gsd-ns-workflow/SKILL.md": "579b7f0dac74e1fb",
|
||||
"skills/gsd-onboard/SKILL.md": "ea0b1217e5ecae23",
|
||||
"skills/gsd-pause-work/SKILL.md": "c7e9ba4f242c4648",
|
||||
"skills/gsd-phase/SKILL.md": "ee78c0c56c814d84",
|
||||
"skills/gsd-plan-phase/SKILL.md": "6abcc3a1568a6bb9",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/qwen.json
vendored
23
tests/fixtures/golden-install-parity/qwen.json
vendored
@@ -38,7 +38,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "6e98d76e955e35a2",
|
||||
"gsd-core/bin/gsd-tools.cjs": "1118403709188168",
|
||||
"gsd-core/bin/gsd-tools.cjs": "6e2a3fff91bacb3e",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -87,6 +87,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "bd249d9024c39c0d",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "10575c928ff9230d",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -173,7 +174,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "213ccd947451ff2b",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "1f687c7a88381080",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -226,7 +227,7 @@
|
||||
"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": "35862ba1f7230247",
|
||||
"gsd-core/workflows/do.md": "68f2607de614157d",
|
||||
"gsd-core/workflows/docs-update.md": "45d2f0d173c84e07",
|
||||
"gsd-core/workflows/edit-phase.md": "0fb5e0123cfc6f36",
|
||||
"gsd-core/workflows/eval-review.md": "6dee8a1e40ececd4",
|
||||
@@ -243,10 +244,10 @@
|
||||
"gsd-core/workflows/graduation.md": "40401655435beee3",
|
||||
"gsd-core/workflows/health.md": "043de14edb6a9723",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "cef457f954a9991e",
|
||||
"gsd-core/workflows/help/modes/full.md": "1e1e826ba1da0bc5",
|
||||
"gsd-core/workflows/help/modes/topic.md": "899d7168a485297f",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "7ca77077085452f5",
|
||||
"gsd-core/workflows/help/modes/full.md": "00c1433a639f6ed8",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6e42db16f1568be9",
|
||||
"gsd-core/workflows/import.md": "e56c18a984396066",
|
||||
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
|
||||
"gsd-core/workflows/ingest-docs.md": "f291310a8a89dbd8",
|
||||
@@ -264,6 +265,7 @@
|
||||
"gsd-core/workflows/next.md": "c13515a80c20d37d",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "42b66686b2c102cb",
|
||||
"gsd-core/workflows/onboard.md": "3c50ed1f1fd07619",
|
||||
"gsd-core/workflows/pause-work.md": "be33f84dc1d4822f",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "d98e98486123eb97",
|
||||
"gsd-core/workflows/plan-phase.md": "8ef2986df30b616e",
|
||||
@@ -275,7 +277,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "cef0f65b16d500b4",
|
||||
"gsd-core/workflows/profile-user.md": "263c0693563d98da",
|
||||
"gsd-core/workflows/progress.md": "3b1b2142a74af85c",
|
||||
"gsd-core/workflows/quick.md": "518740e30dcd4da3",
|
||||
"gsd-core/workflows/quick.md": "0a9a7dacc73f8e53",
|
||||
"gsd-core/workflows/reapply-patches.md": "de0ee8acfe7245b2",
|
||||
"gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0",
|
||||
"gsd-core/workflows/remove-workspace.md": "4ac64de862dc650e",
|
||||
@@ -345,7 +347,7 @@
|
||||
"skills/gsd-ns-context/skills/docs-update/SKILL.md": "3d41e81ec571de60",
|
||||
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "8d38e55e5f8b774b",
|
||||
"skills/gsd-ns-context/skills/graphify/SKILL.md": "ac3661e6e576188b",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "5009595dbde69739",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "cf65e61890448285",
|
||||
"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",
|
||||
@@ -373,7 +375,7 @@
|
||||
"skills/gsd-ns-manage/skills/update/SKILL.md": "530b4206d729b56d",
|
||||
"skills/gsd-ns-manage/skills/workspace/SKILL.md": "047c3f4247bc869e",
|
||||
"skills/gsd-ns-manage/skills/workstreams/SKILL.md": "f4e54cb9b1ca0442",
|
||||
"skills/gsd-ns-project/SKILL.md": "a58b3170197968be",
|
||||
"skills/gsd-ns-project/SKILL.md": "5d7ec93b195555e7",
|
||||
"skills/gsd-ns-project/skills/audit-milestone/SKILL.md": "edb6a0cc6305d724",
|
||||
"skills/gsd-ns-project/skills/complete-milestone/SKILL.md": "f6799a5a713be7bb",
|
||||
"skills/gsd-ns-project/skills/import/SKILL.md": "88f83921a85e536b",
|
||||
@@ -381,6 +383,7 @@
|
||||
"skills/gsd-ns-project/skills/milestone-summary/SKILL.md": "33d51a325d314f3a",
|
||||
"skills/gsd-ns-project/skills/new-milestone/SKILL.md": "ac99ffb8a966fe9d",
|
||||
"skills/gsd-ns-project/skills/new-project/SKILL.md": "9d5c27ebfa2c4746",
|
||||
"skills/gsd-ns-project/skills/onboard/SKILL.md": "46460b479b7524bf",
|
||||
"skills/gsd-ns-project/skills/profile-user/SKILL.md": "78ca46045e85223f",
|
||||
"skills/gsd-ns-project/skills/review-backlog/SKILL.md": "1708aab6cb919223",
|
||||
"skills/gsd-ns-review/SKILL.md": "7badceb8627d8154",
|
||||
|
||||
23
tests/fixtures/golden-install-parity/trae.json
vendored
23
tests/fixtures/golden-install-parity/trae.json
vendored
@@ -38,7 +38,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "de4627dff103d527",
|
||||
"gsd-core/bin/gsd-tools.cjs": "fa4db2ce1f77ef99",
|
||||
"gsd-core/bin/gsd-tools.cjs": "2ed2d11d5be017e2",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -87,6 +87,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "1bd40c3f94d712b1",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "0534754c2758352a",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -173,7 +174,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "213ccd947451ff2b",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "1f687c7a88381080",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -226,7 +227,7 @@
|
||||
"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": "f3328dbd4416f11a",
|
||||
"gsd-core/workflows/do.md": "5131e8796ba9d99b",
|
||||
"gsd-core/workflows/docs-update.md": "f13571f08e083356",
|
||||
"gsd-core/workflows/edit-phase.md": "7facd0faa33c8cad",
|
||||
"gsd-core/workflows/eval-review.md": "37d545d4f0db4927",
|
||||
@@ -243,10 +244,10 @@
|
||||
"gsd-core/workflows/graduation.md": "1ca877cda258a5de",
|
||||
"gsd-core/workflows/health.md": "1f9fd2deea45896f",
|
||||
"gsd-core/workflows/help.md": "08e1349950c5602a",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "1b3c315342701265",
|
||||
"gsd-core/workflows/help/modes/full.md": "62c71f57f047999b",
|
||||
"gsd-core/workflows/help/modes/topic.md": "229e3f3d85bda04b",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "ed7368e0d1b8644a",
|
||||
"gsd-core/workflows/help/modes/full.md": "b3287aa383806758",
|
||||
"gsd-core/workflows/help/modes/topic.md": "8a5344e56fa64ab9",
|
||||
"gsd-core/workflows/import.md": "31af58468db15daf",
|
||||
"gsd-core/workflows/inbox.md": "e9ea37b2d46dc5b4",
|
||||
"gsd-core/workflows/ingest-docs.md": "fba176ad3cc22f5f",
|
||||
@@ -264,6 +265,7 @@
|
||||
"gsd-core/workflows/next.md": "2163195861877de5",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "acc9130fb1e94f0b",
|
||||
"gsd-core/workflows/onboard.md": "61f111302af0f404",
|
||||
"gsd-core/workflows/pause-work.md": "c20d267e28ce92f0",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "26db7b9329b7ddc8",
|
||||
"gsd-core/workflows/plan-phase.md": "dbdcfdb7aeb4a4f0",
|
||||
@@ -275,7 +277,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "79fd55b88ea2db9c",
|
||||
"gsd-core/workflows/profile-user.md": "672821e6b1266645",
|
||||
"gsd-core/workflows/progress.md": "94768f835b0b8908",
|
||||
"gsd-core/workflows/quick.md": "8ac5f3919f754499",
|
||||
"gsd-core/workflows/quick.md": "9bbf907e39688638",
|
||||
"gsd-core/workflows/reapply-patches.md": "21b38c374f19fd78",
|
||||
"gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86",
|
||||
"gsd-core/workflows/remove-workspace.md": "ae0e1c6d4438d663",
|
||||
@@ -323,7 +325,7 @@
|
||||
"skills/gsd-ns-context/skills/docs-update/SKILL.md": "52755343585b987b",
|
||||
"skills/gsd-ns-context/skills/extract-learnings/SKILL.md": "d39269bea995fabc",
|
||||
"skills/gsd-ns-context/skills/graphify/SKILL.md": "e0253aef14693a7e",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "0910348701b1fc65",
|
||||
"skills/gsd-ns-context/skills/map-codebase/SKILL.md": "2982436d8652f172",
|
||||
"skills/gsd-ns-context/skills/mempalace-capture/SKILL.md": "36c486f14abe869a",
|
||||
"skills/gsd-ns-context/skills/mempalace-recall/SKILL.md": "2d95e385fee26dee",
|
||||
"skills/gsd-ns-ideate/SKILL.md": "c10342345c01c91f",
|
||||
@@ -351,7 +353,7 @@
|
||||
"skills/gsd-ns-manage/skills/update/SKILL.md": "2fc47bf059aa7b38",
|
||||
"skills/gsd-ns-manage/skills/workspace/SKILL.md": "257d17b9d20274d8",
|
||||
"skills/gsd-ns-manage/skills/workstreams/SKILL.md": "737841783c7fdd4f",
|
||||
"skills/gsd-ns-project/SKILL.md": "5668e3a5e1d8896d",
|
||||
"skills/gsd-ns-project/SKILL.md": "fac77a9e770fd655",
|
||||
"skills/gsd-ns-project/skills/audit-milestone/SKILL.md": "0ccabc5556d65d56",
|
||||
"skills/gsd-ns-project/skills/complete-milestone/SKILL.md": "a69d534b385d6aaf",
|
||||
"skills/gsd-ns-project/skills/import/SKILL.md": "a11e630bfd8ae4de",
|
||||
@@ -359,6 +361,7 @@
|
||||
"skills/gsd-ns-project/skills/milestone-summary/SKILL.md": "9d408f56712ba948",
|
||||
"skills/gsd-ns-project/skills/new-milestone/SKILL.md": "7cbc011130e8590c",
|
||||
"skills/gsd-ns-project/skills/new-project/SKILL.md": "ff0d57491df30624",
|
||||
"skills/gsd-ns-project/skills/onboard/SKILL.md": "ae4c7b799d9ffb09",
|
||||
"skills/gsd-ns-project/skills/profile-user/SKILL.md": "4f526120cda2f6cc",
|
||||
"skills/gsd-ns-project/skills/review-backlog/SKILL.md": "d8a150558cc9326a",
|
||||
"skills/gsd-ns-review/SKILL.md": "c5afe33c212947dd",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "5636ca0b726871b2",
|
||||
"gsd-core/bin/gsd-tools.cjs": "92e0f7b7eb55af51",
|
||||
"gsd-core/bin/gsd-tools.cjs": "ba7396b5e12d9b70",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -87,6 +87,7 @@
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "6ec36ea6b868655f",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "1bb6a410d6ebfb33",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -173,7 +174,7 @@
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "213ccd947451ff2b",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "1f687c7a88381080",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -226,7 +227,7 @@
|
||||
"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": "cd5d01c9ef84ab73",
|
||||
"gsd-core/workflows/do.md": "a7ff800fdab468c7",
|
||||
"gsd-core/workflows/do.md": "e2b848547e43c7a5",
|
||||
"gsd-core/workflows/docs-update.md": "70f73cc8c27e0ca0",
|
||||
"gsd-core/workflows/edit-phase.md": "c0ae7d0063f3e789",
|
||||
"gsd-core/workflows/eval-review.md": "b28be79ef29f16fd",
|
||||
@@ -243,10 +244,10 @@
|
||||
"gsd-core/workflows/graduation.md": "a766039ff6ca653f",
|
||||
"gsd-core/workflows/health.md": "7b19d6e2c0357c3e",
|
||||
"gsd-core/workflows/help.md": "08e1349950c5602a",
|
||||
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
|
||||
"gsd-core/workflows/help/modes/default.md": "91509fe03bad9795",
|
||||
"gsd-core/workflows/help/modes/full.md": "4c1a43f7095647e1",
|
||||
"gsd-core/workflows/help/modes/topic.md": "177d351f7465787d",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "6a253f1756f74e95",
|
||||
"gsd-core/workflows/help/modes/full.md": "0cd464dcd077bbfc",
|
||||
"gsd-core/workflows/help/modes/topic.md": "cee80e0adfa3b06c",
|
||||
"gsd-core/workflows/import.md": "029cacf530aca86b",
|
||||
"gsd-core/workflows/inbox.md": "797c287852eb8957",
|
||||
"gsd-core/workflows/ingest-docs.md": "78ba04ca39f28e8d",
|
||||
@@ -264,6 +265,7 @@
|
||||
"gsd-core/workflows/next.md": "d48cea140a5153cb",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "1c1e466c764e3deb",
|
||||
"gsd-core/workflows/onboard.md": "20c28136423d40ac",
|
||||
"gsd-core/workflows/pause-work.md": "93fcc1c845da6396",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "7880866ee1caf923",
|
||||
"gsd-core/workflows/plan-phase.md": "c7c5cac6409937ac",
|
||||
@@ -275,7 +277,7 @@
|
||||
"gsd-core/workflows/pr-branch.md": "acd59f915d018ad4",
|
||||
"gsd-core/workflows/profile-user.md": "c4313672b81b5bcd",
|
||||
"gsd-core/workflows/progress.md": "18813a345bd2343a",
|
||||
"gsd-core/workflows/quick.md": "0fcad72e7dbe99c9",
|
||||
"gsd-core/workflows/quick.md": "3acd391cc0c3813a",
|
||||
"gsd-core/workflows/reapply-patches.md": "d449a23d3acf6379",
|
||||
"gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b",
|
||||
"gsd-core/workflows/remove-workspace.md": "b5e60fbb33b3e33a",
|
||||
|
||||
28
tests/fixtures/golden-install-parity/zcode.json
vendored
28
tests/fixtures/golden-install-parity/zcode.json
vendored
@@ -62,7 +62,7 @@
|
||||
"commands/gsd-inbox.md": "da55c1408a6ee6cc",
|
||||
"commands/gsd-ingest-docs.md": "7a3c21ae5d5d133a",
|
||||
"commands/gsd-manager.md": "9f54653a7c589b00",
|
||||
"commands/gsd-map-codebase.md": "8b605921868c03a3",
|
||||
"commands/gsd-map-codebase.md": "709e9572953accc6",
|
||||
"commands/gsd-mempalace-capture.md": "5d4556e444acbb90",
|
||||
"commands/gsd-mempalace-recall.md": "5ac67fa4b7f2b03b",
|
||||
"commands/gsd-milestone-summary.md": "f8fa9c357c08b74b",
|
||||
@@ -73,9 +73,10 @@
|
||||
"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-project.md": "ff67e85bc6f7fc5a",
|
||||
"commands/gsd-ns-review.md": "3766ed10827882a0",
|
||||
"commands/gsd-ns-workflow.md": "c3b3c046a74ec0ee",
|
||||
"commands/gsd-onboard.md": "a35340ce39334fc7",
|
||||
"commands/gsd-pause-work.md": "40a953fcddbedb5d",
|
||||
"commands/gsd-phase.md": "5dd3d40e3461a973",
|
||||
"commands/gsd-plan-phase.md": "2fb6cc9c4caa6a36",
|
||||
@@ -108,7 +109,7 @@
|
||||
"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-tools.cjs": "f1a1a58072e7c35d",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a",
|
||||
@@ -157,6 +158,7 @@
|
||||
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
|
||||
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
|
||||
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
@@ -243,7 +245,7 @@
|
||||
"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/project.md": "4f311fb1b05b823b",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
@@ -296,7 +298,7 @@
|
||||
"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/do.md": "7512dd1892f118a1",
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
@@ -313,10 +315,10 @@
|
||||
"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/help/modes/brief.md": "924860e1f07defb0",
|
||||
"gsd-core/workflows/help/modes/default.md": "08a02976c0c5cc50",
|
||||
"gsd-core/workflows/help/modes/full.md": "895b35f264530d24",
|
||||
"gsd-core/workflows/help/modes/topic.md": "5c160093f3cbf35d",
|
||||
"gsd-core/workflows/import.md": "3d3fa603ceb8bc9f",
|
||||
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
|
||||
"gsd-core/workflows/ingest-docs.md": "c859921c811ac11b",
|
||||
@@ -334,6 +336,7 @@
|
||||
"gsd-core/workflows/next.md": "13fb800f2472d970",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "5a99eb396c744619",
|
||||
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
|
||||
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
|
||||
"gsd-core/workflows/plan-phase.md": "24a8ceaa8b032641",
|
||||
@@ -345,7 +348,7 @@
|
||||
"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/quick.md": "5747c2d8a37affbb",
|
||||
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
@@ -415,7 +418,7 @@
|
||||
"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/map-codebase/SKILL.md": "f48d48b43e2cc4bd",
|
||||
"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",
|
||||
@@ -443,7 +446,7 @@
|
||||
"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/SKILL.md": "5d7ec93b195555e7",
|
||||
"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",
|
||||
@@ -451,6 +454,7 @@
|
||||
"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/onboard/SKILL.md": "84519552f7396347",
|
||||
"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",
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1599,6 +1599,9 @@ describe('cmdInitNewProject', () => {
|
||||
test('brownfield with codebase map does not need map', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"test"}');
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'ARCHITECTURE', 'STRUCTURE', 'CONVENTIONS', 'TESTING', 'INTEGRATIONS', 'CONCERNS']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
|
||||
const result = runGsdTools('init new-project', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
483
tests/onboard-command.test.cjs
Normal file
483
tests/onboard-command.test.cjs
Normal file
@@ -0,0 +1,483 @@
|
||||
// allow-test-rule: source-text-is-the-product (see #1990)
|
||||
// Command/workflow markdown is deployed runtime product; source-text assertions
|
||||
// below verify the installed command contract. CLI assertions exercise real
|
||||
// gsd-tools behavior through the public command boundary.
|
||||
|
||||
const { describe, test, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, cleanup } = require('./helpers.cjs');
|
||||
const { createFixture } = require('./fixtures/index.cjs');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const CMD_PATH = path.join(ROOT, 'commands', 'gsd', 'onboard.md');
|
||||
const WF_PATH = path.join(ROOT, 'gsd-core', 'workflows', 'onboard.md');
|
||||
|
||||
describe('init onboard public CLI projection', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createFixture({ planning: false, projectDoc: false });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('reports brownfield code, docs, and missing planning state', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'src', 'server.ts'), 'export const server = true;\n');
|
||||
fs.mkdirSync(path.join(tmpDir, 'docs', 'adr'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'docs', 'adr', '0001-runtime.md'), '# ADR: Runtime\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.planning_exists, false);
|
||||
assert.strictEqual(parsed.project_exists, false);
|
||||
assert.strictEqual(parsed.has_existing_code, true);
|
||||
assert.strictEqual(parsed.has_codebase_map, false);
|
||||
assert.strictEqual(parsed.has_docs_candidates, true);
|
||||
assert.strictEqual(parsed.doc_candidate_count, 1);
|
||||
assert.deepStrictEqual(parsed.codebase_map_files_present, []);
|
||||
assert.ok(parsed.doc_candidates.includes('docs/adr/0001-runtime.md'));
|
||||
for (const file of ['STACK.md', 'ARCHITECTURE.md', 'STRUCTURE.md', 'CONVENTIONS.md', 'TESTING.md', 'INTEGRATIONS.md', 'CONCERNS.md']) {
|
||||
assert.ok(parsed.missing_codebase_map_files.includes(file), `missing map files should include ${file}`);
|
||||
}
|
||||
assert.strictEqual(parsed.onboarding_summary_exists, false);
|
||||
assert.strictEqual(parsed.text_mode, false);
|
||||
});
|
||||
|
||||
test('detects planning docs in top-level ADR, PRD, and RFC folders', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, 'prd'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'prd', 'product.md'), '# Product Requirements\n');
|
||||
fs.mkdirSync(path.join(tmpDir, 'adr'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'adr', 'decision.md'), '# Architecture Decision\n');
|
||||
fs.mkdirSync(path.join(tmpDir, 'rfc'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'rfc', 'proposal.md'), '# Request for Comments\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.has_docs_candidates, true);
|
||||
assert.strictEqual(parsed.doc_candidate_count, 3);
|
||||
assert.deepStrictEqual(parsed.doc_candidates, [
|
||||
'adr/decision.md',
|
||||
'prd/product.md',
|
||||
'rfc/proposal.md',
|
||||
]);
|
||||
});
|
||||
|
||||
test('detects root-level planning docs without broad repo scan', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'PRD.md'), '# Product Requirements\n');
|
||||
fs.writeFileSync(path.join(tmpDir, 'SPEC.md'), '# Specification\n');
|
||||
fs.writeFileSync(path.join(tmpDir, 'RFC.md'), '# Request for Comments\n');
|
||||
fs.writeFileSync(path.join(tmpDir, 'ADR.md'), '# Architecture Decision\n');
|
||||
fs.writeFileSync(path.join(tmpDir, 'REQUIREMENTS.md'), '# Requirements\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '0001-decision.md'), '# Decision\n');
|
||||
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'src', 'PRD.md'), '# Nested Product Requirements\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.has_docs_candidates, true);
|
||||
assert.strictEqual(parsed.doc_candidate_count, 6);
|
||||
assert.deepStrictEqual(parsed.doc_candidates, [
|
||||
'0001-decision.md',
|
||||
'ADR.md',
|
||||
'PRD.md',
|
||||
'REQUIREMENTS.md',
|
||||
'RFC.md',
|
||||
'SPEC.md',
|
||||
]);
|
||||
});
|
||||
|
||||
test('forwards the --text flag into the public projection', () => {
|
||||
const result = runGsdTools(['init', 'onboard', '--text', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.text_mode, true);
|
||||
});
|
||||
|
||||
test('reports complete codebase map and onboarding summary in existing planning', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'ARCHITECTURE', 'STRUCTURE', 'CONVENTIONS', 'TESTING', 'INTEGRATIONS', 'CONCERNS']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'onboarding'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'onboarding', 'SUMMARY.md'), '# Onboarding Summary\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# Project\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), '# Requirements\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'config.json'), JSON.stringify({ workflow: { text_mode: true } }));
|
||||
|
||||
const trackedFiles = [
|
||||
path.join(tmpDir, '.planning', 'PROJECT.md'),
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
path.join(tmpDir, '.planning', 'onboarding', 'SUMMARY.md'),
|
||||
];
|
||||
const before = new Map(trackedFiles.map(file => [file, fs.readFileSync(file, 'utf8')]));
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
for (const file of trackedFiles) {
|
||||
assert.strictEqual(fs.readFileSync(file, 'utf8'), before.get(file), `${path.basename(file)} must not be mutated by init onboard`);
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.planning_exists, true);
|
||||
assert.strictEqual(parsed.project_exists, true);
|
||||
assert.strictEqual(parsed.requirements_exists, true);
|
||||
assert.strictEqual(parsed.roadmap_exists, true);
|
||||
assert.strictEqual(parsed.state_exists, true);
|
||||
assert.strictEqual(parsed.has_codebase_map, true);
|
||||
assert.deepStrictEqual(parsed.missing_codebase_map_files, []);
|
||||
assert.strictEqual(parsed.onboarding_summary_exists, true);
|
||||
assert.strictEqual(parsed.onboarding_summary_path, '.planning/onboarding/SUMMARY.md');
|
||||
assert.strictEqual(parsed.text_mode, true);
|
||||
});
|
||||
|
||||
test('reports fast codebase map readiness and a complete-map handoff before new-project', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'INTEGRATIONS', 'ARCHITECTURE', 'STRUCTURE']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
|
||||
const result = runGsdTools(['init', 'onboard', '--fast', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.map_readiness, 'fast');
|
||||
assert.strictEqual(parsed.has_codebase_map, false);
|
||||
assert.strictEqual(parsed.has_fast_codebase_map, true);
|
||||
assert.strictEqual(parsed.needs_codebase_map, true);
|
||||
assert.strictEqual(parsed.needs_fast_codebase_map, false);
|
||||
assert.strictEqual(parsed.next_action.kind, 'complete-map-before-new-project');
|
||||
assert.strictEqual(parsed.next_action.command, '/gsd-map-codebase');
|
||||
assert.match(parsed.next_action.reason, /complete codebase map/i);
|
||||
assert.deepStrictEqual(parsed.fast_codebase_map_files_required, [
|
||||
'STACK.md',
|
||||
'INTEGRATIONS.md',
|
||||
'ARCHITECTURE.md',
|
||||
'STRUCTURE.md',
|
||||
]);
|
||||
assert.deepStrictEqual(parsed.missing_fast_codebase_map_files, []);
|
||||
});
|
||||
|
||||
test('routes fast mapped repositories with planning docs to complete map before ingest', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'INTEGRATIONS', 'ARCHITECTURE', 'STRUCTURE']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
fs.mkdirSync(path.join(tmpDir, 'docs', 'adr'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'docs', 'adr', '0001-runtime.md'), '# ADR: Runtime\n');
|
||||
|
||||
const result = runGsdTools(['init', 'onboard', '--fast', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.next_action.kind, 'complete-map-before-new-project');
|
||||
assert.strictEqual(parsed.next_action.command, '/gsd-map-codebase');
|
||||
});
|
||||
|
||||
test('routes planning artifacts without PROJECT.md to partial planning', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), '# Requirements\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.next_action.kind, 'partial-planning');
|
||||
assert.deepStrictEqual(parsed.next_action.missing, ['PROJECT.md']);
|
||||
});
|
||||
|
||||
test('fast mode leaves a fully set-up project at write-summary instead of the complete-map gate (regression #1990: fast map gate misroute)', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'INTEGRATIONS', 'ARCHITECTURE', 'STRUCTURE']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
for (const name of ['PROJECT', 'REQUIREMENTS', 'ROADMAP', 'STATE']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
|
||||
const result = runGsdTools(['init', 'onboard', '--fast', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.map_readiness, 'fast');
|
||||
assert.strictEqual(parsed.project_exists, true);
|
||||
// Once project setup is complete, a fast map must not misroute back to the
|
||||
// pre-new-project complete-map handoff; onboarding advances to the summary.
|
||||
assert.strictEqual(parsed.next_action.kind, 'write-summary');
|
||||
assert.strictEqual(parsed.next_action.summary_path, '.planning/onboarding/SUMMARY.md');
|
||||
});
|
||||
|
||||
test('fast mode routes incomplete planning to partial-planning before the complete-map gate (regression #1990: fast map gate misroute)', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'INTEGRATIONS', 'ARCHITECTURE', 'STRUCTURE']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# Project\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
||||
|
||||
const result = runGsdTools(['init', 'onboard', '--fast', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.map_readiness, 'fast');
|
||||
// Incomplete planning must surface before the fast-map complete-map handoff.
|
||||
assert.strictEqual(parsed.next_action.kind, 'partial-planning');
|
||||
assert.deepStrictEqual(parsed.next_action.missing, ['REQUIREMENTS.md']);
|
||||
});
|
||||
|
||||
test('projects the next action for code, docs, greenfield, partial planning, and summary states', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'src', 'server.ts'), 'export const server = true;\n');
|
||||
let result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'map-codebase',
|
||||
command: '/gsd-map-codebase',
|
||||
reason: 'Existing code was detected, but the required .planning/codebase/ map is missing.',
|
||||
});
|
||||
|
||||
cleanup(tmpDir);
|
||||
tmpDir = createFixture({ planning: false, projectDoc: false });
|
||||
fs.mkdirSync(path.join(tmpDir, 'docs', 'adr'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'docs', 'adr', '0001-runtime.md'), '# ADR: Runtime\n');
|
||||
result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'ingest-docs',
|
||||
command: '/gsd-ingest-docs',
|
||||
reason: 'Detected existing ADR/PRD/SPEC/RFC document(s) before project setup.',
|
||||
});
|
||||
|
||||
cleanup(tmpDir);
|
||||
tmpDir = createFixture({ planning: false, projectDoc: false });
|
||||
result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'new-project',
|
||||
command: '/gsd-new-project',
|
||||
reason: 'No existing code or planning docs were detected.',
|
||||
});
|
||||
|
||||
cleanup(tmpDir);
|
||||
tmpDir = createFixture({ planning: false, projectDoc: false });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# Project\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
||||
result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
const partial = JSON.parse(result.output);
|
||||
assert.strictEqual(partial.next_action.kind, 'partial-planning');
|
||||
assert.deepStrictEqual(partial.next_action.missing, ['REQUIREMENTS.md']);
|
||||
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'codebase'), { recursive: true });
|
||||
for (const name of ['STACK', 'ARCHITECTURE', 'STRUCTURE', 'CONVENTIONS', 'TESTING', 'INTEGRATIONS', 'CONCERNS']) {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'codebase', `${name}.md`), `# ${name}\n`);
|
||||
}
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), '# Requirements\n');
|
||||
result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'write-summary',
|
||||
summary_path: '.planning/onboarding/SUMMARY.md',
|
||||
reason: 'Onboarding summary is missing.',
|
||||
});
|
||||
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'onboarding'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'onboarding', 'SUMMARY.md'), '# Onboarding Summary\n');
|
||||
result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'ready',
|
||||
reason: 'Onboarding summary already exists.',
|
||||
});
|
||||
});
|
||||
|
||||
test('reports missing requirements in otherwise existing planning', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# Project\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.project_exists, true);
|
||||
assert.strictEqual(parsed.requirements_exists, false);
|
||||
assert.strictEqual(parsed.roadmap_exists, true);
|
||||
assert.strictEqual(parsed.state_exists, true);
|
||||
});
|
||||
|
||||
test('ignores generated and vendor directories when detecting existing code', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, 'node_modules', 'pkg'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'node_modules', 'pkg', 'index.ts'), 'export const ignored = true;\n');
|
||||
fs.mkdirSync(path.join(tmpDir, 'dist'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, 'dist', 'bundle.js'), 'console.log("ignored");\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.has_existing_code, false);
|
||||
assert.strictEqual(parsed.has_package_file, false);
|
||||
assert.strictEqual(parsed.is_brownfield, false);
|
||||
assert.strictEqual(parsed.needs_codebase_map, false);
|
||||
});
|
||||
|
||||
test('treats package manifests as brownfield even without source files', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
|
||||
const result = runGsdTools('init onboard --raw', tmpDir, { HOME: tmpDir });
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.has_existing_code, false);
|
||||
assert.strictEqual(parsed.has_package_file, true);
|
||||
assert.strictEqual(parsed.is_brownfield, true);
|
||||
assert.strictEqual(parsed.needs_codebase_map, true);
|
||||
});
|
||||
|
||||
|
||||
test('formats onboard handoff commands for the resolved runtime', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
|
||||
const result = runGsdTools(['init', 'onboard', '--raw'], tmpDir, {
|
||||
HOME: tmpDir,
|
||||
GSD_RUNTIME: 'codex',
|
||||
});
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.next_action.kind, 'map-codebase');
|
||||
assert.strictEqual(parsed.next_action.command, '$gsd-map-codebase');
|
||||
assert.deepStrictEqual(parsed.handoff_commands, {
|
||||
map_codebase: '$gsd-map-codebase',
|
||||
map_codebase_fast: '$gsd-map-codebase --fast',
|
||||
ingest_docs: '$gsd-ingest-docs',
|
||||
manager: '$gsd-manager',
|
||||
new_project: '$gsd-new-project',
|
||||
onboard: '$gsd-onboard',
|
||||
});
|
||||
});
|
||||
|
||||
test('dotted query init.onboard matches direct init onboard', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
|
||||
const direct = runGsdTools(['init', 'onboard', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
const query = runGsdTools(['query', 'init.onboard', '--raw'], tmpDir, { HOME: tmpDir });
|
||||
assert.equal(direct.success, true, direct.error || direct.output);
|
||||
assert.equal(query.success, true, query.error || query.output);
|
||||
assert.deepStrictEqual(JSON.parse(query.output), JSON.parse(direct.output));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('/gsd:onboard command contract', () => {
|
||||
test('command file declares the onboard command and loads its workflow', () => {
|
||||
const content = fs.readFileSync(CMD_PATH, 'utf8');
|
||||
assert.match(content, /^name:\s*gsd:onboard$/m);
|
||||
assert.match(content, /^description:\s*.*(?:existing codebase|brownfield|onboard).*$/mi);
|
||||
assert.match(content, /^\s*- AskUserQuestion$/m);
|
||||
assert.match(content, /^\s*- Agent$/m);
|
||||
assert.ok(content.includes('@~/.claude/gsd-core/workflows/onboard.md'));
|
||||
assert.ok(content.includes('@~/.claude/gsd-core/references/ui-brand.md'));
|
||||
assert.ok(content.includes('@~/.claude/gsd-core/references/gate-prompts.md'));
|
||||
});
|
||||
|
||||
test('workflow renders the init projection without owning the route state machine', () => {
|
||||
const content = fs.readFileSync(WF_PATH, 'utf8');
|
||||
|
||||
assert.ok(content.includes('@~/.claude/gsd-core/references/gsd-run-resolver.md'));
|
||||
assert.ok(
|
||||
!content.includes('_GSD_SHIM_NAME="gsd-tools.cjs"'),
|
||||
'workflow must reference the shared resolver instead of inlining it',
|
||||
);
|
||||
assert.match(content, /init onboard --fast --raw/);
|
||||
assert.match(content, /init onboard --raw/);
|
||||
assert.match(content, /next_action\.kind/);
|
||||
assert.match(content, /map_readiness/);
|
||||
assert.match(content, /codebase_map_summary_status/);
|
||||
assert.match(content, /ONBOARDING_ROOT=\{git_worktree_root \|\| _GSD_RUNTIME_ROOT\}/);
|
||||
|
||||
for (const action of [
|
||||
'map-codebase',
|
||||
'ingest-docs',
|
||||
'complete-map-before-new-project',
|
||||
'new-project',
|
||||
'partial-planning',
|
||||
'write-summary',
|
||||
'ready',
|
||||
]) {
|
||||
assert.ok(content.includes(`next_action.kind == "${action}"`), `workflow must render ${action}`);
|
||||
}
|
||||
|
||||
assert.ok(content.includes('AskUserQuestion'), 'workflow must still support interactive choices');
|
||||
assert.ok(content.includes('--text'), 'workflow must document text-mode fallback');
|
||||
assert.ok(
|
||||
content.includes('Exit. If the user skips mapping:'),
|
||||
'skip mapping must hand off explicitly instead of falling through to summary creation',
|
||||
);
|
||||
const partialPlanningSkipGuard =
|
||||
'If `(project_exists || requirements_exists || roadmap_exists || state_exists) && (!project_exists || !requirements_exists || !roadmap_exists || !state_exists)`, route the skip to the partial planning guard instead:';
|
||||
const docsIngestSkipGuard =
|
||||
'If `has_docs_candidates && !project_exists`, route the skip to docs ingest instead:';
|
||||
assert.ok(
|
||||
content.includes(partialPlanningSkipGuard),
|
||||
'skip mapping must preserve the partial-planning guard when incomplete planning artifacts are present',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes(docsIngestSkipGuard),
|
||||
'skip mapping must preserve the docs-ingest gate when planning docs are present',
|
||||
);
|
||||
assert.ok(
|
||||
content.indexOf(partialPlanningSkipGuard) < content.indexOf(docsIngestSkipGuard),
|
||||
'skip mapping must check partial planning before docs ingest',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('{handoff_commands.ingest_docs}'),
|
||||
'skip mapping docs-preserving handoff must use the projected docs ingest command',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('Skipping codebase mapping may give {handoff_commands.new_project} weaker context.'),
|
||||
'skip mapping must warn about weaker context',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('Exit. If the user skips docs ingest, print:'),
|
||||
'skip docs ingest must hand off explicitly instead of falling through to summary creation',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('Skipping docs ingest may omit existing ADR/PRD/SPEC/RFC context from {handoff_commands.new_project}.'),
|
||||
'skip docs ingest must warn about omitted docs context',
|
||||
);
|
||||
assert.match(content, /do not overwrite/i, 'workflow must protect existing summary/planning');
|
||||
assert.match(content, /query commit "docs: create onboarding summary" --files \.planning\/onboarding\/SUMMARY\.md/);
|
||||
assert.ok(!content.includes('execute-phase'), 'onboarding must not execute implementation phases');
|
||||
assert.ok(!content.includes('gsd:ship'), 'onboarding must not ship work');
|
||||
assert.ok(content.includes('Next recommended command: {handoff_commands.manager}'));
|
||||
assert.ok(!content.includes('/gsd:ingest-docs'), 'skip handoffs must not hard-code legacy ingest command');
|
||||
assert.ok(!content.includes('/gsd:new-project'), 'skip handoffs must not hard-code legacy new-project command');
|
||||
});
|
||||
});
|
||||
@@ -380,6 +380,14 @@ describe('resolveProfile', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('standard profile includes the onboard manager handoff dependency', () => {
|
||||
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
|
||||
const result = resolveProfile({ modes: ['standard'], manifest });
|
||||
assert.ok(result.skills instanceof Set);
|
||||
assert.ok(result.skills.has('onboard'), 'standard profile should include onboard');
|
||||
assert.ok(result.skills.has('manager'), 'onboard handoff must install gsd-manager');
|
||||
});
|
||||
|
||||
test('resolves full profile — returns sentinel', () => {
|
||||
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
|
||||
const result = resolveProfile({ modes: ['full'], manifest });
|
||||
|
||||
@@ -136,6 +136,22 @@ function collectAgentFiles() {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* A workflow/agent file "delegates to the shared resolver" when it pulls the
|
||||
* canonical gsd_run preamble in from gsd-core/references/gsd-run-resolver.md via
|
||||
* an @-include instead of inlining the snippet (see onboard.md / issue #1990).
|
||||
*
|
||||
* Such files are exempt from the inline-preamble parity checks (B / G / H and the
|
||||
* runtime-home propagation checks): they intentionally do NOT inline the preamble
|
||||
* — onboard-command.test.cjs even asserts the absence of the inline form. Their
|
||||
* resolver correctness is guaranteed transitively by:
|
||||
* (1) onboard-command.test.cjs asserting the @-include is present, and
|
||||
* (2) the "resolver reference stays byte-equal to the snippet" guard (B2) below.
|
||||
*/
|
||||
function delegatesToResolverReference(content) {
|
||||
return content.includes('references/gsd-run-resolver.md');
|
||||
}
|
||||
|
||||
describe('runtime-launcher-parity (#373)', () => {
|
||||
// ─── (A) No retired GSD_SDK token ────────────────────────────────────────
|
||||
test('(A) no GSD_SDK token in any workflow .md file', () => {
|
||||
@@ -170,6 +186,9 @@ describe('runtime-launcher-parity (#373)', () => {
|
||||
for (const f of files) {
|
||||
const rel = path.relative(WORKFLOWS_DIR, f);
|
||||
const content = fs.readFileSync(f, 'utf8');
|
||||
// Files that delegate to the shared resolver reference (@-include) do not
|
||||
// inline the preamble — exempt them (see delegatesToResolverReference / (B2)).
|
||||
if (delegatesToResolverReference(content)) continue;
|
||||
const blocks = extractShellBlocks(content);
|
||||
|
||||
// Collect all block lines in document order for flat analysis
|
||||
@@ -226,6 +245,30 @@ describe('runtime-launcher-parity (#373)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// ─── (B2) Shared resolver reference stays byte-equal to the snippet ───────
|
||||
// Workflows may delegate to gsd-core/references/gsd-run-resolver.md instead of
|
||||
// inlining the preamble (see delegatesToResolverReference). That delegation is
|
||||
// only safe if the reference's bash block is byte-equal to the canonical
|
||||
// snippet — otherwise a delegating workflow (e.g. onboard.md) would silently
|
||||
// ship a drifted resolver. This guard replaces the inline-preamble checks for
|
||||
// those files.
|
||||
test('(B2) references/gsd-run-resolver.md preamble is byte-equal to the canonical snippet', () => {
|
||||
const preambleStr = expectedPreamble().join('\n');
|
||||
const refPath = path.join(__dirname, '..', 'gsd-core', 'references', 'gsd-run-resolver.md');
|
||||
const refContent = fs.readFileSync(refPath, 'utf8');
|
||||
const refPreamble = extractShellBlocks(refContent)
|
||||
.map((b) => b.lines.join('\n'))
|
||||
.join('\n')
|
||||
.trim();
|
||||
assert.equal(
|
||||
refPreamble,
|
||||
preambleStr,
|
||||
'gsd-core/references/gsd-run-resolver.md must contain the canonical gsd_run preamble ' +
|
||||
'byte-equal to _runtime-launcher.snippet.sh. Re-copy the snippet into the reference so ' +
|
||||
'workflows that delegate to it via @-include ship the current resolver.',
|
||||
);
|
||||
});
|
||||
|
||||
// ─── (C) Space-safe behavioral test ──────────────────────────────────────
|
||||
test('(C) gsd_run works with a RUNTIME_DIR path containing spaces', () => {
|
||||
// Create temp dir whose path contains a space
|
||||
@@ -402,6 +445,7 @@ describe('runtime-launcher-parity (#373)', () => {
|
||||
const content = fs.readFileSync(f, 'utf8');
|
||||
const blocks = extractShellBlocks(content);
|
||||
const allBlockLines = blocks.flatMap((b) => b.lines);
|
||||
if (delegatesToResolverReference(content)) continue;
|
||||
const fileHasGsdRun = allBlockLines.some((l) => /\bgsd_run\b/.test(l));
|
||||
if (!fileHasGsdRun) continue;
|
||||
const allContent = allBlockLines.join('\n');
|
||||
@@ -433,6 +477,7 @@ describe('runtime-launcher-parity (#373)', () => {
|
||||
const content = fs.readFileSync(f, 'utf8');
|
||||
const blocks = extractShellBlocks(content);
|
||||
const allBlockLines = blocks.flatMap((b) => b.lines);
|
||||
if (delegatesToResolverReference(content)) continue;
|
||||
const fileHasGsdRun = allBlockLines.some((l) => /\bgsd_run\b/.test(l));
|
||||
if (!fileHasGsdRun) continue;
|
||||
if (!allBlockLines.join('\n').includes(CODEX_HOME_PROBE)) {
|
||||
@@ -1346,6 +1391,7 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
const content = fs.readFileSync(f, 'utf8');
|
||||
const blocks = extractShellBlocks(content);
|
||||
const allBlockLines = blocks.flatMap((b) => b.lines);
|
||||
if (delegatesToResolverReference(content)) continue;
|
||||
const fileHasGsdRun = allBlockLines.some((l) => /\bgsd_run\b/.test(l));
|
||||
if (!fileHasGsdRun) continue;
|
||||
const allContent = allBlockLines.join('\n');
|
||||
|
||||
@@ -476,6 +476,8 @@ const KNOWN_SKILLS = new Set([
|
||||
'mvp-phase.md',
|
||||
'new-milestone.md',
|
||||
'new-project.md',
|
||||
// #1990 brownfield onboarding entry command; sibling of new-project, routed under ns-project.
|
||||
'onboard.md',
|
||||
// `next.md` was legitimately reclaimed (#1787): the old workflow-advance
|
||||
// command stays absorbed into `progress.md --next`, while the current
|
||||
// `/gsd:next` is a different state-aware smart-entry launcher.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"discuss-phase-assumptions.md": 27028,
|
||||
"discuss-phase-power.md": 11273,
|
||||
"discuss-phase.md": 31986,
|
||||
"do.md": 10112,
|
||||
"do.md": 10353,
|
||||
"docs-update.md": 55706,
|
||||
"edit-phase.md": 12927,
|
||||
"eval-review.md": 9967,
|
||||
@@ -50,6 +50,7 @@
|
||||
"next.md": 20138,
|
||||
"node-repair.md": 4173,
|
||||
"note.md": 6563,
|
||||
"onboard.md": 8590,
|
||||
"pause-work.md": 14441,
|
||||
"plan-milestone-gaps.md": 11809,
|
||||
"plan-phase.md": 92251,
|
||||
@@ -58,7 +59,7 @@
|
||||
"pr-branch.md": 15963,
|
||||
"profile-user.md": 21246,
|
||||
"progress.md": 30599,
|
||||
"quick.md": 49183,
|
||||
"quick.md": 50452,
|
||||
"reapply-patches.md": 20312,
|
||||
"remove-phase.md": 8513,
|
||||
"remove-workspace.md": 7551,
|
||||
|
||||
Reference in New Issue
Block a user