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,120 @@
1
+ """GATE (roadmap v40) — sensitivity classifier for beyond-ownership dirty paths.
2
+
3
+ `classify_unowned_path(repo_relpath)` maps a repo-relative path to a
4
+ ``SensitivityVerdict`` whose ``sensitivity_class`` is a member of
5
+ ``models.SENSITIVITY_CLASSES``. The graduated closeout gate auto-commits a
6
+ verified beyond-ownership path only when its verdict is ``safe``; everything else
7
+ blocks (deny-by-default).
8
+
9
+ Precedence is load-bearing and deny-by-default:
10
+ 1. UNSAFE-specific patterns first — secrets, lockfiles, CI config. These must win
11
+ over any broad SAFE rule (e.g. a ``.github/workflows/*.yml`` is CI, not docs).
12
+ 2. tests → ``source`` (UNSAFE). Test paths only ever earn owned status via
13
+ structural sibling matching upstream; a test reaching this classifier failed
14
+ that and must not auto-commit.
15
+ 3. narrow SAFE rules — plans, handoffs, docs, and a *tight* config_nonsource
16
+ allowlist (never a ``.toml``/``.yaml``/``.json`` suffix rule).
17
+ 4. fall through → ``source`` (UNSAFE). Unmatched is never SAFE.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass
23
+ from pathlib import PurePosixPath
24
+
25
+ from .models import SAFE_SENSITIVITY_CLASSES
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class SensitivityVerdict:
30
+ sensitivity_class: str
31
+ safe: bool
32
+
33
+
34
+ # Tight allowlists — membership, not broad suffix rules.
35
+ _CONFIG_NONSOURCE_NAMES = frozenset(
36
+ {".gitignore", ".gitattributes", ".editorconfig", ".dockerignore", ".npmrc", ".prettierrc"}
37
+ )
38
+ _CONFIG_NONSOURCE_SUFFIXES = frozenset({".cfg", ".ini"})
39
+
40
+ _SECRET_SUFFIXES = frozenset({".pem", ".key", ".p12", ".pfx", ".crt", ".keystore", ".jks"})
41
+ _LOCKFILE_NAMES = frozenset(
42
+ {
43
+ "package-lock.json",
44
+ "pnpm-lock.yaml",
45
+ "yarn.lock",
46
+ "uv.lock",
47
+ "poetry.lock",
48
+ "cargo.lock",
49
+ "go.sum",
50
+ "gemfile.lock",
51
+ "composer.lock",
52
+ "requirements.txt",
53
+ }
54
+ )
55
+ # Bare suffix → docs only for unambiguous documentation formats. A `.txt` is NOT
56
+ # auto-docs (e.g. src/foo.txt is source-adjacent); it is docs only under docs/.
57
+ _DOC_SUFFIXES = frozenset({".md", ".rst"})
58
+
59
+
60
+ def _verdict(sensitivity_class: str) -> SensitivityVerdict:
61
+ return SensitivityVerdict(
62
+ sensitivity_class=sensitivity_class,
63
+ safe=sensitivity_class in SAFE_SENSITIVITY_CLASSES,
64
+ )
65
+
66
+
67
+ def classify_unowned_path(repo_relpath: str) -> SensitivityVerdict:
68
+ raw = (repo_relpath or "").strip()
69
+ # Normalize: strip leading "./", lowercase for matching.
70
+ norm = raw[2:] if raw.startswith("./") else raw
71
+ lower = norm.lower()
72
+ posix = PurePosixPath(lower)
73
+ name = posix.name
74
+ suffix = posix.suffix
75
+ parts = posix.parts
76
+ slashed = "/" + lower # so "/tests/" infix matches a leading "tests/" too
77
+
78
+ # --- 1. UNSAFE-specific patterns first (precedence) ---
79
+ # secrets
80
+ if (
81
+ name == ".env"
82
+ or name.startswith(".env.")
83
+ or suffix in _SECRET_SUFFIXES
84
+ or "secrets" in parts
85
+ ):
86
+ return _verdict("secrets")
87
+ # lockfiles
88
+ if name in _LOCKFILE_NAMES or name.endswith(".lock") or name.endswith("-lock.json"):
89
+ return _verdict("lockfile")
90
+ # CI config
91
+ if (
92
+ any(part in {".github", ".gitlab", ".circleci", ".gitea"} for part in parts)
93
+ or lower.startswith("ci/")
94
+ or "/workflows/" in slashed
95
+ ):
96
+ return _verdict("ci")
97
+ # tests → source (UNSAFE) — GATE decision (see plan/IF-0-GATE-1)
98
+ if (
99
+ "/tests/" in slashed
100
+ or "__tests__" in parts
101
+ or "__fixtures__" in parts
102
+ or name.startswith("test_")
103
+ or name.endswith("_test.py")
104
+ or ".test." in name
105
+ or ".spec." in name
106
+ ):
107
+ return _verdict("source")
108
+
109
+ # --- 2. narrow SAFE rules ---
110
+ if lower.startswith("plans/"):
111
+ return _verdict("plans")
112
+ if ".dev-skills/handoffs/" in slashed:
113
+ return _verdict("handoffs")
114
+ if "/docs/" in slashed or name == "readme.md" or suffix in _DOC_SUFFIXES:
115
+ return _verdict("docs")
116
+ if name in _CONFIG_NONSOURCE_NAMES or suffix in _CONFIG_NONSOURCE_SUFFIXES:
117
+ return _verdict("config_nonsource")
118
+
119
+ # --- 3. deny-by-default: everything else (incl. .py/.toml/.yaml/.json/.sh) ---
120
+ return _verdict("source")
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from pathlib import Path, PurePosixPath
7
+
8
+
9
+ AUDIT_STATUSES = ("passed", "drift_detected", "skipped")
10
+ MAX_CLAIMS = 32
11
+ MAX_DIFF_PATHS = 64
12
+
13
+ CLAIM_RE = re.compile(r"\b(?:Added|Created|Wrote|Updated)\s+`([^`]+)`", re.IGNORECASE)
14
+ BULLET_CLAIM_RE = re.compile(r"^\s*[-*]\s+[A-Za-z][A-Za-z0-9_-]*\s+`([^`]+)`", re.MULTILINE)
15
+ IDENTIFIER_RE = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b")
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class AuditResult:
20
+ matched_claims: list[str]
21
+ unmatched_claims: list[str]
22
+ audit_status: str
23
+
24
+
25
+ def audit_closeout_evidence(commit_sha: str, phase_alias: str, repo_root: Path) -> AuditResult:
26
+ commit_body = _git_output(repo_root, "log", "-1", "--format=%B", commit_sha)
27
+ claims = _parse_claims(commit_body)
28
+ if not claims:
29
+ return AuditResult(matched_claims=[], unmatched_claims=[], audit_status="skipped")
30
+
31
+ diff_paths = _diff_paths(repo_root, commit_sha)
32
+ diff_body = _git_output(repo_root, "show", "--format=", "--unified=0", commit_sha)
33
+ matched: list[str] = []
34
+ unmatched: list[str] = []
35
+ for claim in claims:
36
+ if _claim_matches_evidence(claim, diff_paths, diff_body):
37
+ matched.append(claim)
38
+ else:
39
+ unmatched.append(claim)
40
+
41
+ return AuditResult(
42
+ matched_claims=matched,
43
+ unmatched_claims=unmatched,
44
+ audit_status="drift_detected" if unmatched else "passed",
45
+ )
46
+
47
+
48
+ def _parse_claims(text: str) -> list[str]:
49
+ claims: list[str] = []
50
+ for pattern in (CLAIM_RE, BULLET_CLAIM_RE):
51
+ for match in pattern.finditer(text):
52
+ claim = match.group(1).strip()
53
+ if claim and claim not in claims:
54
+ claims.append(claim)
55
+ if len(claims) >= MAX_CLAIMS:
56
+ return claims
57
+ return claims
58
+
59
+
60
+ def _diff_paths(repo_root: Path, commit_sha: str) -> list[str]:
61
+ names = _git_output(repo_root, "show", "--format=", "--name-only", commit_sha)
62
+ paths: list[str] = []
63
+ for line in names.splitlines():
64
+ path = line.strip()
65
+ if not path or path in paths:
66
+ continue
67
+ paths.append(path)
68
+ if len(paths) >= MAX_DIFF_PATHS:
69
+ break
70
+ return paths
71
+
72
+
73
+ def _claim_matches_evidence(claim: str, diff_paths: list[str], diff_body: str) -> bool:
74
+ normalized = claim.strip().lstrip("/")
75
+ if not normalized:
76
+ return False
77
+ normalized_posix = normalized.replace("\\", "/")
78
+ claim_path = PurePosixPath(normalized_posix)
79
+ claim_basename = claim_path.name
80
+ claim_stem = claim_path.stem
81
+ for path in diff_paths:
82
+ path_posix = path.replace("\\", "/")
83
+ path_path = PurePosixPath(path_posix)
84
+ if path_posix.endswith(normalized_posix) or path_posix.endswith(f"/{normalized_posix}"):
85
+ return True
86
+ if claim_basename and claim_basename == path_path.name:
87
+ return True
88
+ if claim_stem and claim_stem == path_path.stem:
89
+ return True
90
+ return any(identifier in diff_body for identifier in _identifier_candidates(claim))
91
+
92
+
93
+ def _identifier_candidates(claim: str) -> list[str]:
94
+ candidates: list[str] = []
95
+ for token in IDENTIFIER_RE.findall(claim):
96
+ if len(token) < 4:
97
+ continue
98
+ if not any(char.isupper() for char in token) and "_" not in token:
99
+ continue
100
+ if token not in candidates:
101
+ candidates.append(token)
102
+ return candidates
103
+
104
+
105
+ def _git_output(repo_root: Path, *args: str) -> str:
106
+ result = subprocess.run(
107
+ ["git", "-C", str(repo_root), *args],
108
+ text=True,
109
+ capture_output=True,
110
+ check=True,
111
+ )
112
+ return result.stdout
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any, Mapping
7
+
8
+
9
+ IF_GATE_RE = re.compile(r"\bIF-\d+-[A-Z0-9_-]+-\d+\b")
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class ValidationResult:
14
+ ok: bool
15
+ warning: str | None = None
16
+ blocker_class: str | None = None
17
+ blocker_summary: str | None = None
18
+ expected_gates: tuple[str, ...] = ()
19
+ produced_gates: tuple[str, ...] = ()
20
+ missing_gates: tuple[str, ...] = ()
21
+ unexpected_gates: tuple[str, ...] = ()
22
+
23
+ def to_json(self) -> dict[str, Any]:
24
+ return {
25
+ "ok": self.ok,
26
+ "warning": self.warning,
27
+ "blocker_class": self.blocker_class,
28
+ "blocker_summary": self.blocker_summary,
29
+ "expected_gates": list(self.expected_gates),
30
+ "produced_gates": list(self.produced_gates),
31
+ "missing_gates": list(self.missing_gates),
32
+ "unexpected_gates": list(self.unexpected_gates),
33
+ }
34
+
35
+
36
+ def extract_plan_produces(plan_path: Path) -> tuple[str, ...]:
37
+ try:
38
+ text = plan_path.read_text(encoding="utf-8")
39
+ except OSError:
40
+ return ()
41
+
42
+ gates: list[str] = []
43
+ for line in text.splitlines():
44
+ stripped = line.strip()
45
+ if not (
46
+ stripped.startswith("**Produces**:")
47
+ or stripped.startswith("Produces:")
48
+ or "**Interfaces provided**:" in stripped
49
+ ):
50
+ continue
51
+ for match in IF_GATE_RE.finditer(line):
52
+ gate = match.group(0)
53
+ if gate not in gates:
54
+ gates.append(gate)
55
+ return tuple(gates)
56
+
57
+
58
+ def validate_produced_gates(plan_path: Path, closeout_payload: dict[str, Any]) -> ValidationResult:
59
+ terminal_status = str(
60
+ closeout_payload.get("terminal_status")
61
+ or closeout_payload.get("automation_status")
62
+ or ""
63
+ )
64
+ expected = extract_plan_produces(plan_path)
65
+ if "produced_if_gates" not in closeout_payload:
66
+ if terminal_status == "complete":
67
+ return ValidationResult(
68
+ ok=True,
69
+ warning="produced_if_gates missing during NATIVE compatibility window",
70
+ expected_gates=expected,
71
+ )
72
+ return ValidationResult(ok=True, expected_gates=expected)
73
+
74
+ produced_raw = _normalize_gates(closeout_payload.get("produced_if_gates"))
75
+ # Filter to canonical IF-gate tokens. Executors sometimes emit free-text
76
+ # description sentences in produced_if_gates (e.g. when the plan declares
77
+ # no IF gate, codex has been observed outputting
78
+ # ["...verified...; active plan declares no interface-freeze gate"]).
79
+ # Treat non-IF-token entries as commentary, not as produced gates.
80
+ produced = tuple(g for g in produced_raw if IF_GATE_RE.fullmatch(g))
81
+ non_gate_chatter = tuple(g for g in produced_raw if not IF_GATE_RE.fullmatch(g))
82
+ missing = tuple(gate for gate in expected if gate not in produced)
83
+ unexpected = tuple(gate for gate in produced if gate not in expected)
84
+
85
+ # If the plan declares no IF gates, the phase is internal/tooling and
86
+ # cannot fail the contract check. Allow with a warning if the executor
87
+ # emitted any chatter.
88
+ if not expected:
89
+ warning = None
90
+ if non_gate_chatter:
91
+ warning = (
92
+ "executor produced non-IF-gate strings in produced_if_gates "
93
+ "for a plan that declares no IF gates; treated as commentary"
94
+ )
95
+ return ValidationResult(
96
+ ok=True,
97
+ warning=warning,
98
+ expected_gates=expected,
99
+ produced_gates=produced,
100
+ missing_gates=missing,
101
+ unexpected_gates=unexpected,
102
+ )
103
+
104
+ if terminal_status == "complete" and (not produced or missing or unexpected):
105
+ summary = "completed closeout produced_if_gates did not match the active phase plan"
106
+ if not produced:
107
+ summary = "completed closeout reported zero produced_if_gates"
108
+ return ValidationResult(
109
+ ok=False,
110
+ blocker_class="contract_bug",
111
+ blocker_summary=summary,
112
+ expected_gates=expected,
113
+ produced_gates=produced,
114
+ missing_gates=missing,
115
+ unexpected_gates=unexpected,
116
+ )
117
+
118
+ return ValidationResult(
119
+ ok=True,
120
+ expected_gates=expected,
121
+ produced_gates=produced,
122
+ missing_gates=missing,
123
+ unexpected_gates=unexpected,
124
+ )
125
+
126
+
127
+ def verification_enforcement_mode(env: Mapping[str, str] | None = None) -> str:
128
+ value = str((env or {}).get("PHASE_LOOP_VERIFY_ENFORCE") or "").strip().lower()
129
+ return "warn" if value == "warn" else "hard"
130
+
131
+
132
+ def _normalize_gates(value: Any) -> tuple[str, ...]:
133
+ if isinstance(value, str):
134
+ raw_items = re.split(r"[,\s]+", value)
135
+ elif isinstance(value, (list, tuple, set)):
136
+ raw_items = [str(item) for item in value]
137
+ else:
138
+ raw_items = []
139
+ gates: list[str] = []
140
+ for item in raw_items:
141
+ gate = item.strip().strip("'\"")
142
+ if gate and gate not in gates:
143
+ gates.append(gate)
144
+ return tuple(gates)
@@ -0,0 +1,203 @@
1
+ """Pluggable closeout-validator hook (rigor-v1 P1).
2
+
3
+ The single seam through which review gates (doc-delta, verification-evidence,
4
+ visual-evidence, …) plug into the closeout pass/fail decision **without** each
5
+ gate editing ``closeout.py``'s status logic. ``build_phase_loop_closeout`` runs
6
+ the registered validators once and applies their findings.
7
+
8
+ Autonomy-first severity model (see ``specs/phase-plans-v1.md`` and the
9
+ ``[[phase-loop-autonomy-first-guardrails]]`` constraint):
10
+
11
+ * Each finding carries a severity of ``warn`` or ``block``.
12
+ * The global ``PHASE_LOOP_REVIEW`` control (``off`` | ``warn`` | ``block``,
13
+ **default ``warn``**) sets the effective behavior:
14
+ - ``off`` — validators do not run; no findings.
15
+ - ``warn`` — validators run; every finding is forced to ``warn`` (recorded
16
+ to the closeout for later human spot-check, the loop **continues**).
17
+ - ``block`` — validators run; a ``block`` finding refuses ``complete``.
18
+ * **No validator may set ``human_required``.** A blocking finding produces a
19
+ non-human, agent-recoverable blocker — never a stall waiting on a person.
20
+
21
+ Back-compat: with zero validators registered (the state shipped by P1), the
22
+ runner returns no findings and closeout behavior is byte-for-byte unchanged.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import os
27
+ from dataclasses import dataclass, field, replace
28
+ from typing import Any, Callable, Iterable, Mapping
29
+
30
+ ReviewSeverity = str # "warn" | "block"
31
+ REVIEW_SEVERITIES: tuple[str, ...] = ("warn", "block")
32
+ REVIEW_MODES: tuple[str, ...] = ("off", "warn", "block")
33
+ DEFAULT_REVIEW_MODE = "warn"
34
+ REVIEW_MODE_ENV = "PHASE_LOOP_REVIEW"
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ReviewFinding:
39
+ """A single review-gate observation about a closeout."""
40
+
41
+ code: str
42
+ reason: str
43
+ severity: ReviewSeverity = "warn"
44
+ blocker_class: str | None = None
45
+
46
+ def __post_init__(self) -> None:
47
+ if self.severity not in REVIEW_SEVERITIES:
48
+ raise ValueError(f"invalid review severity: {self.severity!r}")
49
+
50
+ def to_json(self) -> dict[str, Any]:
51
+ payload: dict[str, Any] = {
52
+ "kind": "review_finding",
53
+ "code": self.code,
54
+ "reason": self.reason,
55
+ "severity": self.severity,
56
+ }
57
+ if self.blocker_class is not None:
58
+ payload["blocker_class"] = self.blocker_class
59
+ return payload
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class CloseoutContext:
64
+ """Read-only view of the closeout a validator inspects."""
65
+
66
+ phase_alias: str
67
+ plan_path: str
68
+ terminal: Mapping[str, Any] = field(default_factory=dict)
69
+ automation: Mapping[str, Any] = field(default_factory=dict)
70
+ blocker: Mapping[str, Any] = field(default_factory=dict)
71
+ changed_paths: tuple[str, ...] = ()
72
+ # model-routing-v1 P2: the run_mode axis (autonomous default | governed).
73
+ # Carried so a validator can tell which mode it is in; governed-only review
74
+ # machinery lives in `governed_review`, never in this closeout registry.
75
+ run_mode: str = "autonomous"
76
+ # issue #18 F5: the runner-side docs-freshness pre-scan result (or None when
77
+ # unwired). Threaded read-only so a validator can CORROBORATE a self-attested
78
+ # decision (e.g. `no_doc_delta`) against the path-keyed scan evidence without
79
+ # doing repo IO itself (validators stay pure). None => no corroboration
80
+ # available => the validator must not newly-fail on its absence.
81
+ docs_freshness: Mapping[str, Any] | None = None
82
+
83
+
84
+ # A validator receives the context and returns zero or more findings.
85
+ CloseoutValidator = Callable[[CloseoutContext], Iterable[ReviewFinding]]
86
+
87
+ _VALIDATORS: list[CloseoutValidator] = []
88
+
89
+
90
+ def register_closeout_validator(fn: CloseoutValidator) -> CloseoutValidator:
91
+ """Register a closeout validator. Returns ``fn`` so it can be used as a decorator."""
92
+ if fn not in _VALIDATORS:
93
+ _VALIDATORS.append(fn)
94
+ return fn
95
+
96
+
97
+ def clear_closeout_validators() -> None:
98
+ """Drop all registered validators (test hook)."""
99
+ _VALIDATORS.clear()
100
+
101
+
102
+ def registered_closeout_validators() -> tuple[CloseoutValidator, ...]:
103
+ return tuple(_VALIDATORS)
104
+
105
+
106
+ def resolve_review_mode(env: Mapping[str, str] | None = None) -> str:
107
+ env = os.environ if env is None else env
108
+ value = str(env.get(REVIEW_MODE_ENV) or "").strip().lower()
109
+ return value if value in REVIEW_MODES else DEFAULT_REVIEW_MODE
110
+
111
+
112
+ def run_closeout_validators(
113
+ ctx: CloseoutContext,
114
+ env: Mapping[str, str] | None = None,
115
+ ) -> list[ReviewFinding]:
116
+ """Run every registered validator and return findings at their effective severity.
117
+
118
+ A validator that raises is skipped — a review gate must never break closeout.
119
+ """
120
+ mode = resolve_review_mode(env)
121
+ if mode == "off":
122
+ return []
123
+ findings: list[ReviewFinding] = []
124
+ for fn in tuple(_VALIDATORS):
125
+ try:
126
+ produced = fn(ctx) or ()
127
+ except Exception:
128
+ continue
129
+ for finding in produced:
130
+ effective = "warn" if mode == "warn" else finding.severity
131
+ findings.append(replace(finding, severity=effective))
132
+ return findings
133
+
134
+
135
+ def apply_review_findings(
136
+ *,
137
+ findings: list[ReviewFinding],
138
+ terminal: dict[str, Any],
139
+ automation: dict[str, Any],
140
+ blocker: dict[str, Any],
141
+ ) -> dict[str, Any]:
142
+ """Fold findings into the closeout dicts.
143
+
144
+ ``warn`` findings are recorded as results (audit trail) and do not change
145
+ the outcome. The first ``block`` finding turns the closeout into a
146
+ non-human ``blocked`` outcome, mirroring the verification-evidence gate.
147
+ """
148
+ updated_terminal = dict(terminal)
149
+ updated_automation = dict(automation)
150
+ updated_blocker = dict(blocker)
151
+ results = [f.to_json() for f in findings]
152
+
153
+ blocking = next((f for f in findings if f.severity == "block"), None)
154
+ if blocking is not None:
155
+ blocker_class = blocking.blocker_class or "review_gate_block"
156
+ summary = f"Review gate blocked closeout: {blocking.code} — {blocking.reason}"
157
+ updated_terminal["terminal_status"] = "blocked"
158
+ updated_terminal["verification_status"] = "blocked"
159
+ updated_automation["status"] = "blocked"
160
+ updated_automation["verification_status"] = "blocked"
161
+ updated_automation["blocker_class"] = blocker_class
162
+ updated_automation["blocker_summary"] = summary
163
+ updated_automation["human_required"] = False
164
+ updated_blocker.update(
165
+ {
166
+ "human_required": False,
167
+ "blocker_class": blocker_class,
168
+ "blocker_summary": summary,
169
+ "required_human_inputs": (),
170
+ }
171
+ )
172
+ return {
173
+ "terminal": updated_terminal,
174
+ "automation": updated_automation,
175
+ "blocker": updated_blocker,
176
+ "results": results,
177
+ }
178
+
179
+
180
+ def load_builtin_closeout_validators() -> None:
181
+ """Import the built-in validator modules so they self-register.
182
+
183
+ Extension point: each downstream rigor phase adds its validator module
184
+ (e.g. ``doc_delta_validator``) and one guarded import line here. Imports are
185
+ guarded so an incremental checkout missing a module never breaks closeout.
186
+ P1 ships no built-in validators — the registry is empty by default.
187
+ """
188
+ try:
189
+ from . import doc_delta_validator # noqa: F401 (P2)
190
+ except Exception:
191
+ pass
192
+ try:
193
+ from . import verification_evidence_validator # noqa: F401 (P5)
194
+ except Exception:
195
+ pass
196
+ try:
197
+ from . import visual_evidence_validator # noqa: F401 (P6)
198
+ except Exception:
199
+ pass
200
+ return None
201
+
202
+
203
+ load_builtin_closeout_validators()