prizmkit 1.1.10 → 1.1.13

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 (50) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +10 -46
  3. package/bundled/dev-pipeline/reset-bug.sh +84 -10
  4. package/bundled/dev-pipeline/reset-feature.sh +86 -10
  5. package/bundled/dev-pipeline/reset-refactor.sh +68 -4
  6. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +47 -46
  7. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +7 -12
  8. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +124 -20
  9. package/bundled/dev-pipeline/scripts/utils.py +20 -0
  10. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +13 -7
  11. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +62 -66
  12. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +37 -40
  13. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +35 -48
  14. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +135 -182
  15. package/bundled/dev-pipeline/templates/feature-list-schema.json +6 -21
  16. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +9 -9
  17. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  18. package/bundled/dev-pipeline/templates/sections/feature-context.md +4 -0
  19. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +41 -24
  20. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +4 -12
  21. package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +9 -17
  22. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +1 -1
  23. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +3 -2
  24. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +4 -2
  25. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +0 -18
  26. package/bundled/dev-pipeline/templates/sections/session-context.md +1 -2
  27. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +75 -0
  28. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +66 -0
  29. package/bundled/skills/_metadata.json +1 -1
  30. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +3 -8
  31. package/bundled/skills/feature-pipeline-launcher/SKILL.md +4 -16
  32. package/bundled/skills/feature-planner/SKILL.md +8 -4
  33. package/bundled/skills/feature-planner/assets/planning-guide.md +16 -11
  34. package/bundled/skills/feature-planner/references/browser-interaction.md +9 -8
  35. package/bundled/skills/feature-planner/references/completeness-review.md +1 -1
  36. package/bundled/skills/feature-planner/references/error-recovery.md +1 -1
  37. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +1 -1
  38. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +10 -7
  39. package/bundled/skills/feature-workflow/SKILL.md +61 -34
  40. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +3 -7
  41. package/bundled/skills/recovery-workflow/SKILL.md +3 -3
  42. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +4 -15
  43. package/bundled/skills/refactor-workflow/SKILL.md +72 -66
  44. package/package.json +1 -1
  45. package/bundled/dev-pipeline/retry-bugfix.sh +0 -429
  46. package/bundled/dev-pipeline/retry-feature.sh +0 -445
  47. package/bundled/dev-pipeline/retry-refactor.sh +0 -441
  48. package/bundled/dev-pipeline/templates/sections/failure-log-check.md +0 -9
  49. package/bundled/dev-pipeline/templates/sections/resume-header.md +0 -5
  50. package/bundled/dev-pipeline/templates/sections/test-failure-recovery.md +0 -75
@@ -26,7 +26,7 @@ import os
26
26
  import re
27
27
  import sys
28
28
 
29
- from utils import enrich_global_context, load_json_file, setup_logging
29
+ from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
30
30
 
31
31
 
32
32
  DEFAULT_MAX_RETRIES = 3
@@ -407,27 +407,25 @@ def resolve_project_root(script_dir):
407
407
 
408
408
 
409
409
  def process_conditional_blocks(content, resume_phase):
410
- """Handle conditional blocks based on resume_phase and pipeline mode.
410
+ """Handle conditional blocks based on resume_phase.
411
411
 
412
412
  Supports:
413
413
  - {{IF_FRESH_START}} / {{END_IF_FRESH_START}}
414
414
  - {{IF_RESUME}} / {{END_IF_RESUME}}
