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,425 @@
1
+ """Mechanical lint for phase-plan roadmap specs.
2
+
3
+ Single source of truth for roadmap validation. The skill-bundle script
4
+ ``<harness>-phase-roadmap-builder/scripts/validate_roadmap.py`` is a thin shim
5
+ over this module, and the CLI exposes it as ``phase-loop validate-roadmap`` so
6
+ validation is always available wherever ``phase_loop_runtime`` is installed —
7
+ even on slim skill installs where the bundle ``scripts/`` dir is absent.
8
+
9
+ Checks (all run even if earlier ones fail so the author sees every issue):
10
+
11
+ (A) Required top-level headings present.
12
+ (B) Each ``### Phase N — <Name> (<ALIAS>)`` block carries the required fields.
13
+ (C) Phase numbers non-decreasing; aliases unique.
14
+ (D) IF-gate IDs match ``IF-0-<ALIAS>-\\d+`` and reconcile with phase Produces.
15
+ (E) ``**Depends on**`` references only existing earlier-phase aliases.
16
+ (F) The phase dependency DAG is acyclic.
17
+ (G) Every phase declares a lane-count / partition hint (or is preamble).
18
+
19
+ Zero external deps (stdlib only). Parses by regex on stable headings — not a
20
+ full Markdown parser.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import re
26
+ import sys
27
+ from dataclasses import dataclass, field
28
+ from pathlib import Path
29
+ from typing import Dict, List, Set
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Data model
34
+
35
+ @dataclass
36
+ class Phase:
37
+ number: int
38
+ name: str
39
+ alias: str
40
+ objective: str = ""
41
+ exit_criteria: List[str] = field(default_factory=list)
42
+ scope_notes: str = ""
43
+ non_goals: str = ""
44
+ key_files: List[str] = field(default_factory=list)
45
+ depends_on: List[str] = field(default_factory=list) # aliases, or [] if (none)
46
+ produces: List[str] = field(default_factory=list) # IF-gate ids
47
+ raw_body: str = ""
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Parsing
52
+
53
+ TOP_HEADING_RE = re.compile(r"^## +(?P<name>[^\n]+?)\s*$", re.MULTILINE)
54
+ PHASE_HEADING_RE = re.compile(
55
+ r"^### +Phase\s+(?P<num>\d+)(?P<decimal>\.\d+)?(?P<letter>[A-Z]?)\s*[—\-]\s*(?P<name>.+?)\s*"
56
+ r"\(\s*(?P<alias>[A-Za-z0-9]+)(?:\s*,[^)]*)?\s*\)\s*$",
57
+ re.MULTILINE,
58
+ )
59
+ ANY_PHASE_HEADING_RE = re.compile(r"^### +Phase\s+\d+(?:\.\d+)?[A-Z]?\b.*$", re.MULTILINE)
60
+ FIELD_RE_TEMPLATE = r"^\*\*{label}\*\*\s*\n(?P<body>(?:(?!^\*\*|^### |^## ).*\n?)+)"
61
+ ALIAS_TOKEN_RE = re.compile(r"`([A-Za-z][A-Za-z0-9]*)`|\b([A-Z][A-Z0-9]{1,40}|[Pp]\d+[A-Za-z]?)\b")
62
+
63
+ REQUIRED_TOP_HEADINGS = [
64
+ "Context",
65
+ "Phases",
66
+ "Top Interface-Freeze Gates",
67
+ "Phase Dependency DAG",
68
+ "Execution Notes",
69
+ "Verification",
70
+ ]
71
+
72
+ IF_GATE_RE = re.compile(r"\bIF-0-([A-Za-z0-9]+)-(\d+)\b")
73
+ PREAMBLE_MARKER_RE = re.compile(r"preamble\s*/\s*interface-only|interface-freeze-only|preamble phase", re.IGNORECASE)
74
+
75
+
76
+ def _extract_top_sections(text: str) -> Dict[str, str]:
77
+ sections: Dict[str, str] = {}
78
+ matches = list(TOP_HEADING_RE.finditer(text))
79
+ for i, m in enumerate(matches):
80
+ start = m.end()
81
+ end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
82
+ sections[m.group("name").strip()] = text[start:end]
83
+ return sections
84
+
85
+
86
+ def _next_top_heading(text: str, start: int) -> int:
87
+ m = TOP_HEADING_RE.search(text, start)
88
+ return m.start() if m else len(text)
89
+
90
+
91
+ def _extract_phases(text: str) -> List[Phase]:
92
+ phases: List[Phase] = []
93
+ matches = list(PHASE_HEADING_RE.finditer(text))
94
+ for i, m in enumerate(matches):
95
+ body_start = m.end()
96
+ body_end = matches[i + 1].start() if i + 1 < len(matches) else _next_top_heading(text, body_start)
97
+ body = text[body_start:body_end]
98
+ phase = Phase(
99
+ number=int(m.group("num")),
100
+ name=m.group("name").strip(),
101
+ alias=m.group("alias").strip(),
102
+ raw_body=body,
103
+ )
104
+ phase.objective = _field(body, "Objective")
105
+ phase.exit_criteria = _checkbox_items(_field(body, "Exit criteria"))
106
+ phase.scope_notes = _field(body, "Scope notes")
107
+ phase.non_goals = _field(body, "Non-goals")
108
+ phase.key_files = _bullet_items(_field(body, "Key files"))
109
+ phase.depends_on = _parse_depends_on(_field(body, "Depends on"))
110
+ phase.produces = _parse_produces(_field(body, "Produces"))
111
+ phases.append(phase)
112
+ return phases
113
+
114
+
115
+ def _field(body: str, label: str) -> str:
116
+ pat = re.compile(FIELD_RE_TEMPLATE.format(label=re.escape(label)), re.MULTILINE)
117
+ m = pat.search(body)
118
+ return m.group("body").strip() if m else ""
119
+
120
+
121
+ def _checkbox_items(block: str) -> List[str]:
122
+ return [
123
+ line.strip()[6:].strip()
124
+ for line in block.splitlines()
125
+ if line.strip().startswith("- [ ]") or line.strip().startswith("- [x]")
126
+ ]
127
+
128
+
129
+ def _bullet_items(block: str) -> List[str]:
130
+ return [
131
+ line.strip().lstrip("-").strip()
132
+ for line in block.splitlines()
133
+ if line.strip().startswith("- ")
134
+ ]
135
+
136
+
137
+ def _parse_depends_on(block: str) -> List[str]:
138
+ stripped = block.strip()
139
+ if not stripped or stripped.lower() in {"(none)", "none"}:
140
+ return []
141
+ items: List[str] = []
142
+ for line in stripped.splitlines():
143
+ line = line.strip().lstrip("-").strip()
144
+ if not line or line.lower() in {"(none)", "none"}:
145
+ continue
146
+ for m in ALIAS_TOKEN_RE.finditer(line):
147
+ token = (m.group(1) or m.group(2) or "").strip()
148
+ if token:
149
+ items.append(token.upper())
150
+ return items
151
+
152
+
153
+ def _parse_produces(block: str) -> List[str]:
154
+ stripped = block.strip()
155
+ if not stripped or stripped.lower() in {"(none)", "none"}:
156
+ return []
157
+ return [f"IF-0-{alias}-{n}" for alias, n in IF_GATE_RE.findall(stripped)]
158
+
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # Checks
162
+
163
+ def check_phase_heading_format(text: str, errors: List[str]) -> None:
164
+ for m in ANY_PHASE_HEADING_RE.finditer(text):
165
+ heading = m.group(0).strip()
166
+ if PHASE_HEADING_RE.match(heading):
167
+ continue
168
+ line_no = text.count("\n", 0, m.start()) + 1
169
+ errors.append(
170
+ f"(B) line {line_no}: invalid phase heading `{heading}`; "
171
+ "expected `### Phase N — <Name> (<ALIAS>)`"
172
+ )
173
+
174
+
175
+ def check_required_headings(sections: Dict[str, str], errors: List[str]) -> None:
176
+ present_prefixes = [h.split("(", 1)[0].strip().lower() for h in sections.keys()]
177
+ missing: List[str] = []
178
+ for required in REQUIRED_TOP_HEADINGS:
179
+ if not any(p == required.lower() or p.startswith(required.lower()) for p in present_prefixes):
180
+ missing.append(required)
181
+ if missing:
182
+ errors.append(f"(A) missing required level-2 headings: {', '.join(missing)}")
183
+
184
+
185
+ def check_phase_fields(phases: List[Phase], errors: List[str]) -> None:
186
+ if not phases:
187
+ errors.append("(B) no phases found — expected at least one `### Phase N — <Name> (<ALIAS>)`")
188
+ return
189
+ for ph in phases:
190
+ loc = f"Phase {ph.number} ({ph.alias})"
191
+ if not ph.objective:
192
+ errors.append(f"(B) {loc}: missing **Objective**")
193
+ if not ph.exit_criteria:
194
+ errors.append(f"(B) {loc}: **Exit criteria** missing or has no `- [ ]` checkboxes")
195
+ if not ph.scope_notes:
196
+ errors.append(f"(B) {loc}: missing **Scope notes**")
197
+ if not ph.key_files:
198
+ errors.append(f"(B) {loc}: **Key files** missing or empty")
199
+ if "**Depends on**" not in ph.raw_body:
200
+ errors.append(f"(B) {loc}: missing **Depends on** block (use `(none)` for roots)")
201
+
202
+
203
+ def check_numbering_and_aliases(phases: List[Phase], errors: List[str]) -> None:
204
+ seen_aliases: Set[str] = set()
205
+ last_num = 0
206
+ for ph in phases:
207
+ if ph.number < last_num:
208
+ errors.append(f"(C) phase number {ph.number} ({ph.alias}) decreases from previous ({last_num})")
209
+ last_num = max(last_num, ph.number)
210
+ if ph.alias in seen_aliases:
211
+ errors.append(f"(C) duplicate alias: {ph.alias}")
212
+ seen_aliases.add(ph.alias)
213
+
214
+
215
+ def check_if_gates(phases: List[Phase], sections: Dict[str, str], errors: List[str]) -> None:
216
+ gates_section = sections.get("Top Interface-Freeze Gates", "")
217
+ declared: Set[str] = set()
218
+ for m in IF_GATE_RE.finditer(gates_section):
219
+ gate_id = f"IF-0-{m.group(1)}-{m.group(2)}"
220
+ declared.add(gate_id)
221
+
222
+ valid_aliases = {ph.alias for ph in phases}
223
+ for g in declared:
224
+ alias = g.split("-")[2]
225
+ if alias not in valid_aliases:
226
+ errors.append(f"(D) gate {g} names alias '{alias}' that is not a defined phase")
227
+
228
+ produced_global: Set[str] = set()
229
+ for ph in phases:
230
+ for g in ph.produces:
231
+ owner = g.split("-")[2]
232
+ if owner != ph.alias:
233
+ errors.append(
234
+ f"(D) Phase {ph.number} ({ph.alias}): produces {g} but its alias segment is '{owner}', "
235
+ f"not this phase's alias"
236
+ )
237
+ if g in produced_global:
238
+ errors.append(f"(D) gate {g} is declared in multiple phases' **Produces** blocks")
239
+ produced_global.add(g)
240
+
241
+ only_declared = declared - produced_global
242
+ for g in sorted(only_declared):
243
+ errors.append(f"(D) gate {g} listed in `## Top Interface-Freeze Gates` but not in any phase's **Produces**")
244
+
245
+
246
+ def check_depends_on(phases: List[Phase], errors: List[str]) -> List[Phase]:
247
+ aliases: Set[str] = {ph.alias for ph in phases}
248
+ seen_so_far: Set[str] = set()
249
+ roots: List[Phase] = []
250
+ for ph in phases:
251
+ for dep in ph.depends_on:
252
+ if dep not in aliases:
253
+ errors.append(f"(E) Phase {ph.number} ({ph.alias}): **Depends on** references unknown alias '{dep}'")
254
+ elif dep not in seen_so_far and dep != ph.alias:
255
+ errors.append(
256
+ f"(E) Phase {ph.number} ({ph.alias}): **Depends on** references '{dep}' "
257
+ f"which is not an earlier phase in document order"
258
+ )
259
+ if not ph.depends_on:
260
+ roots.append(ph)
261
+ seen_so_far.add(ph.alias)
262
+ if not roots:
263
+ errors.append("(E) no root phases found — at least one phase must have `**Depends on**` = `(none)`")
264
+ return roots
265
+
266
+
267
+ def check_dag_acyclic(phases: List[Phase], errors: List[str]) -> None:
268
+ edges: Dict[str, List[str]] = {ph.alias: [] for ph in phases}
269
+ indeg: Dict[str, int] = {ph.alias: 0 for ph in phases}
270
+ for ph in phases:
271
+ for dep in ph.depends_on:
272
+ if dep in edges:
273
+ edges[dep].append(ph.alias)
274
+ indeg[ph.alias] += 1
275
+ queue = [a for a, d in indeg.items() if d == 0]
276
+ visited = 0
277
+ while queue:
278
+ cur = queue.pop(0)
279
+ visited += 1
280
+ for nb in edges[cur]:
281
+ indeg[nb] -= 1
282
+ if indeg[nb] == 0:
283
+ queue.append(nb)
284
+ if visited != len(phases):
285
+ unresolved = [a for a, d in indeg.items() if d > 0]
286
+ errors.append(f"(F) cycle detected in phase dependencies; unresolved: {', '.join(unresolved)}")
287
+
288
+
289
+ def check_lane_count_hint(phases: List[Phase], errors: List[str]) -> None:
290
+ word_num = r"(?:two|three|four|five|six|seven|eight|nine|ten)"
291
+ numeric_re = re.compile(
292
+ r"\b(?:\d+(?:\s*[\-–]\s*\d+)?|" + word_num + r"(?:\s*[\-–]\s*" + word_num + r")?)\s+lanes?\b",
293
+ re.IGNORECASE,
294
+ )
295
+ partition_re = re.compile(
296
+ r"\blane\s+[A-Z0-9]+\b|\bpartition|\bdisjoint|\bowns\b|\bsingle lane\b",
297
+ re.IGNORECASE,
298
+ )
299
+ lane_token_re = re.compile(r"\b[A-Z][A-Z0-9]*-lane-[A-Za-z0-9]+\b|\bSL-[A-Za-z0-9]+\b")
300
+ lanes_section_re = re.compile(
301
+ r"^\*\*Lanes\*\*[^\n]*\n(?P<body>(?:(?!^\*\*|^### |^## ).*\n?)+)",
302
+ re.MULTILINE,
303
+ )
304
+ for ph in phases:
305
+ if PREAMBLE_MARKER_RE.search(ph.scope_notes):
306
+ continue
307
+ lanes_match = lanes_section_re.search(ph.raw_body)
308
+ haystack = ph.scope_notes + "\n" + (lanes_match.group("body") if lanes_match else "")
309
+ if numeric_re.search(haystack):
310
+ continue
311
+ if partition_re.search(haystack):
312
+ continue
313
+ if len(set(lane_token_re.findall(haystack))) >= 2:
314
+ continue
315
+ errors.append(
316
+ f"(G) Phase {ph.number} ({ph.alias}): no lane count or partition hint in "
317
+ f"**Scope notes** or **Lanes**. Add e.g., 'decompose into N lanes', "
318
+ f"'Single lane' with justification, or mark as preamble/interface-only."
319
+ )
320
+
321
+
322
+ # ---------------------------------------------------------------------------
323
+ # Public API
324
+
325
+ def lint_roadmap_text(text: str) -> List[str]:
326
+ """Return a list of human-readable issues for roadmap markdown ``text``."""
327
+ errors: List[str] = []
328
+ sections = _extract_top_sections(text)
329
+ phases = _extract_phases(text)
330
+ check_required_headings(sections, errors)
331
+ check_phase_heading_format(text, errors)
332
+ check_phase_fields(phases, errors)
333
+ check_numbering_and_aliases(phases, errors)
334
+ check_if_gates(phases, sections, errors)
335
+ check_depends_on(phases, errors)
336
+ check_dag_acyclic(phases, errors)
337
+ check_lane_count_hint(phases, errors)
338
+ return errors
339
+
340
+
341
+ def lint_roadmap(path: Path | str) -> List[str]:
342
+ """Return a list of issues for the roadmap at ``path`` (raises on read error)."""
343
+ return lint_roadmap_text(Path(path).read_text(encoding="utf-8"))
344
+
345
+
346
+ # ---------------------------------------------------------------------------
347
+ # Train mode — validate a cross-repo release-train roadmap (P2)
348
+
349
+ def lint_train_roadmap_text(text: str) -> List[str]:
350
+ """Return validation issues for a cross-repo train roadmap markdown ``text``.
351
+
352
+ Validates the cross-repo DAG: acyclic, every depended-on node exists, the
353
+ train is serially orderable (topo-sort), and every dependency edge carries
354
+ a valid consumption-channel descriptor. A non-orderable, cyclic, or
355
+ channel-less train fails loud (returns non-empty list).
356
+
357
+ This is a separate validation path from :func:`lint_roadmap_text` — it
358
+ uses the train-roadmap parser (``train_roadmap.parse_train_roadmap``), not
359
+ the phase-plan regex.
360
+ """
361
+ from .train_roadmap import parse_train_roadmap, validate_train
362
+
363
+ try:
364
+ roadmap = parse_train_roadmap(text)
365
+ except ValueError as exc:
366
+ return [f"(T-PARSE) {exc}"]
367
+ return validate_train(roadmap)
368
+
369
+
370
+ def lint_train_roadmap(path: Path | str) -> List[str]:
371
+ """Return validation issues for the train roadmap at ``path``."""
372
+ return lint_train_roadmap_text(Path(path).read_text(encoding="utf-8"))
373
+
374
+
375
+ def main(argv: List[str]) -> int:
376
+ if len(argv) < 2:
377
+ prog = Path(argv[0]).name if argv else "validate_roadmap"
378
+ print(f"usage: {prog} [--train] <roadmap-path>", file=sys.stderr)
379
+ return 2
380
+
381
+ # Handle --train flag
382
+ train_mode = False
383
+ args = list(argv[1:])
384
+ if "--train" in args:
385
+ train_mode = True
386
+ args = [a for a in args if a != "--train"]
387
+
388
+ if len(args) != 1:
389
+ prog = Path(argv[0]).name if argv else "validate_roadmap"
390
+ print(f"usage: {prog} [--train] <roadmap-path>", file=sys.stderr)
391
+ return 2
392
+
393
+ path = Path(args[0])
394
+ try:
395
+ text = path.read_text(encoding="utf-8")
396
+ except FileNotFoundError:
397
+ print(f"error: file not found: {path}", file=sys.stderr)
398
+ return 2
399
+ except OSError as exc:
400
+ print(f"error: could not read {path}: {exc}", file=sys.stderr)
401
+ return 2
402
+
403
+ if train_mode:
404
+ errors = lint_train_roadmap_text(text)
405
+ if errors:
406
+ print(f"validate_roadmap (train): {len(errors)} issue(s) in {path}", file=sys.stderr)
407
+ for e in errors:
408
+ print(f" • {e}", file=sys.stderr)
409
+ return 1
410
+ print(f"validate_roadmap (train): OK — {path}")
411
+ return 0
412
+
413
+ phases = _extract_phases(text)
414
+ errors = lint_roadmap_text(text)
415
+ if errors:
416
+ print(f"validate_roadmap: {len(errors)} issue(s) in {path}", file=sys.stderr)
417
+ for e in errors:
418
+ print(f" • {e}", file=sys.stderr)
419
+ return 1
420
+ print(f"validate_roadmap: OK — {len(phases)} phase(s) in {path}")
421
+ return 0
422
+
423
+
424
+ if __name__ == "__main__":
425
+ sys.exit(main(sys.argv))
@@ -0,0 +1,59 @@
1
+ """Route-decision logging (model-routing-v1 P4).
2
+
3
+ A metadata-only record of *why* a dispatch resolved to a given model/effort, so a
4
+ human auditing a run can answer "what tier ran this, at what effort, and why".
5
+ Built from a `ModelSelection` (which carries only routing provenance — model,
6
+ effort, source, override_reason, model_class), so the record is redaction-safe by
7
+ construction: it never touches artifact bodies, prompts, or credentials.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from .models import ModelSelection
14
+
15
+ ROUTE_LOG_KEY = "route"
16
+
17
+
18
+ def build_route_log(
19
+ selection: ModelSelection,
20
+ *,
21
+ route_reason: str | None = None,
22
+ escalated_from: str | None = None,
23
+ escalated_to: str | None = None,
24
+ ) -> dict[str, Any]:
25
+ """A metadata-only routing record for a dispatch event's ``metadata[route]``."""
26
+ log: dict[str, Any] = {
27
+ "model_class": selection.model_class,
28
+ "concrete_model": selection.model,
29
+ "effort": selection.effort,
30
+ "route_reason": route_reason or selection.override_reason or selection.source,
31
+ }
32
+ if escalated_from is not None:
33
+ log["escalated_from"] = escalated_from
34
+ if escalated_to is not None:
35
+ log["escalated_to"] = escalated_to
36
+ return log
37
+
38
+
39
+ def with_route_log(
40
+ metadata: dict[str, Any] | None,
41
+ selection: ModelSelection,
42
+ *,
43
+ route_reason: str | None = None,
44
+ escalated_from: str | None = None,
45
+ escalated_to: str | None = None,
46
+ ) -> dict[str, Any]:
47
+ """Return ``metadata`` with the route record merged under ``ROUTE_LOG_KEY``.
48
+
49
+ Additive: a dispatch event-emission site calls this on its existing metadata
50
+ (or None) to attach the route record without disturbing other keys.
51
+ """
52
+ merged = dict(metadata or {})
53
+ merged[ROUTE_LOG_KEY] = build_route_log(
54
+ selection,
55
+ route_reason=route_reason,
56
+ escalated_from=escalated_from,
57
+ escalated_to=escalated_to,
58
+ )
59
+ return merged
@@ -0,0 +1,11 @@
1
+ """Language-neutral Claude route-selection policy artifacts.
2
+
3
+ This package is a marker so the golden route-selection fixtures ship as
4
+ importable resource data (`importlib.resources`). The fixtures
5
+ (`fixtures/route_selection.golden.json`) are the frozen, cross-language
6
+ contract that BOTH the Python runtime (`resolve_claude_route` +
7
+ `claude_route_billing_posture` / `claude_route_fallback_posture` in
8
+ `phase_loop_runtime.launcher`) and governed-pipeline's TS `resolveClaudeRoute`
9
+ (gp #25) validate against. See `docs/phase-loop/route-policy-spec.md` for the
10
+ language-neutral decision table.
11
+ """