prizmkit 1.1.109 → 1.1.111

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 (26) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/agents/prizm-dev-team-reviewer.md +4 -0
  3. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +36 -3
  4. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +16 -16
  5. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +18 -18
  6. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +17 -17
  7. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +1 -1
  8. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +8 -8
  9. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +4 -3
  10. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +9 -5
  11. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +1 -1
  12. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
  13. package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
  14. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
  15. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
  16. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
  17. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +1 -1
  18. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +212 -0
  19. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +122 -0
  20. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +124 -0
  21. package/bundled/dev-pipeline/tests/test_unified_cli.py +1 -1
  22. package/bundled/skills/_metadata.json +1 -1
  23. package/bundled/skills/prizmkit/SKILL.md +9 -9
  24. package/bundled/skills/prizmkit-code-review/SKILL.md +24 -2
  25. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +2 -1
  26. package/package.json +1 -1
@@ -21,7 +21,7 @@ Use this protocol whenever the post-review PrizmKit Test gate fails. Its purpose
21
21
 
22
22
  ### Hard Round Cap (BLOCKING)
23
23
 
24
- After **6 total test-fix rounds**, STOP trial-and-error. Write failure-log listing all hypotheses tried. Switch to: read full relevant source section ONCE (via `grep -n`+`sed -n`, never full Read), understand control flow, rewrite failing block in one shot. Do NOT exceed 7 total rounds.
24
+ After **6 total test-fix rounds**, STOP trial-and-error. Write failure-log listing all hypotheses tried. Switch to: locate an anchor with `{{RUNTIME_HELPER_CMD}} text search <file> --pattern "<anchor>" --json`, read one bounded relevant source section with the platform file-reading tool (never full Read), understand control flow, rewrite failing block in one shot. Do NOT exceed 7 total rounds.
25
25
 
26
26
  ### Success Rule
27
27
 
@@ -21,7 +21,7 @@ Use this protocol whenever the post-review PrizmKit Test gate fails. Its purpose
21
21
 
22
22
  ### Hard Round Cap (BLOCKING)
23
23
 
24
- After **6 total test-fix rounds**, STOP trial-and-error. Write failure-log listing all hypotheses tried. Switch to: read full relevant source section ONCE (via `grep -n`+`sed -n`, never full Read), understand control flow, rewrite failing block in one shot. Do NOT exceed 7 total rounds.
24
+ After **6 total test-fix rounds**, STOP trial-and-error. Write failure-log listing all hypotheses tried. Switch to: locate an anchor with `{{RUNTIME_HELPER_CMD}} text search <file> --pattern "<anchor>" --json`, read one bounded relevant source section with the platform file-reading tool (never full Read), understand control flow, rewrite failing block in one shot. Do NOT exceed 7 total rounds.
25
25
 
26
26
  ### Success Rule
27
27
 
@@ -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,
@@ -1214,6 +1215,19 @@ class TestHeadlessPromptCleanupF033:
1214
1215
  assert ".prizmkit-test-started" in prompt
1215
1216
  assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1216
1217
  assert "prizmkit-runtime-helper.py" in prompt
1218
+ # Anti-Bash guardrails for bugfix/refactor branches (F-039)
1219
+ if "Bug ID" in prompt:
1220
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1221
+ assert "prizmkit-runtime-helper.py" in prompt
1222
+ assert "2>/dev/null" not in prompt
1223
+ assert "lsof -ti" not in prompt
1224
+ assert "which " not in prompt # no which-command shell glue
1225
+ if "Refactor ID" in prompt:
1226
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1227
+ assert "prizmkit-runtime-helper.py" in prompt
1228
+ assert "2>/dev/null" not in prompt
1229
+ assert "lsof -ti" not in prompt
1230
+ assert "which " not in prompt # no which-command shell glue
1217
1231
  assert "python3 {{INIT_SCRIPT_PATH}}" not in prompt
1218
1232
 
1219
1233
  def test_legacy_feature_templates_are_clean_when_rendered(self, tmp_path):
@@ -1329,3 +1343,201 @@ class TestHeadlessPromptCleanupF033:
1329
1343
  monkeypatch.setenv("PRIZMKIT_PLATFORM", "codex")
1330
1344
 
1331
1345
  assert resolve_prompt_platform(str(project)) == "codex"
