Skip to content

Commit 79f8530

Browse files
committed
Add progress tracking feature with config command
- Add /speckit.progress command for manual progress tracking - Add specify config CLI command to manage project settings - Add auto-tracking support in /speckit.implement (configurable) - Add progress-template.md and config.json templates - Update README.md with config command documentation - Add .gitignore entries for local development files
1 parent f205fa3 commit 79f8530

File tree

7 files changed

+383
-1
lines changed

7 files changed

+383
-1
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ env/
4444
.genreleases/
4545
*.zip
4646
sdd-*/
47+
48+
# Local development and testing
49+
sam/
50+
.specify/

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ The `specify` command supports the following options:
162162
|-------------|----------------------------------------------------------------|
163163
| `init` | Initialize a new Specify project from the latest template |
164164
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `shai`) |
165+
| `config` | Manage Specify project configuration (progress tracking settings, etc.) |
166+
| `version` | Display version and system information |
165167

166168
### `specify init` Arguments & Options
167169

@@ -223,6 +225,12 @@ specify init my-project --ai claude --github-token ghp_your_token_here
223225

224226
# Check system requirements
225227
specify check
228+
229+
# View and manage configuration
230+
specify config # Show current configuration
231+
specify config --auto-tracking # Enable auto-tracking
232+
specify config --no-auto-tracking # Disable auto-tracking
233+
specify config --get progress.autoTracking # Get specific setting
226234
```
227235

228236
### Available Slash Commands

src/specify_cli/__init__.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,208 @@ def version():
13491349
console.print(panel)
13501350
console.print()
13511351

1352+
def _get_config_path(project_path: Path = None) -> Path:
1353+
"""Get the path to .specify/config.json file."""
1354+
if project_path is None:
1355+
project_path = Path.cwd()
1356+
return project_path / ".specify" / "config.json"
1357+
1358+
def _load_config(project_path: Path = None) -> dict:
1359+
"""Load configuration from .specify/config.json, return default if not exists."""
1360+
config_path = _get_config_path(project_path)
1361+
default_config = {
1362+
"progress": {
1363+
"autoTracking": False,
1364+
"updateOnTaskComplete": True,
1365+
"updateOnPhaseComplete": True
1366+
},
1367+
"version": "1.0"
1368+
}
1369+
1370+
if not config_path.exists():
1371+
return default_config
1372+
1373+
try:
1374+
with open(config_path, 'r', encoding='utf-8') as f:
1375+
user_config = json.load(f)
1376+
# Merge with defaults to ensure all keys exist
1377+
merged = default_config.copy()
1378+
if "progress" in user_config:
1379+
merged["progress"].update(user_config["progress"])
1380+
if "version" in user_config:
1381+
merged["version"] = user_config["version"]
1382+
return merged
1383+
except (json.JSONDecodeError, IOError) as e:
1384+
console.print(f"[yellow]Warning:[/yellow] Could not read config file: {e}")
1385+
console.print("[dim]Using default configuration[/dim]")
1386+
return default_config
1387+
1388+
def _save_config(config: dict, project_path: Path = None) -> bool:
1389+
"""Save configuration to .specify/config.json."""
1390+
config_path = _get_config_path(project_path)
1391+
config_dir = config_path.parent
1392+
1393+
try:
1394+
# Ensure .specify directory exists
1395+
config_dir.mkdir(parents=True, exist_ok=True)
1396+
1397+
with open(config_path, 'w', encoding='utf-8') as f:
1398+
json.dump(config, f, indent=2)
1399+
f.write('\n')
1400+
return True
1401+
except IOError as e:
1402+
console.print(f"[red]Error:[/red] Could not write config file: {e}")
1403+
return False
1404+
1405+
@app.command()
1406+
def config(
1407+
show: bool = typer.Option(False, "--show", "-s", help="Show current configuration"),
1408+
get: str = typer.Option(None, "--get", "-g", help="Get a specific configuration value (e.g., 'progress.autoTracking')"),
1409+
set_key: str = typer.Option(None, "--set", help="Set a configuration key (e.g., 'progress.autoTracking')"),
1410+
set_value: str = typer.Option(None, help="Value for --set (true/false for booleans)"),
1411+
auto_tracking: bool = typer.Option(False, "--auto-tracking/--no-auto-tracking", help="Enable or disable auto-tracking"),
1412+
project_path: str = typer.Option(None, "--project", "-p", help="Project path (default: current directory)"),
1413+
):
1414+
"""
1415+
Manage Specify project configuration.
1416+
1417+
View or modify settings for progress tracking and other features.
1418+
1419+
Examples:
1420+
specify config --show # Show current configuration
1421+
specify config --get progress.autoTracking # Get specific setting
1422+
specify config --auto-tracking true # Enable auto-tracking
1423+
specify config --set progress.autoTracking false # Set specific setting
1424+
"""
1425+
show_banner()
1426+
1427+
# Determine project path
1428+
if project_path:
1429+
proj_path = Path(project_path).resolve()
1430+
if not proj_path.exists():
1431+
console.print(f"[red]Error:[/red] Project path does not exist: {project_path}")
1432+
raise typer.Exit(1)
1433+
else:
1434+
proj_path = Path.cwd()
1435+
1436+
# Check if this is a Specify project
1437+
specify_dir = proj_path / ".specify"
1438+
if not specify_dir.exists():
1439+
console.print("[yellow]Warning:[/yellow] This doesn't appear to be a Specify project.")
1440+
console.print("[dim]Configuration will be created in current directory[/dim]\n")
1441+
1442+
# Load current config
1443+
current_config = _load_config(proj_path)
1444+
1445+
# Handle different operations
1446+
# Check if --auto-tracking or --no-auto-tracking was explicitly used
1447+
# Typer sets the flag to True if --auto-tracking, False if --no-auto-tracking
1448+
# We need to detect if user actually used the flag (not just default False)
1449+
import sys
1450+
auto_tracking_used = "--auto-tracking" in sys.argv or "--no-auto-tracking" in sys.argv
1451+
1452+
if auto_tracking_used:
1453+
# Quick set for auto-tracking
1454+
current_config["progress"]["autoTracking"] = auto_tracking
1455+
if _save_config(current_config, proj_path):
1456+
status = "enabled" if auto_tracking else "disabled"
1457+
console.print(f"[green]✓[/green] Auto-tracking {status}")
1458+
console.print(f"[dim]Configuration saved to: {_get_config_path(proj_path)}[/dim]")
1459+
else:
1460+
raise typer.Exit(1)
1461+
return
1462+
1463+
if set_key and set_value is not None:
1464+
# Set specific key
1465+
keys = set_key.split('.')
1466+
config_ref = current_config
1467+
for key in keys[:-1]:
1468+
if key not in config_ref:
1469+
config_ref[key] = {}
1470+
config_ref = config_ref[key]
1471+
1472+
# Convert value based on type
1473+
final_key = keys[-1]
1474+
if isinstance(config_ref.get(final_key), bool):
1475+
# Boolean conversion
1476+
if set_value.lower() in ('true', '1', 'yes', 'on'):
1477+
config_ref[final_key] = True
1478+
elif set_value.lower() in ('false', '0', 'no', 'off'):
1479+
config_ref[final_key] = False
1480+
else:
1481+
console.print(f"[red]Error:[/red] Invalid boolean value: {set_value}")
1482+
console.print("[dim]Use: true, false, 1, 0, yes, no, on, off[/dim]")
1483+
raise typer.Exit(1)
1484+
elif isinstance(config_ref.get(final_key), int):
1485+
try:
1486+
config_ref[final_key] = int(set_value)
1487+
except ValueError:
1488+
console.print(f"[red]Error:[/red] Invalid integer value: {set_value}")
1489+
raise typer.Exit(1)
1490+
else:
1491+
config_ref[final_key] = set_value
1492+
1493+
if _save_config(current_config, proj_path):
1494+
console.print(f"[green]✓[/green] Set {set_key} = {config_ref[final_key]}")
1495+
console.print(f"[dim]Configuration saved to: {_get_config_path(proj_path)}[/dim]")
1496+
else:
1497+
raise typer.Exit(1)
1498+
return
1499+
1500+
if get:
1501+
# Get specific key
1502+
keys = get.split('.')
1503+
value = current_config
1504+
try:
1505+
for key in keys:
1506+
value = value[key]
1507+
console.print(f"[cyan]{get}:[/cyan] {value}")
1508+
except KeyError:
1509+
console.print(f"[red]Error:[/red] Configuration key not found: {get}")
1510+
raise typer.Exit(1)
1511+
return
1512+
1513+
# Default: show all configuration
1514+
config_table = Table(show_header=False, box=None, padding=(0, 2))
1515+
config_table.add_column("Setting", style="cyan", justify="left")
1516+
config_table.add_column("Value", style="white", justify="left")
1517+
1518+
# Progress settings
1519+
config_table.add_row("", "")
1520+
config_table.add_row("[bold]Progress Tracking[/bold]", "")
1521+
config_table.add_row(" autoTracking", str(current_config["progress"]["autoTracking"]))
1522+
config_table.add_row(" updateOnTaskComplete", str(current_config["progress"]["updateOnTaskComplete"]))
1523+
config_table.add_row(" updateOnPhaseComplete", str(current_config["progress"]["updateOnPhaseComplete"]))
1524+
1525+
# Version
1526+
config_table.add_row("", "")
1527+
config_table.add_row("[bold]Version[/bold]", current_config.get("version", "1.0"))
1528+
1529+
config_panel = Panel(
1530+
config_table,
1531+
title="[bold cyan]Specify Configuration[/bold cyan]",
1532+
border_style="cyan",
1533+
padding=(1, 2)
1534+
)
1535+
1536+
console.print(config_panel)
1537+
console.print()
1538+
1539+
# Show file location
1540+
config_file = _get_config_path(proj_path)
1541+
if config_file.exists():
1542+
console.print(f"[dim]Configuration file: {config_file}[/dim]")
1543+
else:
1544+
console.print(f"[dim]Configuration file: {config_file} (using defaults)[/dim]")
1545+
1546+
console.print()
1547+
console.print("[bold]Usage examples:[/bold]")
1548+
console.print(" [cyan]specify config --auto-tracking[/cyan] # Enable auto-tracking")
1549+
console.print(" [cyan]specify config --no-auto-tracking[/cyan] # Disable auto-tracking")
1550+
console.print(" [cyan]specify config --get progress.autoTracking[/cyan] # Get specific setting")
1551+
console.print(" [cyan]specify config --set progress.autoTracking true[/cyan] # Set specific setting")
1552+
console.print()
1553+
13521554
def main():
13531555
app()
13541556

templates/commands/implement.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ You **MUST** consider the user input before proceeding (if not empty).
122122
123123
8. Progress tracking and error handling:
124124
- Report progress after each completed task
125+
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
126+
- **Auto-tracking (optional)**: Check `.specify/config.json` for `progress.autoTracking` setting:
127+
- If `autoTracking: true`: After each completed task, update PROGRESS.md and STATUS.md files using the same logic as `/speckit.progress` command
128+
- If `autoTracking: false` or config missing: Skip automatic progress file updates (user can run `/speckit.progress` manually)
129+
- Progress files location: FEATURE_DIR/PROGRESS.md and FEATURE_DIR/STATUS.md
125130
- Halt execution if any non-parallel task fails
126131
- For parallel tasks [P], continue with successful tasks, report failed ones
127132
- Provide clear error messages with context for debugging
128133
- Suggest next steps if implementation cannot proceed
129-
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
130134
131135
9. Completion validation:
132136
- Verify all required tasks are completed

templates/commands/progress.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
description: Generate and update progress tracking files (PROGRESS.md and STATUS.md) from tasks.md
3+
scripts:
4+
sh: scripts/bash/check-prerequisites.sh --json --require-tasks
5+
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks
6+
---
7+
8+
## User Input
9+
10+
```text
11+
$ARGUMENTS
12+
```
13+
14+
You **MUST** consider the user input before proceeding (if not empty).
15+
16+
## Outline
17+
18+
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. 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").
19+
20+
2. **Load configuration** (if `.specify/config.json` exists):
21+
- Read `.specify/config.json` to check progress tracking settings
22+
- If file doesn't exist, use default settings (autoTracking: false)
23+
- Extract `progress.autoTracking`, `progress.updateOnTaskComplete`, `progress.updateOnPhaseComplete` values
24+
25+
3. **Load tasks.md**:
26+
- **REQUIRED**: Read FEATURE_DIR/tasks.md for the complete task list
27+
- Parse all tasks with format: `- [ ]` or `- [X]` or `- [x]` followed by task ID, labels, and description
28+
- Extract phase information from section headers (e.g., "## Phase 1: Setup", "## Phase 2: Foundational", "## Phase 3: User Story 1")
29+
- Count completed vs total tasks per phase
30+
31+
4. **Calculate progress metrics**:
32+
- **Total tasks**: Count all lines matching task format
33+
- **Completed tasks**: Count lines with `- [X]` or `- [x]`
34+
- **Remaining tasks**: Total - Completed
35+
- **Overall percentage**: (Completed / Total) * 100
36+
- **Per-phase metrics**: Calculate completed/total/percentage for each phase
37+
- **Current phase**: Identify the phase with the most recent activity (last completed task)
38+
39+
5. **Generate PROGRESS.md**:
40+
- Use `.specify/templates/progress-template.md` as structure
41+
- Replace placeholders:
42+
- `[FEATURE_NAME]`: Extract from tasks.md header or FEATURE_DIR name
43+
- `[TIMESTAMP]`: Current date/time in ISO format
44+
- `[TOTAL]`: Total task count
45+
- `[COMPLETED]`: Completed task count
46+
- `[REMAINING]`: Remaining task count
47+
- `[PERCENTAGE]`: Overall progress percentage (rounded to 1 decimal)
48+
- `[PHASE_STATUS_LIST]`: Generate list of phases with status indicators:
49+
- ✅ Complete: Phase with 100% completion
50+
- 🟡 In Progress: Phase with some tasks completed but not all
51+
- ⏳ Pending: Phase with 0% completion
52+
- Format: `- [STATUS] Phase N: [Name] ([completed]/[total] - [percentage]%)`
53+
- `[RECENT_ACTIVITY_LIST]`: List last 5-10 completed tasks (if any) with timestamps
54+
- Write to FEATURE_DIR/PROGRESS.md
55+
- If file exists, update it (preserve any manual notes if present)
56+
57+
6. **Generate STATUS.md** (quick summary):
58+
- Create a concise status file with:
59+
- Current phase name and progress
60+
- Overall progress percentage
61+
- Next steps (next incomplete task or phase)
62+
- Last update timestamp
63+
- Write to FEATURE_DIR/STATUS.md
64+
65+
7. **Display summary**:
66+
- Show progress summary in console:
67+
- Overall: X/Y tasks completed (Z%)
68+
- Current phase: [Phase Name] - X/Y tasks (Z%)
69+
- Next: [Next task or phase]
70+
- Display file paths to PROGRESS.md and STATUS.md
71+
72+
## Progress Calculation Rules
73+
74+
- **Task format detection**: Match lines starting with `- [ ]`, `- [X]`, or `- [x]` followed by task ID (T###)
75+
- **Phase detection**: Match markdown headers `## Phase N:` or `## Phase N: [Name]`
76+
- **Task assignment**: Assign tasks to the phase they appear under (until next phase header)
77+
- **Completion status**:
78+
- `- [ ]` = incomplete
79+
- `- [X]` or `- [x]` = complete
80+
- **Percentage calculation**: Round to 1 decimal place (e.g., 27.3%)
81+
82+
## File Locations
83+
84+
- **PROGRESS.md**: FEATURE_DIR/PROGRESS.md
85+
- **STATUS.md**: FEATURE_DIR/STATUS.md
86+
- **Config**: REPO_ROOT/.specify/config.json (optional)
87+
88+
## Error Handling
89+
90+
- If tasks.md is missing: Report error and suggest running `/speckit.tasks` first
91+
- If no tasks found: Report "No tasks found in tasks.md"
92+
- If config.json is malformed: Use default settings and continue
93+
- If template is missing: Use inline template structure
94+
95+
## Example Output
96+
97+
**PROGRESS.md**:
98+
```markdown
99+
# Progress Tracking: User Authentication
100+
101+
**Last Updated**: 2025-01-27T14:30:00Z
102+
**Status**: 🟢 Active Development
103+
104+
## Overall Progress
105+
- **Total Tasks**: 95
106+
- **Completed**: 26
107+
- **Remaining**: 69
108+
- **Progress**: 27.4%
109+
110+
## Phase Status
111+
- ✅ Phase 1: Setup (5/5 - 100.0%)
112+
- ✅ Phase 2: Foundational (8/8 - 100.0%)
113+
- 🟡 Phase 3: User Story 1 (13/16 - 81.3%)
114+
- ⏳ Phase 4: User Story 2 (0/19 - 0.0%)
115+
```
116+
117+
**STATUS.md**:
118+
```markdown
119+
# Implementation Status
120+
121+
**Current Phase**: Phase 3: User Story 1
122+
**Overall Progress**: 27.4% (26/95 tasks)
123+
**Phase Progress**: 81.3% (13/16 tasks)
124+
125+
**Next Steps**: Complete remaining 3 tasks in Phase 3
126+
127+
**Last Updated**: 2025-01-27T14:30:00Z
128+
```
129+
130+
Note: This command can be run at any time to refresh progress tracking files, regardless of whether implementation is active.
131+

templates/config.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"progress": {
3+
"autoTracking": false,
4+
"updateOnTaskComplete": true,
5+
"updateOnPhaseComplete": true
6+
},
7+
"version": "1.0"
8+
}
9+

0 commit comments

Comments
 (0)