prizmkit 1.0.144 → 1.0.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.0.144",
3
- "bundledAt": "2026-03-29T13:53:32.397Z",
4
- "bundledFrom": "f48ed27"
2
+ "frameworkVersion": "1.0.147",
3
+ "bundledAt": "2026-03-30T17:09:52.213Z",
4
+ "bundledFrom": "4cdb143"
5
5
  }
@@ -158,25 +158,37 @@ $TEST_CMD 2>&1 | tee /tmp/test-baseline.txt | tail -20
158
158
  **CP-2**: All acceptance criteria met, all tests pass.
159
159
 
160
160
  {{IF_BROWSER_INTERACTION}}
161
- ### Phase 3.5: Browser Verification (playwright-cli)
161
+ ### Phase 3.5: Browser Verification (playwright-cli) — MANDATORY
162
162
 
163
- Verify UI behavior using `playwright-cli` before committing. This phase is best-effort failures are logged but do NOT block the commit.
163
+ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without actually running playwright-cli.
164
164
 
165
- 1. Start dev server if needed: `{{BROWSER_SETUP_COMMAND}}`
166
- 2. Use `playwright-cli` to open `{{BROWSER_URL}}`, then verify:
165
+ **Startup**:
166
+ 1. Check if port is already in use: `lsof -ti:3001 | xargs kill -9 2>/dev/null || true`
167
+ 2. Start dev server: `{{BROWSER_SETUP_COMMAND}}`
168
+ 3. Wait for server to be ready: poll `{{BROWSER_URL}}` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
169
+ 4. If the page requires authentication, use playwright-cli to register a test user and log in first
170
+
171
+ **Verification**:
172
+ 5. Use `playwright-cli` to open `{{BROWSER_URL}}`, then verify:
167
173
  {{BROWSER_VERIFY_STEPS}}
168
- 3. Take a final screenshot for evidence.
169
- 4. Close browser and stop dev server.
170
- 5. Append results to `context-snapshot.md`:
174
+ 6. Take a final screenshot for evidence
175
+
176
+ **Cleanup (REQUIRED you started it, you stop it)**:
177
+ 7. Stop the dev server process you started in step 2 (kill the process)
178
+ 8. Verify port is released: `lsof -ti:3001 | xargs kill -9 2>/dev/null || true`
179
+
180
+ **Reporting**:
181
+ 9. Append results to `context-snapshot.md`:
171
182
  ```
172
183
  ## Browser Verification
173
184
  URL: {{BROWSER_URL}}
174
185
  Steps executed: [list]
175
186
  Screenshot: [path]
176
187
  Result: PASS / FAIL (reason)
188
+ Server cleanup: confirmed
177
189
  ```
178
190
 
179
- If any step fails, log the failure and continue. Do NOT retry browser verification.
191
+ If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
180
192
  {{END_IF_BROWSER_INTERACTION}}
181
193
 
182
194
  ### Phase 4: Architecture Sync & Commit (SINGLE COMMIT)
@@ -269,25 +269,37 @@ If GATE:MISSING — send message to Reviewer (re-spawn if needed): "Write the '#
269
269
  **CP-3**: Tests pass, verdict is not NEEDS_FIXES.
270
270
 
271
271
  {{IF_BROWSER_INTERACTION}}
272
- ### Phase 5.5: Browser Verification (playwright-cli)
272
+ ### Phase 5.5: Browser Verification (playwright-cli) — MANDATORY
273
273
 
274
- Verify UI behavior using `playwright-cli` before committing. This phase is best-effort failures are logged but do NOT block the commit.
274
+ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without actually running playwright-cli.
275
275
 
276
- 1. Start dev server if needed: `{{BROWSER_SETUP_COMMAND}}`
277
- 2. Use `playwright-cli` to open `{{BROWSER_URL}}`, then verify:
276
+ **Startup**:
277
+ 1. Check if port is already in use: `lsof -ti:3001 | xargs kill -9 2>/dev/null || true`
278
+ 2. Start dev server: `{{BROWSER_SETUP_COMMAND}}`
279
+ 3. Wait for server to be ready: poll `{{BROWSER_URL}}` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
280
+ 4. If the page requires authentication, use playwright-cli to register a test user and log in first
281
+
282
+ **Verification**:
283
+ 5. Use `playwright-cli` to open `{{BROWSER_URL}}`, then verify:
278
284
  {{BROWSER_VERIFY_STEPS}}
279
- 3. Take a final screenshot for evidence.
280
- 4. Close browser and stop dev server.
281
- 5. Append results to `context-snapshot.md`:
285
+ 6. Take a final screenshot for evidence
286
+
287
+ **Cleanup (REQUIRED you started it, you stop it)**:
288
+ 7. Stop the dev server process you started in step 2 (kill the process)
289
+ 8. Verify port is released: `lsof -ti:3001 | xargs kill -9 2>/dev/null || true`
290
+
291
+ **Reporting**:
292
+ 9. Append results to `context-snapshot.md`:
282
293
  ```
283
294
  ## Browser Verification
284
295
  URL: {{BROWSER_URL}}
285
296
  Steps executed: [list]
286
297
  Screenshot: [path]
287
298
  Result: PASS / FAIL (reason)
299
+ Server cleanup: confirmed
288
300
  ```
289
301
 
290
- If any step fails, log the failure and continue. Do NOT retry browser verification.
302
+ If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
291
303
  {{END_IF_BROWSER_INTERACTION}}
292
304
 
293
305
  ### Phase 6: Architecture Sync & Commit (SINGLE COMMIT)
@@ -367,25 +367,37 @@ If GATE:MISSING — send message to Reviewer (re-spawn if needed): "Write the '#
367
367
  **CP-3**: Integration tests pass, verdict is not NEEDS_FIXES.
368
368
 
369
369
  {{IF_BROWSER_INTERACTION}}
370
- ### Phase 5.5: Browser Verification (playwright-cli)
370
+ ### Phase 5.5: Browser Verification (playwright-cli) — MANDATORY
371
371
 
