prizmkit 1.1.110 → 1.1.112

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 (113) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/agents/prizm-dev-team-reviewer.md +15 -0
  3. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
  4. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +55 -76
  5. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +283 -421
  6. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  7. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +259 -481
  8. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -0
  9. package/bundled/dev-pipeline/scripts/init-pipeline.py +12 -0
  10. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +24 -1
  11. package/bundled/dev-pipeline/scripts/prompt_framework.py +312 -0
  12. package/bundled/dev-pipeline/scripts/update-bug-status.py +87 -20
  13. package/bundled/dev-pipeline/scripts/update-feature-status.py +82 -16
  14. package/bundled/dev-pipeline/scripts/update-refactor-status.py +68 -28
  15. package/bundled/dev-pipeline/scripts/utils.py +171 -0
  16. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  17. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +10 -18
  18. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +14 -25
  19. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +9 -1
  20. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +11 -8
  21. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +122 -182
  22. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +16 -0
  23. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +12 -0
  24. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +9 -0
  25. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +31 -0
  26. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +22 -0
  27. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +23 -0
  28. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +20 -0
  29. package/bundled/dev-pipeline/templates/sections/bugfix-phase-init.md +9 -0
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +15 -0
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +29 -0
  32. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +11 -0
  33. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +7 -0
  34. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +31 -0
  35. package/bundled/dev-pipeline/templates/sections/bugfix-task-contract.md +40 -0
  36. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +1 -1
  37. package/bundled/dev-pipeline/templates/sections/failure-capture.md +2 -2
  38. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +1 -1
  39. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +17 -0
  40. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +13 -0
  41. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +9 -0
  42. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +37 -0
  43. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +22 -0
  44. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +16 -0
  45. package/bundled/dev-pipeline/templates/sections/refactor-phase-init.md +9 -0
  46. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +22 -0
  47. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +19 -0
  48. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +11 -0
  49. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +7 -0
  50. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +28 -0
  51. package/bundled/dev-pipeline/templates/sections/refactor-task-contract.md +52 -0
  52. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +122 -5
  53. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +122 -0
  54. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +124 -0
  55. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +197 -0
  56. package/bundled/dev-pipeline/tests/test_unified_cli.py +7 -3
  57. package/bundled/dev-pipeline/tests/test_utils.py +66 -1
  58. package/bundled/skills/_metadata.json +1 -1
  59. package/bundled/skills/app-planner/SKILL.md +8 -7
  60. package/bundled/skills/app-planner/references/project-brief-guide.md +2 -2
  61. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +1 -1
  62. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +1 -1
  63. package/bundled/skills/app-planner/references/rules-configuration.md +53 -26
  64. package/bundled/skills/bug-fix-workflow/SKILL.md +191 -336
  65. package/bundled/skills/bug-planner/SKILL.md +6 -5
  66. package/bundled/skills/bug-planner/references/critic-and-verification.md +3 -3
  67. package/bundled/skills/bug-planner/references/schema-validation.md +2 -1
  68. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +30 -1
  69. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +16 -16
  70. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
  71. package/bundled/skills/feature-pipeline-launcher/SKILL.md +19 -11
  72. package/bundled/skills/feature-planner/SKILL.md +9 -11
  73. package/bundled/skills/feature-planner/assets/planning-guide.md +1 -1
  74. package/bundled/skills/feature-planner/references/decomposition-patterns.md +1 -1
  75. package/bundled/skills/feature-planner/references/error-recovery.md +2 -1
  76. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +2 -2
  77. package/bundled/skills/feature-planner/references/new-project-planning.md +35 -39
  78. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -1
  79. package/bundled/skills/feature-workflow/SKILL.md +169 -298
  80. package/bundled/skills/prizmkit/SKILL.md +103 -57
  81. package/bundled/skills/prizmkit-code-review/SKILL.md +97 -94
  82. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
  83. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +13 -1
  84. package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
  85. package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
  86. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +220 -0
  87. package/bundled/skills/prizmkit-implement/SKILL.md +51 -40
  88. package/bundled/skills/prizmkit-init/SKILL.md +4 -3
  89. package/bundled/skills/prizmkit-plan/SKILL.md +85 -70
  90. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +94 -73
  91. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +20 -18
  92. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +18 -14
  93. package/bundled/skills/prizmkit-retrospective/SKILL.md +56 -48
  94. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +79 -27
  95. package/bundled/skills/prizmkit-test/SKILL.md +138 -141
  96. package/bundled/skills/prizmkit-test/references/examples.md +10 -8
  97. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +1 -1
  98. package/bundled/skills/prizmkit-test/references/test-report-template.md +2 -2
  99. package/bundled/skills/recovery-workflow/SKILL.md +195 -256
  100. package/bundled/skills/recovery-workflow/evals/evals.json +21 -13
  101. package/bundled/skills/recovery-workflow/references/detection.md +48 -39
  102. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +258 -322
  103. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -11
  104. package/bundled/skills/refactor-planner/SKILL.md +2 -2
  105. package/bundled/skills/refactor-planner/references/behavior-preservation.md +24 -19
  106. package/bundled/skills/refactor-planner/references/fast-path.md +2 -4
  107. package/bundled/skills/refactor-planner/references/planning-phases.md +2 -2
  108. package/bundled/skills/refactor-workflow/SKILL.md +173 -307
  109. package/package.json +1 -1
  110. package/src/scaffold.js +5 -0
  111. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +0 -6
  112. package/bundled/skills/feature-workflow/references/brainstorm-guide.md +0 -137
  113. package/bundled/skills/refactor-workflow/references/brainstorm-guide.md +0 -116
