@opengsd/gsd-path 1.5.0 → 1.6.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/CHANGELOG.md +138 -0
- package/DOCS.md +4 -3
- package/FULL.md +64 -4
- package/HOOKS.md +63 -2
- package/MIGRATE.md +12 -0
- package/QUICK.md +4 -2
- package/README.md +67 -15
- package/RUNTIME.md +23 -5
- package/UPDATE.md +35 -0
- package/WORKFLOW.md +58 -3
- package/package.json +1 -1
- package/scripts/_common.py +107 -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 +19 -14
- 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 +157 -31
- package/scripts/git_guard.py +24 -5
- package/scripts/guard_hook.py +1216 -47
- package/scripts/install.mjs +76 -3
- package/scripts/install.py +182 -37
- package/scripts/integration.py +98 -64
- package/scripts/isolation.py +148 -11
- package/scripts/members.py +68 -25
- package/scripts/migrate_core.py +2 -0
- package/scripts/model_policy.py +5 -1
- package/scripts/pipeline_diagnose.py +17 -2
- package/scripts/pipeline_git.py +68 -30
- package/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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 +3 -0
- package/skills/gsd-path/ROADMAP.md +16 -6
- package/skills/gsd-path/SHIP.md +26 -4
- 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/operating-rules.md +11 -3
- 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 +9 -0
- package/skills/gsd-path/scripts/_common.py +107 -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 +19 -14
- 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 +157 -31
- package/skills/gsd-path/scripts/git_guard.py +24 -5
- package/skills/gsd-path/scripts/integration.py +98 -64
- package/skills/gsd-path/scripts/isolation.py +148 -11
- package/skills/gsd-path/scripts/members.py +68 -25
- package/skills/gsd-path/scripts/model_policy.py +5 -1
- package/skills/gsd-path/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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/operating-rules.md +11 -3
- 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 +9 -0
- package/skills/gsd-path-build/scripts/_common.py +107 -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 +19 -14
- 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 +157 -31
- package/skills/gsd-path-build/scripts/integration.py +98 -64
- package/skills/gsd-path-build/scripts/isolation.py +148 -11
- package/skills/gsd-path-build/scripts/members.py +68 -25
- package/skills/gsd-path-build/scripts/model_policy.py +5 -1
- package/skills/gsd-path-build/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-build/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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/operating-rules.md +11 -3
- package/skills/gsd-path-decide/references/runtime-selection.md +9 -0
- package/skills/gsd-path-decide/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-decide/scripts/isolation.py +148 -11
- package/skills/gsd-path-decide/scripts/members.py +68 -25
- package/skills/gsd-path-decide/scripts/model_policy.py +5 -1
- package/skills/gsd-path-decide/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-decide/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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 +11 -3
- package/skills/gsd-path-define/references/runtime-selection.md +9 -0
- package/skills/gsd-path-define/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-define/scripts/isolation.py +148 -11
- package/skills/gsd-path-define/scripts/members.py +68 -25
- package/skills/gsd-path-define/scripts/model_policy.py +5 -1
- package/skills/gsd-path-define/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-define/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- package/skills/gsd-path-define/templates/state.md +1 -0
- package/skills/gsd-path-discuss/references/operating-rules.md +11 -3
- package/skills/gsd-path-discuss/references/runtime-selection.md +9 -0
- package/skills/gsd-path-discuss/scripts/_common.py +107 -1
- package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-discuss/scripts/model_policy.py +5 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +11 -3
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +9 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-docs-audit/scripts/isolation.py +148 -11
- package/skills/gsd-path-docs-audit/scripts/members.py +68 -25
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +5 -1
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- package/skills/gsd-path-forensics/references/operating-rules.md +11 -3
- package/skills/gsd-path-forensics/references/runtime-selection.md +9 -0
- package/skills/gsd-path-forensics/scripts/_common.py +107 -1
- package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-forensics/scripts/model_policy.py +5 -1
- 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/operating-rules.md +11 -3
- package/skills/gsd-path-inspect/references/runtime-selection.md +9 -0
- package/skills/gsd-path-inspect/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-inspect/scripts/isolation.py +148 -11
- package/skills/gsd-path-inspect/scripts/members.py +68 -25
- package/skills/gsd-path-inspect/scripts/model_policy.py +5 -1
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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 +11 -3
- package/skills/gsd-path-loop/references/runtime-selection.md +9 -0
- package/skills/gsd-path-loop/scripts/_common.py +107 -1
- package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-loop/scripts/model_policy.py +5 -1
- package/skills/gsd-path-migrate/SKILL.md +8 -0
- package/skills/gsd-path-migrate/references/operating-rules.md +11 -3
- package/skills/gsd-path-migrate/references/runtime-selection.md +9 -0
- package/skills/gsd-path-migrate/scripts/_common.py +107 -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 +5 -1
- package/skills/gsd-path-plan/SKILL.md +3 -0
- package/skills/gsd-path-plan/references/build-recovery.md +13 -0
- package/skills/gsd-path-plan/references/operating-rules.md +11 -3
- package/skills/gsd-path-plan/references/planner.md +13 -1
- package/skills/gsd-path-plan/references/runtime-selection.md +9 -0
- package/skills/gsd-path-plan/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-plan/scripts/isolation.py +148 -11
- package/skills/gsd-path-plan/scripts/members.py +68 -25
- package/skills/gsd-path-plan/scripts/model_policy.py +5 -1
- package/skills/gsd-path-plan/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-plan/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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/operating-rules.md +11 -3
- package/skills/gsd-path-research/references/runtime-selection.md +9 -0
- package/skills/gsd-path-research/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-research/scripts/isolation.py +148 -11
- package/skills/gsd-path-research/scripts/members.py +68 -25
- package/skills/gsd-path-research/scripts/model_policy.py +5 -1
- package/skills/gsd-path-research/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-research/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- package/skills/gsd-path-roadmap/SKILL.md +16 -6
- package/skills/gsd-path-roadmap/references/operating-rules.md +11 -3
- package/skills/gsd-path-roadmap/references/runtime-selection.md +9 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-roadmap/scripts/isolation.py +148 -11
- package/skills/gsd-path-roadmap/scripts/members.py +68 -25
- package/skills/gsd-path-roadmap/scripts/model_policy.py +5 -1
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- package/skills/gsd-path-ship/SKILL.md +26 -4
- package/skills/gsd-path-ship/references/operating-rules.md +11 -3
- 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 +9 -0
- package/skills/gsd-path-ship/scripts/_common.py +107 -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 +19 -14
- 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 +98 -64
- package/skills/gsd-path-ship/scripts/isolation.py +148 -11
- package/skills/gsd-path-ship/scripts/members.py +68 -25
- package/skills/gsd-path-ship/scripts/model_policy.py +5 -1
- package/skills/gsd-path-ship/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-ship/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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 +11 -3
- package/skills/gsd-path-undo/references/runtime-selection.md +9 -0
- package/skills/gsd-path-undo/scripts/_common.py +107 -1
- package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-undo/scripts/model_policy.py +5 -1
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +17 -8
- package/skills/path/DEFINE.md +6 -2
- package/skills/path/PLAN.md +3 -0
- package/skills/path/ROADMAP.md +16 -6
- package/skills/path/SHIP.md +26 -4
- 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/operating-rules.md +11 -3
- 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 +9 -0
- package/skills/path/scripts/_common.py +107 -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 +19 -14
- 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 +157 -31
- package/skills/path/scripts/git_guard.py +24 -5
- package/skills/path/scripts/integration.py +98 -64
- package/skills/path/scripts/isolation.py +148 -11
- package/skills/path/scripts/members.py +68 -25
- package/skills/path/scripts/model_policy.py +5 -1
- package/skills/path/scripts/pipeline_git.py +68 -30
- package/skills/path/scripts/pipeline_state.py +28 -6
- 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 +15 -4
- 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
|
@@ -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",
|
|
@@ -473,11 +485,11 @@ def _require_pull_request_integration_proof(
|
|
|
473
485
|
landing,
|
|
474
486
|
).stdout.split()
|
|
475
487
|
if len(parents) != 3 or parents[2] != ship:
|
|
476
|
-
raise PipelineGitError("
|
|
488
|
+
raise PipelineGitError(f"{mode} integration proof has invalid merge topology")
|
|
477
489
|
first_parent = _run_git(repo, "rev-list", "--first-parent", base).stdout.splitlines()
|
|
478
490
|
if landing not in first_parent:
|
|
479
491
|
raise PipelineGitError(
|
|
480
|
-
"
|
|
492
|
+
f"{mode} integration landing is not on the default branch first-parent history"
|
|
481
493
|
)
|
|
482
494
|
return landing
|
|
483
495
|
|
|
@@ -533,6 +545,34 @@ def _write_bind_next_journal(path: Path, value: dict[str, object]) -> None:
|
|
|
533
545
|
temporary.unlink()
|
|
534
546
|
|
|
535
547
|
|
|
548
|
+
def _require_state_remote_default(repo: Path, remote_default: str) -> str:
|
|
549
|
+
"""Return the default branch STATE.md records; `remote_default` must name it.
|
|
550
|
+
|
|
551
|
+
No STATE.md yet (bind-initial before initialize) or no field means main.
|
|
552
|
+
An unreadable STATE.md also means main, as before the field existed: the
|
|
553
|
+
callers that need a valid state check it themselves.
|
|
554
|
+
"""
|
|
555
|
+
if __package__:
|
|
556
|
+
from . import pipeline_state
|
|
557
|
+
else:
|
|
558
|
+
try:
|
|
559
|
+
import pipeline_state
|
|
560
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
561
|
+
from scripts import pipeline_state
|
|
562
|
+
|
|
563
|
+
default_name, unreadable = "main", ""
|
|
564
|
+
if (repo / ".project" / "STATE.md").exists():
|
|
565
|
+
try:
|
|
566
|
+
default_name = pipeline_state.load_state(repo)[0].default_branch
|
|
567
|
+
except (OSError, pipeline_state.PipelineStateError) as error:
|
|
568
|
+
unreadable = f" (STATE.md is unreadable: {error})"
|
|
569
|
+
if remote_default != f"origin/{default_name}":
|
|
570
|
+
raise PipelineGitError(
|
|
571
|
+
f"remote default must be origin/{default_name}, got {remote_default}{unreadable}"
|
|
572
|
+
)
|
|
573
|
+
return default_name
|
|
574
|
+
|
|
575
|
+
|
|
536
576
|
def _initial_binding_snapshot(repo: Path) -> Optional[tuple]:
|
|
537
577
|
"""Admit only clean work or the validated, untracked initializer state."""
|
|
538
578
|
dirty = _run_git(repo, "status", "--porcelain=v1", "-z", "--untracked-files=all").stdout
|
|
@@ -579,8 +619,7 @@ def bind_initial_milestone_branch(
|
|
|
579
619
|
resolved = _require_worktree_root(repo)
|
|
580
620
|
if not is_bound_branch(branch):
|
|
581
621
|
raise PipelineGitError(f"invalid bound branch: {branch}")
|
|
582
|
-
|
|
583
|
-
raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
|
|
622
|
+
default_name = _require_state_remote_default(resolved, remote_default)
|
|
584
623
|
|
|
585
624
|
default_sha = _run_git(
|
|
586
625
|
resolved,
|
|
@@ -600,7 +639,7 @@ def bind_initial_milestone_branch(
|
|
|
600
639
|
raise PipelineGitError(
|
|
601
640
|
f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
|
|
602
641
|
)
|
|
603
|
-
remote_sha = _remote_ref_sha(resolved, "refs/heads/
|
|
642
|
+
remote_sha = _remote_ref_sha(resolved, f"refs/heads/{default_name}")
|
|
604
643
|
if remote_sha != default_sha:
|
|
605
644
|
raise PipelineGitError(
|
|
606
645
|
f"fetched {remote_default} is stale relative to origin"
|
|
@@ -742,8 +781,7 @@ def bind_next_milestone_branch(
|
|
|
742
781
|
raise PipelineGitError(f"invalid previous bound branch: {previous_branch}")
|
|
743
782
|
if milestone_number(branch) <= milestone_number(previous_branch):
|
|
744
783
|
raise PipelineGitError(f"next branch {branch} must follow {previous_branch}")
|
|
745
|
-
|
|
746
|
-
raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
|
|
784
|
+
default_name = _require_state_remote_default(repo, remote_default)
|
|
747
785
|
|
|
748
786
|
default_sha = _run_git(
|
|
749
787
|
repo,
|
|
@@ -763,7 +801,7 @@ def bind_next_milestone_branch(
|
|
|
763
801
|
raise PipelineGitError(
|
|
764
802
|
f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
|
|
765
803
|
)
|
|
766
|
-
live_default_sha = _remote_ref_sha(repo, "refs/heads/
|
|
804
|
+
live_default_sha = _remote_ref_sha(repo, f"refs/heads/{default_name}")
|
|
767
805
|
if live_default_sha != default_sha:
|
|
768
806
|
raise PipelineGitError(f"fetched {remote_default} is stale relative to origin")
|
|
769
807
|
ship_sha = _run_git(
|
|
@@ -821,7 +859,7 @@ def bind_next_milestone_branch(
|
|
|
821
859
|
detail = landing_integrated.stderr.strip() or landing_integrated.stdout.strip()
|
|
822
860
|
raise PipelineGitError(f"could not verify integrated branch: {detail}")
|
|
823
861
|
if allow_remote_absent:
|
|
824
|
-
proven_landing =
|
|
862
|
+
proven_landing = _require_tag_integration_proof(
|
|
825
863
|
repo,
|
|
826
864
|
ship_sha,
|
|
827
865
|
base_sha,
|
|
@@ -829,7 +867,7 @@ def bind_next_milestone_branch(
|
|
|
829
867
|
)
|
|
830
868
|
if proven_landing != landing_sha:
|
|
831
869
|
raise PipelineGitError(
|
|
832
|
-
"validated landing does not match
|
|
870
|
+
"validated landing does not match the milestone tag integration proof"
|
|
833
871
|
)
|
|
834
872
|
|
|
835
873
|
symbolic_branch = _run_git(
|
|
@@ -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:
|
|
@@ -1009,7 +1028,7 @@ def _bind_next_journal_recovery(
|
|
|
1009
1028
|
raise PipelineStateError("bind-next journal landing does not contain ship")
|
|
1010
1029
|
if not _is_ancestor(repo, landing, base):
|
|
1011
1030
|
raise PipelineStateError("bind-next journal landing is not an ancestor of base")
|
|
1012
|
-
if transaction["remote_default"] != "origin/
|
|
1031
|
+
if transaction["remote_default"] != f"origin/{state.default_branch}":
|
|
1013
1032
|
raise PipelineStateError("bind-next journal has invalid remote default")
|
|
1014
1033
|
if stage not in {"prepared", "switched", "retired"}:
|
|
1015
1034
|
raise PipelineStateError("bind-next journal has invalid stage")
|
|
@@ -1758,7 +1777,10 @@ def transition_state(
|
|
|
1758
1777
|
if unsupported:
|
|
1759
1778
|
raise PipelineStateError(f"unsupported state fields: {', '.join(sorted(unsupported))}")
|
|
1760
1779
|
if not changes:
|
|
1761
|
-
raise PipelineStateError(
|
|
1780
|
+
raise PipelineStateError(
|
|
1781
|
+
"transition requires at least one changed field; "
|
|
1782
|
+
"to record only a Log event, pass --set-status with the current status"
|
|
1783
|
+
)
|
|
1762
1784
|
required_expected = {"phase", "status", "branch", "archive"} | set(changes)
|
|
1763
1785
|
missing_expected = sorted(required_expected - set(expected))
|
|
1764
1786
|
if missing_expected:
|
|
@@ -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,
|
|
@@ -252,7 +252,12 @@ def activate_milestone(text: str, milestone: str) -> str:
|
|
|
252
252
|
return "".join(lines)
|
|
253
253
|
|
|
254
254
|
|
|
255
|
-
def
|
|
255
|
+
def _entry_identity(block: dict) -> tuple:
|
|
256
|
+
"""Heading, Status, Depends on, Archive and Integrated of one entry."""
|
|
257
|
+
return block["id"], block["title"], {name: value for name, (_, value) in block["fields"].items()}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def reslice(before: str, candidate: str, phase: str, selected: str, amend_active: bool = False) -> str:
|
|
256
261
|
old_blocks = roadmap_blocks(before)
|
|
257
262
|
before_lines = before.splitlines(keepends=True)
|
|
258
263
|
candidate_lines = candidate.splitlines(keepends=True)
|
|
@@ -264,6 +269,9 @@ def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
|
|
|
264
269
|
new = milestone_block(candidate, slug)
|
|
265
270
|
if status == "shipped":
|
|
266
271
|
changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
|
|
272
|
+
elif status == "active" and amend_active:
|
|
273
|
+
# Intent is not approved: the scope may change, the identity may not.
|
|
274
|
+
changed = _entry_identity(block) != _entry_identity(new)
|
|
267
275
|
else:
|
|
268
276
|
changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
|
|
269
277
|
if changed:
|
|
@@ -164,9 +164,16 @@ def _approval_details(
|
|
|
164
164
|
raise PipelineStateError(f"unsupported approval kind: {kind}")
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
def _reslice_roadmap(
|
|
167
|
+
def _reslice_roadmap(
|
|
168
|
+
repo: Path, before: str, candidate: str, state: PipelineState, state_text: str, selected: str,
|
|
169
|
+
) -> str:
|
|
170
|
+
# Define may amend its active entry until intent approval. A return to
|
|
171
|
+
# define after that approval always leaves an active recovery record.
|
|
172
|
+
amend_active = state.phase == "define" and not (
|
|
173
|
+
pipeline_state._build_recovery().context(repo, state_text) or {}
|
|
174
|
+
).get("active")
|
|
168
175
|
try:
|
|
169
|
-
return roadmap.reslice(before, candidate, state.phase, selected)
|
|
176
|
+
return roadmap.reslice(before, candidate, state.phase, selected, amend_active)
|
|
170
177
|
except roadmap.RoadmapError as error:
|
|
171
178
|
raise PipelineStateError(str(error)) from error
|
|
172
179
|
|
|
@@ -223,6 +230,10 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
|
|
|
223
230
|
expected_keys.add("baseline_before")
|
|
224
231
|
if not isinstance(journal.get("baseline_before"), str):
|
|
225
232
|
raise PipelineStateError("checkpoint journal has an invalid re-slice baseline")
|
|
233
|
+
if "recovery_reviews" in journal:
|
|
234
|
+
expected_keys.add("recovery_reviews")
|
|
235
|
+
if journal.get("kind") != "plan" or not isinstance(journal["recovery_reviews"], dict):
|
|
236
|
+
raise PipelineStateError("checkpoint journal has an invalid recovery review report")
|
|
226
237
|
if set(journal) != expected_keys:
|
|
227
238
|
raise PipelineStateError("checkpoint journal has invalid fields")
|
|
228
239
|
if journal.get("schema") != CHECKPOINT_SCHEMA or journal.get("repo") != str(repo):
|
|
@@ -307,7 +318,8 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
|
|
|
307
318
|
if journal["roadmap_path"] != ".project/ROADMAP.md":
|
|
308
319
|
raise PipelineStateError("checkpoint journal has an invalid ROADMAP path")
|
|
309
320
|
expected_roadmap = _reslice_roadmap(
|
|
310
|
-
str(journal["baseline_before"]), str(journal["roadmap_before"]), before,
|
|
321
|
+
repo, str(journal["baseline_before"]), str(journal["roadmap_before"]), before,
|
|
322
|
+
str(journal["state_before"]), str(selected),
|
|
311
323
|
) if journal["kind"] == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
312
324
|
str(journal["roadmap_before"]),
|
|
313
325
|
str(selected),
|
|
@@ -423,7 +435,7 @@ def _resume_checkpoint_locked(
|
|
|
423
435
|
raise PipelineStateError("approval checkpoint returned the wrong commit")
|
|
424
436
|
_unlink_checkpoint_journal(journal_path)
|
|
425
437
|
state = pipeline_state._state_from_text(str(transaction["state_after"]))
|
|
426
|
-
|
|
438
|
+
approved: dict[str, object] = {
|
|
427
439
|
"schema": CHECKPOINT_SCHEMA,
|
|
428
440
|
"status": "approved",
|
|
429
441
|
"kind": transaction["kind"],
|
|
@@ -432,6 +444,9 @@ def _resume_checkpoint_locked(
|
|
|
432
444
|
"paths": committed.get("paths"),
|
|
433
445
|
"state": state.json(),
|
|
434
446
|
}
|
|
447
|
+
if "recovery_reviews" in transaction:
|
|
448
|
+
approved["recovery_reviews"] = transaction["recovery_reviews"]
|
|
449
|
+
return approved
|
|
435
450
|
|
|
436
451
|
|
|
437
452
|
def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
|
|
@@ -563,7 +578,10 @@ def checkpoint_approval(
|
|
|
563
578
|
f"{expected_head}^{{commit}}",
|
|
564
579
|
).stdout.strip()
|
|
565
580
|
if resolved_head != expected_head:
|
|
566
|
-
raise PipelineStateError(
|
|
581
|
+
raise PipelineStateError(
|
|
582
|
+
"expected-head must be an exact full commit SHA; "
|
|
583
|
+
"resolve a ref or a short SHA with git rev-parse first"
|
|
584
|
+
)
|
|
567
585
|
if pipeline_state._run_git(resolved, "rev-parse", "HEAD").stdout.strip() != expected_head:
|
|
568
586
|
raise PipelineStateError("approval must start at expected-head")
|
|
569
587
|
active_state, _, _ = pipeline_state.load_state(resolved)
|
|
@@ -578,8 +596,9 @@ def checkpoint_approval(
|
|
|
578
596
|
state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
|
|
579
597
|
changes, event, subject, body = _approval_details(kind, state, selected_milestone)
|
|
580
598
|
_validate_plan_briefs(resolved, kind, project_dir)
|
|
599
|
+
recovery_reviews = None
|
|
581
600
|
if kind == "plan" and project_dir == ".project":
|
|
582
|
-
pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
601
|
+
recovery_reviews = pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
583
602
|
expected = {
|
|
584
603
|
"phase": state.phase,
|
|
585
604
|
"status": state.status,
|
|
@@ -611,7 +630,7 @@ def checkpoint_approval(
|
|
|
611
630
|
if kind == "roadmap-reslice":
|
|
612
631
|
baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
|
|
613
632
|
roadmap_after = _reslice_roadmap(
|
|
614
|
-
baseline_before, roadmap_before, state, selected_milestone,
|
|
633
|
+
resolved, baseline_before, roadmap_before, state, state_before, selected_milestone,
|
|
615
634
|
) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
616
635
|
roadmap_before,
|
|
617
636
|
selected_milestone,
|
|
@@ -641,6 +660,8 @@ def checkpoint_approval(
|
|
|
641
660
|
}
|
|
642
661
|
if baseline_before is not None:
|
|
643
662
|
journal["baseline_before"] = baseline_before
|
|
663
|
+
if recovery_reviews is not None:
|
|
664
|
+
journal["recovery_reviews"] = recovery_reviews
|
|
644
665
|
pipeline_state._write_json(journal_path, journal)
|
|
645
666
|
return _resume_checkpoint_locked(resolved, project, journal_path, journal)
|
|
646
667
|
|
|
@@ -722,8 +743,8 @@ def defer_approval(
|
|
|
722
743
|
if kind == "roadmap-reslice":
|
|
723
744
|
baseline = project / "ROADMAP.before-reslice.md"
|
|
724
745
|
roadmap_after = _reslice_roadmap(
|
|
725
|
-
pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
726
|
-
roadmap_before, state, selected_milestone,
|
|
746
|
+
resolved, pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
747
|
+
roadmap_before, state, state_before, selected_milestone,
|
|
727
748
|
)
|
|
728
749
|
else:
|
|
729
750
|
roadmap_after = _activate_roadmap_milestone(roadmap_before, selected_milestone)
|
|
@@ -377,17 +377,22 @@ def _prepare_promotion(
|
|
|
377
377
|
head = pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
|
|
378
378
|
if head != base:
|
|
379
379
|
raise PipelineStateError(f"promotion must start at base SHA: {head} != {base}")
|
|
380
|
-
|
|
380
|
+
active_state, active_text, _ = pipeline_state.load_state(repo)
|
|
381
|
+
remote_default = f"origin/{active_state.default_branch}"
|
|
382
|
+
remote = pipeline_state._run_git(
|
|
383
|
+
repo, "rev-parse", "--verify", f"{remote_default}^{{commit}}"
|
|
384
|
+
).stdout.strip()
|
|
381
385
|
if remote != base:
|
|
382
386
|
raise PipelineStateError(
|
|
383
|
-
f"base SHA is not current
|
|
387
|
+
f"base SHA is not current {remote_default}: {base} != {remote}"
|
|
384
388
|
)
|
|
385
389
|
if not pipeline_state._is_ancestor(repo, landing, base):
|
|
386
|
-
raise PipelineStateError(
|
|
390
|
+
raise PipelineStateError(
|
|
391
|
+
f"landing is not an ancestor of the current {active_state.default_branch} base"
|
|
392
|
+
)
|
|
387
393
|
if pipeline_state._run_git(repo, "status", "--porcelain", "--untracked-files=all").stdout:
|
|
388
394
|
raise PipelineStateError("promotion must start from a clean worktree")
|
|
389
395
|
|
|
390
|
-
active_state, active_text, _ = pipeline_state.load_state(repo)
|
|
391
396
|
if active_state.phase != "shipped" or active_state.status != "done":
|
|
392
397
|
raise PipelineStateError("promotion requires active STATE shipped/done")
|
|
393
398
|
if active_state.milestone is None:
|
|
@@ -456,6 +461,12 @@ def _prepare_promotion(
|
|
|
456
461
|
raise PipelineStateError(
|
|
457
462
|
"lookahead integration_default does not match the active project"
|
|
458
463
|
)
|
|
464
|
+
if next_state.default_branch != active_state.default_branch:
|
|
465
|
+
raise PipelineStateError(
|
|
466
|
+
"lookahead default_branch does not match the active project: copy "
|
|
467
|
+
f"`default_branch: {active_state.default_branch}` from .project/STATE.md "
|
|
468
|
+
"into .project/next/STATE.md"
|
|
469
|
+
)
|
|
459
470
|
if next_state.milestone != milestone:
|
|
460
471
|
raise PipelineStateError(
|
|
461
472
|
f"requested milestone {milestone} != next STATE milestone {next_state.milestone}"
|
|
@@ -27,7 +27,8 @@ return to `$gsd-path` for ownership checking. Legal entry is
|
|
|
27
27
|
yet, `roadmap/active|blocked`, or a
|
|
28
28
|
milestone-boundary re-slice: `inspect/active` or `define/active` with no
|
|
29
29
|
approved INTENT.md for the next milestone and a user request to re-scope the
|
|
30
|
-
remaining `pending` entries,
|
|
30
|
+
remaining `pending` entries or, at `define/active` only, to change the scope
|
|
31
|
+
of the `active` entry,
|
|
31
32
|
or `roadmap/active` with STATE.milestone null and an `abandoned` entry in
|
|
32
33
|
ROADMAP.md (the post-abandon re-slice the build orchestrator hands off).
|
|
33
34
|
Enter from `decide/done` only with `pipeline_state.py transition`, exact
|
|
@@ -35,6 +36,8 @@ expected phase/status/milestone/branch/archive fields, event `roadmap started`,
|
|
|
35
36
|
and `--set-phase roadmap --set-status active`; never edit STATE directly. In a
|
|
36
37
|
milestone-boundary re-slice, preserve the entering `inspect/active` or
|
|
37
38
|
`define/active` state throughout the re-slice.
|
|
39
|
+
A `define/active` state that the router reports in `corrections` mode already
|
|
40
|
+
has an approved INTENT.md; it blocks.
|
|
38
41
|
Any later phase blocks; mid-milestone
|
|
39
42
|
re-scope is never legal — finish or ship the active milestone first. A
|
|
40
43
|
`decide/done` state with ROADMAP.md already present is milestone-scope decide;
|
|
@@ -71,8 +74,11 @@ precondition fails.
|
|
|
71
74
|
give `.project/ROADMAP.before-reslice.md` with the instruction to preserve
|
|
72
75
|
`shipped` entries byte-for-byte except Status/Archive/Integrated fields and
|
|
73
76
|
to never modify `abandoned` entries at all. For a milestone-boundary
|
|
74
|
-
re-slice
|
|
75
|
-
`pending` entries are in scope.
|
|
77
|
+
re-slice entered at `inspect/active`, also preserve the existing `active`
|
|
78
|
+
entry byte-for-byte; only `pending` entries are in scope. Entered at
|
|
79
|
+
`define/active`, the `active` entry may also change as the user ruled, but
|
|
80
|
+
its heading (id and slug) and its Status, Depends on, Archive, and
|
|
81
|
+
Integrated fields stay unchanged.
|
|
76
82
|
The output is exactly `.project/ROADMAP.md` — no other location is
|
|
77
83
|
canonical.
|
|
78
84
|
3. Gate ROADMAP.md:
|
|
@@ -96,8 +102,11 @@ precondition fails.
|
|
|
96
102
|
- On a re-slice, `shipped` entries differ from
|
|
97
103
|
`.project/ROADMAP.before-reslice.md` only in Status/Archive/Integrated;
|
|
98
104
|
`abandoned` entries are byte-for-byte identical.
|
|
99
|
-
- On a milestone-boundary re-slice
|
|
100
|
-
identical to
|
|
105
|
+
- On a milestone-boundary re-slice entered at `inspect/active`, the prior
|
|
106
|
+
`active` entry is byte-for-byte identical to
|
|
107
|
+
`.project/ROADMAP.before-reslice.md`. Entered at `define/active`, it
|
|
108
|
+
stays the only `active` entry and keeps its heading and its Status,
|
|
109
|
+
Depends on, Archive, and Integrated fields.
|
|
101
110
|
4. Redispatch one complete corrected brief under logical task name `roadmap`,
|
|
102
111
|
following the runtime dispatch contract and including all gate failures.
|
|
103
112
|
Allow one revision round. If it still fails, keep the entering
|
|
@@ -160,7 +169,8 @@ precondition fails.
|
|
|
160
169
|
|
|
161
170
|
The helper owns roadmap selection, expected state, baseline cleanup and
|
|
162
171
|
the approval checkpoint. It journals before mutation, retains the baseline
|
|
163
|
-
bytes for recovery, and refuses changes to settled
|
|
172
|
+
bytes for recovery, and refuses changes to settled entries and every
|
|
173
|
+
`active` entry change that step 2 does not permit.
|
|
164
174
|
A milestone-boundary re-slice retains Inspect or Define; a post-abandon
|
|
165
175
|
re-slice enters Inspect. Require its typed result to report `status:
|
|
166
176
|
approved`, the requested kind, milestone and returned commit. After an
|
|
@@ -38,8 +38,8 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
38
38
|
the lookahead planning track for the next milestone; it follows the same
|
|
39
39
|
marker rule, owns no task frontmatter, and never binds a branch.
|
|
40
40
|
- STATE.integration_default is the project closeout choice and
|
|
41
|
-
STATE.integration is the current milestone choice. Both are `direct
|
|
42
|
-
`pull-request`; STATE.integration_source is `default` or `milestone` and
|
|
41
|
+
STATE.integration is the current milestone choice. Both are `direct`,
|
|
42
|
+
`pull-request`, or `external-landing`; STATE.integration_source is `default` or `milestone` and
|
|
43
43
|
preserves explicit override provenance. Older v2 state defaults to `direct`,
|
|
44
44
|
and modes may be changed only through `pipeline_state.py
|
|
45
45
|
configure-integration` before build. A next milestone resets its current
|
|
@@ -82,7 +82,9 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
82
82
|
`isolation.py attest` may stand in for it: one `.project/`-only commit that
|
|
83
83
|
names the base, the attested HEAD, the declared files that changed, the
|
|
84
84
|
task Verify recorded as passing at that HEAD, and the ruling verbatim.
|
|
85
|
-
`recover` reports it as `attested`, never as a proven landing.
|
|
85
|
+
`recover` reports it as `attested`, never as a proven landing. Attestation
|
|
86
|
+
matches the declared files literally: work that is only below a declared
|
|
87
|
+
directory can land but cannot be attested.
|
|
86
88
|
- All pipeline work for a milestone lives on `gsd-path/M00N` (M001, M002,
|
|
87
89
|
…) bound in STATE.branch. The next milestone binds a new unused
|
|
88
90
|
`gsd-path/M00N` at the remote default after the previous ship integrates.
|
|
@@ -90,6 +92,12 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
90
92
|
branch — either Path's direct merge or a Path-owned PR merged by the user.
|
|
91
93
|
The bound branch never receives merges or back-merges and is never the
|
|
92
94
|
GitHub default.
|
|
95
|
+
- The default branch is the one that STATE `default_branch` records at
|
|
96
|
+
initialization; a STATE file without the field means `main`. `main` and
|
|
97
|
+
`origin/main` in these rules mean that branch. Never change the record and
|
|
98
|
+
never take the name from a later `origin/HEAD` lookup. For a member
|
|
99
|
+
repository, the branch is the one that its `.project/MEMBERS.md` section
|
|
100
|
+
records as `Default branch`; a section without the field means `main`.
|
|
93
101
|
- After initialization writes STATE.md, and before entering the first pipeline
|
|
94
102
|
phase in an existing Git repository, the router calls
|
|
95
103
|
`scripts/pipeline_git.py bind-initial` with the selected M00N and exact
|
|
@@ -20,6 +20,15 @@ references to bundled copies of those helpers. Other skill helpers, templates,
|
|
|
20
20
|
and role briefs remain in the installed skill bundle. Pass `-B` to Python helper
|
|
21
21
|
invocations so execution does not create bytecode in the runtime store.
|
|
22
22
|
|
|
23
|
+
The selected runtime is authoritative for each helper in its `manifest.json`;
|
|
24
|
+
this includes `review_findings.py` and `task_context.py`. A selected runtime
|
|
25
|
+
that was published before a helper became a runtime file does not list that
|
|
26
|
+
helper: run the bundled copy until the project runtime is upgraded.
|
|
27
|
+
`dispatch_driver.py` and `workflow_run.py` always run from the installed skill
|
|
28
|
+
bundle, because they read role briefs and templates from it; a skill update
|
|
29
|
+
replaces them, a runtime upgrade does not. The driver reloads the helper modules
|
|
30
|
+
that it imports from the selected runtime.
|
|
31
|
+
|
|
23
32
|
With no declaration, retain the legacy helper lookup. An existing
|
|
24
33
|
`.gsd-path/runtime/` requires the explicit `--runtime-migrate --project PATH`
|
|
25
34
|
operation before adopting external storage; review the resulting Git diff.
|