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.
- phase_loop_runtime/__init__.py +20 -0
- phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
- phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
- phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
- phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
- phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
- phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
- phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
- phase_loop_runtime/adoption_bundle.py +273 -0
- phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
- phase_loop_runtime/advisor_board/__init__.py +288 -0
- phase_loop_runtime/advisor_board/backing.py +137 -0
- phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
- phase_loop_runtime/advisor_board/config.py +225 -0
- phase_loop_runtime/advisor_board/events.py +115 -0
- phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
- phase_loop_runtime/advisor_board/fixtures.py +91 -0
- phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
- phase_loop_runtime/advisor_board/matrix.py +146 -0
- phase_loop_runtime/advisor_board/observability.py +481 -0
- phase_loop_runtime/advisor_board/presets.py +188 -0
- phase_loop_runtime/advisor_board/registries.py +309 -0
- phase_loop_runtime/advisor_board/resolver.py +253 -0
- phase_loop_runtime/advisor_board/schema.py +269 -0
- phase_loop_runtime/advisor_board/standin.py +166 -0
- phase_loop_runtime/advisor_board/validation.py +159 -0
- phase_loop_runtime/agent_runtime_provider.py +676 -0
- phase_loop_runtime/baml_modular.py +656 -0
- phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
- phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
- phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
- phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
- phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
- phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
- phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
- phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
- phase_loop_runtime/broker.py +181 -0
- phase_loop_runtime/build_bundle.py +294 -0
- phase_loop_runtime/capability_registry.py +835 -0
- phase_loop_runtime/classifier.py +87 -0
- phase_loop_runtime/claude_agent_view.py +625 -0
- phase_loop_runtime/claude_channel_sidecar.py +1086 -0
- phase_loop_runtime/cli.py +2599 -0
- phase_loop_runtime/closeout.py +865 -0
- phase_loop_runtime/closeout_classifier.py +120 -0
- phase_loop_runtime/closeout_evidence_audit.py +112 -0
- phase_loop_runtime/closeout_validation.py +144 -0
- phase_loop_runtime/closeout_validators.py +203 -0
- phase_loop_runtime/consiliency_gates.py +374 -0
- phase_loop_runtime/consiliency_ingest.py +399 -0
- phase_loop_runtime/consiliency_layout.py +176 -0
- phase_loop_runtime/consiliency_scaffold.py +305 -0
- phase_loop_runtime/cross_repo_channel.py +331 -0
- phase_loop_runtime/discovery.py +2368 -0
- phase_loop_runtime/dispatch_lock.py +167 -0
- phase_loop_runtime/doc_delta_validator.py +98 -0
- phase_loop_runtime/docs_audit.py +285 -0
- phase_loop_runtime/docs_freshness.py +468 -0
- phase_loop_runtime/docs_surfaces.py +150 -0
- phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
- phase_loop_runtime/events.py +154 -0
- phase_loop_runtime/events_migration.py +96 -0
- phase_loop_runtime/evidence_audit.py +1082 -0
- phase_loop_runtime/evidence_audit_config.py +172 -0
- phase_loop_runtime/fleet_map.py +426 -0
- phase_loop_runtime/fleet_metrics.py +298 -0
- phase_loop_runtime/fleet_metrics_export.py +196 -0
- phase_loop_runtime/git_discipline.py +479 -0
- phase_loop_runtime/git_ops.py +122 -0
- phase_loop_runtime/git_topology.py +252 -0
- phase_loop_runtime/governed_bundle.py +137 -0
- phase_loop_runtime/governed_premerge.py +219 -0
- phase_loop_runtime/governed_review.py +264 -0
- phase_loop_runtime/handoff.py +932 -0
- phase_loop_runtime/handoff_path.py +14 -0
- phase_loop_runtime/injection.py +740 -0
- phase_loop_runtime/install_status.py +88 -0
- phase_loop_runtime/lane_scheduler.py +317 -0
- phase_loop_runtime/launcher.py +2760 -0
- phase_loop_runtime/lease_store.py +553 -0
- phase_loop_runtime/maintenance.py +401 -0
- phase_loop_runtime/migrate_handoffs.py +201 -0
- phase_loop_runtime/models.py +2144 -0
- phase_loop_runtime/observability.py +1290 -0
- phase_loop_runtime/panel_invoker.py +1638 -0
- phase_loop_runtime/phase_loop_drift_audit.py +388 -0
- phase_loop_runtime/phase_worktree_executor.py +433 -0
- phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
- phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
- phase_loop_runtime/pipeline_adapter/flag.py +55 -0
- phase_loop_runtime/pipeline_adapter/markers.py +13 -0
- phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
- phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
- phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
- phase_loop_runtime/plan_ir.py +523 -0
- phase_loop_runtime/plan_manifest.py +415 -0
- phase_loop_runtime/planner_validation.py +176 -0
- phase_loop_runtime/profiles.py +462 -0
- phase_loop_runtime/prompts.py +365 -0
- phase_loop_runtime/provenance.py +165 -0
- phase_loop_runtime/publishing.py +339 -0
- phase_loop_runtime/py.typed +1 -0
- phase_loop_runtime/reconcile.py +1550 -0
- phase_loop_runtime/redaction.py +184 -0
- phase_loop_runtime/reflection_sync.py +293 -0
- phase_loop_runtime/release_guard.py +186 -0
- phase_loop_runtime/render.py +432 -0
- phase_loop_runtime/repo_validation.py +427 -0
- phase_loop_runtime/review_summary.py +150 -0
- phase_loop_runtime/roadmap_lint.py +425 -0
- phase_loop_runtime/route_log.py +59 -0
- phase_loop_runtime/route_policy/__init__.py +11 -0
- phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
- phase_loop_runtime/runner.py +8400 -0
- phase_loop_runtime/runtime_paths.py +225 -0
- phase_loop_runtime/runtime_projection.py +113 -0
- phase_loop_runtime/runtime_resources.py +36 -0
- phase_loop_runtime/schema_export.py +292 -0
- phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
- phase_loop_runtime/skill_install.py +176 -0
- phase_loop_runtime/skill_inventory.py +662 -0
- phase_loop_runtime/skill_paths.py +42 -0
- phase_loop_runtime/skill_sources_plugin.py +37 -0
- phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/state.py +156 -0
- phase_loop_runtime/state_degradation.py +136 -0
- phase_loop_runtime/state_ops.py +676 -0
- phase_loop_runtime/train_ledger.py +215 -0
- phase_loop_runtime/train_roadmap.py +339 -0
- phase_loop_runtime/train_runner.py +1197 -0
- phase_loop_runtime/verification_evidence.py +650 -0
- phase_loop_runtime/verification_evidence_validator.py +75 -0
- phase_loop_runtime/visual_evidence_validator.py +36 -0
- phase_loop_runtime/worker_pool.py +123 -0
- phase_loop_runtime/worktree_index.py +270 -0
- phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
- phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
- phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
- phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
- phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
- 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()))
|