415
- - {{IF_INIT_NEEDED}} / {{END_IF_INIT_NEEDED}}
416
- - {{IF_INIT_DONE}} / {{END_IF_INIT_DONE}}
417
- - {{IF_MODE_LITE}} / {{END_IF_MODE_LITE}}
418
- - {{IF_MODE_STANDARD}} / {{END_IF_MODE_STANDARD}}
419
- - {{IF_MODE_FULL}} / {{END_IF_MODE_FULL}}
415
+ - {{IF_RETRY}} / {{END_IF_RETRY}}
420
416
  """
421
417
  is_resume = resume_phase != "null"
422
418
 
423
419
  if is_resume:
424
- content = re.sub(r"\{\{IF_RESUME\}\}\n?", "", content)
425
- content = re.sub(r"\{\{END_IF_RESUME\}\}\n?", "", content)
420
+ # Remove fresh-start blocks, keep resume blocks
426
421
  content = re.sub(
427
422
  r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?",
428
423
  "", content, flags=re.DOTALL,
429
424
  )
425
+ content = re.sub(r"\{\{IF_RESUME\}\}\n?", "", content)
426
+ content = re.sub(r"\{\{END_IF_RESUME\}\}\n?", "", content)
430
427
  else:
428
+ # Keep fresh-start blocks, remove resume blocks
431
429
  content = re.sub(r"\{\{IF_FRESH_START\}\}\n?", "", content)
432
430
  content = re.sub(r"\{\{END_IF_FRESH_START\}\}\n?", "", content)
433
431
  content = re.sub(
@@ -848,7 +846,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
848
846
 
849
847
 
850
848
  def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
851
- critic_enabled, browser_enabled):
849
+ critic_enabled, browser_enabled, retry_count=0):
852
850
  """Assemble prompt sections based on tier and conditions.
853
851
 
854
852
  Uses Python code for conditional logic instead of regex-based
@@ -933,15 +931,6 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
933
931
  sections.append(("phase0-skip",
934
932
  "### Phase 0: SKIP (already initialized)\n"))
935
933
 
936
- # --- Resume header (if resuming) ---
937
- if is_resume:
938
- sections.append(("resume-header",
939
- load_section(sections_dir, "resume-header.md")))
940
-
941
- # --- Failure log check ---
942
- sections.append(("failure-log-check",
943
- load_section(sections_dir, "failure-log-check.md")))
944
-
945
934
  # --- Context Snapshot + Plan (tier-dependent) ---
946
935
  if pipeline_mode == "full":
947
936
  # Tier 3: full specify + plan workflow
@@ -995,9 +984,15 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
995
984
  load_section(sections_dir,
996
985
  "phase-implement-agent.md")))
997
986
 
998
- # --- Test Failure Recovery Protocol (all tiers) ---
999
- sections.append(("test-failure-recovery",
1000
- load_section(sections_dir, "test-failure-recovery.md")))
987
+ # --- Test Failure Recovery Protocol (tier-specific) ---
988
+ if pipeline_mode == "lite":
989
+ sections.append(("test-failure-recovery",
990
+ load_section(sections_dir,
991
+ "test-failure-recovery-lite.md")))
992
+ else:
993
+ sections.append(("test-failure-recovery",
994
+ load_section(sections_dir,
995
+ "test-failure-recovery-agent.md")))
1001
996
 
1002
997
  # --- Critic: Code Challenge (only if critic enabled, agent tiers) ---
1003
998
  if critic_enabled and pipeline_mode in ("standard", "full"):
@@ -1213,8 +1208,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1213
1208
  # Make it absolute from project root
1214
1209
  session_status_abs = os.path.join(project_root, session_status_path)
1215
1210
 
1216
- prev_status = get_prev_session_status(args.state_dir, args.feature_id)
1217
-
1218
1211
  # Compute feature slug for per-feature directory naming
