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,662 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import hashlib
5
+ import logging
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from .runtime_resources import packaged_resource_dir
10
+
11
+ _LOGGER = logging.getLogger("phase_loop_runtime.skill_inventory")
12
+
13
+
14
+ HARNESS_INSTALL_ROOT_HINTS = {
15
+ "codex": ("~/.codex/skills",),
16
+ "claude": ("~/.claude/skills",),
17
+ "gemini": ("~/.gemini/skills",),
18
+ "opencode": ("~/.config/opencode/skills",),
19
+ "command": (),
20
+ "manual": (),
21
+ }
22
+
23
+ # DISENTANGLE (EXTRACTSKILLS SL-2): the generic runtime no longer hardcodes the
24
+ # per-harness dotfiles overlay roots here. The neutral generated bundle is the
25
+ # shared-canonical artifact (moves to agent-harness); the 4 per-harness source roots
26
+ # that produce the `_overrides/` are the dotfiles-OVERLAY and are now contributed by
27
+ # the in-tree dotfiles profile through the ``phase_loop_runtime.skill_sources``
28
+ # entry-point group (see :func:`iter_skill_source_roots` and ``skill_sources_plugin``).
29
+ # The keys are retained (callers iterate them) but carry no built-in dotfiles paths;
30
+ # resolution flows through the merged builtin+plugin roots.
31
+ HARNESS_SOURCE_ROOTS = {
32
+ "codex": (),
33
+ "claude": (),
34
+ "gemini": (),
35
+ "opencode": (),
36
+ "command": (),
37
+ "manual": (),
38
+ }
39
+
40
+ def _runner_repo_root() -> Path | None:
41
+ """Resolve the runner's source-of-truth repo (where HARNESS_SOURCE_ROOTS live).
42
+
43
+ DECOUPLE SL-2: the generic runtime no longer walks up the filesystem into the
44
+ dotfiles tree (the old fleet-relative fallback). Source skill roots are an *optional*
45
+ integration: operators/tests set ``PHASE_LOOP_RUNNER_REPO_ROOT`` and profiles
46
+ contribute roots via the ``phase_loop_runtime.skill_sources`` entry-point group
47
+ (see :func:`iter_skill_source_roots`). With neither configured -- e.g. a clean
48
+ wheel install -- this returns ``None`` and source-skill resolution yields
49
+ nothing rather than pointing at an unrelated path.
50
+ """
51
+ import os
52
+ env = os.environ.get("PHASE_LOOP_RUNNER_REPO_ROOT")
53
+ if env:
54
+ return Path(env).expanduser().resolve()
55
+ return None
56
+
57
+
58
+ class SkillSourcePluginError(RuntimeError):
59
+ """A REGISTERED skill-source plugin failed to load or invoke.
60
+
61
+ Raised (not swallowed) when a plugin that WAS configured -- via the
62
+ ``phase_loop_runtime.skill_sources`` entry-point group or a
63
+ ``PHASE_LOOP_SKILL_SOURCE_PLUGINS`` opt-in spec -- errors on import/load/invoke.
64
+ A registered-but-broken provider is a real bug and must fail loud rather than
65
+ silently degrade to zero roots (which would let injection inject an empty skill
66
+ bundle). An ABSENT provider (clean runtime, nothing configured) is NOT an error:
67
+ it simply yields no roots.
68
+ """
69
+
70
+
71
+ def iter_skill_source_roots() -> tuple[tuple[str, tuple[str, ...]], ...]:
72
+ """Yield (harness_target, source_roots) pairs from installed skill-source plugins.
73
+
74
+ Profiles contribute roots two ways, mirroring the ``profile_commands`` seam
75
+ (see :func:`phase_loop_runtime.cli._profile_command_registrars`):
76
+
77
+ 1. the ``phase_loop_runtime.skill_sources`` entry-point group (declared by a
78
+ profile distribution) -- the production seam, live in a real install;
79
+ 2. the explicit ``PHASE_LOOP_SKILL_SOURCE_PLUGINS`` opt-in -- a comma-separated
80
+ list of ``module:attr`` provider specs, used in source-mode runs and tests
81
+ where the dist-info entry point is not live (the in-tree dotfiles profile is
82
+ opted in by the test conftest, exactly as the profile plugin is).
83
+
84
+ Each provider is a callable returning a ``{harness_target: (root, ...)}`` mapping.
85
+ A clean runtime with neither configured yields nothing -- discovery is plugin-
86
+ driven, never a fleet-path walk. Roots are de-duplicated per harness so a provider
87
+ loaded via BOTH the entry point and the env opt-in contributes each root once.
88
+
89
+ Fail-loud (CR #2): a provider that is REGISTERED but raises on load/invoke
90
+ propagates as :class:`SkillSourcePluginError` -- a real bug must not silently
91
+ degrade to zero roots. An absent provider is fine (empty result).
92
+
93
+ Cached on the ``PHASE_LOOP_SKILL_SOURCE_PLUGINS`` value (CR #4): the env opt-in is
94
+ read OUTSIDE the cache and passed as the key, so setting the env AFTER a first call
95
+ takes effect (the installed entry-point set is genuinely process-constant). Tests
96
+ that monkeypatch ``importlib.metadata.entry_points`` or the env var must call
97
+ ``iter_skill_source_roots.cache_clear()``.
98
+ """
99
+ import os
100
+
101
+ opt_in = os.environ.get("PHASE_LOOP_SKILL_SOURCE_PLUGINS", "")
102
+ return _iter_skill_source_roots_cached(opt_in)
103
+
104
+
105
+ @functools.lru_cache(maxsize=8)
106
+ def _iter_skill_source_roots_cached(opt_in: str) -> tuple[tuple[str, tuple[str, ...]], ...]:
107
+ import importlib
108
+ import importlib.metadata
109
+
110
+ providers: list = []
111
+ seen: set = set()
112
+
113
+ def _add(provider) -> None:
114
+ key = id(provider)
115
+ if key in seen:
116
+ return
117
+ seen.add(key)
118
+ providers.append(provider)
119
+
120
+ try:
121
+ entry_points = importlib.metadata.entry_points(group="phase_loop_runtime.skill_sources")
122
+ except TypeError: # pragma: no cover - py<3.10 selectable API
123
+ entry_points = importlib.metadata.entry_points().get("phase_loop_runtime.skill_sources", [])
124
+ for entry_point in entry_points:
125
+ # A registered entry point that fails to load is a real bug -- fail loud.
126
+ try:
127
+ _add(entry_point.load())
128
+ except Exception as exc:
129
+ name = getattr(entry_point, "name", entry_point)
130
+ raise SkillSourcePluginError(
131
+ f"skill-source entry point {name!r} failed to load: {exc}"
132
+ ) from exc
133
+
134
+ for spec in opt_in.split(","):
135
+ spec = spec.strip()
136
+ if not spec or ":" not in spec:
137
+ # An empty/malformed spec is "not configured", not a registered failure.
138
+ continue
139
+ module_name, _, attr = spec.partition(":")
140
+ # A spec the operator DID configure that won't import/resolve is a real bug.
141
+ try:
142
+ module = importlib.import_module(module_name)
143
+ _add(getattr(module, attr))
144
+ except Exception as exc:
145
+ raise SkillSourcePluginError(
146
+ f"skill-source opt-in {spec!r} failed to load: {exc}"
147
+ ) from exc
148
+
149
+ collected: dict[str, tuple[str, ...]] = {}
150
+ for provider in providers:
151
+ # A registered provider that raises when invoked is a real bug -- fail loud.
152
+ try:
153
+ mapping = provider() if callable(provider) else provider
154
+ except Exception as exc:
155
+ raise SkillSourcePluginError(
156
+ f"skill-source provider {provider!r} raised on invoke: {exc}"
157
+ ) from exc
158
+ if not isinstance(mapping, dict):
159
+ continue
160
+ for harness_target, roots in mapping.items():
161
+ existing = collected.get(harness_target, ())
162
+ merged = list(existing)
163
+ for root in roots:
164
+ if root not in merged:
165
+ merged.append(root)
166
+ collected[harness_target] = tuple(merged)
167
+ return tuple(collected.items())
168
+
169
+
170
+ # Re-expose cache_clear on the public function so existing tests
171
+ # (test_phase_loop_runtime_decouple, test_skill_sources_seam) keep working.
172
+ iter_skill_source_roots.cache_clear = _iter_skill_source_roots_cached.cache_clear # type: ignore[attr-defined]
173
+ iter_skill_source_roots.cache_info = _iter_skill_source_roots_cached.cache_info # type: ignore[attr-defined]
174
+
175
+
176
+ BRIDGE_SKILL_NAMES = {
177
+ "codex": "codex-phase-loop",
178
+ "claude": "claude-phase-loop",
179
+ "gemini": "gemini-phase-loop",
180
+ "opencode": "opencode-phase-loop",
181
+ }
182
+ CANONICAL_WORKFLOW_SKILLS = {
183
+ "codex": (
184
+ "codex-advisor-board",
185
+ "codex-phase-roadmap-builder",
186
+ "codex-plan-phase",
187
+ "codex-execute-phase",
188
+ "codex-phase-loop",
189
+ "codex-run-train",
190
+ "codex-plan-detailed",
191
+ "codex-execute-detailed",
192
+ "codex-task-contextualizer",
193
+ "codex-skill-improvement-planner",
194
+ "codex-skill-editor",
195
+ ),
196
+ "claude": (
197
+ "claude-advisor-board",
198
+ "claude-phase-roadmap-builder",
199
+ "claude-plan-phase",
200
+ "claude-execute-phase",
201
+ "claude-phase-loop",
202
+ "claude-run-train",
203
+ "claude-plan-detailed",
204
+ "claude-execute-detailed",
205
+ "claude-task-contextualizer",
206
+ "claude-skill-improvement-planner",
207
+ "claude-skill-editor",
208
+ ),
209
+ "gemini": (
210
+ "gemini-advisor-board",
211
+ "gemini-phase-roadmap-builder",
212
+ "gemini-plan-phase",
213
+ "gemini-execute-phase",
214
+ "gemini-phase-loop",
215
+ "gemini-run-train",
216
+ "gemini-plan-detailed",
217
+ "gemini-execute-detailed",
218
+ "gemini-task-contextualizer",
219
+ "gemini-skill-improvement-planner",
220
+ "gemini-skill-editor",
221
+ ),
222
+ "opencode": (
223
+ "opencode-advisor-board",
224
+ "opencode-phase-roadmap-builder",
225
+ "opencode-plan-phase",
226
+ "opencode-execute-phase",
227
+ "opencode-phase-loop",
228
+ "opencode-run-train",
229
+ "opencode-plan-detailed",
230
+ "opencode-execute-detailed",
231
+ "opencode-task-contextualizer",
232
+ "opencode-skill-improvement-planner",
233
+ "opencode-skill-editor",
234
+ ),
235
+ }
236
+ VESTIGIAL_WORKFLOW_CANDIDATE_ROOTS = (
237
+ "claude-config/skills/plan-phase",
238
+ "claude-config/skills/execute-phase",
239
+ )
240
+ LEGACY_CLAUDE_UTILITY_ROOT = "claude-config/skills"
241
+ PI_ROLE_SKILL_ROOT = "phase-loop-pi/skills"
242
+ NON_CANONICAL_WORKFLOW_ALLOWLIST: tuple[str, ...] = ()
243
+
244
+
245
+ @dataclass(frozen=True)
246
+ class SkillParity:
247
+ recommended_installed_roots: tuple[str, ...]
248
+ installed_skill_roots: tuple[str, ...]
249
+ installed_skill_warnings: tuple[str, ...]
250
+ bridge_skill_inventory: tuple[dict[str, str | tuple[str, ...] | None], ...] = ()
251
+
252
+
253
+ @dataclass(frozen=True)
254
+ class BridgeSkillInventoryRecord:
255
+ harness_target: str
256
+ skill_name: str
257
+ source_dir: str | None
258
+ recommended_installed_roots: tuple[str, ...]
259
+ installed_skill_roots: tuple[str, ...]
260
+ installed_path: str | None
261
+ parity_status: str
262
+ repair_target: str | None
263
+
264
+ def to_json(self) -> dict[str, str | tuple[str, ...] | None]:
265
+ return {
266
+ "harness_target": self.harness_target,
267
+ "skill_name": self.skill_name,
268
+ "source_dir": self.source_dir,
269
+ "recommended_installed_roots": self.recommended_installed_roots,
270
+ "installed_skill_roots": self.installed_skill_roots,
271
+ "installed_path": self.installed_path,
272
+ "parity_status": self.parity_status,
273
+ "repair_target": self.repair_target,
274
+ }
275
+
276
+
277
+ @dataclass(frozen=True)
278
+ class WorkflowSkillInventoryRecord:
279
+ harness_target: str
280
+ skill_name: str
281
+ source_dir: str | None
282
+ recommended_installed_roots: tuple[str, ...]
283
+ installed_skill_roots: tuple[str, ...]
284
+ installed_path: str | None
285
+ parity_status: str
286
+
287
+ def to_json(self) -> dict[str, str | tuple[str, ...] | None]:
288
+ return {
289
+ "harness_target": self.harness_target,
290
+ "skill_name": self.skill_name,
291
+ "source_dir": self.source_dir,
292
+ "recommended_installed_roots": self.recommended_installed_roots,
293
+ "installed_skill_roots": self.installed_skill_roots,
294
+ "installed_path": self.installed_path,
295
+ "parity_status": self.parity_status,
296
+ }
297
+
298
+
299
+ @dataclass(frozen=True)
300
+ class VestigialWorkflowCandidateRecord:
301
+ path: str
302
+ candidate_name: str
303
+ exists: bool
304
+ skill_file: str | None
305
+ status: str
306
+
307
+ def to_json(self) -> dict[str, str | bool | None]:
308
+ return {
309
+ "path": self.path,
310
+ "candidate_name": self.candidate_name,
311
+ "exists": self.exists,
312
+ "skill_file": self.skill_file,
313
+ "status": self.status,
314
+ }
315
+
316
+
317
+ @dataclass(frozen=True)
318
+ class SkillDirectoryClassificationRecord:
319
+ path: str
320
+ skill_name: str
321
+ classification: str
322
+ reason: str
323
+ canonical_replacement: str | None = None
324
+ skill_file: str | None = None
325
+
326
+ def to_json(self) -> dict[str, str | None]:
327
+ return {
328
+ "path": self.path,
329
+ "skill_name": self.skill_name,
330
+ "classification": self.classification,
331
+ "reason": self.reason,
332
+ "canonical_replacement": self.canonical_replacement,
333
+ "skill_file": self.skill_file,
334
+ }
335
+
336
+
337
+ def recommended_installed_roots(harness_target: str) -> tuple[str, ...]:
338
+ return HARNESS_INSTALL_ROOT_HINTS.get(harness_target, ())
339
+
340
+
341
+ def discover_installed_skill_roots(harness_target: str) -> tuple[str, ...]:
342
+ roots: list[str] = []
343
+ for hint in recommended_installed_roots(harness_target):
344
+ path = Path(hint).expanduser()
345
+ if path.exists():
346
+ roots.append(str(path.resolve()))
347
+ return tuple(roots)
348
+
349
+
350
+ @functools.lru_cache(maxsize=1)
351
+ def _packaged_skills_bundle_dir() -> Path | None:
352
+ """The assembled neutral skill bundle shipped as package-data in the wheel (#12).
353
+
354
+ Absolute, install-location-anchored via ``importlib.resources``, so a pinned
355
+ ``pip install`` resolves the workflow skills without a dotfiles overlay. The
356
+ bundle is flat: ``skills_bundle/<harness>-<skill>/`` (e.g.
357
+ ``claude-phase-roadmap-builder``), exactly the ``skill_name`` resolution joins.
358
+ Returns ``None`` when the bundle is not packaged (e.g. an older wheel).
359
+ """
360
+ return packaged_resource_dir("skills_bundle")
361
+
362
+
363
+ @functools.lru_cache(maxsize=1)
364
+ def _canonical_skill_source_present() -> bool:
365
+ """True in a dev/fleet *source* checkout — when the canonical ``phase-loop-skills/``
366
+ source tree is reachable beside the installed package.
367
+
368
+ Used to gate the packaged-bundle fallback (#12 CR): in a source checkout an empty
369
+ skill resolution means "the overlay/plugin wasn't bootstrapped" — a real error that
370
+ must fail loud (so the dev re-runs bootstrap and edits take effect), NOT something to
371
+ paper over with the committed `skills_bundle/` (which would inject stale skills). In a
372
+ pinned `pip install` there is no sibling source, so the packaged bundle is the
373
+ legitimate resolution. Covers both the agent-harness layout (`<root>/phase-loop-skills/`)
374
+ and the dotfiles-vendored layout (`vendor/phase-loop-skills/`); `site-packages` has
375
+ neither.
376
+ """
377
+ here = Path(__file__).resolve()
378
+ return any((parent / "phase-loop-skills").is_dir() for parent in here.parents)
379
+
380
+
381
+ def resolve_source_skill_dir(repo: Path, harness_target: str, skill_name: str) -> Path | None:
382
+ plugin_roots = dict(iter_skill_source_roots()).get(harness_target, ())
383
+ builtin_roots = HARNESS_SOURCE_ROOTS.get(harness_target, ())
384
+ source_roots = (*builtin_roots, *plugin_roots)
385
+ for root in source_roots:
386
+ candidate = repo / root / skill_name
387
+ if candidate.is_dir():
388
+ return candidate.resolve()
389
+ runner_root = _runner_repo_root()
390
+ if runner_root is not None:
391
+ for root in source_roots:
392
+ candidate = runner_root / root / skill_name
393
+ if candidate.is_dir():
394
+ return candidate.resolve()
395
+ # Final fallback: the bundle shipped inside the package (#12). A genuine LAST resort,
396
+ # gated so it fires ONLY in a pinned install (no canonical source reachable). In a
397
+ # dev/fleet source checkout the overlay/plugin roots above are the real source; an
398
+ # empty resolution there must fail loud (re-run bootstrap), not silently serve the
399
+ # committed bundle. So we suppress the fallback when `phase-loop-skills/` is present.
400
+ if not _canonical_skill_source_present():
401
+ packaged = _packaged_skills_bundle_dir()
402
+ if packaged is not None:
403
+ candidate = packaged / skill_name
404
+ if candidate.is_dir():
405
+ return candidate.resolve()
406
+ return None
407
+
408
+
409
+ def inspect_skill_parity(repo: Path, harness_target: str, expected_skill_pack: tuple[str, ...]) -> SkillParity:
410
+ recommended = recommended_installed_roots(harness_target)
411
+ installed_roots = discover_installed_skill_roots(harness_target)
412
+ bridge_inventory = inspect_bridge_skill(repo, harness_target)
413
+ warnings = (_bridge_skill_warning(bridge_inventory),) if bridge_inventory is not None else ()
414
+ inventory_json = (bridge_inventory.to_json(),) if bridge_inventory is not None else ()
415
+ return SkillParity(
416
+ recommended_installed_roots=recommended,
417
+ installed_skill_roots=installed_roots,
418
+ installed_skill_warnings=warnings,
419
+ bridge_skill_inventory=inventory_json,
420
+ )
421
+
422
+
423
+ def inspect_bridge_skill_inventory(
424
+ repo: Path,
425
+ harness_targets: tuple[str, ...] = ("codex", "claude", "gemini", "opencode"),
426
+ ) -> tuple[BridgeSkillInventoryRecord, ...]:
427
+ records: list[BridgeSkillInventoryRecord] = []
428
+ for harness_target in harness_targets:
429
+ record = inspect_bridge_skill(repo, harness_target)
430
+ if record is not None:
431
+ records.append(record)
432
+ return tuple(records)
433
+
434
+
435
+ def inspect_workflow_skill_inventory(
436
+ repo: Path,
437
+ harness_targets: tuple[str, ...] = ("codex", "claude", "gemini", "opencode"),
438
+ ) -> tuple[WorkflowSkillInventoryRecord, ...]:
439
+ records: list[WorkflowSkillInventoryRecord] = []
440
+ for harness_target in harness_targets:
441
+ recommended = recommended_installed_roots(harness_target)
442
+ installed_roots = discover_installed_skill_roots(harness_target)
443
+ for skill_name in CANONICAL_WORKFLOW_SKILLS.get(harness_target, ()):
444
+ source_dir = resolve_source_skill_dir(repo, harness_target, skill_name)
445
+ installed_match = _matching_installed_skill(installed_roots, skill_name)
446
+ if source_dir is None:
447
+ parity_status = "missing_source"
448
+ elif not installed_roots:
449
+ parity_status = "missing_root"
450
+ elif installed_match is None:
451
+ parity_status = "missing_skill"
452
+ elif _paths_equivalent(source_dir, installed_match) or _skill_hash(source_dir) == _skill_hash(installed_match):
453
+ parity_status = "ok"
454
+ else:
455
+ parity_status = "drifted"
456
+ records.append(
457
+ WorkflowSkillInventoryRecord(
458
+ harness_target=harness_target,
459
+ skill_name=skill_name,
460
+ source_dir=str(source_dir) if source_dir is not None else None,
461
+ recommended_installed_roots=recommended,
462
+ installed_skill_roots=installed_roots,
463
+ installed_path=str(installed_match) if installed_match is not None else None,
464
+ parity_status=parity_status,
465
+ )
466
+ )
467
+ return tuple(records)
468
+
469
+
470
+ def inspect_vestigial_workflow_candidates(repo: Path) -> tuple[VestigialWorkflowCandidateRecord, ...]:
471
+ records: list[VestigialWorkflowCandidateRecord] = []
472
+ for root in VESTIGIAL_WORKFLOW_CANDIDATE_ROOTS:
473
+ path = repo / root
474
+ skill_file = path / "SKILL.md"
475
+ records.append(
476
+ VestigialWorkflowCandidateRecord(
477
+ path=str(path),
478
+ candidate_name=path.name,
479
+ exists=path.exists(),
480
+ skill_file=str(skill_file) if skill_file.exists() else None,
481
+ status="remove" if skill_file.exists() else "archived-history",
482
+ )
483
+ )
484
+ return tuple(records)
485
+
486
+
487
+ def classify_skill_like_directories(
488
+ repo: Path,
489
+ harness_targets: tuple[str, ...] = ("codex", "claude", "gemini", "opencode"),
490
+ ) -> tuple[SkillDirectoryClassificationRecord, ...]:
491
+ records: list[SkillDirectoryClassificationRecord] = []
492
+ canonical_names = {
493
+ harness_target: set(CANONICAL_WORKFLOW_SKILLS.get(harness_target, ()))
494
+ for harness_target in harness_targets
495
+ }
496
+ plugin_source_roots = dict(iter_skill_source_roots())
497
+ for harness_target in harness_targets:
498
+ # DISENTANGLE SL-2: iterate the MERGED builtin+plugin roots (mirrors
499
+ # resolve_source_skill_dir above) so canonical records still emit once the
500
+ # built-in HARNESS_SOURCE_ROOTS entries are empty and the dotfiles overlay
501
+ # roots arrive through the skill_sources seam.
502
+ merged_roots = (
503
+ *HARNESS_SOURCE_ROOTS.get(harness_target, ()),
504
+ *plugin_source_roots.get(harness_target, ()),
505
+ )
506
+ for root in merged_roots:
507
+ base = repo / root
508
+ if not base.is_dir():
509
+ continue
510
+ for child in sorted((path for path in base.iterdir() if path.is_dir()), key=lambda path: path.name):
511
+ if child.name.startswith("."):
512
+ continue
513
+ skill_file = child / "SKILL.md"
514
+ if child.name in canonical_names[harness_target]:
515
+ records.append(
516
+ SkillDirectoryClassificationRecord(
517
+ path=str(child),
518
+ skill_name=child.name,
519
+ classification="canonical",
520
+ reason=f"{harness_target} harness workflow source",
521
+ skill_file=str(skill_file) if skill_file.exists() else None,
522
+ )
523
+ )
524
+ legacy_root = repo / LEGACY_CLAUDE_UTILITY_ROOT
525
+ if legacy_root.is_dir():
526
+ vestigial_names = {Path(root).name for root in VESTIGIAL_WORKFLOW_CANDIDATE_ROOTS}
527
+ for child in sorted((path for path in legacy_root.iterdir() if path.is_dir()), key=lambda path: path.name):
528
+ if child.name.startswith("."):
529
+ continue
530
+ skill_file = child / "SKILL.md"
531
+ if child.name in vestigial_names:
532
+ records.append(_classify_vestigial_workflow_directory(child))
533
+ elif skill_file.exists():
534
+ records.append(
535
+ SkillDirectoryClassificationRecord(
536
+ path=str(child),
537
+ skill_name=child.name,
538
+ classification="legacy-utility",
539
+ reason="legacy Claude utility skill root, not a harness workflow source",
540
+ skill_file=str(skill_file),
541
+ )
542
+ )
543
+ pi_root = repo / PI_ROLE_SKILL_ROOT
544
+ if pi_root.is_dir():
545
+ for child in sorted((path for path in pi_root.iterdir() if path.is_dir()), key=lambda path: path.name):
546
+ skill_file = child / "SKILL.md"
547
+ if skill_file.exists():
548
+ records.append(
549
+ SkillDirectoryClassificationRecord(
550
+ path=str(child),
551
+ skill_name=child.name,
552
+ classification="pi-role",
553
+ reason="Pi Agent role-style skill exception",
554
+ skill_file=str(skill_file),
555
+ )
556
+ )
557
+ return tuple(records)
558
+
559
+
560
+ def inspect_bridge_skill(repo: Path, harness_target: str) -> BridgeSkillInventoryRecord | None:
561
+ skill_name = BRIDGE_SKILL_NAMES.get(harness_target)
562
+ if skill_name is None:
563
+ return None
564
+ recommended = recommended_installed_roots(harness_target)
565
+ installed_roots = discover_installed_skill_roots(harness_target)
566
+ source_dir = resolve_source_skill_dir(repo, harness_target, skill_name)
567
+ installed_match = _matching_installed_skill(installed_roots, skill_name)
568
+ repair_target = _repair_target(recommended, installed_roots, skill_name)
569
+ if source_dir is None:
570
+ parity_status = "missing_skill"
571
+ elif not installed_roots:
572
+ parity_status = "missing_root"
573
+ elif installed_match is None:
574
+ parity_status = "missing_skill"
575
+ elif _paths_equivalent(source_dir, installed_match) or _skill_hash(source_dir) == _skill_hash(installed_match):
576
+ parity_status = "ok"
577
+ else:
578
+ parity_status = "drifted"
579
+ return BridgeSkillInventoryRecord(
580
+ harness_target=harness_target,
581
+ skill_name=skill_name,
582
+ source_dir=str(source_dir) if source_dir is not None else None,
583
+ recommended_installed_roots=recommended,
584
+ installed_skill_roots=installed_roots,
585
+ installed_path=str(installed_match) if installed_match is not None else None,
586
+ parity_status=parity_status,
587
+ repair_target=repair_target,
588
+ )
589
+
590
+
591
+ def _classify_vestigial_workflow_directory(path: Path) -> SkillDirectoryClassificationRecord:
592
+ skill_file = path / "SKILL.md"
593
+ replacement = {
594
+ "plan-phase": "claude-plan-phase",
595
+ "execute-phase": "claude-execute-phase",
596
+ }.get(path.name)
597
+ if skill_file.exists() and path.name not in NON_CANONICAL_WORKFLOW_ALLOWLIST:
598
+ classification = "remove"
599
+ reason = "non-canonical workflow skill definition under legacy Claude utility root"
600
+ else:
601
+ classification = "archived-history"
602
+ reason = "ignored historical handoff or reflection residue, not a runtime skill source"
603
+ return SkillDirectoryClassificationRecord(
604
+ path=str(path),
605
+ skill_name=path.name,
606
+ classification=classification,
607
+ reason=reason,
608
+ canonical_replacement=replacement,
609
+ skill_file=str(skill_file) if skill_file.exists() else None,
610
+ )
611
+
612
+
613
+ def _matching_installed_skill(installed_roots: tuple[str, ...], skill_name: str) -> Path | None:
614
+ for root in installed_roots:
615
+ candidate = Path(root) / skill_name
616
+ if candidate.exists():
617
+ return candidate.resolve()
618
+ return None
619
+
620
+
621
+ def _paths_equivalent(left: Path, right: Path) -> bool:
622
+ try:
623
+ return left.samefile(right)
624
+ except OSError:
625
+ return False
626
+
627
+
628
+ def _skill_hash(path: Path) -> str:
629
+ digest = hashlib.sha256()
630
+ skill_file = path / "SKILL.md"
631
+ if skill_file.exists():
632
+ digest.update(skill_file.read_bytes())
633
+ else:
634
+ digest.update(path.name.encode("utf-8"))
635
+ return digest.hexdigest()
636
+
637
+
638
+ def _repair_target(recommended_roots: tuple[str, ...], installed_roots: tuple[str, ...], skill_name: str) -> str | None:
639
+ roots = installed_roots or tuple(str(Path(root).expanduser()) for root in recommended_roots)
640
+ if not roots:
641
+ return None
642
+ return str((Path(roots[0]) / skill_name).expanduser())
643
+
644
+
645
+ def _bridge_skill_warning(record: BridgeSkillInventoryRecord) -> str:
646
+ prefix = f"{record.harness_target}:{record.skill_name}"
647
+ if record.parity_status == "ok":
648
+ return f"{prefix}: installed bridge skill is in sync for manual reentry"
649
+ if record.parity_status == "missing_root":
650
+ return (
651
+ f"{prefix}: installed bridge root missing; autonomous injected bundle remains authoritative, "
652
+ f"but local reentry should run sync-skills --apply"
653
+ )
654
+ if record.parity_status == "missing_skill":
655
+ return (
656
+ f"{prefix}: installed bridge skill missing; autonomous injected bundle remains authoritative, "
657
+ f"but local reentry should run sync-skills --apply"
658
+ )
659
+ return (
660
+ f"{prefix}: installed bridge skill drifted from repo source; autonomous injected bundle remains authoritative, "
661
+ f"but local reentry should run sync-skills --apply"
662
+ )