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,676 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ from .discovery import (
9
+ WORKFLOW_EXECUTE_SKILLS,
10
+ WORKFLOW_PLAN_SKILLS,
11
+ PLAN_RE,
12
+ latest_workflow_handoff,
13
+ repo_identity,
14
+ )
15
+ from .events import event_path, event_read_paths, read_events
16
+ from .git_topology import collect_git_topology
17
+ from .handoff import tui_handoff_path
18
+ from .models import utc_now
19
+ from .observability import (
20
+ phase_loop_metrics_path,
21
+ read_launch_metadata,
22
+ read_run_heartbeat,
23
+ read_task_snapshot,
24
+ read_terminal_summary,
25
+ read_work_unit_metrics,
26
+ summarize_work_unit_metrics,
27
+ )
28
+ from .provenance import roadmap_sha256
29
+ from .reconcile import reconcile
30
+ from .runtime_paths import (
31
+ ensure_phase_loop_excluded,
32
+ phase_loop_active_loop_file,
33
+ phase_loop_active_loop_read_files,
34
+ phase_loop_dir,
35
+ legacy_phase_loop_dir,
36
+ phase_loop_read_dir,
37
+ phase_loop_runs_dir,
38
+ phase_loop_runs_dirs,
39
+ phase_loop_stop_file,
40
+ phase_loop_stop_files,
41
+ phase_loop_tui_handoff_read_file,
42
+ )
43
+ from .state import load_state, state_path, state_read_path
44
+
45
+
46
+ def active_loop_path(repo: Path) -> Path:
47
+ return phase_loop_active_loop_file(repo)
48
+
49
+
50
+ def _active_loop_exists(repo: Path) -> bool:
51
+ return any(_active_loop_file_is_live(path) for path in phase_loop_active_loop_read_files(repo))
52
+
53
+
54
+ def _active_loop_file_is_live(path: Path) -> bool:
55
+ if not path.exists():
56
+ return False
57
+ try:
58
+ data = json.loads(path.read_text(encoding="utf-8"))
59
+ except (OSError, json.JSONDecodeError):
60
+ return True
61
+ pid = data.get("pid") if isinstance(data, dict) else None
62
+ if isinstance(pid, int) and not _pid_is_live(pid):
63
+ path.unlink(missing_ok=True)
64
+ return False
65
+ return True
66
+
67
+
68
+ def _pid_is_live(pid: int) -> bool:
69
+ if pid <= 0:
70
+ return False
71
+ try:
72
+ os.kill(pid, 0)
73
+ except ProcessLookupError:
74
+ return False
75
+ except PermissionError:
76
+ return True
77
+ return True
78
+
79
+
80
+ def archive_root(repo: Path) -> Path:
81
+ return phase_loop_dir(repo) / "archive"
82
+
83
+
84
+ def state_files(repo: Path) -> tuple[Path, ...]:
85
+ files: list[Path] = [
86
+ state_path(repo),
87
+ event_path(repo),
88
+ active_loop_path(repo),
89
+ phase_loop_stop_file(repo),
90
+ tui_handoff_path(repo),
91
+ ]
92
+ files.extend(path for path in event_read_paths(repo) if path not in files)
93
+ files.extend(path for path in phase_loop_active_loop_read_files(repo) if path not in files)
94
+ files.extend(path for path in phase_loop_stop_files(repo) if path not in files)
95
+ legacy_handoff = phase_loop_tui_handoff_read_file(repo)
96
+ if legacy_handoff not in files:
97
+ files.append(legacy_handoff)
98
+ legacy_state = state_read_path(repo)
99
+ if legacy_state not in files:
100
+ files.append(legacy_state)
101
+ legacy_root = legacy_phase_loop_dir(repo)
102
+ for name in ("state.json", "events.jsonl", "active-loop.json", "stop", "tui-handoff.md"):
103
+ legacy_file = legacy_root / name
104
+ if legacy_file not in files:
105
+ files.append(legacy_file)
106
+ return tuple(files)
107
+
108
+
109
+ def inspect_state(repo: Path, roadmap: Path | None = None) -> dict[str, object]:
110
+ state = load_state(repo)
111
+ events = read_events(repo)
112
+ last_event = _last_event_summary(events[-1]) if events else None
113
+ legacy_count = sum(1 for event in events if int(event.get("schema_version", 1)) < 2)
114
+ latest_launch = _latest_launch_metadata(repo, events)
115
+ latest_task_ledger = _latest_task_ledger(latest_launch)
116
+ recent_metrics = read_work_unit_metrics(repo, limit=50)
117
+ latest_metric = recent_metrics[-1] if recent_metrics else None
118
+ summary: dict[str, object] = {
119
+ "repo": str(repo),
120
+ "state_path": str(state_path(repo)),
121
+ "state_read_path": str(state_read_path(repo)),
122
+ "event_path": str(event_path(repo)),
123
+ "event_read_paths": [str(path) for path in event_read_paths(repo) if path.exists()],
124
+ "active_loop_path": str(active_loop_path(repo)),
125
+ "runs_path": str(phase_loop_runs_dir(repo)),
126
+ "stop_file": str(phase_loop_stop_file(repo)),
127
+ "tui_handoff_path": str(tui_handoff_path(repo)),
128
+ "metrics_path": str(phase_loop_metrics_path(repo)),
129
+ "runtime_root": str(phase_loop_dir(repo)),
130
+ "runtime_read_root": str(phase_loop_read_dir(repo)),
131
+ "archive_root": str(archive_root(repo)),
132
+ "state_exists": state_read_path(repo).exists(),
133
+ "events_exists": any(path.exists() for path in event_read_paths(repo)),
134
+ "active_loop_exists": _active_loop_exists(repo),
135
+ "stop_requested": any(path.exists() for path in phase_loop_stop_files(repo)),
136
+ "tui_handoff_exists": phase_loop_tui_handoff_read_file(repo).exists(),
137
+ "event_count": len(events),
138
+ "last_event": last_event,
139
+ "latest_launch_metadata": latest_launch,
140
+ "latest_heartbeat": _latest_heartbeat(repo, events),
141
+ "latest_terminal_summary": _latest_terminal_summary(repo, events),
142
+ "latest_task_snapshot": latest_task_ledger.get("snapshot"),
143
+ "latest_hook_summary": latest_task_ledger.get("hook_summary"),
144
+ "latest_metric": latest_metric,
145
+ "metrics_summary": summarize_work_unit_metrics(recent_metrics),
146
+ "latest_work_unit": state.latest_work_unit if state else None,
147
+ "work_unit_status": _work_unit_status(state.latest_work_unit if state else None),
148
+ "legacy_count": legacy_count + (1 if state and state.schema_version < 2 else 0),
149
+ "stored_roadmap": state.roadmap if state else None,
150
+ "stored_roadmap_sha256": state.roadmap_sha256 if state else None,
151
+ "git_topology": collect_git_topology(repo),
152
+ }
153
+ if roadmap is not None and roadmap.exists():
154
+ snapshot = reconcile(repo, roadmap)
155
+ summary.update(
156
+ {
157
+ "roadmap": str(roadmap),
158
+ "roadmap_sha256": roadmap_sha256(roadmap),
159
+ "current_phase": snapshot.current_phase,
160
+ "phase_status": snapshot.phases.get(snapshot.current_phase) if snapshot.current_phase else None,
161
+ "phases": snapshot.phases,
162
+ "human_required": snapshot.human_required,
163
+ "blocker_class": snapshot.blocker_class,
164
+ "blocker_summary": snapshot.blocker_summary,
165
+ "required_human_inputs": snapshot.required_human_inputs,
166
+ "access_attempts": snapshot.access_attempts,
167
+ "dirty_paths": snapshot.dirty_paths,
168
+ "phase_owned_dirty_paths": snapshot.phase_owned_dirty_paths,
169
+ "previous_phase_owned_paths": snapshot.previous_phase_owned_paths,
170
+ "unowned_dirty_paths": snapshot.unowned_dirty_paths,
171
+ "pre_existing_dirty_paths": snapshot.pre_existing_dirty_paths,
172
+ "phase_owned_dirty": snapshot.phase_owned_dirty,
173
+ "terminal_summary": snapshot.terminal_summary,
174
+ "closeout_terminal_status": snapshot.closeout_terminal_status,
175
+ "closeout_summary": snapshot.closeout_summary,
176
+ "ledger_warnings": list(snapshot.ledger_warnings),
177
+ "mismatch_count": len(snapshot.ledger_warnings),
178
+ }
179
+ )
180
+ if snapshot.terminal_summary:
181
+ summary["latest_terminal_summary"] = snapshot.terminal_summary
182
+ if snapshot.current_phase is None and snapshot.phases and all(status == "complete" for status in snapshot.phases.values()):
183
+ summary["latest_terminal_summary"] = None
184
+ else:
185
+ summary["mismatch_count"] = 0
186
+ summary["monitor_status"] = _monitor_status(summary)
187
+ if isinstance(summary["monitor_status"], dict):
188
+ summary["monitor_status"]["latest_metric"] = latest_metric
189
+ summary["monitor_status"]["metrics_summary"] = summary["metrics_summary"]
190
+ summary["monitor_status"]["work_unit"] = summary.get("latest_work_unit")
191
+ summary["monitor_status"]["work_unit_status"] = summary.get("work_unit_status")
192
+ return summary
193
+
194
+
195
+ def _work_unit_status(work_unit: dict[str, object] | None) -> str | None:
196
+ if not isinstance(work_unit, dict):
197
+ return None
198
+ status = work_unit.get("status")
199
+ return str(status) if status is not None else None
200
+
201
+
202
+ def archive_state(repo: Path, reason: str | None = None, dry_run: bool = False) -> dict[str, object]:
203
+ # #39: --dry-run must be read-only. Compute the planned move set without
204
+ # creating the archive dir, renaming any file, or writing the manifest.
205
+ ensure_phase_loop_excluded(repo)
206
+ files = [path for path in state_files(repo) if path.exists()]
207
+ archive_dir = archive_root(repo) / utc_now().replace(":", "").replace("-", "")
208
+ moved: list[dict[str, str]] = []
209
+ if files:
210
+ if not dry_run:
211
+ archive_dir.mkdir(parents=True, exist_ok=True)
212
+ for source in files:
213
+ try:
214
+ destination_name = source.resolve().relative_to(repo.resolve()).as_posix().replace("/", "__")
215
+ except (OSError, ValueError):
216
+ destination_name = source.name
217
+ destination = archive_dir / destination_name
218
+ if not dry_run:
219
+ shutil.move(str(source), destination)
220
+ moved.append({"source": str(source), "destination": str(destination)})
221
+ manifest = {
222
+ "timestamp": utc_now(),
223
+ "repo": str(repo),
224
+ "reason": reason or "",
225
+ "moved": moved,
226
+ }
227
+ if files and not dry_run:
228
+ (archive_dir / "archive.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
229
+ return {
230
+ "repo": str(repo),
231
+ "archive_path": str(archive_dir) if files else None,
232
+ "moved": moved,
233
+ "archived": bool(files) and not dry_run,
234
+ "dry_run": dry_run,
235
+ "reason": reason or "",
236
+ }
237
+
238
+
239
+ def _last_event_summary(event: dict) -> dict[str, object]:
240
+ keys = ("timestamp", "phase", "action", "status", "source", "schema_version", "roadmap_sha256", "phase_sha256")
241
+ return {key: event.get(key) for key in keys if key in event}
242
+
243
+
244
+ def _trusted_workflow_handoff(repo: Path, roadmap: Path) -> dict[str, object] | None:
245
+ identity = repo_identity(repo)
246
+ handoff = latest_workflow_handoff(
247
+ identity,
248
+ repo,
249
+ roadmap,
250
+ (*WORKFLOW_EXECUTE_SKILLS, *WORKFLOW_PLAN_SKILLS),
251
+ )
252
+ if not handoff:
253
+ return None
254
+ artifact = handoff.get("artifact")
255
+ phase = None
256
+ if artifact:
257
+ match = PLAN_RE.search(Path(str(artifact)).name)
258
+ if match:
259
+ phase = match.group(2).upper()
260
+ return {
261
+ "workflow_skill": handoff.get("workflow_skill"),
262
+ "originating_harness": handoff.get("originating_harness"),
263
+ "phase": phase,
264
+ "status": handoff.get("automation_status"),
265
+ "artifact": artifact,
266
+ "timestamp": handoff.get("timestamp"),
267
+ }
268
+
269
+
270
+ def _latest_manual_import(events: list[dict], roadmap: Path) -> dict[str, object] | None:
271
+ roadmap_value = str(roadmap.resolve())
272
+ for event in reversed(events):
273
+ event_roadmap = event.get("roadmap")
274
+ if isinstance(event_roadmap, str) and event_roadmap:
275
+ if str(Path(event_roadmap).expanduser().resolve()) != roadmap_value:
276
+ continue
277
+ metadata = event.get("metadata") if isinstance(event.get("metadata"), dict) else {}
278
+ manual = metadata.get("manual_import") if isinstance(metadata.get("manual_import"), dict) else {}
279
+ if not manual:
280
+ continue
281
+ return {
282
+ "phase": event.get("phase"),
283
+ "status": event.get("status"),
284
+ "timestamp": event.get("timestamp"),
285
+ "originating_harness": manual.get("originating_harness"),
286
+ "workflow_skill": manual.get("workflow_skill"),
287
+ "artifact": manual.get("artifact"),
288
+ "installed_skill_warnings": manual.get("installed_skill_warnings", []),
289
+ "bridge_skill_inventory": manual.get("bridge_skill_inventory", []),
290
+ }
291
+ return None
292
+
293
+
294
+ def _latest_heartbeat(repo: Path, events: list[dict]) -> dict[str, object] | None:
295
+ active = _active_loop_heartbeat(repo)
296
+ if active:
297
+ return active
298
+ for event in reversed(events):
299
+ metadata = event.get("metadata") or {}
300
+ artifacts = metadata.get("artifacts") or {}
301
+ if isinstance(artifacts, dict):
302
+ heartbeat = read_run_heartbeat(artifacts.get("heartbeat"))
303
+ if heartbeat:
304
+ return heartbeat
305
+ launch = metadata.get("launch") or {}
306
+ if isinstance(launch, dict):
307
+ heartbeat = read_run_heartbeat(launch.get("heartbeat_path"))
308
+ if heartbeat:
309
+ return heartbeat
310
+ return None
311
+
312
+
313
+ def _latest_launch_metadata(repo: Path, events: list[dict]) -> dict[str, object] | None:
314
+ active = _active_loop_launch_metadata(repo)
315
+ if active:
316
+ return active
317
+ for event in reversed(events):
318
+ metadata = event.get("metadata") or {}
319
+ artifacts = metadata.get("artifacts") or {}
320
+ if isinstance(artifacts, dict):
321
+ launch_path = artifacts.get("metadata")
322
+ if launch_path:
323
+ try:
324
+ data = json.loads(Path(str(launch_path)).read_text(encoding="utf-8"))
325
+ except (OSError, json.JSONDecodeError):
326
+ data = None
327
+ if isinstance(data, dict):
328
+ return data
329
+ launch_spec = metadata.get("launch_spec")
330
+ if isinstance(launch_spec, dict):
331
+ return launch_spec
332
+ latest_run = _latest_run_dir(repo)
333
+ if latest_run:
334
+ data = read_launch_metadata(latest_run / "launch.json")
335
+ if data:
336
+ return data
337
+ return None
338
+
339
+
340
+ def _active_loop_launch_metadata(repo: Path) -> dict[str, object] | None:
341
+ if not _active_loop_exists(repo):
342
+ return None
343
+ heartbeat = _active_loop_heartbeat(repo)
344
+ if not heartbeat:
345
+ return None
346
+ heartbeat_path = heartbeat.get("heartbeat_path")
347
+ if not isinstance(heartbeat_path, str) or not heartbeat_path:
348
+ return None
349
+ return read_launch_metadata(Path(heartbeat_path).parent / "launch.json")
350
+
351
+
352
+ def _latest_run_dir(repo: Path) -> Path | None:
353
+ candidates: list[Path] = []
354
+ for runs in phase_loop_runs_dirs(repo):
355
+ if runs.exists():
356
+ candidates.extend(path for path in runs.iterdir() if path.is_dir())
357
+ if not candidates:
358
+ return None
359
+ return max(candidates, key=lambda path: path.stat().st_mtime)
360
+
361
+
362
+ def _latest_task_ledger(launch_metadata: dict[str, object] | None) -> dict[str, object]:
363
+ if not isinstance(launch_metadata, dict):
364
+ return {"snapshot": None, "hook_summary": None}
365
+ task_ledger = launch_metadata.get("task_ledger_artifacts")
366
+ if not isinstance(task_ledger, dict):
367
+ return {"snapshot": None, "hook_summary": None}
368
+ snapshot = read_task_snapshot(task_ledger.get("snapshot_path"))
369
+ hook_summary = None
370
+ hook_manifest = task_ledger.get("hook_manifest_path")
371
+ if isinstance(hook_manifest, str) and hook_manifest:
372
+ try:
373
+ parsed = json.loads(Path(hook_manifest).read_text(encoding="utf-8"))
374
+ except (OSError, json.JSONDecodeError):
375
+ parsed = None
376
+ if isinstance(parsed, dict):
377
+ hook_summary = parsed
378
+ return {"snapshot": snapshot, "hook_summary": hook_summary}
379
+
380
+
381
+ def _active_loop_heartbeat(repo: Path) -> dict[str, object] | None:
382
+ if not _active_loop_exists(repo):
383
+ return None
384
+ candidates: list[Path] = []
385
+ for runs in phase_loop_runs_dirs(repo):
386
+ if runs.exists():
387
+ candidates.extend(runs.glob("*/heartbeat.json"))
388
+ candidates = sorted(candidates, key=lambda path: path.stat().st_mtime if path.exists() else 0, reverse=True)
389
+ for candidate in candidates:
390
+ heartbeat = read_run_heartbeat(candidate)
391
+ if heartbeat:
392
+ return heartbeat
393
+ return None
394
+
395
+
396
+ def _latest_terminal_summary(repo: Path, events: list[dict]) -> dict[str, object] | None:
397
+ state = load_state(repo)
398
+ if state and state.terminal_summary:
399
+ return state.terminal_summary
400
+ superseded_phases: set[str] = set()
401
+ for event in reversed(events):
402
+ phase = str(event.get("phase", "")).upper()
403
+ metadata = event.get("metadata") or {}
404
+ if _terminal_summary_superseded(event):
405
+ if phase:
406
+ superseded_phases.add(phase)
407
+ continue
408
+ terminal = metadata.get("terminal_summary")
409
+ if isinstance(terminal, dict) and phase not in superseded_phases:
410
+ return terminal
411
+ artifacts = metadata.get("artifacts") or {}
412
+ if isinstance(artifacts, dict):
413
+ summary = read_terminal_summary(artifacts.get("terminal"))
414
+ if summary and phase not in superseded_phases:
415
+ return summary
416
+ if phase not in superseded_phases and _terminal_artifact_expected(event, artifacts.get("terminal")):
417
+ return None
418
+ launch = metadata.get("launch") or {}
419
+ if isinstance(launch, dict):
420
+ summary = read_terminal_summary(launch.get("terminal_path"))
421
+ if summary and phase not in superseded_phases:
422
+ return summary
423
+ if phase not in superseded_phases and _terminal_artifact_expected(event, launch.get("terminal_path")):
424
+ return None
425
+ return None
426
+
427
+
428
+ def _terminal_summary_superseded(event: dict) -> bool:
429
+ if event.get("status") not in {"complete", "planned"} or event.get("action") != "manual_repair":
430
+ return False
431
+ metadata = event.get("metadata")
432
+ if not isinstance(metadata, dict):
433
+ return False
434
+ manual_repair = metadata.get("manual_repair")
435
+ if isinstance(manual_repair, dict):
436
+ return bool(manual_repair.get("clears_blocker"))
437
+ return bool(metadata.get("clears_blocker"))
438
+
439
+
440
+ def _monitor_status(summary: dict[str, object]) -> dict[str, object]:
441
+ repo_value = summary.get("repo")
442
+ roadmap_value = summary.get("roadmap")
443
+ repo_path = Path(str(repo_value)).expanduser().resolve() if isinstance(repo_value, str) and repo_value else None
444
+ roadmap_path = Path(str(roadmap_value)).expanduser().resolve() if isinstance(roadmap_value, str) and roadmap_value else None
445
+ phases = summary.get("phases") if isinstance(summary.get("phases"), dict) else {}
446
+ current_phase = summary.get("current_phase")
447
+ current_status = phases.get(current_phase) if current_phase else ("complete" if phases and all(status == "complete" for status in phases.values()) else "unknown")
448
+ heartbeat = summary.get("latest_heartbeat") if isinstance(summary.get("latest_heartbeat"), dict) else {}
449
+ terminal = summary.get("terminal_summary") if isinstance(summary.get("terminal_summary"), dict) else {}
450
+ launch = summary.get("latest_launch_metadata") if isinstance(summary.get("latest_launch_metadata"), dict) else {}
451
+ task_snapshot = summary.get("latest_task_snapshot") if isinstance(summary.get("latest_task_snapshot"), dict) else {}
452
+ hook_summary = summary.get("latest_hook_summary") if isinstance(summary.get("latest_hook_summary"), dict) else {}
453
+ heartbeat_process_alive = bool(heartbeat.get("process_alive"))
454
+ launch_phase = str(launch.get("phase") or "").upper()
455
+ active_heartbeat_for_current_phase = bool(
456
+ heartbeat_process_alive
457
+ and current_phase
458
+ and launch_phase == str(current_phase).upper()
459
+ and heartbeat.get("event_kind", "heartbeat") == "heartbeat"
460
+ )
461
+ reported_current_status = (
462
+ "executing"
463
+ if active_heartbeat_for_current_phase or (heartbeat_process_alive and current_status in {"planned", "executing"})
464
+ else current_status
465
+ )
466
+ event_kind = "heartbeat"
467
+ recommended_action = "Continue observing."
468
+ terminal_status = terminal.get("terminal_status") if terminal else None
469
+ if summary.get("stop_requested"):
470
+ event_kind = "operator_halt"
471
+ recommended_action = "Remove the stop file only after the operator is ready to resume."
472
+ elif current_status == "complete" and current_phase is None:
473
+ event_kind = "complete"
474
+ recommended_action = "No resume command is required; this roadmap is complete."
475
+ elif active_heartbeat_for_current_phase:
476
+ event_kind = str(heartbeat.get("event_kind") or "heartbeat")
477
+ recommended_action = heartbeat.get("recommended_action") or recommended_action
478
+ elif terminal_status and not heartbeat_process_alive:
479
+ event_kind = "terminal_exit"
480
+ recommended_action = terminal.get("next_action") or "Inspect the terminal summary before deciding whether to resume."
481
+ elif current_status == "blocked" or summary.get("human_required"):
482
+ event_kind = "blocked"
483
+ recommended_action = "Inspect the TUI handoff, state JSON, event ledger, and terminal summary before resuming."
484
+ elif current_status == "awaiting_phase_closeout":
485
+ event_kind = "awaiting_phase_closeout"
486
+ recommended_action = "Preserve verified phase-owned output with an explicit closeout policy before relaunching."
487
+ elif heartbeat.get("heartbeat_status") == "stale" or heartbeat.get("quiet_level") == "stale":
488
+ event_kind = "stale"
489
+ recommended_action = heartbeat.get("recommended_action") or "Inspect the latest run log and nudge the owning TUI if needed."
490
+ elif heartbeat_process_alive and heartbeat.get("event_kind"):
491
+ event_kind = str(heartbeat["event_kind"])
492
+ recommended_action = heartbeat.get("recommended_action") or recommended_action
493
+ elif terminal_status:
494
+ event_kind = "terminal_exit"
495
+ recommended_action = terminal.get("next_action") or "Inspect the terminal summary before deciding whether to resume."
496
+ elif heartbeat.get("event_kind"):
497
+ event_kind = str(heartbeat["event_kind"])
498
+ recommended_action = heartbeat.get("recommended_action") or recommended_action
499
+ monitor = {
500
+ "event_kind": event_kind,
501
+ "current_phase": current_phase,
502
+ "current_status": reported_current_status,
503
+ "phase_status": current_status,
504
+ "active_loop_exists": summary.get("active_loop_exists", False),
505
+ "stop_requested": summary.get("stop_requested", False),
506
+ "human_required": summary.get("human_required", False),
507
+ "blocker_class": None if active_heartbeat_for_current_phase else summary.get("blocker_class"),
508
+ "heartbeat_status": heartbeat.get("heartbeat_status") or heartbeat.get("quiet_level"),
509
+ "terminal_status": None if heartbeat_process_alive else terminal_status,
510
+ "recommended_action": recommended_action,
511
+ "state_path": summary.get("state_path"),
512
+ "event_path": summary.get("event_path"),
513
+ "tui_handoff_path": summary.get("tui_handoff_path"),
514
+ "runs_path": summary.get("runs_path"),
515
+ }
516
+ if isinstance(summary.get("latest_work_unit"), dict):
517
+ monitor["work_unit"] = summary["latest_work_unit"]
518
+ monitor["work_unit_status"] = summary.get("work_unit_status")
519
+ dispatch = launch.get("dispatch_decision")
520
+ if isinstance(dispatch, dict):
521
+ monitor["selected_executor"] = dispatch.get("selected_executor")
522
+ monitor["dispatch_source"] = dispatch.get("source")
523
+ monitor["selected_via"] = dispatch.get("selected_via")
524
+ monitor["fallback_applied"] = dispatch.get("fallback_applied", False)
525
+ monitor["considered_executors"] = dispatch.get("considered_executors")
526
+ monitor["dispatch_blocked_reason"] = dispatch.get("blocked_reason")
527
+ elif launch.get("executor"):
528
+ monitor["selected_executor"] = launch.get("executor")
529
+ team_policy = launch.get("claude_team_policy")
530
+ if isinstance(team_policy, dict):
531
+ monitor["claude_team_policy"] = {
532
+ "execution_mode": launch.get("claude_execution_mode"),
533
+ "maturity_label": team_policy.get("maturity_label"),
534
+ "max_teammates": team_policy.get("max_teammates"),
535
+ "max_native_tasks": team_policy.get("max_native_tasks"),
536
+ "max_fanout": team_policy.get("max_fanout"),
537
+ "worktree_posture": team_policy.get("worktree_posture"),
538
+ }
539
+ eligibility = launch.get("phase_team_eligibility")
540
+ if isinstance(eligibility, dict):
541
+ monitor["phase_team_eligibility"] = {
542
+ "allowed_execution_modes": eligibility.get("allowed_execution_modes"),
543
+ "eligible_for_native_team": eligibility.get("eligible_for_native_team"),
544
+ "reason": eligibility.get("reason"),
545
+ "invalid_reasons": eligibility.get("invalid_reasons"),
546
+ }
547
+ task_ledger = launch.get("task_ledger_artifacts")
548
+ if isinstance(task_ledger, dict):
549
+ monitor["task_snapshot_path"] = task_ledger.get("snapshot_path")
550
+ monitor["hook_manifest_path"] = task_ledger.get("hook_manifest_path")
551
+ if task_ledger.get("hook_policy_inventory"):
552
+ monitor["hook_policy_inventory"] = task_ledger.get("hook_policy_inventory")
553
+ freshness = _task_snapshot_freshness(
554
+ task_snapshot=task_snapshot,
555
+ heartbeat=heartbeat,
556
+ terminal=terminal,
557
+ current_status=str(current_status),
558
+ )
559
+ monitor["task_snapshot_freshness"] = freshness
560
+ latest_activity = _task_latest_activity(task_snapshot, terminal, current_status, freshness)
561
+ if latest_activity:
562
+ monitor["latest_team_activity"] = latest_activity
563
+ wait_classification = _claude_wait_classification(
564
+ execution_mode=str(launch.get("claude_execution_mode") or "solo"),
565
+ task_snapshot=task_snapshot,
566
+ freshness=freshness,
567
+ )
568
+ monitor["wait_classification"] = wait_classification
569
+ if hook_summary.get("activity_source"):
570
+ monitor["hook_activity_source"] = hook_summary.get("activity_source")
571
+ delegation = launch.get("delegation_decision")
572
+ if isinstance(delegation, dict):
573
+ monitor["delegation"] = {
574
+ "request_id": delegation.get("request_id"),
575
+ "status": delegation.get("status"),
576
+ "reason_code": delegation.get("reason_code"),
577
+ "summary": delegation.get("summary"),
578
+ "selected_executor": delegation.get("selected_executor"),
579
+ }
580
+ parent_child = launch.get("parent_child")
581
+ if isinstance(parent_child, dict):
582
+ monitor["delegation_lineage"] = {
583
+ "parent_phase": parent_child.get("parent_phase"),
584
+ "parent_executor": parent_child.get("parent_executor"),
585
+ "parent_run_id": parent_child.get("parent_run_id"),
586
+ "child_action": parent_child.get("child_action"),
587
+ "child_executor": parent_child.get("child_executor"),
588
+ "child_artifact_root": parent_child.get("child_artifact_root"),
589
+ "child_worktree_root": parent_child.get("child_worktree_root"),
590
+ "child_closeout_result": parent_child.get("child_closeout_result"),
591
+ }
592
+ trusted_handoff = _trusted_workflow_handoff(repo_path, roadmap_path) if repo_path and roadmap_path and roadmap_path.exists() else None
593
+ if isinstance(trusted_handoff, dict) and trusted_handoff and _trusted_workflow_handoff_matches_phases(trusted_handoff, phases):
594
+ monitor["trusted_workflow_handoff"] = trusted_handoff
595
+ manual_import = _latest_manual_import(read_events(repo_path), roadmap_path) if repo_path and roadmap_path and roadmap_path.exists() else None
596
+ if isinstance(manual_import, dict) and manual_import:
597
+ monitor["latest_manual_import"] = manual_import
598
+ return monitor
599
+
600
+
601
+ def _task_snapshot_freshness(
602
+ *,
603
+ task_snapshot: dict[str, object],
604
+ heartbeat: dict[str, object],
605
+ terminal: dict[str, object],
606
+ current_status: str,
607
+ ) -> str:
608
+ if not task_snapshot or not _task_snapshot_valid(task_snapshot):
609
+ return "missing"
610
+ if terminal.get("terminal_status") and not heartbeat.get("process_alive", False):
611
+ return "superseded"
612
+ if current_status in {"blocked", "complete", "awaiting_phase_closeout"} and terminal.get("terminal_status"):
613
+ return "superseded"
614
+ if heartbeat.get("heartbeat_status") == "stale" or heartbeat.get("quiet_level") == "stale":
615
+ return "stale"
616
+ if heartbeat and not heartbeat.get("process_alive", True):
617
+ return "stale"
618
+ return "fresh"
619
+
620
+
621
+ def _task_snapshot_valid(task_snapshot: dict[str, object]) -> bool:
622
+ required = ("schema_version", "freshness_timestamp", "teammates", "tasks", "latest_activity")
623
+ if not all(key in task_snapshot for key in required):
624
+ return False
625
+ return isinstance(task_snapshot.get("teammates"), list) and isinstance(task_snapshot.get("tasks"), list)
626
+
627
+
628
+ def _task_latest_activity(
629
+ task_snapshot: dict[str, object],
630
+ terminal: dict[str, object],
631
+ current_status: object,
632
+ freshness: str,
633
+ ) -> dict[str, object] | None:
634
+ if freshness == "superseded":
635
+ return {
636
+ "classification": "superseded_by_terminal",
637
+ "summary": terminal.get("next_action") or f"Native-team evidence is superseded by newer {current_status} state.",
638
+ "latest_update_timestamp": terminal.get("finished_at") or task_snapshot.get("freshness_timestamp"),
639
+ "provenance_source": "terminal_summary",
640
+ }
641
+ latest_activity = task_snapshot.get("latest_activity")
642
+ return latest_activity if isinstance(latest_activity, dict) else None
643
+
644
+
645
+ def _claude_wait_classification(
646
+ *,
647
+ execution_mode: str,
648
+ task_snapshot: dict[str, object],
649
+ freshness: str,
650
+ ) -> str:
651
+ if freshness == "superseded":
652
+ return "superseded"
653
+ if freshness == "missing" and execution_mode != "solo":
654
+ return "team_state_unavailable"
655
+ latest_activity = task_snapshot.get("latest_activity") if isinstance(task_snapshot, dict) else {}
656
+ if isinstance(latest_activity, dict) and latest_activity.get("classification"):
657
+ return str(latest_activity["classification"])
658
+ if execution_mode == "agent_team":
659
+ return "claude_agent_team_active"
660
+ if execution_mode == "subagent":
661
+ return "claude_subagent_wait"
662
+ return "claude_solo"
663
+
664
+
665
+ def _trusted_workflow_handoff_matches_phases(handoff: dict[str, object], phases: dict[object, object]) -> bool:
666
+ phase = str(handoff.get("phase") or "").upper()
667
+ status = handoff.get("status")
668
+ if not phase or status is None:
669
+ return True
670
+ return phases.get(phase) == status
671
+
672
+
673
+ def _terminal_artifact_expected(event: dict, terminal_path: object) -> bool:
674
+ if not isinstance(terminal_path, str) or not terminal_path:
675
+ return False
676
+ return event.get("status") in {"blocked", "unknown", "executed", "awaiting_phase_closeout", "complete"}