From d44d51ce9c53f8b0113b2a0d7553a35b16798907 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sat, 15 Aug 2026 15:12:06 +0530 Subject: [PATCH 01/14] feat(commands): add /speckit.revise for in-place living-spec edits Add a core slash command that applies a known requirement delta (add/remove/reword ACs, FRs, stories) to the current feature spec without opening a new specs/ directory. Cascade into plan.md and tasks.md when those files exist, record the change in revisions.md, and teach implement/taskstoissues to skip cancelled tasks. Assisted-by: Grok (model: grok-4.6, supervised) --- CHANGELOG.md | 12 + README.md | 1 + README.zh-CN.md | 1 + docs/concepts/spec-persistence.md | 8 +- docs/guides/evolving-specs.md | 25 +- docs/quickstart.md | 8 + docs/reference/agentic-sdd.md | 18 +- extensions/EXTENSION-API-REFERENCE.md | 2 + extensions/EXTENSION-USER-GUIDE.md | 3 +- extensions/git/README.md | 2 + extensions/git/config-template.yml | 6 + extensions/git/extension.yml | 10 + extensions/git/git-config.yml | 6 + templates/commands/analyze.md | 10 + templates/commands/converge.md | 7 + templates/commands/implement.md | 12 +- templates/commands/revise.md | 305 ++++++++++++++++++ templates/commands/specify.md | 3 +- templates/commands/taskstoissues.md | 1 + tests/extensions/git/test_git_extension.py | 4 + .../integrations/test_integration_copilot.py | 2 +- tests/test_presets.py | 2 +- tests/test_revise_command.py | 110 +++++++ 23 files changed, 535 insertions(+), 23 deletions(-) create mode 100644 templates/commands/revise.md create mode 100644 tests/test_revise_command.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f1805a31d1..cb6a73dd77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ +## [Unreleased] + +### Added + +- feat(commands): add `/speckit.revise` to edit the current feature spec in place (add/remove/reword ACs, FRs, stories) and cascade into plan/tasks + +### Changed + +- teach `/speckit.implement` and `/speckit.taskstoissues` to skip cancelled revision tasks +- hook git auto-commit on `before_revise` / `after_revise` +- document living-spec revise in evolving-specs, spec-persistence, and agentic-sdd + ## [0.16.4] - 2026-08-14 ### Changed diff --git a/README.md b/README.md index 3452eb4a3f..346d7ebb5c 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ Additional commands for enhanced quality and validation: | Command | Agent Skill | Description | | -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `/speckit.clarify` | `speckit-clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) | +| `/speckit.revise` | `speckit-revise` | Revise the current spec in place (add/remove/reword ACs or FRs) and cascade into plan/tasks | | `/speckit.analyze` | `speckit-analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) | | `/speckit.checklist` | `speckit-checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") | diff --git a/README.zh-CN.md b/README.zh-CN.md index b90809eee7..6bd524007f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -184,6 +184,7 @@ Spec Kit 可与 30 多个 AI 编码助手协作 —— 既包括 CLI 工具, | 命令 | 助手技能 | 说明 | | -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------- | | `/speckit.clarify` | `speckit-clarify` | 澄清描述不充分的部分(建议在 `/speckit.plan` 之前使用;旧称 `/quizme`) | +| `/speckit.revise` | `speckit-revise` | 就地修订当前规格(增删或改写验收标准 / 功能需求),并级联更新 plan/tasks | | `/speckit.analyze` | `speckit-analyze` | 跨制品的一致性与覆盖度分析(在 `/speckit.tasks` 之后、`/speckit.implement` 之前运行) | | `/speckit.checklist` | `speckit-checklist` | 生成自定义质量清单,校验需求的完整性、清晰度与一致性(好比"为自然语言写单元测试") | diff --git a/docs/concepts/spec-persistence.md b/docs/concepts/spec-persistence.md index dcef441116..0fb5a77c1a 100644 --- a/docs/concepts/spec-persistence.md +++ b/docs/concepts/spec-persistence.md @@ -68,10 +68,12 @@ that make the lineage easy to follow. Use living spec when `spec.md` is the contract and the other artifacts are derived from it. -In this model, teams update `spec.md` first and then regenerate or revise +In this model, teams update `spec.md` first and then revise `plan.md` and `tasks.md` from that source. The plan and task list are still valuable, but they are treated as disposable derivations rather than permanent -sources of truth. +sources of truth. `/speckit.revise` is the command for that loop: it edits the +current spec in place, records the delta in `revisions.md`, and cascades into +plan and tasks without opening a new feature directory. Living spec works well when: @@ -93,7 +95,7 @@ applies. |---|---|---|---| | Flow-back spec | Edit any artifact, then reconcile | Fast iteration and close collaboration | Silent drift between artifacts | | Flow-forward spec | Create a new feature directory for new requirements | Audit trails and historical clarity | Duplicate or fragmented context | -| Living spec | Edit `spec.md`; regenerate derived artifacts | Spec as contract | Lost rationale in regenerated files | +| Living spec | Edit `spec.md` (via `/speckit.revise`); patch derived artifacts | Spec as contract | Lost rationale if the plan is fully rebuilt | If your team has not chosen a model yet, start by answering two questions: diff --git a/docs/guides/evolving-specs.md b/docs/guides/evolving-specs.md index e2941f08b3..5d06d769c0 100644 --- a/docs/guides/evolving-specs.md +++ b/docs/guides/evolving-specs.md @@ -38,19 +38,26 @@ Use living spec when `spec.md` is the contract and `plan.md` and `tasks.md` are derived from it. When intended behavior changes, revise the existing `spec.md` first. Then -regenerate or manually revise downstream artifacts so they match the updated -spec: +bring `plan.md` and `tasks.md` in line with that contract: 1. Start from a clean working tree or a dedicated branch so every generated change is reviewable. -2. Update `spec.md` with `/speckit.clarify` or an explicit edit. -3. Rerun `/speckit.plan` or revise `plan.md` so the technical approach matches - the revised spec. -4. Rerun `/speckit.tasks` or revise `tasks.md` so implementation work matches - the revised plan. -5. Run `/speckit.analyze` before implementation resumes to catch gaps between - the spec, plan, and tasks. +2. Apply the delta with `/speckit.revise`. Pass the change as arguments — add, + remove, or reword acceptance criteria, functional requirements, stories, or + success criteria. The command edits the **current** `spec.md`, appends + `revisions.md`, patches `plan.md` when it can, and appends or cancels tasks. + Do **not** run `/speckit.specify` (that opens a new feature directory) and do + not use `/speckit.clarify` for a delta you already know — clarify only fills + pre-plan gaps by asking questions. +3. If revise reports `plan_status: needs-rebuild`, rerun `/speckit.plan`. + Otherwise keep the patched plan. +4. Do not rerun `/speckit.tasks` unless there is no `tasks.md` yet. Revise + appends a `Revision R#` phase and cancels open tasks that only served a + retired ID. +5. Run `/speckit.analyze` before implementation resumes to catch gaps and + references to retired IDs. 6. Run `/speckit.implement`, then review the code and artifact diffs together. + Implement skips cancelled tasks. 7. Run `/speckit.converge` to assess completion and append any remaining work to `tasks.md`. If tasks are appended, repeat `/speckit.implement` and `/speckit.converge` until the feature is fully complete. Preserve important implementation rationale before replacing derived artifacts. diff --git a/docs/quickstart.md b/docs/quickstart.md index 4d4eaf89e0..aa10f27cff 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -120,6 +120,14 @@ Checks the codebase against the spec, plan, and tasks. If it finds gaps, it appe /speckit.converge ``` +### After shipping: `/speckit.revise` — change the current spec + +When requirements change on the **same** feature (add or drop an acceptance criterion, retire an FR), do **not** run `/speckit.specify` again. That opens a new feature directory. Use `/speckit.revise` instead, then analyze, implement, and converge. + +```text +/speckit.revise Remove password login. Add AC: Given an expired session, When the user submits, Then redirect to SSO +``` + > [!TIP] > For a full reference on each command — arguments, output, phased implementation, and how they interact — see [Agentic SDD](reference/agentic-sdd.md). diff --git a/docs/reference/agentic-sdd.md b/docs/reference/agentic-sdd.md index dc38e76a5a..c51629d34b 100644 --- a/docs/reference/agentic-sdd.md +++ b/docs/reference/agentic-sdd.md @@ -9,6 +9,8 @@ The commands are designed to run in order, but only `/speckit.specify` is strict ```text /speckit.constitution -> /speckit.specify -> /speckit.clarify -> /speckit.plan -> /speckit.checklist -> /speckit.tasks -> /speckit.analyze -> /speckit.implement -> /speckit.converge +# after ship, when the spec itself changes: +/speckit.revise -> /speckit.analyze -> /speckit.implement -> /speckit.converge ``` ## `/speckit.constitution` @@ -21,7 +23,7 @@ Creates or updates the project **constitution** — the guiding principles that ## `/speckit.specify` -Creates or updates the feature **specification** from a natural-language description. Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. +Creates a **new** feature **specification** from a natural-language description (a new directory under `specs/`). Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. To add, remove, or reword requirements on the **current** spec, run `/speckit.revise` instead. This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` re-evaluates. That lifecycle is separate from custom checklists generated by `/speckit.checklist`. @@ -37,7 +39,7 @@ Asks up to five targeted questions about underspecified areas of the current spe /speckit.clarify Focus on the task card behavior: status changes, comment limits, and who can be assigned. ``` -Clarifying before planning keeps you from designing on top of ambiguity. If `/speckit.analyze` later surfaces requirement gaps, come back and run `/speckit.clarify` (or `/speckit.specify`) again. +Clarifying before planning keeps you from designing on top of ambiguity. If `/speckit.analyze` later surfaces requirement gaps, come back and run `/speckit.clarify` (pre-plan questions), `/speckit.revise` (a known add/remove/reword on the current spec), or `/speckit.specify` (a genuinely new feature). When `checklists/requirements.md` exists, `/speckit.clarify` may update its evaluated state as part of tightening the spec. This exception applies only to the built-in requirements checklist, not to custom review checklists. @@ -83,7 +85,7 @@ Performs a **read-only** cross-artifact consistency and quality analysis across /speckit.analyze ``` -Run it before implementing, while the artifacts can still be adjusted cheaply. If it surfaces issues, **return to the earlier step that owns them** and fix them at the source — `/speckit.specify` or `/speckit.clarify` for requirement problems, `/speckit.plan` for design problems, `/speckit.tasks` to regenerate the task list — then re-run `/speckit.analyze` until it comes back clean. You can also run `/speckit.analyze` again after implementation as an extra review. +Run it before implementing, while the artifacts can still be adjusted cheaply. If it surfaces issues, **return to the earlier step that owns them** and fix them at the source — `/speckit.specify` for a new feature, `/speckit.clarify` for pre-plan gaps, `/speckit.revise` for a known delta on the current spec, `/speckit.plan` for design problems, `/speckit.tasks` only when the task list must be rebuilt — then re-run `/speckit.analyze` until it comes back clean. You can also run `/speckit.analyze` again after implementation as an extra review. If `revisions.md` exists, retired IDs are history; the current `spec.md` wins. ## `/speckit.implement` @@ -109,6 +111,16 @@ For a large feature, work in stages to avoid overwhelming the agent's context Verify each stage works before moving to the next. +## `/speckit.revise` + +Applies a **known requirement delta** to the **current** feature spec — add, remove, or reword acceptance criteria, functional requirements, stories, or success criteria — then cascades into `plan.md` / `tasks.md` when those files exist and records the change in `revisions.md`. It does not create a new feature directory and does not write application code. + +```text +/speckit.revise Remove password login. Add AC: Given an expired session, When the user submits, Then redirect to SSO +``` + +Run this when the spec itself changed. Then `/speckit.analyze` (if plan and tasks exist), `/speckit.implement` (skips cancelled tasks), then `/speckit.converge`. Do not run `/speckit.converge` first — converge assumes the spec is stable. + ## `/speckit.converge` Assesses the codebase against the feature's spec, plan, and tasks to confirm nothing was missed. It is **append-only**: it never edits or deletes code, and its only possible write is adding tasks to `tasks.md`. Run it only after `/speckit.implement` has run on the current `tasks.md`. diff --git a/extensions/EXTENSION-API-REFERENCE.md b/extensions/EXTENSION-API-REFERENCE.md index a7bece0b89..3451226b93 100644 --- a/extensions/EXTENSION-API-REFERENCE.md +++ b/extensions/EXTENSION-API-REFERENCE.md @@ -635,6 +635,8 @@ Standard events (defined by core): - `after_constitution` - After constitution update - `before_taskstoissues` - Before tasks-to-issues conversion - `after_taskstoissues` - After tasks-to-issues conversion +- `before_revise` - Before `/speckit.revise` living-spec revision +- `after_revise` - After `/speckit.revise` living-spec revision ### Hook Configuration diff --git a/extensions/EXTENSION-USER-GUIDE.md b/extensions/EXTENSION-USER-GUIDE.md index c3391dbc75..4f4d0ffdeb 100644 --- a/extensions/EXTENSION-USER-GUIDE.md +++ b/extensions/EXTENSION-USER-GUIDE.md @@ -406,7 +406,8 @@ settings: # before_tasks, after_tasks, before_implement, after_implement, # before_analyze, after_analyze, before_checklist, after_checklist, # before_clarify, after_clarify, before_constitution, after_constitution, -# before_taskstoissues, after_taskstoissues +# before_taskstoissues, after_taskstoissues, +# before_revise, after_revise hooks: after_tasks: - extension: jira diff --git a/extensions/git/README.md b/extensions/git/README.md index c0cb7b5d00..fa99d0caec 100644 --- a/extensions/git/README.md +++ b/extensions/git/README.md @@ -35,6 +35,7 @@ This extension provides Git operations as an optional, self-contained module. It | `before_checklist` | `speckit.git.commit` | Yes | Commit outstanding changes before checklist | | `before_analyze` | `speckit.git.commit` | Yes | Commit outstanding changes before analysis | | `before_taskstoissues` | `speckit.git.commit` | Yes | Commit outstanding changes before issue sync | +| `before_revise` | `speckit.git.commit` | Yes | Commit outstanding changes before living-spec revision | | `after_constitution` | `speckit.git.commit` | Yes | Auto-commit after constitution update | | `after_specify` | `speckit.git.commit` | Yes | Auto-commit after specification | | `after_clarify` | `speckit.git.commit` | Yes | Auto-commit after clarification | @@ -44,6 +45,7 @@ This extension provides Git operations as an optional, self-contained module. It | `after_checklist` | `speckit.git.commit` | Yes | Auto-commit after checklist | | `after_analyze` | `speckit.git.commit` | Yes | Auto-commit after analysis | | `after_taskstoissues` | `speckit.git.commit` | Yes | Auto-commit after issue sync | +| `after_revise` | `speckit.git.commit` | Yes | Auto-commit after living-spec revision | ## Configuration diff --git a/extensions/git/config-template.yml b/extensions/git/config-template.yml index 2ea3471742..0613d1118f 100644 --- a/extensions/git/config-template.yml +++ b/extensions/git/config-template.yml @@ -50,6 +50,9 @@ auto_commit: before_taskstoissues: enabled: false message: "[Spec Kit] Save progress before issue sync" + before_revise: + enabled: false + message: "[Spec Kit] Save progress before spec revision" after_constitution: enabled: false message: "[Spec Kit] Add project constitution" @@ -77,3 +80,6 @@ auto_commit: after_taskstoissues: enabled: false message: "[Spec Kit] Sync tasks to issues" + after_revise: + enabled: false + message: "[Spec Kit] Revise specification" diff --git a/extensions/git/extension.yml b/extensions/git/extension.yml index c92322d8b1..10a6d8b0b6 100644 --- a/extensions/git/extension.yml +++ b/extensions/git/extension.yml @@ -83,6 +83,11 @@ hooks: optional: true prompt: "Commit outstanding changes before issue sync?" description: "Auto-commit before tasks-to-issues conversion" + before_revise: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before spec revision?" + description: "Auto-commit before living-spec revision" after_constitution: command: speckit.git.commit optional: true @@ -128,6 +133,11 @@ hooks: optional: true prompt: "Commit after syncing issues?" description: "Auto-commit after tasks-to-issues conversion" + after_revise: + command: speckit.git.commit + optional: true + prompt: "Commit specification revision?" + description: "Auto-commit after living-spec revision" tags: - "git" diff --git a/extensions/git/git-config.yml b/extensions/git/git-config.yml index 2ea3471742..0613d1118f 100644 --- a/extensions/git/git-config.yml +++ b/extensions/git/git-config.yml @@ -50,6 +50,9 @@ auto_commit: before_taskstoissues: enabled: false message: "[Spec Kit] Save progress before issue sync" + before_revise: + enabled: false + message: "[Spec Kit] Save progress before spec revision" after_constitution: enabled: false message: "[Spec Kit] Add project constitution" @@ -77,3 +80,6 @@ auto_commit: after_taskstoissues: enabled: false message: "[Spec Kit] Sync tasks to issues" + after_revise: + enabled: false + message: "[Spec Kit] Revise specification" diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index 2cd83bd7c0..58f650d53b 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -68,6 +68,7 @@ Run `{SCRIPT}` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_ - SPEC = FEATURE_DIR/spec.md - PLAN = FEATURE_DIR/plan.md - TASKS = FEATURE_DIR/tasks.md +- REVISIONS = FEATURE_DIR/revisions.md (optional; living-spec history) Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). @@ -98,6 +99,12 @@ Load only the minimal necessary context from each artifact: - Phase grouping - Parallel markers [P] - Referenced file paths +- Cancelled tasks (`CANCELLED` / struck-through IDs) — exclude them from coverage gaps + +**From revisions.md (if present):** + +- Retired IDs (`FR-###`, `SC-###`, `US#/AC#`) listed under **Removed (retired)** +- Treat those IDs as **historical**, not current requirements. `spec.md` is the source of truth. **From constitution:** @@ -149,6 +156,9 @@ Focus on high-signal findings. Limit to 50 findings total; aggregate remainder i - Data entities referenced in plan but absent in spec (or vice versa) - Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note) - Conflicting requirements (e.g., one requires Next.js while other specifies Vue) +- Plan or tasks still treating a **retired** ID from `revisions.md` as in-scope (the current `spec.md` no longer requires it) + +If requirement problems need a follow-up edit: use `__SPECKIT_COMMAND_SPECIFY__` only for a **new** feature; use `__SPECKIT_COMMAND_CLARIFY__` to fill pre-plan gaps; if the user already knows the delta (add/remove AC or FR), recommend `__SPECKIT_COMMAND_REVISE__`. ### 5. Severity Assignment diff --git a/templates/commands/converge.md b/templates/commands/converge.md index eadb96ee58..14b3296cd1 100644 --- a/templates/commands/converge.md +++ b/templates/commands/converge.md @@ -68,6 +68,13 @@ of remaining work as a new, traceable task** at the bottom of `tasks.md` so that This is **not** a diff tool and does **not** track changes. It assesses the present state of the code relative to the feature's artifacts — no git, no branch comparison, no history. +If the user just stated a requirement change (add/remove/reword an AC, FR, story, or +success criterion), or `spec.md` has a `Last Revised` stamp and `revisions.md` is newer +than the last implement pass, **STOP** and recommend `__SPECKIT_COMMAND_REVISE__` +instead of converging. Converge assumes the +**spec is stable** and the code lagged. Re-appending tasks for behavior the spec just +retired would undo a living-spec edit. + ## Operating Constraints **APPEND-ONLY, NEVER REWRITE**: The command's **only** write is appending a new diff --git a/templates/commands/implement.md b/templates/commands/implement.md index 742c45e185..a2e964a3ea 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -141,12 +141,15 @@ You **MUST** consider the user input before proceeding (if not empty). - **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt` 5. Parse tasks.md structure and extract: - - **Task phases**: Setup, Tests, Core, Integration, Polish + - **Task phases**: Setup, Tests, Core, Integration, Polish, Convergence, and any `Revision R#` phases - **Task dependencies**: Sequential vs parallel execution rules - **Task details**: ID, description, file paths, parallel markers [P] - **Execution flow**: Order and dependency requirements + - **Cancelled tasks**: A task line is cancelled if it contains `CANCELLED` or a struck-through task ID (`~~T012~~`). Treat cancelled tasks as **not executable**, even when the checkbox is still `- [ ]`. Do not implement them, do not mark them `[X]`, and do not count them as remaining work. 6. Execute implementation following the task plan: + - **Skip cancelled tasks**: never execute a cancelled line + - **Revision phases**: after earlier open (non-cancelled) work, prefer the latest `Phase N: Revision R#` plus any cleanup tasks it added - **Phase-by-phase execution**: Complete each phase before moving to the next - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks @@ -169,8 +172,9 @@ You **MUST** consider the user input before proceeding (if not empty). - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file. 9. Completion validation: - - Verify all required tasks are completed - - Check that implemented features match the original specification + - Verify all required **non-cancelled** tasks are completed + - Ignore cancelled tasks when deciding whether work remains + - Check that implemented features match the current specification (retired IDs in `revisions.md` are not required) - Validate that tests pass and coverage meets requirements - Confirm the implementation follows the technical plan @@ -216,7 +220,7 @@ Report final status with summary of completed work. ## Done When -- [ ] All tasks in tasks.md completed and marked `[X]` +- [ ] All non-cancelled tasks in tasks.md completed and marked `[X]` - [ ] Implementation validated against specification, plan, and test coverage - [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above - [ ] Completion reported to user with summary of completed work diff --git a/templates/commands/revise.md b/templates/commands/revise.md new file mode 100644 index 0000000000..37ceae1669 --- /dev/null +++ b/templates/commands/revise.md @@ -0,0 +1,305 @@ +--- +description: "Revise the current feature spec in place (add/remove ACs, FRs, stories) and cascade the change into plan and tasks." +handoffs: + - label: Implement Revision Tasks + agent: speckit.implement + prompt: Implement the open tasks from the latest revision phase + - label: Rebuild Technical Plan + agent: speckit.plan + prompt: Rebuild the plan so it matches the revised spec + - label: Create Tasks + agent: speckit.tasks + prompt: Create tasks from the revised spec and plan +scripts: + sh: scripts/bash/check-prerequisites.sh --json --paths-only + ps: scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly + py: scripts/python/check_prerequisites.py --json --paths-only +--- + +# Revise Current Spec + +Apply a **requirement delta** to the **current** feature specification. Edit `spec.md` in place, record the change in `revisions.md`, and cascade into `plan.md` / `tasks.md` when those files exist. + +This command is for living-spec edits: adding or removing acceptance criteria, functional requirements, user stories, success criteria, or scope. It is **not** `__SPECKIT_COMMAND_SPECIFY__` (that starts a new feature) and **not** `__SPECKIT_COMMAND_CONVERGE__` (that finds code that lagged an unchanged spec). + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +Treat the input as a delta against the current spec. Typical shapes: + +- Add an acceptance criterion / scenario (Given / When / Then, or a short behavior). +- Remove an acceptance criterion, FR, SC, user story, or edge case. The user may name an ID (`FR-004`, `US2/AC1`, `SC-003`) or describe the behavior. +- Change the wording of an existing item without changing its ID. +- Mix of the above in one request. + +If the input is empty: ask what to add, remove, or change (interactive), or stop with a note that there is nothing to revise (automated). Do **not** invent a revision. + +If the request is clearly a **new feature** (different user, different outcome, no shared stories with the current spec), STOP and recommend `__SPECKIT_COMMAND_SPECIFY__` instead of forcing it into this spec. + +## Pre-Execution Checks + +**Check for extension hooks (before revision)**: + +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_revise` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + + ```text + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + + - **Mandatory hook** (`optional: false`): + + ```text + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Goal. + ``` + + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. + +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + +## Goal + +Keep one feature directory as the source of truth while the contract changes. After this command: + +- `spec.md` describes the **new** intended behavior +- `revisions.md` lists exactly what was added, removed, or reworded +- `plan.md` (if present) no longer describes retired behavior +- `tasks.md` (if present) has new work for additions and cancelled open tasks for removals +- no application code has been edited + +## Operating Constraints + +**IN-PLACE, SAME FEATURE DIRECTORY**: Do **not** create a new `specs/` folder, a new branch, or a new spec file. All writes stay inside the current `FEATURE_DIR`. + +**NO APPLICATION CODE**: Do not create, modify, or delete product source. Completing new tasks is `__SPECKIT_COMMAND_IMPLEMENT__`. Cleaning up code that implemented a *removed* AC is also implement (via a cancellation/cleanup task), not this command. + +**STABLE IDS — NEVER REUSE**: + +- Do not renumber existing `FR-###`, `SC-###`, user-story numbers, or acceptance-scenario indexes that remain. +- Do not reuse a retired ID for a new item. If `FR-004` is removed, the next new requirement is `FR-008` (or whatever the next unused number is), never a new `FR-004`. +- New items take the next free number after the highest ID **ever issued** in this spec, including IDs listed as retired in `revisions.md`. + +**SPEC STAYS FUNCTIONAL**: Write what users need and why. No tech stack, libraries, APIs, or file paths in `spec.md`. Those belong in `plan.md`. + +**CONSTITUTION AUTHORITY**: `/memory/constitution.md` is non-negotiable. A new requirement that violates a `MUST` principle is rejected: report the conflict and do not apply that part of the delta. If the constitution is an unfilled template, skip this check. + +**MINIMAL PLAN/TASKS EDITS**: + +- Do not regenerate `plan.md` or rewrite `tasks.md` from scratch. +- Patch only sections the delta affects. +- Do not reorder, renumber, or delete existing task IDs. Cancel an open task by marking it in place (see Step 6). Leave completed tasks checked. + +## Execution Steps + +### 1. Resolve the current feature + +Run `{SCRIPT}` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: + +- `FEATURE_DIR` +- `FEATURE_SPEC` + +Derive: + +- `SPEC` = `FEATURE_DIR/spec.md` +- `PLAN` = `FEATURE_DIR/plan.md` +- `TASKS` = `FEATURE_DIR/tasks.md` +- `REVISIONS` = `FEATURE_DIR/revisions.md` +- `CONSTITUTION` = `/memory/constitution.md` (if present) + +If JSON parsing fails, or `spec.md` is missing, STOP and instruct the user to run `__SPECKIT_COMMAND_SPECIFY__` first. + +For single quotes in args like "I'm Groot", use escape syntax: e.g `'I'\''m Groot'` (or double-quote if possible: `"I'm Groot"`). + +### 2. Load artifacts + +Read `spec.md` in full. From it, inventory: + +- User stories (number, title, priority) and each **Acceptance Scenario** (stable key `US{n}/AC{i}`) +- Functional Requirements (`FR-###`) +- Success Criteria (`SC-###`) +- Edge cases, out-of-scope, assumptions (if present) +- Highest issued ID per series + +If `revisions.md` exists, load retired IDs so they stay retired. + +If `plan.md` exists, load section headings and any references to FR/SC/story IDs. + +If `tasks.md` exists, load every task ID, checkbox state, phase heading, and which requirement/story it traces to. Compute the next task ID and the next phase number. + +If `CONSTITUTION` exists and is not an unfilled template, load `MUST` / `SHOULD` principles. + +### 3. Classify the delta + +Turn the user input into a list of `Change` records. Each record has: + +- `op`: `add` | `remove` | `reword` +- `kind`: `acceptance-scenario` | `functional-requirement` | `success-criterion` | `user-story` | `edge-case` | `scope` +- `target`: existing ID if the user named or uniquely described one; empty for a new item +- `text`: the new wording (add/reword) or the retired wording (remove) + +Resolution rules: + +- If the user names `FR-004`, `SC-002`, `US1`, or `US2/AC1`, use that ID. +- If they describe behavior ("password login", "CSV export") and exactly one inventory item matches, use that item. +- If several items match, ask (interactive) or STOP listing the candidates (automated). Do not guess. +- Adding an AC: attach it to the user story the user named. If they did not name a story and exactly one story fits, use that. Otherwise ask / STOP. +- Removing a whole user story also removes its ACs (each AC is its own `remove` record). +- Reword vs remove+add: if the user is tightening wording of the same behavior, `reword` and **keep the ID**. If they are replacing behavior with different behavior, `remove` the old ID and `add` a new ID. + +Reject any `add` that conflicts with a constitution `MUST`. Leave the rest of the delta intact if some items are valid. + +If after classification there are zero changes, STOP and say so. Do not write files. + +### 4. Show the planned revision (before writes) + +Output a compact table and wait only if the user asked to preview or if any `remove` targets a P1 story's last remaining AC (that would empty the MVP). Otherwise proceed. + +```text +## Planned Revision R{N} + +| Op | Kind | ID | Summary | +|----|------|----|---------| +| add | acceptance-scenario | US1/AC3 | Expired session → SSO redirect | +| remove | functional-requirement | FR-004 | Password login | +``` + +`N` is 1 if `revisions.md` does not exist, otherwise one more than the highest `R#` already recorded. + +### 5. Edit `spec.md` + +Apply every `Change` to `spec.md`: + +- **add AC**: append a numbered **Given / When / Then** scenario under that story's Acceptance Scenarios. Use the next index for that story only (`US1/AC3` if AC1 and AC2 exist). Do not renumber earlier ACs. +- **remove AC**: delete that numbered scenario from the story. Do **not** renumber the ACs that remain (a hole such as AC1, AC3 is correct). If the story now has zero ACs, keep the story and add an HTML comment `` so the gap is visible. +- **add FR / SC**: append with the next unused ID. +- **remove FR / SC / story / edge case**: delete the item from the active spec. +- **reword**: replace the text; keep the ID and position. + +Also: + +- Set or update `**Last Revised**: {today's date} (R{N})` near the spec header. Do not change `**Created**`. +- If `**Status**` is `Draft` and a plan already exists, leave Status as-is unless the spec had a custom status; do not invent a new status vocabulary. +- Keep the spec's existing section structure. Do not add a "changelog" section inside `spec.md` — that belongs in `revisions.md`. +- Do not embed implementation checklists in the spec. + +### 6. Cascade to plan and tasks (only if those files exist) + +**`plan.md` present:** + +- Remove or strike bullets that exist only to serve a `remove` target. +- Add the smallest possible bullets for each `add` that the plan must acknowledge (data, flow, or constraint — still no new stack unless the user asked for a technical change). +- If the delta cannot be expressed as a small patch (for example it invalidates the chosen architecture), do **not** rewrite the plan. Record `plan_status: needs-rebuild` in the revision entry and tell the user to run `__SPECKIT_COMMAND_PLAN__`. + +**`plan.md` absent:** skip. Next step after this command is `__SPECKIT_COMMAND_PLAN__`. + +**`tasks.md` present:** + +- **Additions**: append a new section at the bottom: + + ```markdown + ## Phase {next}: Revision R{N} + + **Goal**: Implement spec changes from revision R{N} + + - [ ] T{next} [US{{n}}] {concrete task with file path if plan has one} + ``` + + One task per added AC or FR unless two adds are the same code change. Continue task IDs from the current maximum (`T014` after `T013`). Do not reuse cancelled IDs. + +- **Removals**: for each open (`- [ ]`) task that traces only to a removed ID, mark it cancelled **in place**: + + ```markdown + - [ ] ~~T012~~ CANCELLED (R{N}: removed US1/AC2) + ``` + + Do not delete the line. Do not uncheck a completed task. If a **completed** task implemented a removed AC, append one new task under the Revision phase: `Review/remove leftover behavior for {retired ID}` so implement can clean it up. + +**`tasks.md` absent:** skip. Next step is `__SPECKIT_COMMAND_TASKS__` (or `__SPECKIT_COMMAND_PLAN__` if there is no plan either). + +### 7. Append `revisions.md` + +Create the file if it does not exist: + +```markdown +# Spec Revisions: {feature name} + +Append-only history of in-place spec changes. IDs listed under **Retired** must never be reused. +``` + +Then append: + +```markdown +## R{N} — {YYYY-MM-DD} + +**Summary**: {one sentence} + +**Added**: +- `{new-id}`: {text} + +**Removed (retired)**: +- `{old-id}`: {full previous text} + +**Reworded**: +- `{id}`: {old text} → {new text} + +**Cascade**: +- plan.md: patched | needs-rebuild | skipped (missing) +- tasks.md: appended Phase {n} | cancelled {id list} | skipped (missing) + +**Next**: `__SPECKIT_COMMAND_IMPLEMENT__` | `__SPECKIT_COMMAND_PLAN__` | `__SPECKIT_COMMAND_TASKS__` +``` + +Omit empty subsections. Never edit or delete earlier `R#` entries. + +### 8. Report + +```text +## Revision R{N} Applied + +Feature: {FEATURE_DIR} +Spec: {SPEC} + +| Op | ID | Result | +|----|----|--------| +| add | US1/AC3 | written to spec.md | +| remove | FR-004 | retired; T012 cancelled | + +Next: {command} + +Open revision tasks: {task ids or "none"} +``` + +Then run **after_revise** hooks using the same rules as Pre-Execution Checks, reading `hooks.after_revise`. + +## Quick Guidelines + +- Prefer the smallest delta that captures the user's request. +- Adding an AC to an existing story is the common case — do that, don't invent a new story. +- Removing an AC does not delete shipped code; it retires the requirement and leaves a cleanup task if the code already exists. +- If you are unsure whether two phrasings are the same requirement, `reword` (keep the ID). If they are different behaviors, `remove` + `add`. +- When in doubt, ask one question rather than applying a guessed delta. diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 54151e8b42..551cbc9626 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -1,5 +1,5 @@ --- -description: Create or update the feature specification from a natural language feature description. +description: Create a new feature specification from a natural language feature description. handoffs: - label: Build Technical Plan agent: speckit.plan @@ -109,6 +109,7 @@ Given that feature description, do this: - You must only create one feature per `__SPECKIT_COMMAND_SPECIFY__` invocation - The spec directory name and the git branch name are independent — they may be the same but that is the user's choice - The spec directory and file are always created by this command, never by the hook + - This command starts a **new** feature. If the user wants to add, remove, or reword acceptance criteria / requirements on the **current** spec, do not create another directory — recommend `__SPECKIT_COMMAND_REVISE__` instead 4. Load the resolved active `spec-template` file to understand required sections. diff --git a/templates/commands/taskstoissues.md b/templates/commands/taskstoissues.md index 36c12316e5..5be0f253c1 100644 --- a/templates/commands/taskstoissues.md +++ b/templates/commands/taskstoissues.md @@ -67,6 +67,7 @@ git config --get remote.origin.url 1. **Fetch existing issues for deduplication**: Before creating anything, build the set of task IDs you are about to process from `tasks.md` (each is a `T` followed by **at least** three digits, e.g. `T001` — `__SPECKIT_COMMAND_CONVERGE__` assigns new IDs with `T{M+1:03d}`, which is a floor rather than a cap, so once a file has more than 999 tasks the IDs are four digits or longer). Then use the GitHub MCP server's `list_issues` tool to look for issues that already cover those IDs. Do not pass a `state` value, since omitting it makes the tool return both open and closed issues. Request `perPage: 100` to keep the number of calls down, and since the tool uses cursor-based pagination, request pages with the `after` parameter (using the `endCursor` from the previous response). For each issue title, match it against the task ID pattern `\bT\d{3,}\b` (the `{3,}` accepts four-digit and longer IDs — with `\d{3}` a title containing `T1000` would not match at all, because the trailing `\b` cannot fall between two digits, so that task would be silently neither deduplicated nor created; word boundaries still stop a token like `ST001` from matching, and force the whole digit run to be consumed so `T100` can never match inside `T1000`; this also recognises titles written as `T001 ...`, `T001: ...` or `[T001] ...`) and, when it matches one of your task IDs, mark that ID as already having an issue. Stop paginating as soon as every task ID has been matched, or when there are no more pages, so you do not keep fetching the whole repository's issue history once all task IDs are accounted for. This bounds the number of calls on repos with large issue histories and still prevents duplicates when the command is re-run after `tasks.md` is regenerated or the skill is re-invoked. 1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. Task lines in `tasks.md` start with a markdown checkbox, so first strip the leading `- [ ]` (and any `[P]` / `[US#]` markers) to recover the task ID and its description. Create the issue with a single canonical title of the form `T001: `, with the ID written once followed by the task description (for example, the line `- [ ] T001 Create project structure` becomes the title `T001: Create project structure`). - **Skip** any task whose ID is already present in the set of existing issues from the previous step, and report it (for example, `T001 already has an issue, skipping`). + - **Skip** any task line that contains `CANCELLED` or a struck-through task ID (`~~T012~~`). Retired revision work must not become a GitHub issue. Still create issues for new non-cancelled tasks added under a `Revision R#` phase. - Only create issues for tasks that do not yet have a matching issue. > [!CAUTION] diff --git a/tests/extensions/git/test_git_extension.py b/tests/extensions/git/test_git_extension.py index f6be51caf6..72a8e8e3a1 100644 --- a/tests/extensions/git/test_git_extension.py +++ b/tests/extensions/git/test_git_extension.py @@ -168,8 +168,12 @@ def test_manifest_hooks(self): assert "before_specify" in m.hooks assert "after_specify" in m.hooks assert "after_implement" in m.hooks + assert "before_revise" in m.hooks + assert "after_revise" in m.hooks assert m.hooks["before_constitution"]["command"] == "speckit.git.initialize" assert m.hooks["before_specify"]["command"] == "speckit.git.feature" + assert m.hooks["before_revise"]["command"] == "speckit.git.commit" + assert m.hooks["after_revise"]["command"] == "speckit.git.commit" def test_manifest_command_files_exist(self): """All command files referenced in the manifest exist.""" diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index 35d30faf0e..ed9428c9a1 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -192,7 +192,7 @@ def test_setup_falls_back_to_bundled_command_template_without_preset_override(se specify_file = tmp_path / ".github" / "agents" / "speckit.specify.agent.md" content = specify_file.read_text(encoding="utf-8") - assert "Create or update the feature specification" in content + assert "Create a new feature specification" in content assert "preset override content" not in content def test_setup_uses_preset_command_override_when_present(self, tmp_path): diff --git a/tests/test_presets.py b/tests/test_presets.py index dcb7de2d0a..de490f0330 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -5409,7 +5409,7 @@ def test_skill_restored_on_preset_remove_without_project_core_templates(self, pr content = skill_file.read_text(encoding="utf-8") assert "preset:self-test" not in content assert "templates/commands/specify.md" in content - assert "Create or update the feature specification" in content + assert "Create a new feature specification" in content def test_extension_wins_over_bundled_core_on_preset_remove( self, project_dir, monkeypatch diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py new file mode 100644 index 0000000000..c1641f4b8d --- /dev/null +++ b/tests/test_revise_command.py @@ -0,0 +1,110 @@ +"""Contracts for the core ``/speckit.revise`` command. + +The command file is the behavior. These checks lock the rules that make +revise a living-spec edit instead of a second specify, and lock the +cascade so implement / taskstoissues / analyze stay safe. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from specify_cli.integrations.base import IntegrationBase + + +REPO_ROOT = Path(__file__).resolve().parent.parent +COMMANDS = REPO_ROOT / "templates" / "commands" +REVISE = COMMANDS / "revise.md" + + +def test_revise_template_exists(): + assert REVISE.is_file() + + +def test_revise_has_scripts_frontmatter(): + text = REVISE.read_text(encoding="utf-8") + assert "sh: scripts/bash/check-prerequisites.sh --json --paths-only" in text + assert "ps: scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly" in text + assert "py: scripts/python/check_prerequisites.py --json --paths-only" in text + + +@pytest.mark.parametrize("script_type", ["sh", "ps", "py"]) +def test_revise_template_renders(script_type: str, monkeypatch): + monkeypatch.setattr( + "specify_cli.integrations.base.shutil.which", + lambda name: "/usr/bin/python3" if name == "python3" else None, + ) + monkeypatch.setattr( + "specify_cli.integrations.base.IntegrationBase._interpreter_runs", + staticmethod(lambda path: True), + ) + content = REVISE.read_text(encoding="utf-8") + result = IntegrationBase.process_template(content, "agent", script_type) + assert "{SCRIPT}" not in result + assert "$ARGUMENTS" in result or "{{args}}" in result or result + + +class TestReviseInvariants: + def setup_method(self): + self.text = REVISE.read_text(encoding="utf-8") + + def test_does_not_create_a_new_feature_directory(self): + assert "Do **not** create a new `specs/` folder" in self.text + + def test_edits_spec_in_place(self): + assert "Edit `spec.md` in place" in self.text + + def test_records_revisions_changelog(self): + assert "revisions.md" in self.text + + def test_stable_ids_are_never_reused(self): + assert "Do not reuse a retired ID" in self.text + assert "STABLE IDS" in self.text + + def test_handles_add_and_remove_acceptance_criteria(self): + assert "**add AC**" in self.text + assert "**remove AC**" in self.text + + def test_cancels_open_tasks_instead_of_deleting_them(self): + assert "CANCELLED" in self.text + assert "Do not delete the line" in self.text + + def test_does_not_write_application_code(self): + assert "NO APPLICATION CODE" in self.text + + def test_hands_off_to_implement_plan_or_tasks(self): + assert "__SPECKIT_COMMAND_IMPLEMENT__" in self.text + assert "__SPECKIT_COMMAND_PLAN__" in self.text + assert "agent: speckit.tasks" in self.text + + def test_uses_script_placeholder(self): + assert "{SCRIPT}" in self.text + + +class TestCascadeContracts: + def test_implement_skips_cancelled_tasks(self): + text = (COMMANDS / "implement.md").read_text(encoding="utf-8") + assert "CANCELLED" in text + assert "not executable" in text + + def test_taskstoissues_skips_cancelled_tasks(self): + text = (COMMANDS / "taskstoissues.md").read_text(encoding="utf-8") + assert "CANCELLED" in text + assert "must not become a GitHub issue" in text + + def test_analyze_treats_revisions_as_history(self): + text = (COMMANDS / "analyze.md").read_text(encoding="utf-8") + assert "revisions.md" in text + assert "retired" in text.lower() + assert "__SPECKIT_COMMAND_REVISE__" in text + + def test_specify_is_create_not_update(self): + text = (COMMANDS / "specify.md").read_text(encoding="utf-8") + assert "Create a new feature specification" in text + assert "__SPECKIT_COMMAND_REVISE__" in text + + def test_converge_defers_to_revise_when_spec_changed(self): + text = (COMMANDS / "converge.md").read_text(encoding="utf-8") + assert "__SPECKIT_COMMAND_REVISE__" in text From 6c007428f6dbd49f3926f81bfec57d26e64f95c5 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sat, 15 Aug 2026 15:27:12 +0530 Subject: [PATCH 02/14] fix(revise): wire core registries and init inventories Register /speckit.revise in the core command order, skill descriptions, Copilot core set, and Claude/Alquimia argument hints so specify init actually installs it everywhere. Update integration file-inventory tests and align post-hooks with sister commands. Assisted-by: Grok (model: grok-4.6, supervised) --- spec-driven.md | 12 ++++ src/specify_cli/__init__.py | 3 +- src/specify_cli/extensions/__init__.py | 1 + .../integrations/alquimia/__init__.py | 1 + src/specify_cli/integrations/base.py | 1 + .../integrations/claude/__init__.py | 1 + .../integrations/copilot/__init__.py | 1 + templates/commands/revise.md | 56 ++++++++++++++++++- .../test_integration_base_markdown.py | 2 +- .../test_integration_base_skills.py | 4 +- .../test_integration_base_toml.py | 1 + .../test_integration_base_yaml.py | 1 + tests/integrations/test_integration_bob.py | 4 +- .../integrations/test_integration_copilot.py | 10 +++- .../integrations/test_integration_generic.py | 3 + tests/test_revise_command.py | 43 ++++++++++++++ 16 files changed, 134 insertions(+), 10 deletions(-) diff --git a/spec-driven.md b/spec-driven.md index 28259ae28f..9ed12fe98e 100644 --- a/spec-driven.md +++ b/spec-driven.md @@ -83,6 +83,18 @@ This command transforms a simple feature description (the user-prompt) into a co 3. **Template-Based Generation**: Copies and customizes the feature specification template with your requirements 4. **Directory Structure**: Creates the proper `specs/[branch-name]/` structure for all related documents +This command **starts a new feature**. To change acceptance criteria or requirements on an existing spec, use `/speckit.revise` instead. + +### The `/speckit.revise` Command + +When the contract of the **current** feature changes (add, remove, or reword an acceptance criterion, functional requirement, or story), this command updates `spec.md` in place: + +1. **Same feature directory**: Does not create `specs/00N-…` or a new branch +2. **Stable IDs**: Existing FR/SC/AC numbers stay; retired IDs are never reused +3. **Revision log**: Appends `revisions.md` so the delta is reviewable +4. **Cascade**: Patches `plan.md` and appends or cancels tasks in `tasks.md` when those files exist +5. **No application code**: Implementation stays `/speckit.implement` + ### The `/speckit.plan` Command Once a feature specification exists, this command creates a comprehensive implementation plan: diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index f8afcf4f55..108d9388d8 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -367,13 +367,14 @@ def _print_cli_warning( # Constants kept for backward compatibility with presets and extensions. DEFAULT_SKILLS_DIR = ".agents/skills" SKILL_DESCRIPTIONS = { - "specify": "Create or update feature specifications from natural language descriptions.", + "specify": "Create a new feature specification from a natural language description.", "plan": "Generate technical implementation plans from feature specifications.", "tasks": "Break down implementation plans into actionable task lists.", "implement": "Execute all tasks from the task breakdown to build the feature.", "converge": "Assess the codebase against spec.md, plan.md, and tasks.md and append remaining work as new tasks.", "analyze": "Perform cross-artifact consistency analysis across spec.md, plan.md, and tasks.md.", "clarify": "Structured clarification workflow for underspecified requirements.", + "revise": "Revise the current feature spec in place and cascade the change into plan and tasks.", "constitution": "Create or update project governing principles and development guidelines.", "checklist": "Generate custom quality checklists for validating requirements completeness and clarity.", "taskstoissues": "Convert tasks from tasks.md into GitHub issues.", diff --git a/src/specify_cli/extensions/__init__.py b/src/specify_cli/extensions/__init__.py index fb4a30519d..cfd01ca947 100644 --- a/src/specify_cli/extensions/__init__.py +++ b/src/specify_cli/extensions/__init__.py @@ -54,6 +54,7 @@ "converge", "implement", "plan", + "revise", "specify", "tasks", "taskstoissues", diff --git a/src/specify_cli/integrations/alquimia/__init__.py b/src/specify_cli/integrations/alquimia/__init__.py index 132615206d..39de519dac 100644 --- a/src/specify_cli/integrations/alquimia/__init__.py +++ b/src/specify_cli/integrations/alquimia/__init__.py @@ -16,6 +16,7 @@ "implement": "Optional implementation guidance or task filter", "analyze": "Optional focus areas for analysis", "clarify": "Optional areas to clarify in the spec", + "revise": "Requirement delta: add, remove, or reword ACs, FRs, or stories", "constitution": "Principles or values for the project constitution", "checklist": "Domain or focus area for the checklist", "taskstoissues": "Optional filter or label for GitHub issues", diff --git a/src/specify_cli/integrations/base.py b/src/specify_cli/integrations/base.py index 03c7a90e74..b86dd73dee 100644 --- a/src/specify_cli/integrations/base.py +++ b/src/specify_cli/integrations/base.py @@ -49,6 +49,7 @@ "converge", "plan", "checklist", + "revise", "specify", "tasks", "taskstoissues", diff --git a/src/specify_cli/integrations/claude/__init__.py b/src/specify_cli/integrations/claude/__init__.py index 2ce7fb6dcc..81fb4a34dc 100644 --- a/src/specify_cli/integrations/claude/__init__.py +++ b/src/specify_cli/integrations/claude/__init__.py @@ -16,6 +16,7 @@ "implement": "Optional implementation guidance or task filter", "analyze": "Optional focus areas for analysis", "clarify": "Optional areas to clarify in the spec", + "revise": "Requirement delta: add, remove, or reword ACs, FRs, or stories", "constitution": "Principles or values for the project constitution", "checklist": "Domain or focus area for the checklist", "taskstoissues": "Optional filter or label for GitHub issues", diff --git a/src/specify_cli/integrations/copilot/__init__.py b/src/specify_cli/integrations/copilot/__init__.py index 0a9b4e1591..472ed3ce2e 100644 --- a/src/specify_cli/integrations/copilot/__init__.py +++ b/src/specify_cli/integrations/copilot/__init__.py @@ -38,6 +38,7 @@ "converge", "implement", "plan", + "revise", "specify", "tasks", "taskstoissues", diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 37ceae1669..6008b3800f 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -294,7 +294,61 @@ Next: {command} Open revision tasks: {task ids or "none"} ``` -Then run **after_revise** hooks using the same rules as Pre-Execution Checks, reading `hooks.after_revise`. +## Mandatory Post-Execution Hooks + +**You MUST complete this section before reporting completion to the user.** + +Check if `.specify/extensions.yml` exists in the project root. +- If it does not exist, or no hooks are registered under `hooks.after_revise`, skip to the Completion Report. +- If it exists, read it and look for entries under the `hooks.after_revise` key. +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report. +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- For each executable hook, output the following based on its `optional` flag: + - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: + ``` + ## Extension Hooks + + **Automatic Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + +## Completion Report + +Report completion to the user with: +- `FEATURE_DIR` / `SPEC_FILE` +- Revision number `R{N}` +- Added / removed / reworded IDs +- Cascade result for `plan.md` and `tasks.md` +- Next command (`__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`) + +If `FEATURE_DIR/checklists/requirements.md` exists, re-evaluate its items against the revised spec and update pass/fail markers. Do not invent a new checklist. + +## Done When + +- [ ] `spec.md` reflects the requested delta; no new feature directory was created +- [ ] `revisions.md` has a new `R{N}` entry listing added, removed (retired), and reworded IDs +- [ ] Retired IDs were not reused +- [ ] `plan.md` was patched or marked `needs-rebuild` (or skipped if missing) +- [ ] `tasks.md` gained a Revision phase and/or cancelled obsolete open tasks (or skipped if missing) +- [ ] No application code was edited +- [ ] Extension hooks dispatched or skipped according to the rules above +- [ ] Completion reported to the user ## Quick Guidelines diff --git a/tests/integrations/test_integration_base_markdown.py b/tests/integrations/test_integration_base_markdown.py index e94f58c6ff..baca7faf2a 100644 --- a/tests/integrations/test_integration_base_markdown.py +++ b/tests/integrations/test_integration_base_markdown.py @@ -220,7 +220,7 @@ def test_integration_flag_creates_files(self, tmp_path): COMMAND_STEMS = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", ] def _expected_files(self, script_variant: str) -> list[str]: diff --git a/tests/integrations/test_integration_base_skills.py b/tests/integrations/test_integration_base_skills.py index 015152be01..b088c607ce 100644 --- a/tests/integrations/test_integration_base_skills.py +++ b/tests/integrations/test_integration_base_skills.py @@ -95,7 +95,7 @@ def test_skill_directory_structure(self, tmp_path): expected_commands = { "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", } # Derive command names from the skill directory names @@ -466,7 +466,7 @@ def test_options_include_skills_flag(self): _SKILL_COMMANDS = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", ] def _expected_files(self, script_variant: str) -> list[str]: diff --git a/tests/integrations/test_integration_base_toml.py b/tests/integrations/test_integration_base_toml.py index 1b1bb18807..4664680897 100644 --- a/tests/integrations/test_integration_base_toml.py +++ b/tests/integrations/test_integration_base_toml.py @@ -468,6 +468,7 @@ def test_integration_flag_creates_files(self, tmp_path): "implement", "plan", "checklist", + "revise", "specify", "tasks", "taskstoissues", diff --git a/tests/integrations/test_integration_base_yaml.py b/tests/integrations/test_integration_base_yaml.py index 01914c5988..8325779931 100644 --- a/tests/integrations/test_integration_base_yaml.py +++ b/tests/integrations/test_integration_base_yaml.py @@ -382,6 +382,7 @@ def test_integration_flag_creates_files(self, tmp_path): "implement", "plan", "checklist", + "revise", "specify", "tasks", "taskstoissues", diff --git a/tests/integrations/test_integration_bob.py b/tests/integrations/test_integration_bob.py index 52a25ae2c6..84af679b5f 100644 --- a/tests/integrations/test_integration_bob.py +++ b/tests/integrations/test_integration_bob.py @@ -246,7 +246,7 @@ def test_skill_directory_structure(self, tmp_path): expected_commands = { "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", } actual_commands = {f.parent.name.removeprefix("speckit-") for f in created} assert actual_commands == expected_commands @@ -409,7 +409,7 @@ def test_init_default_complete_file_inventory_sh(self, tmp_path): commands = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", ] for cmd in commands: assert (project / ".bob" / "skills" / f"speckit-{cmd}" / "SKILL.md").exists(), ( diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index ed9428c9a1..60aedb4f23 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -145,10 +145,10 @@ def test_directory_structure(self, tmp_path): agents_dir = tmp_path / ".github" / "agents" assert agents_dir.is_dir() agent_files = sorted(agents_dir.glob("speckit.*.agent.md")) - assert len(agent_files) == 10 + assert len(agent_files) == 11 expected_commands = { "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", } actual_commands = {f.name.removeprefix("speckit.").removesuffix(".agent.md") for f in agent_files} assert actual_commands == expected_commands @@ -256,6 +256,7 @@ def test_complete_file_inventory_sh(self, tmp_path): ".github/agents/speckit.converge.agent.md", ".github/agents/speckit.implement.agent.md", ".github/agents/speckit.plan.agent.md", + ".github/agents/speckit.revise.agent.md", ".github/agents/speckit.specify.agent.md", ".github/agents/speckit.tasks.agent.md", ".github/agents/speckit.taskstoissues.agent.md", @@ -266,6 +267,7 @@ def test_complete_file_inventory_sh(self, tmp_path): ".github/prompts/speckit.converge.prompt.md", ".github/prompts/speckit.implement.prompt.md", ".github/prompts/speckit.plan.prompt.md", + ".github/prompts/speckit.revise.prompt.md", ".github/prompts/speckit.specify.prompt.md", ".github/prompts/speckit.tasks.prompt.md", ".github/prompts/speckit.taskstoissues.prompt.md", @@ -321,6 +323,7 @@ def test_complete_file_inventory_ps(self, tmp_path): ".github/agents/speckit.converge.agent.md", ".github/agents/speckit.implement.agent.md", ".github/agents/speckit.plan.agent.md", + ".github/agents/speckit.revise.agent.md", ".github/agents/speckit.specify.agent.md", ".github/agents/speckit.tasks.agent.md", ".github/agents/speckit.taskstoissues.agent.md", @@ -331,6 +334,7 @@ def test_complete_file_inventory_ps(self, tmp_path): ".github/prompts/speckit.converge.prompt.md", ".github/prompts/speckit.implement.prompt.md", ".github/prompts/speckit.plan.prompt.md", + ".github/prompts/speckit.revise.prompt.md", ".github/prompts/speckit.specify.prompt.md", ".github/prompts/speckit.tasks.prompt.md", ".github/prompts/speckit.taskstoissues.prompt.md", @@ -366,7 +370,7 @@ class TestCopilotSkillsMode: _SKILL_COMMANDS = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "revise", "specify", "tasks", "taskstoissues", ] def _make_copilot(self): diff --git a/tests/integrations/test_integration_generic.py b/tests/integrations/test_integration_generic.py index 02176be1b0..c548549830 100644 --- a/tests/integrations/test_integration_generic.py +++ b/tests/integrations/test_integration_generic.py @@ -266,6 +266,7 @@ def test_implement_loads_constitution_context(self, tmp_path): "implement", "plan", "checklist", + "revise", "specify", "tasks", "taskstoissues", @@ -335,6 +336,7 @@ def test_complete_file_inventory_sh(self, tmp_path): ".myagent/commands/speckit.converge.md", ".myagent/commands/speckit.implement.md", ".myagent/commands/speckit.plan.md", + ".myagent/commands/speckit.revise.md", ".myagent/commands/speckit.specify.md", ".myagent/commands/speckit.tasks.md", ".myagent/commands/speckit.taskstoissues.md", @@ -394,6 +396,7 @@ def test_complete_file_inventory_ps(self, tmp_path): ".myagent/commands/speckit.converge.md", ".myagent/commands/speckit.implement.md", ".myagent/commands/speckit.plan.md", + ".myagent/commands/speckit.revise.md", ".myagent/commands/speckit.specify.md", ".myagent/commands/speckit.tasks.md", ".myagent/commands/speckit.taskstoissues.md", diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index c1641f4b8d..f7d0c4b121 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -23,6 +23,44 @@ def test_revise_template_exists(): assert REVISE.is_file() +def test_list_command_templates_includes_revise(): + from specify_cli.integrations.base import MarkdownIntegration + + class _Probe(MarkdownIntegration): + key = "probe" + config = { + "name": "Probe", + "folder": ".probe/", + "commands_subdir": "commands", + "install_url": None, + "requires_cli": False, + } + registrar_config = { + "dir": ".probe/commands", + "format": "markdown", + "args": "$ARGUMENTS", + "extension": ".md", + } + + stems = {p.stem for p in _Probe().list_command_templates()} + assert "revise" in stems + assert "specify" in stems + + +def test_revise_command_ref_resolves_to_speckit_revise(): + resolved = IntegrationBase.resolve_command_refs( + "next: __SPECKIT_COMMAND_REVISE__", separator=".", prefix="/" + ) + assert resolved == "next: /speckit.revise" + + +def test_skill_descriptions_include_revise(): + from specify_cli import SKILL_DESCRIPTIONS + + assert "revise" in SKILL_DESCRIPTIONS + assert "Create a new feature specification" in SKILL_DESCRIPTIONS["specify"] + + def test_revise_has_scripts_frontmatter(): text = REVISE.read_text(encoding="utf-8") assert "sh: scripts/bash/check-prerequisites.sh --json --paths-only" in text @@ -82,6 +120,11 @@ def test_hands_off_to_implement_plan_or_tasks(self): def test_uses_script_placeholder(self): assert "{SCRIPT}" in self.text + def test_has_mandatory_post_execution_hooks(self): + assert "hooks.after_revise" in self.text + assert "EXECUTE_COMMAND:" in self.text + assert "## Done When" in self.text + class TestCascadeContracts: def test_implement_skips_cancelled_tasks(self): From e62cdab17681184f1d3e6c392cd6d727b434056d Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sat, 15 Aug 2026 15:28:24 +0530 Subject: [PATCH 03/14] fix(revise): close verifier docs and prompt gaps List /speckit.revise in the post-init command inventory and extension hook catalog. Send known deltas from clarify/analyze to revise, emit a single completion report after hooks, and note that lean still uses the core revise command. Assisted-by: Grok (model: grok-4.6, supervised) --- docs/installation.md | 1 + extensions/EXTENSION-DEVELOPMENT-GUIDE.md | 1 + presets/lean/README.md | 2 ++ templates/commands/analyze.md | 2 +- templates/commands/clarify.md | 2 ++ templates/commands/revise.md | 41 +++++++++++------------ tests/test_revise_command.py | 5 +++ 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 67b69505e6..5bc3e29c16 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -123,6 +123,7 @@ After initialization, you should see the following commands available in your co - `/speckit.implement` - Execute implementation tasks - `/speckit.analyze` - Validate cross-artifact consistency - `/speckit.clarify` - Identify and resolve ambiguities +- `/speckit.revise` - Revise the current spec in place and cascade into plan/tasks - `/speckit.checklist` - Generate quality checklists - `/speckit.constitution` - Create or update project principles - `/speckit.converge` - Assess codebase against artifacts and append remaining tasks diff --git a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md index ac78029f2a..aa47b18508 100644 --- a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md +++ b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md @@ -225,6 +225,7 @@ Available hook points: - `before_analyze` / `after_analyze`: Before/after cross-artifact analysis - `before_checklist` / `after_checklist`: Before/after checklist generation - `before_clarify` / `after_clarify`: Before/after spec clarification +- `before_revise` / `after_revise`: Before/after living-spec revision - `before_constitution` / `after_constitution`: Before/after constitution update - `before_taskstoissues` / `after_taskstoissues`: Before/after tasks-to-issues conversion diff --git a/presets/lean/README.md b/presets/lean/README.md index ab17257f96..b5162417fb 100644 --- a/presets/lean/README.md +++ b/presets/lean/README.md @@ -20,6 +20,8 @@ Use Lean when you want the structured specify → plan → tasks → implement p Lean overrides the five core workflow commands with self-contained prompts that produce each artifact directly — no separate template files involved. The result is a shorter, more direct workflow. +Living-spec edits still use the unmodified core `/speckit.revise` (lean does not replace it). + ## Installation ```bash diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index 58f650d53b..64fb79fec2 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -205,7 +205,7 @@ At end of report, output a concise Next Actions block: - If CRITICAL issues exist: Recommend resolving before `__SPECKIT_COMMAND_IMPLEMENT__` - If only LOW/MEDIUM: User may proceed, but provide improvement suggestions -- Provide explicit command suggestions: e.g., "Run __SPECKIT_COMMAND_SPECIFY__ with refinement", "Run __SPECKIT_COMMAND_PLAN__ to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" +- Provide explicit command suggestions: e.g., "Run __SPECKIT_COMMAND_SPECIFY__ for a new feature", "Run __SPECKIT_COMMAND_REVISE__ for a known add/remove/reword on the current spec", "Run __SPECKIT_COMMAND_PLAN__ to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" ### 8. Offer Remediation diff --git a/templates/commands/clarify.md b/templates/commands/clarify.md index ea2f20d519..3e4bc4740e 100644 --- a/templates/commands/clarify.md +++ b/templates/commands/clarify.md @@ -57,6 +57,8 @@ You **MUST** consider the user input before proceeding (if not empty). Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file. +If the user already stated a **concrete delta** (add, remove, or reword a named AC, FR, story, or success criterion), do **not** start a clarification question loop. Recommend `__SPECKIT_COMMAND_REVISE__` and stop. + Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `__SPECKIT_COMMAND_PLAN__`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. Execution steps: diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 6008b3800f..887a9c37d5 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -276,24 +276,6 @@ Then append: Omit empty subsections. Never edit or delete earlier `R#` entries. -### 8. Report - -```text -## Revision R{N} Applied - -Feature: {FEATURE_DIR} -Spec: {SPEC} - -| Op | ID | Result | -|----|----|--------| -| add | US1/AC3 | written to spec.md | -| remove | FR-004 | retired; T012 cancelled | - -Next: {command} - -Open revision tasks: {task ids or "none"} -``` - ## Mandatory Post-Execution Hooks **You MUST complete this section before reporting completion to the user.** @@ -330,10 +312,25 @@ Check if `.specify/extensions.yml` exists in the project root. ## Completion Report -Report completion to the user with: -- `FEATURE_DIR` / `SPEC_FILE` -- Revision number `R{N}` -- Added / removed / reworded IDs +After hooks, report completion once: + +```text +## Revision R{N} Applied + +Feature: {FEATURE_DIR} +Spec: {SPEC} + +| Op | ID | Result | +|----|----|--------| +| add | US1/AC3 | written to spec.md | +| remove | FR-004 | retired; T012 cancelled | + +Next: {command} + +Open revision tasks: {task ids or "none"} +``` + +Also include: - Cascade result for `plan.md` and `tasks.md` - Next command (`__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`) diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index f7d0c4b121..3e06dfd0f4 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -151,3 +151,8 @@ def test_specify_is_create_not_update(self): def test_converge_defers_to_revise_when_spec_changed(self): text = (COMMANDS / "converge.md").read_text(encoding="utf-8") assert "__SPECKIT_COMMAND_REVISE__" in text + + def test_clarify_defers_known_deltas_to_revise(self): + text = (COMMANDS / "clarify.md").read_text(encoding="utf-8") + assert "__SPECKIT_COMMAND_REVISE__" in text + assert "concrete delta" in text From c09f7bc480371bf408086aef126629bf86841fd9 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sat, 15 Aug 2026 15:31:03 +0530 Subject: [PATCH 04/14] fix(revise): re-evaluate requirements checklist before git hooks Keep living-spec checklist marker updates in the same commit as the spec delta when the git extension auto-commits after_revise. Assisted-by: Grok (model: grok-4.6, supervised) --- templates/commands/revise.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 887a9c37d5..5ccca826d4 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -276,6 +276,8 @@ Then append: Omit empty subsections. Never edit or delete earlier `R#` entries. +If `FEATURE_DIR/checklists/requirements.md` exists, re-evaluate its items against the revised spec and update pass/fail markers **before** post-execution hooks so a git auto-commit includes those edits. Do not invent a new checklist. + ## Mandatory Post-Execution Hooks **You MUST complete this section before reporting completion to the user.** @@ -334,8 +336,6 @@ Also include: - Cascade result for `plan.md` and `tasks.md` - Next command (`__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`) -If `FEATURE_DIR/checklists/requirements.md` exists, re-evaluate its items against the revised spec and update pass/fail markers. Do not invent a new checklist. - ## Done When - [ ] `spec.md` reflects the requested delta; no new feature directory was created From 6ee5e43326f6c2de81c80d59ce1faa5ea9cc52f8 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 09:19:44 +0530 Subject: [PATCH 05/14] fix(revise): keep revisions.md a small dated log; no-op duplicates spec.md stays the only contract. revisions.md is IDs plus a date. Re-running the same add/remove, or adding what the spec already says, writes nothing and does not bump R{N}. Assisted-by: Grok (model: grok-4.6, supervised) --- templates/commands/analyze.md | 5 ++-- templates/commands/revise.md | 50 +++++++++++++++++++---------------- tests/test_revise_command.py | 6 +++++ 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index 64fb79fec2..28f86b4bc5 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -103,8 +103,9 @@ Load only the minimal necessary context from each artifact: **From revisions.md (if present):** -- Retired IDs (`FR-###`, `SC-###`, `US#/AC#`) listed under **Removed (retired)** -- Treat those IDs as **historical**, not current requirements. `spec.md` is the source of truth. +- This file is a **dated log only**, not a spec. Ignore summaries except for ID lists. +- Collect retired IDs (`retired:` / removed). Treat those IDs as **historical**. +- Current requirements come only from `spec.md`. **From constitution:** diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 5ccca826d4..47d499abfc 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -86,8 +86,8 @@ If the request is clearly a **new feature** (different user, different outcome, Keep one feature directory as the source of truth while the contract changes. After this command: -- `spec.md` describes the **new** intended behavior -- `revisions.md` lists exactly what was added, removed, or reworded +- `spec.md` is the **only** current contract +- `revisions.md` is a **small dated log** of what changed (not a second spec) - `plan.md` (if present) no longer describes retired behavior - `tasks.md` (if present) has new work for additions and cancelled open tasks for removals - no application code has been edited @@ -108,6 +108,21 @@ Keep one feature directory as the source of truth while the contract changes. Af **CONSTITUTION AUTHORITY**: `/memory/constitution.md` is non-negotiable. A new requirement that violates a `MUST` principle is rejected: report the conflict and do not apply that part of the delta. If the constitution is an unfilled template, skip this check. +**REVISIONS.MD IS A LOG, NOT SOURCE OF TRUTH**: +- `spec.md` decides what is required now. Never treat `revisions.md` as the spec. +- Each entry is one short dated block: `R{N}`, date, one-line summary, IDs only. +- Do not copy AC/FR prose, plan notes, or next-command instructions into the log. +- Implement / analyze / converge read `spec.md` (and `CANCELLED` tasks). They may consult the log only for **retired IDs**. + +**DUPLICATES ARE A NO-OP**: +- Compare the requested delta to the **current** `spec.md` (not to the log). +- `add` of behavior already in the spec → drop that change (duplicate). +- `remove` of an ID already absent / already retired → drop that change. +- `reword` that does not change meaning or text → drop that change. +- Same delta as the latest `R#` (re-run) → drop the whole request. +- If every change is a duplicate, STOP. Report which items were already true. Do **not** write `spec.md`, `revisions.md`, `plan.md`, or `tasks.md`. Do **not** bump `R{N}`. +- Partial request: apply only the non-duplicate changes; mention skipped duplicates in the report. + **MINIMAL PLAN/TASKS EDITS**: - Do not regenerate `plan.md` or rewrite `tasks.md` from scratch. @@ -173,7 +188,7 @@ Resolution rules: Reject any `add` that conflicts with a constitution `MUST`. Leave the rest of the delta intact if some items are valid. -If after classification there are zero changes, STOP and say so. Do not write files. +Drop duplicates using the **DUPLICATES ARE A NO-OP** rules above. If nothing remains, STOP. Do not write files and do not append a revision entry. ### 4. Show the planned revision (before writes) @@ -243,38 +258,27 @@ Also: ### 7. Append `revisions.md` -Create the file if it does not exist: +This file is a **dated index**, not a spec. Create it if it does not exist: ```markdown -# Spec Revisions: {feature name} +# Spec Revisions -Append-only history of in-place spec changes. IDs listed under **Retired** must never be reused. +Dated log of in-place edits. `spec.md` is the source of truth. Retired IDs must not be reused. ``` -Then append: +Then append **only** this small block (IDs and a one-line summary — no full AC text, no cascade, no next-command): ```markdown ## R{N} — {YYYY-MM-DD} -**Summary**: {one sentence} - -**Added**: -- `{new-id}`: {text} - -**Removed (retired)**: -- `{old-id}`: {full previous text} - -**Reworded**: -- `{id}`: {old text} → {new text} - -**Cascade**: -- plan.md: patched | needs-rebuild | skipped (missing) -- tasks.md: appended Phase {n} | cancelled {id list} | skipped (missing) +{one sentence} -**Next**: `__SPECKIT_COMMAND_IMPLEMENT__` | `__SPECKIT_COMMAND_PLAN__` | `__SPECKIT_COMMAND_TASKS__` +- added: {id, id} +- retired: {id, id} +- reworded: {id, id} ``` -Omit empty subsections. Never edit or delete earlier `R#` entries. +Omit empty bullets. Never edit or delete earlier `R#` entries. If this revision was a no-op (all duplicates), do not append anything. If `FEATURE_DIR/checklists/requirements.md` exists, re-evaluate its items against the revised spec and update pass/fail markers **before** post-execution hooks so a git auto-commit includes those edits. Do not invent a new checklist. diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index 3e06dfd0f4..9f783c29c8 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -96,6 +96,12 @@ def test_edits_spec_in_place(self): def test_records_revisions_changelog(self): assert "revisions.md" in self.text + assert "dated log" in self.text.lower() or "dated index" in self.text.lower() + assert "not a spec" in self.text.lower() or "NOT SOURCE OF TRUTH" in self.text + + def test_duplicates_are_a_noop(self): + assert "DUPLICATES ARE A NO-OP" in self.text + assert "Do **not** bump `R{N}`" in self.text or "do not append" in self.text.lower() def test_stable_ids_are_never_reused(self): assert "Do not reuse a retired ID" in self.text From 524be7f1e4c1bd6e7d0b39ed9d205159277ae882 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 09:23:47 +0530 Subject: [PATCH 06/14] fix(revise): supersede conflicting items instead of deleting them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a delta contradicts a live FR/AC/SC, mark the old line SUPERSEDED by a new ID and add the replacement. Pure drops are RETIRED in place. Tasks get SUPERSEDED → new T0xx or CANCELLED. implement/analyze skip those lines. Assisted-by: Grok (model: grok-4.6, supervised) --- templates/commands/analyze.md | 10 +++-- templates/commands/implement.md | 14 +++---- templates/commands/revise.md | 63 ++++++++++++++++++++--------- templates/commands/taskstoissues.md | 2 +- tests/test_revise_command.py | 11 ++++- 5 files changed, 68 insertions(+), 32 deletions(-) diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index 28f86b4bc5..361cb0bf3a 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -99,13 +99,17 @@ Load only the minimal necessary context from each artifact: - Phase grouping - Parallel markers [P] - Referenced file paths -- Cancelled tasks (`CANCELLED` / struck-through IDs) — exclude them from coverage gaps +- Cancelled or superseded tasks (`CANCELLED` / `SUPERSEDED` / struck-through IDs) — exclude them from coverage gaps + +**From spec.md (current contract):** + +- Only **live** FRs, ACs, and SCs count. Lines marked `SUPERSEDED` or `RETIRED` (usually struck through) are historical. Do not require tasks or plan coverage for them. **From revisions.md (if present):** - This file is a **dated log only**, not a spec. Ignore summaries except for ID lists. -- Collect retired IDs (`retired:` / removed). Treat those IDs as **historical**. -- Current requirements come only from `spec.md`. +- Collect `superseded:` and `retired:` IDs as historical. +- Current requirements come only from **live** lines in `spec.md`. **From constitution:** diff --git a/templates/commands/implement.md b/templates/commands/implement.md index a2e964a3ea..1074770460 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -145,11 +145,11 @@ You **MUST** consider the user input before proceeding (if not empty). - **Task dependencies**: Sequential vs parallel execution rules - **Task details**: ID, description, file paths, parallel markers [P] - **Execution flow**: Order and dependency requirements - - **Cancelled tasks**: A task line is cancelled if it contains `CANCELLED` or a struck-through task ID (`~~T012~~`). Treat cancelled tasks as **not executable**, even when the checkbox is still `- [ ]`. Do not implement them, do not mark them `[X]`, and do not count them as remaining work. + - **Cancelled or superseded tasks**: A task line is not executable if it contains `CANCELLED`, `SUPERSEDED`, or a struck-through task ID (`~~T012~~`), even when the checkbox is still `- [ ]`. Do not implement them, do not mark them `[X]`, and do not count them as remaining work. 6. Execute implementation following the task plan: - - **Skip cancelled tasks**: never execute a cancelled line - - **Revision phases**: after earlier open (non-cancelled) work, prefer the latest `Phase N: Revision R#` plus any cleanup tasks it added + - **Skip cancelled/superseded tasks**: never execute those lines; do the replacement task (`T020` in `SUPERSEDED (R2 → T020)`) instead + - **Revision phases**: after earlier open (live) work, prefer the latest `Phase N: Revision R#` plus any cleanup tasks it added - **Phase-by-phase execution**: Complete each phase before moving to the next - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks @@ -172,9 +172,9 @@ You **MUST** consider the user input before proceeding (if not empty). - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file. 9. Completion validation: - - Verify all required **non-cancelled** tasks are completed - - Ignore cancelled tasks when deciding whether work remains - - Check that implemented features match the current specification (retired IDs in `revisions.md` are not required) + - Verify all required **live** tasks are completed + - Ignore `CANCELLED` / `SUPERSEDED` tasks when deciding whether work remains + - Check that implemented features match **live** items in `spec.md` (struck `SUPERSEDED` / `RETIRED` lines are not required) - Validate that tests pass and coverage meets requirements - Confirm the implementation follows the technical plan @@ -220,7 +220,7 @@ Report final status with summary of completed work. ## Done When -- [ ] All non-cancelled tasks in tasks.md completed and marked `[X]` +- [ ] All live (non-cancelled, non-superseded) tasks in tasks.md completed and marked `[X]` - [ ] Implementation validated against specification, plan, and test coverage - [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above - [ ] Completion reported to user with summary of completed work diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 47d499abfc..3bed36f395 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -172,19 +172,24 @@ If `CONSTITUTION` exists and is not an unfilled template, load `MUST` / `SHOULD` Turn the user input into a list of `Change` records. Each record has: -- `op`: `add` | `remove` | `reword` +- `op`: `add` | `remove` | `reword` | `supersede` - `kind`: `acceptance-scenario` | `functional-requirement` | `success-criterion` | `user-story` | `edge-case` | `scope` - `target`: existing ID if the user named or uniquely described one; empty for a new item -- `text`: the new wording (add/reword) or the retired wording (remove) +- `replaces`: old ID when `op` is `supersede` +- `text`: the new wording (add/reword/supersede) or the retired wording (remove) + +Ignore inventory lines already marked `SUPERSEDED` or `RETIRED` when matching current behavior. Still count those IDs as issued so they are never reused. Resolution rules: - If the user names `FR-004`, `SC-002`, `US1`, or `US2/AC1`, use that ID. -- If they describe behavior ("password login", "CSV export") and exactly one inventory item matches, use that item. +- If they describe behavior ("password login", "CSV export") and exactly one **live** inventory item matches, use that item. - If several items match, ask (interactive) or STOP listing the candidates (automated). Do not guess. - Adding an AC: attach it to the user story the user named. If they did not name a story and exactly one story fits, use that. Otherwise ask / STOP. -- Removing a whole user story also removes its ACs (each AC is its own `remove` record). -- Reword vs remove+add: if the user is tightening wording of the same behavior, `reword` and **keep the ID**. If they are replacing behavior with different behavior, `remove` the old ID and `add` a new ID. +- **Conflict / replacement (prefer this over silent delete):** if the new behavior **contradicts** a live item, or the user is swapping one behavior for another ("SSO instead of password"), use `supersede`: keep the old ID in place marked `SUPERSEDED by {new-id} (R{N})`, and **add** a new ID for the new text. Do not leave two live items that disagree. +- Tightening wording of the **same** behavior → `reword` (keep the ID). Do not supersede. +- Dropping something with **no** replacement → `remove` (mark `RETIRED (R{N})`, do not delete the line). +- Removing a whole user story: `remove` the story and `remove` or `supersede` each of its ACs depending on whether replacements were given. Reject any `add` that conflicts with a constitution `MUST`. Leave the rest of the delta intact if some items are valid. @@ -199,36 +204,48 @@ Output a compact table and wait only if the user asked to preview or if any `rem | Op | Kind | ID | Summary | |----|------|----|---------| +| supersede | functional-requirement | FR-004 → FR-008 | Password login → SSO only | | add | acceptance-scenario | US1/AC3 | Expired session → SSO redirect | -| remove | functional-requirement | FR-004 | Password login | ``` `N` is 1 if `revisions.md` does not exist, otherwise one more than the highest `R#` already recorded. ### 5. Edit `spec.md` -Apply every `Change` to `spec.md`: +Apply every `Change` to `spec.md`. **Live** items are unmarked lines. `SUPERSEDED` / `RETIRED` lines stay visible but are **not** current requirements. -- **add AC**: append a numbered **Given / When / Then** scenario under that story's Acceptance Scenarios. Use the next index for that story only (`US1/AC3` if AC1 and AC2 exist). Do not renumber earlier ACs. -- **remove AC**: delete that numbered scenario from the story. Do **not** renumber the ACs that remain (a hole such as AC1, AC3 is correct). If the story now has zero ACs, keep the story and add an HTML comment `` so the gap is visible. +- **add AC**: append a numbered **Given / When / Then** under that story. Next index for that story only. Do not renumber earlier ACs (holes are correct). - **add FR / SC**: append with the next unused ID. -- **remove FR / SC / story / edge case**: delete the item from the active spec. -- **reword**: replace the text; keep the ID and position. +- **reword**: replace the live text; keep the ID and position. Do not add a sibling ID. +- **supersede**: do **not** delete the old line. Strike it and point at the new ID, then add the new item next to it (or at the end of that section): + + ```markdown + - **FR-004** ~~Users MUST sign in with email and password~~ — SUPERSEDED by **FR-008** (R2) + - **FR-008**: Users MUST sign in with company SSO only + ``` + + Same pattern for ACs (`US1/AC2` → `US1/AC4`) and SCs (`SC-001` → `SC-005`). +- **remove** (no replacement): do **not** delete the line. Strike it and mark retired: + + ```markdown + - **SC-003** ~~90% first-attempt success~~ — RETIRED (R2) + ``` Also: - Set or update `**Last Revised**: {today's date} (R{N})` near the spec header. Do not change `**Created**`. - If `**Status**` is `Draft` and a plan already exists, leave Status as-is unless the spec had a custom status; do not invent a new status vocabulary. -- Keep the spec's existing section structure. Do not add a "changelog" section inside `spec.md` — that belongs in `revisions.md`. +- Keep the spec's existing section structure. Do not add a changelog section — `revisions.md` stays the small dated log. - Do not embed implementation checklists in the spec. +- Never leave two **live** items that contradict each other. ### 6. Cascade to plan and tasks (only if those files exist) **`plan.md` present:** -- Remove or strike bullets that exist only to serve a `remove` target. -- Add the smallest possible bullets for each `add` that the plan must acknowledge (data, flow, or constraint — still no new stack unless the user asked for a technical change). -- If the delta cannot be expressed as a small patch (for example it invalidates the chosen architecture), do **not** rewrite the plan. Record `plan_status: needs-rebuild` in the revision entry and tell the user to run `__SPECKIT_COMMAND_PLAN__`. +- For `supersede` / `remove`: strike the old bullet in place and mark `SUPERSEDED by {new}` or `RETIRED (R{N})`. Do not delete it. +- For `add` / `supersede`: add the smallest new bullet the plan must acknowledge (data, flow, or constraint — still no new stack unless the user asked for a technical change). +- If the delta cannot be expressed as a small patch (for example it invalidates the chosen architecture), do **not** rewrite the plan. Record `plan_status: needs-rebuild` and tell the user to run `__SPECKIT_COMMAND_PLAN__`. **`plan.md` absent:** skip. Next step after this command is `__SPECKIT_COMMAND_PLAN__`. @@ -244,15 +261,22 @@ Also: - [ ] T{next} [US{{n}}] {concrete task with file path if plan has one} ``` - One task per added AC or FR unless two adds are the same code change. Continue task IDs from the current maximum (`T014` after `T013`). Do not reuse cancelled IDs. + One task per added or superseding AC/FR unless two changes are the same code change. Continue task IDs from the current maximum (`T014` after `T013`). Do not reuse cancelled or superseded IDs. + +- **Supersede**: for each open (`- [ ]`) task that traces only to the old ID, mark it **in place** (do not delete, do not uncheck completed work): + + ```markdown + - [ ] ~~T012~~ SUPERSEDED (R{N} → T020) + ``` -- **Removals**: for each open (`- [ ]`) task that traces only to a removed ID, mark it cancelled **in place**: + Add `T020` in the Revision phase for the new ID. +- **Remove** (no replacement): mark open tasks cancelled: ```markdown - - [ ] ~~T012~~ CANCELLED (R{N}: removed US1/AC2) + - [ ] ~~T012~~ CANCELLED (R{N}: retired US1/AC2) ``` - Do not delete the line. Do not uncheck a completed task. If a **completed** task implemented a removed AC, append one new task under the Revision phase: `Review/remove leftover behavior for {retired ID}` so implement can clean it up. + If a **completed** task implemented a retired or superseded ID, append one cleanup task under the Revision phase: `Review/remove leftover behavior for {old-id}`. **`tasks.md` absent:** skip. Next step is `__SPECKIT_COMMAND_TASKS__` (or `__SPECKIT_COMMAND_PLAN__` if there is no plan either). @@ -274,6 +298,7 @@ Then append **only** this small block (IDs and a one-line summary — no full AC {one sentence} - added: {id, id} +- superseded: {old-id} → {new-id} - retired: {id, id} - reworded: {id, id} ``` diff --git a/templates/commands/taskstoissues.md b/templates/commands/taskstoissues.md index 5be0f253c1..b667578708 100644 --- a/templates/commands/taskstoissues.md +++ b/templates/commands/taskstoissues.md @@ -67,7 +67,7 @@ git config --get remote.origin.url 1. **Fetch existing issues for deduplication**: Before creating anything, build the set of task IDs you are about to process from `tasks.md` (each is a `T` followed by **at least** three digits, e.g. `T001` — `__SPECKIT_COMMAND_CONVERGE__` assigns new IDs with `T{M+1:03d}`, which is a floor rather than a cap, so once a file has more than 999 tasks the IDs are four digits or longer). Then use the GitHub MCP server's `list_issues` tool to look for issues that already cover those IDs. Do not pass a `state` value, since omitting it makes the tool return both open and closed issues. Request `perPage: 100` to keep the number of calls down, and since the tool uses cursor-based pagination, request pages with the `after` parameter (using the `endCursor` from the previous response). For each issue title, match it against the task ID pattern `\bT\d{3,}\b` (the `{3,}` accepts four-digit and longer IDs — with `\d{3}` a title containing `T1000` would not match at all, because the trailing `\b` cannot fall between two digits, so that task would be silently neither deduplicated nor created; word boundaries still stop a token like `ST001` from matching, and force the whole digit run to be consumed so `T100` can never match inside `T1000`; this also recognises titles written as `T001 ...`, `T001: ...` or `[T001] ...`) and, when it matches one of your task IDs, mark that ID as already having an issue. Stop paginating as soon as every task ID has been matched, or when there are no more pages, so you do not keep fetching the whole repository's issue history once all task IDs are accounted for. This bounds the number of calls on repos with large issue histories and still prevents duplicates when the command is re-run after `tasks.md` is regenerated or the skill is re-invoked. 1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. Task lines in `tasks.md` start with a markdown checkbox, so first strip the leading `- [ ]` (and any `[P]` / `[US#]` markers) to recover the task ID and its description. Create the issue with a single canonical title of the form `T001: `, with the ID written once followed by the task description (for example, the line `- [ ] T001 Create project structure` becomes the title `T001: Create project structure`). - **Skip** any task whose ID is already present in the set of existing issues from the previous step, and report it (for example, `T001 already has an issue, skipping`). - - **Skip** any task line that contains `CANCELLED` or a struck-through task ID (`~~T012~~`). Retired revision work must not become a GitHub issue. Still create issues for new non-cancelled tasks added under a `Revision R#` phase. + - **Skip** any task line that contains `CANCELLED`, `SUPERSEDED`, or a struck-through task ID (`~~T012~~`). Retired or replaced work must not become a GitHub issue. Still create issues for the **new** live tasks under a `Revision R#` phase. - Only create issues for tasks that do not yet have a matching issue. > [!CAUTION] diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index 9f783c29c8..4838d93864 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -109,11 +109,17 @@ def test_stable_ids_are_never_reused(self): def test_handles_add_and_remove_acceptance_criteria(self): assert "**add AC**" in self.text - assert "**remove AC**" in self.text + assert "**remove**" in self.text + assert "RETIRED" in self.text def test_cancels_open_tasks_instead_of_deleting_them(self): assert "CANCELLED" in self.text - assert "Do not delete the line" in self.text + assert "SUPERSEDED" in self.text + + def test_supersede_marks_old_and_adds_new(self): + assert "op`: `add` | `remove` | `reword` | `supersede`" in self.text + assert "SUPERSEDED by" in self.text + assert "two **live** items that contradict" in self.text def test_does_not_write_application_code(self): assert "NO APPLICATION CODE" in self.text @@ -136,6 +142,7 @@ class TestCascadeContracts: def test_implement_skips_cancelled_tasks(self): text = (COMMANDS / "implement.md").read_text(encoding="utf-8") assert "CANCELLED" in text + assert "SUPERSEDED" in text assert "not executable" in text def test_taskstoissues_skips_cancelled_tasks(self): From bd4f9a19d566e303a86c6322b7bf8ad765933445 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 09:29:39 +0530 Subject: [PATCH 07/14] refactor(revise): shrink command prompt to cut tokens Keep the same rules (in-place edit, supersede/retire, no-op duplicates, cascade, hooks) in a ~150-line prompt instead of ~385 so each /speckit.revise call sends less to the model. Assisted-by: Grok (model: grok-4.6, supervised) --- templates/commands/revise.md | 393 +++++++---------------------------- tests/test_revise_command.py | 10 +- 2 files changed, 85 insertions(+), 318 deletions(-) diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 3bed36f395..d3bbc4c379 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -18,9 +18,9 @@ scripts: # Revise Current Spec -Apply a **requirement delta** to the **current** feature specification. Edit `spec.md` in place, record the change in `revisions.md`, and cascade into `plan.md` / `tasks.md` when those files exist. +Apply a **requirement delta** to the **current** feature. Edit `spec.md` in place. Cascade `plan.md` / `tasks.md` only if they exist. -This command is for living-spec edits: adding or removing acceptance criteria, functional requirements, user stories, success criteria, or scope. It is **not** `__SPECKIT_COMMAND_SPECIFY__` (that starts a new feature) and **not** `__SPECKIT_COMMAND_CONVERGE__` (that finds code that lagged an unchanged spec). +Not `__SPECKIT_COMMAND_SPECIFY__` (new feature folder). Not `__SPECKIT_COMMAND_CONVERGE__` (code lagged an unchanged spec). ## User Input @@ -28,358 +28,125 @@ This command is for living-spec edits: adding or removing acceptance criteria, f $ARGUMENTS ``` -You **MUST** consider the user input before proceeding (if not empty). +You **MUST** use the input if not empty. Empty → ask (interactive) or stop (automated). Do not invent a delta. New product (different user + outcome, no shared stories) → stop; recommend `__SPECKIT_COMMAND_SPECIFY__`. -Treat the input as a delta against the current spec. Typical shapes: +## Hook protocol -- Add an acceptance criterion / scenario (Given / When / Then, or a short behavior). -- Remove an acceptance criterion, FR, SC, user story, or edge case. The user may name an ID (`FR-004`, `US2/AC1`, `SC-003`) or describe the behavior. -- Change the wording of an existing item without changing its ID. -- Mix of the above in one request. +Use for `hooks.before_revise` (now) and `hooks.after_revise` (after writes, before the Completion Report). -If the input is empty: ask what to add, remove, or change (interactive), or stop with a note that there is nothing to revise (automated). Do **not** invent a revision. +If `.specify/extensions.yml` is missing or unreadable, skip. Read that event key. Skip `enabled: false`. Skip hooks with a non-empty `condition` (leave those to HookExecutor). No `enabled` → enabled. -If the request is clearly a **new feature** (different user, different outcome, no shared stories with the current spec), STOP and recommend `__SPECKIT_COMMAND_SPECIFY__` instead of forcing it into this spec. +- **Mandatory** (`optional: false`): emit and **run** the hook (skills-mode invocation may differ from `{command}`): -## Pre-Execution Checks - -**Check for extension hooks (before revision)**: - -- Check if `.specify/extensions.yml` exists in the project root. -- If it exists, read it and look for entries under the `hooks.before_revise` key -- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally -- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. -- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation -- For each executable hook, output the following based on its `optional` flag: - - **Optional hook** (`optional: true`): - - ```text - ## Extension Hooks - - **Optional Pre-Hook**: {extension} - Command: `/{command}` - Description: {description} - - Prompt: {prompt} - To execute: `/{command}` - ``` - - - **Mandatory hook** (`optional: false`): - - ```text - ## Extension Hooks - - **Automatic Pre-Hook**: {extension} - Executing: `/{command}` - EXECUTE_COMMAND: {command} - - Wait for the result of the hook command before proceeding to the Goal. - ``` - - After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - -- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently - -## Goal - -Keep one feature directory as the source of truth while the contract changes. After this command: - -- `spec.md` is the **only** current contract -- `revisions.md` is a **small dated log** of what changed (not a second spec) -- `plan.md` (if present) no longer describes retired behavior -- `tasks.md` (if present) has new work for additions and cancelled open tasks for removals -- no application code has been edited - -## Operating Constraints - -**IN-PLACE, SAME FEATURE DIRECTORY**: Do **not** create a new `specs/` folder, a new branch, or a new spec file. All writes stay inside the current `FEATURE_DIR`. - -**NO APPLICATION CODE**: Do not create, modify, or delete product source. Completing new tasks is `__SPECKIT_COMMAND_IMPLEMENT__`. Cleaning up code that implemented a *removed* AC is also implement (via a cancellation/cleanup task), not this command. - -**STABLE IDS — NEVER REUSE**: - -- Do not renumber existing `FR-###`, `SC-###`, user-story numbers, or acceptance-scenario indexes that remain. -- Do not reuse a retired ID for a new item. If `FR-004` is removed, the next new requirement is `FR-008` (or whatever the next unused number is), never a new `FR-004`. -- New items take the next free number after the highest ID **ever issued** in this spec, including IDs listed as retired in `revisions.md`. - -**SPEC STAYS FUNCTIONAL**: Write what users need and why. No tech stack, libraries, APIs, or file paths in `spec.md`. Those belong in `plan.md`. - -**CONSTITUTION AUTHORITY**: `/memory/constitution.md` is non-negotiable. A new requirement that violates a `MUST` principle is rejected: report the conflict and do not apply that part of the delta. If the constitution is an unfilled template, skip this check. - -**REVISIONS.MD IS A LOG, NOT SOURCE OF TRUTH**: -- `spec.md` decides what is required now. Never treat `revisions.md` as the spec. -- Each entry is one short dated block: `R{N}`, date, one-line summary, IDs only. -- Do not copy AC/FR prose, plan notes, or next-command instructions into the log. -- Implement / analyze / converge read `spec.md` (and `CANCELLED` tasks). They may consult the log only for **retired IDs**. - -**DUPLICATES ARE A NO-OP**: -- Compare the requested delta to the **current** `spec.md` (not to the log). -- `add` of behavior already in the spec → drop that change (duplicate). -- `remove` of an ID already absent / already retired → drop that change. -- `reword` that does not change meaning or text → drop that change. -- Same delta as the latest `R#` (re-run) → drop the whole request. -- If every change is a duplicate, STOP. Report which items were already true. Do **not** write `spec.md`, `revisions.md`, `plan.md`, or `tasks.md`. Do **not** bump `R{N}`. -- Partial request: apply only the non-duplicate changes; mention skipped duplicates in the report. - -**MINIMAL PLAN/TASKS EDITS**: - -- Do not regenerate `plan.md` or rewrite `tasks.md` from scratch. -- Patch only sections the delta affects. -- Do not reorder, renumber, or delete existing task IDs. Cancel an open task by marking it in place (see Step 6). Leave completed tasks checked. - -## Execution Steps - -### 1. Resolve the current feature - -Run `{SCRIPT}` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: - -- `FEATURE_DIR` -- `FEATURE_SPEC` - -Derive: - -- `SPEC` = `FEATURE_DIR/spec.md` -- `PLAN` = `FEATURE_DIR/plan.md` -- `TASKS` = `FEATURE_DIR/tasks.md` -- `REVISIONS` = `FEATURE_DIR/revisions.md` -- `CONSTITUTION` = `/memory/constitution.md` (if present) - -If JSON parsing fails, or `spec.md` is missing, STOP and instruct the user to run `__SPECKIT_COMMAND_SPECIFY__` first. - -For single quotes in args like "I'm Groot", use escape syntax: e.g `'I'\''m Groot'` (or double-quote if possible: `"I'm Groot"`). - -### 2. Load artifacts - -Read `spec.md` in full. From it, inventory: - -- User stories (number, title, priority) and each **Acceptance Scenario** (stable key `US{n}/AC{i}`) -- Functional Requirements (`FR-###`) -- Success Criteria (`SC-###`) -- Edge cases, out-of-scope, assumptions (if present) -- Highest issued ID per series - -If `revisions.md` exists, load retired IDs so they stay retired. - -If `plan.md` exists, load section headings and any references to FR/SC/story IDs. - -If `tasks.md` exists, load every task ID, checkbox state, phase heading, and which requirement/story it traces to. Compute the next task ID and the next phase number. - -If `CONSTITUTION` exists and is not an unfilled template, load `MUST` / `SHOULD` principles. - -### 3. Classify the delta - -Turn the user input into a list of `Change` records. Each record has: - -- `op`: `add` | `remove` | `reword` | `supersede` -- `kind`: `acceptance-scenario` | `functional-requirement` | `success-criterion` | `user-story` | `edge-case` | `scope` -- `target`: existing ID if the user named or uniquely described one; empty for a new item -- `replaces`: old ID when `op` is `supersede` -- `text`: the new wording (add/reword/supersede) or the retired wording (remove) - -Ignore inventory lines already marked `SUPERSEDED` or `RETIRED` when matching current behavior. Still count those IDs as issued so they are never reused. - -Resolution rules: - -- If the user names `FR-004`, `SC-002`, `US1`, or `US2/AC1`, use that ID. -- If they describe behavior ("password login", "CSV export") and exactly one **live** inventory item matches, use that item. -- If several items match, ask (interactive) or STOP listing the candidates (automated). Do not guess. -- Adding an AC: attach it to the user story the user named. If they did not name a story and exactly one story fits, use that. Otherwise ask / STOP. -- **Conflict / replacement (prefer this over silent delete):** if the new behavior **contradicts** a live item, or the user is swapping one behavior for another ("SSO instead of password"), use `supersede`: keep the old ID in place marked `SUPERSEDED by {new-id} (R{N})`, and **add** a new ID for the new text. Do not leave two live items that disagree. -- Tightening wording of the **same** behavior → `reword` (keep the ID). Do not supersede. -- Dropping something with **no** replacement → `remove` (mark `RETIRED (R{N})`, do not delete the line). -- Removing a whole user story: `remove` the story and `remove` or `supersede` each of its ACs depending on whether replacements were given. - -Reject any `add` that conflicts with a constitution `MUST`. Leave the rest of the delta intact if some items are valid. - -Drop duplicates using the **DUPLICATES ARE A NO-OP** rules above. If nothing remains, STOP. Do not write files and do not append a revision entry. - -### 4. Show the planned revision (before writes) - -Output a compact table and wait only if the user asked to preview or if any `remove` targets a P1 story's last remaining AC (that would empty the MVP). Otherwise proceed. - -```text -## Planned Revision R{N} - -| Op | Kind | ID | Summary | -|----|------|----|---------| -| supersede | functional-requirement | FR-004 → FR-008 | Password login → SSO only | -| add | acceptance-scenario | US1/AC3 | Expired session → SSO redirect | -``` - -`N` is 1 if `revisions.md` does not exist, otherwise one more than the highest `R#` already recorded. - -### 5. Edit `spec.md` - -Apply every `Change` to `spec.md`. **Live** items are unmarked lines. `SUPERSEDED` / `RETIRED` lines stay visible but are **not** current requirements. - -- **add AC**: append a numbered **Given / When / Then** under that story. Next index for that story only. Do not renumber earlier ACs (holes are correct). -- **add FR / SC**: append with the next unused ID. -- **reword**: replace the live text; keep the ID and position. Do not add a sibling ID. -- **supersede**: do **not** delete the old line. Strike it and point at the new ID, then add the new item next to it (or at the end of that section): - - ```markdown - - **FR-004** ~~Users MUST sign in with email and password~~ — SUPERSEDED by **FR-008** (R2) - - **FR-008**: Users MUST sign in with company SSO only - ``` - - Same pattern for ACs (`US1/AC2` → `US1/AC4`) and SCs (`SC-001` → `SC-005`). -- **remove** (no replacement): do **not** delete the line. Strike it and mark retired: - - ```markdown - - **SC-003** ~~90% first-attempt success~~ — RETIRED (R2) - ``` - -Also: - -- Set or update `**Last Revised**: {today's date} (R{N})` near the spec header. Do not change `**Created**`. -- If `**Status**` is `Draft` and a plan already exists, leave Status as-is unless the spec had a custom status; do not invent a new status vocabulary. -- Keep the spec's existing section structure. Do not add a changelog section — `revisions.md` stays the small dated log. -- Do not embed implementation checklists in the spec. -- Never leave two **live** items that contradict each other. - -### 6. Cascade to plan and tasks (only if those files exist) - -**`plan.md` present:** - -- For `supersede` / `remove`: strike the old bullet in place and mark `SUPERSEDED by {new}` or `RETIRED (R{N})`. Do not delete it. -- For `add` / `supersede`: add the smallest new bullet the plan must acknowledge (data, flow, or constraint — still no new stack unless the user asked for a technical change). -- If the delta cannot be expressed as a small patch (for example it invalidates the chosen architecture), do **not** rewrite the plan. Record `plan_status: needs-rebuild` and tell the user to run `__SPECKIT_COMMAND_PLAN__`. - -**`plan.md` absent:** skip. Next step after this command is `__SPECKIT_COMMAND_PLAN__`. - -**`tasks.md` present:** - -- **Additions**: append a new section at the bottom: - - ```markdown - ## Phase {next}: Revision R{N} - - **Goal**: Implement spec changes from revision R{N} - - - [ ] T{next} [US{{n}}] {concrete task with file path if plan has one} + ```text + ## Extension Hooks + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} ``` - One task per added or superseding AC/FR unless two changes are the same code change. Continue task IDs from the current maximum (`T014` after `T013`). Do not reuse cancelled or superseded IDs. + Wait for it. Emitting the block is not enough. -- **Supersede**: for each open (`- [ ]`) task that traces only to the old ID, mark it **in place** (do not delete, do not uncheck completed work): +- **Optional** (`optional: true`): - ```markdown - - [ ] ~~T012~~ SUPERSEDED (R{N} → T020) + ```text + ## Extension Hooks + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + Prompt: {prompt} + To execute: `/{command}` ``` - Add `T020` in the Revision phase for the new ID. -- **Remove** (no replacement): mark open tasks cancelled: +After writes, the same protocol uses **Automatic Hook** / **Optional Hook** labels (not Pre-Hook). - ```markdown - - [ ] ~~T012~~ CANCELLED (R{N}: retired US1/AC2) - ``` +## Rules - If a **completed** task implemented a retired or superseded ID, append one cleanup task under the Revision phase: `Review/remove leftover behavior for {old-id}`. +| | | +|---|---| +| Folder | Same `FEATURE_DIR`. Do **not** create a new `specs/` folder, branch, or spec file. | +| **NO APPLICATION CODE** | No product source. Implement/cleanup is `__SPECKIT_COMMAND_IMPLEMENT__`. | +| **STABLE IDS** | Never renumber live IDs. Never reuse a retired/superseded ID. Next ID = max ever issued (include log + marked lines). | +| Contract | `spec.md` is the only current contract. Functional only (no stack/APIs/paths). | +| Log | `revisions.md` is a **dated log**, not a spec. IDs + one sentence. No AC prose. | +| Live vs dead | Unmarked lines are live. `SUPERSEDED` / `RETIRED` stay visible but are not current. Never two **live** items that contradict. | +| Constitution | Reject `add`/`supersede` that violate a `MUST`. Skip if constitution is an unfilled template. | +| Plan/tasks | Patch only. Do not regenerate. Do not delete or uncheck completed tasks. | -**`tasks.md` absent:** skip. Next step is `__SPECKIT_COMMAND_TASKS__` (or `__SPECKIT_COMMAND_PLAN__` if there is no plan either). +**Ops:** `add` | `reword` | `supersede` | `remove` -### 7. Append `revisions.md` +- Same wording, tighter → `reword` (keep ID). +- New behavior contradicts a live item, or swap ("SSO instead of password") → `supersede`: mark old `SUPERSEDED by {new-id} (R{N})`, **add** new ID. +- Drop, no replacement → `remove`: mark `RETIRED (R{N})`, do not delete the line. +- Named ID wins. Else unique live match. Ambiguous → ask or stop. New AC → named story, or the one story that fits. -This file is a **dated index**, not a spec. Create it if it does not exist: +**DUPLICATES ARE A NO-OP** (compare to **live** `spec.md`, not the log): -```markdown -# Spec Revisions +- add already live / remove already dead / reword unchanged / same as latest `R#` → drop. +- All duplicates → STOP. Report already-true items. Write nothing. Do **not** bump `R{N}`. +- Mixed → apply only new parts; mention skips. -Dated log of in-place edits. `spec.md` is the source of truth. Retired IDs must not be reused. -``` +## Steps -Then append **only** this small block (IDs and a one-line summary — no full AC text, no cascade, no next-command): +1. **Resolve.** Run `{SCRIPT}` once. Parse `FEATURE_DIR`, `FEATURE_SPEC`. Paths: `spec.md`, `plan.md`, `tasks.md`, `revisions.md`, `/memory/constitution.md`. Missing spec → `__SPECKIT_COMMAND_SPECIFY__`. Quotes: `'I'\''m Groot'`. -```markdown -## R{N} — {YYYY-MM-DD} +2. **Load.** Inventory live stories/`US{n}/AC{i}`, `FR-###`, `SC-###`, edge/out-of-scope. Count issued IDs including `SUPERSEDED`/`RETIRED` and the log. Load plan headings/IDs if present; task IDs/checkboxes/phases if present (next T-id and phase). Load constitution MUST/SHOULD if real. -{one sentence} +3. **Classify.** Build `Change{op, kind, target, replaces, text}`. Drop duplicates. Constitution-invalid items out; rest stay. Nothing left → stop, no writes. -- added: {id, id} -- superseded: {old-id} → {new-id} -- retired: {id, id} -- reworded: {id, id} -``` +4. **Preview** only if user asked or a `remove` would empty a P1 story's last AC. Else continue. `N` = 1 or last `R#`+1. -Omit empty bullets. Never edit or delete earlier `R#` entries. If this revision was a no-op (all duplicates), do not append anything. +5. **Write `spec.md`.** + - `add` AC: next index on that story (holes OK). `add` FR/SC: next unused ID. + - `reword`: replace live text; same ID. + - `supersede`: keep old line, strike it, add new: -If `FEATURE_DIR/checklists/requirements.md` exists, re-evaluate its items against the revised spec and update pass/fail markers **before** post-execution hooks so a git auto-commit includes those edits. Do not invent a new checklist. + `- **FR-004** ~~password login~~ — SUPERSEDED by **FR-008** (R2)` + - `remove`: strike + `RETIRED (R2)`. + - Set `**Last Revised**: {date} (R{N})`. Do not change `**Created**`. No new sections, no checklists in the spec. -## Mandatory Post-Execution Hooks +6. **Cascade (files that exist only).** + - `plan.md`: strike old (`SUPERSEDED by {new}` / `RETIRED (R{N})`); add smallest new bullet. Architecture break → do not rewrite; `needs-rebuild` → `__SPECKIT_COMMAND_PLAN__`. Missing plan → next is plan. + - `tasks.md`: append `## Phase {n}: Revision R{N}`. New T-ids from max+1. + - supersede open task: `- [ ] ~~T012~~ SUPERSEDED (R{N} → T020)` and add T020. + - remove open task: `- [ ] ~~T012~~ CANCELLED (R{N}: retired {id})`. + - completed work on old ID → one cleanup task. + Missing tasks → `__SPECKIT_COMMAND_TASKS__` (or plan if no plan). -**You MUST complete this section before reporting completion to the user.** +7. **Log.** Create `revisions.md` if needed (`# Spec Revisions` + one line: spec is truth). Append only: -Check if `.specify/extensions.yml` exists in the project root. -- If it does not exist, or no hooks are registered under `hooks.after_revise`, skip to the Completion Report. -- If it exists, read it and look for entries under the `hooks.after_revise` key. -- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report. -- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. -- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation -- For each executable hook, output the following based on its `optional` flag: - - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**: - ``` - ## Extension Hooks + ```markdown + ## R{N} — {YYYY-MM-DD} + {one sentence} + - added: {id} + - superseded: {old} → {new} + - retired: {id} + - reworded: {id} + ``` - **Automatic Hook**: {extension} - Executing: `/{command}` - EXECUTE_COMMAND: {command} - ``` - After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. - - **Optional hook** (`optional: true`): - ``` - ## Extension Hooks + Omit empty bullets. Never edit prior `R#`. No-op → do not append. - **Optional Hook**: {extension} - Command: `/{command}` - Description: {description} +8. If `checklists/requirements.md` exists, refresh pass/fail **before** after-hooks (so git commit includes it). - Prompt: {prompt} - To execute: `/{command}` - ``` +9. Run **after_revise** via Hook protocol. ## Completion Report -After hooks, report completion once: +Once, after hooks: ```text ## Revision R{N} Applied - Feature: {FEATURE_DIR} -Spec: {SPEC} - | Op | ID | Result | -|----|----|--------| -| add | US1/AC3 | written to spec.md | -| remove | FR-004 | retired; T012 cancelled | - Next: {command} - -Open revision tasks: {task ids or "none"} ``` -Also include: -- Cascade result for `plan.md` and `tasks.md` -- Next command (`__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`) +Next is `__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`. On no-op, say so and skip hooks that would commit empty work if none ran. ## Done When -- [ ] `spec.md` reflects the requested delta; no new feature directory was created -- [ ] `revisions.md` has a new `R{N}` entry listing added, removed (retired), and reworded IDs -- [ ] Retired IDs were not reused -- [ ] `plan.md` was patched or marked `needs-rebuild` (or skipped if missing) -- [ ] `tasks.md` gained a Revision phase and/or cancelled obsolete open tasks (or skipped if missing) -- [ ] No application code was edited -- [ ] Extension hooks dispatched or skipped according to the rules above -- [ ] Completion reported to the user - -## Quick Guidelines - -- Prefer the smallest delta that captures the user's request. -- Adding an AC to an existing story is the common case — do that, don't invent a new story. -- Removing an AC does not delete shipped code; it retires the requirement and leaves a cleanup task if the code already exists. -- If you are unsure whether two phrasings are the same requirement, `reword` (keep the ID). If they are different behaviors, `remove` + `add`. -- When in doubt, ask one question rather than applying a guessed delta. +- [ ] Same feature dir; `spec.md` updated or explicit no-op +- [ ] New `R{N}` only if something changed; IDs not reused +- [ ] Dead lines marked SUPERSEDED/RETIRED; replacements added +- [ ] Plan/tasks patched or skipped; no app code +- [ ] Hooks run or skipped; user reported diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index 4838d93864..68ee965aa6 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -104,12 +104,12 @@ def test_duplicates_are_a_noop(self): assert "Do **not** bump `R{N}`" in self.text or "do not append" in self.text.lower() def test_stable_ids_are_never_reused(self): - assert "Do not reuse a retired ID" in self.text + assert "Never reuse" in self.text assert "STABLE IDS" in self.text def test_handles_add_and_remove_acceptance_criteria(self): - assert "**add AC**" in self.text - assert "**remove**" in self.text + assert "New AC" in self.text or "add AC" in self.text + assert "remove" in self.text assert "RETIRED" in self.text def test_cancels_open_tasks_instead_of_deleting_them(self): @@ -117,9 +117,9 @@ def test_cancels_open_tasks_instead_of_deleting_them(self): assert "SUPERSEDED" in self.text def test_supersede_marks_old_and_adds_new(self): - assert "op`: `add` | `remove` | `reword` | `supersede`" in self.text + assert "supersede" in self.text assert "SUPERSEDED by" in self.text - assert "two **live** items that contradict" in self.text + assert "contradict" in self.text def test_does_not_write_application_code(self): assert "NO APPLICATION CODE" in self.text From e14e546a24ea436948a4b970d579f109f218d2e1 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 09:30:43 +0530 Subject: [PATCH 08/14] refactor(revise): rewrite the command as a concise human prompt Same behavior, spoken as a briefing instead of a spec: what this is, how to classify the delta, what to write, when to stop. Assisted-by: Grok (model: grok-4.6, supervised) --- templates/commands/revise.md | 156 ++++++++++++++++------------------- tests/test_revise_command.py | 21 +++-- 2 files changed, 80 insertions(+), 97 deletions(-) diff --git a/templates/commands/revise.md b/templates/commands/revise.md index d3bbc4c379..a4cc507e58 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -16,104 +16,99 @@ scripts: py: scripts/python/check_prerequisites.py --json --paths-only --- -# Revise Current Spec +The user is changing the **current** feature spec — not starting a new one. -Apply a **requirement delta** to the **current** feature. Edit `spec.md` in place. Cascade `plan.md` / `tasks.md` only if they exist. +Edit `spec.md` in place. Patch `plan.md` and `tasks.md` only if they already exist. Do not write application code; leave that to `__SPECKIT_COMMAND_IMPLEMENT__`. -Not `__SPECKIT_COMMAND_SPECIFY__` (new feature folder). Not `__SPECKIT_COMMAND_CONVERGE__` (code lagged an unchanged spec). +This is not `__SPECKIT_COMMAND_SPECIFY__` (that opens a new `specs/` folder). It is not `__SPECKIT_COMMAND_CONVERGE__` (that assumes the spec is stable and the code lagged). -## User Input +## Input ```text $ARGUMENTS ``` -You **MUST** use the input if not empty. Empty → ask (interactive) or stop (automated). Do not invent a delta. New product (different user + outcome, no shared stories) → stop; recommend `__SPECKIT_COMMAND_SPECIFY__`. +Use it if it isn't empty. If it is empty, ask what changed (or stop if nobody is there). Don't invent a delta. -## Hook protocol +If they described a new product — different user, different outcome, no shared stories — stop and point them at `__SPECKIT_COMMAND_SPECIFY__`. -Use for `hooks.before_revise` (now) and `hooks.after_revise` (after writes, before the Completion Report). +## Before you start (and again after you write) -If `.specify/extensions.yml` is missing or unreadable, skip. Read that event key. Skip `enabled: false`. Skip hooks with a non-empty `condition` (leave those to HookExecutor). No `enabled` → enabled. +If `.specify/extensions.yml` exists, run hooks for `hooks.before_revise` now, and `hooks.after_revise` after the files are written, before you report. -- **Mandatory** (`optional: false`): emit and **run** the hook (skills-mode invocation may differ from `{command}`): +Skip the file if it's missing or invalid. Skip any hook with `enabled: false`. Skip a hook that has a `condition` (don't evaluate it). Missing `enabled` means on. - ```text - ## Extension Hooks - **Automatic Pre-Hook**: {extension} - Executing: `/{command}` - EXECUTE_COMMAND: {command} - ``` +Mandatory hook (`optional: false`) — print this and **actually run** it. Skills-mode names may differ from `{command}`. Waiting on the block alone is not enough. - Wait for it. Emitting the block is not enough. +```text +## Extension Hooks +**Automatic Pre-Hook**: {extension} +Executing: `/{command}` +EXECUTE_COMMAND: {command} +``` + +Optional hook (`optional: true`): + +```text +## Extension Hooks +**Optional Pre-Hook**: {extension} +Command: `/{command}` +Description: {description} +Prompt: {prompt} +To execute: `/{command}` +``` + +After the write, use **Automatic Hook** / **Optional Hook** (drop "Pre-"). + +## How to think about it -- **Optional** (`optional: true`): +Stay in this feature directory. Do **not** create a new `specs/` folder. - ```text - ## Extension Hooks - **Optional Pre-Hook**: {extension} - Command: `/{command}` - Description: {description} - Prompt: {prompt} - To execute: `/{command}` - ``` +`spec.md` is the contract. Only unmarked lines are live. Keep `SUPERSEDED` / `RETIRED` lines so people can see what changed, but don't treat them as current. Never leave two live items that contradict. -After writes, the same protocol uses **Automatic Hook** / **Optional Hook** labels (not Pre-Hook). +`revisions.md` is a dated log, not a spec. One sentence and IDs. No copied AC text. -## Rules +Don't renumber live IDs. Don't reuse a retired or superseded ID. The next number is one past the highest ID ever used (including struck lines and the log). -| | | -|---|---| -| Folder | Same `FEATURE_DIR`. Do **not** create a new `specs/` folder, branch, or spec file. | -| **NO APPLICATION CODE** | No product source. Implement/cleanup is `__SPECKIT_COMMAND_IMPLEMENT__`. | -| **STABLE IDS** | Never renumber live IDs. Never reuse a retired/superseded ID. Next ID = max ever issued (include log + marked lines). | -| Contract | `spec.md` is the only current contract. Functional only (no stack/APIs/paths). | -| Log | `revisions.md` is a **dated log**, not a spec. IDs + one sentence. No AC prose. | -| Live vs dead | Unmarked lines are live. `SUPERSEDED` / `RETIRED` stay visible but are not current. Never two **live** items that contradict. | -| Constitution | Reject `add`/`supersede` that violate a `MUST`. Skip if constitution is an unfilled template. | -| Plan/tasks | Patch only. Do not regenerate. Do not delete or uncheck completed tasks. | +Keep the spec functional — what and why, not stack or file paths. -**Ops:** `add` | `reword` | `supersede` | `remove` +If a new requirement breaks a constitution `MUST`, drop that part. Ignore an unfilled constitution template. -- Same wording, tighter → `reword` (keep ID). -- New behavior contradicts a live item, or swap ("SSO instead of password") → `supersede`: mark old `SUPERSEDED by {new-id} (R{N})`, **add** new ID. -- Drop, no replacement → `remove`: mark `RETIRED (R{N})`, do not delete the line. -- Named ID wins. Else unique live match. Ambiguous → ask or stop. New AC → named story, or the one story that fits. +Don't regenerate the plan or the whole task list. Don't delete task lines or uncheck finished work. -**DUPLICATES ARE A NO-OP** (compare to **live** `spec.md`, not the log): +## What kind of change is this? -- add already live / remove already dead / reword unchanged / same as latest `R#` → drop. -- All duplicates → STOP. Report already-true items. Write nothing. Do **not** bump `R{N}`. -- Mixed → apply only new parts; mention skips. +- Same idea, better wording → **reword**. Keep the ID. +- Replaces something live ("SSO instead of password") → **supersede**. Strike the old line (`SUPERSEDED by FR-008 (R2)`) and add a new ID. Don't delete the old line. +- Gone, nothing replaces it → **remove**. Strike it and mark `RETIRED (R2)`. +- Brand new, no conflict → **add**. Next free ID. A new AC goes on the story they named, or the only story that fits. -## Steps +If they named `FR-004` / `US1/AC2`, use that. If the description matches exactly one live item, use that. If it's ambiguous, ask — don't guess. -1. **Resolve.** Run `{SCRIPT}` once. Parse `FEATURE_DIR`, `FEATURE_SPEC`. Paths: `spec.md`, `plan.md`, `tasks.md`, `revisions.md`, `/memory/constitution.md`. Missing spec → `__SPECKIT_COMMAND_SPECIFY__`. Quotes: `'I'\''m Groot'`. +Already true in the live spec (or the same as the last `R#`) is a duplicate. Skip it. If everything is a duplicate, stop: say so, write nothing, don't bump `R{N}`. If only part is new, do that part and mention what you skipped. -2. **Load.** Inventory live stories/`US{n}/AC{i}`, `FR-###`, `SC-###`, edge/out-of-scope. Count issued IDs including `SUPERSEDED`/`RETIRED` and the log. Load plan headings/IDs if present; task IDs/checkboxes/phases if present (next T-id and phase). Load constitution MUST/SHOULD if real. +## Do the work -3. **Classify.** Build `Change{op, kind, target, replaces, text}`. Drop duplicates. Constitution-invalid items out; rest stay. Nothing left → stop, no writes. +1. Run `{SCRIPT}` once. Read `FEATURE_DIR` and `FEATURE_SPEC`. You need `spec.md`. Also look at `plan.md`, `tasks.md`, `revisions.md`, and `/memory/constitution.md` if they're there. No spec → `__SPECKIT_COMMAND_SPECIFY__`. Awkward quotes: `'I'\''m Groot'`. -4. **Preview** only if user asked or a `remove` would empty a P1 story's last AC. Else continue. `N` = 1 or last `R#`+1. +2. List live stories, ACs (`US{n}/AC{i}`), FRs, SCs. Remember every ID ever issued. Note the next task id and phase if `tasks.md` exists. -5. **Write `spec.md`.** - - `add` AC: next index on that story (holes OK). `add` FR/SC: next unused ID. - - `reword`: replace live text; same ID. - - `supersede`: keep old line, strike it, add new: +3. Turn the input into changes. Drop duplicates and constitution clashes. Nothing left → stop. - `- **FR-004** ~~password login~~ — SUPERSEDED by **FR-008** (R2)` - - `remove`: strike + `RETIRED (R2)`. - - Set `**Last Revised**: {date} (R{N})`. Do not change `**Created**`. No new sections, no checklists in the spec. +4. Only preview if they asked, or if a remove would wipe the last AC on a P1 story. Otherwise just do it. `N` is 1, or last `R#` plus one. -6. **Cascade (files that exist only).** - - `plan.md`: strike old (`SUPERSEDED by {new}` / `RETIRED (R{N})`); add smallest new bullet. Architecture break → do not rewrite; `needs-rebuild` → `__SPECKIT_COMMAND_PLAN__`. Missing plan → next is plan. - - `tasks.md`: append `## Phase {n}: Revision R{N}`. New T-ids from max+1. - - supersede open task: `- [ ] ~~T012~~ SUPERSEDED (R{N} → T020)` and add T020. - - remove open task: `- [ ] ~~T012~~ CANCELLED (R{N}: retired {id})`. - - completed work on old ID → one cleanup task. - Missing tasks → `__SPECKIT_COMMAND_TASKS__` (or plan if no plan). +5. Edit `spec.md`. Holes in AC numbers are fine. Set `**Last Revised**: {today} (R{N})`. Leave `**Created**` alone. -7. **Log.** Create `revisions.md` if needed (`# Spec Revisions` + one line: spec is truth). Append only: + Supersede looks like: `- **FR-004** ~~password login~~ — SUPERSEDED by **FR-008** (R2)` + +6. If `plan.md` exists, strike the old bullet and add a small new one. If the architecture is actually invalid, don't rewrite the plan — say `needs-rebuild` and send them to `__SPECKIT_COMMAND_PLAN__`. No plan → that's the next command. + +7. If `tasks.md` exists, append `## Phase {n}: Revision R{N}` with new T-ids. + - Open task for an old ID: `- [ ] ~~T012~~ SUPERSEDED (R{N} → T020)` and add T020. Or `CANCELLED` if nothing replaces it. + - Finished work for an old ID: one cleanup task. + - No tasks file → `__SPECKIT_COMMAND_TASKS__` (or plan first). + +8. Append to `revisions.md` (create it if needed: title + "spec.md is the source of truth"): ```markdown ## R{N} — {YYYY-MM-DD} @@ -124,29 +119,18 @@ After writes, the same protocol uses **Automatic Hook** / **Optional Hook** labe - reworded: {id} ``` - Omit empty bullets. Never edit prior `R#`. No-op → do not append. - -8. If `checklists/requirements.md` exists, refresh pass/fail **before** after-hooks (so git commit includes it). + Skip empty bullets. Don't edit old entries. Don't append on a no-op. -9. Run **after_revise** via Hook protocol. +9. If `checklists/requirements.md` exists, update its checkboxes before after-hooks so a git commit includes them. -## Completion Report - -Once, after hooks: - -```text -## Revision R{N} Applied -Feature: {FEATURE_DIR} -| Op | ID | Result | -Next: {command} -``` +10. Run `hooks.after_revise`. -Next is `__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`. On no-op, say so and skip hooks that would commit empty work if none ran. +Then tell them once what changed, and what to run next (`__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`). If it was a no-op, say that. ## Done When -- [ ] Same feature dir; `spec.md` updated or explicit no-op -- [ ] New `R{N}` only if something changed; IDs not reused -- [ ] Dead lines marked SUPERSEDED/RETIRED; replacements added -- [ ] Plan/tasks patched or skipped; no app code -- [ ] Hooks run or skipped; user reported +- [ ] Same feature folder; spec updated or you said it was already true +- [ ] New `R{N}` only when something actually changed +- [ ] Old lines marked SUPERSEDED/RETIRED; new IDs added; none reused +- [ ] Plan/tasks patched or skipped; no application code +- [ ] Hooks handled; user got a short report diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index 68ee965aa6..2e42f1c40f 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -89,27 +89,26 @@ def setup_method(self): self.text = REVISE.read_text(encoding="utf-8") def test_does_not_create_a_new_feature_directory(self): - assert "Do **not** create a new `specs/` folder" in self.text + assert "new `specs/` folder" in self.text def test_edits_spec_in_place(self): - assert "Edit `spec.md` in place" in self.text + assert "in place" in self.text + assert "spec.md" in self.text def test_records_revisions_changelog(self): assert "revisions.md" in self.text - assert "dated log" in self.text.lower() or "dated index" in self.text.lower() - assert "not a spec" in self.text.lower() or "NOT SOURCE OF TRUTH" in self.text + assert "dated log" in self.text + assert "not a spec" in self.text def test_duplicates_are_a_noop(self): - assert "DUPLICATES ARE A NO-OP" in self.text - assert "Do **not** bump `R{N}`" in self.text or "do not append" in self.text.lower() + assert "duplicate" in self.text + assert "don't bump `R{N}`" in self.text or "don't append" in self.text def test_stable_ids_are_never_reused(self): - assert "Never reuse" in self.text - assert "STABLE IDS" in self.text + assert "Don't reuse" in self.text or "Don't renumber" in self.text def test_handles_add_and_remove_acceptance_criteria(self): - assert "New AC" in self.text or "add AC" in self.text - assert "remove" in self.text + assert "Brand new" in self.text or "add" in self.text assert "RETIRED" in self.text def test_cancels_open_tasks_instead_of_deleting_them(self): @@ -122,7 +121,7 @@ def test_supersede_marks_old_and_adds_new(self): assert "contradict" in self.text def test_does_not_write_application_code(self): - assert "NO APPLICATION CODE" in self.text + assert "Do not write application code" in self.text or "no application code" in self.text def test_hands_off_to_implement_plan_or_tasks(self): assert "__SPECKIT_COMMAND_IMPLEMENT__" in self.text From 5b8085064d5a3491947225a8515d09557b67ae9c Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 09:57:45 +0530 Subject: [PATCH 09/14] fix(revise): never rewrite artifacts; plan add-and-remove after implement Requirement changes must supersede or retire old IDs, not restyle the same line. plan.md and tasks.md are only appended. After implementation, new work is add-code tasks and invalid work is remove-code tasks. Assisted-by: Grok (model: grok-4.6, supervised) --- templates/commands/revise.md | 117 ++++++++++++++++++++--------------- tests/test_revise_command.py | 6 +- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/templates/commands/revise.md b/templates/commands/revise.md index a4cc507e58..d464464d3a 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -3,10 +3,7 @@ description: "Revise the current feature spec in place (add/remove ACs, FRs, sto handoffs: - label: Implement Revision Tasks agent: speckit.implement - prompt: Implement the open tasks from the latest revision phase - - label: Rebuild Technical Plan - agent: speckit.plan - prompt: Rebuild the plan so it matches the revised spec + prompt: Implement only the new and cleanup tasks from the latest revision phase - label: Create Tasks agent: speckit.tasks prompt: Create tasks from the revised spec and plan @@ -16,11 +13,11 @@ scripts: py: scripts/python/check_prerequisites.py --json --paths-only --- -The user is changing the **current** feature spec — not starting a new one. +The user is changing requirements on the **current** feature. That is why this command exists. -Edit `spec.md` in place. Patch `plan.md` and `tasks.md` only if they already exist. Do not write application code; leave that to `__SPECKIT_COMMAND_IMPLEMENT__`. +Do **not** rewrite `spec.md`. Do **not** rewrite `plan.md`. Do **not** rewrite `tasks.md`. Mid-flight requirement changes used to do that — wipe the file and regenerate — and it destroyed history. You only mark old lines, append new ones, and add a short log. -This is not `__SPECKIT_COMMAND_SPECIFY__` (that opens a new `specs/` folder). It is not `__SPECKIT_COMMAND_CONVERGE__` (that assumes the spec is stable and the code lagged). +This is not `__SPECKIT_COMMAND_SPECIFY__` (new `specs/` folder). Not `__SPECKIT_COMMAND_PLAN__` or `__SPECKIT_COMMAND_TASKS__` (those rebuild artifacts). Not `__SPECKIT_COMMAND_CONVERGE__` (spec unchanged, code lagged). You do not write application code; `__SPECKIT_COMMAND_IMPLEMENT__` does. ## Input @@ -28,17 +25,17 @@ This is not `__SPECKIT_COMMAND_SPECIFY__` (that opens a new `specs/` folder). It $ARGUMENTS ``` -Use it if it isn't empty. If it is empty, ask what changed (or stop if nobody is there). Don't invent a delta. +Use it if it isn't empty. Empty → ask what changed, or stop. Don't invent a delta. -If they described a new product — different user, different outcome, no shared stories — stop and point them at `__SPECKIT_COMMAND_SPECIFY__`. +New product (different user, different outcome, no shared stories) → `__SPECKIT_COMMAND_SPECIFY__`. ## Before you start (and again after you write) -If `.specify/extensions.yml` exists, run hooks for `hooks.before_revise` now, and `hooks.after_revise` after the files are written, before you report. +If `.specify/extensions.yml` exists, run `hooks.before_revise` now and `hooks.after_revise` after writes, before you report. -Skip the file if it's missing or invalid. Skip any hook with `enabled: false`. Skip a hook that has a `condition` (don't evaluate it). Missing `enabled` means on. +Skip if the file is missing or invalid. Skip `enabled: false`. Skip hooks with a `condition`. Missing `enabled` means on. -Mandatory hook (`optional: false`) — print this and **actually run** it. Skills-mode names may differ from `{command}`. Waiting on the block alone is not enough. +Mandatory (`optional: false`) — print this and **run** it. Skills-mode names may differ. The block alone is not enough. ```text ## Extension Hooks @@ -47,7 +44,7 @@ Executing: `/{command}` EXECUTE_COMMAND: {command} ``` -Optional hook (`optional: true`): +Optional (`optional: true`): ```text ## Extension Hooks @@ -58,57 +55,78 @@ Prompt: {prompt} To execute: `/{command}` ``` -After the write, use **Automatic Hook** / **Optional Hook** (drop "Pre-"). +After writes, drop "Pre-" from the labels. -## How to think about it +## The point -Stay in this feature directory. Do **not** create a new `specs/` folder. +Stay in this feature folder. Never create a new `specs/` directory. -`spec.md` is the contract. Only unmarked lines are live. Keep `SUPERSEDED` / `RETIRED` lines so people can see what changed, but don't treat them as current. Never leave two live items that contradict. +`spec.md` is the contract. Live lines are unmarked. Keep old lines visible as `SUPERSEDED by {new-id} (R{N})` or `RETIRED (R{N})`. Never two live items that contradict. -`revisions.md` is a dated log, not a spec. One sentence and IDs. No copied AC text. +Don't edit a live FR/AC/SC in place to mean something else. That hides the change. **Supersede or add.** Reword only for typos / tighter wording of the *same* behavior, and only if nothing has been implemented for it yet. -Don't renumber live IDs. Don't reuse a retired or superseded ID. The next number is one past the highest ID ever used (including struck lines and the log). +`revisions.md` is a dated log, not a spec. One sentence + IDs. -Keep the spec functional — what and why, not stack or file paths. +Don't renumber. Don't reuse IDs. Next ID = highest ever used + 1 (struck lines and the log count). -If a new requirement breaks a constitution `MUST`, drop that part. Ignore an unfilled constitution template. +Spec stays functional (what/why, not stack). Drop anything that violates a constitution `MUST`. Ignore an empty constitution template. -Don't regenerate the plan or the whole task list. Don't delete task lines or uncheck finished work. +## Already implemented? -## What kind of change is this? +Treat implementation as done if any task is `[x]` / `[X]`, or they said they already shipped. Then plan and tasks must cover **new code** and **removing old code** — not just "the spec now says X". -- Same idea, better wording → **reword**. Keep the ID. -- Replaces something live ("SSO instead of password") → **supersede**. Strike the old line (`SUPERSEDED by FR-008 (R2)`) and add a new ID. Don't delete the old line. -- Gone, nothing replaces it → **remove**. Strike it and mark `RETIRED (R2)`. -- Brand new, no conflict → **add**. Next free ID. A new AC goes on the story they named, or the only story that fits. +| Change | Spec | If not implemented yet | If already implemented | +|---|---|---|---| +| New requirement | Add a new ID | Plan + tasks to **build** it | Same: plan + tasks to **add** the new code | +| Replaces a live item | SUPERSEDE old, add new ID | Plan + tasks for the new behavior; cancel open tasks for the old ID | Plan + tasks to **add** new code **and remove** the old code | +| No longer valid | RETIRE the old line; no new ID | Cancel open tasks for that ID | Plan + tasks to **remove** the old code | -If they named `FR-004` / `US1/AC2`, use that. If the description matches exactly one live item, use that. If it's ambiguous, ask — don't guess. +## Classify -Already true in the live spec (or the same as the last `R#`) is a duplicate. Skip it. If everything is a duplicate, stop: say so, write nothing, don't bump `R{N}`. If only part is new, do that part and mention what you skipped. +- Named `FR-004` / `US1/AC2` → that ID. +- Description matches one live item → that item. +- Ambiguous → ask. Don't guess. +- Same idea, clearer words, **not** implemented → reword, keep the ID. +- Swaps or contradicts a live item → supersede (old stays, new ID). +- Drop, nothing replaces it → retire. +- New, no conflict → add. New AC goes on the story they named, or the only story that fits. -## Do the work +Already true on a **live** line (or same as last `R#`) is a duplicate. Skip it. All duplicates → stop, write nothing, don't bump `R{N}`. Mixed → do the new parts only. -1. Run `{SCRIPT}` once. Read `FEATURE_DIR` and `FEATURE_SPEC`. You need `spec.md`. Also look at `plan.md`, `tasks.md`, `revisions.md`, and `/memory/constitution.md` if they're there. No spec → `__SPECKIT_COMMAND_SPECIFY__`. Awkward quotes: `'I'\''m Groot'`. +## Do the work -2. List live stories, ACs (`US{n}/AC{i}`), FRs, SCs. Remember every ID ever issued. Note the next task id and phase if `tasks.md` exists. +1. Run `{SCRIPT}` once. Need `spec.md`. Also read `plan.md`, `tasks.md`, `revisions.md`, constitution if present. No spec → `__SPECKIT_COMMAND_SPECIFY__`. Quotes: `'I'\''m Groot'`. -3. Turn the input into changes. Drop duplicates and constitution clashes. Nothing left → stop. +2. List live stories, ACs, FRs, SCs. Remember every ID ever issued. If `tasks.md` exists, note next T-id, next phase, and whether anything is already `[x]`. -4. Only preview if they asked, or if a remove would wipe the last AC on a P1 story. Otherwise just do it. `N` is 1, or last `R#` plus one. +3. Turn the input into add / reword / supersede / remove. Drop duplicates and constitution clashes. Nothing left → stop. -5. Edit `spec.md`. Holes in AC numbers are fine. Set `**Last Revised**: {today} (R{N})`. Leave `**Created**` alone. +4. Preview only if they asked, or a retire would wipe the last AC on a P1 story. `N` = 1 or last `R#`+1. - Supersede looks like: `- **FR-004** ~~password login~~ — SUPERSEDED by **FR-008** (R2)` +5. **spec.md — append and mark, never replace the file.** + - add: next ID (AC holes are fine). + - reword: only the live sentence, same ID. + - supersede: `- **FR-004** ~~password login~~ — SUPERSEDED by **FR-008** (R2)` plus a new FR-008 line. + - remove: strike + `RETIRED (R2)`. + - Set `**Last Revised**: {today} (R{N})`. Leave `**Created**`. -6. If `plan.md` exists, strike the old bullet and add a small new one. If the architecture is actually invalid, don't rewrite the plan — say `needs-rebuild` and send them to `__SPECKIT_COMMAND_PLAN__`. No plan → that's the next command. +6. **plan.md — only if it exists. Never regenerate.** + - New / supersede: a short bullet for **adding** the new behavior. + - Supersede or retire **and already implemented**: a short bullet for **removing** the old behavior (what to delete, not a new architecture). + - Supersede or retire **and not implemented**: no remove-code bullet; just stop planning the old thing. + - Strike the old plan bullet (`SUPERSEDED` / `RETIRED`). Don't delete it. + - Don't send them to `__SPECKIT_COMMAND_PLAN__` to rebuild. If you truly can't patch, say so in the report — still don't wipe the file. + - No plan yet → next command is `__SPECKIT_COMMAND_PLAN__`. -7. If `tasks.md` exists, append `## Phase {n}: Revision R{N}` with new T-ids. - - Open task for an old ID: `- [ ] ~~T012~~ SUPERSEDED (R{N} → T020)` and add T020. Or `CANCELLED` if nothing replaces it. - - Finished work for an old ID: one cleanup task. - - No tasks file → `__SPECKIT_COMMAND_TASKS__` (or plan first). +7. **tasks.md — only if it exists. Never regenerate. Only append.** + - Add `## Phase {n}: Revision R{N}`. + - New/supersede: tasks to **write the new code**. + - Already implemented + supersede/retire: tasks to **delete or stop using the old code**. + - Open task for an old ID: `- [ ] ~~T012~~ SUPERSEDED (R{N} → T020)` or `CANCELLED` if nothing replaces it. Don't delete the line. + - Finished task for an old ID: leave `[x]`; add the cleanup task in the new phase. + - No tasks file yet → `__SPECKIT_COMMAND_TASKS__` (or plan first). Do not invent a full task list here. -8. Append to `revisions.md` (create it if needed: title + "spec.md is the source of truth"): +8. Append to `revisions.md` (create with a one-line header: spec.md is the source of truth): ```markdown ## R{N} — {YYYY-MM-DD} @@ -121,16 +139,17 @@ Already true in the live spec (or the same as the last `R#`) is a duplicate. Ski Skip empty bullets. Don't edit old entries. Don't append on a no-op. -9. If `checklists/requirements.md` exists, update its checkboxes before after-hooks so a git commit includes them. +9. Refresh `checklists/requirements.md` if it exists, **before** after-hooks. 10. Run `hooks.after_revise`. -Then tell them once what changed, and what to run next (`__SPECKIT_COMMAND_IMPLEMENT__`, `__SPECKIT_COMMAND_PLAN__`, or `__SPECKIT_COMMAND_TASKS__`). If it was a no-op, say that. +Tell them once what was marked, what was added, and whether implement should **add** code, **remove** code, or both. Next is usually `__SPECKIT_COMMAND_IMPLEMENT__` (or plan/tasks if those files are still missing). ## Done When -- [ ] Same feature folder; spec updated or you said it was already true -- [ ] New `R{N}` only when something actually changed -- [ ] Old lines marked SUPERSEDED/RETIRED; new IDs added; none reused -- [ ] Plan/tasks patched or skipped; no application code -- [ ] Hooks handled; user got a short report +- [ ] Same folder; spec was not rewritten from scratch +- [ ] Old requirements SUPERSEDED or RETIRED; new ones are new IDs +- [ ] `tasks.md` / `plan.md` were not regenerated — only patched or skipped +- [ ] After implement: tasks exist to add new code and/or remove old code +- [ ] Dated log only if something changed; no application code +- [ ] Hooks handled; short report diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index 2e42f1c40f..d05a3804e5 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -121,7 +121,11 @@ def test_supersede_marks_old_and_adds_new(self): assert "contradict" in self.text def test_does_not_write_application_code(self): - assert "Do not write application code" in self.text or "no application code" in self.text + assert "application code" in self.text + + def test_never_rewrites_artifacts(self): + assert "Never regenerate" in self.text or "never regenerate" in self.text.lower() or "Do **not** rewrite" in self.text + assert "remove the old code" in self.text or "removing old code" in self.text def test_hands_off_to_implement_plan_or_tasks(self): assert "__SPECKIT_COMMAND_IMPLEMENT__" in self.text From 0bb69a4d955e30c2a278c280cad4d632f1eddb84 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 13:13:17 +0530 Subject: [PATCH 10/14] fix(revise): stop cascades from undoing a living-spec edit The tasks handoff no longer rebuilds tasks.md after a revision. Converge inventories only live IDs and waits for open Revision tasks. Lean implement skips cancelled work. Specify stops before mkdir. Implemented is per-ID; plan_status is a real report field. Assisted-by: Grok (model: grok-4.6, supervised) --- CHANGELOG.md | 2 + presets/lean/README.md | 2 +- presets/lean/commands/speckit.implement.md | 6 ++- presets/lean/commands/speckit.tasks.md | 1 + templates/commands/converge.md | 28 +++++++----- templates/commands/revise.md | 24 +++++----- templates/commands/specify.md | 2 + templates/commands/tasks.md | 2 + tests/test_revise_command.py | 51 +++++++++++++++++++++- 9 files changed, 90 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6a73dd77..fb7dd906da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### Changed - teach `/speckit.implement` and `/speckit.taskstoissues` to skip cancelled revision tasks +- teach lean implement the same skip rules; refuse to regenerate `tasks.md` after a revision +- teach `/speckit.converge` to inventory only live spec IDs and stop when revision tasks are still open - hook git auto-commit on `before_revise` / `after_revise` - document living-spec revise in evolving-specs, spec-persistence, and agentic-sdd diff --git a/presets/lean/README.md b/presets/lean/README.md index b5162417fb..523ac37d27 100644 --- a/presets/lean/README.md +++ b/presets/lean/README.md @@ -13,7 +13,7 @@ Use Lean when you want the structured specify → plan → tasks → implement p | `speckit.specify` | `spec.md` | Create a specification from a feature description | | `speckit.plan` | `plan.md` | Create an implementation plan from the spec | | `speckit.tasks` | `tasks.md` | Create dependency-ordered tasks from spec and plan | -| `speckit.implement` | *(code)* | Execute all tasks in order, marking progress | +| `speckit.implement` | *(code)* | Execute live tasks in order, skipping cancelled or superseded ones | | `speckit.constitution` | `constitution.md` | Create or update the project constitution | ## What It Replaces diff --git a/presets/lean/commands/speckit.implement.md b/presets/lean/commands/speckit.implement.md index fc68a1f8b1..1ff00cf834 100644 --- a/presets/lean/commands/speckit.implement.md +++ b/presets/lean/commands/speckit.implement.md @@ -15,8 +15,10 @@ $ARGUMENTS 2. **Load context**: `.specify/memory/constitution.md` and `/spec.md` and `/plan.md` and `/tasks.md`. 3. **Execute tasks** in order: - - Complete each task before moving to the next + - Skip any task line that contains `CANCELLED`, `SUPERSEDED`, or a struck-through task ID (`~~T012~~`), even when the checkbox is still `- [ ]`. Do not implement them, do not mark them `[x]`, and do not count them as remaining work. + - Prefer the latest `Revision R#` phase plus any cleanup tasks it added + - Complete each remaining live task before moving to the next - Mark completed tasks by changing `- [ ]` to `- [x]` in `/tasks.md` - Halt on failure and report the issue -4. **Validate**: Verify all tasks are completed and the implementation matches the spec. +4. **Validate**: Verify all **live** (non-cancelled, non-superseded) tasks are completed and the implementation matches **live** items in the spec (struck `SUPERSEDED` / `RETIRED` lines are not required). diff --git a/presets/lean/commands/speckit.tasks.md b/presets/lean/commands/speckit.tasks.md index 724a7b8400..dc87859beb 100644 --- a/presets/lean/commands/speckit.tasks.md +++ b/presets/lean/commands/speckit.tasks.md @@ -15,5 +15,6 @@ $ARGUMENTS 2. **Load context**: `.specify/memory/constitution.md` and `/spec.md` and `/plan.md`. 3. Create dependency-ordered implementation tasks and store them in `/tasks.md`. + - If `tasks.md` already exists and has a `Revision R#` phase (or `revisions.md` is present), do **not** overwrite it. Stop and tell the user `/speckit.revise` already maintains the list. - Every task uses checklist format: `- [ ] [TaskID] Description with file path` - Organized by phase: setup, foundational, user stories in priority order, polish diff --git a/templates/commands/converge.md b/templates/commands/converge.md index 14b3296cd1..1ef94b88c9 100644 --- a/templates/commands/converge.md +++ b/templates/commands/converge.md @@ -69,11 +69,13 @@ This is **not** a diff tool and does **not** track changes. It assesses the pres of the code relative to the feature's artifacts — no git, no branch comparison, no history. If the user just stated a requirement change (add/remove/reword an AC, FR, story, or -success criterion), or `spec.md` has a `Last Revised` stamp and `revisions.md` is newer -than the last implement pass, **STOP** and recommend `__SPECKIT_COMMAND_REVISE__` -instead of converging. Converge assumes the -**spec is stable** and the code lagged. Re-appending tasks for behavior the spec just -retired would undo a living-spec edit. +success criterion), **STOP** and recommend `__SPECKIT_COMMAND_REVISE__` +instead of converging. + +If `tasks.md` has a `Revision R#` phase whose live (non-cancelled, non-superseded) +tasks are still `- [ ]`, **STOP** and recommend `__SPECKIT_COMMAND_IMPLEMENT__` first. +Converge assumes the **spec is stable** and the latest revision has been implemented. +Re-appending tasks for behavior the spec just retired would undo a living-spec edit. ## Operating Constraints @@ -115,11 +117,13 @@ Load only the minimal necessary context from each artifact: **From spec.md:** -- Functional Requirements (FR-###) -- Success Criteria (SC-###) — include only items requiring buildable work; exclude +- Only **live** (unmarked) Functional Requirements (FR-###) +- Only **live** Success Criteria (SC-###) — include only items requiring buildable work; exclude post-launch outcome metrics and business KPIs -- User Stories and their Acceptance Scenarios -- Edge Cases (if present) +- Only **live** User Stories and their Acceptance Scenarios +- Edge Cases (if present and not SUPERSEDED / RETIRED) +- Lines marked `SUPERSEDED` or `RETIRED` (usually struck through) are historical. Do not + inventory them and do not append tasks to restore them. **From plan.md:** @@ -127,11 +131,13 @@ Load only the minimal necessary context from each artifact: - Data Model references - Phases and named touch-points (files/components the plan says will be created or edited) - Technical constraints +- Skip plan bullets marked `SUPERSEDED` or `RETIRED` **From tasks.md:** - Task IDs (to compute the next ID and next phase number) - Descriptions, phase grouping, and referenced file paths +- Ignore `CANCELLED` / `SUPERSEDED` / struck-through task lines when deciding remaining work **From constitution (if not an unfilled template):** @@ -141,9 +147,9 @@ Load only the minimal necessary context from each artifact: Create an internal model (do not echo raw artifacts): -- **Requirements inventory**: one stable key per FR-### / SC-### / user-story acceptance +- **Requirements inventory**: one stable key per **live** FR-### / SC-### / user-story acceptance scenario (e.g. `US1/AC2`), plus the plan decisions and constitution principles that - impose buildable obligations. + impose buildable obligations. Do not create keys for `SUPERSEDED` or `RETIRED` IDs. - **Code-scope map**: from the file paths named in `plan.md` and `tasks.md`, plus a keyword search for the concepts each requirement describes, derive the set of source files and components in scope for assessment. Bound the assessment to these — do **not** infer diff --git a/templates/commands/revise.md b/templates/commands/revise.md index d464464d3a..3dbd83e08c 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -6,7 +6,7 @@ handoffs: prompt: Implement only the new and cleanup tasks from the latest revision phase - label: Create Tasks agent: speckit.tasks - prompt: Create tasks from the revised spec and plan + prompt: Only if tasks.md does not exist. Never regenerate an existing task list — revise already appended a Revision phase. scripts: sh: scripts/bash/check-prerequisites.sh --json --paths-only ps: scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly @@ -73,9 +73,9 @@ Spec stays functional (what/why, not stack). Drop anything that violates a const ## Already implemented? -Treat implementation as done if any task is `[x]` / `[X]`, or they said they already shipped. Then plan and tasks must cover **new code** and **removing old code** — not just "the spec now says X". +An ID is implemented only if a task that **references that ID** is `[x]` / `[X]`, or they said that behavior already shipped. A checked setup or foundational task does **not** mean later stories shipped. Any `[x]` on the feature is only a hint that *some* code exists — not a blanket remove-code trigger. -| Change | Spec | If not implemented yet | If already implemented | +| Change | Spec | If that ID is not implemented yet | If that ID is already implemented | |---|---|---|---| | New requirement | Add a new ID | Plan + tasks to **build** it | Same: plan + tasks to **add** the new code | | Replaces a live item | SUPERSEDE old, add new ID | Plan + tasks for the new behavior; cancel open tasks for the old ID | Plan + tasks to **add** new code **and remove** the old code | @@ -95,9 +95,9 @@ Already true on a **live** line (or same as last `R#`) is a duplicate. Skip it. ## Do the work -1. Run `{SCRIPT}` once. Need `spec.md`. Also read `plan.md`, `tasks.md`, `revisions.md`, constitution if present. No spec → `__SPECKIT_COMMAND_SPECIFY__`. Quotes: `'I'\''m Groot'`. +1. Run `{SCRIPT}` once. Need `spec.md`. Also read `plan.md`, `tasks.md`, `revisions.md`, `constitution.md` if present. No spec → `__SPECKIT_COMMAND_SPECIFY__`. Quotes: `'I'\''m Groot'`. -2. List live stories, ACs, FRs, SCs. Remember every ID ever issued. If `tasks.md` exists, note next T-id, next phase, and whether anything is already `[x]`. +2. List live stories, ACs, FRs, SCs. Remember every ID ever issued. If `tasks.md` exists, note next T-id, next phase, and which IDs already have a referencing task marked `[x]`. 3. Turn the input into add / reword / supersede / remove. Drop duplicates and constitution clashes. Nothing left → stop. @@ -112,19 +112,19 @@ Already true on a **live** line (or same as last `R#`) is a duplicate. Skip it. 6. **plan.md — only if it exists. Never regenerate.** - New / supersede: a short bullet for **adding** the new behavior. - - Supersede or retire **and already implemented**: a short bullet for **removing** the old behavior (what to delete, not a new architecture). - - Supersede or retire **and not implemented**: no remove-code bullet; just stop planning the old thing. + - Supersede or retire **and that old ID is implemented**: a short bullet for **removing** the old behavior (what to delete, not a new architecture). + - Supersede or retire **and that old ID is not implemented**: no remove-code bullet; just stop planning the old thing. - Strike the old plan bullet (`SUPERSEDED` / `RETIRED`). Don't delete it. - - Don't send them to `__SPECKIT_COMMAND_PLAN__` to rebuild. If you truly can't patch, say so in the report — still don't wipe the file. - - No plan yet → next command is `__SPECKIT_COMMAND_PLAN__`. + - Don't send them to `__SPECKIT_COMMAND_PLAN__` to rebuild. If you truly can't patch, set `plan_status: needs-rebuild` in the report — still don't wipe the file. + - No plan yet → `plan_status: missing`; next command is `__SPECKIT_COMMAND_PLAN__`. 7. **tasks.md — only if it exists. Never regenerate. Only append.** - Add `## Phase {n}: Revision R{N}`. - New/supersede: tasks to **write the new code**. - - Already implemented + supersede/retire: tasks to **delete or stop using the old code**. + - That old ID is implemented + supersede/retire: tasks to **delete or stop using the old code**. - Open task for an old ID: `- [ ] ~~T012~~ SUPERSEDED (R{N} → T020)` or `CANCELLED` if nothing replaces it. Don't delete the line. - Finished task for an old ID: leave `[x]`; add the cleanup task in the new phase. - - No tasks file yet → `__SPECKIT_COMMAND_TASKS__` (or plan first). Do not invent a full task list here. + - No tasks file yet → `__SPECKIT_COMMAND_TASKS__` (or plan first). Do not invent a full task list here. Never hand off to tasks when `tasks.md` already exists. 8. Append to `revisions.md` (create with a one-line header: spec.md is the source of truth): @@ -143,7 +143,7 @@ Already true on a **live** line (or same as last `R#`) is a duplicate. Skip it. 10. Run `hooks.after_revise`. -Tell them once what was marked, what was added, and whether implement should **add** code, **remove** code, or both. Next is usually `__SPECKIT_COMMAND_IMPLEMENT__` (or plan/tasks if those files are still missing). +Tell them once what was marked, what was added, and whether implement should **add** code, **remove** code, or both. Always include `plan_status:` as one of `patched` (plan bullets added or struck), `needs-rebuild` (could not patch safely), `missing` (no `plan.md`), or `unchanged` (no-op or plan needed no edit). Next is usually `__SPECKIT_COMMAND_IMPLEMENT__` (or plan/tasks if those files are still missing). ## Done When diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 551cbc9626..f413b4405b 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -18,6 +18,8 @@ $ARGUMENTS You **MUST** consider the user input before proceeding (if not empty). +If a current feature already exists (`.specify/feature.json` or an active `spec.md`) and the user stated a **concrete delta** (add, remove, or reword a named AC, FR, story, or success criterion) on that spec, do **not** create another directory and do **not** run `before_specify` hooks. Recommend `__SPECKIT_COMMAND_REVISE__` and stop. A genuinely new product (different user, different outcome, no shared stories) continues below. + ## Pre-Execution Checks **Check for extension hooks (before specification)**: diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 64146a35aa..4468d07d5e 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -60,6 +60,8 @@ You **MUST** consider the user input before proceeding (if not empty). ## Outline +If `tasks.md` already exists and either `revisions.md` is present or `tasks.md` already has a `Revision R#` phase, do **not** regenerate the file. Stop and tell the user `__SPECKIT_COMMAND_REVISE__` already maintains the task list — it appends a Revision phase and cancels retired work. Only generate a full `tasks.md` when the file is missing. + 1. **Setup**: Run `{SCRIPT}` from repo root and parse FEATURE_DIR, TASKS_TEMPLATE_CONTENT, TASKS_TEMPLATE, and AVAILABLE_DOCS list. `FEATURE_DIR` and `TASKS_TEMPLATE` must be absolute paths when provided. `AVAILABLE_DOCS` is a list of document names/relative paths available under `FEATURE_DIR` (for example `research.md` or `contracts/`). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). 2. **Load design documents**: Read from FEATURE_DIR: diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index d05a3804e5..74e78fc293 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -81,7 +81,7 @@ def test_revise_template_renders(script_type: str, monkeypatch): content = REVISE.read_text(encoding="utf-8") result = IntegrationBase.process_template(content, "agent", script_type) assert "{SCRIPT}" not in result - assert "$ARGUMENTS" in result or "{{args}}" in result or result + assert "$ARGUMENTS" in result or "{{args}}" in result class TestReviseInvariants: @@ -125,7 +125,21 @@ def test_does_not_write_application_code(self): def test_never_rewrites_artifacts(self): assert "Never regenerate" in self.text or "never regenerate" in self.text.lower() or "Do **not** rewrite" in self.text - assert "remove the old code" in self.text or "removing old code" in self.text + assert "the old code" in self.text + + def test_implemented_is_per_id_not_any_checked_task(self): + assert "references that ID" in self.text + assert "not a blanket remove-code trigger" in self.text + + def test_reports_named_plan_status(self): + assert "plan_status:" in self.text + assert "needs-rebuild" in self.text + assert "patched" in self.text + assert "missing" in self.text + + def test_tasks_handoff_refuses_to_regenerate(self): + assert "agent: speckit.tasks" in self.text + assert "Never regenerate an existing task list" in self.text def test_hands_off_to_implement_plan_or_tasks(self): assert "__SPECKIT_COMMAND_IMPLEMENT__" in self.text @@ -163,10 +177,43 @@ def test_specify_is_create_not_update(self): text = (COMMANDS / "specify.md").read_text(encoding="utf-8") assert "Create a new feature specification" in text assert "__SPECKIT_COMMAND_REVISE__" in text + assert "do **not** create another directory" in text + assert "do **not** run `before_specify` hooks" in text def test_converge_defers_to_revise_when_spec_changed(self): text = (COMMANDS / "converge.md").read_text(encoding="utf-8") assert "__SPECKIT_COMMAND_REVISE__" in text + assert "SUPERSEDED" in text + assert "RETIRED" in text + assert "Only **live**" in text + assert "Do not create keys for `SUPERSEDED` or `RETIRED` IDs" in text + assert "Revision R#" in text + assert "__SPECKIT_COMMAND_IMPLEMENT__" in text + + def test_tasks_refuses_to_regenerate_after_revise(self): + text = (COMMANDS / "tasks.md").read_text(encoding="utf-8") + assert "do **not** regenerate the file" in text + assert "Revision R#" in text + assert "__SPECKIT_COMMAND_REVISE__" in text + + def test_lean_implement_skips_cancelled_tasks(self): + text = ( + REPO_ROOT + / "presets" + / "lean" + / "commands" + / "speckit.implement.md" + ).read_text(encoding="utf-8") + assert "CANCELLED" in text + assert "SUPERSEDED" in text + assert "not count them as remaining work" in text + + def test_lean_tasks_refuses_to_overwrite_revision_list(self): + text = ( + REPO_ROOT / "presets" / "lean" / "commands" / "speckit.tasks.md" + ).read_text(encoding="utf-8") + assert "do **not** overwrite it" in text + assert "Revision R#" in text def test_clarify_defers_known_deltas_to_revise(self): text = (COMMANDS / "clarify.md").read_text(encoding="utf-8") From 1fdc92e9c988f791a9a2577ada8ba11fbbe161ca Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 13:57:21 +0530 Subject: [PATCH 11/14] fix(revise): close remaining living-spec cascade holes First-generation tasks inventory only live IDs. Persist plan_status on revisions.md and send needs-rebuild to plan. Clarify and checklist no longer overwrite living-spec history. Init lists /speckit.revise. Assisted-by: Grok (model: grok-4.6, supervised) --- CHANGELOG.md | 4 +++ docs/guides/evolving-specs.md | 4 +-- docs/quickstart.md | 2 +- docs/reference/agentic-sdd.md | 6 ++-- presets/lean/commands/speckit.plan.md | 2 ++ presets/lean/commands/speckit.specify.md | 3 ++ presets/lean/commands/speckit.tasks.md | 1 + src/specify_cli/commands/init.py | 1 + templates/commands/analyze.md | 12 ++++--- templates/commands/checklist.md | 10 +++--- templates/commands/clarify.md | 6 ++-- templates/commands/implement.md | 2 +- templates/commands/plan.md | 6 +++- templates/commands/revise.md | 22 +++++++----- templates/commands/tasks.md | 5 +-- tests/test_revise_command.py | 44 ++++++++++++++++++++++++ 16 files changed, 101 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb7dd906da..0638f534b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ - teach `/speckit.implement` and `/speckit.taskstoissues` to skip cancelled revision tasks - teach lean implement the same skip rules; refuse to regenerate `tasks.md` after a revision - teach `/speckit.converge` to inventory only live spec IDs and stop when revision tasks are still open +- teach plan (core and lean) and analyze to ignore SUPERSEDED/RETIRED IDs; lean specify must not overwrite a living spec +- stop implement from sending a Revision-phase task list back to `/speckit.tasks` +- first-generation tasks inventory only live IDs; persist `plan_status` on `revisions.md` +- clarify/checklist do not overwrite living-spec history; init lists `/speckit.revise` - hook git auto-commit on `before_revise` / `after_revise` - document living-spec revise in evolving-specs, spec-persistence, and agentic-sdd diff --git a/docs/guides/evolving-specs.md b/docs/guides/evolving-specs.md index 5d06d769c0..e1f8b3a441 100644 --- a/docs/guides/evolving-specs.md +++ b/docs/guides/evolving-specs.md @@ -49,8 +49,8 @@ bring `plan.md` and `tasks.md` in line with that contract: Do **not** run `/speckit.specify` (that opens a new feature directory) and do not use `/speckit.clarify` for a delta you already know — clarify only fills pre-plan gaps by asking questions. -3. If revise reports `plan_status: needs-rebuild`, rerun `/speckit.plan`. - Otherwise keep the patched plan. +3. If the latest `revisions.md` entry has `plan_status: needs-rebuild`, rerun + `/speckit.plan`. Otherwise keep the patched plan. 4. Do not rerun `/speckit.tasks` unless there is no `tasks.md` yet. Revise appends a `Revision R#` phase and cancels open tasks that only served a retired ID. diff --git a/docs/quickstart.md b/docs/quickstart.md index aa10f27cff..ebfa40a0b9 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -122,7 +122,7 @@ Checks the codebase against the spec, plan, and tasks. If it finds gaps, it appe ### After shipping: `/speckit.revise` — change the current spec -When requirements change on the **same** feature (add or drop an acceptance criterion, retire an FR), do **not** run `/speckit.specify` again. That opens a new feature directory. Use `/speckit.revise` instead, then analyze, implement, and converge. +When requirements change on the **same** feature (add or drop an acceptance criterion, retire an FR), do **not** run `/speckit.specify` again. That opens a new feature directory. Use `/speckit.revise` instead. If `plan.md` or `tasks.md` is still missing, run plan then tasks. If revise reports `plan_status: needs-rebuild`, rerun `/speckit.plan`. Then analyze, implement, and converge. ```text /speckit.revise Remove password login. Add AC: Given an expired session, When the user submits, Then redirect to SSO diff --git a/docs/reference/agentic-sdd.md b/docs/reference/agentic-sdd.md index c51629d34b..9e341f37b8 100644 --- a/docs/reference/agentic-sdd.md +++ b/docs/reference/agentic-sdd.md @@ -10,7 +10,7 @@ The commands are designed to run in order, but only `/speckit.specify` is strict ```text /speckit.constitution -> /speckit.specify -> /speckit.clarify -> /speckit.plan -> /speckit.checklist -> /speckit.tasks -> /speckit.analyze -> /speckit.implement -> /speckit.converge # after ship, when the spec itself changes: -/speckit.revise -> /speckit.analyze -> /speckit.implement -> /speckit.converge +/speckit.revise -> [/speckit.plan if missing or plan_status: needs-rebuild] -> [/speckit.tasks if tasks.md missing] -> /speckit.analyze -> /speckit.implement -> /speckit.converge ``` ## `/speckit.constitution` @@ -25,7 +25,7 @@ Creates or updates the project **constitution** — the guiding principles that Creates a **new** feature **specification** from a natural-language description (a new directory under `specs/`). Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. To add, remove, or reword requirements on the **current** spec, run `/speckit.revise` instead. -This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` re-evaluates. That lifecycle is separate from custom checklists generated by `/speckit.checklist`. +This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` / `/speckit.revise` re-evaluate. That lifecycle is separate from custom checklists generated by `/speckit.checklist`. ```text /speckit.specify Build an application that helps me organize photos into albums grouped by date, re-orderable by drag-and-drop on the main page, with a tile preview inside each album. @@ -67,7 +67,7 @@ Run it with no arguments for a broad pass, or pass a focus area to target one as /speckit.checklist Focus on the Kanban board interactions and comment permissions. ``` -Review the generated checklist. If it surfaces gaps, loop back to `/speckit.clarify` or `/speckit.specify` to tighten the spec before breaking the work down, then mark each custom checklist item `[x]` only after the requirements-quality criterion has been reviewed and satisfied. +Review the generated checklist. If it surfaces gaps, loop back to `/speckit.clarify` (pre-plan questions), `/speckit.revise` (a known add/remove/reword on the current spec), or `/speckit.specify` (a genuinely new feature), then mark each custom checklist item `[x]` only after the requirements-quality criterion has been reviewed and satisfied. ## `/speckit.tasks` diff --git a/presets/lean/commands/speckit.plan.md b/presets/lean/commands/speckit.plan.md index 9fbbe4c371..9806ffbb8c 100644 --- a/presets/lean/commands/speckit.plan.md +++ b/presets/lean/commands/speckit.plan.md @@ -15,5 +15,7 @@ $ARGUMENTS 2. **Load context**: `.specify/memory/constitution.md` and `/spec.md`. 3. Create an implementation plan and store it in `/plan.md`. + - Only plan **live** (unmarked) FRs, ACs, and SCs. Lines marked `SUPERSEDED` or `RETIRED` are historical — do not restore them. + - If `plan.md` already exists and `revisions.md` is present, do **not** overwrite it unless the latest `revisions.md` entry has `plan_status: needs-rebuild` (or the user asked). Even then, do not resurrect retired IDs. - Technical context: tech stack, dependencies, project structure - Design decisions, architecture, file structure diff --git a/presets/lean/commands/speckit.specify.md b/presets/lean/commands/speckit.specify.md index c15353557a..ee5c89b514 100644 --- a/presets/lean/commands/speckit.specify.md +++ b/presets/lean/commands/speckit.specify.md @@ -10,6 +10,8 @@ $ARGUMENTS ## Outline +If `.specify/feature.json` or the named directory already has a `spec.md` and the user stated a **concrete delta** (add, remove, or reword a named AC, FR, story, or success criterion), do **not** write anything. Recommend `/speckit.revise` and stop. Never overwrite an existing `spec.md`. + 1. **Ask the user** for the feature directory path (e.g., `specs/my-feature`). Do not proceed until provided. 2. Create the directory and write `.specify/feature.json`: @@ -18,6 +20,7 @@ $ARGUMENTS ``` 3. Create a specification from the user input and store it in `/spec.md`. + - If that path already has a `spec.md`, stop — do not overwrite it - Overview, functional requirements, user scenarios, success criteria - Every requirement must be testable - Make informed defaults for unspecified details diff --git a/presets/lean/commands/speckit.tasks.md b/presets/lean/commands/speckit.tasks.md index dc87859beb..6f9573bb5d 100644 --- a/presets/lean/commands/speckit.tasks.md +++ b/presets/lean/commands/speckit.tasks.md @@ -16,5 +16,6 @@ $ARGUMENTS 3. Create dependency-ordered implementation tasks and store them in `/tasks.md`. - If `tasks.md` already exists and has a `Revision R#` phase (or `revisions.md` is present), do **not** overwrite it. Stop and tell the user `/speckit.revise` already maintains the list. + - First generation: inventory only **live** (unmarked) FRs, ACs, and SCs. Never emit tasks for `SUPERSEDED` or `RETIRED` lines. - Every task uses checklist format: `- [ ] [TaskID] Description with file path` - Organized by phase: setup, foundational, user stories in priority order, polish diff --git a/src/specify_cli/commands/init.py b/src/specify_cli/commands/init.py index 2bb8452025..1612693fb9 100644 --- a/src/specify_cli/commands/init.py +++ b/src/specify_cli/commands/init.py @@ -1103,6 +1103,7 @@ def _display_cmd(name: str) -> str: f"○ [cyan]{_display_cmd('clarify')}[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]{_display_cmd('plan')}[/] if used)", f"○ [cyan]{_display_cmd('analyze')}[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]{_display_cmd('tasks')}[/], before [cyan]{_display_cmd('implement')}[/])", f"○ [cyan]{_display_cmd('checklist')}[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]{_display_cmd('plan')}[/])", + f"○ [cyan]{_display_cmd('revise')}[/] [bright_black](optional)[/bright_black] - Edit the current feature spec in place when requirements change (do not run [cyan]{_display_cmd('specify')}[/] again)", ] enhancements_title = ( "Enhancement Skills" if native_skill_mode else "Enhancement Commands" diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index 361cb0bf3a..d9368ee42a 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -80,10 +80,11 @@ Load only the minimal necessary context from each artifact: **From spec.md:** - Overview/Context -- Functional Requirements -- Success Criteria (measurable outcomes — e.g., performance, security, availability, user success, business impact) -- User Stories -- Edge Cases (if present) +- Only **live** (unmarked) Functional Requirements +- Only **live** Success Criteria (measurable outcomes — e.g., performance, security, availability, user success, business impact) +- Only **live** User Stories +- Edge Cases (if present and not SUPERSEDED / RETIRED) +- Lines marked `SUPERSEDED` or `RETIRED` (usually struck through) are historical. Do not inventory them. **From plan.md:** @@ -91,6 +92,7 @@ Load only the minimal necessary context from each artifact: - Data Model references - Phases - Technical constraints +- Skip plan bullets marked `SUPERSEDED` or `RETIRED`. Only flag a retired ID as in-scope when a **live** (unmarked) plan bullet or live task still requires it. **From tasks.md:** @@ -119,7 +121,7 @@ Load only the minimal necessary context from each artifact: Create internal representations (do not include raw artifacts in output): -- **Requirements inventory**: For each Functional Requirement (FR-###) and Success Criterion (SC-###), record a stable key. Use the explicit FR-/SC- identifier as the primary key when present, and optionally also derive an imperative-phrase slug for readability (e.g., "User can upload file" → `user-can-upload-file`). Include only Success Criteria items that require buildable work (e.g., load-testing infrastructure, security audit tooling), and exclude post-launch outcome metrics and business KPIs (e.g., "Reduce support tickets by 50%"). +- **Requirements inventory**: For each **live** (unmarked) Functional Requirement (FR-###) and Success Criterion (SC-###), record a stable key. Do not create keys for `SUPERSEDED` or `RETIRED` IDs. Use the explicit FR-/SC- identifier as the primary key when present, and optionally also derive an imperative-phrase slug for readability (e.g., "User can upload file" → `user-can-upload-file`). Include only Success Criteria items that require buildable work (e.g., load-testing infrastructure, security audit tooling), and exclude post-launch outcome metrics and business KPIs (e.g., "Reduce support tickets by 50%"). - **User story/action inventory**: Discrete user actions with acceptance criteria - **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases) - **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements diff --git a/templates/commands/checklist.md b/templates/commands/checklist.md index 5ee239d309..30761768ce 100644 --- a/templates/commands/checklist.md +++ b/templates/commands/checklist.md @@ -34,7 +34,7 @@ scripts: - `[x]` does NOT mean implementation work is complete. - This command generates or appends checklist items; it MUST NOT mark generated items `[x]`. - An agent may assist with evaluating items only when explicitly asked by the reviewer. -- `checklists/requirements.md` is a separate built-in spec-quality checklist maintained by `__SPECKIT_COMMAND_SPECIFY__` and `__SPECKIT_COMMAND_CLARIFY__`; do not treat that exception as applying to custom checklists generated here. +- `checklists/requirements.md` is a separate built-in spec-quality checklist maintained by `__SPECKIT_COMMAND_SPECIFY__`, `__SPECKIT_COMMAND_CLARIFY__`, and `__SPECKIT_COMMAND_REVISE__` (revise re-evaluates checkboxes against live spec lines; it does not rewrite the file); do not treat that exception as applying to custom checklists generated here. ## User Input @@ -126,9 +126,11 @@ You **MUST** consider the user input before proceeding (if not empty). - Infer any missing context from spec/plan/tasks (do NOT hallucinate) 5. **Load feature context**: Read from FEATURE_DIR: - - spec.md: Feature requirements and scope - - plan.md (if exists): Technical details, dependencies - - tasks.md (if exists): Implementation tasks + - spec.md: Feature requirements and scope — only **live** (unmarked) FRs, ACs, and SCs. Skip `SUPERSEDED` / `RETIRED` lines. + - plan.md (if exists): Technical details, dependencies (skip struck plan bullets) + - tasks.md (if exists): Implementation tasks (skip `CANCELLED` / `SUPERSEDED` task lines) + + If this is a living spec (`revisions.md` present) and the gap is an add/remove/reword, recommend `__SPECKIT_COMMAND_REVISE__` rather than `__SPECKIT_COMMAND_SPECIFY__`. **Context Loading Strategy**: - Load only necessary portions relevant to active focus areas (avoid full-file dumping) diff --git a/templates/commands/clarify.md b/templates/commands/clarify.md index 3e4bc4740e..b6caf180cd 100644 --- a/templates/commands/clarify.md +++ b/templates/commands/clarify.md @@ -59,6 +59,8 @@ Goal: Detect and reduce ambiguity or missing decision points in the active featu If the user already stated a **concrete delta** (add, remove, or reword a named AC, FR, story, or success criterion), do **not** start a clarification question loop. Recommend `__SPECKIT_COMMAND_REVISE__` and stop. +If `revisions.md` exists, or `plan.md` / `tasks.md` already exist, do **not** replace or delete live FR/AC/SC lines and do **not** drop `SUPERSEDED` / `RETIRED` history. Recommend `__SPECKIT_COMMAND_REVISE__` for any add/remove/reword or anything that would invalidate an earlier requirement. You may still add a Clarifications session and tighten wording that does not change meaning. + Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `__SPECKIT_COMMAND_PLAN__`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. Execution steps: @@ -194,8 +196,8 @@ Execution steps: - Non-functional constraint → Add/modify measurable criteria in Success Criteria > Measurable Outcomes (convert vague adjective to metric or explicit target). - Edge case / negative flow → Add a new bullet under Edge Cases / Error Handling (or create such subsection if template provides placeholder for it). - Terminology conflict → Normalize term across spec; retain original only if necessary by adding `(formerly referred to as "X")` once. - - If the clarification invalidates an earlier ambiguous statement, replace that statement instead of duplicating; leave no obsolete contradictory text. - - Save the spec file AFTER each integration to minimize risk of context loss (atomic overwrite). + - If the clarification invalidates an earlier ambiguous statement and there is no `revisions.md` / `plan.md` / `tasks.md` yet, replace that statement instead of duplicating. Once those exist, do **not** replace — recommend `__SPECKIT_COMMAND_REVISE__`. + - Save the spec file AFTER each integration to minimize risk of context loss (atomic overwrite). Do not wipe `SUPERSEDED` / `RETIRED` history. - Preserve formatting: do not reorder unrelated sections; keep heading hierarchy intact. - Keep each inserted clarification minimal and testable (avoid narrative drift). diff --git a/templates/commands/implement.md b/templates/commands/implement.md index 1074770460..cf46607c36 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -178,7 +178,7 @@ You **MUST** consider the user input before proceeding (if not empty). - Validate that tests pass and coverage meets requirements - Confirm the implementation follows the technical plan -Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `__SPECKIT_COMMAND_TASKS__` first to regenerate the task list. +Note: This command assumes a task list exists in `tasks.md`. Suggest `__SPECKIT_COMMAND_TASKS__` only when `tasks.md` is **absent**. If the file exists and has cancelled/superseded lines or a `Revision R#` phase, that is the current list — implement the live remainder (or tell the user to run `__SPECKIT_COMMAND_REVISE__` to add work). Do not regenerate. ## Mandatory Post-Execution Hooks diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 664f428114..92d4eafb8f 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -59,9 +59,13 @@ You **MUST** consider the user input before proceeding (if not empty). ## Outline +Plan only **live** (unmarked) FRs, ACs, and SCs from the spec. Lines marked `SUPERSEDED` or `RETIRED` are historical — do not restore them in `plan.md`, `data-model.md`, or contracts. + +If `revisions.md` exists and `plan.md` was already patched, do **not** rebuild unless the latest `revisions.md` entry has `plan_status: needs-rebuild` or the user asked. Even on a rebuild, never re-plan a retired ID. + 1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). -2. **Load context**: Read FEATURE_SPEC and `/memory/constitution.md`. Load IMPL_PLAN template (already copied). +2. **Load context**: Read FEATURE_SPEC and `/memory/constitution.md`. Load IMPL_PLAN template (already copied). Use only **live** spec lines; skip `SUPERSEDED` / `RETIRED`. 3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to: - Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION") diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 3dbd83e08c..57e8cca5c6 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -1,12 +1,15 @@ --- description: "Revise the current feature spec in place (add/remove ACs, FRs, stories) and cascade the change into plan and tasks." handoffs: - - label: Implement Revision Tasks - agent: speckit.implement - prompt: Implement only the new and cleanup tasks from the latest revision phase + - label: Create Plan + agent: speckit.plan + prompt: Only if plan.md is missing, or the latest revisions.md entry has plan_status: needs-rebuild. Inventory only live spec IDs. Do not restore SUPERSEDED or RETIRED lines. - label: Create Tasks agent: speckit.tasks - prompt: Only if tasks.md does not exist. Never regenerate an existing task list — revise already appended a Revision phase. + prompt: Only if tasks.md does not exist. Never regenerate an existing task list — revise already appended a Revision phase. Inventory only live spec IDs. + - label: Implement Revision Tasks + agent: speckit.implement + prompt: Implement only the new and cleanup tasks from the latest revision phase. Skip if plan.md is missing or plan_status is needs-rebuild. scripts: sh: scripts/bash/check-prerequisites.sh --json --paths-only ps: scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly @@ -115,7 +118,7 @@ Already true on a **live** line (or same as last `R#`) is a duplicate. Skip it. - Supersede or retire **and that old ID is implemented**: a short bullet for **removing** the old behavior (what to delete, not a new architecture). - Supersede or retire **and that old ID is not implemented**: no remove-code bullet; just stop planning the old thing. - Strike the old plan bullet (`SUPERSEDED` / `RETIRED`). Don't delete it. - - Don't send them to `__SPECKIT_COMMAND_PLAN__` to rebuild. If you truly can't patch, set `plan_status: needs-rebuild` in the report — still don't wipe the file. + - Prefer a patch. If you truly can't patch, set `plan_status: needs-rebuild` — still don't wipe the file; next command is `__SPECKIT_COMMAND_PLAN__`. - No plan yet → `plan_status: missing`; next command is `__SPECKIT_COMMAND_PLAN__`. 7. **tasks.md — only if it exists. Never regenerate. Only append.** @@ -135,15 +138,18 @@ Already true on a **live** line (or same as last `R#`) is a duplicate. Skip it. - superseded: {old} → {new} - retired: {id} - reworded: {id} + - plan_status: patched | needs-rebuild | missing | unchanged ``` - Skip empty bullets. Don't edit old entries. Don't append on a no-op. + Skip empty bullets. Don't edit old entries. Don't append on a no-op. Always persist `plan_status` on a real revision so the next session can see it. -9. Refresh `checklists/requirements.md` if it exists, **before** after-hooks. +9. Refresh `checklists/requirements.md` if it exists, **before** after-hooks. Re-evaluate checkboxes against **live** spec lines only. Do not rewrite the file from scratch. 10. Run `hooks.after_revise`. -Tell them once what was marked, what was added, and whether implement should **add** code, **remove** code, or both. Always include `plan_status:` as one of `patched` (plan bullets added or struck), `needs-rebuild` (could not patch safely), `missing` (no `plan.md`), or `unchanged` (no-op or plan needed no edit). Next is usually `__SPECKIT_COMMAND_IMPLEMENT__` (or plan/tasks if those files are still missing). +Tell them once what was marked, what was added, and whether implement should **add** code, **remove** code, or both. Always include `plan_status:` as one of `patched` (plan bullets added or struck), `needs-rebuild` (could not patch safely), `missing` (no `plan.md`), or `unchanged` (no-op or plan needed no edit). Persist that same value on the new `revisions.md` entry. + +Next command: `needs-rebuild` or `missing` → `__SPECKIT_COMMAND_PLAN__`. Then if `tasks.md` is missing → `__SPECKIT_COMMAND_TASKS__`. Otherwise `__SPECKIT_COMMAND_IMPLEMENT__`. ## Done When diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 4468d07d5e..7e83953447 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -69,10 +69,11 @@ If `tasks.md` already exists and either `revisions.md` is present or `tasks.md` - **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios) - **IF EXISTS**: Load `/memory/constitution.md` for project principles and governance constraints - Note: Not all projects have all documents. Generate tasks based on what's available. + - Inventory only **live** (unmarked) FRs, ACs, and SCs. Never emit tasks for `SUPERSEDED` or `RETIRED` lines, even on first generation. 3. **Execute task generation workflow**: - - Load plan.md and extract tech stack, libraries, project structure - - Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.) + - Load plan.md and extract tech stack, libraries, project structure (skip `SUPERSEDED` / `RETIRED` bullets) + - Load spec.md and extract **live** user stories with their priorities (P1, P2, P3, etc.) - If data-model.md exists: Extract entities and map to user stories - If contracts/ exists: Map interface contracts to user stories - If research.md exists: Extract decisions for setup tasks diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index 74e78fc293..ef35ae461b 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -136,6 +136,8 @@ def test_reports_named_plan_status(self): assert "needs-rebuild" in self.text assert "patched" in self.text assert "missing" in self.text + assert "- plan_status:" in self.text + assert "Persist that same value" in self.text def test_tasks_handoff_refuses_to_regenerate(self): assert "agent: speckit.tasks" in self.text @@ -145,6 +147,8 @@ def test_hands_off_to_implement_plan_or_tasks(self): assert "__SPECKIT_COMMAND_IMPLEMENT__" in self.text assert "__SPECKIT_COMMAND_PLAN__" in self.text assert "agent: speckit.tasks" in self.text + assert "agent: speckit.plan" in self.text + assert "plan_status: needs-rebuild" in self.text def test_uses_script_placeholder(self): assert "{SCRIPT}" in self.text @@ -161,6 +165,8 @@ def test_implement_skips_cancelled_tasks(self): assert "CANCELLED" in text assert "SUPERSEDED" in text assert "not executable" in text + assert "only when `tasks.md` is **absent**" in text + assert "Do not regenerate" in text def test_taskstoissues_skips_cancelled_tasks(self): text = (COMMANDS / "taskstoissues.md").read_text(encoding="utf-8") @@ -172,6 +178,9 @@ def test_analyze_treats_revisions_as_history(self): assert "revisions.md" in text assert "retired" in text.lower() assert "__SPECKIT_COMMAND_REVISE__" in text + assert "Only **live**" in text + assert "Do not create keys for `SUPERSEDED` or `RETIRED` IDs" in text + assert "Skip plan bullets marked `SUPERSEDED` or `RETIRED`" in text def test_specify_is_create_not_update(self): text = (COMMANDS / "specify.md").read_text(encoding="utf-8") @@ -195,6 +204,8 @@ def test_tasks_refuses_to_regenerate_after_revise(self): assert "do **not** regenerate the file" in text assert "Revision R#" in text assert "__SPECKIT_COMMAND_REVISE__" in text + assert "Inventory only **live**" in text + assert "even on first generation" in text def test_lean_implement_skips_cancelled_tasks(self): text = ( @@ -208,14 +219,47 @@ def test_lean_implement_skips_cancelled_tasks(self): assert "SUPERSEDED" in text assert "not count them as remaining work" in text + def test_plan_inventories_only_live_ids(self): + text = (COMMANDS / "plan.md").read_text(encoding="utf-8") + assert "SUPERSEDED" in text + assert "RETIRED" in text + assert "do not restore them" in text + assert "plan_status: needs-rebuild" in text + + def test_lean_specify_refuses_to_overwrite_existing_spec(self): + text = ( + REPO_ROOT / "presets" / "lean" / "commands" / "speckit.specify.md" + ).read_text(encoding="utf-8") + assert "/speckit.revise" in text + assert "Never overwrite an existing `spec.md`" in text + assert "do not overwrite it" in text + + def test_lean_plan_skips_retired_ids(self): + text = ( + REPO_ROOT / "presets" / "lean" / "commands" / "speckit.plan.md" + ).read_text(encoding="utf-8") + assert "SUPERSEDED" in text + assert "RETIRED" in text + assert "do not restore them" in text + assert "plan_status: needs-rebuild" in text + def test_lean_tasks_refuses_to_overwrite_revision_list(self): text = ( REPO_ROOT / "presets" / "lean" / "commands" / "speckit.tasks.md" ).read_text(encoding="utf-8") assert "do **not** overwrite it" in text assert "Revision R#" in text + assert "Never emit tasks for `SUPERSEDED` or `RETIRED` lines" in text def test_clarify_defers_known_deltas_to_revise(self): text = (COMMANDS / "clarify.md").read_text(encoding="utf-8") assert "__SPECKIT_COMMAND_REVISE__" in text assert "concrete delta" in text + assert "do **not** replace or delete live FR/AC/SC lines" in text + assert "do **not** drop `SUPERSEDED` / `RETIRED` history" in text + + def test_checklist_inventories_only_live_ids(self): + text = (COMMANDS / "checklist.md").read_text(encoding="utf-8") + assert "only **live**" in text + assert "SUPERSEDED" in text + assert "__SPECKIT_COMMAND_REVISE__" in text From a04538b7490053fab310f140f0f193c4eb8445a3 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 14:41:42 +0530 Subject: [PATCH 12/14] docs(revise): keep existing living-spec wording; only mention revise Do not rewrite the evolving-specs steps or persistence model. Add a single line pointing at /speckit.revise and leave the original clarify / plan / tasks loop in place. Assisted-by: Grok (model: grok-4.6, supervised) --- docs/concepts/spec-persistence.md | 8 +++----- docs/guides/evolving-specs.md | 26 ++++++++++---------------- docs/reference/agentic-sdd.md | 10 +++++----- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/docs/concepts/spec-persistence.md b/docs/concepts/spec-persistence.md index 0fb5a77c1a..ef6e56a1bc 100644 --- a/docs/concepts/spec-persistence.md +++ b/docs/concepts/spec-persistence.md @@ -68,12 +68,10 @@ that make the lineage easy to follow. Use living spec when `spec.md` is the contract and the other artifacts are derived from it. -In this model, teams update `spec.md` first and then revise +In this model, teams update `spec.md` first and then regenerate or revise `plan.md` and `tasks.md` from that source. The plan and task list are still valuable, but they are treated as disposable derivations rather than permanent -sources of truth. `/speckit.revise` is the command for that loop: it edits the -current spec in place, records the delta in `revisions.md`, and cascades into -plan and tasks without opening a new feature directory. +sources of truth. Use `/speckit.revise` to edit the current spec in place. Living spec works well when: @@ -95,7 +93,7 @@ applies. |---|---|---|---| | Flow-back spec | Edit any artifact, then reconcile | Fast iteration and close collaboration | Silent drift between artifacts | | Flow-forward spec | Create a new feature directory for new requirements | Audit trails and historical clarity | Duplicate or fragmented context | -| Living spec | Edit `spec.md` (via `/speckit.revise`); patch derived artifacts | Spec as contract | Lost rationale if the plan is fully rebuilt | +| Living spec | Edit `spec.md`; regenerate derived artifacts | Spec as contract | Lost rationale in regenerated files | If your team has not chosen a model yet, start by answering two questions: diff --git a/docs/guides/evolving-specs.md b/docs/guides/evolving-specs.md index e1f8b3a441..475dfa2f60 100644 --- a/docs/guides/evolving-specs.md +++ b/docs/guides/evolving-specs.md @@ -38,26 +38,20 @@ Use living spec when `spec.md` is the contract and `plan.md` and `tasks.md` are derived from it. When intended behavior changes, revise the existing `spec.md` first. Then -bring `plan.md` and `tasks.md` in line with that contract: +regenerate or manually revise downstream artifacts so they match the updated +spec. Use `/speckit.revise` to apply a known add, remove, or reword on the +current spec (do not run `/speckit.specify` again for that). 1. Start from a clean working tree or a dedicated branch so every generated change is reviewable. -2. Apply the delta with `/speckit.revise`. Pass the change as arguments — add, - remove, or reword acceptance criteria, functional requirements, stories, or - success criteria. The command edits the **current** `spec.md`, appends - `revisions.md`, patches `plan.md` when it can, and appends or cancels tasks. - Do **not** run `/speckit.specify` (that opens a new feature directory) and do - not use `/speckit.clarify` for a delta you already know — clarify only fills - pre-plan gaps by asking questions. -3. If the latest `revisions.md` entry has `plan_status: needs-rebuild`, rerun - `/speckit.plan`. Otherwise keep the patched plan. -4. Do not rerun `/speckit.tasks` unless there is no `tasks.md` yet. Revise - appends a `Revision R#` phase and cancels open tasks that only served a - retired ID. -5. Run `/speckit.analyze` before implementation resumes to catch gaps and - references to retired IDs. +2. Update `spec.md` with `/speckit.clarify` or an explicit edit. +3. Rerun `/speckit.plan` or revise `plan.md` so the technical approach matches + the revised spec. +4. Rerun `/speckit.tasks` or revise `tasks.md` so implementation work matches + the revised plan. +5. Run `/speckit.analyze` before implementation resumes to catch gaps between + the spec, plan, and tasks. 6. Run `/speckit.implement`, then review the code and artifact diffs together. - Implement skips cancelled tasks. 7. Run `/speckit.converge` to assess completion and append any remaining work to `tasks.md`. If tasks are appended, repeat `/speckit.implement` and `/speckit.converge` until the feature is fully complete. Preserve important implementation rationale before replacing derived artifacts. diff --git a/docs/reference/agentic-sdd.md b/docs/reference/agentic-sdd.md index 9e341f37b8..9298f243f4 100644 --- a/docs/reference/agentic-sdd.md +++ b/docs/reference/agentic-sdd.md @@ -23,9 +23,9 @@ Creates or updates the project **constitution** — the guiding principles that ## `/speckit.specify` -Creates a **new** feature **specification** from a natural-language description (a new directory under `specs/`). Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. To add, remove, or reword requirements on the **current** spec, run `/speckit.revise` instead. +Creates or updates the feature **specification** from a natural-language description. Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. For a known add, remove, or reword on the current spec, use `/speckit.revise`. -This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` / `/speckit.revise` re-evaluate. That lifecycle is separate from custom checklists generated by `/speckit.checklist`. +This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` re-evaluates. That lifecycle is separate from custom checklists generated by `/speckit.checklist`. ```text /speckit.specify Build an application that helps me organize photos into albums grouped by date, re-orderable by drag-and-drop on the main page, with a tile preview inside each album. @@ -39,7 +39,7 @@ Asks up to five targeted questions about underspecified areas of the current spe /speckit.clarify Focus on the task card behavior: status changes, comment limits, and who can be assigned. ``` -Clarifying before planning keeps you from designing on top of ambiguity. If `/speckit.analyze` later surfaces requirement gaps, come back and run `/speckit.clarify` (pre-plan questions), `/speckit.revise` (a known add/remove/reword on the current spec), or `/speckit.specify` (a genuinely new feature). +Clarifying before planning keeps you from designing on top of ambiguity. If `/speckit.analyze` later surfaces requirement gaps, come back and run `/speckit.clarify` (or `/speckit.specify`) again. For a known add, remove, or reword on the current spec, use `/speckit.revise`. When `checklists/requirements.md` exists, `/speckit.clarify` may update its evaluated state as part of tightening the spec. This exception applies only to the built-in requirements checklist, not to custom review checklists. @@ -67,7 +67,7 @@ Run it with no arguments for a broad pass, or pass a focus area to target one as /speckit.checklist Focus on the Kanban board interactions and comment permissions. ``` -Review the generated checklist. If it surfaces gaps, loop back to `/speckit.clarify` (pre-plan questions), `/speckit.revise` (a known add/remove/reword on the current spec), or `/speckit.specify` (a genuinely new feature), then mark each custom checklist item `[x]` only after the requirements-quality criterion has been reviewed and satisfied. +Review the generated checklist. If it surfaces gaps, loop back to `/speckit.clarify` or `/speckit.specify` to tighten the spec before breaking the work down, then mark each custom checklist item `[x]` only after the requirements-quality criterion has been reviewed and satisfied. For a known current-spec delta, use `/speckit.revise`. ## `/speckit.tasks` @@ -85,7 +85,7 @@ Performs a **read-only** cross-artifact consistency and quality analysis across /speckit.analyze ``` -Run it before implementing, while the artifacts can still be adjusted cheaply. If it surfaces issues, **return to the earlier step that owns them** and fix them at the source — `/speckit.specify` for a new feature, `/speckit.clarify` for pre-plan gaps, `/speckit.revise` for a known delta on the current spec, `/speckit.plan` for design problems, `/speckit.tasks` only when the task list must be rebuilt — then re-run `/speckit.analyze` until it comes back clean. You can also run `/speckit.analyze` again after implementation as an extra review. If `revisions.md` exists, retired IDs are history; the current `spec.md` wins. +Run it before implementing, while the artifacts can still be adjusted cheaply. If it surfaces issues, **return to the earlier step that owns them** and fix them at the source — `/speckit.specify` or `/speckit.clarify` for requirement problems, `/speckit.plan` for design problems, `/speckit.tasks` to regenerate the task list — then re-run `/speckit.analyze` until it comes back clean. You can also run `/speckit.analyze` again after implementation as an extra review. For a known current-spec delta, use `/speckit.revise`. ## `/speckit.implement` From 17ea622d854a2fc61e8b5e5500b3c0942cfbb722 Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 17:07:45 +0530 Subject: [PATCH 13/14] fix(revise): qualify clarify validation; note when to skip regenerate Keep the existing living-spec steps. After /speckit.revise, skip clarify/plan/tasks unless plan or tasks are missing. Clarify must not delete a live line to pass the contradiction check. Assisted-by: Grok (model: grok-4.6, supervised) --- docs/guides/evolving-specs.md | 2 ++ templates/commands/clarify.md | 2 +- tests/test_revise_command.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/guides/evolving-specs.md b/docs/guides/evolving-specs.md index 475dfa2f60..fdc9f7e722 100644 --- a/docs/guides/evolving-specs.md +++ b/docs/guides/evolving-specs.md @@ -54,6 +54,8 @@ current spec (do not run `/speckit.specify` again for that). 6. Run `/speckit.implement`, then review the code and artifact diffs together. 7. Run `/speckit.converge` to assess completion and append any remaining work to `tasks.md`. If tasks are appended, repeat `/speckit.implement` and `/speckit.converge` until the feature is fully complete. +If you used `/speckit.revise`, skip steps 2–4 unless `plan.md`/`tasks.md` are missing or the latest `revisions.md` entry has `plan_status: needs-rebuild`. Then continue from analyze. + Preserve important implementation rationale before replacing derived artifacts. If a plan or task list contains decisions that still matter, carry them forward explicitly. diff --git a/templates/commands/clarify.md b/templates/commands/clarify.md index b6caf180cd..ce1d85c2a5 100644 --- a/templates/commands/clarify.md +++ b/templates/commands/clarify.md @@ -205,7 +205,7 @@ Execution steps: - Clarifications session contains exactly one bullet per accepted answer (no duplicates). - Total asked (accepted) questions ≤ 5. - Updated sections contain no lingering vague placeholders the new answer was meant to resolve. - - No contradictory earlier statement remains (scan for now-invalid alternative choices removed). + - No contradictory earlier statement remains (scan for now-invalid alternative choices removed). If `revisions.md` / `plan.md` / `tasks.md` exist, do not remove the old live line to satisfy this check — recommend `__SPECKIT_COMMAND_REVISE__` instead. - Markdown structure valid; only allowed new headings: `## Clarifications`, `### Session YYYY-MM-DD`. - Terminology consistency: same canonical term used across all updated sections. diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index ef35ae461b..a62a52b0cf 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -257,6 +257,7 @@ def test_clarify_defers_known_deltas_to_revise(self): assert "concrete delta" in text assert "do **not** replace or delete live FR/AC/SC lines" in text assert "do **not** drop `SUPERSEDED` / `RETIRED` history" in text + assert "do not remove the old live line" in text def test_checklist_inventories_only_live_ids(self): text = (COMMANDS / "checklist.md").read_text(encoding="utf-8") From 0ecb75e628eccf9f258727c7750d58124cf589ec Mon Sep 17 00:00:00 2001 From: Harsha Bharadwaj Date: Sun, 16 Aug 2026 17:18:27 +0530 Subject: [PATCH 14/14] fix(revise): specify stays create-or-update; revise after plan/tasks/impl Specify can still create or update a spec before plan, tasks, or implementation exist. Revise is for changing FRs, SCs, or ACs after those exist, and for cascading the related artifacts. Assisted-by: Grok (model: grok-4.6, supervised) --- README.md | 2 +- README.zh-CN.md | 2 +- docs/guides/evolving-specs.md | 4 ++-- docs/installation.md | 2 +- docs/quickstart.md | 4 ++-- docs/reference/agentic-sdd.md | 4 ++-- presets/lean/commands/speckit.specify.md | 6 +++--- src/specify_cli/__init__.py | 2 +- src/specify_cli/commands/init.py | 2 +- templates/commands/analyze.md | 2 +- templates/commands/checklist.md | 2 +- templates/commands/revise.md | 2 +- templates/commands/specify.md | 6 +++--- tests/integrations/test_integration_copilot.py | 2 +- tests/test_presets.py | 2 +- tests/test_revise_command.py | 18 +++++++++--------- 16 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 346d7ebb5c..e891ace45e 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ Additional commands for enhanced quality and validation: | Command | Agent Skill | Description | | -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `/speckit.clarify` | `speckit-clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) | -| `/speckit.revise` | `speckit-revise` | Revise the current spec in place (add/remove/reword ACs or FRs) and cascade into plan/tasks | +| `/speckit.revise` | `speckit-revise` | After plan/tasks/implementation, revise FRs, SCs, or ACs and cascade into related artifacts | | `/speckit.analyze` | `speckit-analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) | | `/speckit.checklist` | `speckit-checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") | diff --git a/README.zh-CN.md b/README.zh-CN.md index 6bd524007f..59af2b6d73 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -184,7 +184,7 @@ Spec Kit 可与 30 多个 AI 编码助手协作 —— 既包括 CLI 工具, | 命令 | 助手技能 | 说明 | | -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------- | | `/speckit.clarify` | `speckit-clarify` | 澄清描述不充分的部分(建议在 `/speckit.plan` 之前使用;旧称 `/quizme`) | -| `/speckit.revise` | `speckit-revise` | 就地修订当前规格(增删或改写验收标准 / 功能需求),并级联更新 plan/tasks | +| `/speckit.revise` | `speckit-revise` | 在已有 plan/tasks/实现之后,修订 FR、SC 或 AC 并级联相关产物 | | `/speckit.analyze` | `speckit-analyze` | 跨制品的一致性与覆盖度分析(在 `/speckit.tasks` 之后、`/speckit.implement` 之前运行) | | `/speckit.checklist` | `speckit-checklist` | 生成自定义质量清单,校验需求的完整性、清晰度与一致性(好比"为自然语言写单元测试") | diff --git a/docs/guides/evolving-specs.md b/docs/guides/evolving-specs.md index fdc9f7e722..cefe00c5fb 100644 --- a/docs/guides/evolving-specs.md +++ b/docs/guides/evolving-specs.md @@ -39,8 +39,8 @@ derived from it. When intended behavior changes, revise the existing `spec.md` first. Then regenerate or manually revise downstream artifacts so they match the updated -spec. Use `/speckit.revise` to apply a known add, remove, or reword on the -current spec (do not run `/speckit.specify` again for that). +spec. Use `/speckit.revise` when plan, tasks, or implementation already exist +and you need to change FRs, SCs, or ACs and the related artifacts. 1. Start from a clean working tree or a dedicated branch so every generated change is reviewable. diff --git a/docs/installation.md b/docs/installation.md index 5bc3e29c16..ec23dbaee7 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -123,7 +123,7 @@ After initialization, you should see the following commands available in your co - `/speckit.implement` - Execute implementation tasks - `/speckit.analyze` - Validate cross-artifact consistency - `/speckit.clarify` - Identify and resolve ambiguities -- `/speckit.revise` - Revise the current spec in place and cascade into plan/tasks +- `/speckit.revise` - After plan, tasks, or implementation, revise FRs, SCs, or ACs and related artifacts - `/speckit.checklist` - Generate quality checklists - `/speckit.constitution` - Create or update project principles - `/speckit.converge` - Assess codebase against artifacts and append remaining tasks diff --git a/docs/quickstart.md b/docs/quickstart.md index ebfa40a0b9..c0b3fdca2e 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -120,9 +120,9 @@ Checks the codebase against the spec, plan, and tasks. If it finds gaps, it appe /speckit.converge ``` -### After shipping: `/speckit.revise` — change the current spec +### After plan, tasks, or implementation: `/speckit.revise` — change FRs, SCs, or ACs -When requirements change on the **same** feature (add or drop an acceptance criterion, retire an FR), do **not** run `/speckit.specify` again. That opens a new feature directory. Use `/speckit.revise` instead. If `plan.md` or `tasks.md` is still missing, run plan then tasks. If revise reports `plan_status: needs-rebuild`, rerun `/speckit.plan`. Then analyze, implement, and converge. +`/speckit.specify` can still create or update a spec before plan, tasks, or implementation exist. Once any of those exist, use `/speckit.revise` to change FRs, SCs, or ACs and cascade into the related artifacts. If revise reports `plan_status: needs-rebuild`, rerun `/speckit.plan`. Then analyze, implement, and converge. ```text /speckit.revise Remove password login. Add AC: Given an expired session, When the user submits, Then redirect to SSO diff --git a/docs/reference/agentic-sdd.md b/docs/reference/agentic-sdd.md index 9298f243f4..3b7a5cfb05 100644 --- a/docs/reference/agentic-sdd.md +++ b/docs/reference/agentic-sdd.md @@ -23,7 +23,7 @@ Creates or updates the project **constitution** — the guiding principles that ## `/speckit.specify` -Creates or updates the feature **specification** from a natural-language description. Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. For a known add, remove, or reword on the current spec, use `/speckit.revise`. +Creates or updates the feature **specification** from a natural-language description. Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`. After plan, tasks, or implementation exist, use `/speckit.revise` to change FRs, SCs, or ACs. This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` re-evaluates. That lifecycle is separate from custom checklists generated by `/speckit.checklist`. @@ -119,7 +119,7 @@ Applies a **known requirement delta** to the **current** feature spec — add, r /speckit.revise Remove password login. Add AC: Given an expired session, When the user submits, Then redirect to SSO ``` -Run this when the spec itself changed. Then `/speckit.analyze` (if plan and tasks exist), `/speckit.implement` (skips cancelled tasks), then `/speckit.converge`. Do not run `/speckit.converge` first — converge assumes the spec is stable. +Run this when FRs, SCs, or ACs change **after** plan, tasks, or implementation exist. Then `/speckit.analyze` (if plan and tasks exist), `/speckit.implement` (skips cancelled tasks), then `/speckit.converge`. Do not run `/speckit.converge` first — converge assumes the spec is stable. ## `/speckit.converge` diff --git a/presets/lean/commands/speckit.specify.md b/presets/lean/commands/speckit.specify.md index ee5c89b514..b0ede4be19 100644 --- a/presets/lean/commands/speckit.specify.md +++ b/presets/lean/commands/speckit.specify.md @@ -10,7 +10,7 @@ $ARGUMENTS ## Outline -If `.specify/feature.json` or the named directory already has a `spec.md` and the user stated a **concrete delta** (add, remove, or reword a named AC, FR, story, or success criterion), do **not** write anything. Recommend `/speckit.revise` and stop. Never overwrite an existing `spec.md`. +If `plan.md`, `tasks.md`, or implementation already exist and the user stated a **concrete delta** (add, remove, or reword a named AC, FR, SC, or story), recommend `/speckit.revise` and stop. If only `spec.md` exists, this command may **update** it. 1. **Ask the user** for the feature directory path (e.g., `specs/my-feature`). Do not proceed until provided. @@ -19,8 +19,8 @@ If `.specify/feature.json` or the named directory already has a `spec.md` and th { "feature_directory": "" } ``` -3. Create a specification from the user input and store it in `/spec.md`. - - If that path already has a `spec.md`, stop — do not overwrite it +3. Create or update the specification from the user input and store it in `/spec.md`. + - If that path already has a `spec.md` and `plan.md` or `tasks.md` exist, stop — recommend `/speckit.revise`. Otherwise you may update the existing spec. - Overview, functional requirements, user scenarios, success criteria - Every requirement must be testable - Make informed defaults for unspecified details diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 108d9388d8..bb2f3916c9 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -367,7 +367,7 @@ def _print_cli_warning( # Constants kept for backward compatibility with presets and extensions. DEFAULT_SKILLS_DIR = ".agents/skills" SKILL_DESCRIPTIONS = { - "specify": "Create a new feature specification from a natural language description.", + "specify": "Create or update feature specifications from natural language descriptions.", "plan": "Generate technical implementation plans from feature specifications.", "tasks": "Break down implementation plans into actionable task lists.", "implement": "Execute all tasks from the task breakdown to build the feature.", diff --git a/src/specify_cli/commands/init.py b/src/specify_cli/commands/init.py index 1612693fb9..2020342445 100644 --- a/src/specify_cli/commands/init.py +++ b/src/specify_cli/commands/init.py @@ -1103,7 +1103,7 @@ def _display_cmd(name: str) -> str: f"○ [cyan]{_display_cmd('clarify')}[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]{_display_cmd('plan')}[/] if used)", f"○ [cyan]{_display_cmd('analyze')}[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]{_display_cmd('tasks')}[/], before [cyan]{_display_cmd('implement')}[/])", f"○ [cyan]{_display_cmd('checklist')}[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]{_display_cmd('plan')}[/])", - f"○ [cyan]{_display_cmd('revise')}[/] [bright_black](optional)[/bright_black] - Edit the current feature spec in place when requirements change (do not run [cyan]{_display_cmd('specify')}[/] again)", + f"○ [cyan]{_display_cmd('revise')}[/] [bright_black](optional)[/bright_black] - Revise FRs, SCs, or ACs after plan, tasks, or implementation exist", ] enhancements_title = ( "Enhancement Skills" if native_skill_mode else "Enhancement Commands" diff --git a/templates/commands/analyze.md b/templates/commands/analyze.md index d9368ee42a..96a1273123 100644 --- a/templates/commands/analyze.md +++ b/templates/commands/analyze.md @@ -165,7 +165,7 @@ Focus on high-signal findings. Limit to 50 findings total; aggregate remainder i - Conflicting requirements (e.g., one requires Next.js while other specifies Vue) - Plan or tasks still treating a **retired** ID from `revisions.md` as in-scope (the current `spec.md` no longer requires it) -If requirement problems need a follow-up edit: use `__SPECKIT_COMMAND_SPECIFY__` only for a **new** feature; use `__SPECKIT_COMMAND_CLARIFY__` to fill pre-plan gaps; if the user already knows the delta (add/remove AC or FR), recommend `__SPECKIT_COMMAND_REVISE__`. +If requirement problems need a follow-up edit: use `__SPECKIT_COMMAND_SPECIFY__` to create or update a spec that is not yet planned, tasked, or implemented; use `__SPECKIT_COMMAND_CLARIFY__` to fill pre-plan gaps; if `plan.md`, `tasks.md`, or implementation already exist and the user knows the FR / SC / AC delta, recommend `__SPECKIT_COMMAND_REVISE__`. ### 5. Severity Assignment diff --git a/templates/commands/checklist.md b/templates/commands/checklist.md index 30761768ce..cd8ae1fd31 100644 --- a/templates/commands/checklist.md +++ b/templates/commands/checklist.md @@ -130,7 +130,7 @@ You **MUST** consider the user input before proceeding (if not empty). - plan.md (if exists): Technical details, dependencies (skip struck plan bullets) - tasks.md (if exists): Implementation tasks (skip `CANCELLED` / `SUPERSEDED` task lines) - If this is a living spec (`revisions.md` present) and the gap is an add/remove/reword, recommend `__SPECKIT_COMMAND_REVISE__` rather than `__SPECKIT_COMMAND_SPECIFY__`. + If `plan.md`, `tasks.md`, or implementation already exist and the gap is an add/remove/reword, recommend `__SPECKIT_COMMAND_REVISE__` rather than `__SPECKIT_COMMAND_SPECIFY__`. **Context Loading Strategy**: - Load only necessary portions relevant to active focus areas (avoid full-file dumping) diff --git a/templates/commands/revise.md b/templates/commands/revise.md index 57e8cca5c6..28f9722111 100644 --- a/templates/commands/revise.md +++ b/templates/commands/revise.md @@ -20,7 +20,7 @@ The user is changing requirements on the **current** feature. That is why this c Do **not** rewrite `spec.md`. Do **not** rewrite `plan.md`. Do **not** rewrite `tasks.md`. Mid-flight requirement changes used to do that — wipe the file and regenerate — and it destroyed history. You only mark old lines, append new ones, and add a short log. -This is not `__SPECKIT_COMMAND_SPECIFY__` (new `specs/` folder). Not `__SPECKIT_COMMAND_PLAN__` or `__SPECKIT_COMMAND_TASKS__` (those rebuild artifacts). Not `__SPECKIT_COMMAND_CONVERGE__` (spec unchanged, code lagged). You do not write application code; `__SPECKIT_COMMAND_IMPLEMENT__` does. +`__SPECKIT_COMMAND_SPECIFY__` still **creates or updates** a spec when there is no plan, tasks, or implementation yet. Use **this** command after `plan.md`, `tasks.md`, or implementation exist, to revise FRs / SCs / ACs and cascade into those artifacts. Not `__SPECKIT_COMMAND_PLAN__` or `__SPECKIT_COMMAND_TASKS__` (those rebuild artifacts). Not `__SPECKIT_COMMAND_CONVERGE__` (spec unchanged, code lagged). You do not write application code; `__SPECKIT_COMMAND_IMPLEMENT__` does. ## Input diff --git a/templates/commands/specify.md b/templates/commands/specify.md index f413b4405b..da8a6d45e4 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -1,5 +1,5 @@ --- -description: Create a new feature specification from a natural language feature description. +description: Create or update the feature specification from a natural language feature description. handoffs: - label: Build Technical Plan agent: speckit.plan @@ -18,7 +18,7 @@ $ARGUMENTS You **MUST** consider the user input before proceeding (if not empty). -If a current feature already exists (`.specify/feature.json` or an active `spec.md`) and the user stated a **concrete delta** (add, remove, or reword a named AC, FR, story, or success criterion) on that spec, do **not** create another directory and do **not** run `before_specify` hooks. Recommend `__SPECKIT_COMMAND_REVISE__` and stop. A genuinely new product (different user, different outcome, no shared stories) continues below. +If the current feature already has `plan.md`, `tasks.md`, or implemented work, and the user stated a **concrete delta** (add, remove, or reword a named AC, FR, SC, or story), do **not** create another directory and do **not** run `before_specify` hooks. Recommend `__SPECKIT_COMMAND_REVISE__` and stop. If only `spec.md` exists (not yet planned, tasked, or implemented), this command can still **update** that spec. A genuinely new product continues below as a new feature. ## Pre-Execution Checks @@ -111,7 +111,7 @@ Given that feature description, do this: - You must only create one feature per `__SPECKIT_COMMAND_SPECIFY__` invocation - The spec directory name and the git branch name are independent — they may be the same but that is the user's choice - The spec directory and file are always created by this command, never by the hook - - This command starts a **new** feature. If the user wants to add, remove, or reword acceptance criteria / requirements on the **current** spec, do not create another directory — recommend `__SPECKIT_COMMAND_REVISE__` instead + - This command can create a new feature or **update** a spec that has no plan, tasks, or implementation yet. If `plan.md`, `tasks.md`, or implementation already exist and the user wants to add, remove, or reword FRs / SCs / ACs, do not create another directory — recommend `__SPECKIT_COMMAND_REVISE__` instead 4. Load the resolved active `spec-template` file to understand required sections. diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index 60aedb4f23..28adad9417 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -192,7 +192,7 @@ def test_setup_falls_back_to_bundled_command_template_without_preset_override(se specify_file = tmp_path / ".github" / "agents" / "speckit.specify.agent.md" content = specify_file.read_text(encoding="utf-8") - assert "Create a new feature specification" in content + assert "Create or update the feature specification" in content assert "preset override content" not in content def test_setup_uses_preset_command_override_when_present(self, tmp_path): diff --git a/tests/test_presets.py b/tests/test_presets.py index de490f0330..dcb7de2d0a 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -5409,7 +5409,7 @@ def test_skill_restored_on_preset_remove_without_project_core_templates(self, pr content = skill_file.read_text(encoding="utf-8") assert "preset:self-test" not in content assert "templates/commands/specify.md" in content - assert "Create a new feature specification" in content + assert "Create or update the feature specification" in content def test_extension_wins_over_bundled_core_on_preset_remove( self, project_dir, monkeypatch diff --git a/tests/test_revise_command.py b/tests/test_revise_command.py index a62a52b0cf..5278e9015e 100644 --- a/tests/test_revise_command.py +++ b/tests/test_revise_command.py @@ -58,7 +58,7 @@ def test_skill_descriptions_include_revise(): from specify_cli import SKILL_DESCRIPTIONS assert "revise" in SKILL_DESCRIPTIONS - assert "Create a new feature specification" in SKILL_DESCRIPTIONS["specify"] + assert "Create or update feature specifications" in SKILL_DESCRIPTIONS["specify"] def test_revise_has_scripts_frontmatter(): @@ -89,7 +89,7 @@ def setup_method(self): self.text = REVISE.read_text(encoding="utf-8") def test_does_not_create_a_new_feature_directory(self): - assert "new `specs/` folder" in self.text + assert "after `plan.md`, `tasks.md`, or implementation exist" in self.text def test_edits_spec_in_place(self): assert "in place" in self.text @@ -182,12 +182,12 @@ def test_analyze_treats_revisions_as_history(self): assert "Do not create keys for `SUPERSEDED` or `RETIRED` IDs" in text assert "Skip plan bullets marked `SUPERSEDED` or `RETIRED`" in text - def test_specify_is_create_not_update(self): + def test_specify_defers_to_revise_after_plan_tasks_or_impl(self): text = (COMMANDS / "specify.md").read_text(encoding="utf-8") - assert "Create a new feature specification" in text + assert "Create or update the feature specification" in text assert "__SPECKIT_COMMAND_REVISE__" in text - assert "do **not** create another directory" in text - assert "do **not** run `before_specify` hooks" in text + assert "`plan.md`, `tasks.md`, or implemented work" in text + assert "this command can still **update** that spec" in text def test_converge_defers_to_revise_when_spec_changed(self): text = (COMMANDS / "converge.md").read_text(encoding="utf-8") @@ -226,13 +226,13 @@ def test_plan_inventories_only_live_ids(self): assert "do not restore them" in text assert "plan_status: needs-rebuild" in text - def test_lean_specify_refuses_to_overwrite_existing_spec(self): + def test_lean_specify_defers_to_revise_after_plan_or_tasks(self): text = ( REPO_ROOT / "presets" / "lean" / "commands" / "speckit.specify.md" ).read_text(encoding="utf-8") assert "/speckit.revise" in text - assert "Never overwrite an existing `spec.md`" in text - assert "do not overwrite it" in text + assert "`plan.md`, `tasks.md`, or implementation already exist" in text + assert "this command may **update** it" in text def test_lean_plan_skips_retired_ids(self): text = (