372
- Verify UI behavior using `playwright-cli` before committing. This phase is best-effort failures are logged but do NOT block the commit.
372
+ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without actually running playwright-cli.
373
373
 
374
- 1. Start dev server if needed: `{{BROWSER_SETUP_COMMAND}}`
375
- 2. Use `playwright-cli` to open `{{BROWSER_URL}}`, then verify:
374
+ **Startup**:
375
+ 1. Check if port is already in use: `lsof -ti:3001 | xargs kill -9 2>/dev/null || true`
376
+ 2. Start dev server: `{{BROWSER_SETUP_COMMAND}}`
377
+ 3. Wait for server to be ready: poll `{{BROWSER_URL}}` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
378
+ 4. If the page requires authentication, use playwright-cli to register a test user and log in first
379
+
380
+ **Verification**:
381
+ 5. Use `playwright-cli` to open `{{BROWSER_URL}}`, then verify:
376
382
  {{BROWSER_VERIFY_STEPS}}
377
- 3. Take a final screenshot for evidence.
378
- 4. Close browser and stop dev server.
379
- 5. Append results to `context-snapshot.md`:
383
+ 6. Take a final screenshot for evidence
384
+
385
+ **Cleanup (REQUIRED you started it, you stop it)**:
386
+ 7. Stop the dev server process you started in step 2 (kill the process)
387
+ 8. Verify port is released: `lsof -ti:3001 | xargs kill -9 2>/dev/null || true`
388
+
389
+ **Reporting**:
390
+ 9. Append results to `context-snapshot.md`:
380
391
  ```
381
392
  ## Browser Verification
382
393
  URL: {{BROWSER_URL}}
383
394
  Steps executed: [list]
384
395
  Screenshot: [path]
385
396
  Result: PASS / FAIL (reason)
397
+ Server cleanup: confirmed
386
398
  ```
387
399
 
388
- If any step fails, log the failure and continue. Do NOT retry browser verification.
400
+ If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
389
401
  {{END_IF_BROWSER_INTERACTION}}
390
402
 
391
403
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.144",
2
+ "version": "1.0.147",
3
3
  "skills": {
4
4
  "prizm-kit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -224,6 +224,13 @@
224
224
  "category": "Custom-skill",
225
225
  "hasAssets": false,
226
226
  "hasScripts": false
227
+ },
228
+ "recovery-workflow": {
229
+ "description": "Recover and resume interrupted feature pipeline sessions. Detects partial work from failed or interrupted sessions and resumes from the correct phase.",
230
+ "tier": "companion",
231
+ "category": "Custom-skill",
232
+ "hasAssets": false,
233
+ "hasScripts": false
227
234
  }
228
235
  },
