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,274 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .flag import branchgov_enabled, branchgov_override_explicit
|
|
9
|
+
from .markers import detect_pipeline_mode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class BranchDecision:
|
|
14
|
+
"""Outcome of a pipeline-branch governance decision (issue #44, IF-0-BRANCH-1).
|
|
15
|
+
|
|
16
|
+
``diverged`` is True when the runner switched away from the operator's
|
|
17
|
+
current branch to the ``consiliency/pipeline/<version>`` convention branch —
|
|
18
|
+
the silent-divergence condition #44 makes visible.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
original_branch: str
|
|
22
|
+
target_branch: str
|
|
23
|
+
action: str # "stay" | "checkout" | "create"
|
|
24
|
+
diverged: bool
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Stable prefix of the BranchGov default-branch refusal message. Shared so
|
|
28
|
+
# reconcile._blocker_precondition_cleared can identify this blocker variant
|
|
29
|
+
# without copying the literal string (the suffix interpolates the branch name).
|
|
30
|
+
REFUSE_DEFAULT_BRANCH_COMMIT_PREFIX = "Refusing git commit on default branch"
|
|
31
|
+
|
|
32
|
+
# Stable prefix of the BranchGov roadmap-orphan refusal (issue #83). The runner
|
|
33
|
+
# consults `roadmap_orphaned_by_branchgov` BEFORE switching and builds the
|
|
34
|
+
# `branch_sync_conflict` blocker from this summary; shared so downstream
|
|
35
|
+
# precondition-cleared logic can recognise the variant without copying the
|
|
36
|
+
# interpolated suffix (which names the branch and roadmap path).
|
|
37
|
+
REFUSE_ROADMAP_ORPHAN_PREFIX = "Refusing branch-governance switch that would orphan roadmap"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PipelineBranchInvariantError(RuntimeError):
|
|
41
|
+
def __init__(self, message: str, *, blocker_class: str = "contract_bug") -> None:
|
|
42
|
+
super().__init__(message)
|
|
43
|
+
self.blocker_class = blocker_class
|
|
44
|
+
self.blocker_summary = message
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PipelineDefaultBranchRefusalError(PipelineBranchInvariantError):
|
|
48
|
+
def __init__(self, message: str) -> None:
|
|
49
|
+
super().__init__(message, blocker_class="branch_sync_conflict")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ensure_pipeline_branch(
|
|
53
|
+
repo_root: Path,
|
|
54
|
+
roadmap_version: str,
|
|
55
|
+
default_branch: str,
|
|
56
|
+
*,
|
|
57
|
+
base_ref: str | None = None,
|
|
58
|
+
base_already_fetched: bool = False,
|
|
59
|
+
) -> BranchDecision:
|
|
60
|
+
repo = Path(repo_root)
|
|
61
|
+
pipeline_branch = f"consiliency/pipeline/{roadmap_version}"
|
|
62
|
+
if not _branchgov_active(repo):
|
|
63
|
+
current = _current_branch(repo)
|
|
64
|
+
return BranchDecision(current, current, "stay", False)
|
|
65
|
+
|
|
66
|
+
current = _current_branch(repo)
|
|
67
|
+
if current == default_branch and _dirty_status(repo):
|
|
68
|
+
raise PipelineDefaultBranchRefusalError(
|
|
69
|
+
f"Refusing pipeline branch operation from dirty default branch {default_branch}."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
base_ref = base_ref or f"origin/{default_branch}"
|
|
73
|
+
# #83: the runner may have already fetched the base once and shared it with
|
|
74
|
+
# the orphan preflight (so both evaluate the SAME fetched base). Skip the
|
|
75
|
+
# redundant fetch in that case; default preserves the standalone behaviour.
|
|
76
|
+
if not base_already_fetched:
|
|
77
|
+
_fetch_base_ref(repo, base_ref, default_branch)
|
|
78
|
+
if not _ref_exists(repo, base_ref):
|
|
79
|
+
raise PipelineBranchInvariantError(
|
|
80
|
+
f"Pipeline branch base ref {base_ref} is unavailable.",
|
|
81
|
+
blocker_class="branch_sync_conflict",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Already on the convention branch: no divergence; just stay current.
|
|
85
|
+
if current == pipeline_branch:
|
|
86
|
+
rebase = _git(repo, "rebase", base_ref)
|
|
87
|
+
if rebase.returncode != 0:
|
|
88
|
+
raise PipelineBranchInvariantError(
|
|
89
|
+
f"Pipeline branch {pipeline_branch} could not rebase onto {base_ref}.",
|
|
90
|
+
blocker_class="merge_conflict",
|
|
91
|
+
)
|
|
92
|
+
return BranchDecision(current, pipeline_branch, "stay", False)
|
|
93
|
+
|
|
94
|
+
# #44: the runner is about to switch the operator's working tree from
|
|
95
|
+
# `current` to the convention branch. Surface it loudly instead of silently.
|
|
96
|
+
_warn_branch_divergence(current, pipeline_branch, roadmap_version)
|
|
97
|
+
|
|
98
|
+
if _local_branch_exists(repo, pipeline_branch):
|
|
99
|
+
checkout = _git(repo, "checkout", pipeline_branch)
|
|
100
|
+
if checkout.returncode != 0:
|
|
101
|
+
raise PipelineBranchInvariantError(
|
|
102
|
+
f"Unable to check out pipeline branch {pipeline_branch}: {_stderr_excerpt(checkout)}",
|
|
103
|
+
blocker_class="branch_sync_conflict",
|
|
104
|
+
)
|
|
105
|
+
rebase = _git(repo, "rebase", base_ref)
|
|
106
|
+
if rebase.returncode != 0:
|
|
107
|
+
raise PipelineBranchInvariantError(
|
|
108
|
+
f"Pipeline branch {pipeline_branch} could not rebase onto {base_ref}.",
|
|
109
|
+
blocker_class="merge_conflict",
|
|
110
|
+
)
|
|
111
|
+
return BranchDecision(current, pipeline_branch, "checkout", True)
|
|
112
|
+
|
|
113
|
+
created = _git(repo, "checkout", "-b", pipeline_branch, base_ref)
|
|
114
|
+
if created.returncode != 0:
|
|
115
|
+
raise PipelineBranchInvariantError(
|
|
116
|
+
f"Unable to create pipeline branch {pipeline_branch} from {base_ref}: {_stderr_excerpt(created)}",
|
|
117
|
+
blocker_class="branch_sync_conflict",
|
|
118
|
+
)
|
|
119
|
+
return BranchDecision(current, pipeline_branch, "create", True)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def roadmap_orphaned_by_branchgov(
|
|
123
|
+
repo_root: Path,
|
|
124
|
+
roadmap: Path,
|
|
125
|
+
roadmap_version: str,
|
|
126
|
+
default_branch: str,
|
|
127
|
+
*,
|
|
128
|
+
base_ref: str | None = None,
|
|
129
|
+
base_already_fetched: bool = False,
|
|
130
|
+
) -> str | None:
|
|
131
|
+
"""Issue #83 preflight: would a branch-governance switch orphan the roadmap?
|
|
132
|
+
|
|
133
|
+
Returns a human-actionable summary string when ALL of the following hold,
|
|
134
|
+
and ``None`` otherwise (the switch is safe / governed):
|
|
135
|
+
|
|
136
|
+
* the switch would DIVERGE — the operator is on a non-convention branch so
|
|
137
|
+
`ensure_pipeline_branch` would `checkout`/`create` the convention branch
|
|
138
|
+
(mirrors the #44 divergence condition), AND
|
|
139
|
+
* the roadmap artifact is committed on the current branch (tracked at HEAD)
|
|
140
|
+
but is NOT reachable from the pipeline-branch base (absent at
|
|
141
|
+
``base_ref``) — i.e. the very file the switch would make vanish, which is
|
|
142
|
+
what crashes the executor with ``FileNotFoundError`` mid-run.
|
|
143
|
+
|
|
144
|
+
The base is evaluated AFTER fetching it, matching `ensure_pipeline_branch`:
|
|
145
|
+
otherwise a stale local ``refs/remotes/origin/<default>`` would
|
|
146
|
+
FALSE-POSITIVE-refuse a roadmap that is in fact pushed and on-base. When the
|
|
147
|
+
runner has already fetched the shared base, pass ``base_already_fetched=True``
|
|
148
|
+
to avoid a redundant fetch (the caller gates branchgov-active, so this
|
|
149
|
+
predicate does not re-check it).
|
|
150
|
+
|
|
151
|
+
Behaviour-preserving for genuinely governed runs: if the roadmap is present
|
|
152
|
+
at the FETCHED base (pushed / on-base) the predicate returns ``None`` and the
|
|
153
|
+
caller proceeds with the existing switch + ``coordinator.branch_switched``.
|
|
154
|
+
"""
|
|
155
|
+
repo = Path(repo_root)
|
|
156
|
+
|
|
157
|
+
pipeline_branch = f"consiliency/pipeline/{roadmap_version}"
|
|
158
|
+
current = _current_branch(repo)
|
|
159
|
+
# No divergence when already on the convention branch (the switch is a no-op
|
|
160
|
+
# "stay"), or on the default branch (handled by the dirty-default refusal).
|
|
161
|
+
if current == pipeline_branch:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
base_ref = base_ref or f"origin/{default_branch}"
|
|
165
|
+
# Evaluate the FETCHED base (the false-positive fix). Skip the fetch when the
|
|
166
|
+
# runner already did it and shared the base with us.
|
|
167
|
+
if not base_already_fetched:
|
|
168
|
+
_fetch_base_ref(repo, base_ref, default_branch)
|
|
169
|
+
if not _ref_exists(repo, base_ref):
|
|
170
|
+
# Base unavailability is surfaced by ensure_pipeline_branch itself; don't
|
|
171
|
+
# second-guess it here.
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
rel = _roadmap_relpath(repo, roadmap)
|
|
175
|
+
if rel is None:
|
|
176
|
+
return None
|
|
177
|
+
# Roadmap must be committed on the current branch (tracked at HEAD) — an
|
|
178
|
+
# uncommitted/untracked roadmap is a different failure mode, not an orphan.
|
|
179
|
+
if not _path_tracked_at(repo, "HEAD", rel):
|
|
180
|
+
return None
|
|
181
|
+
# Safe (governed) when the same roadmap is reachable from the (fetched) base:
|
|
182
|
+
# the switch keeps the file visible.
|
|
183
|
+
if _path_tracked_at(repo, base_ref, rel):
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
f"{REFUSE_ROADMAP_ORPHAN_PREFIX} '{rel}'. Branch governance would switch from "
|
|
188
|
+
f"'{current}' to convention branch '{pipeline_branch}' cut from '{base_ref}', but "
|
|
189
|
+
f"the roadmap is committed only on '{current}' and is not reachable from "
|
|
190
|
+
f"'{base_ref}' — the switch would make it vanish and crash the run. Push the "
|
|
191
|
+
f"roadmap to '{base_ref}' (or land it on the base) to fix this; or, to switch "
|
|
192
|
+
f"anyway, pass --allow-branchgov (PHASE_LOOP_BRANCHGOV_ENABLE=true) — the run "
|
|
193
|
+
f"then fails cleanly (branch_sync_conflict) if the roadmap still isn't on the base."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _roadmap_relpath(repo: Path, roadmap: Path) -> str | None:
|
|
198
|
+
"""POSIX repo-relative pathspec for the roadmap, or None if outside the repo."""
|
|
199
|
+
try:
|
|
200
|
+
rel = Path(roadmap).resolve().relative_to(Path(repo).resolve())
|
|
201
|
+
except (ValueError, OSError):
|
|
202
|
+
return None
|
|
203
|
+
return rel.as_posix()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _path_tracked_at(repo: Path, ref: str, relpath: str) -> bool:
|
|
207
|
+
"""True when ``relpath`` is a tracked object at ``ref`` (predicts the FileNotFound)."""
|
|
208
|
+
return _git(repo, "cat-file", "-e", f"{ref}:{relpath}").returncode == 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _warn_branch_divergence(current: str, target: str, roadmap_version: str) -> None:
|
|
212
|
+
sys.stderr.write(
|
|
213
|
+
f"phase-loop: switching from '{current}' to convention branch '{target}' "
|
|
214
|
+
f"(roadmap {roadmap_version}) per branch governance; files present only on "
|
|
215
|
+
f"'{current}' (e.g. a roadmap/plan not yet on '{target}') will not be "
|
|
216
|
+
f"visible after the switch.\n"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _fetch_base_ref(repo: Path, base_ref: str, default_branch: str) -> None:
|
|
221
|
+
if base_ref.startswith("origin/"):
|
|
222
|
+
_git(repo, "fetch", "origin", base_ref.removeprefix("origin/"))
|
|
223
|
+
elif base_ref == f"origin/{default_branch}":
|
|
224
|
+
_git(repo, "fetch", "origin", default_branch)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def refuse_default_branch_commit(repo_root: Path, default_branch: str) -> None:
|
|
228
|
+
repo = Path(repo_root)
|
|
229
|
+
if not _branchgov_active(repo):
|
|
230
|
+
return
|
|
231
|
+
if _current_branch(repo) == default_branch:
|
|
232
|
+
raise PipelineDefaultBranchRefusalError(
|
|
233
|
+
f"{REFUSE_DEFAULT_BRANCH_COMMIT_PREFIX} {default_branch} while pipeline branch governance is enabled."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _branchgov_active(repo: Path) -> bool:
|
|
238
|
+
return detect_pipeline_mode(repo) and branchgov_enabled()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _current_branch(repo: Path) -> str:
|
|
242
|
+
result = _git(repo, "branch", "--show-current")
|
|
243
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
244
|
+
return result.stdout.strip()
|
|
245
|
+
head = _git(repo, "rev-parse", "--short", "HEAD")
|
|
246
|
+
return f"detached@{head.stdout.strip()}" if head.returncode == 0 and head.stdout.strip() else "unknown"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _dirty_status(repo: Path) -> str:
|
|
250
|
+
result = _git(repo, "status", "--porcelain", "--untracked-files=all")
|
|
251
|
+
return result.stdout.strip() if result.returncode == 0 else ""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _ref_exists(repo: Path, ref: str) -> bool:
|
|
255
|
+
return _git(repo, "rev-parse", "--verify", "--quiet", ref).returncode == 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _local_branch_exists(repo: Path, branch: str) -> bool:
|
|
259
|
+
return _ref_exists(repo, f"refs/heads/{branch}")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
|
263
|
+
return subprocess.run(
|
|
264
|
+
["git", "-C", str(repo), *args],
|
|
265
|
+
check=False,
|
|
266
|
+
stdout=subprocess.PIPE,
|
|
267
|
+
stderr=subprocess.PIPE,
|
|
268
|
+
text=True,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _stderr_excerpt(result: subprocess.CompletedProcess[str]) -> str:
|
|
273
|
+
text = (result.stderr or result.stdout or "").strip()
|
|
274
|
+
return " ".join(text.split())[:300] or "git command failed"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def branchgov_enabled() -> bool:
|
|
7
|
+
return os.environ.get("PHASE_LOOP_BRANCHGOV_ENABLE") != "false"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def branchgov_override_explicit() -> bool:
|
|
11
|
+
"""True only when branch governance is EXPLICITLY opted into (issue #83).
|
|
12
|
+
|
|
13
|
+
Distinct from ``branchgov_enabled()`` (which is True for both the unset
|
|
14
|
+
default and ``"true"``). The default-on case is exactly the bug surface: a
|
|
15
|
+
locally-committed roadmap on the operator's branch would be orphaned by the
|
|
16
|
+
convention-branch switch. The orphan-refusal preflight must therefore key on
|
|
17
|
+
this *explicit* signal — set by ``--allow-branchgov`` (which exports
|
|
18
|
+
``PHASE_LOOP_BRANCHGOV_ENABLE=true``) — so the unset default still refuses
|
|
19
|
+
and only a deliberate ``"true"`` (or ``--allow-branchgov``) switches anyway.
|
|
20
|
+
"""
|
|
21
|
+
return os.environ.get("PHASE_LOOP_BRANCHGOV_ENABLE") == "true"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def trust_executor_evidence_enabled() -> bool:
|
|
25
|
+
return os.environ.get("PHASE_LOOP_TRUST_EXECUTOR_EVIDENCE") != "false"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def allow_lane_ir_override_enabled() -> bool:
|
|
29
|
+
return os.environ.get("PHASE_LOOP_ALLOW_LANE_IR_OVERRIDE") != "false"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def dispatch_lock_enabled() -> bool:
|
|
33
|
+
return os.environ.get("PHASE_LOOP_DISPATCH_LOCK") != "false"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parallel_dispatch_enabled() -> bool:
|
|
37
|
+
return os.environ.get("PHASE_LOOP_PARALLEL_DISPATCH") != "false"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def reconcile_git_reality_enabled() -> bool:
|
|
41
|
+
# SAFE CUTOVER: defaults OFF (opt-in), unlike the flags above. The active body
|
|
42
|
+
# wires into classify_phase — the universal classifier — so on our own runtime
|
|
43
|
+
# the feature lands inert and is flipped on deliberately after validation.
|
|
44
|
+
return os.environ.get("PHASE_LOOP_RECONCILE_GIT_REALITY") == "true"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def concurrent_real_exec_integration_enabled() -> bool:
|
|
48
|
+
# SAFE CUTOVER: defaults OFF (opt-in). Switches the concurrent scheduler's
|
|
49
|
+
# worktree integration from the committed-only merge (integrate_phase_worktree)
|
|
50
|
+
# to dirty-work transport (transfer_phase_worktree_dirty), which lands a real
|
|
51
|
+
# executor's uncommitted phase-owned work onto the pipeline branch so the
|
|
52
|
+
# parent closeout commits it. Only active under `--phase-scheduler concurrent`
|
|
53
|
+
# (itself opt-in); flipped on deliberately after a real concurrent run
|
|
54
|
+
# validates the transport on this runtime's own repo.
|
|
55
|
+
return os.environ.get("PHASE_LOOP_CONCURRENT_REAL_EXEC") == "true"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def detect_pipeline_mode(repo_root: Path) -> bool:
|
|
8
|
+
root = Path(repo_root)
|
|
9
|
+
return (
|
|
10
|
+
(root / ".pipeline").exists()
|
|
11
|
+
or (root / ".github" / "workflows" / "pipeline-bootstrap.yml").exists()
|
|
12
|
+
or os.environ.get("PHASE_LOOP_PIPELINE_MODE") == "true"
|
|
13
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
MERGE_POLICY_ON_PASS = ("required", "auto", "never")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MergePolicyParseError(ValueError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class MergePolicy:
|
|
17
|
+
on_pass: str = "required"
|
|
18
|
+
approvers: tuple[str, ...] = ()
|
|
19
|
+
|
|
20
|
+
def __post_init__(self) -> None:
|
|
21
|
+
if self.on_pass not in MERGE_POLICY_ON_PASS:
|
|
22
|
+
raise MergePolicyParseError(f"invalid merge_policy.on_pass: {self.on_pass}")
|
|
23
|
+
normalized = tuple(str(approver).strip() for approver in self.approvers if str(approver).strip())
|
|
24
|
+
object.__setattr__(self, "approvers", normalized)
|
|
25
|
+
|
|
26
|
+
def to_json(self) -> dict[str, object]:
|
|
27
|
+
return {"on_pass": self.on_pass, "approvers": list(self.approvers)}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parse(phase_block: dict) -> MergePolicy:
|
|
31
|
+
if "merge_policy" not in phase_block or phase_block.get("merge_policy") in (None, ""):
|
|
32
|
+
return MergePolicy(on_pass="required", approvers=())
|
|
33
|
+
|
|
34
|
+
raw = _coerce_policy_block(phase_block["merge_policy"])
|
|
35
|
+
on_pass = str(raw.get("on_pass") or "required").strip()
|
|
36
|
+
if on_pass not in MERGE_POLICY_ON_PASS:
|
|
37
|
+
raise MergePolicyParseError(f"invalid merge_policy.on_pass: {on_pass}")
|
|
38
|
+
|
|
39
|
+
approvers = _approver_tuple(raw.get("approvers"))
|
|
40
|
+
if on_pass == "required" and not approvers:
|
|
41
|
+
raise MergePolicyParseError("explicit merge_policy.on_pass=required requires at least one approver")
|
|
42
|
+
return MergePolicy(on_pass=on_pass, approvers=approvers)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _coerce_policy_block(value: Any) -> dict[str, Any]:
|
|
46
|
+
if isinstance(value, dict):
|
|
47
|
+
return value
|
|
48
|
+
if isinstance(value, str):
|
|
49
|
+
stripped = value.strip()
|
|
50
|
+
if not stripped:
|
|
51
|
+
return {}
|
|
52
|
+
if stripped in MERGE_POLICY_ON_PASS:
|
|
53
|
+
return {"on_pass": stripped}
|
|
54
|
+
try:
|
|
55
|
+
decoded = json.loads(stripped)
|
|
56
|
+
except json.JSONDecodeError as exc:
|
|
57
|
+
raise MergePolicyParseError(f"invalid merge_policy JSON: {exc.msg}") from exc
|
|
58
|
+
if isinstance(decoded, dict):
|
|
59
|
+
return decoded
|
|
60
|
+
raise MergePolicyParseError("merge_policy must be an object or JSON object string")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _approver_tuple(value: Any) -> tuple[str, ...]:
|
|
64
|
+
if value is None:
|
|
65
|
+
return ()
|
|
66
|
+
if isinstance(value, str):
|
|
67
|
+
items = value.split(",")
|
|
68
|
+
elif isinstance(value, (list, tuple)):
|
|
69
|
+
items = value
|
|
70
|
+
else:
|
|
71
|
+
raise MergePolicyParseError("merge_policy.approvers must be a list, tuple, or comma-separated string")
|
|
72
|
+
return tuple(str(item).strip() for item in items if str(item).strip())
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..git_topology import collect_git_topology
|
|
9
|
+
from ..runtime_paths import ensure_phase_loop_excluded, phase_loop_event_file
|
|
10
|
+
from ..models import utc_now
|
|
11
|
+
from .merge_policy import MergePolicy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TRIGGER_PATH = Path(".pipeline") / "ratification-trigger.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def emit_ratification_passed(
|
|
18
|
+
repo_root: Path,
|
|
19
|
+
roadmap_version: str,
|
|
20
|
+
phase_alias: str,
|
|
21
|
+
ratification_gate: str,
|
|
22
|
+
merge_policy: MergePolicy,
|
|
23
|
+
audit: dict[str, Any],
|
|
24
|
+
) -> None:
|
|
25
|
+
repo = Path(repo_root)
|
|
26
|
+
payload = _payload(repo, roadmap_version, phase_alias, ratification_gate, merge_policy, audit)
|
|
27
|
+
ensure_phase_loop_excluded(repo)
|
|
28
|
+
event_path = phase_loop_event_file(repo)
|
|
29
|
+
event_path.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
event = {
|
|
31
|
+
"timestamp": utc_now(),
|
|
32
|
+
"repo": str(repo),
|
|
33
|
+
"roadmap": roadmap_version,
|
|
34
|
+
"phase": phase_alias,
|
|
35
|
+
"event_type": "ratification.passed",
|
|
36
|
+
"action": "ratification",
|
|
37
|
+
"status": "passed",
|
|
38
|
+
"source": "pipeline_adapter",
|
|
39
|
+
"schema_version": 2,
|
|
40
|
+
"payload": payload,
|
|
41
|
+
"git_topology": collect_git_topology(repo),
|
|
42
|
+
}
|
|
43
|
+
with event_path.open("a", encoding="utf-8") as handle:
|
|
44
|
+
handle.write(json.dumps(event, sort_keys=True) + "\n")
|
|
45
|
+
|
|
46
|
+
trigger = repo / TRIGGER_PATH
|
|
47
|
+
trigger.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
trigger.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _payload(
|
|
52
|
+
repo: Path,
|
|
53
|
+
roadmap_version: str,
|
|
54
|
+
phase_alias: str,
|
|
55
|
+
ratification_gate: str,
|
|
56
|
+
merge_policy: MergePolicy,
|
|
57
|
+
audit: dict[str, Any],
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"roadmap_version": roadmap_version,
|
|
61
|
+
"phase_alias": phase_alias,
|
|
62
|
+
"ratification_gate": ratification_gate,
|
|
63
|
+
"merge_policy": merge_policy.to_json(),
|
|
64
|
+
"audit": dict(audit),
|
|
65
|
+
"pipeline_branch": _current_branch(repo),
|
|
66
|
+
"default_branch": _default_branch(repo),
|
|
67
|
+
"head_sha": _git_output_or_empty(repo, "rev-parse", "HEAD") or None,
|
|
68
|
+
"merge_pr_title": f"{roadmap_version} phase {phase_alias} ratification: {ratification_gate}",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _current_branch(repo: Path) -> str:
|
|
73
|
+
return _git_output_or_empty(repo, "branch", "--show-current") or "detached"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _default_branch(repo: Path) -> str:
|
|
77
|
+
remote_head = _git_output_or_empty(repo, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
|
|
78
|
+
if remote_head.startswith("origin/"):
|
|
79
|
+
return remote_head.removeprefix("origin/")
|
|
80
|
+
# Authoritative fallback: ask the remote for its HEAD when origin/HEAD is unset.
|
|
81
|
+
# The previous fallback used @{upstream} which returns the current branch's
|
|
82
|
+
# tracking ref and could mis-identify the pipeline branch as the default.
|
|
83
|
+
ls_remote = _git_output_or_empty(repo, "ls-remote", "--symref", "origin", "HEAD")
|
|
84
|
+
for line in ls_remote.splitlines():
|
|
85
|
+
if line.startswith("ref: refs/heads/"):
|
|
86
|
+
ref = line.split("\t", 1)[0].removeprefix("ref: refs/heads/").strip()
|
|
87
|
+
if ref:
|
|
88
|
+
return ref
|
|
89
|
+
return "main"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _git_output_or_empty(repo: Path, *args: str) -> str:
|
|
93
|
+
try:
|
|
94
|
+
return subprocess.check_output(
|
|
95
|
+
["git", "-C", str(repo), *args],
|
|
96
|
+
text=True,
|
|
97
|
+
stderr=subprocess.DEVNULL,
|
|
98
|
+
).strip()
|
|
99
|
+
except Exception:
|
|
100
|
+
return ""
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import PurePosixPath
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
LOCKFILE_NAMES = {"package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"}
|
|
7
|
+
ENV_SOURCE_NAMES = {"env.ts", "env.js", "env.mjs", "env.cjs", "environment.ts", "environment.js"}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def validate_phase_owned_evidence(
|
|
11
|
+
declared_paths: tuple[str, ...] | list[str],
|
|
12
|
+
actual_paths: tuple[str, ...] | list[str],
|
|
13
|
+
evidence: object,
|
|
14
|
+
) -> tuple[dict[str, str], ...]:
|
|
15
|
+
declared = tuple(filter(None, (_clean_path(path) for path in declared_paths)))
|
|
16
|
+
actual = set(filter(None, (_clean_path(path) for path in actual_paths)))
|
|
17
|
+
accepted: list[dict[str, str]] = []
|
|
18
|
+
seen: set[str] = set()
|
|
19
|
+
|
|
20
|
+
for item in _evidence_items(evidence):
|
|
21
|
+
evidence_path = _clean_path(str(item.get("path") or item.get("actual_path") or item.get("sibling") or ""))
|
|
22
|
+
if not evidence_path or evidence_path not in actual or evidence_path in seen:
|
|
23
|
+
continue
|
|
24
|
+
match = _matching_declared_path(evidence_path, declared)
|
|
25
|
+
if match is None:
|
|
26
|
+
continue
|
|
27
|
+
accepted.append(
|
|
28
|
+
{
|
|
29
|
+
"path": evidence_path,
|
|
30
|
+
"declared_path": match[0],
|
|
31
|
+
"kind": match[1],
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
seen.add(evidence_path)
|
|
35
|
+
|
|
36
|
+
return tuple(accepted)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _evidence_items(evidence: object) -> tuple[dict[str, object], ...]:
|
|
40
|
+
if not isinstance(evidence, list):
|
|
41
|
+
return ()
|
|
42
|
+
items: list[dict[str, object]] = []
|
|
43
|
+
for item in evidence:
|
|
44
|
+
if isinstance(item, str):
|
|
45
|
+
items.append({"path": item})
|
|
46
|
+
elif isinstance(item, dict):
|
|
47
|
+
items.append(item)
|
|
48
|
+
return tuple(items)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _clean_path(path: str) -> str:
|
|
52
|
+
path = path.replace("\\", "/").strip().strip("/")
|
|
53
|
+
if not path or path.startswith("/") or path.startswith("../") or "/../" in path or path == "..":
|
|
54
|
+
return ""
|
|
55
|
+
return path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _matching_declared_path(path: str, declared_paths: tuple[str, ...]) -> tuple[str, str] | None:
|
|
59
|
+
for declared in declared_paths:
|
|
60
|
+
if _is_test_for(path, declared):
|
|
61
|
+
return declared, "test"
|
|
62
|
+
if _is_snapshot_for(path, declared):
|
|
63
|
+
return declared, "snapshot"
|
|
64
|
+
if _is_migration_peer(path, declared):
|
|
65
|
+
return declared, "migration_timestamp"
|
|
66
|
+
if _is_lockfile_for_manifest(path, declared):
|
|
67
|
+
return declared, "package_lock"
|
|
68
|
+
if _is_env_example_for(path, declared):
|
|
69
|
+
return declared, "env_example"
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _stem_without_test_suffix(name: str) -> str:
|
|
74
|
+
for suffix in (".test", ".spec"):
|
|
75
|
+
if name.endswith(suffix):
|
|
76
|
+
return name[: -len(suffix)]
|
|
77
|
+
return name
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_test_for(path: str, declared: str) -> bool:
|
|
81
|
+
candidate = PurePosixPath(path)
|
|
82
|
+
source = PurePosixPath(declared)
|
|
83
|
+
if "__tests__" not in candidate.parts and not candidate.name.startswith("test_"):
|
|
84
|
+
return False
|
|
85
|
+
candidate_stem = _stem_without_test_suffix(candidate.stem)
|
|
86
|
+
source_stem = source.stem
|
|
87
|
+
if candidate.name.startswith("test_"):
|
|
88
|
+
candidate_stem = candidate_stem.removeprefix("test_")
|
|
89
|
+
return candidate_stem == source_stem
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _is_snapshot_for(path: str, declared: str) -> bool:
|
|
93
|
+
candidate = PurePosixPath(path)
|
|
94
|
+
source = PurePosixPath(declared)
|
|
95
|
+
return "__snapshots__" in candidate.parts and candidate.stem.startswith(source.stem)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _migration_timestamp(path: str) -> str | None:
|
|
99
|
+
candidate = PurePosixPath(path)
|
|
100
|
+
if "migrations" not in candidate.parts:
|
|
101
|
+
return None
|
|
102
|
+
prefix = candidate.name.split("_", 1)[0]
|
|
103
|
+
return prefix if prefix.isdigit() and len(prefix) >= 8 else None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _is_migration_peer(path: str, declared: str) -> bool:
|
|
107
|
+
path_ts = _migration_timestamp(path)
|
|
108
|
+
declared_ts = _migration_timestamp(declared)
|
|
109
|
+
return bool(path_ts and declared_ts and path_ts == declared_ts and path != declared)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _is_lockfile_for_manifest(path: str, declared: str) -> bool:
|
|
113
|
+
candidate = PurePosixPath(path)
|
|
114
|
+
source = PurePosixPath(declared)
|
|
115
|
+
return candidate.parent == source.parent and candidate.name in LOCKFILE_NAMES and source.name == "package.json"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _is_env_example_for(path: str, declared: str) -> bool:
|
|
119
|
+
candidate = PurePosixPath(path)
|
|
120
|
+
source = PurePosixPath(declared)
|
|
121
|
+
if candidate.name != ".env.example":
|
|
122
|
+
return False
|
|
123
|
+
return candidate.parent == source.parent and (source.name in ENV_SOURCE_NAMES or "env" in source.stem.lower())
|