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,184 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from pathlib import PurePosixPath
6
+ from typing import Any, Mapping
7
+
8
+ from .models import CHANGED_PATH_CATEGORIES, SourceTruthImpact
9
+
10
+
11
+ _SOURCE_TRUTH_REASON_BY_CATEGORY = {
12
+ "docs": "docs_source_truth_touched",
13
+ "specs": "specs_source_truth_touched",
14
+ "active_canonical_spec": "active_specs_touched",
15
+ "managed_root_mirror_spec": "managed_mirror_specs_touched",
16
+ "mirror_manifest": "mirror_manifests_touched",
17
+ "archive_manifest": "archive_manifests_touched",
18
+ "archived_spec": "archived_specs_touched",
19
+ "unmanaged_spec": "unmanaged_specs_touched",
20
+ "pipeline_sources": "pipeline_sources_touched",
21
+ "portal_contract_refs": "portal_contract_refs_touched",
22
+ "greenfield_authority_refs": "greenfield_authority_refs_touched",
23
+ }
24
+
25
+ _CATEGORY_BY_PROTECTED_SOURCE_ROLE = {
26
+ "active_canonical_spec": "active_canonical_spec",
27
+ "managed_mirror_file": "managed_root_mirror_spec",
28
+ "mirror_manifest": "mirror_manifest",
29
+ "archive_manifest": "archive_manifest",
30
+ "archived_spec": "archived_spec",
31
+ "unmanaged_spec_input": "unmanaged_spec",
32
+ "root_specs_intake": "unmanaged_spec",
33
+ }
34
+
35
+ _FORBIDDEN_METADATA_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
36
+ ("raw_diff", re.compile(r"diff --git|@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@")),
37
+ ("raw_spec_body", re.compile(r"raw spec bod(?:y|ies)|spec body bytes|verbatim spec", re.I)),
38
+ ("raw_transcript", re.compile(r"raw transcript|transcript bytes|verbatim transcript", re.I)),
39
+ ("secret_like_value", re.compile(r"(?:api[_-]?key|secret|token|password)\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{12,}", re.I)),
40
+ ("absolute_private_path", re.compile(r"/(?:home|users|mnt/(?:private|evidence|secure|raw|HC_Volume_[^/\s]+))/(?:[^\"'\s]+)", re.I)),
41
+ ("provider_payload", re.compile(r"raw provider payload|provider payload|anthropic[_-]?payload|openai[_-]?payload", re.I)),
42
+ ("credential_payload", re.compile(r"credential payload|private key|-----begin [a-z ]*private key-----", re.I)),
43
+ ("local_env_value", re.compile(r"local env value|\.env(?:\.local)? value|process\.env\[[^\]]+\]\s*=", re.I)),
44
+ ("private_evidence", re.compile(r"private evidence|evidence bytes|raw evidence", re.I)),
45
+ )
46
+
47
+
48
+ def classify_changed_path(path: str, protected_source_roles: Mapping[str, str] | None = None) -> str:
49
+ normalized = _normalize_path(path)
50
+ parts = PurePosixPath(normalized).parts
51
+ lower = normalized.lower()
52
+ role_category = _category_from_protected_source_role(normalized, protected_source_roles)
53
+ if role_category is not None:
54
+ return role_category
55
+
56
+ if normalized.startswith("tests/") or "/tests/" in normalized or "/fixtures/" in normalized:
57
+ return "tests"
58
+ if lower == "readme.md" or normalized.startswith("docs/") or normalized.endswith(".md") and "/docs/" in normalized:
59
+ return "docs"
60
+ if _looks_like_mirror_manifest(normalized, lower):
61
+ return "mirror_manifest"
62
+ if _looks_like_archive_manifest(normalized, lower):
63
+ return "archive_manifest"
64
+ if _looks_like_active_canonical_spec(normalized, lower):
65
+ return "active_canonical_spec"
66
+ if _looks_like_archived_spec(normalized, lower):
67
+ return "archived_spec"
68
+ if normalized.startswith("specs/") or normalized.startswith("spec/"):
69
+ return "unmanaged_spec"
70
+ if (
71
+ normalized.startswith(".pipeline/")
72
+ or "pipeline.definition.json" in lower
73
+ or normalized.startswith("packages/pipeline-schema/")
74
+ or normalized.startswith("pipeline-sources/")
75
+ ):
76
+ return "pipeline_sources"
77
+ if (
78
+ "portal-contract" in lower
79
+ or normalized.startswith("portal/contracts/")
80
+ or normalized.startswith("contracts/portal/")
81
+ or normalized.startswith("consiliency-portal/contracts/")
82
+ ):
83
+ return "portal_contract_refs"
84
+ if (
85
+ "greenfield-authority" in lower
86
+ or normalized.startswith("greenfield/authority/")
87
+ or normalized.startswith("greenfield/contracts/")
88
+ or normalized.startswith("authority/greenfield/")
89
+ ):
90
+ return "greenfield_authority_refs"
91
+ if _looks_like_code_path(normalized, parts):
92
+ return "code"
93
+ return "unknown"
94
+
95
+
96
+ def build_source_truth_impact(
97
+ changed_paths: tuple[str, ...] | list[str] | Any,
98
+ protected_source_roles: Mapping[str, str] | None = None,
99
+ ) -> SourceTruthImpact:
100
+ paths = _stable_paths(changed_paths)
101
+ boundaries = tuple(
102
+ {"path": path, "category": classify_changed_path(path, protected_source_roles)}
103
+ for path in paths
104
+ )
105
+ reasons: list[str] = []
106
+ for boundary in boundaries:
107
+ category = boundary["category"]
108
+ reason = _SOURCE_TRUTH_REASON_BY_CATEGORY.get(category)
109
+ if reason is not None:
110
+ reasons.append(reason)
111
+ if "adoption" in boundary["path"].lower() and "contract" in boundary["path"].lower():
112
+ reasons.append("adoption_contracts_touched")
113
+ if "contract" in boundary["path"].lower() and category in CHANGED_PATH_CATEGORIES:
114
+ reasons.append("contract_refs_touched")
115
+ return SourceTruthImpact(
116
+ changed_path_boundaries=boundaries,
117
+ canonical_refresh_recommended=bool(reasons),
118
+ canonical_refresh_reason_codes=tuple(sorted(dict.fromkeys(reasons))),
119
+ redaction_posture="metadata_only",
120
+ )
121
+
122
+
123
+ def metadata_redaction_diagnostic(payload: Mapping[str, Any] | None) -> dict[str, str] | None:
124
+ if payload is None:
125
+ return None
126
+ serialized = json.dumps(payload, sort_keys=True)
127
+ for kind, pattern in _FORBIDDEN_METADATA_PATTERNS:
128
+ if pattern.search(serialized):
129
+ return {"kind": "malformed_closeout", "message": f"closeout contains forbidden metadata token: {kind}"}
130
+ return None
131
+
132
+
133
+ def _normalize_path(path: str) -> str:
134
+ normalized = str(path).replace("\\", "/").strip()
135
+ return normalized[2:] if normalized.startswith("./") else normalized
136
+
137
+
138
+ def _stable_paths(paths: tuple[str, ...] | list[str] | Any) -> list[str]:
139
+ if not isinstance(paths, (tuple, list)):
140
+ return []
141
+ return sorted(dict.fromkeys(_normalize_path(str(path)) for path in paths if str(path).strip()))
142
+
143
+
144
+ def _looks_like_code_path(path: str, parts: tuple[str, ...]) -> bool:
145
+ if path.startswith(("codex-config/", "shared/skills/", "scripts/")):
146
+ return True
147
+ if parts and parts[0] in {"bin", "lib", "src"}:
148
+ return True
149
+ return PurePosixPath(path).suffix in {".py", ".sh", ".bash", ".zsh", ".toml", ".yaml", ".yml", ".json"}
150
+
151
+
152
+ def _category_from_protected_source_role(
153
+ path: str,
154
+ protected_source_roles: Mapping[str, str] | None,
155
+ ) -> str | None:
156
+ if not protected_source_roles:
157
+ return None
158
+ role = protected_source_roles.get(path) or protected_source_roles.get(path.lower())
159
+ if role is None:
160
+ return None
161
+ return _CATEGORY_BY_PROTECTED_SOURCE_ROLE.get(role)
162
+
163
+
164
+ def _looks_like_mirror_manifest(path: str, lower: str) -> bool:
165
+ name = PurePosixPath(path).name.lower()
166
+ return name == "mirror-manifest.json" or "mirror_manifest" in lower or "/mirror-manifest" in lower
167
+
168
+
169
+ def _looks_like_archive_manifest(path: str, lower: str) -> bool:
170
+ name = PurePosixPath(path).name.lower()
171
+ return name == "archive-manifest.json" or "archive_manifest" in lower or "/archive-manifest" in lower
172
+
173
+
174
+ def _looks_like_active_canonical_spec(path: str, lower: str) -> bool:
175
+ return (
176
+ path.startswith(".pipeline/specs/active/")
177
+ or path.startswith(".pipeline/specs/canonical/")
178
+ or "/active-canonical/" in lower
179
+ or "/canonical-specs/" in lower
180
+ )
181
+
182
+
183
+ def _looks_like_archived_spec(path: str, lower: str) -> bool:
184
+ return path.startswith(".pipeline/specs/archive/") or "/archived-specs/" in lower
@@ -0,0 +1,293 @@
1
+ """Multi-machine reflection collection (Slice 1).
2
+
3
+ Reflections are written locally by skills at close-out (see
4
+ ``claude-config/shared/runtime-state.md``) and never leave the machine — they
5
+ are gitignored and live in ``~/.<harness>/skills/<skill>/reflections/``. This
6
+ module provides the *collection half*: redact each reflection (so it is safe to
7
+ pool across machines, including off-tailnet collaborators) and copy it under a
8
+ machine-namespaced path into a shared store directory.
9
+
10
+ Two pure, testable building blocks:
11
+
12
+ * :func:`machine_id` — a stable, non-secret per-machine identifier.
13
+ * :func:`redact_reflection_text` — deterministically strips repo/branch/commit/
14
+ path identity from a reflection so the pooled copy is repo-agnostic.
15
+
16
+ and the orchestrator :func:`export_reflections`, invoked by
17
+ ``scripts/reflections-sync``. The git transport and run-id dedup ledger are
18
+ intentionally deferred to Slice 2 (a ``phase-loop reflections sync`` subcommand).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import hashlib
25
+ import os
26
+ import re
27
+ import socket
28
+ import sys
29
+ import uuid
30
+ from pathlib import Path
31
+
32
+ from .skill_paths import resolve_skill_bundle_root
33
+
34
+ DEFAULT_HARNESSES: tuple[str, ...] = ("claude", "codex", "gemini", "opencode")
35
+
36
+ # Repo-local (source-controlled) reflection roots per harness, relative to repo.
37
+ _REPO_LOCAL_ROOTS: dict[str, tuple[str, ...]] = {
38
+ "claude": ("claude-config/claude-skills", "claude-config/skills"),
39
+ "codex": ("codex-config/skills",),
40
+ "gemini": ("gemini-config/skills",),
41
+ "opencode": ("opencode-config/skills",),
42
+ }
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Machine identity
47
+ # ---------------------------------------------------------------------------
48
+
49
+ def _slug(value: str) -> str:
50
+ out = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
51
+ out = re.sub(r"-{2,}", "-", out).strip("-")
52
+ return out or "unknown"
53
+
54
+
55
+ def _machine_id_file() -> Path:
56
+ base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
57
+ return Path(base) / "dotfiles" / "machine-id"
58
+
59
+
60
+ def machine_id() -> str:
61
+ """Return a stable, non-secret identifier for this machine.
62
+
63
+ Persisted at ``$XDG_CONFIG_HOME/dotfiles/machine-id`` (default
64
+ ``~/.config/dotfiles/machine-id``) so it survives across runs and is
65
+ independent of tailnet membership — off-tailnet collaborators have no
66
+ tailscale hostname. ``DOTFILES_MACHINE_ID`` overrides. Falls back to a
67
+ hostname-derived hash if the id file cannot be written.
68
+ """
69
+ override = os.environ.get("DOTFILES_MACHINE_ID")
70
+ if override and override.strip():
71
+ return _slug(override)
72
+
73
+ path = _machine_id_file()
74
+ try:
75
+ existing = path.read_text(encoding="utf-8").strip()
76
+ if existing:
77
+ return _slug(existing)
78
+ except OSError:
79
+ pass
80
+
81
+ new_id = uuid.uuid4().hex[:12]
82
+ try:
83
+ path.parent.mkdir(parents=True, exist_ok=True)
84
+ path.write_text(new_id + "\n", encoding="utf-8")
85
+ return new_id
86
+ except OSError:
87
+ host = socket.gethostname() or "unknown-host"
88
+ return "host-" + hashlib.sha256(host.encode("utf-8")).hexdigest()[:10]
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Redaction
93
+ # ---------------------------------------------------------------------------
94
+
95
+ # Structured Run-context / frontmatter fields whose values reveal identity.
96
+ # Maps a lowercased field name to the placeholder prefix used for its hash.
97
+ _SENSITIVE_FIELDS: dict[str, str] = {
98
+ "repo": "repo",
99
+ "repo root": "repo-root",
100
+ "repo_root": "repo-root",
101
+ "repo path": "repo-root",
102
+ "branch": "branch",
103
+ "branch slug": "branch",
104
+ "branch_slug": "branch",
105
+ "commit": "commit",
106
+ "artifact": "artifact",
107
+ "artifact path": "artifact",
108
+ }
109
+
110
+ # Matches "- Field: value", "Field: value", "field = value" (markdown or yaml).
111
+ _FIELD_RE = re.compile(
112
+ r"^(?P<prefix>\s*(?:[-*]\s+)?(?P<name>[A-Za-z][A-Za-z _]*?)\s*[:=]\s*)(?P<value>.*?)\s*$"
113
+ )
114
+
115
+ # Already-redacted token (e.g. "<repo:ab12cd34ef>") or an explicit empty marker.
116
+ _REDACTED_RE = re.compile(r"^(<[^>]*>|none|n/?a|-)?$", re.IGNORECASE)
117
+
118
+ # Absolute project/worktree roots: collapse the private prefix *and* the project
119
+ # segment to "<repo>", keeping any instructive repo-relative tail.
120
+ _PROJECT_PATH_RE = re.compile(
121
+ r"/(?:mnt/[^/\s]+/(?:code|worktrees)"
122
+ r"|home/[^/\s]+/(?:projects|code|src|repos|dev|work)"
123
+ r"|Users/[^/\s]+/(?:projects|code|src|repos|dev|work))"
124
+ r"/[^/\s:'\"]+"
125
+ )
126
+
127
+ # Remaining home prefixes: drop the username, keep the generic tail (e.g.
128
+ # "/home/jenner/.claude/skills" -> "~/.claude/skills").
129
+ _HOME_PATH_RE = re.compile(r"/home/[^/\s]+|/Users/[^/\s]+|/root(?=/|\b)")
130
+
131
+
132
+ def _hash_token(value: str, prefix: str) -> str:
133
+ digest = hashlib.sha256(value.strip().encode("utf-8")).hexdigest()[:10]
134
+ return f"<{prefix}:{digest}>"
135
+
136
+
137
+ def _redact_paths(text: str) -> str:
138
+ text = _PROJECT_PATH_RE.sub("<repo>", text)
139
+ text = _HOME_PATH_RE.sub("~", text)
140
+ return text
141
+
142
+
143
+ def redact_reflection_text(text: str, machine: str | None = None) -> str:
144
+ """Return a repo-agnostic copy of a reflection, safe to pool across machines.
145
+
146
+ Deterministic and idempotent. Two passes:
147
+
148
+ 1. **Structured fields** (Repo/Branch/Commit/Artifact/repo_root/...): replace
149
+ the value with a stable hash token, so the same repo/branch still
150
+ *correlates* across reflections without revealing its name. Values that
151
+ are already redacted, empty, or "none" are left untouched (idempotency).
152
+ 2. **Free-text paths**: collapse absolute project/worktree paths to
153
+ ``<repo>/...`` and strip usernames from home paths.
154
+
155
+ Known limitation: free-text references to a repo/branch *by bare name*
156
+ (not as a path) can't be detected and are not redacted — the v36 schema's
157
+ "repo-agnostic" intent still applies to authored content. The structured
158
+ fields above are the guaranteed-clean part.
159
+ """
160
+ lines = text.splitlines(keepends=False)
161
+ out: list[str] = []
162
+ already_marked = bool(lines) and lines[0].startswith("<!-- reflection-sync: redacted;")
163
+ if machine and not already_marked:
164
+ out.append(f"<!-- reflection-sync: redacted; machine={_slug(machine)} -->")
165
+
166
+ for line in lines:
167
+ m = _FIELD_RE.match(line)
168
+ if m:
169
+ name = m.group("name").strip().lower()
170
+ prefix = _SENSITIVE_FIELDS.get(name)
171
+ if prefix is not None:
172
+ value = m.group("value")
173
+ if _REDACTED_RE.match(value.strip()):
174
+ out.append(line)
175
+ else:
176
+ out.append(m.group("prefix") + _hash_token(value, prefix))
177
+ continue
178
+ out.append(_redact_paths(line))
179
+
180
+ result = "\n".join(out)
181
+ if text.endswith("\n"):
182
+ result += "\n"
183
+ return result
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # Export
188
+ # ---------------------------------------------------------------------------
189
+
190
+ def _candidate_roots(repo: Path, harness: str) -> list[Path]:
191
+ roots = [resolve_skill_bundle_root(harness)]
192
+ for rel in _REPO_LOCAL_ROOTS.get(harness, ()):
193
+ roots.append(repo / rel)
194
+ return roots
195
+
196
+
197
+ def iter_reflection_files(repo: Path, harnesses: tuple[str, ...] = DEFAULT_HARNESSES):
198
+ """Yield ``(skill_name, rel_path, source_path)`` for every live reflection.
199
+
200
+ ``rel_path`` is relative to the skill's ``reflections/`` dir (e.g.
201
+ ``<repo_hash>/<branch_slug>/<run_id>.md``, or a flat ``<file>.md`` for legacy
202
+ layouts). Archived reflections are skipped. Duplicate (skill, rel) pairs
203
+ seen across roots are de-duplicated.
204
+ """
205
+ repo = Path(repo).expanduser()
206
+ seen: set[tuple[str, str]] = set()
207
+ for harness in harnesses:
208
+ for root in _candidate_roots(repo, harness):
209
+ if not root.exists():
210
+ continue
211
+ for refl_dir in sorted(root.glob("*/reflections")):
212
+ skill = refl_dir.parent.name
213
+ for path in sorted(refl_dir.rglob("*.md")):
214
+ if "archive" in path.parts or not path.is_file():
215
+ continue
216
+ rel = path.relative_to(refl_dir)
217
+ key = (skill, rel.as_posix())
218
+ if key in seen:
219
+ continue
220
+ seen.add(key)
221
+ yield skill, rel, path
222
+
223
+
224
+ def export_reflections(
225
+ repo: Path,
226
+ dest_root: Path,
227
+ machine: str | None = None,
228
+ harnesses: tuple[str, ...] = DEFAULT_HARNESSES,
229
+ ) -> list[Path]:
230
+ """Redact and copy every live reflection into a machine-namespaced store.
231
+
232
+ Destination layout (the ``<machine>`` segment sits directly above the file,
233
+ so machines never collide even on an identical repo_hash/branch):
234
+
235
+ ``<dest_root>/<skill>/<rel.parent>/<machine>/<rel.name>``
236
+
237
+ Returns the list of written destination paths.
238
+ """
239
+ machine = machine or machine_id()
240
+ dest_root = Path(dest_root).expanduser()
241
+ written: list[Path] = []
242
+ for skill, rel, src in iter_reflection_files(repo, harnesses):
243
+ try:
244
+ text = src.read_text(encoding="utf-8", errors="replace")
245
+ except OSError:
246
+ continue
247
+ # Skip reflections that were imported from the pool (already redacted on
248
+ # another machine) so an aggregator never re-pools them under its own id.
249
+ if text.lstrip().startswith("<!-- reflection-sync: redacted;"):
250
+ continue
251
+ redacted = redact_reflection_text(text, machine=machine)
252
+ dest = dest_root / skill / rel.parent / machine / rel.name
253
+ dest.parent.mkdir(parents=True, exist_ok=True)
254
+ dest.write_text(redacted, encoding="utf-8")
255
+ written.append(dest)
256
+ return written
257
+
258
+
259
+ # ---------------------------------------------------------------------------
260
+ # Minimal CLI seam (the full `phase-loop reflections sync` subcommand is Slice 2)
261
+ # ---------------------------------------------------------------------------
262
+
263
+ def main(argv: list[str] | None = None) -> int:
264
+ parser = argparse.ArgumentParser(prog="phase_loop_runtime.reflection_sync")
265
+ sub = parser.add_subparsers(dest="command", required=True)
266
+
267
+ exp = sub.add_parser("export", help="redact + copy local reflections to a store dir")
268
+ exp.add_argument("--repo", required=True, help="repo root to scan")
269
+ exp.add_argument("--dest", required=True, help="destination store root")
270
+ exp.add_argument("--machine", default=None, help="machine id override")
271
+
272
+ mid = sub.add_parser("machine-id", help="print this machine's stable id")
273
+ mid.set_defaults(_is_machine_id=True)
274
+
275
+ args = parser.parse_args(argv)
276
+
277
+ if args.command == "machine-id":
278
+ print(machine_id())
279
+ return 0
280
+
281
+ if args.command == "export":
282
+ written = export_reflections(
283
+ Path(args.repo), Path(args.dest), machine=args.machine
284
+ )
285
+ print(f"exported {len(written)} reflection(s) to {args.dest}")
286
+ return 0
287
+
288
+ parser.error("unknown command")
289
+ return 2
290
+
291
+
292
+ if __name__ == "__main__": # pragma: no cover
293
+ sys.exit(main())
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ import fnmatch
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from .discovery import plan_metadata
10
+
11
+
12
+ RELEASE_DISPATCH_MUTATION = "release_dispatch"
13
+ RELEASE_AFFECTING_PATTERNS = (
14
+ ".github/workflows/**",
15
+ "CHANGELOG*",
16
+ "RELEASE*",
17
+ "VERSION",
18
+ "pyproject.toml",
19
+ "setup.cfg",
20
+ "setup.py",
21
+ "uv.lock",
22
+ "poetry.lock",
23
+ "requirements*.txt",
24
+ "docs/release/**",
25
+ "docs/releases/**",
26
+ "docs/release*.md",
27
+ "scripts/*release*",
28
+ "scripts/download-release.py",
29
+ )
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class ReleaseDispatchBlocker:
34
+ blocker_class: str
35
+ blocker_summary: str
36
+ required_human_inputs: tuple[str, ...]
37
+ metadata: dict[str, Any]
38
+
39
+ def to_blocker(self) -> dict[str, Any]:
40
+ return {
41
+ "human_required": True,
42
+ "blocker_class": self.blocker_class,
43
+ "blocker_summary": self.blocker_summary,
44
+ "required_human_inputs": self.required_human_inputs,
45
+ }
46
+
47
+
48
+ def is_release_dispatch_plan(plan: Path | None) -> bool:
49
+ if plan is None:
50
+ return False
51
+ metadata = plan_metadata(plan)
52
+ if metadata.get("phase_loop_mutation") == RELEASE_DISPATCH_MUTATION:
53
+ return True
54
+ try:
55
+ text = plan.read_text(encoding="utf-8").lower()
56
+ except OSError:
57
+ return False
58
+ return "gh workflow run" in text and "release" in text
59
+
60
+
61
+ def release_dispatch_blocker(repo: Path, plan: Path | None) -> ReleaseDispatchBlocker | None:
62
+ if plan is None or not is_release_dispatch_plan(plan):
63
+ return None
64
+
65
+ metadata = plan_metadata(plan)
66
+ dirty_paths = _dirty_release_affecting_paths(repo)
67
+ if dirty_paths:
68
+ return ReleaseDispatchBlocker(
69
+ blocker_class="dirty_worktree_conflict",
70
+ blocker_summary=(
71
+ "Release dispatch requires clean release-affecting files before the "
72
+ "external workflow is started."
73
+ ),
74
+ required_human_inputs=(
75
+ "Commit, merge, or isolate the release-affecting changes.",
76
+ "Sync the release branch with the configured base ref, then rerun the dispatch phase.",
77
+ ),
78
+ metadata={
79
+ "guard": "release_dispatch",
80
+ "reason": "dirty_release_affecting_paths",
81
+ "plan": str(plan),
82
+ "dirty_paths": dirty_paths,
83
+ "phase_loop_mutation": metadata.get("phase_loop_mutation"),
84
+ },
85
+ )
86
+
87
+ base_ref, base_ref_explicit = _release_base_ref(metadata)
88
+ sync = _branch_sync(repo, base_ref)
89
+ if not sync["base_available"]:
90
+ if not base_ref_explicit and not sync["base_remote_available"]:
91
+ return None
92
+ return ReleaseDispatchBlocker(
93
+ blocker_class="branch_sync_conflict",
94
+ blocker_summary=f"Release dispatch base ref `{base_ref}` is unavailable locally.",
95
+ required_human_inputs=(
96
+ f"Fetch or configure `{base_ref}` before rerunning release dispatch.",
97
+ "Rerun the dispatch phase from a branch that can be compared to the release base.",
98
+ ),
99
+ metadata={
100
+ "guard": "release_dispatch",
101
+ "reason": "base_ref_unavailable",
102
+ "plan": str(plan),
103
+ **sync,
104
+ },
105
+ )
106
+ if sync["head"] != sync["base_commit"]:
107
+ return ReleaseDispatchBlocker(
108
+ blocker_class="branch_sync_conflict",
109
+ blocker_summary=(
110
+ f"Release dispatch requires `HEAD` to match `{base_ref}` before the "
111
+ "external workflow is started."
112
+ ),
113
+ required_human_inputs=(
114
+ f"Merge or sync the release branch so `HEAD` matches `{base_ref}`.",
115
+ "Rerun the dispatch phase from the clean synced branch.",
116
+ ),
117
+ metadata={
118
+ "guard": "release_dispatch",
119
+ "reason": "head_not_at_base_ref",
120
+ "plan": str(plan),
121
+ **sync,
122
+ },
123
+ )
124
+ return None
125
+
126
+
127
+ def _release_base_ref(metadata: dict[str, str]) -> tuple[str, bool]:
128
+ for key in ("release_base_ref", "phase_loop_release_base_ref"):
129
+ value = metadata.get(key)
130
+ if value:
131
+ return value, True
132
+ return "origin/main", False
133
+
134
+
135
+ def _dirty_release_affecting_paths(repo: Path) -> list[str]:
136
+ try:
137
+ status = subprocess.check_output(
138
+ ["git", "-C", str(repo), "status", "--porcelain", "--untracked-files=all"],
139
+ text=True,
140
+ )
141
+ except Exception:
142
+ return []
143
+ paths: list[str] = []
144
+ for line in status.splitlines():
145
+ if not line:
146
+ continue
147
+ path = _status_path(line)
148
+ if path and _is_release_affecting_path(path):
149
+ paths.append(path)
150
+ return sorted(dict.fromkeys(paths))
151
+
152
+
153
+ def _status_path(line: str) -> str:
154
+ path = line[3:] if len(line) > 3 else ""
155
+ if " -> " in path:
156
+ path = path.split(" -> ", 1)[1]
157
+ return path.strip().strip('"')
158
+
159
+
160
+ def _is_release_affecting_path(path: str) -> bool:
161
+ normalized = path.replace("\\", "/")
162
+ return any(fnmatch.fnmatchcase(normalized, pattern) for pattern in RELEASE_AFFECTING_PATTERNS)
163
+
164
+
165
+ def _branch_sync(repo: Path, base_ref: str) -> dict[str, Any]:
166
+ head = _git(repo, "rev-parse", "HEAD")
167
+ base_commit = _git(repo, "rev-parse", "--verify", f"{base_ref}^{{commit}}")
168
+ remote = base_ref.split("/", 1)[0] if "/" in base_ref else ""
169
+ return {
170
+ "base_ref": base_ref,
171
+ "base_available": bool(base_commit),
172
+ "base_remote_available": bool(remote and _git(repo, "remote", "get-url", remote)),
173
+ "head": _short_sha(head),
174
+ "base_commit": _short_sha(base_commit),
175
+ }
176
+
177
+
178
+ def _git(repo: Path, *args: str) -> str:
179
+ try:
180
+ return subprocess.check_output(["git", "-C", str(repo), *args], text=True, stderr=subprocess.DEVNULL).strip()
181
+ except Exception:
182
+ return ""
183
+
184
+
185
+ def _short_sha(value: str) -> str:
186
+ return value[:12] if value else ""