229
236
  "suites": {
@@ -0,0 +1,428 @@
1
+ ---
2
+ name: "recovery-workflow"
3
+ description: "Recover and resume interrupted feature pipeline sessions. Detects partial work (code changes, spec/plan artifacts, branch state, test results) from failed or interrupted sessions and intelligently resumes from the correct phase — instead of discarding everything and restarting from scratch. Use this skill whenever a pipeline session fails mid-execution, times out, gets interrupted, or when the user wants to salvage partial work from a failed feature. Trigger on: 'recover feature', 'resume session', 'continue from where it stopped', 'salvage partial work', 'fix failed feature', 'session interrupted', 'recover F-XXX', 'resume F-XXX', 'continue F-XXX', 'feature stuck', 'pick up where it left off', 'session timed out', 'don't want to restart from scratch', 'token limit exceeded'. (project)"
4
+ ---
5
+
6
+ # Recovery Workflow
7
+
8
+ Intelligently recover and resume interrupted feature pipeline sessions. Instead of discarding partial work and restarting from scratch (which wastes tokens and time), this skill inspects the actual filesystem state — code changes, artifacts, git branches, test results — and continues from the right point.
9
+
10
+ ## When to Use
11
+
12
+ User says:
13
+ - "Recover F-007", "Resume F-007", "Continue F-007 from where it stopped"
14
+ - "Feature session failed/timed out, salvage the work"
15
+ - "Pipeline interrupted, don't want to restart from scratch"
16
+ - "F-003 is stuck, pick up where it left off"
17
+ - "Session timed out but there's code already written"
18
+
19
+ **Do NOT use when:**
20
+ - User wants a clean retry from scratch → `retry-feature.sh` or `reset-feature.sh --clean --run`
21
+ - Pipeline is still running normally → `dev-pipeline-launcher` (Intent B: Check Status)
22
+ - User wants to fix a specific bug → `bug-fix-workflow`
23
+ - Feature never started (no state at all) → `dev-pipeline-launcher` (Intent E: Retry)
24
+
25
+ ## Why This Skill Exists
26
+
27
+ Current retry tools have a fundamental limitation:
28
+
29
+ | Tool | What it does | What's lost |
30
+ |------|-------------|-------------|
31
+ | `retry-feature.sh` | Cleans all artifacts, resets status, spawns fresh session | All code changes, spec, plan — everything |
32
+ | `reset-feature.sh --clean --run` | Even more thorough clean + retry | Same, plus session history |
33
+ | `run.sh --resume-phase N` | Tells new session "start from phase N" via prompt | Better, but doesn't inspect actual worktree state — the new AI session has no memory of what was done |
34
+
35
+ The gap: when a session is interrupted mid-implementation, the **worktree already has real code changes** — possibly a spec, plan, partial or complete implementation, even passing tests. Discarding all of this wastes significant tokens and time. This skill bridges that gap by inspecting the actual filesystem state and resuming intelligently within the current interactive session.
36
+
37
+ ---
38
+
39
+ ## Overview
40
+
41
+ ```
42
+ recovery-workflow <feature-id>
43
+
44
+ ├── Phase 0: Identify feature and gather context
45
+
46
+ ├── Phase 1: Detect state (artifacts, code, tests, branch)
47
+
48
+ ├── Phase 2: Diagnose and present recovery options
49
+
50
+ └── Phase 3: Execute recovery (chain remaining prizmkit skills)
51
+ ```
52
+
53
+ ## Input
54
+
55
+ | Input | Required | Example |
56
+ |-------|----------|---------|
57
+ | Feature ID | Yes | `F-007` |
58
+ | Feature list path | No (default: `feature-list.json`) | `my-features.json` |
59
+
60
+ ---
61
+
62
+ ## Phase 0: Feature Identification
63
+
64
+ **Goal**: Locate the feature and its context.
65
+
66
+ 1. **Parse feature ID** from user input (e.g., "recover F-007", "resume F-007")
67
+ 2. **Read feature-list.json** to get feature metadata:
68
+ ```bash
69
+ python3 -c "
70
+ import json, re
71
+ with open('feature-list.json') as f:
72
+ data = json.load(f)
73
+ for feat in data.get('features', []):
74
+ if feat.get('id') == 'F-007':
75
+ print(json.dumps(feat, indent=2))
76
+ "
77
+ ```
78
+ 3. **Compute feature slug** (same algorithm the pipeline uses):
79
+ ```python
80
+ # F-007 + "User Authentication" → "007-user-authentication"
81
+ numeric = feature_id.replace('F-', '').zfill(3)
82
+ slug = re.sub(r'[^a-z0-9\s-]', '', title.lower())
83
+ slug = re.sub(r'[\s]+', '-', slug.strip()).strip('-')
84
+ slug = f"{numeric}-{slug}"
85
+ ```
86
+
87
+ If the feature is not found, ask the user for the correct ID or file path.
88
+
89
+ ---
90
+
91
+ ## Phase 1: State Detection
92
+
93
+ **Goal**: Build a complete picture of what exists from the interrupted session.
94
+
95
+ Run the detection script:
96
+
97
+ ```bash
98
+ python3 ${SKILL_DIR}/scripts/detect-recovery-state.py \
99
+ --feature-id <FEATURE_ID> \
100
+ --feature-list feature-list.json
101
+ ```
102
+
103
+ The script scans four categories of state and outputs structured JSON:
104
+
105
+ ### 1.1 Pipeline State
106
+ - `dev-pipeline/state/features/{FEATURE_ID}/status.json` — pipeline tracking status, retry count, session history
107
+ - Last session directory — path for log inspection
108
+
109
+ ### 1.2 PrizmKit Artifacts
110
+ - `.prizmkit/specs/{slug}/spec.md` — specification generated?
111
+ - `.prizmkit/specs/{slug}/plan.md` — plan generated?
112
+ - If plan.md exists: count completed vs total tasks (look for `[x]` vs `[ ]` checkboxes)
113
+
114
+ ### 1.3 Git State
115
+ - **Feature branch**: does `feat/{slug}` exist?
116
+ - **Current branch**: are we already on the feature branch?
117
+ - **Uncommitted changes**: `git diff --stat` (working tree changes)
118
+ - **Staged changes**: `git diff --cached --stat`
119
+ - **Commits ahead of main**: `git log main..HEAD --oneline`
120
+
121
+ ### 1.4 Code Changes
122
+ - Number of modified/added/deleted files
123
+ - Which directories were touched
124
+ - Whether test files were created/modified
125
+
126
+ ### Detection Output
127
+
128
+ The script produces a structured JSON report:
129
+
130
+ ```json
131
+ {
132
+ "feature_id": "F-007",
133
+ "feature_title": "User Authentication",
134
+ "feature_slug": "007-user-authentication",
135
+ "pipeline": {
136
+ "status": "failed",
137
+ "retry_count": 1,
138
+ "last_session_id": "F-007-20260329101500",
139
+ "last_session_dir": "dev-pipeline/state/features/F-007/sessions/F-007-20260329101500"
140
+ },
141
+ "artifacts": {
142
+ "spec_exists": true,
143
+ "plan_exists": true,
144
+ "spec_path": ".prizmkit/specs/007-user-authentication/spec.md",
145
+ "plan_path": ".prizmkit/specs/007-user-authentication/plan.md",
146
+ "plan_tasks_total": 5,
147
+ "plan_tasks_completed": 3
148
+ },
149
+ "git": {
150
+ "feature_branch": "feat/007-user-authentication",
151
+ "branch_exists": true,
152
+ "on_feature_branch": true,
153
+ "uncommitted_files": 8,
154
+ "staged_files": 0,
155
+ "commits_ahead_of_main": 0
156
+ },
157
+ "code": {
158
+ "files_modified": 6,
159
+ "files_added": 4,
160
+ "test_files_touched": 2,
161
+ "directories_touched": ["src/auth/", "src/middleware/", "tests/"]
162
+ },
163
+ "recovery": {
164
+ "recommended_action": "continue_implementation",
165
+ "recommended_phase": "implement",
166
+ "reason": "spec and plan exist, 3/5 plan tasks completed, code changes present",
167
+ "remaining_work": "2 tasks + review + commit"
168
+ }
169
+ }
170
+ ```
171
+
172
+ **CHECKPOINT CP-RW-0**: State detection complete, structured report available.
173
+
174
+ ---
175
+
176
+ ## Phase 2: Diagnosis & Recovery Options
177
+
178
+ **Goal**: Present findings to user and agree on a recovery strategy.
179
+
180
+ ### 2.1 Present Discovery Summary
181
+
182
+ Read the detection script output and show the user a clear summary:
183
+
184
+ ```
185
+ ═══════════════════════════════════════════════════
186
+ Recovery Report: F-007 — User Authentication
187
+ ═══════════════════════════════════════════════════
188
+
189
+ Pipeline Status: FAILED (retry 1)
190
+ Feature Branch: feat/007-user-authentication
191
+
192
+ Artifacts Found:
193
+ ✓ spec.md (specification)
194
+ ✓ plan.md (5 tasks, 3 completed)
195
+ ✓ Code changes (10 files — 6 modified, 4 new)
196
+ ✓ Test files (2 found)
197
+
198
+ Recommended: Continue implementation
199
+ Remaining: 2 tasks + review + commit
200
+ ═══════════════════════════════════════════════════
201
+ ```
202
+
203
+ ### 2.2 Run Tests (if code changes exist)
204
+
205
+ Before presenting options, run the project's test suite to understand the health of existing code:
206
+
207
+ ```bash
208
+ # detect test command from .prizmkit/config.json or package.json
209
+ npm test # or the appropriate test command
210
+ ```
211
+
212
+ Include test results in the summary:
213
+ - How many tests pass/fail
214
+ - If there are failures — which tests and why
215
+
216
+ ### 2.3 Present Recovery Options
217
+
218
+ Based on the detected state, offer the appropriate options. The most common scenarios:
219
+
220
+ **Scenario A: Implementation in progress** (spec + plan + code changes)
221
+ Most common case — session died mid-implementation.
222
+
223
+ | Option | Description |
224
+ |--------|-------------|
225
+ | **(a) Smart Resume** (recommended) | Fix test failures if any, complete remaining plan tasks, then review → commit |
226
+ | **(b) Review & Commit Only** | If implementation looks complete (all tasks done, tests passing), skip to code review |
227
+ | **(c) Re-implement from Plan** | Keep spec and plan, discard code changes, re-implement. Useful if partial code is low quality |
228
+ | **(d) Clean Restart** | Discard everything, start from scratch (equivalent to `reset-feature.sh --clean --run`) |
229
+
230
+ **Scenario B: Only planning artifacts** (spec/plan exist, no code changes)
231
+ Session died during or after planning, before implementation started.
232
+
233
+ | Option | Description |
234
+ |--------|-------------|
235
+ | **(a) Start Implementation** (recommended) | Plan is ready, begin implementing |
236
+ | **(b) Re-plan** | Keep spec, regenerate plan (useful if plan was incomplete or poor) |
237
+ | **(c) Clean Restart** | Discard everything and start over |
238
+
239
+ **Scenario C: Code changes but no artifacts** (git changes, no .prizmkit artifacts)
240
+ Unusual — might be manual work or artifacts were cleaned.
241
+
242
+ | Option | Description |
243
+ |--------|-------------|
244
+ | **(a) Adopt & Continue** | Read existing code, proceed to review + commit |
245
+ | **(b) Clean Restart** | Discard and restart |
246
+
247
+ **Scenario D: Already committed** (feature branch has commits ahead of main)
248
+ Session completed implementation but didn't finish post-commit steps.
249
+
250
+ | Option | Description |
251
+ |--------|-------------|
252
+ | **(a) Complete Post-commit** (recommended) | Run code review, retrospective, handle merge |
253
+ | **(b) Clean Restart** | Discard and restart |
254
+
255
+ **Scenario E: No state found** (no branch, no artifacts, no code)
256
+ Feature was never executed or was fully cleaned.
257
+
258
+ | Option | Description |
259
+ |--------|-------------|
260
+ | **(a) Run from Scratch** | Suggest using `dev-pipeline-launcher` (Intent E) or `retry-feature.sh` instead |
261
+
262
+ Ask the user to choose. Default to the recommended option.
263
+
264
+ **CHECKPOINT CP-RW-1**: User confirmed recovery strategy.
265
+
266
+ ---
267
+
268
+ ## Phase 3: Execute Recovery
269
+
270
+ **Goal**: Execute the remaining phases based on the user's chosen strategy.
271
+
272
+ ### 3.0 Branch Setup
273
+
274
+ Ensure we're on the right branch:
275
+
276
+ ```bash
277
+ # If feature branch exists but we're not on it:
278
+ git checkout feat/{slug}
279
+
280
+ # If no feature branch but code changes are on current branch:
281
+ git checkout -b feat/{slug}
282
+ ```
283
+
284
+ ### 3.1 Read Existing Artifacts
285
+
286
+ Load the artifacts that were preserved, so we have full context:
287
+
288
+ 1. **Read spec.md** (if exists) — understand what this feature should do
289
+ 2. **Read plan.md** (if exists) — understand the implementation plan and task breakdown
290
+ 3. **Scan code changes** — `git diff main --stat` to understand what was already done
291
+ 4. **Read relevant `.prizm-docs/`** — load project context (L0 root, relevant L1)
292
+
293
+ This step replaces the Phase 0-2 that the pipeline would have done (project bootstrap, context building, planning). The artifacts serve as memory of the interrupted session.
294
+
295
+ ### 3.2 Fix Existing Test Failures (if any)
296
+
297
+ If tests are failing from the interrupted session, fix them first. Continuing implementation on top of a broken state compounds errors.
298
+
299
+ 1. Read the test failure output
300
+ 2. Read the relevant source files
301
+ 3. Apply minimal fixes to make tests pass
302
+ 4. Verify: run test suite → all green
303
+
304
+ If failures cannot be fixed after 3 attempts, ask the user whether to:
305
+ - Continue anyway (if failures are unrelated to this feature)
306
+ - Switch to "Re-implement from Plan" strategy
307
+ - Clean restart
308
+
309
+ ### 3.3 Continue Implementation
310
+
311
+ If plan.md exists with incomplete tasks:
312
+
313
+ 1. **Read plan.md** and identify remaining tasks — look for unchecked `[ ]` items in the Tasks section
314
+ 2. **For each remaining task**:
315
+ - Read the task description from plan.md
316
+ - Implement the change following the same approach as `/prizmkit-implement`
317
+ - Run full test suite after each task
318
+ - Mark task as complete `[x]` in plan.md
319
+ 3. After all tasks complete → full test suite must pass
320
+
321
+ If no plan.md exists but code changes are present (Scenario C), skip this step — go straight to review.
322
+
323
+ ### 3.4 Code Review
324
+
325
+ Invoke `/prizmkit-code-review` on all changes:
326
+ - Review scope: all files modified/added relative to main branch (`git diff main --stat`)
327
+ - If review returns NEEDS_FIXES → apply fixes, re-review (max 2 rounds)
328
+ - If review returns PASS or PASS_WITH_WARNINGS → proceed
329
+
330
+ ### 3.5 User Verification
331
+
332
+ Ask user: "Implementation recovered and reviewed. Verify before committing?"
333
+ - **(a) Run the app** → suggest and start the dev server
334
+ - **(b) Run a specific command** → execute what the user specifies
335
+ - **(c) Skip** → proceed directly to commit
336
+
337
+ If user reports issues: return to 3.3 (max 2 additional rounds).
338
+
339
+ ### 3.6 Retrospective & Commit
340
+
341
+ 1. **Invoke `/prizmkit-retrospective`** — update `.prizm-docs/` for structural changes caused by this feature
342
+ 2. **Invoke `/prizmkit-committer`** — commit with `feat(<scope>): <description>` prefix
343
+ 3. **Update pipeline state** — mark feature as completed:
344
+ ```bash
345
+ python3 dev-pipeline/scripts/update-feature-status.py \
346
+ --feature-list feature-list.json \
347
+ --state-dir dev-pipeline/state \
348
+ --feature-id <FEATURE_ID> \
349
+ --action complete
350
+ ```
351
+
352
+ ### 3.7 Post-Recovery
353
+
354
+ 1. **Ask merge preference**:
355
+ - (a) Merge to main and delete feature branch
356
+ - (b) Keep feature branch for PR review
357
+ - (c) Decide later
358
+
359
+ 2. **Report completion**:
360
+ ```
361
+ Recovery complete: F-007 — User Authentication
362
+
363
+ Recovered from: implementation phase (3/5 tasks done)
364
+ Completed: 2 remaining tasks + test fixes + review + commit
365
+ Work preserved: spec.md, plan.md, 3 completed tasks
366
+
367
+ Next: Check pipeline status for remaining features,
368
+ or invoke /recovery-workflow for another failed feature.
369
+ ```
370
+
371
+ 3. **Suggest next steps**:
372
+ - If other features are pending/failed → offer to check pipeline status or recover another
373
+ - If this was the last feature → suggest integration verification
374
+
375
+ **CHECKPOINT CP-RW-2**: Feature recovered, committed, and pipeline state updated.
376
+
377
+ ---
378
+
379
+ ## Error Handling
380
+
381
+ | Scenario | Action |
382
+ |----------|--------|
383
+ | Feature ID not found in feature-list.json | Ask user for correct ID or file path |
384
+ | No state to recover (never executed) | Inform user, suggest `dev-pipeline-launcher` instead |
385
+ | Feature branch was deleted | Check if changes exist elsewhere; if not, offer clean start |
386
+ | Merge conflicts on feature branch | Show conflicts, help resolve before continuing |
387
+ | Test failures unfixable after 3 attempts | Escalate to user, offer alternative strategies |
388
+ | Code quality too low to salvage | Recommend re-implement from plan |
389
+ | Pipeline state file inconsistent with git | Trust git and filesystem over status.json — git is the ground truth |
390
+ | detection script fails | Fall back to manual detection (Phase 1 checks can be done individually via bash) |
391
+
392
+ ---
393
+
394
+ ## Relationship to Other Skills
395
+
396
+ | Skill | Relationship |
397
+ |-------|-------------|
398
+ | `dev-pipeline-launcher` | **Upstream** — launcher can suggest this skill when features fail; this skill updates pipeline state after recovery |
399
+ | `retry-feature.sh` | **Alternative** — full clean retry; this skill is the smart alternative |
400
+ | `reset-feature.sh` | **Used by option (d)** — clean restart delegates to reset-feature |
401
+ | `/prizmkit-implement` | **Approach shared in Phase 3.3** — same implementation methodology |
402
+ | `/prizmkit-code-review` | **Called in Phase 3.4** — reviews recovered code |
403
+ | `/prizmkit-committer` | **Called in Phase 3.6** — commits the result |
404
+ | `/prizmkit-retrospective` | **Called in Phase 3.6** — updates .prizm-docs/ |
405
+ | `feature-workflow` | **Sibling** — feature-workflow builds new features; this skill salvages interrupted ones |
406
+
407
+ ---
408
+
409
+ ## Comparison with Existing Recovery Tools
410
+
411
+ | Dimension | recovery-workflow | retry-feature.sh | reset + run | run.sh --resume-phase |
412
+ |-----------|------------------|-------------------|-------------|----------------------|
413
+ | State inspection | Deep (git + artifacts + tests) | None | None | None |
414
+ | Preserves code | Yes | No (cleans) | No (cleans) | Prompt-level only |
415
+ | Preserves artifacts | Yes (selective) | No | No | Prompt-level |
416
+ | Runs tests first | Yes | No | No | No |
417
+ | User choice | Multiple strategies | Always clean | Always clean | Fixed phase number |
418
+ | Execution mode | Interactive (in-session) | Spawns new session | Spawns new session | Spawns new session |
419
+ | Token efficiency | High (skips done work) | Low (redo all) | Low (redo all) | Medium |
420
+ | Context continuity | Full (reads existing artifacts) | None (fresh AI session) | None | Partial |
421
+
422
+ ## Output
423
+
424
+ - Recovered and completed feature implementation
425
+ - Updated pipeline state (feature marked complete)
426
+ - Git commit with `feat(<scope>):` prefix
427
+ - Updated `.prizm-docs/` (via retrospective)
428
+ - Recovery summary showing what was salvaged vs what was re-done
@@ -0,0 +1,483 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ detect-recovery-state.py — Scan filesystem for partial work from an interrupted
4
+ feature pipeline session and output a structured recovery report.
5
+
6
+ Checks four state categories:
7
+ 1. Pipeline state (dev-pipeline/state/)
8
+ 2. PrizmKit artifacts (.prizmkit/specs/{slug}/)
9
+ 3. Git state (branches, uncommitted changes, commits ahead)
10
+ 4. Code changes (file counts, directories touched)
11
+
12
+ Does NOT run tests — that's left to the skill so the user sees output in real time.
13
+
14
+ Usage:
15
+ python3 detect-recovery-state.py --feature-id F-007 --feature-list feature-list.json
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import re
22
+ import subprocess
23
+ import sys
24
+
25
+
26
+ def run_git(args, cwd=None):
27
+ """Run a git command and return stdout, or empty string on failure."""
28
+ try:
29
+ result = subprocess.run(
30
+ ["git"] + args,
31
+ capture_output=True,
32
+ text=True,
33
+ cwd=cwd,
34
+ timeout=10,
35
+ )
36
+ return result.stdout.strip()
37
+ except (subprocess.SubprocessError, FileNotFoundError):
38
+ return ""
39
+
40
+
41
+ def compute_slug(feature_id, title):
42
+ """Compute feature slug using the same algorithm as the pipeline."""
43
+ numeric = feature_id.replace("F-", "").replace("f-", "").zfill(3)
44
+ slug = title.lower()
45
+ slug = re.sub(r"[^a-z0-9\s-]", "", slug)
46
+ slug = re.sub(r"[\s]+", "-", slug.strip())
47
+ slug = re.sub(r"-+", "-", slug).strip("-") or "feature"
48
+ return f"{numeric}-{slug}"
49
+
50
+
51
+ def find_feature(feature_list_path, feature_id):
52
+ """Find feature in feature-list.json."""
53
+ with open(feature_list_path) as f:
54
+ data = json.load(f)
55
+ for feat in data.get("features", []):
56
+ if feat.get("id") == feature_id:
57
+ return feat
58
+ return None
59
+
60
+
61
+ def detect_pipeline_state(state_dir, feature_id):
62
+ """Check dev-pipeline/state/ for feature status."""
63
+ result = {
64
+ "status": "unknown",
65
+ "retry_count": 0,
66
+ "last_session_id": None,
67
+ "last_session_dir": None,
68
+ "has_state": False,
69
+ }
70
+
71
+ status_file = os.path.join(state_dir, "features", feature_id, "status.json")
72
+ if not os.path.isfile(status_file):
73
+ return result
74
+
75
+ try:
76
+ with open(status_file) as f:
77
+ status_data = json.load(f)
78
+ result["has_state"] = True
79
+ result["status"] = status_data.get("status", "unknown")
80
+ result["retry_count"] = status_data.get("retry_count", 0)
81
+
82
+ sessions = status_data.get("sessions", [])
83
+ if sessions:
84
+ last = sessions[-1]
85
+ sid = last.get("session_id", "")
86
+ result["last_session_id"] = sid
87
+ result["last_session_dir"] = os.path.join(
88
+ state_dir, "features", feature_id, "sessions", sid
89
+ )
90
+ except (json.JSONDecodeError, IOError):
91
+ pass
92
+
93
+ return result
94
+
95
+
96
+ def detect_artifacts(project_root, feature_slug):
97
+ """Check .prizmkit/specs/{slug}/ for planning artifacts."""
98
+ specs_dir = os.path.join(project_root, ".prizmkit", "specs", feature_slug)
99
+ result = {
100
+ "spec_exists": False,
101
+ "plan_exists": False,
102
+ "spec_path": None,
103
+ "plan_path": None,
104
+ "plan_tasks_total": 0,
105
+ "plan_tasks_completed": 0,
106
+ "other_artifacts": [],
107
+ }
108
+
109
+ if not os.path.isdir(specs_dir):
110
+ return result
111
+
112
+ spec_path = os.path.join(specs_dir, "spec.md")
113
+ plan_path = os.path.join(specs_dir, "plan.md")
114
+
115
+ if os.path.isfile(spec_path):
116
+ result["spec_exists"] = True
117
+ result["spec_path"] = os.path.relpath(spec_path, project_root)
118
+
119
+ if os.path.isfile(plan_path):
120
+ result["plan_exists"] = True
121
+ result["plan_path"] = os.path.relpath(plan_path, project_root)
122
+
123
+ # Count tasks in plan.md (look for checkbox pattern)
124
+ try:
125
+ with open(plan_path) as f:
126
+ content = f.read()
127
+ # Match both [x] and [ ] patterns (task checkboxes)
128
+ completed = len(re.findall(r"^\s*-\s*\[x\]", content, re.MULTILINE | re.IGNORECASE))
129
+ pending = len(re.findall(r"^\s*-\s*\[ \]", content, re.MULTILINE))
130
+ result["plan_tasks_total"] = completed + pending
131
+ result["plan_tasks_completed"] = completed
132
+ except IOError:
133
+ pass
134
+
135
+ # Check for other artifacts
136
+ try:
137
+ for name in os.listdir(specs_dir):
138
+ if name not in ("spec.md", "plan.md") and os.path.isfile(
139
+ os.path.join(specs_dir, name)
140
+ ):
141
+ result["other_artifacts"].append(name)
142
+ except IOError:
143
+ pass
144
+
145
+ return result
146
+
147
+
148
+ def detect_git_state(project_root, feature_slug):
149
+ """Check git for branch existence, uncommitted changes, commits ahead."""
150
+ result = {
151
+ "feature_branch": f"feat/{feature_slug}",
152
+ "branch_exists": False,
153
+ "on_feature_branch": False,
154
+ "uncommitted_files": 0,
155
+ "staged_files": 0,
156
+ "commits_ahead_of_main": 0,
157
+ "current_branch": "",
158
+ }
159
+
160
+ # Current branch
161
+ current = run_git(["branch", "--show-current"], cwd=project_root)
162
+ result["current_branch"] = current
163
+
164
+ # Check if feature branch exists
165
+ branch_name = f"feat/{feature_slug}"
166
+ branches_output = run_git(["branch", "--list", branch_name], cwd=project_root)
167
+ if branches_output.strip():
168
+ result["branch_exists"] = True
169
+
170
+ # Also check without feat/ prefix (some pipelines use different naming)
171
+ if not result["branch_exists"]:
172
+ alt_branch = f"feature/{feature_slug}"
173
+ alt_output = run_git(["branch", "--list", alt_branch], cwd=project_root)
174
+ if alt_output.strip():
175
+ result["branch_exists"] = True
176
+ result["feature_branch"] = alt_branch
177
+
178
+ result["on_feature_branch"] = current == result["feature_branch"]
179
+
180
+ # Uncommitted changes (working tree)
181
+ diff_stat = run_git(["diff", "--stat"], cwd=project_root)
182
+ if diff_stat:
183
+ # Count "N files changed" from the summary line
184
+ lines = diff_stat.strip().split("\n")
185
+ result["uncommitted_files"] = max(0, len(lines) - 1) # exclude summary line
186
+
187
+ # Staged changes
188
+ staged_stat = run_git(["diff", "--cached", "--stat"], cwd=project_root)
189
+ if staged_stat:
190
+ lines = staged_stat.strip().split("\n")
191
+ result["staged_files"] = max(0, len(lines) - 1)
192
+
193
+ # Commits ahead of main
194
+ main_branch = "main"
195
+ # Try to detect default branch
196
+ for candidate in ["main", "master"]:
197
+ check = run_git(["rev-parse", "--verify", candidate], cwd=project_root)
198
+ if check:
199
+ main_branch = candidate
200
+ break
201
+
202
+ log_output = run_git(
203
+ ["log", f"{main_branch}..HEAD", "--oneline"], cwd=project_root
204
+ )
205
+ if log_output:
206
+ result["commits_ahead_of_main"] = len(log_output.strip().split("\n"))
207
+
208
+ return result
209
+
210
+
211
+ def detect_code_changes(project_root, main_branch="main"):
212
+ """Analyze code changes relative to main branch.
213
+
214
+ Filters out pipeline/config files that aren't source code — only counts
215
+ files that represent actual implementation work.
216
+ """
217
+ # Files/patterns that are pipeline artifacts, not implementation code
218
+ IGNORED_FILES = {
219
+ "feature-list.json",
220
+ "bug-fix-list.json",
221
+ "package-lock.json",
222
+ "yarn.lock",
223
+ "pnpm-lock.yaml",
224
+ }
225
+ IGNORED_PREFIXES = (
226
+ ".prizmkit/",
227
+ "dev-pipeline/state/",
228
+ ".prizm-docs/",
229
+ ".claude/",
230
+ ".codebuddy/",
231
+ )
232
+
233
+ def is_source_file(filepath):
234
+ """Return True if this file represents implementation code."""
235
+ basename = os.path.basename(filepath)
236
+ if basename in IGNORED_FILES:
237
+ return False
238
+ for prefix in IGNORED_PREFIXES:
239
+ if filepath.startswith(prefix):
240
+ return False
241
+ return True
242
+
243
+ result = {
244
+ "files_modified": 0,
245
+ "files_added": 0,
246
+ "files_deleted": 0,
247
+ "test_files_touched": 0,
248
+ "directories_touched": [],
249
+ "has_changes": False,
250
+ }
251
+
252
+ # Get diff stat relative to main
253
+ diff_output = run_git(
254
+ ["diff", main_branch, "--name-status"], cwd=project_root
255
+ )
256
+
257
+ # Also include uncommitted changes
258
+ uncommitted = run_git(["diff", "--name-status"], cwd=project_root)
259
+ untracked = run_git(
260
+ ["ls-files", "--others", "--exclude-standard"], cwd=project_root
261
+ )
262
+
263
+ all_files = set()
264
+ dirs = set()
265
+
266
+ if diff_output:
267
+ for line in diff_output.strip().split("\n"):
268
+ if not line.strip():
269
+ continue
270
+ parts = line.split("\t", 1)
271
+ if len(parts) < 2:
272
+ continue
273
+ status, filepath = parts[0], parts[1]
274
+ if not is_source_file(filepath):
275
+ continue
276
+ all_files.add(filepath)
277
+ if status.startswith("M"):
278
+ result["files_modified"] += 1
279
+ elif status.startswith("A"):
280
+ result["files_added"] += 1
281
+ elif status.startswith("D"):
282
+ result["files_deleted"] += 1
283
+
284
+ if uncommitted:
285
+ for line in uncommitted.strip().split("\n"):
286
+ if not line.strip():
287
+ continue
288
+ parts = line.split("\t", 1)
289
+ if len(parts) >= 2:
290
+ filepath = parts[1]
291
+ if not is_source_file(filepath):
292
+ continue
293
+ all_files.add(filepath)
294
+ result["files_modified"] += 1
295
+
296
+ if untracked:
297
+ for filepath in untracked.strip().split("\n"):
298
+ if filepath.strip() and is_source_file(filepath.strip()):
299
+ all_files.add(filepath.strip())
300
+ result["files_added"] += 1
301
+
302
+ # Analyze file set
303
+ test_patterns = re.compile(
304
+ r"(test|spec|__tests__|\.test\.|\.spec\.)", re.IGNORECASE
305
+ )
306
+ for filepath in all_files:
307
+ if test_patterns.search(filepath):
308
+ result["test_files_touched"] += 1
309
+ parent = os.path.dirname(filepath)
310
+ if parent:
311
+ # Keep first two levels for readability
312
+ parts = parent.split(os.sep)
313
+ dirs.add(os.sep.join(parts[:2]) + "/")
314
+
315
+ result["directories_touched"] = sorted(dirs)
316
+ result["has_changes"] = len(all_files) > 0
317
+
318
+ return result
319
+
320
+
321
+ def determine_recovery(artifacts, git_state, code_changes, pipeline):
322
+ """Recommend a recovery action based on detected state."""
323
+ has_spec = artifacts["spec_exists"]
324
+ has_plan = artifacts["plan_exists"]
325
+ has_code = code_changes["has_changes"]
326
+ has_commits = git_state["commits_ahead_of_main"] > 0
327
+ tasks_total = artifacts["plan_tasks_total"]
328
+ tasks_done = artifacts["plan_tasks_completed"]
329
+
330
+ # Scenario D: Already committed
331
+ if has_commits:
332
+ return {
333
+ "recommended_action": "complete_post_commit",
334
+ "recommended_phase": "review",
335
+ "scenario": "D",
336
+ "reason": f"{git_state['commits_ahead_of_main']} commit(s) ahead of main — implementation may be complete",
337
+ "remaining_work": "code review + retrospective + merge",
338
+ }
339
+
340
+ # Scenario A: Implementation in progress
341
+ if has_plan and has_code:
342
+ if tasks_total > 0 and tasks_done == tasks_total:
343
+ return {
344
+ "recommended_action": "review_and_commit",
345
+ "recommended_phase": "review",
346
+ "scenario": "A",
347
+ "reason": f"all {tasks_total} plan tasks completed, code changes present",
348
+ "remaining_work": "code review + commit",
349
+ }
350
+ else:
351
+ tasks_remaining = tasks_total - tasks_done if tasks_total > 0 else "unknown"
352
+ return {
353
+ "recommended_action": "continue_implementation",
354
+ "recommended_phase": "implement",
355
+ "scenario": "A",
356
+ "reason": f"spec and plan exist, {tasks_done}/{tasks_total} tasks completed, code changes present",
357
+ "remaining_work": f"{tasks_remaining} tasks + review + commit",
358
+ }
359
+
360
+ # Scenario B: Only planning artifacts
361
+ if has_spec or has_plan:
362
+ if has_plan:
363
+ return {
364
+ "recommended_action": "start_implementation",
365
+ "recommended_phase": "implement",
366
+ "scenario": "B",
367
+ "reason": "spec and plan exist, no code changes yet",
368
+ "remaining_work": f"{tasks_total} tasks + review + commit",
369
+ }
370
+ else:
371
+ return {
372
+ "recommended_action": "generate_plan",
373
+ "recommended_phase": "plan",
374
+ "scenario": "B",
375
+ "reason": "spec exists but no plan — session interrupted during planning",
376
+ "remaining_work": "plan + implement + review + commit",
377
+ }
378
+
379
+ # Scenario C: Code changes but no artifacts
380
+ if has_code:
381
+ return {
382
+ "recommended_action": "adopt_and_continue",
383
+ "recommended_phase": "review",
384
+ "scenario": "C",
385
+ "reason": "code changes found but no prizmkit artifacts — possible manual work or artifacts cleaned",
386
+ "remaining_work": "review + commit",
387
+ }
388
+
389
+ # Scenario E: Nothing found
390
+ return {
391
+ "recommended_action": "start_fresh",
392
+ "recommended_phase": "none",
393
+ "scenario": "E",
394
+ "reason": "no artifacts, no code changes, no commits — feature was never executed or fully cleaned",
395
+ "remaining_work": "full pipeline run",
396
+ }
397
+
398
+
399
+ def main():
400
+ parser = argparse.ArgumentParser(
401
+ description="Detect recovery state for an interrupted feature session"
402
+ )
403
+ parser.add_argument("--feature-id", required=True, help="Feature ID (e.g., F-007)")
404
+ parser.add_argument(
405
+ "--feature-list",
406
+ default="feature-list.json",
407
+ help="Path to feature-list.json (default: feature-list.json)",
408
+ )
409
+ parser.add_argument(
410
+ "--state-dir",
411
+ default=None,
412
+ help="Pipeline state directory (default: dev-pipeline/state)",
413
+ )
414
+ parser.add_argument(
415
+ "--project-root",
416
+ default=None,
417
+ help="Project root directory (default: auto-detect from git)",
418
+ )
419
+
420
+ args = parser.parse_args()
421
+
422
+ # Resolve project root
423
+ if args.project_root:
424
+ project_root = os.path.abspath(args.project_root)
425
+ else:
426
+ git_root = run_git(["rev-parse", "--show-toplevel"])
427
+ project_root = git_root if git_root else os.getcwd()
428
+
429
+ # Resolve state dir
430
+ state_dir = args.state_dir or os.path.join(project_root, "dev-pipeline", "state")
431
+
432
+ # Resolve feature list path
433
+ feature_list_path = args.feature_list
434
+ if not os.path.isabs(feature_list_path):
435
+ feature_list_path = os.path.join(project_root, feature_list_path)
436
+
437
+ # Find feature
438
+ if not os.path.isfile(feature_list_path):
439
+ print(
440
+ json.dumps({"error": f"Feature list not found: {feature_list_path}"}),
441
+ file=sys.stderr,
442
+ )
443
+ sys.exit(1)
444
+
445
+ feature = find_feature(feature_list_path, args.feature_id)
446
+ if not feature:
447
+ print(
448
+ json.dumps(
449
+ {
450
+ "error": f"Feature {args.feature_id} not found in {feature_list_path}"
451
+ }
452
+ ),
453
+ file=sys.stderr,
454
+ )
455
+ sys.exit(1)
456
+
457
+ title = feature.get("title", "untitled")
458
+ slug = compute_slug(args.feature_id, title)
459
+
460
+ # Run all detection phases
461
+ pipeline = detect_pipeline_state(state_dir, args.feature_id)
462
+ artifacts = detect_artifacts(project_root, slug)
463
+ git_state = detect_git_state(project_root, slug)
464
+ code_changes = detect_code_changes(project_root)
465
+ recovery = determine_recovery(artifacts, git_state, code_changes, pipeline)
466
+
467
+ # Build report
468
+ report = {
469
+ "feature_id": args.feature_id,
470
+ "feature_title": title,
471
+ "feature_slug": slug,
472
+ "pipeline": pipeline,
473
+ "artifacts": artifacts,
474
+ "git": git_state,
475
+ "code": code_changes,
476
+ "recovery": recovery,
477
+ }
478
+
479
+ print(json.dumps(report, indent=2))
480
+
481
+
482
+ if __name__ == "__main__":
483
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.0.144",
3
+ "version": "1.0.147",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {