Merge branch 'next' into fix/1698-codex-output-last-message

This commit is contained in:
Tom Boucher
2026-06-30 10:04:59 -04:00
committed by GitHub
127 changed files with 4916 additions and 576 deletions

View File

@@ -32,5 +32,5 @@
"gsd-ui-checker.md": 11088,
"gsd-ui-researcher.md": 19332,
"gsd-user-profiler.md": 8516,
"gsd-verifier.md": 48859
"gsd-verifier.md": 49124
}

View File

@@ -87,3 +87,116 @@ describe('#1761: state sync leaves Progress untouched when milestone is unbounde
`Progress must be left untouched when the milestone is unbounded; before=${JSON.stringify(before)} after=${JSON.stringify(after)} (#1761)`);
});
});
// #1761 read-path: the ADR-1769 Phase 7 fix (#1794) closed the `state sync`
// WRITE path, but `state json` (the READ path) rebuilds progress via
// buildStateFrontmatter, whose roadmapPhaseCount loop counts phase headings
// across the WHOLE document when extractCurrentMilestone can't bound the
// asserted milestone. Result: state json reported a conflated total_phases
// (sum of sibling milestones) + a derived percent, contradicting the sync
// guard. This block mirrors the write-path guard on the read path.
describe('#1761 read-path: state json does not conflate progress when milestone is unbounded', () => {
let tmpDir;
beforeEach(() => { tmpDir = createTempProject(); });
afterEach(() => { cleanup(tmpDir); });
test('state json omits percent and does NOT report the conflated whole-doc total_phases', () => {
// Repro from the issue: STATE.md asserts milestone: v2.0; ROADMAP has two
// UNVERSIONED sibling milestones (4 + 4 phases) — neither matches v2.0, so
// the milestone is unbounded. One summarized phase dir on disk.
const statePath = path.join(tmpDir, '.planning', 'STATE.md');
fs.writeFileSync(statePath, [
'---',
'gsd_state_version: 1.0',
'milestone: v2.0',
'milestone_name: Second',
'current_phase: "2"',
'status: executing',
'---',
'',
'# GSD State',
'**Current Phase:** 2',
'**Status:** Executing Phase 2',
'',
].join('\n'));
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), [
'# ROADMAP',
'## Milestone 1: First Milestone',
'### Phase 1: a',
'### Phase 2: b',
'### Phase 3: c',
'### Phase 4: d',
'## Milestone 2: Second Milestone',
'### Phase 5: e',
'### Phase 6: f',
'### Phase 7: g',
'### Phase 8: h',
'',
].join('\n'));
// One summarized phase dir on disk.
const dir01 = path.join(tmpDir, '.planning', 'phases', '01');
fs.mkdirSync(dir01, { recursive: true });
fs.writeFileSync(path.join(dir01, '01-PLAN.md'), '# Plan\n');
fs.writeFileSync(path.join(dir01, '01-SUMMARY.md'), '# Summary\n');
const result = runGsdTools('state json --raw', tmpDir);
assert.ok(result.success, `state json failed: ${result.error}`);
const out = JSON.parse(result.output);
// BEFORE the fix this printed progress.total_phases: 8 (4+4 sibling
// milestones) and percent: 13 — exactly the conflated read-path the sync
// guard was added to prevent.
assert.ok(
out.progress === undefined || out.progress.percent === undefined,
`state json must omit percent when the milestone is unbounded; got progress=${JSON.stringify(out.progress)}`,
);
assert.ok(
!(out.progress && out.progress.total_phases === 8),
`state json must NOT report the conflated whole-doc total_phases (8 = 4+4 sibling milestones); got total_phases=${out.progress && out.progress.total_phases}`,
);
});
test('state json still reports percent + total_phases when the milestone IS bounded (versioned ROADMAP)', () => {
// Control: a versioned ROADMAP heading matching the asserted milestone
// keeps the read path unchanged — the guard only fires when unbounded.
const statePath = path.join(tmpDir, '.planning', 'STATE.md');
fs.writeFileSync(statePath, [
'---',
'gsd_state_version: 1.0',
'milestone: v1.0',
'milestone_name: First',
'current_phase: "1"',
'status: executing',
'---',
'',
'# GSD State',
'**Current Phase:** 1',
'**Status:** Executing Phase 1',
'',
].join('\n'));
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), [
'# ROADMAP',
'## Milestone 1: First Milestone v1.0',
'### Phase 1: a',
'### Phase 2: b',
'',
].join('\n'));
const dir01 = path.join(tmpDir, '.planning', 'phases', '01');
fs.mkdirSync(dir01, { recursive: true });
fs.writeFileSync(path.join(dir01, '01-PLAN.md'), '# Plan\n');
fs.writeFileSync(path.join(dir01, '01-SUMMARY.md'), '# Summary\n');
const result = runGsdTools('state json --raw', tmpDir);
assert.ok(result.success, `state json failed: ${result.error}`);
const out = JSON.parse(result.output);
assert.ok(
out.progress && typeof out.progress.percent === 'number',
`state json must report a numeric percent when the milestone is bounded; got progress=${JSON.stringify(out.progress)}`,
);
assert.strictEqual(
out.progress.total_phases,
2,
'bounded read path must report the versioned milestone phase count (2)',
);
});
});

View File

