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,662 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import hashlib
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .runtime_resources import packaged_resource_dir
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger("phase_loop_runtime.skill_inventory")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
HARNESS_INSTALL_ROOT_HINTS = {
|
|
15
|
+
"codex": ("~/.codex/skills",),
|
|
16
|
+
"claude": ("~/.claude/skills",),
|
|
17
|
+
"gemini": ("~/.gemini/skills",),
|
|
18
|
+
"opencode": ("~/.config/opencode/skills",),
|
|
19
|
+
"command": (),
|
|
20
|
+
"manual": (),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# DISENTANGLE (EXTRACTSKILLS SL-2): the generic runtime no longer hardcodes the
|
|
24
|
+
# per-harness dotfiles overlay roots here. The neutral generated bundle is the
|
|
25
|
+
# shared-canonical artifact (moves to agent-harness); the 4 per-harness source roots
|
|
26
|
+
# that produce the `_overrides/` are the dotfiles-OVERLAY and are now contributed by
|
|
27
|
+
# the in-tree dotfiles profile through the ``phase_loop_runtime.skill_sources``
|
|
28
|
+
# entry-point group (see :func:`iter_skill_source_roots` and ``skill_sources_plugin``).
|
|
29
|
+
# The keys are retained (callers iterate them) but carry no built-in dotfiles paths;
|
|
30
|
+
# resolution flows through the merged builtin+plugin roots.
|
|
31
|
+
HARNESS_SOURCE_ROOTS = {
|
|
32
|
+
"codex": (),
|
|
33
|
+
"claude": (),
|
|
34
|
+
"gemini": (),
|
|
35
|
+
"opencode": (),
|
|
36
|
+
"command": (),
|
|
37
|
+
"manual": (),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def _runner_repo_root() -> Path | None:
|
|
41
|
+
"""Resolve the runner's source-of-truth repo (where HARNESS_SOURCE_ROOTS live).
|
|
42
|
+
|
|
43
|
+
DECOUPLE SL-2: the generic runtime no longer walks up the filesystem into the
|
|
44
|
+
dotfiles tree (the old fleet-relative fallback). Source skill roots are an *optional*
|
|
45
|
+
integration: operators/tests set ``PHASE_LOOP_RUNNER_REPO_ROOT`` and profiles
|
|
46
|
+
contribute roots via the ``phase_loop_runtime.skill_sources`` entry-point group
|
|
47
|
+
(see :func:`iter_skill_source_roots`). With neither configured -- e.g. a clean
|
|
48
|
+
wheel install -- this returns ``None`` and source-skill resolution yields
|
|
49
|
+
nothing rather than pointing at an unrelated path.
|
|
50
|
+
"""
|
|
51
|
+
import os
|
|
52
|
+
env = os.environ.get("PHASE_LOOP_RUNNER_REPO_ROOT")
|
|
53
|
+
if env:
|
|
54
|
+
return Path(env).expanduser().resolve()
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SkillSourcePluginError(RuntimeError):
|
|
59
|
+
"""A REGISTERED skill-source plugin failed to load or invoke.
|
|
60
|
+
|
|
61
|
+
Raised (not swallowed) when a plugin that WAS configured -- via the
|
|
62
|
+
``phase_loop_runtime.skill_sources`` entry-point group or a
|
|
63
|
+
``PHASE_LOOP_SKILL_SOURCE_PLUGINS`` opt-in spec -- errors on import/load/invoke.
|
|
64
|
+
A registered-but-broken provider is a real bug and must fail loud rather than
|
|
65
|
+
silently degrade to zero roots (which would let injection inject an empty skill
|
|
66
|
+
bundle). An ABSENT provider (clean runtime, nothing configured) is NOT an error:
|
|
67
|
+
it simply yields no roots.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def iter_skill_source_roots() -> tuple[tuple[str, tuple[str, ...]], ...]:
|
|
72
|
+
"""Yield (harness_target, source_roots) pairs from installed skill-source plugins.
|
|
73
|
+
|
|
74
|
+
Profiles contribute roots two ways, mirroring the ``profile_commands`` seam
|
|
75
|
+
(see :func:`phase_loop_runtime.cli._profile_command_registrars`):
|
|
76
|
+
|
|
77
|
+
1. the ``phase_loop_runtime.skill_sources`` entry-point group (declared by a
|
|
78
|
+
profile distribution) -- the production seam, live in a real install;
|
|
79
|
+
2. the explicit ``PHASE_LOOP_SKILL_SOURCE_PLUGINS`` opt-in -- a comma-separated
|
|
80
|
+
list of ``module:attr`` provider specs, used in source-mode runs and tests
|
|
81
|
+
where the dist-info entry point is not live (the in-tree dotfiles profile is
|
|
82
|
+
opted in by the test conftest, exactly as the profile plugin is).
|
|
83
|
+
|
|
84
|
+
Each provider is a callable returning a ``{harness_target: (root, ...)}`` mapping.
|
|
85
|
+
A clean runtime with neither configured yields nothing -- discovery is plugin-
|
|
86
|
+
driven, never a fleet-path walk. Roots are de-duplicated per harness so a provider
|
|
87
|
+
loaded via BOTH the entry point and the env opt-in contributes each root once.
|
|
88
|
+
|
|
89
|
+
Fail-loud (CR #2): a provider that is REGISTERED but raises on load/invoke
|
|
90
|
+
propagates as :class:`SkillSourcePluginError` -- a real bug must not silently
|
|
91
|
+
degrade to zero roots. An absent provider is fine (empty result).
|
|
92
|
+
|
|
93
|
+
Cached on the ``PHASE_LOOP_SKILL_SOURCE_PLUGINS`` value (CR #4): the env opt-in is
|
|
94
|
+
read OUTSIDE the cache and passed as the key, so setting the env AFTER a first call
|
|
95
|
+
takes effect (the installed entry-point set is genuinely process-constant). Tests
|
|
96
|
+
that monkeypatch ``importlib.metadata.entry_points`` or the env var must call
|
|
97
|
+
``iter_skill_source_roots.cache_clear()``.
|
|
98
|
+
"""
|
|
99
|
+
import os
|
|
100
|
+
|
|
101
|
+
opt_in = os.environ.get("PHASE_LOOP_SKILL_SOURCE_PLUGINS", "")
|
|
102
|
+
return _iter_skill_source_roots_cached(opt_in)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@functools.lru_cache(maxsize=8)
|
|
106
|
+
def _iter_skill_source_roots_cached(opt_in: str) -> tuple[tuple[str, tuple[str, ...]], ...]:
|
|
107
|
+
import importlib
|
|
108
|
+
import importlib.metadata
|
|
109
|
+
|
|
110
|
+
providers: list = []
|
|
111
|
+
seen: set = set()
|
|
112
|
+
|
|
113
|
+
def _add(provider) -> None:
|
|
114
|
+
key = id(provider)
|
|
115
|
+
if key in seen:
|
|
116
|
+
return
|
|
117
|
+
seen.add(key)
|
|
118
|
+
providers.append(provider)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
entry_points = importlib.metadata.entry_points(group="phase_loop_runtime.skill_sources")
|
|
122
|
+
except TypeError: # pragma: no cover - py<3.10 selectable API
|
|
123
|
+
entry_points = importlib.metadata.entry_points().get("phase_loop_runtime.skill_sources", [])
|
|
124
|
+
for entry_point in entry_points:
|
|
125
|
+
# A registered entry point that fails to load is a real bug -- fail loud.
|
|
126
|
+
try:
|
|
127
|
+
_add(entry_point.load())
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
name = getattr(entry_point, "name", entry_point)
|
|
130
|
+
raise SkillSourcePluginError(
|
|
131
|
+
f"skill-source entry point {name!r} failed to load: {exc}"
|
|
132
|
+
) from exc
|
|
133
|
+
|
|
134
|
+
for spec in opt_in.split(","):
|
|
135
|
+
spec = spec.strip()
|
|
136
|
+
if not spec or ":" not in spec:
|
|
137
|
+
# An empty/malformed spec is "not configured", not a registered failure.
|
|
138
|
+
continue
|
|
139
|
+
module_name, _, attr = spec.partition(":")
|
|
140
|
+
# A spec the operator DID configure that won't import/resolve is a real bug.
|
|
141
|
+
try:
|
|
142
|
+
module = importlib.import_module(module_name)
|
|
143
|
+
_add(getattr(module, attr))
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
raise SkillSourcePluginError(
|
|
146
|
+
f"skill-source opt-in {spec!r} failed to load: {exc}"
|
|
147
|
+
) from exc
|
|
148
|
+
|
|
149
|
+
collected: dict[str, tuple[str, ...]] = {}
|
|
150
|
+
for provider in providers:
|
|
151
|
+
# A registered provider that raises when invoked is a real bug -- fail loud.
|
|
152
|
+
try:
|
|
153
|
+
mapping = provider() if callable(provider) else provider
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
raise SkillSourcePluginError(
|
|
156
|
+
f"skill-source provider {provider!r} raised on invoke: {exc}"
|
|
157
|
+
) from exc
|
|
158
|
+
if not isinstance(mapping, dict):
|
|
159
|
+
continue
|
|
160
|
+
for harness_target, roots in mapping.items():
|
|
161
|
+
existing = collected.get(harness_target, ())
|
|
162
|
+
merged = list(existing)
|
|
163
|
+
for root in roots:
|
|
164
|
+
if root not in merged:
|
|
165
|
+
merged.append(root)
|
|
166
|
+
collected[harness_target] = tuple(merged)
|
|
167
|
+
return tuple(collected.items())
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Re-expose cache_clear on the public function so existing tests
|
|
171
|
+
# (test_phase_loop_runtime_decouple, test_skill_sources_seam) keep working.
|
|
172
|
+
iter_skill_source_roots.cache_clear = _iter_skill_source_roots_cached.cache_clear # type: ignore[attr-defined]
|
|
173
|
+
iter_skill_source_roots.cache_info = _iter_skill_source_roots_cached.cache_info # type: ignore[attr-defined]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
BRIDGE_SKILL_NAMES = {
|
|
177
|
+
"codex": "codex-phase-loop",
|
|
178
|
+
"claude": "claude-phase-loop",
|
|
179
|
+
"gemini": "gemini-phase-loop",
|
|
180
|
+
"opencode": "opencode-phase-loop",
|
|
181
|
+
}
|
|
182
|
+
CANONICAL_WORKFLOW_SKILLS = {
|
|
183
|
+
"codex": (
|
|
184
|
+
"codex-advisor-board",
|
|
185
|
+
"codex-phase-roadmap-builder",
|
|
186
|
+
"codex-plan-phase",
|
|
187
|
+
"codex-execute-phase",
|
|
188
|
+
"codex-phase-loop",
|
|
189
|
+
"codex-run-train",
|
|
190
|
+
"codex-plan-detailed",
|
|
191
|
+
"codex-execute-detailed",
|
|
192
|
+
"codex-task-contextualizer",
|
|
193
|
+
"codex-skill-improvement-planner",
|
|
194
|
+
"codex-skill-editor",
|
|
195
|
+
),
|
|
196
|
+
"claude": (
|
|
197
|
+
"claude-advisor-board",
|
|
198
|
+
"claude-phase-roadmap-builder",
|
|
199
|
+
"claude-plan-phase",
|
|
200
|
+
"claude-execute-phase",
|
|
201
|
+
"claude-phase-loop",
|
|
202
|
+
"claude-run-train",
|
|
203
|
+
"claude-plan-detailed",
|
|
204
|
+
"claude-execute-detailed",
|
|
205
|
+
"claude-task-contextualizer",
|
|
206
|
+
"claude-skill-improvement-planner",
|
|
207
|
+
"claude-skill-editor",
|
|
208
|
+
),
|
|
209
|
+
"gemini": (
|
|
210
|
+
"gemini-advisor-board",
|
|
211
|
+
"gemini-phase-roadmap-builder",
|
|
212
|
+
"gemini-plan-phase",
|
|
213
|
+
"gemini-execute-phase",
|
|
214
|
+
"gemini-phase-loop",
|
|
215
|
+
"gemini-run-train",
|
|
216
|
+
"gemini-plan-detailed",
|
|
217
|
+
"gemini-execute-detailed",
|
|
218
|
+
"gemini-task-contextualizer",
|
|
219
|
+
"gemini-skill-improvement-planner",
|
|
220
|
+
"gemini-skill-editor",
|
|
221
|
+
),
|
|
222
|
+
"opencode": (
|
|
223
|
+
"opencode-advisor-board",
|
|
224
|
+
"opencode-phase-roadmap-builder",
|
|
225
|
+
"opencode-plan-phase",
|
|
226
|
+
"opencode-execute-phase",
|
|
227
|
+
"opencode-phase-loop",
|
|
228
|
+
"opencode-run-train",
|
|
229
|
+
"opencode-plan-detailed",
|
|
230
|
+
"opencode-execute-detailed",
|
|
231
|
+
"opencode-task-contextualizer",
|
|
232
|
+
"opencode-skill-improvement-planner",
|
|
233
|
+
"opencode-skill-editor",
|
|
234
|
+
),
|
|
235
|
+
}
|
|
236
|
+
VESTIGIAL_WORKFLOW_CANDIDATE_ROOTS = (
|
|
237
|
+
"claude-config/skills/plan-phase",
|
|
238
|
+
"claude-config/skills/execute-phase",
|
|
239
|
+
)
|
|
240
|
+
LEGACY_CLAUDE_UTILITY_ROOT = "claude-config/skills"
|
|
241
|
+
PI_ROLE_SKILL_ROOT = "phase-loop-pi/skills"
|
|
242
|
+
NON_CANONICAL_WORKFLOW_ALLOWLIST: tuple[str, ...] = ()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass(frozen=True)
|
|
246
|
+
class SkillParity:
|
|
247
|
+
recommended_installed_roots: tuple[str, ...]
|
|
248
|
+
installed_skill_roots: tuple[str, ...]
|
|
249
|
+
installed_skill_warnings: tuple[str, ...]
|
|
250
|
+
bridge_skill_inventory: tuple[dict[str, str | tuple[str, ...] | None], ...] = ()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclass(frozen=True)
|
|
254
|
+
class BridgeSkillInventoryRecord:
|
|
255
|
+
harness_target: str
|
|
256
|
+
skill_name: str
|
|
257
|
+
source_dir: str | None
|
|
258
|
+
recommended_installed_roots: tuple[str, ...]
|
|
259
|
+
installed_skill_roots: tuple[str, ...]
|
|
260
|
+
installed_path: str | None
|
|
261
|
+
parity_status: str
|
|
262
|
+
repair_target: str | None
|
|
263
|
+
|
|
264
|
+
def to_json(self) -> dict[str, str | tuple[str, ...] | None]:
|
|
265
|
+
return {
|
|
266
|
+
"harness_target": self.harness_target,
|
|
267
|
+
"skill_name": self.skill_name,
|
|
268
|
+
"source_dir": self.source_dir,
|
|
269
|
+
"recommended_installed_roots": self.recommended_installed_roots,
|
|
270
|
+
"installed_skill_roots": self.installed_skill_roots,
|
|
271
|
+
"installed_path": self.installed_path,
|
|
272
|
+
"parity_status": self.parity_status,
|
|
273
|
+
"repair_target": self.repair_target,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@dataclass(frozen=True)
|
|
278
|
+
class WorkflowSkillInventoryRecord:
|
|
279
|
+
harness_target: str
|
|
280
|
+
skill_name: str
|
|
281
|
+
source_dir: str | None
|
|
282
|
+
recommended_installed_roots: tuple[str, ...]
|
|
283
|
+
installed_skill_roots: tuple[str, ...]
|
|
284
|
+
installed_path: str | None
|
|
285
|
+
parity_status: str
|
|
286
|
+
|
|
287
|
+
def to_json(self) -> dict[str, str | tuple[str, ...] | None]:
|
|
288
|
+
return {
|
|
289
|
+
"harness_target": self.harness_target,
|
|
290
|
+
"skill_name": self.skill_name,
|
|
291
|
+
"source_dir": self.source_dir,
|
|
292
|
+
"recommended_installed_roots": self.recommended_installed_roots,
|
|
293
|
+
"installed_skill_roots": self.installed_skill_roots,
|
|
294
|
+
"installed_path": self.installed_path,
|
|
295
|
+
"parity_status": self.parity_status,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@dataclass(frozen=True)
|
|
300
|
+
class VestigialWorkflowCandidateRecord:
|
|
301
|
+
path: str
|
|
302
|
+
candidate_name: str
|
|
303
|
+
exists: bool
|
|
304
|
+
skill_file: str | None
|
|
305
|
+
status: str
|
|
306
|
+
|
|
307
|
+
def to_json(self) -> dict[str, str | bool | None]:
|
|
308
|
+
return {
|
|
309
|
+
"path": self.path,
|
|
310
|
+
"candidate_name": self.candidate_name,
|
|
311
|
+
"exists": self.exists,
|
|
312
|
+
"skill_file": self.skill_file,
|
|
313
|
+
"status": self.status,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass(frozen=True)
|
|
318
|
+
class SkillDirectoryClassificationRecord:
|
|
319
|
+
path: str
|
|
320
|
+
skill_name: str
|
|
321
|
+
classification: str
|
|
322
|
+
reason: str
|
|
323
|
+
canonical_replacement: str | None = None
|
|
324
|
+
skill_file: str | None = None
|
|
325
|
+
|
|
326
|
+
def to_json(self) -> dict[str, str | None]:
|
|
327
|
+
return {
|
|
328
|
+
"path": self.path,
|
|
329
|
+
"skill_name": self.skill_name,
|
|
330
|
+
"classification": self.classification,
|
|
331
|
+
"reason": self.reason,
|
|
332
|
+
"canonical_replacement": self.canonical_replacement,
|
|
333
|
+
"skill_file": self.skill_file,
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def recommended_installed_roots(harness_target: str) -> tuple[str, ...]:
|
|
338
|
+
return HARNESS_INSTALL_ROOT_HINTS.get(harness_target, ())
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def discover_installed_skill_roots(harness_target: str) -> tuple[str, ...]:
|
|
342
|
+
roots: list[str] = []
|
|
343
|
+
for hint in recommended_installed_roots(harness_target):
|
|
344
|
+
path = Path(hint).expanduser()
|
|
345
|
+
if path.exists():
|
|
346
|
+
roots.append(str(path.resolve()))
|
|
347
|
+
return tuple(roots)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@functools.lru_cache(maxsize=1)
|
|
351
|
+
def _packaged_skills_bundle_dir() -> Path | None:
|
|
352
|
+
"""The assembled neutral skill bundle shipped as package-data in the wheel (#12).
|
|
353
|
+
|
|
354
|
+
Absolute, install-location-anchored via ``importlib.resources``, so a pinned
|
|
355
|
+
``pip install`` resolves the workflow skills without a dotfiles overlay. The
|
|
356
|
+
bundle is flat: ``skills_bundle/<harness>-<skill>/`` (e.g.
|
|
357
|
+
``claude-phase-roadmap-builder``), exactly the ``skill_name`` resolution joins.
|
|
358
|
+
Returns ``None`` when the bundle is not packaged (e.g. an older wheel).
|
|
359
|
+
"""
|
|
360
|
+
return packaged_resource_dir("skills_bundle")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@functools.lru_cache(maxsize=1)
|
|
364
|
+
def _canonical_skill_source_present() -> bool:
|
|
365
|
+
"""True in a dev/fleet *source* checkout — when the canonical ``phase-loop-skills/``
|
|
366
|
+
source tree is reachable beside the installed package.
|
|
367
|
+
|
|
368
|
+
Used to gate the packaged-bundle fallback (#12 CR): in a source checkout an empty
|
|
369
|
+
skill resolution means "the overlay/plugin wasn't bootstrapped" — a real error that
|
|
370
|
+
must fail loud (so the dev re-runs bootstrap and edits take effect), NOT something to
|
|
371
|
+
paper over with the committed `skills_bundle/` (which would inject stale skills). In a
|
|
372
|
+
pinned `pip install` there is no sibling source, so the packaged bundle is the
|
|
373
|
+
legitimate resolution. Covers both the agent-harness layout (`<root>/phase-loop-skills/`)
|
|
374
|
+
and the dotfiles-vendored layout (`vendor/phase-loop-skills/`); `site-packages` has
|
|
375
|
+
neither.
|
|
376
|
+
"""
|
|
377
|
+
here = Path(__file__).resolve()
|
|
378
|
+
return any((parent / "phase-loop-skills").is_dir() for parent in here.parents)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def resolve_source_skill_dir(repo: Path, harness_target: str, skill_name: str) -> Path | None:
|
|
382
|
+
plugin_roots = dict(iter_skill_source_roots()).get(harness_target, ())
|
|
383
|
+
builtin_roots = HARNESS_SOURCE_ROOTS.get(harness_target, ())
|
|
384
|
+
source_roots = (*builtin_roots, *plugin_roots)
|
|
385
|
+
for root in source_roots:
|
|
386
|
+
candidate = repo / root / skill_name
|
|
387
|
+
if candidate.is_dir():
|
|
388
|
+
return candidate.resolve()
|
|
389
|
+
runner_root = _runner_repo_root()
|
|
390
|
+
if runner_root is not None:
|
|
391
|
+
for root in source_roots:
|
|
392
|
+
candidate = runner_root / root / skill_name
|
|
393
|
+
if candidate.is_dir():
|
|
394
|
+
return candidate.resolve()
|
|
395
|
+
# Final fallback: the bundle shipped inside the package (#12). A genuine LAST resort,
|
|
396
|
+
# gated so it fires ONLY in a pinned install (no canonical source reachable). In a
|
|
397
|
+
# dev/fleet source checkout the overlay/plugin roots above are the real source; an
|
|
398
|
+
# empty resolution there must fail loud (re-run bootstrap), not silently serve the
|
|
399
|
+
# committed bundle. So we suppress the fallback when `phase-loop-skills/` is present.
|
|
400
|
+
if not _canonical_skill_source_present():
|
|
401
|
+
packaged = _packaged_skills_bundle_dir()
|
|
402
|
+
if packaged is not None:
|
|
403
|
+
candidate = packaged / skill_name
|
|
404
|
+
if candidate.is_dir():
|
|
405
|
+
return candidate.resolve()
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def inspect_skill_parity(repo: Path, harness_target: str, expected_skill_pack: tuple[str, ...]) -> SkillParity:
|
|
410
|
+
recommended = recommended_installed_roots(harness_target)
|
|
411
|
+
installed_roots = discover_installed_skill_roots(harness_target)
|
|
412
|
+
bridge_inventory = inspect_bridge_skill(repo, harness_target)
|
|
413
|
+
warnings = (_bridge_skill_warning(bridge_inventory),) if bridge_inventory is not None else ()
|
|
414
|
+
inventory_json = (bridge_inventory.to_json(),) if bridge_inventory is not None else ()
|
|
415
|
+
return SkillParity(
|
|
416
|
+
recommended_installed_roots=recommended,
|
|
417
|
+
installed_skill_roots=installed_roots,
|
|
418
|
+
installed_skill_warnings=warnings,
|
|
419
|
+
bridge_skill_inventory=inventory_json,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def inspect_bridge_skill_inventory(
|
|
424
|
+
repo: Path,
|
|
425
|
+
harness_targets: tuple[str, ...] = ("codex", "claude", "gemini", "opencode"),
|
|
426
|
+
) -> tuple[BridgeSkillInventoryRecord, ...]:
|
|
427
|
+
records: list[BridgeSkillInventoryRecord] = []
|
|
428
|
+
for harness_target in harness_targets:
|
|
429
|
+
record = inspect_bridge_skill(repo, harness_target)
|
|
430
|
+
if record is not None:
|
|
431
|
+
records.append(record)
|
|
432
|
+
return tuple(records)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def inspect_workflow_skill_inventory(
|
|
436
|
+
repo: Path,
|
|
437
|
+
harness_targets: tuple[str, ...] = ("codex", "claude", "gemini", "opencode"),
|
|
438
|
+
) -> tuple[WorkflowSkillInventoryRecord, ...]:
|
|
439
|
+
records: list[WorkflowSkillInventoryRecord] = []
|
|
440
|
+
for harness_target in harness_targets:
|
|
441
|
+
recommended = recommended_installed_roots(harness_target)
|
|
442
|
+
installed_roots = discover_installed_skill_roots(harness_target)
|
|
443
|
+
for skill_name in CANONICAL_WORKFLOW_SKILLS.get(harness_target, ()):
|
|
444
|
+
source_dir = resolve_source_skill_dir(repo, harness_target, skill_name)
|
|
445
|
+
installed_match = _matching_installed_skill(installed_roots, skill_name)
|
|
446
|
+
if source_dir is None:
|
|
447
|
+
parity_status = "missing_source"
|
|
448
|
+
elif not installed_roots:
|
|
449
|
+
parity_status = "missing_root"
|
|
450
|
+
elif installed_match is None:
|
|
451
|
+
parity_status = "missing_skill"
|
|
452
|
+
elif _paths_equivalent(source_dir, installed_match) or _skill_hash(source_dir) == _skill_hash(installed_match):
|
|
453
|
+
parity_status = "ok"
|
|
454
|
+
else:
|
|
455
|
+
parity_status = "drifted"
|
|
456
|
+
records.append(
|
|
457
|
+
WorkflowSkillInventoryRecord(
|
|
458
|
+
harness_target=harness_target,
|
|
459
|
+
skill_name=skill_name,
|
|
460
|
+
source_dir=str(source_dir) if source_dir is not None else None,
|
|
461
|
+
recommended_installed_roots=recommended,
|
|
462
|
+
installed_skill_roots=installed_roots,
|
|
463
|
+
installed_path=str(installed_match) if installed_match is not None else None,
|
|
464
|
+
parity_status=parity_status,
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
return tuple(records)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def inspect_vestigial_workflow_candidates(repo: Path) -> tuple[VestigialWorkflowCandidateRecord, ...]:
|
|
471
|
+
records: list[VestigialWorkflowCandidateRecord] = []
|
|
472
|
+
for root in VESTIGIAL_WORKFLOW_CANDIDATE_ROOTS:
|
|
473
|
+
path = repo / root
|
|
474
|
+
skill_file = path / "SKILL.md"
|
|
475
|
+
records.append(
|
|
476
|
+
VestigialWorkflowCandidateRecord(
|
|
477
|
+
path=str(path),
|
|
478
|
+
candidate_name=path.name,
|
|
479
|
+
exists=path.exists(),
|
|
480
|
+
skill_file=str(skill_file) if skill_file.exists() else None,
|
|
481
|
+
status="remove" if skill_file.exists() else "archived-history",
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
return tuple(records)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def classify_skill_like_directories(
|
|
488
|
+
repo: Path,
|
|
489
|
+
harness_targets: tuple[str, ...] = ("codex", "claude", "gemini", "opencode"),
|
|
490
|
+
) -> tuple[SkillDirectoryClassificationRecord, ...]:
|
|
491
|
+
records: list[SkillDirectoryClassificationRecord] = []
|
|
492
|
+
canonical_names = {
|
|
493
|
+
harness_target: set(CANONICAL_WORKFLOW_SKILLS.get(harness_target, ()))
|
|
494
|
+
for harness_target in harness_targets
|
|
495
|
+
}
|
|
496
|
+
plugin_source_roots = dict(iter_skill_source_roots())
|
|
497
|
+
for harness_target in harness_targets:
|
|
498
|
+
# DISENTANGLE SL-2: iterate the MERGED builtin+plugin roots (mirrors
|
|
499
|
+
# resolve_source_skill_dir above) so canonical records still emit once the
|
|
500
|
+
# built-in HARNESS_SOURCE_ROOTS entries are empty and the dotfiles overlay
|
|
501
|
+
# roots arrive through the skill_sources seam.
|
|
502
|
+
merged_roots = (
|
|
503
|
+
*HARNESS_SOURCE_ROOTS.get(harness_target, ()),
|
|
504
|
+
*plugin_source_roots.get(harness_target, ()),
|
|
505
|
+
)
|
|
506
|
+
for root in merged_roots:
|
|
507
|
+
base = repo / root
|
|
508
|
+
if not base.is_dir():
|
|
509
|
+
continue
|
|
510
|
+
for child in sorted((path for path in base.iterdir() if path.is_dir()), key=lambda path: path.name):
|
|
511
|
+
if child.name.startswith("."):
|
|
512
|
+
continue
|
|
513
|
+
skill_file = child / "SKILL.md"
|
|
514
|
+
if child.name in canonical_names[harness_target]:
|
|
515
|
+
records.append(
|
|
516
|
+
SkillDirectoryClassificationRecord(
|
|
517
|
+
path=str(child),
|
|
518
|
+
skill_name=child.name,
|
|
519
|
+
classification="canonical",
|
|
520
|
+
reason=f"{harness_target} harness workflow source",
|
|
521
|
+
skill_file=str(skill_file) if skill_file.exists() else None,
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
legacy_root = repo / LEGACY_CLAUDE_UTILITY_ROOT
|
|
525
|
+
if legacy_root.is_dir():
|
|
526
|
+
vestigial_names = {Path(root).name for root in VESTIGIAL_WORKFLOW_CANDIDATE_ROOTS}
|
|
527
|
+
for child in sorted((path for path in legacy_root.iterdir() if path.is_dir()), key=lambda path: path.name):
|
|
528
|
+
if child.name.startswith("."):
|
|
529
|
+
continue
|
|
530
|
+
skill_file = child / "SKILL.md"
|
|
531
|
+
if child.name in vestigial_names:
|
|
532
|
+
records.append(_classify_vestigial_workflow_directory(child))
|
|
533
|
+
elif skill_file.exists():
|
|
534
|
+
records.append(
|
|
535
|
+
SkillDirectoryClassificationRecord(
|
|
536
|
+
path=str(child),
|
|
537
|
+
skill_name=child.name,
|
|
538
|
+
classification="legacy-utility",
|
|
539
|
+
reason="legacy Claude utility skill root, not a harness workflow source",
|
|
540
|
+
skill_file=str(skill_file),
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
pi_root = repo / PI_ROLE_SKILL_ROOT
|
|
544
|
+
if pi_root.is_dir():
|
|
545
|
+
for child in sorted((path for path in pi_root.iterdir() if path.is_dir()), key=lambda path: path.name):
|
|
546
|
+
skill_file = child / "SKILL.md"
|
|
547
|
+
if skill_file.exists():
|
|
548
|
+
records.append(
|
|
549
|
+
SkillDirectoryClassificationRecord(
|
|
550
|
+
path=str(child),
|
|
551
|
+
skill_name=child.name,
|
|
552
|
+
classification="pi-role",
|
|
553
|
+
reason="Pi Agent role-style skill exception",
|
|
554
|
+
skill_file=str(skill_file),
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
return tuple(records)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def inspect_bridge_skill(repo: Path, harness_target: str) -> BridgeSkillInventoryRecord | None:
|
|
561
|
+
skill_name = BRIDGE_SKILL_NAMES.get(harness_target)
|
|
562
|
+
if skill_name is None:
|
|
563
|
+
return None
|
|
564
|
+
recommended = recommended_installed_roots(harness_target)
|
|
565
|
+
installed_roots = discover_installed_skill_roots(harness_target)
|
|
566
|
+
source_dir = resolve_source_skill_dir(repo, harness_target, skill_name)
|
|
567
|
+
installed_match = _matching_installed_skill(installed_roots, skill_name)
|
|
568
|
+
repair_target = _repair_target(recommended, installed_roots, skill_name)
|
|
569
|
+
if source_dir is None:
|
|
570
|
+
parity_status = "missing_skill"
|
|
571
|
+
elif not installed_roots:
|
|
572
|
+
parity_status = "missing_root"
|
|
573
|
+
elif installed_match is None:
|
|
574
|
+
parity_status = "missing_skill"
|
|
575
|
+
elif _paths_equivalent(source_dir, installed_match) or _skill_hash(source_dir) == _skill_hash(installed_match):
|
|
576
|
+
parity_status = "ok"
|
|
577
|
+
else:
|
|
578
|
+
parity_status = "drifted"
|
|
579
|
+
return BridgeSkillInventoryRecord(
|
|
580
|
+
harness_target=harness_target,
|
|
581
|
+
skill_name=skill_name,
|
|
582
|
+
source_dir=str(source_dir) if source_dir is not None else None,
|
|
583
|
+
recommended_installed_roots=recommended,
|
|
584
|
+
installed_skill_roots=installed_roots,
|
|
585
|
+
installed_path=str(installed_match) if installed_match is not None else None,
|
|
586
|
+
parity_status=parity_status,
|
|
587
|
+
repair_target=repair_target,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _classify_vestigial_workflow_directory(path: Path) -> SkillDirectoryClassificationRecord:
|
|
592
|
+
skill_file = path / "SKILL.md"
|
|
593
|
+
replacement = {
|
|
594
|
+
"plan-phase": "claude-plan-phase",
|
|
595
|
+
"execute-phase": "claude-execute-phase",
|
|
596
|
+
}.get(path.name)
|
|
597
|
+
if skill_file.exists() and path.name not in NON_CANONICAL_WORKFLOW_ALLOWLIST:
|
|
598
|
+
classification = "remove"
|
|
599
|
+
reason = "non-canonical workflow skill definition under legacy Claude utility root"
|
|
600
|
+
else:
|
|
601
|
+
classification = "archived-history"
|
|
602
|
+
reason = "ignored historical handoff or reflection residue, not a runtime skill source"
|
|
603
|
+
return SkillDirectoryClassificationRecord(
|
|
604
|
+
path=str(path),
|
|
605
|
+
skill_name=path.name,
|
|
606
|
+
classification=classification,
|
|
607
|
+
reason=reason,
|
|
608
|
+
canonical_replacement=replacement,
|
|
609
|
+
skill_file=str(skill_file) if skill_file.exists() else None,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def _matching_installed_skill(installed_roots: tuple[str, ...], skill_name: str) -> Path | None:
|
|
614
|
+
for root in installed_roots:
|
|
615
|
+
candidate = Path(root) / skill_name
|
|
616
|
+
if candidate.exists():
|
|
617
|
+
return candidate.resolve()
|
|
618
|
+
return None
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def _paths_equivalent(left: Path, right: Path) -> bool:
|
|
622
|
+
try:
|
|
623
|
+
return left.samefile(right)
|
|
624
|
+
except OSError:
|
|
625
|
+
return False
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def _skill_hash(path: Path) -> str:
|
|
629
|
+
digest = hashlib.sha256()
|
|
630
|
+
skill_file = path / "SKILL.md"
|
|
631
|
+
if skill_file.exists():
|
|
632
|
+
digest.update(skill_file.read_bytes())
|
|
633
|
+
else:
|
|
634
|
+
digest.update(path.name.encode("utf-8"))
|
|
635
|
+
return digest.hexdigest()
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _repair_target(recommended_roots: tuple[str, ...], installed_roots: tuple[str, ...], skill_name: str) -> str | None:
|
|
639
|
+
roots = installed_roots or tuple(str(Path(root).expanduser()) for root in recommended_roots)
|
|
640
|
+
if not roots:
|
|
641
|
+
return None
|
|
642
|
+
return str((Path(roots[0]) / skill_name).expanduser())
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _bridge_skill_warning(record: BridgeSkillInventoryRecord) -> str:
|
|
646
|
+
prefix = f"{record.harness_target}:{record.skill_name}"
|
|
647
|
+
if record.parity_status == "ok":
|
|
648
|
+
return f"{prefix}: installed bridge skill is in sync for manual reentry"
|
|
649
|
+
if record.parity_status == "missing_root":
|
|
650
|
+
return (
|
|
651
|
+
f"{prefix}: installed bridge root missing; autonomous injected bundle remains authoritative, "
|
|
652
|
+
f"but local reentry should run sync-skills --apply"
|
|
653
|
+
)
|
|
654
|
+
if record.parity_status == "missing_skill":
|
|
655
|
+
return (
|
|
656
|
+
f"{prefix}: installed bridge skill missing; autonomous injected bundle remains authoritative, "
|
|
657
|
+
f"but local reentry should run sync-skills --apply"
|
|
658
|
+
)
|
|
659
|
+
return (
|
|
660
|
+
f"{prefix}: installed bridge skill drifted from repo source; autonomous injected bundle remains authoritative, "
|
|
661
|
+
f"but local reentry should run sync-skills --apply"
|
|
662
|
+
)
|