diff --git a/README.md b/README.md index 76149512f6..b451f79f6d 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ Additional commands for enhanced quality and validation: | Command | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `/speckit.status` | Display project status, feature progress, and recommended next actions (use anytime to see where you are) | | `/speckit.clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) | | `/speckit.analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) | | `/speckit.checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") | @@ -447,6 +448,7 @@ At this stage, your project folder contents should resemble the following: │ ├── check-prerequisites.sh │ ├── common.sh │ ├── create-new-feature.sh + │ ├── get-project-status.sh │ ├── setup-plan.sh │ └── update-claude-md.sh ├── specs @@ -508,6 +510,7 @@ The output of this step will include a number of implementation detail documents │ ├── check-prerequisites.sh │ ├── common.sh │ ├── create-new-feature.sh +│ ├── get-project-status.sh │ ├── setup-plan.sh │ └── update-claude-md.sh ├── specs diff --git a/scripts/bash/get-project-status.sh b/scripts/bash/get-project-status.sh new file mode 100755 index 0000000000..1d1ce7a48e --- /dev/null +++ b/scripts/bash/get-project-status.sh @@ -0,0 +1,359 @@ +#!/usr/bin/env bash + +# Project status discovery script for /speckit.status command +# +# This script discovers project structure and artifact existence. +# It does NOT parse file contents - that's left to the AI agent. +# +# Usage: ./get-project-status.sh [OPTIONS] +# +# OPTIONS: +# --json Output in JSON format (default: text) +# --feature Focus on specific feature (name, number, or path) +# --help, -h Show help message +# +# OUTPUTS: +# JSON mode: Full project status object +# Text mode: Human-readable status lines + +set -e + +# Parse command line arguments +JSON_MODE=false +TARGET_FEATURE="" + +while [ $# -gt 0 ]; do + case "$1" in + --json) + JSON_MODE=true + shift + ;; + --feature) + if [ -z "$2" ] || [[ "$2" == --* ]]; then + echo "Error: --feature requires a value" >&2 + exit 1 + fi + TARGET_FEATURE="$2" + shift 2 + ;; + --help|-h) + cat << 'EOF' +Usage: get-project-status.sh [OPTIONS] + +Discover project structure and artifact existence for /speckit.status. + +OPTIONS: + --json Output in JSON format (default: text) + --feature Focus on specific feature (by name, number prefix, or path) + --help, -h Show this help message + +EXAMPLES: + # Get full project status in JSON + ./get-project-status.sh --json + + # Get status for specific feature + ./get-project-status.sh --json --feature 002-dashboard + + # Get status by feature number + ./get-project-status.sh --json --feature 002 + +EOF + exit 0 + ;; + *) + # Treat positional arg as feature identifier + if [ -z "$TARGET_FEATURE" ]; then + TARGET_FEATURE="$1" + fi + shift + ;; + esac +done + +# Function to find repository root +find_repo_root() { + local dir="$1" + while [ "$dir" != "/" ]; do + if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} + +# Function to get project name from directory or package.json +get_project_name() { + local repo_root="$1" + + # Try package.json first + if [ -f "$repo_root/package.json" ]; then + local name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$repo_root/package.json" 2>/dev/null | head -1 | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') + if [ -n "$name" ]; then + echo "$name" + return + fi + fi + + # Try pyproject.toml + if [ -f "$repo_root/pyproject.toml" ]; then + local name=$(grep -E '^name\s*=' "$repo_root/pyproject.toml" 2>/dev/null | head -1 | sed 's/^name[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/') + if [ -n "$name" ] && [ "$name" != "$(grep -E '^name\s*=' "$repo_root/pyproject.toml" 2>/dev/null | head -1)" ]; then + echo "$name" + return + fi + fi + + # Fall back to directory name + basename "$repo_root" +} + +# Function to check if path/file exists and is non-empty (for directories) +check_exists() { + local path="$1" + if [ -f "$path" ]; then + echo "true" + elif [ -d "$path" ] && [ -n "$(ls -A "$path" 2>/dev/null)" ]; then + echo "true" + else + echo "false" + fi +} + +# Function to list files in a directory (for checklists) +list_files() { + local dir="$1" + local extension="$2" + + if [ -d "$dir" ]; then + find "$dir" -maxdepth 1 -name "*$extension" -type f -exec basename {} \; 2>/dev/null | sort + fi +} + +# Function to escape string for JSON +json_escape() { + local str="$1" + # Escape backslashes, quotes, and control characters + printf '%s' "$str" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g' | tr -d '\n' +} + +# Resolve repository root +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if git rev-parse --show-toplevel >/dev/null 2>&1; then + REPO_ROOT=$(git rev-parse --show-toplevel) + HAS_GIT=true + CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") +else + REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")" + if [ -z "$REPO_ROOT" ]; then + echo "Error: Could not determine repository root." >&2 + exit 1 + fi + HAS_GIT=false + CURRENT_BRANCH="" +fi + +# Determine specs directory (.specify/specs or specs/) +if [ -d "$REPO_ROOT/.specify/specs" ]; then + SPECS_DIR="$REPO_ROOT/.specify/specs" +elif [ -d "$REPO_ROOT/specs" ]; then + SPECS_DIR="$REPO_ROOT/specs" +else + SPECS_DIR="$REPO_ROOT/.specify/specs" # Default even if doesn't exist +fi + +# Determine memory directory (.specify/memory or memory/) +if [ -d "$REPO_ROOT/.specify/memory" ]; then + MEMORY_DIR="$REPO_ROOT/.specify/memory" +elif [ -d "$REPO_ROOT/memory" ]; then + MEMORY_DIR="$REPO_ROOT/memory" +else + MEMORY_DIR="$REPO_ROOT/.specify/memory" # Default even if doesn't exist +fi + +# Check constitution +CONSTITUTION_PATH="$MEMORY_DIR/constitution.md" +CONSTITUTION_EXISTS=$(check_exists "$CONSTITUTION_PATH") + +# Get project name +PROJECT_NAME=$(get_project_name "$REPO_ROOT") + +# Check if on feature branch (matches NNN-* pattern) +IS_FEATURE_BRANCH=false +if [[ "$CURRENT_BRANCH" =~ ^[0-9]{3}- ]]; then + IS_FEATURE_BRANCH=true +fi + +# Collect all features +declare -a FEATURES=() +if [ -d "$SPECS_DIR" ]; then + for dir in "$SPECS_DIR"/[0-9][0-9][0-9]-*; do + if [ -d "$dir" ]; then + FEATURES+=("$(basename "$dir")") + fi + done +fi + +# Sort features by number +IFS=$'\n' FEATURES=($(sort <<<"${FEATURES[*]}")); unset IFS + +# Function to get feature info +get_feature_info() { + local feature_name="$1" + local feature_dir="$SPECS_DIR/$feature_name" + + local has_spec=$(check_exists "$feature_dir/spec.md") + local has_plan=$(check_exists "$feature_dir/plan.md") + local has_tasks=$(check_exists "$feature_dir/tasks.md") + local has_research=$(check_exists "$feature_dir/research.md") + local has_data_model=$(check_exists "$feature_dir/data-model.md") + local has_quickstart=$(check_exists "$feature_dir/quickstart.md") + local has_contracts=$(check_exists "$feature_dir/contracts") + local has_checklists=$(check_exists "$feature_dir/checklists") + + # Get checklist files if they exist + local checklist_files="" + if [ "$has_checklists" = "true" ]; then + checklist_files=$(list_files "$feature_dir/checklists" ".md" | tr '\n' ',' | sed 's/,$//') + fi + + # Determine if this is the current feature + local is_current=false + if [ "$IS_FEATURE_BRANCH" = "true" ]; then + # Extract prefix from current branch + local current_prefix=$(echo "$CURRENT_BRANCH" | grep -o '^[0-9]\{3\}') + local feature_prefix=$(echo "$feature_name" | grep -o '^[0-9]\{3\}') + if [ "$current_prefix" = "$feature_prefix" ]; then + is_current=true + fi + fi + + if $JSON_MODE; then + printf '{"name":"%s","path":"%s","is_current":%s,"has_spec":%s,"has_plan":%s,"has_tasks":%s,"has_research":%s,"has_data_model":%s,"has_quickstart":%s,"has_contracts":%s,"has_checklists":%s,"checklist_files":[%s]}' \ + "$(json_escape "$feature_name")" \ + "$(json_escape "$feature_dir")" \ + "$is_current" \ + "$has_spec" \ + "$has_plan" \ + "$has_tasks" \ + "$has_research" \ + "$has_data_model" \ + "$has_quickstart" \ + "$has_contracts" \ + "$has_checklists" \ + "$(echo "$checklist_files" | sed 's/\([^,]*\)/"\1"/g')" + else + echo " Name: $feature_name" + echo " Path: $feature_dir" + echo " Current: $is_current" + echo " Artifacts:" + echo " spec.md: $has_spec" + echo " plan.md: $has_plan" + echo " tasks.md: $has_tasks" + echo " research.md: $has_research" + echo " data-model.md: $has_data_model" + echo " quickstart.md: $has_quickstart" + echo " contracts/: $has_contracts" + echo " checklists/: $has_checklists" + if [ -n "$checklist_files" ]; then + echo " checklist_files: $checklist_files" + fi + echo "" + fi +} + +# Resolve target feature if specified +RESOLVED_TARGET="" +if [ -n "$TARGET_FEATURE" ]; then + # Try exact match first + if [ -d "$SPECS_DIR/$TARGET_FEATURE" ]; then + RESOLVED_TARGET="$TARGET_FEATURE" + # Try as path + elif [ -d "$TARGET_FEATURE" ]; then + RESOLVED_TARGET=$(basename "$TARGET_FEATURE") + # Try as number prefix + elif [[ "$TARGET_FEATURE" =~ ^[0-9]+$ ]]; then + PREFIX=$(printf "%03d" "$TARGET_FEATURE") + for f in "${FEATURES[@]}"; do + if [[ "$f" == "$PREFIX"-* ]]; then + RESOLVED_TARGET="$f" + break + fi + done + # Try partial match + else + for f in "${FEATURES[@]}"; do + if [[ "$f" == *"$TARGET_FEATURE"* ]]; then + RESOLVED_TARGET="$f" + break + fi + done + fi + + if [ -z "$RESOLVED_TARGET" ]; then + echo "Error: Feature not found: $TARGET_FEATURE" >&2 + exit 1 + fi +fi + +# Output results +if $JSON_MODE; then + # Build features array + features_json="" + for feature in "${FEATURES[@]}"; do + if [ -n "$features_json" ]; then + features_json="$features_json," + fi + features_json="$features_json$(get_feature_info "$feature")" + done + + # Build main JSON object + printf '{' + printf '"project":"%s",' "$(json_escape "$PROJECT_NAME")" + printf '"repo_root":"%s",' "$(json_escape "$REPO_ROOT")" + printf '"specs_dir":"%s",' "$(json_escape "$SPECS_DIR")" + printf '"has_git":%s,' "$HAS_GIT" + printf '"branch":"%s",' "$(json_escape "$CURRENT_BRANCH")" + printf '"is_feature_branch":%s,' "$IS_FEATURE_BRANCH" + printf '"constitution":{"exists":%s,"path":"%s"},' "$CONSTITUTION_EXISTS" "$(json_escape "$CONSTITUTION_PATH")" + printf '"feature_count":%d,' "${#FEATURES[@]}" + + if [ -n "$RESOLVED_TARGET" ]; then + printf '"target_feature":"%s",' "$(json_escape "$RESOLVED_TARGET")" + else + printf '"target_feature":null,' + fi + + printf '"features":[%s]' "$features_json" + printf '}\n' +else + echo "Project Status Discovery" + echo "========================" + echo "" + echo "Project: $PROJECT_NAME" + echo "Root: $REPO_ROOT" + echo "Specs: $SPECS_DIR" + echo "Git: $HAS_GIT" + echo "Branch: $CURRENT_BRANCH" + echo "Feature Branch: $IS_FEATURE_BRANCH" + echo "Constitution: $CONSTITUTION_EXISTS ($CONSTITUTION_PATH)" + echo "" + + if [ -n "$RESOLVED_TARGET" ]; then + echo "Target Feature: $RESOLVED_TARGET" + echo "" + fi + + echo "Features (${#FEATURES[@]}):" + echo "" + + if [ ${#FEATURES[@]} -eq 0 ]; then + echo " (none)" + else + for feature in "${FEATURES[@]}"; do + get_feature_info "$feature" + done + fi +fi diff --git a/scripts/powershell/Get-ProjectStatus.ps1 b/scripts/powershell/Get-ProjectStatus.ps1 new file mode 100644 index 0000000000..43c6adefa9 --- /dev/null +++ b/scripts/powershell/Get-ProjectStatus.ps1 @@ -0,0 +1,305 @@ +#!/usr/bin/env pwsh + +# Project status discovery script for /speckit.status command +# +# This script discovers project structure and artifact existence. +# It does NOT parse file contents - that's left to the AI agent. +# +# Usage: ./Get-ProjectStatus.ps1 [OPTIONS] +# +# OPTIONS: +# -Json Output in JSON format (default: text) +# -Feature Focus on specific feature (name, number, or path) +# -Help Show help message + +[CmdletBinding()] +param( + [switch]$Json, + [string]$Feature, + [switch]$Help +) + +$ErrorActionPreference = 'Stop' + +# Show help if requested +if ($Help) { + Write-Output @" +Usage: Get-ProjectStatus.ps1 [OPTIONS] + +Discover project structure and artifact existence for /speckit.status. + +OPTIONS: + -Json Output in JSON format (default: text) + -Feature Focus on specific feature (by name, number prefix, or path) + -Help Show this help message + +EXAMPLES: + # Get full project status in JSON + .\Get-ProjectStatus.ps1 -Json + + # Get status for specific feature + .\Get-ProjectStatus.ps1 -Json -Feature 002-dashboard + + # Get status by feature number + .\Get-ProjectStatus.ps1 -Json -Feature 002 + +"@ + exit 0 +} + +# Function to find repository root +function Find-RepoRoot { + param([string]$StartPath) + + $dir = $StartPath + while ($dir -and $dir -ne [System.IO.Path]::GetPathRoot($dir)) { + if ((Test-Path (Join-Path $dir ".git")) -or (Test-Path (Join-Path $dir ".specify"))) { + return $dir + } + $dir = Split-Path $dir -Parent + } + return $null +} + +# Function to get project name +function Get-ProjectName { + param([string]$RepoRoot) + + # Try package.json first + $packageJson = Join-Path $RepoRoot "package.json" + if (Test-Path $packageJson) { + try { + $pkg = Get-Content $packageJson -Raw | ConvertFrom-Json + if ($pkg.name) { + return $pkg.name + } + } catch { + # Ignore parse errors + } + } + + # Try pyproject.toml + $pyproject = Join-Path $RepoRoot "pyproject.toml" + if (Test-Path $pyproject) { + $content = Get-Content $pyproject -Raw + if ($content -match 'name\s*=\s*"([^"]+)"') { + return $matches[1] + } + } + + # Fall back to directory name + return Split-Path $RepoRoot -Leaf +} + +# Function to check if path exists (file or non-empty directory) +function Test-Exists { + param([string]$Path) + + if (Test-Path $Path -PathType Leaf) { + return $true + } + if ((Test-Path $Path -PathType Container) -and (Get-ChildItem $Path -ErrorAction SilentlyContinue | Select-Object -First 1)) { + return $true + } + return $false +} + +# Function to get feature info +function Get-FeatureInfo { + param( + [string]$FeatureName, + [string]$SpecsDir, + [string]$CurrentBranch, + [bool]$IsFeatureBranch + ) + + $featureDir = Join-Path $SpecsDir $FeatureName + + $info = [ordered]@{ + name = $FeatureName + path = $featureDir + is_current = $false + has_spec = Test-Exists (Join-Path $featureDir "spec.md") + has_plan = Test-Exists (Join-Path $featureDir "plan.md") + has_tasks = Test-Exists (Join-Path $featureDir "tasks.md") + has_research = Test-Exists (Join-Path $featureDir "research.md") + has_data_model = Test-Exists (Join-Path $featureDir "data-model.md") + has_quickstart = Test-Exists (Join-Path $featureDir "quickstart.md") + has_contracts = Test-Exists (Join-Path $featureDir "contracts") + has_checklists = Test-Exists (Join-Path $featureDir "checklists") + checklist_files = @() + } + + # Check if this is the current feature + if ($IsFeatureBranch -and $CurrentBranch -match '^(\d{3})-' -and $FeatureName -match '^(\d{3})-') { + $currentPrefix = $CurrentBranch -replace '^(\d{3})-.*', '$1' + $featurePrefix = $FeatureName -replace '^(\d{3})-.*', '$1' + if ($currentPrefix -eq $featurePrefix) { + $info.is_current = $true + } + } + + # Get checklist files if they exist + $checklistsDir = Join-Path $featureDir "checklists" + if (Test-Path $checklistsDir -PathType Container) { + $info.checklist_files = @(Get-ChildItem $checklistsDir -Filter "*.md" -File | Select-Object -ExpandProperty Name | Sort-Object) + } + + return $info +} + +# Resolve repository root +$ScriptDir = $PSScriptRoot + +try { + $gitRoot = git rev-parse --show-toplevel 2>$null + if ($LASTEXITCODE -eq 0) { + $RepoRoot = $gitRoot + $HasGit = $true + $CurrentBranch = git rev-parse --abbrev-ref HEAD 2>$null + if ($LASTEXITCODE -ne 0) { $CurrentBranch = "unknown" } + } else { + throw "Not a git repo" + } +} catch { + $RepoRoot = Find-RepoRoot $ScriptDir + if (-not $RepoRoot) { + Write-Error "Error: Could not determine repository root." + exit 1 + } + $HasGit = $false + $CurrentBranch = "" +} + +# Determine specs directory +$SpecsDir = if (Test-Path (Join-Path $RepoRoot ".specify/specs")) { + Join-Path $RepoRoot ".specify/specs" +} elseif (Test-Path (Join-Path $RepoRoot "specs")) { + Join-Path $RepoRoot "specs" +} else { + Join-Path $RepoRoot ".specify/specs" # Default +} + +# Determine memory directory +$MemoryDir = if (Test-Path (Join-Path $RepoRoot ".specify/memory")) { + Join-Path $RepoRoot ".specify/memory" +} elseif (Test-Path (Join-Path $RepoRoot "memory")) { + Join-Path $RepoRoot "memory" +} else { + Join-Path $RepoRoot ".specify/memory" # Default +} + +# Check constitution +$ConstitutionPath = Join-Path $MemoryDir "constitution.md" +$ConstitutionExists = Test-Path $ConstitutionPath -PathType Leaf + +# Get project name +$ProjectName = Get-ProjectName $RepoRoot + +# Check if on feature branch +$IsFeatureBranch = $CurrentBranch -match '^\d{3}-' + +# Collect all features +$Features = @() +if (Test-Path $SpecsDir -PathType Container) { + $Features = @(Get-ChildItem $SpecsDir -Directory | Where-Object { $_.Name -match '^\d{3}-' } | Sort-Object Name | Select-Object -ExpandProperty Name) +} + +# Resolve target feature if specified +$ResolvedTarget = $null +if ($Feature) { + # Try exact match first + if (Test-Path (Join-Path $SpecsDir $Feature) -PathType Container) { + $ResolvedTarget = $Feature + } + # Try as path + elseif (Test-Path $Feature -PathType Container) { + $ResolvedTarget = Split-Path $Feature -Leaf + } + # Try as number prefix + elseif ($Feature -match '^\d+$') { + $prefix = "{0:D3}" -f [int]$Feature + $match = $Features | Where-Object { $_ -match "^$prefix-" } | Select-Object -First 1 + if ($match) { $ResolvedTarget = $match } + } + # Try partial match + else { + $match = $Features | Where-Object { $_ -like "*$Feature*" } | Select-Object -First 1 + if ($match) { $ResolvedTarget = $match } + } + + if (-not $ResolvedTarget) { + Write-Error "Error: Feature not found: $Feature" + exit 1 + } +} + +# Build output +if ($Json) { + $featuresInfo = @() + foreach ($f in $Features) { + $featuresInfo += Get-FeatureInfo -FeatureName $f -SpecsDir $SpecsDir -CurrentBranch $CurrentBranch -IsFeatureBranch $IsFeatureBranch + } + + $output = [ordered]@{ + project = $ProjectName + repo_root = $RepoRoot + specs_dir = $SpecsDir + has_git = $HasGit + branch = $CurrentBranch + is_feature_branch = $IsFeatureBranch + constitution = [ordered]@{ + exists = $ConstitutionExists + path = $ConstitutionPath + } + feature_count = $Features.Count + target_feature = $ResolvedTarget + features = $featuresInfo + } + + $output | ConvertTo-Json -Depth 10 -Compress +} else { + Write-Output "Project Status Discovery" + Write-Output "========================" + Write-Output "" + Write-Output "Project: $ProjectName" + Write-Output "Root: $RepoRoot" + Write-Output "Specs: $SpecsDir" + Write-Output "Git: $HasGit" + Write-Output "Branch: $CurrentBranch" + Write-Output "Feature Branch: $IsFeatureBranch" + Write-Output "Constitution: $ConstitutionExists ($ConstitutionPath)" + Write-Output "" + + if ($ResolvedTarget) { + Write-Output "Target Feature: $ResolvedTarget" + Write-Output "" + } + + Write-Output "Features ($($Features.Count)):" + Write-Output "" + + if ($Features.Count -eq 0) { + Write-Output " (none)" + } else { + foreach ($f in $Features) { + $info = Get-FeatureInfo -FeatureName $f -SpecsDir $SpecsDir -CurrentBranch $CurrentBranch -IsFeatureBranch $IsFeatureBranch + Write-Output " Name: $($info.name)" + Write-Output " Path: $($info.path)" + Write-Output " Current: $($info.is_current)" + Write-Output " Artifacts:" + Write-Output " spec.md: $($info.has_spec)" + Write-Output " plan.md: $($info.has_plan)" + Write-Output " tasks.md: $($info.has_tasks)" + Write-Output " research.md: $($info.has_research)" + Write-Output " data-model.md: $($info.has_data_model)" + Write-Output " quickstart.md: $($info.has_quickstart)" + Write-Output " contracts/: $($info.has_contracts)" + Write-Output " checklists/: $($info.has_checklists)" + if ($info.checklist_files.Count -gt 0) { + Write-Output " checklist_files: $($info.checklist_files -join ', ')" + } + Write-Output "" + } + } +} diff --git a/spec-driven.md b/spec-driven.md index 70b9789708..8f8cd85416 100644 --- a/spec-driven.md +++ b/spec-driven.md @@ -102,6 +102,17 @@ After a plan is created, this command analyzes the plan and related design docum 3. **Parallelization**: Marks independent tasks `[P]` and outlines safe parallel groups 4. **Output**: Writes `tasks.md` in the feature directory, ready for execution by a Task agent +### The `/speckit.status` Command + +At any point in the workflow, this command provides visibility into project and feature progress: + +1. **Project Overview**: Shows all features with their workflow stage (specify → plan → tasks → implement) +2. **Artifact Status**: Displays which documents exist for the current feature +3. **Progress Tracking**: For features in implementation, shows task completion percentages +4. **Next Action**: Recommends the logical next command based on current state + +This is particularly useful when resuming work after a break, managing multiple features, or onboarding to an existing project. + ### Example: Building a Chat Feature Here's how these commands transform the traditional development workflow: diff --git a/templates/commands/status.md b/templates/commands/status.md new file mode 100644 index 0000000000..109e041736 --- /dev/null +++ b/templates/commands/status.md @@ -0,0 +1,310 @@ +--- +description: Display project status, feature progress, and recommended next actions across the spec-driven development workflow. +scripts: + sh: scripts/bash/get-project-status.sh --json + ps: scripts/powershell/Get-ProjectStatus.ps1 -Json +--- + +## User Input + +```text +$ARGUMENTS +``` + +## Goal + +Provide a clear, at-a-glance view of project status and workflow progress. This command is **READ-ONLY** and helps users understand where they are in the spec-driven development workflow and what to do next. + +This command answers: "Where am I and what should I do next?" + +(For artifact quality and consistency analysis, use `/speckit.analyze` instead.) + +## Input Parsing + +Parse user input for: + +1. **Feature identifier** (optional, positional): + - Feature name: `002-dashboard` + - Feature number prefix: `002` + - Feature path: `specs/002-dashboard` + +2. **Flags**: + - `--all`: Show features overview only, no detail section + - `--verbose`: Include task breakdown and artifact summaries + - `--json`: Output machine-readable JSON instead of formatted text + - `--feature `: Explicit feature selection (alternative to positional) + +**Precedence**: Explicit feature > positional argument > current branch > `--all` required + +## Execution Steps + +### 1. Initialize Context + +Run `{SCRIPT}` from repo root to get REPO_ROOT and BRANCH. Determine: + +- **REPO_ROOT**: Project root directory +- **SPECS_DIR**: `{REPO_ROOT}/.specify/specs` (fall back to `{REPO_ROOT}/specs` if not found) +- **MEMORY_DIR**: `{REPO_ROOT}/.specify/memory` (fall back to `{REPO_ROOT}/memory`) +- **CURRENT_BRANCH**: Current git branch (or fallback per script logic) +- **HAS_GIT**: Whether project is a git repository + +### 2. Load Constitution Status + +Check for constitution file at `{MEMORY_DIR}/constitution.md`: + +- If exists: Extract version from file (look for `## Version` section or `version:` in frontmatter) +- Format: `✓ Defined (v1.2.0)` or `✓ Defined` if no version found +- If missing: `○ Not defined` + +### 3. Scan All Features + +Scan `{SPECS_DIR}` for feature directories (matching pattern `NNN-*` where NNN is 3 digits): + +For each feature directory, detect stage by checking file existence: + +| Stage | Condition | Display | +|-------|-----------|---------| +| Specify | `spec.md` exists | ✓ | +| Specify | `spec.md` missing | ○ | +| Plan | `plan.md` exists | ✓ | +| Plan | `plan.md` missing, spec exists | ○ | +| Plan | `plan.md` missing, no spec | - | +| Tasks | `tasks.md` exists | ✓ | +| Tasks | `tasks.md` missing, plan exists | ○ | +| Tasks | `tasks.md` missing, no plan | - | +| Implement | Parse `tasks.md` for completion | See below | + +**Implementation stage logic** (when `tasks.md` exists): + +- Count total tasks: Lines matching `- [ ]` or `- [x]` or `- [X]` +- Count completed: Lines matching `- [x]` or `- [X]` +- If 0 completed: `○ Ready` +- If all completed: `✓ Complete` +- If partial: `● {completed}/{total} ({percent}%)` + +### 4. Determine Target Feature + +Based on input parsing: + +1. If `--all` flag: Skip detail section, show overview only +2. If feature specified (positional or `--feature`): Use that feature +3. If on feature branch (matches `NNN-*` pattern): Use current branch feature +4. If on non-feature branch (e.g., `main`): + - Add note: `ℹ Not on a feature branch` + - Show overview only (no detail section) + +### 5. Build Feature Detail (if target feature selected) + +For the target feature, gather: + +**Artifacts status**: + +| Artifact | Path | Check | +|----------|------|-------| +| spec.md | `{FEATURE_DIR}/spec.md` | File exists | +| plan.md | `{FEATURE_DIR}/plan.md` | File exists | +| tasks.md | `{FEATURE_DIR}/tasks.md` | File exists | +| research.md | `{FEATURE_DIR}/research.md` | File exists | +| data-model.md | `{FEATURE_DIR}/data-model.md` | File exists | +| quickstart.md | `{FEATURE_DIR}/quickstart.md` | File exists | +| contracts/ | `{FEATURE_DIR}/contracts/` | Directory exists and non-empty | +| checklists/ | `{FEATURE_DIR}/checklists/` | Directory exists and non-empty | + +Display: `✓` exists, `○` ready to create (prerequisite exists), `-` not applicable yet + +**Checklists status** (if `checklists/` exists): + +For each `.md` file in checklists/: +- Count total items: `- [ ]` or `- [x]` or `- [X]` +- Count completed: `- [x]` or `- [X]` +- Format: `✓ {name} {completed}/{total}` or `● {name} {completed}/{total}` + +**Task progress** (if `--verbose` and `tasks.md` exists): + +Parse tasks.md for phase sections (headers containing "Phase"): +- Extract phase name and task counts +- Show: `✓` complete, `●` in progress, `○` not started, `-` blocked + +### 6. Determine Next Action + +Based on target feature state, recommend next command: + +| Current State | Next Action | Message | +|---------------|-------------|---------| +| No spec.md | `/speckit.specify` | Create feature specification | +| spec.md, no plan.md | `/speckit.plan` | Create implementation plan | +| plan.md, no tasks.md | `/speckit.tasks` | Generate implementation tasks | +| tasks.md, 0% complete | `/speckit.implement` | Begin implementation | +| tasks.md, partial | `/speckit.implement` | Continue implementation | +| tasks.md, 100% complete | (none) | Ready for review/merge | + +Optional recommendations based on context: +- If spec.md exists but no clarifications: Mention `/speckit.clarify` as optional +- If tasks.md exists but not analyzed: Mention `/speckit.analyze` as optional + +### 7. Generate Output + +**Human-readable format** (default): + +``` +Spec-Driven Development Status + +Project: {project_name} +Branch: {current_branch} +Constitution: {constitution_status} + +Features ++-----------------+---------+------+-------+------------------+ +| Feature | Specify | Plan | Tasks | Implement | ++-----------------+---------+------+-------+------------------+ +| 001-onboarding | ✓ | ✓ | ✓ | ✓ Complete | +| 002-dashboard | ✓ | ✓ | ✓ | ● 12/18 (67%) | +| 003-user-auth < | ✓ | ✓ | ○ | - | ++-----------------+---------+------+-------+------------------+ + +Legend: ✓ complete ● in progress ○ ready - not started + +{FEATURE_DETAIL_SECTION if target feature selected} + +{EMPTY_STATE_MESSAGE if no features exist} +``` + +**Feature detail section**: + +``` +003-user-auth + +Artifacts: + ✓ spec.md ✓ plan.md ○ tasks.md + ✓ research.md ✓ data-model.md - quickstart.md + ✓ contracts/ - checklists/ + +Checklists: None defined + +Next: /speckit.tasks + Generate implementation tasks from your plan +``` + +**Verbose additions** (when `--verbose`): + +``` +Task Progress: + Phase 1 - Setup: ✓ 4/4 complete + Phase 2 - Foundation: ✓ 3/3 complete + Phase 3 - US1 Login: ● 5/8 in progress + Phase 4 - US2 Register: ○ 0/6 not started + Phase 5 - Polish: - 0/3 blocked + +Checklists: + ✓ security.md 6/6 + ● ux.md 8/12 +``` + +**Empty state** (no features in specs/): + +``` +Spec-Driven Development Status + +Project: {project_name} +Branch: {current_branch} +Constitution: {constitution_status} + +Features ++---------+---------+------+-------+-----------+ +| Feature | Specify | Plan | Tasks | Implement | ++---------+---------+------+-------+-----------+ +| (none) | | | | | ++---------+---------+------+-------+-----------+ + +No features defined yet. +Run /speckit.specify to create your first feature. +``` + +**JSON format** (when `--json`): + +```json +{ + "project": "my-project", + "branch": "003-user-auth", + "is_feature_branch": true, + "constitution": { + "exists": true, + "version": "1.2.0" + }, + "features": [ + { + "name": "001-onboarding", + "path": "/path/to/specs/001-onboarding", + "stages": { + "specify": "complete", + "plan": "complete", + "tasks": "complete", + "implement": "complete" + }, + "tasks": { + "total": 24, + "completed": 24, + "percent": 100 + } + }, + { + "name": "003-user-auth", + "path": "/path/to/specs/003-user-auth", + "is_current": true, + "stages": { + "specify": "complete", + "plan": "complete", + "tasks": "ready", + "implement": "not_started" + }, + "tasks": null + } + ], + "current_feature": { + "name": "003-user-auth", + "artifacts": { + "spec.md": true, + "plan.md": true, + "tasks.md": false, + "research.md": true, + "data-model.md": true, + "quickstart.md": false, + "contracts/": true, + "checklists/": false + }, + "checklists": [], + "next_action": { + "command": "/speckit.tasks", + "message": "Generate implementation tasks from your plan" + } + } +} +``` + +## Operating Principles + +### Read-Only Operation + +- **NEVER** modify any files +- **NEVER** create any files +- This command is purely informational + +### Context Efficiency + +- Scan only what's needed (file existence checks, not full content reads) +- Parse tasks.md minimally (line matching, not full semantic analysis) +- Keep output concise and actionable + +### Graceful Handling + +- Missing directories: Report as empty state +- Missing files: Report as not yet created +- Parse errors: Skip and continue, note in output if critical +- Non-git repos: Function normally, note git status as unavailable + +### User Experience + +- Always show the features overview for project context +- Clearly indicate current/active feature with `<` marker +- Make next action obvious and specific +- Support both quick checks (`/speckit.status`) and deep dives (`--verbose`)