From 3bd4aeae3ea935d81badb4c52cec38f190ef33b9 Mon Sep 17 00:00:00 2001 From: Ben Buttigieg <70525+BenBtg@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:30:56 +0100 Subject: [PATCH] Implement /speckit.converge command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the built-in converge command that assesses the codebase against a feature's spec.md, plan.md, and tasks.md and appends remaining unbuilt work as new traceable tasks to tasks.md (append-only; no git, no change tracking). - templates/commands/converge.md: full command body (load artifacts, assess code, classify findings missing/partial/contradicts/unrequested, append '## Phase N — Convergence' tasks with source-ref + gap-type, read-only guardrails, converged branch, handoff, before/after_converge hooks) - Register converge as a core command across all enumeration sites (SKILL_DESCRIPTIONS, _FALLBACK_CORE_COMMAND_NAMES, ARGUMENT_HINTS, and the integration test command lists incl. copilot/generic file inventories) - init.py Next Steps panel + README Core Commands table - tasks.md: T001-T024 complete (T025 manual quickstart pending) Full suite green: 2343 passed. --- README.md | 1 + specs/001-converge-command/plan.md | 9 +- specs/001-converge-command/tasks.md | 56 ++-- src/specify_cli/__init__.py | 1 + src/specify_cli/commands/init.py | 1 + src/specify_cli/extensions.py | 1 + .../integrations/claude/__init__.py | 1 + templates/commands/converge.md | 258 ++++++++++++++++++ .../test_integration_base_markdown.py | 2 +- .../test_integration_base_skills.py | 4 +- .../test_integration_base_toml.py | 1 + .../test_integration_base_yaml.py | 1 + .../integrations/test_integration_copilot.py | 10 +- .../integrations/test_integration_generic.py | 3 + 14 files changed, 313 insertions(+), 36 deletions(-) create mode 100644 templates/commands/converge.md diff --git a/README.md b/README.md index 0a0b4119b..4501e84a0 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ Essential commands for the Spec-Driven Development workflow: | `/speckit.tasks` | `speckit-tasks` | Generate actionable task lists for implementation | | `/speckit.taskstoissues` | `speckit-taskstoissues`| Convert generated task lists into GitHub issues for tracking and execution | | `/speckit.implement` | `speckit-implement` | Execute all tasks to build the feature according to the plan | +| `/speckit.converge` | `speckit-converge` | Assess the codebase against spec/plan/tasks and append remaining work as new tasks | ### Optional Commands diff --git a/specs/001-converge-command/plan.md b/specs/001-converge-command/plan.md index 918c6d44a..0b5e682c2 100644 --- a/specs/001-converge-command/plan.md +++ b/specs/001-converge-command/plan.md @@ -97,19 +97,22 @@ templates/ └── converge.md # NEW — the command template (primary deliverable) src/specify_cli/ -├── __init__.py # EDIT — add "converge" to SKILL_DESCRIPTIONS + post-init "Next Steps" panel (after implement) +├── __init__.py # EDIT — add "converge" to SKILL_DESCRIPTIONS ├── extensions.py # EDIT — add "converge" to _FALLBACK_CORE_COMMAND_NAMES +├── commands/ +│ └── init.py # EDIT — add "converge" to the post-init "Next Steps" panel (after implement) └── integrations/ └── claude/__init__.py # EDIT — add "converge" argument hint tests/ ├── test_agent_config_consistency.py # VERIFY — converge token resolves correctly └── integrations/ - ├── test_integration_base_markdown.py # EDIT — add "converge" to expected command stems + ├── test_integration_base_markdown.py # EDIT — add "converge" to COMMAND_STEMS ├── test_integration_base_toml.py # EDIT — add "converge" to COMMAND_STEMS ├── test_integration_base_yaml.py # EDIT — add "converge" to COMMAND_STEMS ├── test_integration_base_skills.py # EDIT — add "converge" to expected_commands + _SKILL_COMMANDS - └── test_integration_copilot.py # EDIT — add "converge" to expected_commands + _SKILL_COMMANDS + ├── test_integration_copilot.py # EDIT — add "converge" to expected_commands + _SKILL_COMMANDS + └── test_integration_generic.py # EDIT — add "converge" to the constitution-context parametrize list README.md # EDIT — add "/speckit.converge" to the Core Commands table (the canonical slash-command enumeration; Constitution III & V) ``` diff --git a/specs/001-converge-command/tasks.md b/specs/001-converge-command/tasks.md index 0ad3b6486..56e07fa7f 100644 --- a/specs/001-converge-command/tasks.md +++ b/specs/001-converge-command/tasks.md @@ -26,8 +26,8 @@ in `docs/`. **Purpose**: Establish the command template file and its invocation plumbing. -- [ ] T001 Create the command template skeleton at `templates/commands/converge.md` modeled on `templates/commands/analyze.md`: YAML frontmatter with a `description` and a `scripts:` block (`sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks`, `ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks`), a `## User Input` section echoing `$ARGUMENTS`, and empty section placeholders for the workflow. -- [ ] T002 In `templates/commands/converge.md`, add the `## Execution Steps` initialization step that runs `{SCRIPT}` once, parses JSON for `FEATURE_DIR`/`AVAILABLE_DOCS`, derives `SPEC`/`PLAN`/`TASKS`/constitution paths, and aborts with a prerequisite message if `plan.md` or `tasks.md` is missing (per [contracts/command-interface.md](contracts/command-interface.md)). +- [X] T001 Create the command template skeleton at `templates/commands/converge.md` modeled on `templates/commands/analyze.md`: YAML frontmatter with a `description` and a `scripts:` block (`sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks`, `ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks`), a `## User Input` section echoing `$ARGUMENTS`, and empty section placeholders for the workflow. +- [X] T002 In `templates/commands/converge.md`, add the `## Execution Steps` initialization step that runs `{SCRIPT}` once, parses JSON for `FEATURE_DIR`/`AVAILABLE_DOCS`, derives `SPEC`/`PLAN`/`TASKS`/constitution paths, and aborts with a prerequisite message if `plan.md` or `tasks.md` is missing (per [contracts/command-interface.md](contracts/command-interface.md)). --- @@ -37,15 +37,16 @@ in `docs/`. **⚠️ CRITICAL**: Until registration is complete, the command cannot be installed or tested under any agent, and the integration test suite will fail. -- [ ] T003 [P] Add `"converge"` with a one-line description to `SKILL_DESCRIPTIONS` in `src/specify_cli/__init__.py`. -- [ ] T004 [P] Add `"converge"` to the `_FALLBACK_CORE_COMMAND_NAMES` frozenset in `src/specify_cli/extensions.py` (keep alphabetical order). -- [ ] T005 [P] Add a `"converge"` argument-hint entry to `ARGUMENT_HINTS` in `src/specify_cli/integrations/claude/__init__.py`. -- [ ] T006 [P] Add `"converge"` to the `COMMAND_STEMS` list in `tests/integrations/test_integration_base_yaml.py`. -- [ ] T007 [P] Add `"converge"` to the `COMMAND_STEMS` list in `tests/integrations/test_integration_base_toml.py`. -- [ ] T008 [P] Add `"converge"` to the expected command-stems list in `tests/integrations/test_integration_base_markdown.py`. -- [ ] T009 [P] Add `"converge"` to both command enumerations in `tests/integrations/test_integration_base_skills.py`: the `expected_commands` set (~L102) and the `_SKILL_COMMANDS` list (~L361). -- [ ] T010 [P] Add `"converge"` to both command enumerations in `tests/integrations/test_integration_copilot.py`: the hardcoded `expected_commands` set (~L129) and the `_SKILL_COMMANDS` list (~L286). -- [ ] T011 Run `uv run python -m pytest tests/test_agent_config_consistency.py tests/integrations -q` and confirm the new `converge` entries resolve and all command-set assertions pass (fix any enumeration missed in T003–T010). +- [X] T003 [P] Add `"converge"` with a one-line description to `SKILL_DESCRIPTIONS` in `src/specify_cli/__init__.py`. +- [X] T004 [P] Add `"converge"` to the `_FALLBACK_CORE_COMMAND_NAMES` frozenset in `src/specify_cli/extensions.py` (~L31; it is a frozenset, so simply append the entry — ordering is not significant). +- [X] T005 [P] Add a `"converge"` argument-hint entry to `ARGUMENT_HINTS` in `src/specify_cli/integrations/claude/__init__.py`. +- [X] T006 [P] Add `"converge"` to the `COMMAND_STEMS` list in `tests/integrations/test_integration_base_yaml.py`. +- [X] T007 [P] Add `"converge"` to the `COMMAND_STEMS` list in `tests/integrations/test_integration_base_toml.py`. +- [X] T008 [P] Add `"converge"` to the expected command-stems list in `tests/integrations/test_integration_base_markdown.py`. +- [X] T009 [P] Add `"converge"` to both command enumerations in `tests/integrations/test_integration_base_skills.py`: the `expected_commands` set (~L102) and the `_SKILL_COMMANDS` list (~L395). +- [X] T010 [P] Add `"converge"` to both command enumerations in `tests/integrations/test_integration_copilot.py`: the hardcoded `expected_commands` set (~L129) and the `_SKILL_COMMANDS` list (~L323). +- [X] T010a [P] Add `"converge"` to the `command_stem` parametrize list of `test_command_loads_constitution_context` in `tests/integrations/test_integration_generic.py` (~L211–L223) — converge reads the constitution, so it must reference `constitution.md` like its peer commands (Constitution III; the generic test enforces this). +- [X] T011 Run `uv run python -m pytest tests/test_agent_config_consistency.py tests/integrations -q` and confirm the new `converge` entries resolve and all command-set assertions pass (fix any enumeration missed in T003–T010a). **Checkpoint**: `converge` is a recognized core command; the template installs across integrations and the suite is green. User-story behavior can now be added to the template. @@ -57,11 +58,11 @@ in `docs/`. **Independent Test**: In a feature whose code omits one specified requirement, run the command and confirm a new task describing exactly that requirement is appended to `tasks.md`, with no other file changed (quickstart Scenario 1). -- [ ] T012 [US1] In `templates/commands/converge.md`, add the artifact-loading step that reads `spec.md` (requirements FR-/SC-, acceptance scenarios), `plan.md` (decisions/constraints), `tasks.md` (existing tasks + referenced files), and `.specify/memory/constitution.md` — skipping constitution checks gracefully if it is an unfilled template (per [data-model.md](data-model.md) and FR-001, FR-002). -- [ ] T013 [US1] Add the assessment step that scans the code paths named by the artifacts and produces `Finding` records classified as `missing`/`partial`/`contradicts`/`unrequested` with a severity, bounding scope to the feature artifacts only (FR-003, FR-004; [data-model.md](data-model.md) Finding entity). -- [ ] T014 [US1] Add the task-append step implementing the [contracts/tasks-output.md](contracts/tasks-output.md) format: compute the next phase number and next task IDs from the current max, write a `## Phase N — Convergence` section at the end of `tasks.md`, one checklist item per actionable finding (FR-005, FR-006). -- [ ] T015 [US1] Add explicit read-only guardrail instructions to the template: MUST NOT modify `spec.md` or `plan.md`, MUST NOT rewrite/delete existing tasks, MUST NOT modify application code — the only write is the appended Convergence phase (FR-008, FR-009, FR-010). -- [ ] T016 [US1] Add the in-session findings summary output (severity-graded list/table of what was checked and what remains) ahead of the append (FR-012). +- [X] T012 [US1] In `templates/commands/converge.md`, add the artifact-loading step that reads `spec.md` (requirements FR-/SC-, acceptance scenarios), `plan.md` (decisions/constraints), `tasks.md` (existing tasks + referenced files), and `.specify/memory/constitution.md` — skipping constitution checks gracefully if it is an unfilled template (per [data-model.md](data-model.md) and FR-001, FR-002). +- [X] T013 [US1] Add the assessment step that scans the code paths named by the artifacts and produces `Finding` records classified as `missing`/`partial`/`contradicts`/`unrequested` with a severity, bounding scope to the feature artifacts only (FR-003, FR-004; [data-model.md](data-model.md) Finding entity). +- [X] T014 [US1] Add the task-append step implementing the [contracts/tasks-output.md](contracts/tasks-output.md) format: compute the next phase number and next task IDs from the current max, write a `## Phase N — Convergence` section at the end of `tasks.md`, one checklist item per actionable finding (FR-005, FR-006). +- [X] T015 [US1] Add explicit read-only guardrail instructions to the template: MUST NOT modify `spec.md` or `plan.md`, MUST NOT rewrite/delete existing tasks, MUST NOT modify application code — the only write is the appended Convergence phase (FR-008, FR-009, FR-010). +- [X] T016 [US1] Add the in-session findings summary output (severity-graded list/table of what was checked and what remains) ahead of the append (FR-012). **Checkpoint**: Running the command on a feature with a gap appends a correct, traceable Convergence task and touches nothing else — MVP is functional. @@ -73,8 +74,8 @@ in `docs/`. **Independent Test**: Run against a feature whose code fully satisfies its artifacts and confirm a clean result is reported and `tasks.md` is unchanged (quickstart Scenario 3). -- [ ] T017 [US2] In `templates/commands/converge.md`, add the converged-state branch: when there are no actionable findings, emit a clean "converged" summary with counts of requirements, acceptance criteria, and plan decisions checked, and explicitly do NOT modify `tasks.md` (no empty phase header) (FR-011; [contracts/tasks-output.md](contracts/tasks-output.md) clean case). -- [ ] T018 [US2] Add the next-step handoff guidance to the template: on `converged` suggest proceeding to review/PR; on `tasks_appended` suggest running `/speckit.implement` to continue (FR-014). +- [X] T017 [US2] In `templates/commands/converge.md`, add the converged-state branch: when there are no actionable findings, emit a clean "converged" summary with counts of requirements, acceptance criteria, and plan decisions checked, and explicitly do NOT modify `tasks.md` (no empty phase header) (FR-011; [contracts/tasks-output.md](contracts/tasks-output.md) clean case). +- [X] T018 [US2] Add the next-step handoff guidance to the template: on `converged` suggest proceeding to review/PR; on `tasks_appended` suggest running `/speckit.implement` to continue (FR-014). **Checkpoint**: Both the gap path and the clean path produce correct, distinct outcomes. @@ -86,7 +87,7 @@ in `docs/`. **Independent Test**: Run against a feature with several distinct gaps and confirm each appended task names its originating reference and gap-type label (quickstart Scenario 1 trace assertions). -- [ ] T019 [US3] In `templates/commands/converge.md`, refine the append step so each task line carries its `source-ref` (e.g. `FR-003`, `SC-002`, `US1/AC2`, `plan: …`, `Constitution II`) and a `(gap-type)` label, and so constitution violations are emitted as `CRITICAL` (FR-007; [data-model.md](data-model.md) Convergence Task entity). +- [X] T019 [US3] In `templates/commands/converge.md`, refine the append step so each task line carries its `source-ref` (e.g. `FR-003`, `SC-002`, `US1/AC2`, `plan: …`, `Constitution II`) and a `(gap-type)` label, and so constitution violations are emitted as `CRITICAL` (FR-007; [data-model.md](data-model.md) Convergence Task entity). **Checkpoint**: Appended tasks are fully traceable and prioritized. @@ -96,11 +97,11 @@ in `docs/`. **Purpose**: Hooks, cross-integration discoverability, docs, and full validation. -- [ ] T020 [P] In `templates/commands/converge.md`, add the `before_converge` pre-execution hook block and the `after_converge` post-execution hook block (copied from the `analyze.md` pattern, keys renamed), ensuring `after_converge` receives the `converged` vs `tasks_appended` outcome (FR-015; [contracts/hooks.md](contracts/hooks.md)). -- [ ] T021 [P] Add `converge` to the post-init guidance in `src/specify_cli/__init__.py` (the "Next Steps" panel, ~L1567–L1575) as a step shown after `implement` — using the existing `_display_cmd('converge')` helper so it renders correctly across skill/slash modes (FR-017). -- [ ] T022 [P] Add a `/speckit.converge` row to the **README Core Commands table** in `README.md` (~L300–L307) — the only canonical slash-command enumeration in the repo — describing purpose, scope (code → spec), and append-only behavior (Constitution Principles III & V). Note: `docs/reference/core.md` is the `specify` CLI reference and `docs/reference/workflows.md` only defines the Full SDD Cycle workflow (which converge is not part of), so neither requires edits. -- [ ] T023 Mentally apply `process_template` to `templates/commands/converge.md` and confirm no leftover `{SCRIPT}`, `$ARGUMENTS`, or `__SPECKIT_COMMAND_*__` tokens remain unresolvable and that the frontmatter is valid (FR-016). -- [ ] T024 Run `uv run python -m pytest tests/test_agent_config_consistency.py tests/integrations -q` again to confirm the full suite passes after all edits. +- [X] T020 [P] In `templates/commands/converge.md`, add the `before_converge` pre-execution hook block and the `after_converge` post-execution hook block (copied from the `analyze.md` pattern, keys renamed), ensuring `after_converge` receives the `converged` vs `tasks_appended` outcome (FR-015; [contracts/hooks.md](contracts/hooks.md)). +- [X] T021 [P] Add `converge` to the post-init guidance in `src/specify_cli/commands/init.py` (the "Next Steps" panel, ~L576–L582) as a step shown after `implement` (~L580) — using the existing `_display_cmd('converge')` helper so it renders correctly across skill/slash modes (FR-017). +- [X] T022 [P] Add a `/speckit.converge` row to the **README Core Commands table** in `README.md` (~L158–L165) — the only canonical slash-command enumeration in the repo — describing purpose, scope (code → spec), and append-only behavior (Constitution Principles III & V). Note: `docs/reference/core.md` is the `specify` CLI reference and `docs/reference/workflows.md` only defines the Full SDD Cycle workflow (which converge is not part of), so neither requires edits. +- [X] T023 Mentally apply `process_template` to `templates/commands/converge.md` and confirm no leftover `{SCRIPT}`, `$ARGUMENTS`, or `__SPECKIT_COMMAND_*__` tokens remain unresolvable and that the frontmatter is valid (FR-016). +- [X] T024 Run `uv run python -m pytest tests/test_agent_config_consistency.py tests/integrations -q` again to confirm the full suite passes after all edits. - [ ] T025 Execute the [quickstart.md](quickstart.md) scenarios 1–6 manually through a coding agent and capture agent/OS/shell + pass/fail per scenario for the PR (Constitution Principle IV — manual slash-command validation). --- @@ -110,7 +111,7 @@ in `docs/`. ### Phase Dependencies - **Setup (Phase 1)**: No dependencies — start immediately. T002 depends on T001 (same file). -- **Foundational (Phase 2)**: Depends on Setup. T003–T010 are parallelizable; T011 depends on T003–T010. BLOCKS all user stories (command must be registered to be testable). +- **Foundational (Phase 2)**: Depends on Setup. T003–T010a are parallelizable; T011 depends on T003–T010a. BLOCKS all user stories (command must be registered to be testable). - **User Stories (Phase 3–5)**: All depend on Foundational completion. They edit the same template file, so within the template they are sequential, but each story is independently testable once present. - **Polish (Phase 6)**: Depends on the desired user stories being complete. @@ -127,7 +128,7 @@ in `docs/`. ### Parallel Opportunities -- **Phase 2**: T003, T004, T005, T006, T007, T008, T009, T010 all touch different files → run in parallel; then T011. +- **Phase 2**: T003, T004, T005, T006, T007, T008, T009, T010, T010a all touch different files → run in parallel; then T011. - **Phase 6**: T020 (template), T021 (init.py), T022 (docs) touch different files → run in parallel; then T023/T024/T025. - The template-body tasks (T001–T002, T012–T019) all edit `templates/commands/converge.md` and must be sequential. @@ -145,6 +146,7 @@ Task: T007 Add "converge" to COMMAND_STEMS in tests/integrations/test_integratio Task: T008 Add "converge" to tests/integrations/test_integration_base_markdown.py Task: T009 Add "converge" to tests/integrations/test_integration_base_skills.py Task: T010 Add "converge" to tests/integrations/test_integration_copilot.py +Task: T010a Add "converge" to tests/integrations/test_integration_generic.py # Then: Task: T011 Run the integration + agent-config test suite ``` @@ -156,7 +158,7 @@ Task: T011 Run the integration + agent-config test suite ### MVP First (User Story 1 only) 1. Complete Phase 1: Setup (T001–T002). -2. Complete Phase 2: Foundational registration (T003–T011) — CRITICAL, blocks everything. +2. Complete Phase 2: Foundational registration (T003–T011) — CRITICAL, blocks everything. (Includes T010a.) 3. Complete Phase 3: User Story 1 (T012–T016). 4. **STOP and VALIDATE**: Run quickstart Scenario 1 — confirm a gap becomes a traceable appended task and nothing else changes. This is a usable, shippable increment. diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 186593000..4d412cec8 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -429,6 +429,7 @@ SKILL_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.", + "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.", "constitution": "Create or update project governing principles and development guidelines.", diff --git a/src/specify_cli/commands/init.py b/src/specify_cli/commands/init.py index 8307bb7cf..c25cd2e98 100644 --- a/src/specify_cli/commands/init.py +++ b/src/specify_cli/commands/init.py @@ -578,6 +578,7 @@ def register(app: typer.Typer) -> None: steps_lines.append(f" {step_num}.3 [cyan]{_display_cmd('plan')}[/] - Create implementation plan") steps_lines.append(f" {step_num}.4 [cyan]{_display_cmd('tasks')}[/] - Generate actionable tasks") steps_lines.append(f" {step_num}.5 [cyan]{_display_cmd('implement')}[/] - Execute implementation") + steps_lines.append(f" {step_num}.6 [cyan]{_display_cmd('converge')}[/] - Assess the codebase and append remaining work as tasks") steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1, 2)) console.print() diff --git a/src/specify_cli/extensions.py b/src/specify_cli/extensions.py index db53b7997..00ddcd02d 100644 --- a/src/specify_cli/extensions.py +++ b/src/specify_cli/extensions.py @@ -32,6 +32,7 @@ _FALLBACK_CORE_COMMAND_NAMES = frozenset({ "analyze", "clarify", "constitution", + "converge", "implement", "plan", "checklist", diff --git a/src/specify_cli/integrations/claude/__init__.py b/src/specify_cli/integrations/claude/__init__.py index 57ecb354a..f5238d6ab 100644 --- a/src/specify_cli/integrations/claude/__init__.py +++ b/src/specify_cli/integrations/claude/__init__.py @@ -17,6 +17,7 @@ ARGUMENT_HINTS: dict[str, str] = { "plan": "Optional guidance for the planning phase", "tasks": "Optional task generation constraints", "implement": "Optional implementation guidance or task filter", + "converge": "Optional feature name to converge", "analyze": "Optional focus areas for analysis", "clarify": "Optional areas to clarify in the spec", "constitution": "Principles or values for the project constitution", diff --git a/templates/commands/converge.md b/templates/commands/converge.md new file mode 100644 index 000000000..c96606458 --- /dev/null +++ b/templates/commands/converge.md @@ -0,0 +1,258 @@ +--- +description: Assess the current codebase against the feature's spec, plan, and tasks, then append any remaining unbuilt work as new tasks to tasks.md so implement can complete it. +scripts: + sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks + ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Pre-Execution Checks + +**Check for extension hooks (before convergence)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_converge` 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`): + ``` + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Goal. + ``` +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + +## Goal + +Close the gap between what a feature's specification, plan, and tasks call for and what the +codebase currently implements. Read `spec.md`, `plan.md`, and `tasks.md` as the **sole +source of intent** (with the constitution as governing constraints), assess the current +state of the code, determine which requirements, acceptance criteria, plan decisions, and +existing tasks are unmet, incomplete, or only partially satisfied, and **append each piece +of remaining work as a new, traceable task** at the bottom of `tasks.md` so that +`__SPECKIT_COMMAND_IMPLEMENT__` can complete it. This command MUST run only after +`__SPECKIT_COMMAND_TASKS__` has produced a complete `tasks.md`. + +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. + +## Operating Constraints + +**APPEND-ONLY, NEVER REWRITE**: The command's **only** write is appending a new +`## Phase N — Convergence` section to `tasks.md`. It MUST NOT: + +- modify `spec.md` or `plan.md` in any way; +- rewrite, renumber, reorder, or delete any existing task (including tasks from a prior + Convergence phase); +- modify, create, or delete any application code — completing the appended tasks is the + job of `__SPECKIT_COMMAND_IMPLEMENT__`. + +When the codebase already satisfies everything, the command MUST leave `tasks.md` +**byte-for-byte unchanged** (no empty Convergence header) and report a clean result. + +**Constitution Authority**: The project constitution (`/memory/constitution.md`) is +**non-negotiable**. Code that violates a MUST principle is the highest-severity finding and +produces a corresponding remediation task. If the constitution is an unfilled template, +skip constitution checks gracefully rather than failing. + +## Execution Steps + +### 1. Initialize Convergence Context + +Run `{SCRIPT}` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths: + +- SPEC = FEATURE_DIR/spec.md +- PLAN = FEATURE_DIR/plan.md +- TASKS = FEATURE_DIR/tasks.md +- CONSTITUTION = `.specify/memory/constitution.md` (if present) + +If `plan.md` or `tasks.md` is missing, STOP with a clear, actionable message naming the +prerequisite command to run (`__SPECKIT_COMMAND_PLAN__` for a missing plan, +`__SPECKIT_COMMAND_TASKS__` for missing tasks). Do not produce partial output. +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 (Progressive Disclosure) + +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 + post-launch outcome metrics and business KPIs +- User Stories and their Acceptance Scenarios +- Edge Cases (if present) + +**From plan.md:** + +- Architecture/stack choices and technical decisions +- Data Model references +- Phases and named touch-points (files/components the plan says will be created or edited) +- Technical constraints + +**From tasks.md:** + +- Task IDs (to compute the next ID and next phase number) +- Descriptions, phase grouping, and referenced file paths + +**From constitution (if not an unfilled template):** + +- Principle names and MUST/SHOULD normative statements + +### 3. Build the Intent Inventory + +Create an internal model (do not echo raw artifacts): + +- **Requirements inventory**: one stable key per FR-### / SC-### / user-story acceptance + scenario (e.g. `US1/AC2`), plus the plan decisions and constitution principles that + impose buildable obligations. +- **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 + scope beyond what the artifacts define (FR-001). + +### 4. Assess the Codebase and Classify Findings + +For each item in the intent inventory, inspect the current code in scope and produce a +`Finding` only where there is a gap. Classify every finding by **gap type**: + +- **`missing`**: the required work is absent from the code entirely. +- **`partial`**: the work exists but does not yet fully satisfy the requirement / + acceptance criterion / plan decision. +- **`contradicts`**: the code does something that conflicts with stated intent or a + constitution MUST principle. +- **`unrequested`**: the code contains work not called for by the spec, plan, or tasks + (surfaced for awareness — converge does **not** delete code, it only appends a task to + review/justify or remove it). + +Each `Finding` records: a stable id, the `source-ref` it traces to, the `gap-type`, a +severity, and a short human-readable description with the evidence (the file/area observed). + +**Edge cases:** + +- **Little or no code yet**: treat the entire specified scope as `missing` remaining work + rather than failing. +- **Nothing remains**: produce zero findings and follow the converged branch in Step 7. + +### 5. Assign Severity + +- **CRITICAL**: violates a constitution MUST principle, or a `missing`/`contradicts` gap + that blocks baseline functionality of a P1 user story. +- **HIGH**: a `missing` or `partial` gap on a core functional requirement or acceptance + criterion. +- **MEDIUM**: a `partial` gap on a secondary requirement, or an `unrequested` addition with + unclear justification. +- **LOW**: minor partial gaps, polish, or low-risk `unrequested` additions. + +### 6. Present the In-Session Findings Summary + +Before appending anything, output a compact, severity-graded summary (no file writes yet): + +## Convergence Findings + +| ID | Gap Type | Severity | Source | Evidence | Remaining Work | +|----|----------|----------|--------|----------|----------------| +| F1 | missing | HIGH | FR-008 | converge template has no guardrail text | Add append-only enforcement | + +**Summary metrics:** + +- Requirements / acceptance criteria checked +- Plan decisions checked +- Constitution principles checked (or "skipped — template") +- Findings by gap type (missing / partial / contradicts / unrequested) +- Findings by severity + +### 7. Append Convergence Tasks (or report converged) + +**If there are one or more actionable findings** (`tasks_appended` outcome): + +Append to the **end** of `tasks.md`, per the append contract: + +1. Scan all existing task IDs; let `M` be the maximum. Determine the next phase number `N` + (highest existing phase + 1). +2. Write a single new section header `## Phase N — Convergence`. +3. Emit one checklist item per actionable finding, ordered CRITICAL/HIGH first, assigning + IDs `M+1, M+2, …`: + + ```markdown + - [ ] T: per () + ``` + + - `` traces the task to its origin: e.g. `FR-003`, `SC-002`, `US1/AC2`, + `plan: storage decision`, `Constitution II`. + - `` is one of `missing`, `partial`, `contradicts`, `unrequested`. + - Constitution-violation tasks MUST be emitted first and described as `CRITICAL`. +4. Never reuse or renumber existing IDs. If a prior Convergence phase exists, add a new, + separately-numbered one below it — do not touch the old one. + +**If there are no actionable findings** (`converged` outcome): + +- Do **not** modify `tasks.md` at all — no empty phase header. +- Report: **"✅ Converged — the implementation satisfies the spec, plan, and tasks."** +- Include the summary counts of what was checked. + +### 8. Provide Next Actions (Handoff) + +- On `tasks_appended`: state how many tasks were appended under which phase, and recommend + running `__SPECKIT_COMMAND_IMPLEMENT__` to complete them; note that a follow-up converge + run will find fewer or no remaining items. +- On `converged`: recommend proceeding to review / opening a PR. No further implement pass + is needed for this feature's specified scope. + +### 9. Check for extension hooks + +After producing the result, check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.after_converge` 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 +- Pass the convergence outcome (`converged` or `tasks_appended`) to the hook context so an + extension can branch on it. +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + ``` +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/tests/integrations/test_integration_base_markdown.py b/tests/integrations/test_integration_base_markdown.py index 19b52167a..b0b408a99 100644 --- a/tests/integrations/test_integration_base_markdown.py +++ b/tests/integrations/test_integration_base_markdown.py @@ -254,7 +254,7 @@ class MarkdownIntegrationTests: COMMAND_STEMS = [ "agent-context.update", - "analyze", "clarify", "constitution", "implement", + "analyze", "clarify", "constitution", "converge", "implement", "plan", "checklist", "specify", "tasks", "taskstoissues", ] diff --git a/tests/integrations/test_integration_base_skills.py b/tests/integrations/test_integration_base_skills.py index 8a3f9d0f3..e903d918e 100644 --- a/tests/integrations/test_integration_base_skills.py +++ b/tests/integrations/test_integration_base_skills.py @@ -100,7 +100,7 @@ class SkillsIntegrationTests: skill_files = [f for f in created if "scripts" not in f.parts] expected_commands = { - "analyze", "clarify", "constitution", "implement", + "analyze", "clarify", "constitution", "converge", "implement", "plan", "checklist", "specify", "tasks", "taskstoissues", } @@ -393,7 +393,7 @@ class SkillsIntegrationTests: # -- Complete file inventory ------------------------------------------ _SKILL_COMMANDS = [ - "analyze", "clarify", "constitution", "implement", + "analyze", "clarify", "constitution", "converge", "implement", "plan", "checklist", "specify", "tasks", "taskstoissues", ] diff --git a/tests/integrations/test_integration_base_toml.py b/tests/integrations/test_integration_base_toml.py index 37f6966e3..a9b933875 100644 --- a/tests/integrations/test_integration_base_toml.py +++ b/tests/integrations/test_integration_base_toml.py @@ -486,6 +486,7 @@ class TomlIntegrationTests: "analyze", "clarify", "constitution", + "converge", "implement", "plan", "checklist", diff --git a/tests/integrations/test_integration_base_yaml.py b/tests/integrations/test_integration_base_yaml.py index 7814844c5..646e21607 100644 --- a/tests/integrations/test_integration_base_yaml.py +++ b/tests/integrations/test_integration_base_yaml.py @@ -365,6 +365,7 @@ class YamlIntegrationTests: "analyze", "clarify", "constitution", + "converge", "implement", "plan", "checklist", diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index d5b3c1dee..6b7cc7c13 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -125,9 +125,9 @@ class TestCopilotIntegration: agents_dir = tmp_path / ".github" / "agents" assert agents_dir.is_dir() agent_files = sorted(agents_dir.glob("speckit.*.agent.md")) - assert len(agent_files) == 9 + assert len(agent_files) == 10 expected_commands = { - "analyze", "clarify", "constitution", "implement", + "analyze", "clarify", "constitution", "converge", "implement", "plan", "checklist", "specify", "tasks", "taskstoissues", } actual_commands = {f.name.removeprefix("speckit.").removesuffix(".agent.md") for f in agent_files} @@ -198,6 +198,7 @@ class TestCopilotIntegration: ".github/agents/speckit.checklist.agent.md", ".github/agents/speckit.clarify.agent.md", ".github/agents/speckit.constitution.agent.md", + ".github/agents/speckit.converge.agent.md", ".github/agents/speckit.implement.agent.md", ".github/agents/speckit.plan.agent.md", ".github/agents/speckit.specify.agent.md", @@ -208,6 +209,7 @@ class TestCopilotIntegration: ".github/prompts/speckit.checklist.prompt.md", ".github/prompts/speckit.clarify.prompt.md", ".github/prompts/speckit.constitution.prompt.md", + ".github/prompts/speckit.converge.prompt.md", ".github/prompts/speckit.implement.prompt.md", ".github/prompts/speckit.plan.prompt.md", ".github/prompts/speckit.specify.prompt.md", @@ -268,6 +270,7 @@ class TestCopilotIntegration: ".github/agents/speckit.checklist.agent.md", ".github/agents/speckit.clarify.agent.md", ".github/agents/speckit.constitution.agent.md", + ".github/agents/speckit.converge.agent.md", ".github/agents/speckit.implement.agent.md", ".github/agents/speckit.plan.agent.md", ".github/agents/speckit.specify.agent.md", @@ -278,6 +281,7 @@ class TestCopilotIntegration: ".github/prompts/speckit.checklist.prompt.md", ".github/prompts/speckit.clarify.prompt.md", ".github/prompts/speckit.constitution.prompt.md", + ".github/prompts/speckit.converge.prompt.md", ".github/prompts/speckit.implement.prompt.md", ".github/prompts/speckit.plan.prompt.md", ".github/prompts/speckit.specify.prompt.md", @@ -321,7 +325,7 @@ class TestCopilotSkillsMode: """Tests for Copilot integration in --skills mode.""" _SKILL_COMMANDS = [ - "analyze", "clarify", "constitution", "implement", + "analyze", "clarify", "constitution", "converge", "implement", "plan", "checklist", "specify", "tasks", "taskstoissues", ] diff --git a/tests/integrations/test_integration_generic.py b/tests/integrations/test_integration_generic.py index b7c64cdf6..fe935cc98 100644 --- a/tests/integrations/test_integration_generic.py +++ b/tests/integrations/test_integration_generic.py @@ -214,6 +214,7 @@ class TestGenericIntegration: [ "analyze", "clarify", + "converge", "implement", "plan", "checklist", @@ -306,6 +307,7 @@ class TestGenericIntegration: ".myagent/commands/speckit.checklist.md", ".myagent/commands/speckit.clarify.md", ".myagent/commands/speckit.constitution.md", + ".myagent/commands/speckit.converge.md", ".myagent/commands/speckit.implement.md", ".myagent/commands/speckit.plan.md", ".myagent/commands/speckit.specify.md", @@ -370,6 +372,7 @@ class TestGenericIntegration: ".myagent/commands/speckit.checklist.md", ".myagent/commands/speckit.clarify.md", ".myagent/commands/speckit.constitution.md", + ".myagent/commands/speckit.converge.md", ".myagent/commands/speckit.implement.md", ".myagent/commands/speckit.plan.md", ".myagent/commands/speckit.specify.md",