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,650 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ import time
11
+ from dataclasses import dataclass
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path, PurePath
14
+ from typing import Any, Mapping, Sequence
15
+
16
+
17
+ SCHEMA_VERSION = 1
18
+ ARTIFACT_NAME = "verification.json"
19
+ LOG_NAME = "verification.log"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class VerificationCommandEvidence:
24
+ argv: list[str]
25
+ cwd: str
26
+ exit_code: int
27
+ duration_s: float
28
+ log_offset: int
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class VerificationEnvRefreshEvidence:
33
+ triggered: bool
34
+ manifests: list[str]
35
+ install_argv: list[str]
36
+ exit_code: int
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class VerificationSuiteEvidence:
41
+ argv: list[str]
42
+ exit_code: int
43
+ duration_s: float
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class VerificationResult:
48
+ schema_version: int
49
+ run_id: str
50
+ phase_alias: str
51
+ commands: list[VerificationCommandEvidence]
52
+ env_refresh: VerificationEnvRefreshEvidence | None
53
+ suite: VerificationSuiteEvidence | None
54
+ started_at: str
55
+ finished_at: str
56
+ log_sha256: str
57
+ operational_exemptions: list[dict[str, Any]] | None = None
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class ValidationFinding:
62
+ code: str
63
+ message: str
64
+ command_index: int
65
+ argument_index: int | None = None
66
+ value: str | None = None
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class VerificationArtifactValidation:
71
+ ok: bool
72
+ code: str
73
+ artifact_path: str
74
+ log_path: str | None = None
75
+ exit_summary: dict[str, Any] | None = None
76
+ findings: tuple[str, ...] = ()
77
+
78
+ def to_json(self) -> dict[str, Any]:
79
+ return {
80
+ "ok": self.ok,
81
+ "code": self.code,
82
+ "artifact_path": self.artifact_path,
83
+ "log_path": self.log_path,
84
+ "exit_summary": self.exit_summary or {},
85
+ "findings": list(self.findings),
86
+ }
87
+
88
+
89
+ def run_verification(
90
+ repo: Path,
91
+ run_dir: Path,
92
+ commands: list[list[str]],
93
+ suite_command: list[str] | None,
94
+ env_refresh: object,
95
+ timeout_s: float | None,
96
+ operational_exemptions: list[Mapping[str, Any]] | None = None,
97
+ ) -> VerificationResult:
98
+ repo_path = _resolve_repo(repo)
99
+ run_path = _resolve_run_dir(repo_path, run_dir)
100
+ run_path.mkdir(parents=True, exist_ok=True)
101
+ log_path = run_path / LOG_NAME
102
+ artifact_path = run_path / ARTIFACT_NAME
103
+ started_at = _utc_now()
104
+
105
+ with log_path.open("wb") as log_file:
106
+ env_result = _record_env_refresh(repo_path, log_file, env_refresh, timeout_s)
107
+ command_results = [_run_process(repo_path, log_file, argv, timeout_s) for argv in commands]
108
+ suite_result = None
109
+ if suite_command is not None:
110
+ suite_evidence = _run_process(repo_path, log_file, suite_command, timeout_s)
111
+ suite_result = VerificationSuiteEvidence(
112
+ argv=suite_evidence.argv,
113
+ exit_code=suite_evidence.exit_code,
114
+ duration_s=suite_evidence.duration_s,
115
+ )
116
+
117
+ finished_at = _utc_now()
118
+ log_sha256 = hashlib.sha256(log_path.read_bytes()).hexdigest()
119
+ result = VerificationResult(
120
+ schema_version=SCHEMA_VERSION,
121
+ run_id=run_path.name,
122
+ phase_alias=_phase_alias(repo_path),
123
+ commands=command_results,
124
+ env_refresh=env_result,
125
+ suite=suite_result,
126
+ started_at=started_at,
127
+ finished_at=finished_at,
128
+ log_sha256=log_sha256,
129
+ operational_exemptions=[dict(item) for item in operational_exemptions or []],
130
+ )
131
+ _write_artifact_atomic(artifact_path, _result_to_payload(result))
132
+ return result
133
+
134
+
135
+ def validate_verification_commands(repo: Path, commands: list[list[str]]) -> list[ValidationFinding]:
136
+ repo_path = _resolve_repo(repo)
137
+ findings: list[ValidationFinding] = []
138
+ for command_index, argv in enumerate(commands):
139
+ if not argv:
140
+ findings.append(
141
+ ValidationFinding(
142
+ code="empty_argv",
143
+ message="verification command argv must not be empty",
144
+ command_index=command_index,
145
+ )
146
+ )
147
+ continue
148
+ executable = _executable_argv(argv)[0] if _executable_argv(argv) else ""
149
+ if not _executable_resolves(repo_path, executable):
150
+ findings.append(
151
+ ValidationFinding(
152
+ code="unresolved_executable",
153
+ message="verification command executable does not resolve",
154
+ command_index=command_index,
155
+ argument_index=0,
156
+ value=executable,
157
+ )
158
+ )
159
+ for argument_index, value in _iter_path_arguments(argv):
160
+ finding = _validate_path_argument(repo_path, command_index, argument_index, value)
161
+ if finding is not None:
162
+ findings.append(finding)
163
+ return findings
164
+
165
+
166
+ def load_verification_artifact(path: Path) -> VerificationResult:
167
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
168
+ _require_keys(
169
+ data,
170
+ {
171
+ "schema_version",
172
+ "run_id",
173
+ "phase_alias",
174
+ "commands",
175
+ "env_refresh",
176
+ "suite",
177
+ "started_at",
178
+ "finished_at",
179
+ "log_sha256",
180
+ },
181
+ )
182
+ if data["schema_version"] != SCHEMA_VERSION:
183
+ raise ValueError(f"unsupported verification evidence schema_version: {data['schema_version']}")
184
+ commands = [_command_from_payload(item) for item in _require_list(data["commands"], "commands")]
185
+ env_refresh = None
186
+ if data["env_refresh"] is not None:
187
+ env_refresh = _env_refresh_from_payload(data["env_refresh"])
188
+ suite = None
189
+ if data["suite"] is not None:
190
+ suite = _suite_from_payload(data["suite"])
191
+ return VerificationResult(
192
+ schema_version=data["schema_version"],
193
+ run_id=_require_str(data["run_id"], "run_id"),
194
+ phase_alias=_require_str(data["phase_alias"], "phase_alias"),
195
+ commands=commands,
196
+ env_refresh=env_refresh,
197
+ suite=suite,
198
+ started_at=_require_str(data["started_at"], "started_at"),
199
+ finished_at=_require_str(data["finished_at"], "finished_at"),
200
+ log_sha256=_require_str(data["log_sha256"], "log_sha256"),
201
+ )
202
+
203
+
204
+ def validate_verification_artifact(path: Path) -> VerificationArtifactValidation:
205
+ artifact_path = Path(path)
206
+ log_path = artifact_path.parent / LOG_NAME
207
+ try:
208
+ result = load_verification_artifact(artifact_path)
209
+ except (OSError, json.JSONDecodeError, ValueError) as exc:
210
+ return VerificationArtifactValidation(
211
+ ok=False,
212
+ code="malformed_artifact",
213
+ artifact_path=str(artifact_path),
214
+ log_path=str(log_path),
215
+ findings=(str(exc),),
216
+ )
217
+ try:
218
+ actual_log_sha256 = hashlib.sha256(log_path.read_bytes()).hexdigest()
219
+ except OSError as exc:
220
+ return VerificationArtifactValidation(
221
+ ok=False,
222
+ code="missing_log",
223
+ artifact_path=str(artifact_path),
224
+ log_path=str(log_path),
225
+ exit_summary=_exit_summary(result),
226
+ findings=(str(exc),),
227
+ )
228
+ if actual_log_sha256 != result.log_sha256:
229
+ return VerificationArtifactValidation(
230
+ ok=False,
231
+ code="log_sha256_mismatch",
232
+ artifact_path=str(artifact_path),
233
+ log_path=str(log_path),
234
+ exit_summary=_exit_summary(result),
235
+ findings=("verification.log sha256 does not match verification.json log_sha256",),
236
+ )
237
+ nonzero = _nonzero_exit_findings(result)
238
+ if nonzero:
239
+ return VerificationArtifactValidation(
240
+ ok=False,
241
+ code="nonzero_exit",
242
+ artifact_path=str(artifact_path),
243
+ log_path=str(log_path),
244
+ exit_summary=_exit_summary(result),
245
+ findings=tuple(nonzero),
246
+ )
247
+ return VerificationArtifactValidation(
248
+ ok=True,
249
+ code="ok",
250
+ artifact_path=str(artifact_path),
251
+ log_path=str(log_path),
252
+ exit_summary=_exit_summary(result),
253
+ )
254
+
255
+
256
+ def append_evidence_entry(doc_path: Path, entry: Mapping[str, Any]) -> dict[str, Any]:
257
+ if not isinstance(entry, Mapping):
258
+ raise ValueError("evidence entry must be a metadata mapping")
259
+ path = Path(doc_path)
260
+ path.parent.mkdir(parents=True, exist_ok=True)
261
+ payload = {"timestamp": _utc_now(), "entry": dict(entry)}
262
+ encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n"
263
+ needs_separator = path.exists() and path.stat().st_size > 0
264
+ if needs_separator:
265
+ try:
266
+ with path.open("rb") as existing:
267
+ existing.seek(-1, os.SEEK_END)
268
+ needs_separator = existing.read(1) != b"\n"
269
+ except OSError:
270
+ needs_separator = False
271
+ with path.open("ab") as handle:
272
+ if needs_separator:
273
+ handle.write(b"\n")
274
+ handle.write(encoded.encode("utf-8"))
275
+ return payload
276
+
277
+
278
+ def detect_changed_dependency_manifests(repo: Path, base_ref: str, head_ref: str | None = None) -> list[str]:
279
+ repo_path = _resolve_repo(repo)
280
+ command = ["git", "diff", "--name-only", base_ref]
281
+ if head_ref:
282
+ command.append(head_ref)
283
+ try:
284
+ completed = subprocess.run(
285
+ command,
286
+ cwd=repo_path,
287
+ stdout=subprocess.PIPE,
288
+ stderr=subprocess.DEVNULL,
289
+ text=True,
290
+ check=False,
291
+ )
292
+ except OSError:
293
+ return []
294
+ if completed.returncode not in {0, 1}:
295
+ return []
296
+ manifests: list[str] = []
297
+ for line in completed.stdout.splitlines():
298
+ path = line.strip()
299
+ if not path or path.startswith("../") or path.startswith("/"):
300
+ continue
301
+ name = PurePath(path).name
302
+ if name in {"package.json", "package-lock.json", "pnpm-lock.yaml", "pyproject.toml", "uv.lock"}:
303
+ manifests.append(path)
304
+ elif name.startswith("requirements") and name.endswith(".txt"):
305
+ manifests.append(path)
306
+ return sorted(dict.fromkeys(manifests))
307
+
308
+
309
+ def resolve_install_command(repo: Path, manifests: list[str]) -> list[str] | None:
310
+ repo_path = _resolve_repo(repo)
311
+ names = {PurePath(path).name for path in manifests}
312
+ if names.intersection({"package.json", "package-lock.json"}):
313
+ return ["npm", "install"]
314
+ if "pnpm-lock.yaml" in names:
315
+ return ["pnpm", "install"]
316
+ if "uv.lock" in names or ("pyproject.toml" in names and shutil.which("uv")):
317
+ return ["uv", "sync"]
318
+ requirements = sorted(name for name in names if name.startswith("requirements") and name.endswith(".txt"))
319
+ if requirements:
320
+ return [sys.executable, "-m", "pip", "install", "-r", requirements[0]]
321
+ if "pyproject.toml" in names and (repo_path / "pyproject.toml").exists():
322
+ return [sys.executable, "-m", "pip", "install", "-e", "."]
323
+ return None
324
+
325
+
326
+ def _exit_summary(result: VerificationResult) -> dict[str, Any]:
327
+ return {
328
+ "commands": [command.exit_code for command in result.commands],
329
+ "env_refresh": result.env_refresh.exit_code if result.env_refresh is not None else None,
330
+ "suite": result.suite.exit_code if result.suite is not None else None,
331
+ }
332
+
333
+
334
+ def _nonzero_exit_findings(result: VerificationResult) -> list[str]:
335
+ findings: list[str] = []
336
+ for index, command in enumerate(result.commands):
337
+ if command.exit_code != 0:
338
+ findings.append(f"commands[{index}].exit_code={command.exit_code}")
339
+ if result.env_refresh is not None and result.env_refresh.exit_code != 0:
340
+ findings.append(f"env_refresh.exit_code={result.env_refresh.exit_code}")
341
+ if result.suite is not None and result.suite.exit_code != 0:
342
+ findings.append(f"suite.exit_code={result.suite.exit_code}")
343
+ return findings
344
+
345
+
346
+ def _run_process(repo: Path, log_file: Any, argv: Sequence[str], timeout_s: float | None) -> VerificationCommandEvidence:
347
+ command_argv = [str(part) for part in argv]
348
+ process_env, process_argv = _process_env_and_argv(command_argv)
349
+ offset = log_file.tell()
350
+ started = time.monotonic()
351
+ if not process_argv:
352
+ log_file.write(b"empty verification command argv\n")
353
+ log_file.flush()
354
+ return VerificationCommandEvidence(command_argv, ".", 127, _duration(started), offset)
355
+ try:
356
+ completed = subprocess.run(
357
+ process_argv,
358
+ cwd=repo,
359
+ env=process_env,
360
+ stdout=subprocess.PIPE,
361
+ stderr=subprocess.STDOUT,
362
+ timeout=timeout_s if timeout_s and timeout_s > 0 else None,
363
+ check=False,
364
+ )
365
+ output = completed.stdout or b""
366
+ log_file.write(output)
367
+ log_file.flush()
368
+ exit_code = int(completed.returncode)
369
+ except subprocess.TimeoutExpired as exc:
370
+ output = exc.output or b""
371
+ if isinstance(output, str):
372
+ output = output.encode("utf-8", errors="replace")
373
+ log_file.write(output)
374
+ log_file.write(f"\nverification command timed out after {timeout_s}s\n".encode("utf-8"))
375
+ log_file.flush()
376
+ exit_code = 124
377
+ except FileNotFoundError as exc:
378
+ log_file.write(f"{exc}\n".encode("utf-8", errors="replace"))
379
+ log_file.flush()
380
+ exit_code = 127
381
+ return VerificationCommandEvidence(command_argv, ".", exit_code, _duration(started), offset)
382
+
383
+
384
+ def _record_env_refresh(
385
+ repo: Path,
386
+ log_file: Any,
387
+ env_refresh: object,
388
+ timeout_s: float | None,
389
+ ) -> VerificationEnvRefreshEvidence | None:
390
+ if env_refresh is None:
391
+ return None
392
+ if isinstance(env_refresh, Mapping):
393
+ triggered = bool(env_refresh.get("triggered", True))
394
+ manifests = [str(item) for item in env_refresh.get("manifests", [])]
395
+ install_argv = [str(item) for item in env_refresh.get("install_argv", env_refresh.get("argv", []))]
396
+ if "exit_code" in env_refresh:
397
+ return VerificationEnvRefreshEvidence(triggered, manifests, install_argv, int(env_refresh["exit_code"]))
398
+ if not triggered or not install_argv:
399
+ return VerificationEnvRefreshEvidence(triggered, manifests, install_argv, 0)
400
+ else:
401
+ triggered = True
402
+ manifests = []
403
+ install_argv = [str(item) for item in env_refresh] if isinstance(env_refresh, Sequence) and not isinstance(env_refresh, (str, bytes)) else []
404
+ process = _run_process(repo, log_file, install_argv, timeout_s)
405
+ return VerificationEnvRefreshEvidence(triggered, manifests, install_argv, process.exit_code)
406
+
407
+
408
+ def _result_to_payload(result: VerificationResult) -> dict[str, Any]:
409
+ payload = {
410
+ "schema_version": result.schema_version,
411
+ "run_id": result.run_id,
412
+ "phase_alias": result.phase_alias,
413
+ "commands": [_command_to_payload(command) for command in result.commands],
414
+ "env_refresh": _env_refresh_to_payload(result.env_refresh) if result.env_refresh else None,
415
+ "suite": _suite_to_payload(result.suite) if result.suite else None,
416
+ "started_at": result.started_at,
417
+ "finished_at": result.finished_at,
418
+ "log_sha256": result.log_sha256,
419
+ }
420
+ if result.operational_exemptions:
421
+ payload["operational_exemptions"] = list(result.operational_exemptions)
422
+ return payload
423
+
424
+
425
+ def _command_to_payload(command: VerificationCommandEvidence) -> dict[str, Any]:
426
+ return {
427
+ "argv": list(command.argv),
428
+ "cwd": command.cwd,
429
+ "exit_code": command.exit_code,
430
+ "duration_s": command.duration_s,
431
+ "log_offset": command.log_offset,
432
+ }
433
+
434
+
435
+ def _env_refresh_to_payload(env_refresh: VerificationEnvRefreshEvidence) -> dict[str, Any]:
436
+ return {
437
+ "triggered": env_refresh.triggered,
438
+ "manifests": list(env_refresh.manifests),
439
+ "install_argv": list(env_refresh.install_argv),
440
+ "exit_code": env_refresh.exit_code,
441
+ }
442
+
443
+
444
+ def _suite_to_payload(suite: VerificationSuiteEvidence) -> dict[str, Any]:
445
+ return {
446
+ "argv": list(suite.argv),
447
+ "exit_code": suite.exit_code,
448
+ "duration_s": suite.duration_s,
449
+ }
450
+
451
+
452
+ def _write_artifact_atomic(path: Path, payload: dict[str, Any]) -> None:
453
+ encoded = json.dumps(payload, indent=2, sort_keys=True) + "\n"
454
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as tmp_file:
455
+ tmp_file.write(encoded)
456
+ tmp_path = Path(tmp_file.name)
457
+ tmp_path.replace(path)
458
+
459
+
460
+ def _resolve_repo(repo: Path) -> Path:
461
+ repo_path = Path(repo).resolve()
462
+ if not repo_path.is_dir():
463
+ raise ValueError(f"repo does not exist or is not a directory: {repo}")
464
+ return repo_path
465
+
466
+
467
+ def _resolve_run_dir(repo: Path, run_dir: Path) -> Path:
468
+ run_path = Path(run_dir)
469
+ if not run_path.is_absolute():
470
+ run_path = repo / run_path
471
+ run_path = run_path.resolve()
472
+ if not _is_relative_to(run_path, repo):
473
+ raise ValueError(f"run_dir must be inside repo: {run_dir}")
474
+ return run_path
475
+
476
+
477
+ def _phase_alias(repo: Path) -> str:
478
+ for key in ("PHASE_LOOP_PHASE_ALIAS", "PHASE_ALIAS"):
479
+ value = os.environ.get(key)
480
+ if value:
481
+ return value
482
+ state_path = repo / ".phase-loop/state.json"
483
+ try:
484
+ data = json.loads(state_path.read_text(encoding="utf-8"))
485
+ except (FileNotFoundError, json.JSONDecodeError):
486
+ return "unknown"
487
+ value = data.get("current_phase")
488
+ return value if isinstance(value, str) and value else "unknown"
489
+
490
+
491
+ def _utc_now() -> str:
492
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
493
+
494
+
495
+ def _duration(started: float) -> float:
496
+ return round(time.monotonic() - started, 6)
497
+
498
+
499
+ def _executable_resolves(repo: Path, executable: str) -> bool:
500
+ if not executable:
501
+ return False
502
+ candidate = Path(executable)
503
+ if candidate.is_absolute():
504
+ return candidate.exists() and os.access(candidate, os.X_OK)
505
+ if "/" in executable or "\\" in executable:
506
+ resolved = (repo / candidate).resolve()
507
+ return _is_relative_to(resolved, repo) and resolved.exists() and os.access(resolved, os.X_OK)
508
+ return shutil.which(executable) is not None
509
+
510
+
511
+ def _iter_path_arguments(argv: Sequence[str]) -> list[tuple[int, str]]:
512
+ references: list[tuple[int, str]] = []
513
+ cwd_flags = {"--cwd", "--chdir", "-C"}
514
+ for index, value in enumerate(argv[1:], start=1):
515
+ if _is_env_assignment(value):
516
+ continue
517
+ if value in cwd_flags and index + 1 < len(argv):
518
+ references.append((index + 1, argv[index + 1]))
519
+ continue
520
+ for prefix in ("--cwd=", "--chdir=", "cwd="):
521
+ if value.startswith(prefix):
522
+ references.append((index, value.removeprefix(prefix)))
523
+ break
524
+ else:
525
+ if _looks_path_like(value):
526
+ references.append((index, value))
527
+ return references
528
+
529
+
530
+ def _executable_argv(argv: Sequence[str]) -> list[str]:
531
+ return [str(part) for part in argv if not _is_env_assignment(str(part))]
532
+
533
+
534
+ def _process_env_and_argv(argv: Sequence[str]) -> tuple[dict[str, str] | None, list[str]]:
535
+ env = os.environ.copy()
536
+ command: list[str] = []
537
+ env_changed = False
538
+ for index, part in enumerate(argv):
539
+ value = str(part)
540
+ if not command and _is_env_assignment(value):
541
+ key, raw = value.split("=", 1)
542
+ env[key] = raw
543
+ env_changed = True
544
+ continue
545
+ command = [str(item) for item in argv[index:]]
546
+ break
547
+ return (env if env_changed else None), command
548
+
549
+
550
+ def _is_env_assignment(value: str) -> bool:
551
+ key, sep, _raw = value.partition("=")
552
+ return bool(sep) and key.replace("_", "").isalnum() and key[0].isalpha()
553
+
554
+
555
+ def _looks_path_like(value: str) -> bool:
556
+ return value.startswith(("/", "./", "../")) or "/" in value or "\\" in value
557
+
558
+
559
+ def _validate_path_argument(repo: Path, command_index: int, argument_index: int, value: str) -> ValidationFinding | None:
560
+ raw_path = Path(value)
561
+ resolved = raw_path.resolve() if raw_path.is_absolute() else (repo / raw_path).resolve()
562
+ if not _is_relative_to(resolved, repo):
563
+ return ValidationFinding(
564
+ code="outside_repo_path",
565
+ message="verification command references a path outside the repo",
566
+ command_index=command_index,
567
+ argument_index=argument_index,
568
+ value=value,
569
+ )
570
+ if not resolved.exists():
571
+ return ValidationFinding(
572
+ code="missing_path",
573
+ message="verification command references a repo-relative path that does not exist",
574
+ command_index=command_index,
575
+ argument_index=argument_index,
576
+ value=value,
577
+ )
578
+ return None
579
+
580
+
581
+ def _is_relative_to(path: Path, base: Path) -> bool:
582
+ try:
583
+ path.relative_to(base)
584
+ return True
585
+ except ValueError:
586
+ return False
587
+
588
+
589
+ def _require_keys(data: Any, keys: set[str]) -> None:
590
+ if not isinstance(data, dict):
591
+ raise ValueError("verification artifact must be an object")
592
+ missing = keys.difference(data)
593
+ if missing:
594
+ raise ValueError(f"verification artifact missing required fields: {', '.join(sorted(missing))}")
595
+
596
+
597
+ def _require_list(value: Any, field: str) -> list[Any]:
598
+ if not isinstance(value, list):
599
+ raise ValueError(f"{field} must be a list")
600
+ return value
601
+
602
+
603
+ def _require_str(value: Any, field: str) -> str:
604
+ if not isinstance(value, str):
605
+ raise ValueError(f"{field} must be a string")
606
+ return value
607
+
608
+
609
+ def _require_int(value: Any, field: str) -> int:
610
+ if not isinstance(value, int):
611
+ raise ValueError(f"{field} must be an integer")
612
+ return value
613
+
614
+
615
+ def _require_float(value: Any, field: str) -> float:
616
+ if not isinstance(value, (int, float)):
617
+ raise ValueError(f"{field} must be numeric")
618
+ return float(value)
619
+
620
+
621
+ def _command_from_payload(data: Any) -> VerificationCommandEvidence:
622
+ _require_keys(data, {"argv", "cwd", "exit_code", "duration_s", "log_offset"})
623
+ return VerificationCommandEvidence(
624
+ argv=[_require_str(item, "argv[]") for item in _require_list(data["argv"], "argv")],
625
+ cwd=_require_str(data["cwd"], "cwd"),
626
+ exit_code=_require_int(data["exit_code"], "exit_code"),
627
+ duration_s=_require_float(data["duration_s"], "duration_s"),
628
+ log_offset=_require_int(data["log_offset"], "log_offset"),
629
+ )
630
+
631
+
632
+ def _env_refresh_from_payload(data: Any) -> VerificationEnvRefreshEvidence:
633
+ _require_keys(data, {"triggered", "manifests", "install_argv", "exit_code"})
634
+ if not isinstance(data["triggered"], bool):
635
+ raise ValueError("triggered must be a boolean")
636
+ return VerificationEnvRefreshEvidence(
637
+ triggered=data["triggered"],
638
+ manifests=[_require_str(item, "manifests[]") for item in _require_list(data["manifests"], "manifests")],
639
+ install_argv=[_require_str(item, "install_argv[]") for item in _require_list(data["install_argv"], "install_argv")],
640
+ exit_code=_require_int(data["exit_code"], "exit_code"),
641
+ )
642
+
643
+
644
+ def _suite_from_payload(data: Any) -> VerificationSuiteEvidence:
645
+ _require_keys(data, {"argv", "exit_code", "duration_s"})
646
+ return VerificationSuiteEvidence(
647
+ argv=[_require_str(item, "argv[]") for item in _require_list(data["argv"], "argv")],
648
+ exit_code=_require_int(data["exit_code"], "exit_code"),
649
+ duration_s=_require_float(data["duration_s"], "duration_s"),
650
+ )