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,75 @@
1
+ """Verification-evidence closeout validator (rigor-v1 P5).
2
+
3
+ Closes the generic-phase hole: today only `RG` phases or plans opting in via
4
+ `IF-0-RG-1`/`--verification-log` require an evidence artifact, so any other phase
5
+ can self-report `verification_status=passed` with nothing backing it. This
6
+ validator raises a finding for those generic phases too.
7
+
8
+ Autonomy-first: ``block``-severity, so under the default ``PHASE_LOOP_REVIEW=warn``
9
+ it is recorded and the loop continues; it blocks only on opt-in. The agent
10
+ self-satisfies by attaching a verification artifact or recording a typed opt-out
11
+ reason (``verification_evidence_opt_out``) — no human required.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from typing import Mapping
17
+
18
+ from .closeout_validators import CloseoutContext, ReviewFinding, register_closeout_validator
19
+ from .models import VERIFICATION_EVIDENCE_OPT_OUT_REASONS
20
+
21
+
22
+ def _has_verification_artifact(terminal: Mapping) -> bool:
23
+ """Detect a verification artifact in the same places the upstream gate reads
24
+ (`closeout._verification_artifact_path`): the nested `artifact_paths` map, or
25
+ the top-level convenience key the skills tell executors to write."""
26
+ if terminal.get("verification_artifact_path"):
27
+ return True
28
+ artifact_paths = terminal.get("artifact_paths")
29
+ if isinstance(artifact_paths, Mapping):
30
+ return bool(
31
+ artifact_paths.get("verification")
32
+ or artifact_paths.get("verification_artifact")
33
+ or artifact_paths.get("verification_log")
34
+ or artifact_paths.get("root")
35
+ )
36
+ return False
37
+
38
+
39
+ def _legacy_evidence_gate_owns(phase_alias: str, plan_path: str) -> bool:
40
+ """True when the existing verification-evidence gate already governs this phase
41
+ (RG, or a plan opting into IF-0-RG-1/--verification-log). Those are handled
42
+ upstream in build_phase_loop_closeout, so this validator stays out of them."""
43
+ if phase_alias.upper() == "RG":
44
+ return True
45
+ try:
46
+ text = Path(plan_path).read_text(encoding="utf-8")
47
+ except OSError:
48
+ return False
49
+ return "IF-0-RG-1" in text or "--verification-log" in text
50
+
51
+
52
+ @register_closeout_validator
53
+ def verification_evidence_validator(ctx: CloseoutContext) -> list[ReviewFinding]:
54
+ reported = str(ctx.terminal.get("verification_status") or ctx.automation.get("verification_status") or "")
55
+ if reported != "passed":
56
+ return []
57
+ if _legacy_evidence_gate_owns(ctx.phase_alias, ctx.plan_path):
58
+ return [] # owned by the upstream evidence gate
59
+ if _has_verification_artifact(ctx.terminal):
60
+ return [] # evidence attached
61
+ opt_out = str(ctx.terminal.get("verification_evidence_opt_out") or "").strip()
62
+ if opt_out in VERIFICATION_EVIDENCE_OPT_OUT_REASONS:
63
+ return [] # declined with a typed reason
64
+ return [
65
+ ReviewFinding(
66
+ code="verification_evidence_missing_generic",
67
+ reason=(
68
+ "reported verification_status=passed with no verification_artifact_path; "
69
+ "attach the runner verification log or record a verification_evidence_opt_out "
70
+ f"reason ({', '.join(VERIFICATION_EVIDENCE_OPT_OUT_REASONS)})"
71
+ ),
72
+ severity="block",
73
+ blocker_class="review_gate_block",
74
+ )
75
+ ]
@@ -0,0 +1,36 @@
1
+ """Visual-evidence closeout validator (rigor-v1 P6).
2
+
3
+ When a phase changes UI/visual surfaces but attaches no visual evidence
4
+ (screenshot path + observed outcome), raise a finding. Autonomy-first:
5
+ ``block``-severity, so under the default ``PHASE_LOOP_REVIEW=warn`` it is
6
+ recorded and the loop continues; it blocks only on opt-in. The agent
7
+ self-satisfies by capturing a screenshot (claude-in-chrome / Playwright-via-PMCP)
8
+ and recording its path — no human eyeball is required to pass.
9
+
10
+ The executor records ``visual_evidence_path`` (and optionally
11
+ ``visual_evidence_observed``) in the terminal summary.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from .closeout_validators import CloseoutContext, ReviewFinding, register_closeout_validator
16
+ from .models import ui_change_detected
17
+
18
+
19
+ @register_closeout_validator
20
+ def visual_evidence_validator(ctx: CloseoutContext) -> list[ReviewFinding]:
21
+ if not ui_change_detected(ctx.changed_paths):
22
+ return []
23
+ if ctx.terminal.get("visual_evidence_path") or ctx.terminal.get("visual_evidence"):
24
+ return [] # evidence attached
25
+ return [
26
+ ReviewFinding(
27
+ code="visual_evidence_missing",
28
+ reason=(
29
+ "changed a UI/visual surface (*.tsx/jsx/vue/svelte/css or components/**) "
30
+ "but attached no visual_evidence_path; capture a screenshot "
31
+ "(claude-in-chrome or Playwright-via-PMCP) and record its path"
32
+ ),
33
+ severity="block",
34
+ blocker_class="review_gate_block",
35
+ )
36
+ ]
@@ -0,0 +1,123 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from time import time
9
+ from typing import Any
10
+
11
+ from .launcher import LaunchResult, LaunchSpec, launch_with_spec
12
+ from .observability import write_terminal_summary
13
+ from .runtime_paths import phase_loop_dir
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class PhaseWorkerJob:
18
+ phase: str
19
+ spec: LaunchSpec
20
+ log_path: Path | None = None
21
+ heartbeat_path: Path | None = None
22
+ dry_run: bool = False
23
+ stream_output: bool = False
24
+ heartbeat_interval_seconds: float = 30.0
25
+ quiet_warning_seconds: float = 300.0
26
+ quiet_blocker_seconds: float = 900.0
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class PhaseWorkerResult:
31
+ phase: str
32
+ result: LaunchResult
33
+ summary_path: Path
34
+ terminal_summary: dict[str, object]
35
+
36
+
37
+ def worker_summary_path(repo: Path, roadmap: Path, phase: str) -> Path:
38
+ return phase_loop_dir(repo) / _roadmap_slug(repo, roadmap) / "workers" / f"{phase}.summary.json"
39
+
40
+
41
+ def write_worker_summary(repo: Path, roadmap: Path, phase: str, summary: dict[str, Any]) -> Path:
42
+ path = worker_summary_path(repo, roadmap, phase)
43
+ write_terminal_summary(path, {"phase": phase, **summary})
44
+ return path
45
+
46
+
47
+ def read_worker_summary(
48
+ repo: Path,
49
+ roadmap: Path,
50
+ phase: str,
51
+ *,
52
+ stale_after_seconds: float = 1.0,
53
+ ) -> dict[str, Any]:
54
+ path = worker_summary_path(repo, roadmap, phase)
55
+ if not path.exists():
56
+ return {"status": "missing", "path": str(path)}
57
+ try:
58
+ data = json.loads(path.read_text(encoding="utf-8"))
59
+ except (OSError, json.JSONDecodeError) as exc:
60
+ stat = path.stat()
61
+ age_seconds = max(0.0, time() - stat.st_mtime)
62
+ status = "torn" if age_seconds >= stale_after_seconds and stat.st_size > 0 else "pending"
63
+ return {
64
+ "status": status,
65
+ "path": str(path),
66
+ "size": stat.st_size,
67
+ "age_seconds": age_seconds,
68
+ "error": str(exc),
69
+ }
70
+ if not isinstance(data, dict):
71
+ return {"status": "malformed", "path": str(path), "summary": data}
72
+ return {"status": "ok", "path": str(path), "summary": data}
73
+
74
+
75
+ def run_phase_worker_pool(
76
+ repo: Path,
77
+ roadmap: Path,
78
+ jobs: tuple[PhaseWorkerJob, ...] | list[PhaseWorkerJob],
79
+ *,
80
+ max_workers: int | None = None,
81
+ ) -> tuple[PhaseWorkerResult, ...]:
82
+ if not jobs:
83
+ return ()
84
+ worker_count = max_workers or min(len(jobs), os.cpu_count() or 1)
85
+ worker_count = max(1, min(worker_count, len(jobs)))
86
+ by_phase = {job.phase: index for index, job in enumerate(jobs)}
87
+ results: list[PhaseWorkerResult] = []
88
+ with ThreadPoolExecutor(max_workers=worker_count) as executor:
89
+ futures = {executor.submit(_run_one, repo, roadmap, job): job for job in jobs}
90
+ for future in as_completed(futures):
91
+ results.append(future.result())
92
+ return tuple(sorted(results, key=lambda item: by_phase[item.phase]))
93
+
94
+
95
+ def _run_one(repo: Path, roadmap: Path, job: PhaseWorkerJob) -> PhaseWorkerResult:
96
+ result = launch_with_spec(
97
+ job.spec,
98
+ dry_run=job.dry_run,
99
+ log_path=job.log_path,
100
+ heartbeat_path=job.heartbeat_path,
101
+ stream_output=job.stream_output,
102
+ heartbeat_interval_seconds=job.heartbeat_interval_seconds,
103
+ quiet_warning_seconds=job.quiet_warning_seconds,
104
+ quiet_blocker_seconds=job.quiet_blocker_seconds,
105
+ )
106
+ summary = {
107
+ "phase": job.phase,
108
+ "terminal_status": "blocked" if result.failed else "complete",
109
+ "verification_status": "blocked" if result.failed else "passed",
110
+ "returncode": result.returncode,
111
+ "executor": result.executor,
112
+ "log_path": result.log_path,
113
+ }
114
+ path = write_worker_summary(repo, roadmap, job.phase, summary)
115
+ return PhaseWorkerResult(phase=job.phase, result=result, summary_path=path, terminal_summary=summary)
116
+
117
+
118
+ def _roadmap_slug(repo: Path, roadmap: Path) -> str:
119
+ try:
120
+ value = str(roadmap.relative_to(repo))
121
+ except ValueError:
122
+ value = roadmap.name
123
+ return "".join(char if char.isalnum() or char in "._-" else "-" for char in value).strip("-") or "roadmap"
@@ -0,0 +1,270 @@
1
+ """Git-derived worktree freshness index (CS-0.10a).
2
+
3
+ Answers "where is the freshest working copy of a path, and who's touching it" by
4
+ reading `git worktree list` and diffing each worktree's branch against the base
5
+ ref (default: `origin/<default-branch>`, falling back to `origin/main`). Purely
6
+ git-derived and READ-ONLY: it never writes repo state, never mutates the working
7
+ tree, and holds no persistent index of its own — every call re-derives the answer
8
+ from `git` at invocation time, so it can't drift from reality the way a cached
9
+ index could.
10
+
11
+ If a repo has no worktrees beyond the primary one, or none of them touch the
12
+ queried path, the answer collapses to the `origin/main` baseline entry alone —
13
+ there is nothing else to report.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import subprocess
18
+ from dataclasses import asdict, dataclass
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class WorktreeRef:
25
+ """One entry from `git worktree list --porcelain`."""
26
+
27
+ path: str
28
+ branch: str | None
29
+ head_sha: str | None
30
+ bare: bool = False
31
+ detached: bool = False
32
+
33
+ def to_json(self) -> dict[str, Any]:
34
+ return asdict(self)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class Holder:
39
+ """A working copy (a worktree, or the `origin/main` baseline) that carries a
40
+ commit touching the queried path."""
41
+
42
+ worktree: str
43
+ branch: str
44
+ last_commit_sha: str
45
+ last_commit_time: str
46
+
47
+ def to_json(self) -> dict[str, Any]:
48
+ return asdict(self)
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class PathFreshness:
53
+ path: str
54
+ holders: tuple[Holder, ...]
55
+ main_behind: bool
56
+
57
+ def to_json(self) -> dict[str, Any]:
58
+ return {
59
+ "path": self.path,
60
+ "holders": [holder.to_json() for holder in self.holders],
61
+ "main_behind": self.main_behind,
62
+ }
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class WorktreeIndexReport:
67
+ base_ref: str
68
+ worktrees: tuple[WorktreeRef, ...]
69
+ paths: tuple[PathFreshness, ...]
70
+
71
+ def to_json(self) -> dict[str, Any]:
72
+ return {
73
+ "base_ref": self.base_ref,
74
+ "worktrees": [wt.to_json() for wt in self.worktrees],
75
+ "paths": [pf.to_json() for pf in self.paths],
76
+ }
77
+
78
+
79
+ def _git(repo: Path, *args: str) -> str | None:
80
+ try:
81
+ completed = subprocess.run(
82
+ ["git", "-C", str(repo), *args],
83
+ check=False,
84
+ stdout=subprocess.PIPE,
85
+ stderr=subprocess.DEVNULL,
86
+ text=True,
87
+ timeout=10,
88
+ )
89
+ except Exception:
90
+ return None
91
+ if completed.returncode != 0:
92
+ return None
93
+ return completed.stdout.strip() or None
94
+
95
+
96
+ def _is_ancestor(repo: Path, sha: str, ref: str) -> bool:
97
+ try:
98
+ completed = subprocess.run(
99
+ ["git", "-C", str(repo), "merge-base", "--is-ancestor", sha, ref],
100
+ check=False,
101
+ stdout=subprocess.DEVNULL,
102
+ stderr=subprocess.DEVNULL,
103
+ timeout=10,
104
+ )
105
+ except Exception:
106
+ return False
107
+ return completed.returncode == 0
108
+
109
+
110
+ def default_base_ref(repo: Path) -> str:
111
+ """`origin/<default-branch>` from the remote's HEAD symref, or `origin/main`."""
112
+ default_remote = _git(repo, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
113
+ return default_remote or "origin/main"
114
+
115
+
116
+ def list_worktrees(repo: Path) -> tuple[WorktreeRef, ...]:
117
+ """Parse `git worktree list --porcelain` into `WorktreeRef` entries."""
118
+ output = _git(repo, "worktree", "list", "--porcelain")
119
+ if not output:
120
+ return ()
121
+
122
+ entries: list[WorktreeRef] = []
123
+ path: str | None = None
124
+ head: str | None = None
125
+ branch: str | None = None
126
+ bare = False
127
+ detached = False
128
+
129
+ def flush() -> None:
130
+ nonlocal path, head, branch, bare, detached
131
+ if path is not None:
132
+ entries.append(WorktreeRef(path=path, branch=branch, head_sha=head, bare=bare, detached=detached))
133
+ path, head, branch, bare, detached = None, None, None, False, False
134
+
135
+ for line in output.splitlines():
136
+ if not line.strip():
137
+ flush()
138
+ elif line.startswith("worktree "):
139
+ flush()
140
+ path = line[len("worktree "):].strip()
141
+ elif line.startswith("HEAD "):
142
+ head = line[len("HEAD "):].strip()
143
+ elif line.startswith("branch "):
144
+ branch = line[len("branch "):].strip().removeprefix("refs/heads/")
145
+ elif line == "bare":
146
+ bare = True
147
+ elif line == "detached":
148
+ detached = True
149
+ flush()
150
+ return tuple(entries)
151
+
152
+
153
+ def branch_touched_paths(repo: Path, branch: str, base_ref: str) -> tuple[str, ...]:
154
+ """Repo-relative paths `branch` touches relative to `base_ref` (`git diff --name-only`)."""
155
+ output = _git(repo, "diff", f"{base_ref}...{branch}", "--name-only")
156
+ if not output:
157
+ return ()
158
+ return tuple(line.strip() for line in output.splitlines() if line.strip())
159
+
160
+
161
+ def _last_commit(repo: Path, rev: str, path: str) -> tuple[str, str] | None:
162
+ output = _git(repo, "log", "-1", "--format=%H\x1f%cI", rev, "--", path)
163
+ if not output:
164
+ return None
165
+ parts = output.split("\x1f")
166
+ if len(parts) != 2:
167
+ return None
168
+ sha, when = parts
169
+ return sha, when
170
+
171
+
172
+ def _path_freshness(
173
+ repo: Path,
174
+ path: str,
175
+ base_ref: str,
176
+ touched_by_worktree: dict[WorktreeRef, tuple[str, ...]],
177
+ ) -> PathFreshness:
178
+ holders: list[Holder] = []
179
+
180
+ baseline = _last_commit(repo, base_ref, path)
181
+ if baseline is not None:
182
+ sha, when = baseline
183
+ holders.append(Holder(worktree="origin/main", branch=base_ref, last_commit_sha=sha, last_commit_time=when))
184
+
185
+ main_behind = False
186
+ for worktree, touched in touched_by_worktree.items():
187
+ if path not in touched:
188
+ continue
189
+ commit = _last_commit(repo, worktree.branch or worktree.head_sha or "HEAD", path)
190
+ if commit is None:
191
+ continue
192
+ sha, when = commit
193
+ holders.append(
194
+ Holder(worktree=worktree.path, branch=worktree.branch or "detached", last_commit_sha=sha, last_commit_time=when)
195
+ )
196
+ if not _is_ancestor(repo, sha, base_ref):
197
+ main_behind = True
198
+
199
+ return PathFreshness(path=path, holders=tuple(holders), main_behind=main_behind)
200
+
201
+
202
+ def build_index(repo: Path, *, base_ref: str | None = None, path: str | None = None) -> WorktreeIndexReport:
203
+ """Build the freshness index. `path=None` reports every path any active
204
+ worktree touches relative to `base_ref`; a repo with no diverging worktrees
205
+ yields an empty `paths` tuple — the answer is `origin/main`."""
206
+ resolved_base = base_ref or default_base_ref(repo)
207
+ worktrees = list_worktrees(repo)
208
+
209
+ touched_by_worktree: dict[WorktreeRef, tuple[str, ...]] = {}
210
+ for worktree in worktrees:
211
+ if worktree.bare or not worktree.branch:
212
+ continue
213
+ touched_by_worktree[worktree] = branch_touched_paths(repo, worktree.branch, resolved_base)
214
+
215
+ if path is not None:
216
+ candidate_paths: tuple[str, ...] = (path,)
217
+ else:
218
+ seen: dict[str, None] = {}
219
+ for touched in touched_by_worktree.values():
220
+ for touched_path in touched:
221
+ seen.setdefault(touched_path, None)
222
+ candidate_paths = tuple(sorted(seen))
223
+
224
+ paths = tuple(_path_freshness(repo, p, resolved_base, touched_by_worktree) for p in candidate_paths)
225
+ return WorktreeIndexReport(base_ref=resolved_base, worktrees=worktrees, paths=paths)
226
+
227
+
228
+ def render_human(report: WorktreeIndexReport) -> str:
229
+ lines = [f"base_ref: {report.base_ref}", f"worktrees ({len(report.worktrees)}):"]
230
+ for worktree in report.worktrees:
231
+ tag = " [detached]" if worktree.detached else ""
232
+ lines.append(f" {worktree.path} branch={worktree.branch or '-'}{tag} head={worktree.head_sha or '-'}")
233
+
234
+ if not report.paths:
235
+ lines.append("")
236
+ lines.append("no divergent paths relative to base_ref — origin/main is the answer")
237
+ return "\n".join(lines)
238
+
239
+ lines.append("")
240
+ for path_freshness in report.paths:
241
+ behind = " [main behind]" if path_freshness.main_behind else ""
242
+ lines.append(f"{path_freshness.path}{behind}")
243
+ if not path_freshness.holders:
244
+ lines.append(" (no holders)")
245
+ continue
246
+ for holder in path_freshness.holders:
247
+ lines.append(
248
+ f" {holder.branch:<30} {holder.worktree} {holder.last_commit_sha[:12]} {holder.last_commit_time}"
249
+ )
250
+ return "\n".join(lines)
251
+
252
+
253
+ def main(argv: list[str] | None = None) -> int:
254
+ import argparse
255
+ import json
256
+
257
+ parser = argparse.ArgumentParser(prog="phase-loop worktree-index")
258
+ parser.add_argument("--repo", default=".", help="Repository root (default: cwd).")
259
+ parser.add_argument("--path", help="Report freshness holders for a single repo-relative path.")
260
+ parser.add_argument("--base", help="Diff base ref (default: origin/<default-branch>, falling back to origin/main).")
261
+ parser.add_argument("--json", action="store_true", help="Emit the report as JSON.")
262
+ # tolerate the leading 'worktree-index' token when dispatched from the main CLI
263
+ args = parser.parse_args([a for a in (argv or []) if a != "worktree-index"])
264
+
265
+ report = build_index(Path(args.repo), base_ref=args.base, path=args.path)
266
+ if args.json:
267
+ print(json.dumps(report.to_json(), indent=2))
268
+ else:
269
+ print(render_human(report))
270
+ return 0