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,427 @@
|
|
|
1
|
+
"""Harness-neutral repo-validation resolver (the ``phase-loop repo-validate`` core).
|
|
2
|
+
|
|
3
|
+
This is agent-harness's neutral home for the *local-first agentic validation
|
|
4
|
+
contract*: a single command surface (``fast``/``gate``/``full``/``fix``/
|
|
5
|
+
``affected``/``doctor``) that coding agents run locally, in worktrees, and in CI
|
|
6
|
+
before opening PRs, with GitHub staying the authoritative merge gate. See
|
|
7
|
+
``docs/repo-validation-contract.md`` for the full spec.
|
|
8
|
+
|
|
9
|
+
The resolver only ever runs a repo's *explicit* contract:
|
|
10
|
+
|
|
11
|
+
1. ``just agent::<target>`` when a ``Justfile``/``justfile`` declares
|
|
12
|
+
``mod agent`` and the repo ships an ``agent.just`` module recipe.
|
|
13
|
+
2. a ``package.json`` script named ``agent:<target>``.
|
|
14
|
+
|
|
15
|
+
It NEVER guesses that ``npm test``, ``pytest``, or ``make test`` is the gate.
|
|
16
|
+
Unmigrated repos fail closed (exit 20) so agents do not invent CI.
|
|
17
|
+
|
|
18
|
+
BEHAVIORAL IDENTITY: this resolver is the Python twin of the dotfiles
|
|
19
|
+
``scripts/agent-validation`` bash wrapper and MUST make the same dispatch
|
|
20
|
+
decision and return the same exit code for the same repo, so the local (bash)
|
|
21
|
+
and neutral (Python) surfaces never drift. The one intentional refinement: the
|
|
22
|
+
bash wrapper shells out to ``node`` merely to *read* ``package.json`` and maps a
|
|
23
|
+
missing ``node`` to exit 21. The neutral runtime parses ``package.json`` with the
|
|
24
|
+
Python stdlib, so exit 21 for a package contract means only what the contract
|
|
25
|
+
says it means -- the package-manager *runner* (npm/pnpm/yarn/bun) is missing --
|
|
26
|
+
not that a JSON reader is absent. That is closer to the contract's definition of
|
|
27
|
+
21, not a divergence from its exit-code set.
|
|
28
|
+
|
|
29
|
+
The module is deliberately dependency-light (stdlib only) and splits pure
|
|
30
|
+
discovery/planning (``resolve``) from the subprocess (``run_plan``) so the
|
|
31
|
+
exit-code mapping is testable without a live ``just``/``npm`` on PATH.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import json
|
|
37
|
+
import re
|
|
38
|
+
import shutil
|
|
39
|
+
import subprocess
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
# Frozen exit-code contract (docs/repo-validation-contract.md).
|
|
44
|
+
EXIT_OK = 0
|
|
45
|
+
EXIT_USAGE = 2
|
|
46
|
+
EXIT_NO_REPO = 10
|
|
47
|
+
EXIT_NO_CONTRACT = 20
|
|
48
|
+
EXIT_MISSING_RUNNER = 21
|
|
49
|
+
EXIT_COMMAND_FAILED = 30
|
|
50
|
+
|
|
51
|
+
# The six neutral targets plus the ``check`` alias for ``doctor``.
|
|
52
|
+
TARGETS = ("fast", "gate", "full", "fix", "affected", "doctor")
|
|
53
|
+
TARGET_ALIASES = {"check": "doctor"}
|
|
54
|
+
ALL_TARGET_TOKENS = TARGETS + tuple(TARGET_ALIASES)
|
|
55
|
+
|
|
56
|
+
# ``agent.just`` module candidates, in the same order the bash wrapper probes.
|
|
57
|
+
_AGENT_JUST_CANDIDATES = (
|
|
58
|
+
"agent.just",
|
|
59
|
+
"agent/mod.just",
|
|
60
|
+
"agent/Justfile",
|
|
61
|
+
"agent/justfile",
|
|
62
|
+
"agent/.justfile",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Lockfile -> package-manager mapping, probed before the generic fallback order.
|
|
66
|
+
_LOCKFILE_MANAGERS = (
|
|
67
|
+
("pnpm-lock.yaml", "pnpm"),
|
|
68
|
+
("package-lock.json", "npm"),
|
|
69
|
+
("yarn.lock", "yarn"),
|
|
70
|
+
("bun.lock", "bun"),
|
|
71
|
+
("bun.lockb", "bun"),
|
|
72
|
+
)
|
|
73
|
+
_FALLBACK_MANAGERS = ("pnpm", "npm", "yarn", "bun")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class ValidationPlan:
|
|
78
|
+
"""The resolved dispatch for one ``(repo_root, target)`` pair.
|
|
79
|
+
|
|
80
|
+
Exactly one of ``argv`` (a runnable command) or ``exit_code`` (a terminal
|
|
81
|
+
no-run outcome, 20 or 21) is set. ``resolve`` never touches the network or
|
|
82
|
+
mutates state; ``run_plan`` turns an ``argv`` plan into a 0/30 result.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
target: str
|
|
86
|
+
repo_root: Path
|
|
87
|
+
contract_kind: str | None # "just" | "package" | None
|
|
88
|
+
contract: str | None # e.g. "agent::gate" or "agent:gate"
|
|
89
|
+
argv: tuple[str, ...] | None
|
|
90
|
+
exit_code: int | None
|
|
91
|
+
note: str | None = None
|
|
92
|
+
|
|
93
|
+
def to_json(self) -> dict[str, object]:
|
|
94
|
+
return {
|
|
95
|
+
"target": self.target,
|
|
96
|
+
"repo_root": str(self.repo_root),
|
|
97
|
+
"contract_kind": self.contract_kind,
|
|
98
|
+
"contract": self.contract,
|
|
99
|
+
"argv": list(self.argv) if self.argv is not None else None,
|
|
100
|
+
"exit_code": self.exit_code,
|
|
101
|
+
"note": self.note,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# --------------------------------------------------------------------------- #
|
|
106
|
+
# Repo-root resolution (git worktree aware).
|
|
107
|
+
# --------------------------------------------------------------------------- #
|
|
108
|
+
def find_repo_root(cwd: str | Path) -> Path | None:
|
|
109
|
+
"""Return the git work-tree root for ``cwd``, or ``None`` when outside git.
|
|
110
|
+
|
|
111
|
+
Uses ``git rev-parse --show-toplevel`` so it resolves correctly from
|
|
112
|
+
subdirectories and from linked worktrees, matching the bash wrapper.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
proc = subprocess.run(
|
|
117
|
+
["git", "-C", str(cwd), "rev-parse", "--show-toplevel"],
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
check=False,
|
|
121
|
+
)
|
|
122
|
+
except FileNotFoundError:
|
|
123
|
+
return None
|
|
124
|
+
if proc.returncode != 0:
|
|
125
|
+
return None
|
|
126
|
+
root = proc.stdout.strip()
|
|
127
|
+
return Path(root) if root else None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --------------------------------------------------------------------------- #
|
|
131
|
+
# Justfile contract discovery (pure filesystem).
|
|
132
|
+
# --------------------------------------------------------------------------- #
|
|
133
|
+
_COMMENT_RE = re.compile(r"^\s*#")
|
|
134
|
+
_MOD_AGENT_RE = re.compile(r"^\s*mod\??\s+agent(\s|$)")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def find_justfile(root: Path) -> Path | None:
|
|
138
|
+
for name in ("Justfile", "justfile"):
|
|
139
|
+
candidate = root / name
|
|
140
|
+
if candidate.is_file():
|
|
141
|
+
return candidate
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _iter_noncomment_lines(path: Path):
|
|
146
|
+
try:
|
|
147
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
148
|
+
except OSError:
|
|
149
|
+
return
|
|
150
|
+
for line in text.splitlines():
|
|
151
|
+
if _COMMENT_RE.match(line):
|
|
152
|
+
continue
|
|
153
|
+
yield line
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def has_agent_just_module(justfile: Path) -> bool:
|
|
157
|
+
"""True when the Justfile declares ``mod agent`` (or ``mod? agent``)."""
|
|
158
|
+
|
|
159
|
+
return any(_MOD_AGENT_RE.match(line) for line in _iter_noncomment_lines(justfile))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def find_agent_just_module(root: Path) -> Path | None:
|
|
163
|
+
for candidate in _AGENT_JUST_CANDIDATES:
|
|
164
|
+
path = root / candidate
|
|
165
|
+
if path.is_file():
|
|
166
|
+
return path
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def has_just_recipe(justfile: Path, recipe: str) -> bool:
|
|
171
|
+
"""True when ``justfile`` declares a recipe named ``recipe``."""
|
|
172
|
+
|
|
173
|
+
pattern = re.compile(rf"^\s*{re.escape(recipe)}([\s:]|$)")
|
|
174
|
+
return any(pattern.match(line) for line in _iter_noncomment_lines(justfile))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def just_contract_command(root: Path, target: str) -> str | None:
|
|
178
|
+
"""Return the just invocation (``agent::<t>`` or ``agent:<t>``) or ``None``.
|
|
179
|
+
|
|
180
|
+
Mirrors the bash ``just_contract_command``: prefer the ``mod agent`` module
|
|
181
|
+
recipe, then a flat ``agent:<target>`` recipe in the root Justfile.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
justfile = find_justfile(root)
|
|
185
|
+
if justfile is None:
|
|
186
|
+
return None
|
|
187
|
+
if has_agent_just_module(justfile):
|
|
188
|
+
module = find_agent_just_module(root)
|
|
189
|
+
if module is not None and has_just_recipe(module, target):
|
|
190
|
+
return f"agent::{target}"
|
|
191
|
+
if has_just_recipe(justfile, f"agent:{target}"):
|
|
192
|
+
return f"agent:{target}"
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# --------------------------------------------------------------------------- #
|
|
197
|
+
# package.json contract discovery (stdlib JSON, no node required).
|
|
198
|
+
# --------------------------------------------------------------------------- #
|
|
199
|
+
def package_has_script(root: Path, script: str) -> bool:
|
|
200
|
+
package_json = root / "package.json"
|
|
201
|
+
if not package_json.is_file():
|
|
202
|
+
return False
|
|
203
|
+
try:
|
|
204
|
+
data = json.loads(package_json.read_text(encoding="utf-8"))
|
|
205
|
+
except (OSError, ValueError):
|
|
206
|
+
return False
|
|
207
|
+
scripts = data.get("scripts") if isinstance(data, dict) else None
|
|
208
|
+
return isinstance(scripts, dict) and script in scripts
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def package_manager_for(root: Path) -> str | None:
|
|
212
|
+
"""Pick the package-manager runner, honoring a present lockfile first.
|
|
213
|
+
|
|
214
|
+
When a lockfile pins a manager, that manager must be on PATH (else ``None``,
|
|
215
|
+
which the caller maps to exit 21). With no lockfile, fall back to the first
|
|
216
|
+
available manager, matching the bash wrapper's probe order.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
for lockfile, manager in _LOCKFILE_MANAGERS:
|
|
220
|
+
if (root / lockfile).is_file():
|
|
221
|
+
return manager if shutil.which(manager) else None
|
|
222
|
+
for manager in _FALLBACK_MANAGERS:
|
|
223
|
+
if shutil.which(manager):
|
|
224
|
+
return manager
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# --------------------------------------------------------------------------- #
|
|
229
|
+
# Resolution (pure) + execution (subprocess).
|
|
230
|
+
# --------------------------------------------------------------------------- #
|
|
231
|
+
def resolve(repo_root: Path, target: str) -> ValidationPlan:
|
|
232
|
+
"""Resolve the dispatch for a non-doctor target without running anything.
|
|
233
|
+
|
|
234
|
+
Precedence (identical to the bash wrapper):
|
|
235
|
+
1. just contract + ``just`` on PATH -> run it.
|
|
236
|
+
2. package script present -> run it (or 21 if no manager).
|
|
237
|
+
3. just contract but ``just`` absent -> 21.
|
|
238
|
+
4. nothing -> 20 (fail closed).
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
just_contract = just_contract_command(repo_root, target)
|
|
242
|
+
has_pkg = package_has_script(repo_root, f"agent:{target}")
|
|
243
|
+
just_present = shutil.which("just") is not None
|
|
244
|
+
|
|
245
|
+
if just_contract is not None and just_present:
|
|
246
|
+
return ValidationPlan(
|
|
247
|
+
target=target,
|
|
248
|
+
repo_root=repo_root,
|
|
249
|
+
contract_kind="just",
|
|
250
|
+
contract=just_contract,
|
|
251
|
+
argv=("just", just_contract),
|
|
252
|
+
exit_code=None,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if has_pkg:
|
|
256
|
+
script = f"agent:{target}"
|
|
257
|
+
note = None
|
|
258
|
+
if just_contract is not None and not just_present:
|
|
259
|
+
note = (
|
|
260
|
+
f"{just_contract} is declared, but just is not on PATH; "
|
|
261
|
+
f"running explicit package script {script}."
|
|
262
|
+
)
|
|
263
|
+
manager = package_manager_for(repo_root)
|
|
264
|
+
if manager is None:
|
|
265
|
+
return ValidationPlan(
|
|
266
|
+
target=target,
|
|
267
|
+
repo_root=repo_root,
|
|
268
|
+
contract_kind="package",
|
|
269
|
+
contract=script,
|
|
270
|
+
argv=None,
|
|
271
|
+
exit_code=EXIT_MISSING_RUNNER,
|
|
272
|
+
note=f"package.json declares {script}, but no supported package manager is on PATH.",
|
|
273
|
+
)
|
|
274
|
+
return ValidationPlan(
|
|
275
|
+
target=target,
|
|
276
|
+
repo_root=repo_root,
|
|
277
|
+
contract_kind="package",
|
|
278
|
+
contract=script,
|
|
279
|
+
argv=(manager, "run", script),
|
|
280
|
+
exit_code=None,
|
|
281
|
+
note=note,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if just_contract is not None:
|
|
285
|
+
return ValidationPlan(
|
|
286
|
+
target=target,
|
|
287
|
+
repo_root=repo_root,
|
|
288
|
+
contract_kind="just",
|
|
289
|
+
contract=just_contract,
|
|
290
|
+
argv=None,
|
|
291
|
+
exit_code=EXIT_MISSING_RUNNER,
|
|
292
|
+
note=f"{just_contract} is declared, but just is not on PATH.",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
return ValidationPlan(
|
|
296
|
+
target=target,
|
|
297
|
+
repo_root=repo_root,
|
|
298
|
+
contract_kind=None,
|
|
299
|
+
contract=None,
|
|
300
|
+
argv=None,
|
|
301
|
+
exit_code=EXIT_NO_CONTRACT,
|
|
302
|
+
note=(
|
|
303
|
+
f"no explicit agent validation contract for agent:{target}. "
|
|
304
|
+
f"Declare just agent::{target} or a package.json script named agent:{target}."
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def run_plan(plan: ValidationPlan) -> int:
|
|
310
|
+
"""Execute a resolved plan and map the result to the exit-code contract.
|
|
311
|
+
|
|
312
|
+
A no-run plan returns its terminal code (20/21). A runnable plan returns 0 on
|
|
313
|
+
success and 30 when the repo-local validation command fails.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
if plan.argv is None:
|
|
317
|
+
return int(plan.exit_code if plan.exit_code is not None else EXIT_NO_CONTRACT)
|
|
318
|
+
try:
|
|
319
|
+
proc = subprocess.run(list(plan.argv), cwd=str(plan.repo_root), check=False)
|
|
320
|
+
except FileNotFoundError:
|
|
321
|
+
# The runner vanished between discovery and exec (race / PATH change).
|
|
322
|
+
return EXIT_MISSING_RUNNER
|
|
323
|
+
return EXIT_OK if proc.returncode == 0 else EXIT_COMMAND_FAILED
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# --------------------------------------------------------------------------- #
|
|
327
|
+
# doctor: a neutral environment/contract report (always exits 0).
|
|
328
|
+
# --------------------------------------------------------------------------- #
|
|
329
|
+
def _declared_contracts(root: Path) -> list[tuple[str, str]]:
|
|
330
|
+
found: list[tuple[str, str]] = []
|
|
331
|
+
for target in TARGETS:
|
|
332
|
+
just_contract = just_contract_command(root, target)
|
|
333
|
+
if just_contract is not None:
|
|
334
|
+
found.append((target, f"just {just_contract}"))
|
|
335
|
+
elif package_has_script(root, f"agent:{target}"):
|
|
336
|
+
found.append((target, f"package script agent:{target}"))
|
|
337
|
+
return found
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def doctor_report(root: Path) -> dict[str, object]:
|
|
341
|
+
tools = {
|
|
342
|
+
name: shutil.which(name)
|
|
343
|
+
for name in ("git", "just", "dagger", "docker", "node", "pnpm", "npm", "yarn", "bun", "uv", "python3", "cargo")
|
|
344
|
+
}
|
|
345
|
+
stack_hints = [
|
|
346
|
+
name
|
|
347
|
+
for name in (
|
|
348
|
+
"package.json",
|
|
349
|
+
"pyproject.toml",
|
|
350
|
+
"Cargo.toml",
|
|
351
|
+
"dagger.json",
|
|
352
|
+
"Dockerfile",
|
|
353
|
+
"docker-compose.yml",
|
|
354
|
+
".pre-commit-config.yaml",
|
|
355
|
+
)
|
|
356
|
+
if (root / name).is_file()
|
|
357
|
+
]
|
|
358
|
+
return {
|
|
359
|
+
"repo_root": str(root),
|
|
360
|
+
"tools": tools,
|
|
361
|
+
"stack_hints": stack_hints,
|
|
362
|
+
"declared_contracts": [{"target": t, "runner": r} for t, r in _declared_contracts(root)],
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _print_doctor(report: dict[str, object]) -> None:
|
|
367
|
+
print("Agent validation doctor")
|
|
368
|
+
print(f" repo-root {report['repo_root']}")
|
|
369
|
+
print("")
|
|
370
|
+
print("Tools:")
|
|
371
|
+
for name, path in report["tools"].items(): # type: ignore[index]
|
|
372
|
+
print(f" {name:<16} {'present (' + path + ')' if path else 'missing'}")
|
|
373
|
+
print("")
|
|
374
|
+
print("Repo stack hints:")
|
|
375
|
+
for hint in report["stack_hints"]: # type: ignore[index]
|
|
376
|
+
print(f" {hint}")
|
|
377
|
+
print("")
|
|
378
|
+
print("Declared agent contracts:")
|
|
379
|
+
contracts = report["declared_contracts"] # type: ignore[index]
|
|
380
|
+
if contracts:
|
|
381
|
+
for entry in contracts: # type: ignore[union-attr]
|
|
382
|
+
print(f" {entry['target']:<16} {entry['runner']}")
|
|
383
|
+
else:
|
|
384
|
+
print(" none")
|
|
385
|
+
print("")
|
|
386
|
+
print(
|
|
387
|
+
"This repo is not migrated yet. Validation targets fail closed (exit 20) "
|
|
388
|
+
"until it declares agent:* commands."
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# --------------------------------------------------------------------------- #
|
|
393
|
+
# CLI entry point (wired from cli.py as the ``repo-validate`` subcommand).
|
|
394
|
+
# --------------------------------------------------------------------------- #
|
|
395
|
+
def cli_main(*, target: str, cwd: str | Path = ".", as_json: bool = False) -> int:
|
|
396
|
+
"""Resolve and run a repo's explicit validation contract; return its code."""
|
|
397
|
+
|
|
398
|
+
normalized = TARGET_ALIASES.get(target, target)
|
|
399
|
+
if normalized not in TARGETS:
|
|
400
|
+
print(f"ERROR: unknown validation target '{target}'.", flush=True)
|
|
401
|
+
return EXIT_USAGE
|
|
402
|
+
|
|
403
|
+
root = find_repo_root(cwd)
|
|
404
|
+
if root is None:
|
|
405
|
+
print("ERROR: not inside a git work tree.", flush=True)
|
|
406
|
+
return EXIT_NO_REPO
|
|
407
|
+
|
|
408
|
+
if normalized == "doctor":
|
|
409
|
+
report = doctor_report(root)
|
|
410
|
+
if as_json:
|
|
411
|
+
print(json.dumps(report, indent=2))
|
|
412
|
+
else:
|
|
413
|
+
_print_doctor(report)
|
|
414
|
+
return EXIT_OK
|
|
415
|
+
|
|
416
|
+
plan = resolve(root, normalized)
|
|
417
|
+
if as_json:
|
|
418
|
+
# --json is inspection-only: print the resolved plan and DO NOT execute
|
|
419
|
+
# the repo-local command (keeping stdout clean, machine-parseable JSON).
|
|
420
|
+
# A no-run plan returns its terminal code (20/21); a runnable plan returns
|
|
421
|
+
# 0, meaning "a runnable contract was resolved" -- not "the command ran".
|
|
422
|
+
print(json.dumps(plan.to_json(), indent=2))
|
|
423
|
+
return EXIT_OK if plan.argv is not None else run_plan(plan)
|
|
424
|
+
if plan.note:
|
|
425
|
+
stream_prefix = "WARN" if plan.argv is not None else "ERROR"
|
|
426
|
+
print(f"{stream_prefix}: {plan.note}", flush=True)
|
|
427
|
+
return run_plan(plan)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Run-end review-findings summary (rigor-v1 P7 / acceptance #3).
|
|
2
|
+
|
|
3
|
+
Review gates default to ``warn``: each finding is recorded inside its phase's
|
|
4
|
+
closeout (``verification.results``, ``kind="review_finding"``) and the loop
|
|
5
|
+
continues. So a human reviewing a bounded run between ``--max-phases`` batches
|
|
6
|
+
needs the findings aggregated and surfaced — that is what this module does.
|
|
7
|
+
|
|
8
|
+
``collect_review_findings`` recursively scans ledger events for review-finding
|
|
9
|
+
records (robust to exactly where the closeout nests them) and de-duplicates.
|
|
10
|
+
``render_review_findings_summary`` renders an operator-readable block. The
|
|
11
|
+
runner emits it to stderr at run end.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Iterable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _walk_kind(obj: Any, kind: str) -> Iterable[dict]:
|
|
19
|
+
"""Recursively yield every dict in ``obj`` whose ``kind`` field matches."""
|
|
20
|
+
if isinstance(obj, dict):
|
|
21
|
+
if obj.get("kind") == kind:
|
|
22
|
+
yield obj
|
|
23
|
+
for value in obj.values():
|
|
24
|
+
yield from _walk_kind(value, kind)
|
|
25
|
+
elif isinstance(obj, (list, tuple)):
|
|
26
|
+
for item in obj:
|
|
27
|
+
yield from _walk_kind(item, kind)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _collect_by_kind(events, kind, *, key, tag_phase):
|
|
31
|
+
"""Shared collect-and-dedup skeleton: scan events for records of ``kind``,
|
|
32
|
+
dedup by ``key(record, event_phase)``. When ``tag_phase`` the returned record
|
|
33
|
+
is tagged with its event's ``phase`` (review findings); otherwise the record
|
|
34
|
+
is returned as-is (panel verdicts already carry their own ``phase``)."""
|
|
35
|
+
out: list[dict] = []
|
|
36
|
+
seen: set[tuple] = set()
|
|
37
|
+
for event in events:
|
|
38
|
+
if not isinstance(event, dict):
|
|
39
|
+
continue
|
|
40
|
+
phase = event.get("phase")
|
|
41
|
+
for record in _walk_kind(event, kind):
|
|
42
|
+
k = key(record, phase)
|
|
43
|
+
if k in seen:
|
|
44
|
+
continue
|
|
45
|
+
seen.add(k)
|
|
46
|
+
if tag_phase:
|
|
47
|
+
tagged = dict(record)
|
|
48
|
+
tagged["phase"] = phase
|
|
49
|
+
out.append(tagged)
|
|
50
|
+
else:
|
|
51
|
+
out.append(record)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def collect_review_findings(events: Iterable[Any]) -> list[dict]:
|
|
56
|
+
"""Extract de-duplicated review findings from ledger events, tagging each with
|
|
57
|
+
its event's ``phase``; dedup by (phase, code, reason, severity)."""
|
|
58
|
+
return _collect_by_kind(
|
|
59
|
+
events,
|
|
60
|
+
"review_finding",
|
|
61
|
+
key=lambda r, phase: (phase, r.get("code"), r.get("reason"), r.get("severity")),
|
|
62
|
+
tag_phase=True,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def render_review_findings_summary(findings: list[dict]) -> str:
|
|
67
|
+
"""Operator-readable summary block, or '' when there are no findings."""
|
|
68
|
+
if not findings:
|
|
69
|
+
return ""
|
|
70
|
+
by_sev: dict[str, int] = {}
|
|
71
|
+
for f in findings:
|
|
72
|
+
by_sev[str(f.get("severity") or "warn")] = by_sev.get(str(f.get("severity") or "warn"), 0) + 1
|
|
73
|
+
counts = ", ".join(f"{n} {sev}" for sev, n in sorted(by_sev.items()))
|
|
74
|
+
lines = [
|
|
75
|
+
f"Review findings this run: {len(findings)} ({counts}).",
|
|
76
|
+
"Recorded by autonomy-first gates; the loop was not stalled. "
|
|
77
|
+
"Address them or set PHASE_LOOP_REVIEW=block to enforce on the next run.",
|
|
78
|
+
]
|
|
79
|
+
for f in findings:
|
|
80
|
+
lines.append(
|
|
81
|
+
f" - [{f.get('severity', 'warn')}] {f.get('phase') or '?'}: "
|
|
82
|
+
f"{f.get('code')} — {f.get('reason')}"
|
|
83
|
+
)
|
|
84
|
+
return "\n".join(lines)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def summarize_run_review_findings(events: Iterable[Any]) -> str:
|
|
88
|
+
"""Convenience: collect + render in one call."""
|
|
89
|
+
return render_review_findings_summary(collect_review_findings(events))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# --- model-routing-v1 P4: governed panel verdicts in the run-end summary -------
|
|
93
|
+
|
|
94
|
+
def panel_verdict_record(
|
|
95
|
+
*,
|
|
96
|
+
phase: str,
|
|
97
|
+
outcome: str,
|
|
98
|
+
degraded: bool = False,
|
|
99
|
+
rounds: int | None = None,
|
|
100
|
+
reason: str | None = None,
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
|
+
"""A metadata-only ledger record describing a governed panel outcome.
|
|
103
|
+
|
|
104
|
+
``outcome`` is one of ``mergeable`` / ``blocked`` / ``degraded``; the governed
|
|
105
|
+
pre-merge/planning gates emit this so a human auditing a governed run sees the
|
|
106
|
+
verdict in the run-end summary.
|
|
107
|
+
"""
|
|
108
|
+
record: dict[str, Any] = {"kind": "panel_verdict", "phase": phase, "outcome": outcome, "degraded": bool(degraded)}
|
|
109
|
+
if rounds is not None:
|
|
110
|
+
record["rounds"] = rounds
|
|
111
|
+
if reason is not None:
|
|
112
|
+
record["reason"] = reason
|
|
113
|
+
return record
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def collect_panel_verdicts(events: Iterable[Any]) -> list[dict]:
|
|
117
|
+
"""Extract de-duplicated ``panel_verdict`` records from ledger events
|
|
118
|
+
(records already carry their own ``phase``); dedup by
|
|
119
|
+
(phase, outcome, degraded, rounds)."""
|
|
120
|
+
return _collect_by_kind(
|
|
121
|
+
events,
|
|
122
|
+
"panel_verdict",
|
|
123
|
+
key=lambda r, _phase: (r.get("phase"), r.get("outcome"), r.get("degraded"), r.get("rounds")),
|
|
124
|
+
tag_phase=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def render_panel_verdicts_summary(verdicts: list[dict]) -> str:
|
|
129
|
+
"""Operator-readable panel-verdict block, or '' when there are none."""
|
|
130
|
+
if not verdicts:
|
|
131
|
+
return ""
|
|
132
|
+
lines = [f"Governed panel verdicts this run: {len(verdicts)}."]
|
|
133
|
+
for v in verdicts:
|
|
134
|
+
flag = " (degraded — advisory only)" if v.get("degraded") else ""
|
|
135
|
+
rounds = f", {v['rounds']} round(s)" if v.get("rounds") is not None else ""
|
|
136
|
+
lines.append(
|
|
137
|
+
f" - {v.get('phase') or '?'}: {v.get('outcome')}{flag}{rounds}"
|
|
138
|
+
+ (f" — {v['reason']}" if v.get("reason") else "")
|
|
139
|
+
)
|
|
140
|
+
return "\n".join(lines)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def summarize_run(events: Iterable[Any]) -> str:
|
|
144
|
+
"""The full run-end summary: review findings + governed panel verdicts."""
|
|
145
|
+
events = list(events)
|
|
146
|
+
blocks = [
|
|
147
|
+
render_review_findings_summary(collect_review_findings(events)),
|
|
148
|
+
render_panel_verdicts_summary(collect_panel_verdicts(events)),
|
|
149
|
+
]
|
|
150
|
+
return "\n\n".join(b for b in blocks if b)
|