prizmkit 1.1.119 → 1.1.121
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/bin/create-prizmkit.js +1 -5
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +41 -38
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +76 -0
- package/bundled/dev-pipeline/prizmkit_runtime/config.py +2 -8
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +23 -25
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +8 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +10 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +88 -32
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +28 -6
- package/bundled/dev-pipeline/scripts/continuation.py +51 -6
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +60 -102
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +1 -19
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +1 -19
- package/bundled/dev-pipeline/scripts/{init-dev-team.py → init-change-artifact.py} +6 -16
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +364 -76
- package/bundled/dev-pipeline/scripts/update-bug-status.py +57 -14
- package/bundled/dev-pipeline/scripts/update-feature-status.py +54 -5
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +57 -14
- package/bundled/dev-pipeline/scripts/utils.py +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +9 -11
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +0 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +6 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +6 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +4 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +3 -3
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +5 -3
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +20 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
- package/bundled/dev-pipeline/tests/test_auto_skip.py +5 -5
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +214 -144
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +25 -12
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +11 -14
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +235 -32
- package/bundled/dev-pipeline/tests/test_unified_cli.py +219 -16
- package/bundled/skills/_metadata.json +4 -4
- package/bundled/skills/app-planner/SKILL.md +2 -2
- package/bundled/skills/app-planner/references/architecture-decisions.md +1 -3
- package/bundled/skills/prizmkit-code-review/SKILL.md +50 -47
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +13 -8
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +47 -33
- package/bundled/skills/prizmkit-code-review/references/reviewer-workspace-protocol.md +90 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +21 -1
- package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +67 -0
- package/bundled/skills/prizmkit-test/SKILL.md +159 -155
- package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +420 -0
- package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +116 -0
- package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +112 -0
- package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +97 -0
- package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +76 -192
- package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +65 -0
- package/bundled/skills/prizmkit-test/references/evidence-protocol.md +194 -0
- package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +78 -0
- package/bundled/skills/prizmkit-test/references/examples.md +65 -108
- package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +10 -7
- package/bundled/skills/prizmkit-test/references/test-generation-steps.md +90 -82
- package/bundled/skills/prizmkit-test/references/test-report-template.md +77 -98
- package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +97 -0
- package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +723 -0
- package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +1426 -0
- package/package.json +1 -1
- package/src/clean.js +12 -8
- package/src/config.js +24 -42
- package/src/index.js +1 -10
- package/src/manifest.js +3 -9
- package/src/metadata.js +0 -26
- package/src/prompts.js +0 -13
- package/src/scaffold.js +76 -201
- package/src/upgrade.js +16 -33
- package/bundled/adapters/claude/agent-adapter.js +0 -96
- package/bundled/adapters/claude/team-adapter.js +0 -183
- package/bundled/adapters/codebuddy/agent-adapter.js +0 -42
- package/bundled/adapters/codebuddy/team-adapter.js +0 -46
- package/bundled/adapters/codex/agent-adapter.js +0 -38
- package/bundled/adapters/codex/team-adapter.js +0 -37
- package/bundled/agents/prizm-dev-team-dev.md +0 -123
- package/bundled/agents/prizm-dev-team-reviewer.md +0 -113
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +0 -65
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +0 -436
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +0 -510
- package/bundled/skills/prizmkit-test/scripts/validate_boundary_report.py +0 -347
- package/bundled/team/prizm-dev-team.json +0 -27
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import sys
|
|
7
7
|
import time
|
|
8
|
+
from collections.abc import Mapping
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from .commands import CommandResult # type: ignore # imported only at runtime by commands.py
|
|
@@ -336,6 +337,32 @@ def _progress_data(path: Path) -> dict[str, object]:
|
|
|
336
337
|
return data if isinstance(data, dict) else {}
|
|
337
338
|
|
|
338
339
|
|
|
340
|
+
def _subagent_state_summary(progress: Mapping[str, object]) -> str:
|
|
341
|
+
states = progress.get("subagent_states")
|
|
342
|
+
if not isinstance(states, list):
|
|
343
|
+
return ""
|
|
344
|
+
parts = []
|
|
345
|
+
for item in states:
|
|
346
|
+
if not isinstance(item, Mapping):
|
|
347
|
+
continue
|
|
348
|
+
status = str(item.get("status") or "unknown")
|
|
349
|
+
count = int(item.get("count") or 0)
|
|
350
|
+
if count:
|
|
351
|
+
parts.append(f"{status}={count}")
|
|
352
|
+
return ", ".join(parts)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _child_log_activity_summary(progress: Mapping[str, object]) -> str:
|
|
356
|
+
child_bytes = int(progress.get("child_total_bytes") or 0)
|
|
357
|
+
child_files = progress.get("child_session_files")
|
|
358
|
+
child_file_count = len(child_files) if isinstance(child_files, list) else 0
|
|
359
|
+
if not child_bytes and not child_file_count:
|
|
360
|
+
return ""
|
|
361
|
+
if child_file_count:
|
|
362
|
+
return f"{_bytes_text(child_bytes)} across {child_file_count} files"
|
|
363
|
+
return _bytes_text(child_bytes)
|
|
364
|
+
|
|
365
|
+
|
|
339
366
|
def _emit_prompt_summary(prompt: PromptGenerationResult, session_id: str) -> None:
|
|
340
367
|
if prompt.pipeline_mode:
|
|
341
368
|
_emit_info(f"Pipeline mode: {_pipeline_mode_label(prompt.pipeline_mode)}")
|
|
@@ -359,9 +386,24 @@ def _emit_session_summary(session_result, session_paths: SessionPaths, classific
|
|
|
359
386
|
_emit_info(f"exit_code={getattr(session_result, 'exit_code', None)}")
|
|
360
387
|
_emit_info(f"Session result: {classification.session_status}")
|
|
361
388
|
progress = _progress_data(session_paths.progress_json)
|
|
362
|
-
|
|
363
|
-
if
|
|
364
|
-
|
|
389
|
+
subagent_stats = progress.get("subagent_statistics")
|
|
390
|
+
if isinstance(subagent_stats, Mapping):
|
|
391
|
+
spawned = int(subagent_stats.get("spawned") or 0)
|
|
392
|
+
active = int(subagent_stats.get("active") or 0)
|
|
393
|
+
providers = subagent_stats.get("providers")
|
|
394
|
+
provider_text = ",".join(str(provider) for provider in providers) if isinstance(providers, list) else ""
|
|
395
|
+
else:
|
|
396
|
+
spawned = int(progress.get("subagent_spawn_count") or 0)
|
|
397
|
+
active = int(progress.get("active_subagent_count") or 0)
|
|
398
|
+
provider_text = str(progress.get("subagent_provider") or "")
|
|
399
|
+
state_summary = _subagent_state_summary(progress)
|
|
400
|
+
if spawned or active or state_summary:
|
|
401
|
+
suffix = f" ({provider_text}; source=session.log)" if provider_text else " (source=session.log)"
|
|
402
|
+
state_text = f", states: {state_summary}" if state_summary else ""
|
|
403
|
+
_emit_info(f"Subagents: spawned={spawned}, active={active}{state_text}{suffix}")
|
|
404
|
+
child_summary = _child_log_activity_summary(progress)
|
|
405
|
+
if child_summary:
|
|
406
|
+
_emit_info(f"Child log activity: {child_summary} (heartbeat liveness only)")
|
|
365
407
|
if classification.session_status == "success":
|
|
366
408
|
_emit_info("CHECKPOINT: All workflow steps completed")
|
|
367
409
|
|
|
@@ -489,8 +531,11 @@ def _process_item(
|
|
|
489
531
|
previous_no_progress_count = int(metadata.get("no_progress_count") or 0)
|
|
490
532
|
continuation_pending = bool(metadata.get("continuation_pending"))
|
|
491
533
|
previous_session_id = str(metadata.get("previous_session_id") or metadata.get("last_context_overflow_session_id") or "")
|
|
534
|
+
failure_classification = str(metadata.get("continuation_reason") or "context_overflow")
|
|
492
535
|
continuation_count = int(metadata.get("continuation_count") or 0)
|
|
493
536
|
context_overflow_count = int(metadata.get("context_overflow_count") or 0)
|
|
537
|
+
retry_count = int(metadata.get("retry_count") or 0)
|
|
538
|
+
infra_error_count = int(metadata.get("infra_error_count") or 0)
|
|
494
539
|
|
|
495
540
|
while True:
|
|
496
541
|
session_id = _session_id(item_id)
|
|
@@ -499,8 +544,15 @@ def _process_item(
|
|
|
499
544
|
if continuation_pending:
|
|
500
545
|
continuation = {
|
|
501
546
|
"previous_session_id": previous_session_id,
|
|
547
|
+
"failure_classification": failure_classification,
|
|
502
548
|
"continuation_count": continuation_count,
|
|
503
549
|
"context_overflow_count": context_overflow_count,
|
|
550
|
+
"retry_count": retry_count,
|
|
551
|
+
"infra_error_count": infra_error_count,
|
|
552
|
+
"max_retries": _max_retries(invocation),
|
|
553
|
+
"max_infra_retries": invocation.max_infra_retries,
|
|
554
|
+
"no_progress_count": previous_no_progress_count,
|
|
555
|
+
"progress_fingerprint": previous_fingerprint or {},
|
|
504
556
|
"task_type": family.task_type,
|
|
505
557
|
"active_dev_branch": branch_name,
|
|
506
558
|
"base_branch": base_branch,
|
|
@@ -512,7 +564,7 @@ def _process_item(
|
|
|
512
564
|
item_id=item_id,
|
|
513
565
|
session_id=session_id,
|
|
514
566
|
run_id="run-python",
|
|
515
|
-
retry_count=
|
|
567
|
+
retry_count=retry_count,
|
|
516
568
|
session_paths=session_paths,
|
|
517
569
|
project_root=paths.project_root,
|
|
518
570
|
execution_root=execution_root,
|
|
@@ -557,34 +609,8 @@ def _process_item(
|
|
|
557
609
|
previous_no_progress_count=previous_no_progress_count,
|
|
558
610
|
)
|
|
559
611
|
_emit_session_summary(session_result, session_paths, classification)
|
|
560
|
-
if classification.session_status == "context_overflow":
|
|
561
|
-
summary_path = _continuation_summary_path(execution_root, family, item_id, prompt)
|
|
562
|
-
update = update_item(
|
|
563
|
-
status_family,
|
|
564
|
-
status_invocation,
|
|
565
|
-
item_id,
|
|
566
|
-
"context_overflow",
|
|
567
|
-
session_id,
|
|
568
|
-
status_root,
|
|
569
|
-
active_dev_branch=branch_name,
|
|
570
|
-
base_branch=base_branch,
|
|
571
|
-
last_fatal_error_code=classification.fatal_error_code or "context_overflow",
|
|
572
|
-
continuation_summary_path=summary_path,
|
|
573
|
-
no_progress_count=classification.no_progress_count,
|
|
574
|
-
progress_fingerprint=classification.progress_fingerprint,
|
|
575
|
-
)
|
|
576
|
-
if not update.ok:
|
|
577
|
-
raise RuntimeError(update.text)
|
|
578
|
-
_emit_update_summary(family, item_id, update)
|
|
579
|
-
previous_fingerprint = classification.progress_fingerprint
|
|
580
|
-
previous_no_progress_count = classification.no_progress_count
|
|
581
|
-
continuation_pending = True
|
|
582
|
-
previous_session_id = session_id
|
|
583
|
-
continuation_count += 1
|
|
584
|
-
context_overflow_count += 1
|
|
585
|
-
continue
|
|
586
|
-
|
|
587
612
|
final_session_status = classification.session_status
|
|
613
|
+
summary_path = _continuation_summary_path(execution_root, family, item_id, prompt)
|
|
588
614
|
|
|
589
615
|
_write_session_status(
|
|
590
616
|
session_paths,
|
|
@@ -609,6 +635,7 @@ def _process_item(
|
|
|
609
635
|
active_dev_branch=branch_name,
|
|
610
636
|
base_branch=base_branch,
|
|
611
637
|
last_fatal_error_code=classification.fatal_error_code,
|
|
638
|
+
continuation_summary_path=summary_path,
|
|
612
639
|
no_progress_count=classification.no_progress_count,
|
|
613
640
|
progress_fingerprint=classification.progress_fingerprint,
|
|
614
641
|
)
|
|
@@ -616,6 +643,22 @@ def _process_item(
|
|
|
616
643
|
raise RuntimeError(update.text)
|
|
617
644
|
_emit_update_summary(family, item_id, update)
|
|
618
645
|
new_status = _status_from_update(update)
|
|
646
|
+
update_data = update.data if isinstance(update.data, dict) else {}
|
|
647
|
+
retry_count = int(update_data.get("retry_count", retry_count) or 0)
|
|
648
|
+
infra_error_count = int(update_data.get("infra_error_count", infra_error_count) or 0)
|
|
649
|
+
|
|
650
|
+
if _is_retryable_ai_outcome(final_session_status, new_status):
|
|
651
|
+
previous_fingerprint = classification.progress_fingerprint
|
|
652
|
+
previous_no_progress_count = classification.no_progress_count
|
|
653
|
+
continuation_pending = True
|
|
654
|
+
previous_session_id = session_id
|
|
655
|
+
failure_classification = classification.reason or final_session_status
|
|
656
|
+
continuation_count = int(update_data.get("continuation_count", continuation_count + 1) or 0)
|
|
657
|
+
context_overflow_count = int(update_data.get("context_overflow_count", context_overflow_count) or 0)
|
|
658
|
+
_emit_info(f"Retryable AI outcome persisted ({failure_classification}); continuing in 5s...")
|
|
659
|
+
time.sleep(5)
|
|
660
|
+
continue
|
|
661
|
+
|
|
619
662
|
if final_session_status != "success":
|
|
620
663
|
commit_final_bookkeeping(status_root, status_family, item_id, new_status)
|
|
621
664
|
_save_failure_wip(execution_root, family, item_id, new_status, worktree_runtime)
|
|
@@ -652,6 +695,19 @@ def _process_item(
|
|
|
652
695
|
raise
|
|
653
696
|
|
|
654
697
|
|
|
698
|
+
def _is_retryable_ai_outcome(session_status: str, new_status: str) -> bool:
|
|
699
|
+
"""Return whether a persisted post-launch outcome should continue in-process."""
|
|
700
|
+
if session_status == "success" or session_status == "stalled_context_continuation":
|
|
701
|
+
return False
|
|
702
|
+
return new_status in {"in_progress", "pending"} and session_status in {
|
|
703
|
+
"context_overflow",
|
|
704
|
+
"infra_error",
|
|
705
|
+
"crashed",
|
|
706
|
+
"timed_out",
|
|
707
|
+
"failed",
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
|
|
655
711
|
def _finalize_recovered_success(
|
|
656
712
|
family: RunnerFamily,
|
|
657
713
|
item_id: str,
|
|
@@ -970,7 +1026,7 @@ def _default_branch_name(family: RunnerFamily, item_id: str) -> str:
|
|
|
970
1026
|
|
|
971
1027
|
|
|
972
1028
|
def _session_id(item_id: str) -> str:
|
|
973
|
-
return f"{item_id}-{time.strftime('%Y%m%d%H%M%S')}"
|
|
1029
|
+
return f"{item_id}-{time.strftime('%Y%m%d%H%M%S')}-{time.time_ns()}"
|
|
974
1030
|
|
|
975
1031
|
|
|
976
1032
|
def _continuation_summary_path(project_root: Path, family: RunnerFamily, item_id: str, prompt: PromptGenerationResult | None) -> Path:
|
|
@@ -402,6 +402,10 @@ def _progress_refresh_key(data: Mapping[str, object]) -> tuple[object, ...]:
|
|
|
402
402
|
int(data.get("total_tool_calls") or 0),
|
|
403
403
|
int(data.get("active_subagent_count") or 0),
|
|
404
404
|
int(data.get("subagent_spawn_count") or 0),
|
|
405
|
+
str(data.get("subagent_stats_source") or ""),
|
|
406
|
+
str(data.get("subagent_provider") or ""),
|
|
407
|
+
json.dumps(data.get("subagent_states") or [], sort_keys=True, default=str),
|
|
408
|
+
json.dumps(data.get("subagent_observed_ids") or [], sort_keys=True, default=str),
|
|
405
409
|
str(data.get("child_activity_signature") or ""),
|
|
406
410
|
int(data.get("child_total_bytes") or 0),
|
|
407
411
|
str(data.get("fatal_error_code") or ""),
|
|
@@ -549,12 +553,30 @@ def _compact_progress_line(
|
|
|
549
553
|
f"log: {_format_bytes(log_size)}",
|
|
550
554
|
]
|
|
551
555
|
child_bytes = int(data.get("child_total_bytes") or 0)
|
|
552
|
-
|
|
553
|
-
if
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
556
|
+
child_files = data.get("child_session_files")
|
|
557
|
+
child_file_count = len(child_files) if isinstance(child_files, list) else 0
|
|
558
|
+
spawned = int(data.get("subagent_spawn_count") or 0)
|
|
559
|
+
active = int(data.get("active_subagent_count") or 0)
|
|
560
|
+
subagent_states = data.get("subagent_states")
|
|
561
|
+
if spawned or active or subagent_states:
|
|
562
|
+
state_bits = []
|
|
563
|
+
if isinstance(subagent_states, list):
|
|
564
|
+
for item in subagent_states:
|
|
565
|
+
if not isinstance(item, Mapping):
|
|
566
|
+
continue
|
|
567
|
+
status = str(item.get("status") or "unknown")
|
|
568
|
+
count = int(item.get("count") or 0)
|
|
569
|
+
if count:
|
|
570
|
+
state_bits.append(f"{status}={count}")
|
|
571
|
+
subagent_text = f"spawned={spawned} active={active}"
|
|
572
|
+
if state_bits:
|
|
573
|
+
subagent_text = f"{subagent_text} states:{','.join(state_bits)}"
|
|
574
|
+
parts.append(f"subagents: {subagent_text}")
|
|
575
|
+
if child_bytes or child_file_count:
|
|
576
|
+
child_text = _format_bytes(child_bytes)
|
|
577
|
+
if child_file_count:
|
|
578
|
+
child_text = f"{child_text}/{child_file_count} files"
|
|
579
|
+
parts.append(f"child logs: {child_text}")
|
|
558
580
|
phase = _checkpoint_phase_label(checkpoint_path)
|
|
559
581
|
if phase:
|
|
560
582
|
parts.append(f"phase: {phase}")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Continuation prompt and durable handoff helpers for pipeline prompt generators."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
5
6
|
|
|
@@ -9,7 +10,7 @@ if _PIPELINE_ROOT not in sys.path:
|
|
|
9
10
|
|
|
10
11
|
from prizmkit_runtime.checkpoint_state import checkpoint_cursor as shared_checkpoint_cursor # noqa: E402
|
|
11
12
|
|
|
12
|
-
SUPPORTED_CONTINUATION_MODES = {"context_overflow"}
|
|
13
|
+
SUPPORTED_CONTINUATION_MODES = {"context_overflow", "ai_error"}
|
|
13
14
|
TASK_TYPES = {"feature", "bugfix", "refactor"}
|
|
14
15
|
|
|
15
16
|
|
|
@@ -19,13 +20,18 @@ def add_continuation_args(parser, default_task_type):
|
|
|
19
20
|
"--continuation-mode",
|
|
20
21
|
choices=sorted(SUPPORTED_CONTINUATION_MODES),
|
|
21
22
|
default="",
|
|
22
|
-
help="
|
|
23
|
+
help="Provider-neutral in-process AI error continuation mode.",
|
|
23
24
|
)
|
|
24
25
|
parser.add_argument(
|
|
25
26
|
"--previous-session-id",
|
|
26
27
|
default="",
|
|
27
28
|
help="Session ID that triggered this continuation.",
|
|
28
29
|
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--failure-classification",
|
|
32
|
+
default="context_overflow",
|
|
33
|
+
help="Classified outcome from the previous launched AI session.",
|
|
34
|
+
)
|
|
29
35
|
parser.add_argument(
|
|
30
36
|
"--continuation-count",
|
|
31
37
|
default="0",
|
|
@@ -36,6 +42,12 @@ def add_continuation_args(parser, default_task_type):
|
|
|
36
42
|
default="0",
|
|
37
43
|
help="Cumulative context overflow count for this task.",
|
|
38
44
|
)
|
|
45
|
+
parser.add_argument("--code-retry-count", default="0", help="Persisted code retry count.")
|
|
46
|
+
parser.add_argument("--infra-error-count", default="0", help="Persisted infrastructure retry count.")
|
|
47
|
+
parser.add_argument("--max-code-retries", default="0", help="Configured code retry budget.")
|
|
48
|
+
parser.add_argument("--max-infra-retries", default="0", help="Configured infrastructure retry budget.")
|
|
49
|
+
parser.add_argument("--no-progress-count", default="0", help="Consecutive context continuations without durable progress.")
|
|
50
|
+
parser.add_argument("--progress-fingerprint", default="", help="JSON durable Git/checkpoint/artifact progress summary.")
|
|
39
51
|
parser.add_argument(
|
|
40
52
|
"--task-type",
|
|
41
53
|
choices=sorted(TASK_TYPES),
|
|
@@ -72,6 +84,16 @@ def _safe_int(value, default=0):
|
|
|
72
84
|
return parsed if parsed >= 0 else default
|
|
73
85
|
|
|
74
86
|
|
|
87
|
+
def _safe_json_object(value):
|
|
88
|
+
if isinstance(value, dict):
|
|
89
|
+
return value
|
|
90
|
+
try:
|
|
91
|
+
parsed = json.loads(str(value or ""))
|
|
92
|
+
except (TypeError, ValueError, json.JSONDecodeError):
|
|
93
|
+
return {}
|
|
94
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
95
|
+
|
|
96
|
+
|
|
75
97
|
def _norm(path):
|
|
76
98
|
return str(path).replace(os.sep, "/")
|
|
77
99
|
|
|
@@ -193,7 +215,7 @@ def _build_summary(context):
|
|
|
193
215
|
"## Reason",
|
|
194
216
|
"",
|
|
195
217
|
"- Continuation mode: {}".format(context["mode"]),
|
|
196
|
-
"-
|
|
218
|
+
"- Previous AI session classification: {}".format(context["failure_classification"]),
|
|
197
219
|
"- This artifact is the durable handoff summary; do not rely on the previous full transcript.",
|
|
198
220
|
"",
|
|
199
221
|
"## Previous Session",
|
|
@@ -211,6 +233,17 @@ def _build_summary(context):
|
|
|
211
233
|
"- Base branch: {}".format(context["base_branch"] or "(unknown)"),
|
|
212
234
|
"- Continuation count: {}".format(context["continuation_count"]),
|
|
213
235
|
"- Context overflow count: {}".format(context["context_overflow_count"]),
|
|
236
|
+
"- Code retry count: {} / {}".format(context["code_retry_count"], context["max_code_retries"]),
|
|
237
|
+
"- Infrastructure retry count: {} / {}".format(context["infra_error_count"], context["max_infra_retries"]),
|
|
238
|
+
"- Consecutive no-progress context count: {}".format(context["no_progress_count"]),
|
|
239
|
+
"",
|
|
240
|
+
"## Git and Checkpoint Progress",
|
|
241
|
+
"",
|
|
242
|
+
"- Current checkpoint phase: {}".format(cursor["first_cursor"]),
|
|
243
|
+
"- Git status fingerprint: {}".format(context["progress_fingerprint"].get("git_diff_fingerprint", "(unknown)")),
|
|
244
|
+
"- HEAD commit: {}".format(context["progress_fingerprint"].get("head_commit", "(unknown)")),
|
|
245
|
+
"- Artifact fingerprint: {}".format(context["progress_fingerprint"].get("artifact_fingerprint", "(unknown)")),
|
|
246
|
+
"- Checkpoint cursor fingerprint: {}".format(context["progress_fingerprint"].get("checkpoint_cursor", "(unknown)")),
|
|
214
247
|
"",
|
|
215
248
|
"## Observed Partial Progress",
|
|
216
249
|
"",
|
|
@@ -247,16 +280,18 @@ def _build_addendum(context):
|
|
|
247
280
|
runtime = context["runtime"]
|
|
248
281
|
summary_paths = context["summary_paths"]
|
|
249
282
|
lines = [
|
|
250
|
-
"## Continuation Addendum —
|
|
283
|
+
"## Continuation Addendum — In-Process AI Error Recovery",
|
|
251
284
|
"",
|
|
252
|
-
"This is an automatic continuation after
|
|
285
|
+
"This is an automatic continuation after the previous launched AI session ended with `{}`.".format(context["failure_classification"]),
|
|
253
286
|
"This is a fresh headless AI CLI session for the same task.",
|
|
254
287
|
"Continue on the current branch/worktree.",
|
|
255
288
|
"Do not reset, clean, switch branches, or discard work.",
|
|
256
289
|
"",
|
|
257
290
|
"### Required First Actions",
|
|
258
291
|
"",
|
|
259
|
-
"-
|
|
292
|
+
"- Inspect `git status` and `git diff` before editing.",
|
|
293
|
+
"- Validate the workflow checkpoint and current phase before resuming.",
|
|
294
|
+
"- Inspect existing task artifacts and run only the tests necessary to establish current test state.",
|
|
260
295
|
"- Reuse the same durable task artifact directory: `{}`.".format(_norm(durable["artifact_dir"])),
|
|
261
296
|
"- Treat completed/skipped checkpoint steps as already done.",
|
|
262
297
|
"- Continue from the first pending/in-progress checkpoint step: `{}`.".format(cursor["first_cursor"]),
|
|
@@ -276,7 +311,10 @@ def _build_addendum(context):
|
|
|
276
311
|
"- Failure log path: `{}`".format(_norm(durable["failure_log"])),
|
|
277
312
|
"- Continuation summary path(s): `{}`".format(", ".join(_norm(p) for p in summary_paths)),
|
|
278
313
|
"- Continuation count: `{}`".format(context["continuation_count"]),
|
|
314
|
+
"- Previous failure classification: `{}`".format(context["failure_classification"]),
|
|
279
315
|
"- Context overflow count: `{}`".format(context["context_overflow_count"]),
|
|
316
|
+
"- Code retry count: `{}` / `{}`".format(context["code_retry_count"], context["max_code_retries"]),
|
|
317
|
+
"- Infrastructure retry count: `{}` / `{}`".format(context["infra_error_count"], context["max_infra_retries"]),
|
|
280
318
|
"- Previous session id: `{}`".format(context["previous_session_id"] or "(unknown)"),
|
|
281
319
|
"- Previous session log reference: `{}`".format(runtime["previous_session_log"]),
|
|
282
320
|
"",
|
|
@@ -316,8 +354,15 @@ def append_continuation_handoff(rendered_prompt, args, project_root, task_type,
|
|
|
316
354
|
"task_id": task_id,
|
|
317
355
|
"task_slug": task_slug,
|
|
318
356
|
"previous_session_id": previous_session_id,
|
|
357
|
+
"failure_classification": getattr(args, "failure_classification", "") or "context_overflow",
|
|
319
358
|
"continuation_count": _safe_int(getattr(args, "continuation_count", 0)),
|
|
320
359
|
"context_overflow_count": _safe_int(getattr(args, "context_overflow_count", 0)),
|
|
360
|
+
"code_retry_count": _safe_int(getattr(args, "code_retry_count", 0)),
|
|
361
|
+
"infra_error_count": _safe_int(getattr(args, "infra_error_count", 0)),
|
|
362
|
+
"max_code_retries": _safe_int(getattr(args, "max_code_retries", 0)),
|
|
363
|
+
"max_infra_retries": _safe_int(getattr(args, "max_infra_retries", 0)),
|
|
364
|
+
"no_progress_count": _safe_int(getattr(args, "no_progress_count", 0)),
|
|
365
|
+
"progress_fingerprint": _safe_json_object(getattr(args, "progress_fingerprint", "")),
|
|
321
366
|
"active_dev_branch": getattr(args, "active_dev_branch", ""),
|
|
322
367
|
"base_branch": getattr(args, "base_branch", ""),
|
|
323
368
|
"durable": durable,
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
Supports two modes:
|
|
5
5
|
1. **Section assembly** (preferred): Loads modular section files from
|
|
6
|
-
templates/sections/ and assembles
|
|
7
|
-
feature
|
|
6
|
+
templates/sections/ and assembles the unified full/tier3-equivalent
|
|
7
|
+
feature guidance by default. Conditional logic is handled in Python code,
|
|
8
8
|
not regex-based template blocks.
|
|
9
|
-
2. **Legacy template** (fallback): Reads
|
|
10
|
-
template
|
|
9
|
+
2. **Legacy template** (fallback): Reads the monolithic bootstrap-tier3.md
|
|
10
|
+
template, falling back to bootstrap-prompt.md when tier3 is unavailable,
|
|
11
|
+
and resolves {{PLACEHOLDER}} variables and {{IF_xxx}} blocks.
|
|
11
12
|
|
|
12
|
-
The section assembly mode is used when templates/sections/ directory exists
|
|
13
|
-
Otherwise, falls back to legacy
|
|
13
|
+
The section assembly mode is used when templates/sections/ directory exists
|
|
14
|
+
and no explicit --template path is supplied. Otherwise, falls back to legacy
|
|
15
|
+
templates for backward compatibility.
|
|
14
16
|
|
|
15
17
|
Usage:
|
|
16
18
|
python3 generate-bootstrap-prompt.py \
|
|
@@ -93,8 +95,9 @@ def parse_args():
|
|
|
93
95
|
"--template",
|
|
94
96
|
default=None,
|
|
95
97
|
help=(
|
|
96
|
-
"Custom template path. Defaults to "
|
|
97
|
-
"{script_dir}/../templates/bootstrap-
|
|
98
|
+
"Custom template path. Defaults to section-based full feature "
|
|
99
|
+
"prompt assembly, or {script_dir}/../templates/bootstrap-tier3.md "
|
|
100
|
+
"when sections are unavailable."
|
|
98
101
|
),
|
|
99
102
|
)
|
|
100
103
|
parser.add_argument(
|
|
@@ -620,18 +623,21 @@ def detect_existing_artifacts(project_root, feature_slug):
|
|
|
620
623
|
|
|
621
624
|
|
|
622
625
|
def determine_pipeline_mode(complexity):
|
|
623
|
-
"""
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
626
|
+
"""Return the unified feature prompt mode for any complexity.
|
|
627
|
+
|
|
628
|
+
``estimated_complexity`` remains input metadata for planning summaries,
|
|
629
|
+
dependency ordering, and rendered Session Context, but prompt guidance is
|
|
630
|
+
intentionally no longer tiered by complexity. Every feature task receives
|
|
631
|
+
the full/tier3-equivalent guidance by default.
|
|
632
|
+
"""
|
|
633
|
+
return "full"
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def determine_legacy_template_mode(complexity):
|
|
637
|
+
"""Map complexity for explicit custom template conditional compatibility.
|
|
638
|
+
|
|
639
|
+
This is used only when a caller supplies ``--template`` and omits
|
|
640
|
+
``--mode``. The normal feature prompt path never uses this mapping.
|
|
635
641
|
"""
|
|
636
642
|
mapping = {
|
|
637
643
|
"low": "lite",
|
|
@@ -1214,16 +1220,18 @@ def _tier_header(pipeline_mode):
|
|
|
1214
1220
|
"""Return the tier-specific header and mission description."""
|
|
1215
1221
|
headers = {
|
|
1216
1222
|
"lite": (
|
|
1217
|
-
"# Dev-Pipeline Session Bootstrap —
|
|
1218
|
-
"
|
|
1219
|
-
"
|
|
1223
|
+
"# Dev-Pipeline Session Bootstrap — Unified Full Guidance "
|
|
1224
|
+
"(compatibility mode: lite)\n",
|
|
1225
|
+
"**Unified Full Guidance**: Feature prompts use the "
|
|
1226
|
+
"full/tier3-equivalent sections for every estimated complexity. "
|
|
1227
|
+
"The lite label remains metadata only for compatibility.\n",
|
|
1220
1228
|
),
|
|
1221
1229
|
"standard": (
|
|
1222
|
-
"# Dev-Pipeline Session Bootstrap —
|
|
1223
|
-
"(
|
|
1224
|
-
"**
|
|
1225
|
-
"
|
|
1226
|
-
"
|
|
1230
|
+
"# Dev-Pipeline Session Bootstrap — Unified Full Guidance "
|
|
1231
|
+
"(compatibility mode: standard)\n",
|
|
1232
|
+
"**Unified Full Guidance**: Feature prompts use the "
|
|
1233
|
+
"full/tier3-equivalent sections for every estimated complexity. "
|
|
1234
|
+
"The standard label remains metadata only for compatibility.\n",
|
|
1227
1235
|
),
|
|
1228
1236
|
"full": (
|
|
1229
1237
|
"# Dev-Pipeline Session Bootstrap — Tier 3 "
|
|
@@ -1256,13 +1264,13 @@ def _tier_reminders(pipeline_mode):
|
|
|
1256
1264
|
|
|
1257
1265
|
if pipeline_mode == "lite":
|
|
1258
1266
|
specific = [
|
|
1259
|
-
"- Tier 1:
|
|
1260
|
-
"
|
|
1267
|
+
"- Tier 1: compatibility label only; feature prompts now render "
|
|
1268
|
+
"the unified full/tier3-equivalent guidance by default",
|
|
1261
1269
|
]
|
|
1262
1270
|
elif pipeline_mode == "standard":
|
|
1263
1271
|
specific = [
|
|
1264
|
-
"- Tier 2:
|
|
1265
|
-
"
|
|
1272
|
+
"- Tier 2: compatibility label only; feature prompts now render "
|
|
1273
|
+
"the unified full/tier3-equivalent guidance by default",
|
|
1266
1274
|
"- context-snapshot.md is append-only: orchestrator writes "
|
|
1267
1275
|
"Sections 1-4 + Implementation Log, Reviewer appends Review Notes",
|
|
1268
1276
|
"- Gate checks enforce Implementation Log (with Gate Evidence) "
|
|
@@ -1283,6 +1291,14 @@ def _tier_reminders(pipeline_mode):
|
|
|
1283
1291
|
"written before proceeding",
|
|
1284
1292
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1285
1293
|
"agents",
|
|
1294
|
+
"- If an Agent tool call fails with team/config/lock errors, retry "
|
|
1295
|
+
"at most once; after a second failure, use the documented "
|
|
1296
|
+
"inline/recovery fallback or write `failure-log.md` rather than "
|
|
1297
|
+
"spawning variants or polling indefinitely",
|
|
1298
|
+
"- NEVER delete, modify, or touch any file under `.prizmkit/state/` "
|
|
1299
|
+
"— those are pipeline runtime files managed by the runner",
|
|
1300
|
+
"- NEVER run `rm -rf`, `git clean`, or destructive filesystem "
|
|
1301
|
+
"cleanup during the feature session",
|
|
1286
1302
|
"- On timeout: check snapshot → model:lite → remaining steps "
|
|
1287
1303
|
"only → max 2 retries → orchestrator fallback",
|
|
1288
1304
|
]
|
|
@@ -1575,56 +1591,8 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1575
1591
|
"""Build the full dict of placeholder -> replacement value."""
|
|
1576
1592
|
project_root = _args_project_root(args, script_dir)
|
|
1577
1593
|
|
|
1578
|
-
# Resolve
|
|
1579
|
-
|
|
1580
|
-
# environment, not whichever platform directory happens to be checked first.
|
|
1581
|
-
platform = resolve_prompt_platform(project_root)
|
|
1582
|
-
home_dir = os.path.expanduser("~")
|
|
1583
|
-
|
|
1584
|
-
if platform == "claude":
|
|
1585
|
-
# Claude Code: agents in .claude/agents/, no native team config
|
|
1586
|
-
agents_dir = os.path.join(project_root, ".claude", "agents")
|
|
1587
|
-
team_config_path = os.path.join(
|
|
1588
|
-
project_root, ".claude", "team-info.json",
|
|
1589
|
-
)
|
|
1590
|
-
elif platform == "codex":
|
|
1591
|
-
# Codex: agents and team metadata are project-local references.
|
|
1592
|
-
agents_dir = os.path.join(project_root, ".codex", "agents")
|
|
1593
|
-
team_config_path = os.path.join(
|
|
1594
|
-
project_root, ".codex", "team-info.json",
|
|
1595
|
-
)
|
|
1596
|
-
else:
|
|
1597
|
-
# CodeBuddy: agents in .codebuddy/agents/, team in ~/.codebuddy/teams/
|
|
1598
|
-
agents_dir = os.path.join(project_root, ".codebuddy", "agents")
|
|
1599
|
-
team_config_path = os.path.join(
|
|
1600
|
-
home_dir, ".codebuddy", "teams", "prizm-dev-team", "config.json",
|
|
1601
|
-
)
|
|
1602
|
-
|
|
1603
|
-
# Agent definitions are native .toml for Codex and .md for Claude/CodeBuddy.
|
|
1604
|
-
agent_ext = ".toml" if platform == "codex" else ".md"
|
|
1605
|
-
dev_subagent = os.path.join(
|
|
1606
|
-
agents_dir, f"prizm-dev-team-dev{agent_ext}",
|
|
1607
|
-
)
|
|
1608
|
-
reviewer_subagent = os.path.join(
|
|
1609
|
-
agents_dir, f"prizm-dev-team-reviewer{agent_ext}",
|
|
1610
|
-
)
|
|
1611
|
-
|
|
1612
|
-
# Verify agent files actually exist — missing files cause confusing
|
|
1613
|
-
# errors when the AI session tries to read them later.
|
|
1614
|
-
for agent_path, agent_name in [
|
|
1615
|
-
(dev_subagent, "dev agent"),
|
|
1616
|
-
(reviewer_subagent, "reviewer agent"),
|
|
1617
|
-
]:
|
|
1618
|
-
if not os.path.isfile(agent_path):
|
|
1619
|
-
LOGGER.warning(
|
|
1620
|
-
"Agent file not found: %s (%s). "
|
|
1621
|
-
"Subagent spawning may fail. "
|
|
1622
|
-
"Run `npx prizmkit install` to reinstall agent definitions.",
|
|
1623
|
-
agent_path, agent_name,
|
|
1624
|
-
)
|
|
1625
|
-
# Validator scripts - check if they exist in .codebuddy/scripts/, otherwise use .prizmkit/dev-pipeline/scripts/
|
|
1626
|
-
validator_scripts_dir = os.path.join(project_root, ".prizmkit", "dev-pipeline", "scripts")
|
|
1627
|
-
init_script_path = os.path.join(validator_scripts_dir, "init-dev-team.py")
|
|
1594
|
+
# Resolve platform for platform-specific project convention references only.
|
|
1595
|
+
resolve_prompt_platform(project_root)
|
|
1628
1596
|
|
|
1629
1597
|
# Session status is controlled by the main runner state dir, even when
|
|
1630
1598
|
# rendered project paths are AI-visible worktree paths.
|
|
@@ -1650,10 +1618,15 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1650
1618
|
"estimated_complexity",
|
|
1651
1619
|
feature.get("complexity", "medium"),
|
|
1652
1620
|
)
|
|
1653
|
-
if args.
|
|
1654
|
-
pipeline_mode = args.mode
|
|
1621
|
+
if args.template:
|
|
1622
|
+
pipeline_mode = args.mode or determine_legacy_template_mode(complexity)
|
|
1655
1623
|
else:
|
|
1656
1624
|
pipeline_mode = determine_pipeline_mode(complexity)
|
|
1625
|
+
if args.mode and args.mode != pipeline_mode and not args.template:
|
|
1626
|
+
LOGGER.info(
|
|
1627
|
+
"Ignoring feature prompt --mode=%s; unified full guidance is used",
|
|
1628
|
+
args.mode,
|
|
1629
|
+
)
|
|
1657
1630
|
|
|
1658
1631
|
# Auto-detect resume: if all planning artifacts exist and resume_phase
|
|
1659
1632
|
# is "null" (fresh start), skip to Phase 6
|
|
@@ -1710,10 +1683,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1710
1683
|
),
|
|
1711
1684
|
"{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
|
|
1712
1685
|
"{{PROJECT_BRIEF}}": _read_project_brief(project_root),
|
|
1713
|
-
"{{TEAM_CONFIG_PATH}}": team_config_path,
|
|
1714
|
-
"{{DEV_SUBAGENT_PATH}}": dev_subagent,
|
|
1715
|
-
"{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
|
|
1716
|
-
"{{INIT_SCRIPT_PATH}}": init_script_path,
|
|
1717
1686
|
"{{SESSION_STATUS_PATH}}": session_status_abs,
|
|
1718
1687
|
"{{PROJECT_ROOT}}": project_root,
|
|
1719
1688
|
"{{FEATURE_SLUG}}": feature_slug,
|
|
@@ -1859,20 +1828,9 @@ def main():
|
|
|
1859
1828
|
if args.template:
|
|
1860
1829
|
template_path = args.template
|
|
1861
1830
|
else:
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
)
|
|
1866
|
-
_mode = args.mode or determine_pipeline_mode(complexity)
|
|
1867
|
-
_tier_file_map = {
|
|
1868
|
-
"lite": "bootstrap-tier1.md",
|
|
1869
|
-
"standard": "bootstrap-tier2.md",
|
|
1870
|
-
"full": "bootstrap-tier3.md",
|
|
1871
|
-
}
|
|
1872
|
-
_tier_file = _tier_file_map.get(_mode, "bootstrap-tier2.md")
|
|
1873
|
-
_tier_path = os.path.join(templates_dir, _tier_file)
|
|
1874
|
-
if os.path.isfile(_tier_path):
|
|
1875
|
-
template_path = _tier_path
|
|
1831
|
+
tier3_path = os.path.join(templates_dir, "bootstrap-tier3.md")
|
|
1832
|
+
if os.path.isfile(tier3_path):
|
|
1833
|
+
template_path = tier3_path
|
|
1876
1834
|
else:
|
|
1877
1835
|
template_path = os.path.join(
|
|
1878
1836
|
templates_dir, "bootstrap-prompt.md"
|