prizmkit 1.1.1 → 1.1.4

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 (119) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/claude/agent-adapter.js +18 -0
  3. package/bundled/adapters/claude/command-adapter.js +1 -27
  4. package/bundled/agents/prizm-dev-team-critic.md +2 -0
  5. package/bundled/agents/prizm-dev-team-dev.md +2 -0
  6. package/bundled/agents/prizm-dev-team-reviewer.md +2 -0
  7. package/bundled/dev-pipeline/README.md +63 -63
  8. package/bundled/dev-pipeline/assets/feature-list-example.json +1 -1
  9. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
  10. package/bundled/dev-pipeline/{launch-daemon.sh → launch-feature-daemon.sh} +33 -33
  11. package/bundled/dev-pipeline/launch-refactor-daemon.sh +454 -0
  12. package/bundled/dev-pipeline/lib/branch.sh +1 -1
  13. package/bundled/dev-pipeline/reset-feature.sh +3 -3
  14. package/bundled/dev-pipeline/reset-refactor.sh +312 -0
  15. package/bundled/dev-pipeline/{retry-bug.sh → retry-bugfix.sh} +47 -59
  16. package/bundled/dev-pipeline/retry-feature.sh +41 -54
  17. package/bundled/dev-pipeline/retry-refactor.sh +358 -0
  18. package/bundled/dev-pipeline/run-bugfix.sh +41 -0
  19. package/bundled/dev-pipeline/{run.sh → run-feature.sh} +64 -31
  20. package/bundled/dev-pipeline/run-refactor.sh +787 -0
  21. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +398 -10
  22. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +124 -0
  23. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +419 -0
  24. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +393 -0
  25. package/bundled/dev-pipeline/scripts/update-refactor-status.py +726 -0
  26. package/bundled/dev-pipeline/templates/agent-prompts/critic-code-challenge.md +13 -0
  27. package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +7 -0
  28. package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +7 -0
  29. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +27 -0
  30. package/bundled/dev-pipeline/templates/agent-prompts/dev-resume.md +5 -0
  31. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +5 -0
  32. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +12 -0
  33. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +33 -2
  34. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +13 -9
  35. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +16 -12
  36. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +22 -4
  37. package/bundled/dev-pipeline/templates/feature-list-schema.json +1 -1
  38. package/bundled/dev-pipeline/templates/refactor-list-schema.json +159 -0
  39. package/bundled/dev-pipeline/templates/sections/ac-verification-checklist.md +13 -0
  40. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +36 -0
  41. package/bundled/dev-pipeline/templates/sections/failure-log-check.md +2 -1
  42. package/bundled/dev-pipeline/templates/sections/feature-context.md +1 -1
  43. package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +11 -7
  44. package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +11 -7
  45. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +5 -1
  46. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +3 -0
  47. package/bundled/dev-pipeline/templates/sections/phase-commit.md +3 -0
  48. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +3 -0
  49. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +3 -0
  50. package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +11 -10
  51. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +12 -10
  52. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +11 -9
  53. package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +3 -0
  54. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +10 -10
  55. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +12 -16
  56. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +3 -0
  57. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +3 -0
  58. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +3 -0
  59. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +11 -13
  60. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -20
  61. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -0
  62. package/bundled/dev-pipeline/templates/sections/phase0-init.md +3 -0
  63. package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +3 -0
  64. package/bundled/dev-pipeline/templates/sections/resume-header.md +4 -1
  65. package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +75 -0
  66. package/bundled/rules/prizm/prizm-commit-workflow.md +1 -0
  67. package/bundled/rules/prizm/prizm-documentation.md +15 -15
  68. package/bundled/rules/prizm/prizm-progressive-loading.md +2 -1
  69. package/bundled/skills/_metadata.json +33 -6
  70. package/bundled/skills/app-planner/SKILL.md +105 -320
  71. package/bundled/skills/app-planner/assets/app-design-guide.md +101 -0
  72. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  73. package/bundled/skills/app-planner/references/project-brief-guide.md +49 -80
  74. package/bundled/skills/bug-fix-workflow/SKILL.md +2 -2
  75. package/bundled/skills/bug-planner/SKILL.md +68 -5
  76. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +3 -2
  77. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +19 -5
  78. package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/SKILL.md +32 -32
  79. package/bundled/skills/feature-planner/SKILL.md +337 -0
  80. package/bundled/skills/{app-planner → feature-planner}/assets/evaluation-guide.md +4 -4
  81. package/bundled/skills/{app-planner → feature-planner}/assets/planning-guide.md +3 -171
  82. package/bundled/skills/{app-planner → feature-planner}/references/browser-interaction.md +6 -5
  83. package/bundled/skills/feature-planner/references/decomposition-patterns.md +75 -0
  84. package/bundled/skills/{app-planner → feature-planner}/references/error-recovery.md +8 -8
  85. package/bundled/skills/{app-planner → feature-planner}/references/incremental-feature-planning.md +1 -1
  86. package/bundled/skills/{app-planner/references/new-app-planning.md → feature-planner/references/new-project-planning.md} +1 -1
  87. package/bundled/skills/{app-planner → feature-planner}/scripts/validate-and-generate.py +4 -4
  88. package/bundled/skills/feature-workflow/SKILL.md +23 -23
  89. package/bundled/skills/prizm-kit/SKILL.md +1 -3
  90. package/bundled/skills/prizm-kit/assets/project-memory-template.md +4 -2
  91. package/bundled/skills/prizmkit-analyze/SKILL.md +2 -5
  92. package/bundled/skills/prizmkit-code-review/SKILL.md +2 -2
  93. package/bundled/skills/prizmkit-committer/SKILL.md +32 -8
  94. package/bundled/skills/prizmkit-deploy/SKILL.md +1 -5
  95. package/bundled/skills/prizmkit-implement/SKILL.md +5 -51
  96. package/bundled/skills/prizmkit-init/SKILL.md +7 -78
  97. package/bundled/skills/prizmkit-plan/SKILL.md +1 -12
  98. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +13 -28
  99. package/bundled/skills/prizmkit-prizm-docs/assets/PRIZM-SPEC.md +52 -1
  100. package/bundled/skills/prizmkit-retrospective/SKILL.md +12 -117
  101. package/bundled/skills/recovery-workflow/SKILL.md +168 -316
  102. package/bundled/skills/recovery-workflow/evals/evals.json +29 -13
  103. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +232 -274
  104. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +352 -0
  105. package/bundled/skills/refactor-planner/SKILL.md +436 -0
  106. package/bundled/skills/refactor-planner/assets/planning-guide.md +292 -0
  107. package/bundled/skills/refactor-planner/references/behavior-preservation.md +301 -0
  108. package/bundled/skills/refactor-planner/references/refactor-scoping-guide.md +221 -0
  109. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +786 -0
  110. package/bundled/skills/refactor-workflow/SKILL.md +299 -319
  111. package/bundled/team/prizm-dev-team.json +1 -1
  112. package/package.json +1 -1
  113. package/src/clean.js +3 -3
  114. package/src/scaffold.js +6 -6
  115. package/bundled/skills/prizmkit-plan/assets/spec-template.md +0 -56
  116. package/bundled/skills/prizmkit-plan/references/clarify-guide.md +0 -67
  117. package/src/config.js +0 -504
  118. package/src/prompts.js +0 -210
  119. /package/bundled/skills/{dev-pipeline-launcher → feature-pipeline-launcher}/scripts/preflight-check.py +0 -0