1219
1212
  feature_slug = compute_feature_slug(
1220
1213
  args.feature_id, feature.get("title", "")
@@ -1223,7 +1216,10 @@ def build_replacements(args, feature, features, global_context, script_dir):
1223
1216
  # Detect project state
1224
1217
  init_done = detect_init_status(project_root)
1225
1218
  artifacts = detect_existing_artifacts(project_root, feature_slug)
1226
- complexity = feature.get("estimated_complexity", "medium")
1219
+ complexity = feature.get(
1220
+ "estimated_complexity",
1221
+ feature.get("complexity", "medium"),
1222
+ )
1227
1223
  if args.mode:
1228
1224
  pipeline_mode = args.mode
1229
1225
  else:
@@ -1273,8 +1269,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1273
1269
  # Browser interaction - extract from feature if present and playwright-cli available
1274
1270
  browser_interaction = feature.get("browser_interaction")
1275
1271
  browser_enabled = False
1276
- browser_url = ""
1277
- browser_setup_command = ""
1278
1272
  browser_verify_steps = ""
1279
1273
 
1280
1274
  browser_verify_env = os.environ.get("BROWSER_VERIFY", "").lower()
@@ -1282,18 +1276,27 @@ def build_replacements(args, feature, features, global_context, script_dir):
1282
1276
  browser_interaction = None
1283
1277
 
1284
1278
  if browser_interaction and isinstance(browser_interaction, dict):
1285
- browser_url = browser_interaction.get("url", "")
1286
- if browser_url:
1279
+ # browser_interaction only needs verify_steps — AI auto-detects
1280
+ # dev server command, URL, and port from project config
1281
+ steps = browser_interaction.get("verify_steps", [])
1282
+ if isinstance(browser_interaction, bool) and browser_interaction:
1283
+ # Simple boolean: browser verification enabled, no specific goals
1287
1284
  browser_enabled = True
1288
- browser_setup_command = browser_interaction.get("setup_command", "# no setup needed")
1289
- steps = browser_interaction.get("verify_steps", [])
1290
- if steps:
1291
- browser_verify_steps = "\n".join(
1292
- " # Goal {}: {}".format(i + 1, step)
1293
- for i, step in enumerate(steps)
1294
- )
1295
- else:
1296
- browser_verify_steps = " # (no specific verify goals — just open and screenshot)"
1285
+ browser_verify_steps = (
1286
+ " # (no specific verify goals — explore the app and "
1287
+ "verify the feature works as expected)")
1288
+ elif steps:
1289
+ browser_enabled = True
1290
+ browser_verify_steps = "\n".join(
1291
+ " # Goal {}: {}".format(i + 1, step)
1292
+ for i, step in enumerate(steps)
1293
+ )
1294
+ elif browser_interaction.get("url") or browser_interaction.get("enabled", True):
1295
+ # Backward compat: old format had url/setup_command fields
1296
+ browser_enabled = True
1297
+ browser_verify_steps = (
1298
+ " # (no specific verify goals — explore the app and "
1299
+ "verify the feature works as expected)")
1297
1300
 
1298
1301
  # Auto-detect test commands from project structure
1299
1302
  test_cmd = detect_test_commands(project_root)
@@ -1318,10 +1321,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1318
1321
  "{{FEATURE_ID}}": args.feature_id,
1319
1322
  "{{FEATURE_LIST_PATH}}": os.path.abspath(args.feature_list),
1320
1323
  "{{FEATURE_TITLE}}": feature.get("title", ""),
1321
- "{{RETRY_COUNT}}": str(args.retry_count),
1322
- "{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
1323
- "{{PREV_SESSION_STATUS}}": prev_status,
1324
- "{{RESUME_PHASE}}": args.resume_phase,
1325
1324
  "{{FEATURE_DESCRIPTION}}": feature.get("description", ""),
1326
1325
  "{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
1327
1326
  feature.get("acceptance_criteria", [])
@@ -1331,6 +1330,7 @@ def build_replacements(args, feature, features, global_context, script_dir):
1331
1330
  ),
1332
1331
  "{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
1333
1332
  "{{PROJECT_BRIEF}}": _read_project_brief(project_root),
1333
+ "{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
1334
1334
  "{{TEAM_CONFIG_PATH}}": team_config_path,
1335
1335
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
1336
1336
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
@@ -1349,8 +1349,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1349
1349
  "{{INIT_DONE}}": "true" if init_done else "false",
1350
1350
  "{{HAS_SPEC}}": "true" if artifacts["has_spec"] else "false",
1351
1351
  "{{HAS_PLAN}}": "true" if artifacts["has_plan"] else "false",
1352
- "{{BROWSER_URL}}": browser_url,
1353
- "{{BROWSER_SETUP_COMMAND}}": browser_setup_command,
1354
1352
  "{{BROWSER_VERIFY_STEPS}}": browser_verify_steps,
