prizmkit 1.1.111 → 1.1.113

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/codex/skill-adapter.js +4 -0
  3. package/bundled/agents/prizm-dev-team-reviewer.md +12 -1
  4. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +10 -10
  5. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
  6. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +20 -73
  7. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +290 -421
  8. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  9. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +259 -481
  10. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -0
  11. package/bundled/dev-pipeline/scripts/init-pipeline.py +12 -0
  12. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +24 -1
  13. package/bundled/dev-pipeline/scripts/prompt_framework.py +312 -0
  14. package/bundled/dev-pipeline/scripts/update-bug-status.py +87 -20
  15. package/bundled/dev-pipeline/scripts/update-feature-status.py +82 -16
  16. package/bundled/dev-pipeline/scripts/update-refactor-status.py +68 -28
  17. package/bundled/dev-pipeline/scripts/utils.py +171 -0
  18. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  19. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +7 -1
  20. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +17 -19
  21. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +21 -26
  22. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +9 -1
  23. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +11 -8
  24. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +122 -182
  25. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +16 -0
  26. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +12 -0
  27. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +9 -0
  28. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +31 -0
  29. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +22 -0
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +23 -0
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +20 -0
  32. package/bundled/dev-pipeline/templates/sections/bugfix-phase-init.md +9 -0
  33. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +15 -0
  34. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +29 -0
  35. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +11 -0
  36. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +7 -0
  37. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +31 -0
  38. package/bundled/dev-pipeline/templates/sections/bugfix-task-contract.md +40 -0
  39. package/bundled/dev-pipeline/templates/sections/failure-capture.md +2 -2
  40. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +8 -2
  41. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +7 -1
  42. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +7 -1
  43. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +17 -0
  44. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +13 -0
  45. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +9 -0
  46. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +37 -0
  47. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +22 -0
  48. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +16 -0
  49. package/bundled/dev-pipeline/templates/sections/refactor-phase-init.md +9 -0
  50. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +22 -0
  51. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +19 -0
  52. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +11 -0
  53. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +7 -0
  54. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +28 -0
  55. package/bundled/dev-pipeline/templates/sections/refactor-task-contract.md +52 -0
  56. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +104 -26
  57. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +14 -0
  58. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +239 -0
  59. package/bundled/dev-pipeline/tests/test_unified_cli.py +6 -2
  60. package/bundled/dev-pipeline/tests/test_utils.py +66 -1
  61. package/bundled/skills/_metadata.json +1 -1
  62. package/bundled/skills/app-planner/SKILL.md +8 -7
  63. package/bundled/skills/app-planner/references/project-brief-guide.md +2 -2
  64. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +1 -1
  65. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +1 -1
  66. package/bundled/skills/app-planner/references/rules-configuration.md +53 -26
  67. package/bundled/skills/bug-fix-workflow/SKILL.md +191 -336
  68. package/bundled/skills/bug-planner/SKILL.md +6 -5
  69. package/bundled/skills/bug-planner/references/critic-and-verification.md +3 -3
  70. package/bundled/skills/bug-planner/references/schema-validation.md +2 -1
  71. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +70 -1
  72. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -21
  73. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
  74. package/bundled/skills/feature-pipeline-launcher/SKILL.md +25 -26
  75. package/bundled/skills/feature-planner/SKILL.md +9 -11
  76. package/bundled/skills/feature-planner/assets/planning-guide.md +1 -1
  77. package/bundled/skills/feature-planner/references/decomposition-patterns.md +1 -1
  78. package/bundled/skills/feature-planner/references/error-recovery.md +2 -1
  79. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +2 -2
  80. package/bundled/skills/feature-planner/references/new-project-planning.md +35 -39
  81. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -1
  82. package/bundled/skills/feature-workflow/SKILL.md +170 -298
  83. package/bundled/skills/prizmkit/SKILL.md +103 -57
  84. package/bundled/skills/prizmkit-code-review/SKILL.md +97 -116
  85. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
  86. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +14 -3
  87. package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
  88. package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
  89. package/bundled/skills/prizmkit-deploy/references/database-setup.md +12 -3
  90. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +220 -0
  91. package/bundled/skills/prizmkit-implement/SKILL.md +51 -40
  92. package/bundled/skills/prizmkit-init/SKILL.md +4 -3
  93. package/bundled/skills/prizmkit-plan/SKILL.md +85 -70
  94. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +94 -73
  95. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +20 -18
  96. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +11 -13
  97. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +18 -14
  98. package/bundled/skills/prizmkit-retrospective/SKILL.md +56 -48
  99. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +79 -27
  100. package/bundled/skills/prizmkit-test/SKILL.md +138 -141
  101. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +1 -1
  102. package/bundled/skills/prizmkit-test/references/examples.md +11 -9
  103. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +1 -1
  104. package/bundled/skills/prizmkit-test/references/test-report-template.md +2 -2
  105. package/bundled/skills/recovery-workflow/SKILL.md +195 -256
  106. package/bundled/skills/recovery-workflow/evals/evals.json +21 -13
  107. package/bundled/skills/recovery-workflow/references/detection.md +48 -39
  108. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +258 -322
  109. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +32 -13
  110. package/bundled/skills/refactor-planner/SKILL.md +2 -2
  111. package/bundled/skills/refactor-planner/references/behavior-preservation.md +24 -19
  112. package/bundled/skills/refactor-planner/references/fast-path.md +2 -4
  113. package/bundled/skills/refactor-planner/references/planning-phases.md +2 -2
  114. package/bundled/skills/refactor-workflow/SKILL.md +174 -307
  115. package/package.json +1 -1
  116. package/src/scaffold.js +5 -0
  117. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +0 -6
  118. package/bundled/skills/feature-workflow/references/brainstorm-guide.md +0 -137
  119. package/bundled/skills/refactor-workflow/references/brainstorm-guide.md +0 -116