@@ -10,6 +10,7 @@ from pathlib import Path
10
10
  from generate_bootstrap_prompt import (
11
11
  ACTIVE_AGENT_PROMPTS,
12
12
  compute_feature_slug,
13
+ detect_test_commands,
13
14
  find_feature,
14
15
  format_acceptance_criteria,
15
16
  format_ac_checklist,
@@ -883,7 +884,8 @@ class TestDirectOrchestratorImplementationPrompts:
883
884
  assert FORBIDDEN_DEV_FIX_PLACEHOLDER not in replacements
884
885
  assert FORBIDDEN_DEV_RESUME_PLACEHOLDER not in replacements
885
886
  assert "{{AGENT_PROMPT_CRITIC_PLAN_CHALLENGE}}" in replacements
886
- 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
887
889
  assert (templates_dir / "agent-prompts" / "dev-implement.md").exists() is False
888
890
  assert (templates_dir / "agent-prompts" / FORBIDDEN_DEV_FIX_PROMPT).exists() is False
889
891
  assert (templates_dir / "agent-prompts" / FORBIDDEN_DEV_RESUME_PROMPT).exists() is False
@@ -922,9 +924,19 @@ class TestDirectOrchestratorImplementationPrompts:
922
924
  assert "Implement — Orchestrator Direct" in rendered
923
925
  assert "Spawn Dev subagent" not in rendered
924
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
925
930
  assert "Implement — Dev" not in rendered
926
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
927
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
928
940
  assert FORBIDDEN_REVIEW_DEV_FIX not in rendered
929
941
  assert FORBIDDEN_REVIEWER_DEV_SPAWN not in rendered
930
942
  assert FORBIDDEN_EXPLORE_SUBAGENT not in rendered
@@ -1214,6 +1226,19 @@ class TestHeadlessPromptCleanupF033:
1214
1226
  assert ".prizmkit-test-started" in prompt
1215
1227
  assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1216
1228
  assert "prizmkit-runtime-helper.py" in prompt
1229
+ # Anti-Bash guardrails for bugfix/refactor branches (F-039)
1230
+ if "Bug ID" in prompt:
1231
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1232
+ assert "prizmkit-runtime-helper.py" in prompt
1233
+ assert "2>/dev/null" not in prompt
1234
+ assert "lsof -ti" not in prompt
1235
+ assert "which " not in prompt # no which-command shell glue
1236
+ if "Refactor ID" in prompt:
1237
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1238
+ assert "prizmkit-runtime-helper.py" in prompt
1239
+ assert "2>/dev/null" not in prompt
1240
+ assert "lsof -ti" not in prompt
1241
+ assert "which " not in prompt # no which-command shell glue
1217
1242
  assert "python3 {{INIT_SCRIPT_PATH}}" not in prompt
1218
1243
 
1219
1244
  def test_legacy_feature_templates_are_clean_when_rendered(self, tmp_path):
@@ -1297,7 +1322,6 @@ class TestHeadlessPromptCleanupF033:
1297
1322
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-mixed", platform="claude")
1298
1323
 
1299
1324
  assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].endswith(".claude/agents/prizm-dev-team-reviewer.md")
