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,252 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .models import LoopEvent, StateSnapshot
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def attach_git_topology(repo: Path, value: LoopEvent | StateSnapshot) -> LoopEvent | StateSnapshot:
|
|
13
|
+
if value.git_topology:
|
|
14
|
+
return value
|
|
15
|
+
return replace(value, git_topology=collect_git_topology(repo))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def collect_git_topology(repo: Path) -> dict[str, Any]:
|
|
19
|
+
if not _git_available(repo):
|
|
20
|
+
return {"available": False, "reason": "not a git worktree"}
|
|
21
|
+
|
|
22
|
+
topology: dict[str, Any] = {
|
|
23
|
+
"available": True,
|
|
24
|
+
"branch": _branch(repo),
|
|
25
|
+
"head": _git(repo, "rev-parse", "HEAD"),
|
|
26
|
+
"status_short_branch": _git(repo, "status", "--short", "--branch"),
|
|
27
|
+
}
|
|
28
|
+
topology["clean"] = not bool(_git(repo, "status", "--short"))
|
|
29
|
+
|
|
30
|
+
upstream = _git(repo, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}")
|
|
31
|
+
if upstream:
|
|
32
|
+
topology["upstream_ref"] = upstream
|
|
33
|
+
topology["upstream_head"] = _git(repo, "rev-parse", upstream)
|
|
34
|
+
behind, ahead = _ahead_behind(repo, upstream)
|
|
35
|
+
if ahead is not None:
|
|
36
|
+
topology["ahead_of_upstream"] = ahead
|
|
37
|
+
if behind is not None:
|
|
38
|
+
topology["behind_upstream"] = behind
|
|
39
|
+
|
|
40
|
+
default_remote = _git(repo, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
|
|
41
|
+
if default_remote:
|
|
42
|
+
topology["default_remote_ref"] = default_remote
|
|
43
|
+
|
|
44
|
+
base_ref = os.environ.get("PHASE_LOOP_BASE_REF") or upstream or default_remote or _existing_ref(repo, "origin/main")
|
|
45
|
+
if base_ref:
|
|
46
|
+
topology["base_ref"] = base_ref
|
|
47
|
+
topology["base_head"] = _git(repo, "rev-parse", base_ref)
|
|
48
|
+
behind, ahead = _ahead_behind(repo, base_ref)
|
|
49
|
+
if ahead is not None:
|
|
50
|
+
topology["ahead_of_base"] = ahead
|
|
51
|
+
if behind is not None:
|
|
52
|
+
topology["behind_base"] = behind
|
|
53
|
+
|
|
54
|
+
target_push_ref = os.environ.get("PHASE_LOOP_TARGET_PUSH_REF")
|
|
55
|
+
if target_push_ref:
|
|
56
|
+
topology["target_push_ref"] = target_push_ref
|
|
57
|
+
pr_head_ref = os.environ.get("PHASE_LOOP_PR_HEAD_REF") or os.environ.get("GITHUB_HEAD_REF")
|
|
58
|
+
if pr_head_ref:
|
|
59
|
+
topology["pr_head_ref"] = pr_head_ref
|
|
60
|
+
pr_base_ref = os.environ.get("PHASE_LOOP_PR_BASE_REF") or os.environ.get("GITHUB_BASE_REF")
|
|
61
|
+
if pr_base_ref:
|
|
62
|
+
topology["pr_base_ref"] = pr_base_ref
|
|
63
|
+
pr_url = os.environ.get("PHASE_LOOP_PR_URL")
|
|
64
|
+
if pr_url:
|
|
65
|
+
topology["pr_url"] = pr_url
|
|
66
|
+
elif "pr_url" not in topology:
|
|
67
|
+
topology.update(_matching_pr_metadata(repo, topology.get("head"), default_remote))
|
|
68
|
+
|
|
69
|
+
return {key: value for key, value in topology.items() if value not in (None, "")}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve_closeout_push_target(repo: Path, topology: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
73
|
+
topology = dict(topology or collect_git_topology(repo))
|
|
74
|
+
if not topology.get("available"):
|
|
75
|
+
return {"allowed": False, "refusal_reason": "not_a_git_worktree"}
|
|
76
|
+
|
|
77
|
+
explicit_target = topology.get("target_push_ref")
|
|
78
|
+
upstream = topology.get("upstream_ref")
|
|
79
|
+
remote = _remote_from_upstream(upstream)
|
|
80
|
+
if explicit_target:
|
|
81
|
+
push_ref = str(explicit_target)
|
|
82
|
+
if remote is None:
|
|
83
|
+
remote = _default_push_remote(repo, topology)
|
|
84
|
+
if remote is None:
|
|
85
|
+
return {"allowed": False, "refusal_reason": "missing_push_remote"}
|
|
86
|
+
elif upstream:
|
|
87
|
+
remote, branch_ref = _split_upstream_ref(str(upstream))
|
|
88
|
+
if remote is None or branch_ref is None:
|
|
89
|
+
return {"allowed": False, "refusal_reason": "ambiguous_upstream_ref"}
|
|
90
|
+
push_ref = branch_ref
|
|
91
|
+
else:
|
|
92
|
+
return {"allowed": False, "refusal_reason": "missing_push_target"}
|
|
93
|
+
|
|
94
|
+
if not topology.get("clean", False):
|
|
95
|
+
return {"allowed": False, "remote": remote, "push_ref": push_ref, "refusal_reason": "post_commit_dirty_worktree"}
|
|
96
|
+
if int(topology.get("behind_upstream", 0) or 0) > 0:
|
|
97
|
+
return {"allowed": False, "remote": remote, "push_ref": push_ref, "refusal_reason": "behind_upstream"}
|
|
98
|
+
|
|
99
|
+
return {"allowed": True, "remote": remote, "push_ref": push_ref}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _git_available(repo: Path) -> bool:
|
|
103
|
+
return _git(repo, "rev-parse", "--is-inside-work-tree") == "true"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _branch(repo: Path) -> str:
|
|
107
|
+
branch = _git(repo, "branch", "--show-current")
|
|
108
|
+
if branch:
|
|
109
|
+
return branch
|
|
110
|
+
head = _git(repo, "rev-parse", "--short", "HEAD")
|
|
111
|
+
return f"detached@{head}" if head else "unknown"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _existing_ref(repo: Path, ref: str) -> str | None:
|
|
115
|
+
return ref if _git(repo, "rev-parse", "--verify", "--quiet", ref) else None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _ahead_behind(repo: Path, base_ref: str) -> tuple[int | None, int | None]:
|
|
119
|
+
counts = _git(repo, "rev-list", "--left-right", "--count", f"{base_ref}...HEAD")
|
|
120
|
+
if not counts:
|
|
121
|
+
return None, None
|
|
122
|
+
parts = counts.split()
|
|
123
|
+
if len(parts) != 2:
|
|
124
|
+
return None, None
|
|
125
|
+
try:
|
|
126
|
+
behind = int(parts[0])
|
|
127
|
+
ahead = int(parts[1])
|
|
128
|
+
except ValueError:
|
|
129
|
+
return None, None
|
|
130
|
+
return behind, ahead
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _remote_from_upstream(upstream_ref: Any) -> str | None:
|
|
134
|
+
if not isinstance(upstream_ref, str) or "/" not in upstream_ref:
|
|
135
|
+
return None
|
|
136
|
+
return upstream_ref.split("/", 1)[0] or None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _split_upstream_ref(upstream_ref: str) -> tuple[str | None, str | None]:
|
|
140
|
+
if "/" not in upstream_ref:
|
|
141
|
+
return None, None
|
|
142
|
+
remote, branch = upstream_ref.split("/", 1)
|
|
143
|
+
if not remote or not branch:
|
|
144
|
+
return None, None
|
|
145
|
+
return remote, f"refs/heads/{branch}"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _default_push_remote(repo: Path, topology: dict[str, Any]) -> str | None:
|
|
149
|
+
upstream_remote = _remote_from_upstream(topology.get("upstream_ref"))
|
|
150
|
+
if upstream_remote:
|
|
151
|
+
return upstream_remote
|
|
152
|
+
default_remote = topology.get("default_remote_ref")
|
|
153
|
+
if isinstance(default_remote, str) and "/" in default_remote:
|
|
154
|
+
return default_remote.split("/", 1)[0]
|
|
155
|
+
remotes = _git(repo, "remote")
|
|
156
|
+
if not remotes:
|
|
157
|
+
return None
|
|
158
|
+
names = [line.strip() for line in remotes.splitlines() if line.strip()]
|
|
159
|
+
if len(names) == 1:
|
|
160
|
+
return names[0]
|
|
161
|
+
if "origin" in names:
|
|
162
|
+
return "origin"
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _matching_pr_metadata(repo: Path, head: Any, default_remote: str | None) -> dict[str, Any]:
|
|
167
|
+
if not isinstance(head, str) or not head:
|
|
168
|
+
return {}
|
|
169
|
+
default_branch = default_remote.removeprefix("origin/") if default_remote else "main"
|
|
170
|
+
for ref in _remote_refs_at_head(repo, head):
|
|
171
|
+
if ref == f"origin/{default_branch}":
|
|
172
|
+
continue
|
|
173
|
+
if not ref.startswith("origin/"):
|
|
174
|
+
continue
|
|
175
|
+
metadata = _gh_pr_metadata(repo, ref.removeprefix("origin/"))
|
|
176
|
+
if metadata:
|
|
177
|
+
metadata["matching_remote_ref"] = ref
|
|
178
|
+
return metadata
|
|
179
|
+
return {}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _remote_refs_at_head(repo: Path, head: str) -> list[str]:
|
|
183
|
+
refs = _git(repo, "for-each-ref", "--format=%(refname:short) %(objectname)", "refs/remotes/origin")
|
|
184
|
+
if not refs:
|
|
185
|
+
return []
|
|
186
|
+
matches: list[str] = []
|
|
187
|
+
for line in refs.splitlines():
|
|
188
|
+
parts = line.split()
|
|
189
|
+
if len(parts) == 2 and parts[1] == head:
|
|
190
|
+
matches.append(parts[0])
|
|
191
|
+
return matches
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _gh_pr_metadata(repo: Path, branch: str) -> dict[str, Any]:
|
|
195
|
+
payload = _gh(repo, "pr", "list", "--head", branch, "--state", "open", "--limit", "1", "--json", "number,url,headRefName,baseRefName,reviewDecision,mergeable,isDraft")
|
|
196
|
+
if not payload:
|
|
197
|
+
return {}
|
|
198
|
+
try:
|
|
199
|
+
import json
|
|
200
|
+
|
|
201
|
+
prs = json.loads(payload)
|
|
202
|
+
except Exception:
|
|
203
|
+
return {}
|
|
204
|
+
if not isinstance(prs, list) or not prs:
|
|
205
|
+
return {}
|
|
206
|
+
pr = prs[0]
|
|
207
|
+
if not isinstance(pr, dict):
|
|
208
|
+
return {}
|
|
209
|
+
return {
|
|
210
|
+
"pr_number": pr.get("number"),
|
|
211
|
+
"pr_url": pr.get("url"),
|
|
212
|
+
"pr_head_ref": pr.get("headRefName"),
|
|
213
|
+
"pr_base_ref": pr.get("baseRefName"),
|
|
214
|
+
"pr_review_decision": pr.get("reviewDecision"),
|
|
215
|
+
"pr_mergeable": pr.get("mergeable"),
|
|
216
|
+
"pr_is_draft": pr.get("isDraft"),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _git(repo: Path, *args: str) -> str | None:
|
|
221
|
+
try:
|
|
222
|
+
completed = subprocess.run(
|
|
223
|
+
["git", "-C", str(repo), *args],
|
|
224
|
+
check=False,
|
|
225
|
+
stdout=subprocess.PIPE,
|
|
226
|
+
stderr=subprocess.DEVNULL,
|
|
227
|
+
text=True,
|
|
228
|
+
timeout=5,
|
|
229
|
+
)
|
|
230
|
+
except Exception:
|
|
231
|
+
return None
|
|
232
|
+
if completed.returncode != 0:
|
|
233
|
+
return None
|
|
234
|
+
return completed.stdout.strip() or None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _gh(repo: Path, *args: str) -> str | None:
|
|
238
|
+
try:
|
|
239
|
+
completed = subprocess.run(
|
|
240
|
+
["gh", *args],
|
|
241
|
+
cwd=repo,
|
|
242
|
+
check=False,
|
|
243
|
+
stdout=subprocess.PIPE,
|
|
244
|
+
stderr=subprocess.DEVNULL,
|
|
245
|
+
text=True,
|
|
246
|
+
timeout=5,
|
|
247
|
+
)
|
|
248
|
+
except Exception:
|
|
249
|
+
return None
|
|
250
|
+
if completed.returncode != 0:
|
|
251
|
+
return None
|
|
252
|
+
return completed.stdout.strip() or None
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Governed pre-merge review bundle (model-routing-v2 P1, IF-0-P1-1).
|
|
2
|
+
|
|
3
|
+
The artifact the panel reviews at the pre-merge gate is a *review bundle*, not a
|
|
4
|
+
bare diff: the EXACT staged diff the closeout is about to commit, plus the plan's
|
|
5
|
+
``## Acceptance Criteria``, the verification-command results, and a one-paragraph
|
|
6
|
+
closeout summary. The panel needs the change *and* the spec to judge it against.
|
|
7
|
+
|
|
8
|
+
The gate fires INSIDE ``_perform_phase_closeout`` — after ``git add`` stages the
|
|
9
|
+
owned paths and before the commit is finalized — and passes the staged-index diff
|
|
10
|
+
(``git diff --cached``) in here. So "what the panel reviews" == "what gets
|
|
11
|
+
committed" by construction: no separately-discovered path set to diverge from
|
|
12
|
+
closeout, no untracked-file synthesis, and the renderer never writes a file into
|
|
13
|
+
the repo (advisor-panel reconciliation — the prior independent path discovery and
|
|
14
|
+
in-repo staging were the divergence + worktree-dirtying defects).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import subprocess
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Mapping, Sequence
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def staged_index_diff(repo: Path, paths: Sequence[str]) -> str:
|
|
24
|
+
"""The EXACT diff about to be committed: ``git diff --cached -- <paths>``.
|
|
25
|
+
|
|
26
|
+
After ``git add``, newly-added files appear natively in the cached diff (no
|
|
27
|
+
``--no-index`` synthesis, no untracked probe). This is the staged index, so it
|
|
28
|
+
cannot diverge from what the commit records.
|
|
29
|
+
"""
|
|
30
|
+
if not paths:
|
|
31
|
+
return "(no staged paths)"
|
|
32
|
+
try:
|
|
33
|
+
out = subprocess.run(
|
|
34
|
+
["git", "-C", str(repo), "diff", "--cached", "--", *paths],
|
|
35
|
+
capture_output=True, text=True, timeout=30,
|
|
36
|
+
)
|
|
37
|
+
except Exception:
|
|
38
|
+
return "(staged diff unavailable)"
|
|
39
|
+
return out.stdout.rstrip() or "(empty staged diff)"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _acceptance_criteria(plan_path: str | Path | None) -> str:
|
|
43
|
+
if not plan_path:
|
|
44
|
+
return "(no plan path)"
|
|
45
|
+
try:
|
|
46
|
+
text = Path(plan_path).read_text(encoding="utf-8")
|
|
47
|
+
except OSError:
|
|
48
|
+
return "(plan unreadable)"
|
|
49
|
+
lines = text.splitlines()
|
|
50
|
+
out: list[str] = []
|
|
51
|
+
capture = False
|
|
52
|
+
for line in lines:
|
|
53
|
+
if line.lstrip().lower().startswith("## acceptance"):
|
|
54
|
+
capture = True
|
|
55
|
+
continue
|
|
56
|
+
if capture and line.startswith("## "):
|
|
57
|
+
break
|
|
58
|
+
if capture:
|
|
59
|
+
out.append(line)
|
|
60
|
+
body = "\n".join(out).strip()
|
|
61
|
+
return body or "(no acceptance-criteria section found)"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _verification_results(terminal: Mapping[str, Any]) -> str:
|
|
65
|
+
# The terminal summary records per-command verification under
|
|
66
|
+
# `verification_commands` (list[dict]); `build_terminal_summary` never emits
|
|
67
|
+
# `verification`/`verification_results`, so the old keys always missed and the
|
|
68
|
+
# panel saw "no results recorded" even when commands ran (code-review finding,
|
|
69
|
+
# verified). Accept the real key first; keep the legacy keys as a tolerant
|
|
70
|
+
# fallback for hand-built terminals in tests.
|
|
71
|
+
rows = terminal.get("verification_commands") or ()
|
|
72
|
+
if not rows:
|
|
73
|
+
results = terminal.get("verification") if isinstance(terminal.get("verification"), Mapping) else None
|
|
74
|
+
if isinstance(results, Mapping):
|
|
75
|
+
rows = results.get("results") or results.get("commands") or ()
|
|
76
|
+
else:
|
|
77
|
+
rows = terminal.get("verification_results") or ()
|
|
78
|
+
if not rows:
|
|
79
|
+
unit = terminal.get("latest_verification_unit")
|
|
80
|
+
status = (
|
|
81
|
+
(unit.get("status") if isinstance(unit, Mapping) else None)
|
|
82
|
+
or terminal.get("verification_status")
|
|
83
|
+
or "unknown"
|
|
84
|
+
)
|
|
85
|
+
return f"verification_status: {status} (no per-command results recorded)"
|
|
86
|
+
lines = []
|
|
87
|
+
for r in rows:
|
|
88
|
+
if isinstance(r, Mapping):
|
|
89
|
+
lines.append(f"- {r.get('code') or r.get('command') or r}: {r.get('status') or r.get('ok')}")
|
|
90
|
+
else:
|
|
91
|
+
lines.append(f"- {r}")
|
|
92
|
+
return "\n".join(lines)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _summary(terminal: Mapping[str, Any]) -> str:
|
|
96
|
+
return str(
|
|
97
|
+
terminal.get("next_action")
|
|
98
|
+
or terminal.get("terminal_status")
|
|
99
|
+
or "(no summary)"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def render_governed_bundle(
|
|
104
|
+
*,
|
|
105
|
+
phase_alias: str,
|
|
106
|
+
terminal: Mapping[str, Any],
|
|
107
|
+
plan_path: str | Path | None,
|
|
108
|
+
diff_text: str,
|
|
109
|
+
) -> str:
|
|
110
|
+
"""Render the review bundle text from a PRECOMPUTED staged-index diff.
|
|
111
|
+
|
|
112
|
+
The caller (the in-closeout gate) computes ``diff_text`` via
|
|
113
|
+
``staged_index_diff`` over the exact paths it is committing, so the bundle
|
|
114
|
+
cannot diverge from the commit. The renderer never touches git or the
|
|
115
|
+
filesystem; ``panel_invoker`` stages the bundle as material in a temp dir
|
|
116
|
+
OUTSIDE the repo, so the gate never dirties the worktree under review.
|
|
117
|
+
"""
|
|
118
|
+
return "\n".join(
|
|
119
|
+
[
|
|
120
|
+
f"# Governed pre-merge review — phase {phase_alias}",
|
|
121
|
+
"",
|
|
122
|
+
"## Change (staged diff — exactly what will be committed)",
|
|
123
|
+
"```diff",
|
|
124
|
+
diff_text, # producer (staged_index_diff) owns the empty/unavailable sentinel
|
|
125
|
+
"```",
|
|
126
|
+
"",
|
|
127
|
+
"## Acceptance Criteria (the spec to judge against)",
|
|
128
|
+
_acceptance_criteria(plan_path),
|
|
129
|
+
"",
|
|
130
|
+
"## Verification results",
|
|
131
|
+
_verification_results(terminal),
|
|
132
|
+
"",
|
|
133
|
+
"## Summary",
|
|
134
|
+
_summary(terminal),
|
|
135
|
+
"",
|
|
136
|
+
]
|
|
137
|
+
)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Implementation escalation ladder + governed pre-merge review loop
|
|
2
|
+
(model-routing-v1 P3).
|
|
3
|
+
|
|
4
|
+
Two behaviors, both **mode-branched** and **non-human terminal**:
|
|
5
|
+
|
|
6
|
+
1. ``next_escalation`` — the failure ladder. On repeated failure the model_class
|
|
7
|
+
escalates ``implementer → planner``; a still-failing planner then forks by
|
|
8
|
+
run_mode: ``governed`` invokes the panel, ``autonomous`` terminates as a
|
|
9
|
+
**repairable non-human blocker** (``repeated_verification_failure``, never
|
|
10
|
+
``human_required``).
|
|
11
|
+
|
|
12
|
+
2. ``run_governed_premerge_loop`` — the bounded pre-merge gate. In ``governed``
|
|
13
|
+
mode it runs panel review → fix → re-review, capped at ``max_rounds`` (default
|
|
14
|
+
3). Zero ``block`` findings ⇒ mergeable (``nit`` findings recorded, non-gating).
|
|
15
|
+
Non-convergence — or losing the panel while still failing — terminates as a
|
|
16
|
+
non-human ``review_gate_block`` + halt, surfaced in the run-end summary. In
|
|
17
|
+
``autonomous`` mode the loop does NOT run and never spawns a panel.
|
|
18
|
+
|
|
19
|
+
These are pure, runner-importable functions with a mockable panel seam; nothing
|
|
20
|
+
here adds ``human_required`` and the autonomous path is a literal no-op.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import Callable, Sequence
|
|
26
|
+
|
|
27
|
+
from .closeout_validators import ReviewFinding
|
|
28
|
+
from .governed_review import GateResult, governed_planning_gate
|
|
29
|
+
from .panel_invoker import invoke_panel
|
|
30
|
+
|
|
31
|
+
#: A class is escalated once it has failed this many times (tests or patch retries).
|
|
32
|
+
ESCALATION_FAIL_THRESHOLD = 2
|
|
33
|
+
#: Default cap on governed pre-merge review→fix rounds (bounded; never infinite).
|
|
34
|
+
DEFAULT_MAX_REVIEW_ROUNDS = 3
|
|
35
|
+
|
|
36
|
+
# implementer → planner is the only model_class escalation; planner is terminal.
|
|
37
|
+
_NEXT_CLASS = {"implementer": "planner", "worker": "implementer"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _non_human_blocker(blocker_class: str, summary: str) -> dict:
|
|
41
|
+
"""Every terminal in this module is a repairable, non-human blocker."""
|
|
42
|
+
return {
|
|
43
|
+
"human_required": False,
|
|
44
|
+
"blocker_class": blocker_class,
|
|
45
|
+
"blocker_summary": summary,
|
|
46
|
+
"required_human_inputs": (),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class EscalationDecision:
|
|
52
|
+
action: str # "retry" | "escalate_class" | "invoke_panel" | "terminal_blocker"
|
|
53
|
+
model_class: str # the class to use for the next attempt
|
|
54
|
+
run_mode: str
|
|
55
|
+
reason: str
|
|
56
|
+
blocker: dict | None = None # set iff action == "terminal_blocker" (non-human)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def next_escalation(
|
|
60
|
+
*,
|
|
61
|
+
model_class: str,
|
|
62
|
+
failed_tests: int = 0,
|
|
63
|
+
patch_retries: int = 0,
|
|
64
|
+
run_mode: str = "autonomous",
|
|
65
|
+
) -> EscalationDecision:
|
|
66
|
+
"""The mode-branched failure ladder.
|
|
67
|
+
|
|
68
|
+
Below threshold → ``retry`` (same class). At/over threshold:
|
|
69
|
+
``implementer → planner`` (escalate_class); a failing ``planner`` forks by
|
|
70
|
+
run_mode — ``governed`` ⇒ ``invoke_panel``, ``autonomous`` ⇒ non-human
|
|
71
|
+
``terminal_blocker``.
|
|
72
|
+
"""
|
|
73
|
+
failing = failed_tests >= ESCALATION_FAIL_THRESHOLD or patch_retries >= ESCALATION_FAIL_THRESHOLD
|
|
74
|
+
if not failing:
|
|
75
|
+
return EscalationDecision(
|
|
76
|
+
action="retry", model_class=model_class, run_mode=run_mode,
|
|
77
|
+
reason="below escalation threshold",
|
|
78
|
+
)
|
|
79
|
+
nxt = _NEXT_CLASS.get(model_class)
|
|
80
|
+
if nxt is not None:
|
|
81
|
+
return EscalationDecision(
|
|
82
|
+
action="escalate_class", model_class=nxt, run_mode=run_mode,
|
|
83
|
+
reason=f"{model_class} failed threshold → escalate to {nxt}",
|
|
84
|
+
)
|
|
85
|
+
# planner (terminal class) still failing → branch by mode.
|
|
86
|
+
if run_mode == "governed":
|
|
87
|
+
return EscalationDecision(
|
|
88
|
+
action="invoke_panel", model_class=model_class, run_mode=run_mode,
|
|
89
|
+
reason="planner tier failing in governed mode → invoke advisor panel",
|
|
90
|
+
)
|
|
91
|
+
return EscalationDecision(
|
|
92
|
+
action="terminal_blocker", model_class=model_class, run_mode=run_mode,
|
|
93
|
+
reason="planner tier failing in autonomous mode → non-human terminal",
|
|
94
|
+
blocker=_non_human_blocker(
|
|
95
|
+
"repeated_verification_failure",
|
|
96
|
+
"implementer→planner escalation exhausted; autonomous run halts as a "
|
|
97
|
+
"repairable non-human blocker (no panel, no human_required)",
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class LoopResult:
|
|
104
|
+
mergeable: bool
|
|
105
|
+
ran: bool
|
|
106
|
+
rounds: int = 0
|
|
107
|
+
findings: tuple[ReviewFinding, ...] = ()
|
|
108
|
+
degraded: bool = False
|
|
109
|
+
terminal_blocker: dict | None = None
|
|
110
|
+
reason: str | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def run_governed_premerge_loop(
|
|
114
|
+
*,
|
|
115
|
+
artifact: str,
|
|
116
|
+
author_executor: str,
|
|
117
|
+
run_mode: str,
|
|
118
|
+
author_vendors: Sequence[str] | None = None,
|
|
119
|
+
apply_fix: Callable[[int, str, tuple[ReviewFinding, ...]], str] | None = None,
|
|
120
|
+
available_legs: Sequence[str] | None = None,
|
|
121
|
+
invoke: Callable[..., GateResult] = governed_planning_gate,
|
|
122
|
+
spawn=None,
|
|
123
|
+
repo_dir=None,
|
|
124
|
+
max_rounds: int = DEFAULT_MAX_REVIEW_ROUNDS,
|
|
125
|
+
max_concurrency: int | None = None,
|
|
126
|
+
) -> LoopResult:
|
|
127
|
+
"""Bounded governed pre-merge review loop.
|
|
128
|
+
|
|
129
|
+
Autonomous mode is a no-op (mergeable, no panel). Governed mode reviews up to
|
|
130
|
+
``max_rounds`` times, applying ``apply_fix`` between rounds while ``block``
|
|
131
|
+
findings remain. Terminals are always non-human. ``author_vendors`` (the set
|
|
132
|
+
of vendors that authored the phase) takes precedence over ``author_executor``
|
|
133
|
+
for reviewer≠author exclusion when provided.
|
|
134
|
+
"""
|
|
135
|
+
if run_mode != "governed":
|
|
136
|
+
return LoopResult(mergeable=True, ran=False, reason="autonomous")
|
|
137
|
+
|
|
138
|
+
# Reasons that mean "the gate could not run a real review" (no disjoint
|
|
139
|
+
# reviewer / unknown author / no usable verdict) — surfaced verbatim in the
|
|
140
|
+
# terminal so the operator sees the ACCURATE cause, not a generic
|
|
141
|
+
# "non_convergence" (CR finding).
|
|
142
|
+
_STRUCTURAL_HOLD = frozenset({
|
|
143
|
+
"unknown_author", "no_disjoint_reviewer", "author_vendor_only",
|
|
144
|
+
"no_reviewers", "no_usable_review",
|
|
145
|
+
})
|
|
146
|
+
seen_block = False
|
|
147
|
+
current = artifact
|
|
148
|
+
collected: list[ReviewFinding] = []
|
|
149
|
+
rnd = 0
|
|
150
|
+
last_reason: str | None = None
|
|
151
|
+
for rnd in range(1, max_rounds + 1):
|
|
152
|
+
invoke_kwargs = {
|
|
153
|
+
"artifact": current,
|
|
154
|
+
"author_executor": author_executor,
|
|
155
|
+
"author_vendors": author_vendors,
|
|
156
|
+
"run_mode": "governed",
|
|
157
|
+
"available_legs": available_legs,
|
|
158
|
+
"spawn": spawn,
|
|
159
|
+
}
|
|
160
|
+
if repo_dir is not None:
|
|
161
|
+
invoke_kwargs["repo_dir"] = repo_dir
|
|
162
|
+
# Parallel by default; threaded only when a caller requests a cap/sequential
|
|
163
|
+
# (byte-neutral for the default path + a strict-signature custom ``invoke``).
|
|
164
|
+
if max_concurrency is not None:
|
|
165
|
+
invoke_kwargs["max_concurrency"] = max_concurrency
|
|
166
|
+
gate = invoke(**invoke_kwargs)
|
|
167
|
+
collected.extend(gate.findings)
|
|
168
|
+
last_reason = gate.reason
|
|
169
|
+
|
|
170
|
+
if gate.degraded:
|
|
171
|
+
# FAIL-CLOSED (advisor-panel reconciliation): in governed mode, "no
|
|
172
|
+
# usable disjoint reviewer" is NOT an advisory pass — the prior
|
|
173
|
+
# advisory-pass-before-any-block was a fail-open (a codex-empty phase
|
|
174
|
+
# whose only disjoint reviewer was offline both rendered an empty diff
|
|
175
|
+
# AND advisory-passed). The gate now blocks on no-usable-review
|
|
176
|
+
# directly (promoted=False), so reaching here means a degraded result
|
|
177
|
+
# we treat as a non-human halt regardless of `seen_block`.
|
|
178
|
+
return LoopResult(
|
|
179
|
+
mergeable=False, ran=True, rounds=rnd, findings=tuple(collected),
|
|
180
|
+
terminal_blocker=_non_human_blocker(
|
|
181
|
+
"review_gate_block",
|
|
182
|
+
f"governed pre-merge review could not obtain a usable disjoint "
|
|
183
|
+
f"reviewer after {rnd} round(s); halting (non-human)",
|
|
184
|
+
),
|
|
185
|
+
reason=gate.reason or "panel_unavailable",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if gate.promoted: # zero block findings; any nits already in `collected`
|
|
189
|
+
return LoopResult(
|
|
190
|
+
mergeable=True, ran=True, rounds=rnd, findings=tuple(collected),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Unresolved block findings this round.
|
|
194
|
+
seen_block = True
|
|
195
|
+
if apply_fix is None or rnd == max_rounds:
|
|
196
|
+
break
|
|
197
|
+
current = apply_fix(rnd, current, gate.findings)
|
|
198
|
+
|
|
199
|
+
# Fell out of the loop while held. Distinguish a fail-closed STRUCTURAL hold
|
|
200
|
+
# (no disjoint reviewer / unknown author / no usable verdict — the review never
|
|
201
|
+
# really ran) from genuine NON-CONVERGENCE (real block findings unresolved), so
|
|
202
|
+
# the terminal carries the accurate cause instead of always "non_convergence".
|
|
203
|
+
if last_reason in _STRUCTURAL_HOLD:
|
|
204
|
+
summary = (
|
|
205
|
+
f"governed pre-merge review held ({last_reason}) — no real review could "
|
|
206
|
+
f"run after {rnd} round(s); halting (non-human, surfaced in run-end summary)"
|
|
207
|
+
)
|
|
208
|
+
reason = last_reason
|
|
209
|
+
else:
|
|
210
|
+
summary = (
|
|
211
|
+
f"governed pre-merge review did not converge to zero block findings "
|
|
212
|
+
f"after {rnd} round(s); halting (non-human, surfaced in run-end summary)"
|
|
213
|
+
)
|
|
214
|
+
reason = "non_convergence"
|
|
215
|
+
return LoopResult(
|
|
216
|
+
mergeable=False, ran=True, rounds=rnd, findings=tuple(collected),
|
|
217
|
+
terminal_blocker=_non_human_blocker("review_gate_block", summary),
|
|
218
|
+
reason=reason,
|
|
219
|
+
)
|