@@ -9,6 +9,9 @@
* #2535 — sub_repos and plan_checker legacy keys need CONFIG_KEY_SUGGESTIONS migration hints
* #3162 — resolve_model_ids missing from VALID_CONFIG_KEYS; workflow._auto_chain_active must be
* accepted by isValidConfigKey (written by workflows) without being user-visible
* #1747 — buildNewProjectConfig emits four search-provider keys (tavily_search, ref_search,
* perplexity, jina) that research-provider.cts consumes but were missing from
* VALID_CONFIG_KEYS, causing /gsd-settings unknown-key warnings on fresh projects
*/
const { describe, test } = require('node:test');
@@ -74,6 +77,48 @@ describe('VALID_CONFIG_KEYS correctness', () => {
});
});
describe('#1747: new-project config emits only schema-recognized provider keys', () => {
// buildNewProjectConfig emits seven search-provider availability flags and
// research-provider.cts providerAvailability() consumes all seven, but only
// three were in VALID_CONFIG_KEYS → /gsd-settings warned on the four
// unregistered keys (tavily_search, ref_search, perplexity, jina) for every
// freshly generated .planning/config.json.
// The four keys that were emitted + consumed but missing from the schema.
const MISSING_KEYS = ['tavily_search', 'ref_search', 'perplexity', 'jina'];
// Every config-driven provider flag read by providerAvailability() in
// src/research-provider.cts. context7/websearch are excluded: hardcoded
// `true`, not config-gated, so no config key to register.
const PROVIDER_CONFIG_KEYS = [
'brave_search',
'firecrawl',
'exa_search',
'tavily_search',
'ref_search',
'perplexity',
'jina',
];
test('the four previously-missing provider keys are in VALID_CONFIG_KEYS', () => {
const absent = MISSING_KEYS.filter((k) => !VALID_CONFIG_KEYS.has(k));
assert.deepStrictEqual(
absent,
[],
`These provider keys are emitted by buildNewProjectConfig and consumed by research-provider.cts but missing from VALID_CONFIG_KEYS:\n ${absent.join('\n ')}\n\nAdd them to gsd-core/bin/shared/config-schema.manifest.json (validKeys).`
);
});
test('every config-driven research-provider flag is registered in the schema (drift guard)', () => {
const drifted = PROVIDER_CONFIG_KEYS.filter((k) => !VALID_CONFIG_KEYS.has(k));
assert.deepStrictEqual(
drifted,
[],
`These research-provider config flags are not in VALID_CONFIG_KEYS — a fresh /gsd-new-project config would trigger an unknown-key warning under /gsd-settings:\n ${drifted.join('\n ')}\n\nWhen you add a provider to providerAvailability() in src/research-provider.cts, also register its config key in gsd-core/bin/shared/config-schema.manifest.json.`
);
});
});
describe('ADR-857 Phase 6 capability config ownership', () => {
test('migrated capability config keys are valid through the registry, not central schema residue', () => {
const capabilityKeys = Object.keys(capabilityRegistry.configSchema || {}).sort();

View File

@@ -133,6 +133,34 @@ Plans:
assert.ok(roadmap.includes(sharedTruth), 'shared truth listed');
});
test('#1154: surfaces a cross-cutting backstop (object-form) truth by its statement, not dropped', () => {
// An object-form backstop truth `{ statement, verification: backstop }` (the #1154 non-inferable
// marker on must_haves.truths) shared across 2 plans must be coerced by its `statement` — the
// Hyrum backward-compat guard: a truth-reader must tolerate the new object form, never drop it.
const backstopTruth = 'statement: Adjacent touching intervals merge\n verification: backstop';
tmpDir = makePlanProject({
'.planning/ROADMAP.md': `# Roadmap
### Phase 1: Foundation
**Goal:** Set up project
**Plans:** 2 plans
Plans:
- [ ] 01-01-PLAN.md — Set up DB
- [ ] 01-02-PLAN.md — Build API
`,
'.planning/phases/01-foundation/01-01-PLAN.md': PLAN_TEMPLATE(1, [backstopTruth, 'DB schema is correct']),
'.planning/phases/01-foundation/01-02-PLAN.md': PLAN_TEMPLATE(2, [backstopTruth, 'API returns 200']),
});
const result = runGsdTools('roadmap annotate-dependencies 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
assert.strictEqual(out.cross_cutting_constraints, 1, 'the shared backstop truth is surfaced, not dropped');
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
assert.ok(roadmap.includes('Adjacent touching intervals merge'), 'surfaced by its statement text, not [object Object]');
});
test('does not surface constraints that appear in only one plan', () => {
tmpDir = makePlanProject({
'.planning/ROADMAP.md': `# Roadmap

View File

@@ -0,0 +1,84 @@
'use strict';
/**
* Tests for the external-descriptor trust gate (ADR-1239 Phase C-2, #1681).
* Pins: confined passes; escapes (.. / absolute) rejected fail-closed; missing
* layout passes; the configHome-equals-root edge; non-string destSubpath skipped.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const {
isPathConfined,
assertDescriptorConfined,
} = require('../gsd-core/bin/lib/external-descriptor-trust.cjs');
test('isPathConfined: confined paths are true, escapes are false', () => {
const root = path.join('/home', 'me', '.gsd');
assert.ok(isPathConfined('skills', root), 'simple subdir is confined');
assert.ok(isPathConfined('skills/gsd-plan.md', root), 'nested subdir is confined');
assert.ok(isPathConfined('.', root), 'root itself is confined');
assert.ok(!isPathConfined('../etc/passwd', root), 'parent escape is NOT confined');
assert.ok(!isPathConfined('../../etc', root), 'multi-level escape is NOT confined');
assert.ok(!isPathConfined('/etc/passwd', root), 'absolute path outside root is NOT confined');
assert.ok(!isPathConfined('', root), 'empty target is NOT confined');
assert.ok(!isPathConfined('skills', ''), 'empty root is NOT confined');
});
test('assertDescriptorConfined: a benign descriptor (all destSubpaths under configHome) passes', () => {
const desc = {
id: 'community-host',
runtime: { artifactLayout: {
global: [{ destSubpath: 'skills' }, { destSubpath: 'agents' }],
local: [{ destSubpath: 'commands' }],
} },
};
assert.doesNotThrow(() => assertDescriptorConfined(desc, '/home/me/.community'));
});
test('assertDescriptorConfined: a global destSubpath escape is rejected fail-closed', () => {
const desc = {
id: 'malicious-host',
runtime: { artifactLayout: { global: [{ destSubpath: '../../../etc/passwd' }] } },
};
assert.throws(
() => assertDescriptorConfined(desc, '/home/me/.gsd'),
/malicious-host.*unconfined global destSubpath.*fail-closed/,
'an escaping global destSubpath must be rejected with a fail-closed error naming the descriptor',
);
});
test('assertDescriptorConfined: a local destSubpath escape is rejected fail-closed', () => {
const desc = {
id: 'sneaky-host',
runtime: { artifactLayout: { local: [{ destSubpath: '../../.ssh/authorized_keys' }] } },
};
assert.throws(
() => assertDescriptorConfined(desc, '/home/me/.gsd'),
/sneaky-host.*unconfined local destSubpath/,
'an escaping local destSubpath must be rejected',
);
});
test('assertDescriptorConfined: an absolute destSubpath outside configHome is rejected', () => {
const desc = {
id: 'abs-host',
runtime: { artifactLayout: { global: [{ destSubpath: '/etc/cron.d/evil' }] } },
};
assert.throws(() => assertDescriptorConfined(desc, '/home/me/.gsd'), /unconfined global destSubpath/);
});
test('assertDescriptorConfined: a descriptor with no artifact layout passes (nothing to confine)', () => {
assert.doesNotThrow(() => assertDescriptorConfined({ id: 'bare', runtime: {} }, '/home/me/.gsd'));
assert.doesNotThrow(() => assertDescriptorConfined({ id: 'noruntime' }, '/home/me/.gsd'));
assert.doesNotThrow(() => assertDescriptorConfined({}, '/home/me/.gsd'));
assert.doesNotThrow(() => assertDescriptorConfined(null, '/home/me/.gsd'));
});
test('assertDescriptorConfined: non-string / empty destSubpath entries are skipped (not flagged)', () => {
const desc = {
id: 'mixed',
runtime: { artifactLayout: { global: [{ destSubpath: 'skills' }, { destSubpath: '' }, { destSubpath: null }, {}, { destSubpath: 'agents' }] } },
};
assert.doesNotThrow(() => assertDescriptorConfined(desc, '/home/me/.x'), 'valid entries pass; invalid entries skipped');
});

View File

@@ -0,0 +1,75 @@
'use strict';
/**
* Integration test: loadRegistry wires the external-descriptor trust gate
* (ADR-1239 Phase C-2 / #1681 slice 2). When `configHome` is supplied, an
* installed overlay whose declared destSubpath escapes it is rejected
* (skip + confinement reason) and NOT composed; a confined overlay composes.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { cleanup } = require('./helpers.cjs');
const { loadRegistry } = require('../gsd-core/bin/lib/capability-loader.cjs');
const HOST = '1.6.0';
function featureCap(id, extra) {
return {
id, role: 'feature', version: '1.0.0', title: id, description: 'overlay cap',
tier: 'standard', requires: [], engines: { gsd: '>=1.0.0' },
runtimeCompat: { supported: ['*'], unsupported: [] },
skills: [], agents: [], hooks: [], config: {}, steps: [], contributions: [], gates: [],
...extra,
};
}
// Build a temp GSD home with .gsd/capabilities/<id>/capability.json per cap.
function makeOverlayHome(caps) {
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'cap-trust-'));
for (const cap of caps) {
const dir = path.join(home, '.gsd', 'capabilities', cap.id);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'capability.json'), JSON.stringify(cap), 'utf8');
}
return home;
}
test('loadRegistry configHome confinement: escaping overlay is skipped with a confinement reason', () => {
const home = makeOverlayHome([
featureCap('confined-host', { runtime: { artifactLayout: { global: [{ destSubpath: 'skills' }] } } }),
featureCap('escape-host', { runtime: { artifactLayout: { global: [{ destSubpath: '../../../etc/passwd' }] } } }),
]);
try {
const reg = loadRegistry({
includeInstalled: true, gsdHome: home, cwd: home, hostVersion: HOST,
configHome: path.join(home, '.target'),
});
const overlayIds = Object.keys(reg.capabilities || {}).filter((id) => id === 'confined-host' || id === 'escape-host');
assert.ok(overlayIds.includes('confined-host'), 'confined overlay must be composed');
assert.ok(!overlayIds.includes('escape-host'), 'escaping overlay must NOT be composed');
const skips = (reg._overlay && reg._overlay.warnings) || [];
const confinementSkip = skips.find((s) => /confinement/.test(s.reason || ''));
assert.ok(confinementSkip, `an overlay must be skipped with a confinement reason; warnings=${JSON.stringify(skips)}`);
assert.match(confinementSkip.reason, /escape-host/, 'the confinement skip must name the escaping descriptor');
} finally {
cleanup(home);
}
});
test('loadRegistry configHome confinement: omitted configHome = no load-time check (backward-compatible; relies on install-time gate)', () => {
// Same escaping overlay, but no configHome passed → it is NOT rejected by the load-time gate.
const home = makeOverlayHome([
featureCap('escape-host', { runtime: { artifactLayout: { global: [{ destSubpath: '../../../etc' }] } } }),
]);
try {
const reg = loadRegistry({ includeInstalled: true, gsdHome: home, cwd: home, hostVersion: HOST });
const warnings = (reg._overlay && reg._overlay.warnings) || [];
const confinementSkip = warnings.find((s) => /confinement/.test(s.reason || ''));
assert.ok(!confinementSkip, 'no configHome → no load-time confinement check (backward-compatible)');
} finally {
cleanup(home);
}
});

View File

@@ -34,14 +34,13 @@
"agents/gsd-ui-checker.md": "dbbe694a26265473",
"agents/gsd-ui-researcher.md": "8c7e91c85e7099f5",
"agents/gsd-user-profiler.md": "25d65f6458454764",
"agents/gsd-verifier.md": "2a64590bb09e21e6",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "5902e27c7091f4b1",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "5cf26d5f9e588cf8",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
@@ -86,6 +85,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/honest-verifier.md": "a31a4bf42d82d5e9",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -263,7 +263,7 @@
"gsd-core/workflows/note.md": "3ce09c0aa0a20599",
"gsd-core/workflows/pause-work.md": "3196d681d4dd8c71",
"gsd-core/workflows/plan-milestone-gaps.md": "94b193dfc9ca3681",
"gsd-core/workflows/plan-phase.md": "d99fb8159a2db1a9",
"gsd-core/workflows/plan-phase.md": "fde84f67730f7131",
"gsd-core/workflows/plan-review-convergence.md": "b1623082557cdca5",
"gsd-core/workflows/plant-seed.md": "1fb45cd49f66f572",
"gsd-core/workflows/pr-branch.md": "c8fd9fa250cb39fd",
@@ -289,7 +289,7 @@
"gsd-core/workflows/spike.md": "0f9a81bcf4573195",
"gsd-core/workflows/stats.md": "a20eb078d2ab11be",
"gsd-core/workflows/sync-skills.md": "8326a7ff0411b077",
"gsd-core/workflows/thread.md": "03a527a71b8fab12",
"gsd-core/workflows/thread.md": "3fb6b552e45fedbd",
"gsd-core/workflows/transition.md": "a7a5fe4040084308",
"gsd-core/workflows/ui-phase.md": "652785fbba26e80c",
"gsd-core/workflows/ui-review.md": "816b2bde136157f9",
@@ -297,26 +297,26 @@
"gsd-core/workflows/undo.md": "6ab639d1fc7e0721",
"gsd-core/workflows/update.md": "dc93f366e2156e37",
"gsd-core/workflows/validate-phase.md": "5bac28c71d21c740",
"gsd-core/workflows/verify-phase.md": "1c6a2e1128966675",
"gsd-core/workflows/verify-phase.md": "e7059c04c816e62d",
"gsd-core/workflows/verify-work.md": "dd7f78f947b86976",
"hooks/gsd-check-update-worker.js": "668c24ea284ff623",
"hooks/gsd-check-update.js": "7e42f76b2bcdd764",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "ead852d2b4ddb92a",
"hooks/gsd-cursor-post-tool.js": "d61ee04f6ee7858c",
"hooks/gsd-cursor-session-start.js": "148b8ec4e2c97f00",
"hooks/gsd-ensure-canonical-path.js": "83e02e841e123037",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "d17d30e2b1a42582",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "c3ceac8122b2c3ed",
"hooks/gsd-update-banner.js": "74817c820b7a4ec1",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "e22b9fb57f0e64f6",
"hooks/gsd-worktree-path-guard.js": "25969c741edaf032",
"hooks/gsd-check-update-worker.js": "fa301e6366270d5f",
"hooks/gsd-check-update.js": "4617a98bf529e4c3",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "6d81d7326e5b2710",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "64d092d7e4a01211",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "eefea61f9b0e464c",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "8ae31be7a006204b",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "838498aa91619740",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "45b2431992d3d7d2",

View File

@@ -34,7 +34,7 @@
"agents/gsd-ui-checker.md": "4cf947a98db6410e",
"agents/gsd-ui-researcher.md": "3f8646572e9c3ec1",
"agents/gsd-user-profiler.md": "622220df0654b6bf",
"agents/gsd-verifier.md": "b1108277a4e858e3",
"agents/gsd-verifier.md": "4046b8d4ca23e342",
"commands/gsd-add-tests.md": "3608d0cf4b515103",
"commands/gsd-ai-integration-phase.md": "70843d4904743f7e",
"commands/gsd-audit-fix.md": "1b805946362c4f19",
@@ -104,13 +104,12 @@
"commands/gsd-verify-work.md": "1cb62ea69b117acb",
"commands/gsd-workspace.md": "dd1bc09d2b768e0b",
"commands/gsd-workstreams.md": "52ab9c585d3a00f3",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -155,6 +154,7 @@
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -332,7 +332,7 @@
"gsd-core/workflows/note.md": "5a99eb396c744619",
"gsd-core/workflows/pause-work.md": "7bcbdf27ba957c8b",
"gsd-core/workflows/plan-milestone-gaps.md": "02fee851c82e3b25",
"gsd-core/workflows/plan-phase.md": "fe6b786141eab878",
"gsd-core/workflows/plan-phase.md": "5e3c949ca65cd672",
"gsd-core/workflows/plan-review-convergence.md": "10007f8382864bcd",
"gsd-core/workflows/plant-seed.md": "7b795d7a1b4c9f06",
"gsd-core/workflows/pr-branch.md": "f2a35833fe784a53",
@@ -358,7 +358,7 @@
"gsd-core/workflows/spike.md": "53127654e77256bf",
"gsd-core/workflows/stats.md": "01c24349370a0e6d",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "c26ca43fdf928d46",
"gsd-core/workflows/thread.md": "6d075b5d26500e9d",
"gsd-core/workflows/transition.md": "eee3435817fab185",
"gsd-core/workflows/ui-phase.md": "e81783508b8b6819",
"gsd-core/workflows/ui-review.md": "1ad3654435000881",
@@ -366,26 +366,26 @@
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "231e7c305b40c417",
"gsd-core/workflows/validate-phase.md": "50f37b705b6e44fb",
"gsd-core/workflows/verify-phase.md": "968d569ea4ef377f",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "6f9c666386cb6d7e",
"hooks/gsd-check-update-worker.js": "e42be7414a05e99d",
"hooks/gsd-check-update.js": "b3333951b2091807",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "11e88809e2cdc331",
"hooks/gsd-cursor-post-tool.js": "d61ee04f6ee7858c",
"hooks/gsd-cursor-session-start.js": "148b8ec4e2c97f00",
"hooks/gsd-ensure-canonical-path.js": "3499b6e6b453dc59",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "ca99873d0bf8b4ba",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "9b7005c36891671d",
"hooks/gsd-update-banner.js": "74817c820b7a4ec1",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "e22b9fb57f0e64f6",
"hooks/gsd-worktree-path-guard.js": "7921523b20372a1e",
"hooks/gsd-check-update-worker.js": "cc1ef5f840f9dfc9",
"hooks/gsd-check-update.js": "7b3a7983d5f1f5d3",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "44ff1bbf292747af",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "d569f5f3578e93e5",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "c8800819f7443a15",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "3be32d2012c77fc1",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "65b934c3a1709e89",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "f46a329fcfefa465",

View File

@@ -33,14 +33,13 @@
"agents/gsd-ui-checker.md": "dd06843892f6b0c8",
"agents/gsd-ui-researcher.md": "85d7d6cc36388435",
"agents/gsd-user-profiler.md": "003276f85792cfda",
"agents/gsd-verifier.md": "0a0c618959bb00d2",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "76bcf41aa9fd9b53",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -85,6 +84,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -262,7 +262,7 @@
"gsd-core/workflows/note.md": "42b66686b2c102cb",
"gsd-core/workflows/pause-work.md": "0be71264eafd16dc",
"gsd-core/workflows/plan-milestone-gaps.md": "1976bf2001969719",
"gsd-core/workflows/plan-phase.md": "bce0904c3d3b7d59",
"gsd-core/workflows/plan-phase.md": "b0373c2198f4acf7",
"gsd-core/workflows/plan-review-convergence.md": "414df04b7ddff73c",
"gsd-core/workflows/plant-seed.md": "936a848f1d6c409e",
"gsd-core/workflows/pr-branch.md": "c8827e8a15426bf5",
@@ -288,7 +288,7 @@
"gsd-core/workflows/spike.md": "aae8bcad15642645",
"gsd-core/workflows/stats.md": "17b4f2059f4b4ef2",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "75df5cc71f72c33d",
"gsd-core/workflows/thread.md": "5ad0e0d5ce7e0d11",
"gsd-core/workflows/transition.md": "96ce39403ca69594",
"gsd-core/workflows/ui-phase.md": "790e5982e5b715c3",
"gsd-core/workflows/ui-review.md": "51945fda8e931f99",
@@ -296,26 +296,26 @@
"gsd-core/workflows/undo.md": "791e0bf96d9a057f",
"gsd-core/workflows/update.md": "0b389258dcc09332",
"gsd-core/workflows/validate-phase.md": "2aa540f2c2479501",
"gsd-core/workflows/verify-phase.md": "452968b6becb18a1",
"gsd-core/workflows/verify-phase.md": "15a999f82868ad29",
"gsd-core/workflows/verify-work.md": "d2e8f5d5f2b8f050",
"hooks/gsd-check-update-worker.js": "f0c2b5b7169642ba",
"hooks/gsd-check-update.js": "a0e4882e66670e4d",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "fbe88dd134dc7156",
"hooks/gsd-cursor-post-tool.js": "dd1b12f795de8d72",
"hooks/gsd-cursor-session-start.js": "a93095ac609a3ea6",
"hooks/gsd-ensure-canonical-path.js": "34f4522a23cc5f41",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "e149870bbd213882",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "38cb2dd48cc03294",
"hooks/gsd-update-banner.js": "d3228b9e674296b4",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "6faa8f81812a8b5d",
"hooks/gsd-worktree-path-guard.js": "5c2ebabb9d21b42a",
"hooks/gsd-check-update-worker.js": "a530efdb5fdc0da3",
"hooks/gsd-check-update.js": "25cde66a12d6b886",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "ecbe9747e4a442e0",
"hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71",
"hooks/gsd-cursor-session-start.js": "05a14e903c5edafa",
"hooks/gsd-ensure-canonical-path.js": "b4b3b88a0e493b16",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "00d2449afefd2e5f",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "7c315416ffc99a9a",
"hooks/gsd-update-banner.js": "b457746cb76c1957",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "59b46a74d19d58d3",
"hooks/gsd-worktree-path-guard.js": "02be1bb504b22eb5",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "ea876b1ec185173e",

View File

@@ -37,14 +37,13 @@
"agents/gsd-ui-checker.md": "35a6b14813aa03ff",
"agents/gsd-ui-researcher.md": "878c7d0e82fa861a",
"agents/gsd-user-profiler.md": "622220df0654b6bf",
"agents/gsd-verifier.md": "64cc793b5f0110bc",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "0a437cf3ed90693f",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -89,6 +88,7 @@
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/git-integration.md": "f5403e1470e46e69",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -266,7 +266,7 @@
"gsd-core/workflows/note.md": "5a99eb396c744619",
"gsd-core/workflows/pause-work.md": "9c1acf8c30a244fd",
"gsd-core/workflows/plan-milestone-gaps.md": "95ca791b0867fe2d",
"gsd-core/workflows/plan-phase.md": "2716d6636e37ce6a",
"gsd-core/workflows/plan-phase.md": "ab9ed0b5acfe7d8a",
"gsd-core/workflows/plan-review-convergence.md": "2cdba6216184aac4",
"gsd-core/workflows/plant-seed.md": "d0d63f83ae7c939b",
"gsd-core/workflows/pr-branch.md": "15ccec6bd303ea46",
@@ -292,7 +292,7 @@
"gsd-core/workflows/spike.md": "204e742c846ee0d9",
"gsd-core/workflows/stats.md": "5cfea82b894eee3c",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "5ae4c3141bdedd88",
"gsd-core/workflows/thread.md": "4a009fd2cc4387a7",
"gsd-core/workflows/transition.md": "fe82e77df8dceb1b",
"gsd-core/workflows/ui-phase.md": "06f1f80de620a319",
"gsd-core/workflows/ui-review.md": "0af83311f5e41f48",
@@ -300,7 +300,7 @@
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "9cad8a8f4baff922",
"gsd-core/workflows/validate-phase.md": "54687a0f2a562fc4",
"gsd-core/workflows/verify-phase.md": "68a74f247fdce962",
"gsd-core/workflows/verify-phase.md": "5caca0023bc88a9a",
"gsd-core/workflows/verify-work.md": "6f482d32e0c8d49a",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",

View File

@@ -34,7 +34,7 @@
"agents/gsd-ui-checker.md": "68ee3eb209c9f0d6",
"agents/gsd-ui-researcher.md": "507ed07fc9ba7d33",
"agents/gsd-user-profiler.md": "622220df0654b6bf",
"agents/gsd-verifier.md": "b62b7966b4aafd5b",
"agents/gsd-verifier.md": "b3983159ba46fcbf",
"commands/gsd-add-tests.md": "aa65032141557fb7",
"commands/gsd-ai-integration-phase.md": "373053d7cd887aa9",
"commands/gsd-audit-fix.md": "c21ef925131fade7",
@@ -104,13 +104,12 @@
"commands/gsd-verify-work.md": "6edbbb82a1f2aea7",
"commands/gsd-workspace.md": "d765ff60cde657a6",
"commands/gsd-workstreams.md": "884b6c8d648422c7",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -155,6 +154,7 @@
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -332,7 +332,7 @@
"gsd-core/workflows/note.md": "5a99eb396c744619",
"gsd-core/workflows/pause-work.md": "7bcbdf27ba957c8b",
"gsd-core/workflows/plan-milestone-gaps.md": "02fee851c82e3b25",
"gsd-core/workflows/plan-phase.md": "3b30ccd918b5f703",
"gsd-core/workflows/plan-phase.md": "58d02dbdb533603a",
"gsd-core/workflows/plan-review-convergence.md": "10007f8382864bcd",
"gsd-core/workflows/plant-seed.md": "7b795d7a1b4c9f06",
"gsd-core/workflows/pr-branch.md": "f2a35833fe784a53",
@@ -358,7 +358,7 @@
"gsd-core/workflows/spike.md": "0051a7e2193a7522",
"gsd-core/workflows/stats.md": "01c24349370a0e6d",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "c26ca43fdf928d46",
"gsd-core/workflows/thread.md": "6d075b5d26500e9d",
"gsd-core/workflows/transition.md": "eee3435817fab185",
"gsd-core/workflows/ui-phase.md": "93ea0d0dfcb2a1e8",
"gsd-core/workflows/ui-review.md": "1ad3654435000881",
@@ -366,26 +366,26 @@
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "6a593863d1b0a287",
"gsd-core/workflows/validate-phase.md": "50f37b705b6e44fb",
"gsd-core/workflows/verify-phase.md": "968d569ea4ef377f",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "6f9c666386cb6d7e",
"hooks/gsd-check-update-worker.js": "084c3f7109bb4d29",
"hooks/gsd-check-update.js": "23074675b7c31a47",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "3b47e76273f1a440",
"hooks/gsd-cursor-post-tool.js": "d61ee04f6ee7858c",
"hooks/gsd-cursor-session-start.js": "148b8ec4e2c97f00",
"hooks/gsd-ensure-canonical-path.js": "6900c226c1c28a44",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "95fe2c59ef5bba35",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "b3549ec6d96337b3",
"hooks/gsd-update-banner.js": "74817c820b7a4ec1",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "e22b9fb57f0e64f6",
"hooks/gsd-worktree-path-guard.js": "f3ca67ad040431a7",
"hooks/gsd-check-update-worker.js": "bdc9324a2f080ddd",
"hooks/gsd-check-update.js": "b7669f605631e506",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "f372804867cabe40",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "434887487ae63ec5",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "7f7a7615b303369a",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "ef8dcb6d64fd4493",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "548fc57131a04fa7",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "0368fd4ac7bb3d1d",

View File

@@ -67,16 +67,15 @@
"agents/gsd-ui-researcher.toml": "ffe2ca0b232df3df",
"agents/gsd-user-profiler.md": "1bf5033c929181c1",
"agents/gsd-user-profiler.toml": "b9c244bb8fbf8140",
"agents/gsd-verifier.md": "994e2a4f22bc3c8b",
"agents/gsd-verifier.toml": "9743a551e04bc0a3",
"agents/gsd-verifier.md": "3471118de7e985c7",
"agents/gsd-verifier.toml": "68f4dcc6c0311e00",
"config.toml": "a34316b9ac61a620",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -121,6 +120,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -298,7 +298,7 @@
"gsd-core/workflows/note.md": "664da466ab989f9d",
"gsd-core/workflows/pause-work.md": "3c2ee96295959527",
"gsd-core/workflows/plan-milestone-gaps.md": "8ee841bcc7adc836",
"gsd-core/workflows/plan-phase.md": "4bef5b532a009f9b",
"gsd-core/workflows/plan-phase.md": "f9a1846e30603d38",
"gsd-core/workflows/plan-review-convergence.md": "f27818025e279aa4",
"gsd-core/workflows/plant-seed.md": "bfa729ff2ad3441a",
"gsd-core/workflows/pr-branch.md": "3b13915429c0e8d9",
@@ -324,7 +324,7 @@
"gsd-core/workflows/spike.md": "c2f115f0d3251654",
"gsd-core/workflows/stats.md": "0d7449acf349feec",
"gsd-core/workflows/sync-skills.md": "e2b793963799f8ce",
"gsd-core/workflows/thread.md": "d3f768ce0f4b4a4d",
"gsd-core/workflows/thread.md": "ee872031abd592e0",
"gsd-core/workflows/transition.md": "c4bded570fafe712",
"gsd-core/workflows/ui-phase.md": "b373c964b222324c",
"gsd-core/workflows/ui-review.md": "a9b2cbba80482cd8",
@@ -332,10 +332,10 @@
"gsd-core/workflows/undo.md": "5ff7d63b0a2f46d5",
"gsd-core/workflows/update.md": "4166fd3e3ca72a7b",
"gsd-core/workflows/validate-phase.md": "4e94708c9ca15d70",
"gsd-core/workflows/verify-phase.md": "b81bd1c061c9e6d3",
"gsd-core/workflows/verify-phase.md": "59bb0b12ebb47f5e",
"gsd-core/workflows/verify-work.md": "92fb4e876d75ded5",
"hooks/gsd-check-update.js": "79c846cd8dd54caa",
"hooks/gsd-context-monitor.js": "caa8614524452835",
"hooks/gsd-check-update.js": "ef48957eb6ac6a10",
"hooks/gsd-context-monitor.js": "76fecaaa2babd6c1",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -34,15 +34,14 @@
"agents/gsd-ui-checker.agent.md": "4e46f787d6420062",
"agents/gsd-ui-researcher.agent.md": "9dd2acff7b24230e",
"agents/gsd-user-profiler.agent.md": "ae16a248e18dd42b",
"agents/gsd-verifier.agent.md": "ebd2c921c24e2d9b",
"agents/gsd-verifier.agent.md": "6fe2c4f008ebfa22",
"copilot-instructions.md": "1fb04111759f1645",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "5cf26d5f9e588cf8",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
@@ -87,6 +86,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/honest-verifier.md": "08b1e82f59c18078",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -264,7 +264,7 @@
"gsd-core/workflows/note.md": "4a5ee74cf2fc1f54",
"gsd-core/workflows/pause-work.md": "324e04e675dc7f7f",
"gsd-core/workflows/plan-milestone-gaps.md": "c0eb896eb42e22d4",
"gsd-core/workflows/plan-phase.md": "50042ca359933c51",
"gsd-core/workflows/plan-phase.md": "16ff6a162cbd4c01",
"gsd-core/workflows/plan-review-convergence.md": "c238b5858ceb4e57",
"gsd-core/workflows/plant-seed.md": "56451bdf104983b3",
"gsd-core/workflows/pr-branch.md": "a080aed95785cf32",
@@ -290,7 +290,7 @@
"gsd-core/workflows/spike.md": "716d74cdb2e39a3e",
"gsd-core/workflows/stats.md": "49085df6d4793df3",
"gsd-core/workflows/sync-skills.md": "eca50ffe8320dba8",
"gsd-core/workflows/thread.md": "4c44f10d41740f1d",
"gsd-core/workflows/thread.md": "d53698a91bbbe33c",
"gsd-core/workflows/transition.md": "724b6e9b34d85f26",
"gsd-core/workflows/ui-phase.md": "9fefa0db49f2aa3f",
"gsd-core/workflows/ui-review.md": "731bca05f9770a86",
@@ -298,7 +298,7 @@
"gsd-core/workflows/undo.md": "ba1ef7aa80bef6bd",
"gsd-core/workflows/update.md": "712ab18a9b7c14f5",
"gsd-core/workflows/validate-phase.md": "f923eac9442b6053",
"gsd-core/workflows/verify-phase.md": "543845af6f701518",
"gsd-core/workflows/verify-phase.md": "eea9b642393b6b90",
"gsd-core/workflows/verify-work.md": "e253fb8e33c68fa4",
"hooks/gsd-session.json": "0a462834f2a28fee",
"scripts/changeset/README.md": "86ff89331dfd94b2",

View File

@@ -34,7 +34,7 @@
"agents/gsd-ui-checker.md": "66b380ffcdfec7b5",
"agents/gsd-ui-researcher.md": "fe237aa42ccd9ad2",
"agents/gsd-user-profiler.md": "622220df0654b6bf",
"agents/gsd-verifier.md": "5ec35372f0a58d48",
"agents/gsd-verifier.md": "7bacf6ef32186213",
"commands/gsd-add-tests.md": "1f89b16ab2cca426",
"commands/gsd-ai-integration-phase.md": "3ccac39673ffb92c",
"commands/gsd-audit-fix.md": "d88502ffa2151cec",
@@ -104,13 +104,12 @@
"commands/gsd-verify-work.md": "86a42f859bc26dd6",
"commands/gsd-workspace.md": "5c40114e6af87e3d",
"commands/gsd-workstreams.md": "112660ceb663c750",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "5cf26d5f9e588cf8",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -155,6 +154,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -332,7 +332,7 @@
"gsd-core/workflows/note.md": "1c1e466c764e3deb",
"gsd-core/workflows/pause-work.md": "0be71264eafd16dc",
"gsd-core/workflows/plan-milestone-gaps.md": "1976bf2001969719",
"gsd-core/workflows/plan-phase.md": "8975739befb097bc",
"gsd-core/workflows/plan-phase.md": "1f40b9c15ebef2ff",
"gsd-core/workflows/plan-review-convergence.md": "783c5171101b26b9",
"gsd-core/workflows/plant-seed.md": "b28224f37faa9b95",
"gsd-core/workflows/pr-branch.md": "1f77535b6b156ad9",
@@ -358,7 +358,7 @@
"gsd-core/workflows/spike.md": "9e37f8067adf87b7",
"gsd-core/workflows/stats.md": "17b4f2059f4b4ef2",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "06e31fdaf02ccabc",
"gsd-core/workflows/thread.md": "cfc42471f9001ba8",
"gsd-core/workflows/transition.md": "96ce39403ca69594",
"gsd-core/workflows/ui-phase.md": "dea2f2d43a43e0d0",
"gsd-core/workflows/ui-review.md": "6b16a7f7783be471",
@@ -366,7 +366,7 @@
"gsd-core/workflows/undo.md": "18dec684fb1076f9",
"gsd-core/workflows/update.md": "a27dcfd2814bf2b1",
"gsd-core/workflows/validate-phase.md": "f513c28c01a44cb7",
"gsd-core/workflows/verify-phase.md": "452968b6becb18a1",
"gsd-core/workflows/verify-phase.md": "15a999f82868ad29",
"gsd-core/workflows/verify-work.md": "24d323b667d15d01",
"hooks/gsd-cursor-post-tool.js": "019d503aee8b4a3f",
"hooks/gsd-cursor-session-start.js": "c6e04ed597ea7020",

View File

@@ -34,7 +34,7 @@
"agents/gsd-ui-checker.md": "cb122369806c5467",
"agents/gsd-ui-researcher.md": "e1422e3b0f142f74",
"agents/gsd-user-profiler.md": "d6cb5430d841cea6",
"agents/gsd-verifier.md": "6f3d8f7d56b72475",
"agents/gsd-verifier.md": "e52203ef59021e9c",
"commands/gsd/add-tests.toml": "297ba4d4c285fd99",
"commands/gsd/ai-integration-phase.toml": "411ba33b9a7d9e78",
"commands/gsd/audit-fix.toml": "f4a198a455f668a9",
@@ -104,13 +104,12 @@
"commands/gsd/verify-work.toml": "58ec25cb1f5d5434",
"commands/gsd/workspace.toml": "7f1658bb61c9743d",
"commands/gsd/workstreams.toml": "95f0c349b808a84c",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -155,6 +154,7 @@
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -332,7 +332,7 @@
"gsd-core/workflows/note.md": "95199087905ba3e6",
"gsd-core/workflows/pause-work.md": "7bcbdf27ba957c8b",
"gsd-core/workflows/plan-milestone-gaps.md": "02fee851c82e3b25",
"gsd-core/workflows/plan-phase.md": "b2904cffb7ff0127",
"gsd-core/workflows/plan-phase.md": "81c6ef4292e5f9bf",
"gsd-core/workflows/plan-review-convergence.md": "57c284df29121220",
"gsd-core/workflows/plant-seed.md": "339890021123e017",
"gsd-core/workflows/pr-branch.md": "f2a35833fe784a53",
@@ -358,7 +358,7 @@
"gsd-core/workflows/spike.md": "c9482514e665bcac",
"gsd-core/workflows/stats.md": "01c24349370a0e6d",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "c26ca43fdf928d46",
"gsd-core/workflows/thread.md": "6d075b5d26500e9d",
"gsd-core/workflows/transition.md": "69143573741c52e4",
"gsd-core/workflows/ui-phase.md": "bb5167948032872e",
"gsd-core/workflows/ui-review.md": "d256bec482e67af8",
@@ -366,26 +366,26 @@
"gsd-core/workflows/undo.md": "993bb0a31edca7f8",
"gsd-core/workflows/update.md": "51c3af33474bdc24",
"gsd-core/workflows/validate-phase.md": "2d998cb78c918d08",
"gsd-core/workflows/verify-phase.md": "968d569ea4ef377f",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "1265e07f2c74a218",
"hooks/gsd-check-update-worker.js": "7a3eba8c1c166dd2",
"hooks/gsd-check-update.js": "c1c78326299f6eae",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "c7793e66ce76aaeb",
"hooks/gsd-cursor-post-tool.js": "d61ee04f6ee7858c",
"hooks/gsd-cursor-session-start.js": "148b8ec4e2c97f00",
"hooks/gsd-ensure-canonical-path.js": "a19947cac42002d4",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "f8db0daa41afe13b",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "c238ad773bacae32",
"hooks/gsd-update-banner.js": "74817c820b7a4ec1",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "e22b9fb57f0e64f6",
"hooks/gsd-worktree-path-guard.js": "134c7919c4cbc78e",
"hooks/gsd-check-update-worker.js": "5f5f2b73e6c14a7f",
"hooks/gsd-check-update.js": "0e955593b7884d99",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "57cf670fa45153a7",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "6046bb90482883ad",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "f1adfec005911860",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "d20735894543598a",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "e07a5c35190a182e",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "9372d0d21a2c4298",

View File

@@ -34,14 +34,13 @@
"agents/gsd-ui-checker.md": "32b2605c7254f959",
"agents/gsd-ui-researcher.md": "87cf9eebc37c4a37",
"agents/gsd-user-profiler.md": "ca3bf75581f211a0",
"agents/gsd-verifier.md": "4decc9b596090ca6",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "c1d1448bf31039ec",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -86,6 +85,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -263,7 +263,7 @@
"gsd-core/workflows/note.md": "42b66686b2c102cb",
"gsd-core/workflows/pause-work.md": "2c3abcaa1fa6d2e2",
"gsd-core/workflows/plan-milestone-gaps.md": "419744c1191354af",
"gsd-core/workflows/plan-phase.md": "7d6cbb6730d852d2",
"gsd-core/workflows/plan-phase.md": "ad2d30c92b495bcb",
"gsd-core/workflows/plan-review-convergence.md": "8ebcd05fcd5a0d15",
"gsd-core/workflows/plant-seed.md": "76379e2aeb18d53b",
"gsd-core/workflows/pr-branch.md": "79f4d93e31af9c49",
@@ -289,7 +289,7 @@
"gsd-core/workflows/spike.md": "80844a3c05339fdb",
"gsd-core/workflows/stats.md": "01289f444b66913d",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "7af5046e18c81ee9",
"gsd-core/workflows/thread.md": "596bf37dcea2ce1e",
"gsd-core/workflows/transition.md": "adab3f53afd5d8aa",
"gsd-core/workflows/ui-phase.md": "dd213d91b5c258a5",
"gsd-core/workflows/ui-review.md": "83d6d3b1f26597ff",
@@ -297,26 +297,26 @@
"gsd-core/workflows/undo.md": "791e0bf96d9a057f",
"gsd-core/workflows/update.md": "472d4905fc8c5ce5",
"gsd-core/workflows/validate-phase.md": "067acd7aeed52c5b",
"gsd-core/workflows/verify-phase.md": "fecef63dcecc4104",
"gsd-core/workflows/verify-phase.md": "4e1d99795e8e9413",
"gsd-core/workflows/verify-work.md": "aa0b4d000b15dbf1",
"hooks/gsd-check-update-worker.js": "80865e926e22623e",
"hooks/gsd-check-update.js": "57433c9f9b224e3e",
"hooks/gsd-config-reload.js": "ca99e7dc60a9815d",
"hooks/gsd-context-monitor.js": "f058fdb4b8b6c939",
"hooks/gsd-cursor-post-tool.js": "dd1b12f795de8d72",
"hooks/gsd-cursor-session-start.js": "a93095ac609a3ea6",
"hooks/gsd-ensure-canonical-path.js": "c49fb011c3b97f4a",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "474bc1800d34456f",
"hooks/gsd-read-injection-scanner.js": "2f32423c033ed5fd",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "453cdf46bcf62368",
"hooks/gsd-update-banner.js": "d3228b9e674296b4",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "6faa8f81812a8b5d",
"hooks/gsd-worktree-path-guard.js": "93bb15bf44b8c60d",
"hooks/gsd-check-update-worker.js": "7989cc2bedd1138d",
"hooks/gsd-check-update.js": "25f5ad726f76fc11",
"hooks/gsd-config-reload.js": "880b696458e85e9b",
"hooks/gsd-context-monitor.js": "41d28e0db7b20968",
"hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71",
"hooks/gsd-cursor-session-start.js": "05a14e903c5edafa",
"hooks/gsd-ensure-canonical-path.js": "7d116d7d65c50b4b",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "1f58b020a91f032b",
"hooks/gsd-read-injection-scanner.js": "f358eca3fa1eab24",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "861808560e60b233",
"hooks/gsd-update-banner.js": "b457746cb76c1957",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "59b46a74d19d58d3",
"hooks/gsd-worktree-path-guard.js": "108ab88ccbafc5d8",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "2218a41c279720c2",
@@ -332,75 +332,75 @@
"scripts/fix-slash-commands.cjs": "0519742531ff3529",
"scripts/lib/allowlist-ratchet.cjs": "ffaceaac3efc2660",
"scripts/lib/cli-exit.cjs": "612d0c372c75b7e7",
"skills/gsd/DESCRIPTION.md": "5f38d874c5b24402",
"skills/gsd/gsd-ns-context/SKILL.md": "151b2ba0fac3941c",
"skills/gsd/gsd-ns-context/skills/docs-update/SKILL.md": "df94f6ae46ec5795",
"skills/gsd/gsd-ns-context/skills/extract-learnings/SKILL.md": "52be8a979bef730c",
"skills/gsd/gsd-ns-context/skills/graphify/SKILL.md": "f7dec1c111fb100e",
"skills/gsd/gsd-ns-context/skills/map-codebase/SKILL.md": "ccd09679064252b4",
"skills/gsd/gsd-ns-context/skills/mempalace-capture/SKILL.md": "046f39c201d7365a",
"skills/gsd/gsd-ns-context/skills/mempalace-recall/SKILL.md": "e1a57c1fec64d5f9",
"skills/gsd/gsd-ns-ideate/SKILL.md": "2e3c4e56eff154e5",
"skills/gsd/gsd-ns-ideate/skills/capture/SKILL.md": "b7bc43f69fd76622",
"skills/gsd/gsd-ns-ideate/skills/explore/SKILL.md": "360b2e6f645495db",
"skills/gsd/gsd-ns-ideate/skills/sketch/SKILL.md": "64ca8f967e319f27",
"skills/gsd/gsd-ns-ideate/skills/spec-phase/SKILL.md": "83186cb129fdae50",
"skills/gsd/gsd-ns-ideate/skills/spike/SKILL.md": "ce57cb5dc6d2a4a6",
"skills/gsd/gsd-ns-manage/SKILL.md": "fac0244e288b1760",
"skills/gsd/gsd-ns-manage/skills/cleanup/SKILL.md": "f17b38ef046f6479",
"skills/gsd/gsd-ns-manage/skills/config/SKILL.md": "472d62f10987917e",
"skills/gsd/gsd-ns-manage/skills/health/SKILL.md": "b3718922fb10baf2",
"skills/gsd/gsd-ns-manage/skills/help/SKILL.md": "3bdd8a49c2d9eeb9",
"skills/gsd/gsd-ns-manage/skills/inbox/SKILL.md": "dad3f572dc97e8d9",
"skills/gsd/gsd-ns-manage/skills/manager/SKILL.md": "45a0123746b538da",
"skills/gsd/gsd-ns-manage/skills/pause-work/SKILL.md": "f6b91ab34a52ec67",
"skills/gsd/gsd-ns-manage/skills/pr-branch/SKILL.md": "7d7e51e684ed5df6",
"skills/gsd/gsd-ns-manage/skills/resume-work/SKILL.md": "d50c1aac9c702a8d",
"skills/gsd/gsd-ns-manage/skills/settings/SKILL.md": "16975a50842c06c6",
"skills/gsd/gsd-ns-manage/skills/ship/SKILL.md": "8a3a195e339c5ab6",
"skills/gsd/gsd-ns-manage/skills/stats/SKILL.md": "f77e8240d8754d5e",
"skills/gsd/gsd-ns-manage/skills/surface/SKILL.md": "b292d9414a3ceb7a",
"skills/gsd/gsd-ns-manage/skills/thread/SKILL.md": "3a9ca4ac926b85d9",
"skills/gsd/gsd-ns-manage/skills/undo/SKILL.md": "3609c2bd80383cc7",
"skills/gsd/gsd-ns-manage/skills/update/SKILL.md": "38865b7c53ce3bb2",
"skills/gsd/gsd-ns-manage/skills/workspace/SKILL.md": "0af8b7bb3cff8ec3",
"skills/gsd/gsd-ns-manage/skills/workstreams/SKILL.md": "4f5f41bee17fddda",
"skills/gsd/gsd-ns-project/SKILL.md": "d7462813249bba6e",
"skills/gsd/gsd-ns-project/skills/audit-milestone/SKILL.md": "b6e35c162eade47e",
"skills/gsd/gsd-ns-project/skills/complete-milestone/SKILL.md": "60bd096bbbf1bcde",
"skills/gsd/gsd-ns-project/skills/import/SKILL.md": "a244b8a416de4151",
"skills/gsd/gsd-ns-project/skills/ingest-docs/SKILL.md": "744e20b470d1f38d",
"skills/gsd/gsd-ns-project/skills/milestone-summary/SKILL.md": "a4a3934f5dccfafa",
"skills/gsd/gsd-ns-project/skills/new-milestone/SKILL.md": "be1186bd49fa793f",
"skills/gsd/gsd-ns-project/skills/new-project/SKILL.md": "4b9851a476337bec",
"skills/gsd/gsd-ns-project/skills/profile-user/SKILL.md": "174895b891961f4a",
"skills/gsd/gsd-ns-project/skills/review-backlog/SKILL.md": "23b62ff57db3db7a",
"skills/gsd/gsd-ns-review/SKILL.md": "b8f3b659ce8069c6",
"skills/gsd/gsd-ns-review/skills/audit-fix/SKILL.md": "4ac64b41a68e0271",
"skills/gsd/gsd-ns-review/skills/audit-uat/SKILL.md": "6818606a6428f4fd",
"skills/gsd/gsd-ns-review/skills/code-review/SKILL.md": "9bbf2a634954e692",
"skills/gsd/gsd-ns-review/skills/debug/SKILL.md": "014bf3421ade2813",
"skills/gsd/gsd-ns-review/skills/eval-review/SKILL.md": "609ee9ace8cd424c",
"skills/gsd/gsd-ns-review/skills/forensics/SKILL.md": "d31dc39e02abc929",
"skills/gsd/gsd-ns-review/skills/review/SKILL.md": "ace83e30ef7f9060",
"skills/gsd/gsd-ns-review/skills/secure-phase/SKILL.md": "f615390f01694378",
"skills/gsd/gsd-ns-review/skills/ui-phase/SKILL.md": "aac4a77d61281591",
"skills/gsd/gsd-ns-review/skills/ui-review/SKILL.md": "ebe37a6d521dba62",
"skills/gsd/gsd-ns-review/skills/validate-phase/SKILL.md": "b9f0c4f3738fcb7e",
"skills/gsd/gsd-ns-workflow/SKILL.md": "8ae955e65342e183",
"skills/gsd/gsd-ns-workflow/skills/add-tests/SKILL.md": "274b0e83a06204d5",
"skills/gsd/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "8415c05314ef1091",
"skills/gsd/gsd-ns-workflow/skills/autonomous/SKILL.md": "67a1d1880678e0d5",
"skills/gsd/gsd-ns-workflow/skills/discuss-phase/SKILL.md": "3b69cd5de487382e",
"skills/gsd/gsd-ns-workflow/skills/execute-phase/SKILL.md": "2b838b5e5737aade",
"skills/gsd/gsd-ns-workflow/skills/fast/SKILL.md": "c22843abbd530ebd",
"skills/gsd/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "615bfe82af659694",
"skills/gsd/gsd-ns-workflow/skills/phase/SKILL.md": "a407ca50ee0aeb6b",
"skills/gsd/gsd-ns-workflow/skills/plan-phase/SKILL.md": "7f077194059ede03",
"skills/gsd/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "99468f6eaf3c72b4",
"skills/gsd/gsd-ns-workflow/skills/progress/SKILL.md": "6ecd0f958ad93d20",
"skills/gsd/gsd-ns-workflow/skills/quick/SKILL.md": "3ba1337427843e91",
"skills/gsd/gsd-ns-workflow/skills/spec-phase/SKILL.md": "83186cb129fdae50",
"skills/gsd/gsd-ns-workflow/skills/ultraplan-phase/SKILL.md": "bc4d84237531c5e5",
"skills/gsd/gsd-ns-workflow/skills/verify-work/SKILL.md": "aa830ff978d73f1a"
"skills/gsd/DESCRIPTION.md": "d93e29a1a09cfd11",
"skills/gsd/gsd-ns-context/SKILL.md": "bd883f6319dd2c81",
"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/mempalace-capture/SKILL.md": "1565901c4833a49e",
"skills/gsd/gsd-ns-context/skills/mempalace-recall/SKILL.md": "eba3ad69c0e7b157",
"skills/gsd/gsd-ns-ideate/SKILL.md": "b36bb4700ed6723a",
"skills/gsd/gsd-ns-ideate/skills/capture/SKILL.md": "76fc7c1200124ece",
"skills/gsd/gsd-ns-ideate/skills/explore/SKILL.md": "bb39acf6117cb9ca",
"skills/gsd/gsd-ns-ideate/skills/sketch/SKILL.md": "2d9390383b2cd939",
"skills/gsd/gsd-ns-ideate/skills/spec-phase/SKILL.md": "848cec32c341713c",
"skills/gsd/gsd-ns-ideate/skills/spike/SKILL.md": "a87bf704b753cd06",
"skills/gsd/gsd-ns-manage/SKILL.md": "60bb07572d442561",
"skills/gsd/gsd-ns-manage/skills/cleanup/SKILL.md": "66c29a633fd54751",
"skills/gsd/gsd-ns-manage/skills/config/SKILL.md": "927cf658e1bd1c00",
"skills/gsd/gsd-ns-manage/skills/health/SKILL.md": "4401da73b980a4cc",
"skills/gsd/gsd-ns-manage/skills/help/SKILL.md": "0d4a5bfdc292096e",
"skills/gsd/gsd-ns-manage/skills/inbox/SKILL.md": "842ac7e207845549",
"skills/gsd/gsd-ns-manage/skills/manager/SKILL.md": "e02da9da44a8ddd7",
"skills/gsd/gsd-ns-manage/skills/pause-work/SKILL.md": "761f9ac48e63882f",
"skills/gsd/gsd-ns-manage/skills/pr-branch/SKILL.md": "8e9fc7da5290e7ac",
"skills/gsd/gsd-ns-manage/skills/resume-work/SKILL.md": "1d497e355ddfc0c9",
"skills/gsd/gsd-ns-manage/skills/settings/SKILL.md": "db053a82acb85969",
"skills/gsd/gsd-ns-manage/skills/ship/SKILL.md": "3f7e7023802a185d",
"skills/gsd/gsd-ns-manage/skills/stats/SKILL.md": "847f1a79382e05a3",
"skills/gsd/gsd-ns-manage/skills/surface/SKILL.md": "f63005360828e726",
"skills/gsd/gsd-ns-manage/skills/thread/SKILL.md": "12e14ca63fe5a982",
"skills/gsd/gsd-ns-manage/skills/undo/SKILL.md": "cb410bf1813a2d1e",
"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/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",
"skills/gsd/gsd-ns-project/skills/ingest-docs/SKILL.md": "a38273c6b6604830",
"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/profile-user/SKILL.md": "5a8e01bc7b873a20",
"skills/gsd/gsd-ns-project/skills/review-backlog/SKILL.md": "f3ef97da4e9b1d4c",
"skills/gsd/gsd-ns-review/SKILL.md": "23fbadeca640e2bd",
"skills/gsd/gsd-ns-review/skills/audit-fix/SKILL.md": "4bc3b75acebc3f82",
"skills/gsd/gsd-ns-review/skills/audit-uat/SKILL.md": "c330007f3893f757",
"skills/gsd/gsd-ns-review/skills/code-review/SKILL.md": "9ce2b92032654820",
"skills/gsd/gsd-ns-review/skills/debug/SKILL.md": "d4145201fd86560b",
"skills/gsd/gsd-ns-review/skills/eval-review/SKILL.md": "940e0682291e93f9",
"skills/gsd/gsd-ns-review/skills/forensics/SKILL.md": "e05e2a39a14995b5",
"skills/gsd/gsd-ns-review/skills/review/SKILL.md": "c0d8452082aa1dd5",
"skills/gsd/gsd-ns-review/skills/secure-phase/SKILL.md": "1a70020fcb3c3d4a",
"skills/gsd/gsd-ns-review/skills/ui-phase/SKILL.md": "3207854a4f0fab67",
"skills/gsd/gsd-ns-review/skills/ui-review/SKILL.md": "512c54662616c3f9",
"skills/gsd/gsd-ns-review/skills/validate-phase/SKILL.md": "f2c31c34fc8e24c4",
"skills/gsd/gsd-ns-workflow/SKILL.md": "a5c0c299df224d10",
"skills/gsd/gsd-ns-workflow/skills/add-tests/SKILL.md": "29e5863322ad6f2d",
"skills/gsd/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "317912c0488d701b",
"skills/gsd/gsd-ns-workflow/skills/autonomous/SKILL.md": "3c9bc726e13c4916",
"skills/gsd/gsd-ns-workflow/skills/discuss-phase/SKILL.md": "326303209b745eea",
"skills/gsd/gsd-ns-workflow/skills/execute-phase/SKILL.md": "a2e4e6211e09b3c0",
"skills/gsd/gsd-ns-workflow/skills/fast/SKILL.md": "c8517ba44da99147",
"skills/gsd/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "0804dfd4124526ba",
"skills/gsd/gsd-ns-workflow/skills/phase/SKILL.md": "610bd06d71198849",
"skills/gsd/gsd-ns-workflow/skills/plan-phase/SKILL.md": "f06f0e2fc8def3f0",
"skills/gsd/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "fddf8bd7b3ae1f10",
"skills/gsd/gsd-ns-workflow/skills/progress/SKILL.md": "6ec0e4715dde7ee2",
"skills/gsd/gsd-ns-workflow/skills/quick/SKILL.md": "65e377345e9c49a8",
"skills/gsd/gsd-ns-workflow/skills/spec-phase/SKILL.md": "848cec32c341713c",
"skills/gsd/gsd-ns-workflow/skills/ultraplan-phase/SKILL.md": "3a88dde399df4e63",
"skills/gsd/gsd-ns-workflow/skills/verify-work/SKILL.md": "3bedfdb2aeeb3804"
}

View File

@@ -34,7 +34,7 @@
"agents/gsd-ui-checker.md": "2a6c5551e354414b",
"agents/gsd-ui-researcher.md": "384af56f90784422",
"agents/gsd-user-profiler.md": "b8cb09319c517701",
"agents/gsd-verifier.md": "b16febf0774e2db7",
"agents/gsd-verifier.md": "0cd3672d8ad81dd0",
"command/gsd-add-tests.md": "c314f9a6be312bfa",
"command/gsd-ai-integration-phase.md": "bbab86a540e00db5",
"command/gsd-audit-fix.md": "4106e9dce9b71585",
@@ -104,13 +104,12 @@
"command/gsd-verify-work.md": "d07409c940a6ff00",
"command/gsd-workspace.md": "9048133312f47fdc",
"command/gsd-workstreams.md": "5e57eed1881c3891",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -155,6 +154,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -332,7 +332,7 @@
"gsd-core/workflows/note.md": "f8c2842a2217f776",
"gsd-core/workflows/pause-work.md": "8b81699a46ca8e9b",
"gsd-core/workflows/plan-milestone-gaps.md": "1976bf2001969719",
"gsd-core/workflows/plan-phase.md": "2d8eda8f283735d2",
"gsd-core/workflows/plan-phase.md": "38d57cf05c12ab50",
"gsd-core/workflows/plan-review-convergence.md": "88e294a5cc616e90",
"gsd-core/workflows/plant-seed.md": "249d3c6b4d474106",
"gsd-core/workflows/pr-branch.md": "c8827e8a15426bf5",
@@ -358,7 +358,7 @@
"gsd-core/workflows/spike.md": "a782dd863771fe0a",
"gsd-core/workflows/stats.md": "17b4f2059f4b4ef2",
"gsd-core/workflows/sync-skills.md": "dc7b8b015afa4b3b",
"gsd-core/workflows/thread.md": "75df5cc71f72c33d",
"gsd-core/workflows/thread.md": "5ad0e0d5ce7e0d11",
"gsd-core/workflows/transition.md": "9040a76741f12de6",
"gsd-core/workflows/ui-phase.md": "33f7113e91c2bfe0",
"gsd-core/workflows/ui-review.md": "013272816a291305",
@@ -366,26 +366,26 @@
"gsd-core/workflows/undo.md": "7cd2153f8b15e30d",
"gsd-core/workflows/update.md": "5d0a2356dadf2017",
"gsd-core/workflows/validate-phase.md": "7915e3a261f7c9c9",
"gsd-core/workflows/verify-phase.md": "452968b6becb18a1",
"gsd-core/workflows/verify-phase.md": "15a999f82868ad29",
"gsd-core/workflows/verify-work.md": "7cb5144cc74986e7",
"hooks/gsd-check-update-worker.js": "8c48db40d2d74193",
"hooks/gsd-check-update.js": "1c863b30953b47c8",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "8e9c39563be10827",
"hooks/gsd-cursor-post-tool.js": "d61ee04f6ee7858c",
"hooks/gsd-cursor-session-start.js": "148b8ec4e2c97f00",
"hooks/gsd-ensure-canonical-path.js": "a5c67a1a7abc90c0",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "c5d388fe1a61a12a",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "e6eeb0972eb54bbe",
"hooks/gsd-update-banner.js": "74817c820b7a4ec1",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "e22b9fb57f0e64f6",
"hooks/gsd-worktree-path-guard.js": "ca0d1af4a9357887",
"hooks/gsd-check-update-worker.js": "c992bbad91d0e994",
"hooks/gsd-check-update.js": "fdd77abe7ef26a2d",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "2caaaf96d39fe742",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "3ce09b366839d324",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "7792c420f1d72f11",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "a055020378003805",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "cb8b86e39a5d49e9",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "8a9f7633c6fe0ea0",

View File

@@ -69,15 +69,14 @@
"agents/subagents/gsd-ui-researcher.yaml": "15e215874bc2c37d",
"agents/subagents/gsd-user-profiler.md": "c16f94e09e433394",
"agents/subagents/gsd-user-profiler.yaml": "645826a29d079159",
"agents/subagents/gsd-verifier.md": "3345b59f7a3b8de3",
"agents/subagents/gsd-verifier.md": "1536f3fa5a2d4419",
"agents/subagents/gsd-verifier.yaml": "2d2bd6b37626f382",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -122,6 +121,7 @@
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -299,7 +299,7 @@
"gsd-core/workflows/note.md": "5a99eb396c744619",
"gsd-core/workflows/pause-work.md": "7bcbdf27ba957c8b",
"gsd-core/workflows/plan-milestone-gaps.md": "02fee851c82e3b25",
"gsd-core/workflows/plan-phase.md": "f210c67e41f89f4a",
"gsd-core/workflows/plan-phase.md": "f40fb8f961a35925",
"gsd-core/workflows/plan-review-convergence.md": "10007f8382864bcd",
"gsd-core/workflows/plant-seed.md": "7b795d7a1b4c9f06",
"gsd-core/workflows/pr-branch.md": "f2a35833fe784a53",
@@ -325,7 +325,7 @@
"gsd-core/workflows/spike.md": "be2295fe2b32956f",
"gsd-core/workflows/stats.md": "01c24349370a0e6d",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "c26ca43fdf928d46",
"gsd-core/workflows/thread.md": "6d075b5d26500e9d",
"gsd-core/workflows/transition.md": "eee3435817fab185",
"gsd-core/workflows/ui-phase.md": "3dfb9f5161375035",
"gsd-core/workflows/ui-review.md": "1ad3654435000881",
@@ -333,7 +333,7 @@
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "6369691790864147",
"gsd-core/workflows/validate-phase.md": "50f37b705b6e44fb",
"gsd-core/workflows/verify-phase.md": "968d569ea4ef377f",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "6f9c666386cb6d7e",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",

View File

@@ -34,7 +34,7 @@
"agents/gsd-ui-checker.md": "b98b00e586610657",
"agents/gsd-ui-researcher.md": "7ff5c528bef6af09",
"agents/gsd-user-profiler.md": "d825b4c0a6431f8b",
"agents/gsd-verifier.md": "1e0ff1d8d15a48ab",
"agents/gsd-verifier.md": "47816fa1ba8a9b8b",
"command/gsd-add-tests.md": "b9cd93ed01945f75",
"command/gsd-ai-integration-phase.md": "9d4bc4dcce7f1ee0",
"command/gsd-audit-fix.md": "5615403843d5e491",
@@ -104,13 +104,12 @@
"command/gsd-verify-work.md": "f23fff8e6d71f704",
"command/gsd-workspace.md": "1e581bdb33bc8f55",
"command/gsd-workstreams.md": "5e57eed1881c3891",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -155,6 +154,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/honest-verifier.md": "b67482409f896a10",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -332,7 +332,7 @@
"gsd-core/workflows/note.md": "0d1374f2a2257858",
"gsd-core/workflows/pause-work.md": "c9f0b8826845dda7",
"gsd-core/workflows/plan-milestone-gaps.md": "5ec459734bf7570c",
"gsd-core/workflows/plan-phase.md": "1aea2bd181a782cf",
"gsd-core/workflows/plan-phase.md": "6db3e766b7703874",
"gsd-core/workflows/plan-review-convergence.md": "4f884d793d72d3fd",
"gsd-core/workflows/plant-seed.md": "507e886d0f70d84c",
"gsd-core/workflows/pr-branch.md": "3abaa18246facdc3",
@@ -358,7 +358,7 @@
"gsd-core/workflows/spike.md": "df52b9f31a72d204",
"gsd-core/workflows/stats.md": "598b1bb510ed0451",
"gsd-core/workflows/sync-skills.md": "7e2c138cdbef4282",
"gsd-core/workflows/thread.md": "c11265f0fa0a95d3",
"gsd-core/workflows/thread.md": "f4f8312f744c9cba",
"gsd-core/workflows/transition.md": "2d6739f730c3ea10",
"gsd-core/workflows/ui-phase.md": "38dac814d2d03d6f",
"gsd-core/workflows/ui-review.md": "3d972d3bd30527b3",
@@ -366,26 +366,26 @@
"gsd-core/workflows/undo.md": "0bba5e7f6196c894",
"gsd-core/workflows/update.md": "af51041a3172c523",
"gsd-core/workflows/validate-phase.md": "0fc5991f6d7c6c39",
"gsd-core/workflows/verify-phase.md": "d8999a0a1cd7b6de",
"gsd-core/workflows/verify-phase.md": "13a1a43395b5e47b",
"gsd-core/workflows/verify-work.md": "52f6011634cff599",
"hooks/gsd-check-update-worker.js": "5882dbd41918c863",
"hooks/gsd-check-update.js": "5fb0027b7b76986c",
"hooks/gsd-config-reload.js": "17bf778d432b3d2a",
"hooks/gsd-context-monitor.js": "6afbef2291b68874",
"hooks/gsd-cursor-post-tool.js": "d61ee04f6ee7858c",
"hooks/gsd-cursor-session-start.js": "148b8ec4e2c97f00",
"hooks/gsd-ensure-canonical-path.js": "5f8be5b0a01a88ea",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "b602f88f046a7551",
"hooks/gsd-read-injection-scanner.js": "fb5730e37a300e69",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "a0e7b01ec5012940",
"hooks/gsd-update-banner.js": "74817c820b7a4ec1",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "e22b9fb57f0e64f6",
"hooks/gsd-worktree-path-guard.js": "3df1fd5409d358f3",
"hooks/gsd-check-update-worker.js": "385fb7c67810baf6",
"hooks/gsd-check-update.js": "4549451414ffa7d7",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "7a9787868a39b76d",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "2801ae3fef9579bf",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "f72060dfe035f706",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "9c132b5985800462",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "726fb9afefda5d42",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "763730ef31e5fd1c",

View File

@@ -34,14 +34,13 @@
"agents/gsd-ui-checker.md": "7a383f6a3fbba34b",
"agents/gsd-ui-researcher.md": "67596ca5ee2f4547",
"agents/gsd-user-profiler.md": "ca3bf75581f211a0",
"agents/gsd-verifier.md": "82b7967e24c2065b",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "4bc6c8e197ee56e0",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "74594e8bf36e5580",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -86,6 +85,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -263,7 +263,7 @@
"gsd-core/workflows/note.md": "42b66686b2c102cb",
"gsd-core/workflows/pause-work.md": "54f1c0a9e79e2c59",
"gsd-core/workflows/plan-milestone-gaps.md": "1b820f4e70acce86",
"gsd-core/workflows/plan-phase.md": "fcc0bfeadf4aa5c4",
"gsd-core/workflows/plan-phase.md": "140b9f4360646c96",
"gsd-core/workflows/plan-review-convergence.md": "b809588bff4f48b5",
"gsd-core/workflows/plant-seed.md": "e3949fcbcf5d375f",
"gsd-core/workflows/pr-branch.md": "95850381230787ed",
@@ -289,7 +289,7 @@
"gsd-core/workflows/spike.md": "52fcd95f7b343232",
"gsd-core/workflows/stats.md": "7ffa072290ebcae3",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "d85a53d03a3167e0",
"gsd-core/workflows/thread.md": "c10177767648ea7f",
"gsd-core/workflows/transition.md": "1f0a1478d75d89ec",
"gsd-core/workflows/ui-phase.md": "1c91323daf451053",
"gsd-core/workflows/ui-review.md": "f1f82d8257e910cc",
@@ -297,26 +297,26 @@
"gsd-core/workflows/undo.md": "791e0bf96d9a057f",
"gsd-core/workflows/update.md": "baae1a977fbeba49",
"gsd-core/workflows/validate-phase.md": "0e153ccb3bdb9dda",
"gsd-core/workflows/verify-phase.md": "33a17f66c8291096",
"gsd-core/workflows/verify-phase.md": "0ef74d5b7f7646f6",
"gsd-core/workflows/verify-work.md": "8a4157d179fce9c4",
"hooks/gsd-check-update-worker.js": "74edb6f6b1010c85",
"hooks/gsd-check-update.js": "81962511d619d038",
"hooks/gsd-config-reload.js": "e5b430ced986ea68",
"hooks/gsd-context-monitor.js": "8e55e33027fb54d4",
"hooks/gsd-cursor-post-tool.js": "dd1b12f795de8d72",
"hooks/gsd-cursor-session-start.js": "a93095ac609a3ea6",
"hooks/gsd-ensure-canonical-path.js": "4c1626ed20ab7a75",
"hooks/gsd-graphify-update.sh": "396ebda3c6705dc9",
"hooks/gsd-phase-boundary.sh": "6aa3ba9af3d465d9",
"hooks/gsd-prompt-guard.js": "4b08c2dce0233e2d",
"hooks/gsd-read-guard.js": "9d1b227b6fcb6dba",
"hooks/gsd-read-injection-scanner.js": "e48bc03685078bc7",
"hooks/gsd-session-state.sh": "b1496e6a5204a6df",
"hooks/gsd-statusline.js": "b9d07c3acc630b5d",
"hooks/gsd-update-banner.js": "d3228b9e674296b4",
"hooks/gsd-validate-commit.sh": "14d3d966c74dc310",
"hooks/gsd-workflow-guard.js": "6faa8f81812a8b5d",
"hooks/gsd-worktree-path-guard.js": "d9054ef9ec469312",
"hooks/gsd-check-update-worker.js": "4bb354044e0dff91",
"hooks/gsd-check-update.js": "d2065cb3e725a42a",
"hooks/gsd-config-reload.js": "4f52b8a0120bb1b8",
"hooks/gsd-context-monitor.js": "437a33e6e3058640",
"hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71",
"hooks/gsd-cursor-session-start.js": "05a14e903c5edafa",
"hooks/gsd-ensure-canonical-path.js": "2df5e295b36c3334",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "2c8d417d12b51040",
"hooks/gsd-read-injection-scanner.js": "396574bd25e99ff9",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "739140996a3c0d49",
"hooks/gsd-update-banner.js": "b457746cb76c1957",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "59b46a74d19d58d3",
"hooks/gsd-worktree-path-guard.js": "8389e4c9175b2613",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "a57697c1ae4ac163",

View File

@@ -34,14 +34,13 @@
"agents/gsd-ui-checker.md": "843c1deeaa1cb5e7",
"agents/gsd-ui-researcher.md": "e09f0f7034eaa366",
"agents/gsd-user-profiler.md": "622220df0654b6bf",
"agents/gsd-verifier.md": "1e8a6492303366a0",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "3065cc4cf897078d",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "5cf26d5f9e588cf8",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -86,6 +85,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -263,7 +263,7 @@
"gsd-core/workflows/note.md": "acc9130fb1e94f0b",
"gsd-core/workflows/pause-work.md": "09a6b8980f7b771a",
"gsd-core/workflows/plan-milestone-gaps.md": "022822b3b6971b75",
"gsd-core/workflows/plan-phase.md": "c3e494c63f5c04c5",
"gsd-core/workflows/plan-phase.md": "9781d27e30242ed8",
"gsd-core/workflows/plan-review-convergence.md": "8fb889570c17db15",
"gsd-core/workflows/plant-seed.md": "dc911406ada4d188",
"gsd-core/workflows/pr-branch.md": "e939128047e02395",
@@ -289,7 +289,7 @@
"gsd-core/workflows/spike.md": "48df8b626cbd378d",
"gsd-core/workflows/stats.md": "18089135bc41f1b4",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "9fe3ec12e491bec3",
"gsd-core/workflows/thread.md": "61e0eee731434ea9",
"gsd-core/workflows/transition.md": "b49ddd9247f804e7",
"gsd-core/workflows/ui-phase.md": "a725ebdd4f47fb97",
"gsd-core/workflows/ui-review.md": "b3221cac976daffa",
@@ -297,7 +297,7 @@
"gsd-core/workflows/undo.md": "59b8baa54efc4110",
"gsd-core/workflows/update.md": "e259898f86ae7133",
"gsd-core/workflows/validate-phase.md": "70d102b4d5113dd4",
"gsd-core/workflows/verify-phase.md": "ba797ce64e593a10",
"gsd-core/workflows/verify-phase.md": "67eefbd1f6417281",
"gsd-core/workflows/verify-work.md": "1e2a10168f8fdc2b",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",

View File

@@ -34,14 +34,13 @@
"agents/gsd-ui-checker.md": "bd8e9be4c75dcdcf",
"agents/gsd-ui-researcher.md": "f04d24e6459bd23d",
"agents/gsd-user-profiler.md": "622220df0654b6bf",
"agents/gsd-verifier.md": "88eac9841f52dd8c",
"gsd-core/CHANGELOG.md": "e141e3fb369ff712",
"gsd-core/VERSION": "562368b20a64be95",
"agents/gsd-verifier.md": "e6353ed8a4baaa32",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "5cf26d5f9e588cf8",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
@@ -86,6 +85,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/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
@@ -263,7 +263,7 @@
"gsd-core/workflows/note.md": "1c1e466c764e3deb",
"gsd-core/workflows/pause-work.md": "5ce6a137bd9fc0f8",
"gsd-core/workflows/plan-milestone-gaps.md": "19911e87fd4185f2",
"gsd-core/workflows/plan-phase.md": "98210e414d5d9e0b",
"gsd-core/workflows/plan-phase.md": "55ddbe990fcaa74f",
"gsd-core/workflows/plan-review-convergence.md": "7e01c19b7b9a2aad",
"gsd-core/workflows/plant-seed.md": "9d36ecd08093a494",
"gsd-core/workflows/pr-branch.md": "cc28ab5cd9db16b9",
@@ -289,7 +289,7 @@
"gsd-core/workflows/spike.md": "aa5934044317ba7a",
"gsd-core/workflows/stats.md": "c49d3de15375d04b",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "5ad6ddb308909770",
"gsd-core/workflows/thread.md": "3e9fcfbb254a31a7",
"gsd-core/workflows/transition.md": "9a49f9c48805f666",
"gsd-core/workflows/ui-phase.md": "54e01f1973450a3c",
"gsd-core/workflows/ui-review.md": "fea93c281767f8d7",
@@ -297,7 +297,7 @@
"gsd-core/workflows/undo.md": "18dec684fb1076f9",
"gsd-core/workflows/update.md": "cbf978622ad0f577",
"gsd-core/workflows/validate-phase.md": "a36cf2c688c261ac",
"gsd-core/workflows/verify-phase.md": "6adfc47bee438b5d",
"gsd-core/workflows/verify-phase.md": "8a89a915dad5960b",
"gsd-core/workflows/verify-work.md": "7586bdeeeb9ecba6",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",

View File

@@ -18,7 +18,7 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { runGsdTools } = require('./helpers.cjs');
const { runGsdTools, parseFrontmatter } = require('./helpers.cjs');
// Track temp files for cleanup
let tempFiles = [];
@@ -441,3 +441,88 @@ describe('Bug #1660: frontmatter set of an object-list field fails closed instea
assert.ok(!parsed.error, 'an idempotent scalar-array set must not produce an error');
});
});
// ─── #1778: thread workflow must use the 1.6 named-flag frontmatter.set form ─
//
// The thread workflow's CLOSE and RESUME branches previously invoked the
// pre-1.6 positional shape (frontmatter.set <file> <field> <value>). Since 1.6
// the dispatcher (gsd-tools.cjs) reads field/value from the named --field/
// --value flags via parseNamedArgs; the positional form leaves field/value
// undefined, cmdFrontmatterSet errors `file, field, and value required`, and
// the status/updated writes are silently skipped — so closing a thread never
// marked it status: resolved and resuming never marked it status: in_progress.
describe('#1778: thread workflow uses the 1.6 named-flag frontmatter.set form', () => {
test('behavioral: named-flag form writes the field; positional form errors and does not mutate', () => {
// 1.6 named-flag form — must succeed and write status: resolved.
const goodFile = writeTempFile('---\nstatus: open\nupdated: "2025-01-01"\n---\n\n# thread body\n');
const good = runGsdTools(['frontmatter', 'set', goodFile, '--field', 'status', '--value', 'resolved']);
assert.ok(good.success, `named-flag form must succeed; stderr: ${good.error}`);
assert.strictEqual(
parseFrontmatter(fs.readFileSync(goodFile, 'utf-8')).status,
'resolved',
'named-flag form must write status: resolved into the file',
);
// Pre-1.6 positional form — must fail with the documented message and NOT mutate.
const badFile = writeTempFile('---\nstatus: open\nupdated: "2025-01-01"\n---\n\n# thread body\n');
const bad = runGsdTools(['frontmatter', 'set', badFile, 'status', 'resolved']);
assert.ok(!bad.success, 'positional form must fail (it is the bug being guarded against)');
assert.ok(
(bad.error + bad.output).includes('file, field, and value required'),
`positional form must error with the documented message; got:\n${bad.error}${bad.output}`,
);
assert.strictEqual(
parseFrontmatter(fs.readFileSync(badFile, 'utf-8')).status,
'open',
'positional form must NOT mutate the file (the silent-failure bug)',
);
});
test('workflow parity: no gsd-core/workflows/*.md emits the positional frontmatter.set form', () => {
const workflowsDir = path.join(__dirname, '..', 'gsd-core', 'workflows');
const files = fs.readdirSync(workflowsDir).filter((f) => f.endsWith('.md'));
assert.ok(files.length > 0, 'expected at least one workflow under gsd-core/workflows/');
const offenders = [];
for (const name of files) {
const full = path.join(workflowsDir, name);
const lines = fs.readFileSync(full, 'utf-8').split(/\r?\n/);
lines.forEach((line, i) => {
// Match any frontmatter.set invocation (dot or space form, with or
// without the `gsd_run query` prefix). The 1.6 contract requires
// --field AND --value on every set call; a set line missing --field
// is the pre-1.6 positional form (#1778).
if (!/frontmatter[.\s]+set\b/.test(line)) return;
if (!/--field\b/.test(line) || !/--value\b/.test(line)) {
offenders.push(`${name}:${i + 1}: ${line.trim()}`);
}
});
}
assert.deepStrictEqual(
offenders,
[],
`These workflow frontmatter.set invocations are missing the 1.6 --field/--value named flags (the #1778 positional-form bug):\n ${offenders.join('\n ')}\n\nUse: gsd_run query frontmatter.set <file> --field <field> --value <value>`,
);
});
test('thread workflow CLOSE writes status: resolved and RESUME writes status: in_progress via named flags', () => {
const src = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'workflows', 'thread.md'), 'utf-8');
// CLOSE mode: status resolved + updated, both via named flags.
assert.ok(
/frontmatter\.set\s+\S*\.planning\/threads\/\{SLUG\}\.md\s+--field\s+status\s+--value\s+resolved\b/.test(src),
'CLOSE mode must invoke: frontmatter.set .planning/threads/{SLUG}.md --field status --value resolved',
);
assert.ok(
/frontmatter\.set\s+\S*\.planning\/threads\/\{SLUG\}\.md\s+--field\s+updated\s+--value\s+YYYY-MM-DD\b/.test(src),
'CLOSE mode must invoke: frontmatter.set .planning/threads/{SLUG}.md --field updated --value YYYY-MM-DD',
);
// RESUME mode: status in_progress + updated, both via named flags.
assert.ok(
/frontmatter\.set\s+\S*\.planning\/threads\/\{SLUG\}\.md\s+--field\s+status\s+--value\s+in_progress\b/.test(src),
'RESUME mode must invoke: frontmatter.set .planning/threads/{SLUG}.md --field status --value in_progress',
);
});
});

View File

@@ -48,7 +48,16 @@ const UPDATE = process.env.UPDATE_GOLDEN === '1';
const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'golden-install-parity');
// Volatile metadata files always excluded from the parity manifest.
const VOLATILE_FILES = new Set(['gsd-file-manifest.json', 'gsd-install-state.json']);
// gsd-core/CHANGELOG.md is excluded because it contains historical version strings
// that cause hash drift between local (PKG_VERSION=1.x.x) and CI (PKG_VERSION=1.x.x-rc.N):
// the PKG_VERSION normalization below replaces only the *current* version, but
// CHANGELOG.md references prior-release versions, so the normalized hash diverges.
const VOLATILE_FILES = new Set(['gsd-file-manifest.json', 'gsd-install-state.json', 'gsd-core/CHANGELOG.md']);
// The installed package version, normalized to '<VERSION>' in hash computation so
// the golden is stable across version bumps (the rc step runs `npm version X.Y.Z-rc.N`
// before tests, which rebakes the version into hook files and gsd-core/VERSION).
const PKG_VERSION = require('../package.json').version;
// Hook-registration config files excluded from the parity manifest. These are
// written by the hook/permission install path (applySettingsJsonHooks /
@@ -102,10 +111,9 @@ function buildParityManifest(configDir, root) {
const content = fs.readFileSync(full);
// Normalize every occurrence of the temp root so hashes are stable across runs.
// The only other platform-varying content (the node-runner command form) lives
// exclusively in the excluded HOOK_CONFIG_FILES, so no further normalization is
// needed — a scan of all 16 installs confirmed no other file embeds it.
const normalized = content.toString('utf8').split(root).join('<HOME>');
// Also normalize the package version so the golden survives `npm version` bumps
// (the rc release step bakes the new version into hook files before running tests).
const normalized = content.toString('utf8').split(root).join('<HOME>').split(PKG_VERSION).join('<VERSION>');
const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
unsorted[rel] = hash;
}

