diff --git a/.changeset/2112-commit-files-pathspec.md b/.changeset/2112-commit-files-pathspec.md new file mode 100644 index 000000000..1e8a1118d --- /dev/null +++ b/.changeset/2112-commit-files-pathspec.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2148 +--- +**`commit --files` now commits only the declared paths** — `gsd-tools commit --files A B` previously ran a bare `git commit` that absorbed the entire staged index, silently sweeping in unrelated files the caller never named. The commit now appends a pathspec (`-- `) so only the staged subset of `--files` lands in the commit; the no-`--files` default path is unchanged. Missing tracked files are still skipped (not committed as deletions, #2014), and when all declared files are missing the function short-circuits to `nothing_to_commit` instead of absorbing the index. (#2112) diff --git a/.changeset/2118-milestone-complete-dry-run.md b/.changeset/2118-milestone-complete-dry-run.md new file mode 100644 index 000000000..d0f3d2659 --- /dev/null +++ b/.changeset/2118-milestone-complete-dry-run.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2155 +--- +**`milestone complete --dry-run` now prints a preview plan instead of silently mutating** — `gsd-tools milestone complete --dry-run` was neither parsed nor rejected, so a caller expecting a preview triggered the full destructive mutation (archive phases, move audit artifacts, rewrite STATE.md) with no way to back out. The `--dry-run` flag is now honored: it returns a JSON plan listing `would_archive` (roadmap, requirements, audit, phase dirs) and `would_update` (MILESTONES.md, STATE.md) targets with zero filesystem mutations. (#2118) diff --git a/.changeset/2119-secure-phase-single-writer.md b/.changeset/2119-secure-phase-single-writer.md new file mode 100644 index 000000000..172b29463 --- /dev/null +++ b/.changeset/2119-secure-phase-single-writer.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2154 +--- +**`/gsd-secure-phase` now has a single SECURITY.md writer** — the `gsd-security-auditor` subagent previously held `Write`/`Edit` tools and was instructed to "write SECURITY.md" with no padded `-` prefix and no template frontmatter, while the orchestrator's Step 6 also wrote the phase-scoped `-SECURITY.md` from `templates/SECURITY.md`. The auditor is now return-only (drops `Write`/`Edit`, returns a structured verdict with `threats_open`); the orchestrator is the sole file writer. The workflow's Step 5 spawn constraints explicitly forbid the auditor from writing SECURITY.md. (#2119) diff --git a/.changeset/clever-eagles-romp.md b/.changeset/clever-eagles-romp.md new file mode 100644 index 000000000..423a3ae27 --- /dev/null +++ b/.changeset/clever-eagles-romp.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2231 +--- +**`phase complete --phase N` now works alongside the positional form** — the phase verb family treated the first positional as the phase number, so `--phase 12` was passed as the literal phase name and failed with 'Phase --phase not found'. The phase family now accepts the --phase flag consistently with the state family, and unrecognized flags yield a usage error. (#2201) diff --git a/.changeset/proud-geese-caper.md b/.changeset/proud-geese-caper.md new file mode 100644 index 000000000..d925cf412 --- /dev/null +++ b/.changeset/proud-geese-caper.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2227 +--- +**`/gsd-debug` no longer stalls on a phantom background handoff** — the orchestrator treated the foreground session-manager spawn as a background task and queried its agent ID via TaskOutput (which needs a task ID), then waited on a handoff that was never queryable. The workflow now states the spawn is foreground/blocking, forbids passing an agent ID to TaskOutput, and gives a lost-handoff recovery path. (#2196) diff --git a/.changeset/proud-ravens-jump.md b/.changeset/proud-ravens-jump.md new file mode 100644 index 000000000..94beacdfd --- /dev/null +++ b/.changeset/proud-ravens-jump.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2229 +--- +**`phase complete` no longer checks the wrong ROADMAP checkbox or writes the plan count into a shipped milestone** — the roadmap mutators ran unanchored and un-milestone-scoped, so they could flip a bullet inside a backticked prose literal or a Backlog entry instead of the closing phase's, and write the plan count into a same-numbered phase in a shipped milestone. The checkbox flip is now line-anchored and both writers are scoped to the current milestone. (#2200) diff --git a/.changeset/sturdy-yaks-caper.md b/.changeset/sturdy-yaks-caper.md new file mode 100644 index 000000000..f453fcf0d --- /dev/null +++ b/.changeset/sturdy-yaks-caper.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2228 +--- +**Bullet/em-dash ROADMAP phases no longer resolve to `Phase null`** — the roadmap phase lookup matched only ATX headings with a colon, so a bullet entry like `- [ ] **Phase N — Name**` (which the roadmapper emits) failed to resolve and `Phase null` landed in STATE.md; a bullet-only ROADMAP also broke the milestone phase count. Phase lookup and the milestone filter now accept bullet/checkbox entries with an em-dash/en-dash/hyphen/colon separator. (#2199) diff --git a/agents/gsd-security-auditor.md b/agents/gsd-security-auditor.md index 234573fa0..1ceae604d 100644 --- a/agents/gsd-security-auditor.md +++ b/agents/gsd-security-auditor.md @@ -1,10 +1,8 @@ --- name: gsd-security-auditor -description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Produces SECURITY.md. Spawned by /gsd:secure-phase. +description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Returns structured security verdict (SECURED / OPEN_THREATS / ESCALATE). Spawned by /gsd:secure-phase. tools: - Read - - Write - - Edit - Bash - Glob - Grep @@ -15,11 +13,11 @@ color: red An implemented phase has been submitted for security audit. Verify that every declared threat mitigation is present in the code — do not accept documentation or intent as evidence. -Does NOT scan blindly for new vulnerabilities. Verifies each threat in `` by its declared disposition (mitigate / accept / transfer). Reports gaps. Writes SECURITY.md. +Does NOT scan blindly for new vulnerabilities. Verifies each threat in `` by its declared disposition (mitigate / accept / transfer). Reports gaps. Returns a structured verdict — the orchestrator owns the SECURITY.md file write (#2119: single-writer contract). **Mandatory Initial Read:** If prompt contains ``, load ALL listed files before any action. -**Implementation files are READ-ONLY.** Only create/modify: SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation. +**Implementation files are READ-ONLY.** The auditor does NOT write any files — it returns a structured verdict (SECURED / OPEN_THREATS / ESCALATE). The orchestrator persists SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation. @@ -79,20 +77,20 @@ Classify each threat before verification. Record classification for every threat - L3: deep trace — follow the data flow end-to-end, check edge cases and ordering, confirm no bypass path exists. - + For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`. Apply depth per `asvs_level` (see analyze_threats step). -For `accept` threats: check SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`. +For `accept` threats: check existing SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`. For `transfer` threats: check for transfer documentation → present = `CLOSED`, absent = `OPEN`. -For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in SECURITY.md (not a blocker). +For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in the structured return (not a blocker). **Severity-aware `threats_open` computation (severity order: critical > high > medium > low):** `threats_open` (the SECURITY.md frontmatter gate field) = the count of threats whose status is OPEN AND whose severity rank ≥ the `block_on` rank. `block_on: none` ⇒ 0 (nothing ever blocks). `block_on: low` ⇒ all open threats block. `block_on: high` (default) ⇒ only high and critical open threats block. -Open threats BELOW the block threshold are recorded in SECURITY.md as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`. +Open threats BELOW the block threshold are recorded in the return as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`. **Fail-closed for missing severity:** if an OPEN threat has no severity or an unparseable severity (e.g. a legacy register predating the Severity column), treat it as `critical` for this computation — it COUNTS toward `threats_open` (blocking). Never silently drop an unranked open threat. -Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return structured result. +Return the structured result (SECURED / OPEN_THREATS / ESCALATE) with `threats_open` set to the severity-filtered count. The orchestrator writes SECURITY.md from this data — the auditor does NOT write any files (#2119). @@ -116,7 +114,7 @@ Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return str ### Unregistered Flags {none / list from SUMMARY.md ## Threat Flags with no threat mapping} -SECURITY.md: {path} +**threats_open:** {count} ``` ## OPEN_THREATS @@ -145,9 +143,9 @@ SECURITY.md: {path} *Only blocking-open threats count toward `threats_open` in SECURITY.md frontmatter.* -Next: Implement mitigations or document as accepted in SECURITY.md accepted risks log, then re-run /gsd:secure-phase. +Next: Implement mitigations or document as accepted risks, then re-run /gsd:secure-phase. -SECURITY.md: {path} +**threats_open:** {count} ``` ## ESCALATE @@ -172,6 +170,6 @@ SECURITY.md: {path} - [ ] Each threat verified by disposition type (mitigate / accept / transfer) - [ ] Threat flags from SUMMARY.md `## Threat Flags` incorporated - [ ] Implementation files never modified -- [ ] SECURITY.md written to correct path -- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE +- [ ] No files written — structured verdict returned only (orchestrator writes SECURITY.md) +- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE with `threats_open` count diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 62f12c955..7040a6a42 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -479,10 +479,10 @@ Communication style, decision patterns, debugging approach, UX preferences, vend |----------|-------| | **Spawned by** | `/gsd-secure-phase` | | **Parallelism** | Single instance | -| **Tools** | Read, Write, Edit, Bash, Glob, Grep | +| **Tools** | Read, Bash, Glob, Grep | | **Model (balanced)** | Sonnet | | **Color** | Red | -| **Produces** | `{phase}-SECURITY.md` | +| **Produces** | Structured verdict (SECURED / OPEN_THREATS / ESCALATE) — orchestrator writes `{phase}-SECURITY.md` (#2119) | **Key behaviors:** - Verifies each threat by its declared disposition (mitigate / accept / transfer) diff --git a/gsd-core/bin/gsd-tools.cjs b/gsd-core/bin/gsd-tools.cjs index 376c0a0b0..fdf8aa129 100755 --- a/gsd-core/bin/gsd-tools.cjs +++ b/gsd-core/bin/gsd-tools.cjs @@ -1500,7 +1500,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand // new-milestone never inherits un-archived dirs. --no-archive-phases opts out. const archivePhases = !args.includes('--no-archive-phases'); const force = args.includes('--force'); - milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force }, raw); + // #2118: --dry-run prints a preview plan without mutating. + const dryRun = args.includes('--dry-run'); + milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun }, raw); } else { error('Unknown milestone subcommand. Available: complete', ERROR_REASON.SDK_UNKNOWN_COMMAND); } diff --git a/gsd-core/workflows/add-tests.md b/gsd-core/workflows/add-tests.md index bffc16bff..cc710178a 100644 --- a/gsd-core/workflows/add-tests.md +++ b/gsd-core/workflows/add-tests.md @@ -308,7 +308,7 @@ If there are passing tests to commit: ```bash git add {test files} -git commit -m "test(phase-${phase_number}): add unit and E2E tests from add-tests command" +git commit -m "test(phase-${phase_number}): add unit and E2E tests from add-tests command" -- {test files} ``` Present next steps: diff --git a/gsd-core/workflows/debug.md b/gsd-core/workflows/debug.md index f3ae29109..7bd7a78d4 100644 --- a/gsd-core/workflows/debug.md +++ b/gsd-core/workflows/debug.md @@ -190,6 +190,8 @@ Create `.planning/debug/{slug}.md` with initial state using the Write tool (neve After initial context setup, spawn the session manager to handle the full checkpoint/continuation loop. The session manager handles specialist_hint dispatch internally: when gsd-debugger returns ROOT CAUSE FOUND it extracts the specialist_hint field and invokes the matching skill (e.g. typescript-expert, swift-concurrency) before offering fix options. +> **Foreground, blocking spawn — #2196.** The `Agent(subagent_type="gsd-debug-session-manager", …)` call below is FOREGROUND and BLOCKING — it returns the compact session summary directly. Wait for it; do not background it, and do not poll for it. Never pass an agent or session identifier to `TaskOutput` — an agent ID is NOT a task ID, so `TaskOutput ` always returns `No task found with ID`. If the spawn returns no usable result (the handoff is lost), do NOT claim the session is still running: preserve the checkpoint at `.planning/debug/{slug}.md`, report the failed handoff plainly, and resume by re-spawning the session manager or via `/gsd:debug continue {slug}`. + Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze): ``` [debug] Delegating loop to session manager... diff --git a/gsd-core/workflows/secure-phase.md b/gsd-core/workflows/secure-phase.md index f3831818d..266e35e1c 100644 --- a/gsd-core/workflows/secure-phase.md +++ b/gsd-core/workflows/secure-phase.md @@ -108,7 +108,7 @@ Agent( "{PLAN, SUMMARY, impl files, SECURITY.md}" + "{threat register}" + "asvs_level: {SECURITY_ASVS}, block_on: {SECURITY_BLOCK_ON}" + - "Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps." + + "Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps. Return a structured verdict only — do NOT write SECURITY.md (the orchestrator owns the file write)." + "${AGENT_SKILLS_AUDITOR}", subagent_type="gsd-security-auditor", model="{AUDITOR_MODEL}", diff --git a/gsd-core/workflows/spec-phase.md b/gsd-core/workflows/spec-phase.md index 698b4bb1e..f25731292 100644 --- a/gsd-core/workflows/spec-phase.md +++ b/gsd-core/workflows/spec-phase.md @@ -456,7 +456,7 @@ Write to: `{phase_dir}/{padded_phase}-SPEC.md` ```bash git add "${phase_dir}/${padded_phase}-SPEC.md" -git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)" +git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)" -- "${phase_dir}/${padded_phase}-SPEC.md" ``` If `commit_docs` is false: Skip commit. Note that SPEC.md was written but not committed. diff --git a/scripts/gen-golden-install-parity-zcode.cjs b/scripts/gen-golden-install-parity-zcode.cjs index c20b4e1eb..7c500699f 100644 --- a/scripts/gen-golden-install-parity-zcode.cjs +++ b/scripts/gen-golden-install-parity-zcode.cjs @@ -69,12 +69,17 @@ function cleanup(root) { // With no args, regenerates ALL runtimes. With args, only the named runtimes. const targets = process.argv.slice(2).length > 0 ? process.argv.slice(2) : Object.keys(RUNTIME_META); fs.mkdirSync(FIXTURE_DIR, { recursive: true }); + +// Track the claude root so we can reuse it for the local layout fixture below. +let claudeRoot = null; + for (const runtime of targets) { if (!Object.prototype.hasOwnProperty.call(RUNTIME_META, runtime)) { process.stderr.write(`[gen] unknown runtime '${runtime}' (not in RUNTIME_META) — skipping\n`); continue; } const { configDir, root } = runMinimalInstall({ runtime, scope: 'global' }); + if (runtime === 'claude') claudeRoot = root; let actual; try { actual = buildParityManifest(configDir, root); @@ -85,3 +90,17 @@ for (const runtime of targets) { fs.writeFileSync(fixturePath, JSON.stringify(actual, null, 2) + '\n', 'utf8'); process.stdout.write(`[gen] ${runtime}: wrote ${Object.keys(actual).length} file hashes -> ${fixturePath}\n`); } + +// Also regenerate the claude LOCAL legacy-layout fixture (claude-local.json). +// This layout is distinct from the global install (commands/gsd-*.md + +// agents/gsd-*.md) and has its own parity assertion in the test harness. +const { configDir: localConfigDir, root: localRoot } = runMinimalInstall({ runtime: 'claude', scope: 'local' }); +let localActual; +try { + localActual = buildParityManifest(localConfigDir, localRoot); +} finally { + cleanup(localRoot); +} +const localFixturePath = path.join(FIXTURE_DIR, 'claude-local.json'); +fs.writeFileSync(localFixturePath, JSON.stringify(localActual, null, 2) + '\n', 'utf8'); +process.stdout.write(`[gen] claude-local: wrote ${Object.keys(localActual).length} file hashes -> ${localFixturePath}\n`); diff --git a/src/commands.cts b/src/commands.cts index 7f2704197..1dd767081 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -694,6 +694,7 @@ function cmdCommit(cwd: string, message: string | undefined, files: string[] | u // Stage files const explicitFiles = files && files.length > 0; const filesToStage = explicitFiles ? files : ['.planning/']; + const stagedPaths: string[] = []; for (const file of filesToStage) { const fullPath = path.join(cwd, file); if (!fs.existsSync(fullPath)) { @@ -708,12 +709,32 @@ function cmdCommit(cwd: string, message: string | undefined, files: string[] | u execGit(['rm', '--cached', '--ignore-unmatch', file], { cwd }); } else { execGit(['add', file], { cwd }); + stagedPaths.push(file); } } - // Commit (--no-verify skips pre-commit hooks, used by parallel executor agents) - const commitArgs = amend ? ['commit', '--amend', '--no-edit'] : ['commit', '-m', sanitizedMessage as string]; + // Commit — when the caller declared a scope (--files), append a pathspec so + // only the declared files land in the commit, not the entire index (#2112). + // The pathspec uses stagedPaths (not filesToStage) so skipped missing files + // are excluded — otherwise git would record them as deletions (#2014). + // During a merge, git refuses partial commits — fall back to a bare commit. + // --amend is left without a pathspec: amending with -- is a different + // operation that rewrites the tip with only those paths. + if (explicitFiles && stagedPaths.length === 0 && !amend) { + const result = { committed: false, hash: null, reason: 'nothing_to_commit' }; + output(result, raw, 'nothing'); + return; + } + const isMergeInProgress = execGit(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd }).exitCode === 0; + const canScope = explicitFiles && stagedPaths.length > 0 && !amend + && !isMergeInProgress; + const commitArgs = amend + ? ['commit', '--amend', '--no-edit'] + : ['commit', '-m', sanitizedMessage as string]; if (noVerify) commitArgs.push('--no-verify'); + if (canScope) { + commitArgs.push('--', ...stagedPaths); + } const commitResult = execGit(commitArgs, { cwd }); if (commitResult.exitCode !== 0) { if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) { @@ -817,13 +838,22 @@ function cmdCommitToSubrepo(cwd: string, message: string | undefined, files: str const repoCwd = path.join(cwd, repo); // Stage files (strip sub-repo prefix for paths relative to that repo) + const stagedRelPaths: string[] = []; for (const file of repoFiles) { const relativePath = file.slice(repo.length + 1); - execGit(['add', relativePath], { cwd: repoCwd }); + const addResult = execGit(['add', relativePath], { cwd: repoCwd }); + if (addResult.exitCode === 0) { + stagedRelPaths.push(relativePath); + } } - // Commit - const commitResult = execGit(['commit', '-m', message as string], { cwd: repoCwd }); + // Commit — pathspec limits the commit to the staged files only (#2112) + const isMergeInProgressSub = execGit(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd: repoCwd }).exitCode === 0; + const canScopeSub = stagedRelPaths.length > 0 && !isMergeInProgressSub; + const commitArgs = canScopeSub + ? ['commit', '-m', message as string, '--', ...stagedRelPaths] + : ['commit', '-m', message as string]; + const commitResult = execGit(commitArgs, { cwd: repoCwd }); if (commitResult.exitCode !== 0) { if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) { repos[repo] = { committed: false, hash: null, files: repoFiles, reason: 'nothing_to_commit' }; @@ -969,8 +999,16 @@ function cmdPrSubrepo( } } - // 5. Commit - const commitResult = execGit(['commit', '-m', commitMessage as string], { cwd: repoCwd }); + // 5. Commit — pathspec limits the commit to the staged files only (#2112). + // changedFiles includes both old and new paths for renames so the full + // rename is captured atomically (pathspec on newPath alone would leave the + // deletion of oldPath stranded in the index). + const isMergeInProgressPr = execGit(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd: repoCwd }).exitCode === 0; + const canScopePr = changedFiles.length > 0 && !isMergeInProgressPr; + const commitArgs = canScopePr + ? ['commit', '-m', commitMessage as string, '--', ...changedFiles] + : ['commit', '-m', commitMessage as string]; + const commitResult = execGit(commitArgs, { cwd: repoCwd }); if (commitResult.exitCode !== 0) { rollback(); error(`Failed to commit in ${repo}: ${commitResult.stderr}`); diff --git a/src/milestone.cts b/src/milestone.cts index b7c318ca5..1dbc8142d 100644 --- a/src/milestone.cts +++ b/src/milestone.cts @@ -38,6 +38,7 @@ interface MilestoneCompleteOptions { name?: string; force?: boolean; archivePhases?: boolean; + dryRun?: boolean; } function cmdRequirementsMarkComplete(cwd: string, reqIdsRaw: string[], raw: boolean): void { @@ -161,8 +162,10 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo const today = realClock.localToday(); const milestoneName = options.name || version; - // Ensure archive directory exists - platformEnsureDir(archiveDir); + // Ensure archive directory exists (skipped in dry-run — no mutations) + if (!options.dryRun) { + platformEnsureDir(archiveDir); + } // Scope stats and accomplishments to only the phases belonging to the // current milestone's ROADMAP. Uses the shared filter from roadmap-parser.cjs @@ -295,6 +298,47 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo /* intentionally empty */ } + // #2118: --dry-run preview — compute what WOULD happen without mutating. + // The stats above are read-only; all mutations start at the archive section below. + if (options.dryRun) { + const phaseDirsToArchive: string[] = []; + if (options.archivePhases !== false) { + try { + const entries = fs.readdirSync(phasesDir, { withFileTypes: true }); + for (const e of entries) { + if (e.isDirectory() && isDirInMilestone(e.name)) { + phaseDirsToArchive.push(e.name); + } + } + } catch { /* phasesDir missing — nothing to archive */ } + } + const dryRunResult = { + dry_run: true, + version, + name: milestoneName, + stats: { phases: phaseCount, plans: totalPlans, tasks: totalTasks }, + accomplishments, + would_archive: { + roadmap: fs.existsSync(roadmapPath) + ? { source: path.relative(cwd, roadmapPath).split(path.sep).join('/'), target: path.relative(cwd, path.join(archiveDir, `${version}-ROADMAP.md`)).split(path.sep).join('/') } + : null, + requirements: fs.existsSync(reqPath) + ? { source: path.relative(cwd, reqPath).split(path.sep).join('/'), target: path.relative(cwd, path.join(archiveDir, `${version}-REQUIREMENTS.md`)).split(path.sep).join('/') } + : null, + audit: fs.existsSync(path.join(planningBase, `${version}-MILESTONE-AUDIT.md`)) + ? { source: path.relative(cwd, path.join(planningBase, `${version}-MILESTONE-AUDIT.md`)).split(path.sep).join('/'), target: path.relative(cwd, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)).split(path.sep).join('/') } + : null, + phases: phaseDirsToArchive, + }, + would_update: { + milestones_md: path.relative(cwd, milestonesPath).split(path.sep).join('/'), + state_md: fs.existsSync(statePath) ? path.relative(cwd, statePath).split(path.sep).join('/') : null, + }, + }; + output(dryRunResult, raw); + return; + } + // Archive ROADMAP.md if (fs.existsSync(roadmapPath)) { const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8'); diff --git a/src/phase-command-router.cts b/src/phase-command-router.cts index 86502ec8c..3d4e0822f 100644 --- a/src/phase-command-router.cts +++ b/src/phase-command-router.cts @@ -163,7 +163,28 @@ function routePhaseCommand({ phase, args, cwd, raw, error }: RoutePhaseCommandOp return { ok: true as const, data: null }; }, complete: (_ctx: Record): { ok: true; data: null } => { - phase.cmdPhaseComplete(cwd, args[2], raw); + // #2201: accept --phase N as well as the positional form (the state + // family already accepts --phase). An unrecognized flag is a usage + // error, not "Phase --phase not found". + let phaseNum: string | null = null; + for (let i = 2; i < args.length; i++) { + if (args[i] === '--phase') { + phaseNum = args[++i]; + if (!phaseNum || phaseNum.startsWith('--')) + return makeInvalidArgs('--phase', '--phase requires a value') as never; + } else if (args[i].startsWith('--phase=')) { + phaseNum = args[i].slice(8); + } else if (args[i] === '--raw') { + continue; + } else if (args[i].startsWith('--')) { + return makeInvalidArgs(args[i], `phase complete does not support ${args[i]}`) as never; + } else { + phaseNum = args[i]; + } + } + if (!phaseNum) + return makeInvalidArgs('--phase', 'phase number required (positional or --phase N)') as never; + phase.cmdPhaseComplete(cwd, phaseNum, raw); return { ok: true as const, data: null }; }, 'uat-passed': (_ctx: Record): { ok: true; data: null } => { @@ -185,7 +206,26 @@ function routePhaseCommand({ phase, args, cwd, raw, error }: RoutePhaseCommandOp }, // #1437 — list plan files for a phase 'list-plans': (_ctx: Record): { ok: true; data: null } => { - phase.cmdPhaseListPlans(cwd, args[2], raw); + // #2201: accept --phase N as well as positional. + let phaseNum: string | null = null; + for (let i = 2; i < args.length; i++) { + if (args[i] === '--phase') { + phaseNum = args[++i]; + if (!phaseNum || phaseNum.startsWith('--')) + return makeInvalidArgs('--phase', '--phase requires a value') as never; + } else if (args[i].startsWith('--phase=')) { + phaseNum = args[i].slice(8); + } else if (args[i] === '--raw') { + continue; + } else if (args[i].startsWith('--')) { + return makeInvalidArgs(args[i], `phase list-plans does not support ${args[i]}`) as never; + } else { + phaseNum = args[i]; + } + } + if (!phaseNum) + return makeInvalidArgs('--phase', 'phase number required (positional or --phase N)') as never; + phase.cmdPhaseListPlans(cwd, phaseNum, raw); return { ok: true as const, data: null }; }, }, diff --git a/src/phase.cts b/src/phase.cts index 8bdc40cae..57f23c655 100644 --- a/src/phase.cts +++ b/src/phase.cts @@ -44,7 +44,7 @@ import phaseLocatorMod = require('./phase-locator.cjs'); const { findPhaseInternal, getArchivedPhaseDirs } = phaseLocatorMod; // eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module import roadmapParserMod = require('./roadmap-parser.cjs'); -const { stripShippedMilestones, extractCurrentMilestone, getMilestonePhaseFilter } = roadmapParserMod; +const { stripShippedMilestones, extractCurrentMilestone, getMilestonePhaseFilter, currentMilestoneRawRanges } = roadmapParserMod; // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module import planningWorkspace = require('./planning-workspace.cjs'); // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module @@ -1475,13 +1475,13 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void { // so completing an already-checked phase (idempotent re-run) checked the // wrong phase's box. Mirrors the tight pattern used by phase-insert // (`]\\s*(?:\\*\\*)?Phase`). + // #2067/#2200: line-anchored (^ + m flag, optional leading indent) so an + // inline / backticked prose literal cannot match. Milestone-scoped below + // (mutateMilestonePhase) so a Backlog entry or a same-numbered shipped- + // milestone phase cannot be flipped either. const checkboxPattern = new RegExp( - `(-\\s*\\[)[ ](\\]\\s*(?:\\*\\*)?\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, - 'i', - ); - roadmapContent = roadmapContent.replace( - checkboxPattern, - `$1x$2 (completed ${today})`, + `^[ \\t]*(-\\s*\\[)[ ](\\]\\s*(?:\\*\\*)?\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, + 'im', ); const tableRowPattern = new RegExp( @@ -1522,21 +1522,47 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void { `(#{2,4}\\s*Phase\\s+${phaseEscaped}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i', ); - roadmapContent = roadmapContent.replace( - planCountPattern, - `$1${summaryCount}/${planCount} plans complete`, - ); const phaseInfoSummaries = phaseInfo['summaries'] as string[]; - for (const summaryFile of phaseInfoSummaries) { - const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''); - if (!planId) continue; - const planEscaped = escapeRegex(planId); - const planCheckboxPattern = new RegExp( - `(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, - 'i', - ); - roadmapContent = (roadmapContent).replace(planCheckboxPattern, '$1x$2'); + + // #2200: apply the phase-checkbox flip, the plan-count write, and the + // per-plan checkbox flips ONLY within the current milestone's region(s) + // (primary section + optional Phase Details section). A bullet/heading in + // a shipped milestone, a Backlog section, or a backticked prose literal is + // outside the window and stays untouched. With no versioned active + // milestone, fall back to whole-content mutation (prior behaviour). + const mutateMilestonePhase = (slice: string): string => { + let s = slice; + s = s.replace(checkboxPattern, `$1x$2 (completed ${today})`); + s = s.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`); + for (const summaryFile of phaseInfoSummaries) { + const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''); + if (!planId) continue; + const planEscaped = escapeRegex(planId); + const planCheckboxPattern = new RegExp( + `(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, + 'i', + ); + s = s.replace(planCheckboxPattern, '$1x$2'); + } + return s; + }; + + const milestoneRanges = currentMilestoneRawRanges(roadmapContent, cwd); + if (milestoneRanges) { + // Splice later windows first so an earlier window's offsets are not + // shifted by a length-changing mutation in a later window. + const windows = [milestoneRanges.details, milestoneRanges.primary] + .filter((w): w is { start: number; end: number } => w !== null) + .sort((a, b) => b.start - a.start); + for (const w of windows) { + roadmapContent = + roadmapContent.slice(0, w.start) + + mutateMilestonePhase(roadmapContent.slice(w.start, w.end)) + + roadmapContent.slice(w.end); + } + } else { + roadmapContent = mutateMilestonePhase(roadmapContent); } writes.push({ diff --git a/src/roadmap-parser.cts b/src/roadmap-parser.cts index 33845d957..cfa7d4c69 100644 --- a/src/roadmap-parser.cts +++ b/src/roadmap-parser.cts @@ -202,6 +202,23 @@ function replaceInCurrentMilestone(content: string, pattern: RegExp, replacement // ─── Roadmap phase lookup ───────────────────────────────────────────────────── +// #2199: a bullet/checkbox phase entry, e.g. `- [ ] **Phase 36 — Authentication**` +// (the bundled roadmapper emits this in bullet-house-style ROADMAPs). The number +// is captured in group 1, the name in group 2; the separator may be an em-dash, +// en-dash, hyphen, or colon. Used as a fallback when no ATX heading matches, and +// to count phases in a milestone that uses the bullet form. +const BULLET_PHASE_LINE_PATTERN = + /^\s*[-*]\s+(?:\[[ xX]\]\s+)?\*\*Phase\s+([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*[—–:\-]\s*(.+?)\*\*/im; + +/** Build a bullet-phase-line regex pinned to a specific phase number (#2199). */ +function bulletPhaseLineFor(phaseNum: unknown, phaseSource?: string): RegExp { + const num = phaseSource ?? phaseMarkdownRegexSource(phaseNum); + return new RegExp( + `^\\s*[-*]\\s+(?:\\[[ xX]\\]\\s+)?\\*\\*Phase\\s+(${num})${OPTIONAL_PHASE_TAG_SOURCE}\\s*[—–:\\-]\\s*(.+?)\\*\\*`, + 'im', + ); +} + interface RoadmapPhaseResult { found: boolean; phase_number: string; @@ -241,6 +258,22 @@ function findRoadmapPhaseInContent(content: string, phaseNum: unknown, phaseSour }; } +function findRoadmapBulletPhaseInContent(content: string, phaseNum: unknown, phaseSource?: string): RoadmapPhaseResult | null { + // #2199: bullet/checkbox entry fallback (`- [ ] **Phase N — name**`). Returns + // the single bullet line as the section (no multi-line body) — used only as a + // last resort, AFTER heading lookup on scoped + full content has failed, so a + // heading with a Requirements/Goal section always wins. + const bulletMatch = content.match(bulletPhaseLineFor(phaseNum, phaseSource)); + if (!bulletMatch) return null; + return { + found: true, + phase_number: String(phaseNum), + phase_name: bulletMatch[2].trim(), + goal: null, + section: bulletMatch[0].trim(), + }; +} + function getRoadmapPhaseInternal(cwd: string, phaseNum: unknown): RoadmapPhaseResult | null { if (!phaseNum) return null; const normalizedPhase = stripProjectCodePrefix(phaseNum); @@ -262,6 +295,17 @@ function getRoadmapPhaseInternal(cwd: string, phaseNum: unknown): RoadmapPhaseRe if (fullResult) return fullResult; } + // #2199: no ATX heading matched on scoped or full content — fall back to a + // bullet/checkbox entry (em-dash/en-dash/hyphen/colon separator). Last resort + // so a bullet never pre-empts a heading that carries the Requirements section. + for (const source of roadmapPhaseLookupSources(phaseNum)) { + const scopedBullet = findRoadmapBulletPhaseInContent(content, phaseNum, source); + if (scopedBullet) return scopedBullet; + + const fullBullet = findRoadmapBulletPhaseInContent(fullContent, phaseNum, source); + if (fullBullet) return fullBullet; + } + return null; } catch { return null; @@ -458,6 +502,16 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p // Exclude 999.x backlog phases from milestone phase set. Mirrors init.cts filter. if (pm && !/^999\b/.test(pm[1])) milestonePhaseNums.add(pm[1]); } + // #2199: also count bullet/checkbox phase entries (`- [ ] **Phase N — name**`) + // so a bullet-house-style ROADMAP populates the milestone phase set instead of + // collapsing to a zero-count pass-all filter. + { + let bm: RegExpExecArray | null; + const scanner = new RegExp(BULLET_PHASE_LINE_PATTERN.source, 'gim'); + while ((bm = scanner.exec(roadmap)) !== null) { + if (!/^999\b/.test(bm[1])) milestonePhaseNums.add(bm[1]); + } + } } catch { /* intentionally empty */ } if (milestonePhaseNums.size === 0) { @@ -502,6 +556,92 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p return isDirInMilestone as MilestonePhaseFilter; } +/** + * #2200: raw [start,end) offsets of the current milestone's region(s) in ROADMAP + * content, for scoping write-path mutations (phase-checkbox flip, Plans-count + * writer) so they cannot touch a backticked prose literal, a Backlog entry, or a + * same-numbered phase in a shipped milestone. + * + * Mirrors the region selection in `extractCurrentMilestone` (version detection → + * active heading → next milestone boundary → optional Phase Details section). + * Returns null when there is no versioned active milestone; callers then fall + * back to whole-content mutation (the prior behaviour). + * + * NOTE: keep the region logic here in sync with extractCurrentMilestone. + */ +function currentMilestoneRawRanges( + content: string, + cwd?: string, +): { primary: { start: number; end: number }; details: { start: number; end: number } | null } | null { + if (!cwd) return null; + + let version: string | null = null; + try { + const statePath = path.join(planningDir(cwd), 'STATE.md'); + const stateRaw = platformReadSync(statePath); + if (stateRaw !== null) { + const milestoneMatch = stateRaw.match(/^milestone:\s*(.+)/m); + if (milestoneMatch) version = milestoneMatch[1].trim(); + } + } catch { /* ignore */ } + if (!version) { + const inProgressMatch = content.match(/(?:🚧|🔄)\s*\*\*v(\d+\.\d+)\s/); + if (inProgressMatch) version = 'v' + inProgressMatch[1]; + } + if (!version) return null; + + const escapedVersion = escapeRegex(version); + const sectionPattern = new RegExp( + `(^#{1,3}\\s+(?!Phase\\s+\\S).*${escapedVersion}\\b[^\\n]*)`, + 'gmi', + ); + const headingMatches = [...content.matchAll(sectionPattern)]; + if (headingMatches.length === 0) return null; + + const closedMarkerPattern = /\b(?:CLOSED|ARCHIVED|ABANDONED|SHIPPED|FAILED)\b|✅|🗄/i; + const activeMarkerPattern = /\b(?:STARTED|ACTIVE|WIP)\b|in\s+progress|🚧|🔄/i; + const isClosed = (h: string) => closedMarkerPattern.test(h) && !activeMarkerPattern.test(h); + const firstMatch = headingMatches[0]; + const selected = headingMatches.find((m) => !isClosed(m[1])) || firstMatch; + const sectionStart = selected.index ?? 0; + + const computeSectionEnd = (headingText: string, headingStart: number): number => { + const level = (headingText.match(/^(#{1,3})\s/) ?? ['', '#'])[1].length; + const afterHeading = headingStart + headingText.length; + for (const h of tokenizeHeadings(content)) { + if (h.offset <= headingStart) continue; + if (h.offset < afterHeading) continue; + if (h.level > level) continue; + if (/^Phase\s+\S/i.test(h.text)) continue; + if (!/v\d+\.\d+|✅|📋|🚧/i.test(h.text)) continue; + return h.offset; + } + return content.length; + }; + const sectionEnd = computeSectionEnd(selected[0], sectionStart); + + const selectedVersionToken = selected[1].match( + /v\d+(?:\.\d+)+(?:[-.][A-Za-z0-9]+)*/i, + )?.[0]; + const detailsVersionBoundary = selectedVersionToken + ? new RegExp(`${escapeRegex(selectedVersionToken)}(?![\\w.-])`, 'i') + : null; + const detailsMatch = headingMatches.find( + (m) => + /\(Phase\s+Details\)/i.test(m[1]) && + !isClosed(m[1]) && + (!detailsVersionBoundary || detailsVersionBoundary.test(m[1])) && + (m.index ?? 0) >= sectionEnd, + ); + let details: { start: number; end: number } | null = null; + if (detailsMatch) { + const detailsStart = detailsMatch.index ?? 0; + details = { start: detailsStart, end: computeSectionEnd(detailsMatch[0], detailsStart) }; + } + + return { primary: { start: sectionStart, end: sectionEnd }, details }; +} + export = { stripShippedMilestones, extractCurrentMilestone, @@ -509,4 +649,5 @@ export = { getRoadmapPhaseInternal, getMilestoneInfo, getMilestonePhaseFilter, + currentMilestoneRawRanges, }; diff --git a/tests/agent-size-baseline.json b/tests/agent-size-baseline.json index 3d68c1af2..5044c3856 100644 --- a/tests/agent-size-baseline.json +++ b/tests/agent-size-baseline.json @@ -27,7 +27,7 @@ "gsd-project-researcher.md": 22242, "gsd-research-synthesizer.md": 13847, "gsd-roadmapper.md": 22273, - "gsd-security-auditor.md": 8981, + "gsd-security-auditor.md": 9431, "gsd-ui-auditor.md": 17249, "gsd-ui-checker.md": 14118, "gsd-ui-researcher.md": 19557, diff --git a/tests/commit-files-pathspec.test.cjs b/tests/commit-files-pathspec.test.cjs new file mode 100644 index 000000000..488934209 --- /dev/null +++ b/tests/commit-files-pathspec.test.cjs @@ -0,0 +1,176 @@ +/** + * Regression test for #2112: gsd-tools commit --files commits the entire + * index, not the declared paths. + * + * `cmdCommit` staged exactly the files named in --files but then ran a bare + * `git commit` with no pathspec, absorbing anything else that happened to be + * staged into a commit whose message described only the named files. + * + * The fix adds `'--', ...stagedPaths` to the commit args **only when** the + * caller declared a scope (explicitFiles), and only for paths that were + * actually staged (skipped missing files are excluded to avoid #2014). + */ + +const { describe, test, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const { createTempGitProject, cleanup, runGsdTools } = require('./helpers.cjs'); + +describe('commit --files: pathspec honors declared scope (#2112)', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempGitProject(); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('commit --files does not absorb unrelated staged files', () => { + // Developer stages a WIP file via git add (not via --files). + fs.writeFileSync(path.join(tmpDir, 'src-wip.txt'), 'work in progress\n'); + execSync('git add src-wip.txt', { cwd: tmpDir, stdio: 'pipe' }); + + // GSD writes and commits a planning artifact, naming ONLY that file. + fs.writeFileSync(path.join(tmpDir, '.planning', 'PLAN.md'), '# Plan\n'); + runGsdTools( + ['commit', 'docs(01): add PLAN.md', '--files', '.planning/PLAN.md'], + tmpDir, + ); + + // The commit must contain ONLY .planning/PLAN.md. + const diffOutput = execSync('git diff HEAD~1 HEAD --name-only', { + cwd: tmpDir, + encoding: 'utf-8', + }).trim(); + assert.strictEqual( + diffOutput, + '.planning/PLAN.md', + 'commit --files must contain only the named files, got:\n' + diffOutput, + ); + + // The WIP file must still be staged, not committed. + const statusOutput = execSync('git status --porcelain', { + cwd: tmpDir, + encoding: 'utf-8', + }).trim(); + assert.ok( + statusOutput.includes('A src-wip.txt') || statusOutput.includes('A\tsrc-wip.txt'), + 'src-wip.txt should remain staged, not committed. Status:\n' + statusOutput, + ); + }); + + test('commit --files with two files commits exactly those two', () => { + fs.writeFileSync(path.join(tmpDir, '.planning', 'PLAN.md'), '# Plan\n'); + fs.writeFileSync(path.join(tmpDir, '.planning', 'RESEARCH.md'), '# Research\n'); + + runGsdTools( + ['commit', 'docs: artifacts', '--files', '.planning/PLAN.md', '.planning/RESEARCH.md'], + tmpDir, + ); + + const diffOutput = execSync('git diff HEAD~1 HEAD --name-only', { + cwd: tmpDir, + encoding: 'utf-8', + }); + const files = diffOutput.trim().split('\n').sort(); + assert.deepEqual( + files, + ['.planning/PLAN.md', '.planning/RESEARCH.md'], + 'commit should contain exactly the two named files', + ); + }); + + test('commit without --files still commits the entire .planning/ index (default path)', () => { + // Write a planning artifact and stage it. + fs.writeFileSync(path.join(tmpDir, '.planning', 'PLAN.md'), '# Plan\n'); + execSync('git add .planning/PLAN.md', { cwd: tmpDir, stdio: 'pipe' }); + + // Also stage an unrelated file. + fs.writeFileSync(path.join(tmpDir, 'extra.txt'), 'extra\n'); + execSync('git add extra.txt', { cwd: tmpDir, stdio: 'pipe' }); + + runGsdTools(['commit', 'docs: default commit'], tmpDir); + + // Default path (no --files) commits everything staged. + const diffOutput = execSync('git diff HEAD~1 HEAD --name-only', { + cwd: tmpDir, + encoding: 'utf-8', + }); + const files = diffOutput.trim().split('\n').sort(); + assert.ok( + files.includes('.planning/PLAN.md') && files.includes('extra.txt'), + 'default commit (no --files) should commit everything staged, got:\n' + files, + ); + }); + + test('missing tracked file in --files is still not committed as deletion (#2014 guard)', () => { + // Create and commit STATE.md, then remove it from disk. + fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n'); + execSync('git add .planning/STATE.md', { cwd: tmpDir, stdio: 'pipe' }); + execSync('git commit -m "add STATE.md"', { cwd: tmpDir, stdio: 'pipe' }); + fs.unlinkSync(path.join(tmpDir, '.planning', 'STATE.md')); + + // Also create a valid file to commit. + fs.writeFileSync(path.join(tmpDir, '.planning', 'PLAN.md'), '# Plan\n'); + + runGsdTools( + ['commit', 'docs: add plan', '--files', '.planning/PLAN.md', '.planning/STATE.md'], + tmpDir, + ); + + const diffOutput = execSync('git diff HEAD~1 HEAD --name-status', { + cwd: tmpDir, + encoding: 'utf-8', + }); + assert.ok( + !diffOutput.includes('D\t.planning/STATE.md'), + 'missing tracked file must not appear as a deletion, diff was:\n' + diffOutput, + ); + assert.ok( + diffOutput.includes('.planning/PLAN.md'), + 'PLAN.md should be committed', + ); + }); + + test('commit --files with only missing files returns nothing_to_commit', () => { + // Create and commit STATE.md, then remove it from disk. + fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n'); + execSync('git add .planning/STATE.md', { cwd: tmpDir, stdio: 'pipe' }); + execSync('git commit -m "add STATE.md"', { cwd: tmpDir, stdio: 'pipe' }); + fs.unlinkSync(path.join(tmpDir, '.planning', 'STATE.md')); + + // Stage an unrelated file so the index is non-empty. + fs.writeFileSync(path.join(tmpDir, 'extra.txt'), 'extra\n'); + execSync('git add extra.txt', { cwd: tmpDir, stdio: 'pipe' }); + + const result = runGsdTools( + ['commit', 'docs: try', '--files', '.planning/STATE.md'], + tmpDir, + ); + + const parsed = JSON.parse(result.output); + assert.strictEqual( + parsed.committed, false, + 'should not commit when all --files are missing', + ); + assert.strictEqual( + parsed.reason, 'nothing_to_commit', + 'should report nothing_to_commit, not absorb the index', + ); + + // The unrelated staged file must still be staged, not committed. + const statusOutput = execSync('git status --porcelain', { + cwd: tmpDir, + encoding: 'utf-8', + }).trim(); + assert.ok( + statusOutput.includes('extra.txt'), + 'extra.txt should remain staged, not absorbed into a commit', + ); + }); +}); diff --git a/tests/fix-2196-debug-agent-handoff.test.cjs b/tests/fix-2196-debug-agent-handoff.test.cjs new file mode 100644 index 000000000..623533aaf --- /dev/null +++ b/tests/fix-2196-debug-agent-handoff.test.cjs @@ -0,0 +1,52 @@ +'use strict'; + +/** + * #2194… no — #2196: the /gsd-debug orchestrator misused the foreground + * session-manager Agent() spawn as a background task, then queried the returned + * agent ID via TaskOutput (which expects a task ID) — yielding "No task found + * with ID" and leaving the workflow waiting on a handoff that was never + * queryable, with no recovery. + * + * The fix makes debug.md state explicitly that the spawn is foreground/blocking, + * that an agent ID must never be passed to TaskOutput, and that a lost handoff + * must be recovered (preserve checkpoint + resume). debug.md IS the product the + * runtime loads, so this asserts the deployed text carries that contract. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const DEBUG_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'debug.md'); + +describe('#2196 debug.md session-manager spawn contract', () => { + const content = fs.readFileSync(DEBUG_MD, 'utf-8'); + const sectionStart = content.indexOf('Session Management'); + const section = sectionStart !== -1 ? content.slice(sectionStart) : ''; + + test('debug.md has the Session Management section', () => { + assert.notEqual(sectionStart, -1, 'debug.md must contain the Session Management section'); + }); + + test('the session-manager spawn is declared foreground/blocking (not backgrounded)', () => { + assert.ok(/foreground/i.test(section) && /blocking/i.test(section), + 'the Agent() spawn must be declared foreground and blocking so it is not polled'); + }); + + test('an agent ID must not be passed to TaskOutput', () => { + assert.ok(/TaskOutput/.test(section), + 'the contract must mention TaskOutput by name'); + assert.ok(/agent ID is NOT a task ID|agent ID is not a task ID/i.test(section), + 'the contract must state an agent ID is not a task ID'); + }); + + test('a lost handoff has a recovery path (preserve checkpoint + resume)', () => { + // Pin the CANONICAL colon form — the retired /gsd-debug hyphen syntax is + // rejected by the slash-command-namespace guard, so this must be /gsd:debug. + assert.ok(/\/gsd:debug continue \{slug\}/.test(section), + 'the contract must point to /gsd:debug continue {slug} (canonical colon form) as the resume path'); + assert.ok(/do not claim|do NOT claim/i.test(section), + 'the contract must forbid claiming a lost-handoff session is still running'); + }); +}); diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 2529f592e..ee69a5669 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "85de7f562872ee9b", "agents/gsd-research-synthesizer.md": "18a2e1b30ff7ae3a", "agents/gsd-roadmapper.md": "7a8465ac6d4dd29e", - "agents/gsd-security-auditor.md": "2ce13af25179dd10", + "agents/gsd-security-auditor.md": "7730a026680cb821", "agents/gsd-ui-auditor.md": "f777f9c7bf62788c", "agents/gsd-ui-checker.md": "216af34b01e277aa", "agents/gsd-ui-researcher.md": "5f86de1decbd16d2", @@ -38,7 +38,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74", - "gsd-core/bin/gsd-tools.cjs": "e8ccbbe8cefc2e9a", + "gsd-core/bin/gsd-tools.cjs": "600e9ed0cb6ed7bb", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -198,7 +198,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "8cc2e884a021b79b", "gsd-core/workflows/add-phase.md": "f82afa2f93be19fa", - "gsd-core/workflows/add-tests.md": "f695210f67d2635e", + "gsd-core/workflows/add-tests.md": "ad6d49634e04ca7c", "gsd-core/workflows/add-todo.md": "de1ac76acfcc0133", "gsd-core/workflows/ai-integration-phase.md": "22f5466085c47c00", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -211,7 +211,7 @@ "gsd-core/workflows/code-review-fix.md": "60640e633b0a124b", "gsd-core/workflows/code-review.md": "5c40505c01871153", "gsd-core/workflows/complete-milestone.md": "aaf272074acec69d", - "gsd-core/workflows/debug.md": "68f1ddc74886ebe5", + "gsd-core/workflows/debug.md": "af2d1ae03b24fc71", "gsd-core/workflows/diagnose-issues.md": "c8c41993c277363c", "gsd-core/workflows/discovery-phase.md": "3de990caffdde4f8", "gsd-core/workflows/discuss-phase-assumptions.md": "8581fd77def7ce84", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "98e2cf8908e73a52", "gsd-core/workflows/review.md": "43c052bba1cbd4ac", "gsd-core/workflows/scan.md": "a7fecd67e5cd655f", - "gsd-core/workflows/secure-phase.md": "96b199dfac00e60f", + "gsd-core/workflows/secure-phase.md": "52ddc46233e8fa66", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "6d25100a9de15b31", "gsd-core/workflows/settings-integrations.md": "4d3001ad2b5dad8f", @@ -296,7 +296,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "0f842a609851a401", "gsd-core/workflows/sketch.md": "314b7d323c6b57eb", "gsd-core/workflows/smart-entry.md": "3ce5b6228238fdb6", - "gsd-core/workflows/spec-phase.md": "11c9e6b01bd2880a", + "gsd-core/workflows/spec-phase.md": "978462218855c213", "gsd-core/workflows/spike-wrap-up.md": "8db41edc87cb7886", "gsd-core/workflows/spike.md": "dd8d139947a8fb31", "gsd-core/workflows/stats.md": "b64bda5c0a1a06fb", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 9715eb856..61d2c8ad0 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "4531b7cc8f5e5f7d", "agents/gsd-research-synthesizer.md": "4a4f68e6c75b133a", "agents/gsd-roadmapper.md": "bb2f57695dbab32c", - "agents/gsd-security-auditor.md": "f22374cc1db28dca", + "agents/gsd-security-auditor.md": "4db2c41181ad1f97", "agents/gsd-ui-auditor.md": "dcd5712e6b160a53", "agents/gsd-ui-checker.md": "5c27ec0d88ef87c2", "agents/gsd-ui-researcher.md": "bcc591f2dfebdb60", @@ -109,7 +109,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -269,7 +269,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", - "gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2", + "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", @@ -282,7 +282,7 @@ "gsd-core/workflows/code-review-fix.md": "2e113d1f4350a075", "gsd-core/workflows/code-review.md": "334c90c401f291f8", "gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2", - "gsd-core/workflows/debug.md": "849d7e5b9c12dcae", + "gsd-core/workflows/debug.md": "c581e89aa9d9d71e", "gsd-core/workflows/diagnose-issues.md": "6cc3900891dfb927", "gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761", "gsd-core/workflows/discuss-phase-assumptions.md": "f5b765d33eba4f88", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", @@ -367,7 +367,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "5f5ebb6a80d610c6", "gsd-core/workflows/sketch.md": "8319cedf3f93fc35", "gsd-core/workflows/smart-entry.md": "449238eb94abe187", - "gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f", + "gsd-core/workflows/spec-phase.md": "fa8669b75392cf6c", "gsd-core/workflows/spike-wrap-up.md": "8467dadf2af758e6", "gsd-core/workflows/spike.md": "7727c07bb221c8cc", "gsd-core/workflows/stats.md": "76a42cbeaf6007c2", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index e825323e6..f660e3b97 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -28,7 +28,7 @@ "agents/gsd-project-researcher.md": "d7f355894519f9fe", "agents/gsd-research-synthesizer.md": "1c738df9932d325a", "agents/gsd-roadmapper.md": "453e9471ad27c7ea", - "agents/gsd-security-auditor.md": "45bd98918cd3a004", + "agents/gsd-security-auditor.md": "4551e5f621cf13ff", "agents/gsd-ui-auditor.md": "a0b09cc8e4645956", "agents/gsd-ui-checker.md": "33ffdc73d2105a24", "agents/gsd-ui-researcher.md": "4b36852c839f1134", @@ -108,7 +108,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -268,7 +268,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "8d05775f367d1e48", "gsd-core/workflows/add-phase.md": "7285e6e8894a41c5", - "gsd-core/workflows/add-tests.md": "8012263b2d27b83e", + "gsd-core/workflows/add-tests.md": "ffbaf55a25f455a2", "gsd-core/workflows/add-todo.md": "1fc850476cd8340d", "gsd-core/workflows/ai-integration-phase.md": "3503f52a7356caf0", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -281,7 +281,7 @@ "gsd-core/workflows/code-review-fix.md": "78c716068ccdf820", "gsd-core/workflows/code-review.md": "2d21452eb0449fdd", "gsd-core/workflows/complete-milestone.md": "9962377cddee50d7", - "gsd-core/workflows/debug.md": "3354c726abbfd75b", + "gsd-core/workflows/debug.md": "18c97b804f2dd5dd", "gsd-core/workflows/diagnose-issues.md": "db6a599674efbc4d", "gsd-core/workflows/discovery-phase.md": "a20dfb32adec51de", "gsd-core/workflows/discuss-phase-assumptions.md": "35a3b2d1285565d8", @@ -357,7 +357,7 @@ "gsd-core/workflows/resume-project.md": "af9761bcec0f6fe9", "gsd-core/workflows/review.md": "eec3a15bebb7fcf0", "gsd-core/workflows/scan.md": "75c670d08cee8680", - "gsd-core/workflows/secure-phase.md": "64ec4d06ca85720a", + "gsd-core/workflows/secure-phase.md": "8030d2b2a5bfdf07", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "94d61da368e9f85b", "gsd-core/workflows/settings-integrations.md": "dfe3672c4fabf139", @@ -366,7 +366,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "d52a5462bafda830", "gsd-core/workflows/sketch.md": "dbe6acc4d976060c", "gsd-core/workflows/smart-entry.md": "1850447c045f36d8", - "gsd-core/workflows/spec-phase.md": "e03fa9f1a44613dc", + "gsd-core/workflows/spec-phase.md": "ec4d7e4e83bb001d", "gsd-core/workflows/spike-wrap-up.md": "4bdfaf9d05c63e7c", "gsd-core/workflows/spike.md": "1571a05457beea8d", "gsd-core/workflows/stats.md": "3953356f476b5053", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 89a1f3e6f..7e0d512eb 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -28,7 +28,7 @@ "agents/gsd-project-researcher.md": "f468e96f8339d1e0", "agents/gsd-research-synthesizer.md": "7be02e47f4fd901b", "agents/gsd-roadmapper.md": "8a7f1f1256a6aed5", - "agents/gsd-security-auditor.md": "4f9fc3f654af4944", + "agents/gsd-security-auditor.md": "757f72894f79f11b", "agents/gsd-ui-auditor.md": "40c0dcc15bcfb9fb", "agents/gsd-ui-checker.md": "8126405043f99cb6", "agents/gsd-ui-researcher.md": "9e3ac030767167e0", @@ -37,7 +37,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -197,7 +197,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "485c4b6673ddf832", "gsd-core/workflows/add-phase.md": "0d90083e9c17bec1", - "gsd-core/workflows/add-tests.md": "330b3e7d969fc2d1", + "gsd-core/workflows/add-tests.md": "c4b133a102d36a27", "gsd-core/workflows/add-todo.md": "8488f10f56ac24ca", "gsd-core/workflows/ai-integration-phase.md": "a898d99b8d844215", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -210,7 +210,7 @@ "gsd-core/workflows/code-review-fix.md": "722416b71b31c5fc", "gsd-core/workflows/code-review.md": "506412604f767adc", "gsd-core/workflows/complete-milestone.md": "dcf1182398efb1ca", - "gsd-core/workflows/debug.md": "a9397fd35cca2240", + "gsd-core/workflows/debug.md": "7183935f8e145a01", "gsd-core/workflows/diagnose-issues.md": "75ffc381ac3059ff", "gsd-core/workflows/discovery-phase.md": "6161c60d752d0058", "gsd-core/workflows/discuss-phase-assumptions.md": "a8cd1db094fefd35", @@ -286,7 +286,7 @@ "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", "gsd-core/workflows/review.md": "b0baf1dafebe3821", "gsd-core/workflows/scan.md": "47371c2073d6c0be", - "gsd-core/workflows/secure-phase.md": "59d3c50aba8c9a6c", + "gsd-core/workflows/secure-phase.md": "d6ac1f4db6a5da75", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "339def28c34b0797", "gsd-core/workflows/settings-integrations.md": "53649313d20694ae", @@ -295,7 +295,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "121ed4b8127abf04", "gsd-core/workflows/sketch.md": "737c0492686fea2d", "gsd-core/workflows/smart-entry.md": "ad20cf74ae2e8291", - "gsd-core/workflows/spec-phase.md": "82150c52a9077602", + "gsd-core/workflows/spec-phase.md": "d935df4ab9dd4f7c", "gsd-core/workflows/spike-wrap-up.md": "e2704024992b3fee", "gsd-core/workflows/spike.md": "6dce83a2c5e7e49c", "gsd-core/workflows/stats.md": "e7852c5d8f2b05a9", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index d7f323bbc..598d068aa 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -32,7 +32,7 @@ "agents/gsd-project-researcher.md": "049f816c6caa4316", "agents/gsd-research-synthesizer.md": "2f7dcbff50371d4c", "agents/gsd-roadmapper.md": "bbb23d3097911516", - "agents/gsd-security-auditor.md": "c35c8ec2f85b331f", + "agents/gsd-security-auditor.md": "38935b2d0c532c29", "agents/gsd-ui-auditor.md": "9338efa31b9b7b99", "agents/gsd-ui-checker.md": "151d12b4e007cbbf", "agents/gsd-ui-researcher.md": "1bb303f3a3c4dfc9", @@ -41,7 +41,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "476aa24e8c4f03cf", - "gsd-core/bin/gsd-tools.cjs": "93273f4eda2750d2", + "gsd-core/bin/gsd-tools.cjs": "a84914f7cab74332", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -201,7 +201,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "6c4393113fa9d209", "gsd-core/workflows/add-phase.md": "cac9b86f66ab26f5", - "gsd-core/workflows/add-tests.md": "dd80832a428afb45", + "gsd-core/workflows/add-tests.md": "0354376bffdc0080", "gsd-core/workflows/add-todo.md": "b71e80304fe484eb", "gsd-core/workflows/ai-integration-phase.md": "5159c6bdf102f74b", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", @@ -214,7 +214,7 @@ "gsd-core/workflows/code-review-fix.md": "e4549af672e74e6f", "gsd-core/workflows/code-review.md": "a65e3e869508f89e", "gsd-core/workflows/complete-milestone.md": "c0808127038a8f86", - "gsd-core/workflows/debug.md": "f42e8e3cbc298694", + "gsd-core/workflows/debug.md": "337fc2076e7fc449", "gsd-core/workflows/diagnose-issues.md": "e616d0d730328d68", "gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761", "gsd-core/workflows/discuss-phase-assumptions.md": "9efcb2ef6a9245b3", @@ -290,7 +290,7 @@ "gsd-core/workflows/resume-project.md": "e23981178fa37b3d", "gsd-core/workflows/review.md": "6c689f4ff8146d28", "gsd-core/workflows/scan.md": "dfd92717caea0ce7", - "gsd-core/workflows/secure-phase.md": "cf78183f06a02582", + "gsd-core/workflows/secure-phase.md": "00de56d6d993bb2c", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "69f3a19bf2c61160", "gsd-core/workflows/settings-integrations.md": "76eee76d6eb57657", @@ -299,7 +299,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "1f44789553180d84", "gsd-core/workflows/sketch.md": "2226779b6003a71c", "gsd-core/workflows/smart-entry.md": "9a64f43927641ca4", - "gsd-core/workflows/spec-phase.md": "55d727dbf400ed9a", + "gsd-core/workflows/spec-phase.md": "641208ed90684364", "gsd-core/workflows/spike-wrap-up.md": "846958e0012a0899", "gsd-core/workflows/spike.md": "868b15cdf229cc43", "gsd-core/workflows/stats.md": "f9241bb65770c5a4", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 3ef9ae7aa..aa369e9c0 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "e43c59f7f1f2f37a", "agents/gsd-research-synthesizer.md": "87955470c3c129b2", "agents/gsd-roadmapper.md": "20b69eff61a7a9fa", - "agents/gsd-security-auditor.md": "d3b8f44034a76c9d", + "agents/gsd-security-auditor.md": "bcd9c4859eb5b448", "agents/gsd-ui-auditor.md": "a26bbc733817959b", "agents/gsd-ui-checker.md": "25822359044cd708", "agents/gsd-ui-researcher.md": "e37d9f53ade25d1f", @@ -109,7 +109,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -269,7 +269,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", - "gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2", + "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", @@ -282,7 +282,7 @@ "gsd-core/workflows/code-review-fix.md": "2e113d1f4350a075", "gsd-core/workflows/code-review.md": "334c90c401f291f8", "gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2", - "gsd-core/workflows/debug.md": "849d7e5b9c12dcae", + "gsd-core/workflows/debug.md": "c581e89aa9d9d71e", "gsd-core/workflows/diagnose-issues.md": "77d98ac07c4a26ff", "gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761", "gsd-core/workflows/discuss-phase-assumptions.md": "f5b765d33eba4f88", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", @@ -367,7 +367,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "2aba89ecd8f41a0d", "gsd-core/workflows/sketch.md": "5eedd93f9a5b49d5", "gsd-core/workflows/smart-entry.md": "449238eb94abe187", - "gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f", + "gsd-core/workflows/spec-phase.md": "fa8669b75392cf6c", "gsd-core/workflows/spike-wrap-up.md": "2b004744c52e565c", "gsd-core/workflows/spike.md": "24a0fee1447b6f0a", "gsd-core/workflows/stats.md": "76a42cbeaf6007c2", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 15d7132dd..36c21e16f 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -128,8 +128,8 @@ "agents/gsd-research-synthesizer.toml": "053616d4941a1458", "agents/gsd-roadmapper.md": "e15d82d7781dbff3", "agents/gsd-roadmapper.toml": "0b17f618eafe9be8", - "agents/gsd-security-auditor.md": "a4008a6b1d01833b", - "agents/gsd-security-auditor.toml": "42f529475bd28c22", + "agents/gsd-security-auditor.md": "d7b62f9a93cfbf78", + "agents/gsd-security-auditor.toml": "e991aa6e7c70f813", "agents/gsd-ui-auditor.md": "abbf560bc8d5069c", "agents/gsd-ui-auditor.toml": "5e9dd62a12a16a1e", "agents/gsd-ui-checker.md": "b65d350a4e0564e9", @@ -140,11 +140,11 @@ "agents/gsd-user-profiler.toml": "b9c244bb8fbf8140", "agents/gsd-verifier.md": "4ac4b860e2504374", "agents/gsd-verifier.toml": "8ed9fb961409e894", - "config.toml": "fa48d84b92174890", + "config.toml": "b5f627b42f060910", "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -304,7 +304,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "62431b27424ff1ff", "gsd-core/workflows/add-phase.md": "27f282bf5b8af59d", - "gsd-core/workflows/add-tests.md": "995918665fc78d39", + "gsd-core/workflows/add-tests.md": "5cdb15508c4b6075", "gsd-core/workflows/add-todo.md": "097e171f797737df", "gsd-core/workflows/ai-integration-phase.md": "1dfa15d8f28c022d", "gsd-core/workflows/analyze-dependencies.md": "f799abc00907377f", @@ -317,7 +317,7 @@ "gsd-core/workflows/code-review-fix.md": "ae7f9c6b39a23c12", "gsd-core/workflows/code-review.md": "eadada9e0a89adf2", "gsd-core/workflows/complete-milestone.md": "017df7443bd08da5", - "gsd-core/workflows/debug.md": "cc7b2d2fd4307a78", + "gsd-core/workflows/debug.md": "7c3b407470762585", "gsd-core/workflows/diagnose-issues.md": "e38bb21d06dff077", "gsd-core/workflows/discovery-phase.md": "71a4b78ff876a854", "gsd-core/workflows/discuss-phase-assumptions.md": "0d936ec25299917c", @@ -393,7 +393,7 @@ "gsd-core/workflows/resume-project.md": "9965f87eb278f7f8", "gsd-core/workflows/review.md": "5faef3f4feb45c99", "gsd-core/workflows/scan.md": "1a3caa5d724d39e9", - "gsd-core/workflows/secure-phase.md": "db91810d16964b1e", + "gsd-core/workflows/secure-phase.md": "ab387a4bca381c18", "gsd-core/workflows/session-report.md": "dd8fa011c9394075", "gsd-core/workflows/settings-advanced.md": "2431433811616f76", "gsd-core/workflows/settings-integrations.md": "77730321d3d6d317", @@ -402,7 +402,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "07724a390fbb43f6", "gsd-core/workflows/sketch.md": "576f300dfdde1b7d", "gsd-core/workflows/smart-entry.md": "6f686195ae531b03", - "gsd-core/workflows/spec-phase.md": "deaec2464a6560ec", + "gsd-core/workflows/spec-phase.md": "3dd8ae6137492d80", "gsd-core/workflows/spike-wrap-up.md": "ace265c114298376", "gsd-core/workflows/spike.md": "f42a9983eb9c89a1", "gsd-core/workflows/stats.md": "6dfc500555849d74", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 813333959..27c773c07 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.agent.md": "d73bdbe986ffa8a6", "agents/gsd-research-synthesizer.agent.md": "f03eed4aa89e47c5", "agents/gsd-roadmapper.agent.md": "322048cf8ddcb4e5", - "agents/gsd-security-auditor.agent.md": "6f6a88b35dc2a24b", + "agents/gsd-security-auditor.agent.md": "464cfbe9aafcd5af", "agents/gsd-ui-auditor.agent.md": "92f50549e84ef482", "agents/gsd-ui-checker.agent.md": "47d8cf3486009e11", "agents/gsd-ui-researcher.agent.md": "0746daeb54f83008", @@ -39,7 +39,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74", - "gsd-core/bin/gsd-tools.cjs": "e8ccbbe8cefc2e9a", + "gsd-core/bin/gsd-tools.cjs": "600e9ed0cb6ed7bb", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -199,7 +199,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "98f2dbb7c7cc93c2", "gsd-core/workflows/add-phase.md": "34e6dd38c25b5b61", - "gsd-core/workflows/add-tests.md": "e88bc8bc3416bfdd", + "gsd-core/workflows/add-tests.md": "7f5da4d2a9fa8a1a", "gsd-core/workflows/add-todo.md": "10253591112b7d06", "gsd-core/workflows/ai-integration-phase.md": "ef0c2474cee76b00", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -212,7 +212,7 @@ "gsd-core/workflows/code-review-fix.md": "fda53892ae4b17fc", "gsd-core/workflows/code-review.md": "f1c045ec4d33abc8", "gsd-core/workflows/complete-milestone.md": "470cf39261400ee2", - "gsd-core/workflows/debug.md": "36cb536be452d79e", + "gsd-core/workflows/debug.md": "505ed67d48b1fa9d", "gsd-core/workflows/diagnose-issues.md": "42acbe2a43fc886e", "gsd-core/workflows/discovery-phase.md": "8e99da61fb2b7074", "gsd-core/workflows/discuss-phase-assumptions.md": "ab0c432b84038681", @@ -288,7 +288,7 @@ "gsd-core/workflows/resume-project.md": "40db7f350f5866d8", "gsd-core/workflows/review.md": "4b649f31865a1785", "gsd-core/workflows/scan.md": "dcc2f76d0850e2fb", - "gsd-core/workflows/secure-phase.md": "d87bd706f85bcad6", + "gsd-core/workflows/secure-phase.md": "9bec6635ee1cbaed", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "230a658de9c017a6", "gsd-core/workflows/settings-integrations.md": "a1d146d6bfd14db5", @@ -297,7 +297,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "f2590cb6ddbfad94", "gsd-core/workflows/sketch.md": "e2063966439af8c1", "gsd-core/workflows/smart-entry.md": "6c707b959a41900b", - "gsd-core/workflows/spec-phase.md": "858184a1966d4d79", + "gsd-core/workflows/spec-phase.md": "44bd9c50b42e73fb", "gsd-core/workflows/spike-wrap-up.md": "a34d1e9ec15ae904", "gsd-core/workflows/spike.md": "89f299c07ac0edd2", "gsd-core/workflows/stats.md": "1db01bd96aa570fe", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index f16b51a78..870503ea7 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "beeac940d3a10e76", "agents/gsd-research-synthesizer.md": "6315f016d55176f4", "agents/gsd-roadmapper.md": "d28e7d4bac46dde2", - "agents/gsd-security-auditor.md": "5ff44ee432387d93", + "agents/gsd-security-auditor.md": "1e6a10833f556e4c", "agents/gsd-ui-auditor.md": "d824acc3b2a18c53", "agents/gsd-ui-checker.md": "c6c24e8066470830", "agents/gsd-ui-researcher.md": "0f5be5e55501f7e8", @@ -109,7 +109,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "2525f1ae8b086828", - "gsd-core/bin/gsd-tools.cjs": "3fa86b509ea5c8fc", + "gsd-core/bin/gsd-tools.cjs": "db16ded31dd6eecf", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -269,7 +269,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "2926457ed07b4500", "gsd-core/workflows/add-phase.md": "0d90083e9c17bec1", - "gsd-core/workflows/add-tests.md": "1837b9d2affa6913", + "gsd-core/workflows/add-tests.md": "37f031c4b3236a32", "gsd-core/workflows/add-todo.md": "d483ebe8edcf193a", "gsd-core/workflows/ai-integration-phase.md": "b79f320d59a68773", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -282,7 +282,7 @@ "gsd-core/workflows/code-review-fix.md": "c57af378033b1b58", "gsd-core/workflows/code-review.md": "32c37bcbec8b8698", "gsd-core/workflows/complete-milestone.md": "1400a4856f592f3e", - "gsd-core/workflows/debug.md": "a73d8018986131ba", + "gsd-core/workflows/debug.md": "c02dec84c8a346b9", "gsd-core/workflows/diagnose-issues.md": "cd582747131726e3", "gsd-core/workflows/discovery-phase.md": "7dcf150998559c11", "gsd-core/workflows/discuss-phase-assumptions.md": "c25c6a6c633d71b6", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", "gsd-core/workflows/review.md": "3ba8bb85c8ede5b8", "gsd-core/workflows/scan.md": "47371c2073d6c0be", - "gsd-core/workflows/secure-phase.md": "c55975672c4e1895", + "gsd-core/workflows/secure-phase.md": "3063b0b6f7b56d46", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "bf8ea69c8f7ae019", "gsd-core/workflows/settings-integrations.md": "166ca51b1f33c2a3", @@ -367,7 +367,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "5be73b7bdf96b539", "gsd-core/workflows/sketch.md": "373bc0d83368a411", "gsd-core/workflows/smart-entry.md": "c8fc316358cdcd7b", - "gsd-core/workflows/spec-phase.md": "d6e23579376c9e91", + "gsd-core/workflows/spec-phase.md": "239b6e6cbddaf8c2", "gsd-core/workflows/spike-wrap-up.md": "4a3bf1058691e55d", "gsd-core/workflows/spike.md": "f741bead24d5669a", "gsd-core/workflows/stats.md": "e7852c5d8f2b05a9", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 7a187e3b0..5eb2530ec 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "425a7df7f37a5c06", "agents/gsd-research-synthesizer.md": "9d31c87fc2c87ffa", "agents/gsd-roadmapper.md": "64dce5d5f9fa5654", - "agents/gsd-security-auditor.md": "e4d35ada4ea67d7f", + "agents/gsd-security-auditor.md": "ed330ecbd9dbc377", "agents/gsd-ui-auditor.md": "86797e85f718dfac", "agents/gsd-ui-checker.md": "cfc8a3bac0a0ef5b", "agents/gsd-ui-researcher.md": "8799b6013e06ae49", @@ -38,7 +38,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "3a3409215044af9f", - "gsd-core/bin/gsd-tools.cjs": "e979f092ce2b15ee", + "gsd-core/bin/gsd-tools.cjs": "b3f927ee4b4c4711", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -198,7 +198,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "79a453a2270483fb", "gsd-core/workflows/add-phase.md": "b4c4b32c111a116a", - "gsd-core/workflows/add-tests.md": "63390575b418e270", + "gsd-core/workflows/add-tests.md": "02f9e11ad2371c00", "gsd-core/workflows/add-todo.md": "5fe3ddc3227e0e22", "gsd-core/workflows/ai-integration-phase.md": "ddab2912d025db65", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -211,7 +211,7 @@ "gsd-core/workflows/code-review-fix.md": "e829d3baf9901b54", "gsd-core/workflows/code-review.md": "50a05ab8957bd05f", "gsd-core/workflows/complete-milestone.md": "f1866541148dc291", - "gsd-core/workflows/debug.md": "15cf6999e85dcc8c", + "gsd-core/workflows/debug.md": "639348c8657e7147", "gsd-core/workflows/diagnose-issues.md": "16f2d2a85335641f", "gsd-core/workflows/discovery-phase.md": "6161c60d752d0058", "gsd-core/workflows/discuss-phase-assumptions.md": "3a1e215890d2b3f4", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "a0443839f1f83c2d", "gsd-core/workflows/review.md": "761dd1ae5be40613", "gsd-core/workflows/scan.md": "b28f65d88c522767", - "gsd-core/workflows/secure-phase.md": "f2957d4b88fb3746", + "gsd-core/workflows/secure-phase.md": "a503dc469fd7a252", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "49be159144d7f426", "gsd-core/workflows/settings-integrations.md": "1dce76db0aca08a5", @@ -296,7 +296,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "f1ece50ac65ea281", "gsd-core/workflows/sketch.md": "d5887983e62b574a", "gsd-core/workflows/smart-entry.md": "47f5c5e8608e5f7a", - "gsd-core/workflows/spec-phase.md": "6c8835fc65ea1cb8", + "gsd-core/workflows/spec-phase.md": "6ace28390408475f", "gsd-core/workflows/spike-wrap-up.md": "367325e6d567b904", "gsd-core/workflows/spike.md": "cdacb2887a321e11", "gsd-core/workflows/stats.md": "49a991d0d3905a56", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index c363502d4..c274dadf3 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "60573a38d3dfd9fe", "agents/gsd-research-synthesizer.md": "1f7cd286c5783c86", "agents/gsd-roadmapper.md": "277e0a3252553ab7", - "agents/gsd-security-auditor.md": "0113435969e869c4", + "agents/gsd-security-auditor.md": "30b1f87cdfc05de1", "agents/gsd-ui-auditor.md": "9b988b95d28e56ed", "agents/gsd-ui-checker.md": "e078ea5a07313976", "agents/gsd-ui-researcher.md": "cc9578c4f686d926", @@ -109,7 +109,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -269,7 +269,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "485c4b6673ddf832", "gsd-core/workflows/add-phase.md": "0d90083e9c17bec1", - "gsd-core/workflows/add-tests.md": "ecb959f321820e5e", + "gsd-core/workflows/add-tests.md": "6e54f3d71a202671", "gsd-core/workflows/add-todo.md": "5082510bf0449204", "gsd-core/workflows/ai-integration-phase.md": "c85c6afdc64f0da6", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -282,7 +282,7 @@ "gsd-core/workflows/code-review-fix.md": "722416b71b31c5fc", "gsd-core/workflows/code-review.md": "506412604f767adc", "gsd-core/workflows/complete-milestone.md": "59753bf44d4300da", - "gsd-core/workflows/debug.md": "a72d830ac3df74aa", + "gsd-core/workflows/debug.md": "28c964bceb6f03af", "gsd-core/workflows/diagnose-issues.md": "210b5b313e8a559a", "gsd-core/workflows/discovery-phase.md": "ca7b2be46e59e862", "gsd-core/workflows/discuss-phase-assumptions.md": "3ef1df313e715387", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", "gsd-core/workflows/review.md": "f8109b9ec1f56962", "gsd-core/workflows/scan.md": "47371c2073d6c0be", - "gsd-core/workflows/secure-phase.md": "e8855104c1e0417c", + "gsd-core/workflows/secure-phase.md": "4977cf9e0462745b", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "edd858cd6cfddaf1", "gsd-core/workflows/settings-integrations.md": "3ea8095d5fad891b", @@ -367,7 +367,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "888c0548e63197b3", "gsd-core/workflows/sketch.md": "34a0c10fa56af7ea", "gsd-core/workflows/smart-entry.md": "7ffe4fdb93935400", - "gsd-core/workflows/spec-phase.md": "c231f8eb723e7604", + "gsd-core/workflows/spec-phase.md": "a5f3de15eeb7ce72", "gsd-core/workflows/spike-wrap-up.md": "458230d4d7c44b09", "gsd-core/workflows/spike.md": "8e63427fc146b45c", "gsd-core/workflows/stats.md": "e7852c5d8f2b05a9", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 43ff9373a..cd7ac3baa 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -30,7 +30,7 @@ ".kimi/hooks/managed-hooks-registry.cjs": "08ec2585a3f8f132", ".kimi/package.json": "dbf8353f77358bc1", "agents/gsd.md": "60fee7782ae4f2c6", - "agents/gsd.yaml": "253a23ddda06c6c2", + "agents/gsd.yaml": "b5f16c9fcf92cbff", "agents/subagents/gsd-advisor-researcher.md": "81bdc6cbd8fcde40", "agents/subagents/gsd-advisor-researcher.yaml": "662ced4837207406", "agents/subagents/gsd-ai-researcher.md": "f4cd2f17d2c3e35f", @@ -87,8 +87,8 @@ "agents/subagents/gsd-research-synthesizer.yaml": "898104ab3bb0b81a", "agents/subagents/gsd-roadmapper.md": "62359d6876022b48", "agents/subagents/gsd-roadmapper.yaml": "679772cb14f3a015", - "agents/subagents/gsd-security-auditor.md": "e621d1ee6b7aee6c", - "agents/subagents/gsd-security-auditor.yaml": "fe8a4345cc13571d", + "agents/subagents/gsd-security-auditor.md": "c6108af63b5f481d", + "agents/subagents/gsd-security-auditor.yaml": "924783f1da6777b4", "agents/subagents/gsd-ui-auditor.md": "e4a319070959ebbc", "agents/subagents/gsd-ui-auditor.yaml": "3fc98c1d9e8f10fd", "agents/subagents/gsd-ui-checker.md": "07cd4e382ca55994", @@ -102,7 +102,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -262,7 +262,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", - "gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2", + "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", @@ -275,7 +275,7 @@ "gsd-core/workflows/code-review-fix.md": "2e113d1f4350a075", "gsd-core/workflows/code-review.md": "334c90c401f291f8", "gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2", - "gsd-core/workflows/debug.md": "849d7e5b9c12dcae", + "gsd-core/workflows/debug.md": "c581e89aa9d9d71e", "gsd-core/workflows/diagnose-issues.md": "67c058fc7ae6026b", "gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761", "gsd-core/workflows/discuss-phase-assumptions.md": "f5b765d33eba4f88", @@ -351,7 +351,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", @@ -360,7 +360,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "b767a1d3db129a8a", "gsd-core/workflows/sketch.md": "88cfdf4edcf222ab", "gsd-core/workflows/smart-entry.md": "449238eb94abe187", - "gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f", + "gsd-core/workflows/spec-phase.md": "fa8669b75392cf6c", "gsd-core/workflows/spike-wrap-up.md": "da0dcb252c54fa82", "gsd-core/workflows/spike.md": "7813e120577282d4", "gsd-core/workflows/stats.md": "76a42cbeaf6007c2", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 8cbf839b3..0e3348de8 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "dae210ae0b3c6e2b", "agents/gsd-research-synthesizer.md": "e02c6ad5d1b74171", "agents/gsd-roadmapper.md": "1658a40b20d8b575", - "agents/gsd-security-auditor.md": "e36e436c0d5c26d5", + "agents/gsd-security-auditor.md": "64aa435793af52ec", "agents/gsd-ui-auditor.md": "e810012e685b2466", "agents/gsd-ui-checker.md": "4f88fd4c9d4c56a3", "agents/gsd-ui-researcher.md": "ecb617901cb7ad06", @@ -109,7 +109,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -269,7 +269,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "08289088a88d410f", "gsd-core/workflows/add-phase.md": "5154a5fb3e0d4370", - "gsd-core/workflows/add-tests.md": "81d0a836878e1a10", + "gsd-core/workflows/add-tests.md": "b4deb88c74d491c7", "gsd-core/workflows/add-todo.md": "72fcef9fd0cafda5", "gsd-core/workflows/ai-integration-phase.md": "90e5f97018715b18", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -282,7 +282,7 @@ "gsd-core/workflows/code-review-fix.md": "adb9388bb157610c", "gsd-core/workflows/code-review.md": "ae6bcbd1575aeec4", "gsd-core/workflows/complete-milestone.md": "614299b2c08e66c3", - "gsd-core/workflows/debug.md": "9d4a8afc8d36be93", + "gsd-core/workflows/debug.md": "437b47e14eba8786", "gsd-core/workflows/diagnose-issues.md": "2971c699d52f1b85", "gsd-core/workflows/discovery-phase.md": "724408336596c50c", "gsd-core/workflows/discuss-phase-assumptions.md": "92e43cd12200c610", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "ad9f06a10bab8cc0", "gsd-core/workflows/review.md": "2d28a6683ff587de", "gsd-core/workflows/scan.md": "ad8ebcad4626d4a8", - "gsd-core/workflows/secure-phase.md": "e9a488cec3b4efdc", + "gsd-core/workflows/secure-phase.md": "71e6e689e80288ec", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "252b0d3edc315339", "gsd-core/workflows/settings-integrations.md": "d1711a95f44fdbde", @@ -367,7 +367,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "681800323681c5c6", "gsd-core/workflows/sketch.md": "fcb7af914159ef7b", "gsd-core/workflows/smart-entry.md": "2a253fe437496eea", - "gsd-core/workflows/spec-phase.md": "cb6cbe6eac816b0a", + "gsd-core/workflows/spec-phase.md": "5c4a3001095ca722", "gsd-core/workflows/spike-wrap-up.md": "30a73aa19a1db271", "gsd-core/workflows/spike.md": "f8b07d411473560a", "gsd-core/workflows/stats.md": "846cd2808461ccdc", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index ece2925e8..15e4a4e56 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -5,7 +5,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -165,7 +165,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", - "gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2", + "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", @@ -178,7 +178,7 @@ "gsd-core/workflows/code-review-fix.md": "2e113d1f4350a075", "gsd-core/workflows/code-review.md": "334c90c401f291f8", "gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2", - "gsd-core/workflows/debug.md": "849d7e5b9c12dcae", + "gsd-core/workflows/debug.md": "c581e89aa9d9d71e", "gsd-core/workflows/diagnose-issues.md": "d6d978fddfd5da8d", "gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761", "gsd-core/workflows/discuss-phase-assumptions.md": "f5b765d33eba4f88", @@ -254,7 +254,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", @@ -263,7 +263,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "838c701bd072ae73", "gsd-core/workflows/sketch.md": "c7725562b3efd311", "gsd-core/workflows/smart-entry.md": "449238eb94abe187", - "gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f", + "gsd-core/workflows/spec-phase.md": "fa8669b75392cf6c", "gsd-core/workflows/spike-wrap-up.md": "0b24057c340a8a17", "gsd-core/workflows/spike.md": "9134cdc3b75282c9", "gsd-core/workflows/stats.md": "76a42cbeaf6007c2", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index f1213ed48..256b73ba0 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "b5baac64a15c85e2", "agents/gsd-research-synthesizer.md": "6cd9b501dc97bd50", "agents/gsd-roadmapper.md": "c357a77ab919e9e5", - "agents/gsd-security-auditor.md": "d6d8f82501f10b92", + "agents/gsd-security-auditor.md": "9e3bc9a62036352c", "agents/gsd-ui-auditor.md": "47937e784c9ae541", "agents/gsd-ui-checker.md": "7d708c53a106f748", "agents/gsd-ui-researcher.md": "e3768304c1c77753", @@ -38,7 +38,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "6e98d76e955e35a2", - "gsd-core/bin/gsd-tools.cjs": "0341a6cf54b9acbe", + "gsd-core/bin/gsd-tools.cjs": "6454021dec4d9563", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -198,7 +198,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "cee41d5fc52d4e37", "gsd-core/workflows/add-phase.md": "cc63a3e108c9a065", - "gsd-core/workflows/add-tests.md": "b62585823e5636bb", + "gsd-core/workflows/add-tests.md": "7cd945118e83246e", "gsd-core/workflows/add-todo.md": "5e037b444e657557", "gsd-core/workflows/ai-integration-phase.md": "afdc7c15f03a95fc", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -211,7 +211,7 @@ "gsd-core/workflows/code-review-fix.md": "f3725ae9d685bed2", "gsd-core/workflows/code-review.md": "29125604bed2c467", "gsd-core/workflows/complete-milestone.md": "40085d32b15805c8", - "gsd-core/workflows/debug.md": "c23d067580b09f63", + "gsd-core/workflows/debug.md": "a4e4c2f6d004460e", "gsd-core/workflows/diagnose-issues.md": "652ae26975f82242", "gsd-core/workflows/discovery-phase.md": "6161c60d752d0058", "gsd-core/workflows/discuss-phase-assumptions.md": "18712f78bb960ec8", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "7f20769f302e5427", "gsd-core/workflows/review.md": "bcbc20cb8df021cc", "gsd-core/workflows/scan.md": "949692db4834dd27", - "gsd-core/workflows/secure-phase.md": "6758f1acf4113e9e", + "gsd-core/workflows/secure-phase.md": "ef7b5ad194b687bf", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "5e05212fb5cba531", "gsd-core/workflows/settings-integrations.md": "29c5de27fbbb18e9", @@ -296,7 +296,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "89e0eab2af946b04", "gsd-core/workflows/sketch.md": "483387542d6fc3af", "gsd-core/workflows/smart-entry.md": "d309710bcabd4675", - "gsd-core/workflows/spec-phase.md": "0d888493bde5146b", + "gsd-core/workflows/spec-phase.md": "546b3a8eb1a97013", "gsd-core/workflows/spike-wrap-up.md": "fe04cb3d30dcbc6c", "gsd-core/workflows/spike.md": "fad28f2ecf2dac52", "gsd-core/workflows/stats.md": "89a08155b92f4a2d", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index aa3551cf0..4a5148a3f 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "ddf7794e81300032", "agents/gsd-research-synthesizer.md": "a124b00271748d07", "agents/gsd-roadmapper.md": "493ef92b42b12cf4", - "agents/gsd-security-auditor.md": "fbd3798eec23651b", + "agents/gsd-security-auditor.md": "1e2eea5b2ab16d6e", "agents/gsd-ui-auditor.md": "771ae08260534d5c", "agents/gsd-ui-checker.md": "7ecd910efa6eb00e", "agents/gsd-ui-researcher.md": "9e1a84a55a4cac99", @@ -38,7 +38,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "de4627dff103d527", - "gsd-core/bin/gsd-tools.cjs": "75bf5691ded4927b", + "gsd-core/bin/gsd-tools.cjs": "c28c9076058c5e73", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -198,7 +198,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "1851e97996d445d5", "gsd-core/workflows/add-phase.md": "09bdebbc62f3310e", - "gsd-core/workflows/add-tests.md": "ff3a598cdbc5aa6e", + "gsd-core/workflows/add-tests.md": "8ad08d29415f8f43", "gsd-core/workflows/add-todo.md": "0fe07cbd29ec252b", "gsd-core/workflows/ai-integration-phase.md": "d469eb120e52de0f", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -211,7 +211,7 @@ "gsd-core/workflows/code-review-fix.md": "f2761f7f8c4a5674", "gsd-core/workflows/code-review.md": "47663a2922756c5e", "gsd-core/workflows/complete-milestone.md": "6e918b72bd885426", - "gsd-core/workflows/debug.md": "197d3642a6704de5", + "gsd-core/workflows/debug.md": "7783c3cb81fef70d", "gsd-core/workflows/diagnose-issues.md": "9274b11a3db98c65", "gsd-core/workflows/discovery-phase.md": "b32b6197b66c9a13", "gsd-core/workflows/discuss-phase-assumptions.md": "e376b1cf29379df4", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "f242e4c8aba18ea2", "gsd-core/workflows/review.md": "835cf8c9594f17c1", "gsd-core/workflows/scan.md": "63631467651d9ca8", - "gsd-core/workflows/secure-phase.md": "6cc236e53c2e7d56", + "gsd-core/workflows/secure-phase.md": "4a647aec1e4d2dfe", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "39e66386f6c48025", "gsd-core/workflows/settings-integrations.md": "f8f756709ec02363", @@ -296,7 +296,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "dbec602d104cb951", "gsd-core/workflows/sketch.md": "44ff275150b6d045", "gsd-core/workflows/smart-entry.md": "d8018578571f08d5", - "gsd-core/workflows/spec-phase.md": "ab8fad8038b14cad", + "gsd-core/workflows/spec-phase.md": "7b8873b0cd3eb9d7", "gsd-core/workflows/spike-wrap-up.md": "6fe876cb87603bc3", "gsd-core/workflows/spike.md": "c042a437baaf10ad", "gsd-core/workflows/stats.md": "e4f84542fb5721ef", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 7bec92ba7..625774ee5 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "f6697b316b5995ba", "agents/gsd-research-synthesizer.md": "04036f38c1d373ea", "agents/gsd-roadmapper.md": "fb62e1e3de84b5f9", - "agents/gsd-security-auditor.md": "13b660e2d336ed8e", + "agents/gsd-security-auditor.md": "cacf711cb835300b", "agents/gsd-ui-auditor.md": "20cc99872e9b998b", "agents/gsd-ui-checker.md": "56698909c270b130", "agents/gsd-ui-researcher.md": "c348aa3ff8412ecb", @@ -38,7 +38,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "5636ca0b726871b2", - "gsd-core/bin/gsd-tools.cjs": "dd746ae946645155", + "gsd-core/bin/gsd-tools.cjs": "568e7c6bdd415c1a", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -198,7 +198,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "61169973a10b1321", "gsd-core/workflows/add-phase.md": "f9b1f7cc229b57f8", - "gsd-core/workflows/add-tests.md": "b5e03955cb29d7d8", + "gsd-core/workflows/add-tests.md": "d51e69bb433535a4", "gsd-core/workflows/add-todo.md": "cab0d8215579fbdd", "gsd-core/workflows/ai-integration-phase.md": "8948988717506320", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", @@ -211,7 +211,7 @@ "gsd-core/workflows/code-review-fix.md": "b99b1f20bb27c291", "gsd-core/workflows/code-review.md": "faa87faf07ae765a", "gsd-core/workflows/complete-milestone.md": "f463bf4e86ac26f6", - "gsd-core/workflows/debug.md": "52243eb936a43150", + "gsd-core/workflows/debug.md": "d0ee63e547f4d997", "gsd-core/workflows/diagnose-issues.md": "447072aa72385271", "gsd-core/workflows/discovery-phase.md": "7dcf150998559c11", "gsd-core/workflows/discuss-phase-assumptions.md": "b091b3d3e580dd29", @@ -287,7 +287,7 @@ "gsd-core/workflows/resume-project.md": "82cfe1b8cb17c085", "gsd-core/workflows/review.md": "3e72508a5dccd45a", "gsd-core/workflows/scan.md": "12c11b2edc165df9", - "gsd-core/workflows/secure-phase.md": "7bf923689bf58288", + "gsd-core/workflows/secure-phase.md": "185a15d389951e6e", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "2f86ec7b998f9485", "gsd-core/workflows/settings-integrations.md": "b082fc518b484c07", @@ -296,7 +296,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "10063f56c2c7f141", "gsd-core/workflows/sketch.md": "25c1f8f7acfb1da9", "gsd-core/workflows/smart-entry.md": "bd81482cb6a7ac53", - "gsd-core/workflows/spec-phase.md": "c05348d644f9d77a", + "gsd-core/workflows/spec-phase.md": "744faedd273da0f7", "gsd-core/workflows/spike-wrap-up.md": "8029ca4effb5b716", "gsd-core/workflows/spike.md": "a94af5171bb9d2ce", "gsd-core/workflows/stats.md": "d58325fc3fb8d1b6", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index f9aa16f65..fe60606c9 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -29,7 +29,7 @@ "agents/gsd-project-researcher.md": "f572892f138734ff", "agents/gsd-research-synthesizer.md": "29949bf3f049a8f1", "agents/gsd-roadmapper.md": "840ac933e3b094f9", - "agents/gsd-security-auditor.md": "b7202c44366697dd", + "agents/gsd-security-auditor.md": "4b86fa11ebda981e", "agents/gsd-ui-auditor.md": "76f446c50edf81f8", "agents/gsd-ui-checker.md": "15949ccab982b71c", "agents/gsd-ui-researcher.md": "0e8ec8509d476904", @@ -109,7 +109,7 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", + "gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", @@ -269,7 +269,7 @@ "gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335", "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", - "gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2", + "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", @@ -282,7 +282,7 @@ "gsd-core/workflows/code-review-fix.md": "2e113d1f4350a075", "gsd-core/workflows/code-review.md": "334c90c401f291f8", "gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2", - "gsd-core/workflows/debug.md": "849d7e5b9c12dcae", + "gsd-core/workflows/debug.md": "c581e89aa9d9d71e", "gsd-core/workflows/diagnose-issues.md": "aa8d787db8f3c46c", "gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761", "gsd-core/workflows/discuss-phase-assumptions.md": "f5b765d33eba4f88", @@ -358,7 +358,7 @@ "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", "gsd-core/workflows/review.md": "ad7c0f372ed986ae", "gsd-core/workflows/scan.md": "003883d71c37da7d", - "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", + "gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", "gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44", "gsd-core/workflows/settings-integrations.md": "70515c5838fb9826", @@ -367,7 +367,7 @@ "gsd-core/workflows/sketch-wrap-up.md": "86db87b16548117e", "gsd-core/workflows/sketch.md": "e96f1866d3e60cab", "gsd-core/workflows/smart-entry.md": "449238eb94abe187", - "gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f", + "gsd-core/workflows/spec-phase.md": "fa8669b75392cf6c", "gsd-core/workflows/spike-wrap-up.md": "d643447e16e95753", "gsd-core/workflows/spike.md": "265e5c5a13ea0deb", "gsd-core/workflows/stats.md": "76a42cbeaf6007c2", diff --git a/tests/milestone.test.cjs b/tests/milestone.test.cjs index 915c7a3d1..60f96aed0 100644 --- a/tests/milestone.test.cjs +++ b/tests/milestone.test.cjs @@ -113,6 +113,131 @@ describe('milestone complete command', () => { assert.ok(milestones.includes('Set up project infrastructure')); }); + test('#2118 — --dry-run does NOT mutate: no archive, no STATE.md rewrite, no phase move', () => { + writeRoadmap(tmpDir, `# Roadmap v1.0 MVP\n\n### Phase 1: Foundation\n**Goal:** Setup\n`); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), + `# Requirements\n\n- [x] User auth\n`, + ); + writeState(tmpDir); + const phasePath = mkPhaseDir(tmpDir, '01-foundation', { oneLiner: 'Set up project infrastructure' }); + + // Capture pre-state + const stateBefore = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + + const result = runGsdTools('milestone complete v1.0 --name Test --dry-run', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.dry_run, true, 'dry_run must be true'); + assert.strictEqual(output.version, 'v1.0'); + assert.ok(output.stats.phases >= 1, 'should count at least 1 phase'); + assert.ok(output.would_archive.roadmap, 'should list roadmap archive plan'); + assert.ok(output.would_archive.requirements, 'should list requirements archive plan'); + assert.ok( + output.would_archive.phases.includes('01-foundation'), + 'should list phase dir for archive', + ); + assert.ok( + Array.isArray(output.accomplishments) && output.accomplishments.includes('Set up project infrastructure'), + 'dry-run preview should surface accomplishments from phase SUMMARY one-liners (#2118)', + ); + + // CRITICAL: no mutations occurred + assert.ok( + fs.existsSync(path.join(tmpDir, '.planning', 'ROADMAP.md')), + 'ROADMAP.md must NOT be archived (dry-run)', + ); + assert.ok( + fs.existsSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md')), + 'REQUIREMENTS.md must NOT be archived (dry-run)', + ); + assert.ok( + fs.existsSync(phasePath), + 'phase directory must NOT be moved (dry-run)', + ); + assert.ok( + !fs.existsSync(path.join(tmpDir, '.planning', 'MILESTONES.md')), + 'MILESTONES.md must NOT be created (dry-run)', + ); + assert.strictEqual( + fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'), + stateBefore, + 'STATE.md must be unchanged (dry-run)', + ); + assert.ok( + !fs.existsSync(path.join(tmpDir, '.planning', 'milestones')), + 'archive dir must NOT be created (dry-run) — platformEnsureDir must be gated', + ); + }); + + test('#2118 — --dry-run --no-archive-phases omits phase list from preview', () => { + writeRoadmap(tmpDir, `# Roadmap v1.0 MVP\n\n### Phase 1: Foundation\n**Goal:** Setup\n`); + writeState(tmpDir); + mkPhaseDir(tmpDir, '01-foundation'); + + const result = runGsdTools('milestone complete v1.0 --dry-run --no-archive-phases', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.dry_run, true); + assert.deepStrictEqual(output.would_archive.phases, [], 'phases list must be empty with --no-archive-phases'); + }); + + test('#2118 — --dry-run --force bypasses the unstarted-phases guard', () => { + writeRoadmap(tmpDir, `# Roadmap v1.0 MVP\n\n### Phase 1: Foundation\n**Goal:** Setup\n`); + writeState(tmpDir, 'milestone: v1.0\n'); + // No phase directory for Phase 1 — guard would block without --force + + // Capture pre-state + const stateBefore = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + + const result = runGsdTools('milestone complete v1.0 --dry-run --force', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.dry_run, true, 'preview should run past the guard with --force'); + + // CRITICAL: --force must still be a zero-mutation dry-run preview + assert.ok( + !fs.existsSync(path.join(tmpDir, '.planning', 'milestones')), + 'archive dir must NOT be created (dry-run --force) — platformEnsureDir must be gated', + ); + assert.strictEqual( + fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'), + stateBefore, + 'STATE.md must be unchanged (dry-run --force)', + ); + assert.ok( + !fs.existsSync(path.join(tmpDir, '.planning', 'MILESTONES.md')), + 'MILESTONES.md must NOT be created (dry-run --force)', + ); + }); + + test('#2118 — --dry-run --raw emits structured preview JSON, not the literal "dry-run" string', () => { + writeRoadmap(tmpDir, `# Roadmap v1.0 MVP\n\n### Phase 1: Foundation\n**Goal:** Setup\n`); + writeState(tmpDir); + mkPhaseDir(tmpDir, '01-foundation', { oneLiner: 'Set up project infrastructure' }); + + const result = runGsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--dry-run', '--raw'], tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + // Before the fix, output(dryRunResult, raw, 'dry-run') meant --raw discarded + // the structured payload and printed only the literal string "dry-run", + // which is not parseable JSON. + let output; + assert.doesNotThrow( + () => { output = JSON.parse(result.output); }, + `--dry-run --raw output must be parseable JSON, not the literal "dry-run" string; got ${JSON.stringify(result.output)}`, + ); + assert.strictEqual(output.dry_run, true, 'dry_run must be true, not the literal string "dry-run"'); + assert.strictEqual(output.version, 'v1.0'); + assert.ok( + Array.isArray(output.accomplishments) && output.accomplishments.includes('Set up project infrastructure'), + 'structured preview surviving --raw should include accomplishments', + ); + }); + test('prepends to existing MILESTONES.md (reverse chronological)', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'MILESTONES.md'), diff --git a/tests/roadmap-parser.test.cjs b/tests/roadmap-parser.test.cjs index ec46fa630..493cc9c84 100644 --- a/tests/roadmap-parser.test.cjs +++ b/tests/roadmap-parser.test.cjs @@ -1964,3 +1964,167 @@ describe('feat-3594: roadmap parser does not crash on ANY corpus fixture', () => }); }); } + +// ─── #2200: currentMilestoneRawRanges scopes phase-complete writes ──────────── +// The phase-complete roadmap mutators (checkbox-flip + Plans writer) must mutate +// only within the active milestone so they cannot touch a backticked prose +// literal, a Backlog entry, or a same-numbered phase in a shipped milestone. +// This tests the scoping helper the fix rests on (the mutators' command path is +// covered by the existing phase-complete suite; gsd-test confirms no regression). +{ + const { describe: d3, test: t3 } = require('node:test'); + const a3 = require('node:assert/strict'); + const fs3 = require('node:fs'); + const path3 = require('node:path'); + const { createTempProject: ctp3, cleanup: cu3 } = require('./helpers.cjs'); + const rp3 = require('../gsd-core/bin/lib/roadmap-parser.cjs'); + + d3('#2200 currentMilestoneRawRanges — scopes writes to the active milestone', () => { + t3('the active window contains the active phase bullet, excludes Backlog + prose + shipped', () => { + const tmpDir = ctp3('fix-2200-'); + try { + // Shipped milestone (in a
block) + Backlog + a backticked prose + // literal all come BEFORE the active milestone — the typical layout. + const roadmap = [ + '# Roadmap', '', + '## Backlog', '- [ ] **Phase 1: Some Future Idea**', '', + '> See `- [ ] **Phase 1: Alpha**` in the active milestone.', '', + '
✅ v0.9 Old', + '- [x] **Phase 1: Legacy**', + '### Phase 1: Legacy', + '**Plans:** 9/9 plans complete', + '
', '', + '## v1.0 — Active', '', + '- [ ] **Phase 1: Alpha**', '', + '### Phase 1: Alpha', + '**Plans:** 0/1 plans complete', '', + ].join('\n'); + fs3.writeFileSync(path3.join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + fs3.writeFileSync(path3.join(tmpDir, '.planning', 'STATE.md'), '---\nmilestone: v1.0\ncurrent_phase: 1\n---\n'); + const ranges = rp3.currentMilestoneRawRanges(roadmap, tmpDir); + a3.ok(ranges, 'a versioned active milestone must yield ranges'); + const primary = roadmap.slice(ranges.primary.start, ranges.primary.end); + a3.ok(primary.includes('- [ ] **Phase 1: Alpha**'), 'active phase bullet is inside the window'); + a3.ok(!primary.includes('Some Future Idea'), 'a Backlog entry is outside the active window'); + a3.ok(!primary.includes('See `- [ ]'), 'a backticked prose literal is outside the active window'); + a3.ok(!primary.includes('9/9 plans complete'), 'a shipped milestone plan-count line is outside the active window'); + } finally { + cu3(tmpDir); + } + }); + + t3('returns null without a versioned active milestone (whole-content fallback)', () => { + a3.strictEqual(rp3.currentMilestoneRawRanges('# Roadmap\n- [ ] **Phase 1: X**\n', undefined), null); + }); + }); +} +// ─── #2199: bullet/em-dash ROADMAP phase resolution ─────────────────────────── +// Self-contained block: phase lookup + milestone filter must accept bullet/ +// checkbox entries with an em-dash/en-dash/hyphen/colon separator, not just the +// ATX-heading + colon form. Previously such an entry resolved found:false and +// `Phase null` was written into STATE.md; a bullet-only ROADMAP collapsed the +// milestone filter to a zero-count pass-all. +{ + const { describe: d2, test: t2, beforeEach: be2, afterEach: ae2 } = require('node:test'); + const a2 = require('node:assert/strict'); + const fs2 = require('node:fs'); + const path2 = require('node:path'); + const { createTempProject: ctp2, cleanup: cu2 } = require('./helpers.cjs'); + const rp2 = require('../gsd-core/bin/lib/roadmap-parser.cjs'); + const writeRoadmap2 = (d, c) => fs2.writeFileSync(path2.join(d, '.planning', 'ROADMAP.md'), c); + + d2('#2199 roadmap bullet/em-dash phase resolution', () => { + let tmpDir; + be2(() => { tmpDir = ctp2('fix-2199-'); }); + ae2(() => { cu2(tmpDir); }); + + t2('an all-bullet em-dash ROADMAP resolves each phase (no Phase null)', () => { + writeRoadmap2(tmpDir, [ + '# Roadmap', '', '## v1.0 Active', '', + '- [ ] **Phase 1 — Authentication**: login flow', + '- [ ] **Phase 2 — Authorization**: RBAC', + '- [x] **Phase 3 — Audit Logging**: events', + '', + ].join('\n')); + const p1 = rp2.getRoadmapPhaseInternal(tmpDir, '1'); + a2.ok(p1 && p1.found, 'Phase 1 must resolve on a bullet ROADMAP'); + a2.strictEqual(p1.phase_name, 'Authentication'); + const p2 = rp2.getRoadmapPhaseInternal(tmpDir, '2'); + a2.ok(p2 && p2.found); + a2.strictEqual(p2.phase_name, 'Authorization'); + const p3 = rp2.getRoadmapPhaseInternal(tmpDir, '3'); + a2.ok(p3 && p3.found, 'a checked [x] bullet must also resolve'); + a2.strictEqual(p3.phase_name, 'Audit Logging'); + const absent = rp2.getRoadmapPhaseInternal(tmpDir, '99'); + a2.ok(!absent || !absent.found, 'an absent phase must not resolve'); + }); + + t2('bullet entries with colon / en-dash / hyphen separators all resolve', () => { + writeRoadmap2(tmpDir, [ + '# Roadmap', '', '## v1.0 Active', '', + '- [ ] **Phase 1: Colon Sep**: one', + '- [ ] **Phase 2 – En Dash**: two', + '- [ ] **Phase 3 - Hyphen Sep**: three', + '', + ].join('\n')); + a2.strictEqual(rp2.getRoadmapPhaseInternal(tmpDir, '1').phase_name, 'Colon Sep'); + a2.strictEqual(rp2.getRoadmapPhaseInternal(tmpDir, '2').phase_name, 'En Dash'); + a2.strictEqual(rp2.getRoadmapPhaseInternal(tmpDir, '3').phase_name, 'Hyphen Sep'); + }); + + t2('mixed heading + bullet forms both resolve', () => { + writeRoadmap2(tmpDir, [ + '# Roadmap', '', '## v1.0 Active', '', + '### Phase 1: Heading Form', + 'body', + '- [ ] **Phase 2 — Bullet Form**: two', + '', + ].join('\n')); + const p1 = rp2.getRoadmapPhaseInternal(tmpDir, '1'); + a2.ok(p1 && p1.found, 'heading form still resolves (no regression)'); + a2.ok(/Heading Form/.test(p1.phase_name)); + const p2 = rp2.getRoadmapPhaseInternal(tmpDir, '2'); + a2.ok(p2 && p2.found, 'bullet form resolves alongside heading form'); + a2.strictEqual(p2.phase_name, 'Bullet Form'); + }); + + t2('milestone phase-count counts bullet-form phases (not zero)', () => { + writeRoadmap2(tmpDir, [ + '# Roadmap', '', '## v1.0 Active', '', + '- [ ] **Phase 1 — One**: a', + '- [ ] **Phase 2 — Two**: b', + '- [ ] **Phase 3 — Three**: c', + '', + ].join('\n')); + const filter = rp2.getMilestonePhaseFilter(tmpDir); + a2.strictEqual(filter.phaseCount, 3, + 'a bullet-only ROADMAP must populate the milestone phase set (was a zero-count pass-all before #2199)'); + a2.ok(filter('1'), 'phase 1 dir is in the milestone set'); + a2.ok(filter('2'), 'phase 2 dir is in the milestone set'); + a2.ok(!filter('99'), 'a non-listed phase is excluded'); + }); + + t2('#2199 heading in Phase Details (full content) beats a bullet in the active scope', () => { + // The exact first-attempt regression: a bullet for the phase exists in the + // active-milestone scope, but the heading (carrying Requirements) lives in a + // Phase Details section outside that scope (only in fullContent). The heading + // MUST win — otherwise the bullet's single-line section yields null req_ids. + writeRoadmap2(tmpDir, [ + '# Roadmap', '', + '## Milestones', '', + '- 🚧 **v1.0 Active** - Phases 10-11', '', + '## v1.0 Active', '', + '- [ ] **Phase 11 — Second Active Phase**', + '', + '## Phase Details', '', + '### Phase 11: Second Active Phase', + '**Requirements**: REQ-02, REQ-03', + '', + ].join('\n')); + const p11 = rp2.getRoadmapPhaseInternal(tmpDir, '11'); + a2.ok(p11 && p11.found, 'phase 11 resolves'); + a2.ok(/REQ-02/.test(p11.section), + 'the heading section (with Requirements) must win over the scoped bullet line'); + }); + }); +} diff --git a/tests/secure-phase-single-writer.test.cjs b/tests/secure-phase-single-writer.test.cjs new file mode 100644 index 000000000..0122c2598 --- /dev/null +++ b/tests/secure-phase-single-writer.test.cjs @@ -0,0 +1,73 @@ +/** + * Regression test for #2119: /gsd-secure-phase dual SECURITY.md writers. + * + * The gsd-security-auditor agent must NOT have Write/Edit tools — the + * orchestrator (secure-phase.md Step 6) is the sole SECURITY.md writer. + * The auditor returns a structured verdict; it never writes files. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const AGENT_PATH = path.join(__dirname, '..', 'agents', 'gsd-security-auditor.md'); + +function parseYamlTools(content) { + const lines = content.split(/\r?\n/); + let inFrontmatter = false; + let inTools = false; + const tools = []; + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed === '---') { + inFrontmatter = !inFrontmatter; + if (!inFrontmatter) break; + continue; + } + if (!inFrontmatter) continue; + if (trimmed.startsWith('tools:')) { + inTools = true; + continue; + } + if (inTools) { + if (trimmed.startsWith('- ')) { + tools.push(trimmed.slice(2).trim()); + } else if (trimmed && !trimmed.startsWith('#')) { + inTools = false; + } + } + } + return tools; +} + +describe('#2119 — security auditor is return-only (no file writes)', () => { + const content = fs.readFileSync(AGENT_PATH, 'utf-8'); + + test('auditor tools do not include Write or Edit', () => { + const tools = parseYamlTools(content); + assert.ok(tools.length > 0, 'tools list should be non-empty'); + assert.ok( + !tools.includes('Write'), + `Write must not be in auditor tools (got: ${tools.join(', ')}) — orchestrator is the sole SECURITY.md writer (#2119)`, + ); + assert.ok( + !tools.includes('Edit'), + `Edit must not be in auditor tools (got: ${tools.join(', ')}) — orchestrator is the sole SECURITY.md writer (#2119)`, + ); + }); + + test('auditor description does not claim to produce SECURITY.md', () => { + const lines = content.split(/\r?\n/); + const descLine = lines.find((l) => l.startsWith('description:')); + assert.ok(descLine, 'description field must exist'); + assert.ok( + !descLine.includes('Produces SECURITY.md'), + 'description must not claim to produce SECURITY.md — auditor returns a verdict, orchestrator writes (#2119)', + ); + assert.ok( + descLine.includes('Returns structured') || descLine.includes('returns'), + 'description should state the auditor returns a structured verdict', + ); + }); +}); diff --git a/tests/secure-phase.test.cjs b/tests/secure-phase.test.cjs index b676e0795..526f249d2 100644 --- a/tests/secure-phase.test.cjs +++ b/tests/secure-phase.test.cjs @@ -57,15 +57,18 @@ describe('SECURE: gsd-security-auditor agent', () => { ); }); - test('tools include Read, Write, Bash, Glob, Grep', () => { + test('tools include Read, Bash, Glob, Grep but NOT Write or Edit (#2119)', () => { const content = fs.readFileSync(agentPath, 'utf-8'); - const requiredTools = ['Read', 'Write', 'Bash', 'Glob', 'Grep']; + const requiredTools = ['Read', 'Bash', 'Glob', 'Grep']; for (const tool of requiredTools) { assert.ok( content.includes(`- ${tool}`), `tools must include ${tool}` ); } + // #2119: auditor is return-only — orchestrator is the sole SECURITY.md writer + assert.ok(!content.includes('- Write'), 'tools must NOT include Write (#2119)'); + assert.ok(!content.includes('- Edit'), 'tools must NOT include Edit (#2119)'); }); test('has section', () => { diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 9e7b9fc87..b91272241 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -1,7 +1,7 @@ { "add-backlog.md": 7176, "add-phase.md": 7247, - "add-tests.md": 16945, + "add-tests.md": 16961, "add-todo.md": 8996, "ai-integration-phase.md": 14805, "analyze-dependencies.md": 3887, @@ -14,7 +14,7 @@ "code-review-fix.md": 24320, "code-review.md": 31916, "complete-milestone.md": 31071, - "debug.md": 13549, + "debug.md": 14241, "diagnose-issues.md": 12864, "discovery-phase.md": 8651, "discuss-phase-assumptions.md": 27302, @@ -66,7 +66,7 @@ "resume-project.md": 17270, "review.md": 47168, "scan.md": 7732, - "secure-phase.md": 13520, + "secure-phase.md": 13622, "session-report.md": 4044, "settings-advanced.md": 40019, "settings-integrations.md": 15892, @@ -75,7 +75,7 @@ "sketch-wrap-up.md": 14267, "sketch.md": 20004, "smart-entry.md": 11124, - "spec-phase.md": 31945, + "spec-phase.md": 31987, "spike-wrap-up.md": 15136, "spike.md": 24561, "stats.md": 6762,