prizmkit 1.1.152 → 1.1.154

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.
Files changed (44) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +100 -87
  3. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  4. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +14 -0
  5. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +192 -110
  6. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  7. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +3 -5
  8. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  9. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  10. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  11. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  12. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +19 -52
  14. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  15. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  16. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +10 -12
  17. package/bundled/dev-pipeline/scripts/update-bug-status.py +196 -67
  18. package/bundled/dev-pipeline/scripts/update-checkpoint.py +16 -3
  19. package/bundled/dev-pipeline/scripts/update-feature-status.py +45 -117
  20. package/bundled/dev-pipeline/scripts/update-refactor-status.py +45 -119
  21. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  22. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  23. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  24. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +148 -12
  25. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +0 -19
  26. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +113 -188
  27. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +211 -0
  28. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  29. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -5
  30. package/bundled/dev-pipeline/tests/test_unified_cli.py +544 -181
  31. package/bundled/skills/_metadata.json +1 -1
  32. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  33. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  34. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  35. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  36. package/bundled/skills/recovery-workflow/SKILL.md +7 -5
  37. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  38. package/bundled/skills/recovery-workflow/references/detection.md +3 -3
  39. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +1 -1
  40. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  41. package/bundled/templates/project-memory-template.md +19 -11
  42. package/package.json +1 -1
  43. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  44. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