@@ -101,6 +101,12 @@ def parse_args():
101
101
  default=None,
102
102
  help="Override critic enablement (default: read from feature field)",
103
103
  )
104
+ parser.add_argument(
105
+ "--extract-baselines",
106
+ action="store_true",
107
+ help="Run tests and extract baseline failures (slower, optional)",
108
+ )
109
+
104
110
  return parser.parse_args()
105
111
 
106
112
 
@@ -154,6 +160,101 @@ def format_acceptance_criteria(criteria):
154
160
  lines.append("- {}".format(item))
155
161
  return "\n".join(lines)
156
162
 
163
+ def detect_test_commands(project_root):
164
+ """
165
+ Auto-detect test commands based on project structure.
166
+
167
+ Returns: space-separated string of test commands (e.g., "npm test go test ./...")
168
+ """
169
+ test_commands = []
170
+
171
+ # Check for npm/package.json
172
+ if os.path.exists(os.path.join(project_root, "package.json")):
173
+ test_commands.append("npm test")
174
+
175
+ # Check for Go
176
+ if os.path.exists(os.path.join(project_root, "go.mod")):
177
+ test_commands.append("go test ./...")
178
+
179
+ # Check for Rust/Cargo
180
+ if os.path.exists(os.path.join(project_root, "Cargo.toml")):
181
+ test_commands.append("cargo test")
182
+
183
+ # Check for Python pytest
184
+ if os.path.exists(os.path.join(project_root, "pytest.ini")) or \
185
+ os.path.exists(os.path.join(project_root, "setup.py")):
186
+ test_commands.append("pytest")
187
+
188
+ # Check for Make test target
189
+ makefile_path = os.path.join(project_root, "Makefile")
190
+ if os.path.exists(makefile_path):
191
+ try:
192
+ with open(makefile_path, 'r') as f:
193
+ if 'test:' in f.read():
194
+ test_commands.append("make test")
195
+ except Exception:
196
+ pass
197
+
198
+ # Return deduplicated space-separated list
199
+ return " ".join(dict.fromkeys(test_commands)) if test_commands else ""
200
+
201
+
202
+ def extract_baseline_failures(test_cmd, project_root):
203
+ """
204
+ Run test command and extract failing tests.
205
+
206
+ Returns: semicolon-separated list of failing test names
207
+ """
208
+ if not test_cmd or test_cmd.startswith("(auto-detection"):
209
+ return ""
210
+
211
+ try:
212
+ import subprocess
213
+ original_cwd = os.getcwd()
214
+ os.chdir(project_root)
215
+
216
+ result = subprocess.run(
217
+ test_cmd,
218
+ shell=True,
219
+ capture_output=True,
220
+ text=True,
221
+ timeout=120
222
+ )
223
+
224
+ os.chdir(original_cwd)
225
+
226
+ output = result.stdout + result.stderr
227
+ failures = []
228
+
229
+ for line in output.split('\n'):
230
+ if 'FAILED' in line and '::' in line:
231
+ parts = line.split('FAILED')
232
+ if len(parts) > 1:
233
+ test_name = parts[1].strip().split(' ')[0]
234
+ if test_name and test_name not in failures:
235
+ failures.append(test_name)
236
+
237
+ return ";".join(failures) if failures else ""
238
+
239
+ except Exception as e:
240
+ return f"(error: {str(e)})"
241
+ finally:
242
+ try:
243
+ os.chdir(original_cwd)
244
+ except Exception:
245
+ pass
246
+
247
+
248
+ def format_ac_checklist(acceptance_criteria):
249
+ """Format acceptance criteria as a markdown checkbox list."""
250
+ if not acceptance_criteria:
251
+ return "- [ ] (no acceptance criteria specified)"
252
+ lines = []
253
+ for item in acceptance_criteria:
254
+ lines.append("- [ ] {}".format(item))
255
+ return "\n".join(lines)
256
+
257
+
157
258
 