1355
1353
  "{{AC_CHECKLIST}}": format_ac_checklist(
1356
1354
  feature.get("acceptance_criteria", [])
@@ -1438,7 +1436,6 @@ def main():
1438
1436
  replacements, effective_resume, browser_enabled = build_replacements(
1439
1437
  args, feature, features, global_context, script_dir
1440
1438
  )
1441
- replacements["{{RESUME_PHASE}}"] = effective_resume
1442
1439
 
1443
1440
  # Load agent prompt templates and merge into replacements
1444
1441
  agent_prompt_replacements = load_agent_prompts(templates_dir)
@@ -1460,6 +1457,7 @@ def main():
1460
1457
  sections = assemble_sections(
1461
1458
  pipeline_mode, sections_dir, init_done, is_resume,
1462
1459
  critic_enabled, browser_enabled,
1460
+ retry_count=int(args.retry_count),
1463
1461
  )
1464
1462
  rendered = render_from_sections(sections, replacements)
1465
1463
  except FileNotFoundError as exc:
@@ -1474,7 +1472,10 @@ def main():
1474
1472
  if args.template:
1475
1473
  template_path = args.template
1476
1474
  else:
1477
- complexity = feature.get("estimated_complexity", "medium")
1475
+ complexity = feature.get(
1476
+ "estimated_complexity",
1477
+ feature.get("complexity", "medium"),
1478
+ )
1478
1479
  _mode = args.mode or determine_pipeline_mode(complexity)
1479
1480
  _tier_file_map = {
1480
1481
  "lite": "bootstrap-tier1.md",
@@ -19,7 +19,7 @@ import os
19
19
  import re
20
20
  import sys
21
21
 
22
- from utils import enrich_global_context, load_json_file, setup_logging
22
+ from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
23
23
 
24
24
 
25
25
  DEFAULT_MAX_RETRIES = 3
@@ -223,8 +223,6 @@ def build_replacements(args, bug, global_context, script_dir):
223
223
  "sessions", args.session_id, "session-status.json"
224
224
  )
225
225
 
226
- prev_status = get_prev_session_status(args.state_dir, args.bug_id)
227
-
228
226
  # Error source
229
227
  error_source = bug.get("error_source", {})
230
228
  error_type = error_source.get("type", "unknown") if isinstance(error_source, dict) else "unknown"
@@ -246,10 +244,6 @@ def build_replacements(args, bug, global_context, script_dir):
246
244
  "{{BUG_TITLE}}": bug.get("title", ""),
247
245
  "{{SEVERITY}}": bug.get("severity", "medium"),
248
246
  "{{VERIFICATION_TYPE}}": vtype,
249
- "{{RETRY_COUNT}}": str(args.retry_count),
250
- "{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
251
- "{{PREV_SESSION_STATUS}}": prev_status,
252
- "{{RESUME_PHASE}}": args.resume_phase,
253
247
  "{{BUG_DESCRIPTION}}": bug.get("description", ""),
254
248
  "{{ERROR_SOURCE_TYPE}}": error_type,
255
249
  "{{ERROR_SOURCE_DETAILS}}": format_error_source_details(error_source),
@@ -258,6 +252,7 @@ def build_replacements(args, bug, global_context, script_dir):
258
252
  ),
259
253
  "{{ENVIRONMENT}}": format_environment(bug.get("environment")),
260
254
  "{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
255
+ "{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
261
256
  "{{TEAM_CONFIG_PATH}}": team_config_path,
262
257
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
263
258
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
@@ -331,13 +326,13 @@ def emit_failure(message):
331
326
 
