phase-loop-runtime 0.2.0__py3-none-any.whl

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 (370) hide show
  1. phase_loop_runtime/__init__.py +20 -0
  2. phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
  3. phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
  4. phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
  5. phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
  6. phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
  7. phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
  8. phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
  9. phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
  10. phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
  11. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
  12. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
  13. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
  14. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
  15. phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
  16. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
  17. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
  18. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
  19. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
  20. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
  21. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
  22. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
  23. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
  24. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
  25. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
  26. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
  27. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
  28. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
  29. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
  30. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
  31. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
  32. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
  33. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
  34. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
  35. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
  36. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
  37. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
  38. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
  39. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
  40. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
  41. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
  42. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
  43. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
  44. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
  45. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
  46. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
  47. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
  48. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
  49. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
  50. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
  51. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
  52. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
  53. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
  54. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
  55. phase_loop_runtime/adoption_bundle.py +273 -0
  56. phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
  57. phase_loop_runtime/advisor_board/__init__.py +288 -0
  58. phase_loop_runtime/advisor_board/backing.py +137 -0
  59. phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
  60. phase_loop_runtime/advisor_board/config.py +225 -0
  61. phase_loop_runtime/advisor_board/events.py +115 -0
  62. phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
  63. phase_loop_runtime/advisor_board/fixtures.py +91 -0
  64. phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
  65. phase_loop_runtime/advisor_board/matrix.py +146 -0
  66. phase_loop_runtime/advisor_board/observability.py +481 -0
  67. phase_loop_runtime/advisor_board/presets.py +188 -0
  68. phase_loop_runtime/advisor_board/registries.py +309 -0
  69. phase_loop_runtime/advisor_board/resolver.py +253 -0
  70. phase_loop_runtime/advisor_board/schema.py +269 -0
  71. phase_loop_runtime/advisor_board/standin.py +166 -0
  72. phase_loop_runtime/advisor_board/validation.py +159 -0
  73. phase_loop_runtime/agent_runtime_provider.py +676 -0
  74. phase_loop_runtime/baml_modular.py +656 -0
  75. phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
  76. phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
  77. phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
  78. phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
  79. phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
  80. phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
  81. phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
  82. phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
  83. phase_loop_runtime/broker.py +181 -0
  84. phase_loop_runtime/build_bundle.py +294 -0
  85. phase_loop_runtime/capability_registry.py +835 -0
  86. phase_loop_runtime/classifier.py +87 -0
  87. phase_loop_runtime/claude_agent_view.py +625 -0
  88. phase_loop_runtime/claude_channel_sidecar.py +1086 -0
  89. phase_loop_runtime/cli.py +2599 -0
  90. phase_loop_runtime/closeout.py +865 -0
  91. phase_loop_runtime/closeout_classifier.py +120 -0
  92. phase_loop_runtime/closeout_evidence_audit.py +112 -0
  93. phase_loop_runtime/closeout_validation.py +144 -0
  94. phase_loop_runtime/closeout_validators.py +203 -0
  95. phase_loop_runtime/consiliency_gates.py +374 -0
  96. phase_loop_runtime/consiliency_ingest.py +399 -0
  97. phase_loop_runtime/consiliency_layout.py +176 -0
  98. phase_loop_runtime/consiliency_scaffold.py +305 -0
  99. phase_loop_runtime/cross_repo_channel.py +331 -0
  100. phase_loop_runtime/discovery.py +2368 -0
  101. phase_loop_runtime/dispatch_lock.py +167 -0
  102. phase_loop_runtime/doc_delta_validator.py +98 -0
  103. phase_loop_runtime/docs_audit.py +285 -0
  104. phase_loop_runtime/docs_freshness.py +468 -0
  105. phase_loop_runtime/docs_surfaces.py +150 -0
  106. phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
  107. phase_loop_runtime/events.py +154 -0
  108. phase_loop_runtime/events_migration.py +96 -0
  109. phase_loop_runtime/evidence_audit.py +1082 -0
  110. phase_loop_runtime/evidence_audit_config.py +172 -0
  111. phase_loop_runtime/fleet_map.py +426 -0
  112. phase_loop_runtime/fleet_metrics.py +298 -0
  113. phase_loop_runtime/fleet_metrics_export.py +196 -0
  114. phase_loop_runtime/git_discipline.py +479 -0
  115. phase_loop_runtime/git_ops.py +122 -0
  116. phase_loop_runtime/git_topology.py +252 -0
  117. phase_loop_runtime/governed_bundle.py +137 -0
  118. phase_loop_runtime/governed_premerge.py +219 -0
  119. phase_loop_runtime/governed_review.py +264 -0
  120. phase_loop_runtime/handoff.py +932 -0
  121. phase_loop_runtime/handoff_path.py +14 -0
  122. phase_loop_runtime/injection.py +740 -0
  123. phase_loop_runtime/install_status.py +88 -0
  124. phase_loop_runtime/lane_scheduler.py +317 -0
  125. phase_loop_runtime/launcher.py +2760 -0
  126. phase_loop_runtime/lease_store.py +553 -0
  127. phase_loop_runtime/maintenance.py +401 -0
  128. phase_loop_runtime/migrate_handoffs.py +201 -0
  129. phase_loop_runtime/models.py +2144 -0
  130. phase_loop_runtime/observability.py +1290 -0
  131. phase_loop_runtime/panel_invoker.py +1638 -0
  132. phase_loop_runtime/phase_loop_drift_audit.py +388 -0
  133. phase_loop_runtime/phase_worktree_executor.py +433 -0
  134. phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
  135. phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
  136. phase_loop_runtime/pipeline_adapter/flag.py +55 -0
  137. phase_loop_runtime/pipeline_adapter/markers.py +13 -0
  138. phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
  139. phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
  140. phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
  141. phase_loop_runtime/plan_ir.py +523 -0
  142. phase_loop_runtime/plan_manifest.py +415 -0
  143. phase_loop_runtime/planner_validation.py +176 -0
  144. phase_loop_runtime/profiles.py +462 -0
  145. phase_loop_runtime/prompts.py +365 -0
  146. phase_loop_runtime/provenance.py +165 -0
  147. phase_loop_runtime/publishing.py +339 -0
  148. phase_loop_runtime/py.typed +1 -0
  149. phase_loop_runtime/reconcile.py +1550 -0
  150. phase_loop_runtime/redaction.py +184 -0
  151. phase_loop_runtime/reflection_sync.py +293 -0
  152. phase_loop_runtime/release_guard.py +186 -0
  153. phase_loop_runtime/render.py +432 -0
  154. phase_loop_runtime/repo_validation.py +427 -0
  155. phase_loop_runtime/review_summary.py +150 -0
  156. phase_loop_runtime/roadmap_lint.py +425 -0
  157. phase_loop_runtime/route_log.py +59 -0
  158. phase_loop_runtime/route_policy/__init__.py +11 -0
  159. phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
  160. phase_loop_runtime/runner.py +8400 -0
  161. phase_loop_runtime/runtime_paths.py +225 -0
  162. phase_loop_runtime/runtime_projection.py +113 -0
  163. phase_loop_runtime/runtime_resources.py +36 -0
  164. phase_loop_runtime/schema_export.py +292 -0
  165. phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
  166. phase_loop_runtime/skill_install.py +176 -0
  167. phase_loop_runtime/skill_inventory.py +662 -0
  168. phase_loop_runtime/skill_paths.py +42 -0
  169. phase_loop_runtime/skill_sources_plugin.py +37 -0
  170. phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
  171. phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
  172. phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
  173. phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
  174. phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
  175. phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
  176. phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
  177. phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
  178. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  179. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  180. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  181. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  182. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  183. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  184. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  185. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
  186. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  187. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
  188. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
  189. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
  190. phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
  191. phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
  192. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
  193. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
  194. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
  195. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  196. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
  197. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  198. phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
  199. phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
  200. phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
  201. phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
  202. phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
  203. phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
  204. phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
  205. phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
  206. phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
  207. phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
  208. phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
  209. phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
  210. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
  211. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
  212. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  213. phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
  214. phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
  215. phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
  216. phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
  217. phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
  218. phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
  219. phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
  220. phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
  221. phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
  222. phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
  223. phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
  224. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  225. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  226. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  227. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  228. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  229. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  230. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  231. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
  232. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  233. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
  234. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
  235. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
  236. phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
  237. phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
  238. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
  239. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
  240. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
  241. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  242. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
  243. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  244. phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
  245. phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
  246. phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
  247. phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
  248. phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
  249. phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
  250. phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
  251. phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
  252. phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
  253. phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
  254. phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
  255. phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
  256. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
  257. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
  258. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  259. phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
  260. phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
  261. phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
  262. phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
  263. phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
  264. phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
  265. phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
  266. phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
  267. phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
  268. phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
  269. phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
  270. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  271. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  272. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  273. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  274. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  275. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  276. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  277. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
  278. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  279. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
  280. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
  281. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
  282. phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
  283. phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
  284. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
  285. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
  286. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
  287. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  288. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
  289. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  290. phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
  291. phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
  292. phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
  293. phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
  294. phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
  295. phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
  296. phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
  297. phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
  298. phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
  299. phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
  300. phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
  301. phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
  302. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
  303. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
  304. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  305. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
  306. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
  307. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
  308. phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
  309. phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
  310. phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
  311. phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
  312. phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
  313. phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
  314. phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
  315. phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
  316. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  317. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  318. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  319. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  320. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  321. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  322. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  323. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
  324. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  325. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
  326. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
  327. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
  328. phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
  329. phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
  330. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
  331. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
  332. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
  333. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  334. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
  335. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  336. phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
  337. phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
  338. phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
  339. phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
  340. phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
  341. phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
  342. phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
  343. phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
  344. phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
  345. phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
  346. phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
  347. phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
  348. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
  349. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
  350. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  351. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
  352. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
  353. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
  354. phase_loop_runtime/state.py +156 -0
  355. phase_loop_runtime/state_degradation.py +136 -0
  356. phase_loop_runtime/state_ops.py +676 -0
  357. phase_loop_runtime/train_ledger.py +215 -0
  358. phase_loop_runtime/train_roadmap.py +339 -0
  359. phase_loop_runtime/train_runner.py +1197 -0
  360. phase_loop_runtime/verification_evidence.py +650 -0
  361. phase_loop_runtime/verification_evidence_validator.py +75 -0
  362. phase_loop_runtime/visual_evidence_validator.py +36 -0
  363. phase_loop_runtime/worker_pool.py +123 -0
  364. phase_loop_runtime/worktree_index.py +270 -0
  365. phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
  366. phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
  367. phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
  368. phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
  369. phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
  370. phase_loop_runtime-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,252 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ from dataclasses import replace
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from .models import LoopEvent, StateSnapshot
10
+
11
+
12
+ def attach_git_topology(repo: Path, value: LoopEvent | StateSnapshot) -> LoopEvent | StateSnapshot:
13
+ if value.git_topology:
14
+ return value
15
+ return replace(value, git_topology=collect_git_topology(repo))
16
+
17
+
18
+ def collect_git_topology(repo: Path) -> dict[str, Any]:
19
+ if not _git_available(repo):
20
+ return {"available": False, "reason": "not a git worktree"}
21
+
22
+ topology: dict[str, Any] = {
23
+ "available": True,
24
+ "branch": _branch(repo),
25
+ "head": _git(repo, "rev-parse", "HEAD"),
26
+ "status_short_branch": _git(repo, "status", "--short", "--branch"),
27
+ }
28
+ topology["clean"] = not bool(_git(repo, "status", "--short"))
29
+
30
+ upstream = _git(repo, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}")
31
+ if upstream:
32
+ topology["upstream_ref"] = upstream
33
+ topology["upstream_head"] = _git(repo, "rev-parse", upstream)
34
+ behind, ahead = _ahead_behind(repo, upstream)
35
+ if ahead is not None:
36
+ topology["ahead_of_upstream"] = ahead
37
+ if behind is not None:
38
+ topology["behind_upstream"] = behind
39
+
40
+ default_remote = _git(repo, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
41
+ if default_remote:
42
+ topology["default_remote_ref"] = default_remote
43
+
44
+ base_ref = os.environ.get("PHASE_LOOP_BASE_REF") or upstream or default_remote or _existing_ref(repo, "origin/main")
45
+ if base_ref:
46
+ topology["base_ref"] = base_ref
47
+ topology["base_head"] = _git(repo, "rev-parse", base_ref)
48
+ behind, ahead = _ahead_behind(repo, base_ref)
49
+ if ahead is not None:
50
+ topology["ahead_of_base"] = ahead
51
+ if behind is not None:
52
+ topology["behind_base"] = behind
53
+
54
+ target_push_ref = os.environ.get("PHASE_LOOP_TARGET_PUSH_REF")
55
+ if target_push_ref:
56
+ topology["target_push_ref"] = target_push_ref
57
+ pr_head_ref = os.environ.get("PHASE_LOOP_PR_HEAD_REF") or os.environ.get("GITHUB_HEAD_REF")
58
+ if pr_head_ref:
59
+ topology["pr_head_ref"] = pr_head_ref
60
+ pr_base_ref = os.environ.get("PHASE_LOOP_PR_BASE_REF") or os.environ.get("GITHUB_BASE_REF")
61
+ if pr_base_ref:
62
+ topology["pr_base_ref"] = pr_base_ref
63
+ pr_url = os.environ.get("PHASE_LOOP_PR_URL")
64
+ if pr_url:
65
+ topology["pr_url"] = pr_url
66
+ elif "pr_url" not in topology:
67
+ topology.update(_matching_pr_metadata(repo, topology.get("head"), default_remote))
68
+
69
+ return {key: value for key, value in topology.items() if value not in (None, "")}
70
+
71
+
72
+ def resolve_closeout_push_target(repo: Path, topology: dict[str, Any] | None = None) -> dict[str, Any]:
73
+ topology = dict(topology or collect_git_topology(repo))
74
+ if not topology.get("available"):
75
+ return {"allowed": False, "refusal_reason": "not_a_git_worktree"}
76
+
77
+ explicit_target = topology.get("target_push_ref")
78
+ upstream = topology.get("upstream_ref")
79
+ remote = _remote_from_upstream(upstream)
80
+ if explicit_target:
81
+ push_ref = str(explicit_target)
82
+ if remote is None:
83
+ remote = _default_push_remote(repo, topology)
84
+ if remote is None:
85
+ return {"allowed": False, "refusal_reason": "missing_push_remote"}
86
+ elif upstream:
87
+ remote, branch_ref = _split_upstream_ref(str(upstream))
88
+ if remote is None or branch_ref is None:
89
+ return {"allowed": False, "refusal_reason": "ambiguous_upstream_ref"}
90
+ push_ref = branch_ref
91
+ else:
92
+ return {"allowed": False, "refusal_reason": "missing_push_target"}
93
+
94
+ if not topology.get("clean", False):
95
+ return {"allowed": False, "remote": remote, "push_ref": push_ref, "refusal_reason": "post_commit_dirty_worktree"}
96
+ if int(topology.get("behind_upstream", 0) or 0) > 0:
97
+ return {"allowed": False, "remote": remote, "push_ref": push_ref, "refusal_reason": "behind_upstream"}
98
+
99
+ return {"allowed": True, "remote": remote, "push_ref": push_ref}
100
+
101
+
102
+ def _git_available(repo: Path) -> bool:
103
+ return _git(repo, "rev-parse", "--is-inside-work-tree") == "true"
104
+
105
+
106
+ def _branch(repo: Path) -> str:
107
+ branch = _git(repo, "branch", "--show-current")
108
+ if branch:
109
+ return branch
110
+ head = _git(repo, "rev-parse", "--short", "HEAD")
111
+ return f"detached@{head}" if head else "unknown"
112
+
113
+
114
+ def _existing_ref(repo: Path, ref: str) -> str | None:
115
+ return ref if _git(repo, "rev-parse", "--verify", "--quiet", ref) else None
116
+
117
+
118
+ def _ahead_behind(repo: Path, base_ref: str) -> tuple[int | None, int | None]:
119
+ counts = _git(repo, "rev-list", "--left-right", "--count", f"{base_ref}...HEAD")
120
+ if not counts:
121
+ return None, None
122
+ parts = counts.split()
123
+ if len(parts) != 2:
124
+ return None, None
125
+ try:
126
+ behind = int(parts[0])
127
+ ahead = int(parts[1])
128
+ except ValueError:
129
+ return None, None
130
+ return behind, ahead
131
+
132
+
133
+ def _remote_from_upstream(upstream_ref: Any) -> str | None:
134
+ if not isinstance(upstream_ref, str) or "/" not in upstream_ref:
135
+ return None
136
+ return upstream_ref.split("/", 1)[0] or None
137
+
138
+
139
+ def _split_upstream_ref(upstream_ref: str) -> tuple[str | None, str | None]:
140
+ if "/" not in upstream_ref:
141
+ return None, None
142
+ remote, branch = upstream_ref.split("/", 1)
143
+ if not remote or not branch:
144
+ return None, None
145
+ return remote, f"refs/heads/{branch}"
146
+
147
+
148
+ def _default_push_remote(repo: Path, topology: dict[str, Any]) -> str | None:
149
+ upstream_remote = _remote_from_upstream(topology.get("upstream_ref"))
150
+ if upstream_remote:
151
+ return upstream_remote
152
+ default_remote = topology.get("default_remote_ref")
153
+ if isinstance(default_remote, str) and "/" in default_remote:
154
+ return default_remote.split("/", 1)[0]
155
+ remotes = _git(repo, "remote")
156
+ if not remotes:
157
+ return None
158
+ names = [line.strip() for line in remotes.splitlines() if line.strip()]
159
+ if len(names) == 1:
160
+ return names[0]
161
+ if "origin" in names:
162
+ return "origin"
163
+ return None
164
+
165
+
166
+ def _matching_pr_metadata(repo: Path, head: Any, default_remote: str | None) -> dict[str, Any]:
167
+ if not isinstance(head, str) or not head:
168
+ return {}
169
+ default_branch = default_remote.removeprefix("origin/") if default_remote else "main"
170
+ for ref in _remote_refs_at_head(repo, head):
171
+ if ref == f"origin/{default_branch}":
172
+ continue
173
+ if not ref.startswith("origin/"):
174
+ continue
175
+ metadata = _gh_pr_metadata(repo, ref.removeprefix("origin/"))
176
+ if metadata:
177
+ metadata["matching_remote_ref"] = ref
178
+ return metadata
179
+ return {}
180
+
181
+
182
+ def _remote_refs_at_head(repo: Path, head: str) -> list[str]:
183
+ refs = _git(repo, "for-each-ref", "--format=%(refname:short) %(objectname)", "refs/remotes/origin")
184
+ if not refs:
185
+ return []
186
+ matches: list[str] = []
187
+ for line in refs.splitlines():
188
+ parts = line.split()
189
+ if len(parts) == 2 and parts[1] == head:
190
+ matches.append(parts[0])
191
+ return matches
192
+
193
+
194
+ def _gh_pr_metadata(repo: Path, branch: str) -> dict[str, Any]:
195
+ payload = _gh(repo, "pr", "list", "--head", branch, "--state", "open", "--limit", "1", "--json", "number,url,headRefName,baseRefName,reviewDecision,mergeable,isDraft")
196
+ if not payload:
197
+ return {}
198
+ try:
199
+ import json
200
+
201
+ prs = json.loads(payload)
202
+ except Exception:
203
+ return {}
204
+ if not isinstance(prs, list) or not prs:
205
+ return {}
206
+ pr = prs[0]
207
+ if not isinstance(pr, dict):
208
+ return {}
209
+ return {
210
+ "pr_number": pr.get("number"),
211
+ "pr_url": pr.get("url"),
212
+ "pr_head_ref": pr.get("headRefName"),
213
+ "pr_base_ref": pr.get("baseRefName"),
214
+ "pr_review_decision": pr.get("reviewDecision"),
215
+ "pr_mergeable": pr.get("mergeable"),
216
+ "pr_is_draft": pr.get("isDraft"),
217
+ }
218
+
219
+
220
+ def _git(repo: Path, *args: str) -> str | None:
221
+ try:
222
+ completed = subprocess.run(
223
+ ["git", "-C", str(repo), *args],
224
+ check=False,
225
+ stdout=subprocess.PIPE,
226
+ stderr=subprocess.DEVNULL,
227
+ text=True,
228
+ timeout=5,
229
+ )
230
+ except Exception:
231
+ return None
232
+ if completed.returncode != 0:
233
+ return None
234
+ return completed.stdout.strip() or None
235
+
236
+
237
+ def _gh(repo: Path, *args: str) -> str | None:
238
+ try:
239
+ completed = subprocess.run(
240
+ ["gh", *args],
241
+ cwd=repo,
242
+ check=False,
243
+ stdout=subprocess.PIPE,
244
+ stderr=subprocess.DEVNULL,
245
+ text=True,
246
+ timeout=5,
247
+ )
248
+ except Exception:
249
+ return None
250
+ if completed.returncode != 0:
251
+ return None
252
+ return completed.stdout.strip() or None
@@ -0,0 +1,137 @@
1
+ """Governed pre-merge review bundle (model-routing-v2 P1, IF-0-P1-1).
2
+
3
+ The artifact the panel reviews at the pre-merge gate is a *review bundle*, not a
4
+ bare diff: the EXACT staged diff the closeout is about to commit, plus the plan's
5
+ ``## Acceptance Criteria``, the verification-command results, and a one-paragraph
6
+ closeout summary. The panel needs the change *and* the spec to judge it against.
7
+
8
+ The gate fires INSIDE ``_perform_phase_closeout`` — after ``git add`` stages the
9
+ owned paths and before the commit is finalized — and passes the staged-index diff
10
+ (``git diff --cached``) in here. So "what the panel reviews" == "what gets
11
+ committed" by construction: no separately-discovered path set to diverge from
12
+ closeout, no untracked-file synthesis, and the renderer never writes a file into
13
+ the repo (advisor-panel reconciliation — the prior independent path discovery and
14
+ in-repo staging were the divergence + worktree-dirtying defects).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import subprocess
19
+ from pathlib import Path
20
+ from typing import Any, Mapping, Sequence
21
+
22
+
23
+ def staged_index_diff(repo: Path, paths: Sequence[str]) -> str:
24
+ """The EXACT diff about to be committed: ``git diff --cached -- <paths>``.
25
+
26
+ After ``git add``, newly-added files appear natively in the cached diff (no
27
+ ``--no-index`` synthesis, no untracked probe). This is the staged index, so it
28
+ cannot diverge from what the commit records.
29
+ """
30
+ if not paths:
31
+ return "(no staged paths)"
32
+ try:
33
+ out = subprocess.run(
34
+ ["git", "-C", str(repo), "diff", "--cached", "--", *paths],
35
+ capture_output=True, text=True, timeout=30,
36
+ )
37
+ except Exception:
38
+ return "(staged diff unavailable)"
39
+ return out.stdout.rstrip() or "(empty staged diff)"
40
+
41
+
42
+ def _acceptance_criteria(plan_path: str | Path | None) -> str:
43
+ if not plan_path:
44
+ return "(no plan path)"
45
+ try:
46
+ text = Path(plan_path).read_text(encoding="utf-8")
47
+ except OSError:
48
+ return "(plan unreadable)"
49
+ lines = text.splitlines()
50
+ out: list[str] = []
51
+ capture = False
52
+ for line in lines:
53
+ if line.lstrip().lower().startswith("## acceptance"):
54
+ capture = True
55
+ continue
56
+ if capture and line.startswith("## "):
57
+ break
58
+ if capture:
59
+ out.append(line)
60
+ body = "\n".join(out).strip()
61
+ return body or "(no acceptance-criteria section found)"
62
+
63
+
64
+ def _verification_results(terminal: Mapping[str, Any]) -> str:
65
+ # The terminal summary records per-command verification under
66
+ # `verification_commands` (list[dict]); `build_terminal_summary` never emits
67
+ # `verification`/`verification_results`, so the old keys always missed and the
68
+ # panel saw "no results recorded" even when commands ran (code-review finding,
69
+ # verified). Accept the real key first; keep the legacy keys as a tolerant
70
+ # fallback for hand-built terminals in tests.
71
+ rows = terminal.get("verification_commands") or ()
72
+ if not rows:
73
+ results = terminal.get("verification") if isinstance(terminal.get("verification"), Mapping) else None
74
+ if isinstance(results, Mapping):
75
+ rows = results.get("results") or results.get("commands") or ()
76
+ else:
77
+ rows = terminal.get("verification_results") or ()
78
+ if not rows:
79
+ unit = terminal.get("latest_verification_unit")
80
+ status = (
81
+ (unit.get("status") if isinstance(unit, Mapping) else None)
82
+ or terminal.get("verification_status")
83
+ or "unknown"
84
+ )
85
+ return f"verification_status: {status} (no per-command results recorded)"
86
+ lines = []
87
+ for r in rows:
88
+ if isinstance(r, Mapping):
89
+ lines.append(f"- {r.get('code') or r.get('command') or r}: {r.get('status') or r.get('ok')}")
90
+ else:
91
+ lines.append(f"- {r}")
92
+ return "\n".join(lines)
93
+
94
+
95
+ def _summary(terminal: Mapping[str, Any]) -> str:
96
+ return str(
97
+ terminal.get("next_action")
98
+ or terminal.get("terminal_status")
99
+ or "(no summary)"
100
+ )
101
+
102
+
103
+ def render_governed_bundle(
104
+ *,
105
+ phase_alias: str,
106
+ terminal: Mapping[str, Any],
107
+ plan_path: str | Path | None,
108
+ diff_text: str,
109
+ ) -> str:
110
+ """Render the review bundle text from a PRECOMPUTED staged-index diff.
111
+
112
+ The caller (the in-closeout gate) computes ``diff_text`` via
113
+ ``staged_index_diff`` over the exact paths it is committing, so the bundle
114
+ cannot diverge from the commit. The renderer never touches git or the
115
+ filesystem; ``panel_invoker`` stages the bundle as material in a temp dir
116
+ OUTSIDE the repo, so the gate never dirties the worktree under review.
117
+ """
118
+ return "\n".join(
119
+ [
120
+ f"# Governed pre-merge review — phase {phase_alias}",
121
+ "",
122
+ "## Change (staged diff — exactly what will be committed)",
123
+ "```diff",
124
+ diff_text, # producer (staged_index_diff) owns the empty/unavailable sentinel
125
+ "```",
126
+ "",
127
+ "## Acceptance Criteria (the spec to judge against)",
128
+ _acceptance_criteria(plan_path),
129
+ "",
130
+ "## Verification results",
131
+ _verification_results(terminal),
132
+ "",
133
+ "## Summary",
134
+ _summary(terminal),
135
+ "",
136
+ ]
137
+ )
@@ -0,0 +1,219 @@
1
+ """Implementation escalation ladder + governed pre-merge review loop
2
+ (model-routing-v1 P3).
3
+
4
+ Two behaviors, both **mode-branched** and **non-human terminal**:
5
+
6
+ 1. ``next_escalation`` — the failure ladder. On repeated failure the model_class
7
+ escalates ``implementer → planner``; a still-failing planner then forks by
8
+ run_mode: ``governed`` invokes the panel, ``autonomous`` terminates as a
9
+ **repairable non-human blocker** (``repeated_verification_failure``, never
10
+ ``human_required``).
11
+
12
+ 2. ``run_governed_premerge_loop`` — the bounded pre-merge gate. In ``governed``
13
+ mode it runs panel review → fix → re-review, capped at ``max_rounds`` (default
14
+ 3). Zero ``block`` findings ⇒ mergeable (``nit`` findings recorded, non-gating).
15
+ Non-convergence — or losing the panel while still failing — terminates as a
16
+ non-human ``review_gate_block`` + halt, surfaced in the run-end summary. In
17
+ ``autonomous`` mode the loop does NOT run and never spawns a panel.
18
+
19
+ These are pure, runner-importable functions with a mockable panel seam; nothing
20
+ here adds ``human_required`` and the autonomous path is a literal no-op.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass
25
+ from typing import Callable, Sequence
26
+
27
+ from .closeout_validators import ReviewFinding
28
+ from .governed_review import GateResult, governed_planning_gate
29
+ from .panel_invoker import invoke_panel
30
+
31
+ #: A class is escalated once it has failed this many times (tests or patch retries).
32
+ ESCALATION_FAIL_THRESHOLD = 2
33
+ #: Default cap on governed pre-merge review→fix rounds (bounded; never infinite).
34
+ DEFAULT_MAX_REVIEW_ROUNDS = 3
35
+
36
+ # implementer → planner is the only model_class escalation; planner is terminal.
37
+ _NEXT_CLASS = {"implementer": "planner", "worker": "implementer"}
38
+
39
+
40
+ def _non_human_blocker(blocker_class: str, summary: str) -> dict:
41
+ """Every terminal in this module is a repairable, non-human blocker."""
42
+ return {
43
+ "human_required": False,
44
+ "blocker_class": blocker_class,
45
+ "blocker_summary": summary,
46
+ "required_human_inputs": (),
47
+ }
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class EscalationDecision:
52
+ action: str # "retry" | "escalate_class" | "invoke_panel" | "terminal_blocker"
53
+ model_class: str # the class to use for the next attempt
54
+ run_mode: str
55
+ reason: str
56
+ blocker: dict | None = None # set iff action == "terminal_blocker" (non-human)
57
+
58
+
59
+ def next_escalation(
60
+ *,
61
+ model_class: str,
62
+ failed_tests: int = 0,
63
+ patch_retries: int = 0,
64
+ run_mode: str = "autonomous",
65
+ ) -> EscalationDecision:
66
+ """The mode-branched failure ladder.
67
+
68
+ Below threshold → ``retry`` (same class). At/over threshold:
69
+ ``implementer → planner`` (escalate_class); a failing ``planner`` forks by
70
+ run_mode — ``governed`` ⇒ ``invoke_panel``, ``autonomous`` ⇒ non-human
71
+ ``terminal_blocker``.
72
+ """
73
+ failing = failed_tests >= ESCALATION_FAIL_THRESHOLD or patch_retries >= ESCALATION_FAIL_THRESHOLD
74
+ if not failing:
75
+ return EscalationDecision(
76
+ action="retry", model_class=model_class, run_mode=run_mode,
77
+ reason="below escalation threshold",
78
+ )
79
+ nxt = _NEXT_CLASS.get(model_class)
80
+ if nxt is not None:
81
+ return EscalationDecision(
82
+ action="escalate_class", model_class=nxt, run_mode=run_mode,
83
+ reason=f"{model_class} failed threshold → escalate to {nxt}",
84
+ )
85
+ # planner (terminal class) still failing → branch by mode.
86
+ if run_mode == "governed":
87
+ return EscalationDecision(
88
+ action="invoke_panel", model_class=model_class, run_mode=run_mode,
89
+ reason="planner tier failing in governed mode → invoke advisor panel",
90
+ )
91
+ return EscalationDecision(
92
+ action="terminal_blocker", model_class=model_class, run_mode=run_mode,
93
+ reason="planner tier failing in autonomous mode → non-human terminal",
94
+ blocker=_non_human_blocker(
95
+ "repeated_verification_failure",
96
+ "implementer→planner escalation exhausted; autonomous run halts as a "
97
+ "repairable non-human blocker (no panel, no human_required)",
98
+ ),
99
+ )
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class LoopResult:
104
+ mergeable: bool
105
+ ran: bool
106
+ rounds: int = 0
107
+ findings: tuple[ReviewFinding, ...] = ()
108
+ degraded: bool = False
109
+ terminal_blocker: dict | None = None
110
+ reason: str | None = None
111
+
112
+
113
+ def run_governed_premerge_loop(
114
+ *,
115
+ artifact: str,
116
+ author_executor: str,
117
+ run_mode: str,
118
+ author_vendors: Sequence[str] | None = None,
119
+ apply_fix: Callable[[int, str, tuple[ReviewFinding, ...]], str] | None = None,
120
+ available_legs: Sequence[str] | None = None,
121
+ invoke: Callable[..., GateResult] = governed_planning_gate,
122
+ spawn=None,
123
+ repo_dir=None,
124
+ max_rounds: int = DEFAULT_MAX_REVIEW_ROUNDS,
125
+ max_concurrency: int | None = None,
126
+ ) -> LoopResult:
127
+ """Bounded governed pre-merge review loop.
128
+
129
+ Autonomous mode is a no-op (mergeable, no panel). Governed mode reviews up to
130
+ ``max_rounds`` times, applying ``apply_fix`` between rounds while ``block``
131
+ findings remain. Terminals are always non-human. ``author_vendors`` (the set
132
+ of vendors that authored the phase) takes precedence over ``author_executor``
133
+ for reviewer≠author exclusion when provided.
134
+ """
135
+ if run_mode != "governed":
136
+ return LoopResult(mergeable=True, ran=False, reason="autonomous")
137
+
138
+ # Reasons that mean "the gate could not run a real review" (no disjoint
139
+ # reviewer / unknown author / no usable verdict) — surfaced verbatim in the
140
+ # terminal so the operator sees the ACCURATE cause, not a generic
141
+ # "non_convergence" (CR finding).
142
+ _STRUCTURAL_HOLD = frozenset({
143
+ "unknown_author", "no_disjoint_reviewer", "author_vendor_only",
144
+ "no_reviewers", "no_usable_review",
145
+ })
146
+ seen_block = False
147
+ current = artifact
148
+ collected: list[ReviewFinding] = []
149
+ rnd = 0
150
+ last_reason: str | None = None
151
+ for rnd in range(1, max_rounds + 1):
152
+ invoke_kwargs = {
153
+ "artifact": current,
154
+ "author_executor": author_executor,
155
+ "author_vendors": author_vendors,
156
+ "run_mode": "governed",
157
+ "available_legs": available_legs,
158
+ "spawn": spawn,
159
+ }
160
+ if repo_dir is not None:
161
+ invoke_kwargs["repo_dir"] = repo_dir
162
+ # Parallel by default; threaded only when a caller requests a cap/sequential
163
+ # (byte-neutral for the default path + a strict-signature custom ``invoke``).
164
+ if max_concurrency is not None:
165
+ invoke_kwargs["max_concurrency"] = max_concurrency
166
+ gate = invoke(**invoke_kwargs)
167
+ collected.extend(gate.findings)
168
+ last_reason = gate.reason
169
+
170
+ if gate.degraded:
171
+ # FAIL-CLOSED (advisor-panel reconciliation): in governed mode, "no
172
+ # usable disjoint reviewer" is NOT an advisory pass — the prior
173
+ # advisory-pass-before-any-block was a fail-open (a codex-empty phase
174
+ # whose only disjoint reviewer was offline both rendered an empty diff
175
+ # AND advisory-passed). The gate now blocks on no-usable-review
176
+ # directly (promoted=False), so reaching here means a degraded result
177
+ # we treat as a non-human halt regardless of `seen_block`.
178
+ return LoopResult(
179
+ mergeable=False, ran=True, rounds=rnd, findings=tuple(collected),
180
+ terminal_blocker=_non_human_blocker(
181
+ "review_gate_block",
182
+ f"governed pre-merge review could not obtain a usable disjoint "
183
+ f"reviewer after {rnd} round(s); halting (non-human)",
184
+ ),
185
+ reason=gate.reason or "panel_unavailable",
186
+ )
187
+
188
+ if gate.promoted: # zero block findings; any nits already in `collected`
189
+ return LoopResult(
190
+ mergeable=True, ran=True, rounds=rnd, findings=tuple(collected),
191
+ )
192
+
193
+ # Unresolved block findings this round.
194
+ seen_block = True
195
+ if apply_fix is None or rnd == max_rounds:
196
+ break
197
+ current = apply_fix(rnd, current, gate.findings)
198
+
199
+ # Fell out of the loop while held. Distinguish a fail-closed STRUCTURAL hold
200
+ # (no disjoint reviewer / unknown author / no usable verdict — the review never
201
+ # really ran) from genuine NON-CONVERGENCE (real block findings unresolved), so
202
+ # the terminal carries the accurate cause instead of always "non_convergence".
203
+ if last_reason in _STRUCTURAL_HOLD:
204
+ summary = (
205
+ f"governed pre-merge review held ({last_reason}) — no real review could "
206
+ f"run after {rnd} round(s); halting (non-human, surfaced in run-end summary)"
207
+ )
208
+ reason = last_reason
209
+ else:
210
+ summary = (
211
+ f"governed pre-merge review did not converge to zero block findings "
212
+ f"after {rnd} round(s); halting (non-human, surfaced in run-end summary)"
213
+ )
214
+ reason = "non_convergence"
215
+ return LoopResult(
216
+ mergeable=False, ran=True, rounds=rnd, findings=tuple(collected),
217
+ terminal_blocker=_non_human_blocker("review_gate_block", summary),
218
+ reason=reason,
219
+ )