@opengsd/gsd-path 1.1.0 → 1.3.0
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.
- package/AGENTS.md +5 -2
- package/CHANGELOG.md +87 -0
- package/DOCS.md +73 -1
- package/FULL.md +3 -2
- package/HOOKS.md +18 -9
- package/QUICK.md +3 -4
- package/README.md +72 -5
- package/RUNTIME.md +9 -4
- package/UPDATE.md +56 -11
- package/WORKFLOW.md +5 -2
- package/package.json +9 -1
- package/platforms/claude/dispatch.md +4 -4
- package/platforms/copilot/dispatch.md +4 -4
- package/platforms/cursor/dispatch.md +5 -10
- package/platforms/grok/dispatch.md +4 -6
- package/platforms/kimi/dispatch.md +4 -4
- package/platforms/kiro/dispatch.md +4 -6
- package/platforms/opencode/dispatch.md +4 -4
- package/platforms/qwen/dispatch.md +4 -4
- package/platforms/shared-agents/dispatch.md +6 -19
- package/scripts/_common.py +5 -0
- package/scripts/archive_milestone.py +20 -6
- package/scripts/bootstrap_repository.py +8 -1
- package/scripts/build_recovery.py +5 -0
- package/scripts/build_state.py +2 -0
- package/scripts/check_docs_audit.py +2 -0
- package/scripts/check_handoffs.py +6 -0
- package/scripts/check_task_briefs.py +3 -0
- package/scripts/detect_project.py +11 -1
- package/scripts/discussion_records.py +3 -0
- package/scripts/discussion_validate.py +8 -3
- package/scripts/dispatch_driver.py +230 -21
- package/scripts/git_guard.py +12 -4
- package/scripts/guard_hook.py +11 -1
- package/scripts/install.mjs +74 -1448
- package/scripts/install.py +165 -201
- package/scripts/integration.py +28 -8
- package/scripts/isolation.py +15 -5
- package/scripts/jev_review.py +138 -0
- package/scripts/lean_verification.py +2 -0
- package/scripts/loop_run.py +2 -0
- package/scripts/model_policy.py +279 -0
- package/scripts/path_config.py +248 -0
- package/scripts/pipeline_diagnose.py +3 -0
- package/scripts/pipeline_git.py +3 -0
- package/scripts/pipeline_state.py +4 -1
- package/scripts/pipeline_undo.py +3 -0
- package/scripts/promote_lookahead.py +3 -0
- package/scripts/review_panel.py +11 -0
- package/scripts/roadmap.py +5 -0
- package/scripts/runtime_store.py +281 -0
- package/scripts/skill-resources.json +104 -4
- package/scripts/state_checkpoint.py +5 -0
- package/scripts/state_promote.py +5 -0
- package/scripts/status_runtime.py +92 -6
- package/scripts/wizard.mjs +8 -2
- package/scripts/workflow_run.py +12 -1
- package/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path/BUILD.md +7 -0
- package/skills/gsd-path/DECIDE.md +2 -0
- package/skills/gsd-path/DEFINE.md +13 -7
- package/skills/gsd-path/DOCS-AUDIT.md +2 -0
- package/skills/gsd-path/INSPECT.md +2 -0
- package/skills/gsd-path/PLAN.md +4 -2
- package/skills/gsd-path/RESEARCH.md +2 -0
- package/skills/gsd-path/ROADMAP.md +2 -0
- package/skills/gsd-path/SHIP.md +24 -13
- package/skills/gsd-path/SKILL.md +23 -9
- package/skills/gsd-path/references/build-native.md +15 -7
- package/skills/gsd-path/references/config.md +57 -0
- package/skills/gsd-path/references/dispatch.md +6 -19
- package/skills/gsd-path/references/jev-review.md +82 -0
- package/skills/gsd-path/references/model-policy.md +139 -0
- package/skills/gsd-path/references/reviewer-panel.md +1 -1
- package/skills/gsd-path/references/reviewer.md +2 -0
- package/skills/gsd-path/references/runtime-selection.md +25 -0
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/_common.py +5 -0
- package/skills/gsd-path/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path/scripts/bootstrap_repository.py +8 -1
- package/skills/gsd-path/scripts/build_recovery.py +5 -0
- package/skills/gsd-path/scripts/build_state.py +2 -0
- package/skills/gsd-path/scripts/check_docs_audit.py +2 -0
- package/skills/gsd-path/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path/scripts/detect_project.py +11 -1
- package/skills/gsd-path/scripts/discussion_records.py +3 -0
- package/skills/gsd-path/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path/scripts/dispatch_driver.py +230 -21
- package/skills/gsd-path/scripts/git_guard.py +768 -0
- package/skills/gsd-path/scripts/integration.py +28 -8
- package/skills/gsd-path/scripts/isolation.py +15 -5
- package/skills/gsd-path/scripts/jev_review.py +138 -0
- package/skills/gsd-path/scripts/lean_verification.py +2 -0
- package/skills/gsd-path/scripts/loop_run.py +2 -0
- package/skills/gsd-path/scripts/model_policy.py +279 -0
- package/skills/gsd-path/scripts/path_config.py +248 -0
- package/skills/gsd-path/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path/scripts/review_panel.py +11 -0
- package/skills/gsd-path/scripts/roadmap.py +5 -0
- package/skills/gsd-path/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path/scripts/state_promote.py +5 -0
- package/skills/gsd-path/scripts/status_runtime.py +208 -0
- package/skills/gsd-path/scripts/workflow_run.py +12 -1
- package/skills/gsd-path/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path/templates/skeptic.md +1 -1
- package/skills/gsd-path/templates/task.md +2 -0
- package/skills/gsd-path/templates/wave-panel.md +1 -1
- package/skills/gsd-path-build/SKILL.md +7 -0
- package/skills/gsd-path-build/references/build-native.md +15 -7
- package/skills/gsd-path-build/references/dispatch.md +6 -19
- package/skills/gsd-path-build/references/jev-review.md +82 -0
- package/skills/gsd-path-build/references/model-policy.md +139 -0
- package/skills/gsd-path-build/references/reviewer-panel.md +1 -1
- package/skills/gsd-path-build/references/reviewer.md +2 -0
- package/skills/gsd-path-build/references/runtime-selection.md +25 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/_common.py +5 -0
- package/skills/gsd-path-build/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-build/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-build/scripts/build_state.py +2 -0
- package/skills/gsd-path-build/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-build/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-build/scripts/detect_project.py +11 -1
- package/skills/gsd-path-build/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-build/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-build/scripts/dispatch_driver.py +230 -21
- package/skills/gsd-path-build/scripts/integration.py +28 -8
- package/skills/gsd-path-build/scripts/isolation.py +15 -5
- package/skills/gsd-path-build/scripts/jev_review.py +138 -0
- package/skills/gsd-path-build/scripts/loop_run.py +2 -0
- package/skills/gsd-path-build/scripts/model_policy.py +279 -0
- package/skills/gsd-path-build/scripts/path_config.py +248 -0
- package/skills/gsd-path-build/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-build/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-build/scripts/review_panel.py +11 -0
- package/skills/gsd-path-build/scripts/roadmap.py +5 -0
- package/skills/gsd-path-build/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-build/scripts/state_promote.py +5 -0
- package/skills/gsd-path-build/scripts/workflow_run.py +12 -1
- package/skills/gsd-path-build/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-build/templates/skeptic.md +1 -1
- package/skills/gsd-path-build/templates/task.md +2 -0
- package/skills/gsd-path-build/templates/wave-panel.md +1 -1
- package/skills/gsd-path-decide/SKILL.md +2 -0
- package/skills/gsd-path-decide/references/dispatch.md +6 -19
- package/skills/gsd-path-decide/references/model-policy.md +139 -0
- package/skills/gsd-path-decide/references/runtime-selection.md +25 -0
- package/skills/gsd-path-decide/scripts/_common.py +5 -0
- package/skills/gsd-path-decide/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-decide/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-decide/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-decide/scripts/detect_project.py +11 -1
- package/skills/gsd-path-decide/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-decide/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-decide/scripts/integration.py +28 -8
- package/skills/gsd-path-decide/scripts/isolation.py +15 -5
- package/skills/gsd-path-decide/scripts/loop_run.py +839 -0
- package/skills/gsd-path-decide/scripts/model_policy.py +279 -0
- package/skills/gsd-path-decide/scripts/path_config.py +248 -0
- package/skills/gsd-path-decide/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-decide/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-decide/scripts/review_panel.py +11 -0
- package/skills/gsd-path-decide/scripts/roadmap.py +5 -0
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-decide/scripts/state_promote.py +5 -0
- package/skills/gsd-path-decide/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-define/SKILL.md +13 -7
- package/skills/gsd-path-define/references/runtime-selection.md +25 -0
- package/skills/gsd-path-define/scripts/_common.py +5 -0
- package/skills/gsd-path-define/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-define/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-define/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-define/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-define/scripts/detect_project.py +11 -1
- package/skills/gsd-path-define/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-define/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-define/scripts/integration.py +28 -8
- package/skills/gsd-path-define/scripts/isolation.py +15 -5
- package/skills/gsd-path-define/scripts/loop_run.py +839 -0
- package/skills/gsd-path-define/scripts/model_policy.py +279 -0
- package/skills/gsd-path-define/scripts/path_config.py +248 -0
- package/skills/gsd-path-define/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-define/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-define/scripts/review_panel.py +11 -0
- package/skills/gsd-path-define/scripts/roadmap.py +5 -0
- package/skills/gsd-path-define/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-define/scripts/state_promote.py +5 -0
- package/skills/gsd-path-define/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-discuss/SKILL.md +2 -0
- package/skills/gsd-path-discuss/references/runtime-selection.md +25 -0
- package/skills/gsd-path-discuss/scripts/_common.py +5 -0
- package/skills/gsd-path-discuss/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-discuss/scripts/loop_run.py +839 -0
- package/skills/gsd-path-discuss/scripts/model_policy.py +279 -0
- package/skills/gsd-path-discuss/scripts/path_config.py +248 -0
- package/skills/gsd-path-discuss/scripts/review_panel.py +564 -0
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-docs-audit/SKILL.md +2 -0
- package/skills/gsd-path-docs-audit/references/dispatch.md +6 -19
- package/skills/gsd-path-docs-audit/references/model-policy.md +139 -0
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +25 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +2 -0
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +11 -1
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-docs-audit/scripts/integration.py +28 -8
- package/skills/gsd-path-docs-audit/scripts/isolation.py +15 -5
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +839 -0
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +279 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +248 -0
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +11 -0
- package/skills/gsd-path-docs-audit/scripts/roadmap.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-forensics/SKILL.md +9 -5
- package/skills/gsd-path-forensics/references/runtime-selection.md +25 -0
- package/skills/gsd-path-forensics/scripts/_common.py +5 -0
- package/skills/gsd-path-forensics/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-forensics/scripts/loop_run.py +839 -0
- package/skills/gsd-path-forensics/scripts/model_policy.py +279 -0
- package/skills/gsd-path-forensics/scripts/path_config.py +248 -0
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +3 -0
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +3 -0
- package/skills/gsd-path-forensics/scripts/review_panel.py +564 -0
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-inspect/SKILL.md +2 -0
- package/skills/gsd-path-inspect/references/dispatch.md +6 -19
- package/skills/gsd-path-inspect/references/model-policy.md +139 -0
- package/skills/gsd-path-inspect/references/runtime-selection.md +25 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/_common.py +5 -0
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-inspect/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +2 -0
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-inspect/scripts/detect_project.py +11 -1
- package/skills/gsd-path-inspect/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-inspect/scripts/integration.py +28 -8
- package/skills/gsd-path-inspect/scripts/isolation.py +15 -5
- package/skills/gsd-path-inspect/scripts/loop_run.py +839 -0
- package/skills/gsd-path-inspect/scripts/model_policy.py +279 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +248 -0
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-inspect/scripts/review_panel.py +11 -0
- package/skills/gsd-path-inspect/scripts/roadmap.py +5 -0
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-inspect/scripts/state_promote.py +5 -0
- package/skills/gsd-path-inspect/scripts/workflow_run.py +12 -1
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-loop/SKILL.md +2 -0
- package/skills/gsd-path-loop/references/runtime-selection.md +25 -0
- package/skills/gsd-path-loop/scripts/_common.py +5 -0
- package/skills/gsd-path-loop/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-loop/scripts/loop_run.py +2 -0
- package/skills/gsd-path-loop/scripts/model_policy.py +279 -0
- package/skills/gsd-path-loop/scripts/path_config.py +248 -0
- package/skills/gsd-path-loop/scripts/review_panel.py +564 -0
- package/skills/gsd-path-loop/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-migrate/SKILL.md +2 -0
- package/skills/gsd-path-migrate/references/runtime-selection.md +25 -0
- package/skills/gsd-path-migrate/scripts/_common.py +185 -0
- package/skills/gsd-path-migrate/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-migrate/scripts/loop_run.py +839 -0
- package/skills/gsd-path-migrate/scripts/model_policy.py +279 -0
- package/skills/gsd-path-migrate/scripts/path_config.py +248 -0
- package/skills/gsd-path-migrate/scripts/review_panel.py +564 -0
- package/skills/gsd-path-plan/SKILL.md +4 -2
- package/skills/gsd-path-plan/references/dispatch.md +6 -19
- package/skills/gsd-path-plan/references/model-policy.md +139 -0
- package/skills/gsd-path-plan/references/runtime-selection.md +25 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/_common.py +5 -0
- package/skills/gsd-path-plan/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-plan/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-plan/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-plan/scripts/detect_project.py +11 -1
- package/skills/gsd-path-plan/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-plan/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-plan/scripts/integration.py +28 -8
- package/skills/gsd-path-plan/scripts/isolation.py +15 -5
- package/skills/gsd-path-plan/scripts/loop_run.py +839 -0
- package/skills/gsd-path-plan/scripts/model_policy.py +279 -0
- package/skills/gsd-path-plan/scripts/path_config.py +248 -0
- package/skills/gsd-path-plan/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-plan/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-plan/scripts/review_panel.py +11 -0
- package/skills/gsd-path-plan/scripts/roadmap.py +5 -0
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-plan/scripts/state_promote.py +5 -0
- package/skills/gsd-path-plan/scripts/workflow_run.py +12 -1
- package/skills/gsd-path-plan/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-plan/templates/task.md +2 -0
- package/skills/gsd-path-research/SKILL.md +2 -0
- package/skills/gsd-path-research/references/dispatch.md +6 -19
- package/skills/gsd-path-research/references/model-policy.md +139 -0
- package/skills/gsd-path-research/references/runtime-selection.md +25 -0
- package/skills/gsd-path-research/scripts/_common.py +5 -0
- package/skills/gsd-path-research/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-research/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-research/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-research/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-research/scripts/detect_project.py +11 -1
- package/skills/gsd-path-research/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-research/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-research/scripts/integration.py +28 -8
- package/skills/gsd-path-research/scripts/isolation.py +15 -5
- package/skills/gsd-path-research/scripts/loop_run.py +839 -0
- package/skills/gsd-path-research/scripts/model_policy.py +279 -0
- package/skills/gsd-path-research/scripts/path_config.py +248 -0
- package/skills/gsd-path-research/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-research/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-research/scripts/review_panel.py +11 -0
- package/skills/gsd-path-research/scripts/roadmap.py +5 -0
- package/skills/gsd-path-research/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-research/scripts/state_promote.py +5 -0
- package/skills/gsd-path-research/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-roadmap/SKILL.md +2 -0
- package/skills/gsd-path-roadmap/references/dispatch.md +6 -19
- package/skills/gsd-path-roadmap/references/model-policy.md +139 -0
- package/skills/gsd-path-roadmap/references/runtime-selection.md +25 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +5 -0
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-roadmap/scripts/detect_project.py +11 -1
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-roadmap/scripts/integration.py +28 -8
- package/skills/gsd-path-roadmap/scripts/isolation.py +15 -5
- package/skills/gsd-path-roadmap/scripts/loop_run.py +839 -0
- package/skills/gsd-path-roadmap/scripts/model_policy.py +279 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +248 -0
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-roadmap/scripts/review_panel.py +11 -0
- package/skills/gsd-path-roadmap/scripts/roadmap.py +5 -0
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-roadmap/scripts/state_promote.py +5 -0
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-ship/SKILL.md +24 -13
- package/skills/gsd-path-ship/references/dispatch.md +6 -19
- package/skills/gsd-path-ship/references/jev-review.md +82 -0
- package/skills/gsd-path-ship/references/model-policy.md +139 -0
- package/skills/gsd-path-ship/references/reviewer-panel.md +1 -1
- package/skills/gsd-path-ship/references/reviewer.md +2 -0
- package/skills/gsd-path-ship/references/runtime-selection.md +25 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/_common.py +5 -0
- package/skills/gsd-path-ship/scripts/archive_milestone.py +20 -6
- package/skills/gsd-path-ship/scripts/build_recovery.py +5 -0
- package/skills/gsd-path-ship/scripts/build_state.py +2 -0
- package/skills/gsd-path-ship/scripts/check_handoffs.py +6 -0
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-ship/scripts/detect_project.py +11 -1
- package/skills/gsd-path-ship/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-ship/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-ship/scripts/integration.py +28 -8
- package/skills/gsd-path-ship/scripts/isolation.py +15 -5
- package/skills/gsd-path-ship/scripts/jev_review.py +138 -0
- package/skills/gsd-path-ship/scripts/lean_verification.py +2 -0
- package/skills/gsd-path-ship/scripts/loop_run.py +839 -0
- package/skills/gsd-path-ship/scripts/model_policy.py +279 -0
- package/skills/gsd-path-ship/scripts/path_config.py +248 -0
- package/skills/gsd-path-ship/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-ship/scripts/pipeline_state.py +4 -1
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -0
- package/skills/gsd-path-ship/scripts/review_panel.py +11 -0
- package/skills/gsd-path-ship/scripts/roadmap.py +5 -0
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +5 -0
- package/skills/gsd-path-ship/scripts/state_promote.py +5 -0
- package/skills/gsd-path-ship/scripts/workflow_run.py +12 -1
- package/skills/gsd-path-ship/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-undo/SKILL.md +2 -0
- package/skills/gsd-path-undo/references/runtime-selection.md +25 -0
- package/skills/gsd-path-undo/scripts/_common.py +5 -0
- package/skills/gsd-path-undo/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-undo/scripts/loop_run.py +839 -0
- package/skills/gsd-path-undo/scripts/model_policy.py +279 -0
- package/skills/gsd-path-undo/scripts/path_config.py +248 -0
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +3 -0
- package/skills/gsd-path-undo/scripts/review_panel.py +564 -0
- package/skills/gsd-path-undo/scripts/worktree_paths.py +94 -0
- package/skills/path/BUILD.md +7 -0
- package/skills/path/DECIDE.md +2 -0
- package/skills/path/DEFINE.md +13 -7
- package/skills/path/DOCS-AUDIT.md +2 -0
- package/skills/path/INSPECT.md +2 -0
- package/skills/path/PLAN.md +4 -2
- package/skills/path/RESEARCH.md +2 -0
- package/skills/path/ROADMAP.md +2 -0
- package/skills/path/SHIP.md +24 -13
- package/skills/path/SKILL.md +23 -9
- package/skills/path/references/build-native.md +15 -7
- package/skills/path/references/config.md +57 -0
- package/skills/path/references/dispatch.md +6 -19
- package/skills/path/references/jev-review.md +82 -0
- package/skills/path/references/model-policy.md +139 -0
- package/skills/path/references/reviewer-panel.md +1 -1
- package/skills/path/references/reviewer.md +2 -0
- package/skills/path/references/runtime-selection.md +25 -0
- package/skills/path/scripts/_common.py +5 -0
- package/skills/path/scripts/archive_milestone.py +20 -6
- package/skills/path/scripts/bootstrap_repository.py +8 -1
- package/skills/path/scripts/build_recovery.py +5 -0
- package/skills/path/scripts/build_state.py +2 -0
- package/skills/path/scripts/check_docs_audit.py +2 -0
- package/skills/path/scripts/check_handoffs.py +6 -0
- package/skills/path/scripts/check_task_briefs.py +3 -0
- package/skills/path/scripts/detect_project.py +11 -1
- package/skills/path/scripts/discussion_records.py +3 -0
- package/skills/path/scripts/discussion_validate.py +8 -3
- package/skills/path/scripts/dispatch_driver.py +230 -21
- package/skills/path/scripts/git_guard.py +768 -0
- package/skills/path/scripts/integration.py +28 -8
- package/skills/path/scripts/isolation.py +15 -5
- package/skills/path/scripts/jev_review.py +138 -0
- package/skills/path/scripts/lean_verification.py +2 -0
- package/skills/path/scripts/loop_run.py +2 -0
- package/skills/path/scripts/model_policy.py +279 -0
- package/skills/path/scripts/path_config.py +248 -0
- package/skills/path/scripts/pipeline_git.py +3 -0
- package/skills/path/scripts/pipeline_state.py +4 -1
- package/skills/path/scripts/promote_lookahead.py +3 -0
- package/skills/path/scripts/review_panel.py +11 -0
- package/skills/path/scripts/roadmap.py +5 -0
- package/skills/path/scripts/state_checkpoint.py +5 -0
- package/skills/path/scripts/state_promote.py +5 -0
- package/skills/path/scripts/status_runtime.py +208 -0
- package/skills/path/scripts/workflow_run.py +12 -1
- package/skills/path/scripts/worktree_paths.py +94 -0
- package/skills/path/templates/skeptic.md +1 -1
- package/skills/path/templates/task.md +2 -0
- package/skills/path/templates/wave-panel.md +1 -1
- package/skills/gsd-path/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/discussion_records.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/integration.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-forensics/scripts/__pycache__/pipeline_undo.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# gsd-path project runtime
|
|
3
|
+
"""Show and edit Path preferences without advancing a pipeline phase."""
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.dont_write_bytecode = True
|
|
12
|
+
|
|
13
|
+
ROLES = ('coder', 'reviewer', 'review_panel', 'skeptic', 'inspect_codebase',
|
|
14
|
+
'inspect_docs', 'docs_audit', 'research', 'decide', 'roadmap', 'plan', 'plan_patch')
|
|
15
|
+
HOSTS = ('codex', 'claude', 'grok', 'opencode', 'copilot', 'qwen', 'antigravity',
|
|
16
|
+
'cursor', 'zed', 'kiro', 'kimi')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sibling(name):
|
|
20
|
+
import importlib
|
|
21
|
+
return importlib.import_module(f'{__package__}.{name}' if __package__ else name)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def safe_path(path):
|
|
25
|
+
# Resolve host aliases (macOS /var) at the repo/home boundary, not here.
|
|
26
|
+
for item in (path, path.parent):
|
|
27
|
+
if item.is_symlink():
|
|
28
|
+
raise ValueError(f'unsafe symlink: {item}')
|
|
29
|
+
if path.exists() and (not path.is_file() or path.stat().st_nlink != 1):
|
|
30
|
+
raise ValueError(f'expected a regular unlinked file: {path}')
|
|
31
|
+
return path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def read_object(path):
|
|
35
|
+
safe_path(path)
|
|
36
|
+
if not path.exists():
|
|
37
|
+
return {}
|
|
38
|
+
value = json.loads(path.read_text(encoding='utf-8'))
|
|
39
|
+
if not isinstance(value, dict):
|
|
40
|
+
raise ValueError(f'{path} must contain an object')
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def user_path():
|
|
45
|
+
return Path.home().resolve() / '.gsd-path/config.json'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def model_key(key):
|
|
49
|
+
bits = key.split('.')
|
|
50
|
+
if len(bits) == 4 and bits[:2] == ['models', 'roles']:
|
|
51
|
+
role, field = bits[2:]
|
|
52
|
+
elif len(bits) == 5 and bits[:2] == ['models', 'hosts'] and bits[2] in HOSTS:
|
|
53
|
+
role, field = bits[3:]
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError(f'unknown setting: {key}')
|
|
56
|
+
if role not in ROLES or field not in ('model', 'effort'):
|
|
57
|
+
raise ValueError(f'unknown setting: {key}')
|
|
58
|
+
return bits
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def flatten(data, prefix=''):
|
|
62
|
+
result = {}
|
|
63
|
+
for key, value in data.items():
|
|
64
|
+
name = f'{prefix}.{key}' if prefix else key
|
|
65
|
+
if isinstance(value, dict):
|
|
66
|
+
result.update(flatten(value, name))
|
|
67
|
+
else:
|
|
68
|
+
result[name] = value
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_value(key, value):
|
|
73
|
+
if not isinstance(value, str) or not value or value != value.strip():
|
|
74
|
+
raise ValueError(f'{key} requires a nonempty string')
|
|
75
|
+
if key == 'integration':
|
|
76
|
+
if value not in ('direct', 'pull-request'):
|
|
77
|
+
raise ValueError('integration must be direct or pull-request')
|
|
78
|
+
elif key == 'review_panel':
|
|
79
|
+
if any(char.isspace() for char in value):
|
|
80
|
+
raise ValueError('use off, detected, or comma-separated families without spaces')
|
|
81
|
+
sibling('review_panel').parse_review_panel_value(value)
|
|
82
|
+
else:
|
|
83
|
+
model_key(key)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def validate_models(data):
|
|
87
|
+
if not isinstance(data, dict) or set(data) - {'roles', 'hosts'}:
|
|
88
|
+
raise ValueError('models must contain roles and/or hosts')
|
|
89
|
+
for group, entries in data.items():
|
|
90
|
+
if not isinstance(entries, dict):
|
|
91
|
+
raise ValueError(f'models.{group} must be an object')
|
|
92
|
+
groups = [('roles', entries)] if group == 'roles' else [(f'hosts.{host}', roles) for host, roles in entries.items()]
|
|
93
|
+
for prefix, roles in groups:
|
|
94
|
+
if not isinstance(roles, dict) or (prefix.startswith('hosts.') and prefix[6:] not in HOSTS):
|
|
95
|
+
raise ValueError(f'unknown or malformed model group: {prefix}')
|
|
96
|
+
for role, fields in roles.items():
|
|
97
|
+
if role not in ROLES or not isinstance(fields, dict):
|
|
98
|
+
raise ValueError(f'unknown or malformed role: {role}')
|
|
99
|
+
for field, value in fields.items():
|
|
100
|
+
validate_value(f'models.{prefix}.{role}.{field}', value)
|
|
101
|
+
return data
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def preferences(path, user=False):
|
|
105
|
+
data = read_object(path)
|
|
106
|
+
allowed = {'integration', 'review_panel', 'models'} if user else {'review_panel'}
|
|
107
|
+
if set(data) - allowed:
|
|
108
|
+
raise ValueError(f'{path}: unknown setting')
|
|
109
|
+
for key, value in data.items():
|
|
110
|
+
if key == 'models':
|
|
111
|
+
validate_models(value)
|
|
112
|
+
else:
|
|
113
|
+
validate_value(key, value)
|
|
114
|
+
return data
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def user_defaults():
|
|
118
|
+
return preferences(user_path(), user=True)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def initial_state(text):
|
|
122
|
+
mode = user_defaults().get('integration', 'direct')
|
|
123
|
+
for key in ('integration_default', 'integration'):
|
|
124
|
+
text = re.sub(rf'(?m)^{key}: direct\b', f'{key}: {mode}', text)
|
|
125
|
+
return text
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def show(repo, scope='project'):
|
|
129
|
+
defaults = user_defaults()
|
|
130
|
+
values = {'integration': {'value': defaults.get('integration', 'direct'),
|
|
131
|
+
'source': str(user_path()) if 'integration' in defaults else 'built-in'},
|
|
132
|
+
'review_panel': {'value': defaults.get('review_panel', 'off'),
|
|
133
|
+
'source': str(user_path()) if 'review_panel' in defaults else 'built-in'}}
|
|
134
|
+
models = {key: {'value': value, 'source': str(user_path())}
|
|
135
|
+
for key, value in flatten(defaults.get('models', {}), 'models').items()}
|
|
136
|
+
result = {'scope': scope, 'settings': values, 'models': models, 'roles': ROLES,
|
|
137
|
+
'hosts': HOSTS, 'locked': None, 'approved_review_panel': None}
|
|
138
|
+
if scope == 'project':
|
|
139
|
+
state_api = sibling('pipeline_state')
|
|
140
|
+
repo = state_api._repo_root(repo)
|
|
141
|
+
state, text, path = state_api.load_state(repo)
|
|
142
|
+
config = repo / '.project/config.json'
|
|
143
|
+
local = preferences(config)
|
|
144
|
+
if 'review_panel' in local:
|
|
145
|
+
values['review_panel'] = {'value': local['review_panel'], 'source': str(config)}
|
|
146
|
+
policy_path = repo / '.project/model-policy.json'
|
|
147
|
+
policy = validate_models(read_object(policy_path))
|
|
148
|
+
models.update({key: {'value': value, 'source': str(policy_path)}
|
|
149
|
+
for key, value in flatten(policy, 'models').items()})
|
|
150
|
+
values['integration'] = {'value': state.integration_default, 'source': str(path),
|
|
151
|
+
'current': state.integration, 'current_source': state.integration_source}
|
|
152
|
+
locked = state.phase in ('build', 'ship', 'shipped') or sibling('build_recovery').context(repo, text)
|
|
153
|
+
values['integration']['locked'] = 'Shipping mode is locked when build starts.' if locked else None
|
|
154
|
+
if state.phase in ('ship', 'shipped') or state.archive:
|
|
155
|
+
result['locked'] = 'Project settings are locked during ship and after shipment.'
|
|
156
|
+
panel = sibling('review_panel')
|
|
157
|
+
paths = [repo / '.project/plan/PLAN.md', repo / '.project/intent/INTENT.md', repo / '.project/CHARTER.md']
|
|
158
|
+
texts = [p.read_text(encoding='utf-8') if safe_path(p).exists() else None for p in paths]
|
|
159
|
+
approved = panel.parse_sources(*texts)
|
|
160
|
+
result['approved_review_panel'] = {'value': ','.join(approved['families']) if approved['mode'] == 'named' else approved['mode'],
|
|
161
|
+
'source': approved['source']}
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def set_value(repo, scope, key, value, reset=False):
|
|
166
|
+
repo = repo.resolve()
|
|
167
|
+
if not reset:
|
|
168
|
+
validate_value(key, value)
|
|
169
|
+
elif key not in ('integration', 'review_panel'):
|
|
170
|
+
model_key(key)
|
|
171
|
+
current = show(repo, scope)
|
|
172
|
+
if current['locked']:
|
|
173
|
+
raise ValueError(current['locked'])
|
|
174
|
+
if scope == 'project':
|
|
175
|
+
state, _, _ = sibling('pipeline_state').load_state(repo)
|
|
176
|
+
branch = subprocess.run(['git', '-C', str(repo), 'branch', '--show-current'],
|
|
177
|
+
capture_output=True, text=True)
|
|
178
|
+
if branch.returncode or not state.branch or branch.stdout.strip() != state.branch:
|
|
179
|
+
raise ValueError('Run the router to bind the project branch before editing settings.')
|
|
180
|
+
head = subprocess.run(['git', '-C', str(repo), 'rev-parse', '--verify', 'HEAD'], capture_output=True)
|
|
181
|
+
if head.returncode == 0:
|
|
182
|
+
closed = subprocess.run([sys.executable, '-B', str(Path(__file__).with_name('git_guard.py')),
|
|
183
|
+
'closed-milestone'], cwd=repo, capture_output=True, text=True)
|
|
184
|
+
if closed.returncode:
|
|
185
|
+
raise ValueError(closed.stderr.strip() or 'Cannot verify the milestone branch is open.')
|
|
186
|
+
if scope == 'project' and key == 'integration':
|
|
187
|
+
if reset:
|
|
188
|
+
raise ValueError('choose an explicit project shipping mode')
|
|
189
|
+
sibling('pipeline_state').configure_integration(repo, 'default', value)
|
|
190
|
+
return show(repo, scope)
|
|
191
|
+
path = user_path() if scope == 'user' else repo / '.project' / ('model-policy.json' if key.startswith('models.') else 'config.json')
|
|
192
|
+
safe_path(path)
|
|
193
|
+
safe_path(path.with_name(f'.{path.name}.lock'))
|
|
194
|
+
lock = (sibling('pipeline_state')._state_lock(repo / '.project') if scope == 'project'
|
|
195
|
+
else sibling('loop_run').log_lock({'log': str(path)}))
|
|
196
|
+
with lock:
|
|
197
|
+
current = show(repo, scope)
|
|
198
|
+
if current['locked']:
|
|
199
|
+
raise ValueError(current['locked'])
|
|
200
|
+
data = read_object(path)
|
|
201
|
+
bits = key.split('.')
|
|
202
|
+
if scope == 'project' and bits[0] == 'models':
|
|
203
|
+
bits = bits[1:]
|
|
204
|
+
node = data
|
|
205
|
+
for bit in bits[:-1]:
|
|
206
|
+
node = node.setdefault(bit, {})
|
|
207
|
+
if reset:
|
|
208
|
+
node.pop(bits[-1], None)
|
|
209
|
+
else:
|
|
210
|
+
node[bits[-1]] = value
|
|
211
|
+
safe_path(path)
|
|
212
|
+
safe_path(path.with_name(f'.{path.name}.gsd-path-tmp'))
|
|
213
|
+
sibling('_common').atomic_write(path, json.dumps(data, indent=2, sort_keys=True) + '\n')
|
|
214
|
+
return show(repo, scope)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main():
|
|
218
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
219
|
+
parser.add_argument('action', choices=('show', 'validate', 'set', 'reset'))
|
|
220
|
+
parser.add_argument('key', nargs='?')
|
|
221
|
+
parser.add_argument('value', nargs='?')
|
|
222
|
+
parser.add_argument('--scope', choices=('user', 'project'), default='project')
|
|
223
|
+
parser.add_argument('--repo', type=Path, default=Path.cwd())
|
|
224
|
+
args = parser.parse_args()
|
|
225
|
+
try:
|
|
226
|
+
declaration = args.repo / '.gsd-path/runtime.json'
|
|
227
|
+
if args.scope == 'project' and (declaration.exists() or declaration.is_symlink()):
|
|
228
|
+
runtime = sibling('status_runtime').resolve_runtime(args.repo.resolve())
|
|
229
|
+
helper = runtime / 'path_config.py'
|
|
230
|
+
if not helper.is_file():
|
|
231
|
+
raise ValueError('Update the selected project runtime to use Path settings.')
|
|
232
|
+
if helper.resolve() != Path(__file__).resolve():
|
|
233
|
+
return subprocess.call([sys.executable, '-B', str(helper), *sys.argv[1:]])
|
|
234
|
+
if args.action in ('set', 'reset'):
|
|
235
|
+
if not args.key or (args.action == 'set' and args.value is None):
|
|
236
|
+
raise ValueError('set requires KEY VALUE; reset requires KEY')
|
|
237
|
+
result = set_value(args.repo, args.scope, args.key, args.value, args.action == 'reset')
|
|
238
|
+
else:
|
|
239
|
+
result = show(args.repo, args.scope)
|
|
240
|
+
print(json.dumps(result))
|
|
241
|
+
return 0
|
|
242
|
+
except (OSError, ValueError, RuntimeError) as error:
|
|
243
|
+
print(json.dumps({'error': str(error)}))
|
|
244
|
+
return 2
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == '__main__':
|
|
248
|
+
raise SystemExit(main())
|
package/scripts/pipeline_git.py
CHANGED
|
@@ -19,6 +19,9 @@ import shutil
|
|
|
19
19
|
import stat
|
|
20
20
|
import subprocess
|
|
21
21
|
import sys
|
|
22
|
+
|
|
23
|
+
# Runtime helpers must not modify their immutable installation.
|
|
24
|
+
sys.dont_write_bytecode = True
|
|
22
25
|
from dataclasses import asdict, dataclass
|
|
23
26
|
from datetime import date
|
|
24
27
|
from pathlib import Path, PurePosixPath
|
|
@@ -615,7 +618,7 @@ def _completion_status(repo: Path, state: Mapping[str, object], project_dir: str
|
|
|
615
618
|
import integration
|
|
616
619
|
try:
|
|
617
620
|
proof = integration.validate_integrated(
|
|
618
|
-
repo, str(state.get("milestone") or ""), refresh=False)
|
|
621
|
+
repo, str(state.get("milestone") or ""), refresh=False, historical=True)
|
|
619
622
|
except (integration.ArchiveError, OSError) as error:
|
|
620
623
|
return {"status": "unverified", "reason": str(error)}
|
|
621
624
|
return {"status": "verified", "proof": proof}
|
package/scripts/pipeline_undo.py
CHANGED
|
@@ -17,6 +17,9 @@ import os
|
|
|
17
17
|
import re
|
|
18
18
|
import shutil
|
|
19
19
|
import sys
|
|
20
|
+
|
|
21
|
+
# Runtime helpers must not modify their immutable installation.
|
|
22
|
+
sys.dont_write_bytecode = True
|
|
20
23
|
import tempfile
|
|
21
24
|
from pathlib import Path, PurePosixPath
|
|
22
25
|
from typing import Optional, Sequence
|
package/scripts/review_panel.py
CHANGED
|
@@ -15,6 +15,9 @@ import argparse
|
|
|
15
15
|
import json
|
|
16
16
|
import re
|
|
17
17
|
import sys
|
|
18
|
+
|
|
19
|
+
# Runtime helpers must not modify their immutable installation.
|
|
20
|
+
sys.dont_write_bytecode = True
|
|
18
21
|
from pathlib import Path
|
|
19
22
|
from typing import Iterable, Optional, Sequence
|
|
20
23
|
|
|
@@ -214,6 +217,7 @@ def resolve_panel(
|
|
|
214
217
|
advertised: Sequence[str],
|
|
215
218
|
parent_family: Optional[str] = None,
|
|
216
219
|
parent_slug: Optional[str] = None,
|
|
220
|
+
excluded_families: Sequence[str] = (),
|
|
217
221
|
) -> dict:
|
|
218
222
|
inferred_parent = parent_family or (
|
|
219
223
|
family_of_slug(parent_slug) if parent_slug else None
|
|
@@ -244,7 +248,12 @@ def resolve_panel(
|
|
|
244
248
|
"named review_panel families are not advertised: " + ", ".join(missing)
|
|
245
249
|
)
|
|
246
250
|
|
|
251
|
+
if config["mode"] == "named" and set(wanted) & set(excluded_families):
|
|
252
|
+
raise ReviewPanelError("named review_panel must use an independent family")
|
|
247
253
|
for family in wanted:
|
|
254
|
+
if family in excluded_families:
|
|
255
|
+
skipped.append({"family": family, "reason": "excluded reviewer family"})
|
|
256
|
+
continue
|
|
248
257
|
if family == inferred_parent:
|
|
249
258
|
skipped.append({"family": family, "reason": "parent family"})
|
|
250
259
|
continue
|
|
@@ -488,6 +497,7 @@ def parser() -> argparse.ArgumentParser:
|
|
|
488
497
|
result.add_argument("--advertised", default="")
|
|
489
498
|
result.add_argument("--parent-family")
|
|
490
499
|
result.add_argument("--parent-slug")
|
|
500
|
+
result.add_argument("--exclude-family", action="append", choices=KNOWN_FAMILIES, default=[])
|
|
491
501
|
result.add_argument("--kind", choices=("plan", "wave"))
|
|
492
502
|
result.add_argument("--inputs", default="")
|
|
493
503
|
result.add_argument("--output", type=Path)
|
|
@@ -541,6 +551,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
541
551
|
split_csv(arguments.advertised),
|
|
542
552
|
arguments.parent_family,
|
|
543
553
|
arguments.parent_slug,
|
|
554
|
+
arguments.exclude_family,
|
|
544
555
|
)
|
|
545
556
|
return emit(resolved)
|
|
546
557
|
except ReviewPanelError as error:
|
package/scripts/roadmap.py
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
"""Interpret Roadmap text for selection, approval, promotion and milestone close."""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
# Runtime helpers must not modify their immutable installation.
|
|
9
|
+
sys.dont_write_bytecode = True
|
|
10
|
+
|
|
6
11
|
import re
|
|
7
12
|
from typing import Optional
|
|
8
13
|
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Own immutable project runtimes, explicit upgrades, restoration and migration."""
|
|
3
|
+
import hashlib
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import shutil
|
|
9
|
+
import shlex
|
|
10
|
+
import tempfile
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from . import status_runtime
|
|
15
|
+
except ImportError:
|
|
16
|
+
import status_runtime
|
|
17
|
+
|
|
18
|
+
RUNTIME_FILES = (
|
|
19
|
+
"pipeline_state.py", "roadmap.py", "check_handoffs.py", "check_task_briefs.py",
|
|
20
|
+
"isolation.py", "discussion_records.py", "pipeline_git.py", "promote_lookahead.py",
|
|
21
|
+
"detect_project.py", "pipeline_diagnose.py", "pipeline_undo.py", "archive_milestone.py",
|
|
22
|
+
"review_panel.py", "_common.py", "worktree_paths.py", "build_recovery.py",
|
|
23
|
+
"state_checkpoint.py", "state_promote.py", "discussion_validate.py", "integration.py",
|
|
24
|
+
"build_state.py", "lean_verification.py", "check_docs_audit.py",
|
|
25
|
+
"path_config.py", "model_policy.py", "loop_run.py",
|
|
26
|
+
)
|
|
27
|
+
GUARDS = ("guard_hook.py", "git_guard.py")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def guard_launcher(name):
|
|
31
|
+
if name not in GUARDS:
|
|
32
|
+
raise ValueError("unknown runtime guard")
|
|
33
|
+
return ("#!/usr/bin/env python3\n# gsd-path guard — stable runtime launcher\n"
|
|
34
|
+
"import sys\nsys.dont_write_bytecode = True\nfrom pathlib import Path\n"
|
|
35
|
+
"from status_runtime import run_guard\n"
|
|
36
|
+
"try:\n"
|
|
37
|
+
f" run_guard(Path(__file__).resolve().parent.parent, {name!r})\n"
|
|
38
|
+
"except (OSError, ValueError) as error:\n"
|
|
39
|
+
" print(str(error), file=sys.stderr)\n"
|
|
40
|
+
f" raise SystemExit({2 if name == 'guard_hook.py' else 1})\n")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def source_manifest(source):
|
|
44
|
+
version = json.loads((source / "package.json").read_text())["version"]
|
|
45
|
+
files = {}
|
|
46
|
+
for name in (*RUNTIME_FILES, *GUARDS, "status_runtime.py"):
|
|
47
|
+
path = source / "scripts" / name
|
|
48
|
+
if path.is_symlink():
|
|
49
|
+
raise ValueError(f"symlinked runtime source: {path}")
|
|
50
|
+
files[name] = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
51
|
+
manifest = {"version": version, "files": files}
|
|
52
|
+
pin = {"schema": status_runtime.RUNTIME_SCHEMA, "version": version,
|
|
53
|
+
"digest": status_runtime.manifest_digest(manifest)}
|
|
54
|
+
return manifest, pin
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pin_text(pin):
|
|
58
|
+
return json.dumps(pin, indent=2, sort_keys=True) + "\n"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@contextmanager
|
|
62
|
+
def publication_lock(parent, digest):
|
|
63
|
+
"""The OS releases this external lock even if publication is interrupted."""
|
|
64
|
+
lock = parent / ("." + digest + ".lock")
|
|
65
|
+
if lock.is_symlink():
|
|
66
|
+
raise ValueError(f"unsafe runtime publication lock: {lock}")
|
|
67
|
+
descriptor = os.open(lock, os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0), 0o600)
|
|
68
|
+
try:
|
|
69
|
+
if os.name == "nt":
|
|
70
|
+
import msvcrt
|
|
71
|
+
if os.fstat(descriptor).st_size == 0:
|
|
72
|
+
os.write(descriptor, b"\0")
|
|
73
|
+
os.lseek(descriptor, 0, os.SEEK_SET)
|
|
74
|
+
msvcrt.locking(descriptor, msvcrt.LK_NBLCK, 1)
|
|
75
|
+
else:
|
|
76
|
+
import fcntl
|
|
77
|
+
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
78
|
+
yield
|
|
79
|
+
finally:
|
|
80
|
+
os.close(descriptor)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def publish(source, *, expected=None, dry_run=False, repair=False):
|
|
84
|
+
manifest, pin = source_manifest(source)
|
|
85
|
+
if expected is not None and any(pin[key] != expected.get(key) for key in pin):
|
|
86
|
+
raise ValueError(f"source does not match declared runtime {expected['version']} ({expected['digest']}); use its matching package")
|
|
87
|
+
if dry_run:
|
|
88
|
+
return pin
|
|
89
|
+
parent = status_runtime.runtime_home()
|
|
90
|
+
if any(p.is_symlink() for p in (parent, parent.parent)):
|
|
91
|
+
raise ValueError(f"unsafe runtime storage: {parent}")
|
|
92
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
destination = parent / pin["digest"]
|
|
94
|
+
if destination.is_symlink():
|
|
95
|
+
raise ValueError(f"unsafe runtime storage: {destination}")
|
|
96
|
+
if destination.exists():
|
|
97
|
+
try:
|
|
98
|
+
status_runtime.validate_runtime(pin)
|
|
99
|
+
return pin
|
|
100
|
+
except ValueError:
|
|
101
|
+
if not repair:
|
|
102
|
+
raise
|
|
103
|
+
with tempfile.TemporaryDirectory(prefix=".runtime-stage-", dir=parent) as temporary:
|
|
104
|
+
staging = Path(temporary) / "runtime"
|
|
105
|
+
staging.mkdir()
|
|
106
|
+
for name, digest in manifest["files"].items():
|
|
107
|
+
shutil.copyfile(source / "scripts" / name, staging / name)
|
|
108
|
+
if hashlib.sha256((staging / name).read_bytes()).hexdigest() != digest:
|
|
109
|
+
raise ValueError(f"runtime source changed while installing: {name}")
|
|
110
|
+
(staging / "manifest.json").write_text(pin_text(manifest), encoding="utf-8")
|
|
111
|
+
# Serialize publication/repair, including two explicit restorations.
|
|
112
|
+
previous = Path(temporary) / "previous"
|
|
113
|
+
with publication_lock(parent, pin["digest"]):
|
|
114
|
+
if destination.exists():
|
|
115
|
+
try:
|
|
116
|
+
status_runtime.validate_runtime(pin)
|
|
117
|
+
return pin
|
|
118
|
+
except ValueError:
|
|
119
|
+
if not repair:
|
|
120
|
+
raise
|
|
121
|
+
destination.rename(previous)
|
|
122
|
+
try:
|
|
123
|
+
staging.rename(destination)
|
|
124
|
+
status_runtime.validate_runtime(pin)
|
|
125
|
+
except BaseException:
|
|
126
|
+
if previous.exists() and not destination.exists():
|
|
127
|
+
previous.rename(destination)
|
|
128
|
+
raise
|
|
129
|
+
return pin
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def prepare(source, project, *, dry_run=False):
|
|
133
|
+
declaration = project / ".gsd-path/runtime.json"
|
|
134
|
+
if os.path.lexists(declaration):
|
|
135
|
+
pin = status_runtime.declaration(project)
|
|
136
|
+
status_runtime.validate_runtime(pin)
|
|
137
|
+
return pin
|
|
138
|
+
if os.path.lexists(project / ".gsd-path/runtime"):
|
|
139
|
+
raise ValueError("legacy project runtime requires explicit migration before updating; run: "
|
|
140
|
+
f"npx @opengsd/gsd-path@latest --runtime-migrate --project {shlex.quote(str(project.resolve()))}; "
|
|
141
|
+
"then retry the update. Migration produces a reviewable Git diff")
|
|
142
|
+
return publish(source, dry_run=dry_run)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def atomic_config(path, content):
|
|
146
|
+
if path.is_symlink() or path.parent.is_symlink():
|
|
147
|
+
raise ValueError(f"unsafe project runtime configuration: {path}")
|
|
148
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
fd, name = tempfile.mkstemp(prefix=f".{path.name}.gsd-path-tmp-", dir=path.parent)
|
|
150
|
+
try:
|
|
151
|
+
with os.fdopen(fd, "wb") as handle:
|
|
152
|
+
handle.write(content.encode("utf-8"))
|
|
153
|
+
os.replace(name, path)
|
|
154
|
+
finally:
|
|
155
|
+
if os.path.exists(name):
|
|
156
|
+
os.unlink(name)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def migration_journal(project):
|
|
160
|
+
identity = hashlib.sha256(os.fsencode(project.resolve())).hexdigest()
|
|
161
|
+
return Path.home() / ".gsd-path" / "runtime-migrations" / (identity + ".json")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def recover_migration(project):
|
|
165
|
+
journal = migration_journal(project)
|
|
166
|
+
if not os.path.lexists(journal):
|
|
167
|
+
return
|
|
168
|
+
if journal.is_symlink():
|
|
169
|
+
raise ValueError(f"unsafe runtime migration journal: {journal}")
|
|
170
|
+
data = json.loads(journal.read_text())
|
|
171
|
+
recorded_project = data.get("project")
|
|
172
|
+
if (data.get("schema") != "gsd-path/runtime-migration/v1"
|
|
173
|
+
or not isinstance(recorded_project, str)
|
|
174
|
+
or Path(recorded_project).resolve() != project.resolve()):
|
|
175
|
+
raise ValueError(f"invalid runtime migration journal: {journal}")
|
|
176
|
+
allowed = {".gsd-path/runtime.json", ".gsd-path/status_runtime.py"}
|
|
177
|
+
allowed.update(".gsd-path/" + n for n in GUARDS)
|
|
178
|
+
allowed.update(".gsd-path/runtime/" + n for n in RUNTIME_FILES)
|
|
179
|
+
entries = data.get("files")
|
|
180
|
+
if not isinstance(entries, dict) or not set(entries) <= allowed:
|
|
181
|
+
raise ValueError(f"invalid runtime migration paths: {journal}")
|
|
182
|
+
# Refuse to overwrite edits made after interruption.
|
|
183
|
+
for relative, change in entries.items():
|
|
184
|
+
path = project / relative
|
|
185
|
+
if path.is_symlink() or path.parent.is_symlink():
|
|
186
|
+
raise ValueError(f"unsafe migration recovery path: {path}")
|
|
187
|
+
current = base64.b64encode(path.read_bytes()).decode() if path.exists() else None
|
|
188
|
+
if current not in (change["before"], change["after"]):
|
|
189
|
+
raise ValueError(f"migration recovery would overwrite a changed file: {path}")
|
|
190
|
+
for relative, change in entries.items():
|
|
191
|
+
path = project / relative
|
|
192
|
+
if change["before"] is None:
|
|
193
|
+
path.unlink(missing_ok=True)
|
|
194
|
+
else:
|
|
195
|
+
atomic_config(path, base64.b64decode(change["before"]).decode("utf-8"))
|
|
196
|
+
journal.unlink()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def operate(source, project, action, *, dry_run=False):
|
|
200
|
+
if project.is_symlink() or (project / ".gsd-path").is_symlink():
|
|
201
|
+
raise ValueError("unsafe project runtime directory")
|
|
202
|
+
if action == "restore":
|
|
203
|
+
pin = status_runtime.declaration(project)
|
|
204
|
+
publish(source, expected=pin, dry_run=dry_run, repair=True)
|
|
205
|
+
return pin
|
|
206
|
+
if action == "upgrade":
|
|
207
|
+
status_runtime.declaration(project)
|
|
208
|
+
pin = publish(source, dry_run=dry_run)
|
|
209
|
+
if not dry_run:
|
|
210
|
+
launcher = project / ".gsd-path/status_runtime.py"
|
|
211
|
+
original = launcher.read_text(encoding="utf-8")
|
|
212
|
+
replacement = (source / "scripts/status_runtime.py").read_text(encoding="utf-8")
|
|
213
|
+
# Both launchers understand the same declaration schema. Publishing
|
|
214
|
+
# the compatible launcher first leaves the old selection usable.
|
|
215
|
+
if replacement != original:
|
|
216
|
+
atomic_config(launcher, replacement)
|
|
217
|
+
try:
|
|
218
|
+
atomic_config(project / ".gsd-path/runtime.json", pin_text(pin))
|
|
219
|
+
except BaseException:
|
|
220
|
+
if replacement != original:
|
|
221
|
+
atomic_config(launcher, original)
|
|
222
|
+
raise
|
|
223
|
+
return pin
|
|
224
|
+
if action != "migrate":
|
|
225
|
+
raise ValueError("unknown runtime operation")
|
|
226
|
+
# Migration changes only reviewed managed files and never stages or commits.
|
|
227
|
+
import subprocess
|
|
228
|
+
if not dry_run:
|
|
229
|
+
recover_migration(project)
|
|
230
|
+
runtime = project / ".gsd-path/runtime"
|
|
231
|
+
if runtime.is_symlink() or not runtime.is_dir():
|
|
232
|
+
raise ValueError("migration requires a real legacy runtime directory")
|
|
233
|
+
entries = list(runtime.iterdir())
|
|
234
|
+
if {p.name for p in entries} - set(RUNTIME_FILES):
|
|
235
|
+
raise ValueError("legacy runtime contains unknown files; preserve and resolve them before migration")
|
|
236
|
+
managed = entries + [project / ".gsd-path/status_runtime.py"]
|
|
237
|
+
managed += [project / ".gsd-path" / n for n in GUARDS if (project / ".gsd-path" / n).exists()]
|
|
238
|
+
for path in managed:
|
|
239
|
+
if path.is_symlink() or not path.is_file():
|
|
240
|
+
raise ValueError(f"unsafe migration file: {path}")
|
|
241
|
+
marker = ("gsd-path project runtime" if path.parent == runtime else
|
|
242
|
+
"gsd-path project status launcher" if path.name == "status_runtime.py" else "gsd-path guard")
|
|
243
|
+
if marker not in path.read_text(encoding="utf-8"):
|
|
244
|
+
raise ValueError(f"unmanaged migration file: {path}")
|
|
245
|
+
relative = path.relative_to(project).as_posix()
|
|
246
|
+
tracked = subprocess.run(["git", "ls-files", "--error-unmatch", "--", relative], cwd=project, capture_output=True)
|
|
247
|
+
if tracked.returncode == 0:
|
|
248
|
+
clean = subprocess.run(["git", "status", "--porcelain", "--", relative], cwd=project, capture_output=True, check=True)
|
|
249
|
+
if clean.stdout:
|
|
250
|
+
raise ValueError(f"locally modified migration file: {path}")
|
|
251
|
+
elif path.read_bytes() != (source / "scripts" / path.name).read_bytes():
|
|
252
|
+
raise ValueError(f"untracked migration file differs from source: {path}")
|
|
253
|
+
pin = publish(source, dry_run=dry_run)
|
|
254
|
+
if dry_run:
|
|
255
|
+
return pin
|
|
256
|
+
originals = {p: p.read_bytes() for p in managed}
|
|
257
|
+
declaration = project / ".gsd-path/runtime.json"
|
|
258
|
+
if os.path.lexists(declaration):
|
|
259
|
+
raise ValueError("runtime declaration already exists; refusing legacy migration")
|
|
260
|
+
updates = {project / ".gsd-path/status_runtime.py": (source / "scripts/status_runtime.py").read_bytes(),
|
|
261
|
+
declaration: pin_text(pin).encode()}
|
|
262
|
+
updates.update({p: None for p in entries})
|
|
263
|
+
updates.update({p: guard_launcher(p.name).encode() for p in managed if p.name in GUARDS})
|
|
264
|
+
changes = {p.relative_to(project).as_posix(): {
|
|
265
|
+
"before": base64.b64encode(originals[p]).decode() if p in originals else None,
|
|
266
|
+
"after": base64.b64encode(content).decode() if content is not None else None,
|
|
267
|
+
} for p, content in updates.items()}
|
|
268
|
+
journal = migration_journal(project)
|
|
269
|
+
atomic_config(journal, pin_text({"schema": "gsd-path/runtime-migration/v1", "project": str(project.resolve()), "files": changes}))
|
|
270
|
+
try:
|
|
271
|
+
for path, content in updates.items():
|
|
272
|
+
if content is None:
|
|
273
|
+
path.unlink()
|
|
274
|
+
else:
|
|
275
|
+
atomic_config(path, content.decode("utf-8"))
|
|
276
|
+
runtime.rmdir()
|
|
277
|
+
except BaseException:
|
|
278
|
+
recover_migration(project)
|
|
279
|
+
raise
|
|
280
|
+
journal.unlink()
|
|
281
|
+
return pin
|