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.
Files changed (91) hide show
  1. package/bin/create-prizmkit.js +1 -5
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/dev-pipeline/README.md +41 -38
  4. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +76 -0
  5. package/bundled/dev-pipeline/prizmkit_runtime/config.py +2 -8
  6. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -1
  7. package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +3 -3
  8. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +23 -25
  9. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +8 -2
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +10 -1
  11. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +88 -32
  12. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +28 -6
  13. package/bundled/dev-pipeline/scripts/continuation.py +51 -6
  14. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +60 -102
  15. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +1 -19
  16. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +1 -19
  17. package/bundled/dev-pipeline/scripts/{init-dev-team.py → init-change-artifact.py} +6 -16
  18. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +364 -76
  19. package/bundled/dev-pipeline/scripts/update-bug-status.py +57 -14
  20. package/bundled/dev-pipeline/scripts/update-feature-status.py +54 -5
  21. package/bundled/dev-pipeline/scripts/update-refactor-status.py +57 -14
  22. package/bundled/dev-pipeline/scripts/utils.py +1 -1
  23. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
  24. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +9 -11
  25. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -1
  26. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
  27. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  28. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  29. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -1
  30. package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +0 -2
  31. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -1
  32. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +6 -7
  33. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +6 -7
  34. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +4 -2
  35. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +3 -3
  36. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +5 -3
  37. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +20 -0
  38. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  39. package/bundled/dev-pipeline/tests/test_auto_skip.py +5 -5
  40. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +214 -144
  41. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +25 -12
  42. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +11 -14
  43. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +235 -32
  44. package/bundled/dev-pipeline/tests/test_unified_cli.py +219 -16
  45. package/bundled/skills/_metadata.json +4 -4
  46. package/bundled/skills/app-planner/SKILL.md +2 -2
  47. package/bundled/skills/app-planner/references/architecture-decisions.md +1 -3
  48. package/bundled/skills/prizmkit-code-review/SKILL.md +50 -47
  49. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +13 -8
  50. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +47 -33
  51. package/bundled/skills/prizmkit-code-review/references/reviewer-workspace-protocol.md +90 -0
  52. package/bundled/skills/prizmkit-implement/SKILL.md +21 -1
  53. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +67 -0
  54. package/bundled/skills/prizmkit-test/SKILL.md +159 -155
  55. package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +420 -0
  56. package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +116 -0
  57. package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +112 -0
  58. package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +97 -0
  59. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +76 -192
  60. package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +65 -0
  61. package/bundled/skills/prizmkit-test/references/evidence-protocol.md +194 -0
  62. package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +78 -0
  63. package/bundled/skills/prizmkit-test/references/examples.md +65 -108
  64. package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +10 -7
  65. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +90 -82
  66. package/bundled/skills/prizmkit-test/references/test-report-template.md +77 -98
  67. package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +97 -0
  68. package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +723 -0
  69. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +1426 -0
  70. package/package.json +1 -1
  71. package/src/clean.js +12 -8
  72. package/src/config.js +24 -42
  73. package/src/index.js +1 -10
  74. package/src/manifest.js +3 -9
  75. package/src/metadata.js +0 -26
  76. package/src/prompts.js +0 -13
  77. package/src/scaffold.js +76 -201
  78. package/src/upgrade.js +16 -33
  79. package/bundled/adapters/claude/agent-adapter.js +0 -96
  80. package/bundled/adapters/claude/team-adapter.js +0 -183
  81. package/bundled/adapters/codebuddy/agent-adapter.js +0 -42
  82. package/bundled/adapters/codebuddy/team-adapter.js +0 -46
  83. package/bundled/adapters/codex/agent-adapter.js +0 -38
  84. package/bundled/adapters/codex/team-adapter.js +0 -37
  85. package/bundled/agents/prizm-dev-team-dev.md +0 -123
  86. package/bundled/agents/prizm-dev-team-reviewer.md +0 -113
  87. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +0 -65
  88. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +0 -436
  89. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +0 -510
  90. package/bundled/skills/prizmkit-test/scripts/validate_boundary_report.py +0 -347
  91. 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
- subagent_calls = int(progress.get("subagent_spawn_count") or 0)
363
- if subagent_calls:
364
- _emit_info(f"Subagent calls detected in session: {subagent_calls}")
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=0,
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
- subagents = int(data.get("subagent_spawn_count") or 0)
553
- if child_bytes or subagents:
554
- child = _format_bytes(child_bytes)
555
- if subagents:
556
- child = f"{child}/{subagents}"
557
- parts.append(f"child: {child}")
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="Continuation recovery mode, currently context_overflow.",
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
- "- This continuation was started after context overflow.",
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 — Context Overflow Recovery",
283
+ "## Continuation Addendum — In-Process AI Error Recovery",
251
284
  "",
252
- "This is an automatic continuation after context overflow.",
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
- "- Start with `git status` and `git diff`.",
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 them based on tier, conditions, and
7
- feature configuration. Conditional logic is handled in Python code,
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 a monolithic bootstrap-tier{1,2,3}.md
10
- template and resolves {{PLACEHOLDER}} variables and {{IF_xxx}} blocks.
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 templates for backward compatibility.
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-prompt.md"
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
- """Map estimated_complexity to pipeline mode.
624
-
625
- Returns: 'lite', 'standard', or 'full'
626
-
627
- Tier assignment rationale:
628
- - low + medium → lite (single agent): most features don't benefit from
629
- the orchestrator→dev→reviewer overhead. A single agent reading
630
- .prizmkit/prizm-docs + implementing directly is faster and cheaper.
631
- - high → standard (orchestrator + dev + reviewer): complex features
632
- need the spec→plan→analyze→implement→review pipeline.
633
- - critical → full (full team + framework guardrails): architectural
634
- changes that touch many files and need extra safety checks.
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 — Tier 1 (Single Agent)\n",
1218
- "**Tier 1 — Single Agent**: You handle everything directly. "
1219
- "No normal work subagents. No TeamCreate.\n",
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 — Tier 2 "
1223
- "(Orchestrator + Reviewer)\n",
1224
- "**Tier 2 Orchestrator + Reviewer**: You handle "
1225
- "context + planning + implementation directly. Reviewer reviews. "
1226
- "No Dev subagent orchestrator implements directly (P0-6).\n",
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: you handle everything directly no normal work "
1260
- "subagents needed",
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: orchestrator builds context+plan, implements directly "
1265
- "(no Dev subagent), and Reviewer reviews — see P0-6",
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 paths - platform-aware agent/team resolution.
1579
- # Mixed `--platform all` installs must render commands for the actual AI
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.mode:
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
- complexity = feature.get(
1863
- "estimated_complexity",
1864
- feature.get("complexity", "medium"),
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"