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,523 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from collections.abc import Iterator
5
+ from fnmatch import fnmatchcase
6
+ from pathlib import Path
7
+
8
+ from .models import (
9
+ LaneDependency,
10
+ LaneIRDiagnostic,
11
+ LaneTaskSet,
12
+ PhasePlanIR,
13
+ PhasePlanLane,
14
+ )
15
+
16
+
17
+ LANE_HEADING_RE = re.compile(
18
+ r"^###\s+(?P<id>(?:SL|SG|LG|CG|GM|OP|CMD)-\d+[A-Z]?|[A-Z][A-Z0-9]+-\d+[A-Z]?|Lane\s+\d+|Swim\s*lane\s+\d+)\s*(?:[-—:]\s*(?P<name>.+?))?\s*$",
19
+ re.IGNORECASE | re.MULTILINE,
20
+ )
21
+ LANE_INDEX_LINE_RE = re.compile(
22
+ r"^\s*-?\s*(?P<id>(?:SL|SG|LG|CG|GM|OP|CMD)-\d+[A-Z]?|[A-Z][A-Z0-9]+-\d+[A-Z]?|Lane\s+\d+|Swim\s*lane\s+\d+)\s*(?:[-—:]\s*(?P<name>.*?))?(?:;\s*(?P<meta>.*))?$",
23
+ re.IGNORECASE,
24
+ )
25
+ FIELD_RE = re.compile(r"^\s*(?:-\s+)?\*\*(?P<label>[^*]+)\*\*:\s*(?P<value>.+?)\s*$", re.IGNORECASE)
26
+ TASK_RE = re.compile(r"^\s*-\s*(?P<bucket>test|impl|verify)\s*:\s*(?P<value>.+?)\s*$", re.IGNORECASE)
27
+ ROADMAP_PHASE_RE = re.compile(
28
+ r"^###\s+Phase\s+\d+(?:\.\d+)?[A-Z]?\s+.*?\((?P<alias>[A-Z][A-Z0-9._-]*)(?:\s*,[^)]*)?\)[ \t]*(?:\S[^\n]*)?$",
29
+ re.MULTILINE,
30
+ )
31
+ ROADMAP_DEPENDS_RE = re.compile(
32
+ r"^\s*\*\*Depends on\*\*\s*$\n(?P<body>.*?)(?=^\s*\*\*[A-Z][^*]*\*\*|^---\s*$|^##\s+\S|^###\s+Phase|\Z)",
33
+ re.IGNORECASE | re.MULTILINE | re.DOTALL,
34
+ )
35
+ ROADMAP_DAG_SECTION_RE = re.compile(
36
+ r"^##\s+Phase Dependency DAG\s*$\n(?P<body>.*?)(?=^##\s+\S|\Z)",
37
+ re.IGNORECASE | re.MULTILINE | re.DOTALL,
38
+ )
39
+ ROADMAP_ARROW_RE = re.compile(r"\s*(?:[-=]+>|[─-]+>|→)\s*")
40
+ ROADMAP_ALIAS_TOKEN_RE = re.compile(r"\b[A-Z][A-Z0-9._-]*\b")
41
+
42
+
43
+ def iter_waves(roadmap_path: Path) -> Iterator[tuple[str, ...]]:
44
+ """Yield roadmap phase aliases in dependency waves."""
45
+ text = roadmap_path.read_text(encoding="utf-8")
46
+ aliases = tuple(match.group("alias").strip().upper() for match in ROADMAP_PHASE_RE.finditer(text))
47
+ if not aliases:
48
+ return
49
+
50
+ dependencies = _roadmap_phase_dependencies(text, aliases)
51
+ emitted: set[str] = set()
52
+ remaining = set(aliases)
53
+ while remaining:
54
+ wave = tuple(alias for alias in aliases if alias in remaining and dependencies[alias] <= emitted)
55
+ if not wave:
56
+ cycle_aliases = tuple(alias for alias in aliases if alias in remaining)
57
+ raise LaneIRDiagnostic(
58
+ kind="cycle",
59
+ message=f"roadmap phase dependency cycle detected: {' -> '.join(cycle_aliases)}",
60
+ details={"cycle": cycle_aliases},
61
+ )
62
+ yield wave
63
+ emitted.update(wave)
64
+ remaining.difference_update(wave)
65
+
66
+
67
+ def parse_phase_plan_ir(plan: Path) -> PhasePlanIR:
68
+ try:
69
+ text = plan.read_text(encoding="utf-8")
70
+ except OSError:
71
+ return PhasePlanIR(
72
+ plan_path=str(plan),
73
+ metadata={},
74
+ diagnostics=(
75
+ LaneIRDiagnostic(
76
+ kind="missing_lane_sections",
77
+ message=f"phase plan is unreadable: {plan}",
78
+ ),
79
+ ),
80
+ )
81
+
82
+ from .discovery import parse_dispatch_hints, parse_execution_policy, parse_frontmatter
83
+
84
+ metadata = parse_frontmatter(text)
85
+ merge_policy = None
86
+ diagnostics: list[LaneIRDiagnostic] = []
87
+ if "merge_policy" in metadata:
88
+ try:
89
+ from .pipeline_adapter.merge_policy import parse as parse_merge_policy
90
+
91
+ merge_policy = parse_merge_policy(metadata)
92
+ except Exception as exc:
93
+ diagnostics.append(
94
+ LaneIRDiagnostic(
95
+ kind="unsupported_lane_policy",
96
+ message=f"invalid merge_policy frontmatter: {exc}",
97
+ blocker_class="contract_bug",
98
+ )
99
+ )
100
+ try:
101
+ execution_policy = parse_execution_policy(plan, kind="plan")
102
+ except ValueError as exc:
103
+ execution_policy = None
104
+ diagnostics.append(
105
+ LaneIRDiagnostic(
106
+ kind="unsupported_lane_policy",
107
+ message=str(exc),
108
+ blocker_class="contract_bug",
109
+ )
110
+ )
111
+ dispatch_hints = parse_dispatch_hints(plan, kind="plan")
112
+ lane_index = _parse_lane_index(text)
113
+ sections = parse_lane_sections(text)
114
+ if not sections:
115
+ diagnostics.append(
116
+ LaneIRDiagnostic(
117
+ kind="missing_lane_sections",
118
+ message="phase plan does not contain lane sections",
119
+ )
120
+ )
121
+
122
+ lanes: list[PhasePlanLane] = []
123
+ for lane_id, heading, body in sections:
124
+ index_data = lane_index.get(lane_id, {})
125
+ lane_diagnostics, lane = _parse_lane(plan, lane_id, heading, body, index_data, execution_policy)
126
+ diagnostics.extend(lane_diagnostics)
127
+ lanes.append(lane)
128
+
129
+ diagnostics.extend(validate_lane_ir(lanes))
130
+ dependencies = tuple(
131
+ LaneDependency(source_lane_id=dependency, target_lane_id=lane.lane_id)
132
+ for lane in lanes
133
+ for dependency in lane.depends_on
134
+ )
135
+ return PhasePlanIR(
136
+ plan_path=str(plan),
137
+ metadata=metadata,
138
+ lanes=tuple(lanes),
139
+ dependencies=dependencies,
140
+ diagnostics=tuple(diagnostics),
141
+ execution_policy=execution_policy,
142
+ dispatch_hints=dispatch_hints,
143
+ merge_policy=merge_policy,
144
+ )
145
+
146
+
147
+ def _roadmap_phase_dependencies(text: str, aliases: tuple[str, ...]) -> dict[str, set[str]]:
148
+ alias_set = set(aliases)
149
+ dependencies = {alias: set() for alias in aliases}
150
+ phase_matches = list(ROADMAP_PHASE_RE.finditer(text))
151
+ for index, match in enumerate(phase_matches):
152
+ alias = match.group("alias").strip().upper()
153
+ end = phase_matches[index + 1].start() if index + 1 < len(phase_matches) else len(text)
154
+ section = text[match.end() : end]
155
+ depends_match = ROADMAP_DEPENDS_RE.search(section)
156
+ if not depends_match:
157
+ continue
158
+ for dependency in ROADMAP_ALIAS_TOKEN_RE.findall(depends_match.group("body")):
159
+ dependency = dependency.upper()
160
+ if dependency in alias_set and dependency != alias:
161
+ dependencies[alias].add(dependency)
162
+
163
+ if not any(dependencies.values()):
164
+ for source, target in _roadmap_dag_edges(text, alias_set):
165
+ if source != target:
166
+ dependencies[target].add(source)
167
+ return dependencies
168
+
169
+
170
+ def _roadmap_dag_edges(text: str, aliases: set[str]) -> tuple[tuple[str, str], ...]:
171
+ match = ROADMAP_DAG_SECTION_RE.search(text)
172
+ if not match:
173
+ return ()
174
+ edges: list[tuple[str, str]] = []
175
+ for raw_line in match.group("body").splitlines():
176
+ line = raw_line.strip()
177
+ if not line or line.startswith("```") or line.startswith("#"):
178
+ continue
179
+ line = re.sub(r"\([^)]*\)", "", line)
180
+ parts = [part.strip() for part in ROADMAP_ARROW_RE.split(line) if part.strip()]
181
+ if len(parts) < 2:
182
+ continue
183
+ chain: list[str] = []
184
+ for part in parts:
185
+ tokens = [token.upper() for token in ROADMAP_ALIAS_TOKEN_RE.findall(part) if token.upper() in aliases]
186
+ if len(tokens) == 1:
187
+ chain.append(tokens[0])
188
+ edges.extend((left, right) for left, right in zip(chain, chain[1:]))
189
+ return tuple(edges)
190
+
191
+
192
+ def parse_lane_sections(text: str) -> tuple[tuple[str, str, str], ...]:
193
+ starts = list(LANE_HEADING_RE.finditer(text))
194
+ sections: list[tuple[str, str, str]] = []
195
+ for index, start in enumerate(starts):
196
+ end = starts[index + 1].start() if index + 1 < len(starts) else len(text)
197
+ lane_id = _normalize_lane_id(start.group("id"))
198
+ sections.append((lane_id, start.group(0).strip(), text[start.start() : end]))
199
+ return tuple(sections)
200
+
201
+
202
+ def validate_lane_ir(lanes: list[PhasePlanLane] | tuple[PhasePlanLane, ...]) -> tuple[LaneIRDiagnostic, ...]:
203
+ diagnostics: list[LaneIRDiagnostic] = []
204
+ lane_ids = {lane.lane_id for lane in lanes}
205
+ for lane in lanes:
206
+ for dependency in lane.depends_on:
207
+ if dependency not in lane_ids:
208
+ diagnostics.append(
209
+ LaneIRDiagnostic(
210
+ kind="malformed_dependencies",
211
+ lane_id=lane.lane_id,
212
+ message=f"{lane.lane_id} depends on unknown lane {dependency}",
213
+ details={"dependency": dependency},
214
+ )
215
+ )
216
+ for blocked in lane.blocks:
217
+ if blocked not in lane_ids:
218
+ diagnostics.append(
219
+ LaneIRDiagnostic(
220
+ kind="malformed_dependencies",
221
+ lane_id=lane.lane_id,
222
+ message=f"{lane.lane_id} blocks unknown lane {blocked}",
223
+ details={"blocked": blocked},
224
+ )
225
+ )
226
+
227
+ diagnostics.extend(_cycle_diagnostics(lanes))
228
+ diagnostics.extend(_ownership_diagnostics(lanes))
229
+ diagnostics.extend(_producer_dependency_diagnostics(lanes))
230
+ return tuple(diagnostics)
231
+
232
+
233
+ def detect_reducer_lane(lane_id: str, name: str, body: str) -> str:
234
+ haystack = f"{lane_id} {name} {body}".lower()
235
+ if "acceptance" in haystack or "exit criteria" in haystack:
236
+ return "acceptance_reducer"
237
+ if "compatibility" in haystack:
238
+ return "compatibility_reducer"
239
+ if "verification" in haystack or "verify" in haystack:
240
+ return "verification_reducer"
241
+ if "summary" in haystack or "synthesize" in haystack or "final" in haystack:
242
+ return "summary_reducer"
243
+ return "none"
244
+
245
+
246
+ def _parse_lane(
247
+ plan: Path,
248
+ lane_id: str,
249
+ heading: str,
250
+ body: str,
251
+ index_data: dict[str, object],
252
+ execution_policy,
253
+ ) -> tuple[list[LaneIRDiagnostic], PhasePlanLane]:
254
+ from .discovery import execution_policy_for_lane
255
+
256
+ diagnostics: list[LaneIRDiagnostic] = []
257
+ fields = _section_fields(body)
258
+ name = _heading_name(heading, lane_id) or str(index_data.get("name") or lane_id)
259
+ raw_owned = fields.get("owned files")
260
+ owned_files: tuple[str, ...] = ()
261
+ read_only = False
262
+ if raw_owned is None:
263
+ diagnostics.append(
264
+ LaneIRDiagnostic(
265
+ kind="missing_owned_files",
266
+ lane_id=lane_id,
267
+ message=f"{lane_id} is missing an owned-files contract",
268
+ details={"heading": heading},
269
+ )
270
+ )
271
+ elif raw_owned.strip().lower().startswith(("none", "(none")):
272
+ read_only = True
273
+ else:
274
+ owned = tuple(item.strip() for item in re.findall(r"`([^`]+)`", raw_owned) if item.strip())
275
+ if not owned:
276
+ diagnostics.append(
277
+ LaneIRDiagnostic(
278
+ kind="malformed_owned_files",
279
+ lane_id=lane_id,
280
+ message=f"{lane_id} owned-files contract must use backtick-delimited literals",
281
+ details={"heading": heading},
282
+ )
283
+ )
284
+ owned_files = owned
285
+
286
+ depends_on = tuple(_lane_list(str(index_data.get("depends_on") or fields.get("depends on") or "")))
287
+ blocks = tuple(_lane_list(str(index_data.get("blocks") or fields.get("blocks") or "")))
288
+ tasks = _task_set(body)
289
+ verification_commands = tuple(
290
+ command for command in tasks.verify if command.startswith("`") and command.endswith("`")
291
+ )
292
+ verification_commands = tuple(command.strip("`") for command in verification_commands)
293
+ policy = None
294
+ if execution_policy is not None:
295
+ try:
296
+ policy = execution_policy_for_lane(execution_policy, "execute", lane_id)
297
+ except ValueError as exc:
298
+ diagnostics.append(
299
+ LaneIRDiagnostic(
300
+ kind="unsupported_lane_policy",
301
+ lane_id=lane_id,
302
+ message=str(exc),
303
+ )
304
+ )
305
+
306
+ return diagnostics, PhasePlanLane(
307
+ lane_id=lane_id,
308
+ name=name,
309
+ heading=heading,
310
+ owned_files=owned_files,
311
+ read_only=read_only,
312
+ depends_on=depends_on,
313
+ blocks=blocks,
314
+ interfaces_provided=_interface_list(fields.get("interfaces provided", "")),
315
+ interfaces_consumed=_interface_list(fields.get("interfaces consumed", "")),
316
+ tasks=tasks,
317
+ verification_commands=verification_commands,
318
+ parallel_safe=_yes_no(str(index_data.get("parallel_safe") or fields.get("parallel-safe") or "")),
319
+ reducer_kind=detect_reducer_lane(lane_id, name, body),
320
+ execution_policy=policy,
321
+ )
322
+
323
+
324
+ def _parse_lane_index(text: str) -> dict[str, dict[str, object]]:
325
+ match = re.search(r"^##\s+Lane Index.*?\n(?P<body>.*?)(?=^##\s+\S|\Z)", text, re.IGNORECASE | re.MULTILINE | re.DOTALL)
326
+ if not match:
327
+ return {}
328
+ result: dict[str, dict[str, object]] = {}
329
+ current: str | None = None
330
+ for line in match.group("body").splitlines():
331
+ lane_match = LANE_INDEX_LINE_RE.match(line)
332
+ if lane_match:
333
+ current = _normalize_lane_id(lane_match.group("id"))
334
+ data = result.setdefault(current, {})
335
+ if lane_match.group("name"):
336
+ data["name"] = lane_match.group("name").strip()
337
+ if lane_match.group("meta"):
338
+ _merge_index_meta(data, lane_match.group("meta"))
339
+ continue
340
+ if current and ":" in line:
341
+ _merge_index_meta(result[current], line.strip())
342
+ return result
343
+
344
+
345
+ def _merge_index_meta(data: dict[str, object], raw: str) -> None:
346
+ for key, value in re.findall(r"(Depends on|Blocks|Parallel-safe)\s*:\s*([^;]+)", raw, re.IGNORECASE):
347
+ normalized = key.lower().replace("-", "_").replace(" ", "_")
348
+ data[normalized] = value.strip()
349
+
350
+
351
+ def _section_fields(section: str) -> dict[str, str]:
352
+ lines = section.splitlines()
353
+ fields: dict[str, str] = {}
354
+ index = 0
355
+ while index < len(lines):
356
+ match = FIELD_RE.match(lines[index])
357
+ if not match:
358
+ index += 1
359
+ continue
360
+ label = match.group("label").strip().lower()
361
+ parts = [match.group("value").strip()]
362
+ index += 1
363
+ while index < len(lines):
364
+ stripped = lines[index].strip()
365
+ if not stripped:
366
+ break
367
+ if FIELD_RE.match(lines[index]) or stripped.startswith("#") or TASK_RE.match(lines[index]):
368
+ break
369
+ if "`" in stripped or stripped.startswith("-"):
370
+ parts.append(stripped)
371
+ index += 1
372
+ continue
373
+ break
374
+ fields[label] = " ".join(parts)
375
+ return fields
376
+
377
+
378
+ def _task_set(section: str) -> LaneTaskSet:
379
+ buckets: dict[str, list[str]] = {"test": [], "impl": [], "verify": [], "other": []}
380
+ for match in TASK_RE.finditer(section):
381
+ buckets[match.group("bucket").lower()].append(match.group("value").strip())
382
+ return LaneTaskSet(
383
+ test=tuple(buckets["test"]),
384
+ impl=tuple(buckets["impl"]),
385
+ verify=tuple(buckets["verify"]),
386
+ other=tuple(buckets["other"]),
387
+ )
388
+
389
+
390
+ def _interface_list(raw: str) -> tuple[str, ...]:
391
+ if not raw:
392
+ return ()
393
+ quoted = re.findall(r"`([^`]+)`", raw)
394
+ values = quoted or re.split(r",|\band\b", raw)
395
+ cleaned: list[str] = []
396
+ for value in values:
397
+ item = value.strip().strip(".")
398
+ if not item or item.lower().startswith(("none", "pre-existing")):
399
+ continue
400
+ if item not in cleaned:
401
+ cleaned.append(item)
402
+ return tuple(cleaned)
403
+
404
+
405
+ def _lane_list(raw: str) -> tuple[str, ...]:
406
+ raw = raw.strip()
407
+ if not raw or raw.lower().startswith(("(none", "none")):
408
+ return ()
409
+ values = re.findall(r"(?:SL|SG|LG|CG|GM|OP|CMD)-\d+[A-Z]?|[A-Z][A-Z0-9]+-\d+[A-Z]?|Lane\s+\d+|Swim\s*lane\s+\d+", raw, re.IGNORECASE)
410
+ return tuple(dict.fromkeys(_normalize_lane_id(value) for value in values))
411
+
412
+
413
+ def _cycle_diagnostics(lanes: list[PhasePlanLane] | tuple[PhasePlanLane, ...]) -> tuple[LaneIRDiagnostic, ...]:
414
+ graph = {lane.lane_id: lane.depends_on for lane in lanes}
415
+ diagnostics: list[LaneIRDiagnostic] = []
416
+ visiting: set[str] = set()
417
+ visited: set[str] = set()
418
+
419
+ def visit(lane_id: str, path: tuple[str, ...]) -> None:
420
+ if lane_id in visiting:
421
+ cycle = (*path, lane_id)
422
+ diagnostics.append(
423
+ LaneIRDiagnostic(
424
+ kind="cycle",
425
+ lane_id=lane_id,
426
+ message=f"lane dependency cycle detected: {' -> '.join(cycle)}",
427
+ details={"cycle": cycle},
428
+ )
429
+ )
430
+ return
431
+ if lane_id in visited:
432
+ return
433
+ visiting.add(lane_id)
434
+ for dependency in graph.get(lane_id, ()):
435
+ if dependency in graph:
436
+ visit(dependency, (*path, lane_id))
437
+ visiting.remove(lane_id)
438
+ visited.add(lane_id)
439
+
440
+ for lane_id in graph:
441
+ visit(lane_id, ())
442
+ return tuple(diagnostics)
443
+
444
+
445
+ def _ownership_diagnostics(lanes: list[PhasePlanLane] | tuple[PhasePlanLane, ...]) -> tuple[LaneIRDiagnostic, ...]:
446
+ diagnostics: list[LaneIRDiagnostic] = []
447
+ writers = [lane for lane in lanes if not lane.read_only]
448
+ for index, left in enumerate(writers):
449
+ for right in writers[index + 1 :]:
450
+ if _patterns_overlap_any(left.owned_files, right.owned_files):
451
+ diagnostics.append(
452
+ LaneIRDiagnostic(
453
+ kind="overlapping_write_ownership",
454
+ lane_id=right.lane_id,
455
+ message=f"{left.lane_id} and {right.lane_id} claim overlapping owned files",
456
+ details={"left": left.lane_id, "right": right.lane_id},
457
+ )
458
+ )
459
+ return tuple(diagnostics)
460
+
461
+
462
+ def _producer_dependency_diagnostics(lanes: list[PhasePlanLane] | tuple[PhasePlanLane, ...]) -> tuple[LaneIRDiagnostic, ...]:
463
+ providers: dict[str, str] = {}
464
+ for lane in lanes:
465
+ for interface in lane.interfaces_provided:
466
+ providers.setdefault(interface, lane.lane_id)
467
+ diagnostics: list[LaneIRDiagnostic] = []
468
+ for lane in lanes:
469
+ for interface in lane.interfaces_consumed:
470
+ provider = providers.get(interface)
471
+ if provider and provider != lane.lane_id and provider not in lane.depends_on:
472
+ diagnostics.append(
473
+ LaneIRDiagnostic(
474
+ kind="missing_producer_dependency",
475
+ lane_id=lane.lane_id,
476
+ message=f"{lane.lane_id} consumes {interface} from {provider} without depending on it",
477
+ details={"interface": interface, "producer": provider},
478
+ )
479
+ )
480
+ return tuple(diagnostics)
481
+
482
+
483
+ def _patterns_overlap_any(left_patterns: tuple[str, ...], right_patterns: tuple[str, ...]) -> bool:
484
+ return any(_patterns_overlap(left, right) for left in left_patterns for right in right_patterns)
485
+
486
+
487
+ def _patterns_overlap(left: str, right: str) -> bool:
488
+ if left == right:
489
+ return True
490
+ left_has_glob = _has_glob(left)
491
+ right_has_glob = _has_glob(right)
492
+ if left_has_glob and fnmatchcase(right, left):
493
+ return True
494
+ if right_has_glob and fnmatchcase(left, right):
495
+ return True
496
+ if left_has_glob and right_has_glob:
497
+ left_prefix = _pattern_prefix(left)
498
+ right_prefix = _pattern_prefix(right)
499
+ return bool(left_prefix and right_prefix and (left_prefix.startswith(right_prefix) or right_prefix.startswith(left_prefix)))
500
+ return False
501
+
502
+
503
+ def _has_glob(pattern: str) -> bool:
504
+ return any(token in pattern for token in ("*", "?", "["))
505
+
506
+
507
+ def _pattern_prefix(pattern: str) -> str:
508
+ match = re.match(r"^[^*?\[]+", pattern)
509
+ return match.group(0).rstrip("/") if match else ""
510
+
511
+
512
+ def _heading_name(heading: str, lane_id: str) -> str:
513
+ text = re.sub(r"^###\s+", "", heading).strip()
514
+ text = re.sub(rf"^{re.escape(lane_id)}\s*[-—:]?\s*", "", text, flags=re.IGNORECASE).strip()
515
+ return text
516
+
517
+
518
+ def _normalize_lane_id(value: str) -> str:
519
+ return re.sub(r"\s+", " ", value.strip()).upper().replace("SWIM LANE", "SWIM-LANE").replace("LANE ", "LANE-")
520
+
521
+
522
+ def _yes_no(value: str) -> bool:
523
+ return value.strip().lower() in {"yes", "true", "1"}