@opengsd/gsd-path 1.5.0 → 1.6.1
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 +1 -1
- package/CHANGELOG.md +169 -0
- package/DOCS.md +4 -3
- package/FULL.md +68 -7
- package/HOOKS.md +81 -4
- package/MIGRATE.md +12 -0
- package/QUICK.md +4 -2
- package/README.md +69 -16
- package/RUNTIME.md +23 -5
- package/UPDATE.md +35 -0
- package/WORKFLOW.md +66 -7
- package/package.json +1 -1
- package/scripts/_common.py +150 -1
- package/scripts/build_recovery.py +62 -30
- package/scripts/build_state.py +195 -5
- package/scripts/check_handoffs.py +92 -30
- package/scripts/check_task_briefs.py +27 -19
- package/scripts/detect_project.py +17 -2
- package/scripts/discussion_records.py +9 -1
- package/scripts/discussion_validate.py +7 -6
- package/scripts/dispatch_driver.py +174 -36
- package/scripts/git_guard.py +42 -7
- package/scripts/guard_hook.py +1216 -47
- package/scripts/install.mjs +76 -3
- package/scripts/install.py +182 -37
- package/scripts/integration.py +115 -68
- package/scripts/isolation.py +192 -23
- package/scripts/members.py +68 -25
- package/scripts/migrate_core.py +2 -0
- package/scripts/model_policy.py +13 -2
- package/scripts/pipeline_diagnose.py +17 -2
- package/scripts/pipeline_git.py +87 -35
- package/scripts/pipeline_state.py +31 -7
- package/scripts/pipeline_undo.py +17 -8
- package/scripts/promote_lookahead.py +3 -3
- package/scripts/review_findings.py +180 -44
- package/scripts/roadmap.py +9 -1
- package/scripts/runtime_store.py +1 -0
- package/scripts/state_checkpoint.py +30 -9
- package/scripts/state_promote.py +92 -5
- package/scripts/task_context.py +2 -0
- package/scripts/workflow_run.py +9 -2
- package/skills/gsd-path/DEFINE.md +6 -2
- package/skills/gsd-path/PLAN.md +6 -2
- package/skills/gsd-path/ROADMAP.md +16 -6
- package/skills/gsd-path/SHIP.md +38 -11
- package/skills/gsd-path/SKILL.md +52 -24
- package/skills/gsd-path/references/build-native.md +96 -9
- package/skills/gsd-path/references/build-recovery.md +13 -0
- package/skills/gsd-path/references/coder.md +7 -3
- package/skills/gsd-path/references/model-policy.md +3 -1
- package/skills/gsd-path/references/operating-rules.md +14 -5
- package/skills/gsd-path/references/planner.md +13 -1
- package/skills/gsd-path/references/reviewer-final.md +5 -5
- package/skills/gsd-path/references/reviewer-wave.md +46 -13
- package/skills/gsd-path/references/runtime-selection.md +10 -0
- package/skills/gsd-path/scripts/_common.py +150 -1
- package/skills/gsd-path/scripts/build_recovery.py +62 -30
- package/skills/gsd-path/scripts/build_state.py +195 -5
- package/skills/gsd-path/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path/scripts/detect_project.py +17 -2
- package/skills/gsd-path/scripts/discussion_records.py +9 -1
- package/skills/gsd-path/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path/scripts/dispatch_driver.py +174 -36
- package/skills/gsd-path/scripts/git_guard.py +42 -7
- package/skills/gsd-path/scripts/integration.py +115 -68
- package/skills/gsd-path/scripts/isolation.py +192 -23
- package/skills/gsd-path/scripts/members.py +68 -25
- package/skills/gsd-path/scripts/model_policy.py +13 -2
- package/skills/gsd-path/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path/scripts/review_findings.py +180 -44
- package/skills/gsd-path/scripts/roadmap.py +9 -1
- package/skills/gsd-path/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path/scripts/state_promote.py +92 -5
- package/skills/gsd-path/scripts/task_context.py +2 -0
- package/skills/gsd-path/scripts/workflow_run.py +9 -2
- package/skills/gsd-path/templates/final-review.md +5 -5
- package/skills/gsd-path/templates/state.md +1 -0
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +18 -9
- package/skills/gsd-path-build/references/build-native.md +96 -9
- package/skills/gsd-path-build/references/build-recovery.md +13 -0
- package/skills/gsd-path-build/references/coder.md +7 -3
- package/skills/gsd-path-build/references/model-policy.md +3 -1
- package/skills/gsd-path-build/references/operating-rules.md +14 -5
- package/skills/gsd-path-build/references/reviewer-final.md +5 -5
- package/skills/gsd-path-build/references/reviewer-wave.md +46 -13
- package/skills/gsd-path-build/references/runtime-selection.md +10 -0
- package/skills/gsd-path-build/scripts/_common.py +150 -1
- package/skills/gsd-path-build/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-build/scripts/build_state.py +195 -5
- package/skills/gsd-path-build/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-build/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-build/scripts/detect_project.py +17 -2
- package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-build/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-build/scripts/dispatch_driver.py +174 -36
- package/skills/gsd-path-build/scripts/integration.py +115 -68
- package/skills/gsd-path-build/scripts/isolation.py +192 -23
- package/skills/gsd-path-build/scripts/members.py +68 -25
- package/skills/gsd-path-build/scripts/model_policy.py +13 -2
- package/skills/gsd-path-build/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-build/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-build/scripts/review_findings.py +180 -44
- package/skills/gsd-path-build/scripts/roadmap.py +9 -1
- package/skills/gsd-path-build/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-build/scripts/state_promote.py +92 -5
- package/skills/gsd-path-build/scripts/task_context.py +2 -0
- package/skills/gsd-path-build/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +18 -9
- package/skills/gsd-path-decide/references/model-policy.md +3 -1
- package/skills/gsd-path-decide/references/operating-rules.md +14 -5
- package/skills/gsd-path-decide/references/runtime-selection.md +10 -0
- package/skills/gsd-path-decide/scripts/_common.py +150 -1
- package/skills/gsd-path-decide/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-decide/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-decide/scripts/detect_project.py +17 -2
- package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-decide/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-decide/scripts/integration.py +115 -68
- package/skills/gsd-path-decide/scripts/isolation.py +192 -23
- package/skills/gsd-path-decide/scripts/members.py +68 -25
- package/skills/gsd-path-decide/scripts/model_policy.py +13 -2
- package/skills/gsd-path-decide/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-decide/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-decide/scripts/roadmap.py +9 -1
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-decide/scripts/state_promote.py +92 -5
- package/skills/gsd-path-define/SKILL.md +6 -2
- package/skills/gsd-path-define/references/build-recovery.md +13 -0
- package/skills/gsd-path-define/references/operating-rules.md +14 -5
- package/skills/gsd-path-define/references/runtime-selection.md +10 -0
- package/skills/gsd-path-define/scripts/_common.py +150 -1
- package/skills/gsd-path-define/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-define/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-define/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-define/scripts/detect_project.py +17 -2
- package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-define/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-define/scripts/integration.py +115 -68
- package/skills/gsd-path-define/scripts/isolation.py +192 -23
- package/skills/gsd-path-define/scripts/members.py +68 -25
- package/skills/gsd-path-define/scripts/model_policy.py +13 -2
- package/skills/gsd-path-define/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-define/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-define/scripts/roadmap.py +9 -1
- package/skills/gsd-path-define/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-define/scripts/state_promote.py +92 -5
- package/skills/gsd-path-define/templates/state.md +1 -0
- package/skills/gsd-path-discuss/references/operating-rules.md +14 -5
- package/skills/gsd-path-discuss/references/runtime-selection.md +10 -0
- package/skills/gsd-path-discuss/scripts/_common.py +150 -1
- package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-discuss/scripts/model_policy.py +13 -2
- package/skills/gsd-path-docs-audit/references/model-policy.md +3 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +14 -5
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +10 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +150 -1
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +17 -2
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-docs-audit/scripts/integration.py +115 -68
- package/skills/gsd-path-docs-audit/scripts/isolation.py +192 -23
- package/skills/gsd-path-docs-audit/scripts/members.py +68 -25
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +13 -2
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-docs-audit/scripts/roadmap.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +92 -5
- package/skills/gsd-path-forensics/references/operating-rules.md +14 -5
- package/skills/gsd-path-forensics/references/runtime-selection.md +10 -0
- package/skills/gsd-path-forensics/scripts/_common.py +150 -1
- package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-forensics/scripts/model_policy.py +13 -2
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +17 -8
- package/skills/gsd-path-inspect/references/model-policy.md +3 -1
- package/skills/gsd-path-inspect/references/operating-rules.md +14 -5
- package/skills/gsd-path-inspect/references/runtime-selection.md +10 -0
- package/skills/gsd-path-inspect/scripts/_common.py +150 -1
- package/skills/gsd-path-inspect/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-inspect/scripts/detect_project.py +17 -2
- package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-inspect/scripts/integration.py +115 -68
- package/skills/gsd-path-inspect/scripts/isolation.py +192 -23
- package/skills/gsd-path-inspect/scripts/members.py +68 -25
- package/skills/gsd-path-inspect/scripts/model_policy.py +13 -2
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-inspect/scripts/roadmap.py +9 -1
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-inspect/scripts/state_promote.py +92 -5
- package/skills/gsd-path-inspect/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-inspect/templates/state.md +1 -0
- package/skills/gsd-path-loop/references/operating-rules.md +14 -5
- package/skills/gsd-path-loop/references/runtime-selection.md +10 -0
- package/skills/gsd-path-loop/scripts/_common.py +150 -1
- package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-loop/scripts/model_policy.py +13 -2
- package/skills/gsd-path-migrate/SKILL.md +8 -0
- package/skills/gsd-path-migrate/references/operating-rules.md +14 -5
- package/skills/gsd-path-migrate/references/runtime-selection.md +10 -0
- package/skills/gsd-path-migrate/scripts/_common.py +150 -1
- package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -0
- package/skills/gsd-path-migrate/scripts/model_policy.py +13 -2
- package/skills/gsd-path-plan/SKILL.md +6 -2
- package/skills/gsd-path-plan/references/build-recovery.md +13 -0
- package/skills/gsd-path-plan/references/model-policy.md +3 -1
- package/skills/gsd-path-plan/references/operating-rules.md +14 -5
- package/skills/gsd-path-plan/references/planner.md +13 -1
- package/skills/gsd-path-plan/references/runtime-selection.md +10 -0
- package/skills/gsd-path-plan/scripts/_common.py +150 -1
- package/skills/gsd-path-plan/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-plan/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-plan/scripts/detect_project.py +17 -2
- package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-plan/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-plan/scripts/integration.py +115 -68
- package/skills/gsd-path-plan/scripts/isolation.py +192 -23
- package/skills/gsd-path-plan/scripts/members.py +68 -25
- package/skills/gsd-path-plan/scripts/model_policy.py +13 -2
- package/skills/gsd-path-plan/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-plan/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-plan/scripts/roadmap.py +9 -1
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-plan/scripts/state_promote.py +92 -5
- package/skills/gsd-path-plan/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/references/model-policy.md +3 -1
- package/skills/gsd-path-research/references/operating-rules.md +14 -5
- package/skills/gsd-path-research/references/runtime-selection.md +10 -0
- package/skills/gsd-path-research/scripts/_common.py +150 -1
- package/skills/gsd-path-research/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-research/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-research/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-research/scripts/detect_project.py +17 -2
- package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-research/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-research/scripts/integration.py +115 -68
- package/skills/gsd-path-research/scripts/isolation.py +192 -23
- package/skills/gsd-path-research/scripts/members.py +68 -25
- package/skills/gsd-path-research/scripts/model_policy.py +13 -2
- package/skills/gsd-path-research/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-research/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-research/scripts/roadmap.py +9 -1
- package/skills/gsd-path-research/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-research/scripts/state_promote.py +92 -5
- package/skills/gsd-path-roadmap/SKILL.md +16 -6
- package/skills/gsd-path-roadmap/references/model-policy.md +3 -1
- package/skills/gsd-path-roadmap/references/operating-rules.md +14 -5
- package/skills/gsd-path-roadmap/references/runtime-selection.md +10 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +150 -1
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-roadmap/scripts/detect_project.py +17 -2
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-roadmap/scripts/integration.py +115 -68
- package/skills/gsd-path-roadmap/scripts/isolation.py +192 -23
- package/skills/gsd-path-roadmap/scripts/members.py +68 -25
- package/skills/gsd-path-roadmap/scripts/model_policy.py +13 -2
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-roadmap/scripts/roadmap.py +9 -1
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-roadmap/scripts/state_promote.py +92 -5
- package/skills/gsd-path-ship/SKILL.md +38 -11
- package/skills/gsd-path-ship/references/model-policy.md +3 -1
- package/skills/gsd-path-ship/references/operating-rules.md +14 -5
- package/skills/gsd-path-ship/references/reviewer-final.md +5 -5
- package/skills/gsd-path-ship/references/reviewer-wave.md +46 -13
- package/skills/gsd-path-ship/references/runtime-selection.md +10 -0
- package/skills/gsd-path-ship/scripts/_common.py +150 -1
- package/skills/gsd-path-ship/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-ship/scripts/build_state.py +195 -5
- package/skills/gsd-path-ship/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +27 -19
- package/skills/gsd-path-ship/scripts/detect_project.py +17 -2
- package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-ship/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-ship/scripts/integration.py +115 -68
- package/skills/gsd-path-ship/scripts/isolation.py +192 -23
- package/skills/gsd-path-ship/scripts/members.py +68 -25
- package/skills/gsd-path-ship/scripts/model_policy.py +13 -2
- package/skills/gsd-path-ship/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-ship/scripts/pipeline_state.py +31 -7
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-ship/scripts/roadmap.py +9 -1
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +30 -9
- package/skills/gsd-path-ship/scripts/state_promote.py +92 -5
- package/skills/gsd-path-ship/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-ship/templates/final-review.md +5 -5
- package/skills/gsd-path-ship/templates/wave-review.md +18 -9
- package/skills/gsd-path-undo/references/operating-rules.md +14 -5
- package/skills/gsd-path-undo/references/runtime-selection.md +10 -0
- package/skills/gsd-path-undo/scripts/_common.py +150 -1
- package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-undo/scripts/model_policy.py +13 -2
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +17 -8
- package/skills/path/DEFINE.md +6 -2
- package/skills/path/PLAN.md +6 -2
- package/skills/path/ROADMAP.md +16 -6
- package/skills/path/SHIP.md +38 -11
- package/skills/path/SKILL.md +52 -24
- package/skills/path/references/build-native.md +96 -9
- package/skills/path/references/build-recovery.md +13 -0
- package/skills/path/references/coder.md +7 -3
- package/skills/path/references/model-policy.md +3 -1
- package/skills/path/references/operating-rules.md +14 -5
- package/skills/path/references/planner.md +13 -1
- package/skills/path/references/reviewer-final.md +5 -5
- package/skills/path/references/reviewer-wave.md +46 -13
- package/skills/path/references/runtime-selection.md +10 -0
- package/skills/path/scripts/_common.py +150 -1
- package/skills/path/scripts/build_recovery.py +62 -30
- package/skills/path/scripts/build_state.py +195 -5
- package/skills/path/scripts/check_handoffs.py +92 -30
- package/skills/path/scripts/check_task_briefs.py +27 -19
- package/skills/path/scripts/detect_project.py +17 -2
- package/skills/path/scripts/discussion_records.py +9 -1
- package/skills/path/scripts/discussion_validate.py +7 -6
- package/skills/path/scripts/dispatch_driver.py +174 -36
- package/skills/path/scripts/git_guard.py +42 -7
- package/skills/path/scripts/integration.py +115 -68
- package/skills/path/scripts/isolation.py +192 -23
- package/skills/path/scripts/members.py +68 -25
- package/skills/path/scripts/model_policy.py +13 -2
- package/skills/path/scripts/pipeline_git.py +87 -35
- package/skills/path/scripts/pipeline_state.py +31 -7
- package/skills/path/scripts/promote_lookahead.py +3 -3
- package/skills/path/scripts/review_findings.py +180 -44
- package/skills/path/scripts/roadmap.py +9 -1
- package/skills/path/scripts/state_checkpoint.py +30 -9
- package/skills/path/scripts/state_promote.py +92 -5
- package/skills/path/scripts/task_context.py +2 -0
- package/skills/path/scripts/workflow_run.py +9 -2
- package/skills/path/templates/final-review.md +5 -5
- package/skills/path/templates/state.md +1 -0
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +18 -9
|
@@ -13,6 +13,7 @@ import json
|
|
|
13
13
|
import os
|
|
14
14
|
import shlex
|
|
15
15
|
import sys
|
|
16
|
+
import traceback
|
|
16
17
|
|
|
17
18
|
# Runtime helpers must not modify their immutable installation.
|
|
18
19
|
sys.dont_write_bytecode = True
|
|
@@ -86,7 +87,7 @@ def _load_diagnose_modules():
|
|
|
86
87
|
pipeline_git,
|
|
87
88
|
pipeline_state,
|
|
88
89
|
)
|
|
89
|
-
except
|
|
90
|
+
except ImportError as error: # a foreign `scripts` package
|
|
90
91
|
if error.name not in {
|
|
91
92
|
"scripts",
|
|
92
93
|
"scripts.archive_milestone",
|
|
@@ -98,6 +99,13 @@ def _load_diagnose_modules():
|
|
|
98
99
|
"scripts.pipeline_state",
|
|
99
100
|
}:
|
|
100
101
|
raise
|
|
102
|
+
# A plain ImportError raised while a `scripts` module loaded is not a
|
|
103
|
+
# foreign package. A missing module falls back, as before.
|
|
104
|
+
if not isinstance(error, ModuleNotFoundError) and any(
|
|
105
|
+
frame.f_globals.get("__name__", "").startswith("scripts.")
|
|
106
|
+
for frame, _ in traceback.walk_tb(error.__traceback__)
|
|
107
|
+
):
|
|
108
|
+
raise
|
|
101
109
|
shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
|
|
102
110
|
sys.path.insert(0, str(shared))
|
|
103
111
|
import archive_milestone
|
|
@@ -147,9 +155,16 @@ else:
|
|
|
147
155
|
raise
|
|
148
156
|
try:
|
|
149
157
|
from scripts import pipeline_undo
|
|
150
|
-
except
|
|
158
|
+
except ImportError as error: # a foreign `scripts` package
|
|
151
159
|
if error.name not in {"scripts", "scripts.pipeline_undo"}:
|
|
152
160
|
raise
|
|
161
|
+
# A plain ImportError raised while a `scripts` module loaded is not a
|
|
162
|
+
# foreign package. A missing module falls back, as before.
|
|
163
|
+
if not isinstance(error, ModuleNotFoundError) and any(
|
|
164
|
+
frame.f_globals.get("__name__", "").startswith("scripts.")
|
|
165
|
+
for frame, _ in traceback.walk_tb(error.__traceback__)
|
|
166
|
+
):
|
|
167
|
+
raise
|
|
153
168
|
shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
|
|
154
169
|
if str(shared) not in sys.path:
|
|
155
170
|
sys.path.insert(0, str(shared))
|
package/scripts/pipeline_git.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
# gsd-path project runtime
|
|
3
3
|
"""Deterministic GSD Path branch names and pipeline commit messages.
|
|
4
4
|
|
|
5
|
-
Bound work lives on `gsd-path/M00N` for that milestone. The
|
|
6
|
-
|
|
5
|
+
Bound work lives on `gsd-path/M00N` for that milestone. The remote default
|
|
6
|
+
that STATE.md records (`main` when unrecorded) stays a separate trunk. Ship
|
|
7
|
+
merges the bound branch onto it;
|
|
7
8
|
the next milestone binds a new unused `gsd-path/M00N` and retires the
|
|
8
9
|
integrated previous branch, locally and on origin.
|
|
9
10
|
"""
|
|
@@ -98,7 +99,8 @@ def ship_subject(archive_name: str) -> str:
|
|
|
98
99
|
|
|
99
100
|
|
|
100
101
|
def integrate_subject(archive_name: str, default_branch: str) -> str:
|
|
101
|
-
|
|
102
|
+
# The bare name, under the rule that records it; `origin/trunk` is a legal bare name.
|
|
103
|
+
if not _common.valid_default_branch(default_branch):
|
|
102
104
|
raise PipelineGitError(f"default branch is invalid: {default_branch}")
|
|
103
105
|
number = milestone_number(archive_name)
|
|
104
106
|
return (
|
|
@@ -363,7 +365,7 @@ def _remote_branch_exists(repo: Path, branch: str) -> bool:
|
|
|
363
365
|
return _remote_ref_sha(repo, f"refs/heads/{branch}") is not None
|
|
364
366
|
|
|
365
367
|
|
|
366
|
-
def
|
|
368
|
+
def _milestone_tag_fields(repo: Path, ref: str) -> dict[str, str]:
|
|
367
369
|
tag_type = _run_git(repo, "cat-file", "-t", ref, check=False)
|
|
368
370
|
if tag_type.returncode != 0 or tag_type.stdout.strip() != "tag":
|
|
369
371
|
return {}
|
|
@@ -373,7 +375,7 @@ def _pull_request_tag_fields(repo: Path, ref: str) -> dict[str, str]:
|
|
|
373
375
|
match = re.fullmatch(r"(Mode|Pull-Request|Ship|Landing): (.+)", line)
|
|
374
376
|
if match:
|
|
375
377
|
if match.group(1) in fields:
|
|
376
|
-
raise PipelineGitError("
|
|
378
|
+
raise PipelineGitError("milestone tag repeats metadata")
|
|
377
379
|
fields[match.group(1)] = match.group(2)
|
|
378
380
|
return fields
|
|
379
381
|
|
|
@@ -404,7 +406,15 @@ def _validated_shipped_state(repo: Path) -> dict[str, object]:
|
|
|
404
406
|
return state
|
|
405
407
|
|
|
406
408
|
|
|
407
|
-
|
|
409
|
+
# Metadata a published milestone tag must carry, per integration mode, before
|
|
410
|
+
# bind-next accepts a previous bound branch that the forge deleted on merge.
|
|
411
|
+
TAG_PROOF_FIELDS = {
|
|
412
|
+
"pull-request": {"Mode", "Pull-Request", "Ship", "Landing"},
|
|
413
|
+
"external-landing": {"Mode", "Ship", "Landing"},
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _require_tag_integration_proof(
|
|
408
418
|
repo: Path,
|
|
409
419
|
ship: str,
|
|
410
420
|
base: str,
|
|
@@ -415,8 +425,11 @@ def _require_pull_request_integration_proof(
|
|
|
415
425
|
raise PipelineGitError("bind-next requires shipped/done state")
|
|
416
426
|
if state.get("branch") != previous_branch:
|
|
417
427
|
raise PipelineGitError("shipped state does not name the previous branch")
|
|
418
|
-
|
|
419
|
-
|
|
428
|
+
mode = state.get("integration")
|
|
429
|
+
if mode not in TAG_PROOF_FIELDS:
|
|
430
|
+
raise PipelineGitError(
|
|
431
|
+
"shipped integration mode is not pull-request or external-landing"
|
|
432
|
+
)
|
|
420
433
|
archive = state.get("archive")
|
|
421
434
|
if not isinstance(archive, str):
|
|
422
435
|
raise PipelineGitError("shipped state does not name an archive")
|
|
@@ -427,24 +440,23 @@ def _require_pull_request_integration_proof(
|
|
|
427
440
|
):
|
|
428
441
|
raise PipelineGitError("shipped archive does not match the previous branch")
|
|
429
442
|
ref = f"refs/tags/milestone/{match.group(1)}"
|
|
430
|
-
fields =
|
|
431
|
-
if set(fields) !=
|
|
432
|
-
raise PipelineGitError("
|
|
433
|
-
if fields["Mode"] !=
|
|
434
|
-
raise PipelineGitError("
|
|
435
|
-
|
|
436
|
-
if re.fullmatch(
|
|
443
|
+
fields = _milestone_tag_fields(repo, ref)
|
|
444
|
+
if set(fields) != TAG_PROOF_FIELDS[mode]:
|
|
445
|
+
raise PipelineGitError(f"{mode} integration proof is incomplete")
|
|
446
|
+
if fields["Mode"] != mode or fields["Ship"] != ship:
|
|
447
|
+
raise PipelineGitError(f"{mode} integration proof does not match shipped state")
|
|
448
|
+
if mode == "pull-request" and re.fullmatch(
|
|
437
449
|
r"https://github\.com/[^/]+/[^/]+/pull/[1-9][0-9]*",
|
|
438
|
-
|
|
450
|
+
fields["Pull-Request"],
|
|
439
451
|
) is None:
|
|
440
452
|
raise PipelineGitError("pull-request integration proof has an invalid PR URL")
|
|
441
453
|
landing = fields["Landing"]
|
|
442
454
|
if re.fullmatch(r"[0-9a-f]{40}", landing) is None:
|
|
443
|
-
raise PipelineGitError("
|
|
455
|
+
raise PipelineGitError(f"{mode} integration proof has an invalid landing")
|
|
444
456
|
local_object = _run_git(repo, "rev-parse", ref).stdout.strip()
|
|
445
457
|
local_target = _run_git(repo, "rev-parse", f"{ref}^{{commit}}").stdout.strip()
|
|
446
458
|
if local_target != landing:
|
|
447
|
-
raise PipelineGitError("
|
|
459
|
+
raise PipelineGitError(f"{mode} integration tag does not point at landing")
|
|
448
460
|
remote = _run_git(
|
|
449
461
|
repo,
|
|
450
462
|
"ls-remote",
|
|
@@ -456,14 +468,14 @@ def _require_pull_request_integration_proof(
|
|
|
456
468
|
check=False,
|
|
457
469
|
)
|
|
458
470
|
if remote.returncode != 0:
|
|
459
|
-
raise PipelineGitError("
|
|
471
|
+
raise PipelineGitError(f"{mode} integration tag is not published")
|
|
460
472
|
rows = dict(
|
|
461
473
|
line.split("\t", 1)[::-1]
|
|
462
474
|
for line in remote.stdout.splitlines()
|
|
463
475
|
if "\t" in line
|
|
464
476
|
)
|
|
465
477
|
if rows != {ref: local_object, f"{ref}^{{}}": landing}:
|
|
466
|
-
raise PipelineGitError("published
|
|
478
|
+
raise PipelineGitError(f"published {mode} integration tag differs")
|
|
467
479
|
parents = _run_git(
|
|
468
480
|
repo,
|
|
469
481
|
"rev-list",
|
|
@@ -472,12 +484,17 @@ def _require_pull_request_integration_proof(
|
|
|
472
484
|
"1",
|
|
473
485
|
landing,
|
|
474
486
|
).stdout.split()
|
|
475
|
-
|
|
476
|
-
|
|
487
|
+
rebased = (
|
|
488
|
+
mode == "pull-request"
|
|
489
|
+
and len(parents) == 2
|
|
490
|
+
and _common.rebase_landing_problem(repo, ship, landing) is None
|
|
491
|
+
)
|
|
492
|
+
if not rebased and (len(parents) != 3 or parents[2] != ship):
|
|
493
|
+
raise PipelineGitError(f"{mode} integration proof has invalid merge topology")
|
|
477
494
|
first_parent = _run_git(repo, "rev-list", "--first-parent", base).stdout.splitlines()
|
|
478
495
|
if landing not in first_parent:
|
|
479
496
|
raise PipelineGitError(
|
|
480
|
-
"
|
|
497
|
+
f"{mode} integration landing is not on the default branch first-parent history"
|
|
481
498
|
)
|
|
482
499
|
return landing
|
|
483
500
|
|
|
@@ -533,6 +550,34 @@ def _write_bind_next_journal(path: Path, value: dict[str, object]) -> None:
|
|
|
533
550
|
temporary.unlink()
|
|
534
551
|
|
|
535
552
|
|
|
553
|
+
def _require_state_remote_default(repo: Path, remote_default: str) -> str:
|
|
554
|
+
"""Return the default branch STATE.md records; `remote_default` must name it.
|
|
555
|
+
|
|
556
|
+
No STATE.md yet (bind-initial before initialize) or no field means main.
|
|
557
|
+
An unreadable STATE.md also means main, as before the field existed: the
|
|
558
|
+
callers that need a valid state check it themselves.
|
|
559
|
+
"""
|
|
560
|
+
if __package__:
|
|
561
|
+
from . import pipeline_state
|
|
562
|
+
else:
|
|
563
|
+
try:
|
|
564
|
+
import pipeline_state
|
|
565
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
566
|
+
from scripts import pipeline_state
|
|
567
|
+
|
|
568
|
+
default_name, unreadable = "main", ""
|
|
569
|
+
if (repo / ".project" / "STATE.md").exists():
|
|
570
|
+
try:
|
|
571
|
+
default_name = pipeline_state.load_state(repo)[0].default_branch
|
|
572
|
+
except (OSError, pipeline_state.PipelineStateError) as error:
|
|
573
|
+
unreadable = f" (STATE.md is unreadable: {error})"
|
|
574
|
+
if remote_default != f"origin/{default_name}":
|
|
575
|
+
raise PipelineGitError(
|
|
576
|
+
f"remote default must be origin/{default_name}, got {remote_default}{unreadable}"
|
|
577
|
+
)
|
|
578
|
+
return default_name
|
|
579
|
+
|
|
580
|
+
|
|
536
581
|
def _initial_binding_snapshot(repo: Path) -> Optional[tuple]:
|
|
537
582
|
"""Admit only clean work or the validated, untracked initializer state."""
|
|
538
583
|
dirty = _run_git(repo, "status", "--porcelain=v1", "-z", "--untracked-files=all").stdout
|
|
@@ -579,8 +624,7 @@ def bind_initial_milestone_branch(
|
|
|
579
624
|
resolved = _require_worktree_root(repo)
|
|
580
625
|
if not is_bound_branch(branch):
|
|
581
626
|
raise PipelineGitError(f"invalid bound branch: {branch}")
|
|
582
|
-
|
|
583
|
-
raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
|
|
627
|
+
default_name = _require_state_remote_default(resolved, remote_default)
|
|
584
628
|
|
|
585
629
|
default_sha = _run_git(
|
|
586
630
|
resolved,
|
|
@@ -600,7 +644,7 @@ def bind_initial_milestone_branch(
|
|
|
600
644
|
raise PipelineGitError(
|
|
601
645
|
f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
|
|
602
646
|
)
|
|
603
|
-
remote_sha = _remote_ref_sha(resolved, "refs/heads/
|
|
647
|
+
remote_sha = _remote_ref_sha(resolved, f"refs/heads/{default_name}")
|
|
604
648
|
if remote_sha != default_sha:
|
|
605
649
|
raise PipelineGitError(
|
|
606
650
|
f"fetched {remote_default} is stale relative to origin"
|
|
@@ -655,6 +699,7 @@ def retire_previous_branch(
|
|
|
655
699
|
expected_sha: str,
|
|
656
700
|
*,
|
|
657
701
|
allow_remote_absent: bool = False,
|
|
702
|
+
rebased: bool = False,
|
|
658
703
|
) -> None:
|
|
659
704
|
"""Delete an integrated previous bound branch, locally and on origin.
|
|
660
705
|
|
|
@@ -702,7 +747,8 @@ def retire_previous_branch(
|
|
|
702
747
|
if _ref_exists(repo, tracking_ref):
|
|
703
748
|
_run_git(repo, "update-ref", "-d", tracking_ref)
|
|
704
749
|
if _ref_exists(repo, local_ref):
|
|
705
|
-
|
|
750
|
+
# A rebased ship is not an ancestor of HEAD, so -d would refuse it.
|
|
751
|
+
_run_git(repo, "branch", "-D" if rebased else "-d", previous_branch)
|
|
706
752
|
|
|
707
753
|
|
|
708
754
|
def _retire_members(repo: Path, ship_sha: str) -> None:
|
|
@@ -742,8 +788,7 @@ def bind_next_milestone_branch(
|
|
|
742
788
|
raise PipelineGitError(f"invalid previous bound branch: {previous_branch}")
|
|
743
789
|
if milestone_number(branch) <= milestone_number(previous_branch):
|
|
744
790
|
raise PipelineGitError(f"next branch {branch} must follow {previous_branch}")
|
|
745
|
-
|
|
746
|
-
raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
|
|
791
|
+
default_name = _require_state_remote_default(repo, remote_default)
|
|
747
792
|
|
|
748
793
|
default_sha = _run_git(
|
|
749
794
|
repo,
|
|
@@ -763,7 +808,7 @@ def bind_next_milestone_branch(
|
|
|
763
808
|
raise PipelineGitError(
|
|
764
809
|
f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
|
|
765
810
|
)
|
|
766
|
-
live_default_sha = _remote_ref_sha(repo, "refs/heads/
|
|
811
|
+
live_default_sha = _remote_ref_sha(repo, f"refs/heads/{default_name}")
|
|
767
812
|
if live_default_sha != default_sha:
|
|
768
813
|
raise PipelineGitError(f"fetched {remote_default} is stale relative to origin")
|
|
769
814
|
ship_sha = _run_git(
|
|
@@ -802,9 +847,15 @@ def bind_next_milestone_branch(
|
|
|
802
847
|
landing_sha,
|
|
803
848
|
check=False,
|
|
804
849
|
)
|
|
805
|
-
|
|
850
|
+
# A forge rebase merge lands copies of the ship commits (#418); the
|
|
851
|
+
# pull-request milestone tag must then prove the landing.
|
|
852
|
+
rebased = (
|
|
853
|
+
integrated.returncode == 1
|
|
854
|
+
and _common.rebase_landing_problem(repo, ship_sha, landing_sha) is None
|
|
855
|
+
)
|
|
856
|
+
if integrated.returncode == 1 and not rebased:
|
|
806
857
|
raise PipelineGitError("milestone landing does not contain the ship commit")
|
|
807
|
-
if integrated.returncode
|
|
858
|
+
if integrated.returncode not in (0, 1):
|
|
808
859
|
detail = integrated.stderr.strip() or integrated.stdout.strip()
|
|
809
860
|
raise PipelineGitError(f"could not verify milestone landing: {detail}")
|
|
810
861
|
landing_integrated = _run_git(
|
|
@@ -820,8 +871,8 @@ def bind_next_milestone_branch(
|
|
|
820
871
|
if landing_integrated.returncode != 0:
|
|
821
872
|
detail = landing_integrated.stderr.strip() or landing_integrated.stdout.strip()
|
|
822
873
|
raise PipelineGitError(f"could not verify integrated branch: {detail}")
|
|
823
|
-
if allow_remote_absent:
|
|
824
|
-
proven_landing =
|
|
874
|
+
if allow_remote_absent or rebased:
|
|
875
|
+
proven_landing = _require_tag_integration_proof(
|
|
825
876
|
repo,
|
|
826
877
|
ship_sha,
|
|
827
878
|
base_sha,
|
|
@@ -829,7 +880,7 @@ def bind_next_milestone_branch(
|
|
|
829
880
|
)
|
|
830
881
|
if proven_landing != landing_sha:
|
|
831
882
|
raise PipelineGitError(
|
|
832
|
-
"validated landing does not match
|
|
883
|
+
"validated landing does not match the milestone tag integration proof"
|
|
833
884
|
)
|
|
834
885
|
|
|
835
886
|
symbolic_branch = _run_git(
|
|
@@ -956,6 +1007,7 @@ def bind_next_milestone_branch(
|
|
|
956
1007
|
allow_remote_absent=(
|
|
957
1008
|
allow_remote_absent or journal["stage"] in {"switched", "retired"}
|
|
958
1009
|
),
|
|
1010
|
+
rebased=rebased,
|
|
959
1011
|
)
|
|
960
1012
|
_retire_members(repo, ship_sha)
|
|
961
1013
|
journal["stage"] = "retired"
|
|
@@ -173,9 +173,14 @@ class PipelineState:
|
|
|
173
173
|
integration_default: str
|
|
174
174
|
integration: str
|
|
175
175
|
integration_source: str
|
|
176
|
+
default_branch: str = "main"
|
|
176
177
|
|
|
177
178
|
def json(self) -> dict[str, Optional[str]]:
|
|
178
|
-
|
|
179
|
+
# ponytail: default_branch stays out of this payload; guard_hook, install
|
|
180
|
+
# and the daemon probe accept only the closed STATE_FIELDS set.
|
|
181
|
+
payload = asdict(self)
|
|
182
|
+
del payload["default_branch"]
|
|
183
|
+
return payload
|
|
179
184
|
|
|
180
185
|
|
|
181
186
|
@dataclass(frozen=True)
|
|
@@ -290,7 +295,7 @@ def _bound_branch_number(value: str) -> Optional[int]:
|
|
|
290
295
|
def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
|
|
291
296
|
values = _parse_frontmatter(text, label)
|
|
292
297
|
missing = [field for field in LEGACY_STATE_FIELDS if field not in values]
|
|
293
|
-
extra = sorted(set(values) - set(STATE_FIELDS))
|
|
298
|
+
extra = sorted(set(values) - set(STATE_FIELDS) - {"default_branch"})
|
|
294
299
|
if missing:
|
|
295
300
|
raise PipelineStateError(f"{label} is missing fields: {', '.join(missing)}")
|
|
296
301
|
if extra:
|
|
@@ -322,6 +327,7 @@ def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
|
|
|
322
327
|
integration_default=integration_default,
|
|
323
328
|
integration=integration,
|
|
324
329
|
integration_source=integration_source,
|
|
330
|
+
default_branch=values.get("default_branch", "main"),
|
|
325
331
|
)
|
|
326
332
|
_validate_state_values(state, label)
|
|
327
333
|
return state
|
|
@@ -355,6 +361,10 @@ def _validate_state_values(state: PipelineState, label: str) -> None:
|
|
|
355
361
|
raise PipelineStateError(
|
|
356
362
|
f"{label} default-sourced integration must match integration_default"
|
|
357
363
|
)
|
|
364
|
+
if not _common.valid_default_branch(state.default_branch):
|
|
365
|
+
raise PipelineStateError(
|
|
366
|
+
f"{label} has invalid default_branch: {state.default_branch}"
|
|
367
|
+
)
|
|
358
368
|
if state.branch is not None and _bound_branch_number(state.branch) is None:
|
|
359
369
|
raise PipelineStateError(f"{label} has invalid branch: {state.branch}")
|
|
360
370
|
archive_match = ARCHIVE_RE.fullmatch(state.archive or "")
|
|
@@ -416,6 +426,7 @@ def validate_state(repo: Path, project_dir: str = ".project") -> dict[str, objec
|
|
|
416
426
|
"status": "valid",
|
|
417
427
|
"path": str(path),
|
|
418
428
|
"state": state.json(),
|
|
429
|
+
"default_branch": state.default_branch,
|
|
419
430
|
}
|
|
420
431
|
|
|
421
432
|
|
|
@@ -698,8 +709,10 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
698
709
|
if branch
|
|
699
710
|
else None
|
|
700
711
|
)
|
|
712
|
+
# The key keeps its name for status consumers; the ref follows STATE.
|
|
713
|
+
default_branch = load_state(resolved, project_dir)[0].default_branch
|
|
701
714
|
origin_main = (
|
|
702
|
-
_optional_rev(resolved, "refs/remotes/origin/
|
|
715
|
+
_optional_rev(resolved, f"refs/remotes/origin/{default_branch}") if has_git else None
|
|
703
716
|
)
|
|
704
717
|
pending, pending_error = _pending_answers(resolved)
|
|
705
718
|
lookahead = resolved / ".project" / "next"
|
|
@@ -722,6 +735,7 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
722
735
|
"advance": False,
|
|
723
736
|
"completion": completion,
|
|
724
737
|
"state": state,
|
|
738
|
+
"default_branch": default_branch,
|
|
725
739
|
"route": route,
|
|
726
740
|
"path": str(_track_root(resolved, project_dir) / "STATE.md"),
|
|
727
741
|
"git": {
|
|
@@ -881,7 +895,12 @@ def _route_result(
|
|
|
881
895
|
route["phase"] = phase
|
|
882
896
|
if mode is not None:
|
|
883
897
|
route["mode"] = mode
|
|
884
|
-
return {
|
|
898
|
+
return {
|
|
899
|
+
"schema": ROUTE_SCHEMA,
|
|
900
|
+
"state": state.json(),
|
|
901
|
+
"default_branch": state.default_branch,
|
|
902
|
+
"route": route,
|
|
903
|
+
}
|
|
885
904
|
|
|
886
905
|
|
|
887
906
|
def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
|
|
@@ -1005,11 +1024,13 @@ def _bind_next_journal_recovery(
|
|
|
1005
1024
|
)
|
|
1006
1025
|
if resolved_landing.returncode != 0 or resolved_landing.stdout.strip() != landing:
|
|
1007
1026
|
raise PipelineStateError("bind-next journal landing is not an existing full SHA")
|
|
1008
|
-
if not _is_ancestor(repo, ship, landing)
|
|
1027
|
+
if not _is_ancestor(repo, ship, landing) and _common.rebase_landing_problem(
|
|
1028
|
+
repo, ship, landing
|
|
1029
|
+
) is not None:
|
|
1009
1030
|
raise PipelineStateError("bind-next journal landing does not contain ship")
|
|
1010
1031
|
if not _is_ancestor(repo, landing, base):
|
|
1011
1032
|
raise PipelineStateError("bind-next journal landing is not an ancestor of base")
|
|
1012
|
-
if transaction["remote_default"] != "origin/
|
|
1033
|
+
if transaction["remote_default"] != f"origin/{state.default_branch}":
|
|
1013
1034
|
raise PipelineStateError("bind-next journal has invalid remote default")
|
|
1014
1035
|
if stage not in {"prepared", "switched", "retired"}:
|
|
1015
1036
|
raise PipelineStateError("bind-next journal has invalid stage")
|
|
@@ -1758,7 +1779,10 @@ def transition_state(
|
|
|
1758
1779
|
if unsupported:
|
|
1759
1780
|
raise PipelineStateError(f"unsupported state fields: {', '.join(sorted(unsupported))}")
|
|
1760
1781
|
if not changes:
|
|
1761
|
-
raise PipelineStateError(
|
|
1782
|
+
raise PipelineStateError(
|
|
1783
|
+
"transition requires at least one changed field; "
|
|
1784
|
+
"to record only a Log event, pass --set-status with the current status"
|
|
1785
|
+
)
|
|
1762
1786
|
required_expected = {"phase", "status", "branch", "archive"} | set(changes)
|
|
1763
1787
|
missing_expected = sorted(required_expected - set(expected))
|
|
1764
1788
|
if missing_expected:
|
package/scripts/pipeline_undo.py
CHANGED
|
@@ -22,6 +22,7 @@ from datetime import date
|
|
|
22
22
|
# Runtime helpers must not modify their immutable installation.
|
|
23
23
|
sys.dont_write_bytecode = True
|
|
24
24
|
import tempfile
|
|
25
|
+
import traceback
|
|
25
26
|
from pathlib import Path, PurePosixPath
|
|
26
27
|
from typing import Optional, Sequence
|
|
27
28
|
|
|
@@ -53,7 +54,7 @@ def _load_pipeline_modules():
|
|
|
53
54
|
try:
|
|
54
55
|
from scripts import archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
55
56
|
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
56
|
-
except
|
|
57
|
+
except ImportError as error: # a foreign `scripts` package
|
|
57
58
|
if error.name not in {
|
|
58
59
|
"scripts",
|
|
59
60
|
"scripts.archive_milestone",
|
|
@@ -63,6 +64,13 @@ def _load_pipeline_modules():
|
|
|
63
64
|
"scripts.state_checkpoint",
|
|
64
65
|
}:
|
|
65
66
|
raise
|
|
67
|
+
# A plain ImportError raised while a `scripts` module loaded is not a
|
|
68
|
+
# foreign package. A missing module falls back, as before.
|
|
69
|
+
if not isinstance(error, ModuleNotFoundError) and any(
|
|
70
|
+
frame.f_globals.get("__name__", "").startswith("scripts.")
|
|
71
|
+
for frame, _ in traceback.walk_tb(error.__traceback__)
|
|
72
|
+
):
|
|
73
|
+
raise
|
|
66
74
|
shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
|
|
67
75
|
sys.path.insert(0, str(shared))
|
|
68
76
|
import archive_milestone
|
|
@@ -772,14 +780,15 @@ def _member_undo_parent(repo: Path, member: str, landing: str) -> str:
|
|
|
772
780
|
bound = entry["branch"]
|
|
773
781
|
if _run_git(checkout, "rev-parse", f"refs/heads/{bound}").stdout.strip() != landing:
|
|
774
782
|
raise UndoError(f"member {member} {bound} moved past this landing")
|
|
775
|
-
_require_member_unpublished(checkout, member, landing, bound)
|
|
783
|
+
_require_member_unpublished(repo, checkout, member, landing, bound)
|
|
776
784
|
return _run_git(checkout, "rev-parse", f"{landing}^").stdout.strip()
|
|
777
785
|
|
|
778
786
|
|
|
779
|
-
def _require_member_unpublished(checkout: Path, member: str, landing: str, bound: str) -> None:
|
|
780
|
-
for
|
|
787
|
+
def _require_member_unpublished(repo: Path, checkout: Path, member: str, landing: str, bound: str) -> None:
|
|
788
|
+
for branch in (isolation.member_default_branch(repo, member), bound):
|
|
789
|
+
ref = f"refs/remotes/origin/{branch}"
|
|
781
790
|
if _run_git(checkout, "merge-base", "--is-ancestor", landing, ref, check=False).returncode == 0:
|
|
782
|
-
raise UndoError(f"member {member} landing is already on {
|
|
791
|
+
raise UndoError(f"member {member} landing is already on origin/{branch}")
|
|
783
792
|
|
|
784
793
|
|
|
785
794
|
def _require_no_member_landing_journal(repo: Path) -> None:
|
|
@@ -794,7 +803,7 @@ def _undo_member_landing(repo: Path, transaction: dict[str, object]) -> None:
|
|
|
794
803
|
checkout, _, entry = isolation._member_context(repo, member)
|
|
795
804
|
tip = _run_git(checkout, "rev-parse", f"refs/heads/{entry['branch']}").stdout.strip()
|
|
796
805
|
if tip == transaction["member_parent"]:
|
|
797
|
-
_require_member_unpublished(checkout, member, str(transaction["landing"]), entry["branch"])
|
|
806
|
+
_require_member_unpublished(repo, checkout, member, str(transaction["landing"]), entry["branch"])
|
|
798
807
|
return
|
|
799
808
|
if tip != transaction["landing"]:
|
|
800
809
|
raise UndoError(f"member {member} bound branch moved during undo")
|
|
@@ -861,7 +870,7 @@ def classify_undo(repo: Path) -> dict[str, object]:
|
|
|
861
870
|
[f"an in-progress helper journal exists ({names}); resume that transaction first"]
|
|
862
871
|
)
|
|
863
872
|
if git.get("ancestor_of_origin_main"):
|
|
864
|
-
return _blocked(["HEAD is an ancestor of origin
|
|
873
|
+
return _blocked(["HEAD is an ancestor of the origin default branch; undo would rewrite shipped history"])
|
|
865
874
|
|
|
866
875
|
product = _product_dirty(dirty)
|
|
867
876
|
if product:
|
|
@@ -1081,7 +1090,7 @@ def _require_unpublished_reset(repo: Path, expected_branch: str) -> None:
|
|
|
1081
1090
|
if git.get("published"):
|
|
1082
1091
|
raise UndoError("HEAD became published on its recorded remote branch")
|
|
1083
1092
|
if git.get("ancestor_of_origin_main"):
|
|
1084
|
-
raise UndoError("HEAD became published through origin
|
|
1093
|
+
raise UndoError("HEAD became published through the origin default branch")
|
|
1085
1094
|
|
|
1086
1095
|
|
|
1087
1096
|
def _remove_untracked_tree(repo: Path, relative: str) -> None:
|
|
@@ -198,10 +198,10 @@ def select_fetched_base(
|
|
|
198
198
|
use_lookahead: bool,
|
|
199
199
|
) -> dict:
|
|
200
200
|
root = repo.resolve()
|
|
201
|
-
if not
|
|
201
|
+
if not remote_default.startswith("origin/") or not _common.valid_default_branch(
|
|
202
|
+
remote_default.removeprefix("origin/")
|
|
203
|
+
):
|
|
202
204
|
raise LookaheadError("remote default must be an origin branch")
|
|
203
|
-
if ".." in remote_default or "//" in remote_default:
|
|
204
|
-
raise LookaheadError("remote default is invalid")
|
|
205
205
|
resolved = subprocess.run(
|
|
206
206
|
["git", "-C", str(root), "rev-parse", "--verify", f"{remote_default}^{{commit}}"],
|
|
207
207
|
capture_output=True,
|