Skip to content

Commit 80419db

Browse files
philoserfclaude
andcommitted
feat: add use-trueup skill
Detects drift between implementation and spec before committing. Compares staged diffs against spec files, surfaces decisions made in code that aren't reflected in the spec, and reconciles them through an interactive review flow. Includes coverage check subcommand for spec-to-test gap analysis. Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent 0a243c0 commit 80419db

File tree

2 files changed

+356
-0
lines changed

2 files changed

+356
-0
lines changed

skills/use-trueup/SKILL.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
name: use-trueup
3+
description: Detects drift between implementation and spec before committing. Use when about to commit, after implementation, or asking "is the spec accurate?" Compares staged diffs against specs to surface and reconcile decisions.
4+
argument-hint: "coverage"
5+
---
6+
7+
# true-up
8+
9+
Detect decisions made during implementation that aren't in the spec. Review them. Reconcile. Then commit.
10+
11+
```text
12+
brainstorming → writing-plans → [implementation] → true-up → commit
13+
```
14+
15+
true-up fills the gap between "start building" and "commit." It catches choices made in code that the spec doesn't reflect — architecture decisions, behavior changes, data model choices — and ensures the spec stays true before the commit lands.
16+
17+
## Reference Files
18+
19+
- [coverage.md](references/coverage.md) — Spec-to-test coverage analysis, invoked via `/use-trueup coverage`
20+
21+
## When to Use
22+
23+
- Before committing, after implementation work
24+
- When asked "is the spec still accurate?"
25+
- When superpowers' 1% rule triggers on commit-related activity
26+
27+
## Spec Discovery
28+
29+
Find the spec files to reconcile against. Check in this order:
30+
31+
1. If a `.true-up` file exists in the project root, read it as JSON and use `spec_paths`:
32+
```json
33+
{
34+
"spec_paths": ["docs/spec.md", "docs/design/"]
35+
}
36+
```
37+
2. If `docs/superpowers/specs/` exists, use all `*.md` files in it.
38+
3. If neither exists, ask the user: "Where are your spec files? I need a path to check implementation decisions against."
39+
40+
Store the resolved spec paths for the rest of this invocation. Do not cache across sessions.
41+
42+
## Drift Detection
43+
44+
### Step 1: Check for staged changes
45+
46+
Run `git diff --cached --stat`. If empty, tell the user "No staged changes to review" and stop.
47+
48+
### Step 2: Read the full staged diff
49+
50+
Run `git diff --cached` to get the complete diff.
51+
52+
### Step 3: Read all spec files
53+
54+
Read each spec file found during discovery. Hold the full content in context.
55+
56+
### Step 4: Check for pending decisions
57+
58+
If the sidecar file exists (`.true-up/decisions.jsonl` relative to the spec root — see Sidecar File section), read it. Any entries with `"status": "pending"` are carried forward into this review session.
59+
60+
### Step 5: Identify new decisions
61+
62+
Compare the staged diff against the spec content. Look for prescriptive choices in the diff that are NOT already captured in the spec. A "decision" is a choice that affects:
63+
64+
- **System behavior** — what the software does
65+
- **Architecture** — how components relate
66+
- **Data models** — what's stored, how it's structured
67+
- **External interfaces** — APIs, protocols, formats
68+
69+
**Ignore** changes that are only:
70+
71+
- Formatting or style
72+
- Variable or function naming
73+
- Import ordering
74+
- Tooling or editor configuration
75+
- Diagnostic or debug output
76+
77+
For each decision found, determine:
78+
79+
- A plain-English **question** framing the choice (e.g., "How should API responses be cached?")
80+
- The **decision made** in the code (e.g., "In-memory LRU cache with 5-minute TTL")
81+
- Which **spec file** it relates to
82+
- Which **spec section** it belongs in (the nearest relevant header)
83+
- The **diff context** (file and line range)
84+
85+
### Step 6: Deduplicate
86+
87+
If there are pending or rejected decisions from the sidecar (Step 4), compare each new decision against them:
88+
89+
- If a new decision matches a **pending** one (same choice, different wording), drop the new one.
90+
- If a new decision matches a **rejected** one, surface it with context: "This decision was previously rejected ([reason]). Do you want to re-evaluate it?"
91+
- Use your judgment — different words for the same choice is a duplicate; a related but distinct choice is not.
92+
93+
## Decision Review
94+
95+
If no decisions are found (no new decisions and no pending decisions from sidecar), tell the user "No spec drift detected. Ready to commit." and proceed to the Commit section.
96+
97+
Otherwise, present each decision one at a time using this format:
98+
99+
```text
100+
true-up found [N] decision(s) to review.
101+
102+
Decision [X of N]
103+
Question: [question]
104+
Decision made: [decision]
105+
Spec: [spec_file] § [spec_section]
106+
Diff: [file:lines]
107+
108+
How would you like to handle this?
109+
- Approve — update the spec to reflect this decision
110+
- Reject — modify the staged code to undo this decision
111+
- Edit — rewrite the decision before approving
112+
- Skip — leave pending for next invocation
113+
```
114+
115+
Wait for the user's response before proceeding to the next decision.
116+
117+
### Approve
118+
119+
1. Edit the spec file using the Edit tool.
120+
2. Insert the decision as a natural requirement in the identified section.
121+
3. The spec must read as if it was always written that way — no "Decision:" markers, no metadata, no timestamps.
122+
4. If the right section is ambiguous (decision could fit in multiple places), ask the user which section to update.
123+
5. Tell the user what changed: "Updated [spec_file] § [section] to reflect: [decision]"
124+
6. Remove this decision from the sidecar if it was loaded from there.
125+
126+
### Reject
127+
128+
1. Ask: "What's the reason for rejecting this?"
129+
2. After getting the reason, revert only the specific lines identified in `diff_context`. If the revert is non-trivial (spans multiple files or requires redesign), describe the required changes to the user and ask them to confirm before touching code.
130+
3. Run the project's test command to verify the change doesn't break anything.
131+
4. If tests pass: stage the modified files with `git add`, tell the user what changed.
132+
5. If tests fail: show the test output, tell the user "Automatic reversal broke tests. Please resolve manually." Write the decision to the sidecar with `"status": "rejected"` and the rejection reason.
133+
6. Remove from sidecar if it was a pending decision that's now resolved.
134+
135+
### Edit
136+
137+
1. Ask: "How would you rewrite this decision?"
138+
2. Take the user's rewritten text.
139+
3. Follow the same flow as Approve, but use the edited text instead of the original. The section identified during detection applies unless the edit changes the decision's scope — in that case, re-evaluate section placement.
140+
141+
### Skip
142+
143+
1. Write the decision to the sidecar file with `"status": "pending"`.
144+
2. Tell the user: "Skipped. Will resurface next time you run true-up."
145+
3. Move to the next decision.
146+
147+
**All decisions must be explicitly reviewed.** Never auto-approve. Never skip without the user saying to skip.
148+
149+
## Sidecar File
150+
151+
The sidecar stores pending and rejected decisions between invocations.
152+
153+
**Location:** `.true-up/decisions.jsonl` relative to the resolved spec root. If specs are in `docs/superpowers/specs/`, the sidecar is `docs/superpowers/specs/.true-up/decisions.jsonl`. If specs are in a custom path from `.true-up` config, derive from the first `spec_paths` entry's parent directory.
154+
155+
**Format:** One JSON object per line:
156+
157+
```json
158+
{
159+
"id": "dec-<8 random hex chars>",
160+
"status": "pending",
161+
"spec_file": "docs/superpowers/specs/2026-04-01-auth-design.md",
162+
"spec_section": "## Token Management",
163+
"question": "Should tokens expire on inactivity or only on logout?",
164+
"decision": "Tokens expire after 30 minutes of inactivity.",
165+
"diff_context": "src/auth.py:42-58",
166+
"rejection_reason": null,
167+
"created_at": "2026-04-06T14:32:00Z"
168+
}
169+
```
170+
171+
### Writing to the sidecar
172+
173+
- Create the `.true-up/` directory under the spec root if it doesn't exist.
174+
- Generate `id` as `dec-` followed by 8 random hex characters.
175+
- Set `created_at` to current ISO 8601 timestamp.
176+
- Append one JSON line per decision — never rewrite the whole file.
177+
178+
### Reading from the sidecar
179+
180+
- If the file doesn't exist, there are no pending decisions.
181+
- Read all lines, parse as JSON.
182+
- Only consider entries with `"status": "pending"` as active.
183+
- Entries with `"status": "rejected"` are kept for reference but not re-presented.
184+
185+
### Cleaning the sidecar
186+
187+
- After a decision is approved or edited (spec updated), remove its line from the sidecar.
188+
- After all decisions are resolved and the commit lands, delete the sidecar file if it's empty.
189+
190+
### Gitignore
191+
192+
On first invocation, check that the sidecar directory is in `.gitignore`. If not, append the path (e.g., `docs/superpowers/specs/.true-up/`) to `.gitignore` with a `# true-up working state` comment.
193+
194+
## Commit
195+
196+
After all decisions have been reviewed (none remaining):
197+
198+
1. If any spec files were edited during this session, stage them: `git add [spec files]`
199+
2. Tell the user: "All decisions reviewed. Spec is up to date."
200+
3. If this invocation was part of a commit flow, proceed with the commit. If the user invoked true-up ad hoc (e.g., "is the spec still accurate?"), stop here — do not initiate a commit.
201+
202+
If any decisions were skipped, warn: "[N] decision(s) were skipped and will resurface next time." The user decides whether to commit with skipped decisions or go back and resolve them.
203+
204+
## Coverage Check
205+
206+
When the user asks about spec-to-test coverage, read and follow the instructions in `references/coverage.md`.
207+
208+
## Example Session
209+
210+
```text
211+
> /use-trueup
212+
213+
Checking staged changes... 3 files changed.
214+
Reading specs from docs/superpowers/specs/...
215+
Comparing diff against 2026-04-01-api-design.md...
216+
217+
true-up found 2 decision(s) to review.
218+
219+
Decision 1 of 2
220+
Question: How should API responses be cached?
221+
Decision made: Added in-memory LRU cache with 5-minute TTL.
222+
Spec: 2026-04-01-api-design.md § Performance
223+
Diff: src/api/handler.py:42-58
224+
225+
> approve
226+
227+
Updated 2026-04-01-api-design.md § Performance to reflect:
228+
Added in-memory LRU cache with 5-minute TTL.
229+
230+
Decision 2 of 2
231+
Question: Should input validation run on internal endpoints?
232+
Decision made: Skipped validation for internal-only routes.
233+
Spec: 2026-04-01-api-design.md § Validation
234+
Diff: src/api/middleware.py:15-22
235+
236+
> reject
237+
What's the reason? > Internal endpoints still receive untrusted data from other services.
238+
239+
Reverting src/api/middleware.py:15-22...
240+
Running tests... 14/14 passed.
241+
Staged modified files.
242+
243+
All decisions reviewed. Spec is up to date. Ready to commit.
244+
```
245+
246+
## Error Handling
247+
248+
- **No spec files found:** Tell the user. Ask where their specs live. Do not proceed without specs.
249+
- **No staged changes:** Tell the user "No staged changes to review." Stop.
250+
- **No drift detected:** Tell the user "No spec drift detected. Ready to commit." Proceed.
251+
- **Sidecar directory missing:** Create it silently under the spec root.
252+
- **Ambiguous section placement:** Ask the user which spec section the decision belongs in. Never guess.
253+
- **Spec file deleted:** If a pending decision references a spec file that no longer exists, tell the user and ask whether to drop the decision or redirect it to a different spec.
254+
255+
## Rules
256+
257+
- Never auto-approve decisions. Every decision needs explicit user action.
258+
- Never edit `.claude/memory/` — design decisions belong in the spec, not in memory.
259+
- Never modify superpowers skill files or workflow.
260+
- Never write to `~/.claude/` global directories.
261+
- Never generate tests — flag coverage gaps only.
262+
- The spec must read naturally after edits. No "Decision:" prefixes, no metadata markers, no timestamps in the spec.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Coverage Check
2+
3+
Analyze spec-to-test coverage. Report gaps only — never generate tests.
4+
5+
## Step 1: Discover Spec Files
6+
7+
Use the same discovery logic as the main skill:
8+
9+
1. If `.true-up` exists in the project root, read it as JSON and use `spec_paths`.
10+
2. If `docs/superpowers/specs/` exists, use all `*.md` files in it.
11+
3. If neither exists, ask the user where their spec files live.
12+
13+
## Step 2: Extract Requirements
14+
15+
Read each spec file. Identify individual testable requirements — atomic statements of behavior that could have exactly one test.
16+
17+
Include:
18+
19+
- Behavioral statements ("the system shall...", "when X happens, Y occurs")
20+
- Constraints ("must not exceed...", "limited to...")
21+
- Data rules ("fields are required", "values must be unique")
22+
- Error handling ("if X fails, return Y")
23+
24+
Skip:
25+
26+
- Section headings
27+
- Context paragraphs and background
28+
- Non-behavioral content (motivation, alternatives considered, open questions)
29+
- Formatting or style guidance
30+
31+
Record each requirement with:
32+
33+
- The requirement text (one sentence, imperative)
34+
- The source spec file
35+
- The source section (nearest heading)
36+
37+
## Step 3: Find Test Files
38+
39+
Locate tests in the project. Search these locations:
40+
41+
- `tests/`, `test/`, `__tests__/`
42+
- Files matching `*_test.*`, `*.test.*`, `*.spec.*`
43+
- Language-specific conventions: `*_test.go`, `test_*.py`, `*.test.ts`, `*.spec.ts`
44+
45+
If no test files are found, report "No test files found in project" and list all uncovered requirements.
46+
47+
## Step 4: Match Requirements to Tests
48+
49+
For each requirement, attempt to find a covering test using two strategies in order:
50+
51+
### Marker match
52+
53+
Search test files for explicit requirement markers:
54+
55+
- Comments like `# req: <text>`, `// req: <text>`
56+
- Test names that reference the requirement verbatim or by keyword
57+
58+
If a marker match is found, the requirement is covered. Record the test file and test name.
59+
60+
### Content match
61+
62+
If no marker match, reason about whether existing tests exercise the requirement based on:
63+
64+
- Test function/method names
65+
- Assertion targets and expected values
66+
- Setup and fixture data
67+
- Test descriptions and docstrings
68+
69+
A test covers a requirement only if the test would fail when the requirement is violated. If that cannot be established from reading the test, mark the requirement as not covered.
70+
71+
## Step 5: Report
72+
73+
Present results in this format:
74+
75+
```text
76+
Spec-to-Test Coverage: [covered]/[total] requirements ([percentage]%)
77+
78+
Covered:
79+
✓ [requirement text] — [test file:test name]
80+
81+
Not covered:
82+
✗ [requirement text] (from [spec file] § [section])
83+
```
84+
85+
Sort covered requirements by spec file, then by section order. Sort uncovered requirements the same way.
86+
87+
If coverage is 100%, say so: "Full coverage. All [N] requirements have matching tests."
88+
89+
## Boundaries
90+
91+
- Do NOT generate tests.
92+
- Do NOT offer to generate tests.
93+
- Do NOT suggest test implementations.
94+
- Report gaps only. TDD owns test creation.

0 commit comments

Comments
 (0)