1346
+
1347
+
1348
+
1349
+ class TestFeatureBootstrapShellExtraction:
1350
+ RETIRED_EXTRACTED_SNIPPETS = [
1351
+ "find . -maxdepth",
1352
+ "| sed",
1353
+ "grep -c '^\\- \\[ \\]'",
1354
+ "grep -r \"123-prompt-cleanup\"",
1355
+ "2>/dev/null",
1356
+ "/dev/null",
1357
+ "$DEV_PORT",
1358
+ "$!",
1359
+ "$HOME",
1360
+ "if [ ",
1361
+ "which playwright-cli",
1362
+ "which opencli",
1363
+ "lsof -ti",
1364
+ "xargs",
1365
+ " kill -",
1366
+ "kill -",
1367
+ "|| echo",
1368
+ "grep -q '^/binary-name$'",
1369
+ "echo '/binary-name' >> .gitignore",
1370
+ "git log --oneline | grep",
1371
+ "grep -n ",
1372
+ "sed -n",
1373
+ "wc -l",
1374
+ "opencli browser open http://localhost:<DEV_PORT> && opencli browser state",
1375
+ "opencli browser type 3 \"hello\" && opencli browser click 7",
1376
+ ]
1377
+
1378
+ @staticmethod
1379
+ def assert_shell_extracted_operations_removed(prompt):
1380
+ for snippet in TestFeatureBootstrapShellExtraction.RETIRED_EXTRACTED_SNIPPETS:
1381
+ assert snippet not in prompt
1382
+ assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1383
+ assert "python" in prompt.lower()
1384
+ assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
1385
+ assert "artifact touch .prizmkit/specs/123-prompt-cleanup/.prizmkit-test-started" in prompt
1386
+ assert "text contains .prizmkit/specs/123-prompt-cleanup/review-report.md --pattern \"## Verdict\"" in prompt
1387
+
1388
+ def test_rendered_section_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
1389
+ for mode in ("lite", "standard", "full"):
1390
+ prompt = _render_feature_prompt(
1391
+ tmp_path / f"section-{mode}",
1392
+ mode=mode,
1393
+ critic=(mode != "lite"),
1394
+ browser_interaction={"tool": "auto", "verify_steps": ["Open the page"]},
1395
+ )
1396
+
1397
+ self.assert_shell_extracted_operations_removed(prompt)
1398
+ assert "executable version playwright-cli --arg=--version" in prompt
1399
+ assert "executable version opencli --arg=--version" in prompt
1400
+ assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
1401
+ 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
1403
+
1404
+ def test_rendered_legacy_tier_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
1405
+ cases = [
1406
+ ("bootstrap-tier1.md", "lite"),
1407
+ ("bootstrap-tier2.md", "standard"),
1408
+ ("bootstrap-tier3.md", "full"),
1409
+ ]
1410
+ for template, mode in cases:
1411
+ prompt = _render_feature_prompt(
1412
+ tmp_path / f"legacy-{template}",
1413
+ mode=mode,
1414
+ critic=(mode != "lite"),
1415
+ template=template,
1416
+ browser_interaction={"tool": "playwright-cli", "verify_steps": ["Open the page"]},
1417
+ )
1418
+
1419
+ self.assert_shell_extracted_operations_removed(prompt)
1420
+ assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
1421
+ assert "process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>" in prompt
1422
+ assert "port check 3000" in prompt
1423
+
1424
+ def test_legacy_tier_specific_extracted_operations_map_to_helper_commands(self, tmp_path):
1425
+ tier1 = _render_feature_prompt(
1426
+ tmp_path / "legacy-tier1-helper-map",
1427
+ mode="lite",
1428
+ template="bootstrap-tier1.md",
1429
+ )
1430
+ assert "artifact tree . --max-depth 2" in tier1
1431
+ assert "text contains .gitignore --pattern \"/binary-name\"" in tier1
1432
+ assert "NEEDS_IGNORE_ENTRY" in tier1
1433
+
1434
+ tier2 = _render_feature_prompt(
1435
+ tmp_path / "legacy-tier2-helper-map",
1436
+ mode="standard",
1437
+ critic=True,
1438
+ template="bootstrap-tier2.md",
1439
+ )
1440
+ assert "artifact exists .prizmkit/specs/123-prompt-cleanup/failure-log.md" in tier2
1441
+ assert "artifact tree . --max-depth 2" in tier2
1442
+ assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier2
1443
+
1444
+ tier3 = _render_feature_prompt(
1445
+ tmp_path / "legacy-tier3-helper-map",
1446
+ mode="full",
1447
+ critic=True,
1448
+ template="bootstrap-tier3.md",
1449
+ )
1450
+ assert "text search . --pattern \"123-prompt-cleanup\" --json" in tier3
1451
+ assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier3
1452
+ assert "Check if feature already committed by inspecting recent git history" in tier3
1453
+
1454
+
1455
+ # ---------------------------------------------------------------------------
1456
+ # detect_test_commands
1457
+ # ---------------------------------------------------------------------------
1458
+
1459
+ import json as _json
1460
+
1461
+ class TestDetectTestCommands:
1462
+ def test_no_package_json_returns_empty(self, tmp_path):
1463
+ """When no package.json exists, no npm test emitted."""
1464
+ result = detect_test_commands(str(tmp_path))
1465
+ assert "npm test" not in result
1466
+ assert "pnpm test" not in result
1467
+ assert "yarn test" not in result
1468
+
1469
+ def test_package_json_no_test_script(self, tmp_path):
1470
+ """package.json exists but has no test script → no npm test emitted."""
1471
+ pkg = tmp_path / "package.json"
1472
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"start": "node index.js"}}), encoding="utf-8")
1473
+ result = detect_test_commands(str(tmp_path))
1474
+ # Bug: current code returns "npm test" even with no test script
1475
+ assert "npm test" not in result, (
1476
+ "Should NOT emit npm test when package.json has no test script"
1477
+ )
1478
+
1479
+ def test_package_json_with_test_script(self, tmp_path):
1480
+ """package.json with scripts.test → emits npm test."""
1481
+ pkg = tmp_path / "package.json"
1482
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test": "vitest run"}}), encoding="utf-8")
1483
+ result = detect_test_commands(str(tmp_path))
1484
+ # Current code returns "npm test" (hardcoded), but at minimum it should
1485
+ # still return something test-like. After fix, it becomes "npm test"
1486
+ assert "npm test" in result, (
1487
+ "Should emit npm test when package.json has test script"
1488
+ )
1489
+
1490
+ def test_package_json_non_test_scripts_only(self, tmp_path):
1491
+ """package.json exists but scripts only has non-test entries."""
1492
+ pkg = tmp_path / "package.json"
1493
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"build": "tsc", "start": "node dist/index.js"}}), encoding="utf-8")
1494
+ result = detect_test_commands(str(tmp_path))
1495
+ assert "npm test" not in result, (
1496
+ "Should NOT emit npm test when no test-related scripts exist"
1497
+ )
1498
+
1499
+ def test_pnpm_detected_from_lockfile(self, tmp_path):
1500
+ """pnpm-lock.yaml → emits pnpm test when test script exists."""
1501
+ pkg = tmp_path / "package.json"
1502
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test": "vitest"}}), encoding="utf-8")
1503
+ (tmp_path / "pnpm-lock.yaml").write_text("", encoding="utf-8")
1504
+ result = detect_test_commands(str(tmp_path))
1505
+ assert "pnpm test" in result
1506
+
1507
+ def test_yarn_detected_from_lockfile(self, tmp_path):
1508
+ """yarn.lock → emits yarn test when test script exists."""
1509
+ pkg = tmp_path / "package.json"
1510
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test": "jest"}}), encoding="utf-8")
1511
+ (tmp_path / "yarn.lock").write_text("", encoding="utf-8")
1512
+ result = detect_test_commands(str(tmp_path))
1513
+ assert "yarn test" in result
1514
+
1515
+ def test_corrupt_package_json_graceful_fallback(self, tmp_path):
1516
+ """Corrupt/unparseable package.json → skip JS detection gracefully."""
1517
+ (tmp_path / "package.json").write_text("not valid json {{{", encoding="utf-8")
1518
+ result = detect_test_commands(str(tmp_path))
1519
+ # Should not crash, should not emit npm test
1520
+ assert "npm test" not in result
1521
+
1522
+ def test_non_js_detections_preserved(self, tmp_path):
1523
+ """Go, Rust, Python, Makefile detections unchanged."""
1524
+ (tmp_path / "go.mod").write_text("", encoding="utf-8")
1525
+ (tmp_path / "Cargo.toml").write_text("", encoding="utf-8")
1526
+ (tmp_path / "setup.py").write_text("", encoding="utf-8")
1527
+ makefile = tmp_path / "Makefile"
1528
+ makefile.write_text("test:\n\tpytest\n", encoding="utf-8")
1529
+ result = detect_test_commands(str(tmp_path))
1530
+ assert "go test ./..." in result
1531
+ assert "cargo test" in result
1532
+ assert "pytest" in result
1533
+ assert "make test" in result
1534
+
1535
+ def test_package_json_test_unit_script_only(self, tmp_path):
1536
+ """package.json with test:unit but no test → fallback to available test script."""
1537
+ pkg = tmp_path / "package.json"
1538
+ pkg.write_text(_json.dumps({"name": "test", "scripts": {"test:unit": "vitest run"}}), encoding="utf-8")
1539
+ (tmp_path / "package-lock.json").write_text("", encoding="utf-8")
1540
+ result = detect_test_commands(str(tmp_path))
1541
+ # Should detect test:unit as a viable test script
1542
+ assert "npm run test:unit" in result
1543
+
@@ -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}"
@@ -804,7 +804,7 @@ def test_headless_subagent_prompt_guidance_keeps_no_worktree_boundary_visible():
804
804
  for path in guidance_files:
