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,184 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import PurePosixPath
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
from .models import CHANGED_PATH_CATEGORIES, SourceTruthImpact
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_SOURCE_TRUTH_REASON_BY_CATEGORY = {
|
|
12
|
+
"docs": "docs_source_truth_touched",
|
|
13
|
+
"specs": "specs_source_truth_touched",
|
|
14
|
+
"active_canonical_spec": "active_specs_touched",
|
|
15
|
+
"managed_root_mirror_spec": "managed_mirror_specs_touched",
|
|
16
|
+
"mirror_manifest": "mirror_manifests_touched",
|
|
17
|
+
"archive_manifest": "archive_manifests_touched",
|
|
18
|
+
"archived_spec": "archived_specs_touched",
|
|
19
|
+
"unmanaged_spec": "unmanaged_specs_touched",
|
|
20
|
+
"pipeline_sources": "pipeline_sources_touched",
|
|
21
|
+
"portal_contract_refs": "portal_contract_refs_touched",
|
|
22
|
+
"greenfield_authority_refs": "greenfield_authority_refs_touched",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_CATEGORY_BY_PROTECTED_SOURCE_ROLE = {
|
|
26
|
+
"active_canonical_spec": "active_canonical_spec",
|
|
27
|
+
"managed_mirror_file": "managed_root_mirror_spec",
|
|
28
|
+
"mirror_manifest": "mirror_manifest",
|
|
29
|
+
"archive_manifest": "archive_manifest",
|
|
30
|
+
"archived_spec": "archived_spec",
|
|
31
|
+
"unmanaged_spec_input": "unmanaged_spec",
|
|
32
|
+
"root_specs_intake": "unmanaged_spec",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_FORBIDDEN_METADATA_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
|
|
36
|
+
("raw_diff", re.compile(r"diff --git|@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@")),
|
|
37
|
+
("raw_spec_body", re.compile(r"raw spec bod(?:y|ies)|spec body bytes|verbatim spec", re.I)),
|
|
38
|
+
("raw_transcript", re.compile(r"raw transcript|transcript bytes|verbatim transcript", re.I)),
|
|
39
|
+
("secret_like_value", re.compile(r"(?:api[_-]?key|secret|token|password)\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{12,}", re.I)),
|
|
40
|
+
("absolute_private_path", re.compile(r"/(?:home|users|mnt/(?:private|evidence|secure|raw|HC_Volume_[^/\s]+))/(?:[^\"'\s]+)", re.I)),
|
|
41
|
+
("provider_payload", re.compile(r"raw provider payload|provider payload|anthropic[_-]?payload|openai[_-]?payload", re.I)),
|
|
42
|
+
("credential_payload", re.compile(r"credential payload|private key|-----begin [a-z ]*private key-----", re.I)),
|
|
43
|
+
("local_env_value", re.compile(r"local env value|\.env(?:\.local)? value|process\.env\[[^\]]+\]\s*=", re.I)),
|
|
44
|
+
("private_evidence", re.compile(r"private evidence|evidence bytes|raw evidence", re.I)),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def classify_changed_path(path: str, protected_source_roles: Mapping[str, str] | None = None) -> str:
|
|
49
|
+
normalized = _normalize_path(path)
|
|
50
|
+
parts = PurePosixPath(normalized).parts
|
|
51
|
+
lower = normalized.lower()
|
|
52
|
+
role_category = _category_from_protected_source_role(normalized, protected_source_roles)
|
|
53
|
+
if role_category is not None:
|
|
54
|
+
return role_category
|
|
55
|
+
|
|
56
|
+
if normalized.startswith("tests/") or "/tests/" in normalized or "/fixtures/" in normalized:
|
|
57
|
+
return "tests"
|
|
58
|
+
if lower == "readme.md" or normalized.startswith("docs/") or normalized.endswith(".md") and "/docs/" in normalized:
|
|
59
|
+
return "docs"
|
|
60
|
+
if _looks_like_mirror_manifest(normalized, lower):
|
|
61
|
+
return "mirror_manifest"
|
|
62
|
+
if _looks_like_archive_manifest(normalized, lower):
|
|
63
|
+
return "archive_manifest"
|
|
64
|
+
if _looks_like_active_canonical_spec(normalized, lower):
|
|
65
|
+
return "active_canonical_spec"
|
|
66
|
+
if _looks_like_archived_spec(normalized, lower):
|
|
67
|
+
return "archived_spec"
|
|
68
|
+
if normalized.startswith("specs/") or normalized.startswith("spec/"):
|
|
69
|
+
return "unmanaged_spec"
|
|
70
|
+
if (
|
|
71
|
+
normalized.startswith(".pipeline/")
|
|
72
|
+
or "pipeline.definition.json" in lower
|
|
73
|
+
or normalized.startswith("packages/pipeline-schema/")
|
|
74
|
+
or normalized.startswith("pipeline-sources/")
|
|
75
|
+
):
|
|
76
|
+
return "pipeline_sources"
|
|
77
|
+
if (
|
|
78
|
+
"portal-contract" in lower
|
|
79
|
+
or normalized.startswith("portal/contracts/")
|
|
80
|
+
or normalized.startswith("contracts/portal/")
|
|
81
|
+
or normalized.startswith("consiliency-portal/contracts/")
|
|
82
|
+
):
|
|
83
|
+
return "portal_contract_refs"
|
|
84
|
+
if (
|
|
85
|
+
"greenfield-authority" in lower
|
|
86
|
+
or normalized.startswith("greenfield/authority/")
|
|
87
|
+
or normalized.startswith("greenfield/contracts/")
|
|
88
|
+
or normalized.startswith("authority/greenfield/")
|
|
89
|
+
):
|
|
90
|
+
return "greenfield_authority_refs"
|
|
91
|
+
if _looks_like_code_path(normalized, parts):
|
|
92
|
+
return "code"
|
|
93
|
+
return "unknown"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_source_truth_impact(
|
|
97
|
+
changed_paths: tuple[str, ...] | list[str] | Any,
|
|
98
|
+
protected_source_roles: Mapping[str, str] | None = None,
|
|
99
|
+
) -> SourceTruthImpact:
|
|
100
|
+
paths = _stable_paths(changed_paths)
|
|
101
|
+
boundaries = tuple(
|
|
102
|
+
{"path": path, "category": classify_changed_path(path, protected_source_roles)}
|
|
103
|
+
for path in paths
|
|
104
|
+
)
|
|
105
|
+
reasons: list[str] = []
|
|
106
|
+
for boundary in boundaries:
|
|
107
|
+
category = boundary["category"]
|
|
108
|
+
reason = _SOURCE_TRUTH_REASON_BY_CATEGORY.get(category)
|
|
109
|
+
if reason is not None:
|
|
110
|
+
reasons.append(reason)
|
|
111
|
+
if "adoption" in boundary["path"].lower() and "contract" in boundary["path"].lower():
|
|
112
|
+
reasons.append("adoption_contracts_touched")
|
|
113
|
+
if "contract" in boundary["path"].lower() and category in CHANGED_PATH_CATEGORIES:
|
|
114
|
+
reasons.append("contract_refs_touched")
|
|
115
|
+
return SourceTruthImpact(
|
|
116
|
+
changed_path_boundaries=boundaries,
|
|
117
|
+
canonical_refresh_recommended=bool(reasons),
|
|
118
|
+
canonical_refresh_reason_codes=tuple(sorted(dict.fromkeys(reasons))),
|
|
119
|
+
redaction_posture="metadata_only",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def metadata_redaction_diagnostic(payload: Mapping[str, Any] | None) -> dict[str, str] | None:
|
|
124
|
+
if payload is None:
|
|
125
|
+
return None
|
|
126
|
+
serialized = json.dumps(payload, sort_keys=True)
|
|
127
|
+
for kind, pattern in _FORBIDDEN_METADATA_PATTERNS:
|
|
128
|
+
if pattern.search(serialized):
|
|
129
|
+
return {"kind": "malformed_closeout", "message": f"closeout contains forbidden metadata token: {kind}"}
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _normalize_path(path: str) -> str:
|
|
134
|
+
normalized = str(path).replace("\\", "/").strip()
|
|
135
|
+
return normalized[2:] if normalized.startswith("./") else normalized
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _stable_paths(paths: tuple[str, ...] | list[str] | Any) -> list[str]:
|
|
139
|
+
if not isinstance(paths, (tuple, list)):
|
|
140
|
+
return []
|
|
141
|
+
return sorted(dict.fromkeys(_normalize_path(str(path)) for path in paths if str(path).strip()))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _looks_like_code_path(path: str, parts: tuple[str, ...]) -> bool:
|
|
145
|
+
if path.startswith(("codex-config/", "shared/skills/", "scripts/")):
|
|
146
|
+
return True
|
|
147
|
+
if parts and parts[0] in {"bin", "lib", "src"}:
|
|
148
|
+
return True
|
|
149
|
+
return PurePosixPath(path).suffix in {".py", ".sh", ".bash", ".zsh", ".toml", ".yaml", ".yml", ".json"}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _category_from_protected_source_role(
|
|
153
|
+
path: str,
|
|
154
|
+
protected_source_roles: Mapping[str, str] | None,
|
|
155
|
+
) -> str | None:
|
|
156
|
+
if not protected_source_roles:
|
|
157
|
+
return None
|
|
158
|
+
role = protected_source_roles.get(path) or protected_source_roles.get(path.lower())
|
|
159
|
+
if role is None:
|
|
160
|
+
return None
|
|
161
|
+
return _CATEGORY_BY_PROTECTED_SOURCE_ROLE.get(role)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _looks_like_mirror_manifest(path: str, lower: str) -> bool:
|
|
165
|
+
name = PurePosixPath(path).name.lower()
|
|
166
|
+
return name == "mirror-manifest.json" or "mirror_manifest" in lower or "/mirror-manifest" in lower
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _looks_like_archive_manifest(path: str, lower: str) -> bool:
|
|
170
|
+
name = PurePosixPath(path).name.lower()
|
|
171
|
+
return name == "archive-manifest.json" or "archive_manifest" in lower or "/archive-manifest" in lower
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _looks_like_active_canonical_spec(path: str, lower: str) -> bool:
|
|
175
|
+
return (
|
|
176
|
+
path.startswith(".pipeline/specs/active/")
|
|
177
|
+
or path.startswith(".pipeline/specs/canonical/")
|
|
178
|
+
or "/active-canonical/" in lower
|
|
179
|
+
or "/canonical-specs/" in lower
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _looks_like_archived_spec(path: str, lower: str) -> bool:
|
|
184
|
+
return path.startswith(".pipeline/specs/archive/") or "/archived-specs/" in lower
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Multi-machine reflection collection (Slice 1).
|
|
2
|
+
|
|
3
|
+
Reflections are written locally by skills at close-out (see
|
|
4
|
+
``claude-config/shared/runtime-state.md``) and never leave the machine — they
|
|
5
|
+
are gitignored and live in ``~/.<harness>/skills/<skill>/reflections/``. This
|
|
6
|
+
module provides the *collection half*: redact each reflection (so it is safe to
|
|
7
|
+
pool across machines, including off-tailnet collaborators) and copy it under a
|
|
8
|
+
machine-namespaced path into a shared store directory.
|
|
9
|
+
|
|
10
|
+
Two pure, testable building blocks:
|
|
11
|
+
|
|
12
|
+
* :func:`machine_id` — a stable, non-secret per-machine identifier.
|
|
13
|
+
* :func:`redact_reflection_text` — deterministically strips repo/branch/commit/
|
|
14
|
+
path identity from a reflection so the pooled copy is repo-agnostic.
|
|
15
|
+
|
|
16
|
+
and the orchestrator :func:`export_reflections`, invoked by
|
|
17
|
+
``scripts/reflections-sync``. The git transport and run-id dedup ledger are
|
|
18
|
+
intentionally deferred to Slice 2 (a ``phase-loop reflections sync`` subcommand).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import hashlib
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import socket
|
|
28
|
+
import sys
|
|
29
|
+
import uuid
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
from .skill_paths import resolve_skill_bundle_root
|
|
33
|
+
|
|
34
|
+
DEFAULT_HARNESSES: tuple[str, ...] = ("claude", "codex", "gemini", "opencode")
|
|
35
|
+
|
|
36
|
+
# Repo-local (source-controlled) reflection roots per harness, relative to repo.
|
|
37
|
+
_REPO_LOCAL_ROOTS: dict[str, tuple[str, ...]] = {
|
|
38
|
+
"claude": ("claude-config/claude-skills", "claude-config/skills"),
|
|
39
|
+
"codex": ("codex-config/skills",),
|
|
40
|
+
"gemini": ("gemini-config/skills",),
|
|
41
|
+
"opencode": ("opencode-config/skills",),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Machine identity
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
def _slug(value: str) -> str:
|
|
50
|
+
out = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
|
|
51
|
+
out = re.sub(r"-{2,}", "-", out).strip("-")
|
|
52
|
+
return out or "unknown"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _machine_id_file() -> Path:
|
|
56
|
+
base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
|
|
57
|
+
return Path(base) / "dotfiles" / "machine-id"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def machine_id() -> str:
|
|
61
|
+
"""Return a stable, non-secret identifier for this machine.
|
|
62
|
+
|
|
63
|
+
Persisted at ``$XDG_CONFIG_HOME/dotfiles/machine-id`` (default
|
|
64
|
+
``~/.config/dotfiles/machine-id``) so it survives across runs and is
|
|
65
|
+
independent of tailnet membership — off-tailnet collaborators have no
|
|
66
|
+
tailscale hostname. ``DOTFILES_MACHINE_ID`` overrides. Falls back to a
|
|
67
|
+
hostname-derived hash if the id file cannot be written.
|
|
68
|
+
"""
|
|
69
|
+
override = os.environ.get("DOTFILES_MACHINE_ID")
|
|
70
|
+
if override and override.strip():
|
|
71
|
+
return _slug(override)
|
|
72
|
+
|
|
73
|
+
path = _machine_id_file()
|
|
74
|
+
try:
|
|
75
|
+
existing = path.read_text(encoding="utf-8").strip()
|
|
76
|
+
if existing:
|
|
77
|
+
return _slug(existing)
|
|
78
|
+
except OSError:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
new_id = uuid.uuid4().hex[:12]
|
|
82
|
+
try:
|
|
83
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
path.write_text(new_id + "\n", encoding="utf-8")
|
|
85
|
+
return new_id
|
|
86
|
+
except OSError:
|
|
87
|
+
host = socket.gethostname() or "unknown-host"
|
|
88
|
+
return "host-" + hashlib.sha256(host.encode("utf-8")).hexdigest()[:10]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Redaction
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
# Structured Run-context / frontmatter fields whose values reveal identity.
|
|
96
|
+
# Maps a lowercased field name to the placeholder prefix used for its hash.
|
|
97
|
+
_SENSITIVE_FIELDS: dict[str, str] = {
|
|
98
|
+
"repo": "repo",
|
|
99
|
+
"repo root": "repo-root",
|
|
100
|
+
"repo_root": "repo-root",
|
|
101
|
+
"repo path": "repo-root",
|
|
102
|
+
"branch": "branch",
|
|
103
|
+
"branch slug": "branch",
|
|
104
|
+
"branch_slug": "branch",
|
|
105
|
+
"commit": "commit",
|
|
106
|
+
"artifact": "artifact",
|
|
107
|
+
"artifact path": "artifact",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Matches "- Field: value", "Field: value", "field = value" (markdown or yaml).
|
|
111
|
+
_FIELD_RE = re.compile(
|
|
112
|
+
r"^(?P<prefix>\s*(?:[-*]\s+)?(?P<name>[A-Za-z][A-Za-z _]*?)\s*[:=]\s*)(?P<value>.*?)\s*$"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Already-redacted token (e.g. "<repo:ab12cd34ef>") or an explicit empty marker.
|
|
116
|
+
_REDACTED_RE = re.compile(r"^(<[^>]*>|none|n/?a|-)?$", re.IGNORECASE)
|
|
117
|
+
|
|
118
|
+
# Absolute project/worktree roots: collapse the private prefix *and* the project
|
|
119
|
+
# segment to "<repo>", keeping any instructive repo-relative tail.
|
|
120
|
+
_PROJECT_PATH_RE = re.compile(
|
|
121
|
+
r"/(?:mnt/[^/\s]+/(?:code|worktrees)"
|
|
122
|
+
r"|home/[^/\s]+/(?:projects|code|src|repos|dev|work)"
|
|
123
|
+
r"|Users/[^/\s]+/(?:projects|code|src|repos|dev|work))"
|
|
124
|
+
r"/[^/\s:'\"]+"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Remaining home prefixes: drop the username, keep the generic tail (e.g.
|
|
128
|
+
# "/home/jenner/.claude/skills" -> "~/.claude/skills").
|
|
129
|
+
_HOME_PATH_RE = re.compile(r"/home/[^/\s]+|/Users/[^/\s]+|/root(?=/|\b)")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _hash_token(value: str, prefix: str) -> str:
|
|
133
|
+
digest = hashlib.sha256(value.strip().encode("utf-8")).hexdigest()[:10]
|
|
134
|
+
return f"<{prefix}:{digest}>"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _redact_paths(text: str) -> str:
|
|
138
|
+
text = _PROJECT_PATH_RE.sub("<repo>", text)
|
|
139
|
+
text = _HOME_PATH_RE.sub("~", text)
|
|
140
|
+
return text
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def redact_reflection_text(text: str, machine: str | None = None) -> str:
|
|
144
|
+
"""Return a repo-agnostic copy of a reflection, safe to pool across machines.
|
|
145
|
+
|
|
146
|
+
Deterministic and idempotent. Two passes:
|
|
147
|
+
|
|
148
|
+
1. **Structured fields** (Repo/Branch/Commit/Artifact/repo_root/...): replace
|
|
149
|
+
the value with a stable hash token, so the same repo/branch still
|
|
150
|
+
*correlates* across reflections without revealing its name. Values that
|
|
151
|
+
are already redacted, empty, or "none" are left untouched (idempotency).
|
|
152
|
+
2. **Free-text paths**: collapse absolute project/worktree paths to
|
|
153
|
+
``<repo>/...`` and strip usernames from home paths.
|
|
154
|
+
|
|
155
|
+
Known limitation: free-text references to a repo/branch *by bare name*
|
|
156
|
+
(not as a path) can't be detected and are not redacted — the v36 schema's
|
|
157
|
+
"repo-agnostic" intent still applies to authored content. The structured
|
|
158
|
+
fields above are the guaranteed-clean part.
|
|
159
|
+
"""
|
|
160
|
+
lines = text.splitlines(keepends=False)
|
|
161
|
+
out: list[str] = []
|
|
162
|
+
already_marked = bool(lines) and lines[0].startswith("<!-- reflection-sync: redacted;")
|
|
163
|
+
if machine and not already_marked:
|
|
164
|
+
out.append(f"<!-- reflection-sync: redacted; machine={_slug(machine)} -->")
|
|
165
|
+
|
|
166
|
+
for line in lines:
|
|
167
|
+
m = _FIELD_RE.match(line)
|
|
168
|
+
if m:
|
|
169
|
+
name = m.group("name").strip().lower()
|
|
170
|
+
prefix = _SENSITIVE_FIELDS.get(name)
|
|
171
|
+
if prefix is not None:
|
|
172
|
+
value = m.group("value")
|
|
173
|
+
if _REDACTED_RE.match(value.strip()):
|
|
174
|
+
out.append(line)
|
|
175
|
+
else:
|
|
176
|
+
out.append(m.group("prefix") + _hash_token(value, prefix))
|
|
177
|
+
continue
|
|
178
|
+
out.append(_redact_paths(line))
|
|
179
|
+
|
|
180
|
+
result = "\n".join(out)
|
|
181
|
+
if text.endswith("\n"):
|
|
182
|
+
result += "\n"
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# Export
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def _candidate_roots(repo: Path, harness: str) -> list[Path]:
|
|
191
|
+
roots = [resolve_skill_bundle_root(harness)]
|
|
192
|
+
for rel in _REPO_LOCAL_ROOTS.get(harness, ()):
|
|
193
|
+
roots.append(repo / rel)
|
|
194
|
+
return roots
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def iter_reflection_files(repo: Path, harnesses: tuple[str, ...] = DEFAULT_HARNESSES):
|
|
198
|
+
"""Yield ``(skill_name, rel_path, source_path)`` for every live reflection.
|
|
199
|
+
|
|
200
|
+
``rel_path`` is relative to the skill's ``reflections/`` dir (e.g.
|
|
201
|
+
``<repo_hash>/<branch_slug>/<run_id>.md``, or a flat ``<file>.md`` for legacy
|
|
202
|
+
layouts). Archived reflections are skipped. Duplicate (skill, rel) pairs
|
|
203
|
+
seen across roots are de-duplicated.
|
|
204
|
+
"""
|
|
205
|
+
repo = Path(repo).expanduser()
|
|
206
|
+
seen: set[tuple[str, str]] = set()
|
|
207
|
+
for harness in harnesses:
|
|
208
|
+
for root in _candidate_roots(repo, harness):
|
|
209
|
+
if not root.exists():
|
|
210
|
+
continue
|
|
211
|
+
for refl_dir in sorted(root.glob("*/reflections")):
|
|
212
|
+
skill = refl_dir.parent.name
|
|
213
|
+
for path in sorted(refl_dir.rglob("*.md")):
|
|
214
|
+
if "archive" in path.parts or not path.is_file():
|
|
215
|
+
continue
|
|
216
|
+
rel = path.relative_to(refl_dir)
|
|
217
|
+
key = (skill, rel.as_posix())
|
|
218
|
+
if key in seen:
|
|
219
|
+
continue
|
|
220
|
+
seen.add(key)
|
|
221
|
+
yield skill, rel, path
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def export_reflections(
|
|
225
|
+
repo: Path,
|
|
226
|
+
dest_root: Path,
|
|
227
|
+
machine: str | None = None,
|
|
228
|
+
harnesses: tuple[str, ...] = DEFAULT_HARNESSES,
|
|
229
|
+
) -> list[Path]:
|
|
230
|
+
"""Redact and copy every live reflection into a machine-namespaced store.
|
|
231
|
+
|
|
232
|
+
Destination layout (the ``<machine>`` segment sits directly above the file,
|
|
233
|
+
so machines never collide even on an identical repo_hash/branch):
|
|
234
|
+
|
|
235
|
+
``<dest_root>/<skill>/<rel.parent>/<machine>/<rel.name>``
|
|
236
|
+
|
|
237
|
+
Returns the list of written destination paths.
|
|
238
|
+
"""
|
|
239
|
+
machine = machine or machine_id()
|
|
240
|
+
dest_root = Path(dest_root).expanduser()
|
|
241
|
+
written: list[Path] = []
|
|
242
|
+
for skill, rel, src in iter_reflection_files(repo, harnesses):
|
|
243
|
+
try:
|
|
244
|
+
text = src.read_text(encoding="utf-8", errors="replace")
|
|
245
|
+
except OSError:
|
|
246
|
+
continue
|
|
247
|
+
# Skip reflections that were imported from the pool (already redacted on
|
|
248
|
+
# another machine) so an aggregator never re-pools them under its own id.
|
|
249
|
+
if text.lstrip().startswith("<!-- reflection-sync: redacted;"):
|
|
250
|
+
continue
|
|
251
|
+
redacted = redact_reflection_text(text, machine=machine)
|
|
252
|
+
dest = dest_root / skill / rel.parent / machine / rel.name
|
|
253
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
254
|
+
dest.write_text(redacted, encoding="utf-8")
|
|
255
|
+
written.append(dest)
|
|
256
|
+
return written
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# Minimal CLI seam (the full `phase-loop reflections sync` subcommand is Slice 2)
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
def main(argv: list[str] | None = None) -> int:
|
|
264
|
+
parser = argparse.ArgumentParser(prog="phase_loop_runtime.reflection_sync")
|
|
265
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
266
|
+
|
|
267
|
+
exp = sub.add_parser("export", help="redact + copy local reflections to a store dir")
|
|
268
|
+
exp.add_argument("--repo", required=True, help="repo root to scan")
|
|
269
|
+
exp.add_argument("--dest", required=True, help="destination store root")
|
|
270
|
+
exp.add_argument("--machine", default=None, help="machine id override")
|
|
271
|
+
|
|
272
|
+
mid = sub.add_parser("machine-id", help="print this machine's stable id")
|
|
273
|
+
mid.set_defaults(_is_machine_id=True)
|
|
274
|
+
|
|
275
|
+
args = parser.parse_args(argv)
|
|
276
|
+
|
|
277
|
+
if args.command == "machine-id":
|
|
278
|
+
print(machine_id())
|
|
279
|
+
return 0
|
|
280
|
+
|
|
281
|
+
if args.command == "export":
|
|
282
|
+
written = export_reflections(
|
|
283
|
+
Path(args.repo), Path(args.dest), machine=args.machine
|
|
284
|
+
)
|
|
285
|
+
print(f"exported {len(written)} reflection(s) to {args.dest}")
|
|
286
|
+
return 0
|
|
287
|
+
|
|
288
|
+
parser.error("unknown command")
|
|
289
|
+
return 2
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__": # pragma: no cover
|
|
293
|
+
sys.exit(main())
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import fnmatch
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .discovery import plan_metadata
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RELEASE_DISPATCH_MUTATION = "release_dispatch"
|
|
13
|
+
RELEASE_AFFECTING_PATTERNS = (
|
|
14
|
+
".github/workflows/**",
|
|
15
|
+
"CHANGELOG*",
|
|
16
|
+
"RELEASE*",
|
|
17
|
+
"VERSION",
|
|
18
|
+
"pyproject.toml",
|
|
19
|
+
"setup.cfg",
|
|
20
|
+
"setup.py",
|
|
21
|
+
"uv.lock",
|
|
22
|
+
"poetry.lock",
|
|
23
|
+
"requirements*.txt",
|
|
24
|
+
"docs/release/**",
|
|
25
|
+
"docs/releases/**",
|
|
26
|
+
"docs/release*.md",
|
|
27
|
+
"scripts/*release*",
|
|
28
|
+
"scripts/download-release.py",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class ReleaseDispatchBlocker:
|
|
34
|
+
blocker_class: str
|
|
35
|
+
blocker_summary: str
|
|
36
|
+
required_human_inputs: tuple[str, ...]
|
|
37
|
+
metadata: dict[str, Any]
|
|
38
|
+
|
|
39
|
+
def to_blocker(self) -> dict[str, Any]:
|
|
40
|
+
return {
|
|
41
|
+
"human_required": True,
|
|
42
|
+
"blocker_class": self.blocker_class,
|
|
43
|
+
"blocker_summary": self.blocker_summary,
|
|
44
|
+
"required_human_inputs": self.required_human_inputs,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_release_dispatch_plan(plan: Path | None) -> bool:
|
|
49
|
+
if plan is None:
|
|
50
|
+
return False
|
|
51
|
+
metadata = plan_metadata(plan)
|
|
52
|
+
if metadata.get("phase_loop_mutation") == RELEASE_DISPATCH_MUTATION:
|
|
53
|
+
return True
|
|
54
|
+
try:
|
|
55
|
+
text = plan.read_text(encoding="utf-8").lower()
|
|
56
|
+
except OSError:
|
|
57
|
+
return False
|
|
58
|
+
return "gh workflow run" in text and "release" in text
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def release_dispatch_blocker(repo: Path, plan: Path | None) -> ReleaseDispatchBlocker | None:
|
|
62
|
+
if plan is None or not is_release_dispatch_plan(plan):
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
metadata = plan_metadata(plan)
|
|
66
|
+
dirty_paths = _dirty_release_affecting_paths(repo)
|
|
67
|
+
if dirty_paths:
|
|
68
|
+
return ReleaseDispatchBlocker(
|
|
69
|
+
blocker_class="dirty_worktree_conflict",
|
|
70
|
+
blocker_summary=(
|
|
71
|
+
"Release dispatch requires clean release-affecting files before the "
|
|
72
|
+
"external workflow is started."
|
|
73
|
+
),
|
|
74
|
+
required_human_inputs=(
|
|
75
|
+
"Commit, merge, or isolate the release-affecting changes.",
|
|
76
|
+
"Sync the release branch with the configured base ref, then rerun the dispatch phase.",
|
|
77
|
+
),
|
|
78
|
+
metadata={
|
|
79
|
+
"guard": "release_dispatch",
|
|
80
|
+
"reason": "dirty_release_affecting_paths",
|
|
81
|
+
"plan": str(plan),
|
|
82
|
+
"dirty_paths": dirty_paths,
|
|
83
|
+
"phase_loop_mutation": metadata.get("phase_loop_mutation"),
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
base_ref, base_ref_explicit = _release_base_ref(metadata)
|
|
88
|
+
sync = _branch_sync(repo, base_ref)
|
|
89
|
+
if not sync["base_available"]:
|
|
90
|
+
if not base_ref_explicit and not sync["base_remote_available"]:
|
|
91
|
+
return None
|
|
92
|
+
return ReleaseDispatchBlocker(
|
|
93
|
+
blocker_class="branch_sync_conflict",
|
|
94
|
+
blocker_summary=f"Release dispatch base ref `{base_ref}` is unavailable locally.",
|
|
95
|
+
required_human_inputs=(
|
|
96
|
+
f"Fetch or configure `{base_ref}` before rerunning release dispatch.",
|
|
97
|
+
"Rerun the dispatch phase from a branch that can be compared to the release base.",
|
|
98
|
+
),
|
|
99
|
+
metadata={
|
|
100
|
+
"guard": "release_dispatch",
|
|
101
|
+
"reason": "base_ref_unavailable",
|
|
102
|
+
"plan": str(plan),
|
|
103
|
+
**sync,
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
if sync["head"] != sync["base_commit"]:
|
|
107
|
+
return ReleaseDispatchBlocker(
|
|
108
|
+
blocker_class="branch_sync_conflict",
|
|
109
|
+
blocker_summary=(
|
|
110
|
+
f"Release dispatch requires `HEAD` to match `{base_ref}` before the "
|
|
111
|
+
"external workflow is started."
|
|
112
|
+
),
|
|
113
|
+
required_human_inputs=(
|
|
114
|
+
f"Merge or sync the release branch so `HEAD` matches `{base_ref}`.",
|
|
115
|
+
"Rerun the dispatch phase from the clean synced branch.",
|
|
116
|
+
),
|
|
117
|
+
metadata={
|
|
118
|
+
"guard": "release_dispatch",
|
|
119
|
+
"reason": "head_not_at_base_ref",
|
|
120
|
+
"plan": str(plan),
|
|
121
|
+
**sync,
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _release_base_ref(metadata: dict[str, str]) -> tuple[str, bool]:
|
|
128
|
+
for key in ("release_base_ref", "phase_loop_release_base_ref"):
|
|
129
|
+
value = metadata.get(key)
|
|
130
|
+
if value:
|
|
131
|
+
return value, True
|
|
132
|
+
return "origin/main", False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _dirty_release_affecting_paths(repo: Path) -> list[str]:
|
|
136
|
+
try:
|
|
137
|
+
status = subprocess.check_output(
|
|
138
|
+
["git", "-C", str(repo), "status", "--porcelain", "--untracked-files=all"],
|
|
139
|
+
text=True,
|
|
140
|
+
)
|
|
141
|
+
except Exception:
|
|
142
|
+
return []
|
|
143
|
+
paths: list[str] = []
|
|
144
|
+
for line in status.splitlines():
|
|
145
|
+
if not line:
|
|
146
|
+
continue
|
|
147
|
+
path = _status_path(line)
|
|
148
|
+
if path and _is_release_affecting_path(path):
|
|
149
|
+
paths.append(path)
|
|
150
|
+
return sorted(dict.fromkeys(paths))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _status_path(line: str) -> str:
|
|
154
|
+
path = line[3:] if len(line) > 3 else ""
|
|
155
|
+
if " -> " in path:
|
|
156
|
+
path = path.split(" -> ", 1)[1]
|
|
157
|
+
return path.strip().strip('"')
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _is_release_affecting_path(path: str) -> bool:
|
|
161
|
+
normalized = path.replace("\\", "/")
|
|
162
|
+
return any(fnmatch.fnmatchcase(normalized, pattern) for pattern in RELEASE_AFFECTING_PATTERNS)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _branch_sync(repo: Path, base_ref: str) -> dict[str, Any]:
|
|
166
|
+
head = _git(repo, "rev-parse", "HEAD")
|
|
167
|
+
base_commit = _git(repo, "rev-parse", "--verify", f"{base_ref}^{{commit}}")
|
|
168
|
+
remote = base_ref.split("/", 1)[0] if "/" in base_ref else ""
|
|
169
|
+
return {
|
|
170
|
+
"base_ref": base_ref,
|
|
171
|
+
"base_available": bool(base_commit),
|
|
172
|
+
"base_remote_available": bool(remote and _git(repo, "remote", "get-url", remote)),
|
|
173
|
+
"head": _short_sha(head),
|
|
174
|
+
"base_commit": _short_sha(base_commit),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _git(repo: Path, *args: str) -> str:
|
|
179
|
+
try:
|
|
180
|
+
return subprocess.check_output(["git", "-C", str(repo), *args], text=True, stderr=subprocess.DEVNULL).strip()
|
|
181
|
+
except Exception:
|
|
182
|
+
return ""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _short_sha(value: str) -> str:
|
|
186
|
+
return value[:12] if value else ""
|