prizmkit 1.1.27 → 1.1.30
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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/run-bugfix.sh +3 -8
- package/bundled/dev-pipeline/run-feature.sh +3 -8
- package/bundled/dev-pipeline/run-recovery.sh +691 -0
- package/bundled/dev-pipeline/run-refactor.sh +3 -8
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +759 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/recovery-workflow/SKILL.md +72 -9
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +111 -42
- package/package.json +1 -1
|
@@ -20,6 +20,29 @@ User says:
|
|
|
20
20
|
- User wants a clean restart → use the original workflow skill directly (`/feature-workflow`, `/bug-fix-workflow`, `/refactor-workflow`)
|
|
21
21
|
- Nothing was ever started → use the original workflow skill
|
|
22
22
|
|
|
23
|
+
## Pipeline Recovery (Recommended)
|
|
24
|
+
|
|
25
|
+
**IMPORTANT**: In Phase 1.3, you MUST present the user with a choice between pipeline recovery (`run-recovery.sh`) and interactive recovery. **NEVER skip this choice. NEVER decide for the user.** The pipeline approach is recommended because it generates a comprehensive bootstrap prompt that explicitly lists every remaining phase with full instructions, ensuring the AI completes the full workflow — not just the implementation part.
|
|
26
|
+
|
|
27
|
+
Pipeline commands (for reference — Phase 1.3 will present these as a selectable option):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
./dev-pipeline/run-recovery.sh # Auto-detect and recover
|
|
31
|
+
./dev-pipeline/run-recovery.sh detect # Detection report only
|
|
32
|
+
./dev-pipeline/run-recovery.sh run --dry-run # Generate prompt, don't execute
|
|
33
|
+
./dev-pipeline/run-recovery.sh run --yes # Skip confirmation
|
|
34
|
+
./dev-pipeline/run-recovery.sh run --model <model> # Override AI model
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### When to use pipeline vs interactive recovery
|
|
38
|
+
|
|
39
|
+
| Scenario | Approach |
|
|
40
|
+
|----------|----------|
|
|
41
|
+
| Pipeline session timed out / crashed | `./run-recovery.sh` — autonomous, completes all phases reliably |
|
|
42
|
+
| Interactive session interrupted | This skill (`/recovery-workflow`) — for in-session interactive use |
|
|
43
|
+
| Want to inspect before recovering | `./run-recovery.sh detect` or `./run-recovery.sh run --dry-run` |
|
|
44
|
+
| Daemon/scripted use | `./run-recovery.sh run --yes` — no user confirmation needed |
|
|
45
|
+
|
|
23
46
|
## Supported Workflows
|
|
24
47
|
|
|
25
48
|
| Workflow | Branch Pattern | Key Artifacts |
|
|
@@ -42,10 +65,10 @@ recovery-workflow
|
|
|
42
65
|
│ ├── Based on artifact presence → infer current phase
|
|
43
66
|
│ └── No match → reject and guide user
|
|
44
67
|
│
|
|
45
|
-
├── Phase 1: Report + user
|
|
68
|
+
├── Phase 1: Report + user choice
|
|
46
69
|
│ ├── Display detection results
|
|
47
70
|
│ ├── If code changes exist → run test suite
|
|
48
|
-
│ └── User
|
|
71
|
+
│ └── User chooses: run-recovery.sh (recommended) | interactive | start fresh
|
|
49
72
|
│
|
|
50
73
|
└── Phase 2: Execute remaining steps
|
|
51
74
|
├── Read target workflow's SKILL.md
|
|
@@ -137,18 +160,55 @@ Include test results in the report:
|
|
|
137
160
|
- How many tests pass/fail
|
|
138
161
|
- If failures exist — which tests and why
|
|
139
162
|
|
|
140
|
-
### 1.3 Ask User to
|
|
163
|
+
### 1.3 Ask User to Choose Recovery Approach
|
|
164
|
+
|
|
165
|
+
**User choice required (mandatory)** — Use `AskUserQuestion` to present interactive selectable options. **NEVER skip this step. NEVER choose for the user.**
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
AskUserQuestion:
|
|
169
|
+
question: "Interrupted {workflow_type} detected at Phase {N} ({phase_name}). How would you like to recover?"
|
|
170
|
+
header: "Recovery"
|
|
171
|
+
options:
|
|
172
|
+
- label: "Run recovery script (Recommended)"
|
|
173
|
+
description: "Execute ./dev-pipeline/run-recovery.sh — autonomously completes ALL remaining phases (review, commit, merge, etc.) via a dedicated AI session with explicit phase instructions"
|
|
174
|
+
- label: "Copy command and run manually"
|
|
175
|
+
description: "I'll give you the exact shell command to paste into your terminal — you run it yourself outside this session"
|
|
176
|
+
- label: "Resume interactively in this session"
|
|
177
|
+
description: "Continue from Phase {N} within this conversation — more control, but may not complete all phases if session is interrupted again"
|
|
178
|
+
- label: "Start fresh"
|
|
179
|
+
description: "Discard interrupted work and restart the original workflow from scratch"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**If "Run recovery script"** → Execute the pipeline recovery:
|
|
183
|
+
```bash
|
|
184
|
+
./dev-pipeline/run-recovery.sh
|
|
185
|
+
```
|
|
186
|
+
The script handles everything: detection, confirmation, prompt generation, session spawn, and post-session validation. **End this skill after launching the script** — do not proceed to Phase 2.
|
|
141
187
|
|
|
188
|
+
**If "Copy command and run manually"** → Output the command for the user to copy and run in their own terminal:
|
|
142
189
|
```
|
|
143
|
-
|
|
190
|
+
To recover, run this command in your project root:
|
|
191
|
+
|
|
192
|
+
./dev-pipeline/run-recovery.sh
|
|
193
|
+
|
|
194
|
+
Or with options:
|
|
195
|
+
./dev-pipeline/run-recovery.sh run --dry-run # Preview the recovery prompt first
|
|
196
|
+
./dev-pipeline/run-recovery.sh run --yes # Skip confirmation
|
|
197
|
+
./dev-pipeline/run-recovery.sh run --model <model> # Specify AI model
|
|
144
198
|
```
|
|
199
|
+
**End this skill** — do not proceed to Phase 2. The user will run the command themselves.
|
|
200
|
+
|
|
201
|
+
**If "Resume interactively"** → Continue to Phase 2 below (execute remaining steps in this session).
|
|
145
202
|
|
|
146
|
-
If the
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
203
|
+
**If "Start fresh"** → Suggest the appropriate original workflow skill:
|
|
204
|
+
- bug-fix-workflow → `/bug-fix-workflow`
|
|
205
|
+
- feature-workflow → `/feature-workflow`
|
|
206
|
+
- refactor-workflow → `/refactor-workflow`
|
|
207
|
+
End this skill.
|
|
150
208
|
|
|
151
|
-
**
|
|
209
|
+
**NEVER proceed to Phase 2 without explicit user selection via `AskUserQuestion`. Do NOT render options as plain text — the user must be able to click/select.**
|
|
210
|
+
|
|
211
|
+
**CHECKPOINT CP-REC-1**: User chose recovery approach.
|
|
152
212
|
|
|
153
213
|
---
|
|
154
214
|
|
|
@@ -181,6 +241,8 @@ Phase inference table:
|
|
|
181
241
|
| All docs + review passed | Phase 6: User Verification | Ask user to verify the fix works |
|
|
182
242
|
| All docs + committed | Phase 7: Merge Decision | Ask merge vs keep branch |
|
|
183
243
|
|
|
244
|
+
**Note**: Bug-fix Phases 1-3 (Diagnosis, Triage, Reproduce) collapse to Phase 1 for detection purposes because these phases don't produce persistent artifacts. If interrupted during these phases, recovery restarts from Phase 1 (diagnosis), which re-derives understanding from available inputs (bug description, code) without interactive Q&A.
|
|
245
|
+
|
|
184
246
|
**Execution for each remaining phase**: Follow the bug-fix-workflow SKILL.md instructions exactly. Call the same prizmkit sub-commands (`/prizmkit-code-review`, `/prizmkit-committer`) at the same points.
|
|
185
247
|
|
|
186
248
|
**Special handling**:
|
|
@@ -259,6 +321,7 @@ Recovery complete.
|
|
|
259
321
|
| `feature-pipeline-launcher` | **Called in Phase 2.2** — launches or checks pipeline status for feature recovery |
|
|
260
322
|
| `reset-feature.sh --clean --run` | **Alternative** — full clean retry for pipeline failures; this skill is the smart interactive alternative |
|
|
261
323
|
| `reset-bug.sh --clean --run` | **Alternative** — full clean retry for bugfix pipeline failures |
|
|
324
|
+
| `run-recovery.sh` | **Pipeline counterpart** — shell-driven recovery that generates bootstrap prompt and spawns AI CLI session for autonomous completion |
|
|
262
325
|
| `/prizmkit-code-review` | **Called in Phase 2.1** — reviews recovered bug-fix code |
|
|
263
326
|
| `/prizmkit-committer` | **Called in Phase 2.1** — commits the recovered result |
|
|
264
327
|
|
|
@@ -114,6 +114,58 @@ def detect_workflow_type(project_root):
|
|
|
114
114
|
return (None, None)
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
def detect_other_workflows(project_root, primary_type):
|
|
118
|
+
"""Scan for other interrupted workflow signals beyond the primary match.
|
|
119
|
+
|
|
120
|
+
Returns a list of workflow type strings that also have signals present,
|
|
121
|
+
excluding the primary_type already detected.
|
|
122
|
+
"""
|
|
123
|
+
others = []
|
|
124
|
+
branch = run_git(["branch", "--show-current"], cwd=project_root)
|
|
125
|
+
|
|
126
|
+
# Bug-fix signals
|
|
127
|
+
if primary_type != "bug-fix-workflow":
|
|
128
|
+
if branch.startswith("fix/"):
|
|
129
|
+
others.append("bug-fix-workflow")
|
|
130
|
+
else:
|
|
131
|
+
bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix")
|
|
132
|
+
if os.path.isdir(bugfix_dir):
|
|
133
|
+
bug_ids = [
|
|
134
|
+
d for d in os.listdir(bugfix_dir)
|
|
135
|
+
if os.path.isdir(os.path.join(bugfix_dir, d))
|
|
136
|
+
]
|
|
137
|
+
if bug_ids:
|
|
138
|
+
others.append("bug-fix-workflow")
|
|
139
|
+
|
|
140
|
+
# Refactor signals
|
|
141
|
+
if primary_type != "refactor-workflow":
|
|
142
|
+
if branch.startswith("refactor/"):
|
|
143
|
+
others.append("refactor-workflow")
|
|
144
|
+
else:
|
|
145
|
+
for path in [
|
|
146
|
+
os.path.join(project_root, ".prizmkit", "plans", "refactor-list.json"),
|
|
147
|
+
os.path.join(project_root, "refactor-list.json"),
|
|
148
|
+
]:
|
|
149
|
+
if os.path.isfile(path):
|
|
150
|
+
others.append("refactor-workflow")
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
# Feature signals
|
|
154
|
+
if primary_type != "feature-workflow":
|
|
155
|
+
if branch.startswith("feat/"):
|
|
156
|
+
others.append("feature-workflow")
|
|
157
|
+
else:
|
|
158
|
+
for path in [
|
|
159
|
+
os.path.join(project_root, ".prizmkit", "plans", "feature-list.json"),
|
|
160
|
+
os.path.join(project_root, "feature-list.json"),
|
|
161
|
+
]:
|
|
162
|
+
if os.path.isfile(path):
|
|
163
|
+
others.append("feature-workflow")
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
return others
|
|
167
|
+
|
|
168
|
+
|
|
117
169
|
# ---------------------------------------------------------------------------
|
|
118
170
|
# Phase inference — one function per workflow
|
|
119
171
|
# ---------------------------------------------------------------------------
|
|
@@ -285,18 +337,21 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
285
337
|
|
|
286
338
|
Filters out pipeline/config files that aren't source code — only counts
|
|
287
339
|
files that represent actual implementation work.
|
|
340
|
+
|
|
341
|
+
Uses a file_statuses dict keyed by filepath to avoid double-counting
|
|
342
|
+
files that appear in both committed diff and uncommitted changes.
|
|
288
343
|
"""
|
|
289
344
|
IGNORED_FILES = {
|
|
290
|
-
|
|
291
|
-
".prizmkit/plans/bug-fix-list.json",
|
|
292
|
-
".prizmkit/plans/refactor-list.json",
|
|
345
|
+
# Basename-matched list files (root-level legacy paths)
|
|
293
346
|
"feature-list.json",
|
|
294
347
|
"bug-fix-list.json",
|
|
295
348
|
"refactor-list.json",
|
|
349
|
+
# Lock files
|
|
296
350
|
"package-lock.json",
|
|
297
351
|
"yarn.lock",
|
|
298
352
|
"pnpm-lock.yaml",
|
|
299
353
|
}
|
|
354
|
+
# Note: .prizmkit/plans/*.json paths are caught by IGNORED_PREFIXES below
|
|
300
355
|
IGNORED_PREFIXES = (
|
|
301
356
|
".prizmkit/",
|
|
302
357
|
".prizm-docs/",
|
|
@@ -314,29 +369,16 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
314
369
|
return False
|
|
315
370
|
return True
|
|
316
371
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"test_files_touched": 0,
|
|
322
|
-
"directories_touched": [],
|
|
323
|
-
"has_changes": False,
|
|
324
|
-
}
|
|
372
|
+
# Track unique file → status to avoid double-counting.
|
|
373
|
+
# Later sources (uncommitted, untracked) update the status if the file
|
|
374
|
+
# was already seen in a committed diff.
|
|
375
|
+
file_statuses = {} # filepath → "M" | "A" | "D"
|
|
325
376
|
|
|
326
|
-
# Diff relative to main
|
|
377
|
+
# Diff relative to main (committed changes on branch)
|
|
327
378
|
diff_output = run_git(
|
|
328
379
|
["diff", main_branch, "--name-status"], cwd=project_root
|
|
329
380
|
)
|
|
330
381
|
|
|
331
|
-
# Also include uncommitted changes
|
|
332
|
-
uncommitted = run_git(["diff", "--name-status"], cwd=project_root)
|
|
333
|
-
untracked = run_git(
|
|
334
|
-
["ls-files", "--others", "--exclude-standard"], cwd=project_root
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
all_files = set()
|
|
338
|
-
dirs = set()
|
|
339
|
-
|
|
340
382
|
if diff_output:
|
|
341
383
|
for line in diff_output.strip().split("\n"):
|
|
342
384
|
if not line.strip():
|
|
@@ -344,40 +386,61 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
344
386
|
parts = line.split("\t", 1)
|
|
345
387
|
if len(parts) < 2:
|
|
346
388
|
continue
|
|
347
|
-
status, filepath = parts[0], parts[1]
|
|
389
|
+
status, filepath = parts[0][0], parts[1] # first char of status
|
|
348
390
|
if not is_source_file(filepath):
|
|
349
391
|
continue
|
|
350
|
-
|
|
351
|
-
if status.startswith("M"):
|
|
352
|
-
result["files_modified"] += 1
|
|
353
|
-
elif status.startswith("A"):
|
|
354
|
-
result["files_added"] += 1
|
|
355
|
-
elif status.startswith("D"):
|
|
356
|
-
result["files_deleted"] += 1
|
|
392
|
+
file_statuses[filepath] = status
|
|
357
393
|
|
|
394
|
+
# Uncommitted working tree changes — update status for already-seen files
|
|
395
|
+
uncommitted = run_git(["diff", "--name-status"], cwd=project_root)
|
|
358
396
|
if uncommitted:
|
|
359
397
|
for line in uncommitted.strip().split("\n"):
|
|
360
398
|
if not line.strip():
|
|
361
399
|
continue
|
|
362
400
|
parts = line.split("\t", 1)
|
|
363
|
-
if len(parts)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
401
|
+
if len(parts) < 2:
|
|
402
|
+
continue
|
|
403
|
+
status, filepath = parts[0][0], parts[1]
|
|
404
|
+
if not is_source_file(filepath):
|
|
405
|
+
continue
|
|
406
|
+
if filepath not in file_statuses:
|
|
407
|
+
file_statuses[filepath] = "M" # uncommitted change = modified
|
|
408
|
+
# If already tracked from branch diff, keep the branch-level status
|
|
369
409
|
|
|
410
|
+
# Untracked files
|
|
411
|
+
untracked = run_git(
|
|
412
|
+
["ls-files", "--others", "--exclude-standard"], cwd=project_root
|
|
413
|
+
)
|
|
370
414
|
if untracked:
|
|
371
415
|
for filepath in untracked.strip().split("\n"):
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
416
|
+
filepath = filepath.strip()
|
|
417
|
+
if filepath and is_source_file(filepath):
|
|
418
|
+
if filepath not in file_statuses:
|
|
419
|
+
file_statuses[filepath] = "A" # untracked = added
|
|
420
|
+
|
|
421
|
+
# Count by status
|
|
422
|
+
result = {
|
|
423
|
+
"files_modified": 0,
|
|
424
|
+
"files_added": 0,
|
|
425
|
+
"files_deleted": 0,
|
|
426
|
+
"test_files_touched": 0,
|
|
427
|
+
"directories_touched": [],
|
|
428
|
+
"has_changes": False,
|
|
429
|
+
}
|
|
375
430
|
|
|
376
|
-
# Analyze file set
|
|
377
431
|
test_patterns = re.compile(
|
|
378
432
|
r"(test|spec|__tests__|\.test\.|\.spec\.)", re.IGNORECASE
|
|
379
433
|
)
|
|
380
|
-
|
|
434
|
+
dirs = set()
|
|
435
|
+
|
|
436
|
+
for filepath, status in file_statuses.items():
|
|
437
|
+
if status == "M":
|
|
438
|
+
result["files_modified"] += 1
|
|
439
|
+
elif status == "A":
|
|
440
|
+
result["files_added"] += 1
|
|
441
|
+
elif status == "D":
|
|
442
|
+
result["files_deleted"] += 1
|
|
443
|
+
|
|
381
444
|
if test_patterns.search(filepath):
|
|
382
445
|
result["test_files_touched"] += 1
|
|
383
446
|
parent = os.path.dirname(filepath)
|
|
@@ -386,7 +449,7 @@ def detect_code_changes(project_root, main_branch="main"):
|
|
|
386
449
|
dirs.add(os.sep.join(parts[:2]) + "/")
|
|
387
450
|
|
|
388
451
|
result["directories_touched"] = sorted(dirs)
|
|
389
|
-
result["has_changes"] = len(
|
|
452
|
+
result["has_changes"] = len(file_statuses) > 0
|
|
390
453
|
|
|
391
454
|
return result
|
|
392
455
|
|
|
@@ -428,7 +491,10 @@ def main():
|
|
|
428
491
|
),
|
|
429
492
|
}
|
|
430
493
|
print(json.dumps(report, indent=2))
|
|
431
|
-
sys.exit(
|
|
494
|
+
sys.exit(0)
|
|
495
|
+
|
|
496
|
+
# Check for other interrupted workflows (informational)
|
|
497
|
+
other_workflows = detect_other_workflows(project_root, workflow_type)
|
|
432
498
|
|
|
433
499
|
# Step 2: Collect git state and code changes once (shared across phase inference + report)
|
|
434
500
|
cached_branch = context.get("branch")
|
|
@@ -467,6 +533,9 @@ def main():
|
|
|
467
533
|
},
|
|
468
534
|
}
|
|
469
535
|
|
|
536
|
+
if other_workflows:
|
|
537
|
+
report["other_interrupted_workflows"] = other_workflows
|
|
538
|
+
|
|
470
539
|
print(json.dumps(report, indent=2))
|
|
471
540
|
|
|
472
541
|
|