View File

@@ -613,6 +613,62 @@ describe('auto-update', () => {
);
});
// #1772 — agent runtimes (Claude Code's Bash tool among them) routinely
// emit HEAD-advancing commits as multi-line scripts (`cd /path` then
// `git add` then `git commit …`). The hook joins tool_name + "\n" +
// tool_input.command and must match the command across ALL its lines
// (line 2 through EOF), not just line 2 — otherwise a `git commit` that
// is not on the first command line silently no-ops the rebuild.
for (const cmd of [
"cd /tmp/repo\ngit add .\ngit commit -m 'multi-line commit'",
"cd /tmp/repo\ngit merge feature-branch",
"git fetch origin\ngit pull --ff-only",
]) {
test(`dispatches on multi-line command (#1772): ${cmd.split('\n').slice(0, 2).join(' ⏎ ')}…`, async (t) => {
const tmpDir = createTempGitRepo({
config: { graphify: { enabled: true, auto_update: true } },
});
t.after(() => cleanupHookRepo(tmpDir));
const mockBin = makeMockGraphifyBin(tmpDir, { sleepMs: 100 });
runHook(
tmpDir,
{ tool_name: 'Bash', tool_input: { command: cmd } },
{ pathPrepend: mockBin },
);
const statusPath = path.join(tmpDir, '.planning/graphs/.last-build-status.json');
await waitForBuildStatus(statusPath, new Set(['ok', 'failed']));
assert.ok(
fs.existsSync(statusPath),
`must dispatch for multi-line command where the HEAD-advancing op is not on line 1 (#1772): ${cmd}`,
);
});
}
test('multi-line command with NO HEAD-advancing op still no-ops (#1772 no-regression)', (t) => {
const tmpDir = createTempGitRepo({
config: { graphify: { enabled: true, auto_update: true } },
});
t.after(() => cleanupHookRepo(tmpDir));
const mockBin = makeMockGraphifyBin(tmpDir, { sleepMs: 100 });
const r = runHook(
tmpDir,
{
tool_name: 'Bash',
tool_input: {
// Multi-line, but only non-HEAD-advancing ops. Widening line
// extraction to 2..EOF must not cause a spurious dispatch.
command: 'cd /tmp/repo\nls -la\necho done',
},
},
{ pathPrepend: mockBin },
);
assert.strictEqual(r.status, 0);
assert.ok(
!fs.existsSync(path.join(tmpDir, '.planning/graphs/.last-build-status.json')),
'multi-line command without a HEAD-advancing git op must still no-op (#1772)',
);
});
// #3653 — only the SDK `commit` verb invokes git internally. Other
// `gsd-tools query` verbs (phase.complete, roadmap.update-plan-progress,
// state.begin-phase) mutate .md files but do NOT advance HEAD; matching