158
259
  def format_global_context(global_context, project_root=None):
159
260
  """Format global_context dict as a key-value list.
@@ -251,8 +352,9 @@ def _read_project_brief(project_root):
251
352
 
252
353
  Returns the file content as a string, or a fallback message if absent.
253
354
  This brief is generated by app-planner during interactive planning and
254
- captures cross-feature design decisions, business intent, and user
255
- preferences that individual feature descriptions may not include.
355
+ captures the user's product ideas as a checklist. Each line is one idea,
356
+ marked [ ] for pending or [x] for completed. Feature sessions should mark
357
+ items [x] and append key file paths when implementing relevant ideas.
256
358
  """
257
359
  brief_path = os.path.join(project_root, "project-brief.md")
258
360
  if os.path.isfile(brief_path):
@@ -428,6 +530,171 @@ def determine_pipeline_mode(complexity):
428
530
  return mapping.get(complexity, "lite")
429
531
 
430
532
 
533
+ # ============================================================
534
+ # Checkpoint generation
535
+ # ============================================================
536
+
537
+ # Mapping: section name -> (skill_key, display_name, required_artifacts)
538
+ # skill_key is a unique identifier in the checkpoint, not necessarily the
539
+ # prizmkit skill name. This ensures each section has a distinct key so
540
+ # merge_checkpoint_state() never collides.
541
+ SECTION_TO_SKILL = {
542
+ "phase0-init": ("prizmkit-init", "Project Bootstrap",
543
+ [".prizm-docs/root.prizm", ".prizmkit/config.json"]),
544
+ "phase0-test-baseline": ("test-baseline", "Test Baseline", []),
545
+ "phase-context-snapshot": ("context-snapshot", "Build Context Snapshot",
546
+ [".prizmkit/specs/{slug}/context-snapshot.md"]),
547
+ "phase-specify-plan": ("context-snapshot-and-plan", "Specify & Plan",
548
+ [".prizmkit/specs/{slug}/context-snapshot.md",
549
+ ".prizmkit/specs/{slug}/plan.md"]),
550
+ "phase-plan": ("prizmkit-plan", "Plan & Tasks",
551
+ [".prizmkit/specs/{slug}/plan.md"]),
552
+ "phase-analyze": ("prizmkit-analyze", "Analyze", []),
553
+ "phase-critic-plan": ("critic-plan-review", "Critic: Plan Review", []),
554
+ "phase-implement": ("prizmkit-implement", "Implement + Test", []),
555
+ "phase-critic-code": ("critic-code-review", "Critic: Code Review", []),
556
+ "phase-review": ("prizmkit-code-review", "Code Review", []),
557
+ "phase-browser": ("browser-verification", "Browser Verification", []),
558
+ "phase-deploy": ("deploy-verification", "Deploy Verification", []),
559
+ "phase-commit": None, # special: split into retrospective + committer
560
+ }
561
+
562
+ # phase-commit is split into two steps
563
+ _COMMIT_STEPS = [
564
+ ("prizmkit-retrospective", "Retrospective", []),
565
+ ("prizmkit-committer", "Commit", []),
566
+ ]
567
+
568
+
569
+ def _resolve_artifacts(artifact_templates, slug):
570
+ """Replace {slug} placeholder with the actual feature slug."""
571
+ return [a.replace("{slug}", slug) for a in artifact_templates]
572
+
573
+
574
+ def generate_checkpoint_definition(sections, pipeline_mode, workflow_type,
575
+ item_id, item_slug, session_id,
576
+ init_done=False):
577
+ """Derive checkpoint step definitions from the assembled sections list.
578
+
579
+ Args:
580
+ sections: list of (name, content) tuples from assemble_sections()
581
+ pipeline_mode: "lite" | "standard" | "full"
582
+ workflow_type: "feature-pipeline"
583
+ item_id: feature ID (e.g. "F-001")
584
+ item_slug: feature slug (e.g. "001-user-auth")
585
+ session_id: current session ID
586
+ init_done: whether project is already initialized (Phase 0 skip)
587
+
588
+ Returns:
589
+ dict suitable for writing as workflow-checkpoint.json
590
+ """
591
+ steps = []
592
+ step_counter = 1
593
+ prev_step_id = None
594
+
595
+ for section_name, _content in sections:
596
+ if section_name not in SECTION_TO_SKILL:
597
+ continue
598
+
599
+ mapping = SECTION_TO_SKILL[section_name]
600
+
601
+ if mapping is None:
602
+ # phase-commit -> split into retrospective + committer
603
+ for skill, name, artifacts in _COMMIT_STEPS:
604
+ step_id = "S{:02d}".format(step_counter)
605
+ steps.append({
606
+ "id": step_id,
607
+ "skill": skill,
608
+ "name": name,
609
+ "status": "pending",
610
+ "required_artifacts": _resolve_artifacts(artifacts, item_slug),
611
+ "depends_on": prev_step_id,
612
+ })
613
+ prev_step_id = step_id
614
+ step_counter += 1
615
+ continue
616
+
617
+ skill, name, artifacts = mapping
618
+ step_id = "S{:02d}".format(step_counter)
619
+
620
+ status = "pending"
621
+ if init_done and section_name in ("phase0-init", "phase0-test-baseline"):
622
+ status = "skipped"
623
+
624
+ steps.append({
625
+ "id": step_id,
626
+ "skill": skill,
627
+ "name": name,
628
+ "status": status,
629
+ "required_artifacts": _resolve_artifacts(artifacts, item_slug),
630
+ "depends_on": prev_step_id,
631
+ })
632
+
633
+ prev_step_id = step_id
634
+ step_counter += 1
635
+
636
+ return {
637
+ "version": 1,
638
+ "workflow_type": workflow_type,
639
+ "pipeline_mode": pipeline_mode,
640
+ "item_id": item_id,
641
+ "item_slug": item_slug,
642
+ "session_id": session_id,
643
+ "steps": steps,
644
+ }
645
+
646
+
647
+ def merge_checkpoint_state(existing, fresh, project_root):
648
+ """Merge existing checkpoint state into a freshly generated definition.
649
+
650
+ Matching is by skill_key (not step ID), since tier changes across retries
651
+ may shift step IDs.
652
+
653
+ Merge rules:
654
+ 1. Only keep completed steps whose required_artifacts all exist on disk
655
+ 2. Keep skipped steps unconditionally
656
+ 3. Once a step is NOT completed/skipped, break the dependency chain:
657
+ all subsequent steps reset to pending
658
+ """
659
+ existing_status = {}
660
+ existing_artifacts = {}
661
+ for step in existing.get("steps", []):
662
+ existing_status[step["skill"]] = step["status"]
663
+ existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
664
+
665
+ # Determine which completed steps have valid artifacts
666
+ valid_completed = set()
667
+ for skill_key, status in existing_status.items():
668
+ if status == "completed":
669
+ artifacts = existing_artifacts.get(skill_key, [])
670
+ if all(os.path.exists(os.path.join(project_root, a))
671
+ for a in artifacts):
672
+ valid_completed.add(skill_key)
673
+ else:
674
+ LOGGER.warning(
675
+ "Step '%s' was completed but artifacts missing — "
676
+ "resetting to pending", skill_key
677
+ )
678
+ elif status == "skipped":
679
+ valid_completed.add(skill_key)
680
+
681
+ # Apply to fresh steps; break chain on first non-valid step
682
+ chain_broken = False
683
+ for step in fresh["steps"]:
684
+ if chain_broken:
685
+ step["status"] = "pending"
686
+ continue
687
+
688
+ prev = existing_status.get(step["skill"])
689
+ if step["skill"] in valid_completed:
690
+ step["status"] = prev # completed or skipped
691
+ else:
692
+ chain_broken = True
693
+ step["status"] = "pending"
694
+
695
+ return fresh
696
+
697
+
431
698
  # ============================================================
