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,120 @@
|
|
|
1
|
+
"""GATE (roadmap v40) — sensitivity classifier for beyond-ownership dirty paths.
|
|
2
|
+
|
|
3
|
+
`classify_unowned_path(repo_relpath)` maps a repo-relative path to a
|
|
4
|
+
``SensitivityVerdict`` whose ``sensitivity_class`` is a member of
|
|
5
|
+
``models.SENSITIVITY_CLASSES``. The graduated closeout gate auto-commits a
|
|
6
|
+
verified beyond-ownership path only when its verdict is ``safe``; everything else
|
|
7
|
+
blocks (deny-by-default).
|
|
8
|
+
|
|
9
|
+
Precedence is load-bearing and deny-by-default:
|
|
10
|
+
1. UNSAFE-specific patterns first — secrets, lockfiles, CI config. These must win
|
|
11
|
+
over any broad SAFE rule (e.g. a ``.github/workflows/*.yml`` is CI, not docs).
|
|
12
|
+
2. tests → ``source`` (UNSAFE). Test paths only ever earn owned status via
|
|
13
|
+
structural sibling matching upstream; a test reaching this classifier failed
|
|
14
|
+
that and must not auto-commit.
|
|
15
|
+
3. narrow SAFE rules — plans, handoffs, docs, and a *tight* config_nonsource
|
|
16
|
+
allowlist (never a ``.toml``/``.yaml``/``.json`` suffix rule).
|
|
17
|
+
4. fall through → ``source`` (UNSAFE). Unmatched is never SAFE.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from pathlib import PurePosixPath
|
|
24
|
+
|
|
25
|
+
from .models import SAFE_SENSITIVITY_CLASSES
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class SensitivityVerdict:
|
|
30
|
+
sensitivity_class: str
|
|
31
|
+
safe: bool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Tight allowlists — membership, not broad suffix rules.
|
|
35
|
+
_CONFIG_NONSOURCE_NAMES = frozenset(
|
|
36
|
+
{".gitignore", ".gitattributes", ".editorconfig", ".dockerignore", ".npmrc", ".prettierrc"}
|
|
37
|
+
)
|
|
38
|
+
_CONFIG_NONSOURCE_SUFFIXES = frozenset({".cfg", ".ini"})
|
|
39
|
+
|
|
40
|
+
_SECRET_SUFFIXES = frozenset({".pem", ".key", ".p12", ".pfx", ".crt", ".keystore", ".jks"})
|
|
41
|
+
_LOCKFILE_NAMES = frozenset(
|
|
42
|
+
{
|
|
43
|
+
"package-lock.json",
|
|
44
|
+
"pnpm-lock.yaml",
|
|
45
|
+
"yarn.lock",
|
|
46
|
+
"uv.lock",
|
|
47
|
+
"poetry.lock",
|
|
48
|
+
"cargo.lock",
|
|
49
|
+
"go.sum",
|
|
50
|
+
"gemfile.lock",
|
|
51
|
+
"composer.lock",
|
|
52
|
+
"requirements.txt",
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
# Bare suffix → docs only for unambiguous documentation formats. A `.txt` is NOT
|
|
56
|
+
# auto-docs (e.g. src/foo.txt is source-adjacent); it is docs only under docs/.
|
|
57
|
+
_DOC_SUFFIXES = frozenset({".md", ".rst"})
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _verdict(sensitivity_class: str) -> SensitivityVerdict:
|
|
61
|
+
return SensitivityVerdict(
|
|
62
|
+
sensitivity_class=sensitivity_class,
|
|
63
|
+
safe=sensitivity_class in SAFE_SENSITIVITY_CLASSES,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def classify_unowned_path(repo_relpath: str) -> SensitivityVerdict:
|
|
68
|
+
raw = (repo_relpath or "").strip()
|
|
69
|
+
# Normalize: strip leading "./", lowercase for matching.
|
|
70
|
+
norm = raw[2:] if raw.startswith("./") else raw
|
|
71
|
+
lower = norm.lower()
|
|
72
|
+
posix = PurePosixPath(lower)
|
|
73
|
+
name = posix.name
|
|
74
|
+
suffix = posix.suffix
|
|
75
|
+
parts = posix.parts
|
|
76
|
+
slashed = "/" + lower # so "/tests/" infix matches a leading "tests/" too
|
|
77
|
+
|
|
78
|
+
# --- 1. UNSAFE-specific patterns first (precedence) ---
|
|
79
|
+
# secrets
|
|
80
|
+
if (
|
|
81
|
+
name == ".env"
|
|
82
|
+
or name.startswith(".env.")
|
|
83
|
+
or suffix in _SECRET_SUFFIXES
|
|
84
|
+
or "secrets" in parts
|
|
85
|
+
):
|
|
86
|
+
return _verdict("secrets")
|
|
87
|
+
# lockfiles
|
|
88
|
+
if name in _LOCKFILE_NAMES or name.endswith(".lock") or name.endswith("-lock.json"):
|
|
89
|
+
return _verdict("lockfile")
|
|
90
|
+
# CI config
|
|
91
|
+
if (
|
|
92
|
+
any(part in {".github", ".gitlab", ".circleci", ".gitea"} for part in parts)
|
|
93
|
+
or lower.startswith("ci/")
|
|
94
|
+
or "/workflows/" in slashed
|
|
95
|
+
):
|
|
96
|
+
return _verdict("ci")
|
|
97
|
+
# tests → source (UNSAFE) — GATE decision (see plan/IF-0-GATE-1)
|
|
98
|
+
if (
|
|
99
|
+
"/tests/" in slashed
|
|
100
|
+
or "__tests__" in parts
|
|
101
|
+
or "__fixtures__" in parts
|
|
102
|
+
or name.startswith("test_")
|
|
103
|
+
or name.endswith("_test.py")
|
|
104
|
+
or ".test." in name
|
|
105
|
+
or ".spec." in name
|
|
106
|
+
):
|
|
107
|
+
return _verdict("source")
|
|
108
|
+
|
|
109
|
+
# --- 2. narrow SAFE rules ---
|
|
110
|
+
if lower.startswith("plans/"):
|
|
111
|
+
return _verdict("plans")
|
|
112
|
+
if ".dev-skills/handoffs/" in slashed:
|
|
113
|
+
return _verdict("handoffs")
|
|
114
|
+
if "/docs/" in slashed or name == "readme.md" or suffix in _DOC_SUFFIXES:
|
|
115
|
+
return _verdict("docs")
|
|
116
|
+
if name in _CONFIG_NONSOURCE_NAMES or suffix in _CONFIG_NONSOURCE_SUFFIXES:
|
|
117
|
+
return _verdict("config_nonsource")
|
|
118
|
+
|
|
119
|
+
# --- 3. deny-by-default: everything else (incl. .py/.toml/.yaml/.json/.sh) ---
|
|
120
|
+
return _verdict("source")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
AUDIT_STATUSES = ("passed", "drift_detected", "skipped")
|
|
10
|
+
MAX_CLAIMS = 32
|
|
11
|
+
MAX_DIFF_PATHS = 64
|
|
12
|
+
|
|
13
|
+
CLAIM_RE = re.compile(r"\b(?:Added|Created|Wrote|Updated)\s+`([^`]+)`", re.IGNORECASE)
|
|
14
|
+
BULLET_CLAIM_RE = re.compile(r"^\s*[-*]\s+[A-Za-z][A-Za-z0-9_-]*\s+`([^`]+)`", re.MULTILINE)
|
|
15
|
+
IDENTIFIER_RE = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class AuditResult:
|
|
20
|
+
matched_claims: list[str]
|
|
21
|
+
unmatched_claims: list[str]
|
|
22
|
+
audit_status: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def audit_closeout_evidence(commit_sha: str, phase_alias: str, repo_root: Path) -> AuditResult:
|
|
26
|
+
commit_body = _git_output(repo_root, "log", "-1", "--format=%B", commit_sha)
|
|
27
|
+
claims = _parse_claims(commit_body)
|
|
28
|
+
if not claims:
|
|
29
|
+
return AuditResult(matched_claims=[], unmatched_claims=[], audit_status="skipped")
|
|
30
|
+
|
|
31
|
+
diff_paths = _diff_paths(repo_root, commit_sha)
|
|
32
|
+
diff_body = _git_output(repo_root, "show", "--format=", "--unified=0", commit_sha)
|
|
33
|
+
matched: list[str] = []
|
|
34
|
+
unmatched: list[str] = []
|
|
35
|
+
for claim in claims:
|
|
36
|
+
if _claim_matches_evidence(claim, diff_paths, diff_body):
|
|
37
|
+
matched.append(claim)
|
|
38
|
+
else:
|
|
39
|
+
unmatched.append(claim)
|
|
40
|
+
|
|
41
|
+
return AuditResult(
|
|
42
|
+
matched_claims=matched,
|
|
43
|
+
unmatched_claims=unmatched,
|
|
44
|
+
audit_status="drift_detected" if unmatched else "passed",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _parse_claims(text: str) -> list[str]:
|
|
49
|
+
claims: list[str] = []
|
|
50
|
+
for pattern in (CLAIM_RE, BULLET_CLAIM_RE):
|
|
51
|
+
for match in pattern.finditer(text):
|
|
52
|
+
claim = match.group(1).strip()
|
|
53
|
+
if claim and claim not in claims:
|
|
54
|
+
claims.append(claim)
|
|
55
|
+
if len(claims) >= MAX_CLAIMS:
|
|
56
|
+
return claims
|
|
57
|
+
return claims
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _diff_paths(repo_root: Path, commit_sha: str) -> list[str]:
|
|
61
|
+
names = _git_output(repo_root, "show", "--format=", "--name-only", commit_sha)
|
|
62
|
+
paths: list[str] = []
|
|
63
|
+
for line in names.splitlines():
|
|
64
|
+
path = line.strip()
|
|
65
|
+
if not path or path in paths:
|
|
66
|
+
continue
|
|
67
|
+
paths.append(path)
|
|
68
|
+
if len(paths) >= MAX_DIFF_PATHS:
|
|
69
|
+
break
|
|
70
|
+
return paths
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _claim_matches_evidence(claim: str, diff_paths: list[str], diff_body: str) -> bool:
|
|
74
|
+
normalized = claim.strip().lstrip("/")
|
|
75
|
+
if not normalized:
|
|
76
|
+
return False
|
|
77
|
+
normalized_posix = normalized.replace("\\", "/")
|
|
78
|
+
claim_path = PurePosixPath(normalized_posix)
|
|
79
|
+
claim_basename = claim_path.name
|
|
80
|
+
claim_stem = claim_path.stem
|
|
81
|
+
for path in diff_paths:
|
|
82
|
+
path_posix = path.replace("\\", "/")
|
|
83
|
+
path_path = PurePosixPath(path_posix)
|
|
84
|
+
if path_posix.endswith(normalized_posix) or path_posix.endswith(f"/{normalized_posix}"):
|
|
85
|
+
return True
|
|
86
|
+
if claim_basename and claim_basename == path_path.name:
|
|
87
|
+
return True
|
|
88
|
+
if claim_stem and claim_stem == path_path.stem:
|
|
89
|
+
return True
|
|
90
|
+
return any(identifier in diff_body for identifier in _identifier_candidates(claim))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _identifier_candidates(claim: str) -> list[str]:
|
|
94
|
+
candidates: list[str] = []
|
|
95
|
+
for token in IDENTIFIER_RE.findall(claim):
|
|
96
|
+
if len(token) < 4:
|
|
97
|
+
continue
|
|
98
|
+
if not any(char.isupper() for char in token) and "_" not in token:
|
|
99
|
+
continue
|
|
100
|
+
if token not in candidates:
|
|
101
|
+
candidates.append(token)
|
|
102
|
+
return candidates
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _git_output(repo_root: Path, *args: str) -> str:
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
["git", "-C", str(repo_root), *args],
|
|
108
|
+
text=True,
|
|
109
|
+
capture_output=True,
|
|
110
|
+
check=True,
|
|
111
|
+
)
|
|
112
|
+
return result.stdout
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
IF_GATE_RE = re.compile(r"\bIF-\d+-[A-Z0-9_-]+-\d+\b")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ValidationResult:
|
|
14
|
+
ok: bool
|
|
15
|
+
warning: str | None = None
|
|
16
|
+
blocker_class: str | None = None
|
|
17
|
+
blocker_summary: str | None = None
|
|
18
|
+
expected_gates: tuple[str, ...] = ()
|
|
19
|
+
produced_gates: tuple[str, ...] = ()
|
|
20
|
+
missing_gates: tuple[str, ...] = ()
|
|
21
|
+
unexpected_gates: tuple[str, ...] = ()
|
|
22
|
+
|
|
23
|
+
def to_json(self) -> dict[str, Any]:
|
|
24
|
+
return {
|
|
25
|
+
"ok": self.ok,
|
|
26
|
+
"warning": self.warning,
|
|
27
|
+
"blocker_class": self.blocker_class,
|
|
28
|
+
"blocker_summary": self.blocker_summary,
|
|
29
|
+
"expected_gates": list(self.expected_gates),
|
|
30
|
+
"produced_gates": list(self.produced_gates),
|
|
31
|
+
"missing_gates": list(self.missing_gates),
|
|
32
|
+
"unexpected_gates": list(self.unexpected_gates),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def extract_plan_produces(plan_path: Path) -> tuple[str, ...]:
|
|
37
|
+
try:
|
|
38
|
+
text = plan_path.read_text(encoding="utf-8")
|
|
39
|
+
except OSError:
|
|
40
|
+
return ()
|
|
41
|
+
|
|
42
|
+
gates: list[str] = []
|
|
43
|
+
for line in text.splitlines():
|
|
44
|
+
stripped = line.strip()
|
|
45
|
+
if not (
|
|
46
|
+
stripped.startswith("**Produces**:")
|
|
47
|
+
or stripped.startswith("Produces:")
|
|
48
|
+
or "**Interfaces provided**:" in stripped
|
|
49
|
+
):
|
|
50
|
+
continue
|
|
51
|
+
for match in IF_GATE_RE.finditer(line):
|
|
52
|
+
gate = match.group(0)
|
|
53
|
+
if gate not in gates:
|
|
54
|
+
gates.append(gate)
|
|
55
|
+
return tuple(gates)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def validate_produced_gates(plan_path: Path, closeout_payload: dict[str, Any]) -> ValidationResult:
|
|
59
|
+
terminal_status = str(
|
|
60
|
+
closeout_payload.get("terminal_status")
|
|
61
|
+
or closeout_payload.get("automation_status")
|
|
62
|
+
or ""
|
|
63
|
+
)
|
|
64
|
+
expected = extract_plan_produces(plan_path)
|
|
65
|
+
if "produced_if_gates" not in closeout_payload:
|
|
66
|
+
if terminal_status == "complete":
|
|
67
|
+
return ValidationResult(
|
|
68
|
+
ok=True,
|
|
69
|
+
warning="produced_if_gates missing during NATIVE compatibility window",
|
|
70
|
+
expected_gates=expected,
|
|
71
|
+
)
|
|
72
|
+
return ValidationResult(ok=True, expected_gates=expected)
|
|
73
|
+
|
|
74
|
+
produced_raw = _normalize_gates(closeout_payload.get("produced_if_gates"))
|
|
75
|
+
# Filter to canonical IF-gate tokens. Executors sometimes emit free-text
|
|
76
|
+
# description sentences in produced_if_gates (e.g. when the plan declares
|
|
77
|
+
# no IF gate, codex has been observed outputting
|
|
78
|
+
# ["...verified...; active plan declares no interface-freeze gate"]).
|
|
79
|
+
# Treat non-IF-token entries as commentary, not as produced gates.
|
|
80
|
+
produced = tuple(g for g in produced_raw if IF_GATE_RE.fullmatch(g))
|
|
81
|
+
non_gate_chatter = tuple(g for g in produced_raw if not IF_GATE_RE.fullmatch(g))
|
|
82
|
+
missing = tuple(gate for gate in expected if gate not in produced)
|
|
83
|
+
unexpected = tuple(gate for gate in produced if gate not in expected)
|
|
84
|
+
|
|
85
|
+
# If the plan declares no IF gates, the phase is internal/tooling and
|
|
86
|
+
# cannot fail the contract check. Allow with a warning if the executor
|
|
87
|
+
# emitted any chatter.
|
|
88
|
+
if not expected:
|
|
89
|
+
warning = None
|
|
90
|
+
if non_gate_chatter:
|
|
91
|
+
warning = (
|
|
92
|
+
"executor produced non-IF-gate strings in produced_if_gates "
|
|
93
|
+
"for a plan that declares no IF gates; treated as commentary"
|
|
94
|
+
)
|
|
95
|
+
return ValidationResult(
|
|
96
|
+
ok=True,
|
|
97
|
+
warning=warning,
|
|
98
|
+
expected_gates=expected,
|
|
99
|
+
produced_gates=produced,
|
|
100
|
+
missing_gates=missing,
|
|
101
|
+
unexpected_gates=unexpected,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if terminal_status == "complete" and (not produced or missing or unexpected):
|
|
105
|
+
summary = "completed closeout produced_if_gates did not match the active phase plan"
|
|
106
|
+
if not produced:
|
|
107
|
+
summary = "completed closeout reported zero produced_if_gates"
|
|
108
|
+
return ValidationResult(
|
|
109
|
+
ok=False,
|
|
110
|
+
blocker_class="contract_bug",
|
|
111
|
+
blocker_summary=summary,
|
|
112
|
+
expected_gates=expected,
|
|
113
|
+
produced_gates=produced,
|
|
114
|
+
missing_gates=missing,
|
|
115
|
+
unexpected_gates=unexpected,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return ValidationResult(
|
|
119
|
+
ok=True,
|
|
120
|
+
expected_gates=expected,
|
|
121
|
+
produced_gates=produced,
|
|
122
|
+
missing_gates=missing,
|
|
123
|
+
unexpected_gates=unexpected,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def verification_enforcement_mode(env: Mapping[str, str] | None = None) -> str:
|
|
128
|
+
value = str((env or {}).get("PHASE_LOOP_VERIFY_ENFORCE") or "").strip().lower()
|
|
129
|
+
return "warn" if value == "warn" else "hard"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _normalize_gates(value: Any) -> tuple[str, ...]:
|
|
133
|
+
if isinstance(value, str):
|
|
134
|
+
raw_items = re.split(r"[,\s]+", value)
|
|
135
|
+
elif isinstance(value, (list, tuple, set)):
|
|
136
|
+
raw_items = [str(item) for item in value]
|
|
137
|
+
else:
|
|
138
|
+
raw_items = []
|
|
139
|
+
gates: list[str] = []
|
|
140
|
+
for item in raw_items:
|
|
141
|
+
gate = item.strip().strip("'\"")
|
|
142
|
+
if gate and gate not in gates:
|
|
143
|
+
gates.append(gate)
|
|
144
|
+
return tuple(gates)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Pluggable closeout-validator hook (rigor-v1 P1).
|
|
2
|
+
|
|
3
|
+
The single seam through which review gates (doc-delta, verification-evidence,
|
|
4
|
+
visual-evidence, …) plug into the closeout pass/fail decision **without** each
|
|
5
|
+
gate editing ``closeout.py``'s status logic. ``build_phase_loop_closeout`` runs
|
|
6
|
+
the registered validators once and applies their findings.
|
|
7
|
+
|
|
8
|
+
Autonomy-first severity model (see ``specs/phase-plans-v1.md`` and the
|
|
9
|
+
``[[phase-loop-autonomy-first-guardrails]]`` constraint):
|
|
10
|
+
|
|
11
|
+
* Each finding carries a severity of ``warn`` or ``block``.
|
|
12
|
+
* The global ``PHASE_LOOP_REVIEW`` control (``off`` | ``warn`` | ``block``,
|
|
13
|
+
**default ``warn``**) sets the effective behavior:
|
|
14
|
+
- ``off`` — validators do not run; no findings.
|
|
15
|
+
- ``warn`` — validators run; every finding is forced to ``warn`` (recorded
|
|
16
|
+
to the closeout for later human spot-check, the loop **continues**).
|
|
17
|
+
- ``block`` — validators run; a ``block`` finding refuses ``complete``.
|
|
18
|
+
* **No validator may set ``human_required``.** A blocking finding produces a
|
|
19
|
+
non-human, agent-recoverable blocker — never a stall waiting on a person.
|
|
20
|
+
|
|
21
|
+
Back-compat: with zero validators registered (the state shipped by P1), the
|
|
22
|
+
runner returns no findings and closeout behavior is byte-for-byte unchanged.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
from dataclasses import dataclass, field, replace
|
|
28
|
+
from typing import Any, Callable, Iterable, Mapping
|
|
29
|
+
|
|
30
|
+
ReviewSeverity = str # "warn" | "block"
|
|
31
|
+
REVIEW_SEVERITIES: tuple[str, ...] = ("warn", "block")
|
|
32
|
+
REVIEW_MODES: tuple[str, ...] = ("off", "warn", "block")
|
|
33
|
+
DEFAULT_REVIEW_MODE = "warn"
|
|
34
|
+
REVIEW_MODE_ENV = "PHASE_LOOP_REVIEW"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class ReviewFinding:
|
|
39
|
+
"""A single review-gate observation about a closeout."""
|
|
40
|
+
|
|
41
|
+
code: str
|
|
42
|
+
reason: str
|
|
43
|
+
severity: ReviewSeverity = "warn"
|
|
44
|
+
blocker_class: str | None = None
|
|
45
|
+
|
|
46
|
+
def __post_init__(self) -> None:
|
|
47
|
+
if self.severity not in REVIEW_SEVERITIES:
|
|
48
|
+
raise ValueError(f"invalid review severity: {self.severity!r}")
|
|
49
|
+
|
|
50
|
+
def to_json(self) -> dict[str, Any]:
|
|
51
|
+
payload: dict[str, Any] = {
|
|
52
|
+
"kind": "review_finding",
|
|
53
|
+
"code": self.code,
|
|
54
|
+
"reason": self.reason,
|
|
55
|
+
"severity": self.severity,
|
|
56
|
+
}
|
|
57
|
+
if self.blocker_class is not None:
|
|
58
|
+
payload["blocker_class"] = self.blocker_class
|
|
59
|
+
return payload
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class CloseoutContext:
|
|
64
|
+
"""Read-only view of the closeout a validator inspects."""
|
|
65
|
+
|
|
66
|
+
phase_alias: str
|
|
67
|
+
plan_path: str
|
|
68
|
+
terminal: Mapping[str, Any] = field(default_factory=dict)
|
|
69
|
+
automation: Mapping[str, Any] = field(default_factory=dict)
|
|
70
|
+
blocker: Mapping[str, Any] = field(default_factory=dict)
|
|
71
|
+
changed_paths: tuple[str, ...] = ()
|
|
72
|
+
# model-routing-v1 P2: the run_mode axis (autonomous default | governed).
|
|
73
|
+
# Carried so a validator can tell which mode it is in; governed-only review
|
|
74
|
+
# machinery lives in `governed_review`, never in this closeout registry.
|
|
75
|
+
run_mode: str = "autonomous"
|
|
76
|
+
# issue #18 F5: the runner-side docs-freshness pre-scan result (or None when
|
|
77
|
+
# unwired). Threaded read-only so a validator can CORROBORATE a self-attested
|
|
78
|
+
# decision (e.g. `no_doc_delta`) against the path-keyed scan evidence without
|
|
79
|
+
# doing repo IO itself (validators stay pure). None => no corroboration
|
|
80
|
+
# available => the validator must not newly-fail on its absence.
|
|
81
|
+
docs_freshness: Mapping[str, Any] | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# A validator receives the context and returns zero or more findings.
|
|
85
|
+
CloseoutValidator = Callable[[CloseoutContext], Iterable[ReviewFinding]]
|
|
86
|
+
|
|
87
|
+
_VALIDATORS: list[CloseoutValidator] = []
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def register_closeout_validator(fn: CloseoutValidator) -> CloseoutValidator:
|
|
91
|
+
"""Register a closeout validator. Returns ``fn`` so it can be used as a decorator."""
|
|
92
|
+
if fn not in _VALIDATORS:
|
|
93
|
+
_VALIDATORS.append(fn)
|
|
94
|
+
return fn
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def clear_closeout_validators() -> None:
|
|
98
|
+
"""Drop all registered validators (test hook)."""
|
|
99
|
+
_VALIDATORS.clear()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def registered_closeout_validators() -> tuple[CloseoutValidator, ...]:
|
|
103
|
+
return tuple(_VALIDATORS)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def resolve_review_mode(env: Mapping[str, str] | None = None) -> str:
|
|
107
|
+
env = os.environ if env is None else env
|
|
108
|
+
value = str(env.get(REVIEW_MODE_ENV) or "").strip().lower()
|
|
109
|
+
return value if value in REVIEW_MODES else DEFAULT_REVIEW_MODE
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def run_closeout_validators(
|
|
113
|
+
ctx: CloseoutContext,
|
|
114
|
+
env: Mapping[str, str] | None = None,
|
|
115
|
+
) -> list[ReviewFinding]:
|
|
116
|
+
"""Run every registered validator and return findings at their effective severity.
|
|
117
|
+
|
|
118
|
+
A validator that raises is skipped — a review gate must never break closeout.
|
|
119
|
+
"""
|
|
120
|
+
mode = resolve_review_mode(env)
|
|
121
|
+
if mode == "off":
|
|
122
|
+
return []
|
|
123
|
+
findings: list[ReviewFinding] = []
|
|
124
|
+
for fn in tuple(_VALIDATORS):
|
|
125
|
+
try:
|
|
126
|
+
produced = fn(ctx) or ()
|
|
127
|
+
except Exception:
|
|
128
|
+
continue
|
|
129
|
+
for finding in produced:
|
|
130
|
+
effective = "warn" if mode == "warn" else finding.severity
|
|
131
|
+
findings.append(replace(finding, severity=effective))
|
|
132
|
+
return findings
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def apply_review_findings(
|
|
136
|
+
*,
|
|
137
|
+
findings: list[ReviewFinding],
|
|
138
|
+
terminal: dict[str, Any],
|
|
139
|
+
automation: dict[str, Any],
|
|
140
|
+
blocker: dict[str, Any],
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
"""Fold findings into the closeout dicts.
|
|
143
|
+
|
|
144
|
+
``warn`` findings are recorded as results (audit trail) and do not change
|
|
145
|
+
the outcome. The first ``block`` finding turns the closeout into a
|
|
146
|
+
non-human ``blocked`` outcome, mirroring the verification-evidence gate.
|
|
147
|
+
"""
|
|
148
|
+
updated_terminal = dict(terminal)
|
|
149
|
+
updated_automation = dict(automation)
|
|
150
|
+
updated_blocker = dict(blocker)
|
|
151
|
+
results = [f.to_json() for f in findings]
|
|
152
|
+
|
|
153
|
+
blocking = next((f for f in findings if f.severity == "block"), None)
|
|
154
|
+
if blocking is not None:
|
|
155
|
+
blocker_class = blocking.blocker_class or "review_gate_block"
|
|
156
|
+
summary = f"Review gate blocked closeout: {blocking.code} — {blocking.reason}"
|
|
157
|
+
updated_terminal["terminal_status"] = "blocked"
|
|
158
|
+
updated_terminal["verification_status"] = "blocked"
|
|
159
|
+
updated_automation["status"] = "blocked"
|
|
160
|
+
updated_automation["verification_status"] = "blocked"
|
|
161
|
+
updated_automation["blocker_class"] = blocker_class
|
|
162
|
+
updated_automation["blocker_summary"] = summary
|
|
163
|
+
updated_automation["human_required"] = False
|
|
164
|
+
updated_blocker.update(
|
|
165
|
+
{
|
|
166
|
+
"human_required": False,
|
|
167
|
+
"blocker_class": blocker_class,
|
|
168
|
+
"blocker_summary": summary,
|
|
169
|
+
"required_human_inputs": (),
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
return {
|
|
173
|
+
"terminal": updated_terminal,
|
|
174
|
+
"automation": updated_automation,
|
|
175
|
+
"blocker": updated_blocker,
|
|
176
|
+
"results": results,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def load_builtin_closeout_validators() -> None:
|
|
181
|
+
"""Import the built-in validator modules so they self-register.
|
|
182
|
+
|
|
183
|
+
Extension point: each downstream rigor phase adds its validator module
|
|
184
|
+
(e.g. ``doc_delta_validator``) and one guarded import line here. Imports are
|
|
185
|
+
guarded so an incremental checkout missing a module never breaks closeout.
|
|
186
|
+
P1 ships no built-in validators — the registry is empty by default.
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
from . import doc_delta_validator # noqa: F401 (P2)
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
try:
|
|
193
|
+
from . import verification_evidence_validator # noqa: F401 (P5)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
try:
|
|
197
|
+
from . import visual_evidence_validator # noqa: F401 (P6)
|
|
198
|
+
except Exception:
|
|
199
|
+
pass
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
load_builtin_closeout_validators()
|