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,479 @@
|
|
|
1
|
+
"""Git-discipline guardrail + bounded top-of-loop self-heal (roadmap Slice G).
|
|
2
|
+
|
|
3
|
+
CONSUMER of the neutral ``@consiliency/contract`` git-discipline contract -- it
|
|
4
|
+
does NOT redefine it. It reads two contract artifacts through the existing thin
|
|
5
|
+
loaders (``consiliency_contract.load_registry`` / ``load_schema``):
|
|
6
|
+
|
|
7
|
+
* registry ``pipeline_ref_classes`` -- the falsifiable, ordered enumeration of
|
|
8
|
+
pipeline-owned ref families plus the human default (everything else).
|
|
9
|
+
* schema ``git_discipline_protocol`` -- the write-footprint allowlist, the
|
|
10
|
+
self-heal scope, and the invariants (``never_delete_human_refs`` ...). All of
|
|
11
|
+
its knobs are JSON-Schema ``const``s, so the allowlist etc. are read out of
|
|
12
|
+
the schema structure at runtime, never hand-copied.
|
|
13
|
+
|
|
14
|
+
Two responsibilities, both bounded and autonomy-first:
|
|
15
|
+
|
|
16
|
+
1. **GUARDRAIL (verification).** Classify the repo's refs against the registry
|
|
17
|
+
and check the write-footprint / branch-naming invariants. Surfaced as the
|
|
18
|
+
fifth L0 ``.consiliency`` gate (``consiliency_gates.git_discipline``):
|
|
19
|
+
SOFT/warn by default, blocking only under
|
|
20
|
+
``PHASE_LOOP_CONSILIENCY_GATES=hard`` (opt-in). The gate layer NEVER sets
|
|
21
|
+
``human_required``.
|
|
22
|
+
|
|
23
|
+
2. **SELF-HEAL PARTITION (top-of-loop).** Decide which refs a bounded,
|
|
24
|
+
idempotent self-heal may reconcile. The partition is PURE and default-deny: a
|
|
25
|
+
ref is eligible for self-heal deletion ONLY when it is pipeline-owned, in a
|
|
26
|
+
ref-class flagged ``deletable_by_self_heal``, currently leased, AND already
|
|
27
|
+
merged. Every human ref, every unleased pipeline ref, and every non-deletable
|
|
28
|
+
pipeline ref is PROTECTED. This is the machine form of the
|
|
29
|
+
NEVER-DELETE-HUMAN-REFS invariant; ``never_deleted_human_refs`` is provably a
|
|
30
|
+
subset of ``protected`` and disjoint from ``deletable_by_self_heal``.
|
|
31
|
+
|
|
32
|
+
Contract-absent degrade: when the installed ``consiliency_contract`` predates the
|
|
33
|
+
git-discipline contract (< 0.4, no ``pipeline_ref_classes`` registry), every
|
|
34
|
+
entry point degrades to a neutral no-op (``available()`` is ``False``) -- NOT a
|
|
35
|
+
warning -- so existing governed scans are byte-for-byte unaffected until the
|
|
36
|
+
contract carrying it is installed.
|
|
37
|
+
"""
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import re
|
|
41
|
+
import subprocess
|
|
42
|
+
from dataclasses import dataclass
|
|
43
|
+
from fnmatch import fnmatchcase
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from typing import Any, Callable, Mapping, Sequence
|
|
46
|
+
|
|
47
|
+
REF_CLASS_REGISTRY = "pipeline_ref_classes"
|
|
48
|
+
GIT_DISCIPLINE_SCHEMA = "git_discipline_protocol"
|
|
49
|
+
|
|
50
|
+
# One placeholder ``{name}`` OR a glob ``*`` -- each spans exactly one path
|
|
51
|
+
# segment (``[^/]+``). Anchoring the compiled pattern (below) is load-bearing:
|
|
52
|
+
# unanchored, ``pipeline/{phase}-{node}`` would match ``my-pipeline/foo-bar`` as
|
|
53
|
+
# a substring and misclassify a human ref as pipeline-owned/deletable.
|
|
54
|
+
_TOKEN_RE = re.compile(r"\{[^}]+\}|\*")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# --------------------------------------------------------------------------- #
|
|
58
|
+
# Contract loading (guarded; contract-absent -> neutral no-op)
|
|
59
|
+
# --------------------------------------------------------------------------- #
|
|
60
|
+
def load_ref_classes() -> dict[str, Any] | None:
|
|
61
|
+
"""The ``pipeline_ref_classes`` registry, or ``None`` when the installed
|
|
62
|
+
contract predates the git-discipline contract (< 0.4)."""
|
|
63
|
+
try:
|
|
64
|
+
from consiliency_contract import load_registry
|
|
65
|
+
|
|
66
|
+
registry = load_registry(REF_CLASS_REGISTRY)
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
return registry if isinstance(registry, Mapping) else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_protocol() -> dict[str, Any] | None:
|
|
73
|
+
"""The ``git_discipline_protocol`` schema, or ``None`` when absent."""
|
|
74
|
+
try:
|
|
75
|
+
from consiliency_contract import load_schema
|
|
76
|
+
|
|
77
|
+
schema = load_schema(GIT_DISCIPLINE_SCHEMA)
|
|
78
|
+
except Exception:
|
|
79
|
+
return None
|
|
80
|
+
return schema if isinstance(schema, Mapping) else None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def available(registry: Mapping[str, Any] | None = None) -> bool:
|
|
84
|
+
"""True when the installed contract carries the git-discipline ref-class
|
|
85
|
+
registry (i.e. the guardrail can run against a real contract)."""
|
|
86
|
+
reg = load_ref_classes() if registry is None else registry
|
|
87
|
+
return isinstance(reg, Mapping) and bool(reg.get("ref_classes"))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# --------------------------------------------------------------------------- #
|
|
91
|
+
# Ref classification (pure)
|
|
92
|
+
# --------------------------------------------------------------------------- #
|
|
93
|
+
def _pattern_to_regex(pattern: str) -> re.Pattern[str]:
|
|
94
|
+
"""Compile a registry ``pattern`` to an anchored regex. Literal spans are
|
|
95
|
+
``re.escape``-d; each ``{placeholder}`` / ``*`` becomes one path segment."""
|
|
96
|
+
parts: list[str] = []
|
|
97
|
+
last = 0
|
|
98
|
+
for match in _TOKEN_RE.finditer(pattern):
|
|
99
|
+
parts.append(re.escape(pattern[last : match.start()]))
|
|
100
|
+
parts.append("[^/]+")
|
|
101
|
+
last = match.end()
|
|
102
|
+
parts.append(re.escape(pattern[last:]))
|
|
103
|
+
return re.compile("^" + "".join(parts) + "$")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class RefClassification:
|
|
108
|
+
name: str
|
|
109
|
+
owner: str # "pipeline" | "human"
|
|
110
|
+
class_id: str
|
|
111
|
+
lease_required: bool
|
|
112
|
+
deletable_by_self_heal: bool
|
|
113
|
+
merge_policy: str | None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _classification_from(entry: Mapping[str, Any], name: str) -> RefClassification:
|
|
117
|
+
return RefClassification(
|
|
118
|
+
name=name,
|
|
119
|
+
owner=str(entry.get("owner", "human")),
|
|
120
|
+
class_id=str(entry.get("id", "human")),
|
|
121
|
+
lease_required=bool(entry.get("lease_required", False)),
|
|
122
|
+
deletable_by_self_heal=bool(entry.get("deletable_by_self_heal", False)),
|
|
123
|
+
merge_policy=entry.get("merge_policy"),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def classify_ref(name: str, registry: Mapping[str, Any]) -> RefClassification:
|
|
128
|
+
"""Classify ``name`` by first-match against ``ref_classes`` in order; a ref
|
|
129
|
+
that matches no pipeline-owned pattern falls through to the human default.
|
|
130
|
+
|
|
131
|
+
Default-deny: anything not provably matching a pipeline-owned pattern is
|
|
132
|
+
human-owned and therefore never eligible for self-heal deletion.
|
|
133
|
+
"""
|
|
134
|
+
for entry in registry.get("ref_classes", []):
|
|
135
|
+
pattern = str(entry.get("pattern") or "")
|
|
136
|
+
if not pattern:
|
|
137
|
+
continue
|
|
138
|
+
if _pattern_to_regex(pattern).match(name):
|
|
139
|
+
return _classification_from(entry, name)
|
|
140
|
+
human_default = registry.get("human_default") or {"owner": "human", "id": "human"}
|
|
141
|
+
return _classification_from(human_default, name)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# --------------------------------------------------------------------------- #
|
|
145
|
+
# Self-heal partition (pure) -- the NEVER-DELETE-HUMAN-REFS invariant in code
|
|
146
|
+
# --------------------------------------------------------------------------- #
|
|
147
|
+
@dataclass(frozen=True)
|
|
148
|
+
class RefState:
|
|
149
|
+
"""A ref plus the two facts self-heal eligibility turns on."""
|
|
150
|
+
|
|
151
|
+
name: str
|
|
152
|
+
leased: bool = False
|
|
153
|
+
merged: bool = False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _eligible_for_self_heal(cls: RefClassification, ref: RefState) -> bool:
|
|
157
|
+
"""The registry ``self_heal_deletion_rule``: eligible iff owner is
|
|
158
|
+
``pipeline`` AND the ref-class is ``deletable_by_self_heal`` AND the ref is
|
|
159
|
+
currently leased AND (for the phase-worktree classes -- the only deletable
|
|
160
|
+
ones) already merged. Required uniformly here: any other class is
|
|
161
|
+
non-deletable, so demanding ``merged`` on the deletable ones is exact and
|
|
162
|
+
default-deny for everything else."""
|
|
163
|
+
return (
|
|
164
|
+
cls.owner == "pipeline"
|
|
165
|
+
and cls.deletable_by_self_heal
|
|
166
|
+
and ref.leased
|
|
167
|
+
and ref.merged
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _protected_reason(cls: RefClassification, ref: RefState) -> str:
|
|
172
|
+
if cls.owner == "human":
|
|
173
|
+
return "human ref -- never auto-deleted"
|
|
174
|
+
if not cls.deletable_by_self_heal:
|
|
175
|
+
return f"pipeline ref in non-deletable class {cls.class_id!r} -- reconcile rebases, never deletes"
|
|
176
|
+
if not ref.leased:
|
|
177
|
+
return "pipeline ref in a deletable class but UNLEASED -- self-heal needs a lease"
|
|
178
|
+
if not ref.merged:
|
|
179
|
+
return "pipeline ref leased but not yet merged -- self-heal never moves in-progress work"
|
|
180
|
+
return "protected"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def self_heal_partition(
|
|
184
|
+
refs: Sequence[RefState], registry: Mapping[str, Any]
|
|
185
|
+
) -> dict[str, Any]:
|
|
186
|
+
"""Partition ``refs`` into what a bounded self-heal MAY delete vs MUST
|
|
187
|
+
protect. Pure -- the caller supplies the lease/merge facts.
|
|
188
|
+
|
|
189
|
+
The returned shape mirrors the contract conformance vector
|
|
190
|
+
``git-discipline-never-delete-human-refs`` so the vector can drive a replay
|
|
191
|
+
test: ``deletable_by_self_heal``, ``protected``, ``human_refs``,
|
|
192
|
+
``never_deleted_human_refs``, and per-ref ``protected_reasons``.
|
|
193
|
+
"""
|
|
194
|
+
deletable: list[str] = []
|
|
195
|
+
protected: list[str] = []
|
|
196
|
+
human_refs: list[str] = []
|
|
197
|
+
reasons: dict[str, str] = {}
|
|
198
|
+
for ref in refs:
|
|
199
|
+
cls = classify_ref(ref.name, registry)
|
|
200
|
+
if cls.owner == "human":
|
|
201
|
+
human_refs.append(ref.name)
|
|
202
|
+
if _eligible_for_self_heal(cls, ref):
|
|
203
|
+
deletable.append(ref.name)
|
|
204
|
+
else:
|
|
205
|
+
protected.append(ref.name)
|
|
206
|
+
reasons[ref.name] = _protected_reason(cls, ref)
|
|
207
|
+
deletable_set = set(deletable)
|
|
208
|
+
return {
|
|
209
|
+
"deletable_by_self_heal": deletable,
|
|
210
|
+
"protected": protected,
|
|
211
|
+
"human_refs": human_refs,
|
|
212
|
+
# A human ref is NEVER eligible, so this is provably == human_refs and
|
|
213
|
+
# disjoint from deletable_by_self_heal (the invariant, asserted in tests).
|
|
214
|
+
"never_deleted_human_refs": [n for n in human_refs if n not in deletable_set],
|
|
215
|
+
"protected_reasons": reasons,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# --------------------------------------------------------------------------- #
|
|
220
|
+
# Guardrail evaluation (pure) -- write-footprint + branch-naming invariants
|
|
221
|
+
# --------------------------------------------------------------------------- #
|
|
222
|
+
def write_footprint_allowlist(protocol: Mapping[str, Any] | None) -> tuple[str, ...]:
|
|
223
|
+
"""The pipeline write-footprint allowlist, read out of the schema ``const``
|
|
224
|
+
(never hand-copied). Empty tuple when the schema is absent/misshaped."""
|
|
225
|
+
try:
|
|
226
|
+
allow = protocol["properties"]["write_footprint"]["properties"]["allowlist"]["const"] # type: ignore[index]
|
|
227
|
+
except (KeyError, TypeError):
|
|
228
|
+
return ()
|
|
229
|
+
return tuple(str(p) for p in allow) if isinstance(allow, list) else ()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _allowlisted(path: str, allowlist: Sequence[str]) -> bool:
|
|
233
|
+
for pattern in allowlist:
|
|
234
|
+
if pattern.endswith("/**"):
|
|
235
|
+
prefix = pattern[:-3]
|
|
236
|
+
if path == prefix or path.startswith(prefix + "/"):
|
|
237
|
+
return True
|
|
238
|
+
elif path == pattern or fnmatchcase(path, pattern):
|
|
239
|
+
return True
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _pipeline_prefixes(registry: Mapping[str, Any]) -> list[str]:
|
|
244
|
+
"""The static (pre-placeholder) prefix of each pipeline-owned pattern, e.g.
|
|
245
|
+
``consiliency/pipeline/``, ``phase-loop/sched/``, ``pipeline/``. Used to spot
|
|
246
|
+
a branch that SITS under a pipeline family prefix but matches no canonical
|
|
247
|
+
pattern -- i.e. a pipeline-owned ref that drifted from the contract shape."""
|
|
248
|
+
prefixes: list[str] = []
|
|
249
|
+
for entry in registry.get("ref_classes", []):
|
|
250
|
+
if entry.get("owner") != "pipeline":
|
|
251
|
+
continue
|
|
252
|
+
pattern = str(entry.get("pattern") or "")
|
|
253
|
+
token = _TOKEN_RE.search(pattern)
|
|
254
|
+
prefix = pattern[: token.start()] if token else pattern
|
|
255
|
+
if prefix:
|
|
256
|
+
prefixes.append(prefix)
|
|
257
|
+
return prefixes
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def evaluate_git_discipline(
|
|
261
|
+
*,
|
|
262
|
+
current_branch: str,
|
|
263
|
+
dirty_paths: Sequence[str],
|
|
264
|
+
local_branches: Sequence[str],
|
|
265
|
+
registry: Mapping[str, Any],
|
|
266
|
+
protocol: Mapping[str, Any] | None,
|
|
267
|
+
) -> list[dict[str, Any]]:
|
|
268
|
+
"""Return git-discipline findings for the repo (pure; the caller injects the
|
|
269
|
+
git facts). Severity is applied by the gate wrapper, not here."""
|
|
270
|
+
findings: list[dict[str, Any]] = []
|
|
271
|
+
|
|
272
|
+
# (1) Write-footprint: on a pipeline-owned working branch, dirty writes must
|
|
273
|
+
# stay inside the contract allowlist (schema policy
|
|
274
|
+
# ``unauthorized-pipeline-write-refused``).
|
|
275
|
+
current_cls = classify_ref(current_branch, registry)
|
|
276
|
+
if current_cls.owner == "pipeline":
|
|
277
|
+
allowlist = write_footprint_allowlist(protocol)
|
|
278
|
+
if allowlist: # only enforce when the contract actually declares one
|
|
279
|
+
for path in dirty_paths:
|
|
280
|
+
if not _allowlisted(path, allowlist):
|
|
281
|
+
findings.append(
|
|
282
|
+
{
|
|
283
|
+
"code": "write_footprint_violation",
|
|
284
|
+
"branch": current_branch,
|
|
285
|
+
"path": path,
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# (2) Branch-naming: a branch under a pipeline-owned family prefix that
|
|
290
|
+
# matches no canonical pattern (default-deny -> classified human) has
|
|
291
|
+
# drifted from the contract shape.
|
|
292
|
+
prefixes = _pipeline_prefixes(registry)
|
|
293
|
+
for branch in local_branches:
|
|
294
|
+
if classify_ref(branch, registry).owner == "pipeline":
|
|
295
|
+
continue
|
|
296
|
+
if any(branch.startswith(prefix) for prefix in prefixes):
|
|
297
|
+
findings.append({"code": "pipeline_branch_naming_drift", "branch": branch})
|
|
298
|
+
|
|
299
|
+
return findings
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# --------------------------------------------------------------------------- #
|
|
303
|
+
# Impure adapters -- gather git facts (kept out of the pure core above)
|
|
304
|
+
# --------------------------------------------------------------------------- #
|
|
305
|
+
def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
|
306
|
+
return subprocess.run(
|
|
307
|
+
["git", "-C", str(repo), *args],
|
|
308
|
+
check=False,
|
|
309
|
+
stdout=subprocess.PIPE,
|
|
310
|
+
stderr=subprocess.PIPE,
|
|
311
|
+
text=True,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def gather_repo_ref_facts(repo: Path) -> dict[str, Any]:
|
|
316
|
+
"""Best-effort snapshot for :func:`evaluate_git_discipline`. Never raises;
|
|
317
|
+
a git failure degrades to empty facts (the guardrail then finds nothing)."""
|
|
318
|
+
current = _git(repo, "branch", "--show-current")
|
|
319
|
+
current_branch = current.stdout.strip() if current.returncode == 0 else ""
|
|
320
|
+
|
|
321
|
+
branches: list[str] = []
|
|
322
|
+
listed = _git(repo, "for-each-ref", "--format=%(refname:short)", "refs/heads/")
|
|
323
|
+
if listed.returncode == 0:
|
|
324
|
+
branches = [line.strip() for line in listed.stdout.splitlines() if line.strip()]
|
|
325
|
+
|
|
326
|
+
dirty_paths: list[str] = []
|
|
327
|
+
status = _git(repo, "status", "--porcelain", "--untracked-files=all")
|
|
328
|
+
if status.returncode == 0:
|
|
329
|
+
for line in status.stdout.splitlines():
|
|
330
|
+
entry = line[3:].strip() if len(line) > 3 else ""
|
|
331
|
+
if " -> " in entry: # renames: "old -> new"
|
|
332
|
+
entry = entry.split(" -> ", 1)[1]
|
|
333
|
+
if entry:
|
|
334
|
+
dirty_paths.append(entry)
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
"current_branch": current_branch,
|
|
338
|
+
"dirty_paths": tuple(dirty_paths),
|
|
339
|
+
"local_branches": tuple(branches),
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def gather_pipeline_ref_states(
|
|
344
|
+
repo: Path,
|
|
345
|
+
registry: Mapping[str, Any],
|
|
346
|
+
*,
|
|
347
|
+
base_ref: str | None = None,
|
|
348
|
+
lease_predicate: Callable[[str], bool] | None = None,
|
|
349
|
+
) -> list[RefState]:
|
|
350
|
+
"""Snapshot local branches as :class:`RefState` (name / leased / merged) for
|
|
351
|
+
:func:`self_heal_partition`. ``leased`` defaults to ``False`` (conservative
|
|
352
|
+
-> nothing eligible) unless a ``lease_predicate`` is supplied; ``merged`` is
|
|
353
|
+
computed against ``base_ref`` (default: the repo's default branch)."""
|
|
354
|
+
listed = _git(repo, "for-each-ref", "--format=%(refname:short)", "refs/heads/")
|
|
355
|
+
branches = (
|
|
356
|
+
[line.strip() for line in listed.stdout.splitlines() if line.strip()]
|
|
357
|
+
if listed.returncode == 0
|
|
358
|
+
else []
|
|
359
|
+
)
|
|
360
|
+
base = base_ref or _default_branch(repo)
|
|
361
|
+
merged: set[str] = set()
|
|
362
|
+
if base:
|
|
363
|
+
result = _git(repo, "branch", "--format=%(refname:short)", "--merged", base)
|
|
364
|
+
if result.returncode == 0:
|
|
365
|
+
merged = {line.strip() for line in result.stdout.splitlines() if line.strip()}
|
|
366
|
+
predicate = lease_predicate or (lambda _name: False)
|
|
367
|
+
return [
|
|
368
|
+
RefState(name=b, leased=bool(predicate(b)), merged=b in merged) for b in branches
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _default_branch(repo: Path) -> str:
|
|
373
|
+
result = _git(repo, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
|
|
374
|
+
head = result.stdout.strip() if result.returncode == 0 else ""
|
|
375
|
+
return head.removeprefix("origin/") if head.startswith("origin/") else ""
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# --------------------------------------------------------------------------- #
|
|
379
|
+
# Bounded top-of-loop self-heal -- detect + idempotent-safe fix; delegate deletes
|
|
380
|
+
# --------------------------------------------------------------------------- #
|
|
381
|
+
def reconcile_git_discipline(
|
|
382
|
+
repo: Path,
|
|
383
|
+
*,
|
|
384
|
+
registry: Mapping[str, Any] | None = None,
|
|
385
|
+
ref_states: Sequence[RefState] | None = None,
|
|
386
|
+
execute_prune: bool = False,
|
|
387
|
+
) -> dict[str, Any]:
|
|
388
|
+
"""Bounded top-of-loop self-heal. Detection is always safe (pure reporting);
|
|
389
|
+
the only auto-fix is ``git worktree prune`` (removes stale worktree admin
|
|
390
|
+
records -- it never deletes a branch or a live worktree, so it is inherently
|
|
391
|
+
idempotent-safe). Branch/worktree DELETION for the deletable set is NOT
|
|
392
|
+
performed here; it is surfaced as a plan and left to
|
|
393
|
+
:func:`apply_self_heal_deletions` (opt-in), which re-asserts the partition.
|
|
394
|
+
|
|
395
|
+
Returns ``{"status": "skipped"|"reconciled", "partition": {...},
|
|
396
|
+
"findings": [...]}``. ``findings`` are advisory, ``human_required=False``.
|
|
397
|
+
Never raises.
|
|
398
|
+
"""
|
|
399
|
+
# Contract-absent: git-discipline is entirely latent when the installed
|
|
400
|
+
# contract predates it (< 0.4) -- nothing to consent to or enforce.
|
|
401
|
+
reg = load_ref_classes() if registry is None else registry
|
|
402
|
+
if not available(reg):
|
|
403
|
+
return {"status": "skipped", "reason": "contract-absent", "findings": []}
|
|
404
|
+
assert reg is not None # narrowed by available()
|
|
405
|
+
|
|
406
|
+
# Consent gate (design §11.5): the git-discipline contract is post-ingestion
|
|
407
|
+
# and opt-in. A repo without a `.consiliency/manifest.json` is a pure no-op --
|
|
408
|
+
# no classification, no advisories, no `git worktree prune`. This mirrors
|
|
409
|
+
# `scan_consiliency_gates`, which the guardrail gate already rides.
|
|
410
|
+
try:
|
|
411
|
+
from .consiliency_layout import find_consiliency_manifest
|
|
412
|
+
|
|
413
|
+
if find_consiliency_manifest(Path(repo)) is None:
|
|
414
|
+
return {"status": "skipped", "reason": "no-consent", "findings": []}
|
|
415
|
+
except Exception:
|
|
416
|
+
return {"status": "skipped", "reason": "no-consent", "findings": []}
|
|
417
|
+
|
|
418
|
+
states = (
|
|
419
|
+
list(ref_states)
|
|
420
|
+
if ref_states is not None
|
|
421
|
+
else gather_pipeline_ref_states(repo, reg)
|
|
422
|
+
)
|
|
423
|
+
partition = self_heal_partition(states, reg)
|
|
424
|
+
|
|
425
|
+
findings: list[dict[str, Any]] = []
|
|
426
|
+
# Drift is advisory: never auto-moved (moving possibly-in-progress work is
|
|
427
|
+
# exactly the failure mode the contract forbids).
|
|
428
|
+
facts = gather_repo_ref_facts(repo)
|
|
429
|
+
for branch in facts["local_branches"]:
|
|
430
|
+
if classify_ref(branch, reg).owner == "pipeline":
|
|
431
|
+
continue
|
|
432
|
+
if any(branch.startswith(p) for p in _pipeline_prefixes(reg)):
|
|
433
|
+
findings.append(
|
|
434
|
+
{
|
|
435
|
+
"code": "pipeline_branch_naming_drift",
|
|
436
|
+
"branch": branch,
|
|
437
|
+
"human_required": False,
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
if execute_prune:
|
|
442
|
+
# `git worktree prune` only garbage-collects admin records for worktrees
|
|
443
|
+
# whose working directory is already gone; it cannot touch a ref.
|
|
444
|
+
_git(repo, "worktree", "prune")
|
|
445
|
+
|
|
446
|
+
return {"status": "reconciled", "partition": partition, "findings": findings}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def apply_self_heal_deletions(
|
|
450
|
+
repo: Path,
|
|
451
|
+
partition: Mapping[str, Any],
|
|
452
|
+
*,
|
|
453
|
+
registry: Mapping[str, Any] | None = None,
|
|
454
|
+
) -> dict[str, list[str]]:
|
|
455
|
+
"""Delete ONLY the refs the partition marked ``deletable_by_self_heal``,
|
|
456
|
+
using ``git branch -d`` (lowercase) so git's own merged-check is a second
|
|
457
|
+
gate behind our merged-check. Opt-in; not called by the top-of-loop hook by
|
|
458
|
+
default.
|
|
459
|
+
|
|
460
|
+
Runtime guard: every candidate is re-classified and re-checked against the
|
|
461
|
+
registry before deletion; a candidate that is NOT provably pipeline-owned +
|
|
462
|
+
deletable is refused (``refused``), so even a caller bug cannot delete a
|
|
463
|
+
human ref. This is the NEVER-DELETE-HUMAN-REFS invariant enforced a second
|
|
464
|
+
time at the mutation boundary.
|
|
465
|
+
"""
|
|
466
|
+
reg = load_ref_classes() if registry is None else registry
|
|
467
|
+
deleted: list[str] = []
|
|
468
|
+
refused: list[str] = []
|
|
469
|
+
if not available(reg):
|
|
470
|
+
return {"deleted": deleted, "refused": list(partition.get("deletable_by_self_heal", []))}
|
|
471
|
+
assert reg is not None
|
|
472
|
+
for name in partition.get("deletable_by_self_heal", []):
|
|
473
|
+
cls = classify_ref(name, reg)
|
|
474
|
+
if not (cls.owner == "pipeline" and cls.deletable_by_self_heal):
|
|
475
|
+
refused.append(name)
|
|
476
|
+
continue
|
|
477
|
+
result = _git(repo, "branch", "-d", name)
|
|
478
|
+
(deleted if result.returncode == 0 else refused).append(name)
|
|
479
|
+
return {"deleted": deleted, "refused": refused}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from fnmatch import fnmatchcase
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .discovery import PlanOwnership
|
|
11
|
+
from .models import PhaseSourceBundle
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def snapshot_git_dirty_paths(repo: Path) -> tuple[str, ...]:
|
|
15
|
+
try:
|
|
16
|
+
output = subprocess.check_output(
|
|
17
|
+
["git", "-C", str(repo), "status", "--porcelain", "--untracked-files=all"],
|
|
18
|
+
text=True,
|
|
19
|
+
stderr=subprocess.DEVNULL,
|
|
20
|
+
)
|
|
21
|
+
except Exception:
|
|
22
|
+
return ()
|
|
23
|
+
paths: list[str] = []
|
|
24
|
+
for line in output.splitlines():
|
|
25
|
+
if len(line) < 4:
|
|
26
|
+
continue
|
|
27
|
+
path = line[3:]
|
|
28
|
+
if " -> " in path:
|
|
29
|
+
path = path.split(" -> ", 1)[1]
|
|
30
|
+
if path and path not in paths:
|
|
31
|
+
paths.append(path)
|
|
32
|
+
return tuple(paths)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class PipelineWriteBoundaryDiagnostic:
|
|
37
|
+
kind: str
|
|
38
|
+
message: str
|
|
39
|
+
path: str
|
|
40
|
+
blocker_class: str = "contract_bug"
|
|
41
|
+
human_required: bool = False
|
|
42
|
+
|
|
43
|
+
def to_json(self) -> dict[str, object]:
|
|
44
|
+
return {key: value for key, value in asdict(self).items() if value not in (None, "")}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def pipeline_authorized_write_patterns(bundle: "PhaseSourceBundle") -> tuple[str, ...]:
|
|
48
|
+
policy = bundle.delegated_write_policy if isinstance(bundle.delegated_write_policy, dict) else {}
|
|
49
|
+
patterns: list[str] = []
|
|
50
|
+
for value in policy.get("owned_files") or ():
|
|
51
|
+
if isinstance(value, str) and value.strip():
|
|
52
|
+
patterns.append(value.strip())
|
|
53
|
+
if bundle.artifact_target_root:
|
|
54
|
+
root = bundle.artifact_target_root.strip().rstrip("/")
|
|
55
|
+
if root:
|
|
56
|
+
patterns.append(root)
|
|
57
|
+
patterns.append(f"{root}/**")
|
|
58
|
+
return tuple(dict.fromkeys(patterns))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def pipeline_write_boundary_diagnostic(
|
|
62
|
+
repo: Path,
|
|
63
|
+
dirty_paths: tuple[str, ...] | list[str],
|
|
64
|
+
*,
|
|
65
|
+
plan_ownership: "PlanOwnership",
|
|
66
|
+
bundle: "PhaseSourceBundle | None",
|
|
67
|
+
) -> PipelineWriteBoundaryDiagnostic | None:
|
|
68
|
+
if bundle is None:
|
|
69
|
+
return None
|
|
70
|
+
protected_paths = {source.path for source in bundle.protected_sources}
|
|
71
|
+
authorized = pipeline_authorized_write_patterns(bundle)
|
|
72
|
+
for path in dirty_paths:
|
|
73
|
+
rel_path = _repo_relative_path(repo, path)
|
|
74
|
+
if rel_path in protected_paths:
|
|
75
|
+
if not (plan_ownership.matches(rel_path) and _matches_any(rel_path, authorized)):
|
|
76
|
+
return PipelineWriteBoundaryDiagnostic(
|
|
77
|
+
kind="protected_pipeline_source_write",
|
|
78
|
+
message="Protected Pipeline source write is not authorized by both the phase plan and source bundle",
|
|
79
|
+
path=rel_path,
|
|
80
|
+
)
|
|
81
|
+
continue
|
|
82
|
+
if _is_pipeline_owned(rel_path, bundle):
|
|
83
|
+
if not (plan_ownership.matches(rel_path) and _matches_any(rel_path, authorized)):
|
|
84
|
+
return PipelineWriteBoundaryDiagnostic(
|
|
85
|
+
kind="unauthorized_pipeline_write",
|
|
86
|
+
message="Pipeline-owned write is outside the combined phase-plan and source-bundle boundary",
|
|
87
|
+
path=rel_path,
|
|
88
|
+
)
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _repo_relative_path(repo: Path, path: str) -> str:
|
|
93
|
+
candidate = Path(path)
|
|
94
|
+
if candidate.is_absolute():
|
|
95
|
+
try:
|
|
96
|
+
return candidate.resolve().relative_to(repo.resolve()).as_posix()
|
|
97
|
+
except ValueError:
|
|
98
|
+
return candidate.as_posix()
|
|
99
|
+
return candidate.as_posix()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _is_pipeline_owned(path: str, bundle: "PhaseSourceBundle") -> bool:
|
|
103
|
+
if path.startswith(".pipeline/") or path == ".pipeline":
|
|
104
|
+
return True
|
|
105
|
+
if path == "pipeline.definition.json":
|
|
106
|
+
return True
|
|
107
|
+
if path in {source.path for source in bundle.protected_sources}:
|
|
108
|
+
return True
|
|
109
|
+
portal_contracts = {
|
|
110
|
+
source.path for source in bundle.protected_sources if source.category == "portal_contracts"
|
|
111
|
+
}
|
|
112
|
+
return path in portal_contracts
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _matches_any(path: str, patterns: tuple[str, ...]) -> bool:
|
|
116
|
+
return any(path == pattern or fnmatchcase(path, pattern) or _globstar_match(path, pattern) for pattern in patterns)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _globstar_match(path: str, pattern: str) -> bool:
|
|
120
|
+
if not pattern.endswith("/**"):
|
|
121
|
+
return False
|
|
122
|
+
return path.startswith(pattern[:-3].rstrip("/") + "/")
|