432
699
  # Section Assembly (new modular approach)
433
700
  # ============================================================
@@ -445,6 +712,38 @@ def load_section(sections_dir, name):
445
712
  return f.read()
446
713
 
447
714
 
715
+ def load_agent_prompts(templates_dir):
716
+ """Load agent prompt templates from agent-prompts/ directory.
717
+
718
+ Returns a dict of {{AGENT_PROMPT_XXX}} -> prompt content replacements.
719
+ If the directory does not exist, returns an empty dict (backward compat).
720
+ """
721
+ agent_prompts_dir = os.path.join(templates_dir, "agent-prompts")
722
+ if not os.path.isdir(agent_prompts_dir):
723
+ LOGGER.debug("No agent-prompts/ directory found, skipping")
724
+ return {}
725
+
726
+ # Map filename -> placeholder name
727
+ # e.g. dev-implement.md -> {{AGENT_PROMPT_DEV_IMPLEMENT}}
728
+ prompt_map = {}
729
+ for filename in sorted(os.listdir(agent_prompts_dir)):
730
+ if not filename.endswith(".md"):
731
+ continue
732
+ stem = filename[:-3] # remove .md
733
+ placeholder = "{{{{AGENT_PROMPT_{}}}}}".format(
734
+ stem.upper().replace("-", "_")
735
+ )
736
+ filepath = os.path.join(agent_prompts_dir, filename)
737
+ try:
738
+ with open(filepath, "r", encoding="utf-8") as f:
739
+ prompt_map[placeholder] = f.read().strip()
740
+ LOGGER.debug("Loaded agent prompt: %s -> %s", filename, placeholder)
741
+ except IOError as exc:
742
+ LOGGER.warning("Failed to load agent prompt %s: %s", filename, exc)
743
+
744
+ return prompt_map
745
+
746
+
448
747
  def _tier_header(pipeline_mode):