@@ -884,7 +884,8 @@ class TestDirectOrchestratorImplementationPrompts:
884
884
  assert FORBIDDEN_DEV_FIX_PLACEHOLDER not in replacements
885
885
  assert FORBIDDEN_DEV_RESUME_PLACEHOLDER not in replacements
886
886
  assert "{{AGENT_PROMPT_CRITIC_PLAN_CHALLENGE}}" in replacements
887
- assert "{{AGENT_PROMPT_REVIEWER_REVIEW}}" in replacements
887
+ assert "{{AGENT_PROMPT_REVIEWER_REVIEW}}" not in replacements
888
+ assert (templates_dir / "agent-prompts" / "reviewer-review.md").exists() is False
888
889
  assert (templates_dir / "agent-prompts" / "dev-implement.md").exists() is False
889
890
  assert (templates_dir / "agent-prompts" / FORBIDDEN_DEV_FIX_PROMPT).exists() is False
890
891
  assert (templates_dir / "agent-prompts" / FORBIDDEN_DEV_RESUME_PROMPT).exists() is False
@@ -923,9 +924,19 @@ class TestDirectOrchestratorImplementationPrompts:
923
924
  assert "Implement — Orchestrator Direct" in rendered
924
925
  assert "Spawn Dev subagent" not in rendered
925
926
  assert "Spawn Dev agent" not in rendered
927
+ assert "Spawn Reviewer agent" not in rendered
928
+ assert "Reviewer Agent Def" not in rendered
929
+ assert "subagent_type=\"prizm-dev-team-reviewer\"" not in rendered
926
930
  assert "Implement — Dev" not in rendered
927
931
  assert "{{AGENT_PROMPT_DEV_IMPLEMENT}}" not in rendered
932
+ assert "{{AGENT_PROMPT_REVIEWER_REVIEW}}" not in rendered
933
+ assert "reviewer-review.md" not in rendered
928
934
  assert "dev-implement.md" not in rendered
935
+ assert "Bug Fix Documentation Policy" not in rendered
936
+ assert "For bug-fix sessions" not in rendered
937
+ assert "fix(<scope>)" not in rendered
938
+ assert "port check 3000" not in rendered
939
+ assert "localhost:3000" not in rendered
929
940
  assert FORBIDDEN_REVIEW_DEV_FIX not in rendered
930
941
  assert FORBIDDEN_REVIEWER_DEV_SPAWN not in rendered
931
942
  assert FORBIDDEN_EXPLORE_SUBAGENT not in rendered
