You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: staleness detection — specsync stale command + check --stale flag (#189)
* feat: staleness detection — `specsync stale` command + check --stale flag (#188)
Add git-based staleness detection to warn when source files have changed
but specs haven't been updated. Three integration points:
- New `specsync stale` subcommand: focused detection with per-file details,
all output formats (text/json/markdown), configurable threshold (default 5)
- `specsync check --stale[=N]`: integrates git drift warnings into check
- Scoring freshness: penalizes specs 5+ commits behind source files
Extracted git helpers from report.rs into shared git_utils.rs module.
Full specs for git_utils and cmd_stale. 100% file/LOC coverage maintained.
Closes#188
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ci): add missing requirements.md for cmd_stale and git_utils specs
CI uses --strict which treats warnings as errors. The two new specs
were missing companion requirements.md files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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
Shared git utility functions for querying repository history. Provides commit hash lookup, commit distance counting, and git repository detection. Used by the `stale`, `report`, and `scoring` modules to determine spec freshness relative to source file changes.
17
+
18
+
## Public API
19
+
20
+
### Exported Functions
21
+
22
+
| Function | Parameters | Returns | Description |
23
+
|----------|-----------|---------|-------------|
24
+
|`git_last_commit_hash`|`root: &Path, file: &str`|`Option<String>`| Get the SHA hash of the last commit that touched a file |
25
+
|`git_commits_between`|`root: &Path, spec_file: &str, source_file: &str`|`usize`| Count commits to source_file since spec_file was last modified |
26
+
|`is_git_repo`|`root: &Path`|`bool`| Check if a directory is inside a git work tree |
27
+
28
+
### Exported Types
29
+
30
+
| Type | Kind | Description |
31
+
|------|------|-------------|
32
+
|`StaleInfo`| struct | Staleness summary for a single spec: path, module name, max commits behind, per-file details |
33
+
34
+
## Invariants
35
+
36
+
1. All git commands execute with `current_dir(root)` to ensure correct repository context
37
+
2. Functions return safe defaults (None, 0, false) when git is unavailable or commands fail
0 commit comments