449
748
  """Return the tier-specific header and mission description."""
450
749
  headers = {
@@ -587,6 +886,12 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
587
886
  load_section(sections_dir,
588
887
  "subagent-timeout-recovery.md")))
589
888
 
889
+ # --- Checkpoint System ---
890
+ checkpoint_section_path = os.path.join(sections_dir, "checkpoint-system.md")
891
+ if os.path.isfile(checkpoint_section_path):
892
+ sections.append(("checkpoint-system",
893
+ load_section(sections_dir, "checkpoint-system.md")))
894
+
590
895
  # --- Execution header ---
591
896
  sections.append(("execution-header", "---\n\n## Execution\n"))
592
897
 
@@ -741,9 +1046,13 @@ def render_from_sections(sections, replacements):
741
1046
  """
742
1047
  content = "\n".join(text for _, text in sections)
743
1048
 
744
- # Replace all placeholders
745
- for placeholder, value in replacements.items():
746
- content = content.replace(placeholder, value)
1049
+ # Replace all placeholders — run twice to handle agent prompt templates
1050
+ # that contain their own {{PLACEHOLDER}} variables. First pass injects
1051
+ # agent prompt content (e.g. {{AGENT_PROMPT_DEV_IMPLEMENT}} expands to a
1052
+ # block containing {{FEATURE_ID}}). Second pass replaces the inner vars.
1053
+ for _pass in range(2):
1054
+ for placeholder, value in replacements.items():
1055
+ content = content.replace(placeholder, value)
747
1056
 
748
1057
  return content
749
1058
 
@@ -954,11 +1263,28 @@ def build_replacements(args, feature, features, global_context, script_dir):
954
1263
  steps = browser_interaction.get("verify_steps", [])
955
1264
  if steps:
956
1265
  browser_verify_steps = "\n".join(
957
- " # Step {}: {}".format(i + 1, step)
1266
+ " # Goal {}: {}".format(i + 1, step)
958
1267
  for i, step in enumerate(steps)
959
1268
  )
960
1269
  else:
961
- browser_verify_steps = " # (no specific verify steps — just open and screenshot)"
1270
+ browser_verify_steps = " # (no specific verify goals — just open and screenshot)"
1271
+
1272
+ # Auto-detect test commands from project structure
1273
+ test_cmd = detect_test_commands(project_root)
1274
+ if not test_cmd:
1275
+ test_cmd = "(auto-detection found no standard test commands; manually specify TEST_CMD)"
1276
+
1277
+ # Optionally extract baseline failures from test execution
1278
+ baseline_failures = ""
1279
+ if args.extract_baselines:
1280
+ baseline_failures = extract_baseline_failures(test_cmd, project_root)
1281
+
1282
+ # Extract coverage target from feature.testing field (new in v2)
1283
+ coverage_target = "80" # Default coverage target
1284
+ testing_config = feature.get("testing", {})
1285
+ if isinstance(testing_config, dict):
1286
+ coverage_target = str(testing_config.get("coverage_target", 80))
1287
+
962
1288
 
963
1289
  replacements = {
964
1290
  "{{RUN_ID}}": args.run_id,
@@ -988,6 +1314,9 @@ def build_replacements(args, feature, features, global_context, script_dir):
988
1314
  "{{SESSION_STATUS_PATH}}": session_status_abs,
989
1315
  "{{PROJECT_ROOT}}": project_root,
990
1316
  "{{FEATURE_SLUG}}": feature_slug,
1317
+ "{{CHECKPOINT_PATH}}": os.path.join(
1318
+ ".prizmkit", "specs", feature_slug, "workflow-checkpoint.json",
1319
+ ),
991
1320
  "{{PIPELINE_MODE}}": pipeline_mode,
992
1321
  "{{COMPLEXITY}}": complexity,
993
1322
  "{{CRITIC_ENABLED}}": "true" if critic_enabled else "false",
@@ -999,6 +1328,12 @@ def build_replacements(args, feature, features, global_context, script_dir):
999
1328
  "{{BROWSER_URL}}": browser_url,
1000
1329
  "{{BROWSER_SETUP_COMMAND}}": browser_setup_command,
1001
1330
  "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
1331
+ "{{AC_CHECKLIST}}": format_ac_checklist(
1332
+ feature.get("acceptance_criteria", [])
1333
+ ),
1334
+ "{{TEST_CMD}}": test_cmd,
1335
+ "{{BASELINE_FAILURES}}": baseline_failures,
1336
+ "{{COVERAGE_TARGET}}": coverage_target,
1002
1337
  }
1003
1338
 
1004
1339
  return replacements, effective_resume, browser_enabled
@@ -1016,9 +1351,11 @@ def render_template(template_content, replacements, resume_phase, browser_enable
1016
1351
  content = process_mode_blocks(content, pipeline_mode, init_done, critic_enabled,
1017
1352
  browser_enabled)
1018
1353
 
1019
- # Step 3: Replace all {{PLACEHOLDER}} variables
1020
- for placeholder, value in replacements.items():
1021
- content = content.replace(placeholder, value)
1354
+ # Step 3: Replace all {{PLACEHOLDER}} variables (two passes for nested
1355
+ # agent prompt templates that may contain their own placeholders)
1356
+ for _pass in range(2):
1357
+ for placeholder, value in replacements.items():
1358
+ content = content.replace(placeholder, value)
1022
1359
 
1023
1360
  return content
1024
1361
 
@@ -1079,6 +1416,10 @@ def main():
1079
1416
  )
1080
1417
  replacements["{{RESUME_PHASE}}"] = effective_resume
1081
1418
 
1419
+ # Load agent prompt templates and merge into replacements
1420
+ agent_prompt_replacements = load_agent_prompts(templates_dir)
1421
+ replacements.update(agent_prompt_replacements)
1422
+
1082
1423
  # Extract state needed for assembly
1083
1424
  pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
1084
1425
  init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
@@ -1147,6 +1488,52 @@ def main():
1147
1488
  if err:
1148
1489
  emit_failure(err)
1149
1490
 
1491
+ # ── Generate checkpoint file ──────────────────────────────────────
1492
+ project_root = resolve_project_root(
1493
+ os.path.dirname(os.path.abspath(__file__))
1494
+ )
1495
+ feature_slug = replacements.get("{{FEATURE_SLUG}}", "")
1496
+ checkpoint_path = ""
1497
+
1498
+ if use_sections and feature_slug:
1499
+ checkpoint = generate_checkpoint_definition(
1500
+ sections=sections,
1501
+ pipeline_mode=pipeline_mode,
1502
+ workflow_type="feature-pipeline",
1503
+ item_id=args.feature_id,
1504
+ item_slug=feature_slug,
1505
+ session_id=args.session_id,
1506
+ init_done=init_done,
1507
+ )
1508
+
1509
+ checkpoint_dir = os.path.join(
1510
+ project_root, ".prizmkit", "specs", feature_slug,
1511
+ )
1512
+ os.makedirs(checkpoint_dir, exist_ok=True)
1513
+ checkpoint_path = os.path.join(
1514
+ checkpoint_dir, "workflow-checkpoint.json",
1515
+ )
1516
+
1517
+ # On resume, merge existing completed state (with artifact validation)
1518
+ if is_resume and os.path.exists(checkpoint_path):
1519
+ try:
1520
+ with open(checkpoint_path, "r", encoding="utf-8") as f:
1521
+ existing = json.load(f)
1522
+ checkpoint = merge_checkpoint_state(
1523
+ existing, checkpoint, project_root,
1524
+ )
1525
+ LOGGER.info("Merged existing checkpoint state from %s",
1526
+ checkpoint_path)
1527
+ except (json.JSONDecodeError, KeyError) as exc:
1528
+ LOGGER.warning(
1529
+ "Existing checkpoint corrupted (%s) — generating fresh",
1530
+ exc,
1531
+ )
1532
+
1533
+ with open(checkpoint_path, "w", encoding="utf-8") as f:
1534
+ json.dump(checkpoint, f, indent=2, ensure_ascii=False)
1535
+ LOGGER.info("Wrote checkpoint to %s", checkpoint_path)
1536
+
1150
1537
  # ── Success JSON ───────────────────────────────────────────────────
1151
1538
  feature_model = feature.get("model", "")
1152
1539
  mode_agent_counts = {"lite": 1, "standard": 3, "full": 3}
@@ -1164,6 +1551,7 @@ def main():
1164
1551
  "render_mode": "sections" if use_sections else "legacy",
1165
1552
  "validation_warnings": len(warnings),
1166
1553
  "validation_errors": len(errors),
1554
+ "checkpoint_path": checkpoint_path,
1167
1555
  }
1168
1556
  print(json.dumps(output, indent=2, ensure_ascii=False))
1169
1557
  sys.exit(0)
@@ -324,6 +324,95 @@ def emit_failure(message):
324
324
  sys.exit(1)
325
325
 
326
326
 
327
+ # ============================================================
328
+ # Checkpoint generation for bugfix pipeline
329
+ # ============================================================
330
+
331
+ BUGFIX_STEPS = [
332
+ ("prizmkit-init", "Initialize", []),
333
+ ("bug-diagnosis", "Bug Diagnosis & Fix Plan",
334
+ [".prizmkit/bugfix/{slug}/fix-plan.md"]),
335
+ ("bug-reproduce", "Write Reproduction Test", []),
336
+ ("bug-fix", "Implement Fix", []),
337
+ ("prizmkit-code-review", "Code Review", []),
338
+ ("prizmkit-committer", "Commit", []),
339
+ ("bug-report", "Generate Fix Report & Update TRAPS",
340
+ [".prizmkit/bugfix/{slug}/fix-report.md"]),
341
+ ]
342
+
343
+
344
+ def generate_bugfix_checkpoint(bug_id, session_id):
345
+ """Generate a checkpoint definition for bugfix pipeline.
346
+
347
+ Returns a dict suitable for writing as workflow-checkpoint.json.
348
+ """
349
+ steps = []
350
+ prev_id = None
351
+ for i, (skill, name, artifacts) in enumerate(BUGFIX_STEPS, 1):
352
+ step_id = "S{:02d}".format(i)
353
+ steps.append({
354
+ "id": step_id,
355
+ "skill": skill,
356
+ "name": name,
357
+ "status": "pending",
358
+ "required_artifacts": [a.replace("{slug}", bug_id) for a in artifacts],
359
+ "depends_on": prev_id,
360
+ })
361
+ prev_id = step_id
362
+
363
+ return {
364
+ "version": 1,
365
+ "workflow_type": "bugfix-pipeline",
366
+ "pipeline_mode": "single",
367
+ "item_id": bug_id,
368
+ "item_slug": bug_id,
369
+ "session_id": session_id,
370
+ "steps": steps,
371
+ }
372
+
373
+
374
+ def merge_bugfix_checkpoint_state(existing, fresh, project_root):
375
+ """Merge existing bugfix checkpoint state into fresh definition.
376
+
377
+ Same logic as feature pipeline: validate artifacts, break chain on
378
+ first invalid step.
379
+ """
380
+ existing_status = {}
381
+ existing_artifacts = {}
382
+ for step in existing.get("steps", []):
383
+ existing_status[step["skill"]] = step["status"]
384
+ existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
385
+
386
+ valid_completed = set()
387
+ for skill_key, status in existing_status.items():
388
+ if status == "completed":
389
+ artifacts = existing_artifacts.get(skill_key, [])
390
+ if all(os.path.exists(os.path.join(project_root, a))
391
+ for a in artifacts):
392
+ valid_completed.add(skill_key)
393
+ else:
394
+ LOGGER.warning(
395
+ "Step '%s' was completed but artifacts missing — "
396
+ "resetting to pending", skill_key
397
+ )
398
+ elif status == "skipped":
399
+ valid_completed.add(skill_key)
400
+
401
+ chain_broken = False
402
+ for step in fresh["steps"]:
403
+ if chain_broken:
404
+ step["status"] = "pending"
405
+ continue
406
+ prev = existing_status.get(step["skill"])
407
+ if step["skill"] in valid_completed:
408
+ step["status"] = prev
409
+ else:
410
+ chain_broken = True
411
+ step["status"] = "pending"
412
+
413
+ return fresh
414
+
415
+
327
416
  def main():
328
417
  args = parse_args()
329
418
 
@@ -366,6 +455,12 @@ def main():
366
455
  # Build replacements
367
456
  replacements = build_replacements(args, bug, global_context, script_dir)
368
457
 
458
+ # Add checkpoint path to replacements
459
+ checkpoint_rel = os.path.join(
460
+ ".prizmkit", "bugfix", args.bug_id, "workflow-checkpoint.json",
461
+ )
462
+ replacements["{{CHECKPOINT_PATH}}"] = checkpoint_rel
463
+
369
464
  # Render the template
370
465
  rendered = render_template(template_content, replacements, bug)
371
466
 
@@ -374,10 +469,39 @@ def main():
374
469
  if err:
375
470
  emit_failure(err)
376
471
 
472
+ # Generate checkpoint file
473
+ project_root = resolve_project_root(script_dir)
474
+ checkpoint_path = os.path.join(project_root, checkpoint_rel)
475
+ checkpoint_dir = os.path.dirname(checkpoint_path)
476
+ os.makedirs(checkpoint_dir, exist_ok=True)
477
+
478
+ checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
479
+
480
+ is_resume = args.resume_phase != "null"
481
+ if is_resume and os.path.exists(checkpoint_path):
482
+ try:
483
+ with open(checkpoint_path, "r", encoding="utf-8") as f:
484
+ existing = json.load(f)
485
+ checkpoint = merge_bugfix_checkpoint_state(
486
+ existing, checkpoint, project_root,
487
+ )
488
+ LOGGER.info("Merged existing bugfix checkpoint from %s",
489
+ checkpoint_path)
490
+ except (json.JSONDecodeError, KeyError) as exc:
491
+ LOGGER.warning(
492
+ "Existing bugfix checkpoint corrupted (%s) — generating fresh",
493
+ exc,
494
+ )
495
+
496
+ with open(checkpoint_path, "w", encoding="utf-8") as f:
497
+ json.dump(checkpoint, f, indent=2, ensure_ascii=False)
498
+ LOGGER.info("Wrote bugfix checkpoint to %s", checkpoint_path)
499
+
377
500
  # Success
378
501
  output = {
379
502
  "success": True,
380
503
  "output_path": os.path.abspath(args.output),
504
+ "checkpoint_path": checkpoint_path,
381
505
  }
382
506
  print(json.dumps(output, indent=2, ensure_ascii=False))
383
507
  sys.exit(0)