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,427 @@
1
+ """Harness-neutral repo-validation resolver (the ``phase-loop repo-validate`` core).
2
+
3
+ This is agent-harness's neutral home for the *local-first agentic validation
4
+ contract*: a single command surface (``fast``/``gate``/``full``/``fix``/
5
+ ``affected``/``doctor``) that coding agents run locally, in worktrees, and in CI
6
+ before opening PRs, with GitHub staying the authoritative merge gate. See
7
+ ``docs/repo-validation-contract.md`` for the full spec.
8
+
9
+ The resolver only ever runs a repo's *explicit* contract:
10
+
11
+ 1. ``just agent::<target>`` when a ``Justfile``/``justfile`` declares
12
+ ``mod agent`` and the repo ships an ``agent.just`` module recipe.
13
+ 2. a ``package.json`` script named ``agent:<target>``.
14
+
15
+ It NEVER guesses that ``npm test``, ``pytest``, or ``make test`` is the gate.
16
+ Unmigrated repos fail closed (exit 20) so agents do not invent CI.
17
+
18
+ BEHAVIORAL IDENTITY: this resolver is the Python twin of the dotfiles
19
+ ``scripts/agent-validation`` bash wrapper and MUST make the same dispatch
20
+ decision and return the same exit code for the same repo, so the local (bash)
21
+ and neutral (Python) surfaces never drift. The one intentional refinement: the
22
+ bash wrapper shells out to ``node`` merely to *read* ``package.json`` and maps a
23
+ missing ``node`` to exit 21. The neutral runtime parses ``package.json`` with the
24
+ Python stdlib, so exit 21 for a package contract means only what the contract
25
+ says it means -- the package-manager *runner* (npm/pnpm/yarn/bun) is missing --
26
+ not that a JSON reader is absent. That is closer to the contract's definition of
27
+ 21, not a divergence from its exit-code set.
28
+
29
+ The module is deliberately dependency-light (stdlib only) and splits pure
30
+ discovery/planning (``resolve``) from the subprocess (``run_plan``) so the
31
+ exit-code mapping is testable without a live ``just``/``npm`` on PATH.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import json
37
+ import re
38
+ import shutil
39
+ import subprocess
40
+ from dataclasses import dataclass
41
+ from pathlib import Path
42
+
43
+ # Frozen exit-code contract (docs/repo-validation-contract.md).
44
+ EXIT_OK = 0
45
+ EXIT_USAGE = 2
46
+ EXIT_NO_REPO = 10
47
+ EXIT_NO_CONTRACT = 20
48
+ EXIT_MISSING_RUNNER = 21
49
+ EXIT_COMMAND_FAILED = 30
50
+
51
+ # The six neutral targets plus the ``check`` alias for ``doctor``.
52
+ TARGETS = ("fast", "gate", "full", "fix", "affected", "doctor")
53
+ TARGET_ALIASES = {"check": "doctor"}
54
+ ALL_TARGET_TOKENS = TARGETS + tuple(TARGET_ALIASES)
55
+
56
+ # ``agent.just`` module candidates, in the same order the bash wrapper probes.
57
+ _AGENT_JUST_CANDIDATES = (
58
+ "agent.just",
59
+ "agent/mod.just",
60
+ "agent/Justfile",
61
+ "agent/justfile",
62
+ "agent/.justfile",
63
+ )
64
+
65
+ # Lockfile -> package-manager mapping, probed before the generic fallback order.
66
+ _LOCKFILE_MANAGERS = (
67
+ ("pnpm-lock.yaml", "pnpm"),
68
+ ("package-lock.json", "npm"),
69
+ ("yarn.lock", "yarn"),
70
+ ("bun.lock", "bun"),
71
+ ("bun.lockb", "bun"),
72
+ )
73
+ _FALLBACK_MANAGERS = ("pnpm", "npm", "yarn", "bun")
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class ValidationPlan:
78
+ """The resolved dispatch for one ``(repo_root, target)`` pair.
79
+
80
+ Exactly one of ``argv`` (a runnable command) or ``exit_code`` (a terminal
81
+ no-run outcome, 20 or 21) is set. ``resolve`` never touches the network or
82
+ mutates state; ``run_plan`` turns an ``argv`` plan into a 0/30 result.
83
+ """
84
+
85
+ target: str
86
+ repo_root: Path
87
+ contract_kind: str | None # "just" | "package" | None
88
+ contract: str | None # e.g. "agent::gate" or "agent:gate"
89
+ argv: tuple[str, ...] | None
90
+ exit_code: int | None
91
+ note: str | None = None
92
+
93
+ def to_json(self) -> dict[str, object]:
94
+ return {
95
+ "target": self.target,
96
+ "repo_root": str(self.repo_root),
97
+ "contract_kind": self.contract_kind,
98
+ "contract": self.contract,
99
+ "argv": list(self.argv) if self.argv is not None else None,
100
+ "exit_code": self.exit_code,
101
+ "note": self.note,
102
+ }
103
+
104
+
105
+ # --------------------------------------------------------------------------- #
106
+ # Repo-root resolution (git worktree aware).
107
+ # --------------------------------------------------------------------------- #
108
+ def find_repo_root(cwd: str | Path) -> Path | None:
109
+ """Return the git work-tree root for ``cwd``, or ``None`` when outside git.
110
+
111
+ Uses ``git rev-parse --show-toplevel`` so it resolves correctly from
112
+ subdirectories and from linked worktrees, matching the bash wrapper.
113
+ """
114
+
115
+ try:
116
+ proc = subprocess.run(
117
+ ["git", "-C", str(cwd), "rev-parse", "--show-toplevel"],
118
+ capture_output=True,
119
+ text=True,
120
+ check=False,
121
+ )
122
+ except FileNotFoundError:
123
+ return None
124
+ if proc.returncode != 0:
125
+ return None
126
+ root = proc.stdout.strip()
127
+ return Path(root) if root else None
128
+
129
+
130
+ # --------------------------------------------------------------------------- #
131
+ # Justfile contract discovery (pure filesystem).
132
+ # --------------------------------------------------------------------------- #
133
+ _COMMENT_RE = re.compile(r"^\s*#")
134
+ _MOD_AGENT_RE = re.compile(r"^\s*mod\??\s+agent(\s|$)")
135
+
136
+
137
+ def find_justfile(root: Path) -> Path | None:
138
+ for name in ("Justfile", "justfile"):
139
+ candidate = root / name
140
+ if candidate.is_file():
141
+ return candidate
142
+ return None
143
+
144
+
145
+ def _iter_noncomment_lines(path: Path):
146
+ try:
147
+ text = path.read_text(encoding="utf-8", errors="replace")
148
+ except OSError:
149
+ return
150
+ for line in text.splitlines():
151
+ if _COMMENT_RE.match(line):
152
+ continue
153
+ yield line
154
+
155
+
156
+ def has_agent_just_module(justfile: Path) -> bool:
157
+ """True when the Justfile declares ``mod agent`` (or ``mod? agent``)."""
158
+
159
+ return any(_MOD_AGENT_RE.match(line) for line in _iter_noncomment_lines(justfile))
160
+
161
+
162
+ def find_agent_just_module(root: Path) -> Path | None:
163
+ for candidate in _AGENT_JUST_CANDIDATES:
164
+ path = root / candidate
165
+ if path.is_file():
166
+ return path
167
+ return None
168
+
169
+
170
+ def has_just_recipe(justfile: Path, recipe: str) -> bool:
171
+ """True when ``justfile`` declares a recipe named ``recipe``."""
172
+
173
+ pattern = re.compile(rf"^\s*{re.escape(recipe)}([\s:]|$)")
174
+ return any(pattern.match(line) for line in _iter_noncomment_lines(justfile))
175
+
176
+
177
+ def just_contract_command(root: Path, target: str) -> str | None:
178
+ """Return the just invocation (``agent::<t>`` or ``agent:<t>``) or ``None``.
179
+
180
+ Mirrors the bash ``just_contract_command``: prefer the ``mod agent`` module
181
+ recipe, then a flat ``agent:<target>`` recipe in the root Justfile.
182
+ """
183
+
184
+ justfile = find_justfile(root)
185
+ if justfile is None:
186
+ return None
187
+ if has_agent_just_module(justfile):
188
+ module = find_agent_just_module(root)
189
+ if module is not None and has_just_recipe(module, target):
190
+ return f"agent::{target}"
191
+ if has_just_recipe(justfile, f"agent:{target}"):
192
+ return f"agent:{target}"
193
+ return None
194
+
195
+
196
+ # --------------------------------------------------------------------------- #
197
+ # package.json contract discovery (stdlib JSON, no node required).
198
+ # --------------------------------------------------------------------------- #
199
+ def package_has_script(root: Path, script: str) -> bool:
200
+ package_json = root / "package.json"
201
+ if not package_json.is_file():
202
+ return False
203
+ try:
204
+ data = json.loads(package_json.read_text(encoding="utf-8"))
205
+ except (OSError, ValueError):
206
+ return False
207
+ scripts = data.get("scripts") if isinstance(data, dict) else None
208
+ return isinstance(scripts, dict) and script in scripts
209
+
210
+
211
+ def package_manager_for(root: Path) -> str | None:
212
+ """Pick the package-manager runner, honoring a present lockfile first.
213
+
214
+ When a lockfile pins a manager, that manager must be on PATH (else ``None``,
215
+ which the caller maps to exit 21). With no lockfile, fall back to the first
216
+ available manager, matching the bash wrapper's probe order.
217
+ """
218
+
219
+ for lockfile, manager in _LOCKFILE_MANAGERS:
220
+ if (root / lockfile).is_file():
221
+ return manager if shutil.which(manager) else None
222
+ for manager in _FALLBACK_MANAGERS:
223
+ if shutil.which(manager):
224
+ return manager
225
+ return None
226
+
227
+
228
+ # --------------------------------------------------------------------------- #
229
+ # Resolution (pure) + execution (subprocess).
230
+ # --------------------------------------------------------------------------- #
231
+ def resolve(repo_root: Path, target: str) -> ValidationPlan:
232
+ """Resolve the dispatch for a non-doctor target without running anything.
233
+
234
+ Precedence (identical to the bash wrapper):
235
+ 1. just contract + ``just`` on PATH -> run it.
236
+ 2. package script present -> run it (or 21 if no manager).
237
+ 3. just contract but ``just`` absent -> 21.
238
+ 4. nothing -> 20 (fail closed).
239
+ """
240
+
241
+ just_contract = just_contract_command(repo_root, target)
242
+ has_pkg = package_has_script(repo_root, f"agent:{target}")
243
+ just_present = shutil.which("just") is not None
244
+
245
+ if just_contract is not None and just_present:
246
+ return ValidationPlan(
247
+ target=target,
248
+ repo_root=repo_root,
249
+ contract_kind="just",
250
+ contract=just_contract,
251
+ argv=("just", just_contract),
252
+ exit_code=None,
253
+ )
254
+
255
+ if has_pkg:
256
+ script = f"agent:{target}"
257
+ note = None
258
+ if just_contract is not None and not just_present:
259
+ note = (
260
+ f"{just_contract} is declared, but just is not on PATH; "
261
+ f"running explicit package script {script}."
262
+ )
263
+ manager = package_manager_for(repo_root)
264
+ if manager is None:
265
+ return ValidationPlan(
266
+ target=target,
267
+ repo_root=repo_root,
268
+ contract_kind="package",
269
+ contract=script,
270
+ argv=None,
271
+ exit_code=EXIT_MISSING_RUNNER,
272
+ note=f"package.json declares {script}, but no supported package manager is on PATH.",
273
+ )
274
+ return ValidationPlan(
275
+ target=target,
276
+ repo_root=repo_root,
277
+ contract_kind="package",
278
+ contract=script,
279
+ argv=(manager, "run", script),
280
+ exit_code=None,
281
+ note=note,
282
+ )
283
+
284
+ if just_contract is not None:
285
+ return ValidationPlan(
286
+ target=target,
287
+ repo_root=repo_root,
288
+ contract_kind="just",
289
+ contract=just_contract,
290
+ argv=None,
291
+ exit_code=EXIT_MISSING_RUNNER,
292
+ note=f"{just_contract} is declared, but just is not on PATH.",
293
+ )
294
+
295
+ return ValidationPlan(
296
+ target=target,
297
+ repo_root=repo_root,
298
+ contract_kind=None,
299
+ contract=None,
300
+ argv=None,
301
+ exit_code=EXIT_NO_CONTRACT,
302
+ note=(
303
+ f"no explicit agent validation contract for agent:{target}. "
304
+ f"Declare just agent::{target} or a package.json script named agent:{target}."
305
+ ),
306
+ )
307
+
308
+
309
+ def run_plan(plan: ValidationPlan) -> int:
310
+ """Execute a resolved plan and map the result to the exit-code contract.
311
+
312
+ A no-run plan returns its terminal code (20/21). A runnable plan returns 0 on
313
+ success and 30 when the repo-local validation command fails.
314
+ """
315
+
316
+ if plan.argv is None:
317
+ return int(plan.exit_code if plan.exit_code is not None else EXIT_NO_CONTRACT)
318
+ try:
319
+ proc = subprocess.run(list(plan.argv), cwd=str(plan.repo_root), check=False)
320
+ except FileNotFoundError:
321
+ # The runner vanished between discovery and exec (race / PATH change).
322
+ return EXIT_MISSING_RUNNER
323
+ return EXIT_OK if proc.returncode == 0 else EXIT_COMMAND_FAILED
324
+
325
+
326
+ # --------------------------------------------------------------------------- #
327
+ # doctor: a neutral environment/contract report (always exits 0).
328
+ # --------------------------------------------------------------------------- #
329
+ def _declared_contracts(root: Path) -> list[tuple[str, str]]:
330
+ found: list[tuple[str, str]] = []
331
+ for target in TARGETS:
332
+ just_contract = just_contract_command(root, target)
333
+ if just_contract is not None:
334
+ found.append((target, f"just {just_contract}"))
335
+ elif package_has_script(root, f"agent:{target}"):
336
+ found.append((target, f"package script agent:{target}"))
337
+ return found
338
+
339
+
340
+ def doctor_report(root: Path) -> dict[str, object]:
341
+ tools = {
342
+ name: shutil.which(name)
343
+ for name in ("git", "just", "dagger", "docker", "node", "pnpm", "npm", "yarn", "bun", "uv", "python3", "cargo")
344
+ }
345
+ stack_hints = [
346
+ name
347
+ for name in (
348
+ "package.json",
349
+ "pyproject.toml",
350
+ "Cargo.toml",
351
+ "dagger.json",
352
+ "Dockerfile",
353
+ "docker-compose.yml",
354
+ ".pre-commit-config.yaml",
355
+ )
356
+ if (root / name).is_file()
357
+ ]
358
+ return {
359
+ "repo_root": str(root),
360
+ "tools": tools,
361
+ "stack_hints": stack_hints,
362
+ "declared_contracts": [{"target": t, "runner": r} for t, r in _declared_contracts(root)],
363
+ }
364
+
365
+
366
+ def _print_doctor(report: dict[str, object]) -> None:
367
+ print("Agent validation doctor")
368
+ print(f" repo-root {report['repo_root']}")
369
+ print("")
370
+ print("Tools:")
371
+ for name, path in report["tools"].items(): # type: ignore[index]
372
+ print(f" {name:<16} {'present (' + path + ')' if path else 'missing'}")
373
+ print("")
374
+ print("Repo stack hints:")
375
+ for hint in report["stack_hints"]: # type: ignore[index]
376
+ print(f" {hint}")
377
+ print("")
378
+ print("Declared agent contracts:")
379
+ contracts = report["declared_contracts"] # type: ignore[index]
380
+ if contracts:
381
+ for entry in contracts: # type: ignore[union-attr]
382
+ print(f" {entry['target']:<16} {entry['runner']}")
383
+ else:
384
+ print(" none")
385
+ print("")
386
+ print(
387
+ "This repo is not migrated yet. Validation targets fail closed (exit 20) "
388
+ "until it declares agent:* commands."
389
+ )
390
+
391
+
392
+ # --------------------------------------------------------------------------- #
393
+ # CLI entry point (wired from cli.py as the ``repo-validate`` subcommand).
394
+ # --------------------------------------------------------------------------- #
395
+ def cli_main(*, target: str, cwd: str | Path = ".", as_json: bool = False) -> int:
396
+ """Resolve and run a repo's explicit validation contract; return its code."""
397
+
398
+ normalized = TARGET_ALIASES.get(target, target)
399
+ if normalized not in TARGETS:
400
+ print(f"ERROR: unknown validation target '{target}'.", flush=True)
401
+ return EXIT_USAGE
402
+
403
+ root = find_repo_root(cwd)
404
+ if root is None:
405
+ print("ERROR: not inside a git work tree.", flush=True)
406
+ return EXIT_NO_REPO
407
+
408
+ if normalized == "doctor":
409
+ report = doctor_report(root)
410
+ if as_json:
411
+ print(json.dumps(report, indent=2))
412
+ else:
413
+ _print_doctor(report)
414
+ return EXIT_OK
415
+
416
+ plan = resolve(root, normalized)
417
+ if as_json:
418
+ # --json is inspection-only: print the resolved plan and DO NOT execute
419
+ # the repo-local command (keeping stdout clean, machine-parseable JSON).
420
+ # A no-run plan returns its terminal code (20/21); a runnable plan returns
421
+ # 0, meaning "a runnable contract was resolved" -- not "the command ran".
422
+ print(json.dumps(plan.to_json(), indent=2))
423
+ return EXIT_OK if plan.argv is not None else run_plan(plan)
424
+ if plan.note:
425
+ stream_prefix = "WARN" if plan.argv is not None else "ERROR"
426
+ print(f"{stream_prefix}: {plan.note}", flush=True)
427
+ return run_plan(plan)
@@ -0,0 +1,150 @@
1
+ """Run-end review-findings summary (rigor-v1 P7 / acceptance #3).
2
+
3
+ Review gates default to ``warn``: each finding is recorded inside its phase's
4
+ closeout (``verification.results``, ``kind="review_finding"``) and the loop
5
+ continues. So a human reviewing a bounded run between ``--max-phases`` batches
6
+ needs the findings aggregated and surfaced — that is what this module does.
7
+
8
+ ``collect_review_findings`` recursively scans ledger events for review-finding
9
+ records (robust to exactly where the closeout nests them) and de-duplicates.
10
+ ``render_review_findings_summary`` renders an operator-readable block. The
11
+ runner emits it to stderr at run end.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Iterable
16
+
17
+
18
+ def _walk_kind(obj: Any, kind: str) -> Iterable[dict]:
19
+ """Recursively yield every dict in ``obj`` whose ``kind`` field matches."""
20
+ if isinstance(obj, dict):
21
+ if obj.get("kind") == kind:
22
+ yield obj
23
+ for value in obj.values():
24
+ yield from _walk_kind(value, kind)
25
+ elif isinstance(obj, (list, tuple)):
26
+ for item in obj:
27
+ yield from _walk_kind(item, kind)
28
+
29
+
30
+ def _collect_by_kind(events, kind, *, key, tag_phase):
31
+ """Shared collect-and-dedup skeleton: scan events for records of ``kind``,
32
+ dedup by ``key(record, event_phase)``. When ``tag_phase`` the returned record
33
+ is tagged with its event's ``phase`` (review findings); otherwise the record
34
+ is returned as-is (panel verdicts already carry their own ``phase``)."""
35
+ out: list[dict] = []
36
+ seen: set[tuple] = set()
37
+ for event in events:
38
+ if not isinstance(event, dict):
39
+ continue
40
+ phase = event.get("phase")
41
+ for record in _walk_kind(event, kind):
42
+ k = key(record, phase)
43
+ if k in seen:
44
+ continue
45
+ seen.add(k)
46
+ if tag_phase:
47
+ tagged = dict(record)
48
+ tagged["phase"] = phase
49
+ out.append(tagged)
50
+ else:
51
+ out.append(record)
52
+ return out
53
+
54
+
55
+ def collect_review_findings(events: Iterable[Any]) -> list[dict]:
56
+ """Extract de-duplicated review findings from ledger events, tagging each with
57
+ its event's ``phase``; dedup by (phase, code, reason, severity)."""
58
+ return _collect_by_kind(
59
+ events,
60
+ "review_finding",
61
+ key=lambda r, phase: (phase, r.get("code"), r.get("reason"), r.get("severity")),
62
+ tag_phase=True,
63
+ )
64
+
65
+
66
+ def render_review_findings_summary(findings: list[dict]) -> str:
67
+ """Operator-readable summary block, or '' when there are no findings."""
68
+ if not findings:
69
+ return ""
70
+ by_sev: dict[str, int] = {}
71
+ for f in findings:
72
+ by_sev[str(f.get("severity") or "warn")] = by_sev.get(str(f.get("severity") or "warn"), 0) + 1
73
+ counts = ", ".join(f"{n} {sev}" for sev, n in sorted(by_sev.items()))
74
+ lines = [
75
+ f"Review findings this run: {len(findings)} ({counts}).",
76
+ "Recorded by autonomy-first gates; the loop was not stalled. "
77
+ "Address them or set PHASE_LOOP_REVIEW=block to enforce on the next run.",
78
+ ]
79
+ for f in findings:
80
+ lines.append(
81
+ f" - [{f.get('severity', 'warn')}] {f.get('phase') or '?'}: "
82
+ f"{f.get('code')} — {f.get('reason')}"
83
+ )
84
+ return "\n".join(lines)
85
+
86
+
87
+ def summarize_run_review_findings(events: Iterable[Any]) -> str:
88
+ """Convenience: collect + render in one call."""
89
+ return render_review_findings_summary(collect_review_findings(events))
90
+
91
+
92
+ # --- model-routing-v1 P4: governed panel verdicts in the run-end summary -------
93
+
94
+ def panel_verdict_record(
95
+ *,
96
+ phase: str,
97
+ outcome: str,
98
+ degraded: bool = False,
99
+ rounds: int | None = None,
100
+ reason: str | None = None,
101
+ ) -> dict[str, Any]:
102
+ """A metadata-only ledger record describing a governed panel outcome.
103
+
104
+ ``outcome`` is one of ``mergeable`` / ``blocked`` / ``degraded``; the governed
105
+ pre-merge/planning gates emit this so a human auditing a governed run sees the
106
+ verdict in the run-end summary.
107
+ """
108
+ record: dict[str, Any] = {"kind": "panel_verdict", "phase": phase, "outcome": outcome, "degraded": bool(degraded)}
109
+ if rounds is not None:
110
+ record["rounds"] = rounds
111
+ if reason is not None:
112
+ record["reason"] = reason
113
+ return record
114
+
115
+
116
+ def collect_panel_verdicts(events: Iterable[Any]) -> list[dict]:
117
+ """Extract de-duplicated ``panel_verdict`` records from ledger events
118
+ (records already carry their own ``phase``); dedup by
119
+ (phase, outcome, degraded, rounds)."""
120
+ return _collect_by_kind(
121
+ events,
122
+ "panel_verdict",
123
+ key=lambda r, _phase: (r.get("phase"), r.get("outcome"), r.get("degraded"), r.get("rounds")),
124
+ tag_phase=False,
125
+ )
126
+
127
+
128
+ def render_panel_verdicts_summary(verdicts: list[dict]) -> str:
129
+ """Operator-readable panel-verdict block, or '' when there are none."""
130
+ if not verdicts:
131
+ return ""
132
+ lines = [f"Governed panel verdicts this run: {len(verdicts)}."]
133
+ for v in verdicts:
134
+ flag = " (degraded — advisory only)" if v.get("degraded") else ""
135
+ rounds = f", {v['rounds']} round(s)" if v.get("rounds") is not None else ""
136
+ lines.append(
137
+ f" - {v.get('phase') or '?'}: {v.get('outcome')}{flag}{rounds}"
138
+ + (f" — {v['reason']}" if v.get("reason") else "")
139
+ )
140
+ return "\n".join(lines)
141
+
142
+
143
+ def summarize_run(events: Iterable[Any]) -> str:
144
+ """The full run-end summary: review findings + governed panel verdicts."""
145
+ events = list(events)
146
+ blocks = [
147
+ render_review_findings_summary(collect_review_findings(events)),
148
+ render_panel_verdicts_summary(collect_panel_verdicts(events)),
149
+ ]
150
+ return "\n\n".join(b for b in blocks if b)