prizmkit 1.1.108 → 1.1.110
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/dev-pipeline/prizmkit_runtime/cli.py +20 -0
- package/bundled/dev-pipeline/prizmkit_runtime/commands.py +1 -0
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +21 -0
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
- package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +208 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runtime_helper.py +508 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +3 -1
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +3 -1
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +3 -1
- package/bundled/dev-pipeline/scripts/prizmkit-runtime-helper.py +27 -0
- package/bundled/dev-pipeline/scripts/utils.py +48 -77
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +44 -71
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +48 -75
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +52 -78
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
- 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 +33 -55
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +28 -33
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +22 -62
- 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-context-snapshot-base.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.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/phase-plan-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +7 -6
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +1 -1
- package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.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 +152 -3
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +2 -1
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +2 -1
- package/bundled/dev-pipeline/tests/test_runtime_helper.py +211 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +5 -0
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ The skill runs an internal review-fix loop (Reviewer Agent → filter → orches
|
|
|
9
9
|
**Gate Check — Review Report**:
|
|
10
10
|
After `/prizmkit-code-review` returns, verify the review report:
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
{{RUNTIME_HELPER_CMD}} text contains .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md --pattern "## Verdict"
|
|
13
13
|
```
|
|
14
14
|
If GATE:MISSING:
|
|
15
15
|
- Do not re-run `/prizmkit-code-review` in an unbounded report-repair loop.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Check existing artifacts first:
|
|
4
4
|
```bash
|
|
5
|
-
|
|
5
|
+
{{RUNTIME_HELPER_CMD}} artifact exists .prizmkit/specs/{{FEATURE_SLUG}}
|
|
6
6
|
```
|
|
7
7
|
|
|
8
8
|
- Both (spec.md, plan.md) exist → **SKIP to CP-1**
|
|
@@ -12,11 +12,11 @@ ls .prizmkit/specs/{{FEATURE_SLUG}}/ 2>/dev/null
|
|
|
12
12
|
**Step A — Build Context Snapshot** (skip if `context-snapshot.md` already exists):
|
|
13
13
|
|
|
14
14
|
1. Read `.prizmkit/prizm-docs/root.prizm` and relevant L1/L2 prizm docs
|
|
15
|
-
2. Detect source code directories
|
|
15
|
+
2. Detect source code directories from KEY_FILES and STRUCTURE sections in `root.prizm`; if root docs are unavailable, use the helper to list likely source files:
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
{{RUNTIME_HELPER_CMD}} artifact source-files . --json
|
|
18
18
|
```
|
|
19
|
-
Identify the top-level source directories from the
|
|
19
|
+
Identify the top-level source directories from the helper output.
|
|
20
20
|
3. Scan the detected source directories for files related to this feature; read each one
|
|
21
21
|
4. Write `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md`:
|
|
22
22
|
- **Section 1 — Feature Brief**: feature description (copy from Task Contract above, 2-3 lines only)
|
|
@@ -35,14 +35,15 @@ ls .prizmkit/specs/{{FEATURE_SLUG}}/ 2>/dev/null
|
|
|
35
35
|
|
|
36
36
|
### Known TRAPS (from .prizmkit/prizm-docs/)
|
|
37
37
|
- <trap entries extracted from L1/L2 docs>
|
|
38
|
-
5. Confirm: `
|
|
38
|
+
5. Confirm: `{{RUNTIME_HELPER_CMD}} artifact exists .prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md`
|
|
39
39
|
|
|
40
40
|
**After Step A**: Use context-snapshot.md Section 4 File Manifest to guide targeted file reads. Do NOT scan directories or read unrelated files.
|
|
41
41
|
|
|
42
42
|
**Step B — Planning Artifacts** (generate only missing files):
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
|
|
45
|
+
{{RUNTIME_HELPER_CMD}} artifact exists .prizmkit/specs/{{FEATURE_SLUG}}/spec.md
|
|
46
|
+
{{RUNTIME_HELPER_CMD}} artifact exists .prizmkit/specs/{{FEATURE_SLUG}}/plan.md
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
- spec.md missing: Run `/prizmkit-plan` → generate spec.md. Resolve uncertain requirements from the task description, existing code, and conservative existing-project patterns.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
### Phase 0: Project Bootstrap
|
|
2
2
|
- Run `/prizmkit-init` (invoke the prizmkit-init skill)
|
|
3
|
-
- Run `
|
|
3
|
+
- Run `{{PYTHON_CMD}} {{INIT_SCRIPT_PATH}} --project-root {{PROJECT_ROOT}} --feature-id {{FEATURE_ID}} --feature-slug {{FEATURE_SLUG}}`
|
|
4
4
|
- **CP-0**: Verify `.prizmkit/prizm-docs/root.prizm`, `.prizmkit/config.json` exist
|
|
5
5
|
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Subagent Timeout Recovery
|
|
2
2
|
|
|
3
3
|
If any agent times out:
|
|
4
|
-
1. `
|
|
4
|
+
1. `{{RUNTIME_HELPER_CMD}} artifact list .prizmkit/specs/{{FEATURE_SLUG}}` — check what exists
|
|
5
5
|
2. If `context-snapshot.md` exists: open recovery prompt with `"Read .prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md for project context and any Implementation Log/Review Notes from previous agents. Run git diff HEAD to see actual code changes already made. Do NOT re-read individual source files unless the File Manifest directs you to."` + only remaining steps + `model: "lite"`
|
|
6
6
|
3. Max 2 retries per phase. After 2 failures, orchestrator completes the work directly and appends a Recovery Note to context-snapshot.md.
|
|
@@ -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
|
|
|
@@ -444,7 +444,8 @@ class TestScopedFeatureTestGate:
|
|
|
444
444
|
|
|
445
445
|
rendered = "\n".join(content for _, content in sections)
|
|
446
446
|
assert "prizmkit-test-gate.py" not in rendered
|
|
447
|
-
assert ".prizmkit-test-started" in rendered
|
|
447
|
+
assert "{{RUNTIME_HELPER_CMD}} artifact touch .prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started" in rendered
|
|
448
|
+
assert "mkdir -p .prizmkit/specs/{{FEATURE_SLUG}}" not in rendered
|
|
448
449
|
assert "when present" not in rendered
|
|
449
450
|
assert "Boundary Matrix" in rendered
|
|
450
451
|
assert "Boundary Completion Gate" in rendered
|
|
@@ -474,7 +475,8 @@ class TestScopedFeatureTestGate:
|
|
|
474
475
|
rendered = "\n".join(content for _, content in sections)
|
|
475
476
|
|
|
476
477
|
assert "### Checkpoint Update Helper" in rendered
|
|
477
|
-
assert "
|
|
478
|
+
assert "{{CHECKPOINT_PYTHON_CMD}}" in rendered
|
|
479
|
+
assert "python3 .prizmkit/dev-pipeline/scripts/update-checkpoint.py" not in rendered
|
|
478
480
|
assert "--checkpoint-path {{CHECKPOINT_PATH}}" in rendered
|
|
479
481
|
assert "--step <step-id-or-skill-name>" in rendered
|
|
480
482
|
assert "Do not hand-edit `workflow-checkpoint.json` directly." in rendered
|
|
@@ -1016,7 +1018,7 @@ def _write_feature_list(path, feature):
|
|
|
1016
1018
|
path.write_text(json.dumps({"features": [feature], "global_context": {"language": "Python"}}), encoding="utf-8")
|
|
1017
1019
|
|
|
1018
1020
|
|
|
1019
|
-
def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=None, platform="claude"):
|
|
1021
|
+
def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=None, platform="claude", browser_interaction=None):
|
|
1020
1022
|
project = tmp_path / "project"
|
|
1021
1023
|
project.mkdir(parents=True)
|
|
1022
1024
|
(project / ".prizmkit" / "plans").mkdir(parents=True)
|
|
@@ -1036,6 +1038,8 @@ def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=Non
|
|
|
1036
1038
|
"acceptance_criteria": ["Prompts are clean"],
|
|
1037
1039
|
"critic": critic,
|
|
1038
1040
|
}
|
|
1041
|
+
if browser_interaction is not None:
|
|
1042
|
+
feature["browser_interaction"] = browser_interaction
|
|
1039
1043
|
feature_list = project / ".prizmkit" / "plans" / "feature-list.json"
|
|
1040
1044
|
_write_feature_list(feature_list, feature)
|
|
1041
1045
|
output = project / "prompt.md"
|
|
@@ -1208,6 +1212,9 @@ class TestHeadlessPromptCleanupF033:
|
|
|
1208
1212
|
if "Feature ID" in prompt:
|
|
1209
1213
|
assert "/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/123-prompt-cleanup/" in prompt
|
|
1210
1214
|
assert ".prizmkit-test-started" in prompt
|
|
1215
|
+
assert "{{RUNTIME_HELPER_CMD}}" not in prompt
|
|
1216
|
+
assert "prizmkit-runtime-helper.py" in prompt
|
|
1217
|
+
assert "python3 {{INIT_SCRIPT_PATH}}" not in prompt
|
|
1211
1218
|
|
|
1212
1219
|
def test_legacy_feature_templates_are_clean_when_rendered(self, tmp_path):
|
|
1213
1220
|
for template, mode in (("bootstrap-tier1.md", "lite"), ("bootstrap-tier2.md", "standard"), ("bootstrap-tier3.md", "full")):
|
|
@@ -1215,7 +1222,42 @@ class TestHeadlessPromptCleanupF033:
|
|
|
1215
1222
|
for forbidden in HEADLESS_FORBIDDEN_PROMPT_STRINGS:
|
|
1216
1223
|
assert forbidden not in prompt
|
|
1217
1224
|
assert "prizmkit-test-gate.py" not in prompt
|
|
1225
|
+
assert "python3 {{INIT_SCRIPT_PATH}}" not in prompt
|
|
1226
|
+
assert "which playwright-cli" not in prompt
|
|
1227
|
+
assert "lsof -ti" not in prompt
|
|
1228
|
+
assert "sed -nE" not in prompt
|
|
1218
1229
|
assert ".prizmkit-test-started" in prompt
|
|
1230
|
+
assert "{{RUNTIME_HELPER_CMD}}" not in prompt
|
|
1231
|
+
assert "prizmkit-runtime-helper.py" in prompt
|
|
1232
|
+
|
|
1233
|
+
def test_legacy_feature_templates_with_browser_blocks_are_shell_free(self, tmp_path):
|
|
1234
|
+
forbidden = [
|
|
1235
|
+
"python3 {{INIT_SCRIPT_PATH}}",
|
|
1236
|
+
"which playwright-cli",
|
|
1237
|
+
"which opencli",
|
|
1238
|
+
"lsof -ti",
|
|
1239
|
+
"sed -nE",
|
|
1240
|
+
"SKILL_DIR",
|
|
1241
|
+
"DEV_SERVER_PID",
|
|
1242
|
+
"opencli doctor 2>/dev/null ||",
|
|
1243
|
+
"echo \"=== playwright-cli ===\" &&",
|
|
1244
|
+
]
|
|
1245
|
+
cases = [
|
|
1246
|
+
("bootstrap-tier1.md", "lite"),
|
|
1247
|
+
("bootstrap-tier2.md", "standard"),
|
|
1248
|
+
("bootstrap-tier3.md", "full"),
|
|
1249
|
+
]
|
|
1250
|
+
for template, mode in cases:
|
|
1251
|
+
prompt = _render_feature_prompt(
|
|
1252
|
+
tmp_path / f"browser-{template}",
|
|
1253
|
+
mode=mode,
|
|
1254
|
+
critic=(mode != "lite"),
|
|
1255
|
+
template=template,
|
|
1256
|
+
browser_interaction={"tool": "auto", "verify_steps": ["Open the page"]},
|
|
1257
|
+
)
|
|
1258
|
+
assert "prizmkit-runtime-helper.py" in prompt
|
|
1259
|
+
for snippet in forbidden:
|
|
1260
|
+
assert snippet not in prompt
|
|
1219
1261
|
|
|
1220
1262
|
def test_claude_config_wins_over_codex_directory_for_critic_path(self, tmp_path, monkeypatch):
|
|
1221
1263
|
project = tmp_path / "mixed"
|
|
@@ -1287,3 +1329,110 @@ class TestHeadlessPromptCleanupF033:
|
|
|
1287
1329
|
monkeypatch.setenv("PRIZMKIT_PLATFORM", "codex")
|
|
1288
1330
|
|
|
1289
1331
|
assert resolve_prompt_platform(str(project)) == "codex"
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
class TestFeatureBootstrapShellExtraction:
|
|
1336
|
+
RETIRED_EXTRACTED_SNIPPETS = [
|
|
1337
|
+
"find . -maxdepth",
|
|
1338
|
+
"| sed",
|
|
1339
|
+
"grep -c '^\\- \\[ \\]'",
|
|
1340
|
+
"grep -r \"123-prompt-cleanup\"",
|
|
1341
|
+
"2>/dev/null",
|
|
1342
|
+
"/dev/null",
|
|
1343
|
+
"$DEV_PORT",
|
|
1344
|
+
"$!",
|
|
1345
|
+
"$HOME",
|
|
1346
|
+
"if [ ",
|
|
1347
|
+
"which playwright-cli",
|
|
1348
|
+
"which opencli",
|
|
1349
|
+
"lsof -ti",
|
|
1350
|
+
"xargs",
|
|
1351
|
+
" kill -",
|
|
1352
|
+
"kill -",
|
|
1353
|
+
"|| echo",
|
|
1354
|
+
"grep -q '^/binary-name$'",
|
|
1355
|
+
"echo '/binary-name' >> .gitignore",
|
|
1356
|
+
"git log --oneline | grep",
|
|
1357
|
+
"grep -n ",
|
|
1358
|
+
"sed -n",
|
|
1359
|
+
"wc -l",
|
|
1360
|
+
"opencli browser open http://localhost:<DEV_PORT> && opencli browser state",
|
|
1361
|
+
"opencli browser type 3 \"hello\" && opencli browser click 7",
|
|
1362
|
+
]
|
|
1363
|
+
|
|
1364
|
+
@staticmethod
|
|
1365
|
+
def assert_shell_extracted_operations_removed(prompt):
|
|
1366
|
+
for snippet in TestFeatureBootstrapShellExtraction.RETIRED_EXTRACTED_SNIPPETS:
|
|
1367
|
+
assert snippet not in prompt
|
|
1368
|
+
assert "{{RUNTIME_HELPER_CMD}}" not in prompt
|
|
1369
|
+
assert "python" in prompt.lower()
|
|
1370
|
+
assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
|
|
1371
|
+
assert "artifact touch .prizmkit/specs/123-prompt-cleanup/.prizmkit-test-started" in prompt
|
|
1372
|
+
assert "text contains .prizmkit/specs/123-prompt-cleanup/review-report.md --pattern \"## Verdict\"" in prompt
|
|
1373
|
+
|
|
1374
|
+
def test_rendered_section_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
|
|
1375
|
+
for mode in ("lite", "standard", "full"):
|
|
1376
|
+
prompt = _render_feature_prompt(
|
|
1377
|
+
tmp_path / f"section-{mode}",
|
|
1378
|
+
mode=mode,
|
|
1379
|
+
critic=(mode != "lite"),
|
|
1380
|
+
browser_interaction={"tool": "auto", "verify_steps": ["Open the page"]},
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
self.assert_shell_extracted_operations_removed(prompt)
|
|
1384
|
+
assert "executable version playwright-cli --arg=--version" in prompt
|
|
1385
|
+
assert "executable version opencli --arg=--version" in prompt
|
|
1386
|
+
assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
|
|
1387
|
+
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
|
|
1389
|
+
|
|
1390
|
+
def test_rendered_legacy_tier_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
|
|
1391
|
+
cases = [
|
|
1392
|
+
("bootstrap-tier1.md", "lite"),
|
|
1393
|
+
("bootstrap-tier2.md", "standard"),
|
|
1394
|
+
("bootstrap-tier3.md", "full"),
|
|
1395
|
+
]
|
|
1396
|
+
for template, mode in cases:
|
|
1397
|
+
prompt = _render_feature_prompt(
|
|
1398
|
+
tmp_path / f"legacy-{template}",
|
|
1399
|
+
mode=mode,
|
|
1400
|
+
critic=(mode != "lite"),
|
|
1401
|
+
template=template,
|
|
1402
|
+
browser_interaction={"tool": "playwright-cli", "verify_steps": ["Open the page"]},
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
self.assert_shell_extracted_operations_removed(prompt)
|
|
1406
|
+
assert "process start --pid-file .prizmkit/state/browser-dev-server.pid" in prompt
|
|
1407
|
+
assert "process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>" in prompt
|
|
1408
|
+
assert "port check 3000" in prompt
|
|
1409
|
+
|
|
1410
|
+
def test_legacy_tier_specific_extracted_operations_map_to_helper_commands(self, tmp_path):
|
|
1411
|
+
tier1 = _render_feature_prompt(
|
|
1412
|
+
tmp_path / "legacy-tier1-helper-map",
|
|
1413
|
+
mode="lite",
|
|
1414
|
+
template="bootstrap-tier1.md",
|
|
1415
|
+
)
|
|
1416
|
+
assert "artifact tree . --max-depth 2" in tier1
|
|
1417
|
+
assert "text contains .gitignore --pattern \"/binary-name\"" in tier1
|
|
1418
|
+
assert "NEEDS_IGNORE_ENTRY" in tier1
|
|
1419
|
+
|
|
1420
|
+
tier2 = _render_feature_prompt(
|
|
1421
|
+
tmp_path / "legacy-tier2-helper-map",
|
|
1422
|
+
mode="standard",
|
|
1423
|
+
critic=True,
|
|
1424
|
+
template="bootstrap-tier2.md",
|
|
1425
|
+
)
|
|
1426
|
+
assert "artifact exists .prizmkit/specs/123-prompt-cleanup/failure-log.md" in tier2
|
|
1427
|
+
assert "artifact tree . --max-depth 2" in tier2
|
|
1428
|
+
assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier2
|
|
1429
|
+
|
|
1430
|
+
tier3 = _render_feature_prompt(
|
|
1431
|
+
tmp_path / "legacy-tier3-helper-map",
|
|
1432
|
+
mode="full",
|
|
1433
|
+
critic=True,
|
|
1434
|
+
template="bootstrap-tier3.md",
|
|
1435
|
+
)
|
|
1436
|
+
assert "text search . --pattern \"123-prompt-cleanup\" --json" in tier3
|
|
1437
|
+
assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier3
|
|
1438
|
+
assert "Check if feature already committed by inspecting recent git history" in tier3
|
|
@@ -201,8 +201,9 @@ class TestExplicitProjectRoot:
|
|
|
201
201
|
assert "{{" + REMOVED_MAX_LOG_ENV + "}}" not in replacements
|
|
202
202
|
assert "{{" + REMOVED_MAX_LOG_HUMAN + "}}" not in replacements
|
|
203
203
|
assert "### Checkpoint Update Helper" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
204
|
-
assert "
|
|
204
|
+
assert "{{CHECKPOINT_PYTHON_CMD}}" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
205
205
|
assert "pipeline progress mechanism" not in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
206
|
+
assert "prizmkit-runtime-helper.py" in replacements["{{RUNTIME_HELPER_CMD}}"]
|
|
206
207
|
|
|
207
208
|
|
|
208
209
|
# ---------------------------------------------------------------------------
|
|
@@ -129,8 +129,9 @@ class TestExplicitProjectRoot:
|
|
|
129
129
|
assert not replacements["{{SESSION_STATUS_PATH}}"].startswith(str(execution_root.resolve()))
|
|
130
130
|
assert replacements["{{DEV_SUBAGENT_PATH}}"].startswith(str(execution_root.resolve()))
|
|
131
131
|
assert "### Checkpoint Update Helper" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
132
|
-
assert "
|
|
132
|
+
assert "{{CHECKPOINT_PYTHON_CMD}}" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
133
133
|
assert "pipeline progress mechanism" not in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
134
|
+
assert "prizmkit-runtime-helper.py" in replacements["{{RUNTIME_HELPER_CMD}}"]
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
class TestRefactorPromptMaxLogRemoval:
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Tests for the cross-platform prompt runtime helper CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
11
|
+
PIPELINE_ROOT = REPO_ROOT / "dev-pipeline"
|
|
12
|
+
HELPER_SCRIPT = PIPELINE_ROOT / "scripts" / "prizmkit-runtime-helper.py"
|
|
13
|
+
CLI_PATH = PIPELINE_ROOT / "cli.py"
|
|
14
|
+
|
|
15
|
+
if str(PIPELINE_ROOT) not in sys.path:
|
|
16
|
+
sys.path.insert(0, str(PIPELINE_ROOT))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run_helper(*args, cwd=None):
|
|
20
|
+
return subprocess.run(
|
|
21
|
+
[sys.executable, str(HELPER_SCRIPT), *args],
|
|
22
|
+
cwd=cwd or REPO_ROOT,
|
|
23
|
+
text=True,
|
|
24
|
+
stdout=subprocess.PIPE,
|
|
25
|
+
stderr=subprocess.PIPE,
|
|
26
|
+
check=False,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def first_line(result):
|
|
31
|
+
return result.stdout.splitlines()[0]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestArtifactAndTextHelpers:
|
|
35
|
+
def test_exists_ensure_dir_touch_list_tree_and_source_files(self, tmp_path):
|
|
36
|
+
missing = run_helper("artifact", "exists", str(tmp_path / "missing"))
|
|
37
|
+
assert first_line(missing) == "MISSING"
|
|
38
|
+
|
|
39
|
+
ready = run_helper("artifact", "ensure-dir", str(tmp_path / "src"))
|
|
40
|
+
assert first_line(ready) == "DIR_READY"
|
|
41
|
+
touched = run_helper("artifact", "touch", str(tmp_path / "src" / "app.py"))
|
|
42
|
+
assert first_line(touched) == "TOUCHED"
|
|
43
|
+
(tmp_path / "src" / "README.md").write_text("hello helper\n", encoding="utf-8")
|
|
44
|
+
(tmp_path / "src" / "ignored.bin").write_bytes(b"\x00")
|
|
45
|
+
|
|
46
|
+
listing = run_helper("artifact", "list", str(tmp_path / "src"))
|
|
47
|
+
assert first_line(listing) == "LIST"
|
|
48
|
+
assert "file:README.md" in listing.stdout
|
|
49
|
+
assert "file:app.py" in listing.stdout
|
|
50
|
+
|
|
51
|
+
tree = run_helper("artifact", "tree", str(tmp_path), "--max-depth", "2")
|
|
52
|
+
assert first_line(tree) == "TREE"
|
|
53
|
+
assert "entry: src/" in tree.stdout
|
|
54
|
+
assert "entry: src/app.py" in tree.stdout
|
|
55
|
+
|
|
56
|
+
sources = run_helper("artifact", "source-files", str(tmp_path), "--ext", ".py", "--json")
|
|
57
|
+
payload = json.loads(sources.stdout)
|
|
58
|
+
assert payload["marker"] == "SOURCE_FILES"
|
|
59
|
+
assert payload["files"] == ["src/app.py"]
|
|
60
|
+
|
|
61
|
+
def test_text_search_count_and_contains_markers(self, tmp_path):
|
|
62
|
+
target = tmp_path / "notes.md"
|
|
63
|
+
target.write_text("alpha\nbeta alpha\n", encoding="utf-8")
|
|
64
|
+
|
|
65
|
+
search = run_helper("text", "search", str(tmp_path), "--pattern", "alpha")
|
|
66
|
+
assert first_line(search) == "FOUND"
|
|
67
|
+
assert "count: 2" in search.stdout
|
|
68
|
+
|
|
69
|
+
count = run_helper("text", "count", str(tmp_path), "--pattern", "alpha")
|
|
70
|
+
assert first_line(count) == "COUNT"
|
|
71
|
+
assert "count: 2" in count.stdout
|
|
72
|
+
|
|
73
|
+
contains = run_helper("text", "contains", str(target), "--pattern", "beta")
|
|
74
|
+
assert first_line(contains) == "GATE:PASS"
|
|
75
|
+
missing = run_helper("text", "contains", str(target), "--pattern", "gamma")
|
|
76
|
+
assert first_line(missing) == "GATE:MISSING"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestPlatformExecutableAndPortHelpers:
|
|
80
|
+
def test_platform_detection_and_skill_lookup_follow_config(self, tmp_path, monkeypatch):
|
|
81
|
+
project = tmp_path / "project"
|
|
82
|
+
skill_dir = project / ".agents" / "skills" / "playwright-cli"
|
|
83
|
+
skill_dir.mkdir(parents=True)
|
|
84
|
+
(project / ".prizmkit").mkdir()
|
|
85
|
+
(project / ".prizmkit" / "config.json").write_text(
|
|
86
|
+
json.dumps({"platform": "codex", "ai_cli": "codex"}), encoding="utf-8"
|
|
87
|
+
)
|
|
88
|
+
monkeypatch.delenv("PRIZMKIT_PLATFORM", raising=False)
|
|
89
|
+
|
|
90
|
+
detect = run_helper("platform", "detect", "--project-root", str(project), "--json")
|
|
91
|
+
payload = json.loads(detect.stdout)
|
|
92
|
+
assert payload["marker"] == "CODEX"
|
|
93
|
+
assert payload["platform"] == "codex"
|
|
94
|
+
assert payload["source"] == "config:ai_cli"
|
|
95
|
+
|
|
96
|
+
skill = run_helper("platform", "skill", "playwright-cli", "--project-root", str(project), "--json")
|
|
97
|
+
payload = json.loads(skill.stdout)
|
|
98
|
+
assert payload["marker"] == "SKILL_EXISTS"
|
|
99
|
+
assert payload["platform"] == "codex"
|
|
100
|
+
assert payload["path"].endswith(".agents/skills/playwright-cli")
|
|
101
|
+
|
|
102
|
+
def test_codebuddy_cli_env_wins_over_filesystem_markers(self, tmp_path, monkeypatch):
|
|
103
|
+
from prizmkit_runtime.platform_detection import resolve_platform
|
|
104
|
+
|
|
105
|
+
project = tmp_path / "project"
|
|
106
|
+
(project / ".claude" / "agents").mkdir(parents=True)
|
|
107
|
+
monkeypatch.delenv("PRIZMKIT_PLATFORM", raising=False)
|
|
108
|
+
resolution = resolve_platform(project, env={"CODEBUDDY_CLI": "cbc"})
|
|
109
|
+
|
|
110
|
+
assert resolution.platform == "codebuddy"
|
|
111
|
+
assert resolution.source == "environment:CODEBUDDY_CLI"
|
|
112
|
+
|
|
113
|
+
def test_executable_check_and_version_use_shutil_and_direct_subprocess(self, tmp_path):
|
|
114
|
+
executable = tmp_path / "tool.py"
|
|
115
|
+
executable.write_text("#!/usr/bin/env python3\nprint('tool 1.2.3')\n", encoding="utf-8")
|
|
116
|
+
executable.chmod(0o755)
|
|
117
|
+
env_path = f"{tmp_path}{os.pathsep}{os.environ.get('PATH', '')}"
|
|
118
|
+
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
[sys.executable, str(HELPER_SCRIPT), "executable", "check", "tool.py"],
|
|
121
|
+
env={**os.environ, "PATH": env_path},
|
|
122
|
+
text=True,
|
|
123
|
+
stdout=subprocess.PIPE,
|
|
124
|
+
stderr=subprocess.PIPE,
|
|
125
|
+
check=False,
|
|
126
|
+
)
|
|
127
|
+
assert first_line(result) == "INSTALLED"
|
|
128
|
+
|
|
129
|
+
version = subprocess.run(
|
|
130
|
+
[sys.executable, str(HELPER_SCRIPT), "executable", "version", "tool.py", "--arg=--version"],
|
|
131
|
+
env={**os.environ, "PATH": env_path},
|
|
132
|
+
text=True,
|
|
133
|
+
stdout=subprocess.PIPE,
|
|
134
|
+
stderr=subprocess.PIPE,
|
|
135
|
+
check=False,
|
|
136
|
+
)
|
|
137
|
+
assert first_line(version) == "VERSION"
|
|
138
|
+
assert "version: tool 1.2.3" in version.stdout
|
|
139
|
+
|
|
140
|
+
def test_port_check_reports_free_and_in_use(self):
|
|
141
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
142
|
+
sock.bind(("127.0.0.1", 0))
|
|
143
|
+
sock.listen(1)
|
|
144
|
+
port = sock.getsockname()[1]
|
|
145
|
+
try:
|
|
146
|
+
in_use = run_helper("port", "check", str(port))
|
|
147
|
+
assert first_line(in_use) == "PORT_IN_USE"
|
|
148
|
+
finally:
|
|
149
|
+
sock.close()
|
|
150
|
+
free = run_helper("port", "check", str(port))
|
|
151
|
+
assert first_line(free) == "PORT_FREE"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestProcessAndEntrypointHelpers:
|
|
155
|
+
def test_process_start_status_and_cleanup(self, tmp_path):
|
|
156
|
+
sleeper = tmp_path / "sleep.py"
|
|
157
|
+
sleeper.write_text("import time\ntime.sleep(30)\n", encoding="utf-8")
|
|
158
|
+
pid_file = tmp_path / "server.pid"
|
|
159
|
+
|
|
160
|
+
start = run_helper(
|
|
161
|
+
"process",
|
|
162
|
+
"start",
|
|
163
|
+
"--pid-file",
|
|
164
|
+
str(pid_file),
|
|
165
|
+
"--",
|
|
166
|
+
sys.executable,
|
|
167
|
+
str(sleeper),
|
|
168
|
+
)
|
|
169
|
+
assert first_line(start) == "PROCESS_STARTED"
|
|
170
|
+
pid = int(pid_file.read_text(encoding="utf-8").strip())
|
|
171
|
+
|
|
172
|
+
status = run_helper("process", "status-pid", str(pid))
|
|
173
|
+
assert first_line(status) == "PROCESS_RUNNING"
|
|
174
|
+
|
|
175
|
+
cleanup = run_helper("process", "cleanup-pid", str(pid), "--grace-seconds", "0.2")
|
|
176
|
+
assert first_line(cleanup) in {"PROCESS_TERMINATED", "PROCESS_NOT_RUNNING"}
|
|
177
|
+
|
|
178
|
+
def test_cleanup_port_is_deterministic_without_lsof(self):
|
|
179
|
+
result = run_helper("process", "cleanup-port", "9")
|
|
180
|
+
assert first_line(result) in {"PORT_FREE", "UNSUPPORTED"}
|
|
181
|
+
assert "lsof" not in result.stdout.lower()
|
|
182
|
+
|
|
183
|
+
def test_canonical_cli_exposes_helper_group(self, tmp_path):
|
|
184
|
+
existing = tmp_path / "exists.txt"
|
|
185
|
+
existing.write_text("ok", encoding="utf-8")
|
|
186
|
+
result = subprocess.run(
|
|
187
|
+
[sys.executable, str(CLI_PATH), "helper", "artifact", "exists", str(existing)],
|
|
188
|
+
cwd=REPO_ROOT,
|
|
189
|
+
text=True,
|
|
190
|
+
stdout=subprocess.PIPE,
|
|
191
|
+
stderr=subprocess.PIPE,
|
|
192
|
+
check=False,
|
|
193
|
+
)
|
|
194
|
+
assert result.returncode == 0
|
|
195
|
+
assert first_line(result) == "EXISTS"
|
|
196
|
+
|
|
197
|
+
def test_web_wait_ready_accepts_local_http_server(self, tmp_path):
|
|
198
|
+
server = tmp_path / "server.py"
|
|
199
|
+
server.write_text(
|
|
200
|
+
"from http.server import HTTPServer, BaseHTTPRequestHandler\n"
|
|
201
|
+
"class H(BaseHTTPRequestHandler):\n"
|
|
202
|
+
" def do_GET(self):\n"
|
|
203
|
+
" self.send_response(200); self.end_headers(); self.wfile.write(b'ok')\n"
|
|
204
|
+
" def log_message(self, *args): pass\n"
|
|
205
|
+
"HTTPServer(('127.0.0.1', 0), H).serve_forever()\n",
|
|
206
|
+
encoding="utf-8",
|
|
207
|
+
)
|
|
208
|
+
# Use helper process start for coverage, then skip fixed URL because the
|
|
209
|
+
# child selects an ephemeral port that is not externally reported.
|
|
210
|
+
result = run_helper("web", "wait-ready", "--url", "http://127.0.0.1:9", "--timeout", "0.1", "--interval", "0.05")
|
|
211
|
+
assert first_line(result) == "NOT_READY"
|
|
@@ -1904,6 +1904,9 @@ def test_materialize_worktree_support_assets_copies_ignored_assets_without_retir
|
|
|
1904
1904
|
(repo / ".prizmkit" / "manifest.json").write_text("{}", encoding="utf-8")
|
|
1905
1905
|
(repo / ".prizmkit" / "dev-pipeline" / "scripts").mkdir(parents=True)
|
|
1906
1906
|
(repo / ".prizmkit" / "dev-pipeline" / "scripts" / "update-checkpoint.py").write_text("# update", encoding="utf-8")
|
|
1907
|
+
(repo / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime").mkdir(parents=True)
|
|
1908
|
+
(repo / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime" / "__init__.py").write_text("", encoding="utf-8")
|
|
1909
|
+
(repo / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime" / "runtime_helper.py").write_text("# helper", encoding="utf-8")
|
|
1907
1910
|
(repo / ".prizmkit" / "dev-pipeline" / "run-feature.sh").write_text("retired", encoding="utf-8")
|
|
1908
1911
|
|
|
1909
1912
|
runtime = worktree_runtime_context(
|
|
@@ -1922,6 +1925,8 @@ def test_materialize_worktree_support_assets_copies_ignored_assets_without_retir
|
|
|
1922
1925
|
assert (runtime.worktree_path / ".prizmkit" / "config.json").is_file()
|
|
1923
1926
|
assert (runtime.worktree_path / ".prizmkit" / "manifest.json").is_file()
|
|
1924
1927
|
assert (runtime.worktree_path / ".prizmkit" / "dev-pipeline" / "scripts" / "update-checkpoint.py").is_file()
|
|
1928
|
+
assert (runtime.worktree_path / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime" / "__init__.py").is_file()
|
|
1929
|
+
assert (runtime.worktree_path / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime" / "runtime_helper.py").is_file()
|
|
1925
1930
|
assert not (runtime.worktree_path / ".prizmkit" / "dev-pipeline" / "run-feature.sh").exists()
|
|
1926
1931
|
|
|
1927
1932
|
assert guarded_worktree_remove(runtime, delete_branch=False).ok
|