@@ -1089,7 +1100,10 @@ def _ensure_agent_files(project, platforms=("claude", "codex")):
1089
1100
  (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
1090
1101
 
1091
1102
 
1092
- def _render_bugfix_prompt(tmp_path, platform="claude", explicit_platform=None):
1103
+ def _render_bugfix_prompt(
1104
+ tmp_path, platform="claude", explicit_platform=None,
1105
+ bug_overrides=None, retry_count="0", resume_phase="null",
1106
+ ):
1093
1107
  from generate_bugfix_prompt import main as bugfix_main
1094
1108
 
1095
1109
  project = tmp_path / "bugfix-project"
@@ -1101,8 +1115,11 @@ def _render_bugfix_prompt(tmp_path, platform="claude", explicit_platform=None):
1101
1115
  _ensure_agent_files(project)
1102
1116
  bug_list = project / ".prizmkit" / "plans" / "bug-fix-list.json"
1103
1117
  bug_list.parent.mkdir(parents=True)
1118
+ bug = {"id": "B-001", "title": "Broken", "description": "Fix it", "acceptance_criteria": ["Works"]}
1119
+ if bug_overrides:
1120
+ bug.update(bug_overrides)
1104
1121
  bug_list.write_text(json.dumps({
1105
- "bugs": [{"id": "B-001", "title": "Broken", "description": "Fix it", "acceptance_criteria": ["Works"]}],
1122
+ "bugs": [bug],
1106
1123
  "global_context": {"language": "Python"},
1107
1124
  }), encoding="utf-8")
1108
1125
  output = project / "bugfix-prompt.md"
@@ -1119,8 +1136,8 @@ def _render_bugfix_prompt(tmp_path, platform="claude", explicit_platform=None):
1119
1136
  "--bug-id", "B-001",
1120
1137
  "--session-id", "session-1",
1121
1138
  "--run-id", "run-1",
1122
- "--retry-count", "0",
1123
- "--resume-phase", "null",
1139
+ "--retry-count", retry_count,
1140
+ "--resume-phase", resume_phase,
1124
1141
  "--state-dir", str(project / ".prizmkit" / "state" / "bugfix"),
1125
1142
  "--project-root", str(project),
1126
1143
  "--output", str(output),
@@ -1139,7 +1156,7 @@ def _render_bugfix_prompt(tmp_path, platform="claude", explicit_platform=None):
1139
1156
  return output.read_text(encoding="utf-8")
1140
1157
 
1141
1158
 
1142
- def _render_refactor_prompt(tmp_path, platform="claude", explicit_platform=None):
1159
+ def _render_refactor_prompt(tmp_path, platform="claude", explicit_platform=None, refactor_overrides=None):
1143
1160
  from generate_refactor_prompt import main as refactor_main
1144
1161
 
1145
1162
  project = tmp_path / "refactor-project"
@@ -1151,14 +1168,17 @@ def _render_refactor_prompt(tmp_path, platform="claude", explicit_platform=None)
1151
1168
  _ensure_agent_files(project)
1152
1169
  refactor_list = project / ".prizmkit" / "plans" / "refactor-list.json"
1153
1170
  refactor_list.parent.mkdir(parents=True)
1171
+ refactor = {
1172
+ "id": "R-001",
1173
+ "title": "Restructure",
1174
+ "description": "Preserve behavior",
1175
+ "acceptance_criteria": ["Behavior preserved"],
1176
+ "scope": {"files": ["src/a.py"], "modules": ["src"]},
1177
+ }
1178
+ if refactor_overrides:
1179
+ refactor.update(refactor_overrides)
1154
1180
  refactor_list.write_text(json.dumps({
1155
- "refactors": [{
1156
- "id": "R-001",
1157
- "title": "Restructure",
1158
- "description": "Preserve behavior",
1159
- "acceptance_criteria": ["Behavior preserved"],
1160
- "scope": {"files": ["src/a.py"], "modules": ["src"]},
1161
- }],
1181
+ "refactors": [refactor],
1162
1182
  "global_context": {"language": "Python"},
1163
1183
  }), encoding="utf-8")
1164
1184
  output = project / "refactor-prompt.md"
@@ -1178,18 +1198,14 @@ def _render_refactor_prompt(tmp_path, platform="claude", explicit_platform=None)
1178
1198
  "--retry-count", "0",
1179
1199
  "--resume-phase", "null",
1180
1200
  "--state-dir", str(project / ".prizmkit" / "state" / "refactor"),
1201
+ "--project-root", str(project),
1181
1202
  "--output", str(output),
1182
1203
  ]
1183
- old_cwd = os.getcwd()
1184
- os.chdir(project)
1185
1204
  try:
1186
- try:
1187
- refactor_main()
1188
- except SystemExit as exc:
1189
- if exc.code not in (0, None):
1190
- raise
1191
- finally:
1192
- os.chdir(old_cwd)
1205
+ refactor_main()
1206
+ except SystemExit as exc:
1207
+ if exc.code not in (0, None):
1208
+ raise
1193
1209
  finally:
1194
1210
  os.sys.argv = old_argv
1195
1211
  if old_platform is None:
@@ -1199,6 +1215,67 @@ def _render_refactor_prompt(tmp_path, platform="claude", explicit_platform=None)
1199
1215
  return output.read_text(encoding="utf-8")
1200
1216
 
1201
1217
 