332
327
  BUGFIX_STEPS = [
333
328
  ("prizmkit-init", "Initialize", []),
334
- ("bug-diagnosis", "Bug Diagnosis & Fix Plan",
335
- [".prizmkit/bugfix/{slug}/fix-plan.md"]),
336
- ("bug-reproduce", "Write Reproduction Test", []),
337
- ("bug-fix", "Implement Fix", []),
329
+ ("bug-diagnosis-and-plan", "Diagnose & Plan",
330
+ [".prizmkit/bugfix/{slug}/spec.md",
331
+ ".prizmkit/bugfix/{slug}/plan.md"]),
332
+ ("prizmkit-implement", "Implement Fix", []),
338
333
  ("prizmkit-code-review", "Code Review", []),
339
334
  ("prizmkit-committer", "Commit", []),
340
- ("bug-report", "Generate Fix Report & Update TRAPS",
335
+ ("bug-report", "Generate Fix Report",
341
336
  [".prizmkit/bugfix/{slug}/fix-report.md"]),
342
337
  ]
343
338
 
@@ -19,7 +19,7 @@ import os
19
19
  import re
20
20
  import sys
21
21
 
22
- from utils import enrich_global_context, load_json_file, setup_logging
22
+ from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
23
23
 
24
24
 
25
25
  DEFAULT_MAX_RETRIES = 3
@@ -27,6 +27,97 @@ DEFAULT_MAX_RETRIES = 3
27
27
  LOGGER = setup_logging("generate-refactor-prompt")
28
28
 
29
29
 
30
+ # Refactor pipeline checkpoint steps (skill_key, display_name, required_artifacts)
31
+ # Artifacts use {slug} placeholder, replaced with refactor_id at runtime.
32
+ REFACTOR_STEPS = [
33
+ ("prizmkit-init", "Initialize",
34
+ [".prizmkit/refactor/{slug}"]),
35
+ ("prizmkit-plan", "Plan — Specification & Plan Generation",
36
+ [".prizmkit/refactor/{slug}/spec.md",
37
+ ".prizmkit/refactor/{slug}/plan.md"]),
38
+ ("prizmkit-implement", "Implement — Behavior-Preserving Refactoring",
39
+ [".prizmkit/refactor/{slug}/plan.md"]),
40
+ ("prizmkit-code-review", "Review — Code Review & Behavior Verification",
41
+ [".prizmkit/refactor/{slug}/review-report.md"]),
42
+ ("prizmkit-committer", "Commit",
43
+ []),
44
+ ("refactor-report", "Generate Refactor Report",
45
+ [".prizmkit/refactor/{slug}/refactor-report.md"]),
46
+ ]
47
+
48
+
49
+ def generate_refactor_checkpoint(refactor_id, session_id):
50
+ """Generate a checkpoint definition for refactor pipeline.
51
+
52
+ Returns a dict suitable for writing as workflow-checkpoint.json.
53
+ """
54
+ steps = []
55
+ prev_id = None
56
+ for i, (skill, name, artifacts) in enumerate(REFACTOR_STEPS, 1):
57
+ step_id = "S{:02d}".format(i)
58
+ steps.append({
59
+ "id": step_id,
60
+ "skill": skill,
61
+ "name": name,
62
+ "status": "pending",
63
+ "required_artifacts": [a.replace("{slug}", refactor_id) for a in artifacts],
64
+ "depends_on": prev_id,
65
+ })
66
+ prev_id = step_id
67
+
68
+ return {
69
+ "version": 1,
70
+ "workflow_type": "refactor-pipeline",
71
+ "pipeline_mode": "standard",
72
+ "item_id": refactor_id,
73
+ "item_slug": refactor_id,
74
+ "session_id": session_id,
75
+ "steps": steps,
76
+ }
77
+
78
+
79
+ def merge_refactor_checkpoint_state(existing, fresh, project_root):
80
+ """Merge existing refactor checkpoint state into fresh definition.
81
+
82
+ Same logic as feature/bugfix pipelines: validate artifacts, break chain
83
+ on first invalid step.
84
+ """
85
+ existing_status = {}
86
+ existing_artifacts = {}
87
+ for step in existing.get("steps", []):
88
+ existing_status[step["skill"]] = step["status"]
89
+ existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
90
+
91
+ valid_completed = set()
92
+ for skill_key, status in existing_status.items():
93
+ if status == "completed":
94
+ artifacts = existing_artifacts.get(skill_key, [])
95
+ if all(os.path.exists(os.path.join(project_root, a))
96
+ for a in artifacts):
97
+ valid_completed.add(skill_key)
98
+ else:
99
+ LOGGER.warning(
100
+ "Step '%s' was completed but artifacts missing — "
101
+ "resetting to pending", skill_key
102
+ )
103
+ elif status == "skipped":
104
+ valid_completed.add(skill_key)
105
+
106
+ chain_broken = False
107
+ for step in fresh["steps"]:
108
+ if chain_broken:
109
+ step["status"] = "pending"
110
+ continue
111
+ prev = existing_status.get(step["skill"])
112
+ if step["skill"] in valid_completed:
113
+ step["status"] = prev
114
+ else:
115
+ chain_broken = True
116
+ step["status"] = "pending"
117
+
118
+ return fresh
119
+
120
+
30
121
  def parse_args():
31
122
  parser = argparse.ArgumentParser(
32
123
  description=(
@@ -281,8 +372,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
281
372
  "sessions", args.session_id, "session-status.json"
282
373
  )
283
374
 
284
- prev_status = get_prev_session_status(args.state_dir, args.refactor_id)
285
-
286
375
  # Scope
287
376
  scope = refactor.get("scope", {})
288
377
 
@@ -321,10 +410,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
321
410
  "{{NEW_TESTS_NEEDED}}": new_tests_str,
322
411
  "{{PRIORITY}}": refactor.get("priority", "medium"),
323
412
  "{{COMPLEXITY}}": refactor.get("complexity", "medium"),
324
- "{{RETRY_COUNT}}": str(args.retry_count),
325
- "{{MAX_RETRIES}}": str(DEFAULT_MAX_RETRIES),
326
- "{{PREV_SESSION_STATUS}}": prev_status,
327
- "{{RESUME_PHASE}}": args.resume_phase,
328
413
  "{{REFACTOR_DESCRIPTION}}": refactor.get("description", ""),
329
414
  "{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(
330
415
  refactor.get("acceptance_criteria", [])
@@ -342,6 +427,7 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
342
427
  ".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
343
428
  ),
344
429
  "{{TIMESTAMP}}": "", # Placeholder — agent fills in timestamp
430
+ "{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
345
431
  }
