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,305 @@
1
+ """CS-0.5 -- the `.consiliency/` scaffolder (first-writer).
2
+
3
+ `phase-loop consiliency-scaffold --repo <path> --archetype <name>` writes a
4
+ schema-valid `.consiliency/` layout: a manifest declaring the archetype(s) +
5
+ governed-doc allowlist, a contract-version/status artifact, an (initially
6
+ empty) interface declaration, and presence-only L0 stubs for the archetype's
7
+ required baseline docs.
8
+
9
+ Design constraints (roadmap CS-0.5):
10
+
11
+ * **Additive, never destructive.** This module never modifies or deletes
12
+ `.phase-loop/` or `.pipeline/`, and it never overwrites a file that already
13
+ exists -- "first-writer" means the scaffolder only fills gaps. Re-running it
14
+ on an already-scaffolded (or hand-edited) repo is a safe no-op for anything
15
+ present.
16
+ * **No fake projections.** Where the required-documents registry marks a doc
17
+ ``l0_stub_allowed: true`` (the common case), the scaffolder writes a minimal
18
+ presence-only stub with an explicit "authored zone" -- honest about being
19
+ unauthored, never a hand-written imitation of real content. Where a doc is
20
+ ``l0_stub_allowed: false`` (``readme``, ``license``, and the ``document``
21
+ archetype's ``document-index``), the scaffolder never invents content: it
22
+ references the file if the repo already has it at the conventional path,
23
+ and otherwise only *declares* the manifest entry, leaving the gap for a
24
+ human -- the CS-0.6 presence gate reports that gap as a WARN finding.
25
+ * **Vendored schemas only.** All shapes come from ``consiliency_contract``
26
+ (the published shared contract package); nothing here re-implements or
27
+ copies schema literals.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import json
32
+ import re
33
+ import subprocess
34
+ from dataclasses import dataclass
35
+ from pathlib import Path
36
+ from typing import Any
37
+
38
+ from jsonschema import Draft202012Validator
39
+
40
+ from .consiliency_layout import (
41
+ ARCHETYPE_IDS,
42
+ MODIFIER_IDS,
43
+ RequiredDocRow,
44
+ compose_required_documents,
45
+ consiliency_root,
46
+ contract_version_status_schema,
47
+ find_consiliency_manifest,
48
+ installed_contract_version,
49
+ interface_declaration_schema,
50
+ interfaces_path,
51
+ manifest_path,
52
+ manifest_schema,
53
+ status_path,
54
+ )
55
+ from .models import utc_now
56
+
57
+ #: Docs the scaffolder generates as REAL minimal artifacts (not stubs) -- their
58
+ #: content is true/current information (installed package version, an empty
59
+ #: declared-edge set), never a placeholder standing in for unauthored prose.
60
+ _MACHINE_REAL_IDS = ("contract-version-status", "interface-declaration")
61
+
62
+ #: Well-known repo-root filenames for the handful of baseline docs that have
63
+ #: an obvious conventional home. Everything else is scaffolded under
64
+ #: `.consiliency/docs/<id>.md`, a namespace that can never collide with a
65
+ #: repo's own doc layout.
66
+ _ROOT_CONVENTION_PATHS: dict[str, str] = {
67
+ "readme": "README.md",
68
+ "license": "LICENSE",
69
+ "contributing": "CONTRIBUTING.md",
70
+ "changelog": "CHANGELOG.md",
71
+ "codeowners": "CODEOWNERS",
72
+ }
73
+
74
+ _SLUG_RE = re.compile(r"[^a-z0-9_.-]+")
75
+
76
+
77
+ def _slugify(name: str) -> str:
78
+ slug = _SLUG_RE.sub("-", name.strip().lower()).strip("-.")
79
+ if not slug or not slug[0].isalnum():
80
+ slug = f"repo-{slug}" if slug else "repo"
81
+ return slug
82
+
83
+
84
+ def _default_branch(repo: Path) -> str:
85
+ try:
86
+ ref = subprocess.check_output(
87
+ ["git", "-C", str(repo), "symbolic-ref", "--short", "HEAD"],
88
+ text=True,
89
+ stderr=subprocess.DEVNULL,
90
+ ).strip()
91
+ if ref:
92
+ return ref
93
+ except Exception:
94
+ pass
95
+ return "main"
96
+
97
+
98
+ def _doc_target_path(row: RequiredDocRow) -> str:
99
+ if row.id in _MACHINE_REAL_IDS:
100
+ return f".consiliency/{'status.json' if row.id == 'contract-version-status' else 'interfaces.json'}"
101
+ if row.id in _ROOT_CONVENTION_PATHS:
102
+ return _ROOT_CONVENTION_PATHS[row.id]
103
+ return f".consiliency/docs/{row.id}.md"
104
+
105
+
106
+ def _stub_body(row: RequiredDocRow) -> str:
107
+ title = row.id.replace("-", " ").title()
108
+ note = row.l0_note or (
109
+ f"This is an L0 presence-only stub. Its existence satisfies the Consiliency "
110
+ f"`{row.id}` document surface at maturity `presence-only` -- it declares that "
111
+ "this surface is tracked, not that its content has been authored."
112
+ )
113
+ if row.id == "codeowners":
114
+ return (
115
+ f"# Generated by: phase-loop consiliency-scaffold (doc-id={row.id}, maturity=presence-only)\n"
116
+ f"# {note}\n"
117
+ "# consiliency:authored-zone:start\n"
118
+ "# TODO: author real CODEOWNERS entries.\n"
119
+ "# consiliency:authored-zone:end\n"
120
+ )
121
+ return (
122
+ f"<!-- consiliency:generated-by=phase-loop consiliency-scaffold -->\n"
123
+ f"<!-- consiliency:doc-id={row.id} -->\n"
124
+ f"<!-- consiliency:maturity=presence-only -->\n\n"
125
+ f"# {title}\n\n"
126
+ f"{note}\n\n"
127
+ "<!-- consiliency:authored-zone:start -->\n"
128
+ "TODO: author this document's real content.\n"
129
+ "<!-- consiliency:authored-zone:end -->\n"
130
+ )
131
+
132
+
133
+ @dataclass(frozen=True)
134
+ class ScaffoldResult:
135
+ repo: Path
136
+ dry_run: bool
137
+ manifest_path: Path
138
+ manifest_written: bool
139
+ created_paths: tuple[str, ...]
140
+ referenced_paths: tuple[str, ...]
141
+ declared_missing_paths: tuple[str, ...]
142
+ already_present: bool
143
+
144
+ def to_json(self) -> dict[str, Any]:
145
+ return {
146
+ "repo": str(self.repo),
147
+ "dry_run": self.dry_run,
148
+ "manifest_path": str(self.manifest_path),
149
+ "manifest_written": self.manifest_written,
150
+ "created_paths": list(self.created_paths),
151
+ "referenced_paths": list(self.referenced_paths),
152
+ "declared_missing_paths": list(self.declared_missing_paths),
153
+ "already_present": self.already_present,
154
+ }
155
+
156
+
157
+ class ScaffoldError(ValueError):
158
+ """Bad scaffold input (unknown archetype/modifier, baseline-only + archetypes, ...)."""
159
+
160
+
161
+ def scaffold(
162
+ repo: str | Path,
163
+ *,
164
+ mode: str,
165
+ archetypes: tuple[str, ...] = (),
166
+ modifiers: tuple[str, ...] = (),
167
+ repo_id: str | None = None,
168
+ display_name: str | None = None,
169
+ dry_run: bool = False,
170
+ ) -> ScaffoldResult:
171
+ repo = Path(repo)
172
+ if mode not in ("baseline-only", "archetyped"):
173
+ raise ScaffoldError(f"invalid mode: {mode!r} (expected 'baseline-only' or 'archetyped')")
174
+ if mode == "baseline-only" and (archetypes or modifiers):
175
+ raise ScaffoldError("baseline-only mode does not accept --archetype/--modifier")
176
+ if mode == "archetyped" and not archetypes:
177
+ raise ScaffoldError("archetyped mode requires at least one --archetype")
178
+ unknown_archetypes = sorted(set(archetypes) - set(ARCHETYPE_IDS))
179
+ if unknown_archetypes:
180
+ raise ScaffoldError(f"unknown archetype(s): {unknown_archetypes}; known: {list(ARCHETYPE_IDS)}")
181
+ unknown_modifiers = sorted(set(modifiers) - set(MODIFIER_IDS))
182
+ if unknown_modifiers:
183
+ raise ScaffoldError(f"unknown modifier(s): {unknown_modifiers}; known: {list(MODIFIER_IDS)}")
184
+
185
+ existing_manifest = find_consiliency_manifest(repo)
186
+ already_present = existing_manifest is not None
187
+ target_manifest_path = manifest_path(repo)
188
+
189
+ rows = compose_required_documents(mode=mode, archetypes=archetypes, modifiers=modifiers)
190
+
191
+ created: list[str] = []
192
+ referenced: list[str] = []
193
+ declared_missing: list[str] = []
194
+ documents: list[dict[str, Any]] = []
195
+
196
+ for row in sorted(rows, key=lambda r: r.id):
197
+ rel_path = _doc_target_path(row)
198
+ abs_path = repo / rel_path
199
+ documents.append(
200
+ {
201
+ "id": row.id,
202
+ "class": row.doc_class,
203
+ "path": rel_path,
204
+ "maturity": "presence-only",
205
+ "target_level": "L0",
206
+ "required": row.required,
207
+ }
208
+ )
209
+ if abs_path.exists():
210
+ referenced.append(rel_path)
211
+ continue
212
+ if row.id in _MACHINE_REAL_IDS or row.l0_stub_allowed:
213
+ created.append(rel_path)
214
+ if not dry_run:
215
+ abs_path.parent.mkdir(parents=True, exist_ok=True)
216
+ if row.id == "contract-version-status":
217
+ abs_path.write_text(
218
+ json.dumps(_status_payload(), indent=2, sort_keys=True) + "\n", encoding="utf-8"
219
+ )
220
+ elif row.id == "interface-declaration":
221
+ abs_path.write_text(
222
+ json.dumps(_interfaces_payload(repo_id or _slugify(repo.name)), indent=2, sort_keys=True) + "\n",
223
+ encoding="utf-8",
224
+ )
225
+ else:
226
+ abs_path.write_text(_stub_body(row), encoding="utf-8")
227
+ else:
228
+ # l0_stub_allowed: false and nothing on disk -- do NOT fabricate.
229
+ # Declared in the manifest; the presence gate reports the gap.
230
+ declared_missing.append(rel_path)
231
+
232
+ manifest = {
233
+ "schema": "consiliency.manifest.v1",
234
+ "contract_version": installed_contract_version(),
235
+ "repo": {
236
+ "id": repo_id or _slugify(repo.name),
237
+ "display_name": display_name or repo.name,
238
+ "default_branch": _default_branch(repo),
239
+ },
240
+ "declaration": (
241
+ {"mode": "baseline-only"}
242
+ if mode == "baseline-only"
243
+ else {
244
+ "mode": "archetyped",
245
+ "archetypes": sorted(archetypes),
246
+ **({"modifiers": sorted(modifiers)} if modifiers else {}),
247
+ }
248
+ ),
249
+ "documents": documents,
250
+ "interfaces": ".consiliency/interfaces.json",
251
+ }
252
+ Draft202012Validator(manifest_schema()).validate(manifest)
253
+
254
+ manifest_written = False
255
+ if not already_present and not dry_run:
256
+ target_manifest_path.parent.mkdir(parents=True, exist_ok=True)
257
+ target_manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
258
+ manifest_written = True
259
+ elif not already_present:
260
+ manifest_written = True # what WOULD be written, for dry-run reporting
261
+
262
+ return ScaffoldResult(
263
+ repo=repo,
264
+ dry_run=dry_run,
265
+ manifest_path=target_manifest_path,
266
+ manifest_written=manifest_written,
267
+ created_paths=tuple(created),
268
+ referenced_paths=tuple(referenced),
269
+ declared_missing_paths=tuple(declared_missing),
270
+ already_present=already_present,
271
+ )
272
+
273
+
274
+ def _status_payload() -> dict[str, Any]:
275
+ version = installed_contract_version()
276
+ return {
277
+ "schema": "consiliency.contract_version_status.v1",
278
+ "package": {"name": "consiliency-contract", "version": version},
279
+ "repo_contract_version": version,
280
+ "compatibility": "compatible",
281
+ "maturity": "presence-only",
282
+ "checked_at": utc_now(),
283
+ "evidence": [],
284
+ }
285
+
286
+
287
+ def _interfaces_payload(repo_id: str) -> dict[str, Any]:
288
+ return {
289
+ "schema": "consiliency.interface_declaration.v1",
290
+ "repo_id": repo_id,
291
+ "realized_edges": [],
292
+ "promised_edges": [],
293
+ }
294
+
295
+
296
+ # Defensive self-checks -- the schemas ARE the contract; validate our own
297
+ # generated shapes against them so a drift in this module fails loud in tests
298
+ # rather than silently shipping a manifest the gates can't parse.
299
+ def _validate_generated_artifacts(repo: Path) -> None:
300
+ status_file = status_path(repo)
301
+ if status_file.is_file():
302
+ Draft202012Validator(contract_version_status_schema()).validate(json.loads(status_file.read_text(encoding="utf-8")))
303
+ interfaces_file = interfaces_path(repo)
304
+ if interfaces_file.is_file():
305
+ Draft202012Validator(interface_declaration_schema()).validate(json.loads(interfaces_file.read_text(encoding="utf-8")))
@@ -0,0 +1,331 @@
1
+ """Cross-repo consumption-channel descriptor and injection primitive.
2
+
3
+ IF-0-P2-2 contract: every cross-repo dependency edge declares HOW the
4
+ downstream workspace references the upstream — via a manifest-file version pin,
5
+ a ``git submodule``, or a workspace/path override. **NOT a git rebase**: two
6
+ repos have unrelated git histories.
7
+
8
+ The load-bearing primitive is :func:`set_upstream_ref`, which the coordinator
9
+ calls (with the upstream draft branch ref in P3 and the upstream merged SHA in
10
+ P4) **before** invoking the unchanged per-repo ``run_loop`` in the downstream
11
+ workspace. This is how an unchanged ``run_loop`` can consume the upstream at
12
+ all.
13
+
14
+ Channel kinds (closed set — no plugin system):
15
+
16
+ ``pin``
17
+ A manifest-file version pin: the executor rewrites a file the downstream
18
+ build ACTUALLY reads (e.g. ``requirements.txt``, ``package.json``, a
19
+ lockfile, a plain version file).
20
+
21
+ Two forms:
22
+ ``pin file=<path>`` — plain version/ref file: writes ``ref``
23
+ as the sole file content.
24
+ ``pin file=<path> key=<a.b.c>`` — JSON manifest: loads existing JSON,
25
+ sets the nested dotted key to ``ref``,
26
+ writes back with 2-space indent.
27
+
28
+ Required param: ``file`` (repo-relative path of the file to rewrite).
29
+ Optional param: ``key`` (dotted JSON key; absent → plain file).
30
+
31
+ ``submodule``
32
+ A ``git submodule``. Params: ``path`` (submodule path relative to the
33
+ downstream workspace root).
34
+
35
+ ``workspace``
36
+ A workspace/path override (e.g. a ``[tool.uv.sources]`` workspace entry or
37
+ a Cargo workspace path dep). Params: ``path`` (the override path).
38
+ **Rejected at train validation (T-E)**: workspace injection is not
39
+ implemented for real consumption and is rejected at preflight.
40
+
41
+ ``none``
42
+ No upstream dependency; declared explicitly for root nodes.
43
+
44
+ Zero external deps (stdlib only).
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import json
50
+ import subprocess
51
+ from dataclasses import dataclass, field
52
+ from pathlib import Path
53
+ from typing import Callable, Dict, List, Literal, Optional
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Channel descriptor — IF-0-P2-2
57
+
58
+ ChannelKind = Literal["pin", "submodule", "workspace", "none", "order-only"]
59
+
60
+ VALID_CHANNEL_KINDS: frozenset[str] = frozenset(
61
+ {"pin", "submodule", "workspace", "none", "order-only"}
62
+ )
63
+
64
+ _NONE_VALUES = frozenset({"(none)", "none", ""})
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class ChannelDescriptor:
69
+ """Per-edge consumption-channel descriptor (IF-0-P2-2).
70
+
71
+ Attributes:
72
+ kind: One of ``pin``, ``submodule``, ``workspace``, or ``none``.
73
+ params: Kind-specific parameters (see module docstring).
74
+ """
75
+
76
+ kind: ChannelKind
77
+ params: Dict[str, str] = field(default_factory=dict, compare=True, hash=False)
78
+
79
+ def __hash__(self) -> int:
80
+ return hash((self.kind, tuple(sorted(self.params.items()))))
81
+
82
+ def __repr__(self) -> str: # pragma: no cover
83
+ if self.kind == "none":
84
+ return "ChannelDescriptor(none)"
85
+ params_str = " ".join(f"{k}={v}" for k, v in sorted(self.params.items()))
86
+ return f"ChannelDescriptor({self.kind} {params_str})"
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Parser
91
+
92
+ def parse_channel_line(raw: str) -> ChannelDescriptor:
93
+ """Parse a ``**Channel:**`` field value into a :class:`ChannelDescriptor`.
94
+
95
+ Accepted forms::
96
+
97
+ (none) → ChannelDescriptor(kind="none")
98
+ submodule path=vendor/consiliency-portal
99
+ pin file=manifest.json → plain version/ref file
100
+ pin file=manifest.json key=deps.schema → JSON manifest, dotted key
101
+ workspace path=../mylib
102
+
103
+ Raises :exc:`ValueError` on unrecognised channel kind or missing required params.
104
+ """
105
+ stripped = raw.strip()
106
+ if stripped.lower() in _NONE_VALUES:
107
+ return ChannelDescriptor(kind="none", params={})
108
+
109
+ parts = stripped.split()
110
+ kind = parts[0].lower()
111
+ if kind not in VALID_CHANNEL_KINDS or kind == "none":
112
+ raise ValueError(
113
+ f"unknown channel kind '{kind}'; expected one of: pin, submodule, "
114
+ f"workspace, order-only"
115
+ )
116
+
117
+ params: Dict[str, str] = {}
118
+ for token in parts[1:]:
119
+ if "=" in token:
120
+ k, _, v = token.partition("=")
121
+ params[k.strip()] = v.strip()
122
+ else:
123
+ raise ValueError(
124
+ f"channel param '{token}' has no '='; expected 'key=value' form"
125
+ )
126
+
127
+ _validate_params(kind, params, raw) # type: ignore[arg-type]
128
+ return ChannelDescriptor(kind=kind, params=params) # type: ignore[arg-type]
129
+
130
+
131
+ def _validate_params(kind: ChannelKind, params: Dict[str, str], raw: str) -> None:
132
+ if kind == "pin":
133
+ if "file" not in params:
134
+ raise ValueError(
135
+ f"pin channel requires a 'file' param (the manifest file that gets "
136
+ f"rewritten with the upstream ref); got: {raw!r}"
137
+ )
138
+ elif kind in ("submodule", "workspace"):
139
+ if "path" not in params:
140
+ raise ValueError(
141
+ f"{kind} channel requires a 'path' param; got: {raw!r}"
142
+ )
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Injection primitive — IF-0-P2-2
147
+
148
+ # The git/fs boundary is injectable for tests (stub it; never call real git in
149
+ # unit tests). The protocol is a callable:
150
+ # executor(workspace: Path, kind: ChannelKind, params: dict, ref: str) -> List[str]
151
+ # The return value is the list of workspace-relative paths modified (for the
152
+ # coordinator to union into the downstream's published owned_paths).
153
+
154
+ ChannelExecutor = Callable[[Path, str, Dict[str, str], str], List[str]]
155
+
156
+
157
+ def _assert_within_workspace(resolved: Path, workspace_resolved: Path, param: str) -> None:
158
+ """Fail loud if ``resolved`` escapes ``workspace_resolved``.
159
+
160
+ Raises :exc:`ValueError` if the resolved path is not strictly within the
161
+ workspace root (``../`` traversal or absolute path that escapes the tree).
162
+ """
163
+ try:
164
+ resolved.relative_to(workspace_resolved)
165
+ except ValueError:
166
+ raise ValueError(
167
+ f"channel param {param!r} resolves to {str(resolved)!r}, which is "
168
+ f"outside the workspace {str(workspace_resolved)!r}; "
169
+ f"refusing to read/write outside the workspace (path containment violation)"
170
+ )
171
+
172
+
173
+ class UnsupportedChannelKind(ValueError):
174
+ """Raised by the live executor when a channel kind is valid in the schema but
175
+ not yet implemented for real consumption in this MVP.
176
+
177
+ Using a hollow sentinel file (written but never read by the downstream
178
+ build) would silently build the downstream against the absent upstream,
179
+ corrupting the whole train. We fail loud instead.
180
+ """
181
+
182
+
183
+ def _default_executor(
184
+ workspace: Path,
185
+ kind: str,
186
+ params: Dict[str, str],
187
+ ref: str,
188
+ ) -> List[str]:
189
+ """Default live executor — runs real git/fs operations.
190
+
191
+ Channel support:
192
+ ``submodule`` — git fetch + checkout; the downstream build ACTUALLY
193
+ consumes the injected ref (submodule HEAD is updated).
194
+ ``pin`` — rewrites the manifest file the downstream build reads:
195
+ - ``file`` only → writes ``ref`` as the plain file content (+ newline).
196
+ - ``file`` + ``key`` → loads existing JSON, sets the nested dotted key
197
+ to ``ref``, writes back with 2-space indent + trailing newline.
198
+ The downstream build reads the file directly, so injection is real.
199
+ ``workspace`` — NOT IMPLEMENTED for real consumption; raises
200
+ :exc:`UnsupportedChannelKind`. Workspace edges are rejected at train
201
+ validation (T-E) before any executor is reached.
202
+
203
+ Returns the workspace-relative paths modified (for the coordinator to union
204
+ into the downstream's published owned_paths so pin/submodule changes always
205
+ ship in the PR even if run_loop's snapshot excludes them).
206
+
207
+ Hardening:
208
+ - Path containment: rejects ``file``/``path`` params that escape the
209
+ workspace root via ``../`` traversal or absolute paths.
210
+ - JSON key safety: rejects empty or malformed ``key`` params; raises when
211
+ a non-dict intermediate value would be silently overwritten.
212
+
213
+ Stubbing the executor (``_executor=stub``) is the correct approach for
214
+ tests that exercise workspace channel kinds (which remain unimplemented).
215
+ """
216
+ workspace_resolved = workspace.resolve()
217
+
218
+ if kind == "submodule":
219
+ submodule_path = params["path"]
220
+ # Path containment: reject ../- or absolute-escaping params.
221
+ resolved = (workspace / submodule_path).resolve()
222
+ _assert_within_workspace(resolved, workspace_resolved, submodule_path)
223
+ # Dereference the submodule to the given ref so the downstream build
224
+ # actually runs against the injected upstream content.
225
+ subprocess.run(
226
+ ["git", "fetch", "origin"],
227
+ cwd=workspace / submodule_path,
228
+ check=True,
229
+ )
230
+ subprocess.run(
231
+ ["git", "checkout", ref],
232
+ cwd=workspace / submodule_path,
233
+ check=True,
234
+ )
235
+ return [submodule_path]
236
+ elif kind == "pin":
237
+ file_param = params["file"]
238
+ file_path = workspace / file_param
239
+ # Path containment: reject ../- or absolute-escaping params.
240
+ resolved = file_path.resolve()
241
+ _assert_within_workspace(resolved, workspace_resolved, file_param)
242
+ key = params.get("key")
243
+ if key is not None:
244
+ # Validate the key: reject empty or malformed dotted paths.
245
+ if not key or not all(part for part in key.split(".")):
246
+ raise ValueError(
247
+ f"pin 'key' param is empty or malformed: {key!r}; "
248
+ f"expected a non-empty dotted path like 'deps.schema'"
249
+ )
250
+ # JSON manifest: load existing file (or start with empty dict), set
251
+ # the nested dotted key to ref, write back with consistent indent.
252
+ data: Dict = json.loads(file_path.read_text(encoding="utf-8")) if file_path.exists() else {}
253
+ keys = key.split(".")
254
+ node: Dict = data
255
+ for k in keys[:-1]:
256
+ if k not in node:
257
+ node[k] = {}
258
+ elif not isinstance(node[k], dict):
259
+ # Safety: raise rather than silently clobber a non-dict value.
260
+ raise ValueError(
261
+ f"cannot set dotted key {key!r}: intermediate key {k!r} "
262
+ f"exists but is not a dict (got {type(node[k]).__name__!r}); "
263
+ f"refusing to silently overwrite"
264
+ )
265
+ node = node[k]
266
+ node[keys[-1]] = ref
267
+ file_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
268
+ else:
269
+ # Plain version/ref file: write ref as the sole content.
270
+ file_path.write_text(ref + "\n", encoding="utf-8")
271
+ return [file_param]
272
+ elif kind == "workspace":
273
+ raise UnsupportedChannelKind(
274
+ f"'workspace' channel injection is not implemented for real consumption "
275
+ f"(path={params.get('path')!r}). Workspace edges are rejected at train "
276
+ f"validation (T-E) before reaching the executor; a workspace channel in a "
277
+ f"live train means the train roadmap was not validated — check preflight."
278
+ )
279
+ elif kind == "order-only":
280
+ # #47: an order-only edge carries NO physical channel — the downstream does
281
+ # not consume the upstream artifact, so there is nothing to inject. The
282
+ # coordinator skips set_upstream_ref for order-only edges; reaching the
283
+ # executor here means a caller forgot to skip — fail loud, never no-op.
284
+ raise UnsupportedChannelKind(
285
+ "'order-only' edges carry no consumption channel and must never be "
286
+ "injected; the train coordinator skips set_upstream_ref for them "
287
+ "(they enforce merge order only)."
288
+ )
289
+ else:
290
+ raise ValueError(f"unknown channel kind for executor: {kind!r}")
291
+
292
+
293
+ def set_upstream_ref(
294
+ workspace: Path,
295
+ channel: ChannelDescriptor,
296
+ ref: str,
297
+ *,
298
+ _executor: Optional[ChannelExecutor] = None,
299
+ ) -> List[str]:
300
+ """Re-resolve the downstream workspace's dependency channel to ``ref``.
301
+
302
+ This is the **load-bearing injection primitive** (IF-0-P2-2): the
303
+ coordinator calls this *before* invoking the unchanged per-repo
304
+ ``run_loop`` to point the downstream's consumption channel at a specific
305
+ upstream ref — the draft branch ``head_sha`` in P3 and the upstream
306
+ **merge SHA** in P4.
307
+
308
+ Returns the workspace-relative paths modified by the injection (e.g.
309
+ ``["manifest.json"]`` for a pin, ``["vendor/repo-a"]`` for a submodule).
310
+ The coordinator unions these into the downstream node's ``owned_paths``
311
+ before publishing so the pin/submodule change always ships in the PR even
312
+ if ``run_loop``'s snapshot excludes the injected file.
313
+
314
+ Args:
315
+ workspace: Absolute path to the downstream repo's worktree.
316
+ channel: The :class:`ChannelDescriptor` for this edge.
317
+ ref: The upstream ref or SHA to pin the channel to.
318
+ _executor: Optional injectable executor (for testing). Defaults to
319
+ the live git/fs executor.
320
+
321
+ Raises:
322
+ ValueError: If the channel kind is ``none`` (root nodes have no
323
+ channel to re-resolve) or unknown.
324
+ """
325
+ if channel.kind == "none":
326
+ raise ValueError(
327
+ "set_upstream_ref called on a 'none' channel — root nodes have no "
328
+ "upstream dependency to resolve"
329
+ )
330
+ executor = _executor if _executor is not None else _default_executor
331
+ return executor(workspace, channel.kind, dict(channel.params), ref) or []