805
805
  text = path.read_text(encoding="utf-8")
806
806
  assert "default/no worktree" in text, path
807
- assert "do not request worktree isolation" in text.lower(), path
807
+ assert "do not request or create" in text.lower() or "do not request worktree isolation" in text.lower(), path
808
808
  assert ".claude/worktrees/..." in text, path
809
809
  assert ".prizmkit/state/worktrees/..." in text, path
810
810
  assert "USE_WORKTREE" in text, path
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.109",
2
+ "version": "1.1.111",
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.",
@@ -67,15 +67,15 @@ All three follow the same per-task flow. Detailed documentation policies (when t
67
67
 
68
68
  | Skill | Purpose | Trigger Phrases |
69
69
  |-------|---------|-----------------|
70
- | `/prizmkit-plan` | Specify + plan: natural language spec.md plan.md + tasks | "specify", "plan", "new feature", "I want to add...", "architect", "break it down" |
71
- | `/prizmkit-implement` | Execute plan.md tasks, write code (TDD) | "implement", "build", "code it", "start coding" |
72
- | `/prizmkit-code-review` | Diagnose issues + produce Fix Instructions | "review", "check code", "is it ready to commit" |
73
- | `/prizmkit-retrospective` | Sync .prizmkit/prizm-docs/ with code changes | "retrospective", "retro", "sync docs", "wrap up" |
74
- | `/prizmkit-committer` | Safe git commit with Conventional Commits | "commit", "submit", "finish", "ship it" |
75
- | `/prizmkit-test` | Generate + run tests, analyze coverage gaps, unified quality report | "test", "run tests", "check quality", "verify code", "add tests" |
76
- | `/prizmkit-deploy` | Universal deployment gateway: SSH automation, cloud/Docker guided setup, status/logs/rollback | "deploy", "ship it", "go live", "rollback", "deploy status" |
77
- | `/prizmkit-init` | Project bootstrap + .prizmkit/prizm-docs/ setup | "init", "initialize", "take over this project" |
78
- | `/prizmkit-prizm-docs` | Doc management (init/status/rebuild/validate) | "check docs", "rebuild docs", "validate docs" |
70
+ | `/prizmkit-plan` | Specify and plan: turns natural language into spec.md + plan.md with architecture and executable tasks. Use as the **first implementation step** in the lifecycle, before /prizmkit-implement. | "specify", "plan", "new feature", "I want to add...", "architect", "break it down" |
71
+ | `/prizmkit-implement` | Execute plan.md tasks with **TDD approach**, respecting task ordering and dependencies. Use after /prizmkit-plan as the **implementation step** of the lifecycle. | "implement", "build", "code it", "start coding" |
72
+ | `/prizmkit-code-review` | **Iterative review-fix loop** against spec and plan (max 3 rounds until PASS). Spawns a reviewer agent; the main orchestrator applies accepted fixes directly. Use after /prizmkit-implement as the **quality gate**. | "review", "check code", "code review", "is it ready to commit" |
73
+ | `/prizmkit-retrospective` | Incremental .prizmkit/prizm-docs/ maintainer: (1) **structural sync** of KEY_FILES/INTERFACES/DEPENDENCIES, (2) inject **TRAPS/RULES/DECISIONS**. Run after code review and **before committing**. | "retrospective", "retro", "update docs", "sync docs", "wrap up" |
74
+ | `/prizmkit-committer` | **Pure git commit** with safety checks: stages files, generates Conventional Commits message, commits. Does NOT modify docs — run /prizmkit-retrospective first. Use as the **final lifecycle step**. | "commit", "submit", "finish", "ship it" |
75
+ | `/prizmkit-test` | **Full-stack test orchestration**: detects architecture, analyzes coverage gaps, generates missing unit/integration/E2E tests with a boundary coverage matrix, and outputs a unified report. Use after development and **before deploy** for quality verification. | "test", "run tests", "check quality", "verify code", "generate tests", "add tests" |
76
+ | `/prizmkit-deploy` | **Universal deployment gateway**: discovers project type/target, automates SSH Linux PM2+Nginx+blue-green deploys, and manages operations — status, logs, restart, rollback, health checks. | "deploy", "ship it", "go live", "rollback", "deploy status" |
77
+ | `/prizmkit-init` | **Project bootstrap**: scans codebase, generates .prizmkit/prizm-docs/ and project brief, detects tech stack. Use as the **first lifecycle step for new projects**, or after `npx prizmkit install`. | "init", "initialize", "take over this project", "bootstrap" |
78
+ | `/prizmkit-prizm-docs` | **Doc system spec and management**: defines the 3-level .prizmkit/prizm-docs/ hierarchy (L0/L1/L2), format rules, size limits, and loading protocol. Use for doc init/status/rebuild/validate/migrate. For **incremental code-driven updates**, use /prizmkit-retrospective instead. | "check docs", "rebuild docs", "validate docs", "prizm docs" |
79
79
 
80
80
  **Reading guide**:
81
81
  - Need code structure/modules/interfaces/traps/decisions? → `.prizmkit/prizm-docs/`
@@ -37,7 +37,12 @@ The loop repeats until the Reviewer finds no issues or the max round limit is re
37
37
  - If `context-snapshot.md` contains `## PrizmKit Test Gate`, read that summary too.
38
38
  - If this is a feature artifact under `.prizmkit/specs/` and no test report pointer or summary exists, pass `No scoped /prizmkit-test report found (expected for pre-test review)` into the Reviewer context as informational scope context only. Do not ask the Reviewer to report it as a missing gate unless the calling workflow explicitly requires a prior test report.
39
39
  4. **Read dev rules** (if configured): If `.prizmkit/prizm-docs/root.prizm` exists, read it and check for a `RULES:` line. If present, read all referenced `.prizmkit/rules/<layer>-rules.md` files. If `root.prizm` or a referenced rules file does not exist, continue with "No custom dev rules configured — use general best practices."
40
- 5. **Capture workspace diff**: run `git diff` (unstaged) + `git diff --cached` (staged) + `git status` to understand the full scope of changes. For new files in git status, note their paths for the Reviewer to read.
40
+ 5. **Capture workspace context from the active checkout** (do NOT open a separate worktree or clone):
41
+ - Run `git status` to list modified, staged, untracked, and deleted files.
42
+ - Run `git diff` for unstaged changes and `git diff --cached` for staged changes.
43
+ - For new (untracked) files and renamed files in `git status`, note their full paths so the Reviewer can request targeted reads.
44
+ - Capture the current project context: read `.prizmkit/prizm-docs/root.prizm` for relevant RULES, and the artifact's `plan.md` for architecture decisions — this is the reviewer's context baseline.
45
+ - The workspace context is the **primary review scope**. The Reviewer must review this context and request only targeted file reads for lines it needs — it must not rediscover or re-index the whole repository.
41
46
  - If no changes are detected, skip Phase 1 and proceed to Phase 2 with verdict PASS, rounds 0, and an empty findings list. Always write `review-report.md`; downstream pipeline steps use it as the review gate.
42
47
 
43
48
  ## Phase 1: Review-Fix Loop
@@ -115,7 +120,24 @@ When Python is unavailable, apply these rules exactly — they match the script'
115
120
 
116
121
  ### Step 1: Spawn Reviewer Agent
117
122
 
118
- Include the dev rules read in Phase 0 step 4 and the workspace diff/status captured in Phase 0 step 5 in the Reviewer prompt. In Claude Code, spawn the dedicated PrizmKit Reviewer with `subagent_type: prizm-dev-team-reviewer`, `run_in_background=false`, and default/no worktree isolation. Do not request worktree isolation; this prevents AI tool-created `.claude/worktrees/...` subagent worktrees. PrizmKit pipeline-managed linked git worktrees under `.prizmkit/state/worktrees/...` are separate and controlled by `USE_WORKTREE`. Do not route the PrizmKit reviewer flow through `Explore` or a generic code-explorer agent. If the current platform has no dedicated Reviewer agent, perform the review pass in the Main Agent without editing files during that pass. Read `${SKILL_DIR}/references/reviewer-agent-prompt.md` for the full prompt template — fill `{goals}`, `{plan decisions}`, `{scoped test report}`, `{workspace context}`, `{dev rules}`, `{round N}`, and `{round_context}` before spawning.
123
+ Include the dev rules read in Phase 0 step 4 and the workspace context captured in Phase 0 step 5 in the Reviewer prompt.
124
+
125
+ **Reviewer Agent Invocation Contract (MANDATORY):**
126
+
127
+ | Parameter | Value | Rationale |
128
+ |-----------|-------|-----------|
129
+ | `subagent_type` | `prizm-dev-team-reviewer` | Dedicated PrizmKit reviewer agent |
130
+ | `run_in_background` | `false` | Orchestrator waits for review results |
131
+ | `isolation` | **omit or `"none"`** — default/no worktree isolation | Reviewer must review the active checkout's changes, not a separate repository copy |
132
+ | worktree tool access | **Forbidden** — do NOT pass `EnterWorktree` tool; do NOT request or create `.claude/worktrees/...` subagent worktrees | Tool-created worktrees defeat checkout-scoped review |
133
+
134
+ PrizmKit pipeline-managed linked git worktrees under `.prizmkit/state/worktrees/...` are separate infrastructure controlled by `USE_WORKTREE` and are not relevant to the Reviewer invocation.
135
+
136
+ Do not route the PrizmKit reviewer flow through `Explore` or a generic code-explorer agent.
137
+
138
+ **Fallback clause**: If the current platform or Agent-tool invocation path cannot guarantee a no-worktree Reviewer Agent (e.g., the platform does not support `prizm-dev-team-reviewer` or always creates worktree isolation for subagents), perform the review pass **in the Main Agent as a read-only pass** in the active checkout — do NOT edit files during that pass. Use the same review dimensions and output format as the Reviewer prompt template, and write `review-report.md` directly.
139
+
140
+ Read `${SKILL_DIR}/references/reviewer-agent-prompt.md` for the full prompt template — fill `{goals}`, `{plan decisions}`, `{scoped test report}`, `{workspace context}`, `{dev rules}`, `{round N}`, and `{round_context}` before spawning.
119
141
 
120
142
  Round context varies by round:
121
143
  - Round 1: "This is the first review. Examine all changes comprehensively."
@@ -15,7 +15,7 @@ You are the dedicated PrizmKit Reviewer agent (`prizm-dev-team-reviewer`). Revie
15
15
  {summary from artifact_dir/test-report-path.txt and test-report.md, including Scope, Generated / Updated Tests, In-Scope Failures, Baseline Failures, Out of Scope Gaps, Boundary Matrix / Boundary Completion Gate if present, and Verdict. If no scoped report exists for a feature artifact, write "No scoped /prizmkit-test report found (expected for pre-test review)." Treat that absence as informational unless the calling workflow explicitly requires a prior test report.}
16
16
 
17
17
  ## Workspace Context
18
- {git status, unstaged diff summary, staged diff summary, and new-file paths captured by the Main Agent in Phase 0. Use this context as the review scope; request targeted file context only when needed instead of re-running broad discovery.}
18
+ {git status, unstaged diff summary, staged diff summary, and new-file paths captured by the Main Agent in Phase 0 from the active checkout. This is your primary review scope — use this context to understand what changed. Request targeted file reads only when you need specific line-level detail for a finding, not to re-discover or re-index the repository. Do not read files that are not in the workspace context unless you need them to verify impact (callers, dependents, shared patterns). Do not switch to a separate worktree or repository copy — the Main Agent has provided everything needed to understand the change set.}
19
19
 
20
20
  ## Dev Rules (per-layer conventions)
21
21
  {rules from .prizmkit/rules/<layer>-rules.md, or "No custom dev rules configured — use general best practices."}
@@ -28,6 +28,7 @@ Use the orchestrator-provided workspace context as the source of truth for chang
28
28
  - For new files listed in workspace status, read their full content.
29
29
  - For modified files, read enough surrounding context to understand the change.
30
30
  - Do not edit files, stage changes, run test suites, or spawn other agents.
31
+ - Do not request or create worktrees — you are reviewing the active checkout's changes. The orchestrator has already captured the full diff and status context; re-discovering the whole repository is wasteful and unnecessary.
31
32
 
32
33
  ## Review Dimensions
33
34
  Evaluate the changes across these dimensions (focus on what's relevant):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.109",
3
+ "version": "1.1.111",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {