prizmkit 1.1.102 → 1.1.105
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/README.md +6 -7
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +4 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +357 -157
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +146 -7
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +37 -28
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +4 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +62 -38
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +57 -49
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +0 -1
- package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +2 -2
- package/bundled/dev-pipeline/templates/sections/directory-convention-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -0
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -0
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +46 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +118 -10
- package/bundled/dev-pipeline/tests/test_unified_cli.py +76 -0
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +0 -71
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Dev-Pipeline Session Bootstrap — Tier 3 (
|
|
1
|
+
# Dev-Pipeline Session Bootstrap — Tier 3 (Orchestrator + Critic/Reviewer)
|
|
2
2
|
|
|
3
3
|
## Session Context
|
|
4
4
|
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATURE_TITLE}}".
|
|
12
12
|
|
|
13
|
-
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed. When you spawn
|
|
13
|
+
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed. When you spawn Critic or Reviewer agents, wait for each to finish (run_in_background=false). Do NOT spawn agents in background and exit — that kills the session.
|
|
14
14
|
|
|
15
|
-
**Tier 3 —
|
|
15
|
+
**Tier 3 — Orchestrator + Critic/Reviewer**: For complex features, the main orchestrator handles context, planning, and implementation directly. Critic challenges the plan and Reviewer reviews. Do not spawn a top-level Dev implementation agent.
|
|
16
16
|
|
|
17
17
|
**Agent spawn failure policy (all Agent tool calls)**:
|
|
18
|
-
- If spawning
|
|
18
|
+
- If spawning Reviewer or Critic fails with team/config/lock errors, retry at most once.
|
|
19
19
|
- If the second attempt fails, do not keep spawning variants and do not enter artifact polling for Implementation Log, challenge report, or review report markers.
|
|
20
|
-
- Use the documented inline/recovery fallback for that phase: write the required report yourself where possible, complete remaining
|
|
20
|
+
- Use the documented inline/recovery fallback for that phase: write the required report yourself where possible, complete remaining work directly in the orchestrator when safe, or write `failure-log.md` with the spawn error and last observable state before stopping for recovery.
|
|
21
21
|
- Apply the same cap to any re-spawn for report repair or resume prompts; do not burn multiple minutes on identical team/config/lock failures.
|
|
22
22
|
|
|
23
23
|
### Feature Description
|
|
@@ -71,12 +71,12 @@ You are running in **headless non-interactive mode** with a FINITE context windo
|
|
|
71
71
|
## PrizmKit Directory Convention
|
|
72
72
|
|
|
73
73
|
```
|
|
74
|
-
.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md ← orchestrator writes Sections 1-4
|
|
74
|
+
.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md ← orchestrator writes Sections 1-4 + Implementation Log; Reviewer appends Review Notes
|
|
75
75
|
.prizmkit/specs/{{FEATURE_SLUG}}/spec.md
|
|
76
76
|
.prizmkit/specs/{{FEATURE_SLUG}}/plan.md ← includes Tasks section
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4
|
|
79
|
+
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4 and the Implementation Log; Reviewer appends Review Notes. This eliminates redundant I/O across review and orchestration phases.
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
@@ -268,55 +268,64 @@ After all critics return, read all 3 reports:
|
|
|
268
268
|
**CP-2.5**: Plan challenges reviewed and resolved.
|
|
269
269
|
{{END_IF_CRITIC_ENABLED}}
|
|
270
270
|
|
|
271
|
-
###
|
|
271
|
+
### Implement — Orchestrator Direct (no Dev subagent)
|
|
272
272
|
|
|
273
|
-
**
|
|
273
|
+
**Rationale**: Development is deterministic plan execution — the orchestrator already built full context in Phase 1-2. Spawning a Dev subagent forces it to rebuild context (re-reading files the orchestrator already read), wastes context, and risks worktree double-edit. The orchestrator implements directly.
|
|
274
274
|
|
|
275
|
-
Before
|
|
275
|
+
Before starting, check plan.md Tasks:
|
|
276
276
|
```bash
|
|
277
277
|
grep -c '^\- \[ \]' .prizmkit/specs/{{FEATURE_SLUG}}/plan.md 2>/dev/null || true
|
|
278
278
|
```
|
|
279
|
-
- If result is `0` (all tasks already `[x]`) → **SKIP
|
|
280
|
-
- If
|
|
279
|
+
- If result is `0` (all tasks already `[x]`) → **SKIP to Review**.
|
|
280
|
+
- If non-zero → implement directly below.
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
**Build artifacts rule**: After any build/compile command, ensure output is in `.gitignore`. Never commit binaries/build output.
|
|
283
283
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
Prompt:
|
|
287
|
-
> "Read {{DEV_SUBAGENT_PATH}}. Implement feature {{FEATURE_ID}} (slug: {{FEATURE_SLUG}}).
|
|
288
|
-
> **IMPORTANT**: Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` FIRST — Section 3 has Prizm Context (TRAPS/RULES), Section 4 has File Manifest with paths and interfaces.
|
|
289
|
-
> ⚠️ DO NOT re-read source files already listed in Section 4 File Manifest unless you need implementation detail beyond the interface summary.
|
|
290
|
-
> 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` for full context.
|
|
291
|
-
> 2. Run `/prizmkit-implement` to execute the tasks in plan.md. Run tests with: `{{TEST_CMD}}`. Known baseline failures (pre-existing, not your fault): `{{BASELINE_FAILURES}}`.
|
|
292
|
-
> 3. If plan.md has more than 5 tasks: update durable checkpoints/artifacts after every 3 tasks, minimize output, and rely on runner continuation if context overflow occurs. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
293
|
-
> 4. **Verification Gate Self-Check (BLOCKING)**: before returning, write a `### Gate Evidence` section to Implementation Log — one evidence line per gate (test name / file:line / fixture path). Any gate without evidence is BLOCKED, not success.
|
|
294
|
-
> 5. **Layered test strategy**: debug with single-file tests (`npx vitest run <file> -t '<name>'`), only run full suite as gate when single-file is green. Never run full suite while a single-file test is still red.
|
|
295
|
-
> 6. **Anti re-read**: 20-step window (full Read forbidden within 20 tool calls of last read), 3-strike (3 full Reads same file → BLOCKED). Use `grep -n`+`sed -n` for specific lines.
|
|
296
|
-
> 7. **Read offset safety**: if Read returns "shorter than offset"/"empty"/"wasted" — run `wc -l` then `sed -n`, do NOT retry same offset. 2 consecutive → STOP Reading that file.
|
|
297
|
-
> 8. **Hard Round Cap**: after 6 total test-fix rounds, STOP trial-and-error. Write failure-log. Switch to read-once-then-rewrite strategy. Do NOT exceed 7 rounds.
|
|
298
|
-
> 9. After implement completes, verify the '## Implementation Log' section (with Gate Evidence) was written to context-snapshot.md.
|
|
299
|
-
> 10. Do NOT execute any git commands (no git add/commit/reset/push).
|
|
300
|
-
> Do NOT exit until all tasks are [x] and the '## Implementation Log' section (with Gate Evidence) is written in context-snapshot.md."
|
|
301
|
-
|
|
302
|
-
**Gate Check — Implementation Log**:
|
|
303
|
-
After Dev agent returns, verify the Implementation Log was written:
|
|
284
|
+
**3a.** Detect test commands and record baseline:
|
|
304
285
|
```bash
|
|
305
|
-
|
|
286
|
+
($TEST_CMD) 2>&1 | tee /tmp/test-baseline.txt | tail -20
|
|
287
|
+
```
|
|
288
|
+
Save pre-existing failing tests as `BASELINE_FAILURES`.
|
|
289
|
+
|
|
290
|
+
**3b.** Run `/prizmkit-implement` directly — you (the orchestrator) execute the full cycle:
|
|
291
|
+
- Read plan.md Tasks from `.prizmkit/specs/{{FEATURE_SLUG}}/`
|
|
292
|
+
- Use context-snapshot.md Section 4 File Manifest for targeted reads — do NOT re-read files already summarized there
|
|
293
|
+
- Implements task-by-task, marking each `[x]` immediately
|
|
294
|
+
- Creates/updates L2 `.prizm` docs when creating new modules
|
|
295
|
+
- Defers scoped/full test execution until after the code-review loop completes
|
|
296
|
+
- If plan.md has >5 tasks: update checkpoints after every 3 tasks
|
|
297
|
+
|
|
298
|
+
**3c. Focused checks only (no test gate here)**:
|
|
299
|
+
- During implementation, do not run mandatory periodic or full-suite tests.
|
|
300
|
+
- If a tiny targeted check is needed to understand a local failure, run only the smallest relevant command and capture output to `/tmp/test-out.txt`.
|
|
301
|
+
- The scoped feature test gate runs after code review; do not declare implementation success based on an early full-suite run.
|
|
302
|
+
|
|
303
|
+
**3d. Verification Gate Self-Check (BLOCKING — before declaring implement done)**:
|
|
304
|
+
For each Verification Gate from the Task Contract, write one evidence line to Implementation Log:
|
|
305
|
+
```
|
|
306
|
+
### Gate Evidence
|
|
307
|
+
- [x] G1: <gate text> — Evidence: <test name / file:line / fixture path>
|
|
308
|
+
- [ ] G3: <gate text> — BLOCKED: <reason> ← no evidence = BLOCKED, not success
|
|
306
309
|
```
|
|
307
|
-
If GATE:MISSING — send message to Dev (re-spawn if needed): "Write the '## Implementation Log' section to context-snapshot.md before I can proceed to review. Include: files changed/created, key decisions, deviations from plan, notable discoveries."
|
|
308
310
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
311
|
+
**3e.** Append `## Implementation Log` (with Gate Evidence) to `context-snapshot.md`:
|
|
312
|
+
- files changed/created
|
|
313
|
+
- key decisions
|
|
314
|
+
- deviations from plan
|
|
315
|
+
- test results
|
|
316
|
+
- Gate Evidence section
|
|
317
|
+
- unresolved blockers
|
|
318
|
+
|
|
319
|
+
**CP-2**: All tasks `[x]`, Implementation Log written with Gate Evidence, and blocked gates documented in `failure-log.md`. Test execution is deferred until after code review.
|
|
320
|
+
|
|
321
|
+
**Checkpoint update**:
|
|
322
|
+
```bash
|
|
323
|
+
python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
|
|
324
|
+
--checkpoint-path .prizmkit/specs/{{FEATURE_SLUG}}/workflow-checkpoint.json \
|
|
325
|
+
--step prizmkit-implement \
|
|
326
|
+
--status completed
|
|
327
|
+
```
|
|
318
328
|
|
|
319
|
-
All tasks `[x]`, tests pass.
|
|
320
329
|
|
|
321
330
|
### Scoped Feature Test Gate — PrizmKit Test
|
|
322
331
|
|
|
@@ -618,7 +627,6 @@ Rules for writing completion notes:
|
|
|
618
627
|
| Feature Artifacts Dir | `.prizmkit/specs/{{FEATURE_SLUG}}/` |
|
|
619
628
|
| Context Snapshot | `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` |
|
|
620
629
|
| Team Config | `{{TEAM_CONFIG_PATH}}` |
|
|
621
|
-
| Dev Agent Def | {{DEV_SUBAGENT_PATH}} |
|
|
622
630
|
| Reviewer Agent Def | {{REVIEWER_SUBAGENT_PATH}} |
|
|
623
631
|
{{IF_CRITIC_ENABLED}}
|
|
624
632
|
| Critic Agent Def | {{CRITIC_SUBAGENT_PATH}} |
|
|
@@ -645,10 +653,10 @@ If you encounter an unrecoverable error, context overflow, or are about to exit
|
|
|
645
653
|
|
|
646
654
|
## Reminders
|
|
647
655
|
|
|
648
|
-
- Tier 3:
|
|
649
|
-
- context-snapshot.md is append-only: orchestrator writes Sections 1-4
|
|
656
|
+
- Tier 3: orchestrator implements directly; Critic challenges plan; Reviewer reviews
|
|
657
|
+
- context-snapshot.md is append-only: orchestrator writes Sections 1-4 + Implementation Log, Reviewer appends Review Notes
|
|
650
658
|
- Gate checks enforce Implementation Log and Review Notes are written before proceeding
|
|
651
|
-
- Do NOT use `run_in_background=true` when spawning
|
|
659
|
+
- Do NOT use `run_in_background=true` when spawning Critic or Reviewer agents
|
|
652
660
|
- Commit phase must use `/prizmkit-committer`; do NOT replace with manual git commit commands
|
|
653
661
|
- **NEVER delete, modify, or touch any file under `.prizmkit/state/`** — those are pipeline runtime files managed by the runner
|
|
654
662
|
- NEVER run `rm -rf`, `git clean`, or any destructive filesystem operations
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|----------|------|
|
|
5
5
|
| Feature Artifacts Dir | `.prizmkit/specs/{{FEATURE_SLUG}}/` |
|
|
6
6
|
| Context Snapshot | `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` |
|
|
7
|
-
| Dev Agent Def | {{DEV_SUBAGENT_PATH}} |
|
|
8
7
|
| Reviewer Agent Def | {{REVIEWER_SUBAGENT_PATH}} |
|
|
9
8
|
| Critic Agent Def (if enabled) | {{CRITIC_SUBAGENT_PATH}} |
|
|
10
9
|
| Project Root | {{PROJECT_ROOT}} |
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
| Feature Artifacts Dir | `.prizmkit/specs/{{FEATURE_SLUG}}/` |
|
|
6
6
|
| Context Snapshot | `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` |
|
|
7
7
|
| Team Config | `{{TEAM_CONFIG_PATH}}` |
|
|
8
|
-
| Dev Agent Def | {{DEV_SUBAGENT_PATH}} |
|
|
9
8
|
| Reviewer Agent Def | {{REVIEWER_SUBAGENT_PATH}} |
|
|
10
9
|
| Critic Agent Def | {{CRITIC_SUBAGENT_PATH}} |
|
|
11
10
|
| Project Root | {{PROJECT_ROOT}} |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
## PrizmKit Directory Convention
|
|
2
2
|
|
|
3
3
|
```
|
|
4
|
-
.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md ← orchestrator writes Sections 1-4
|
|
4
|
+
.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md ← orchestrator writes Sections 1-4 + Implementation Log; Reviewer appends Review Notes
|
|
5
5
|
.prizmkit/specs/{{FEATURE_SLUG}}/plan.md ← includes Tasks section
|
|
6
6
|
```
|
|
7
7
|
|
|
8
|
-
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4
|
|
8
|
+
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4 and the Implementation Log; Reviewer appends Review Notes. Append-only after initial creation.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
## PrizmKit Directory Convention
|
|
2
2
|
|
|
3
3
|
```
|
|
4
|
-
.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md ← orchestrator writes Sections 1-4
|
|
4
|
+
.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md ← orchestrator writes Sections 1-4 + Implementation Log; Reviewer appends Review Notes
|
|
5
5
|
.prizmkit/specs/{{FEATURE_SLUG}}/spec.md
|
|
6
6
|
.prizmkit/specs/{{FEATURE_SLUG}}/plan.md ← includes Tasks section
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4
|
|
9
|
+
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4 and the Implementation Log; Reviewer appends Review Notes. This eliminates redundant I/O across review and orchestration phases.
|
|
@@ -22,6 +22,7 @@ grep -c '^\- \[ \]' .prizmkit/specs/{{FEATURE_SLUG}}/plan.md 2>/dev/null || true
|
|
|
22
22
|
- Implements task-by-task, marking each `[x]` immediately
|
|
23
23
|
- Creates/updates L2 `.prizm` docs when creating new modules
|
|
24
24
|
- Defers scoped/full test execution until after the code-review loop completes
|
|
25
|
+
- Preserves Reviewer/Critic behavior; only the top-level implementation Dev handoff is removed
|
|
25
26
|
- If plan.md has >5 tasks: update checkpoints after every 3 tasks
|
|
26
27
|
|
|
27
28
|
**3c. Focused checks only (no test gate here)**:
|
|
@@ -23,6 +23,7 @@ Save pre-existing failing tests as `BASELINE_FAILURES`.
|
|
|
23
23
|
- Implements task-by-task, marking each `[x]` immediately
|
|
24
24
|
- Creates/updates L2 `.prizm` docs when creating new modules
|
|
25
25
|
- Defers scoped/full test execution until after the code-review loop completes
|
|
26
|
+
- Preserves Reviewer/Critic behavior; only the top-level implementation Dev handoff is removed
|
|
26
27
|
- If plan.md has >5 tasks: update checkpoints after every 3 tasks
|
|
27
28
|
|
|
28
29
|
**3c. Focused checks only (no test gate here)**:
|
|
@@ -8,6 +8,7 @@ from argparse import Namespace
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from generate_bootstrap_prompt import (
|
|
11
|
+
ACTIVE_AGENT_PROMPTS,
|
|
11
12
|
compute_feature_slug,
|
|
12
13
|
find_feature,
|
|
13
14
|
format_acceptance_criteria,
|
|
@@ -21,6 +22,7 @@ from generate_bootstrap_prompt import (
|
|
|
21
22
|
build_replacements,
|
|
22
23
|
generate_checkpoint_definition,
|
|
23
24
|
merge_checkpoint_state,
|
|
25
|
+
load_active_agent_prompts,
|
|
24
26
|
load_log_size_section,
|
|
25
27
|
main as generate_bootstrap_main,
|
|
26
28
|
)
|
|
@@ -809,6 +811,50 @@ class TestHeadlessRecoveryReframing:
|
|
|
809
811
|
assert "run `/compact` after" not in rendered
|
|
810
812
|
|
|
811
813
|
|
|
814
|
+
class TestDirectOrchestratorImplementationPrompts:
|
|
815
|
+
def test_active_agent_prompt_loader_excludes_retired_dev_implement_prompt(self):
|
|
816
|
+
templates_dir = Path("dev-pipeline/templates").resolve()
|
|
817
|
+
|
|
818
|
+
replacements = load_active_agent_prompts(str(templates_dir))
|
|
819
|
+
|
|
820
|
+
assert "dev-implement.md" not in ACTIVE_AGENT_PROMPTS
|
|
821
|
+
assert "{{AGENT_PROMPT_DEV_IMPLEMENT}}" not in replacements
|
|
822
|
+
assert "{{AGENT_PROMPT_CRITIC_PLAN_CHALLENGE}}" in replacements
|
|
823
|
+
assert (templates_dir / "agent-prompts" / "dev-implement.md").exists() is False
|
|
824
|
+
|
|
825
|
+
def test_sections_all_modes_use_direct_orchestrator_implementation(self):
|
|
826
|
+
sections_dir = Path("dev-pipeline/templates/sections").resolve()
|
|
827
|
+
|
|
828
|
+
for mode in ("lite", "standard", "full"):
|
|
829
|
+
rendered = "\n".join(
|
|
830
|
+
section for _name, section in assemble_sections(
|
|
831
|
+
mode, str(sections_dir), init_done=True, is_resume=False,
|
|
832
|
+
critic_enabled=(mode == "full"), browser_enabled=False,
|
|
833
|
+
)
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
assert "/prizmkit-implement" in rendered
|
|
837
|
+
assert "orchestrator) execute" in rendered or "Run `/prizmkit-implement`" in rendered
|
|
838
|
+
assert "Spawn Dev subagent" not in rendered
|
|
839
|
+
assert "Spawn Dev agent" not in rendered
|
|
840
|
+
assert "Implement — Dev" not in rendered
|
|
841
|
+
assert "{{AGENT_PROMPT_DEV_IMPLEMENT}}" not in rendered
|
|
842
|
+
assert "dev-implement.md" not in rendered
|
|
843
|
+
assert "Reviewer Agent → filter → Dev Agent fix" in rendered
|
|
844
|
+
|
|
845
|
+
def test_legacy_tier_templates_use_direct_orchestrator_implementation(self):
|
|
846
|
+
for template_name in ("bootstrap-tier2.md", "bootstrap-tier3.md"):
|
|
847
|
+
rendered = (Path("dev-pipeline/templates") / template_name).read_text(encoding="utf-8")
|
|
848
|
+
|
|
849
|
+
assert "/prizmkit-implement" in rendered
|
|
850
|
+
assert "Implement — Orchestrator Direct" in rendered
|
|
851
|
+
assert "Spawn Dev subagent" not in rendered
|
|
852
|
+
assert "Spawn Dev agent" not in rendered
|
|
853
|
+
assert "Implement — Dev" not in rendered
|
|
854
|
+
assert "{{AGENT_PROMPT_DEV_IMPLEMENT}}" not in rendered
|
|
855
|
+
assert "dev-implement.md" not in rendered
|
|
856
|
+
|
|
857
|
+
|
|
812
858
|
# ---------------------------------------------------------------------------
|
|
813
859
|
# continuation handoff
|
|
814
860
|
# ---------------------------------------------------------------------------
|
|
@@ -103,7 +103,7 @@ def test_python_parity_suite_declares_coverage_for_all_legacy_unix_snapshot_scen
|
|
|
103
103
|
"envResolution": {
|
|
104
104
|
"verboseAndStreamJson": ["test_python_env_resolution_scenarios_match_snapshot_contracts"],
|
|
105
105
|
"effortModelAndCli": ["test_ai_session_command_builders_match_unix_runtime_snapshot"],
|
|
106
|
-
"worktreeIgnored": ["
|
|
106
|
+
"worktreeIgnored": ["test_runner_environment_disables_worktree_by_default_and_accepts_truthy_opt_in"],
|
|
107
107
|
"autoPushFlag": ["test_successful_worktree_session_merges_cleans_branch_and_honors_auto_push"],
|
|
108
108
|
},
|
|
109
109
|
"aiCliCommands": {
|
|
@@ -320,13 +320,14 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
320
320
|
assert env.strict_behavior_check is False
|
|
321
321
|
|
|
322
322
|
|
|
323
|
-
def
|
|
323
|
+
def test_runner_environment_disables_worktree_by_default_and_accepts_truthy_opt_in():
|
|
324
324
|
from prizmkit_runtime.runner_models import RunnerEnvironment
|
|
325
325
|
|
|
326
|
-
assert RunnerEnvironment.from_env({}).use_worktree is
|
|
327
|
-
assert RunnerEnvironment.from_env({"USE_WORKTREE": "
|
|
328
|
-
|
|
329
|
-
|
|
326
|
+
assert RunnerEnvironment.from_env({}).use_worktree is False
|
|
327
|
+
assert RunnerEnvironment.from_env({"USE_WORKTREE": "unexpected"}).use_worktree is False
|
|
328
|
+
|
|
329
|
+
for truthy in ("1", "true", "yes", "on"):
|
|
330
|
+
assert RunnerEnvironment.from_env({"USE_WORKTREE": truthy}).use_worktree is True
|
|
330
331
|
|
|
331
332
|
for falsey in ("0", "false", "no", "off"):
|
|
332
333
|
assert RunnerEnvironment.from_env({"USE_WORKTREE": falsey}).use_worktree is False
|
|
@@ -639,10 +640,11 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
639
640
|
|
|
640
641
|
|
|
641
642
|
@pytest.mark.parametrize(("kind", "item_id", "branch"), [("feature", "F-001", "dev/F-001-test"), ("bugfix", "B-001", "bugfix/B-001-test")])
|
|
642
|
-
def
|
|
643
|
+
def test_feature_and_bugfix_explicit_worktree_launches_from_worktree_and_renders_worktree_root(monkeypatch, tmp_path, kind, item_id, branch):
|
|
643
644
|
from prizmkit_runtime import runners
|
|
644
645
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
645
646
|
|
|
647
|
+
monkeypatch.setenv("USE_WORKTREE", "1")
|
|
646
648
|
monkeypatch.setenv("DEV_BRANCH", branch)
|
|
647
649
|
paths = _make_paths(tmp_path / kind)
|
|
648
650
|
family = family_for(kind, paths)
|
|
@@ -672,6 +674,7 @@ def test_successful_worktree_session_merges_cleans_branch_and_honors_auto_push(m
|
|
|
672
674
|
from prizmkit_runtime import runners
|
|
673
675
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
674
676
|
|
|
677
|
+
monkeypatch.setenv("USE_WORKTREE", "1")
|
|
675
678
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
|
|
676
679
|
monkeypatch.setenv("AUTO_PUSH", "1")
|
|
677
680
|
paths = _make_paths(tmp_path)
|
|
@@ -692,6 +695,7 @@ def test_worktree_merge_conflict_writes_session_status_and_updates_merge_conflic
|
|
|
692
695
|
from prizmkit_runtime import runners
|
|
693
696
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
694
697
|
|
|
698
|
+
monkeypatch.setenv("USE_WORKTREE", "1")
|
|
695
699
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
|
|
696
700
|
paths = _make_paths(tmp_path)
|
|
697
701
|
family = family_for("feature", paths)
|
|
@@ -719,6 +723,7 @@ def test_terminal_worktree_failure_saves_wip_and_removes_worktree_without_deleti
|
|
|
719
723
|
from prizmkit_runtime import runners
|
|
720
724
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
721
725
|
|
|
726
|
+
monkeypatch.setenv("USE_WORKTREE", "1")
|
|
722
727
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
|
|
723
728
|
paths = _make_paths(tmp_path)
|
|
724
729
|
family = family_for("feature", paths)
|
|
@@ -735,7 +740,7 @@ def test_terminal_worktree_failure_saves_wip_and_removes_worktree_without_deleti
|
|
|
735
740
|
assert observed.updates[-1][0] == "crashed"
|
|
736
741
|
|
|
737
742
|
|
|
738
|
-
def
|
|
743
|
+
def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and_launch(monkeypatch, tmp_path):
|
|
739
744
|
from prizmkit_runtime import runners
|
|
740
745
|
from prizmkit_runtime.paths import resolve_runtime_paths
|
|
741
746
|
from prizmkit_runtime.runner_models import PromptGenerationResult, SessionClassification, family_for, parse_invocation
|
|
@@ -766,6 +771,7 @@ def test_default_worktree_materializes_ignored_support_assets_before_prompt_and_
|
|
|
766
771
|
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
767
772
|
launch_cwds = []
|
|
768
773
|
|
|
774
|
+
monkeypatch.setenv("USE_WORKTREE", "1")
|
|
769
775
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-support-assets")
|
|
770
776
|
monkeypatch.setattr(
|
|
771
777
|
runners,
|
|
@@ -830,11 +836,10 @@ def test_default_worktree_materializes_ignored_support_assets_before_prompt_and_
|
|
|
830
836
|
assert not launch_cwds[0].exists()
|
|
831
837
|
|
|
832
838
|
|
|
833
|
-
def
|
|
839
|
+
def test_default_no_worktree_preserves_main_worktree_branch_launch(monkeypatch, tmp_path):
|
|
834
840
|
from prizmkit_runtime import runners
|
|
835
841
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
836
842
|
|
|
837
|
-
monkeypatch.setenv("USE_WORKTREE", "0")
|
|
838
843
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
|
|
839
844
|
paths = _make_paths(tmp_path)
|
|
840
845
|
family = family_for("feature", paths)
|
|
@@ -859,10 +864,103 @@ def test_use_worktree_false_preserves_main_worktree_branch_launch(monkeypatch, t
|
|
|
859
864
|
assert "branch_merge" in plan_names
|
|
860
865
|
|
|
861
866
|
|
|
867
|
+
def test_no_worktree_runner_ignores_recovery_side_effect_branch(monkeypatch, tmp_path):
|
|
868
|
+
from prizmkit_runtime import runners
|
|
869
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
870
|
+
|
|
871
|
+
monkeypatch.delenv("DEV_BRANCH", raising=False)
|
|
872
|
+
paths = _make_paths(tmp_path)
|
|
873
|
+
family = family_for("feature", paths)
|
|
874
|
+
invocation = parse_invocation(family, "run", ())
|
|
875
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
876
|
+
observed = _install_runner_session_fakes(monkeypatch, runners)
|
|
877
|
+
branch_names = []
|
|
878
|
+
|
|
879
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-recovery-20260707")
|
|
880
|
+
monkeypatch.setattr(runners, "current_branch", lambda _root: "dev/F-001-recovery-20260707")
|
|
881
|
+
|
|
882
|
+
def fake_run_git_plan(project_root, plan):
|
|
883
|
+
if plan.name == "branch_create":
|
|
884
|
+
branch_names.append(plan.commands[1].args[2])
|
|
885
|
+
return SimpleNamespace(ok=True, plan=plan, results=())
|
|
886
|
+
|
|
887
|
+
monkeypatch.setattr(runners, "run_git_plan", fake_run_git_plan)
|
|
888
|
+
|
|
889
|
+
status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
890
|
+
|
|
891
|
+
assert status == "completed"
|
|
892
|
+
assert branch_names and branch_names[0].startswith("dev/F-001-")
|
|
893
|
+
assert "recovery" not in branch_names[0]
|
|
894
|
+
assert observed.updates[-1][2]["active_dev_branch"] == branch_names[0]
|
|
895
|
+
assert observed.updates[-1][2]["base_branch"] == "main"
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_path, capsys):
|
|
899
|
+
from prizmkit_runtime import runners
|
|
900
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
901
|
+
|
|
902
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-output")
|
|
903
|
+
paths = _make_paths(tmp_path)
|
|
904
|
+
family = family_for("feature", paths)
|
|
905
|
+
invocation = parse_invocation(family, "run", ())
|
|
906
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
907
|
+
_install_runner_session_fakes(monkeypatch, runners)
|
|
908
|
+
monkeypatch.setattr(runners, "run_git_plan", lambda project_root, plan: SimpleNamespace(ok=True, plan=plan, results=()))
|
|
909
|
+
|
|
910
|
+
status = runners._process_item(
|
|
911
|
+
family,
|
|
912
|
+
invocation,
|
|
913
|
+
"F-001",
|
|
914
|
+
paths,
|
|
915
|
+
initial_metadata={"title": "Readable output", "retry_count": 1, "infra_error_count": 2},
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
captured = capsys.readouterr()
|
|
919
|
+
assert status == "completed"
|
|
920
|
+
assert "────────────────────────────────────────────────────" in captured.err
|
|
921
|
+
assert "Feature: F-001 — Readable output" in captured.err
|
|
922
|
+
assert "Code retry: 1 / 3" in captured.err
|
|
923
|
+
assert "Infrastructure retry: 2 / 3" in captured.err
|
|
924
|
+
assert "Pipeline mode: lite (Tier 1 — Single Agent)" in captured.err
|
|
925
|
+
assert "Agents: 1 (critic: disabled)" in captured.err
|
|
926
|
+
assert "Spawning AI CLI session: F-001-session-1" in captured.err
|
|
927
|
+
assert "Session result: success" in captured.err
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_path):
|
|
931
|
+
from prizmkit_runtime import runners
|
|
932
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
933
|
+
|
|
934
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-interrupt")
|
|
935
|
+
paths = _make_paths(tmp_path)
|
|
936
|
+
family = family_for("feature", paths)
|
|
937
|
+
invocation = parse_invocation(family, "run", ())
|
|
938
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
939
|
+
_install_runner_session_fakes(monkeypatch, runners)
|
|
940
|
+
ensure_calls = []
|
|
941
|
+
monkeypatch.setattr(runners, "run_git_plan", lambda project_root, plan: SimpleNamespace(ok=True, plan=plan, results=()))
|
|
942
|
+
monkeypatch.setattr(runners, "branch_ensure_return", lambda project_root, base, branch: ensure_calls.append((base, branch)))
|
|
943
|
+
|
|
944
|
+
class InterruptingLauncher:
|
|
945
|
+
def __init__(self, config):
|
|
946
|
+
self.config = config
|
|
947
|
+
|
|
948
|
+
def run(self):
|
|
949
|
+
raise KeyboardInterrupt
|
|
950
|
+
|
|
951
|
+
monkeypatch.setattr(runners, "AISessionLauncher", InterruptingLauncher)
|
|
952
|
+
|
|
953
|
+
with pytest.raises(KeyboardInterrupt):
|
|
954
|
+
runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
955
|
+
|
|
956
|
+
assert ensure_calls == [("main", "dev/F-001-interrupt")]
|
|
957
|
+
|
|
958
|
+
|
|
862
959
|
def test_context_overflow_reuses_worktree_and_skips_cleanup_until_terminal_outcome(monkeypatch, tmp_path):
|
|
863
960
|
from prizmkit_runtime import runners
|
|
864
961
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
865
962
|
|
|
963
|
+
monkeypatch.setenv("USE_WORKTREE", "1")
|
|
866
964
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
|
|
867
965
|
paths = _make_paths(tmp_path)
|
|
868
966
|
family = family_for("feature", paths)
|
|
@@ -958,6 +1056,14 @@ def test_classification_stalls_after_repeated_no_progress_context_overflow(tmp_p
|
|
|
958
1056
|
assert classification.no_progress_count == 2
|
|
959
1057
|
|
|
960
1058
|
|
|
1059
|
+
def test_recovery_prompt_keeps_existing_branch_instead_of_recovery_suffix():
|
|
1060
|
+
text = (PIPELINE_ROOT / "scripts" / "generate-recovery-prompt.py").read_text(encoding="utf-8")
|
|
1061
|
+
|
|
1062
|
+
assert "fix/{bug_id}-recovery" not in text
|
|
1063
|
+
assert "do not create an additional" in text
|
|
1064
|
+
assert "last-resort fallback" in text
|
|
1065
|
+
|
|
1066
|
+
|
|
961
1067
|
def test_recovery_detector_resolver_prefers_installed_platform_asset(tmp_path):
|
|
962
1068
|
from prizmkit_runtime.paths import resolve_runtime_paths
|
|
963
1069
|
from prizmkit_runtime.runner_recovery import RecoveryDetectorResolver
|
|
@@ -1117,6 +1223,7 @@ def test_python_daemon_controlled_start_status_stop_matches_snapshot(tmp_path):
|
|
|
1117
1223
|
"#!/usr/bin/env python3\n"
|
|
1118
1224
|
"import os, sys, time\n"
|
|
1119
1225
|
"print('payload env=' + os.environ.get('FOO', ''))\n"
|
|
1226
|
+
"print('payload live=' + os.environ.get('PRIZMKIT_LIVE_OUTPUT', ''))\n"
|
|
1120
1227
|
"print('payload args=' + ' '.join(sys.argv[1:]))\n"
|
|
1121
1228
|
"sys.stdout.flush()\n"
|
|
1122
1229
|
"time.sleep(30)\n",
|
|
@@ -1147,6 +1254,7 @@ def test_python_daemon_controlled_start_status_stop_matches_snapshot(tmp_path):
|
|
|
1147
1254
|
assert json.loads(stop.render())["message"] in {"stopped", "already exited", "not running"}
|
|
1148
1255
|
log_text = (paths.feature_state_dir / "pipeline-daemon.log").read_text(encoding="utf-8")
|
|
1149
1256
|
assert "FOO=bar" in log_text or "payload env=bar" in log_text
|
|
1257
|
+
assert "payload live=0" in log_text
|
|
1150
1258
|
assert "--project-root" in log_text
|
|
1151
1259
|
|
|
1152
1260
|
|
|
@@ -617,6 +617,82 @@ def test_session_dual_writes_progress_and_recovers_backup(tmp_path):
|
|
|
617
617
|
assert second_log.read_text(encoding="utf-8") == "larger recovered log"
|
|
618
618
|
|
|
619
619
|
|
|
620
|
+
def test_session_live_output_prints_paths_and_verbose_cli_output(tmp_path, capsys):
|
|
621
|
+
from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher
|
|
622
|
+
|
|
623
|
+
fake_cli = tmp_path / "live_cli.py"
|
|
624
|
+
fake_cli.write_text(
|
|
625
|
+
"#!/usr/bin/env python3\n"
|
|
626
|
+
"print('live-line', flush=True)\n",
|
|
627
|
+
encoding="utf-8",
|
|
628
|
+
)
|
|
629
|
+
fake_cli.chmod(0o755)
|
|
630
|
+
prompt = tmp_path / "prompt.md"
|
|
631
|
+
prompt.write_text("hello", encoding="utf-8")
|
|
632
|
+
session_log = tmp_path / "state" / "sessions" / "sess-live" / "logs" / "session.log"
|
|
633
|
+
progress = session_log.with_name("progress.json")
|
|
634
|
+
heartbeat = session_log.with_name("heartbeat.json")
|
|
635
|
+
|
|
636
|
+
result = AISessionLauncher(
|
|
637
|
+
AISessionConfig(
|
|
638
|
+
cli_command=str(fake_cli),
|
|
639
|
+
platform="codebuddy",
|
|
640
|
+
model=None,
|
|
641
|
+
prompt_path=prompt,
|
|
642
|
+
cwd=tmp_path,
|
|
643
|
+
log_path=session_log,
|
|
644
|
+
progress_path=progress,
|
|
645
|
+
heartbeat_path=heartbeat,
|
|
646
|
+
verbose=True,
|
|
647
|
+
live_output=True,
|
|
648
|
+
)
|
|
649
|
+
).run()
|
|
650
|
+
|
|
651
|
+
captured = capsys.readouterr()
|
|
652
|
+
assert result.exit_code == 0
|
|
653
|
+
assert "live-line" in captured.out
|
|
654
|
+
assert "[prizmkit] AI session started" in captured.err
|
|
655
|
+
assert f"[prizmkit] Prompt: {prompt}" in captured.err
|
|
656
|
+
assert f"[prizmkit] Session log: {session_log}" in captured.err
|
|
657
|
+
assert f"[prizmkit] Progress: {progress}" in captured.err
|
|
658
|
+
assert f"[prizmkit] Heartbeat: {heartbeat}" in captured.err
|
|
659
|
+
assert "[prizmkit] AI session finished" in captured.err
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def test_session_live_output_does_not_forward_cli_output_without_verbose(tmp_path, capsys):
|
|
663
|
+
from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher
|
|
664
|
+
|
|
665
|
+
fake_cli = tmp_path / "quiet_cli.py"
|
|
666
|
+
fake_cli.write_text(
|
|
667
|
+
"#!/usr/bin/env python3\n"
|
|
668
|
+
"print('hidden-line', flush=True)\n",
|
|
669
|
+
encoding="utf-8",
|
|
670
|
+
)
|
|
671
|
+
fake_cli.chmod(0o755)
|
|
672
|
+
prompt = tmp_path / "prompt.md"
|
|
673
|
+
prompt.write_text("hello", encoding="utf-8")
|
|
674
|
+
session_log = tmp_path / "state" / "sessions" / "sess-quiet" / "logs" / "session.log"
|
|
675
|
+
|
|
676
|
+
result = AISessionLauncher(
|
|
677
|
+
AISessionConfig(
|
|
678
|
+
cli_command=str(fake_cli),
|
|
679
|
+
platform="codebuddy",
|
|
680
|
+
model=None,
|
|
681
|
+
prompt_path=prompt,
|
|
682
|
+
cwd=tmp_path,
|
|
683
|
+
log_path=session_log,
|
|
684
|
+
live_output=True,
|
|
685
|
+
)
|
|
686
|
+
).run()
|
|
687
|
+
|
|
688
|
+
captured = capsys.readouterr()
|
|
689
|
+
assert result.exit_code == 0
|
|
690
|
+
assert "hidden-line" not in captured.out
|
|
691
|
+
assert "hidden-line" in session_log.read_text(encoding="utf-8")
|
|
692
|
+
assert "[prizmkit] AI session started" in captured.err
|
|
693
|
+
assert "[prizmkit] AI session finished" in captured.err
|
|
694
|
+
|
|
695
|
+
|
|
620
696
|
def test_session_final_progress_merge_preserves_tracker_fatal_error(tmp_path):
|
|
621
697
|
from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher
|
|
622
698
|
|