346
432
 
347
433
  return replacements
@@ -350,33 +436,20 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
350
436
  def process_conditional_blocks(content, resume_phase):
351
437
  """Handle conditional blocks based on resume_phase.
352
438
 
353
- - {{IF_RESUME}}...{{END_IF_RESUME}} — include only when resuming (resume_phase != "null")
354
439
  - {{IF_FRESH_START}}...{{END_IF_FRESH_START}} — include only on fresh start (resume_phase == "null")
355
440
  """
356
441
  is_resume = resume_phase != "null"
357
442
 
358
443
  if is_resume:
359
- # Keep IF_RESUME content, strip markers
360
- content = content.replace("{{IF_RESUME}}\n", "")
361
- content = content.replace("{{IF_RESUME}}", "")
362
- content = content.replace("{{END_IF_RESUME}}\n", "")
363
- content = content.replace("{{END_IF_RESUME}}", "")
364
- # Remove IF_FRESH_START blocks entirely
365
444
  content = re.sub(
366
445
  r"\{\{IF_FRESH_START\}\}.*?\{\{END_IF_FRESH_START\}\}\n?",
367
446
  "", content, flags=re.DOTALL,
368
447
  )
369
448
  else:
370
- # Keep IF_FRESH_START content, strip markers
371
449
  content = content.replace("{{IF_FRESH_START}}\n", "")
372
450
  content = content.replace("{{IF_FRESH_START}}", "")
373
451
  content = content.replace("{{END_IF_FRESH_START}}\n", "")
374
452
  content = content.replace("{{END_IF_FRESH_START}}", "")
375
- # Remove IF_RESUME blocks entirely
376
- content = re.sub(
377
- r"\{\{IF_RESUME\}\}.*?\{\{END_IF_RESUME\}\}\n?",
378
- "", content, flags=re.DOTALL,
379
- )
380
453
 
381
454
  return content
382
455
 
@@ -470,6 +543,37 @@ def main():
470
543
  if err:
471
544
  emit_failure(err)
472
545
 