1300
- assert ".claude/agents/prizm-dev-team-reviewer.md" in refactor_prompt
1301
1325
  assert ".codex/agents/prizm-dev-team-reviewer.toml" not in refactor_prompt
1302
1326
 
1303
1327
  def test_bugfix_refactor_explicit_codex_override(self, tmp_path, monkeypatch):
@@ -1319,7 +1343,6 @@ class TestHeadlessPromptCleanupF033:
1319
1343
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-codex", platform="claude", explicit_platform="codex")
1320
1344
 
1321
1345
  assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].endswith(".codex/agents/prizm-dev-team-reviewer.toml")
1322
- assert ".codex/agents/prizm-dev-team-reviewer.toml" in refactor_prompt
1323
1346
  assert ".claude/agents/prizm-dev-team-reviewer.md" not in refactor_prompt
1324
1347
 
1325
1348
  def test_explicit_codex_env_still_renders_codex_critic_path(self, tmp_path, monkeypatch):
@@ -1385,7 +1408,9 @@ class TestFeatureBootstrapShellExtraction:
1385
1408
  assert "executable version opencli --arg=--version" in prompt
1386
1409
  assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
1387
1410
  assert "process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>" in prompt
1388
- assert "port check <DEV_PORT>" in prompt or "port check 3000" in prompt
1411
+ assert "port check <DEV_PORT>" in prompt
1412
+ assert "port check 3000" not in prompt
1413
+ assert "localhost:3000" not in prompt
1389
1414
 
1390
1415
  def test_rendered_legacy_tier_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
