prizmkit 1.1.108 → 1.1.109
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 +33 -60
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +35 -62
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +40 -66
- 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/phase-browser-verification-auto.md +31 -54
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +21 -30
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +22 -62
- 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-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/tests/test_generate_bootstrap_prompt.py +45 -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
|
@@ -10,9 +10,9 @@ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without
|
|
|
10
10
|
|
|
11
11
|
**Step 0 — Playwright CLI Readiness Check (BLOCKING — must pass before any browser action)**:
|
|
12
12
|
|
|
13
|
-
0a. Check if `playwright-cli` is installed:
|
|
13
|
+
0a. Check if `playwright-cli` is installed using the runtime helper:
|
|
14
14
|
```bash
|
|
15
|
-
|
|
15
|
+
{{RUNTIME_HELPER_CMD}} executable version playwright-cli --arg=--version
|
|
16
16
|
```
|
|
17
17
|
If output is `NOT_INSTALLED`, install it:
|
|
18
18
|
```bash
|
|
@@ -20,7 +20,7 @@ npm install -g @playwright/cli@latest
|
|
|
20
20
|
```
|
|
21
21
|
Then verify installation succeeded:
|
|
22
22
|
```bash
|
|
23
|
-
playwright-cli --version
|
|
23
|
+
{{RUNTIME_HELPER_CMD}} executable version playwright-cli --arg=--version
|
|
24
24
|
```
|
|
25
25
|
If installation fails, log the error in context-snapshot.md under `## Browser Verification: SKIPPED — playwright-cli installation failed` and proceed to the next phase. Do NOT attempt browser verification without playwright-cli.
|
|
26
26
|
|
|
@@ -32,43 +32,9 @@ Use this output to determine the correct commands for your verification steps. D
|
|
|
32
32
|
|
|
33
33
|
0c. Check if playwright-cli skill is installed for the current AI platform:
|
|
34
34
|
```bash
|
|
35
|
-
|
|
36
|
-
CURRENT_PLATFORM=""
|
|
37
|
-
if which claude >/dev/null 2>&1; then
|
|
38
|
-
CURRENT_PLATFORM="claude"
|
|
39
|
-
SKILL_DIR="$HOME/.claude/skills"
|
|
40
|
-
elif which cbc >/dev/null 2>&1; then
|
|
41
|
-
CURRENT_PLATFORM="codebuddy"
|
|
42
|
-
SKILL_DIR="$HOME/.cbc/skills"
|
|
43
|
-
else
|
|
44
|
-
# Try to detect from environment or config
|
|
45
|
-
CURRENT_PLATFORM="unknown"
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
# Check if playwright-cli skill exists
|
|
49
|
-
if [ -d "$SKILL_DIR/playwright-cli" ] || ls "$SKILL_DIR"/playwright* 2>/dev/null | grep -q .; then
|
|
50
|
-
echo "SKILL_EXISTS"
|
|
51
|
-
else
|
|
52
|
-
echo "SKILL_MISSING"
|
|
53
|
-
fi
|
|
54
|
-
```
|
|
55
|
-
If `SKILL_MISSING`:
|
|
56
|
-
```bash
|
|
57
|
-
# Install playwright-cli skills (defaults to claude platform)
|
|
58
|
-
playwright-cli install --skills
|
|
59
|
-
```
|
|
60
|
-
If the current platform is NOT claude, move the installed skill files to the correct location:
|
|
61
|
-
```bash
|
|
62
|
-
# Skills are installed to claude's default location — move to current platform's skill dir
|
|
63
|
-
if [ "$CURRENT_PLATFORM" != "claude" ] && [ "$CURRENT_PLATFORM" != "unknown" ]; then
|
|
64
|
-
CLAUDE_SKILL_DIR="$HOME/.claude/skills"
|
|
65
|
-
if [ -d "$CLAUDE_SKILL_DIR/playwright-cli" ]; then
|
|
66
|
-
mkdir -p "$SKILL_DIR"
|
|
67
|
-
cp -r "$CLAUDE_SKILL_DIR/playwright-cli" "$SKILL_DIR/"
|
|
68
|
-
echo "Moved playwright-cli skill from claude to $CURRENT_PLATFORM"
|
|
69
|
-
fi
|
|
70
|
-
fi
|
|
35
|
+
{{RUNTIME_HELPER_CMD}} platform skill playwright-cli --prefix
|
|
71
36
|
```
|
|
37
|
+
If `SKILL_MISSING`: run `playwright-cli install --skills`. If current platform is NOT claude, copy installed skill from `$HOME/.claude/skills/playwright-cli` to the path shown by the helper output for the current platform.
|
|
72
38
|
|
|
73
39
|
0d. Read the installed playwright-cli skill for workflow guidance:
|
|
74
40
|
After skill installation, read the skill's SKILL.md to understand recommended workflows and patterns. Use these patterns to construct your verification flow — do NOT invent your own patterns if the skill provides them.
|
|
@@ -78,31 +44,20 @@ After skill installation, read the skill's SKILL.md to understand recommended wo
|
|
|
78
44
|
You know this project's tech stack. Detect and start the dev server yourself:
|
|
79
45
|
|
|
80
46
|
1. Identify the dev server start command from project config (`package.json` scripts, `Makefile`, `docker-compose.yml`, etc.)
|
|
81
|
-
2.
|
|
47
|
+
2. Use the pre-detected port if available, otherwise determine the dev server port from project config. Do NOT hardcode or guess the port. Record it as `DEV_PORT` in your notes.
|
|
48
|
+
3. Verify the port is available:
|
|
82
49
|
```bash
|
|
83
|
-
|
|
84
|
-
DEV_PORT={{DEV_PORT}}
|
|
85
|
-
# If DEV_PORT is still a placeholder, detect at runtime:
|
|
86
|
-
if [ "$DEV_PORT" = "{{DEV_PORT}}" ]; then
|
|
87
|
-
DEV_PORT=$(node -e "const s=require('./package.json').scripts.dev; const m=s.match(/-p\s+(\d+)/); console.log(m?m[1]:'')")
|
|
88
|
-
if [ -z "$DEV_PORT" ]; then
|
|
89
|
-
DEV_PORT=$(echo "$NEXT_PUBLIC_SITE_URL" | sed -nE 's|.*:([0-9]+).*|\1|p')
|
|
90
|
-
fi
|
|
91
|
-
DEV_PORT=${DEV_PORT:-3000}
|
|
92
|
-
fi
|
|
93
|
-
echo "Detected DEV_PORT=$DEV_PORT"
|
|
50
|
+
{{RUNTIME_HELPER_CMD}} port check {{DEV_PORT}}
|
|
94
51
|
```
|
|
95
|
-
|
|
52
|
+
4. Start the dev server with the runtime helper and capture a PID file:
|
|
96
53
|
```bash
|
|
97
|
-
|
|
54
|
+
{{RUNTIME_HELPER_CMD}} process start --pid-file .prizmkit/state/browser-dev-server.pid -- <start-command tokens>
|
|
98
55
|
```
|
|
99
|
-
|
|
56
|
+
5. Wait for server to be ready:
|
|
100
57
|
```bash
|
|
101
|
-
|
|
102
|
-
DEV_SERVER_PID=$!
|
|
58
|
+
{{RUNTIME_HELPER_CMD}} web wait-ready --url http://localhost:{{DEV_PORT}} --timeout 30 --interval 2
|
|
103
59
|
```
|
|
104
|
-
|
|
105
|
-
6. Open the app in playwright-cli: `playwright-cli open http://localhost:$DEV_PORT`
|
|
60
|
+
6. Open the app in playwright-cli: `playwright-cli open http://localhost:{{DEV_PORT}}`
|
|
106
61
|
7. If the page requires authentication, use playwright-cli to register a test user and log in first
|
|
107
62
|
|
|
108
63
|
**Step 2 — Verification**:
|
|
@@ -122,15 +77,21 @@ Take a final screenshot for evidence: `playwright-cli screenshot`
|
|
|
122
77
|
**Step 3 — Cleanup (REQUIRED — you started it, you stop it)**:
|
|
123
78
|
|
|
124
79
|
1. Close the playwright-cli browser: `playwright-cli close`
|
|
125
|
-
2.
|
|
126
|
-
|
|
80
|
+
2. Stop the dev server process using the PID file written in Step 1:
|
|
81
|
+
```bash
|
|
82
|
+
{{RUNTIME_HELPER_CMD}} process cleanup-pid <pid-from-.prizmkit/state/browser-dev-server.pid>
|
|
83
|
+
```
|
|
84
|
+
3. Verify port is released:
|
|
85
|
+
```bash
|
|
86
|
+
{{RUNTIME_HELPER_CMD}} port check {{DEV_PORT}}
|
|
87
|
+
```
|
|
127
88
|
|
|
128
89
|
**Step 4 — Reporting**:
|
|
129
90
|
|
|
130
91
|
Append results to `context-snapshot.md`:
|
|
131
92
|
```
|
|
132
93
|
## Browser Verification
|
|
133
|
-
URL: http://localhost
|
|
94
|
+
URL: http://localhost:<DEV_PORT>
|
|
134
95
|
Dev Server Command: <actual command used>
|
|
135
96
|
playwright-cli version: <version>
|
|
136
97
|
Steps executed: [list of playwright-cli commands used]
|
|
@@ -142,4 +103,3 @@ Browser cleanup: confirmed
|
|
|
142
103
|
|
|
143
104
|
If verification fails, log the failure details but continue to commit. Failures do NOT block the commit, but you MUST attempt verification and MUST clean up the dev server.
|
|
144
105
|
|
|
145
|
-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
### Build Context Snapshot
|
|
2
2
|
|
|
3
3
|
```bash
|
|
4
|
-
|
|
4
|
+
{{RUNTIME_HELPER_CMD}} artifact exists .prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
If MISSING — build it now:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Guard**: Verify critic agent file exists before spawning:
|
|
4
4
|
```bash
|
|
5
|
-
|
|
5
|
+
{{RUNTIME_HELPER_CMD}} artifact exists {{CRITIC_SUBAGENT_PATH}} --exists-marker CRITIC:READY --missing-marker CRITIC:MISSING
|
|
6
6
|
```
|
|
7
7
|
If CRITIC:MISSING — skip this phase entirely and proceed. Log: "Critic agent not installed — skipping Plan Challenge."
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Guard**: Verify critic agent file exists before spawning:
|
|
4
4
|
```bash
|
|
5
|
-
|
|
5
|
+
{{RUNTIME_HELPER_CMD}} artifact exists {{CRITIC_SUBAGENT_PATH}} --exists-marker CRITIC:READY --missing-marker CRITIC:MISSING
|
|
6
6
|
```
|
|
7
7
|
If CRITIC:MISSING — skip this phase entirely and proceed. Log: "Critic agent not installed — skipping Plan Challenge."
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
### Plan & Tasks
|
|
2
2
|
|
|
3
3
|
```bash
|
|
4
|
-
|
|
4
|
+
{{RUNTIME_HELPER_CMD}} artifact exists .prizmkit/specs/{{FEATURE_SLUG}}/plan.md
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
If missing, run `/prizmkit-plan` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to generate `plan.md`:
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
Create a current-run marker immediately before invoking the skill:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
touch .prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started
|
|
8
|
+
{{RUNTIME_HELPER_CMD}} artifact ensure-dir .prizmkit/specs/{{FEATURE_SLUG}}
|
|
9
|
+
{{RUNTIME_HELPER_CMD}} artifact touch .prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Run `/prizmkit-test` with the feature artifact directory:
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -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"
|
|
@@ -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
|