1218
+ class TestBrowserBlockingPromptPolicy:
1219
+ def test_feature_bugfix_refactor_blocking_browser_policy_stops_before_commit(self, tmp_path):
1220
+ browser_interaction = {
1221
+ "tool": "auto",
1222
+ "blocking": True,
1223
+ "verify_steps": ["Open the page"],
1224
+ }
1225
+ prompts = [
1226
+ _render_feature_prompt(tmp_path / "feature-browser-blocking", browser_interaction=browser_interaction),
1227
+ _render_bugfix_prompt(tmp_path / "bugfix-browser-blocking", bug_overrides={"browser_interaction": browser_interaction}),
1228
+ _render_refactor_prompt(tmp_path / "refactor-browser-blocking", refactor_overrides={"browser_interaction": browser_interaction}),
1229
+ ]
1230
+
1231
+ for prompt in prompts:
1232
+ assert "Browser blocking policy: `true`" in prompt
1233
+ assert "stop before commit" in prompt
1234
+ assert "Failures do NOT block the commit" not in prompt
1235
+
1236
+ def test_feature_bugfix_refactor_nonblocking_browser_policy_continues(self, tmp_path):
1237
+ browser_interaction = {
1238
+ "tool": "auto",
1239
+ "blocking": False,
1240
+ "verify_steps": ["Open the page"],
1241
+ }
1242
+ prompts = [
1243
+ _render_feature_prompt(tmp_path / "feature-browser-nonblocking", browser_interaction=browser_interaction),
1244
+ _render_bugfix_prompt(tmp_path / "bugfix-browser-nonblocking", bug_overrides={"browser_interaction": browser_interaction}),
1245
+ _render_refactor_prompt(tmp_path / "refactor-browser-nonblocking", refactor_overrides={"browser_interaction": browser_interaction}),
1246
+ ]
1247
+
1248
+ for prompt in prompts:
1249
+ assert "Browser blocking policy: `false`" in prompt
1250
+ assert "clean up the dev server/browser, and continue" in prompt
1251
+ assert "Failures do NOT block the commit" not in prompt
1252
+
1253
+
1254
+ class TestManualHybridBugfixPromptPolicy:
1255
+ def test_fresh_manual_bugfix_prompt_stops_before_commit_phase(self, tmp_path):
1256
+ prompt = _render_bugfix_prompt(
1257
+ tmp_path / "fresh-manual-bugfix",
1258
+ bug_overrides={"verification_type": "manual"},
1259
+ )
1260
+
1261
+ assert "Manual / Hybrid Verification Gate" in prompt
1262
+ assert "Stop before `/prizmkit-retrospective` and `/prizmkit-committer`" in prompt
1263
+ assert "### Phase 4: Commit & Learn" not in prompt
1264
+ assert "Run `/prizmkit-committer` with commit prefix" not in prompt
1265
+
1266
+ def test_recovery_manual_bugfix_prompt_includes_commit_phase_with_substitution_policy(self, tmp_path):
1267
+ prompt = _render_bugfix_prompt(
1268
+ tmp_path / "recovery-manual-bugfix",
1269
+ bug_overrides={"verification_type": "manual"},
1270
+ retry_count="1",
1271
+ )
1272
+
1273
+ assert "Manual / Hybrid Verification Gate" not in prompt
1274
+ assert "Recovery-class session: automated verification may substitute for manual/hybrid UAT" in prompt
1275
+ assert "### Phase 4: Commit & Learn" in prompt
1276
+ assert "Run `/prizmkit-committer` with commit prefix" in prompt
1277
+
1278
+
1202
1279
  class TestHeadlessPromptCleanupF033:
1203
1280
  def test_rendered_feature_bugfix_refactor_prompts_are_clean(self, tmp_path):
1204
1281
  prompts = []
@@ -1311,7 +1388,6 @@ class TestHeadlessPromptCleanupF033:
1311
1388
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-mixed", platform="claude")
1312
1389
 
1313
1390
  assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].endswith(".claude/agents/prizm-dev-team-reviewer.md")
1314
- assert ".claude/agents/prizm-dev-team-reviewer.md" in refactor_prompt
1315
1391
  assert ".codex/agents/prizm-dev-team-reviewer.toml" not in refactor_prompt
1316
1392
 
1317
1393
  def test_bugfix_refactor_explicit_codex_override(self, tmp_path, monkeypatch):
@@ -1333,7 +1409,6 @@ class TestHeadlessPromptCleanupF033:
1333
1409
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-codex", platform="claude", explicit_platform="codex")
1334
1410
 
1335
1411
  assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].endswith(".codex/agents/prizm-dev-team-reviewer.toml")
1336
- assert ".codex/agents/prizm-dev-team-reviewer.toml" in refactor_prompt
1337
1412
  assert ".claude/agents/prizm-dev-team-reviewer.md" not in refactor_prompt
1338
1413
 
1339
1414
  def test_explicit_codex_env_still_renders_codex_critic_path(self, tmp_path, monkeypatch):
