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,425 @@
|
|
|
1
|
+
"""Mechanical lint for phase-plan roadmap specs.
|
|
2
|
+
|
|
3
|
+
Single source of truth for roadmap validation. The skill-bundle script
|
|
4
|
+
``<harness>-phase-roadmap-builder/scripts/validate_roadmap.py`` is a thin shim
|
|
5
|
+
over this module, and the CLI exposes it as ``phase-loop validate-roadmap`` so
|
|
6
|
+
validation is always available wherever ``phase_loop_runtime`` is installed —
|
|
7
|
+
even on slim skill installs where the bundle ``scripts/`` dir is absent.
|
|
8
|
+
|
|
9
|
+
Checks (all run even if earlier ones fail so the author sees every issue):
|
|
10
|
+
|
|
11
|
+
(A) Required top-level headings present.
|
|
12
|
+
(B) Each ``### Phase N — <Name> (<ALIAS>)`` block carries the required fields.
|
|
13
|
+
(C) Phase numbers non-decreasing; aliases unique.
|
|
14
|
+
(D) IF-gate IDs match ``IF-0-<ALIAS>-\\d+`` and reconcile with phase Produces.
|
|
15
|
+
(E) ``**Depends on**`` references only existing earlier-phase aliases.
|
|
16
|
+
(F) The phase dependency DAG is acyclic.
|
|
17
|
+
(G) Every phase declares a lane-count / partition hint (or is preamble).
|
|
18
|
+
|
|
19
|
+
Zero external deps (stdlib only). Parses by regex on stable headings — not a
|
|
20
|
+
full Markdown parser.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Dict, List, Set
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Data model
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Phase:
|
|
37
|
+
number: int
|
|
38
|
+
name: str
|
|
39
|
+
alias: str
|
|
40
|
+
objective: str = ""
|
|
41
|
+
exit_criteria: List[str] = field(default_factory=list)
|
|
42
|
+
scope_notes: str = ""
|
|
43
|
+
non_goals: str = ""
|
|
44
|
+
key_files: List[str] = field(default_factory=list)
|
|
45
|
+
depends_on: List[str] = field(default_factory=list) # aliases, or [] if (none)
|
|
46
|
+
produces: List[str] = field(default_factory=list) # IF-gate ids
|
|
47
|
+
raw_body: str = ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Parsing
|
|
52
|
+
|
|
53
|
+
TOP_HEADING_RE = re.compile(r"^## +(?P<name>[^\n]+?)\s*$", re.MULTILINE)
|
|
54
|
+
PHASE_HEADING_RE = re.compile(
|
|
55
|
+
r"^### +Phase\s+(?P<num>\d+)(?P<decimal>\.\d+)?(?P<letter>[A-Z]?)\s*[—\-]\s*(?P<name>.+?)\s*"
|
|
56
|
+
r"\(\s*(?P<alias>[A-Za-z0-9]+)(?:\s*,[^)]*)?\s*\)\s*$",
|
|
57
|
+
re.MULTILINE,
|
|
58
|
+
)
|
|
59
|
+
ANY_PHASE_HEADING_RE = re.compile(r"^### +Phase\s+\d+(?:\.\d+)?[A-Z]?\b.*$", re.MULTILINE)
|
|
60
|
+
FIELD_RE_TEMPLATE = r"^\*\*{label}\*\*\s*\n(?P<body>(?:(?!^\*\*|^### |^## ).*\n?)+)"
|
|
61
|
+
ALIAS_TOKEN_RE = re.compile(r"`([A-Za-z][A-Za-z0-9]*)`|\b([A-Z][A-Z0-9]{1,40}|[Pp]\d+[A-Za-z]?)\b")
|
|
62
|
+
|
|
63
|
+
REQUIRED_TOP_HEADINGS = [
|
|
64
|
+
"Context",
|
|
65
|
+
"Phases",
|
|
66
|
+
"Top Interface-Freeze Gates",
|
|
67
|
+
"Phase Dependency DAG",
|
|
68
|
+
"Execution Notes",
|
|
69
|
+
"Verification",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
IF_GATE_RE = re.compile(r"\bIF-0-([A-Za-z0-9]+)-(\d+)\b")
|
|
73
|
+
PREAMBLE_MARKER_RE = re.compile(r"preamble\s*/\s*interface-only|interface-freeze-only|preamble phase", re.IGNORECASE)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _extract_top_sections(text: str) -> Dict[str, str]:
|
|
77
|
+
sections: Dict[str, str] = {}
|
|
78
|
+
matches = list(TOP_HEADING_RE.finditer(text))
|
|
79
|
+
for i, m in enumerate(matches):
|
|
80
|
+
start = m.end()
|
|
81
|
+
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
|
|
82
|
+
sections[m.group("name").strip()] = text[start:end]
|
|
83
|
+
return sections
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _next_top_heading(text: str, start: int) -> int:
|
|
87
|
+
m = TOP_HEADING_RE.search(text, start)
|
|
88
|
+
return m.start() if m else len(text)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _extract_phases(text: str) -> List[Phase]:
|
|
92
|
+
phases: List[Phase] = []
|
|
93
|
+
matches = list(PHASE_HEADING_RE.finditer(text))
|
|
94
|
+
for i, m in enumerate(matches):
|
|
95
|
+
body_start = m.end()
|
|
96
|
+
body_end = matches[i + 1].start() if i + 1 < len(matches) else _next_top_heading(text, body_start)
|
|
97
|
+
body = text[body_start:body_end]
|
|
98
|
+
phase = Phase(
|
|
99
|
+
number=int(m.group("num")),
|
|
100
|
+
name=m.group("name").strip(),
|
|
101
|
+
alias=m.group("alias").strip(),
|
|
102
|
+
raw_body=body,
|
|
103
|
+
)
|
|
104
|
+
phase.objective = _field(body, "Objective")
|
|
105
|
+
phase.exit_criteria = _checkbox_items(_field(body, "Exit criteria"))
|
|
106
|
+
phase.scope_notes = _field(body, "Scope notes")
|
|
107
|
+
phase.non_goals = _field(body, "Non-goals")
|
|
108
|
+
phase.key_files = _bullet_items(_field(body, "Key files"))
|
|
109
|
+
phase.depends_on = _parse_depends_on(_field(body, "Depends on"))
|
|
110
|
+
phase.produces = _parse_produces(_field(body, "Produces"))
|
|
111
|
+
phases.append(phase)
|
|
112
|
+
return phases
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _field(body: str, label: str) -> str:
|
|
116
|
+
pat = re.compile(FIELD_RE_TEMPLATE.format(label=re.escape(label)), re.MULTILINE)
|
|
117
|
+
m = pat.search(body)
|
|
118
|
+
return m.group("body").strip() if m else ""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _checkbox_items(block: str) -> List[str]:
|
|
122
|
+
return [
|
|
123
|
+
line.strip()[6:].strip()
|
|
124
|
+
for line in block.splitlines()
|
|
125
|
+
if line.strip().startswith("- [ ]") or line.strip().startswith("- [x]")
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _bullet_items(block: str) -> List[str]:
|
|
130
|
+
return [
|
|
131
|
+
line.strip().lstrip("-").strip()
|
|
132
|
+
for line in block.splitlines()
|
|
133
|
+
if line.strip().startswith("- ")
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _parse_depends_on(block: str) -> List[str]:
|
|
138
|
+
stripped = block.strip()
|
|
139
|
+
if not stripped or stripped.lower() in {"(none)", "none"}:
|
|
140
|
+
return []
|
|
141
|
+
items: List[str] = []
|
|
142
|
+
for line in stripped.splitlines():
|
|
143
|
+
line = line.strip().lstrip("-").strip()
|
|
144
|
+
if not line or line.lower() in {"(none)", "none"}:
|
|
145
|
+
continue
|
|
146
|
+
for m in ALIAS_TOKEN_RE.finditer(line):
|
|
147
|
+
token = (m.group(1) or m.group(2) or "").strip()
|
|
148
|
+
if token:
|
|
149
|
+
items.append(token.upper())
|
|
150
|
+
return items
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _parse_produces(block: str) -> List[str]:
|
|
154
|
+
stripped = block.strip()
|
|
155
|
+
if not stripped or stripped.lower() in {"(none)", "none"}:
|
|
156
|
+
return []
|
|
157
|
+
return [f"IF-0-{alias}-{n}" for alias, n in IF_GATE_RE.findall(stripped)]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Checks
|
|
162
|
+
|
|
163
|
+
def check_phase_heading_format(text: str, errors: List[str]) -> None:
|
|
164
|
+
for m in ANY_PHASE_HEADING_RE.finditer(text):
|
|
165
|
+
heading = m.group(0).strip()
|
|
166
|
+
if PHASE_HEADING_RE.match(heading):
|
|
167
|
+
continue
|
|
168
|
+
line_no = text.count("\n", 0, m.start()) + 1
|
|
169
|
+
errors.append(
|
|
170
|
+
f"(B) line {line_no}: invalid phase heading `{heading}`; "
|
|
171
|
+
"expected `### Phase N — <Name> (<ALIAS>)`"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def check_required_headings(sections: Dict[str, str], errors: List[str]) -> None:
|
|
176
|
+
present_prefixes = [h.split("(", 1)[0].strip().lower() for h in sections.keys()]
|
|
177
|
+
missing: List[str] = []
|
|
178
|
+
for required in REQUIRED_TOP_HEADINGS:
|
|
179
|
+
if not any(p == required.lower() or p.startswith(required.lower()) for p in present_prefixes):
|
|
180
|
+
missing.append(required)
|
|
181
|
+
if missing:
|
|
182
|
+
errors.append(f"(A) missing required level-2 headings: {', '.join(missing)}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def check_phase_fields(phases: List[Phase], errors: List[str]) -> None:
|
|
186
|
+
if not phases:
|
|
187
|
+
errors.append("(B) no phases found — expected at least one `### Phase N — <Name> (<ALIAS>)`")
|
|
188
|
+
return
|
|
189
|
+
for ph in phases:
|
|
190
|
+
loc = f"Phase {ph.number} ({ph.alias})"
|
|
191
|
+
if not ph.objective:
|
|
192
|
+
errors.append(f"(B) {loc}: missing **Objective**")
|
|
193
|
+
if not ph.exit_criteria:
|
|
194
|
+
errors.append(f"(B) {loc}: **Exit criteria** missing or has no `- [ ]` checkboxes")
|
|
195
|
+
if not ph.scope_notes:
|
|
196
|
+
errors.append(f"(B) {loc}: missing **Scope notes**")
|
|
197
|
+
if not ph.key_files:
|
|
198
|
+
errors.append(f"(B) {loc}: **Key files** missing or empty")
|
|
199
|
+
if "**Depends on**" not in ph.raw_body:
|
|
200
|
+
errors.append(f"(B) {loc}: missing **Depends on** block (use `(none)` for roots)")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def check_numbering_and_aliases(phases: List[Phase], errors: List[str]) -> None:
|
|
204
|
+
seen_aliases: Set[str] = set()
|
|
205
|
+
last_num = 0
|
|
206
|
+
for ph in phases:
|
|
207
|
+
if ph.number < last_num:
|
|
208
|
+
errors.append(f"(C) phase number {ph.number} ({ph.alias}) decreases from previous ({last_num})")
|
|
209
|
+
last_num = max(last_num, ph.number)
|
|
210
|
+
if ph.alias in seen_aliases:
|
|
211
|
+
errors.append(f"(C) duplicate alias: {ph.alias}")
|
|
212
|
+
seen_aliases.add(ph.alias)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def check_if_gates(phases: List[Phase], sections: Dict[str, str], errors: List[str]) -> None:
|
|
216
|
+
gates_section = sections.get("Top Interface-Freeze Gates", "")
|
|
217
|
+
declared: Set[str] = set()
|
|
218
|
+
for m in IF_GATE_RE.finditer(gates_section):
|
|
219
|
+
gate_id = f"IF-0-{m.group(1)}-{m.group(2)}"
|
|
220
|
+
declared.add(gate_id)
|
|
221
|
+
|
|
222
|
+
valid_aliases = {ph.alias for ph in phases}
|
|
223
|
+
for g in declared:
|
|
224
|
+
alias = g.split("-")[2]
|
|
225
|
+
if alias not in valid_aliases:
|
|
226
|
+
errors.append(f"(D) gate {g} names alias '{alias}' that is not a defined phase")
|
|
227
|
+
|
|
228
|
+
produced_global: Set[str] = set()
|
|
229
|
+
for ph in phases:
|
|
230
|
+
for g in ph.produces:
|
|
231
|
+
owner = g.split("-")[2]
|
|
232
|
+
if owner != ph.alias:
|
|
233
|
+
errors.append(
|
|
234
|
+
f"(D) Phase {ph.number} ({ph.alias}): produces {g} but its alias segment is '{owner}', "
|
|
235
|
+
f"not this phase's alias"
|
|
236
|
+
)
|
|
237
|
+
if g in produced_global:
|
|
238
|
+
errors.append(f"(D) gate {g} is declared in multiple phases' **Produces** blocks")
|
|
239
|
+
produced_global.add(g)
|
|
240
|
+
|
|
241
|
+
only_declared = declared - produced_global
|
|
242
|
+
for g in sorted(only_declared):
|
|
243
|
+
errors.append(f"(D) gate {g} listed in `## Top Interface-Freeze Gates` but not in any phase's **Produces**")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def check_depends_on(phases: List[Phase], errors: List[str]) -> List[Phase]:
|
|
247
|
+
aliases: Set[str] = {ph.alias for ph in phases}
|
|
248
|
+
seen_so_far: Set[str] = set()
|
|
249
|
+
roots: List[Phase] = []
|
|
250
|
+
for ph in phases:
|
|
251
|
+
for dep in ph.depends_on:
|
|
252
|
+
if dep not in aliases:
|
|
253
|
+
errors.append(f"(E) Phase {ph.number} ({ph.alias}): **Depends on** references unknown alias '{dep}'")
|
|
254
|
+
elif dep not in seen_so_far and dep != ph.alias:
|
|
255
|
+
errors.append(
|
|
256
|
+
f"(E) Phase {ph.number} ({ph.alias}): **Depends on** references '{dep}' "
|
|
257
|
+
f"which is not an earlier phase in document order"
|
|
258
|
+
)
|
|
259
|
+
if not ph.depends_on:
|
|
260
|
+
roots.append(ph)
|
|
261
|
+
seen_so_far.add(ph.alias)
|
|
262
|
+
if not roots:
|
|
263
|
+
errors.append("(E) no root phases found — at least one phase must have `**Depends on**` = `(none)`")
|
|
264
|
+
return roots
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def check_dag_acyclic(phases: List[Phase], errors: List[str]) -> None:
|
|
268
|
+
edges: Dict[str, List[str]] = {ph.alias: [] for ph in phases}
|
|
269
|
+
indeg: Dict[str, int] = {ph.alias: 0 for ph in phases}
|
|
270
|
+
for ph in phases:
|
|
271
|
+
for dep in ph.depends_on:
|
|
272
|
+
if dep in edges:
|
|
273
|
+
edges[dep].append(ph.alias)
|
|
274
|
+
indeg[ph.alias] += 1
|
|
275
|
+
queue = [a for a, d in indeg.items() if d == 0]
|
|
276
|
+
visited = 0
|
|
277
|
+
while queue:
|
|
278
|
+
cur = queue.pop(0)
|
|
279
|
+
visited += 1
|
|
280
|
+
for nb in edges[cur]:
|
|
281
|
+
indeg[nb] -= 1
|
|
282
|
+
if indeg[nb] == 0:
|
|
283
|
+
queue.append(nb)
|
|
284
|
+
if visited != len(phases):
|
|
285
|
+
unresolved = [a for a, d in indeg.items() if d > 0]
|
|
286
|
+
errors.append(f"(F) cycle detected in phase dependencies; unresolved: {', '.join(unresolved)}")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def check_lane_count_hint(phases: List[Phase], errors: List[str]) -> None:
|
|
290
|
+
word_num = r"(?:two|three|four|five|six|seven|eight|nine|ten)"
|
|
291
|
+
numeric_re = re.compile(
|
|
292
|
+
r"\b(?:\d+(?:\s*[\-–]\s*\d+)?|" + word_num + r"(?:\s*[\-–]\s*" + word_num + r")?)\s+lanes?\b",
|
|
293
|
+
re.IGNORECASE,
|
|
294
|
+
)
|
|
295
|
+
partition_re = re.compile(
|
|
296
|
+
r"\blane\s+[A-Z0-9]+\b|\bpartition|\bdisjoint|\bowns\b|\bsingle lane\b",
|
|
297
|
+
re.IGNORECASE,
|
|
298
|
+
)
|
|
299
|
+
lane_token_re = re.compile(r"\b[A-Z][A-Z0-9]*-lane-[A-Za-z0-9]+\b|\bSL-[A-Za-z0-9]+\b")
|
|
300
|
+
lanes_section_re = re.compile(
|
|
301
|
+
r"^\*\*Lanes\*\*[^\n]*\n(?P<body>(?:(?!^\*\*|^### |^## ).*\n?)+)",
|
|
302
|
+
re.MULTILINE,
|
|
303
|
+
)
|
|
304
|
+
for ph in phases:
|
|
305
|
+
if PREAMBLE_MARKER_RE.search(ph.scope_notes):
|
|
306
|
+
continue
|
|
307
|
+
lanes_match = lanes_section_re.search(ph.raw_body)
|
|
308
|
+
haystack = ph.scope_notes + "\n" + (lanes_match.group("body") if lanes_match else "")
|
|
309
|
+
if numeric_re.search(haystack):
|
|
310
|
+
continue
|
|
311
|
+
if partition_re.search(haystack):
|
|
312
|
+
continue
|
|
313
|
+
if len(set(lane_token_re.findall(haystack))) >= 2:
|
|
314
|
+
continue
|
|
315
|
+
errors.append(
|
|
316
|
+
f"(G) Phase {ph.number} ({ph.alias}): no lane count or partition hint in "
|
|
317
|
+
f"**Scope notes** or **Lanes**. Add e.g., 'decompose into N lanes', "
|
|
318
|
+
f"'Single lane' with justification, or mark as preamble/interface-only."
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ---------------------------------------------------------------------------
|
|
323
|
+
# Public API
|
|
324
|
+
|
|
325
|
+
def lint_roadmap_text(text: str) -> List[str]:
|
|
326
|
+
"""Return a list of human-readable issues for roadmap markdown ``text``."""
|
|
327
|
+
errors: List[str] = []
|
|
328
|
+
sections = _extract_top_sections(text)
|
|
329
|
+
phases = _extract_phases(text)
|
|
330
|
+
check_required_headings(sections, errors)
|
|
331
|
+
check_phase_heading_format(text, errors)
|
|
332
|
+
check_phase_fields(phases, errors)
|
|
333
|
+
check_numbering_and_aliases(phases, errors)
|
|
334
|
+
check_if_gates(phases, sections, errors)
|
|
335
|
+
check_depends_on(phases, errors)
|
|
336
|
+
check_dag_acyclic(phases, errors)
|
|
337
|
+
check_lane_count_hint(phases, errors)
|
|
338
|
+
return errors
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def lint_roadmap(path: Path | str) -> List[str]:
|
|
342
|
+
"""Return a list of issues for the roadmap at ``path`` (raises on read error)."""
|
|
343
|
+
return lint_roadmap_text(Path(path).read_text(encoding="utf-8"))
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# ---------------------------------------------------------------------------
|
|
347
|
+
# Train mode — validate a cross-repo release-train roadmap (P2)
|
|
348
|
+
|
|
349
|
+
def lint_train_roadmap_text(text: str) -> List[str]:
|
|
350
|
+
"""Return validation issues for a cross-repo train roadmap markdown ``text``.
|
|
351
|
+
|
|
352
|
+
Validates the cross-repo DAG: acyclic, every depended-on node exists, the
|
|
353
|
+
train is serially orderable (topo-sort), and every dependency edge carries
|
|
354
|
+
a valid consumption-channel descriptor. A non-orderable, cyclic, or
|
|
355
|
+
channel-less train fails loud (returns non-empty list).
|
|
356
|
+
|
|
357
|
+
This is a separate validation path from :func:`lint_roadmap_text` — it
|
|
358
|
+
uses the train-roadmap parser (``train_roadmap.parse_train_roadmap``), not
|
|
359
|
+
the phase-plan regex.
|
|
360
|
+
"""
|
|
361
|
+
from .train_roadmap import parse_train_roadmap, validate_train
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
roadmap = parse_train_roadmap(text)
|
|
365
|
+
except ValueError as exc:
|
|
366
|
+
return [f"(T-PARSE) {exc}"]
|
|
367
|
+
return validate_train(roadmap)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def lint_train_roadmap(path: Path | str) -> List[str]:
|
|
371
|
+
"""Return validation issues for the train roadmap at ``path``."""
|
|
372
|
+
return lint_train_roadmap_text(Path(path).read_text(encoding="utf-8"))
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def main(argv: List[str]) -> int:
|
|
376
|
+
if len(argv) < 2:
|
|
377
|
+
prog = Path(argv[0]).name if argv else "validate_roadmap"
|
|
378
|
+
print(f"usage: {prog} [--train] <roadmap-path>", file=sys.stderr)
|
|
379
|
+
return 2
|
|
380
|
+
|
|
381
|
+
# Handle --train flag
|
|
382
|
+
train_mode = False
|
|
383
|
+
args = list(argv[1:])
|
|
384
|
+
if "--train" in args:
|
|
385
|
+
train_mode = True
|
|
386
|
+
args = [a for a in args if a != "--train"]
|
|
387
|
+
|
|
388
|
+
if len(args) != 1:
|
|
389
|
+
prog = Path(argv[0]).name if argv else "validate_roadmap"
|
|
390
|
+
print(f"usage: {prog} [--train] <roadmap-path>", file=sys.stderr)
|
|
391
|
+
return 2
|
|
392
|
+
|
|
393
|
+
path = Path(args[0])
|
|
394
|
+
try:
|
|
395
|
+
text = path.read_text(encoding="utf-8")
|
|
396
|
+
except FileNotFoundError:
|
|
397
|
+
print(f"error: file not found: {path}", file=sys.stderr)
|
|
398
|
+
return 2
|
|
399
|
+
except OSError as exc:
|
|
400
|
+
print(f"error: could not read {path}: {exc}", file=sys.stderr)
|
|
401
|
+
return 2
|
|
402
|
+
|
|
403
|
+
if train_mode:
|
|
404
|
+
errors = lint_train_roadmap_text(text)
|
|
405
|
+
if errors:
|
|
406
|
+
print(f"validate_roadmap (train): {len(errors)} issue(s) in {path}", file=sys.stderr)
|
|
407
|
+
for e in errors:
|
|
408
|
+
print(f" • {e}", file=sys.stderr)
|
|
409
|
+
return 1
|
|
410
|
+
print(f"validate_roadmap (train): OK — {path}")
|
|
411
|
+
return 0
|
|
412
|
+
|
|
413
|
+
phases = _extract_phases(text)
|
|
414
|
+
errors = lint_roadmap_text(text)
|
|
415
|
+
if errors:
|
|
416
|
+
print(f"validate_roadmap: {len(errors)} issue(s) in {path}", file=sys.stderr)
|
|
417
|
+
for e in errors:
|
|
418
|
+
print(f" • {e}", file=sys.stderr)
|
|
419
|
+
return 1
|
|
420
|
+
print(f"validate_roadmap: OK — {len(phases)} phase(s) in {path}")
|
|
421
|
+
return 0
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
if __name__ == "__main__":
|
|
425
|
+
sys.exit(main(sys.argv))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Route-decision logging (model-routing-v1 P4).
|
|
2
|
+
|
|
3
|
+
A metadata-only record of *why* a dispatch resolved to a given model/effort, so a
|
|
4
|
+
human auditing a run can answer "what tier ran this, at what effort, and why".
|
|
5
|
+
Built from a `ModelSelection` (which carries only routing provenance — model,
|
|
6
|
+
effort, source, override_reason, model_class), so the record is redaction-safe by
|
|
7
|
+
construction: it never touches artifact bodies, prompts, or credentials.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .models import ModelSelection
|
|
14
|
+
|
|
15
|
+
ROUTE_LOG_KEY = "route"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_route_log(
|
|
19
|
+
selection: ModelSelection,
|
|
20
|
+
*,
|
|
21
|
+
route_reason: str | None = None,
|
|
22
|
+
escalated_from: str | None = None,
|
|
23
|
+
escalated_to: str | None = None,
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
"""A metadata-only routing record for a dispatch event's ``metadata[route]``."""
|
|
26
|
+
log: dict[str, Any] = {
|
|
27
|
+
"model_class": selection.model_class,
|
|
28
|
+
"concrete_model": selection.model,
|
|
29
|
+
"effort": selection.effort,
|
|
30
|
+
"route_reason": route_reason or selection.override_reason or selection.source,
|
|
31
|
+
}
|
|
32
|
+
if escalated_from is not None:
|
|
33
|
+
log["escalated_from"] = escalated_from
|
|
34
|
+
if escalated_to is not None:
|
|
35
|
+
log["escalated_to"] = escalated_to
|
|
36
|
+
return log
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def with_route_log(
|
|
40
|
+
metadata: dict[str, Any] | None,
|
|
41
|
+
selection: ModelSelection,
|
|
42
|
+
*,
|
|
43
|
+
route_reason: str | None = None,
|
|
44
|
+
escalated_from: str | None = None,
|
|
45
|
+
escalated_to: str | None = None,
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""Return ``metadata`` with the route record merged under ``ROUTE_LOG_KEY``.
|
|
48
|
+
|
|
49
|
+
Additive: a dispatch event-emission site calls this on its existing metadata
|
|
50
|
+
(or None) to attach the route record without disturbing other keys.
|
|
51
|
+
"""
|
|
52
|
+
merged = dict(metadata or {})
|
|
53
|
+
merged[ROUTE_LOG_KEY] = build_route_log(
|
|
54
|
+
selection,
|
|
55
|
+
route_reason=route_reason,
|
|
56
|
+
escalated_from=escalated_from,
|
|
57
|
+
escalated_to=escalated_to,
|
|
58
|
+
)
|
|
59
|
+
return merged
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Language-neutral Claude route-selection policy artifacts.
|
|
2
|
+
|
|
3
|
+
This package is a marker so the golden route-selection fixtures ship as
|
|
4
|
+
importable resource data (`importlib.resources`). The fixtures
|
|
5
|
+
(`fixtures/route_selection.golden.json`) are the frozen, cross-language
|
|
6
|
+
contract that BOTH the Python runtime (`resolve_claude_route` +
|
|
7
|
+
`claude_route_billing_posture` / `claude_route_fallback_posture` in
|
|
8
|
+
`phase_loop_runtime.launcher`) and governed-pipeline's TS `resolveClaudeRoute`
|
|
9
|
+
(gp #25) validate against. See `docs/phase-loop/route-policy-spec.md` for the
|
|
10
|
+
language-neutral decision table.
|
|
11
|
+
"""
|