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,172 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ try:
9
+ import yaml
10
+ except Exception: # pragma: no cover - only used in stripped installs
11
+ yaml = None
12
+
13
+
14
+ DEFAULT_TIER3_CONFIDENCE_THRESHOLD = 0.85
15
+
16
+
17
+ class EvidenceAuditConfigError(ValueError):
18
+ pass
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class EvidenceAuditPhaseConfig:
23
+ tier2_enabled: bool = True
24
+ tier3_enabled: bool = False
25
+ tier3_confidence_threshold: float = DEFAULT_TIER3_CONFIDENCE_THRESHOLD
26
+ disable_detectors: tuple[str, ...] = ()
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class EvidenceAuditConfig:
31
+ tier2_enabled: bool = True
32
+ tier3_enabled: bool = False
33
+ tier3_confidence_threshold: float = DEFAULT_TIER3_CONFIDENCE_THRESHOLD
34
+ phase_aliases_exclude_tier3: tuple[str, ...] = ()
35
+ phases: dict[str, EvidenceAuditPhaseConfig] = field(default_factory=dict)
36
+
37
+ def phase_config(self, phase_alias: str) -> EvidenceAuditPhaseConfig:
38
+ alias = phase_alias.strip().upper()
39
+ override = self.phases.get(alias)
40
+ if override is not None:
41
+ return override
42
+ return EvidenceAuditPhaseConfig(
43
+ tier2_enabled=self.tier2_enabled,
44
+ tier3_enabled=self.tier3_enabled,
45
+ tier3_confidence_threshold=self.tier3_confidence_threshold,
46
+ )
47
+
48
+ def tier3_excluded(self, phase_alias: str) -> bool:
49
+ return phase_alias.strip().upper() in set(self.phase_aliases_exclude_tier3)
50
+
51
+
52
+ def default_config_path(repo: Path) -> Path:
53
+ return repo / ".phase-loop" / "evidence-audit.yaml"
54
+
55
+
56
+ def load_evidence_audit_config(repo: Path, path: Path | None = None) -> EvidenceAuditConfig:
57
+ config_path = path or default_config_path(repo)
58
+ if not config_path.exists():
59
+ return EvidenceAuditConfig()
60
+ try:
61
+ text = config_path.read_text(encoding="utf-8")
62
+ except OSError as exc:
63
+ raise EvidenceAuditConfigError(f"cannot read evidence audit config: {exc}") from exc
64
+ if not text.strip():
65
+ return EvidenceAuditConfig()
66
+ if yaml is None:
67
+ raise EvidenceAuditConfigError(
68
+ "PyYAML is required to read evidence-audit.yaml; run through scripts/phase-loop-python "
69
+ "or install phase-loop-runtime dependencies"
70
+ )
71
+ try:
72
+ raw = yaml.safe_load(text)
73
+ except Exception as exc:
74
+ raise EvidenceAuditConfigError(f"malformed evidence audit config: {exc}") from exc
75
+ if raw is None:
76
+ return EvidenceAuditConfig()
77
+ if not isinstance(raw, dict):
78
+ raise EvidenceAuditConfigError("evidence audit config must be a mapping")
79
+ allowed = {
80
+ "tier2_enabled",
81
+ "tier3_enabled",
82
+ "tier3_confidence_threshold",
83
+ "phase_aliases_exclude_tier3",
84
+ "phases",
85
+ }
86
+ unknown = sorted(set(raw) - allowed)
87
+ if unknown:
88
+ raise EvidenceAuditConfigError(f"unknown evidence audit config fields: {', '.join(unknown)}")
89
+ return EvidenceAuditConfig(
90
+ tier2_enabled=_bool(raw.get("tier2_enabled", True), "tier2_enabled"),
91
+ tier3_enabled=_bool(raw.get("tier3_enabled", False), "tier3_enabled"),
92
+ tier3_confidence_threshold=_threshold(
93
+ raw.get("tier3_confidence_threshold", DEFAULT_TIER3_CONFIDENCE_THRESHOLD),
94
+ "tier3_confidence_threshold",
95
+ ),
96
+ phase_aliases_exclude_tier3=_aliases(raw.get("phase_aliases_exclude_tier3", ())),
97
+ phases=_phases(raw.get("phases", {}), raw),
98
+ )
99
+
100
+
101
+ def _bool(value: Any, field_name: str) -> bool:
102
+ if not isinstance(value, bool):
103
+ raise EvidenceAuditConfigError(f"{field_name} must be a bool")
104
+ return value
105
+
106
+
107
+ def _threshold(value: Any, field_name: str) -> float:
108
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
109
+ raise EvidenceAuditConfigError(f"{field_name} must be a number")
110
+ value = float(value)
111
+ if value < 0.0 or value > 1.0:
112
+ raise EvidenceAuditConfigError(f"{field_name} must be between 0.0 and 1.0")
113
+ return value
114
+
115
+
116
+ def _aliases(value: Any) -> tuple[str, ...]:
117
+ if value is None:
118
+ return ()
119
+ if not isinstance(value, (list, tuple)):
120
+ raise EvidenceAuditConfigError("phase_aliases_exclude_tier3 must be a list")
121
+ aliases: list[str] = []
122
+ for item in value:
123
+ if not isinstance(item, str) or not item.strip():
124
+ raise EvidenceAuditConfigError("phase_aliases_exclude_tier3 entries must be non-empty strings")
125
+ aliases.append(item.strip().upper())
126
+ return tuple(aliases)
127
+
128
+
129
+ def _detectors(value: Any, field_name: str = "disable_detectors") -> tuple[str, ...]:
130
+ if value is None:
131
+ return ()
132
+ if not isinstance(value, (list, tuple)):
133
+ raise EvidenceAuditConfigError(f"{field_name} must be a list")
134
+ detectors: list[str] = []
135
+ for item in value:
136
+ if not isinstance(item, str) or not item.strip():
137
+ raise EvidenceAuditConfigError(f"{field_name} entries must be non-empty strings")
138
+ detectors.append(item.strip())
139
+ return tuple(detectors)
140
+
141
+
142
+ def _phases(value: Any, root: dict[str, Any]) -> dict[str, EvidenceAuditPhaseConfig]:
143
+ if value is None:
144
+ return {}
145
+ if not isinstance(value, dict):
146
+ raise EvidenceAuditConfigError("phases must be a mapping")
147
+ phases: dict[str, EvidenceAuditPhaseConfig] = {}
148
+ default_tier2 = _bool(root.get("tier2_enabled", True), "tier2_enabled")
149
+ default_tier3 = _bool(root.get("tier3_enabled", False), "tier3_enabled")
150
+ default_threshold = _threshold(
151
+ root.get("tier3_confidence_threshold", DEFAULT_TIER3_CONFIDENCE_THRESHOLD),
152
+ "tier3_confidence_threshold",
153
+ )
154
+ allowed = {"tier2_enabled", "tier3_enabled", "tier3_confidence_threshold", "disable_detectors"}
155
+ for alias, raw_phase in value.items():
156
+ if not isinstance(alias, str) or not alias.strip():
157
+ raise EvidenceAuditConfigError("phase aliases must be non-empty strings")
158
+ if not isinstance(raw_phase, dict):
159
+ raise EvidenceAuditConfigError(f"phase {alias} config must be a mapping")
160
+ unknown = sorted(set(raw_phase) - allowed)
161
+ if unknown:
162
+ raise EvidenceAuditConfigError(f"unknown fields for phase {alias}: {', '.join(unknown)}")
163
+ phases[alias.strip().upper()] = EvidenceAuditPhaseConfig(
164
+ tier2_enabled=_bool(raw_phase.get("tier2_enabled", default_tier2), f"phases.{alias}.tier2_enabled"),
165
+ tier3_enabled=_bool(raw_phase.get("tier3_enabled", default_tier3), f"phases.{alias}.tier3_enabled"),
166
+ tier3_confidence_threshold=_threshold(
167
+ raw_phase.get("tier3_confidence_threshold", default_threshold),
168
+ f"phases.{alias}.tier3_confidence_threshold",
169
+ ),
170
+ disable_detectors=_detectors(raw_phase.get("disable_detectors", ()), f"phases.{alias}.disable_detectors"),
171
+ )
172
+ return phases
@@ -0,0 +1,426 @@
1
+ """CS-0.7 realized-edge / fleet-map v0 extractor.
2
+
3
+ Extracts the **realized** cross-repo interface graph from a set of repo paths —
4
+ deliberately NOT a package-lockfile dependency scan. The Consiliency
5
+ standardization roadmap's verified premise is that between the core repos
6
+ there are ~zero package-level deps; the real cross-repo edges are git+ref
7
+ pins, copied-literal (vendored) contract/schema drift, and hard-coded
8
+ host-path references in source. A lockfile-only scan renders those edges
9
+ invisible (see `run_lockfile_baseline_scan`, and
10
+ `test_lockfile_baseline_scan_is_empty_over_the_same_fixture_tree` in
11
+ tests/test_fleet_map.py, which proves the point over a fixture tree that
12
+ also carries ordinary, unrelated third-party manifest deps).
13
+
14
+ Three edge kinds (`EDGE_KINDS`):
15
+ - pin: a `*/tools/agent-harness.pin.json` / `*greenfield-contract.lock.json`
16
+ file, or a `git+<scheme>://...#subdirectory=...`-style ref inside a
17
+ dotfiles/bootstrap.sh-style script.
18
+ - copied-literal: a same-named contract/schema file vendored into two or
19
+ more repos whose contents hash-diverge (drift).
20
+ - host-path: a hard-coded sibling path like `/home/<user>/code/<repo>/...`
21
+ found in source.
22
+
23
+ Every edge carries a `maturity_label` (`MATURITY_LABELS`) grading how strong
24
+ the evidence is: `presence-only` (the artifact exists but the target repo
25
+ could not be confirmed among the scanned repos), `hash-checked` (a content
26
+ hash comparison produced the finding), or `realized-edge-observed` (the
27
+ target repo is one of the scanned repos, so the edge is concretely
28
+ resolved).
29
+
30
+ This module is v0: no network calls, no git remote resolution — everything is
31
+ static file inspection over the repo paths the caller supplies.
32
+ """
33
+ from __future__ import annotations
34
+
35
+ from dataclasses import dataclass, field
36
+ import hashlib
37
+ import itertools
38
+ import json
39
+ from pathlib import Path
40
+ import re
41
+ from typing import Any, Iterable
42
+
43
+
44
+ EDGE_KINDS = ("pin", "copied-literal", "host-path", "lockfile-dep")
45
+ MATURITY_LABELS = ("presence-only", "hash-checked", "realized-edge-observed")
46
+
47
+ # Directories skipped everywhere a repo tree is walked — VCS internals and
48
+ # dependency/build caches carry no realized fleet-map evidence and are large.
49
+ _SKIP_DIR_NAMES = {".git", "node_modules", "__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", "dist", "build"}
50
+
51
+ _SOURCE_EXTENSIONS = {".py", ".js", ".mjs", ".ts", ".tsx", ".jsx", ".sh", ".mts", ".cts"}
52
+ _CONTRACT_EXTENSIONS = {".json", ".yaml", ".yml"}
53
+
54
+ _HOST_PATH_RE = re.compile(r"/home/[\w.\-]+/code/([\w.\-]+)")
55
+ _GIT_PIN_RE = re.compile(r"git\+[a-zA-Z][a-zA-Z0-9+.\-]*://[^\s\"'#]+(?:#subdirectory=[^\s\"']+)?")
56
+
57
+ _LOCKFILE_MANIFEST_NAMES = {
58
+ "package.json",
59
+ "package-lock.json",
60
+ "requirements.txt",
61
+ "Pipfile",
62
+ "Pipfile.lock",
63
+ "pyproject.toml",
64
+ "Cargo.toml",
65
+ "Cargo.lock",
66
+ "go.mod",
67
+ "go.sum",
68
+ }
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class FleetEdge:
73
+ from_repo: str
74
+ to_repo: str
75
+ kind: str
76
+ evidence: str
77
+ maturity_label: str
78
+
79
+ def key(self) -> tuple[str, str, str, str]:
80
+ return (self.from_repo, self.to_repo, self.kind, self.evidence)
81
+
82
+ def to_json(self) -> dict[str, Any]:
83
+ return {
84
+ "from_repo": self.from_repo,
85
+ "to_repo": self.to_repo,
86
+ "kind": self.kind,
87
+ "evidence": self.evidence,
88
+ "maturity_label": self.maturity_label,
89
+ }
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class SetupDiagnostic:
94
+ repo: str
95
+ message: str
96
+
97
+ def to_json(self) -> dict[str, str]:
98
+ return {"repo": self.repo, "message": self.message}
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class FleetMapResult:
103
+ repos: tuple[str, ...]
104
+ edges: tuple[FleetEdge, ...] = field(default_factory=tuple)
105
+ lockfile_baseline_edges: tuple[FleetEdge, ...] = field(default_factory=tuple)
106
+ setup_diagnostics: tuple[SetupDiagnostic, ...] = field(default_factory=tuple)
107
+
108
+ def has_setup_errors(self) -> bool:
109
+ return bool(self.setup_diagnostics)
110
+
111
+ def to_json(self) -> dict[str, Any]:
112
+ return {
113
+ "repos": list(self.repos),
114
+ "counts": {
115
+ "repos": len(self.repos),
116
+ "edges": len(self.edges),
117
+ "lockfile_baseline_edges": len(self.lockfile_baseline_edges),
118
+ "setup_errors": len(self.setup_diagnostics),
119
+ },
120
+ "edges": [edge.to_json() for edge in self.edges],
121
+ "lockfile_baseline_edges": [edge.to_json() for edge in self.lockfile_baseline_edges],
122
+ "setup_diagnostics": [item.to_json() for item in self.setup_diagnostics],
123
+ }
124
+
125
+ def render_text(self) -> str:
126
+ lines = ["Fleet Map (realized cross-repo interface graph)", f"repos: {', '.join(self.repos)}", ""]
127
+ if self.setup_diagnostics:
128
+ lines.append("Setup diagnostics:")
129
+ for diagnostic in self.setup_diagnostics:
130
+ lines.append(f" {diagnostic.repo}: {diagnostic.message}")
131
+ lines.append("")
132
+ if not self.edges:
133
+ lines.append("Edges: none")
134
+ else:
135
+ lines.append("Edges:")
136
+ for edge in self.edges:
137
+ lines.append(f" [{edge.kind}] {edge.from_repo} -> {edge.to_repo} ({edge.maturity_label}) {edge.evidence}")
138
+ lines.append("")
139
+ lines.append(f"Lockfile-only baseline: {len(self.lockfile_baseline_edges)} edge(s)")
140
+ return "\n".join(lines)
141
+
142
+
143
+ def build_fleet_map(repos: Iterable[str | Path]) -> FleetMapResult:
144
+ """Extract the realized-edge fleet map over `repos` (see module docstring)."""
145
+ resolved, repo_names, setup_diagnostics = _resolve_repos(repos)
146
+
147
+ edges: list[FleetEdge] = []
148
+ for repo_name, repo_path in resolved.items():
149
+ edges.extend(_scan_pin_edges(repo_path, repo_name, repo_names))
150
+ edges.extend(_scan_host_path_edges(repo_path, repo_name, repo_names))
151
+ edges.extend(_scan_copied_literal_edges(resolved))
152
+
153
+ baseline = _run_lockfile_baseline(resolved, repo_names)
154
+
155
+ return FleetMapResult(
156
+ repos=tuple(sorted(repo_names)),
157
+ edges=_dedupe_and_sort(edges),
158
+ lockfile_baseline_edges=_dedupe_and_sort(baseline),
159
+ setup_diagnostics=tuple(sorted(setup_diagnostics, key=lambda item: (item.repo, item.message))),
160
+ )
161
+
162
+
163
+ def run_lockfile_baseline_scan(repos: Iterable[str | Path]) -> tuple[FleetEdge, ...]:
164
+ """Package-lockfile-only baseline: scans manifests for cross-repo git deps.
165
+
166
+ This is the counterfactual the CS-0.7 premise argues against: run this
167
+ over the same fixture tree `build_fleet_map` finds three edges in, and it
168
+ comes back empty, even though the fixture repos carry ordinary
169
+ (non-cross-repo) third-party manifest deps — the emptiness is not because
170
+ nothing was scanned, but because package-level deps aren't how these
171
+ repos are actually wired together.
172
+ """
173
+ resolved, repo_names, _ = _resolve_repos(repos)
174
+ return _dedupe_and_sort(_run_lockfile_baseline(resolved, repo_names))
175
+
176
+
177
+ # --------------------------------------------------------------------------
178
+ # repo resolution
179
+ # --------------------------------------------------------------------------
180
+
181
+
182
+ def _resolve_repos(repos: Iterable[str | Path]) -> tuple[dict[str, Path], set[str], list[SetupDiagnostic]]:
183
+ resolved: dict[str, Path] = {}
184
+ diagnostics: list[SetupDiagnostic] = []
185
+ for repo_input in repos:
186
+ repo = Path(repo_input).expanduser().resolve()
187
+ label = str(repo)
188
+ if not repo.exists() or not repo.is_dir():
189
+ diagnostics.append(SetupDiagnostic(repo=label, message="repo path is missing or not a directory"))
190
+ continue
191
+ resolved[repo.name] = repo
192
+ return resolved, set(resolved), diagnostics
193
+
194
+
195
+ def _iter_files(repo: Path) -> Iterable[Path]:
196
+ for path in sorted(repo.rglob("*")):
197
+ if not path.is_file():
198
+ continue
199
+ if _SKIP_DIR_NAMES & set(path.relative_to(repo).parts[:-1]):
200
+ continue
201
+ yield path
202
+
203
+
204
+ def _relative(path: Path, repo: Path) -> str:
205
+ try:
206
+ return str(path.relative_to(repo))
207
+ except ValueError:
208
+ return str(path)
209
+
210
+
211
+ # --------------------------------------------------------------------------
212
+ # pin edges
213
+ # --------------------------------------------------------------------------
214
+
215
+
216
+ def _is_pin_file(path: Path) -> bool:
217
+ if path.name == "agent-harness.pin.json" and path.parent.name == "tools":
218
+ return True
219
+ return path.name.endswith("greenfield-contract.lock.json")
220
+
221
+
222
+ def _scan_pin_edges(repo: Path, repo_name: str, known_repos: set[str]) -> list[FleetEdge]:
223
+ edges: list[FleetEdge] = []
224
+ for path in _iter_files(repo):
225
+ if path.suffix == ".json" and _is_pin_file(path):
226
+ edges.extend(_scan_pin_json(path, repo, repo_name, known_repos))
227
+ elif path.suffix in {".sh", ".bash"}:
228
+ edges.extend(_scan_pin_script(path, repo, repo_name, known_repos))
229
+ return edges
230
+
231
+
232
+ def _scan_pin_json(path: Path, repo: Path, repo_name: str, known_repos: set[str]) -> list[FleetEdge]:
233
+ try:
234
+ payload = json.loads(path.read_text(encoding="utf-8"))
235
+ except (OSError, json.JSONDecodeError):
236
+ return []
237
+ if not isinstance(payload, dict):
238
+ return []
239
+ raw_repo = payload.get("repo") or payload.get("url") or payload.get("source")
240
+ if not isinstance(raw_repo, str) or not raw_repo:
241
+ return []
242
+ to_repo = _last_path_segment(raw_repo)
243
+ if not to_repo or to_repo == repo_name:
244
+ return []
245
+ return [
246
+ FleetEdge(
247
+ from_repo=repo_name,
248
+ to_repo=to_repo,
249
+ kind="pin",
250
+ evidence=f"{_relative(path, repo)}:1",
251
+ maturity_label="realized-edge-observed" if to_repo in known_repos else "presence-only",
252
+ )
253
+ ]
254
+
255
+
256
+ def _scan_pin_script(path: Path, repo: Path, repo_name: str, known_repos: set[str]) -> list[FleetEdge]:
257
+ edges: list[FleetEdge] = []
258
+ try:
259
+ lines = path.read_text(encoding="utf-8").splitlines()
260
+ except OSError:
261
+ return []
262
+ for line_number, line in enumerate(lines, start=1):
263
+ for match in _GIT_PIN_RE.finditer(line):
264
+ to_repo, _ref, _subdirectory = _parse_git_pin_url(match.group(0))
265
+ if not to_repo or to_repo == repo_name:
266
+ continue
267
+ edges.append(
268
+ FleetEdge(
269
+ from_repo=repo_name,
270
+ to_repo=to_repo,
271
+ kind="pin",
272
+ evidence=f"{_relative(path, repo)}:{line_number}",
273
+ maturity_label="realized-edge-observed" if to_repo in known_repos else "presence-only",
274
+ )
275
+ )
276
+ return edges
277
+
278
+
279
+ def _parse_git_pin_url(raw: str) -> tuple[str | None, str | None, str | None]:
280
+ subdirectory: str | None = None
281
+ remainder = raw
282
+ if "#subdirectory=" in remainder:
283
+ remainder, _, subdirectory = remainder.partition("#subdirectory=")
284
+ ref: str | None = None
285
+ scheme_sep = remainder.find("://")
286
+ url_part = remainder
287
+ if scheme_sep != -1 and "@" in remainder[scheme_sep + 3 :]:
288
+ url_part, _, ref = remainder.rpartition("@")
289
+ path = url_part.split("://", 1)[-1]
290
+ segments = [segment for segment in path.split("/") if segment]
291
+ if not segments:
292
+ return None, ref, subdirectory
293
+ repo_name = segments[-1]
294
+ if repo_name.endswith(".git"):
295
+ repo_name = repo_name[: -len(".git")]
296
+ return repo_name or None, ref, subdirectory
297
+
298
+
299
+ def _last_path_segment(value: str) -> str | None:
300
+ trimmed = value.strip().rstrip("/")
301
+ if trimmed.endswith(".git"):
302
+ trimmed = trimmed[: -len(".git")]
303
+ segments = [segment for segment in re.split(r"[/:]", trimmed) if segment]
304
+ return segments[-1] if segments else None
305
+
306
+
307
+ # --------------------------------------------------------------------------
308
+ # host-path edges
309
+ # --------------------------------------------------------------------------
310
+
311
+
312
+ def _scan_host_path_edges(repo: Path, repo_name: str, known_repos: set[str]) -> list[FleetEdge]:
313
+ edges: list[FleetEdge] = []
314
+ for path in _iter_files(repo):
315
+ if path.suffix not in _SOURCE_EXTENSIONS:
316
+ continue
317
+ try:
318
+ lines = path.read_text(encoding="utf-8").splitlines()
319
+ except (OSError, UnicodeDecodeError):
320
+ continue
321
+ for line_number, line in enumerate(lines, start=1):
322
+ for match in _HOST_PATH_RE.finditer(line):
323
+ to_repo = match.group(1)
324
+ if to_repo == repo_name:
325
+ continue
326
+ edges.append(
327
+ FleetEdge(
328
+ from_repo=repo_name,
329
+ to_repo=to_repo,
330
+ kind="host-path",
331
+ evidence=f"{_relative(path, repo)}:{line_number}",
332
+ maturity_label="realized-edge-observed" if to_repo in known_repos else "presence-only",
333
+ )
334
+ )
335
+ return edges
336
+
337
+
338
+ # --------------------------------------------------------------------------
339
+ # copied-literal (vendored contract/schema drift) edges
340
+ # --------------------------------------------------------------------------
341
+
342
+
343
+ def _is_contract_literal_candidate(path: Path) -> bool:
344
+ if path.suffix not in _CONTRACT_EXTENSIONS:
345
+ return False
346
+ if _is_pin_file(path):
347
+ return False
348
+ return "contracts" in path.parts or "schemas" in path.parts or "contract" in path.stem or "schema" in path.stem
349
+
350
+
351
+ def _scan_copied_literal_edges(resolved: dict[str, Path]) -> list[FleetEdge]:
352
+ by_basename: dict[str, list[tuple[str, Path]]] = {}
353
+ for repo_name, repo_path in resolved.items():
354
+ for path in _iter_files(repo_path):
355
+ if _is_contract_literal_candidate(path):
356
+ by_basename.setdefault(path.name, []).append((repo_name, path))
357
+
358
+ edges: list[FleetEdge] = []
359
+ for basename, occurrences in by_basename.items():
360
+ if len(occurrences) < 2:
361
+ continue
362
+ for (repo_a, path_a), (repo_b, path_b) in itertools.combinations(occurrences, 2):
363
+ if repo_a == repo_b:
364
+ continue
365
+ hash_a = _hash_file(path_a)
366
+ hash_b = _hash_file(path_b)
367
+ if hash_a is None or hash_b is None or hash_a == hash_b:
368
+ continue
369
+ from_repo, from_path = (repo_a, path_a) if repo_a < repo_b else (repo_b, path_b)
370
+ to_repo = repo_b if from_repo == repo_a else repo_a
371
+ edges.append(
372
+ FleetEdge(
373
+ from_repo=from_repo,
374
+ to_repo=to_repo,
375
+ kind="copied-literal",
376
+ evidence=f"{_relative(from_path, resolved[from_repo])}:1",
377
+ maturity_label="hash-checked",
378
+ )
379
+ )
380
+ return edges
381
+
382
+
383
+ def _hash_file(path: Path) -> str | None:
384
+ try:
385
+ return hashlib.sha256(path.read_bytes()).hexdigest()
386
+ except OSError:
387
+ return None
388
+
389
+
390
+ # --------------------------------------------------------------------------
391
+ # lockfile-only baseline (the counterfactual)
392
+ # --------------------------------------------------------------------------
393
+
394
+
395
+ def _run_lockfile_baseline(resolved: dict[str, Path], known_repos: set[str]) -> list[FleetEdge]:
396
+ edges: list[FleetEdge] = []
397
+ for repo_name, repo_path in resolved.items():
398
+ for path in _iter_files(repo_path):
399
+ if path.name not in _LOCKFILE_MANIFEST_NAMES:
400
+ continue
401
+ try:
402
+ text = path.read_text(encoding="utf-8")
403
+ except (OSError, UnicodeDecodeError):
404
+ continue
405
+ for line_number, line in enumerate(text.splitlines(), start=1):
406
+ for match in _GIT_PIN_RE.finditer(line):
407
+ to_repo, _ref, _subdirectory = _parse_git_pin_url(match.group(0))
408
+ if not to_repo or to_repo == repo_name or to_repo not in known_repos:
409
+ continue
410
+ edges.append(
411
+ FleetEdge(
412
+ from_repo=repo_name,
413
+ to_repo=to_repo,
414
+ kind="lockfile-dep",
415
+ evidence=f"{_relative(path, repo_path)}:{line_number}",
416
+ maturity_label="realized-edge-observed",
417
+ )
418
+ )
419
+ return edges
420
+
421
+
422
+ def _dedupe_and_sort(edges: Iterable[FleetEdge]) -> tuple[FleetEdge, ...]:
423
+ seen: dict[tuple[str, str, str, str], FleetEdge] = {}
424
+ for edge in edges:
425
+ seen[edge.key()] = edge
426
+ return tuple(sorted(seen.values(), key=lambda edge: edge.key()))