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
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
import queue
|
|
9
9
|
import shutil
|
|
10
10
|
import subprocess
|
|
11
|
+
import sys
|
|
11
12
|
import threading
|
|
12
13
|
import time
|
|
13
14
|
from collections import Counter
|
|
@@ -45,12 +46,16 @@ class AISessionConfig:
|
|
|
45
46
|
heartbeat_path: Path | None = None
|
|
46
47
|
effort: str | None = None
|
|
47
48
|
verbose: bool = False
|
|
49
|
+
live_output: bool = False
|
|
48
50
|
use_stream_json: bool = False
|
|
49
51
|
stream_json_format: str = ""
|
|
50
52
|
permission_mode: str = "dangerously-skip-permissions"
|
|
51
53
|
total_timeout_seconds: float | None = None
|
|
52
54
|
stale_kill_threshold_seconds: float = 0
|
|
53
55
|
stale_kill_grace_seconds: float = 10
|
|
56
|
+
live_progress_interval_seconds: float = 30
|
|
57
|
+
live_banner: bool = True
|
|
58
|
+
suppress_stream_output: bool = False
|
|
54
59
|
codex_subagent_timeout_seconds: int = 3300
|
|
55
60
|
extra_args: tuple[str, ...] = ()
|
|
56
61
|
environment_overrides: Mapping[str, str] = field(default_factory=dict)
|
|
@@ -363,12 +368,109 @@ def _write_progress_summary(progress_path: Path | None, summary: ProgressSummary
|
|
|
363
368
|
progress_path.write_text(json.dumps(summary.to_dict(), indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
364
369
|
|
|
365
370
|
|
|
371
|
+
def _emit_live_line(message: str) -> None:
|
|
372
|
+
"""Print foreground runner status without interfering with file logging."""
|
|
373
|
+
print(message, file=sys.stderr, flush=True)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _emit_live_output(line: str) -> None:
|
|
377
|
+
"""Forward AI CLI output for verbose foreground runs."""
|
|
378
|
+
sys.stdout.write(line)
|
|
379
|
+
sys.stdout.flush()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _format_bytes(size: int) -> str:
|
|
383
|
+
if size >= 1024 * 1024:
|
|
384
|
+
return f"{round(size / (1024 * 1024))}MB"
|
|
385
|
+
if size >= 1024:
|
|
386
|
+
return f"{round(size / 1024)}KB"
|
|
387
|
+
return f"{size}B"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _format_elapsed(seconds: float) -> str:
|
|
391
|
+
total = max(0, int(seconds))
|
|
392
|
+
minutes, remainder = divmod(total, 60)
|
|
393
|
+
return f"{minutes}m{remainder:02d}s"
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _read_json_object(path: Path | None) -> dict[str, object]:
|
|
397
|
+
if path is None or not path.is_file():
|
|
398
|
+
return {}
|
|
399
|
+
try:
|
|
400
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
401
|
+
except (OSError, json.JSONDecodeError):
|
|
402
|
+
return {}
|
|
403
|
+
return data if isinstance(data, dict) else {}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _compact_progress_line(started_epoch: float, session_log: Path, progress_path: Path | None) -> str:
|
|
407
|
+
data = _read_json_object(progress_path)
|
|
408
|
+
try:
|
|
409
|
+
log_size = session_log.stat().st_size
|
|
410
|
+
except OSError:
|
|
411
|
+
log_size = 0
|
|
412
|
+
parts = [
|
|
413
|
+
f" ▶ [HEARTBEAT] {_format_elapsed(time.time() - started_epoch)}",
|
|
414
|
+
f"log: {_format_bytes(log_size)}",
|
|
415
|
+
]
|
|
416
|
+
child_bytes = int(data.get("child_total_bytes") or 0)
|
|
417
|
+
subagents = int(data.get("subagent_spawn_count") or 0)
|
|
418
|
+
if child_bytes or subagents:
|
|
419
|
+
child = _format_bytes(child_bytes)
|
|
420
|
+
if subagents:
|
|
421
|
+
child = f"{child}/{subagents}"
|
|
422
|
+
parts.append(f"child: {child}")
|
|
423
|
+
phase = str(data.get("current_phase") or "").strip()
|
|
424
|
+
if phase:
|
|
425
|
+
parts.append(f"phase: {phase}")
|
|
426
|
+
tool = str(data.get("current_tool") or "").strip()
|
|
427
|
+
if tool:
|
|
428
|
+
parts.append(f"tool: {tool}")
|
|
429
|
+
messages = int(data.get("message_count") or 0)
|
|
430
|
+
if messages:
|
|
431
|
+
parts.append(f"msgs: {messages}")
|
|
432
|
+
tool_calls = int(data.get("total_tool_calls") or 0)
|
|
433
|
+
if tool_calls:
|
|
434
|
+
parts.append(f"{tool_calls} tool calls")
|
|
435
|
+
return " | ".join(parts)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _emit_progress_tick(started_epoch: float, session_log: Path, progress_path: Path | None, last_line: str) -> str:
|
|
439
|
+
line = _compact_progress_line(started_epoch, session_log, progress_path)
|
|
440
|
+
if line != last_line:
|
|
441
|
+
_emit_live_line(line)
|
|
442
|
+
return line
|
|
443
|
+
return last_line
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _emit_session_start(config: AISessionConfig, command: AISessionCommand, session_log: Path, backup_log: Path) -> None:
|
|
447
|
+
_emit_live_line("[prizmkit] AI session started")
|
|
448
|
+
_emit_live_line(f"[prizmkit] Provider: {command.provider}")
|
|
449
|
+
_emit_live_line(f"[prizmkit] Prompt: {config.prompt_path}")
|
|
450
|
+
_emit_live_line(f"[prizmkit] Session log: {session_log}")
|
|
451
|
+
_emit_live_line(f"[prizmkit] Backup log: {backup_log}")
|
|
452
|
+
if config.progress_path is not None:
|
|
453
|
+
_emit_live_line(f"[prizmkit] Progress: {config.progress_path}")
|
|
454
|
+
if config.heartbeat_path is not None:
|
|
455
|
+
_emit_live_line(f"[prizmkit] Heartbeat: {config.heartbeat_path}")
|
|
456
|
+
if config.verbose:
|
|
457
|
+
_emit_live_line("[prizmkit] VERBOSE=1: compact live progress enabled")
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _emit_session_finish(result: AISessionResult) -> None:
|
|
461
|
+
duration = max(0.0, result.ended_epoch - result.started_epoch)
|
|
462
|
+
status = result.termination_reason or f"exit_code={result.exit_code}"
|
|
463
|
+
_emit_live_line(f"[prizmkit] AI session finished ({status}, {duration:.1f}s)")
|
|
464
|
+
_emit_live_line(f"[prizmkit] Session log: {result.session_log}")
|
|
465
|
+
|
|
466
|
+
|
|
366
467
|
def _reader_thread(
|
|
367
468
|
pipe,
|
|
368
469
|
session_log: Path,
|
|
369
470
|
backup_log: Path,
|
|
370
471
|
output_queue: "queue.Queue[str]",
|
|
371
472
|
progress_path: Path | None = None,
|
|
473
|
+
live_output: bool = False,
|
|
372
474
|
) -> None:
|
|
373
475
|
lines: list[str] = []
|
|
374
476
|
tracker = None
|
|
@@ -387,6 +489,8 @@ def _reader_thread(
|
|
|
387
489
|
with backup_log.open("a", encoding="utf-8") as backup_fh:
|
|
388
490
|
backup_fh.write(line)
|
|
389
491
|
output_queue.put(line)
|
|
492
|
+
if live_output:
|
|
493
|
+
_emit_live_output(line)
|
|
390
494
|
lines.append(line)
|
|
391
495
|
if progress_path is not None:
|
|
392
496
|
if tracker is not None:
|
|
@@ -472,12 +576,22 @@ class AISessionLauncher:
|
|
|
472
576
|
environment_unsets=("CLAUDECODE",),
|
|
473
577
|
stdin_text=command.stdin_text,
|
|
474
578
|
)
|
|
579
|
+
if self.config.live_output and self.config.live_banner:
|
|
580
|
+
_emit_session_start(self.config, command, session_log, backup_log)
|
|
581
|
+
|
|
475
582
|
handle = launch_process(spec, stderr=subprocess.STDOUT)
|
|
476
583
|
assert handle.popen is not None
|
|
477
584
|
output_queue: queue.Queue[str] = queue.Queue()
|
|
478
585
|
reader = threading.Thread(
|
|
479
586
|
target=_reader_thread,
|
|
480
|
-
args=(
|
|
587
|
+
args=(
|
|
588
|
+
handle.popen.stdout,
|
|
589
|
+
session_log,
|
|
590
|
+
backup_log,
|
|
591
|
+
output_queue,
|
|
592
|
+
self.config.progress_path,
|
|
593
|
+
self.config.live_output and self.config.verbose and not self.config.suppress_stream_output,
|
|
594
|
+
),
|
|
481
595
|
daemon=True,
|
|
482
596
|
)
|
|
483
597
|
reader.start()
|
|
@@ -510,12 +624,34 @@ class AISessionLauncher:
|
|
|
510
624
|
timed_out = False
|
|
511
625
|
termination_reason = None
|
|
512
626
|
stale_marker = None
|
|
627
|
+
last_progress_line = ""
|
|
628
|
+
last_progress_emit = 0.0
|
|
513
629
|
try:
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
630
|
+
while True:
|
|
631
|
+
timeout = 0.2
|
|
632
|
+
if self.config.total_timeout_seconds is not None:
|
|
633
|
+
remaining = self.config.total_timeout_seconds - (time.time() - started)
|
|
634
|
+
if remaining <= 0:
|
|
635
|
+
timed_out = True
|
|
636
|
+
termination_reason = "timeout"
|
|
637
|
+
terminate_process_tree(handle, "timeout", grace_seconds=self.config.stale_kill_grace_seconds)
|
|
638
|
+
break
|
|
639
|
+
timeout = min(timeout, remaining)
|
|
640
|
+
try:
|
|
641
|
+
handle.popen.wait(timeout=timeout)
|
|
642
|
+
break
|
|
643
|
+
except subprocess.TimeoutExpired:
|
|
644
|
+
if self.config.live_output and (not self.config.verbose or self.config.suppress_stream_output):
|
|
645
|
+
interval = max(1.0, float(self.config.live_progress_interval_seconds or 30))
|
|
646
|
+
now = time.time()
|
|
647
|
+
if last_progress_emit == 0.0 or now - last_progress_emit >= interval:
|
|
648
|
+
last_progress_line = _emit_progress_tick(started, session_log, self.config.progress_path, last_progress_line)
|
|
649
|
+
last_progress_emit = now
|
|
650
|
+
except KeyboardInterrupt:
|
|
651
|
+
terminate_process_tree(handle, "interrupted", grace_seconds=self.config.stale_kill_grace_seconds)
|
|
652
|
+
raise
|
|
653
|
+
if self.config.live_output and (not self.config.verbose or self.config.suppress_stream_output):
|
|
654
|
+
_emit_progress_tick(started, session_log, self.config.progress_path, last_progress_line)
|
|
519
655
|
reader.join(timeout=5)
|
|
520
656
|
heartbeat_result = heartbeat.stop() if heartbeat is not None else None
|
|
521
657
|
if heartbeat_result and heartbeat_result.killed:
|
|
@@ -544,7 +680,7 @@ class AISessionLauncher:
|
|
|
544
680
|
self.config.progress_path.write_text(json.dumps(merged_progress, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
545
681
|
final_summary = _summary_from_progress_dict(merged_progress, summary)
|
|
546
682
|
ended = time.time()
|
|
547
|
-
|
|
683
|
+
result = AISessionResult(
|
|
548
684
|
command=command,
|
|
549
685
|
exit_code=handle.popen.returncode,
|
|
550
686
|
timed_out=timed_out,
|
|
@@ -558,3 +694,6 @@ class AISessionLauncher:
|
|
|
558
694
|
started_epoch=started,
|
|
559
695
|
ended_epoch=ended,
|
|
560
696
|
)
|
|
697
|
+
if self.config.live_output and self.config.live_banner:
|
|
698
|
+
_emit_session_finish(result)
|
|
699
|
+
return result
|
|
@@ -1296,32 +1296,36 @@ def load_section(sections_dir, name):
|
|
|
1296
1296
|
return f.read()
|
|
1297
1297
|
|
|
1298
1298
|
|
|
1299
|
-
|
|
1300
|
-
"""
|
|
1299
|
+
ACTIVE_AGENT_PROMPTS = {
|
|
1300
|
+
"critic-plan-challenge.md": "{{AGENT_PROMPT_CRITIC_PLAN_CHALLENGE}}",
|
|
1301
|
+
"reviewer-review.md": "{{AGENT_PROMPT_REVIEWER_REVIEW}}",
|
|
1302
|
+
"dev-fix.md": "{{AGENT_PROMPT_DEV_FIX}}",
|
|
1303
|
+
"dev-resume.md": "{{AGENT_PROMPT_DEV_RESUME}}",
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1301
1306
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1307
|
+
def load_active_agent_prompts(templates_dir):
|
|
1308
|
+
"""Load explicit active agent prompt templates.
|
|
1309
|
+
|
|
1310
|
+
The retired top-level Dev implementation prompt is intentionally not loaded;
|
|
1311
|
+
feature implementation is performed by the main orchestrator. Remaining
|
|
1312
|
+
prompt placeholders support Critic/Reviewer and review-fix helper flows.
|
|
1304
1313
|
"""
|
|
1305
1314
|
agent_prompts_dir = os.path.join(templates_dir, "agent-prompts")
|
|
1306
1315
|
if not os.path.isdir(agent_prompts_dir):
|
|
1307
1316
|
LOGGER.debug("No agent-prompts/ directory found, skipping")
|
|
1308
1317
|
return {}
|
|
1309
1318
|
|
|
1310
|
-
# Map filename -> placeholder name
|
|
1311
|
-
# e.g. dev-implement.md -> {{AGENT_PROMPT_DEV_IMPLEMENT}}
|
|
1312
1319
|
prompt_map = {}
|
|
1313
|
-
for filename in
|
|
1314
|
-
if not filename.endswith(".md"):
|
|
1315
|
-
continue
|
|
1316
|
-
stem = filename[:-3] # remove .md
|
|
1317
|
-
placeholder = "{{{{AGENT_PROMPT_{}}}}}".format(
|
|
1318
|
-
stem.upper().replace("-", "_")
|
|
1319
|
-
)
|
|
1320
|
+
for filename, placeholder in ACTIVE_AGENT_PROMPTS.items():
|
|
1320
1321
|
filepath = os.path.join(agent_prompts_dir, filename)
|
|
1322
|
+
if not os.path.isfile(filepath):
|
|
1323
|
+
LOGGER.debug("Active agent prompt not found, skipping: %s", filename)
|
|
1324
|
+
continue
|
|
1321
1325
|
try:
|
|
1322
1326
|
with open(filepath, "r", encoding="utf-8") as f:
|
|
1323
1327
|
prompt_map[placeholder] = f.read().strip()
|
|
1324
|
-
LOGGER.debug("Loaded agent prompt: %s -> %s", filename, placeholder)
|
|
1328
|
+
LOGGER.debug("Loaded active agent prompt: %s -> %s", filename, placeholder)
|
|
1325
1329
|
except IOError as exc:
|
|
1326
1330
|
LOGGER.warning("Failed to load agent prompt %s: %s", filename, exc)
|
|
1327
1331
|
|
|
@@ -1345,10 +1349,13 @@ def _tier_header(pipeline_mode):
|
|
|
1345
1349
|
"orchestrator implements directly (P0-6).\n",
|
|
1346
1350
|
),
|
|
1347
1351
|
"full": (
|
|
1348
|
-
"# Dev-Pipeline Session Bootstrap — Tier 3
|
|
1349
|
-
"
|
|
1350
|
-
"
|
|
1351
|
-
"
|
|
1352
|
+
"# Dev-Pipeline Session Bootstrap — Tier 3 "
|
|
1353
|
+
"(Orchestrator + Critic/Reviewer)\n",
|
|
1354
|
+
"**Tier 3 — Orchestrator + Critic/Reviewer**: For complex "
|
|
1355
|
+
"features, the main orchestrator handles context, planning, "
|
|
1356
|
+
"and implementation directly. Critic challenges the plan "
|
|
1357
|
+
"when enabled, Reviewer reviews, and no top-level Dev "
|
|
1358
|
+
"implementation subagent is spawned.\n",
|
|
1352
1359
|
),
|
|
1353
1360
|
}
|
|
1354
1361
|
return headers.get(pipeline_mode, headers["lite"])
|
|
@@ -1392,11 +1399,12 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1392
1399
|
]
|
|
1393
1400
|
else: # full
|
|
1394
1401
|
specific = [
|
|
1395
|
-
"- Tier 3: full
|
|
1396
|
-
"(
|
|
1402
|
+
"- Tier 3: full orchestration — orchestrator implements "
|
|
1403
|
+
"directly (no top-level Dev subagent), Critic challenges "
|
|
1404
|
+
"plan, Reviewer reviews",
|
|
1397
1405
|
"- context-snapshot.md is append-only: orchestrator writes "
|
|
1398
|
-
"Sections 1-4
|
|
1399
|
-
"
|
|
1406
|
+
"Sections 1-4 + Implementation Log, Reviewer appends "
|
|
1407
|
+
"Review Notes",
|
|
1400
1408
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1401
1409
|
"written before proceeding",
|
|
1402
1410
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
@@ -1638,10 +1646,10 @@ def render_from_sections(sections, replacements):
|
|
|
1638
1646
|
"""
|
|
1639
1647
|
content = "\n".join(text for _, text in sections)
|
|
1640
1648
|
|
|
1641
|
-
# Replace all placeholders — run twice to handle agent prompt
|
|
1642
|
-
# that contain their own {{PLACEHOLDER}} variables.
|
|
1643
|
-
#
|
|
1644
|
-
#
|
|
1649
|
+
# Replace all placeholders — run twice to handle active agent prompt
|
|
1650
|
+
# templates that contain their own {{PLACEHOLDER}} variables. First pass
|
|
1651
|
+
# injects prompt content (for example, Critic plan challenge text); second
|
|
1652
|
+
# pass replaces inner feature/session variables.
|
|
1645
1653
|
for _pass in range(2):
|
|
1646
1654
|
for placeholder, value in replacements.items():
|
|
1647
1655
|
content = content.replace(placeholder, value)
|
|
@@ -2077,8 +2085,9 @@ def main():
|
|
|
2077
2085
|
args, feature, features, global_context, script_dir
|
|
2078
2086
|
)
|
|
2079
2087
|
|
|
2080
|
-
# Load agent prompt templates and merge into replacements
|
|
2081
|
-
|
|
2088
|
+
# Load active agent prompt templates and merge into replacements.
|
|
2089
|
+
# The retired top-level Dev implementation prompt is intentionally absent.
|
|
2090
|
+
agent_prompt_replacements = load_active_agent_prompts(templates_dir)
|
|
2082
2091
|
replacements.update(agent_prompt_replacements)
|
|
2083
2092
|
|
|
2084
2093
|
if is_continuation(args):
|
|
@@ -42,10 +42,11 @@ BUGFIX_PHASES = {
|
|
|
42
42
|
"Branch Setup",
|
|
43
43
|
"""\
|
|
44
44
|
Check current branch. You should already be on a fix/* branch from the
|
|
45
|
-
interrupted session. If so, continue on it
|
|
46
|
-
a new fix branch
|
|
45
|
+
interrupted session. If so, continue on it and do not create an additional
|
|
46
|
+
recovery branch. If somehow on main, create a new fix branch only as a
|
|
47
|
+
last-resort fallback:
|
|
47
48
|
```bash
|
|
48
|
-
git checkout -b fix/{bug_id}
|
|
49
|
+
git checkout -b fix/{bug_id}
|
|
49
50
|
```""",
|
|
50
51
|
),
|
|
51
52
|
1: (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Dev-Pipeline Session Bootstrap — Tier 2 (
|
|
1
|
+
# Dev-Pipeline Session Bootstrap — Tier 2 (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).
|
|
14
14
|
|
|
15
|
-
**Tier 2 —
|
|
15
|
+
**Tier 2 — Orchestrator + Critic/Reviewer**: You handle context, planning, and implementation directly. Critic challenges the plan when enabled, Reviewer reviews. Do not spawn a top-level Dev subagent for implementation.
|
|
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,11 +71,11 @@ 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}}/plan.md ← includes Tasks section
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
**`context-snapshot.md`** is the shared knowledge base. Orchestrator writes Sections 1-4
|
|
78
|
+
**`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.
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
@@ -267,38 +267,63 @@ After all critics return, read all 3 reports:
|
|
|
267
267
|
**CP-2.5**: Plan challenges reviewed and resolved.
|
|
268
268
|
{{END_IF_CRITIC_ENABLED}}
|
|
269
269
|
|
|
270
|
-
###
|
|
270
|
+
### Implement — Orchestrator Direct (no Dev subagent)
|
|
271
271
|
|
|
272
|
-
**
|
|
272
|
+
**Rationale**: Development is deterministic plan execution — the orchestrator already built full context in Phase 1. 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.
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
Before starting, check plan.md Tasks:
|
|
275
|
+
```bash
|
|
276
|
+
grep -c '^\- \[ \]' .prizmkit/specs/{{FEATURE_SLUG}}/plan.md 2>/dev/null || true
|
|
277
|
+
```
|
|
278
|
+
- If result is `0` (all tasks already `[x]`) → **SKIP to Review**.
|
|
279
|
+
- If non-zero → implement directly below.
|
|
275
280
|
|
|
276
|
-
|
|
281
|
+
**Build artifacts rule**: After any build/compile command, ensure output is in `.gitignore`. Never commit binaries/build output.
|
|
277
282
|
|
|
278
|
-
|
|
279
|
-
> "Read {{DEV_SUBAGENT_PATH}}. Implement feature {{FEATURE_ID}} (slug: {{FEATURE_SLUG}}).
|
|
280
|
-
> **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.
|
|
281
|
-
> ⚠️ DO NOT re-read source files already listed in Section 4 File Manifest unless you need implementation detail beyond the interface summary.
|
|
282
|
-
> 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` for full context.
|
|
283
|
-
> 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}}`.
|
|
284
|
-
> 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.
|
|
285
|
-
> 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.
|
|
286
|
-
> 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.
|
|
287
|
-
> 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.
|
|
288
|
-
> 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.
|
|
289
|
-
> 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.
|
|
290
|
-
> 9. After implement completes, verify the '## Implementation Log' section (with Gate Evidence) was written to context-snapshot.md.
|
|
291
|
-
> 10. Do NOT execute any git commands (no git add/commit/reset/push).
|
|
292
|
-
> Do NOT exit until all tasks are [x] and the '## Implementation Log' section (with Gate Evidence) is written in context-snapshot.md."
|
|
293
|
-
|
|
294
|
-
Wait for Dev to return. All tasks must be `[x]`, tests pass.
|
|
295
|
-
|
|
296
|
-
**Gate Check — Implementation Log**:
|
|
297
|
-
After Dev agent returns, verify the Implementation Log was written:
|
|
283
|
+
**3a.** Detect test commands and record baseline:
|
|
298
284
|
```bash
|
|
299
|
-
|
|
285
|
+
($TEST_CMD) 2>&1 | tee /tmp/test-baseline.txt | tail -20
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**3b.** Run `/prizmkit-implement` directly — you (the orchestrator) execute the full cycle:
|
|
289
|
+
- Read plan.md Tasks from `.prizmkit/specs/{{FEATURE_SLUG}}/`
|
|
290
|
+
- Use context-snapshot.md Section 4 File Manifest for targeted reads — do NOT re-read files already summarized there
|
|
291
|
+
- Implements task-by-task, marking each `[x]` immediately
|
|
292
|
+
- Creates/updates L2 `.prizm` docs when creating new modules
|
|
293
|
+
- Defers scoped/full test execution until after the code-review loop completes
|
|
294
|
+
- If plan.md has >5 tasks: update checkpoints after every 3 tasks
|
|
295
|
+
|
|
296
|
+
**3c. Focused checks only (no test gate here)**:
|
|
297
|
+
- During implementation, do not run mandatory periodic or full-suite tests.
|
|
298
|
+
- 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`.
|
|
299
|
+
- The scoped feature test gate runs after code review; do not declare implementation success based on an early full-suite run.
|
|
300
|
+
|
|
301
|
+
**3d. Verification Gate Self-Check (BLOCKING — before declaring implement done)**:
|
|
302
|
+
For each Verification Gate from the Task Contract, write one evidence line to Implementation Log:
|
|
303
|
+
```
|
|
304
|
+
### Gate Evidence
|
|
305
|
+
- [x] G1: <gate text> — Evidence: <test name / file:line / fixture path>
|
|
306
|
+
- [ ] G3: <gate text> — BLOCKED: <reason> ← no evidence = BLOCKED, not success
|
|
300
307
|
```
|
|
301
|
-
|
|
308
|
+
|
|
309
|
+
**3e.** Append `## Implementation Log` (with Gate Evidence) to `context-snapshot.md`:
|
|
310
|
+
- files changed/created
|
|
311
|
+
- key decisions
|
|
312
|
+
- deviations from plan
|
|
313
|
+
- test results
|
|
314
|
+
- Gate Evidence section
|
|
315
|
+
- unresolved blockers
|
|
316
|
+
|
|
317
|
+
**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.
|
|
318
|
+
|
|
319
|
+
**Checkpoint update**:
|
|
320
|
+
```bash
|
|
321
|
+
python3 $PIPELINE_DIR/scripts/update-checkpoint.py \
|
|
322
|
+
--checkpoint-path .prizmkit/specs/{{FEATURE_SLUG}}/workflow-checkpoint.json \
|
|
323
|
+
--step prizmkit-implement \
|
|
324
|
+
--status completed
|
|
325
|
+
```
|
|
326
|
+
|
|
302
327
|
|
|
303
328
|
### Scoped Feature Test Gate — PrizmKit Test
|
|
304
329
|
|
|
@@ -584,7 +609,6 @@ Rules for writing completion notes:
|
|
|
584
609
|
|----------|------|
|
|
585
610
|
| Feature Artifacts Dir | `.prizmkit/specs/{{FEATURE_SLUG}}/` |
|
|
586
611
|
| Context Snapshot | `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` |
|
|
587
|
-
| Dev Agent Def | {{DEV_SUBAGENT_PATH}} |
|
|
588
612
|
| Reviewer Agent Def | {{REVIEWER_SUBAGENT_PATH}} |
|
|
589
613
|
{{IF_CRITIC_ENABLED}}
|
|
590
614
|
| Critic Agent Def | {{CRITIC_SUBAGENT_PATH}} |
|
|
@@ -610,11 +634,11 @@ If you encounter an unrecoverable error, context overflow, or are about to exit
|
|
|
610
634
|
|
|
611
635
|
## Reminders
|
|
612
636
|
|
|
613
|
-
- Tier 2: orchestrator builds context+plan
|
|
614
|
-
- Build context-snapshot.md FIRST;
|
|
615
|
-
- context-snapshot.md is append-only: orchestrator writes Sections 1-4
|
|
637
|
+
- Tier 2: orchestrator builds context+plan and implements directly; Critic challenges plan when enabled; Reviewer reviews
|
|
638
|
+
- Build context-snapshot.md FIRST; use it throughout instead of re-reading source files
|
|
639
|
+
- context-snapshot.md is append-only: orchestrator writes Sections 1-4 + Implementation Log, Reviewer appends Review Notes
|
|
616
640
|
- Gate checks enforce Implementation Log and Review Notes are written before proceeding
|
|
617
|
-
- Do NOT use `run_in_background=true` when spawning
|
|
641
|
+
- Do NOT use `run_in_background=true` when spawning Critic or Reviewer agents
|
|
618
642
|
- **NEVER delete, modify, or touch any file under `.prizmkit/state/`** — those are pipeline runtime files managed by the runner
|
|
619
643
|
- NEVER run `rm -rf`, `git clean`, or any destructive filesystem operations
|
|
620
644
|
- `/prizmkit-committer` is mandatory, and must not be replaced with manual git commit commands
|