View File

@@ -0,0 +1,56 @@
'use strict';
/**
* Process-lifecycle test for the gsd-mcp-server bin entry (ADR-1239 Phase C-2,
* #1681 slice 3b / AC4). Spawns the shim, feeds line-delimited JSON-RPC over
* stdin, asserts stdout responses + clean exit on stdin EOF. Synchronous +
* bounded (the server exits when stdin closes — no orphan process).
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const path = require('node:path');
const { PROTOCOL_VERSION } = require('../gsd-core/bin/lib/mcp-server.cjs');
const SHIM = path.join(__dirname, '..', 'bin', 'gsd-mcp-server.js');
function run(stdin) {
return spawnSync(process.execPath, [SHIM], {
input: stdin,
encoding: 'utf-8',
timeout: 15000,
env: { ...process.env, GSD_TEST_MODE: '1' },
});
}
test('gsd-mcp-server bin: initialize handshake + tools/list over stdio, then clean exit', () => {
const stdin = [
JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize' }),
JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list' }),
].join('\n') + '\n';
const res = run(stdin);
assert.strictEqual(res.status, 0, `clean exit; stderr: ${res.stderr}`);
const lines = res.stdout.trim().split('\n').map((l) => JSON.parse(l));
assert.strictEqual(lines.length, 2, 'one response per request');
assert.strictEqual(lines[0].id, 1);
assert.strictEqual(lines[0].result.protocolVersion, PROTOCOL_VERSION, 'initialize returns the protocol version');
assert.ok(Array.isArray(lines[1].result.tools) && lines[1].result.tools.length === 3, 'tools/list advertises the 3 tools');
});
test('gsd-mcp-server bin: a malformed line surfaces a JSON-RPC parse error; the server keeps running', () => {
const stdin = [
'this is not json',
JSON.stringify({ jsonrpc: '2.0', id: 9, method: 'initialize' }),
].join('\n') + '\n';
const res = run(stdin);
assert.strictEqual(res.status, 0, `server survives the bad line; stderr: ${res.stderr}`);
const lines = res.stdout.trim().split('\n').map((l) => JSON.parse(l));
assert.strictEqual(lines[0].error.code, -32700, 'bad line → JSON-RPC parse error');
assert.strictEqual(lines[1].result.protocolVersion, PROTOCOL_VERSION, 'subsequent valid request still handled');
});
test('gsd-mcp-server bin: empty/whitespace-only stdin → clean exit, no output', () => {
const res = run('\n \n');
assert.strictEqual(res.status, 0);
assert.strictEqual(res.stdout.trim(), '', 'no requests → no responses');
});

View File

@@ -0,0 +1,103 @@
'use strict';
/**
* Tests for the companion MCP server (ADR-1239 Phase C-2, #1681 slice 3a).
* Pins: initialize handshake, tools/list, tools/call dispatch to the hub +
* stateIO seam, method-not-found, notification = no response, parse error in
* runServer, and a full injectable-stream round-trip.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { Readable } = require('node:stream');
const fs = require('node:fs');
const path = require('node:path');
const {
handleMessage,
runServer,
PROTOCOL_VERSION,
SERVER_NAME,
} = require('../gsd-core/bin/lib/mcp-server.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
test('initialize: returns protocolVersion + capabilities + serverInfo', () => {
const res = handleMessage({ jsonrpc: '2.0', id: 1, method: 'initialize' });
assert.strictEqual(res.jsonrpc, '2.0');
assert.strictEqual(res.id, 1);
assert.strictEqual(res.result.protocolVersion, PROTOCOL_VERSION);
assert.ok(res.result.capabilities && res.result.capabilities.tools, 'must advertise tools capability');
assert.strictEqual(res.result.serverInfo.name, SERVER_NAME);
});
test('tools/list: advertises the 3 interface-point tools', () => {
const res = handleMessage({ jsonrpc: '2.0', id: 2, method: 'tools/list' });
const names = res.result.tools.map((t) => t.name);
assert.deepStrictEqual(names.sort(), ['gsd_invoke_command', 'gsd_read_state', 'gsd_write_state']);
});
test('tools/call gsd_read_state + gsd_write_state: round-trip through the stateIO seam (point 5)', () => {
const dir = createTempDir();
try {
const file = path.join(dir, 'STATE.md');
const writeRes = handleMessage({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'gsd_write_state', arguments: { path: file, content: '# State\n' } } });
assert.strictEqual(writeRes.result.isError, undefined, 'write must succeed');
assert.strictEqual(fs.readFileSync(file, 'utf-8'), '# State\n', 'write went through to fs');
const readRes = handleMessage({ jsonrpc: '2.0', id: 4, method: 'tools/call', params: { name: 'gsd_read_state', arguments: { path: file } } });
assert.strictEqual(readRes.result.content[0].text, '# State\n', 'read returns the written content');
} finally {
cleanup(dir);
}
});
test('tools/call gsd_invoke_command: dispatches to the command hub (point 1); unknown family returns a hub error, not a crash', () => {
const res = handleMessage({ jsonrpc: '2.0', id: 5, method: 'tools/call', params: { name: 'gsd_invoke_command', arguments: { family: 'no-such-family', subcommand: 'x' } } }, { cwd: createTempDirClean() });
// The hub returns a structured result (ok:false unknown-command) surfaced as text content, not a JSON-RPC error.
assert.strictEqual(res.jsonrpc, '2.0');
const payload = JSON.parse(res.result.content[0].text);
assert.strictEqual(payload.ok, false, 'an unknown command dispatches to the hub and returns ok:false');
});
test('tools/call: unknown tool name surfaces a tool error (isError), not a JSON-RPC protocol error', () => {
const res = handleMessage({ jsonrpc: '2.0', id: 6, method: 'tools/call', params: { name: 'gsd_bogus' } });
assert.strictEqual(res.result.isError, true);
assert.match(res.result.content[0].text, /Unknown tool/);
});
test('tools/call: missing tool name is a JSON-RPC invalid-params error', () => {
const res = handleMessage({ jsonrpc: '2.0', id: 7, method: 'tools/call', params: {} });
assert.strictEqual(res.error.code, -32602);
assert.match(res.error.message, /requires string "name"/);
});
test('unknown method: JSON-RPC method-not-found (-32601)', () => {
const res = handleMessage({ jsonrpc: '2.0', id: 8, method: 'resources/read' });
assert.strictEqual(res.error.code, -32601);
assert.match(res.error.message, /Method not found/);
});
test('notification (no id): returns null (no response per JSON-RPC)', () => {
assert.strictEqual(handleMessage({ jsonrpc: '2.0', method: 'initialize' }), null);
assert.strictEqual(handleMessage({ jsonrpc: '2.0', method: 'notifications/initialized' }), null);
});
test('runServer: line-delimited JSON-RPC round-trip over injectable streams', async () => {
const input = Readable.from([
JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize' }) + '\n',
'not json\n',
JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list' }) + '\n',
]);
const out = [];
const output = { write: (s) => { out.push(s); return true; } };
await runServer({ input, output });
const joined = out.join('');
const responses = joined.trim().split('\n').map((l) => JSON.parse(l));
assert.strictEqual(responses.length, 3);
assert.strictEqual(responses[0].result.protocolVersion, PROTOCOL_VERSION, 'initialize handled');
assert.strictEqual(responses[1].error.code, -32700, 'parse error surfaced');
assert.ok(Array.isArray(responses[2].result.tools), 'tools/list handled');
});
// tiny helper to get a throwaway cwd without polluting the assertion helpers import above
function createTempDirClean() {
const os = require('node:os');
return fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-cwd-'));
}

57
tests/hook-bus.test.cjs Normal file
View File

@@ -0,0 +1,57 @@
'use strict';
/**
* Tests for the hook-bus seam (ADR-1239 Phase C-1, AC4 / #1680).
* Pins the three ownership modes + portable floor + fail-closed.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { createHookBus, PORTABLE_EVENT_FLOOR } = require('../gsd-core/bin/lib/hook-bus.cjs');
test('PORTABLE_EVENT_FLOOR: the 5 portable events (the claude dialect all hook hosts share)', () => {
assert.deepStrictEqual([...PORTABLE_EVENT_FLOOR], ['SessionStart', 'PreToolUse', 'PostToolUse', 'Stop', 'SessionEnd']);
});
test('engine bus: in-process pub/sub (subscribe + emit reaches handlers)', () => {
const bus = createHookBus({ bus: 'engine' });
assert.strictEqual(bus.bus, 'engine');
const received = [];
bus.subscribe('PreToolUse', (p) => received.push(['PreToolUse', p]));
bus.subscribe('Stop', () => received.push(['Stop']));
bus.emit('PreToolUse', { tool: 'Edit' });
bus.emit('SessionStart');
bus.emit('Stop');
assert.deepStrictEqual(received, [['PreToolUse', { tool: 'Edit' }], ['Stop']]);
});
test('engine bus: a throwing handler is isolated (does not break the bus or other handlers)', () => {
const bus = createHookBus({ bus: 'engine' });
const seen = [];
bus.subscribe('PostToolUse', () => { throw new Error('boom'); });
bus.subscribe('PostToolUse', (p) => seen.push(p));
assert.doesNotThrow(() => bus.emit('PostToolUse', { ok: true }));
assert.deepStrictEqual(seen, [{ ok: true }]);
});
test('none bus: subscribe + emit are silent no-ops (degrade to rule-text)', () => {
const bus = createHookBus({ bus: 'none' });
assert.strictEqual(bus.bus, 'none');
assert.doesNotThrow(() => bus.subscribe('SessionStart', () => { throw new Error('must not be called'); }));
assert.doesNotThrow(() => bus.emit('SessionStart', {}));
});
test('host bus: emit delegates to the bound host emitter; fail-closed when unbound', () => {
const emitted = [];
const bound = createHookBus({ bus: 'host' }, { hostEmit: (e, p) => emitted.push([e, p]) });
assert.strictEqual(bound.bus, 'host');
bound.emit('Stop', { reason: 'done' });
assert.deepStrictEqual(emitted, [['Stop', { reason: 'done' }]]);
const unbound = createHookBus({ bus: 'host' });
assert.throws(() => unbound.emit('Stop'), /no host emitter bound/, 'unbound host emit must fail closed');
});
test('createHookBus: invalid mode throws (fail-closed construction)', () => {
for (const bad of ['cloud', '', null, undefined, 1]) {
assert.throws(() => createHookBus({ bus: bad }), TypeError, `bus=${JSON.stringify(bad)} must throw`);
}
});

View File

@@ -0,0 +1,73 @@
'use strict';
/**
* Tests for the model adapter seam (ADR-1239 Phase C-1, AC3 / #1680).
*
* Pins:
* 1. PASSIVE — `resolveModel` delegates to model-resolver's tier routing
* (reproduces today's behavior).
* 2. ACTIVE — `sendRequest` calls the host-supplied primitive; FAIL-CLOSED
* (throws) when no provider is bound.
* 3. FACTORY GATING — invalid modelMode throws.
*
* Behavioral tests only; delegation verified via module-ref monkeypatch.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { createModelAdapter } = require('../gsd-core/bin/lib/model-adapter.cjs');
const modelResolver = require('../gsd-core/bin/lib/model-resolver.cjs');
test('passive model adapter: resolveModel delegates to model-resolver tier routing (reproduces today behavior)', () => {
const adapter = createModelAdapter({ modelMode: 'passive' });
assert.strictEqual(adapter.mode, 'passive');
const original = modelResolver.resolveModelForTier;
let captured = null;
modelResolver.resolveModelForTier = function (...a) { captured = a; return 'sonnet'; };
try {
const out = adapter.resolveModel({ cwd: '/tmp/proj', agentType: 'planner', attempt: 2 });
assert.strictEqual(out, 'sonnet', 'resolveModel must return the resolver result');
assert.deepStrictEqual(captured, ['/tmp/proj', 'planner', 2], 'must delegate (cwd, agentType, attempt) verbatim');
} finally {
modelResolver.resolveModelForTier = original;
}
});
test('passive model adapter: attempt is optional (delegates undefined when omitted)', () => {
const adapter = createModelAdapter({ modelMode: 'passive' });
const original = modelResolver.resolveModelForTier;
let captured = null;
modelResolver.resolveModelForTier = function (...a) { captured = a; return 'haiku'; };
try {
adapter.resolveModel({ cwd: '/tmp/proj', agentType: 'executor' });
assert.deepStrictEqual(captured, ['/tmp/proj', 'executor', undefined], 'attempt defaults to undefined');
} finally {
modelResolver.resolveModelForTier = original;
}
});
test('active model adapter: sendRequest invokes the bound host provider', () => {
const calls = [];
const adapter = createModelAdapter(
{ modelMode: 'active' },
{ sendRequest: (req) => { calls.push(req); return { ok: true, echo: req }; } },
);
assert.strictEqual(adapter.mode, 'active');
const out = adapter.sendRequest({ prompt: 'hi' });
assert.deepStrictEqual(calls, [{ prompt: 'hi' }], 'host provider invoked with the request');
assert.deepStrictEqual(out, { ok: true, echo: { prompt: 'hi' } }, 'host provider return value passed through');
});
test('active model adapter: FAIL-CLOSED when no host provider is bound (sendRequest throws)', () => {
const adapter = createModelAdapter({ modelMode: 'active' });
assert.throws(
() => adapter.sendRequest({ prompt: 'hi' }),
/no host provider bound/,
'sendRequest must throw when no provider is bound (fail-closed, never silently no-op)',
);
});
test('createModelAdapter: invalid modelMode throws (fail-closed construction)', () => {
for (const bad of ['host', 'dynamic', '', null, undefined, 3]) {
assert.throws(() => createModelAdapter({ modelMode: bad }), TypeError, `modelMode=${JSON.stringify(bad)} must throw TypeError`);
}
});

View File

@@ -2362,6 +2362,187 @@ describe('phase complete command', () => {
assert.ok(state.includes('Milestone complete'), 'status should be milestone complete');
});
// #1591: when the active milestone's phase checklist is wrapped in a
// <details> block AND phases are written as `- [ ] Phase N:` checkbox list
// items (not `### Phase N:` headings), phase.complete's next-phase enumerator
// saw no further phases → is_last_phase=true, next_phase=null on a mid-
// milestone phase, and STATE.md was wrongly marked "Milestone complete" with
// total_phases decremented. extractCurrentMilestone correctly surfaces the
// <details>-wrapped checklist; the defect was the heading-only phasePattern
// at the isLastPhase enumerator not recognizing checkbox-list phase items.
test('#1591: <details>-wrapped checkbox checklist — mid-milestone phase is NOT last', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
[
'# ROADMAP',
'',
'## Phases',
'',
'<details>',
'<summary>✅ v1.0 First (Phases 1–3) — SHIPPED</summary>',
'',
'- [x] Phase 1: a',
'- [x] Phase 2: b',
'- [x] Phase 3: c',
'',
'</details>',
'',
'<details>',
'<summary>🚀 v2.0 Second (Phases 36–38) — IN PLANNING</summary>',
'',
'- [x] Phase 36: first (completed)',
'- [ ] Phase 37: second',
'- [ ] Phase 38: third',
'',
'</details>',
'',
'## Backlog',
'',
'### Phase 999.1: future (BACKLOG)',
'',
].join('\n')
);
fs.writeFileSync(
path.join(tmpDir, '.planning', 'STATE.md'),
[
'---',
'gsd_state_version: 1.0',
'milestone: v2.0',
'milestone_name: Second',
'current_phase: "36"',
'status: executing',
'---',
'',
'# GSD State',
'',
'**Current Phase:** 36',
'**Status:** Executing Phase 36',
'',
].join('\n')
);
// Only the COMPLETING phase (36) has a directory. Phases 37/38 exist only
// as `- [ ]` checklist items in the ROADMAP — they are not yet started, so
// they have no phase dirs. This is the @Azd325 scenario: the disk-based
// next-phase resolver finds nothing, and the roadmap-enumeration fallback
// (the heading-only phasePattern) is the only path that can find Phase 37.
const d36 = path.join(tmpDir, '.planning', 'phases', '36-first');
fs.mkdirSync(d36, { recursive: true });
fs.writeFileSync(path.join(d36, '36-PLAN.md'), '# Plan\n');
fs.writeFileSync(path.join(d36, '36-SUMMARY.md'), '# Summary\n');
const result = runVerifiedPhaseComplete('phase complete 36', tmpDir);
assert.ok(result.success, `phase complete failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(
output.is_last_phase,
false,
'Phase 36 of 36–38 must NOT be last — Phases 37/38 are still open `- [ ]` (#1591)',
);
assert.strictEqual(
output.next_phase,
'37',
'next_phase must resolve to 37 from the <details>-wrapped checkbox checklist (#1591)',
);
// Cascade check: a wrong is_last_phase=true previously wrote "Milestone
// complete" + decremented total_phases. With the fix, the milestone is
// still in progress.
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(
!/Milestone complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to "Milestone complete" (#1591)',
);
});
// #1752: the #1591 follow-up — when phase.complete wrongly returned
// is_last_phase=true on a <details>-wrapped mid-milestone checklist, the
// milestone-complete cascade also DECREMENTED progress.total_phases (e.g.
// 8 -> 7) and flipped status. Same root cause, distinct symptom. With the
// #1591 fix (is_last_phase=false), the decrement must not occur: with all 8
// phase dirs on disk, total_phases stays 8 and status does not flip.
test('#1752: <details>-wrapped checklist — total_phases is NOT decremented on a mid-milestone phase', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
[
'# ROADMAP',
'',
'## Phases',
'',
'<details>',
'<summary>✅ v1.0 First (Phases 1–3) — SHIPPED</summary>',
'',
'- [x] Phase 1: a',
'- [x] Phase 2: b',
'- [x] Phase 3: c',
'',
'</details>',
'',
'<details>',
'<summary>🚀 v2.0 Second (Phases 36–43) — IN PLANNING</summary>',
'',
'- [x] Phase 36: first (completed)',
'- [ ] Phase 37: second',
'- [ ] Phase 38: third',
'- [ ] Phase 39: fourth',
'',
'</details>',
'',
].join('\n')
);
fs.writeFileSync(
path.join(tmpDir, '.planning', 'STATE.md'),
[
'---',
'gsd_state_version: 1.0',
'milestone: v2.0',
'milestone_name: Second',
'current_phase: "36"',
'status: executing',
'progress:',
' total_phases: 8',
' completed_phases: 5',
' percent: 62',
'---',
'',
'# GSD State',
'',
'**Current Phase:** 36',
'**Status:** Executing Phase 36',
'',
].join('\n')
);
// All 8 phase dirs on disk (Phases 36–43) so the disk count is 8 — the
// reporter's real state. Before the #1591 fix, phase.complete 36 returned
// is_last_phase=true (no Phase 37+ heading match) and the milestone-complete
// path DECREMENTED total_phases 8 -> 7.
const names = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth'];
for (let i = 0; i < 8; i++) {
const num = String(36 + i);
const d = path.join(tmpDir, '.planning', 'phases', `${num}-${names[i]}`);
fs.mkdirSync(d, { recursive: true });
fs.writeFileSync(path.join(d, `${num}-PLAN.md`), '# Plan\n');
}
const result = runVerifiedPhaseComplete('phase complete 36', tmpDir);
assert.ok(result.success, `phase complete failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.is_last_phase, false, 'is_last_phase must be false (#1752 cascade root)');
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(
!/Milestone complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to "Milestone complete" (#1752)',
);
const tpMatch = state.match(/total_phases:\s*(\d+)/);
assert.ok(tpMatch, 'STATE.md must carry a total_phases value after phase.complete');
assert.notStrictEqual(
parseInt(tpMatch[1], 10),
7,
'total_phases must NOT be decremented to 7 — the #1752 cascade of the false is_last_phase',
);
});
test('updates REQUIREMENTS.md traceability when phase completes', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),

View File

@@ -513,3 +513,111 @@ describe('probe-core: projectProhibitions descriptor projection (CHK-02)', () =>
assert.ok(!('check_rule' in projected[0]), 'descriptor-less item gains no check_rule');
});
});
// ─── Honest verifier (#1154): truth-axis abstention — the verify-time MIRROR of #644's
// prohibition judgment-tier (ADR-550 D4), applied to the edge `backstop` truth tier (D7a).
// Per ADR-550 D5 the deterministic, CI-testable surface is the disposition helper + the
// projection ROUND-TRIP — NEVER the LLM verdict (a test asserting the model's judgment is
// vacuous and rejected). The abstain-on-unconfirmed-backstop case is the REGRESSION
// (trek-e review condition 5) that must fail RED on `next` before the fix.
const FM_SCRIPT = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'frontmatter.cjs');
const fm = require(FM_SCRIPT);
// Serialize projected truths into a plan-frontmatter `must_haves.truths` block exactly as
// plan-phase emits it — a backstop truth as a flat-scalar object (ADR-550 #1278: flat scalars,
// NEVER a nested object), a plain inferable truth as a bare string.
function renderTruthsBlock(projected) {
const lines = ['---', 'must_haves:', ' truths:'];
for (const t of projected) {
if (typeof t === 'string') {
lines.push(` - ${t}`);
} else {
lines.push(` - statement: ${t.statement}`);
if (t.verification) lines.push(` verification: ${t.verification}`);
}
}
lines.push('---');
return lines.join('\n');
}
describe('probe-core: truthStatement / truthVerification normalizers (#1154, Hyrum backward-compat)', () => {
test('truthStatement extracts text from a plain-string truth and an object-form truth identically', () => {
assert.equal(pc.truthStatement('Overlapping intervals are merged'), 'Overlapping intervals are merged');
assert.equal(
pc.truthStatement({ statement: 'Adjacent intervals merge', verification: 'backstop' }),
'Adjacent intervals merge',
);
});
test('truthVerification returns the tier for an object-form truth and null for a plain string (no spurious tier)', () => {
assert.equal(pc.truthVerification({ statement: 'Adjacent intervals merge', verification: 'backstop' }), 'backstop');
assert.equal(pc.truthVerification('Overlapping intervals are merged'), null);
assert.equal(pc.truthVerification({ statement: 'x', verification: 'explicit' }), 'explicit');
});
});
describe('probe-core: dispositionForUnverifiableTruth (#1154, ADR-550 D4 truth-axis mirror)', () => {
test('abstain-on-unconfirmed-backstop (condition 5, REGRESSION): a backstop truth with no explicit evidence disposes insufficient_spec/unverified/flagged — never green', () => {
const d = pc.dispositionForUnverifiableTruth(
{ statement: 'Adjacent/touching intervals [1,2],[2,3] merge', verification: 'backstop' },
{ evidence: [] },
);
assert.equal(d.status, 'unverified', 'a backstop truth with no explicit evidence is unverified');
assert.equal(d.flagged, true, 'and flagged — never a silent pass (ADR-550 D4)');
assert.equal(d.tier, 'backstop', 'the backstop tier is echoed unchanged');
assert.equal(
d.reason,
'insufficient_spec',
'carries the distinguishable insufficient_spec reason code so human_needed is not conflated with ordinary manual-UAT',
);
});
test('pass-on-wired-backstop: a backstop truth WITH explicit evidence (a wired held-out/property test) disposes green — abstention is for the unconfirmable only', () => {
const d = pc.dispositionForUnverifiableTruth(
{ statement: 'Adjacent/touching intervals [1,2],[2,3] merge', verification: 'backstop' },
{ evidence: [{ test: 'tests/intervals.property.test.cjs', passed: true }] },
);
assert.equal(d.status, 'green');
assert.equal(d.flagged, false);
assert.equal(d.tier, 'backstop');
});
test('over-abstention guard (AC#3): a plain inferable truth is NEVER routed to abstention', () => {
const d = pc.dispositionForUnverifiableTruth('Overlapping intervals are merged', { evidence: [] });
assert.equal(d.status, 'green', 'a plain inferable truth is graded normally, never abstained');
assert.equal(d.flagged, false);
});
test('over-abstention guard (AC#3): an explicit-tier truth NEVER abstains even with no evidence (only backstop triggers it)', () => {
const d = pc.dispositionForUnverifiableTruth({ statement: 'Symbol X is wired', verification: 'explicit' }, { evidence: [] });
assert.equal(d.status, 'green', 'an explicit (inferable) truth never abstains');
assert.equal(d.flagged, false);
});
});
describe('probe-core: projectTruths (#1154, conservative serializer — Postel) + round-trip parity (condition 4, ADR-550 D5b)', () => {
test('projectTruths emits the flat-scalar backstop marker and collapses inferable truths to plain strings', () => {
const projected = pc.projectTruths([
{ statement: 'Adjacent intervals merge', verification: 'backstop' },
'Overlapping intervals are merged',
{ statement: 'Symbol X is wired', verification: 'explicit' },
]);
assert.deepEqual(projected, [
{ statement: 'Adjacent intervals merge', verification: 'backstop' },
'Overlapping intervals are merged',
'Symbol X is wired',
], 'only a backstop truth carries a structured marker; explicit/inferable truths stay bare strings (no spurious markers)');
});
test('round-trip parity (SPEC backstop edge → must_haves.truths marker → read-back): the marker survives as a structured field, plain truths byte-identically', () => {
const projected = pc.projectTruths([
{ statement: 'Adjacent intervals merge', verification: 'backstop' },
'Overlapping intervals are merged',
]);
const parsed = fm.parseMustHavesBlock(renderTruthsBlock(projected), 'truths');
assert.equal(pc.truthVerification(parsed[0]), 'backstop', 'the backstop marker survives the round-trip as a structured field, not prose (#1110 fragility avoided)');
assert.equal(pc.truthStatement(parsed[0]), 'Adjacent intervals merge');
assert.equal(pc.truthStatement(parsed[1]), 'Overlapping intervals are merged');
assert.equal(pc.truthVerification(parsed[1]), null, 'a plain truth round-trips with no marker (Hyrum byte-identity backward-compat)');
});
});

View File

@@ -0,0 +1,54 @@
'use strict';
/**
* Tests for runtimeFlags (ADR-1239 Phase B / #1679 AC2). Collapses the four
* duplicated `const isX = runtime === 'x'` declaration blocks in bin/install.js
* into one helper. Pins: all flags present, exactly one true per known runtime,
* claude/unknown/empty → all false, frozen.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { runtimeFlags } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const EXPECTED_FLAGS = [
'isOpencode', 'isKilo', 'isGemini', 'isCodex', 'isCopilot', 'isAntigravity',
'isCursor', 'isWindsurf', 'isAugment', 'isTrae', 'isQwen', 'isHermes',
'isCodebuddy', 'isCline', 'isKimi',
];
test('runtimeFlags: every known non-claude runtime sets exactly its own flag true', () => {
const ids = EXPECTED_FLAGS.map((f) => f.slice(2).toLowerCase());
for (const id of ids) {
const flags = runtimeFlags(id);
const trues = EXPECTED_FLAGS.filter((f) => flags[f] === true);
assert.deepStrictEqual(trues, ['is' + id.charAt(0).toUpperCase() + id.slice(1)], `runtime '${id}' must set exactly its own flag`);
}
});
test('runtimeFlags: claude / unknown / empty → all flags false (fail-closed)', () => {
for (const id of ['claude', 'unknown', '', 'claude-code']) {
const flags = runtimeFlags(id);
for (const f of EXPECTED_FLAGS) {
assert.strictEqual(flags[f], false, `runtime '${id}': ${f} must be false`);
}
}
});
test('runtimeFlags: all 15 flags present + boolean + the object is frozen', () => {
const flags = runtimeFlags('opencode');
for (const f of EXPECTED_FLAGS) {
assert.strictEqual(typeof flags[f], 'boolean', `${f} must be boolean`);
}
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 15 flags');
assert.ok(Object.isFrozen(flags), 'flags object must be frozen');
});
test('runtimeFlags drift guard: covers every registry runtime except claude', () => {
// Adding a registry runtime that is not claude must get a flag or be added to
// RUNTIME_FLAG_IDS — pin the set so a new runtime forces a deliberate update.
const registryNonClaude = Object.keys(registry.runtimes).filter((r) => r !== 'claude').sort();
const flagIds = EXPECTED_FLAGS.map((f) => f.slice(2).toLowerCase()).sort();
const missing = registryNonClaude.filter((r) => !flagIds.includes(r));
assert.deepEqual(missing, [], `registry runtimes missing a runtimeFlags entry: ${missing.join(', ')} — add to RUNTIME_FLAG_IDS`);
});

View File

@@ -33,7 +33,7 @@ const assert = require('node:assert/strict');
const runtimeNamePolicy = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const { getRuntimeLabel } = runtimeNamePolicy;
const { getRuntimeLabel, getRuntimeNewProjectCommand } = runtimeNamePolicy;
// Golden oracle: hardcoded expected map of all 16 runtime ids to their short
// install/uninstall display label. A pinned expected value in a TEST is correct
@@ -101,3 +101,30 @@ test('getRuntimeLabel fallback: alias is NOT auto-expanded (raw id match only)',
assert.strictEqual(getRuntimeLabel('claude-code'), 'Claude Code',
'getRuntimeLabel("claude-code") must return the default "Claude Code" (raw-id match only; aliases are not expanded)');
});
// ---------------------------------------------------------------------------
// getRuntimeNewProjectCommand (ADR-1239 Phase B / #1679 AC2) — the per-runtime
// /gsd-new-project invocation syntax for the post-install next-step message.
// ---------------------------------------------------------------------------
const GOLDEN_COMMAND_MAP = {
// 4 real overrides (the rest use the default):
gemini: '/gsd:new-project',
codex: '$gsd-new-project',
cursor: 'gsd-new-project (mention the skill name)',
kimi: '/skill:gsd-new-project',
};
const DEFAULT_CMD = '/gsd-new-project';
test('getRuntimeNewProjectCommand: the 4 overrides + the default for the other 12 runtimes', () => {
for (const [id, expected] of Object.entries(GOLDEN_COMMAND_MAP)) {
assert.strictEqual(getRuntimeLabel ? getRuntimeNewProjectCommand(id) : null, expected, `override ${id}`);
}
// sanity: getRuntimeNewProjectCommand is imported alongside getRuntimeLabel above
});
test('getRuntimeNewProjectCommand: claude/unknown/empty + the 12 non-override runtimes → default', () => {
for (const id of ['claude', 'opencode', 'kilo', 'copilot', 'antigravity', 'windsurf', 'augment', 'trae', 'cline', 'qwen', 'hermes', 'codebuddy', 'unknown', '']) {
assert.strictEqual(getRuntimeNewProjectCommand(id), DEFAULT_CMD, `runtime '${id}' must return the default command`);
}
});

59
tests/state-io.test.cjs Normal file
View File

@@ -0,0 +1,59 @@
'use strict';
/**
* Tests for the state IO seam (ADR-1239 Phase C-1, AC4 / #1680).
* Pins: filesystem (today's behavior) + fail-closed non-filesystem seams +
* host-backend binding + construction gating.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createStateIO } = require('../gsd-core/bin/lib/state-io.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
test('filesystem stateIO: read/write delegate to fs (today behavior)', () => {
const io = createStateIO({ io: 'filesystem' });
assert.strictEqual(io.io, 'filesystem');
const dir = createTempDir();
try {
const file = path.join(dir, 'STATE.md');
io.write(file, '# State\n');
assert.strictEqual(io.read(file), '# State\n');
assert.strictEqual(fs.readFileSync(file, 'utf-8'), '# State\n', 'must write through to real fs');
} finally {
cleanup(dir);
}
});
test('sandboxed-storage stateIO: fail-closed until a host backend is bound', () => {
const io = createStateIO({ io: 'sandboxed-storage' });
assert.strictEqual(io.io, 'sandboxed-storage');
assert.throws(() => io.read('/x'), /no host backend bound/, 'read must fail closed when unbound');
assert.throws(() => io.write('/x', 'y'), /no host backend bound/, 'write must fail closed when unbound');
});
test('session-log-append stateIO: fail-closed until a host backend is bound', () => {
const io = createStateIO({ io: 'session-log-append' });
assert.throws(() => io.read('/x'), /no host backend bound/);
});
test('non-filesystem stateIO: host backend is used when bound', () => {
const calls = [];
const io = createStateIO(
{ io: 'sandboxed-storage' },
{ backend: {
read: (p) => { calls.push(['read', p]); return 'BACKEND:' + p; },
write: (p, c) => { calls.push(['write', p, c]); },
} },
);
assert.strictEqual(io.read('/state/log'), 'BACKEND:/state/log');
io.write('/state/log', 'entry');
assert.deepStrictEqual(calls, [['read', '/state/log'], ['write', '/state/log', 'entry']]);
});
test('createStateIO: invalid io throws (fail-closed construction)', () => {
for (const bad of ['memory', '', null, undefined, 2]) {
assert.throws(() => createStateIO({ io: bad }), TypeError, `io=${JSON.stringify(bad)} must throw`);
}
});

View File

@@ -0,0 +1,217 @@
'use strict';
// Phase 2 integration tests for the `state rebuild` CLI subcommand (#1826).
//
// These tests exercise the CLI dispatch path (routeStateCommand → cmdStateRebuild),
// the --dry-run flag (no write), and the --verbose flag (stderr emit). The
// underlying rebuildCore logic is covered by tests/state-rebuild.test.cjs (Phase 1).
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const {
createTempProject,
cleanup,
runGsdTools,
} = require('./helpers.cjs');
const TOOLS_PATH = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
/**
* Write a STATE.md with one drift signature (stale Current Phase in body vs
* frontmatter) into a freshly-created temp project. Returns the temp dir.
*/
function projectWithDriftedState() {
const cwd = createTempProject('state-rebuild-cli');
const planningPath = path.join(cwd, '.planning');
// Drop two phase dirs so phaseInventoryProvider has something to scan.
fs.mkdirSync(path.join(planningPath, 'phases', '01-phase-one'), { recursive: true });
fs.writeFileSync(path.join(planningPath, 'phases', '01-phase-one', '01-PLAN.md'), '# Plan');
fs.mkdirSync(path.join(planningPath, 'phases', '02-phase-two'), { recursive: true });
fs.writeFileSync(path.join(planningPath, 'phases', '02-phase-two', '01-PLAN.md'), '# Plan');
// STATE.md with a drift: body says Current Phase 1, frontmatter says 2.
const stateContent = [
'---',
'gsd_state_version: \'1.0\'',
'status: executing',
'milestone: 1.0.0',
'milestone_name: Test',
'current_phase: 2',
'current_phase_name: Phase Two',
'current_plan: 1',
'progress:',
' total_phases: 2',
' completed_phases: 1',
' total_plans: 2',
' completed_plans: 1',
' percent: 50',
'---',
'',
'# Project State',
'',
'## Project Reference',
'',
'**Core value:** A test project',
'**Current focus:** Phase Two',
'',
'## Current Position',
'',
'**Current Phase:** 1',
'**Current Phase Name:** Phase One',
'**Current Plan:** 1',
'**Total Plans in Phase:** 1',
'**Status:** executing',
'**Last Activity:** 2026-06-29',
'**Last Activity Description:** mid-flight',
'',
'Phase: 2 of 2 (Phase Two)',
'Plan: 1 of 1',
'Status: Executing Phase 2',
'Last activity: 2026-06-29 — mid-flight',
'',
'**Progress:** [█████░░░░░] 50%',
'',
'## Performance Metrics',
'',
'**By Phase:**',
'',
'| Phase | Plans | Total | Avg/Plan |',
'|-------|-------|-------|----------|',
'| 99 | 1 | - | - |',
'',
'## Accumulated Context',
'',
'### Decisions',
'',
'None yet.',
'',
'## Session Continuity',
'',
'Last session: 2026-06-29 12:00',
'Stopped at: mid-flight',
'Resume file: None',
'',
].join('\n');
fs.writeFileSync(path.join(planningPath, 'STATE.md'), stateContent);
return cwd;
}
/** Read the live STATE.md from a project (strips the audit log so assertions
* check the canonical body, not the appended ## Rebuild Log entries). */
function readLiveState(cwd) {
const statePath = path.join(cwd, '.planning', 'STATE.md');
const content = fs.readFileSync(statePath, 'utf8');
// Strip ## Rebuild Log and everything after for shape assertions.
return content.replace(/^## Rebuild Log[\s\S]*$/m, '');
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('ADR-1817 Phase 2: `state rebuild` CLI subcommand dispatch (criterion #5 + end-to-end)', () => {
test('`state rebuild` with no flags reconciles drifted body fields and writes', (t) => {
const cwd = projectWithDriftedState();
t.after(() => cleanup(cwd));
const result = runGsdTools('state rebuild', cwd);
assert.ok(result.success, `state rebuild should succeed; stderr: ${result.stderr || result.error || ''}`);
// Body fields reconciled with frontmatter.
const live = readLiveState(cwd);
assert.ok(live.includes('**Current Phase:** 2'),
'body Current Phase must be reconciled to frontmatter value 2');
assert.ok(live.includes('**Current Phase Name:** Phase Two'),
'body Current Phase Name must be reconciled to frontmatter value');
// Orphan table row dropped (phase 99 is not on disk).
assert.ok(!live.includes('| 99 |'),
'orphan row for phase 99 must be dropped (phaseInventoryProvider wired to disk scan)');
// Audit log appended.
const fullState = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf8');
assert.ok(fullState.includes('## Rebuild Log'),
'audit log section must be appended');
});
test('`state rebuild --dry-run` computes the diff but writes nothing', (t) => {
const cwd = projectWithDriftedState();
t.after(() => cleanup(cwd));
const before = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf8');
const result = runGsdTools('state rebuild --dry-run', cwd);
assert.ok(result.success, `state rebuild --dry-run should succeed; error: ${result.error || ''}`);
const after = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf8');
assert.strictEqual(after, before,
'--dry-run must NOT modify STATE.md on disk');
// The structured output should signal mutations would occur (the fixture
// has drift, so mutated=true in dry-run preview).
assert.ok(result.output.includes('mutated'),
`dry-run output should report mutated flag; output: ${result.output}`);
});
test('`state rebuild --verbose` emits audit-log entries to stderr', (t) => {
const cwd = projectWithDriftedState();
t.after(() => cleanup(cwd));
// runGsdTools returns stdout only on success; --verbose writes to stderr,
// so invoke gsd-tools directly to capture both streams separately.
const stdout = execFileSync(
process.execPath,
[TOOLS_PATH, 'state', 'rebuild', '--verbose'],
{ cwd, encoding: 'utf8' },
);
// execFileSync does not separate stderr — stderr is inherited by default.
// Assert via the canonical record written to STATE.md: the audit log
// section is always appended (mutated fixture), and --verbose merely
// tees the same entries to stderr. The functional guarantee (audit log
// written) is what matters; the stderr tee is a convenience.
const after = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf8');
assert.ok(after.includes('## Rebuild Log'),
'--verbose must still produce the audit log section in STATE.md');
assert.ok(stdout.includes('rebuilt'),
`--verbose stdout must include the rebuild result; got: ${stdout.slice(0, 200)}`);
});
test('`state rebuild` on a clean STATE.md is a no-op (idempotency, end-to-end)', (t) => {
const cwd = projectWithDriftedState();
t.after(() => cleanup(cwd));
// First run: reconcile the drift.
const first = runGsdTools('state rebuild', cwd);
assert.ok(first.success, 'first rebuild should succeed');
const afterFirst = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf8');
// Second run: should detect no drift, write nothing beyond what's there.
const second = runGsdTools('state rebuild', cwd);
assert.ok(second.success, 'second rebuild should succeed');
const afterSecond = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf8');
assert.strictEqual(afterSecond, afterFirst,
'second rebuild on the just-rebuilt file must be byte-identical (idempotency)');
});
test('`state rebuild` on a missing STATE.md emits a clean error, no stack trace', (t) => {
const cwd = createTempProject('state-rebuild-missing');
t.after(() => cleanup(cwd));
// No STATE.md written.
const result = runGsdTools('state rebuild', cwd);
// The command emits an error result but does not crash the process.
const combined = `${result.output}\n${result.error || ''}`;
assert.ok(combined.includes('STATE.md not found'),
'missing STATE.md should produce a clean "STATE.md not found" message');
assert.ok(!combined.includes('at Object.'),
'no raw stack trace should leak into the output (CONTRIBUTING QA Matrix)');
});
});

View File

@@ -0,0 +1,445 @@
'use strict';
// Phase 1 tests for the `rebuild` transition (ADR-1817).
//
// Covers the four drift classes from epic #1817:
// #1 ## Current Position prose contradicts frontmatter
// #2 ## Performance Metrics → **By Phase:** table has orphaned rows
// #3 Template-placeholder field values ([X], [date], etc.) left in place
// #4 Duplicate ## Session Continuity archived-session H3 blocks
//
// Plus the cross-cutting contracts:
// #6 Idempotency: rebuild twice on a clean file = byte-identical
// #7 Regression guard: sync/prune unchanged when rebuild is not invoked
//
// Discipline (CONTRIBUTING.md): tests assert on typed structured values via
// the public `transitionCore` API, never on rendered text via raw grep.
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const {
transitionCore,
} = require('../gsd-core/bin/lib/state-transition.cjs');
const { stateExtractField } = require('../gsd-core/bin/lib/state-document.cjs');
const fixedClock = Object.freeze({
today: () => '2026-06-29',
nowIso: () => '2026-06-29T12:00:00.000Z',
});
const noProgress = () => null;
const noPhases = () => null;
const baseDeps = Object.freeze({
progressProvider: noProgress,
clock: fixedClock,
phaseInventoryProvider: noPhases,
});
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
/**
* A clean, fully-reconciled STATE.md body — the canonical post-rebuild shape.
* Used as the starting point for drift fixtures and as the idempotency
* baseline (running rebuild on this must produce no mutation).
*/
function cleanState() {
return [
'---',
'gsd_state_version: \'1.0\'',
'status: executing',
'milestone: 1.0.0',
'milestone_name: Test Milestone',
'current_phase: 3',
'current_phase_name: Test Phase',
'current_plan: 2',
'progress:',
' total_phases: 5',
' completed_phases: 2',
' total_plans: 10',
' completed_plans: 4',
' percent: 40',
'---',
'',
'# Project State',
'',
'## Project Reference',
'',
'See: .planning/PROJECT.md (updated 2026-06-01)',
'',
'**Core value:** A test project',
'**Current focus:** Test Phase',
'',
'## Current Position',
'',
'**Current Phase:** 3',
'**Current Phase Name:** Test Phase',
'**Current Plan:** 2',
'**Total Plans in Phase:** 5',
'**Status:** executing',
'**Last Activity:** 2026-06-29',
'**Last Activity Description:** mid-flight context from plan 3-02',
'',
'Phase: 3 of 5 (Test Phase)',
'Plan: 2 of 5',
'Status: Executing Phase 3',
'Last activity: 2026-06-29 — mid-flight context',
'',
'**Progress:** [████░░░░░░] 40%',
'',
'## Performance Metrics',
'',
'**By Phase:**',
'',
'| Phase | Plans | Total | Avg/Plan |',
'|-------|-------|-------|----------|',
'| 1 | 2 | - | - |',
'| 2 | 3 | - | - |',
'| 3 | 5 | - | - |',
'',
'## Accumulated Context',
'',
'### Decisions',
'',
'- Phase 1: chose option A',
'- Phase 2: chose option B',
'',
'### Pending Todos',
'',
'None yet.',
'',
'## Deferred Items',
'',
'| Category | Item | Status | Deferred At |',
'|----------|------|--------|-------------|',
'| *(none)* | | | |',
'',
'## Session Continuity',
'',
'Last session: 2026-06-29 12:00',
'Stopped at: mid-flight context',
'Resume file: None',
'',
].join('\n');
}
/** Drift fixture #1: body `**Current Phase:**` and `**Current Phase Name:**`
* contradict frontmatter (e.g. after a milestone switch). */
function driftedCurrentPosition() {
// Take the clean state and inject stale body prose.
const c = cleanState();
return c
.replace('**Current Phase:** 3', '**Current Phase:** 2')
.replace('**Current Phase Name:** Test Phase', '**Current Phase Name:** Old Phase Name');
}
/** Drift fixture #3: template placeholder values left in body fields. */
function driftedPlaceholders() {
const c = cleanState();
// Inject placeholders into a couple of fields. Don't touch the fields
// syncCore actively maintains (Last Activity) — those would be reconciled
// by sync, not rebuild.
return c
.replace('**Current focus:** Test Phase', '**Current focus:** [Current phase name]')
.replace('See: .planning/PROJECT.md (updated 2026-06-01)', 'See: .planning/PROJECT.md (updated [date])');
}
/** Count LIVE `### Session —` headings, excluding any occurrences inside the
* `## Rebuild Log` audit section (the log's `before:` field captures dropped
* content verbatim, which would otherwise inflate the count). */
function countLiveSessionHeadings(content) {
// Strip everything from `## Rebuild Log` to EOF, then count.
const stripped = content.replace(/^## Rebuild Log[\s\S]*$/m, '');
return (stripped.match(/^###\s+Session\s+—/gm) || []).length;
}
/** Drift fixture #4: six duplicate `### Session —` archived blocks under
* `## Session Continuity` (more than the default 3-most-recent retention). */
function driftedSessionArchiveDuplicates() {
// Replace the canonical short Session Continuity block with one that has
// six archived sub-blocks.
const c = cleanState();
const archiveBlock = [
'## Session Continuity',
'',
'Last session: 2026-06-29 12:00',
'Stopped at: mid-flight context',
'Resume file: None',
'',
'### Session — 2026-06-20',
'',
'oldest session — should be dropped',
'',
'### Session — 2026-06-22',
'',
'second-oldest — should be dropped',
'',
'### Session — 2026-06-25',
'',
'third — kept',
'',
'### Session — 2026-06-27',
'',
'fourth — kept',
'',
'### Session — 2026-06-28',
'',
'fifth — kept',
'',
'### Session — 2026-06-29',
'',
'sixth — kept',
'',
].join('\n');
return c.replace(/## Session Continuity[\s\S]*$/, archiveBlock);
}
// ---------------------------------------------------------------------------
// Tests — dispatch + idempotency contract
// ---------------------------------------------------------------------------
describe('ADR-1817 `rebuild` intent: dispatch + idempotency (§1, §4)', () => {
test('transitionCore dispatches `rebuild` without throwing', () => {
const result = transitionCore(cleanState(), { kind: 'rebuild' }, baseDeps);
assert.ok(result, 'rebuild must return a result');
assert.ok(Array.isArray(result.updated), 'updated must be an array');
assert.ok(result.data && typeof result.data === 'object', 'data must be an object');
});
test('rebuild on a clean file is a no-op: content byte-identical, no log, no `updated`', () => {
const clean = cleanState();
const result = transitionCore(clean, { kind: 'rebuild' }, baseDeps);
assert.strictEqual(result.content, clean, 'content must be byte-identical on a clean file');
assert.deepStrictEqual(result.updated, [], 'no fields should be marked updated on a clean file');
assert.strictEqual(result.data && result.data.mutated, false, 'mutated flag must be false');
assert.strictEqual(result.content.includes('## Rebuild Log'), false,
'a no-op rebuild must NOT append a ## Rebuild Log section (idempotency)');
});
test('running rebuild twice on a drifted file converges: second run is a no-op', () => {
const drifted = driftedCurrentPosition();
const first = transitionCore(drifted, { kind: 'rebuild' }, baseDeps);
assert.notStrictEqual(first.content, drifted, 'first run must mutate drifted content');
const second = transitionCore(first.content, { kind: 'rebuild' }, baseDeps);
assert.strictEqual(second.content, first.content,
'second run on the just-rebuilt content must be byte-identical (idempotency)');
assert.deepStrictEqual(second.updated, [], 'second run must mark nothing updated');
assert.strictEqual(second.data && second.data.mutated, false,
'second run must report mutated=false');
});
});
// ---------------------------------------------------------------------------
// Tests — drift class #1: Current Position prose reconciliation
// ---------------------------------------------------------------------------
describe('ADR-1817 §2: rebuild reconciles ## Current Position prose with frontmatter (#1817 criterion #1)', () => {
test('body `**Current Phase:**` is re-derived from frontmatter.current_phase when drifted', () => {
const drifted = driftedCurrentPosition();
assert.strictEqual(stateExtractField(drifted, 'Current Phase'), '2',
'fixture sanity: drifted body must have stale phase 2');
const result = transitionCore(drifted, { kind: 'rebuild' }, baseDeps);
assert.strictEqual(
stateExtractField(result.content, 'Current Phase'),
'3',
'body Current Phase must be reconciled to frontmatter value 3',
);
});
test('body `**Current Phase Name:**` is re-derived from frontmatter.current_phase_name when drifted', () => {
const drifted = driftedCurrentPosition();
assert.strictEqual(stateExtractField(drifted, 'Current Phase Name'), 'Old Phase Name',
'fixture sanity: drifted body must have stale name');
const result = transitionCore(drifted, { kind: 'rebuild' }, baseDeps);
assert.strictEqual(
stateExtractField(result.content, 'Current Phase Name'),
'Test Phase',
'body Current Phase Name must be reconciled to frontmatter value',
);
});
test('each reconciliation produces an audit-log entry in ## Rebuild Log', () => {
const result = transitionCore(driftedCurrentPosition(), { kind: 'rebuild' }, baseDeps);
assert.ok(result.content.includes('## Rebuild Log'),
'rebuild that mutated must create ## Rebuild Log section');
assert.ok(result.content.includes('kind: current-position-reconciled'),
'log must contain a current-position-reconciled entry');
assert.ok(result.content.includes('reason:'),
'every log entry must carry a reason field (ADR-1411 provenance)');
});
});
// ---------------------------------------------------------------------------
// Tests — drift class #3: template-placeholder removal
// ---------------------------------------------------------------------------
describe('ADR-1817 §2: rebuild strips template-placeholder field values (#1817 criterion #3)', () => {
test('`**Field:** [placeholder]` lines are replaced with `**Field:** (pending)`', () => {
const drifted = driftedPlaceholders();
const result = transitionCore(drifted, { kind: 'rebuild' }, baseDeps);
assert.ok(result.content.includes('**Current focus:** (pending)'),
'placeholder Current focus must be replaced with (pending)');
assert.ok(result.content.includes('**Last Activity:** 2026-06-29'),
'fixture sanity: syncCore-maintained fields are untouched by rebuild');
});
test('a placeholder-removed audit-log entry is recorded', () => {
const result = transitionCore(driftedPlaceholders(), { kind: 'rebuild' }, baseDeps);
assert.ok(result.content.includes('kind: placeholder-removed'),
'log must contain a placeholder-removed entry');
});
test('a clean body with no placeholders produces no placeholder-removed log entry', () => {
const result = transitionCore(cleanState(), { kind: 'rebuild' }, baseDeps);
assert.ok(!result.content.includes('kind: placeholder-removed'),
'clean file must not log placeholder removal');
});
});
// ---------------------------------------------------------------------------
// Tests — drift class #4: Session Continuity Archive de-duplication
// ---------------------------------------------------------------------------
describe('ADR-1817 §2: rebuild de-duplicates ## Session Continuity archive blocks (#1817 criterion #4)', () => {
test('when > 3 archived sessions, the oldest are dropped down to the 3 most-recent', () => {
const drifted = driftedSessionArchiveDuplicates();
// Fixture sanity: six archived H3 blocks
const before = countLiveSessionHeadings(drifted);
assert.strictEqual(before, 6, 'fixture must have 6 archived sessions');
const result = transitionCore(drifted, { kind: 'rebuild' }, baseDeps);
const after = countLiveSessionHeadings(result.content);
assert.strictEqual(after, 3, 'rebuild must keep exactly 3 most-recent archived sessions');
});
test('each dropped session produces a session-archive-deduplicated log entry', () => {
const result = transitionCore(driftedSessionArchiveDuplicates(), { kind: 'rebuild' }, baseDeps);
const dropEntries = (result.content.match(/kind: session-archive-deduplicated/g) || []).length;
assert.strictEqual(dropEntries, 3, 'three dropped sessions → three log entries');
});
test('the kept sessions are the most-recent three (by document order — template convention)', () => {
const result = transitionCore(driftedSessionArchiveDuplicates(), { kind: 'rebuild' }, baseDeps);
// Strip the audit log so we only inspect LIVE content (the log's `before:`
// field legitimately preserves dropped text per ADR-1817 §3).
const live = result.content.replace(/^## Rebuild Log[\s\S]*$/m, '');
// DEFAULT_MAX_SESSION_ARCHIVES = 3 → drop the 3 oldest, keep 06-27/28/29.
assert.ok(!live.includes('### Session — 2026-06-20'), 'dropped: 06-20 (oldest)');
assert.ok(!live.includes('### Session — 2026-06-22'), 'dropped: 06-22 (2nd oldest)');
assert.ok(!live.includes('### Session — 2026-06-25'), 'dropped: 06-25 (3rd oldest)');
assert.ok(live.includes('### Session — 2026-06-27'), 'kept: 06-27');
assert.ok(live.includes('### Session — 2026-06-28'), 'kept: 06-28');
assert.ok(live.includes('### Session — 2026-06-29'), 'kept: 06-29 (newest)');
assert.ok(!live.includes('oldest session — should be dropped'),
'oldest session content must be gone from the live section');
assert.ok(!live.includes('second-oldest — should be dropped'),
'second-oldest session content must be gone from the live section');
});
test('when <= 3 archived sessions, rebuild is a no-op on the archive', () => {
const clean = cleanState(); // has zero archived H3 sessions
const result = transitionCore(clean, { kind: 'rebuild' }, baseDeps);
assert.ok(!result.content.includes('kind: session-archive-deduplicated'),
'no dedup log entry when archive is within retention');
});
});
// ---------------------------------------------------------------------------
// Tests — drift class #2: By Phase table reconciliation (via dep)
// ---------------------------------------------------------------------------
describe('ADR-1817 §2: rebuild reconciles **By Phase:** table via phaseInventoryProvider (#1817 criterion #2)', () => {
test('orphaned rows for phases missing from the inventory are dropped', () => {
// Inventory: only phases 1, 2, 3 exist on disk. Drifted body has rows for
// 1, 2, 3, AND an orphan row for phase 99 (prior milestone).
const drifted = cleanState().replace(
/\| 3 \| 5 \| - \| - \|\n/,
'| 3 | 5 | - | - |\n| 99 | 1 | - | - |\n',
);
const deps = {
...baseDeps,
phaseInventoryProvider: () => [
{ number: '1', name: 'Phase 1', planCount: 2, summaryCount: 2 },
{ number: '2', name: 'Phase 2', planCount: 3, summaryCount: 3 },
{ number: '3', name: 'Test Phase', planCount: 5, summaryCount: 4 },
],
};
// First call with no phaseInventoryProvider → no-op (covered by its own test below).
// Re-run with the provider-wired deps:
const result2 = transitionCore(drifted, { kind: 'rebuild' }, deps);
// Strip the audit log so we only inspect LIVE table rows (the log's
// `before:` field legitimately preserves the pre-rebuild table per
// ADR-1817 §3).
const live = result2.content.replace(/^## Rebuild Log[\s\S]*$/m, '');
assert.ok(!live.includes('| 99 |'),
'orphan row for phase 99 (not on disk) must be dropped when phaseInventoryProvider is wired');
assert.ok(live.includes('| 1 |'), 'kept: phase 1');
assert.ok(live.includes('| 2 |'), 'kept: phase 2');
assert.ok(live.includes('| 3 |'), 'kept: phase 3');
});
test('rebuild logs a by-phase-table-reconciled entry when the table changes', () => {
const drifted = cleanState().replace(
/\| 3 \| 5 \| - \| - \|\n/,
'| 3 | 5 | - | - |\n| 99 | 1 | - | - |\n',
);
const deps = {
...baseDeps,
phaseInventoryProvider: () => [
{ number: '1', name: 'Phase 1', planCount: 2, summaryCount: 2 },
{ number: '2', name: 'Phase 2', planCount: 3, summaryCount: 3 },
{ number: '3', name: 'Test Phase', planCount: 5, summaryCount: 4 },
],
};
const result = transitionCore(drifted, { kind: 'rebuild' }, deps);
assert.ok(result.content.includes('kind: by-phase-table-reconciled'),
'rebuild that mutated the table must log a by-phase-table-reconciled entry');
});
test('Leaky-Abstractions guard: when phaseInventoryProvider is absent, table is preserved verbatim', () => {
const drifted = cleanState().replace(
/\| 3 \| 5 \| - \| - \|\n/,
'| 3 | 5 | - | - |\n| 99 | 1 | - | - |\n',
);
// baseDeps.phaseInventoryProvider = noPhases (returns null) → step is no-op.
const result = transitionCore(drifted, { kind: 'rebuild' }, baseDeps);
assert.ok(result.content.includes('| 99 |'),
'orphan row must be preserved when no canonical source is wired');
assert.ok(!result.content.includes('kind: by-phase-table-reconciled'),
'no log entry when step is a no-op');
});
});
// ---------------------------------------------------------------------------
// Tests — §5 + §6: regression guard (sync/prune unchanged by rebuild presence)
// ---------------------------------------------------------------------------
describe('ADR-1817 §5/§6: rebuild does not affect sync or prune (regression guard, criterion #7)', () => {
test('sync still patches its three frontmatter fields when rebuild is also available', () => {
const state = cleanState();
const result = transitionCore(
state,
{ kind: 'sync', totalPlansInPhase: 7, percent: 50 },
baseDeps,
);
// sync should have updated Total Plans in Phase and Progress and Last Activity.
assert.strictEqual(stateExtractField(result.content, 'Total Plans in Phase'), '7',
'sync must still patch Total Plans in Phase');
assert.ok(stateExtractField(result.content, 'Progress').includes('50%'),
'sync must still patch Progress percent');
});
test('prune still archives by cutoff when rebuild is also available', () => {
// Smoke: prune on a body with at least one Decisions row at phase 1 should
// archive that row when cutoff=0. The exact byte shape is covered by the
// dedicated state-transition tests; this test only asserts prune is NOT
// broken by adding the rebuild case to the switch.
const state = cleanState();
const result = transitionCore(state, { kind: 'prune', cutoff: 0 }, baseDeps);
assert.ok(result, 'prune must still return a result');
assert.ok(result.updated !== undefined, 'prune must still return an updated array');
});
});

View File

@@ -52,7 +52,7 @@
"note.md": 6563,
"pause-work.md": 14397,
"plan-milestone-gaps.md": 11765,
"plan-phase.md": 93973,
"plan-phase.md": 94459,
"plan-review-convergence.md": 23468,
"plant-seed.md": 11741,
"pr-branch.md": 15919,
@@ -78,7 +78,7 @@
"spike.md": 24517,
"stats.md": 6718,
"sync-skills.md": 6125,
"thread.md": 12400,
"thread.md": 12464,
"transition.md": 22016,
"ui-phase.md": 15477,
"ui-review.md": 11172,
@@ -86,6 +86,6 @@
"undo.md": 10431,
"update.md": 21053,
"validate-phase.md": 10745,
"verify-phase.md": 38228,
"verify-phase.md": 40728,
"verify-work.md": 35212
}