1391
1416
  cases = [
@@ -1405,7 +1430,8 @@ class TestFeatureBootstrapShellExtraction:
1405
1430
  self.assert_shell_extracted_operations_removed(prompt)
1406
1431
  assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
1407
1432
  assert "process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>" in prompt
1408
- assert "port check 3000" in prompt
1433
+ assert "port check 3000" not in prompt
1434
+ assert "localhost:3000" not in prompt
1409
1435
 
1410
1436
  def test_legacy_tier_specific_extracted_operations_map_to_helper_commands(self, tmp_path):
1411
1437
  tier1 = _render_feature_prompt(
@@ -1436,3 +1462,94 @@ class TestFeatureBootstrapShellExtraction:
1436
1462
  assert "text search . --pattern \"123-prompt-cleanup\" --json" in tier3
1437
1463
  assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier3
1438
1464
  assert "Check if feature already committed by inspecting recent git history" in tier3
1465
+
1466
+
1467
+ # ---------------------------------------------------------------------------
1468
+ # detect_test_commands
1469
+ # ---------------------------------------------------------------------------
1470
+
1471
+ import json as _json
1472
+
1473
+ class TestDetectTestCommands:
1474
+ def test_no_package_json_returns_empty(self, tmp_path):
1475
+ """When no package.json exists, no npm test emitted."""
1476
+ result = detect_test_commands(str(tmp_path))
1477
+ assert "npm test" not in result
1478
+ assert "pnpm test" not in result
1479
+ assert "yarn test" not in result
1480
+
1481
+ def test_package_json_no_test_script(self, tmp_path):
1482
+ """package.json exists but has no test script → no npm test emitted."""
1483
+ pkg = tmp_path / "package.json"
1484
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"start": "node index.js"}}), encoding="utf-8")
1485
+ result = detect_test_commands(str(tmp_path))
1486
+ # Bug: current code returns "npm test" even with no test script
1487
+ assert "npm test" not in result, (
1488
+ "Should NOT emit npm test when package.json has no test script"
1489
+ )
1490
+
1491
+ def test_package_json_with_test_script(self, tmp_path):
1492
+ """package.json with scripts.test → emits npm test."""
1493
+ pkg = tmp_path / "package.json"
1494
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test": "vitest run"}}), encoding="utf-8")
1495
+ result = detect_test_commands(str(tmp_path))
1496
+ # Current code returns "npm test" (hardcoded), but at minimum it should
1497
+ # still return something test-like. After fix, it becomes "npm test"
1498
+ assert "npm test" in result, (
1499
+ "Should emit npm test when package.json has test script"
1500
+ )
1501
+
1502
+ def test_package_json_non_test_scripts_only(self, tmp_path):
1503
+ """package.json exists but scripts only has non-test entries."""
1504
+ pkg = tmp_path / "package.json"
1505
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"build": "tsc", "start": "node dist/index.js"}}), encoding="utf-8")
1506
+ result = detect_test_commands(str(tmp_path))
1507
+ assert "npm test" not in result, (
1508
+ "Should NOT emit npm test when no test-related scripts exist"
1509
+ )
1510
+
1511
+ def test_pnpm_detected_from_lockfile(self, tmp_path):
1512
+ """pnpm-lock.yaml → emits pnpm test when test script exists."""
1513
+ pkg = tmp_path / "package.json"
1514
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test": "vitest"}}), encoding="utf-8")
1515
+ (tmp_path / "pnpm-lock.yaml").write_text("", encoding="utf-8")
1516
+ result = detect_test_commands(str(tmp_path))
1517
+ assert "pnpm test" in result
1518
+
1519
+ def test_yarn_detected_from_lockfile(self, tmp_path):
1520
+ """yarn.lock → emits yarn test when test script exists."""
1521
+ pkg = tmp_path / "package.json"
1522
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test": "jest"}}), encoding="utf-8")
1523
+ (tmp_path / "yarn.lock").write_text("", encoding="utf-8")
1524
+ result = detect_test_commands(str(tmp_path))
1525
+ assert "yarn test" in result
1526
+
1527
+ def test_corrupt_package_json_graceful_fallback(self, tmp_path):
1528
+ """Corrupt/unparseable package.json → skip JS detection gracefully."""
1529
+ (tmp_path / "package.json").write_text("not valid json {{{", encoding="utf-8")
1530
+ result = detect_test_commands(str(tmp_path))
1531
+ # Should not crash, should not emit npm test
1532
+ assert "npm test" not in result
1533
+
1534
+ def test_non_js_detections_preserved(self, tmp_path):
1535
+ """Go, Rust, Python, Makefile detections unchanged."""
1536
+ (tmp_path / "go.mod").write_text("", encoding="utf-8")
1537
+ (tmp_path / "Cargo.toml").write_text("", encoding="utf-8")
1538
+ (tmp_path / "setup.py").write_text("", encoding="utf-8")
1539
+ makefile = tmp_path / "Makefile"
1540
+ makefile.write_text("test:\n\tpytest\n", encoding="utf-8")
1541
+ result = detect_test_commands(str(tmp_path))
1542
+ assert "go test ./..." in result
1543
+ assert "cargo test" in result
1544
+ assert "pytest" in result
1545
+ assert "make test" in result
1546
+
1547
+ def test_package_json_test_unit_script_only(self, tmp_path):
1548
+ """package.json with test:unit but no test → fallback to available test script."""
1549
+ pkg = tmp_path / "package.json"
1550
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test:unit": "vitest run"}}), encoding="utf-8")
1551
+ (tmp_path / "package-lock.json").write_text("", encoding="utf-8")
1552
+ result = detect_test_commands(str(tmp_path))
1553
+ # Should detect test:unit as a viable test script
1554
+ assert "npm run test:unit" in result
1555
+
@@ -2,6 +2,8 @@
2
2
 
3
3
  from argparse import Namespace
4
4
  from pathlib import Path
5
+ import json
6
+ import os
5
7
 