@@ -1,767 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Generate a recovery bootstrap prompt from detection output.
3
-
4
- Reads the JSON output of detect-recovery-state.py, determines which workflow
5
- was interrupted and at which phase, then assembles a comprehensive bootstrap
6
- prompt that explicitly enumerates every remaining phase with full instructions.
7
-
8
- Unlike the feature/bugfix prompt generators that use template files, this script
9
- builds the prompt programmatically because recovery prompts vary dramatically
10
- by workflow type and phase.
11
-
12
- Usage:
13
- python3 generate-recovery-prompt.py \
14
- --detection-json <path> \
15
- --output <path> \
16
- [--project-root <path>] \
17
- [--session-id <id>]
18
- """
19
-
20
- import argparse
21
- import json
22
- import os
23
- import subprocess
24
- import sys
25
-
26
- from utils import load_json_file, setup_logging
27
-
28
-
29
- LOGGER = setup_logging("generate-recovery-prompt")
30
-
31
-
32
- # ============================================================
33
- # Phase instruction maps — one per workflow type
34
- #
35
- # Each phase maps to (name, instructions) where instructions are
36
- # adapted for autonomous (non-interactive) recovery mode.
37
- # These must be kept in sync with the corresponding SKILL.md files.
38
- # ============================================================
39
-
40
- BUGFIX_PHASES = {
41
- 0: (
42
- "Branch Setup",
43
- """\
44
- Check current branch. You should already be on a fix/* branch from the
45
- interrupted session. If so, continue on it and do not create an additional
46
- recovery branch. If somehow on main, create a new fix branch only as a
47
- last-resort fallback:
48
- ```bash
49
- git checkout -b fix/{bug_id}
50
- ```""",
51
- ),
52
- 1: (
53
- "Deep Bug Diagnosis",
54
- """\
55
- Read the bug description and all available artifacts to understand the bug.
56
- Since this is an autonomous recovery session, skip interactive Q&A.
57
- Use whatever information is available:
58
- - Read bug entry from `.prizmkit/plans/bug-fix-list.json` if bug ID is known
59
- - Read any existing artifacts in `.prizmkit/bugfix/{bug_id}/`
60
- - Read relevant source code and test files
61
- - Read `.prizmkit/prizm-docs/` for affected modules
62
-
63
- Produce a bug summary with: symptom, reproduction steps, expected behavior,
64
- affected files, and root cause hypothesis.""",
65
- ),
66
- 2: (
67
- "Triage",
68
- """\
69
- Locate affected code and identify root cause:
70
- 1. Read `.prizmkit/prizm-docs/root.prizm` then the relevant module indexes and detail docs for affected modules
71
- 2. Read files mentioned in the bug description or error/stack trace
72
- 3. Check `.prizmkit/prizm-docs/` TRAPS for known patterns
73
- 4. Classify: root cause (confirmed/suspected), blast radius, fix complexity
74
- 5. Log your diagnosis (no need to ask for user confirmation in autonomous mode)""",
75
- ),
76
- 3: (
77
- "Reproduce",
78
- """\
79
- Create a failing test that proves the bug exists:
80
- 1. Write a reproduction test: `<module>.test.ts` with test case `should handle <bug scenario>`
81
- 2. Run the test — confirm it FAILS (red)
82
- 3. If the bug is hard to reproduce automatically, write a best-effort test
83
- and proceed""",
84
- ),
85
- 4: (
86
- "Fix",
87
- """\
88
- Implement the minimal fix (red → green):
89
- 1. Read fix-plan.md if it exists for the planned approach
90
- 2. Change the minimum code to fix the root cause — do NOT refactor
91
- 3. Run the reproduction test — must PASS (green)
92
- 4. Run the full module test suite — must pass (no regressions)
93
- 5. If regressions occur, fix them (max 3 attempts)""",
94
- ),
95
- 5: (
96
- "Review",
97
- """\
98
- Verify fix quality:
99
- 1. Run `/prizmkit-code-review` with the current bugfix artifact directory.
100
- 2. Follow the skill's current review contract for review execution, repairs, verification, evidence, and handoff.
101
- 3. Do not invoke another review skill or add a second review path outside the skill contract.
102
- 4. Continue only after `review-report.md` has a valid last Final Result; preserve append-only progress for recovery.
103
- 5. A missing, incomplete, unsafe, or non-converged review produces NEEDS_FIXES rather than a synthesized pass.""",
104
- ),
105
- 6: (
106
- "User Verification",
107
- """\
108
- Since this is an autonomous recovery session, substitute automated verification
109
- for manual user testing:
110
- 1. Run the full test suite
111
- 2. Verify ALL tests pass
112
- 3. If tests fail, fix and retry (max 3 attempts)
113
- 4. Proceed to next phase once all tests are green""",
114
- ),
115
- 7: (
116
- "Commit & Merge",
117
- """\
118
- Commit the fix and finalize:
119
- 1. Run `/prizmkit-retrospective` (structural sync only — update file counts,
120
- interfaces, dependencies in .prizmkit/prizm-docs/)
121
- 2. Stage all changed files explicitly (NEVER use `git add -A` or `git add .`)
122
- 3. Run `/prizmkit-committer` with commit prefix `fix(<scope>): <description>`
123
- 4. Verify working tree is clean: `git status --short`
124
- 5. Write `fix-report.md` to `.prizmkit/bugfix/{bug_id}/fix-report.md` with:
125
- - Root cause summary
126
- - Fix description
127
- - Files changed
128
- - Test results""",
129
- ),
130
- }
131
-
132
- FEATURE_PHASES = {
133
- 1: (
134
- "Brainstorm",
135
- """\
136
- Since this is an autonomous recovery session, work with whatever context is
137
- available. Read existing project files, `.prizmkit/prizm-docs/`, and any user-provided
138
- materials to understand the requirements. Skip interactive Q&A.
139
- Produce a requirements summary if one doesn't already exist.""",
140
- ),
141
- 2: (
142
- "Plan",
143
- """\
144
- Invoke `/feature-planner` skill with the requirements summary to generate
145
- `.prizmkit/plans/feature-list.json`. Validate the output exists and contains
146
- properly structured features.""",
147
- ),
148
- 3: (
149
- "Launch",
150
- """\
151
- Invoke `/feature-pipeline-launcher` skill:
152
- - Input: path to `.prizmkit/plans/feature-list.json`
153
- - The launcher handles execution mode selection and prerequisites
154
- - Let the launcher present options and manage the pipeline start
155
-
156
- If `/feature-pipeline-launcher` is not available, run the pipeline directly:
157
- ```bash
158
- python3 ./.prizmkit/dev-pipeline/cli.py feature run .prizmkit/plans/feature-list.json
159
- ```""",
160
- ),
161
- 4: (
162
- "Monitor",
163
- """\
164
- Check pipeline status and report results:
165
- ```bash
166
- python3 .prizmkit/dev-pipeline/scripts/update-feature-status.py \\
167
- --feature-list .prizmkit/plans/feature-list.json \\
168
- --state-dir .prizmkit/state/features \\
169
- --action status
170
- ```
171
- Report completion status for each feature.""",
172
- ),
173
- }
174
-
175
- REFACTOR_PHASES = {
176
- 1: (
177
- "Brainstorm",
178
- """\
179
- Since this is an autonomous recovery session, work with whatever context is
180
- available. Read existing project files, `.prizmkit/prizm-docs/`, and any materials
181
- to understand the refactoring goals. Skip interactive Q&A.
182
- Produce a refactoring goals summary if one doesn't already exist.""",
183
- ),
184
- 2: (
185
- "Plan",
186
- """\
187
- Invoke `/refactor-planner` skill with the goals summary to generate
188
- `.prizmkit/plans/refactor-list.json`. Validate the output exists and contains
189
- properly structured refactor items.""",
190
- ),
191
- 3: (
192
- "Launch",
193
- """\
194
- Invoke `/refactor-pipeline-launcher` skill:
195
- - Input: path to `.prizmkit/plans/refactor-list.json`
196
- - The launcher handles execution mode selection and prerequisites
197
- - Let the launcher present options and manage the pipeline start
198
-
199
- If `/refactor-pipeline-launcher` is not available, run the pipeline directly:
200
- ```bash
201
- python3 ./.prizmkit/dev-pipeline/cli.py refactor run .prizmkit/plans/refactor-list.json
202
- ```""",
203
- ),
204
- 4: (
205
- "Monitor",
206
- """\
207
- Check pipeline status and report results:
208
- ```bash
209
- python3 .prizmkit/dev-pipeline/scripts/update-refactor-status.py \\
210
- --refactor-list .prizmkit/plans/refactor-list.json \\
211
- --state-dir .prizmkit/state/refactor \\
212
- --action status
213
- ```
214
- Report completion status for each refactor item.""",
215
- ),
216
- }
217
-
218
- # Maps workflow_type to (phase_map, all_phases_ordered)
219
- WORKFLOW_REGISTRY = {
220
- "bug-fix-workflow": (BUGFIX_PHASES, [0, 1, 2, 3, 4, 5, 6, 7]),
221
- "feature-workflow": (FEATURE_PHASES, [1, 2, 3, 4]),
222
- "refactor-workflow": (REFACTOR_PHASES, [1, 2, 3, 4]),
223
- }
224
-
225
-
226
- # ============================================================
227
- # Artifact reading
228
- # ============================================================
229
-
230
- def read_file_safe(path, max_chars=8000):
231
- """Read a file, truncate if too large. Returns content or None."""
232
- if not os.path.isfile(path):
233
- return None
234
- try:
235
- with open(path, "r", encoding="utf-8") as f:
236
- content = f.read()
237
- if len(content) > max_chars:
238
- content = content[:max_chars] + "\n\n... (truncated)"
239
- return content
240
- except (IOError, UnicodeDecodeError):
241
- return None
242
-
243
-
244
- def read_bugfix_artifacts(project_root, bug_id):
245
- """Read existing bug fix artifacts for context injection."""
246
- if not bug_id:
247
- return {}
248
- bugfix_dir = os.path.join(project_root, ".prizmkit", "bugfix", bug_id)
249
- artifacts = {}
250
- for name in [
251
- "fix-plan.md", "spec.md", "plan.md",
252
- "context-snapshot.md", "fix-report.md",
253
- ]:
254
- path = os.path.join(bugfix_dir, name)
255
- content = read_file_safe(path)
256
- if content:
257
- artifacts[name] = content
258
- return artifacts
259
-
260
-
261
- def read_feature_artifacts(project_root):
262
- """Read existing feature workflow artifacts."""
263
- artifacts = {}
264
- # Feature list
265
- for location in [
266
- os.path.join(project_root, ".prizmkit", "plans", "feature-list.json"),
267
- os.path.join(project_root, "feature-list.json"),
268
- ]:
269
- content = read_file_safe(location)
270
- if content:
271
- artifacts["feature-list.json"] = content
272
- break
273
- return artifacts
274
-
275
-
276
- def read_refactor_artifacts(project_root):
277
- """Read existing refactor workflow artifacts."""
278
- artifacts = {}
279
- for location in [
280
- os.path.join(project_root, ".prizmkit", "plans", "refactor-list.json"),
281
- os.path.join(project_root, "refactor-list.json"),
282
- ]:
283
- content = read_file_safe(location)
284
- if content:
285
- artifacts["refactor-list.json"] = content
286
- break
287
- return artifacts
288
-
289
-
290
- def get_code_diff_summary(project_root, main_branch="main"):
291
- """Get a summary of code changes for context."""
292
- try:
293
- result = subprocess.run(
294
- ["git", "diff", main_branch, "--stat"],
295
- capture_output=True, text=True, cwd=project_root, timeout=10,
296
- )
297
- if result.stdout.strip():
298
- return result.stdout.strip()
299
- except (subprocess.SubprocessError, FileNotFoundError):
300
- pass
301
- return "(no diff available)"
302
-
303
-
304
- def read_bug_description(project_root, bug_id):
305
- """Try to read bug description from bug-fix-list.json."""
306
- if not bug_id:
307
- return None
308
- for location in [
309
- os.path.join(project_root, ".prizmkit", "plans", "bug-fix-list.json"),
310
- os.path.join(project_root, "bug-fix-list.json"),
311
- ]:
312
- if os.path.isfile(location):
313
- try:
314
- with open(location, "r", encoding="utf-8") as f:
315
- data = json.load(f)
316
- for bug in data.get("bugs", []):
317
- if bug.get("id") == bug_id:
318
- return bug
319
- except (json.JSONDecodeError, IOError):
320
- pass
321
- return None
322
-
323
-
324
- # ============================================================
325
- # Prompt assembly
326
- # ============================================================
327
-
328
- def get_remaining_phases(workflow_type, current_phase):
329
- """Get list of remaining phase numbers (inclusive of current)."""
330
- phase_map, all_phases = WORKFLOW_REGISTRY.get(workflow_type, ({}, []))
331
- remaining = [p for p in all_phases if p >= current_phase]
332
- return remaining, phase_map
333
-
334
-
335
- def format_artifact_section(artifacts):
336
- """Format artifacts dict as markdown sections."""
337
- if not artifacts:
338
- return "(no artifacts found from previous session)"
339
- sections = []
340
- for name, content in sorted(artifacts.items()):
341
- sections.append(
342
- "### {name}\n\n```\n{content}\n```".format(
343
- name=name, content=content,
344
- )
345
- )
346
- return "\n\n".join(sections)
347
-
348
-
349
- def build_bugfix_prompt(detection, project_root):
350
- """Build recovery prompt for bug-fix-workflow."""
351
- context = detection.get("context", {})
352
- bug_id = context.get("bug_id", "UNKNOWN")
353
- branch = context.get("branch", "unknown")
354
- phase = detection.get("phase", 1)
355
- phase_name = detection.get("phase_name", "Unknown")
356
- git_state = detection.get("git", {})
357
- code_state = detection.get("code", {})
358
- recovery = detection.get("recovery", {})
359
-
360
- # Read artifacts
361
- artifacts = read_bugfix_artifacts(project_root, bug_id)
362
- bug_desc = read_bug_description(project_root, bug_id)
363
- diff_summary = get_code_diff_summary(project_root, "main")
364
-
365
- # Get remaining phases
366
- remaining, phase_map = get_remaining_phases("bug-fix-workflow", phase)
367
-
368
- # Build prompt
369
- lines = []
370
- lines.append("# Recovery Session — Bug Fix Workflow")
371
- lines.append("")
372
- lines.append("## Context")
373
- lines.append("")
374
- lines.append("You are RECOVERING an interrupted bug-fix-workflow session.")
375
- lines.append("")
376
- lines.append("- **Bug ID**: {}".format(bug_id))
377
- lines.append("- **Branch**: {}".format(branch))
378
- lines.append("- **Interrupted at**: Phase {} — {}".format(phase, phase_name))
379
- lines.append("- **Remaining work**: {}".format(recovery.get("remaining_work", "unknown")))
380
- lines.append("")
381
-
382
- lines.append("## CRITICAL RULES")
383
- lines.append("")
384
- lines.append("1. You MUST complete ALL remaining phases listed below — do NOT stop after implementation")
385
- lines.append("2. Execute phases in ORDER. Do NOT skip any phase.")
386
- lines.append("3. After the LAST phase, output a recovery summary.")
387
- lines.append("4. This is a NON-INTERACTIVE autonomous session — proceed without asking for user input.")
388
- lines.append("5. Use `/prizmkit-code-review`, `/prizmkit-committer`, `/prizmkit-retrospective` as specified in each phase.")
389
- lines.append("6. When staging files for commit, always use explicit file names — NEVER use `git add -A` or `git add .`.")
390
- lines.append("")
391
-
392
- # Bug description (if available)
393
- if bug_desc:
394
- lines.append("## Bug Description (from bug-fix-list.json)")
395
- lines.append("")
396
- lines.append("- **ID**: {}".format(bug_desc.get("id", bug_id)))
397
- lines.append("- **Title**: {}".format(bug_desc.get("title", "(untitled)")))
398
- lines.append("- **Description**: {}".format(bug_desc.get("description", "(none)")))
399
- lines.append("- **Severity**: {}".format(bug_desc.get("severity", "(unset)")))
400
- if bug_desc.get("acceptance_criteria"):
401
- lines.append("- **Acceptance Criteria**:")
402
- for ac in bug_desc["acceptance_criteria"]:
403
- lines.append(" - {}".format(ac))
404
- lines.append("")
405
-
406
- # Git state
407
- lines.append("## Git State")
408
- lines.append("")
409
- lines.append("- **Branch**: {}".format(branch))
410
- lines.append("- **Commits ahead of main**: {}".format(
411
- git_state.get("commits_ahead_of_main", 0)))
412
- lines.append("- **Uncommitted files**: {}".format(
413
- git_state.get("uncommitted_files", 0)))
414
- lines.append("- **Staged files**: {}".format(
415
- git_state.get("staged_files", 0)))
416
- lines.append("")
417
-
418
- # Code changes summary
419
- if code_state.get("has_changes"):
420
- lines.append("## Code Changes (from interrupted session)")
421
- lines.append("")
422
- lines.append("- Files modified: {}".format(code_state.get("files_modified", 0)))
423
- lines.append("- Files added: {}".format(code_state.get("files_added", 0)))
424
- lines.append("- Files deleted: {}".format(code_state.get("files_deleted", 0)))
425
- lines.append("- Test files touched: {}".format(code_state.get("test_files_touched", 0)))
426
- lines.append("- Directories touched: {}".format(
427
- ", ".join(code_state.get("directories_touched", [])) or "(none)"))
428
- lines.append("")
429
- lines.append("### Diff Summary")
430
- lines.append("")
431
- lines.append("```")
432
- lines.append(diff_summary)
433
- lines.append("```")
434
- lines.append("")
435
-
436
- # Existing artifacts
437
- if artifacts:
438
- lines.append("## Existing Artifacts (from interrupted session)")
439
- lines.append("")
440
- lines.append(format_artifact_section(artifacts))
441
- lines.append("")
442
-
443
- # Remaining phases
444
- lines.append("---")
445
- lines.append("")
446
- lines.append("## Remaining Phases — Execute ALL of these in order")
447
- lines.append("")
448
-
449
- for i, phase_num in enumerate(remaining):
450
- name, instructions = phase_map.get(phase_num, ("Unknown", ""))
451
- # Substitute {bug_id} in instructions
452
- instructions = instructions.replace("{bug_id}", bug_id)
453
-
454
- label = "CURRENT PHASE" if i == 0 else ""
455
- if label:
456
- lines.append("### Phase {}: {} — {}".format(phase_num, name, label))
457
- else:
458
- lines.append("### Phase {}: {}".format(phase_num, name))
459
- lines.append("")
460
- lines.append(instructions)
461
- lines.append("")
462
-
463
- # Completion section
464
- lines.append("---")
465
- lines.append("")
466
- lines.append("## FINAL: Recovery Summary")
467
- lines.append("")
468
- lines.append("After ALL phases above are complete, output:")
469
- lines.append("")
470
- lines.append("```")
471
- lines.append("Recovery complete.")
472
- lines.append(" Workflow: bug-fix-workflow")
473
- lines.append(" Bug: {}".format(bug_id))
474
- lines.append(" Recovered from: Phase {} ({})".format(phase, phase_name))
475
- lines.append(" Completed: {}".format(
476
- " → ".join("Phase {} ({})".format(p, phase_map.get(p, ("?",))[0])
477
- for p in remaining)))
478
- lines.append(" Commit: <commit hash>")
479
- lines.append("```")
480
- lines.append("")
481
-
482
- # Reminders
483
- lines.append("## Reminders")
484
- lines.append("")
485
- lines.append("- All bug-fix artifacts go in `.prizmkit/bugfix/{}/`".format(bug_id))
486
- lines.append("- Commit with `fix(<scope>): <description>` prefix")
487
- lines.append("- Do NOT ask for user input — this is autonomous")
488
- lines.append("- Do NOT stop before completing ALL remaining phases")
489
- lines.append("- `/prizmkit-committer` is MANDATORY — do NOT skip the commit phase")
490
- lines.append("- `/prizmkit-retrospective` is MANDATORY — do NOT skip the docs sync phase")
491
-
492
- return "\n".join(lines)
493
-
494
-
495
- def build_feature_prompt(detection, project_root):
496
- """Build recovery prompt for feature-workflow."""
497
- context = detection.get("context", {})
498
- branch = context.get("branch", "unknown")
499
- phase = detection.get("phase", 1)
500
- phase_name = detection.get("phase_name", "Unknown")
501
- recovery = detection.get("recovery", {})
502
-
503
- artifacts = read_feature_artifacts(project_root)
504
- remaining, phase_map = get_remaining_phases("feature-workflow", phase)
505
-
506
- lines = []
507
- lines.append("# Recovery Session — Feature Workflow")
508
- lines.append("")
509
- lines.append("## Context")
510
- lines.append("")
511
- lines.append("You are RECOVERING an interrupted feature-workflow session.")
512
- lines.append("")
513
- lines.append("- **Branch**: {}".format(branch))
514
- lines.append("- **Interrupted at**: Phase {} — {}".format(phase, phase_name))
515
- lines.append("- **Remaining work**: {}".format(recovery.get("remaining_work", "unknown")))
516
- lines.append("")
517
-
518
- lines.append("## CRITICAL RULES")
519
- lines.append("")
520
- lines.append("1. You MUST complete ALL remaining phases listed below — do NOT stop early")
521
- lines.append("2. Execute phases in ORDER. Do NOT skip any phase.")
522
- lines.append("3. After the LAST phase, output a recovery summary.")
523
- lines.append("4. This is a NON-INTERACTIVE autonomous session — proceed without asking for user input.")
524
- lines.append("")
525
-
526
- if artifacts:
527
- lines.append("## Existing Artifacts")
528
- lines.append("")
529
- lines.append(format_artifact_section(artifacts))
530
- lines.append("")
531
-
532
- lines.append("---")
533
- lines.append("")
534
- lines.append("## Remaining Phases — Execute ALL of these in order")
535
- lines.append("")
536
-
537
- for i, phase_num in enumerate(remaining):
538
- name, instructions = phase_map.get(phase_num, ("Unknown", ""))
539
- label = "CURRENT PHASE" if i == 0 else ""
540
- if label:
541
- lines.append("### Phase {}: {} — {}".format(phase_num, name, label))
542
- else:
543
- lines.append("### Phase {}: {}".format(phase_num, name))
544
- lines.append("")
545
- lines.append(instructions)
546
- lines.append("")
547
-
548
- lines.append("---")
549
- lines.append("")
550
- lines.append("## FINAL: Recovery Summary")
551
- lines.append("")
552
- lines.append("After ALL phases above are complete, output:")
553
- lines.append("")
554
- lines.append("```")
555
- lines.append("Recovery complete.")
556
- lines.append(" Workflow: feature-workflow")
557
- lines.append(" Recovered from: Phase {} ({})".format(phase, phase_name))
558
- lines.append(" Completed: {}".format(
559
- " → ".join("Phase {} ({})".format(p, phase_map.get(p, ("?",))[0])
560
- for p in remaining)))
561
- lines.append("```")
562
- lines.append("")
563
-
564
- lines.append("## Reminders")
565
- lines.append("")
566
- lines.append("- Use `/feature-pipeline-launcher` to start the pipeline (Phase 3)")
567
- lines.append("- Do NOT ask for user input — this is autonomous")
568
- lines.append("- Do NOT stop before completing ALL remaining phases")
569
-
570
- return "\n".join(lines)
571
-
572
-
573
- def build_refactor_prompt(detection, project_root):
574
- """Build recovery prompt for refactor-workflow."""
575
- context = detection.get("context", {})
576
- branch = context.get("branch", "unknown")
577
- phase = detection.get("phase", 1)
578
- phase_name = detection.get("phase_name", "Unknown")
579
- recovery = detection.get("recovery", {})
580
-
581
- artifacts = read_refactor_artifacts(project_root)
582
- remaining, phase_map = get_remaining_phases("refactor-workflow", phase)
583
-
584
- lines = []
585
- lines.append("# Recovery Session — Refactor Workflow")
586
- lines.append("")
587
- lines.append("## Context")
588
- lines.append("")
589
- lines.append("You are RECOVERING an interrupted refactor-workflow session.")
590
- lines.append("")
591
- lines.append("- **Branch**: {}".format(branch))
592
- lines.append("- **Interrupted at**: Phase {} — {}".format(phase, phase_name))
593
- lines.append("- **Remaining work**: {}".format(recovery.get("remaining_work", "unknown")))
594
- lines.append("")
595
-
596
- lines.append("## CRITICAL RULES")
597
- lines.append("")
598
- lines.append("1. You MUST complete ALL remaining phases listed below — do NOT stop early")
599
- lines.append("2. Execute phases in ORDER. Do NOT skip any phase.")
600
- lines.append("3. After the LAST phase, output a recovery summary.")
601
- lines.append("4. This is a NON-INTERACTIVE autonomous session — proceed without asking for user input.")
602
- lines.append("")
603
-
604
- if artifacts:
605
- lines.append("## Existing Artifacts")
606
- lines.append("")
607
- lines.append(format_artifact_section(artifacts))
608
- lines.append("")
609
-
610
- lines.append("---")
611
- lines.append("")
612
- lines.append("## Remaining Phases — Execute ALL of these in order")
613
- lines.append("")
614
-
615
- for i, phase_num in enumerate(remaining):
616
- name, instructions = phase_map.get(phase_num, ("Unknown", ""))
617
- label = "CURRENT PHASE" if i == 0 else ""
618
- if label:
619
- lines.append("### Phase {}: {} — {}".format(phase_num, name, label))
620
- else:
621
- lines.append("### Phase {}: {}".format(phase_num, name))
622
- lines.append("")
623
- lines.append(instructions)
624
- lines.append("")
625
-
626
- lines.append("---")
627
- lines.append("")
628
- lines.append("## FINAL: Recovery Summary")
629
- lines.append("")
630
- lines.append("After ALL phases above are complete, output:")
631
- lines.append("")
632
- lines.append("```")
633
- lines.append("Recovery complete.")
634
- lines.append(" Workflow: refactor-workflow")
635
- lines.append(" Recovered from: Phase {} ({})".format(phase, phase_name))
636
- lines.append(" Completed: {}".format(
637
- " → ".join("Phase {} ({})".format(p, phase_map.get(p, ("?",))[0])
638
- for p in remaining)))
639
- lines.append("```")
640
- lines.append("")
641
-
642
- lines.append("## Reminders")
643
- lines.append("")
644
- lines.append("- Use `/refactor-pipeline-launcher` to start the pipeline (Phase 3)")
645
- lines.append("- Do NOT ask for user input — this is autonomous")
646
- lines.append("- Do NOT stop before completing ALL remaining phases")
647
-
648
- return "\n".join(lines)
649
-
650
-
651
- # ============================================================
652
- # Main
653
- # ============================================================
654
-
655
- PROMPT_BUILDERS = {
656
- "bug-fix-workflow": build_bugfix_prompt,
657
- "feature-workflow": build_feature_prompt,
658
- "refactor-workflow": build_refactor_prompt,
659
- }
660
-
661
-
662
- def parse_args():
663
- parser = argparse.ArgumentParser(
664
- description="Generate a recovery bootstrap prompt from detection output",
665
- )
666
- parser.add_argument(
667
- "--detection-json",
668
- required=True,
669
- help="Path to JSON file with detect-recovery-state.py output",
670
- )
671
- parser.add_argument(
672
- "--output",
673
- required=True,
674
- help="Output path for the rendered prompt",
675
- )
676
- parser.add_argument(
677
- "--project-root",
678
- default=None,
679
- help="Project root directory (default: auto-detect from git)",
680
- )
681
- parser.add_argument(
682
- "--session-id",
683
- default=None,
684
- help="Session ID for this recovery session",
685
- )
686
- return parser.parse_args()
687
-
688
-
689
- def resolve_project_root(given_root):
690
- """Resolve project root from argument or git."""
691
- if given_root:
692
- return os.path.abspath(given_root)
693
- # Auto-detect across two layouts:
694
- # <project>/.prizmkit/dev-pipeline/scripts/ → up 3 levels
695
- # <repo>/dev-pipeline/scripts/ → up 2 levels
696
- script_dir = os.path.dirname(os.path.abspath(__file__))
697
- pipeline_dir = os.path.dirname(script_dir)
698
- pipeline_parent = os.path.dirname(pipeline_dir)
699
- if os.path.basename(pipeline_parent) == ".prizmkit":
700
- return os.path.dirname(pipeline_parent)
701
- return pipeline_parent
702
-
703
-
704
- def emit_failure(message):
705
- """Emit standardized failure JSON and exit."""
706
- print(json.dumps({"success": False, "error": message}, indent=2, ensure_ascii=False))
707
- sys.exit(1)
708
-
709
-
710
- def main():
711
- args = parse_args()
712
- project_root = resolve_project_root(args.project_root)
713
-
714
- # Load detection JSON
715
- detection, err = load_json_file(args.detection_json)
716
- if err:
717
- emit_failure("Detection JSON error: {}".format(err))
718
-
719
- if not detection.get("detected"):
720
- emit_failure("No interrupted workflow detected — nothing to recover")
721
-
722
- workflow_type = detection.get("workflow_type")
723
- if workflow_type not in PROMPT_BUILDERS:
724
- emit_failure("Unknown workflow type: {}".format(workflow_type))
725
-
726
- # Build prompt
727
- builder = PROMPT_BUILDERS[workflow_type]
728
- prompt_content = builder(detection, project_root)
729
-
730
- # Write output
731
- output_path = os.path.abspath(args.output)
732
- output_dir = os.path.dirname(output_path)
733
- if output_dir and not os.path.isdir(output_dir):
734
- os.makedirs(output_dir, exist_ok=True)
735
-
736
- try:
737
- with open(output_path, "w", encoding="utf-8") as f:
738
- f.write(prompt_content)
739
- except IOError as e:
740
- emit_failure("Cannot write output: {}".format(str(e)))
741
-
742
- # Success JSON
743
- phase = detection.get("phase", 0)
744
- phase_name = detection.get("phase_name", "Unknown")
745
- remaining, _ = get_remaining_phases(workflow_type, phase)
746
- output = {
747
- "success": True,
748
- "workflow_type": workflow_type,
749
- "resume_phase": phase,
750
- "resume_phase_name": phase_name,
751
- "remaining_phases": remaining,
752
- "prompt_path": output_path,
753
- }
754
- print(json.dumps(output, indent=2, ensure_ascii=False))
755
- sys.exit(0)
756
-
757
-
758
- if __name__ == "__main__":
759
- try:
760
- main()
761
- except KeyboardInterrupt:
762
- emit_failure("generate-recovery-prompt interrupted")
763
- except SystemExit:
764
- raise
765
- except Exception as exc:
766
- LOGGER.exception("Unhandled exception in generate-recovery-prompt")
767
- emit_failure("Unexpected error: {}".format(str(exc)))