|
| 1 | +--- |
| 2 | +module: cmd_stale |
| 3 | +version: 1 |
| 4 | +status: stable |
| 5 | +files: |
| 6 | + - src/commands/stale.rs |
| 7 | +db_tables: [] |
| 8 | +tracks: |
| 9 | + - 188 |
| 10 | +depends_on: |
| 11 | + - specs/commands/commands.spec.md |
| 12 | + - specs/git_utils/git_utils.spec.md |
| 13 | + - specs/parser/parser.spec.md |
| 14 | + - specs/types/types.spec.md |
| 15 | +--- |
| 16 | + |
| 17 | +# Cmd Stale |
| 18 | + |
| 19 | +## Purpose |
| 20 | + |
| 21 | +Implements the `specsync stale` command — a focused staleness detection tool that identifies specs whose source files have diverged via git commit history. Reports which specs need updating, how many commits they are behind, and which specific source files have drifted. Supports text, JSON, markdown, and GitHub output formats. |
| 22 | + |
| 23 | +## Public API |
| 24 | + |
| 25 | +### Exported Functions |
| 26 | + |
| 27 | +| Function | Parameters | Returns | Description | |
| 28 | +|----------|-----------|---------|-------------| |
| 29 | +| `cmd_stale` | `root: &Path, format: OutputFormat, threshold: usize` | `()` | Detect and report stale specs based on git commit distance | |
| 30 | + |
| 31 | +## Invariants |
| 32 | + |
| 33 | +1. Staleness is determined by `git_commits_between`: a spec is stale when any source file has >= `threshold` commits since the spec was last modified (default: 5) |
| 34 | +2. Specs with no `files` in frontmatter are counted as fresh (no source files to compare against) |
| 35 | +3. Specs not yet tracked by git (no commit history) are skipped and counted as fresh |
| 36 | +4. Results are sorted by most-stale-first (highest `max_commits_behind`) |
| 37 | +5. Exit code is 1 when any stale specs are found, 0 when all are fresh |
| 38 | +6. Requires a git repository — exits with error if `is_git_repo` returns false |
| 39 | + |
| 40 | +## Behavioral Examples |
| 41 | + |
| 42 | +### Scenario: All specs fresh |
| 43 | + |
| 44 | +- **Given** all specs were updated after their source files |
| 45 | +- **When** `specsync stale` is run |
| 46 | +- **Then** prints "All specs are up to date" and exits 0 |
| 47 | + |
| 48 | +### Scenario: Spec behind source by 8 commits (threshold 5) |
| 49 | + |
| 50 | +- **Given** module "auth" has source file `src/auth.rs` with 8 commits since spec was last updated |
| 51 | +- **When** `specsync stale --threshold 5` is run |
| 52 | +- **Then** reports auth as stale with "8 commits behind" and exits 1 |
| 53 | + |
| 54 | +### Scenario: JSON output |
| 55 | + |
| 56 | +- **Given** 2 stale specs out of 10 total |
| 57 | +- **When** `specsync stale --format json` is run |
| 58 | +- **Then** outputs JSON with `total_specs: 10`, `stale_count: 2`, `stale_specs` array with per-file details |
| 59 | + |
| 60 | +## Error Cases |
| 61 | + |
| 62 | +| Condition | Behavior | |
| 63 | +|-----------|----------| |
| 64 | +| Not a git repository | Prints error, exits 1 | |
| 65 | +| Spec file unreadable | Skipped silently | |
| 66 | +| No frontmatter | Skipped silently | |
| 67 | +| Source file doesn't exist on disk | Skipped in commit distance check | |
| 68 | + |
| 69 | +## Dependencies |
| 70 | + |
| 71 | +- `git_utils` — git commit history queries |
| 72 | +- `parser` — frontmatter parsing for module name and files list |
| 73 | +- `commands` — `load_and_discover` for config and spec file discovery |
| 74 | + |
| 75 | +## Change Log |
| 76 | + |
| 77 | +| Date | Change | |
| 78 | +|------|--------| |
| 79 | +| 2026-04-10 | Initial — dedicated staleness detection command (closes #188) | |
0 commit comments