6
8
  from generate_bugfix_prompt import (
7
9
  build_replacements,
@@ -303,3 +305,123 @@ def test_template_source_is_clean_for_headless_prompt_contract():
303
305
  content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
304
306
  for forbidden in HEADLESS_FORBIDDEN_PROMPT_STRINGS:
305
307
  assert forbidden not in content
308
+
309
+
310
+ # ---------------------------------------------------------------------------
311
+ # Anti-Bash regression guardrails (F-039)
312
+ # Note: grep -n, sed -n, grep -q, wc -l excluded — referenced in bugfix context
313
+ # budget rules as AI tool hints, not as operational shell snippets.
314
+ # | sed (trailing space) only matches pipe-to-sed operational patterns.
315
+ # ---------------------------------------------------------------------------
316
+
317
+ RETIRED_EXTRACTED_SNIPPETS_BUGFIX = [
318
+ "find . -maxdepth",
319
+ "| sed ",
320
+ r"grep -c '^\- \[ \]'",
321
+ "2>/dev/null",
322
+ "/dev/null",
323
+ "$DEV_PORT",
324
+ "$!",
325
+ "$HOME",
326
+ "if [ ",
327
+ "which playwright-cli",
328
+ "which opencli",
329
+ "lsof -ti",
330
+ "xargs",
331
+ " kill -",
332
+ "kill -",
333
+ "|| echo",
334
+ "mkdir -p",
335
+ "touch .prizmkit",
336
+ ]
337
+
338
+
339
+ def _render_bugfix_prompt(tmp_path, platform="claude"):
340
+ """Render a complete bugfix prompt through the generator."""
341
+ from generate_bugfix_prompt import main as bugfix_main
342
+
343
+ project = tmp_path / "bugfix-anti-bash"
344
+ project.mkdir(parents=True)
345
+ (project / ".prizmkit").mkdir(exist_ok=True)
346
+ (project / ".prizmkit" / "config.json").write_text(
347
+ json.dumps({"platform": platform, "ai_cli": platform}), encoding="utf-8"
348
+ )
349
+ _ensure_agent_files(project)
350
+ bug_list = project / ".prizmkit" / "plans" / "bug-fix-list.json"
351
+ bug_list.parent.mkdir(parents=True)
352
+ bug_list.write_text(json.dumps({
353
+ "bugs": [{"id": "B-001", "title": "Broken", "description": "Fix it",
354
+ "acceptance_criteria": ["Works"]}],
355
+ "global_context": {"language": "Python"},
356
+ }), encoding="utf-8")
357
+ output = project / "bugfix-prompt.md"
358
+ old_argv = os.sys.argv
359
+ old_platform = os.environ.get("PRIZMKIT_PLATFORM")
360
+ try:
361
+ os.environ.pop("PRIZMKIT_PLATFORM", None)
362
+ os.sys.argv = [
363
+ "generate-bugfix-prompt.py",
364
+ "--bug-list", str(bug_list),
365
+ "--bug-id", "B-001",
366
+ "--session-id", "session-1",
367
+ "--run-id", "run-1",
368
+ "--retry-count", "0",
369
+ "--resume-phase", "null",
370
+ "--state-dir", str(project / ".prizmkit" / "state" / "bugfix"),
371
+ "--project-root", str(project),
372
+ "--output", str(output),
373
+ ]
374
+ try:
375
+ bugfix_main()
376
+ except SystemExit as exc:
377
+ if exc.code not in (0, None):
378
+ raise
379
+ finally:
380
+ os.sys.argv = old_argv
381
+ if old_platform is None:
382
+ os.environ.pop("PRIZMKIT_PLATFORM", None)
383
+ else:
384
+ os.environ["PRIZMKIT_PLATFORM"] = old_platform
385
+ return output.read_text(encoding="utf-8")
386
+
387
+
388
+ def _ensure_agent_files(project, platforms=None):
389
+ """Create minimal agent files so platform resolution succeeds."""
390
+ if platforms is None:
391
+ platforms = ("claude", "codex")
392
+ for plat in platforms:
393
+ platform_dir = ".codex" if plat == "codex" else ".claude"
394
+ suffix = ".toml" if plat == "codex" else ".md"
395
+ agents_dir = project / platform_dir / "agents"
396
+ agents_dir.mkdir(parents=True, exist_ok=True)
397
+ for name in ("dev", "reviewer", "critic"):
398
+ (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
399
+
400
+
401
+ class TestBugfixBootstrapShellExtraction:
402
+ """Regression guardrails: bugfix rendered prompts and source use helper, not shell."""
403
+
404
+ def test_rendered_bugfix_prompt_uses_runtime_helper_not_extracted_bash(self, tmp_path):
405
+ prompt = _render_bugfix_prompt(tmp_path)
406
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_BUGFIX:
407
+ assert snippet not in prompt, f"Retired shell snippet found in bugfix prompt: {snippet}"
408
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
409
+ assert "python" in prompt.lower()
410
+ assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
411
+ assert "artifact ensure-dir .prizmkit/bugfix/B-001" in prompt
412
+ assert "text contains .prizmkit/bugfix/B-001/review-report.md --pattern \"## Verdict\"" in prompt
413
+
414
+ def test_bugfix_template_source_is_clean_of_helper_covered_snippets(self):
415
+ content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
416
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_BUGFIX:
417
+ assert snippet not in content, f"Shell snippet in bugfix template: {snippet}"
418
+
419
+ def test_continuation_source_is_clean_of_helper_covered_snippets(self):
420
+ content = Path("dev-pipeline/scripts/continuation.py").read_text(encoding="utf-8")
421
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_BUGFIX:
422
+ assert snippet not in content, f"Shell snippet in continuation.py: {snippet}"
423
+
424
+ def test_bugfix_generator_source_is_clean_of_helper_covered_snippets(self):
425
+ content = Path("dev-pipeline/scripts/generate-bugfix-prompt.py").read_text(encoding="utf-8")
426
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_BUGFIX:
427
+ assert snippet not in content, f"Shell snippet in generate-bugfix-prompt.py: {snippet}"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from argparse import Namespace
4
4
  from pathlib import Path
5
+ import os
5
6
 
6
7
  from continuation import append_continuation_handoff
7
8
  from generate_refactor_prompt import (
@@ -194,3 +195,126 @@ def test_template_source_is_clean_for_headless_prompt_contract():
194
195
  content = Path("dev-pipeline/templates/refactor-bootstrap-prompt.md").read_text(encoding="utf-8")
195
196
  for forbidden in HEADLESS_FORBIDDEN_PROMPT_STRINGS:
196
197
  assert forbidden not in content
198
+
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # Anti-Bash regression guardrails (F-039)
202
+ # Note: grep -n, sed -n, grep -q, wc -l excluded — may appear in cross-pipeline
203
+ # context budget rules as AI tool hints, not as operational shell snippets.
204
+ # | sed (trailing space) only matches pipe-to-sed operational patterns.
205
+ # ---------------------------------------------------------------------------
206
+
207
+ RETIRED_EXTRACTED_SNIPPETS_REFACTOR = [
208
+ "find . -maxdepth",
209
+ "| sed ",
210
+ r"grep -c '^\- \[ \]'",
211
+ "2>/dev/null",
212
+ "/dev/null",
213
+ "$DEV_PORT",
214
+ "$!",
215
+ "$HOME",
216
+ "if [ ",
217
+ "which playwright-cli",
218
+ "which opencli",
219
+ "lsof -ti",
220
+ "xargs",
221
+ " kill -",
222
+ "kill -",
223
+ "|| echo",
224
+ "mkdir -p",
225
+ "touch .prizmkit",
226
+ ]
227
+
228
+
229
+ def _render_refactor_prompt_full(tmp_path, platform="claude"):
230
+ """Render a complete refactor prompt through the generator."""
231
+ import json, os
232
+ from generate_refactor_prompt import main as refactor_main
233
+
234
+ project = tmp_path / "refactor-anti-bash"
235
+ project.mkdir(parents=True)
236
+ (project / ".prizmkit").mkdir(exist_ok=True)
237
+ (project / ".prizmkit" / "config.json").write_text(
238
+ json.dumps({"platform": platform, "ai_cli": platform}), encoding="utf-8"
239
+ )
240
+ # Ensure agent files
241
+ for plat in ("claude", "codex"):
242
+ platform_dir = ".codex" if plat == "codex" else ".claude"
243
+ suffix = ".toml" if plat == "codex" else ".md"
244
+ agents_dir = project / platform_dir / "agents"
245
+ agents_dir.mkdir(parents=True, exist_ok=True)
246
+ for name in ("dev", "reviewer", "critic"):
247
+ (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
248
+
249
+ refactor_list = project / ".prizmkit" / "plans" / "refactor-list.json"
250
+ refactor_list.parent.mkdir(parents=True)
251
+ refactor_list.write_text(json.dumps({
252
+ "refactors": [{
253
+ "id": "R-001", "title": "Restructure", "description": "Preserve behavior",
254
+ "acceptance_criteria": ["Behavior preserved"],
255
+ "scope": {"files": ["src/a.py"], "modules": ["src"]},
256
+ }],
257
+ "global_context": {"language": "Python"},
258
+ }), encoding="utf-8")
259
+ output = project / "refactor-prompt.md"
260
+ old_argv = os.sys.argv
261
+ old_platform = os.environ.get("PRIZMKIT_PLATFORM")
262
+ try:
263
+ os.environ.pop("PRIZMKIT_PLATFORM", None)
264
+ os.sys.argv = [
265
+ "generate-refactor-prompt.py",
266
+ "--refactor-list", str(refactor_list),
267
+ "--refactor-id", "R-001",
268
+ "--session-id", "session-1",
269
+ "--run-id", "run-1",
270
+ "--retry-count", "0",
271
+ "--resume-phase", "null",
272
+ "--state-dir", str(project / ".prizmkit" / "state" / "refactor"),
273
+ "--output", str(output),
274
+ ]
275
+ old_cwd = os.getcwd()
276
+ os.chdir(project)
277
+ try:
278
+ try:
279
+ refactor_main()
280
+ except SystemExit as exc:
281
+ if exc.code not in (0, None):
282
+ raise
283
+ finally:
284
+ os.chdir(old_cwd)
285
+ finally:
286
+ os.sys.argv = old_argv
287
+ if old_platform is None:
288
+ os.environ.pop("PRIZMKIT_PLATFORM", None)
289
+ else:
290
+ os.environ["PRIZMKIT_PLATFORM"] = old_platform
291
+ return output.read_text(encoding="utf-8")
292
+
293
+
294
+ class TestRefactorBootstrapShellExtraction:
295
+ """Regression guardrails: refactor rendered prompts and source use helper, not shell."""
296
+
297
+ def test_rendered_refactor_prompt_uses_runtime_helper_not_extracted_bash(self, tmp_path):
298
+ prompt = _render_refactor_prompt_full(tmp_path)
299
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_REFACTOR:
300
+ assert snippet not in prompt, f"Retired shell snippet found in refactor prompt: {snippet}"
301
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
302
+ assert "python" in prompt.lower()
303
+ assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
304
+ assert "artifact ensure-dir .prizmkit/refactor/R-001" in prompt
305
+ assert "text contains .prizmkit/refactor/R-001/review-report.md --pattern \"## Verdict\"" in prompt
306
+
307
+ def test_refactor_template_source_is_clean_of_helper_covered_snippets(self):
308
+ content = Path("dev-pipeline/templates/refactor-bootstrap-prompt.md").read_text(encoding="utf-8")
309
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_REFACTOR:
310
+ assert snippet not in content, f"Shell snippet in refactor template: {snippet}"
311
+
312
+ def test_continuation_source_is_clean_of_helper_covered_snippets(self):
313
+ content = Path("dev-pipeline/scripts/continuation.py").read_text(encoding="utf-8")
314
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_REFACTOR:
315
+ assert snippet not in content, f"Shell snippet in continuation.py: {snippet}"
316
+
317
+ def test_refactor_generator_source_is_clean_of_helper_covered_snippets(self):
318
+ content = Path("dev-pipeline/scripts/generate-refactor-prompt.py").read_text(encoding="utf-8")
319
+ for snippet in RETIRED_EXTRACTED_SNIPPETS_REFACTOR:
320
+ assert snippet not in content, f"Shell snippet in generate-refactor-prompt.py: {snippet}"