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,225 @@
1
+ from __future__ import annotations
2
+
3
+ import subprocess
4
+ import re
5
+ from pathlib import Path
6
+
7
+
8
+ PHASE_LOOP_DIRNAME = ".phase-loop"
9
+ LEGACY_PHASE_LOOP_PARENT = ".codex"
10
+ LEGACY_PHASE_LOOP_DIRNAME = "phase-loop"
11
+ EXCLUDE_ENTRIES = (".phase-loop/", ".codex/phase-loop/")
12
+
13
+
14
+ def phase_loop_dir(repo: Path) -> Path:
15
+ return repo / PHASE_LOOP_DIRNAME
16
+
17
+
18
+ def legacy_phase_loop_dir(repo: Path) -> Path:
19
+ return repo / LEGACY_PHASE_LOOP_PARENT / LEGACY_PHASE_LOOP_DIRNAME
20
+
21
+
22
+ def phase_loop_read_dir(repo: Path) -> Path:
23
+ canonical = phase_loop_dir(repo)
24
+ if canonical.exists():
25
+ return canonical
26
+ legacy = legacy_phase_loop_dir(repo)
27
+ return legacy if legacy.exists() else canonical
28
+
29
+
30
+ def phase_loop_read_dirs(repo: Path) -> tuple[Path, ...]:
31
+ canonical = phase_loop_dir(repo)
32
+ legacy = legacy_phase_loop_dir(repo)
33
+ dirs = []
34
+ if canonical.exists():
35
+ dirs.append(canonical)
36
+ if legacy.exists():
37
+ dirs.append(legacy)
38
+ return tuple(dirs or (canonical,))
39
+
40
+
41
+ def phase_loop_runs_dir(repo: Path) -> Path:
42
+ return phase_loop_dir(repo) / "runs"
43
+
44
+
45
+ def lane_worktree_root(repo: Path, *, workspace_mount: Path | None = None) -> Path:
46
+ mount = workspace_mount or Path("/mnt/workspace")
47
+ if mount.exists():
48
+ return mount / "worktrees"
49
+ return repo.parent
50
+
51
+
52
+ def lane_worktree_path(
53
+ repo: Path,
54
+ *,
55
+ branch: str,
56
+ lane_id: str,
57
+ project: str | None = None,
58
+ workspace_mount: Path | None = None,
59
+ ) -> Path:
60
+ project_slug = _path_slug(project or repo.name)
61
+ branch_slug = _path_slug(branch)
62
+ lane_slug = _path_slug(lane_id)
63
+ return lane_worktree_root(repo, workspace_mount=workspace_mount) / f"{project_slug}-{branch_slug}-{lane_slug}"
64
+
65
+
66
+ def phase_loop_runs_dirs(repo: Path) -> tuple[Path, ...]:
67
+ dirs = tuple(path / "runs" for path in phase_loop_read_dirs(repo))
68
+ existing = tuple(path for path in dirs if path.exists())
69
+ return existing or (phase_loop_runs_dir(repo),)
70
+
71
+
72
+ def phase_loop_run_context_file(run_root: Path) -> Path:
73
+ return run_root / "context.md"
74
+
75
+
76
+ def phase_loop_claude_bundle_root(run_root: Path) -> Path:
77
+ return run_root / "claude-bundle"
78
+
79
+
80
+ def phase_loop_claude_plugin_dir(run_root: Path) -> Path:
81
+ return phase_loop_claude_bundle_root(run_root) / "plugin"
82
+
83
+
84
+ def phase_loop_claude_settings_file(run_root: Path) -> Path:
85
+ return phase_loop_claude_bundle_root(run_root) / "settings.json"
86
+
87
+
88
+ def phase_loop_claude_agents_file(run_root: Path) -> Path:
89
+ return phase_loop_claude_bundle_root(run_root) / "agents.json"
90
+
91
+
92
+ def phase_loop_claude_mcp_config_file(run_root: Path) -> Path:
93
+ return phase_loop_claude_bundle_root(run_root) / "mcp.json"
94
+
95
+
96
+ def phase_loop_stop_file(repo: Path) -> Path:
97
+ return phase_loop_dir(repo) / "stop"
98
+
99
+
100
+ def phase_loop_stop_files(repo: Path) -> tuple[Path, ...]:
101
+ canonical = phase_loop_stop_file(repo)
102
+ legacy = legacy_phase_loop_dir(repo) / "stop"
103
+ files = [canonical]
104
+ if legacy.exists():
105
+ files.append(legacy)
106
+ return tuple(files)
107
+
108
+
109
+ def phase_loop_tui_handoff_file(repo: Path) -> Path:
110
+ return phase_loop_dir(repo) / "tui-handoff.md"
111
+
112
+
113
+ def phase_loop_tui_handoff_read_file(repo: Path) -> Path:
114
+ canonical = phase_loop_tui_handoff_file(repo)
115
+ if canonical.exists():
116
+ return canonical
117
+ legacy = legacy_phase_loop_dir(repo) / "tui-handoff.md"
118
+ return legacy if legacy.exists() else canonical
119
+
120
+
121
+ def phase_loop_state_file(repo: Path) -> Path:
122
+ return phase_loop_dir(repo) / "state.json"
123
+
124
+
125
+ def phase_loop_executor_degradation_file(repo: Path) -> Path:
126
+ return phase_loop_dir(repo) / "executor-degradation.json"
127
+
128
+
129
+ def phase_loop_state_read_file(repo: Path) -> Path:
130
+ canonical = phase_loop_state_file(repo)
131
+ if canonical.exists():
132
+ return canonical
133
+ legacy = legacy_phase_loop_dir(repo) / "state.json"
134
+ return legacy if legacy.exists() else canonical
135
+
136
+
137
+ def phase_loop_event_file(repo: Path) -> Path:
138
+ return phase_loop_dir(repo) / "events.jsonl"
139
+
140
+
141
+ def phase_loop_event_read_files(repo: Path) -> tuple[Path, ...]:
142
+ legacy = legacy_phase_loop_dir(repo) / "events.jsonl"
143
+ canonical = phase_loop_event_file(repo)
144
+ if canonical.exists():
145
+ return (canonical,)
146
+ if legacy.exists():
147
+ return (legacy,)
148
+ return (canonical,)
149
+
150
+
151
+ def phase_loop_fleet_metrics_file(repo: Path) -> Path:
152
+ """Append-only sibling ledger for CS-2.1 SA fleet-metric events.
153
+
154
+ Kept as a SEPARATE file from ``events.jsonl`` so the existing event ledger's
155
+ bytes and reconcile/fold semantics are provably untouched by this additive
156
+ observability slice. Enforcement-owned (inside ``.phase-loop/``); the Portal
157
+ wall forbids Portal from ever reading it — the sanitized aggregates are
158
+ PUSHED to Portal, never pulled.
159
+ """
160
+ return phase_loop_dir(repo) / "fleet-metrics.jsonl"
161
+
162
+
163
+ def phase_loop_fleet_metrics_read_files(repo: Path) -> tuple[Path, ...]:
164
+ canonical = phase_loop_fleet_metrics_file(repo)
165
+ legacy = legacy_phase_loop_dir(repo) / "fleet-metrics.jsonl"
166
+ if canonical.exists():
167
+ return (canonical,)
168
+ if legacy.exists():
169
+ return (legacy,)
170
+ return (canonical,)
171
+
172
+
173
+ def phase_loop_active_loop_file(repo: Path) -> Path:
174
+ return phase_loop_dir(repo) / "active-loop.json"
175
+
176
+
177
+ def phase_loop_active_loop_read_files(repo: Path) -> tuple[Path, ...]:
178
+ canonical = phase_loop_active_loop_file(repo)
179
+ legacy = legacy_phase_loop_dir(repo) / "active-loop.json"
180
+ if canonical.exists():
181
+ return (canonical,)
182
+ if legacy.exists():
183
+ return (legacy,)
184
+ return (canonical,)
185
+
186
+
187
+ def git_info_exclude(repo: Path) -> Path | None:
188
+ try:
189
+ path_text = subprocess.check_output(
190
+ ["git", "-C", str(repo), "rev-parse", "--git-path", "info/exclude"],
191
+ text=True,
192
+ stderr=subprocess.DEVNULL,
193
+ ).strip()
194
+ except Exception:
195
+ return None
196
+ if not path_text:
197
+ return None
198
+ path = Path(path_text)
199
+ if not path.is_absolute():
200
+ path = repo / path
201
+ return path
202
+
203
+
204
+ def ensure_phase_loop_excluded(repo: Path) -> None:
205
+ exclude = git_info_exclude(repo)
206
+ if exclude is None:
207
+ return
208
+ try:
209
+ exclude.parent.mkdir(parents=True, exist_ok=True)
210
+ text = exclude.read_text() if exclude.exists() else ""
211
+ existing = {line.strip() for line in text.splitlines()}
212
+ missing = [entry for entry in EXCLUDE_ENTRIES if entry not in existing]
213
+ if not missing:
214
+ return
215
+ prefix = "" if not text or text.endswith("\n") else "\n"
216
+ with exclude.open("a", encoding="utf-8") as handle:
217
+ handle.write(prefix + "\n".join(missing) + "\n")
218
+ except OSError:
219
+ return
220
+
221
+
222
+ def _path_slug(value: str) -> str:
223
+ slug = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
224
+ slug = re.sub(r"-+", "-", slug).strip("-._")
225
+ return slug or "unknown"
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from . import __version__
8
+ from .baml_modular import parse_baml_response
9
+ from .events import read_events
10
+ from .install_status import build_install_status
11
+ from .models import PIPELINE_MODE_LITERALS, StateSnapshot, require_literal, utc_now
12
+ from .plan_manifest import read_manifest
13
+ from .reconcile import reconcile
14
+ from .skill_paths import current_harness
15
+
16
+
17
+ def build_runtime_projection(
18
+ repo: Path,
19
+ roadmap: Path,
20
+ snapshot: StateSnapshot | None = None,
21
+ pipeline_mode: str = "standalone",
22
+ ) -> dict[str, object]:
23
+ require_literal(pipeline_mode, PIPELINE_MODE_LITERALS, "pipeline mode")
24
+ resolved = snapshot or reconcile(repo, roadmap)
25
+ install = build_install_status(repo)
26
+ plan_activity = _plan_activity(repo)
27
+ payload: dict[str, object] = {
28
+ "runtime_version": __version__,
29
+ "protocol_version": "phase-loop-protocol-v1",
30
+ "harness": current_harness(),
31
+ "source_bundle_digest": _source_bundle_digest(repo),
32
+ "closeout_status": _closeout_status(resolved),
33
+ "handoff_status": "written" if (repo / ".phase-loop" / "tui-handoff.md").is_file() else "missing",
34
+ "current_phase_boundary": resolved.current_phase or "none",
35
+ "last_event_iso": _last_event_iso(repo) or resolved.timestamp or utc_now(),
36
+ "plans_in_flight": plan_activity["plans_in_flight"],
37
+ "plans_executing": plan_activity["plans_executing"],
38
+ "last_plan_event_iso": plan_activity["last_plan_event_iso"],
39
+ "install_status": str(install.get("summary") or "unknown"),
40
+ "gitignore_init_status": _gitignore_init_status(repo),
41
+ "operating_mode": pipeline_mode,
42
+ }
43
+ _assert_redacted(payload)
44
+ parse_baml_response("DotfilesRuntimeProjection", json.dumps(payload))
45
+ return payload
46
+
47
+
48
+ def _source_bundle_digest(repo: Path) -> str:
49
+ for event in reversed(read_events(repo)):
50
+ metadata = event.get("metadata")
51
+ if not isinstance(metadata, dict):
52
+ continue
53
+ bundle = metadata.get("pipeline_source_bundle")
54
+ if isinstance(bundle, dict) and isinstance(bundle.get("sha256"), str) and bundle["sha256"]:
55
+ return f"sha256:{bundle['sha256']}"
56
+ return "none"
57
+
58
+
59
+ def _closeout_status(snapshot: StateSnapshot) -> str:
60
+ if snapshot.closeout_terminal_status:
61
+ return snapshot.closeout_terminal_status
62
+ if isinstance(snapshot.terminal_summary, dict):
63
+ status = snapshot.terminal_summary.get("terminal_status")
64
+ if isinstance(status, str) and status:
65
+ return status
66
+ if snapshot.current_phase and snapshot.current_phase in snapshot.phases:
67
+ return snapshot.phases[snapshot.current_phase]
68
+ return "unknown"
69
+
70
+
71
+ def _last_event_iso(repo: Path) -> str | None:
72
+ events = read_events(repo)
73
+ if not events:
74
+ return None
75
+ timestamp = events[-1].get("timestamp")
76
+ return str(timestamp) if timestamp else None
77
+
78
+
79
+ def _plan_activity(repo: Path) -> dict[str, object]:
80
+ try:
81
+ manifest = read_manifest(repo)
82
+ except (OSError, ValueError):
83
+ return {"plans_in_flight": 0, "plans_executing": [], "last_plan_event_iso": "none"}
84
+ in_flight_statuses = {"committed", "executing"}
85
+ plans_in_flight = sum(1 for entry in manifest.plans if entry.status in in_flight_statuses)
86
+ plans_executing = sorted(entry.slug for entry in manifest.plans if entry.status == "executing")
87
+ lifecycle_timestamps: list[str] = []
88
+ updated_timestamps: list[str] = []
89
+ for entry in manifest.plans:
90
+ lifecycle_timestamps.extend(event.at for event in entry.lifecycle if event.at)
91
+ if entry.updated_at:
92
+ updated_timestamps.append(entry.updated_at)
93
+ return {
94
+ "plans_in_flight": plans_in_flight,
95
+ "plans_executing": plans_executing,
96
+ "last_plan_event_iso": max(lifecycle_timestamps) if lifecycle_timestamps else max(updated_timestamps, default="none"),
97
+ }
98
+
99
+
100
+ def _gitignore_init_status(repo: Path) -> str:
101
+ gitignore = repo / ".gitignore"
102
+ if not gitignore.is_file():
103
+ return "missing"
104
+ lines = gitignore.read_text(encoding="utf-8").splitlines()
105
+ return "present" if "/.dev-skills/" in lines or ".dev-skills/" in lines else "missing"
106
+
107
+
108
+ def _assert_redacted(payload: dict[str, Any]) -> None:
109
+ serialized = json.dumps(payload, sort_keys=True)
110
+ forbidden = ("/home/", "/Users/", "/mnt/", "op://", "sk-", "AKIA", "ghp_")
111
+ leaked = [token for token in forbidden if token in serialized]
112
+ if leaked:
113
+ raise ValueError(f"runtime projection contains forbidden metadata token: {leaked[0]}")
@@ -0,0 +1,36 @@
1
+ """Locate package-data shipped inside the ``phase_loop_runtime`` wheel.
2
+
3
+ Single home for the ``importlib.resources`` wheel-anchor idiom that was triplicated
4
+ across ``baml_modular``/``schema_export``/``skill_inventory`` (#12 CR). Returns a real
5
+ on-disk ``Path``.
6
+
7
+ Limitation (consistent with the prior per-module helpers): under a zip-imported app
8
+ (PEX/shiv/zipapp) ``importlib.resources.files`` yields a non-filesystem ``Traversable``
9
+ whose ``str()`` is not a real directory, so these return ``None``. ``pip install``
10
+ (extracted ``site-packages``) is the supported mode.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+
16
+
17
+ def package_root() -> Path | None:
18
+ """The extracted on-disk root of the installed ``phase_loop_runtime`` package, or
19
+ ``None`` when it can't be resolved to a real directory."""
20
+ try:
21
+ import importlib.resources
22
+
23
+ root = Path(str(importlib.resources.files("phase_loop_runtime")))
24
+ except (ModuleNotFoundError, TypeError, FileNotFoundError):
25
+ return None
26
+ return root if root.is_dir() else None
27
+
28
+
29
+ def packaged_resource_dir(name: str) -> Path | None:
30
+ """A package-data subdirectory shipped in the wheel (e.g. ``"skills_bundle"``), or
31
+ ``None`` if the package root or the subdirectory is unavailable."""
32
+ root = package_root()
33
+ if root is None:
34
+ return None
35
+ candidate = root / name
36
+ return candidate if candidate.is_dir() else None
@@ -0,0 +1,292 @@
1
+ """Canonical phase-loop closeout schema export (SCHEMA SL-1).
2
+
3
+ The ONE canonical closeout shape is the nested :class:`PhaseLoopCloseout`
4
+ dataclass (``models.py``). This module *derives* a declared JSON-Schema and a flat
5
+ field-list FROM that dataclass — walking ``dataclasses.fields`` +
6
+ ``typing.get_type_hints`` and RECURSING into the nested sub-dataclasses
7
+ (``PhaseLoopAutomation``/``Artifacts``/``Verification``/``Blocker``/``SourceBundle``/
8
+ ``SourceTruthImpact``/``SpecDeltaCloseout``). Scalar types, nested required-sets,
9
+ nullable unions, and enums all come from the dataclass, so "single source of
10
+ truth" is real: a dataclass change that isn't regenerated fails ``--check`` and the
11
+ committed-artifact drift test.
12
+
13
+ The generated artifact is committed at ``schemas/phase_loop_closeout.schema.json``
14
+ and bundled as package-data; :func:`canonical_schema` resolves it via
15
+ ``importlib.resources`` (the post-DECOUPLE pattern, mirroring ``baml_modular``)
16
+ so the bundled-artifact path works from a wheel-installed venv. :func:`check`
17
+ builds the canonical schema *fresh* from the live dataclass, so a stale committed
18
+ artifact cannot mask drift.
19
+
20
+ ``required`` semantics: a field is optional iff its resolved annotation is a union
21
+ containing ``None`` AND it is not part of the closeout validator's mandatory set.
22
+ The runtime's malformed-closeout validator (``closeout.py``) is the ground truth for
23
+ the top-level required set; this module exposes that set as the single source the
24
+ validator consumes, killing the duplicate hand-maintained tuple.
25
+
26
+ Generation is deterministic (sorted keys, explicitly sorted lists, no volatile
27
+ content) so the byte-stable artifact is a meaningful parity baseline.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import dataclasses
33
+ import importlib.resources
34
+ import json
35
+ import types
36
+ import typing
37
+ from pathlib import Path
38
+ from typing import Any
39
+
40
+ from .models import (
41
+ PIPELINE_CLOSEOUT_OUTCOMES,
42
+ PIPELINE_CLOSEOUT_SCHEMA,
43
+ PhaseLoopCloseout,
44
+ )
45
+
46
+ #: Repo-relative location of the committed/bundled artifact (resolved via
47
+ #: importlib.resources at runtime; this constant is for docs/diagnostics).
48
+ SCHEMA_RESOURCE = "schemas/phase_loop_closeout.schema.json"
49
+
50
+ #: Static provenance recorded INSIDE the JSON (never a // comment — the artifact
51
+ #: must remain valid JSON for json.load + --check). No timestamps/abs paths so it
52
+ #: stays byte-stable and metadata_only.
53
+ _PROVENANCE = (
54
+ "GENERATED from phase_loop_runtime.models.PhaseLoopCloseout by "
55
+ "`phase-loop export-schema`. Do not hand-edit; regenerate instead."
56
+ )
57
+
58
+ #: The top-level closeout fields the runtime's malformed-closeout validator
59
+ #: requires present-and-non-null (closeout.py:phase_loop_closeout_diagnostic).
60
+ #: `schema` is validated separately (it is a fixed literal), and
61
+ #: `spec_delta_closeout` is genuinely optional, so neither appears here. This is
62
+ #: the SINGLE SOURCE the validator consumes — do not maintain a second copy.
63
+ CLOSEOUT_REQUIRED_NESTED_FIELDS: tuple[str, ...] = (
64
+ "phase",
65
+ "terminal_status",
66
+ "automation",
67
+ "artifacts",
68
+ "verification",
69
+ "blocker",
70
+ "source_bundle",
71
+ "source_truth_impact",
72
+ )
73
+
74
+
75
+ def _strip_optional(annotation: Any) -> tuple[Any, bool]:
76
+ """Return ``(inner_type, is_optional)`` for a possibly-``X | None`` annotation."""
77
+ origin = typing.get_origin(annotation)
78
+ if origin in (typing.Union, types.UnionType):
79
+ args = tuple(a for a in typing.get_args(annotation) if a is not type(None))
80
+ is_optional = len(args) != len(typing.get_args(annotation))
81
+ inner = args[0] if len(args) == 1 else annotation
82
+ return inner, is_optional
83
+ return annotation, False
84
+
85
+
86
+ def _scalar_schema(tp: Any) -> dict[str, Any] | None:
87
+ """Map a scalar Python type to its JSON-Schema type, else ``None``."""
88
+ # bool is a subclass of int — check it first.
89
+ if tp is bool:
90
+ return {"type": "boolean"}
91
+ if tp is int:
92
+ return {"type": "integer"}
93
+ if tp is float:
94
+ return {"type": "number"}
95
+ if tp is str:
96
+ return {"type": "string"}
97
+ return None
98
+
99
+
100
+ def _annotation_schema(annotation: Any) -> dict[str, Any]:
101
+ """Derive a JSON-Schema fragment from a (non-optional) resolved annotation."""
102
+ inner, _ = _strip_optional(annotation)
103
+ origin = typing.get_origin(inner)
104
+ scalar = _scalar_schema(inner)
105
+ if scalar is not None:
106
+ return scalar
107
+ if dataclasses.is_dataclass(inner):
108
+ return _dataclass_schema(inner)
109
+ if origin in (tuple, list):
110
+ args = typing.get_args(inner)
111
+ # tuple[X, ...] / list[X]; freeform `dict[..., Any]` items collapse to object.
112
+ item = args[0] if args else Any
113
+ item_inner, _ = _strip_optional(item)
114
+ if dataclasses.is_dataclass(item_inner):
115
+ return {"type": "array", "items": _dataclass_schema(item_inner)}
116
+ item_scalar = _scalar_schema(item_inner)
117
+ if item_scalar is not None:
118
+ return {"type": "array", "items": item_scalar}
119
+ return {"type": "array", "items": {"type": "object"}}
120
+ if origin is dict:
121
+ return {"type": "object"}
122
+ # Any / unrecognised -> permissive object (don't over-specify freeform shapes).
123
+ return {"type": "object"}
124
+
125
+
126
+ def _dataclass_schema(cls: Any) -> dict[str, Any]:
127
+ """Recursively build a JSON-Schema `object` for a dataclass.
128
+
129
+ A nested field is treated as required iff its annotation is NOT a
130
+ union-with-None — a conservative contract convention. (It is not a guarantee of
131
+ presence in every emitted instance: a few fields with custom `to_json` `or None`
132
+ emit logic, e.g. an empty `protected_sources` tuple, may be absent in practice.
133
+ Nothing validates live instances against this artifact, so the convention is the
134
+ declared shape, not an instance invariant.) Nullable fields get a
135
+ `["...","null"]` union type and are excluded from `required`.
136
+ """
137
+ hints = typing.get_type_hints(cls)
138
+ properties: dict[str, dict[str, Any]] = {}
139
+ required: list[str] = []
140
+ for fld in dataclasses.fields(cls):
141
+ annotation = hints[fld.name]
142
+ _, is_optional = _strip_optional(annotation)
143
+ prop = _annotation_schema(annotation)
144
+ if is_optional:
145
+ base_type = prop.get("type", "object")
146
+ if isinstance(base_type, str):
147
+ prop = {**prop, "type": [base_type, "null"]}
148
+ else:
149
+ required.append(fld.name)
150
+ properties[fld.name] = prop
151
+ return {
152
+ "type": "object",
153
+ "additionalProperties": False,
154
+ "required": sorted(required),
155
+ "properties": {name: properties[name] for name in sorted(properties)},
156
+ }
157
+
158
+
159
+ def field_names() -> tuple[str, ...]:
160
+ """The declared field set of :class:`PhaseLoopCloseout`, in declaration order."""
161
+ return tuple(f.name for f in dataclasses.fields(PhaseLoopCloseout))
162
+
163
+
164
+ def required_closeout_fields() -> tuple[str, ...]:
165
+ """Single source for the closeout validator's mandatory nested fields.
166
+
167
+ Consumed by ``closeout.py``'s malformed-closeout diagnostic so the required
168
+ set is not maintained in two places.
169
+ """
170
+ return CLOSEOUT_REQUIRED_NESTED_FIELDS
171
+
172
+
173
+ def _top_level_property(name: str, annotation: Any) -> dict[str, Any]:
174
+ """Property entry for a top-level closeout field (with closeout-specific consts).
175
+
176
+ A top-level field is null-allowed only when it is BOTH annotated nullable AND
177
+ not in the validator's mandatory set. ``source_truth_impact`` is annotated
178
+ ``... | None`` (the builder always populates it) but the validator requires it
179
+ present-and-non-null, so it stays non-null here — matching what consumers
180
+ actually enforce.
181
+ """
182
+ if name == "schema":
183
+ return {"type": "string", "const": PIPELINE_CLOSEOUT_SCHEMA}
184
+ if name == "terminal_status":
185
+ return {"type": "string", "enum": sorted(PIPELINE_CLOSEOUT_OUTCOMES)}
186
+ _, is_optional = _strip_optional(annotation)
187
+ prop = _annotation_schema(annotation)
188
+ null_allowed = is_optional and name not in CLOSEOUT_REQUIRED_NESTED_FIELDS
189
+ if null_allowed:
190
+ base_type = prop.get("type", "object")
191
+ if isinstance(base_type, str):
192
+ prop = {**prop, "type": [base_type, "null"]}
193
+ return prop
194
+
195
+
196
+ def build_schema() -> dict[str, Any]:
197
+ """Build the canonical JSON-Schema FROM the dataclass (single source of truth)."""
198
+ hints = typing.get_type_hints(PhaseLoopCloseout)
199
+ fields = dataclasses.fields(PhaseLoopCloseout)
200
+ properties = {f.name: _top_level_property(f.name, hints[f.name]) for f in fields}
201
+ # Required = the validator's mandatory nested fields + the `schema` literal.
202
+ # `spec_delta_closeout` is the only genuinely optional top-level field.
203
+ required = sorted({*CLOSEOUT_REQUIRED_NESTED_FIELDS, "schema"})
204
+ return {
205
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
206
+ "$id": PIPELINE_CLOSEOUT_SCHEMA,
207
+ "title": "phase_loop_closeout",
208
+ "$comment": _PROVENANCE,
209
+ "type": "object",
210
+ "required": required,
211
+ "additionalProperties": False,
212
+ "properties": {name: properties[name] for name in sorted(properties)},
213
+ }
214
+
215
+
216
+ def build_field_list() -> dict[str, Any]:
217
+ """Build the flat field-list payload gp consumes (all declared fields)."""
218
+ return {
219
+ "schema_id": PIPELINE_CLOSEOUT_SCHEMA,
220
+ "source": "phase_loop_runtime.models.PhaseLoopCloseout",
221
+ "fields": sorted(field_names()),
222
+ "required": sorted({*CLOSEOUT_REQUIRED_NESTED_FIELDS, "schema"}),
223
+ }
224
+
225
+
226
+ def render(schema: dict[str, Any]) -> str:
227
+ """Deterministic serialization: sorted keys, trailing newline, no volatile data."""
228
+ return json.dumps(schema, indent=2, sort_keys=True) + "\n"
229
+
230
+
231
+ def _resource_path() -> Path:
232
+ """Resolve the bundled artifact via importlib.resources (wheel-safe)."""
233
+ from .runtime_resources import package_root
234
+
235
+ root = package_root()
236
+ if root is not None:
237
+ packaged = root / SCHEMA_RESOURCE
238
+ if packaged.is_file():
239
+ return packaged
240
+ # Editable/source-layout fallback (same dir importlib.resources resolves once
241
+ # installed from a wheel).
242
+ fallback = Path(__file__).resolve().parent / SCHEMA_RESOURCE
243
+ if fallback.is_file():
244
+ return fallback
245
+ raise FileNotFoundError(
246
+ f"bundled closeout schema not found ({SCHEMA_RESOURCE}); regenerate via "
247
+ "`phase-loop export-schema --output <artifact>`"
248
+ )
249
+
250
+
251
+ def canonical_schema() -> dict[str, Any]:
252
+ """Load the bundled canonical schema artifact (the in-package, wheel-resolved copy)."""
253
+ return json.loads(_resource_path().read_text(encoding="utf-8"))
254
+
255
+
256
+ def _diff_json(canonical: Any, supplied: Any, path: str = "") -> list[str]:
257
+ """Recursively diff two JSON values, naming the actual differing paths."""
258
+ if isinstance(canonical, dict) and isinstance(supplied, dict):
259
+ diffs: list[str] = []
260
+ for key in sorted(set(canonical) | set(supplied)):
261
+ here = f"{path}.{key}" if path else key
262
+ if key not in supplied:
263
+ diffs.append(f"{here}: missing (canonical={canonical[key]!r})")
264
+ elif key not in canonical:
265
+ diffs.append(f"{here}: unexpected (supplied={supplied[key]!r})")
266
+ else:
267
+ diffs.extend(_diff_json(canonical[key], supplied[key], here))
268
+ return diffs
269
+ if isinstance(canonical, list) and isinstance(supplied, list):
270
+ if canonical != supplied:
271
+ return [f"{path or '<root>'}: list differs (canonical={canonical!r} supplied={supplied!r})"]
272
+ return []
273
+ if canonical != supplied:
274
+ return [f"{path or '<root>'}: canonical={canonical!r} supplied={supplied!r}"]
275
+ return []
276
+
277
+
278
+ def check(path: Path) -> list[str]:
279
+ """Compare a supplied artifact against the canonical schema built FRESH from the
280
+ live dataclass.
281
+
282
+ Building fresh (not loading the committed artifact) means a stale committed
283
+ artifact cannot mask a dataclass change — ``--check`` fails on any divergence
284
+ between the supplied artifact and the current dataclass-derived shape. Returns a
285
+ list of human-readable diffs naming the differing fields; empty means parity.
286
+ """
287
+ canonical = build_schema()
288
+ try:
289
+ supplied = json.loads(Path(path).read_text(encoding="utf-8"))
290
+ except (OSError, json.JSONDecodeError) as exc:
291
+ return [f"could not read/parse supplied artifact: {exc}"]
292
+ return _diff_json(canonical, supplied)