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,401 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ from contextlib import contextmanager
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Iterator
10
+
11
+ from .events import append_event
12
+ from .handoff import write_tui_handoff
13
+ from .launcher import LaunchResult, build_codex_command, launch
14
+ from .models import Blocker, LoopEvent, StateSnapshot, utc_now
15
+ from .observability import operator_halt_metadata, run_artifacts, stop_requested
16
+ from .profiles import resolve_profile
17
+ from .prompts import build_skill_maintenance_prompt
18
+ from .provenance import roadmap_sha256, snapshot_provenance
19
+ from .reconcile import reconcile
20
+ from .runtime_paths import ensure_phase_loop_excluded, phase_loop_dir
21
+ from .skill_inventory import (
22
+ classify_skill_like_directories,
23
+ inspect_bridge_skill_inventory,
24
+ inspect_vestigial_workflow_candidates,
25
+ inspect_workflow_skill_inventory,
26
+ )
27
+ from .state import write_state
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class MaintenanceOptions:
32
+ min_reflections: int = 2
33
+ apply_skill_edits: bool = False
34
+ allow_skills: tuple[str, ...] = ()
35
+ improvement_plan: Path | None = None
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class SyncSkillsOptions:
40
+ harnesses: tuple[str, ...] = ("codex", "claude", "gemini", "opencode")
41
+ apply: bool = False
42
+
43
+
44
+ def collect_reflection_inventory(repo: Path) -> dict[str, object]:
45
+ roots = (
46
+ Path.home() / ".codex" / "skills",
47
+ repo / "codex-config" / "skills",
48
+ )
49
+ by_root: list[dict[str, object]] = []
50
+ total = 0
51
+ for root in roots:
52
+ count = 0
53
+ if root.exists():
54
+ for reflections in root.glob("codex-*/reflections"):
55
+ for path in reflections.rglob("*"):
56
+ if path.is_file() and "archive" not in path.parts:
57
+ count += 1
58
+ total += count
59
+ by_root.append({"root": str(root), "count": count})
60
+ return {"total": total, "roots": by_root}
61
+
62
+
63
+ def validate_maintenance_options(options: MaintenanceOptions) -> Blocker | None:
64
+ if options.apply_skill_edits and not options.allow_skills:
65
+ return Blocker(
66
+ human_required=True,
67
+ blocker_class="product_decision_missing",
68
+ blocker_summary="Skill edits require at least one --allow-skill codex-* target.",
69
+ required_human_inputs=("--allow-skill codex-*",),
70
+ )
71
+ if options.apply_skill_edits and options.improvement_plan is None:
72
+ return Blocker(
73
+ human_required=True,
74
+ blocker_class="product_decision_missing",
75
+ blocker_summary="Skill edits require --improvement-plan for the approved planner artifact.",
76
+ required_human_inputs=("--improvement-plan path",),
77
+ )
78
+ invalid = tuple(skill for skill in options.allow_skills if not skill.startswith("codex-"))
79
+ if invalid:
80
+ return Blocker(
81
+ human_required=True,
82
+ blocker_class="product_decision_missing",
83
+ blocker_summary="Skill maintenance allowlist accepts only codex-* skill names.",
84
+ required_human_inputs=("codex-* allowlist only",),
85
+ )
86
+ return None
87
+
88
+
89
+ def sync_bridge_skills(repo: Path, options: SyncSkillsOptions) -> dict[str, object]:
90
+ blocker = active_loop_blocker(repo, desired_mode="sync-skills") if options.apply else None
91
+ records = [record.to_json() for record in inspect_bridge_skill_inventory(repo, options.harnesses)]
92
+ workflow_records = [record.to_json() for record in inspect_workflow_skill_inventory(repo, options.harnesses)]
93
+ vestigial_records = [record.to_json() for record in inspect_vestigial_workflow_candidates(repo)]
94
+ classification_records = [record.to_json() for record in classify_skill_like_directories(repo, options.harnesses)]
95
+ changed: list[dict[str, str]] = []
96
+ unrepaired: list[dict[str, str]] = []
97
+ if blocker:
98
+ return {
99
+ "mode": "apply" if options.apply else "check",
100
+ "repo": str(repo),
101
+ "harnesses": list(options.harnesses),
102
+ "bridge_skills": records,
103
+ "workflow_sources": workflow_records,
104
+ "vestigial_workflow_candidates": vestigial_records,
105
+ "skill_classifications": classification_records,
106
+ "changed": changed,
107
+ "unrepaired": unrepaired,
108
+ "blocked": True,
109
+ "blocker": blocker.to_json(),
110
+ }
111
+ if options.apply:
112
+ for record in records:
113
+ if record.get("parity_status") == "ok":
114
+ continue
115
+ source_dir = record.get("source_dir")
116
+ repair_target = record.get("repair_target")
117
+ if not isinstance(source_dir, str) or not isinstance(repair_target, str):
118
+ # #14: never silently skip — record what could not be repaired so
119
+ # `--apply` cannot mimic `--check` with a clean exit 0.
120
+ unrepaired.append(
121
+ {
122
+ "harness_target": str(record.get("harness_target")),
123
+ "skill_name": str(record.get("skill_name")),
124
+ "parity_status": str(record.get("parity_status")),
125
+ "reason": "no_source_resolved" if not isinstance(source_dir, str) else "no_repair_target",
126
+ }
127
+ )
128
+ continue
129
+ _repair_bridge_skill(Path(source_dir), Path(repair_target))
130
+ changed.append(
131
+ {
132
+ "harness_target": str(record["harness_target"]),
133
+ "skill_name": str(record["skill_name"]),
134
+ "repair_target": repair_target,
135
+ }
136
+ )
137
+ records = [record.to_json() for record in inspect_bridge_skill_inventory(repo, options.harnesses)]
138
+ workflow_records = [record.to_json() for record in inspect_workflow_skill_inventory(repo, options.harnesses)]
139
+ classification_records = [record.to_json() for record in classify_skill_like_directories(repo, options.harnesses)]
140
+ return {
141
+ "mode": "apply" if options.apply else "check",
142
+ "repo": str(repo),
143
+ "harnesses": list(options.harnesses),
144
+ "bridge_skills": records,
145
+ "workflow_sources": workflow_records,
146
+ "vestigial_workflow_candidates": vestigial_records,
147
+ "skill_classifications": classification_records,
148
+ "changed": changed,
149
+ "unrepaired": unrepaired,
150
+ "blocked": False,
151
+ "blocker": None,
152
+ }
153
+
154
+
155
+ def run_maintenance(
156
+ repo: Path,
157
+ roadmap: Path,
158
+ options: MaintenanceOptions,
159
+ model_profile: str | None = None,
160
+ model: str | None = None,
161
+ effort: str | None = None,
162
+ dry_run: bool = False,
163
+ json_output: bool = False,
164
+ observe: bool = True,
165
+ stream_output: bool = False,
166
+ bypass_approvals: bool = False,
167
+ heartbeat_interval_seconds: int = 30,
168
+ quiet_warning_seconds: int = 600,
169
+ quiet_blocker_seconds: int = 1800,
170
+ heartbeat_enabled: bool = True,
171
+ ) -> tuple[StateSnapshot, list[LaunchResult]]:
172
+ phases = reconcile(repo, roadmap).phases
173
+ selection = resolve_profile(model_profile or "skill-maintenance", model=model, effort=effort)
174
+ inventory = collect_reflection_inventory(repo)
175
+ blocker = validate_maintenance_options(options) or active_loop_blocker(repo, desired_mode="skill-maintenance")
176
+ if blocker:
177
+ snapshot = StateSnapshot(
178
+ timestamp=utc_now(),
179
+ repo=str(repo),
180
+ roadmap=str(roadmap),
181
+ phases=phases,
182
+ current_phase=next((phase for phase, status in phases.items() if status != "complete"), None),
183
+ last_action="maintain-skills",
184
+ model=selection.model,
185
+ reasoning_effort=selection.effort,
186
+ source=selection.source,
187
+ override_reason=selection.override_reason,
188
+ human_required=blocker.human_required,
189
+ blocker_class=blocker.blocker_class,
190
+ blocker_summary=blocker.blocker_summary,
191
+ required_human_inputs=blocker.required_human_inputs,
192
+ **snapshot_provenance(roadmap),
193
+ )
194
+ _write_state_and_handoff(repo, roadmap, snapshot, results=[])
195
+ append_event(repo, _maintenance_event(repo, roadmap, "blocked", selection, inventory, blocker=blocker))
196
+ return snapshot, []
197
+
198
+ prompt = build_skill_maintenance_prompt(options)
199
+ command = build_codex_command(repo, selection, prompt, json_output=json_output, bypass_approvals=bypass_approvals)
200
+ if stop_requested(repo):
201
+ metadata = operator_halt_metadata(repo)
202
+ metadata["reflection_inventory"] = inventory
203
+ append_event(repo, _maintenance_event(repo, roadmap, "unknown", selection, inventory, command=command, metadata=metadata))
204
+ snapshot = StateSnapshot(
205
+ timestamp=utc_now(),
206
+ repo=str(repo),
207
+ roadmap=str(roadmap),
208
+ phases=phases,
209
+ current_phase=next((phase for phase, status in phases.items() if status != "complete"), None),
210
+ last_action="maintain-skills",
211
+ model=selection.model,
212
+ reasoning_effort=selection.effort,
213
+ source=selection.source,
214
+ override_reason=selection.override_reason,
215
+ **snapshot_provenance(roadmap),
216
+ )
217
+ _write_state_and_handoff(repo, roadmap, snapshot, results=[])
218
+ return snapshot, []
219
+
220
+ artifacts = run_artifacts(repo, "SKILL-MAINTENANCE", "maintain-skills", 1, command) if observe else {}
221
+ if dry_run:
222
+ result = launch(
223
+ command,
224
+ dry_run=True,
225
+ log_path=artifacts.get("log"),
226
+ heartbeat_path=artifacts.get("heartbeat") if heartbeat_enabled else None,
227
+ stream_output=stream_output,
228
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
229
+ quiet_warning_seconds=quiet_warning_seconds,
230
+ quiet_blocker_seconds=quiet_blocker_seconds,
231
+ )
232
+ else:
233
+ with active_loop(repo, "skill-maintenance"):
234
+ result = launch(
235
+ command,
236
+ dry_run=False,
237
+ log_path=artifacts.get("log"),
238
+ heartbeat_path=artifacts.get("heartbeat") if heartbeat_enabled else None,
239
+ stream_output=stream_output,
240
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
241
+ quiet_warning_seconds=quiet_warning_seconds,
242
+ quiet_blocker_seconds=quiet_blocker_seconds,
243
+ )
244
+ if result.failed:
245
+ append_event(
246
+ repo,
247
+ _maintenance_event(
248
+ repo,
249
+ roadmap,
250
+ "unknown",
251
+ selection,
252
+ inventory,
253
+ command=command,
254
+ metadata={"launch": result.event_metadata(), "artifacts": {key: str(value) for key, value in artifacts.items()}},
255
+ ),
256
+ )
257
+ snapshot = StateSnapshot(
258
+ timestamp=utc_now(),
259
+ repo=str(repo),
260
+ roadmap=str(roadmap),
261
+ phases=phases,
262
+ current_phase=next((phase for phase, status in phases.items() if status != "complete"), None),
263
+ last_action="maintain-skills",
264
+ model=selection.model,
265
+ reasoning_effort=selection.effort,
266
+ source=selection.source,
267
+ override_reason=selection.override_reason,
268
+ **snapshot_provenance(roadmap),
269
+ )
270
+ _write_state_and_handoff(repo, roadmap, snapshot, results=[result])
271
+ return snapshot, [result]
272
+ status = "planned" if dry_run else "executing"
273
+ append_event(
274
+ repo,
275
+ _maintenance_event(
276
+ repo,
277
+ roadmap,
278
+ status,
279
+ selection,
280
+ inventory,
281
+ command=command,
282
+ metadata={"launch": result.event_metadata(), "artifacts": {key: str(value) for key, value in artifacts.items()}} if artifacts else {"launch": result.event_metadata()},
283
+ ),
284
+ )
285
+ snapshot = StateSnapshot(
286
+ timestamp=utc_now(),
287
+ repo=str(repo),
288
+ roadmap=str(roadmap),
289
+ phases=phases,
290
+ current_phase=next((phase for phase, status in phases.items() if status != "complete"), None),
291
+ last_action="maintain-skills",
292
+ model=selection.model,
293
+ reasoning_effort=selection.effort,
294
+ source=selection.source,
295
+ override_reason=selection.override_reason,
296
+ **snapshot_provenance(roadmap),
297
+ )
298
+ _write_state_and_handoff(repo, roadmap, snapshot, results=[result])
299
+ return snapshot, [result]
300
+
301
+
302
+ def active_loop_blocker(repo: Path, desired_mode: str) -> Blocker | None:
303
+ data = _read_active_loop(repo)
304
+ if not data:
305
+ return None
306
+ mode = str(data.get("mode", ""))
307
+ if mode and mode != desired_mode:
308
+ return Blocker(
309
+ human_required=True,
310
+ blocker_class="dirty_worktree_conflict",
311
+ blocker_summary=f"Active {mode} loop is running; refusing {desired_mode}.",
312
+ required_human_inputs=("wait for active loop to finish",),
313
+ )
314
+ return None
315
+
316
+
317
+ @contextmanager
318
+ def active_loop(repo: Path, mode: str) -> Iterator[None]:
319
+ ensure_phase_loop_excluded(repo)
320
+ path = _active_loop_path(repo)
321
+ path.parent.mkdir(parents=True, exist_ok=True)
322
+ path.write_text(json.dumps({"mode": mode, "pid": os.getpid(), "timestamp": utc_now()}, sort_keys=True) + "\n")
323
+ try:
324
+ yield
325
+ finally:
326
+ try:
327
+ if path.exists():
328
+ data = json.loads(path.read_text())
329
+ if data.get("pid") == os.getpid() and data.get("mode") == mode:
330
+ path.unlink()
331
+ except Exception:
332
+ pass
333
+
334
+
335
+ def _maintenance_event(repo, roadmap, status, selection, inventory, command=None, blocker=None, metadata=None):
336
+ event_metadata = {"reflection_inventory": inventory}
337
+ if metadata:
338
+ event_metadata.update(metadata)
339
+ return LoopEvent(
340
+ timestamp=utc_now(),
341
+ repo=str(repo),
342
+ roadmap=str(roadmap),
343
+ phase="SKILL-MAINTENANCE",
344
+ action="maintain-skills",
345
+ status=status,
346
+ model=selection.model,
347
+ reasoning_effort=selection.effort,
348
+ source=selection.source,
349
+ override_reason=selection.override_reason,
350
+ command=command,
351
+ blocker=blocker.to_json() if blocker else None,
352
+ metadata=event_metadata,
353
+ schema_version=2,
354
+ roadmap_sha256=roadmap_sha256(roadmap),
355
+ )
356
+
357
+
358
+ def _write_state_and_handoff(repo: Path, roadmap: Path, snapshot: StateSnapshot, *, results: list[LaunchResult]) -> None:
359
+ write_state(repo, snapshot)
360
+ write_tui_handoff(repo, roadmap, snapshot, action="maintain-skills", results=results, mode="skill-maintenance")
361
+
362
+
363
+ def _active_loop_path(repo: Path) -> Path:
364
+ return phase_loop_dir(repo) / "active-loop.json"
365
+
366
+
367
+ def _read_active_loop(repo: Path) -> dict[str, object] | None:
368
+ path = _active_loop_path(repo)
369
+ if not path.exists():
370
+ return None
371
+ try:
372
+ data = json.loads(path.read_text())
373
+ except json.JSONDecodeError:
374
+ return {"mode": "unknown"}
375
+ pid = data.get("pid")
376
+ if isinstance(pid, int) and not _pid_is_live(pid):
377
+ path.unlink(missing_ok=True)
378
+ return None
379
+ return data
380
+
381
+
382
+ def _pid_is_live(pid: int) -> bool:
383
+ if pid <= 0:
384
+ return False
385
+ try:
386
+ os.kill(pid, 0)
387
+ except ProcessLookupError:
388
+ return False
389
+ except PermissionError:
390
+ return True
391
+ return True
392
+
393
+
394
+ def _repair_bridge_skill(source_dir: Path, repair_target: Path) -> None:
395
+ repair_target.parent.mkdir(parents=True, exist_ok=True)
396
+ if repair_target.exists() or repair_target.is_symlink():
397
+ if repair_target.is_symlink() or repair_target.is_file():
398
+ repair_target.unlink()
399
+ else:
400
+ shutil.rmtree(repair_target)
401
+ repair_target.symlink_to(source_dir, target_is_directory=True)
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import shutil
5
+ import time
6
+ from dataclasses import asdict, dataclass
7
+ from importlib import util
8
+ from pathlib import Path
9
+ from typing import Iterable
10
+
11
+ from .discovery import parse_frontmatter, repo_identity
12
+ from .runtime_paths import phase_loop_dir, phase_loop_event_file, phase_loop_state_file
13
+
14
+
15
+ TERMINAL_PHASE_STATES = {"complete", "unplanned", "none"}
16
+ ALLOWED_RECENT_ACTIONS = {"closeout", "manual_repair"}
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class MigrationRecord:
21
+ skill_name: str
22
+ source: str
23
+ target: str
24
+ action: str
25
+ status: str
26
+
27
+
28
+ def _shared_resolver():
29
+ # Import directly from the package so resolution works whether running
30
+ # from source tree or installed via pip. The package-local module mirrors
31
+ # shared/phase-loop/handoff_path.py until NEUTRALIZE consolidates them.
32
+ from .handoff_path import resolve_handoff_path
33
+ return resolve_handoff_path
34
+
35
+
36
+ def _legacy_skill_roots(home: Path | None = None) -> tuple[Path, ...]:
37
+ home = Path(home or Path.home()).expanduser()
38
+ return (
39
+ home / ".claude" / "skills",
40
+ home / ".codex" / "skills",
41
+ home / ".gemini" / "skills",
42
+ home / ".gemini" / "antigravity" / "skills",
43
+ home / ".config" / "opencode" / "skills",
44
+ )
45
+
46
+
47
+ def _quiesced(repo: Path) -> bool:
48
+ return quiescence_blocker(repo) is None
49
+
50
+
51
+ def quiescence_blocker(repo: Path) -> str | None:
52
+ loop_dir = phase_loop_dir(repo)
53
+ state_path = phase_loop_state_file(repo)
54
+ if state_path.exists():
55
+ try:
56
+ data = json.loads(state_path.read_text(encoding="utf-8"))
57
+ except (OSError, json.JSONDecodeError):
58
+ return f"state file is unreadable: {state_path}"
59
+ phases = data.get("phases")
60
+ if isinstance(phases, dict):
61
+ for phase, status in phases.items():
62
+ if str(status) not in TERMINAL_PHASE_STATES:
63
+ return f"phase {phase} is {status}"
64
+ event_path = phase_loop_event_file(repo)
65
+ now = time.time()
66
+ if event_path.exists():
67
+ try:
68
+ lines = event_path.read_text(encoding="utf-8").splitlines()
69
+ except OSError:
70
+ return f"event ledger is unreadable: {event_path}"
71
+ for line in reversed(lines):
72
+ if not line.strip():
73
+ continue
74
+ try:
75
+ event = json.loads(line)
76
+ except json.JSONDecodeError:
77
+ continue
78
+ timestamp = _event_epoch(event.get("timestamp"))
79
+ if timestamp is None or now - timestamp > 60:
80
+ continue
81
+ action = str(event.get("action") or "")
82
+ if action not in ALLOWED_RECENT_ACTIONS:
83
+ return f"recent event action {action or 'unknown'} is active"
84
+ if loop_dir.exists():
85
+ lock = next(loop_dir.glob("*.lock"), None)
86
+ if lock is not None:
87
+ return f"lock file exists: {lock.name}"
88
+ return None
89
+
90
+
91
+ def migrate_handoffs(repo: Path, *, apply: bool = False, home: Path | None = None) -> tuple[MigrationRecord, ...]:
92
+ repo = repo.expanduser().resolve()
93
+ if apply:
94
+ blocker = quiescence_blocker(repo)
95
+ if blocker is not None:
96
+ return (
97
+ MigrationRecord(
98
+ skill_name="",
99
+ source="",
100
+ target="",
101
+ action="blocked",
102
+ status=f"not_quiesced: {blocker}",
103
+ ),
104
+ )
105
+ records: list[MigrationRecord] = []
106
+ for latest in _candidate_latest_files(home):
107
+ records.extend(_records_for_latest(repo, latest, apply=apply))
108
+ return tuple(records)
109
+
110
+
111
+ def records_to_json(records: Iterable[MigrationRecord]) -> str:
112
+ return json.dumps([asdict(record) for record in records], indent=2, sort_keys=True)
113
+
114
+
115
+ def _candidate_latest_files(home: Path | None) -> Iterable[Path]:
116
+ for root in _legacy_skill_roots(home):
117
+ if not root.exists():
118
+ continue
119
+ yield from sorted(root.glob("*/handoffs/**/latest.md"))
120
+
121
+
122
+ def _records_for_latest(repo: Path, latest: Path, *, apply: bool) -> tuple[MigrationRecord, ...]:
123
+ skill_name = _skill_name_from_latest(latest)
124
+ text = _read_text(latest)
125
+ if text is None:
126
+ return (_record(skill_name, latest, repo, "skip", "unreadable"),)
127
+ if not _matches_current_repo(repo, text):
128
+ return (_record(skill_name, latest, repo, "skip", "other_repo_or_malformed"),)
129
+ target_latest = _shared_resolver()(repo, skill_name)
130
+ source_files = sorted(path for path in latest.parent.iterdir() if path.is_file() and path.suffix == ".md")
131
+ records: list[MigrationRecord] = []
132
+ for source in source_files:
133
+ target = target_latest.parent / source.name
134
+ action, status = _migrate_one(source, target, apply=apply)
135
+ records.append(MigrationRecord(skill_name, str(source), str(target), action, status))
136
+ return tuple(records)
137
+
138
+
139
+ def _migrate_one(source: Path, target: Path, *, apply: bool) -> tuple[str, str]:
140
+ if target.exists():
141
+ if _read_text(source) == _read_text(target):
142
+ if apply:
143
+ source.unlink()
144
+ return ("noop", "already_migrated")
145
+ return ("skip", "target_exists_different")
146
+ if not apply:
147
+ return ("move", "dry_run")
148
+ target.parent.mkdir(parents=True, exist_ok=True)
149
+ shutil.move(str(source), str(target))
150
+ return ("move", "migrated")
151
+
152
+
153
+ def _skill_name_from_latest(latest: Path) -> str:
154
+ for parent in latest.parents:
155
+ if parent.name == "handoffs":
156
+ return parent.parent.name
157
+ return latest.parents[3].name
158
+
159
+
160
+ def _matches_current_repo(repo: Path, text: str) -> bool:
161
+ frontmatter = parse_frontmatter(text)
162
+ if not frontmatter:
163
+ return False
164
+ identity = repo_identity(repo)
165
+ repo_root = frontmatter.get("repo_root")
166
+ if repo_root:
167
+ try:
168
+ if Path(repo_root).expanduser().resolve() != repo:
169
+ return False
170
+ except OSError:
171
+ return False
172
+ repo_key = frontmatter.get("repo")
173
+ if repo_key and repo_key not in {identity.repo_hash, str(repo)}:
174
+ return False
175
+ branch_slug = frontmatter.get("branch_slug")
176
+ if branch_slug and branch_slug != identity.branch_slug:
177
+ return False
178
+ return bool(repo_root or repo_key)
179
+
180
+
181
+ def _record(skill_name: str, source: Path, repo: Path, action: str, status: str) -> MigrationRecord:
182
+ target = _shared_resolver()(repo, skill_name) if skill_name else Path("")
183
+ return MigrationRecord(skill_name, str(source), str(target), action, status)
184
+
185
+
186
+ def _read_text(path: Path) -> str | None:
187
+ try:
188
+ return path.read_text(encoding="utf-8")
189
+ except OSError:
190
+ return None
191
+
192
+
193
+ def _event_epoch(raw: object) -> float | None:
194
+ if not isinstance(raw, str):
195
+ return None
196
+ try:
197
+ from datetime import datetime
198
+
199
+ return datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp()
200
+ except ValueError:
201
+ return None