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.
Files changed (46) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +20 -0
  3. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +1 -0
  4. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +21 -0
  5. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
  6. package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +208 -0
  7. package/bundled/dev-pipeline/prizmkit_runtime/runtime_helper.py +508 -0
  8. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +3 -1
  9. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +3 -1
  10. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +3 -1
  11. package/bundled/dev-pipeline/scripts/prizmkit-runtime-helper.py +27 -0
  12. package/bundled/dev-pipeline/scripts/utils.py +48 -77
  13. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +44 -71
  14. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +48 -75
  15. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +52 -78
  16. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -2
  17. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
  18. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +1 -1
  19. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +8 -8
  20. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +33 -55
  21. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +28 -33
  22. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +22 -62
  23. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
  24. package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
  25. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +1 -1
  26. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +1 -1
  27. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +1 -1
  28. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
  29. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
  30. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +1 -1
  31. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +1 -1
  32. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +2 -2
  33. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +1 -1
  34. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +1 -1
  35. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +7 -6
  36. package/bundled/dev-pipeline/templates/sections/phase0-init.md +1 -1
  37. package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +1 -1
  38. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
  39. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +1 -1
  40. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +152 -3
  41. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +2 -1
  42. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +2 -1
  43. package/bundled/dev-pipeline/tests/test_runtime_helper.py +211 -0
  44. package/bundled/dev-pipeline/tests/test_unified_cli.py +5 -0
  45. package/bundled/skills/_metadata.json +1 -1
  46. 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
- grep -q "## Verdict" .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md && echo "GATE:PASS" || echo "GATE:MISSING"
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
- ls .prizmkit/specs/{{FEATURE_SLUG}}/ 2>/dev/null
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: read KEY_FILES and STRUCTURE sections from `root.prizm` to identify where source code lives (e.g. `src/`, `app/`, `lib/`, `cmd/`, `packages/`, or project root). If `root.prizm` is missing, scan the project tree:
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
- find . -maxdepth 2 -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.go" -o -name "*.java" -o -name "*.rb" -o -name "*.rs" \) -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/build/*' -not -path '*/vendor/*' | head -30
17
+ {{RUNTIME_HELPER_CMD}} artifact source-files . --json
18
18
  ```
19
- Identify the top-level source directories from the results.
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: `ls .prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md`
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
- ls .prizmkit/specs/{{FEATURE_SLUG}}/spec.md .prizmkit/specs/{{FEATURE_SLUG}}/plan.md 2>/dev/null
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 `python3 {{INIT_SCRIPT_PATH}} --project-root {{PROJECT_ROOT}} --feature-id {{FEATURE_ID}} --feature-slug {{FEATURE_SLUG}}`
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. `ls .prizmkit/specs/{{FEATURE_SLUG}}/` — check what exists
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 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
 
@@ -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 "python3 .prizmkit/dev-pipeline/scripts/update-checkpoint.py" in rendered
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 "python3 .prizmkit/dev-pipeline/scripts/update-checkpoint.py" in replacements["{{CHECKPOINT_SYSTEM}}"]
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 "python3 .prizmkit/dev-pipeline/scripts/update-checkpoint.py" in replacements["{{CHECKPOINT_SYSTEM}}"]
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.108",
2
+ "version": "1.1.110",
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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.108",
3
+ "version": "1.1.110",
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": {