546
+ # Generate checkpoint file
547
+ project_root = resolve_project_root(script_dir)
548
+ checkpoint_rel = os.path.join(
549
+ ".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
550
+ )
551
+ checkpoint_path = os.path.join(project_root, checkpoint_rel)
552
+ checkpoint_dir = os.path.dirname(checkpoint_path)
553
+ os.makedirs(checkpoint_dir, exist_ok=True)
554
+
555
+ checkpoint = generate_refactor_checkpoint(args.refactor_id, args.session_id)
556
+
557
+ is_resume = args.resume_phase != "null"
558
+ if is_resume and os.path.exists(checkpoint_path):
559
+ try:
560
+ with open(checkpoint_path, "r", encoding="utf-8") as f:
561
+ existing = json.load(f)
562
+ checkpoint = merge_refactor_checkpoint_state(
563
+ existing, checkpoint, project_root,
564
+ )
565
+ LOGGER.info("Merged existing refactor checkpoint from %s",
566
+ checkpoint_path)
567
+ except (json.JSONDecodeError, KeyError) as exc:
568
+ LOGGER.warning(
569
+ "Existing refactor checkpoint corrupted (%s) — generating fresh",
570
+ exc,
571
+ )
572
+
573
+ with open(checkpoint_path, "w", encoding="utf-8") as f:
574
+ json.dump(checkpoint, f, indent=2, ensure_ascii=False)
575
+ LOGGER.info("Wrote refactor checkpoint to %s", checkpoint_path)
576
+
473
577
  # Resolve critic and mode
474
578
  refactor_critic = refactor.get("critic", False)
475
579
  if args.critic is not None:
@@ -518,3 +518,23 @@ def enrich_global_context(global_context, project_root):
518
518
  global_context[ctx_key] = detected[det_key] + " (auto-detected)"
519
519
 
520
520
  return global_context
521
+
522
+
523
+ def read_platform_conventions(project_root):
524
+ """Resolve the path to CLAUDE.md or CODEBUDDY.md for project-level conventions.
525
+
526
+ Returns a path reference for the AI agent to read at runtime,
527
+ rather than inlining the full file content into the prompt.
528
+ """
529
+ platform = os.environ.get("PRIZMKIT_PLATFORM", "claude")
530
+ if platform == "codebuddy":
531
+ candidates = ["CODEBUDDY.md", "CLAUDE.md"]
532
+ else:
533
+ candidates = ["CLAUDE.md", "CODEBUDDY.md"]
534
+
535
+ for filename in candidates:
536
+ filepath = os.path.join(project_root, filename)
537
+ if os.path.isfile(filepath):
538
+ return "`{}`".format(filename)
539
+
540
+ return "(No project conventions file found — CLAUDE.md or CODEBUDDY.md)"
@@ -14,14 +14,20 @@ Update the AC Verification Checklist in context-snapshot.md by marking each item
14
14
  - All [ ] must become [x] — if any AC remains unverified, the feature is incomplete
15
15
  - Document any AC that cannot be verified due to test failures
16
16
 
17
- ## Test Failure Recovery (Max 2 Fix Rounds)
17
+ ## Test Failure Recovery (Convergence-Based)
18
18
 
19
- If tests fail during implementation:
20
- - **Round 1**: Analyze failure (code bug vs. brittle test vs. environment), apply fix, re-run tests
21
- - **Round 2 (if still failing)**: Try alternate fix approach, re-run tests
22
- - **After Round 2**: Document failure in Implementation Log with root cause, category, and fix attempted
23
- - **Do NOT block completion** if unable to resolve — only NEW REGRESSIONS (not in baseline) require fixing
24
- - **If any AC cannot be verified** due to test failure: the feature is incomplete, add to failure notes
19
+ If tests fail during implementation, use convergence-based recovery — keep fixing as long as progress is being made:
20
+
21
+ 1. **Run tests, record results**: count failures, note which tests failed (exclude baseline failures)
22
+ 2. **Check termination**:
23
+ - All tests pass Done
24
+ - Plateau: same failures for 3 consecutive rounds Cannot resolve, document and stop
25
+ - Failures decreased → Continue fixing
26
+ 3. **Fix and iterate**: analyze, apply fix, re-run `$TEST_CMD`, go back to step 1
27
+
28
+ **Key rule**: If failures decrease (even by 1), the plateau counter resets to 0.
29
+ **Do NOT block completion** if unable to resolve — only NEW REGRESSIONS (not in baseline) require fixing.
30
+ **If any AC cannot be verified** due to test failure: the feature is incomplete, add to failure notes.
25
31
 
26
32
  4. Do NOT execute any git commands (no git add/commit/reset/push).
27
33
  Do NOT exit until all tasks are [x], the '## Implementation Log' section is written, and AC Verification Checklist is 100% complete in context-snapshot.md."