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,274 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import sys
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from .flag import branchgov_enabled, branchgov_override_explicit
9
+ from .markers import detect_pipeline_mode
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class BranchDecision:
14
+ """Outcome of a pipeline-branch governance decision (issue #44, IF-0-BRANCH-1).
15
+
16
+ ``diverged`` is True when the runner switched away from the operator's
17
+ current branch to the ``consiliency/pipeline/<version>`` convention branch —
18
+ the silent-divergence condition #44 makes visible.
19
+ """
20
+
21
+ original_branch: str
22
+ target_branch: str
23
+ action: str # "stay" | "checkout" | "create"
24
+ diverged: bool
25
+
26
+
27
+ # Stable prefix of the BranchGov default-branch refusal message. Shared so
28
+ # reconcile._blocker_precondition_cleared can identify this blocker variant
29
+ # without copying the literal string (the suffix interpolates the branch name).
30
+ REFUSE_DEFAULT_BRANCH_COMMIT_PREFIX = "Refusing git commit on default branch"
31
+
32
+ # Stable prefix of the BranchGov roadmap-orphan refusal (issue #83). The runner
33
+ # consults `roadmap_orphaned_by_branchgov` BEFORE switching and builds the
34
+ # `branch_sync_conflict` blocker from this summary; shared so downstream
35
+ # precondition-cleared logic can recognise the variant without copying the
36
+ # interpolated suffix (which names the branch and roadmap path).
37
+ REFUSE_ROADMAP_ORPHAN_PREFIX = "Refusing branch-governance switch that would orphan roadmap"
38
+
39
+
40
+ class PipelineBranchInvariantError(RuntimeError):
41
+ def __init__(self, message: str, *, blocker_class: str = "contract_bug") -> None:
42
+ super().__init__(message)
43
+ self.blocker_class = blocker_class
44
+ self.blocker_summary = message
45
+
46
+
47
+ class PipelineDefaultBranchRefusalError(PipelineBranchInvariantError):
48
+ def __init__(self, message: str) -> None:
49
+ super().__init__(message, blocker_class="branch_sync_conflict")
50
+
51
+
52
+ def ensure_pipeline_branch(
53
+ repo_root: Path,
54
+ roadmap_version: str,
55
+ default_branch: str,
56
+ *,
57
+ base_ref: str | None = None,
58
+ base_already_fetched: bool = False,
59
+ ) -> BranchDecision:
60
+ repo = Path(repo_root)
61
+ pipeline_branch = f"consiliency/pipeline/{roadmap_version}"
62
+ if not _branchgov_active(repo):
63
+ current = _current_branch(repo)
64
+ return BranchDecision(current, current, "stay", False)
65
+
66
+ current = _current_branch(repo)
67
+ if current == default_branch and _dirty_status(repo):
68
+ raise PipelineDefaultBranchRefusalError(
69
+ f"Refusing pipeline branch operation from dirty default branch {default_branch}."
70
+ )
71
+
72
+ base_ref = base_ref or f"origin/{default_branch}"
73
+ # #83: the runner may have already fetched the base once and shared it with
74
+ # the orphan preflight (so both evaluate the SAME fetched base). Skip the
75
+ # redundant fetch in that case; default preserves the standalone behaviour.
76
+ if not base_already_fetched:
77
+ _fetch_base_ref(repo, base_ref, default_branch)
78
+ if not _ref_exists(repo, base_ref):
79
+ raise PipelineBranchInvariantError(
80
+ f"Pipeline branch base ref {base_ref} is unavailable.",
81
+ blocker_class="branch_sync_conflict",
82
+ )
83
+
84
+ # Already on the convention branch: no divergence; just stay current.
85
+ if current == pipeline_branch:
86
+ rebase = _git(repo, "rebase", base_ref)
87
+ if rebase.returncode != 0:
88
+ raise PipelineBranchInvariantError(
89
+ f"Pipeline branch {pipeline_branch} could not rebase onto {base_ref}.",
90
+ blocker_class="merge_conflict",
91
+ )
92
+ return BranchDecision(current, pipeline_branch, "stay", False)
93
+
94
+ # #44: the runner is about to switch the operator's working tree from
95
+ # `current` to the convention branch. Surface it loudly instead of silently.
96
+ _warn_branch_divergence(current, pipeline_branch, roadmap_version)
97
+
98
+ if _local_branch_exists(repo, pipeline_branch):
99
+ checkout = _git(repo, "checkout", pipeline_branch)
100
+ if checkout.returncode != 0:
101
+ raise PipelineBranchInvariantError(
102
+ f"Unable to check out pipeline branch {pipeline_branch}: {_stderr_excerpt(checkout)}",
103
+ blocker_class="branch_sync_conflict",
104
+ )
105
+ rebase = _git(repo, "rebase", base_ref)
106
+ if rebase.returncode != 0:
107
+ raise PipelineBranchInvariantError(
108
+ f"Pipeline branch {pipeline_branch} could not rebase onto {base_ref}.",
109
+ blocker_class="merge_conflict",
110
+ )
111
+ return BranchDecision(current, pipeline_branch, "checkout", True)
112
+
113
+ created = _git(repo, "checkout", "-b", pipeline_branch, base_ref)
114
+ if created.returncode != 0:
115
+ raise PipelineBranchInvariantError(
116
+ f"Unable to create pipeline branch {pipeline_branch} from {base_ref}: {_stderr_excerpt(created)}",
117
+ blocker_class="branch_sync_conflict",
118
+ )
119
+ return BranchDecision(current, pipeline_branch, "create", True)
120
+
121
+
122
+ def roadmap_orphaned_by_branchgov(
123
+ repo_root: Path,
124
+ roadmap: Path,
125
+ roadmap_version: str,
126
+ default_branch: str,
127
+ *,
128
+ base_ref: str | None = None,
129
+ base_already_fetched: bool = False,
130
+ ) -> str | None:
131
+ """Issue #83 preflight: would a branch-governance switch orphan the roadmap?
132
+
133
+ Returns a human-actionable summary string when ALL of the following hold,
134
+ and ``None`` otherwise (the switch is safe / governed):
135
+
136
+ * the switch would DIVERGE — the operator is on a non-convention branch so
137
+ `ensure_pipeline_branch` would `checkout`/`create` the convention branch
138
+ (mirrors the #44 divergence condition), AND
139
+ * the roadmap artifact is committed on the current branch (tracked at HEAD)
140
+ but is NOT reachable from the pipeline-branch base (absent at
141
+ ``base_ref``) — i.e. the very file the switch would make vanish, which is
142
+ what crashes the executor with ``FileNotFoundError`` mid-run.
143
+
144
+ The base is evaluated AFTER fetching it, matching `ensure_pipeline_branch`:
145
+ otherwise a stale local ``refs/remotes/origin/<default>`` would
146
+ FALSE-POSITIVE-refuse a roadmap that is in fact pushed and on-base. When the
147
+ runner has already fetched the shared base, pass ``base_already_fetched=True``
148
+ to avoid a redundant fetch (the caller gates branchgov-active, so this
149
+ predicate does not re-check it).
150
+
151
+ Behaviour-preserving for genuinely governed runs: if the roadmap is present
152
+ at the FETCHED base (pushed / on-base) the predicate returns ``None`` and the
153
+ caller proceeds with the existing switch + ``coordinator.branch_switched``.
154
+ """
155
+ repo = Path(repo_root)
156
+
157
+ pipeline_branch = f"consiliency/pipeline/{roadmap_version}"
158
+ current = _current_branch(repo)
159
+ # No divergence when already on the convention branch (the switch is a no-op
160
+ # "stay"), or on the default branch (handled by the dirty-default refusal).
161
+ if current == pipeline_branch:
162
+ return None
163
+
164
+ base_ref = base_ref or f"origin/{default_branch}"
165
+ # Evaluate the FETCHED base (the false-positive fix). Skip the fetch when the
166
+ # runner already did it and shared the base with us.
167
+ if not base_already_fetched:
168
+ _fetch_base_ref(repo, base_ref, default_branch)
169
+ if not _ref_exists(repo, base_ref):
170
+ # Base unavailability is surfaced by ensure_pipeline_branch itself; don't
171
+ # second-guess it here.
172
+ return None
173
+
174
+ rel = _roadmap_relpath(repo, roadmap)
175
+ if rel is None:
176
+ return None
177
+ # Roadmap must be committed on the current branch (tracked at HEAD) — an
178
+ # uncommitted/untracked roadmap is a different failure mode, not an orphan.
179
+ if not _path_tracked_at(repo, "HEAD", rel):
180
+ return None
181
+ # Safe (governed) when the same roadmap is reachable from the (fetched) base:
182
+ # the switch keeps the file visible.
183
+ if _path_tracked_at(repo, base_ref, rel):
184
+ return None
185
+
186
+ return (
187
+ f"{REFUSE_ROADMAP_ORPHAN_PREFIX} '{rel}'. Branch governance would switch from "
188
+ f"'{current}' to convention branch '{pipeline_branch}' cut from '{base_ref}', but "
189
+ f"the roadmap is committed only on '{current}' and is not reachable from "
190
+ f"'{base_ref}' — the switch would make it vanish and crash the run. Push the "
191
+ f"roadmap to '{base_ref}' (or land it on the base) to fix this; or, to switch "
192
+ f"anyway, pass --allow-branchgov (PHASE_LOOP_BRANCHGOV_ENABLE=true) — the run "
193
+ f"then fails cleanly (branch_sync_conflict) if the roadmap still isn't on the base."
194
+ )
195
+
196
+
197
+ def _roadmap_relpath(repo: Path, roadmap: Path) -> str | None:
198
+ """POSIX repo-relative pathspec for the roadmap, or None if outside the repo."""
199
+ try:
200
+ rel = Path(roadmap).resolve().relative_to(Path(repo).resolve())
201
+ except (ValueError, OSError):
202
+ return None
203
+ return rel.as_posix()
204
+
205
+
206
+ def _path_tracked_at(repo: Path, ref: str, relpath: str) -> bool:
207
+ """True when ``relpath`` is a tracked object at ``ref`` (predicts the FileNotFound)."""
208
+ return _git(repo, "cat-file", "-e", f"{ref}:{relpath}").returncode == 0
209
+
210
+
211
+ def _warn_branch_divergence(current: str, target: str, roadmap_version: str) -> None:
212
+ sys.stderr.write(
213
+ f"phase-loop: switching from '{current}' to convention branch '{target}' "
214
+ f"(roadmap {roadmap_version}) per branch governance; files present only on "
215
+ f"'{current}' (e.g. a roadmap/plan not yet on '{target}') will not be "
216
+ f"visible after the switch.\n"
217
+ )
218
+
219
+
220
+ def _fetch_base_ref(repo: Path, base_ref: str, default_branch: str) -> None:
221
+ if base_ref.startswith("origin/"):
222
+ _git(repo, "fetch", "origin", base_ref.removeprefix("origin/"))
223
+ elif base_ref == f"origin/{default_branch}":
224
+ _git(repo, "fetch", "origin", default_branch)
225
+
226
+
227
+ def refuse_default_branch_commit(repo_root: Path, default_branch: str) -> None:
228
+ repo = Path(repo_root)
229
+ if not _branchgov_active(repo):
230
+ return
231
+ if _current_branch(repo) == default_branch:
232
+ raise PipelineDefaultBranchRefusalError(
233
+ f"{REFUSE_DEFAULT_BRANCH_COMMIT_PREFIX} {default_branch} while pipeline branch governance is enabled."
234
+ )
235
+
236
+
237
+ def _branchgov_active(repo: Path) -> bool:
238
+ return detect_pipeline_mode(repo) and branchgov_enabled()
239
+
240
+
241
+ def _current_branch(repo: Path) -> str:
242
+ result = _git(repo, "branch", "--show-current")
243
+ if result.returncode == 0 and result.stdout.strip():
244
+ return result.stdout.strip()
245
+ head = _git(repo, "rev-parse", "--short", "HEAD")
246
+ return f"detached@{head.stdout.strip()}" if head.returncode == 0 and head.stdout.strip() else "unknown"
247
+
248
+
249
+ def _dirty_status(repo: Path) -> str:
250
+ result = _git(repo, "status", "--porcelain", "--untracked-files=all")
251
+ return result.stdout.strip() if result.returncode == 0 else ""
252
+
253
+
254
+ def _ref_exists(repo: Path, ref: str) -> bool:
255
+ return _git(repo, "rev-parse", "--verify", "--quiet", ref).returncode == 0
256
+
257
+
258
+ def _local_branch_exists(repo: Path, branch: str) -> bool:
259
+ return _ref_exists(repo, f"refs/heads/{branch}")
260
+
261
+
262
+ def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
263
+ return subprocess.run(
264
+ ["git", "-C", str(repo), *args],
265
+ check=False,
266
+ stdout=subprocess.PIPE,
267
+ stderr=subprocess.PIPE,
268
+ text=True,
269
+ )
270
+
271
+
272
+ def _stderr_excerpt(result: subprocess.CompletedProcess[str]) -> str:
273
+ text = (result.stderr or result.stdout or "").strip()
274
+ return " ".join(text.split())[:300] or "git command failed"
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+
6
+ def branchgov_enabled() -> bool:
7
+ return os.environ.get("PHASE_LOOP_BRANCHGOV_ENABLE") != "false"
8
+
9
+
10
+ def branchgov_override_explicit() -> bool:
11
+ """True only when branch governance is EXPLICITLY opted into (issue #83).
12
+
13
+ Distinct from ``branchgov_enabled()`` (which is True for both the unset
14
+ default and ``"true"``). The default-on case is exactly the bug surface: a
15
+ locally-committed roadmap on the operator's branch would be orphaned by the
16
+ convention-branch switch. The orphan-refusal preflight must therefore key on
17
+ this *explicit* signal — set by ``--allow-branchgov`` (which exports
18
+ ``PHASE_LOOP_BRANCHGOV_ENABLE=true``) — so the unset default still refuses
19
+ and only a deliberate ``"true"`` (or ``--allow-branchgov``) switches anyway.
20
+ """
21
+ return os.environ.get("PHASE_LOOP_BRANCHGOV_ENABLE") == "true"
22
+
23
+
24
+ def trust_executor_evidence_enabled() -> bool:
25
+ return os.environ.get("PHASE_LOOP_TRUST_EXECUTOR_EVIDENCE") != "false"
26
+
27
+
28
+ def allow_lane_ir_override_enabled() -> bool:
29
+ return os.environ.get("PHASE_LOOP_ALLOW_LANE_IR_OVERRIDE") != "false"
30
+
31
+
32
+ def dispatch_lock_enabled() -> bool:
33
+ return os.environ.get("PHASE_LOOP_DISPATCH_LOCK") != "false"
34
+
35
+
36
+ def parallel_dispatch_enabled() -> bool:
37
+ return os.environ.get("PHASE_LOOP_PARALLEL_DISPATCH") != "false"
38
+
39
+
40
+ def reconcile_git_reality_enabled() -> bool:
41
+ # SAFE CUTOVER: defaults OFF (opt-in), unlike the flags above. The active body
42
+ # wires into classify_phase — the universal classifier — so on our own runtime
43
+ # the feature lands inert and is flipped on deliberately after validation.
44
+ return os.environ.get("PHASE_LOOP_RECONCILE_GIT_REALITY") == "true"
45
+
46
+
47
+ def concurrent_real_exec_integration_enabled() -> bool:
48
+ # SAFE CUTOVER: defaults OFF (opt-in). Switches the concurrent scheduler's
49
+ # worktree integration from the committed-only merge (integrate_phase_worktree)
50
+ # to dirty-work transport (transfer_phase_worktree_dirty), which lands a real
51
+ # executor's uncommitted phase-owned work onto the pipeline branch so the
52
+ # parent closeout commits it. Only active under `--phase-scheduler concurrent`
53
+ # (itself opt-in); flipped on deliberately after a real concurrent run
54
+ # validates the transport on this runtime's own repo.
55
+ return os.environ.get("PHASE_LOOP_CONCURRENT_REAL_EXEC") == "true"
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+
7
+ def detect_pipeline_mode(repo_root: Path) -> bool:
8
+ root = Path(repo_root)
9
+ return (
10
+ (root / ".pipeline").exists()
11
+ or (root / ".github" / "workflows" / "pipeline-bootstrap.yml").exists()
12
+ or os.environ.get("PHASE_LOOP_PIPELINE_MODE") == "true"
13
+ )
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+
8
+ MERGE_POLICY_ON_PASS = ("required", "auto", "never")
9
+
10
+
11
+ class MergePolicyParseError(ValueError):
12
+ pass
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class MergePolicy:
17
+ on_pass: str = "required"
18
+ approvers: tuple[str, ...] = ()
19
+
20
+ def __post_init__(self) -> None:
21
+ if self.on_pass not in MERGE_POLICY_ON_PASS:
22
+ raise MergePolicyParseError(f"invalid merge_policy.on_pass: {self.on_pass}")
23
+ normalized = tuple(str(approver).strip() for approver in self.approvers if str(approver).strip())
24
+ object.__setattr__(self, "approvers", normalized)
25
+
26
+ def to_json(self) -> dict[str, object]:
27
+ return {"on_pass": self.on_pass, "approvers": list(self.approvers)}
28
+
29
+
30
+ def parse(phase_block: dict) -> MergePolicy:
31
+ if "merge_policy" not in phase_block or phase_block.get("merge_policy") in (None, ""):
32
+ return MergePolicy(on_pass="required", approvers=())
33
+
34
+ raw = _coerce_policy_block(phase_block["merge_policy"])
35
+ on_pass = str(raw.get("on_pass") or "required").strip()
36
+ if on_pass not in MERGE_POLICY_ON_PASS:
37
+ raise MergePolicyParseError(f"invalid merge_policy.on_pass: {on_pass}")
38
+
39
+ approvers = _approver_tuple(raw.get("approvers"))
40
+ if on_pass == "required" and not approvers:
41
+ raise MergePolicyParseError("explicit merge_policy.on_pass=required requires at least one approver")
42
+ return MergePolicy(on_pass=on_pass, approvers=approvers)
43
+
44
+
45
+ def _coerce_policy_block(value: Any) -> dict[str, Any]:
46
+ if isinstance(value, dict):
47
+ return value
48
+ if isinstance(value, str):
49
+ stripped = value.strip()
50
+ if not stripped:
51
+ return {}
52
+ if stripped in MERGE_POLICY_ON_PASS:
53
+ return {"on_pass": stripped}
54
+ try:
55
+ decoded = json.loads(stripped)
56
+ except json.JSONDecodeError as exc:
57
+ raise MergePolicyParseError(f"invalid merge_policy JSON: {exc.msg}") from exc
58
+ if isinstance(decoded, dict):
59
+ return decoded
60
+ raise MergePolicyParseError("merge_policy must be an object or JSON object string")
61
+
62
+
63
+ def _approver_tuple(value: Any) -> tuple[str, ...]:
64
+ if value is None:
65
+ return ()
66
+ if isinstance(value, str):
67
+ items = value.split(",")
68
+ elif isinstance(value, (list, tuple)):
69
+ items = value
70
+ else:
71
+ raise MergePolicyParseError("merge_policy.approvers must be a list, tuple, or comma-separated string")
72
+ return tuple(str(item).strip() for item in items if str(item).strip())
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from ..git_topology import collect_git_topology
9
+ from ..runtime_paths import ensure_phase_loop_excluded, phase_loop_event_file
10
+ from ..models import utc_now
11
+ from .merge_policy import MergePolicy
12
+
13
+
14
+ TRIGGER_PATH = Path(".pipeline") / "ratification-trigger.json"
15
+
16
+
17
+ def emit_ratification_passed(
18
+ repo_root: Path,
19
+ roadmap_version: str,
20
+ phase_alias: str,
21
+ ratification_gate: str,
22
+ merge_policy: MergePolicy,
23
+ audit: dict[str, Any],
24
+ ) -> None:
25
+ repo = Path(repo_root)
26
+ payload = _payload(repo, roadmap_version, phase_alias, ratification_gate, merge_policy, audit)
27
+ ensure_phase_loop_excluded(repo)
28
+ event_path = phase_loop_event_file(repo)
29
+ event_path.parent.mkdir(parents=True, exist_ok=True)
30
+ event = {
31
+ "timestamp": utc_now(),
32
+ "repo": str(repo),
33
+ "roadmap": roadmap_version,
34
+ "phase": phase_alias,
35
+ "event_type": "ratification.passed",
36
+ "action": "ratification",
37
+ "status": "passed",
38
+ "source": "pipeline_adapter",
39
+ "schema_version": 2,
40
+ "payload": payload,
41
+ "git_topology": collect_git_topology(repo),
42
+ }
43
+ with event_path.open("a", encoding="utf-8") as handle:
44
+ handle.write(json.dumps(event, sort_keys=True) + "\n")
45
+
46
+ trigger = repo / TRIGGER_PATH
47
+ trigger.parent.mkdir(parents=True, exist_ok=True)
48
+ trigger.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
49
+
50
+
51
+ def _payload(
52
+ repo: Path,
53
+ roadmap_version: str,
54
+ phase_alias: str,
55
+ ratification_gate: str,
56
+ merge_policy: MergePolicy,
57
+ audit: dict[str, Any],
58
+ ) -> dict[str, Any]:
59
+ return {
60
+ "roadmap_version": roadmap_version,
61
+ "phase_alias": phase_alias,
62
+ "ratification_gate": ratification_gate,
63
+ "merge_policy": merge_policy.to_json(),
64
+ "audit": dict(audit),
65
+ "pipeline_branch": _current_branch(repo),
66
+ "default_branch": _default_branch(repo),
67
+ "head_sha": _git_output_or_empty(repo, "rev-parse", "HEAD") or None,
68
+ "merge_pr_title": f"{roadmap_version} phase {phase_alias} ratification: {ratification_gate}",
69
+ }
70
+
71
+
72
+ def _current_branch(repo: Path) -> str:
73
+ return _git_output_or_empty(repo, "branch", "--show-current") or "detached"
74
+
75
+
76
+ def _default_branch(repo: Path) -> str:
77
+ remote_head = _git_output_or_empty(repo, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
78
+ if remote_head.startswith("origin/"):
79
+ return remote_head.removeprefix("origin/")
80
+ # Authoritative fallback: ask the remote for its HEAD when origin/HEAD is unset.
81
+ # The previous fallback used @{upstream} which returns the current branch's
82
+ # tracking ref and could mis-identify the pipeline branch as the default.
83
+ ls_remote = _git_output_or_empty(repo, "ls-remote", "--symref", "origin", "HEAD")
84
+ for line in ls_remote.splitlines():
85
+ if line.startswith("ref: refs/heads/"):
86
+ ref = line.split("\t", 1)[0].removeprefix("ref: refs/heads/").strip()
87
+ if ref:
88
+ return ref
89
+ return "main"
90
+
91
+
92
+ def _git_output_or_empty(repo: Path, *args: str) -> str:
93
+ try:
94
+ return subprocess.check_output(
95
+ ["git", "-C", str(repo), *args],
96
+ text=True,
97
+ stderr=subprocess.DEVNULL,
98
+ ).strip()
99
+ except Exception:
100
+ return ""
@@ -0,0 +1,123 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import PurePosixPath
4
+
5
+
6
+ LOCKFILE_NAMES = {"package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"}
7
+ ENV_SOURCE_NAMES = {"env.ts", "env.js", "env.mjs", "env.cjs", "environment.ts", "environment.js"}
8
+
9
+
10
+ def validate_phase_owned_evidence(
11
+ declared_paths: tuple[str, ...] | list[str],
12
+ actual_paths: tuple[str, ...] | list[str],
13
+ evidence: object,
14
+ ) -> tuple[dict[str, str], ...]:
15
+ declared = tuple(filter(None, (_clean_path(path) for path in declared_paths)))
16
+ actual = set(filter(None, (_clean_path(path) for path in actual_paths)))
17
+ accepted: list[dict[str, str]] = []
18
+ seen: set[str] = set()
19
+
20
+ for item in _evidence_items(evidence):
21
+ evidence_path = _clean_path(str(item.get("path") or item.get("actual_path") or item.get("sibling") or ""))
22
+ if not evidence_path or evidence_path not in actual or evidence_path in seen:
23
+ continue
24
+ match = _matching_declared_path(evidence_path, declared)
25
+ if match is None:
26
+ continue
27
+ accepted.append(
28
+ {
29
+ "path": evidence_path,
30
+ "declared_path": match[0],
31
+ "kind": match[1],
32
+ }
33
+ )
34
+ seen.add(evidence_path)
35
+
36
+ return tuple(accepted)
37
+
38
+
39
+ def _evidence_items(evidence: object) -> tuple[dict[str, object], ...]:
40
+ if not isinstance(evidence, list):
41
+ return ()
42
+ items: list[dict[str, object]] = []
43
+ for item in evidence:
44
+ if isinstance(item, str):
45
+ items.append({"path": item})
46
+ elif isinstance(item, dict):
47
+ items.append(item)
48
+ return tuple(items)
49
+
50
+
51
+ def _clean_path(path: str) -> str:
52
+ path = path.replace("\\", "/").strip().strip("/")
53
+ if not path or path.startswith("/") or path.startswith("../") or "/../" in path or path == "..":
54
+ return ""
55
+ return path
56
+
57
+
58
+ def _matching_declared_path(path: str, declared_paths: tuple[str, ...]) -> tuple[str, str] | None:
59
+ for declared in declared_paths:
60
+ if _is_test_for(path, declared):
61
+ return declared, "test"
62
+ if _is_snapshot_for(path, declared):
63
+ return declared, "snapshot"
64
+ if _is_migration_peer(path, declared):
65
+ return declared, "migration_timestamp"
66
+ if _is_lockfile_for_manifest(path, declared):
67
+ return declared, "package_lock"
68
+ if _is_env_example_for(path, declared):
69
+ return declared, "env_example"
70
+ return None
71
+
72
+
73
+ def _stem_without_test_suffix(name: str) -> str:
74
+ for suffix in (".test", ".spec"):
75
+ if name.endswith(suffix):
76
+ return name[: -len(suffix)]
77
+ return name
78
+
79
+
80
+ def _is_test_for(path: str, declared: str) -> bool:
81
+ candidate = PurePosixPath(path)
82
+ source = PurePosixPath(declared)
83
+ if "__tests__" not in candidate.parts and not candidate.name.startswith("test_"):
84
+ return False
85
+ candidate_stem = _stem_without_test_suffix(candidate.stem)
86
+ source_stem = source.stem
87
+ if candidate.name.startswith("test_"):
88
+ candidate_stem = candidate_stem.removeprefix("test_")
89
+ return candidate_stem == source_stem
90
+
91
+
92
+ def _is_snapshot_for(path: str, declared: str) -> bool:
93
+ candidate = PurePosixPath(path)
94
+ source = PurePosixPath(declared)
95
+ return "__snapshots__" in candidate.parts and candidate.stem.startswith(source.stem)
96
+
97
+
98
+ def _migration_timestamp(path: str) -> str | None:
99
+ candidate = PurePosixPath(path)
100
+ if "migrations" not in candidate.parts:
101
+ return None
102
+ prefix = candidate.name.split("_", 1)[0]
103
+ return prefix if prefix.isdigit() and len(prefix) >= 8 else None
104
+
105
+
106
+ def _is_migration_peer(path: str, declared: str) -> bool:
107
+ path_ts = _migration_timestamp(path)
108
+ declared_ts = _migration_timestamp(declared)
109
+ return bool(path_ts and declared_ts and path_ts == declared_ts and path != declared)
110
+
111
+
112
+ def _is_lockfile_for_manifest(path: str, declared: str) -> bool:
113
+ candidate = PurePosixPath(path)
114
+ source = PurePosixPath(declared)
115
+ return candidate.parent == source.parent and candidate.name in LOCKFILE_NAMES and source.name == "package.json"
116
+
117
+
118
+ def _is_env_example_for(path: str, declared: str) -> bool:
119
+ candidate = PurePosixPath(path)
120
+ source = PurePosixPath(declared)
121
+ if candidate.name != ".env.example":
122
+ return False
123
+ return candidate.parent == source.parent and (source.name in ENV_SOURCE_NAMES or "env" in source.stem.lower())