@@ -1399,7 +1474,9 @@ class TestFeatureBootstrapShellExtraction:
1399
1474
  assert "executable version opencli --arg=--version" in prompt
1400
1475
  assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
1401
1476
  assert "process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>" in prompt
1402
- assert "port check <DEV_PORT>" in prompt or "port check 3000" in prompt
1477
+ assert "port check <DEV_PORT>" in prompt
1478
+ assert "port check 3000" not in prompt
1479
+ assert "localhost:3000" not in prompt
1403
1480
 
1404
1481
  def test_rendered_legacy_tier_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
1405
1482
  cases = [
@@ -1419,7 +1496,8 @@ class TestFeatureBootstrapShellExtraction:
1419
1496
  self.assert_shell_extracted_operations_removed(prompt)
1420
1497
  assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
1421
1498
  assert "process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>" in prompt
1422
- assert "port check 3000" in prompt
1499
+ assert "port check 3000" not in prompt
1500
+ assert "localhost:3000" not in prompt
1423
1501
 
1424
1502
  def test_legacy_tier_specific_extracted_operations_map_to_helper_commands(self, tmp_path):
1425
1503
  tier1 = _render_feature_prompt(
@@ -307,6 +307,20 @@ def test_template_source_is_clean_for_headless_prompt_contract():
307
307
  assert forbidden not in content
308
308
 
309
309
 
310
+ def test_bugfix_prompt_keeps_fine_grained_phases_out_of_external_status_contract():
311
+ content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
312
+ old_statuses = ["triaging", "reproducing", "fixing", "verifying"]
313
+
314
+ assert "external bug-list status field" in content
315
+ assert "pending`, `in_progress`, `completed`, `failed`, `skipped`, or `needs_info`" in content
316
+ for status in old_statuses:
317
+ assert f"status=\"{status}\"" not in content
318
+ assert f"status = \"{status}\"" not in content
319
+ assert f"status='{status}'" not in content
320
+ assert f"status = '{status}'" not in content
321
+ assert f"Set bug status to `{status}`" not in content
322
+
323
+
310
324
  # ---------------------------------------------------------------------------
311
325
  # Anti-Bash regression guardrails (F-039)
312
326
  # Note: grep -n, sed -n, grep -q, wc -l excluded — referenced in bugfix context
@@ -61,6 +61,35 @@ def _write_feature_list(paths):
61
61
  )
62
62
 
63
63
 
64
+ def _write_family_plan(paths, kind, items):
65
+ plan_config = {
66
+ "feature": (paths.feature_plan, "features", "dev-pipeline-feature-list-v1"),
67
+ "bugfix": (paths.bugfix_plan, "bugs", "dev-pipeline-bug-fix-list-v1"),
68
+ "refactor": (paths.refactor_plan, "refactors", "dev-pipeline-refactor-list-v1"),
69
+ }
70
+ plan_path, list_key, schema = plan_config[kind]
71
+ plan_path.write_text(
72
+ json.dumps({"$schema": schema, "project_name": "Fake", list_key: items}),
73
+ encoding="utf-8",
74
+ )
75
+ return plan_path
76
+
77
+
78
+ def _get_next_result(kind, paths):
79
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
80
+ from prizmkit_runtime.runner_status import get_next
81
+
82
+ family = family_for(kind, paths)
83
+ invocation = parse_invocation(family, "run", ())
84
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": family.plan_path})
85
+ return get_next(family, invocation, paths.project_root)
86
+
87
+
88
+ def _item_id_from_next_result(kind, result):
89
+ key = {"feature": "feature_id", "bugfix": "bug_id", "refactor": "refactor_id"}[kind]
90
+ return result.data[key]
91
+
92
+
64
93
  def test_retired_shell_runtime_files_are_absent_from_source():
65
94
  retired = [
66
95
  "run-feature.sh",
@@ -360,6 +389,216 @@ def test_status_bridge_preserves_feature_filter_selection(tmp_path):
360
389
  assert update.data["context_overflow_count"] == 1
361
390
 
362
391
 
392
+ def test_status_bridge_uses_stable_dependency_order_across_pipeline_families(tmp_path):
393
+ cases = {
394
+ "feature": [
395
+ {"id": "F-001", "title": "Blocked", "status": "pending", "priority": "critical", "dependencies": ["F-002"]},
396
+ {"id": "F-002", "title": "First", "status": "pending", "priority": "low", "dependencies": []},
397
+ {"id": "F-003", "title": "Independent", "status": "pending", "priority": "high", "dependencies": []},
398
+ ],
399
+ "bugfix": [
400
+ {"id": "B-001", "title": "Blocked", "status": "pending", "severity": "critical", "priority": "high", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": ["B-002"]},
401
+ {"id": "B-002", "title": "First", "status": "pending", "severity": "low", "priority": "low", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
402
+ {"id": "B-003", "title": "Independent", "status": "pending", "severity": "high", "priority": "high", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
403
+ ],
404
+ "refactor": [
405
+ {"id": "R-001", "title": "Blocked", "description": "blocked", "scope": {}, "type": "restructure", "priority": "critical", "complexity": "high", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": ["R-002"], "status": "pending"},
406
+ {"id": "R-002", "title": "First", "description": "first", "scope": {}, "type": "extract", "priority": "low", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "pending"},
407
+ {"id": "R-003", "title": "Independent", "description": "independent", "scope": {}, "type": "simplify", "priority": "high", "complexity": "medium", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "pending"},
408
+ ],
409
+ }
410
+
411
+ for kind, items in cases.items():
412
+ paths = _make_paths(tmp_path / f"stable-{kind}")
413
+ _write_family_plan(paths, kind, items)
414
+
415
+ result = _get_next_result(kind, paths)
416
+
417
+ assert result.ok
418
+ assert _item_id_from_next_result(kind, result) == {"feature": "F-002", "bugfix": "B-002", "refactor": "R-002"}[kind]
419
+
420
+
421
+ def test_status_bridge_prefers_in_progress_items_with_satisfied_dependencies(tmp_path):
422
+ cases = {
423
+ "feature": [
424
+ {"id": "F-001", "title": "Done", "status": "completed", "priority": "low", "dependencies": []},
425
+ {"id": "F-002", "title": "Resume", "status": "in_progress", "priority": "low", "dependencies": ["F-001"]},
426
+ {"id": "F-003", "title": "Pending", "status": "pending", "priority": "critical", "dependencies": []},
427
+ ],
428
+ "bugfix": [
429
+ {"id": "B-001", "title": "Done", "status": "completed", "severity": "low", "priority": "low", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
430
+ {"id": "B-002", "title": "Resume", "status": "in_progress", "severity": "low", "priority": "low", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": ["B-001"]},
431
+ {"id": "B-003", "title": "Pending", "status": "pending", "severity": "critical", "priority": "high", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
432
+ ],
433
+ "refactor": [
434
+ {"id": "R-001", "title": "Done", "description": "done", "scope": {}, "type": "extract", "priority": "low", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "completed"},
435
+ {"id": "R-002", "title": "Resume", "description": "resume", "scope": {}, "type": "extract", "priority": "low", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": ["R-001"], "status": "in_progress"},
436
+ {"id": "R-003", "title": "Pending", "description": "pending", "scope": {}, "type": "migrate", "priority": "critical", "complexity": "high", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "pending"},
437
+ ],
438
+ }
439
+
440
+ for kind, items in cases.items():
441
+ paths = _make_paths(tmp_path / f"resume-{kind}")
442
+ _write_family_plan(paths, kind, items)
443
+
444
+ result = _get_next_result(kind, paths)
445
+
446
+ assert result.ok
447
+ assert _item_id_from_next_result(kind, result) == {"feature": "F-002", "bugfix": "B-002", "refactor": "R-002"}[kind]
448
+
449
+
450
+ def test_refactor_dependencies_require_completed_not_other_terminal_statuses(tmp_path):
451
+ paths = _make_paths(tmp_path)
452
+ _write_family_plan(
453
+ paths,
454
+ "refactor",
455
+ [
456
+ {"id": "R-001", "title": "Failed Dependency", "description": "failed", "scope": {}, "type": "extract", "priority": "high", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "failed"},
457
+ {"id": "R-002", "title": "Blocked", "description": "blocked", "scope": {}, "type": "restructure", "priority": "high", "complexity": "medium", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": ["R-001"], "status": "pending"},
458
+ ],
459
+ )
460
+
461
+ result = _get_next_result("refactor", paths)
462
+
463
+ assert result.ok
464
+ assert result.text == "PIPELINE_BLOCKED"
465
+ assert result.data is None
466
+
467
+
468
+ def test_init_scripts_reject_missing_self_and_cycle_dependencies(tmp_path):
469
+ script_config = {
470
+ "feature": ("init-pipeline.py", "--feature-list", "F-", "missing dependency"),
471
+ "bugfix": ("init-bugfix-pipeline.py", "--bug-list", "B-", "missing dependency"),
472
+ "refactor": ("init-refactor-pipeline.py", "--refactor-list", "R-", "missing dependency"),
473
+ }
474
+ scenarios = {
475
+ "missing": lambda kind, prefix: _family_items(kind, [
476
+ (f"{prefix}001", [f"{prefix}999"], "pending"),
477
+ ]),
478
+ "self": lambda kind, prefix: _family_items(kind, [
479
+ (f"{prefix}001", [f"{prefix}001"], "pending"),
480
+ ]),
481
+ "cycle": lambda kind, prefix: _family_items(kind, [
482
+ (f"{prefix}001", [f"{prefix}002"], "pending"),
483
+ (f"{prefix}002", [f"{prefix}001"], "pending"),
484
+ ]),
485
+ }
486
+ expected_fragments = {
487
+ "missing": "missing dependency",
488
+ "self": "cannot depend on itself",
489
+ "cycle": "Dependency cycle detected",
490
+ }
491
+
492
+ for kind, (script_name, list_arg, prefix, _missing_fragment) in script_config.items():
493
+ for scenario, build_items in scenarios.items():
494
+ paths = _make_paths(tmp_path / f"init-{kind}-{scenario}")
495
+ plan_path = _write_family_plan(paths, kind, build_items(kind, prefix))
496
+ result = subprocess.run(
497
+ [
498
+ PYTHON_310_PLUS,
499
+ str(PIPELINE_ROOT / "scripts" / script_name),
500
+ list_arg,
501
+ str(plan_path),
502
+ "--state-dir",
503
+ str(paths.project_root / ".prizmkit" / "state" / f"init-{kind}-{scenario}"),
504
+ ],
505
+ cwd=REPO_ROOT,
506
+ stdout=subprocess.PIPE,
507
+ stderr=subprocess.PIPE,
508
+ text=True,
509
+ check=False,
510
+ )
511
+
512
+ assert result.returncode == 1, (kind, scenario, result.stdout, result.stderr)
513
+ payload = json.loads(result.stdout)
514
+ assert any(expected_fragments[scenario] in error for error in payload["errors"])
515
+
516
+
517
+ def test_bug_planner_validator_rejects_dependency_cycles():
518
+ from importlib.util import module_from_spec, spec_from_file_location
519
+
520
+ script_path = REPO_ROOT / "core" / "skills" / "orchestration-skill" / "pipelines" / "bug-planner" / "scripts" / "validate-bug-list.py"
521
+ spec = spec_from_file_location("bug_planner_validate_bug_list", script_path)
522
+ module = module_from_spec(spec)
523
+ spec.loader.exec_module(module)
524
+
525
+ result = module.validate({
526
+ "$schema": "dev-pipeline-bug-fix-list-v1",
527
+ "project_name": "Fake",
528
+ "bugs": [
529
+ {
530
+ "id": "B-001",
531
+ "title": "First",
532
+ "description": "First bug",
533
+ "severity": "medium",
534
+ "error_source": {"type": "user_report"},
535
+ "verification_type": "manual",
536
+ "acceptance_criteria": ["fixed"],
537
+ "dependencies": ["B-002"],
538
+ "status": "pending",
539
+ },
540
+ {
541
+ "id": "B-002",
542
+ "title": "Second",
543
+ "description": "Second bug",
544
+ "severity": "medium",
545
+ "error_source": {"type": "user_report"},
546
+ "verification_type": "manual",
547
+ "acceptance_criteria": ["fixed"],
548
+ "dependencies": ["B-001"],
549
+ "status": "pending",
550
+ },
551
+ ],
552
+ })
553
+
554
+ assert result["valid"] is False
555
+ assert any("Dependency cycle detected" in error for error in result["errors"])
556
+
557
+
558
+
559
+ def _family_items(kind, entries):
560
+ items = []
561
+ for item_id, dependencies, status in entries:
562
+ if kind == "feature":
563
+ items.append({
564
+ "id": item_id,
565
+ "title": item_id,
566
+ "description": "Implement this feature with enough detail for initialization validation.",
567
+ "priority": "medium",
568
+ "dependencies": dependencies,
569
+ "acceptance_criteria": ["done"],
570
+ "status": status,
571
+ })
572
+ elif kind == "bugfix":
573
+ items.append({
574
+ "id": item_id,
575
+ "title": item_id,
576
+ "description": "Bug description",
577
+ "severity": "medium",
578
+ "priority": "medium",
579
+ "error_source": {"type": "user_report"},
580
+ "verification_type": "manual",
581
+ "acceptance_criteria": ["fixed"],
582
+ "dependencies": dependencies,
583
+ "status": status,
584
+ })
585
+ else:
586
+ items.append({
587
+ "id": item_id,
588
+ "title": item_id,
589
+ "description": "Refactor description",
590
+ "scope": {},
591
+ "type": "extract",
592
+ "priority": "medium",
593
+ "complexity": "low",
594
+ "behavior_preservation": {"strategy": "test-gate"},
595
+ "acceptance_criteria": ["done"],
596
+ "dependencies": dependencies,
597
+ "status": status,
598
+ })
599
+ return items
600
+
601
+
363
602
  def test_status_bridge_passes_max_retries_from_invocation_or_environment(monkeypatch, tmp_path):
364
603
  import subprocess
365
604
  from prizmkit_runtime.runner_models import family_for, parse_invocation
@@ -458,7 +458,7 @@ def test_interoperability_preserves_existing_utility_mapping():
458
458
  explicitly_library_only = set(UTILITY_LIBRARY_ONLY)
459
459
 
460
460
  assert actual_scripts == mapped_scripts | explicitly_library_only
461
- assert explicitly_library_only == {"continuation.py", "utils.py"}
461
+ assert explicitly_library_only == {"continuation.py", "prompt_framework.py", "utils.py"}
462
462
  assert "parse-stream-progress.py" in mapped_scripts
463
463
  assert "prizmkit-test-gate.py" not in mapped_scripts
464
464
 
@@ -803,7 +803,11 @@ def test_headless_subagent_prompt_guidance_keeps_no_worktree_boundary_visible():
803
803
 
804
804
  for path in guidance_files:
805
805
  text = path.read_text(encoding="utf-8")
806
- assert "default/no worktree" in text, path
806
+ assert (
807
+ "default/no worktree" in text
808
+ or "current-workspace / active-checkout / no-worktree" in text
809
+ or "current workspace / active checkout / no worktree" in text
810
+ ), path
807
811
  assert "do not request or create" in text.lower() or "do not request worktree isolation" in text.lower(), path
808
812
  assert ".claude/worktrees/..." in text, path
809
813
  assert ".prizmkit/state/worktrees/..." in text, path
@@ -4,7 +4,13 @@ import json
4
4
  import os
5
5
  import tempfile
6
6
 
7
- from utils import load_json_file, write_json_file, pad_right, _build_progress_bar
7
+ from utils import (
8
+ load_json_file,
9
+ write_json_file,
10
+ pad_right,
11
+ _build_progress_bar,
12
+ stable_topological_order,
13
+ )
8
14
 
9
15
 
10
16
  # ---------------------------------------------------------------------------
@@ -80,3 +86,62 @@ class TestBuildProgressBar:
80
86
  def test_fifty_percent(self):
81
87
  bar = _build_progress_bar(50, width=10)
82
88
  assert "50%" in bar
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # stable_topological_order
93
+ # ---------------------------------------------------------------------------
94
+
95
+ class TestStableTopologicalOrder:
96
+ def test_dependencies_precede_dependents_with_stable_tie_breaking(self):
97
+ items = [
98
+ {"id": "F-001", "dependencies": []},
99
+ {"id": "F-002", "dependencies": ["F-001"]},
100
+ {"id": "F-003", "dependencies": []},
101
+ ]
102
+
103
+ ordered, errors = stable_topological_order(items, item_label="Feature")
104
+
105
+ assert errors == []
106
+ assert [item["id"] for item in ordered] == ["F-001", "F-002", "F-003"]
107
+
108
+ def test_unrelated_items_preserve_original_list_order(self):
109
+ items = [
110
+ {"id": "F-010", "dependencies": []},
111
+ {"id": "F-002", "dependencies": []},
112
+ {"id": "F-007", "dependencies": []},
113
+ ]
114
+
115
+ ordered, errors = stable_topological_order(items, item_label="Feature")
116
+
117
+ assert errors == []
118
+ assert [item["id"] for item in ordered] == ["F-010", "F-002", "F-007"]
119
+
120
+ def test_missing_dependency_is_blocking_error(self):
121
+ items = [
122
+ {"id": "B-001", "dependencies": ["B-999"]},
123
+ ]
124
+
125
+ _ordered, errors = stable_topological_order(items, item_label="Bug")
126
+
127
+ assert errors == ["Bug 'B-001' depends on missing dependency 'B-999'"]
128
+
129
+ def test_self_dependency_is_blocking_error(self):
130
+ items = [
131
+ {"id": "R-001", "dependencies": ["R-001"]},
132
+ ]
133
+
134
+ _ordered, errors = stable_topological_order(items, item_label="Refactor")
135
+
136
+ assert errors == ["Refactor 'R-001' cannot depend on itself"]
137
+
138
+ def test_cycle_reports_chain(self):
139
+ items = [
140
+ {"id": "F-001", "dependencies": ["F-003"]},
141
+ {"id": "F-002", "dependencies": ["F-001"]},
142
+ {"id": "F-003", "dependencies": ["F-002"]},
143
+ ]
144
+
145
+ _ordered, errors = stable_topological_order(items, item_label="Feature")
146
+
147
+ assert errors == ["Dependency cycle detected: F-001 -> F-003 -> F-002 -> F-001"]
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.111",
2
+ "version": "1.1.113",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",