From 97f8d8e5e93409503ded433c0630f0c935538ef8 Mon Sep 17 00:00:00 2001 From: Mirko Sacripanti Date: Tue, 14 Apr 2026 10:45:38 +0200 Subject: [PATCH] Add status command template --- README.md | 1 + src/specify_cli/__init__.py | 2 + .../integrations/claude/__init__.py | 1 + templates/commands/status.md | 201 ++++++++++++++++++ .../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 | 8 +- .../integrations/test_integration_generic.py | 2 + 10 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 templates/commands/status.md diff --git a/README.md b/README.md index c729fe02f1..04d2efb3ed 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,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.status` | `speckit-status` | Summarize workflow status across specs, including artifacts, task progress, checklists, and next actions | | `/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/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 0bbf42ad5a..fb9d88d8c1 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -863,6 +863,7 @@ def _get_skills_dir(project_path: Path, selected_ai: str) -> Path: "tasks": "Break down implementation plans into actionable task lists.", "implement": "Execute all tasks from the task breakdown to build the feature.", "analyze": "Perform cross-artifact consistency analysis across spec.md, plan.md, and tasks.md.", + "status": "Summarize workflow status across all specs.", "clarify": "Structured clarification workflow for underspecified requirements.", "constitution": "Create or update project governing principles and development guidelines.", "checklist": "Generate custom quality checklists for validating requirements completeness and clarity.", @@ -1436,6 +1437,7 @@ def _display_cmd(name: str) -> str: enhancement_intro, "", 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('status')}[/] [bright_black](optional)[/bright_black] - Summarize workflow status across specs, including artifacts, tasks, checklists, and next actions", 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')}[/])" ] diff --git a/src/specify_cli/integrations/claude/__init__.py b/src/specify_cli/integrations/claude/__init__.py index 31972c4b0e..a6948b68a3 100644 --- a/src/specify_cli/integrations/claude/__init__.py +++ b/src/specify_cli/integrations/claude/__init__.py @@ -18,6 +18,7 @@ "tasks": "Optional task generation constraints", "implement": "Optional implementation guidance or task filter", "analyze": "Optional focus areas for analysis", + "status": "Optional feature filter or status scope", "clarify": "Optional areas to clarify in the spec", "constitution": "Principles or values for the project constitution", "checklist": "Domain or focus area for the checklist", diff --git a/templates/commands/status.md b/templates/commands/status.md new file mode 100644 index 0000000000..8a3a77f220 --- /dev/null +++ b/templates/commands/status.md @@ -0,0 +1,201 @@ +--- +description: Summarize Spec Kit workflow status across all specs without modifying files. +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Pre-Execution Checks + +**Check for extension hooks (before status reporting)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_status` 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 + +Produce a read-only status report for every Spec Kit feature under `specs/`, including artifact availability, task progress, checklist progress, inferred workflow phase, and the next suggested command. + +## Operating Constraints + +**STRICTLY READ-ONLY**: Do **not** modify any files. Do not create, update, or delete specs, plans, tasks, checklists, branches, or configuration. + +**Repository Scope**: Work from the repository root. Prefer the current working directory if it contains `.specify/` or `specs/`; otherwise, walk upward until a directory with `.specify/` or `specs/` is found. If neither is found, report that this does not appear to be a Spec Kit project. + +**Input Handling**: If the user input is empty, report all specs. If the user input names a feature id, feature directory, branch, or substring, filter the report to matching spec directories and state the filter used. + +## Execution Steps + +### 1. Discover Project and Specs + +- Locate the project root. +- Locate `specs/` under the project root. +- Enumerate direct child directories under `specs/`. +- Treat each child directory as one feature spec. Prefer directories whose names look like `NNN-feature-name`, but do not hide other directories if they contain Spec Kit artifacts. +- Sort features by numeric prefix when present, then by directory name. +- If `specs/` does not exist or no feature directories are found, output a concise empty-state report and stop. + +### 2. Identify the Active Feature + +- Try to detect the current git branch with `git branch --show-current`. +- If git is unavailable, the command fails, or the project is not a git repository, continue without an active feature marker. +- Mark a feature as active when its directory name exactly matches the current branch, or when the branch name ends with the feature directory name. + +### 3. Inspect Artifacts + +For each feature directory, inspect only file existence and compact metadata: + +- Required workflow artifacts: + - `spec.md` + - `plan.md` + - `tasks.md` +- Optional design artifacts: + - `research.md` + - `data-model.md` + - `contracts/` with at least one file + - `quickstart.md` + - `checklists/` with at least one `.md` file + +Do not load full artifact contents. For `tasks.md` and checklist files, read only enough to count checklist item states. + +### 4. Calculate Progress + +For each `tasks.md`, count task items matching: + +- Complete: lines that begin with `- [x]` or `- [X]` +- Incomplete: lines that begin with `- [ ]` + +For each checklist file under `checklists/`, count checklist items with the same checkbox pattern. + +Derive task progress: + +- `n/a` when `tasks.md` is missing +- `0/0` when `tasks.md` exists but has no checkbox items +- `{completed}/{total}` otherwise + +Derive checklist status: + +- `n/a` when no checklist files exist +- `PASS {completed}/{total}` when all checklist items are complete and total is greater than zero +- `FAIL {completed}/{total}` when at least one checklist item is incomplete +- `0/0` when checklist files exist but contain no checkbox items + +### 5. Infer Workflow Phase + +Use the following precedence: + +- `Missing spec` when `spec.md` is absent +- `Specified` when `spec.md` exists and `plan.md` is absent +- `Planned` when `plan.md` exists and `tasks.md` is absent +- `Ready` when `tasks.md` exists and has task checkboxes but none are complete +- `In progress` when some, but not all, task checkboxes are complete +- `Complete` when all task checkboxes are complete and total is greater than zero +- `Tasked` when `tasks.md` exists but has no task checkboxes + +### 6. Suggest Next Action + +Use this default mapping, adjusting only when the inspected artifacts clearly indicate a better next action: + +- `Missing spec` -> `/speckit.specify` +- `Specified` -> `/speckit.plan` +- `Planned` -> `/speckit.tasks` +- `Ready` -> `/speckit.implement` +- `In progress` -> `/speckit.implement` +- `Tasked` -> Review `tasks.md` formatting +- `Complete` -> Review or ship + +If checklist status is `FAIL`, add `complete checklist items` to the next action. + +### 7. Produce the Status Report + +Output a Markdown report with this structure: + +```markdown +## Spec Kit Status + +| Active | Feature | Phase | Artifacts | Tasks | Checklists | Next | +|--------|---------|-------|-----------|-------|------------|------| +| * | 001-example | In progress | spec, plan, research, tasks | 12/20 | PASS 8/8 | /speckit.implement | + +**Summary** + +- Total specs: N +- Active feature: FEATURE_NAME or n/a +- Complete: N +- In progress: N +- Ready for implementation: N +- Missing next artifact: N +``` + +Artifact display rules: + +- List present artifacts by short names: `spec`, `plan`, `research`, `data-model`, `contracts`, `quickstart`, `tasks`, `checklists` +- If an expected workflow artifact is missing, include `missing: spec`, `missing: plan`, or `missing: tasks` +- Keep each row compact; do not include raw artifact content + +### 8. Check for Extension Hooks + +After reporting, check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.after_status` 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 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 + +## Context + +{ARGS} diff --git a/tests/integrations/test_integration_base_markdown.py b/tests/integrations/test_integration_base_markdown.py index e274b52242..2fe83c6090 100644 --- a/tests/integrations/test_integration_base_markdown.py +++ b/tests/integrations/test_integration_base_markdown.py @@ -207,7 +207,7 @@ def test_integration_flag_creates_files(self, tmp_path): COMMAND_STEMS = [ "analyze", "checklist", "clarify", "constitution", - "implement", "plan", "specify", "tasks", "taskstoissues", + "implement", "plan", "specify", "status", "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 007386611c..7d6b086b84 100644 --- a/tests/integrations/test_integration_base_skills.py +++ b/tests/integrations/test_integration_base_skills.py @@ -101,7 +101,7 @@ def test_skill_directory_structure(self, tmp_path): expected_commands = { "analyze", "checklist", "clarify", "constitution", - "implement", "plan", "specify", "tasks", "taskstoissues", + "implement", "plan", "specify", "status", "tasks", "taskstoissues", } # Derive command names from the skill directory names @@ -299,7 +299,7 @@ def test_options_include_skills_flag(self): _SKILL_COMMANDS = [ "analyze", "checklist", "clarify", "constitution", - "implement", "plan", "specify", "tasks", "taskstoissues", + "implement", "plan", "specify", "status", "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 4d0bfe2cfe..d4100fb022 100644 --- a/tests/integrations/test_integration_base_toml.py +++ b/tests/integrations/test_integration_base_toml.py @@ -451,6 +451,7 @@ def test_integration_flag_creates_files(self, tmp_path): "implement", "plan", "specify", + "status", "tasks", "taskstoissues", ] diff --git a/tests/integrations/test_integration_base_yaml.py b/tests/integrations/test_integration_base_yaml.py index b0f59a627d..ecc56632ad 100644 --- a/tests/integrations/test_integration_base_yaml.py +++ b/tests/integrations/test_integration_base_yaml.py @@ -330,6 +330,7 @@ def test_integration_flag_creates_files(self, tmp_path): "implement", "plan", "specify", + "status", "tasks", "taskstoissues", ] diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index 5db0155bdb..0ec4888f8a 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -123,10 +123,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) == 9 + assert len(agent_files) == 10 expected_commands = { "analyze", "checklist", "clarify", "constitution", - "implement", "plan", "specify", "tasks", "taskstoissues", + "implement", "plan", "specify", "status", "tasks", "taskstoissues", } actual_commands = {f.name.removeprefix("speckit.").removesuffix(".agent.md") for f in agent_files} assert actual_commands == expected_commands @@ -169,6 +169,7 @@ def test_complete_file_inventory_sh(self, tmp_path): ".github/agents/speckit.implement.agent.md", ".github/agents/speckit.plan.agent.md", ".github/agents/speckit.specify.agent.md", + ".github/agents/speckit.status.agent.md", ".github/agents/speckit.tasks.agent.md", ".github/agents/speckit.taskstoissues.agent.md", ".github/prompts/speckit.analyze.prompt.md", @@ -178,6 +179,7 @@ def test_complete_file_inventory_sh(self, tmp_path): ".github/prompts/speckit.implement.prompt.md", ".github/prompts/speckit.plan.prompt.md", ".github/prompts/speckit.specify.prompt.md", + ".github/prompts/speckit.status.prompt.md", ".github/prompts/speckit.tasks.prompt.md", ".github/prompts/speckit.taskstoissues.prompt.md", ".vscode/settings.json", @@ -229,6 +231,7 @@ def test_complete_file_inventory_ps(self, tmp_path): ".github/agents/speckit.implement.agent.md", ".github/agents/speckit.plan.agent.md", ".github/agents/speckit.specify.agent.md", + ".github/agents/speckit.status.agent.md", ".github/agents/speckit.tasks.agent.md", ".github/agents/speckit.taskstoissues.agent.md", ".github/prompts/speckit.analyze.prompt.md", @@ -238,6 +241,7 @@ def test_complete_file_inventory_ps(self, tmp_path): ".github/prompts/speckit.implement.prompt.md", ".github/prompts/speckit.plan.prompt.md", ".github/prompts/speckit.specify.prompt.md", + ".github/prompts/speckit.status.prompt.md", ".github/prompts/speckit.tasks.prompt.md", ".github/prompts/speckit.taskstoissues.prompt.md", ".vscode/settings.json", diff --git a/tests/integrations/test_integration_generic.py b/tests/integrations/test_integration_generic.py index 2815456f21..0f3642be2a 100644 --- a/tests/integrations/test_integration_generic.py +++ b/tests/integrations/test_integration_generic.py @@ -228,6 +228,7 @@ def test_complete_file_inventory_sh(self, tmp_path): ".myagent/commands/speckit.implement.md", ".myagent/commands/speckit.plan.md", ".myagent/commands/speckit.specify.md", + ".myagent/commands/speckit.status.md", ".myagent/commands/speckit.tasks.md", ".myagent/commands/speckit.taskstoissues.md", ".specify/init-options.json", @@ -284,6 +285,7 @@ def test_complete_file_inventory_ps(self, tmp_path): ".myagent/commands/speckit.implement.md", ".myagent/commands/speckit.plan.md", ".myagent/commands/speckit.specify.md", + ".myagent/commands/speckit.status.md", ".myagent/commands/speckit.tasks.md", ".myagent/commands/speckit.taskstoissues.md", ".specify/init-options.json",