diff --git a/commands/gsd/edit-phase.md b/commands/gsd/edit-phase.md new file mode 100644 index 000000000..8c0a60678 --- /dev/null +++ b/commands/gsd/edit-phase.md @@ -0,0 +1,35 @@ +--- +name: gsd:edit-phase +description: Edit any field of an existing roadmap phase in place, preserving number and position +argument-hint: [--force] +allowed-tools: + - Read + - Write + - Bash +--- + + +Modify any field of an existing phase in ROADMAP.md in place. + +Supports: +- Editing individual fields (title, description/goal, requirements, success criteria, depends_on) +- Full regeneration of all fields from a clarified intent +- Guarded edits: refuses in_progress/completed phases unless --force is passed +- Depends-on validation: blocks invalid references with a clear error +- Diff + confirmation before writing + + + +@~/.claude/get-shit-done/workflows/edit-phase.md + + + +Arguments: $ARGUMENTS (format: [--force]) + +Roadmap and state are resolved in-workflow via `init phase-op` and targeted reads. + + + +Execute the edit-phase workflow from @~/.claude/get-shit-done/workflows/edit-phase.md end-to-end. +Preserve all validation gates (phase existence, status guard, depends_on validation, diff + confirmation). + diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index f9d5dcc76..b87dd05e7 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -425,6 +425,28 @@ Append new phase to roadmap. /gsd-add-phase # Interactive — describe the phase ``` +### `/gsd-edit-phase` + +Edit any field of an existing roadmap phase in place. + +| Argument | Required | Description | +|----------|----------|-------------| +| `N` | Yes | Phase number to edit | + +| Flag | Description | +|------|-------------| +| `--force` | Allow editing in-progress or completed phases | + +**Prerequisites:** `.planning/ROADMAP.md` exists, phase N must exist +**Produces:** Updated phase section in ROADMAP.md (in place, number and position preserved) + +```bash +/gsd-edit-phase 5 # Edit any field of phase 5 (future phases only) +/gsd-edit-phase 5 --force # Edit phase 5 even if in-progress or completed +``` + +--- + ### `/gsd-insert-phase` Insert urgent work between phases using decimal numbering. diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 91615b319..162abe1d4 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -56,6 +56,7 @@ "/gsd-discuss-phase", "/gsd-do", "/gsd-docs-update", + "/gsd-edit-phase", "/gsd-eval-review", "/gsd-execute-phase", "/gsd-explore", @@ -145,6 +146,7 @@ "discuss-phase.md", "do.md", "docs-update.md", + "edit-phase.md", "eval-review.md", "execute-phase.md", "execute-plan.md", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 62b6f459e..b45c9dc3b 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -54,7 +54,7 @@ Full roster at `agents/gsd-*.md`. The "Primary doc" column flags whether [`docs/ --- -## Commands (85 shipped) +## Commands (86 shipped) Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md` section order; each row carries the command name, a one-line role derived from the command's frontmatter `description:`, and a link to the source file. `tests/command-count-sync.test.cjs` locks the count against the filesystem. @@ -92,6 +92,7 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md | Command | Role | Source | |---------|------|--------| | `/gsd-add-phase` | Add phase to end of current milestone in roadmap. | [commands/gsd/add-phase.md](../commands/gsd/add-phase.md) | +| `/gsd-edit-phase` | Edit any field of an existing roadmap phase in place, preserving number and position. | [commands/gsd/edit-phase.md](../commands/gsd/edit-phase.md) | | `/gsd-insert-phase` | Insert urgent work as decimal phase (e.g., 72.1) between existing phases. | [commands/gsd/insert-phase.md](../commands/gsd/insert-phase.md) | | `/gsd-remove-phase` | Remove a future phase from roadmap and renumber subsequent phases. | [commands/gsd/remove-phase.md](../commands/gsd/remove-phase.md) | | `/gsd-add-tests` | Generate tests for a completed phase based on UAT criteria and implementation. | [commands/gsd/add-tests.md](../commands/gsd/add-tests.md) | @@ -175,7 +176,7 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md --- -## Workflows (83 shipped) +## Workflows (84 shipped) Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators that commands reference internally; most are not read directly by end users. Rows below map each workflow file to its role (derived from the `` block) and, where applicable, to the command that invokes it. @@ -202,6 +203,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators | `discuss-phase.md` | Extract implementation decisions through iterative gray-area discussion. | `/gsd-discuss-phase` | | `do.md` | Route freeform text from the user to the best matching GSD command. | `/gsd-do` | | `docs-update.md` | Generate, update, and verify canonical and hand-written project documentation. | `/gsd-docs-update` | +| `edit-phase.md` | Edit any field of an existing phase in ROADMAP.md in place, preserving number and position. | `/gsd-edit-phase` | | `eval-review.md` | Retroactive audit of an implemented AI phase's evaluation coverage. | `/gsd-eval-review` | | `execute-phase.md` | Execute all plans in a phase using wave-based parallel execution. | `/gsd-execute-phase` | | `execute-plan.md` | Execute a phase prompt (PLAN.md) and create the outcome summary (SUMMARY.md). | `execute-phase.md` (per-plan subagent) | diff --git a/get-shit-done/workflows/edit-phase.md b/get-shit-done/workflows/edit-phase.md new file mode 100644 index 000000000..494f57fee --- /dev/null +++ b/get-shit-done/workflows/edit-phase.md @@ -0,0 +1,294 @@ + +Edit any field of an existing phase in ROADMAP.md in place. The phase number and position are always preserved. Guarded against in-progress and completed phases unless --force is passed. Validates depends_on references before writing. Shows a diff and requests confirmation before writing. + + + +Read all files referenced by the invoking prompt's execution_context before starting. + + + + + +Parse the command arguments: +- First argument: phase number to edit (integer or decimal) +- Optional flag: --force (allow editing in_progress/completed phases) + +Examples: + `/gsd-edit-phase 5` → phase = 5, force = false + `/gsd-edit-phase 5 --force` → phase = 5, force = true + `/gsd-edit-phase 12.1` → phase = 12.1, force = false + +If no argument provided: + +``` +ERROR: Phase number required +Usage: /gsd-edit-phase [--force] +Example: /gsd-edit-phase 5 +Example: /gsd-edit-phase 5 --force +``` + +Exit. + + + +Load phase operation context: + +```bash +INIT=$(gsd-sdk query init.phase-op "${target}") +if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi +``` + +Check `roadmap_exists` from init JSON. If false: +``` +ERROR: No roadmap found (.planning/ROADMAP.md) +Run /gsd-new-project to initialize. +``` +Exit. + + + +Read the current phase section from ROADMAP.md: + +```bash +PHASE_DATA=$(gsd-sdk query roadmap get-phase "${target}") +``` + +Parse the JSON result. If `found` is false: + +``` +ERROR: Phase {target} not found in ROADMAP.md + +Available phases can be seen with /gsd-progress. +``` + +Exit. + +Extract from the result: +- `phase_name` — the phase title +- `goal` — the phase goal/description +- `success_criteria` — array of criteria +- `section` — full raw section text (preserves depends_on, requirements, plans, etc.) + +Also parse the full section text to extract additional fields not in the SDK result: +- `depends_on` — from `**Depends on:** ...` or `**Depends on**: ...` line +- `requirements` — from `**Requirements:** ...` block if present + + + +Determine the phase status from disk. Compare against STATE.md current phase: + +```bash +ANALYZE=$(gsd-sdk query roadmap analyze) +``` + +Find the phase entry in the `phases` array. Extract `disk_status`. + +Map disk_status to a user-friendly status: +- `complete` → status = `completed` +- `planned` or `partial` → status = `in_progress` +- `empty`, `no_directory`, `discussed`, `researched` → status = `future` + +If status is `in_progress` or `completed` AND `--force` was NOT passed: + +``` +ERROR: Cannot edit Phase {target} — status is {status} + +Editing an in-progress or completed phase may invalidate executed plans. + +To edit anyway, run: + /gsd-edit-phase {target} --force +``` + +Exit. + +If `--force` was passed and status is `in_progress` or `completed`, continue with a warning printed to the user: + +``` +WARNING: Editing Phase {target} which is {status}. Proceeding due to --force. +``` + + + +Display the current phase fields clearly: + +``` +Current values for Phase {target}: {phase_name} + +Title: {phase_name} +Goal: {goal} +Depends on: {depends_on or "(none)"} +Requirements: {requirements or "(none)"} +Success Criteria: + 1. {criterion_1} + 2. {criterion_2} + ... +``` + +Then ask the user what they want to change: + +``` +What would you like to do? + + [1] Edit specific fields (title, goal, depends_on, requirements, success_criteria) + [2] Regenerate all fields from a clarified intent + [3] Cancel + +Enter choice (1, 2, or 3): +``` + +Wait for user input. + + + + +**If user chose [3] Cancel:** Exit cleanly. + +**If user chose [1] Edit specific fields:** + +Ask which fields to edit. For each field the user wants to change, prompt for the new value. Only fields the user explicitly answers become updates; empty answers preserve the existing value. + +``` +Which fields do you want to update? (comma-separated or "all") +Options: title, goal, depends_on, requirements, success_criteria +``` + +For each selected field, ask: + +``` +New value for {field} [current: {current_value}]: +``` + +Build an `updates` map of {field → new_value} for non-empty answers. + +**If user chose [2] Regenerate all from clarified intent:** + +Ask the user: + +``` +Describe the revised intent for Phase {target} (replace the current description): +``` + +Wait for user input. Use the clarified intent to rewrite all fields: +- Generate a clear, concise `title` from the intent +- Write a complete `goal` statement +- Produce updated `requirements` if the original had them +- Generate `success_criteria` (3-5 measurable criteria) +- Preserve `depends_on` unless the user explicitly mentioned changing it + + + +If `depends_on` is being updated (or preserved as non-empty), validate that every referenced phase number exists in ROADMAP.md: + +```bash +ALL_PHASES=$(gsd-sdk query roadmap analyze) +``` + +Parse the `phases` array to get all valid phase numbers. + +For each phase number referenced in `depends_on`: +- Normalize it (strip whitespace, "Phase" prefix if present) +- Check it is in the valid phase numbers set +- It must not reference itself (phase {target}) + +If any reference is invalid: + +``` +ERROR: depends_on references invalid phase(s): {bad_refs} + +Valid phase numbers: {valid_list} + +Fix the depends_on field and try again. +``` + +Exit (do not write). + + + +Build the updated phase section by applying the changes to the original `section` text: + +- For `title`: replace the heading text after `Phase {N}:` +- For `goal`: replace the `**Goal:**` line value +- For `depends_on`: replace or add the `**Depends on:**` line +- For `requirements`: replace or add the requirements block +- For `success_criteria`: replace the numbered list under `**Success Criteria**:` +- For full regeneration: rebuild the entire section from the new field values + +Show a unified-style diff of old vs. new: + +``` +Proposed changes to Phase {target}: + +--- current ++++ updated +@@ ... +- **Goal:** {old_goal} ++ **Goal:** {new_goal} +... + +Apply these changes? (y/n): +``` + +Wait for confirmation. If the user says `n`, exit without writing. + + + +Write the updated phase back in place in ROADMAP.md. + +Read the full ROADMAP.md content, locate the phase section by its header (`## Phase {N}:` or `### Phase {N}:`), and replace exactly the old section text with the new section text. All content before and after the section (including other phases, milestone headers, and the summary checklist) must be left unchanged. + +After writing ROADMAP.md, update STATE.md Roadmap Evolution: + +```bash +gsd-sdk query state.add-roadmap-evolution \ + --phase {target} \ + --action edited \ + --note "edited fields: {changed_field_list}" +``` + + + +Present completion summary: + +``` +Phase {target} updated in ROADMAP.md. + +Fields changed: {changed_field_list} + +--- + +## What's Next + +- `/gsd-progress` — view updated roadmap +- `/gsd-plan-phase {target}` — re-plan this phase (if needed) +- `/gsd-discuss-phase {target}` — discuss implementation approach + +--- +``` + + + + + +- Don't renumber the phase — number and position must be preserved exactly +- Don't modify other phases when editing one +- Don't skip depends_on validation (invalid references block writes) +- Don't write without showing a diff and getting confirmation +- Don't edit in_progress/completed phases without --force +- Don't use raw Write on ROADMAP.md without reading it first; always replace section in place +- Don't modify the phase directory structure — only ROADMAP.md changes +- Don't commit the change — that's the user's decision + + + +Edit-phase is complete when: + +- [ ] Phase {target} found and loaded from ROADMAP.md +- [ ] Status check performed; in_progress/completed blocked without --force +- [ ] Current values presented to user +- [ ] User chose edit mode (specific fields or full regeneration) +- [ ] depends_on references validated; invalid references blocked +- [ ] Diff shown and confirmed by user +- [ ] Updated phase written back in place; number, position, and status preserved +- [ ] STATE.md Roadmap Evolution updated +- [ ] User informed of next steps + diff --git a/tests/edit-phase.test.cjs b/tests/edit-phase.test.cjs new file mode 100644 index 000000000..b252f106c --- /dev/null +++ b/tests/edit-phase.test.cjs @@ -0,0 +1,374 @@ +'use strict'; + +/** + * Tests for /gsd-edit-phase (#2617) + * + * Covers: + * - Command file and workflow file existence + * - Single-field edit instructions + * - Full-phase regeneration from clarified intent + * - Invalid depends_on blocks with clear error + * - Guarded edit of in_progress phase without --force + * - --force override of status guard + * - Invalid phase number produces clear error + * - Diff + confirmation before writing + * - Phase number and position are preserved + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const ROOT = path.resolve(__dirname, '..'); + +const COMMAND_PATH = path.join(ROOT, 'commands', 'gsd', 'edit-phase.md'); +const WORKFLOW_PATH = path.join(ROOT, 'get-shit-done', 'workflows', 'edit-phase.md'); + +// ─── File existence ────────────────────────────────────────────────────────── + +describe('edit-phase: file existence', () => { + test('commands/gsd/edit-phase.md exists', () => { + assert.ok(fs.existsSync(COMMAND_PATH), 'commands/gsd/edit-phase.md should exist'); + }); + + test('get-shit-done/workflows/edit-phase.md exists', () => { + assert.ok(fs.existsSync(WORKFLOW_PATH), 'get-shit-done/workflows/edit-phase.md should exist'); + }); +}); + +// ─── Command file structure ─────────────────────────────────────────────────── + +describe('edit-phase: command file structure', () => { + test('command file has correct name frontmatter', () => { + const content = fs.readFileSync(COMMAND_PATH, 'utf-8'); + assert.ok(/^name:\s*gsd:edit-phase/m.test(content), 'name should be gsd:edit-phase'); + }); + + test('command file has description frontmatter', () => { + const content = fs.readFileSync(COMMAND_PATH, 'utf-8'); + assert.ok(/^description:/m.test(content), 'should have description frontmatter'); + }); + + test('command file references edit-phase workflow', () => { + const content = fs.readFileSync(COMMAND_PATH, 'utf-8'); + assert.ok( + content.includes('edit-phase.md'), + 'command file should reference edit-phase workflow' + ); + }); + + test('command file documents --force flag', () => { + const content = fs.readFileSync(COMMAND_PATH, 'utf-8'); + assert.ok(content.includes('--force'), 'command file should document --force flag'); + }); +}); + +// ─── Workflow: single-field edit ───────────────────────────────────────────── + +describe('edit-phase workflow: single-field edit', () => { + test('workflow instructs presenting current field values before editing', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const showsCurrentValues = ( + /current\s+value/i.test(content) || + /present.*current/i.test(content) || + /display.*current/i.test(content) || + /current_value/i.test(content) + ); + assert.ok(showsCurrentValues, 'workflow must present current field values before editing'); + }); + + test('workflow supports editing specific fields individually', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const supportsIndividualFields = ( + /specific\s+field/i.test(content) || + /individual\s+field/i.test(content) || + /edit.*field/i.test(content) + ); + assert.ok(supportsIndividualFields, 'workflow must support editing individual fields'); + }); + + test('workflow covers title, goal, depends_on, requirements, success_criteria fields', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + assert.ok(/\btitle\b/i.test(content), 'workflow should mention title field'); + assert.ok(/\bgoal\b/i.test(content), 'workflow should mention goal field'); + assert.ok(/depends_on/i.test(content), 'workflow should mention depends_on field'); + assert.ok(/requirements/i.test(content), 'workflow should mention requirements field'); + assert.ok(/success_criteria/i.test(content), 'workflow should mention success_criteria field'); + }); +}); + +// ─── Workflow: full-phase regeneration ─────────────────────────────────────── + +describe('edit-phase workflow: full-phase regeneration', () => { + test('workflow supports regenerating all fields from clarified intent', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const supportsRegen = ( + /regenerate/i.test(content) || + /rewrite.*all/i.test(content) || + /all.*from.*clarified/i.test(content) || + /clarified.*intent/i.test(content) + ); + assert.ok(supportsRegen, 'workflow must support full regeneration from clarified intent'); + }); + + test('workflow prompts user for clarified intent during full regeneration', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const promptsClarifiedIntent = ( + /clarified?\s+intent/i.test(content) || + /revised\s+intent/i.test(content) || + /describe.*revised/i.test(content) + ); + assert.ok( + promptsClarifiedIntent, + 'workflow must prompt user for clarified intent during full regeneration' + ); + }); +}); + +// ─── Workflow: invalid depends_on ──────────────────────────────────────────── + +describe('edit-phase workflow: depends_on validation', () => { + test('workflow validates depends_on references against existing phases', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const validatesDepends = ( + /validate.*depends/i.test(content) || + /depends.*valid/i.test(content) || + /invalid.*depends/i.test(content) || + /depends_on.*valid/i.test(content) + ); + assert.ok(validatesDepends, 'workflow must validate depends_on references'); + }); + + test('workflow blocks write when depends_on references invalid phase', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const blocksInvalidRef = ( + /invalid.*phase/i.test(content) && + /exit|block|error/i.test(content) + ); + assert.ok(blocksInvalidRef, 'workflow must block write for invalid depends_on references'); + }); + + test('workflow validates that depends_on does not reference the phase itself', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const preventsCircular = ( + /not reference itself/i.test(content) || + /circular/i.test(content) || + /self-reference/i.test(content) || + /itself/i.test(content) + ); + assert.ok(preventsCircular, 'workflow must prevent self-referencing depends_on'); + }); +}); + +// ─── Workflow: status guard ─────────────────────────────────────────────────── + +describe('edit-phase workflow: in-progress/completed status guard', () => { + test('workflow checks phase status before allowing edit', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const checksStatus = ( + /disk_status/i.test(content) || + /phase.*status/i.test(content) || + /status.*check/i.test(content) + ); + assert.ok(checksStatus, 'workflow must check phase status before allowing edit'); + }); + + test('workflow refuses to edit in_progress phases without --force', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const refusesInProgress = ( + /in.progress/i.test(content) && + /--force/i.test(content) + ); + assert.ok(refusesInProgress, 'workflow must refuse in_progress edits without --force'); + }); + + test('workflow refuses to edit completed phases without --force', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const refusesCompleted = ( + /completed/i.test(content) && + /--force/i.test(content) + ); + assert.ok(refusesCompleted, 'workflow must refuse completed phase edits without --force'); + }); + + test('workflow allows edit with --force flag override', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const forcePath = content.match(/--force[\s\S]{0,300}/i); + assert.ok(forcePath, 'workflow must handle --force flag'); + const forceSection = forcePath[0]; + const allowsForce = ( + /proceed|continue|allow|override/i.test(forceSection) || + /force.*was.*passed/i.test(content) || + /force.*passed/i.test(content) + ); + assert.ok(allowsForce, 'workflow must allow editing when --force is passed'); + }); +}); + +// ─── Workflow: invalid phase number ────────────────────────────────────────── + +describe('edit-phase workflow: invalid phase number', () => { + test('workflow produces clear error when phase number does not exist', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const handlesNotFound = ( + /not.*found/i.test(content) || + /phase.*not.*found/i.test(content) || + /does not exist/i.test(content) + ); + assert.ok(handlesNotFound, 'workflow must error clearly when phase number does not exist'); + }); + + test('workflow errors on missing phase number argument', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const handlesNoArg = ( + /no.*argument/i.test(content) || + /required/i.test(content) || + /phase number required/i.test(content) + ); + assert.ok(handlesNoArg, 'workflow must error when phase number argument is missing'); + }); +}); + +// ─── Workflow: diff + confirmation ─────────────────────────────────────────── + +describe('edit-phase workflow: diff and confirmation', () => { + test('workflow shows diff of changes before writing', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const showsDiff = ( + /diff/i.test(content) || + /proposed.*change/i.test(content) || + /show.*change/i.test(content) + ); + assert.ok(showsDiff, 'workflow must show a diff of changes before writing'); + }); + + test('workflow asks for confirmation before writing', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const asksConfirmation = ( + /confirm/i.test(content) || + /apply.*change/i.test(content) || + /y\/n/i.test(content) || + /yes.*no/i.test(content) + ); + assert.ok(asksConfirmation, 'workflow must ask for confirmation before writing'); + }); + + test('workflow exits without writing if user declines', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const handlesDecline = ( + /says.*n/i.test(content) || + /user says.*n/i.test(content) || + /if.*user.*n/i.test(content) || + /exit.*without.*writing/i.test(content) || + /without writing/i.test(content) + ); + assert.ok(handlesDecline, 'workflow must exit without writing if user declines confirmation'); + }); +}); + +// ─── Workflow: phase number and position preservation ──────────────────────── + +describe('edit-phase workflow: phase number and position preservation', () => { + test('workflow preserves phase number when writing back', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const preservesNumber = ( + /number.*preserved/i.test(content) || + /preserve.*number/i.test(content) || + /position.*preserved/i.test(content) || + /number and position/i.test(content) + ); + assert.ok(preservesNumber, 'workflow must preserve phase number and position'); + }); + + test('anti_patterns block renumbering', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const antiPatterns = content.match(/([\s\S]*?)<\/anti_patterns>/i); + assert.ok(antiPatterns, 'workflow should have anti_patterns section'); + assert.ok( + /renumber|number.*preserved|preserve.*number/i.test(antiPatterns[1]), + 'anti_patterns must prohibit renumbering' + ); + }); + + test('workflow writes phase back in place (replaces section, not full file)', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const inPlace = ( + /in.*place/i.test(content) || + /replace.*section/i.test(content) || + /section.*replace/i.test(content) || + /replace.*old.*section/i.test(content) + ); + assert.ok(inPlace, 'workflow must write phase back in place (section replacement)'); + }); +}); + +// ─── Workflow: STATE.md update ──────────────────────────────────────────────── + +describe('edit-phase workflow: STATE.md roadmap evolution', () => { + test('workflow updates STATE.md Roadmap Evolution after edit', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + const updatesState = ( + /state\.add-roadmap-evolution/i.test(content) || + /Roadmap Evolution/i.test(content) + ); + assert.ok(updatesState, 'workflow must update STATE.md Roadmap Evolution after edit'); + }); +}); + +// ─── Docs registration ──────────────────────────────────────────────────────── + +describe('edit-phase: documentation registration', () => { + test('INVENTORY.md contains /gsd-edit-phase', () => { + const inventory = fs.readFileSync( + path.join(ROOT, 'docs', 'INVENTORY.md'), + 'utf-8' + ); + assert.ok( + inventory.includes('/gsd-edit-phase'), + 'docs/INVENTORY.md must contain /gsd-edit-phase' + ); + }); + + test('INVENTORY.md contains edit-phase.md workflow', () => { + const inventory = fs.readFileSync( + path.join(ROOT, 'docs', 'INVENTORY.md'), + 'utf-8' + ); + assert.ok( + inventory.includes('edit-phase.md'), + 'docs/INVENTORY.md must contain edit-phase.md workflow row' + ); + }); + + test('INVENTORY-MANIFEST.json contains /gsd-edit-phase in commands', () => { + const manifest = JSON.parse( + fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY-MANIFEST.json'), 'utf-8') + ); + assert.ok( + manifest.families.commands.includes('/gsd-edit-phase'), + 'INVENTORY-MANIFEST.json must list /gsd-edit-phase in commands' + ); + }); + + test('INVENTORY-MANIFEST.json contains edit-phase.md in workflows', () => { + const manifest = JSON.parse( + fs.readFileSync(path.join(ROOT, 'docs', 'INVENTORY-MANIFEST.json'), 'utf-8') + ); + assert.ok( + manifest.families.workflows.includes('edit-phase.md'), + 'INVENTORY-MANIFEST.json must list edit-phase.md in workflows' + ); + }); + + test('docs/COMMANDS.md contains /gsd-edit-phase', () => { + const commands = fs.readFileSync( + path.join(ROOT, 'docs', 'COMMANDS.md'), + 'utf-8' + ); + assert.ok( + commands.includes('/gsd-edit-phase'), + 'docs/COMMANDS.md must document /gsd-edit-phase' + ); + }); +});