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.
- package/bundled/VERSION.json +3 -3
- package/bundled/agents/prizm-dev-team-reviewer.md +4 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +36 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +16 -16
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +18 -18
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +17 -17
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +1 -1
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +8 -8
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +4 -3
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +9 -5
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +1 -1
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +212 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +122 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +124 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +1 -1
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/prizmkit/SKILL.md +9 -9
- package/bundled/skills/prizmkit-code-review/SKILL.md +24 -2
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +2 -1
- 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
|
|
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
|
|
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
|
|
@@ -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
|
|
71
|
-
| `/prizmkit-implement` | Execute plan.md tasks
|
|
72
|
-
| `/prizmkit-code-review` |
|
|
73
|
-
| `/prizmkit-retrospective` |
|
|
74
|
-
| `/prizmkit-committer` |
|
|
75
|
-
| `/prizmkit-test` |
|
|
76
|
-
| `/prizmkit-deploy` | Universal deployment gateway
|
|
77
|
-
| `/prizmkit-init` | Project bootstrap
|
|
78
|
-
| `/prizmkit-prizm-docs` | Doc management (init/status/rebuild/validate
|
|
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
|
|
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
|
|
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.
|
|
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):
|