@opengsd/gsd-path 1.4.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 +460 -341
- package/DOCS.md +16 -5
- package/FULL.md +67 -6
- package/HOOKS.md +158 -10
- package/MIGRATE.md +12 -0
- package/QUICK.md +4 -2
- package/README.md +136 -116
- package/RUNTIME.md +24 -6
- package/UPDATE.md +39 -3
- package/WORKFLOW.md +80 -4
- package/package.json +5 -2
- package/platforms/shared-agents/dispatch.md +13 -0
- package/scripts/_common.py +256 -2
- package/scripts/archive_milestone.py +14 -13
- package/scripts/build_recovery.py +155 -58
- package/scripts/build_state.py +274 -8
- package/scripts/check_docs_audit.py +77 -4
- package/scripts/check_handoffs.py +635 -57
- package/scripts/check_task_briefs.py +293 -32
- package/scripts/detect_project.py +260 -20
- package/scripts/discussion_records.py +9 -1
- package/scripts/discussion_validate.py +93 -35
- package/scripts/dispatch_driver.py +549 -53
- package/scripts/git_guard.py +27 -8
- package/scripts/guard_hook.py +2630 -210
- package/scripts/install.mjs +99 -11
- package/scripts/install.py +231 -46
- package/scripts/integration.py +244 -65
- package/scripts/isolation.py +539 -70
- package/scripts/loop_run.py +16 -4
- package/scripts/members.py +77 -32
- package/scripts/migrate_core.py +2 -0
- package/scripts/model_policy.py +5 -1
- package/scripts/path_config.py +3 -3
- package/scripts/pipeline_diagnose.py +17 -2
- package/scripts/pipeline_git.py +131 -42
- package/scripts/pipeline_state.py +139 -39
- package/scripts/pipeline_undo.py +118 -16
- 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 +21 -2
- package/scripts/skill-resources.json +9 -2
- package/scripts/state_checkpoint.py +39 -36
- package/scripts/state_promote.py +15 -4
- package/scripts/status_runtime.py +22 -4
- package/scripts/task_context.py +14 -3
- package/scripts/wizard.mjs +1 -0
- package/scripts/workflow_run.py +14 -4
- package/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path/DEFINE.md +34 -5
- package/skills/gsd-path/PLAN.md +14 -3
- package/skills/gsd-path/RESEARCH.md +11 -0
- package/skills/gsd-path/ROADMAP.md +16 -6
- package/skills/gsd-path/SHIP.md +32 -5
- package/skills/gsd-path/SKILL.md +52 -24
- package/skills/gsd-path/references/build-native.md +112 -10
- package/skills/gsd-path/references/build-recovery.md +13 -0
- package/skills/gsd-path/references/coder.md +7 -3
- package/skills/gsd-path/references/dispatch.md +13 -0
- package/skills/gsd-path/references/docs-auditor.md +5 -0
- package/skills/gsd-path/references/operating-rules.md +11 -3
- package/skills/gsd-path/references/planner.md +26 -4
- package/skills/gsd-path/references/reviewer-final.md +20 -2
- package/skills/gsd-path/references/reviewer-wave.md +56 -12
- package/skills/gsd-path/references/reviewer.md +3 -1
- package/skills/gsd-path/references/runtime-selection.md +9 -0
- package/skills/gsd-path/references/spec-probes.md +150 -0
- package/skills/gsd-path/scripts/_common.py +256 -2
- package/skills/gsd-path/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path/scripts/build_recovery.py +155 -58
- package/skills/gsd-path/scripts/build_state.py +274 -8
- package/skills/gsd-path/scripts/check_docs_audit.py +77 -4
- package/skills/gsd-path/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path/scripts/detect_project.py +260 -20
- package/skills/gsd-path/scripts/discussion_records.py +9 -1
- package/skills/gsd-path/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path/scripts/dispatch_driver.py +549 -53
- package/skills/gsd-path/scripts/git_guard.py +27 -8
- package/skills/gsd-path/scripts/integration.py +244 -65
- package/skills/gsd-path/scripts/isolation.py +539 -70
- package/skills/gsd-path/scripts/loop_run.py +16 -4
- package/skills/gsd-path/scripts/members.py +77 -32
- package/skills/gsd-path/scripts/model_policy.py +5 -1
- package/skills/gsd-path/scripts/path_config.py +3 -3
- package/skills/gsd-path/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path/scripts/state_promote.py +15 -4
- package/skills/gsd-path/scripts/status_runtime.py +22 -4
- package/skills/gsd-path/scripts/task_context.py +14 -3
- package/skills/gsd-path/scripts/workflow_run.py +14 -4
- package/skills/gsd-path/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path/templates/docs-audit.md +5 -1
- package/skills/gsd-path/templates/final-review.md +11 -2
- package/skills/gsd-path/templates/intent.md +30 -0
- package/skills/gsd-path/templates/patch-findings.md +3 -3
- package/skills/gsd-path/templates/plan.md +17 -2
- package/skills/gsd-path/templates/research-handoff.md +14 -3
- package/skills/gsd-path/templates/state.md +2 -1
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +28 -9
- package/skills/gsd-path-build/references/build-native.md +112 -10
- 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/dispatch.md +13 -0
- package/skills/gsd-path-build/references/operating-rules.md +11 -3
- package/skills/gsd-path-build/references/reviewer-final.md +20 -2
- package/skills/gsd-path-build/references/reviewer-wave.md +56 -12
- package/skills/gsd-path-build/references/reviewer.md +3 -1
- package/skills/gsd-path-build/references/runtime-selection.md +9 -0
- package/skills/gsd-path-build/references/spec-probes.md +150 -0
- package/skills/gsd-path-build/scripts/_common.py +256 -2
- package/skills/gsd-path-build/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-build/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-build/scripts/build_state.py +274 -8
- package/skills/gsd-path-build/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-build/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-build/scripts/detect_project.py +260 -20
- package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-build/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-build/scripts/dispatch_driver.py +549 -53
- package/skills/gsd-path-build/scripts/integration.py +244 -65
- package/skills/gsd-path-build/scripts/isolation.py +539 -70
- package/skills/gsd-path-build/scripts/loop_run.py +16 -4
- package/skills/gsd-path-build/scripts/members.py +77 -32
- package/skills/gsd-path-build/scripts/model_policy.py +5 -1
- package/skills/gsd-path-build/scripts/path_config.py +3 -3
- package/skills/gsd-path-build/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-build/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-build/scripts/state_promote.py +15 -4
- package/skills/gsd-path-build/scripts/task_context.py +14 -3
- package/skills/gsd-path-build/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-build/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +28 -9
- package/skills/gsd-path-decide/references/dispatch.md +13 -0
- 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 +256 -2
- package/skills/gsd-path-decide/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-decide/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-decide/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-decide/scripts/detect_project.py +260 -20
- package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-decide/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-decide/scripts/integration.py +244 -65
- package/skills/gsd-path-decide/scripts/isolation.py +539 -70
- package/skills/gsd-path-decide/scripts/loop_run.py +16 -4
- package/skills/gsd-path-decide/scripts/members.py +77 -32
- package/skills/gsd-path-decide/scripts/model_policy.py +5 -1
- package/skills/gsd-path-decide/scripts/path_config.py +3 -3
- package/skills/gsd-path-decide/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-decide/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-decide/scripts/state_promote.py +15 -4
- package/skills/gsd-path-decide/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-define/SKILL.md +34 -5
- 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/references/spec-probes.md +150 -0
- package/skills/gsd-path-define/scripts/_common.py +256 -2
- package/skills/gsd-path-define/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-define/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-define/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-define/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-define/scripts/detect_project.py +260 -20
- package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-define/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-define/scripts/integration.py +244 -65
- package/skills/gsd-path-define/scripts/isolation.py +539 -70
- package/skills/gsd-path-define/scripts/loop_run.py +16 -4
- package/skills/gsd-path-define/scripts/members.py +77 -32
- package/skills/gsd-path-define/scripts/model_policy.py +5 -1
- package/skills/gsd-path-define/scripts/path_config.py +3 -3
- package/skills/gsd-path-define/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-define/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-define/scripts/state_promote.py +15 -4
- package/skills/gsd-path-define/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-define/templates/intent.md +30 -0
- package/skills/gsd-path-define/templates/state.md +2 -1
- 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 +256 -2
- package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-discuss/scripts/loop_run.py +16 -4
- package/skills/gsd-path-discuss/scripts/model_policy.py +5 -1
- package/skills/gsd-path-discuss/scripts/path_config.py +3 -3
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +5 -0
- 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 +256 -2
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +77 -4
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +260 -20
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-docs-audit/scripts/integration.py +244 -65
- package/skills/gsd-path-docs-audit/scripts/isolation.py +539 -70
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +16 -4
- package/skills/gsd-path-docs-audit/scripts/members.py +77 -32
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +5 -1
- package/skills/gsd-path-docs-audit/scripts/path_config.py +3 -3
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +15 -4
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-docs-audit/templates/docs-audit.md +5 -1
- 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 +256 -2
- package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-forensics/scripts/loop_run.py +16 -4
- package/skills/gsd-path-forensics/scripts/model_policy.py +5 -1
- package/skills/gsd-path-forensics/scripts/path_config.py +3 -3
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +118 -16
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-inspect/references/dispatch.md +13 -0
- package/skills/gsd-path-inspect/references/docs-auditor.md +5 -0
- 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 +256 -2
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-inspect/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +77 -4
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-inspect/scripts/detect_project.py +260 -20
- package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-inspect/scripts/integration.py +244 -65
- package/skills/gsd-path-inspect/scripts/isolation.py +539 -70
- package/skills/gsd-path-inspect/scripts/loop_run.py +16 -4
- package/skills/gsd-path-inspect/scripts/members.py +77 -32
- package/skills/gsd-path-inspect/scripts/model_policy.py +5 -1
- package/skills/gsd-path-inspect/scripts/path_config.py +3 -3
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-inspect/scripts/state_promote.py +15 -4
- package/skills/gsd-path-inspect/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-inspect/templates/docs-audit.md +5 -1
- package/skills/gsd-path-inspect/templates/state.md +2 -1
- 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 +256 -2
- package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-loop/scripts/loop_run.py +16 -4
- package/skills/gsd-path-loop/scripts/model_policy.py +5 -1
- package/skills/gsd-path-loop/scripts/path_config.py +3 -3
- package/skills/gsd-path-loop/scripts/worktree_paths.py +54 -2
- 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 +256 -2
- package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-migrate/scripts/loop_run.py +16 -4
- 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-migrate/scripts/path_config.py +3 -3
- package/skills/gsd-path-plan/SKILL.md +14 -3
- package/skills/gsd-path-plan/references/build-recovery.md +13 -0
- package/skills/gsd-path-plan/references/dispatch.md +13 -0
- package/skills/gsd-path-plan/references/operating-rules.md +11 -3
- package/skills/gsd-path-plan/references/planner.md +26 -4
- package/skills/gsd-path-plan/references/runtime-selection.md +9 -0
- package/skills/gsd-path-plan/references/spec-probes.md +150 -0
- package/skills/gsd-path-plan/scripts/_common.py +256 -2
- package/skills/gsd-path-plan/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-plan/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-plan/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-plan/scripts/detect_project.py +260 -20
- package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-plan/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-plan/scripts/integration.py +244 -65
- package/skills/gsd-path-plan/scripts/isolation.py +539 -70
- package/skills/gsd-path-plan/scripts/loop_run.py +16 -4
- package/skills/gsd-path-plan/scripts/members.py +77 -32
- package/skills/gsd-path-plan/scripts/model_policy.py +5 -1
- package/skills/gsd-path-plan/scripts/path_config.py +3 -3
- package/skills/gsd-path-plan/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-plan/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-plan/scripts/state_promote.py +15 -4
- package/skills/gsd-path-plan/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-plan/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
- package/skills/gsd-path-plan/templates/plan.md +17 -2
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/SKILL.md +11 -0
- package/skills/gsd-path-research/references/dispatch.md +13 -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 +256 -2
- package/skills/gsd-path-research/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-research/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-research/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-research/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-research/scripts/detect_project.py +260 -20
- package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-research/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-research/scripts/integration.py +244 -65
- package/skills/gsd-path-research/scripts/isolation.py +539 -70
- package/skills/gsd-path-research/scripts/loop_run.py +16 -4
- package/skills/gsd-path-research/scripts/members.py +77 -32
- package/skills/gsd-path-research/scripts/model_policy.py +5 -1
- package/skills/gsd-path-research/scripts/path_config.py +3 -3
- package/skills/gsd-path-research/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-research/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-research/scripts/state_promote.py +15 -4
- package/skills/gsd-path-research/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-research/templates/research-handoff.md +14 -3
- package/skills/gsd-path-roadmap/SKILL.md +16 -6
- package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
- 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 +256 -2
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-roadmap/scripts/detect_project.py +260 -20
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-roadmap/scripts/integration.py +244 -65
- package/skills/gsd-path-roadmap/scripts/isolation.py +539 -70
- package/skills/gsd-path-roadmap/scripts/loop_run.py +16 -4
- package/skills/gsd-path-roadmap/scripts/members.py +77 -32
- package/skills/gsd-path-roadmap/scripts/model_policy.py +5 -1
- package/skills/gsd-path-roadmap/scripts/path_config.py +3 -3
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-roadmap/scripts/state_promote.py +15 -4
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-ship/SKILL.md +32 -5
- package/skills/gsd-path-ship/references/dispatch.md +13 -0
- package/skills/gsd-path-ship/references/operating-rules.md +11 -3
- package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
- package/skills/gsd-path-ship/references/reviewer-wave.md +56 -12
- package/skills/gsd-path-ship/references/reviewer.md +3 -1
- package/skills/gsd-path-ship/references/runtime-selection.md +9 -0
- package/skills/gsd-path-ship/references/spec-probes.md +150 -0
- package/skills/gsd-path-ship/scripts/_common.py +256 -2
- package/skills/gsd-path-ship/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-ship/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-ship/scripts/build_state.py +274 -8
- package/skills/gsd-path-ship/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-ship/scripts/detect_project.py +260 -20
- package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-ship/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-ship/scripts/integration.py +244 -65
- package/skills/gsd-path-ship/scripts/isolation.py +539 -70
- package/skills/gsd-path-ship/scripts/loop_run.py +16 -4
- package/skills/gsd-path-ship/scripts/members.py +77 -32
- package/skills/gsd-path-ship/scripts/model_policy.py +5 -1
- package/skills/gsd-path-ship/scripts/path_config.py +3 -3
- package/skills/gsd-path-ship/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-ship/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/gsd-path-ship/scripts/state_promote.py +15 -4
- package/skills/gsd-path-ship/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-ship/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-ship/templates/final-review.md +11 -2
- package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
- package/skills/gsd-path-ship/templates/wave-review.md +28 -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 +256 -2
- package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-undo/scripts/loop_run.py +16 -4
- package/skills/gsd-path-undo/scripts/model_policy.py +5 -1
- package/skills/gsd-path-undo/scripts/path_config.py +3 -3
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +118 -16
- package/skills/gsd-path-undo/scripts/worktree_paths.py +54 -2
- package/skills/path/DEFINE.md +34 -5
- package/skills/path/PLAN.md +14 -3
- package/skills/path/RESEARCH.md +11 -0
- package/skills/path/ROADMAP.md +16 -6
- package/skills/path/SHIP.md +32 -5
- package/skills/path/SKILL.md +52 -24
- package/skills/path/references/build-native.md +112 -10
- package/skills/path/references/build-recovery.md +13 -0
- package/skills/path/references/coder.md +7 -3
- package/skills/path/references/dispatch.md +13 -0
- package/skills/path/references/docs-auditor.md +5 -0
- package/skills/path/references/operating-rules.md +11 -3
- package/skills/path/references/planner.md +26 -4
- package/skills/path/references/reviewer-final.md +20 -2
- package/skills/path/references/reviewer-wave.md +56 -12
- package/skills/path/references/reviewer.md +3 -1
- package/skills/path/references/runtime-selection.md +9 -0
- package/skills/path/references/spec-probes.md +150 -0
- package/skills/path/scripts/_common.py +256 -2
- package/skills/path/scripts/archive_milestone.py +14 -13
- package/skills/path/scripts/build_recovery.py +155 -58
- package/skills/path/scripts/build_state.py +274 -8
- package/skills/path/scripts/check_docs_audit.py +77 -4
- package/skills/path/scripts/check_handoffs.py +635 -57
- package/skills/path/scripts/check_task_briefs.py +293 -32
- package/skills/path/scripts/detect_project.py +260 -20
- package/skills/path/scripts/discussion_records.py +9 -1
- package/skills/path/scripts/discussion_validate.py +93 -35
- package/skills/path/scripts/dispatch_driver.py +549 -53
- package/skills/path/scripts/git_guard.py +27 -8
- package/skills/path/scripts/integration.py +244 -65
- package/skills/path/scripts/isolation.py +539 -70
- package/skills/path/scripts/loop_run.py +16 -4
- package/skills/path/scripts/members.py +77 -32
- package/skills/path/scripts/model_policy.py +5 -1
- package/skills/path/scripts/path_config.py +3 -3
- package/skills/path/scripts/pipeline_git.py +131 -42
- package/skills/path/scripts/pipeline_state.py +139 -39
- 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 +39 -36
- package/skills/path/scripts/state_promote.py +15 -4
- package/skills/path/scripts/status_runtime.py +22 -4
- package/skills/path/scripts/task_context.py +14 -3
- package/skills/path/scripts/workflow_run.py +14 -4
- package/skills/path/scripts/worktree_paths.py +54 -2
- package/skills/path/templates/docs-audit.md +5 -1
- package/skills/path/templates/final-review.md +11 -2
- package/skills/path/templates/intent.md +30 -0
- package/skills/path/templates/patch-findings.md +3 -3
- package/skills/path/templates/plan.md +17 -2
- package/skills/path/templates/research-handoff.md +14 -3
- package/skills/path/templates/state.md +2 -1
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +28 -9
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
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 (
|
|
@@ -146,6 +148,29 @@ def ship_commit_body(archive_path: str, reviewed_head: str, members: Sequence[di
|
|
|
146
148
|
)
|
|
147
149
|
|
|
148
150
|
|
|
151
|
+
_TRAILER_LINE = re.compile(r"^[A-Za-z-]+:\s")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def ship_body_matches(body: str, expected_body: str) -> bool:
|
|
155
|
+
"""True when body equals the contract or adds only optional git trailers after it."""
|
|
156
|
+
normalized = body.rstrip("\n")
|
|
157
|
+
expected = expected_body.rstrip("\n")
|
|
158
|
+
if normalized == expected:
|
|
159
|
+
return True
|
|
160
|
+
prefix = expected + "\n"
|
|
161
|
+
if not normalized.startswith(prefix):
|
|
162
|
+
return False
|
|
163
|
+
remainder = normalized[len(prefix):]
|
|
164
|
+
if not remainder:
|
|
165
|
+
return True
|
|
166
|
+
for line in remainder.splitlines():
|
|
167
|
+
if not line.strip():
|
|
168
|
+
return False
|
|
169
|
+
if not _TRAILER_LINE.match(line):
|
|
170
|
+
return False
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
|
|
149
174
|
def integrate_commit_body(
|
|
150
175
|
archive_path: str,
|
|
151
176
|
ship_commit: str,
|
|
@@ -270,22 +295,50 @@ def _symbolic_branch(repo: Path) -> str:
|
|
|
270
295
|
return result.stdout.strip()
|
|
271
296
|
|
|
272
297
|
|
|
273
|
-
def
|
|
274
|
-
"""Return
|
|
275
|
-
|
|
298
|
+
def worktree_list_porcelain(repo: Path) -> tuple[str, bool]:
|
|
299
|
+
"""Return worktree list output and whether it is NUL-separated."""
|
|
300
|
+
result = _run_git(repo, "worktree", "list", "--porcelain", "-z", check=False)
|
|
301
|
+
if result.returncode == 0:
|
|
302
|
+
return result.stdout, True
|
|
303
|
+
result = _run_git(repo, "worktree", "list", "--porcelain", check=False)
|
|
304
|
+
if result.returncode != 0:
|
|
305
|
+
message = (result.stderr or result.stdout).strip() or "could not list worktrees"
|
|
306
|
+
raise PipelineGitError(message)
|
|
307
|
+
return result.stdout, False
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def branch_worktrees_from_porcelain(output: str, *, nul_separated: bool) -> dict[str, Path]:
|
|
276
311
|
worktrees: dict[str, Path] = {}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
312
|
+
if nul_separated:
|
|
313
|
+
current_path: Optional[Path] = None
|
|
314
|
+
for field in output.split("\0"):
|
|
315
|
+
if not field:
|
|
316
|
+
continue
|
|
317
|
+
if field.startswith("worktree "):
|
|
318
|
+
current_path = Path(field.removeprefix("worktree ")).resolve()
|
|
319
|
+
elif field.startswith("branch ") and current_path is not None:
|
|
320
|
+
ref = field.removeprefix("branch ")
|
|
321
|
+
prefix = "refs/heads/"
|
|
322
|
+
if ref.startswith(prefix):
|
|
323
|
+
worktrees[ref.removeprefix(prefix)] = current_path
|
|
324
|
+
return worktrees
|
|
325
|
+
for block in output.strip().split("\n\n"):
|
|
326
|
+
fields = block.splitlines()
|
|
327
|
+
if not fields or not fields[0].startswith("worktree "):
|
|
328
|
+
continue
|
|
329
|
+
current_path = Path(fields[0].removeprefix("worktree ")).resolve()
|
|
330
|
+
for line in fields[1:]:
|
|
331
|
+
if line.startswith("branch refs/heads/"):
|
|
332
|
+
worktrees[line.removeprefix("branch refs/heads/")] = current_path
|
|
286
333
|
return worktrees
|
|
287
334
|
|
|
288
335
|
|
|
336
|
+
def _branch_worktrees(repo: Path) -> dict[str, Path]:
|
|
337
|
+
"""Return every checked-out local branch from Git's worktree registry."""
|
|
338
|
+
output, nul_separated = worktree_list_porcelain(repo)
|
|
339
|
+
return branch_worktrees_from_porcelain(output, nul_separated=nul_separated)
|
|
340
|
+
|
|
341
|
+
|
|
289
342
|
def _remote_ref_sha(repo: Path, ref: str) -> Optional[str]:
|
|
290
343
|
if not _origin_remote_exists(repo):
|
|
291
344
|
raise PipelineGitError("origin remote is not configured")
|
|
@@ -312,7 +365,7 @@ def _remote_branch_exists(repo: Path, branch: str) -> bool:
|
|
|
312
365
|
return _remote_ref_sha(repo, f"refs/heads/{branch}") is not None
|
|
313
366
|
|
|
314
367
|
|
|
315
|
-
def
|
|
368
|
+
def _milestone_tag_fields(repo: Path, ref: str) -> dict[str, str]:
|
|
316
369
|
tag_type = _run_git(repo, "cat-file", "-t", ref, check=False)
|
|
317
370
|
if tag_type.returncode != 0 or tag_type.stdout.strip() != "tag":
|
|
318
371
|
return {}
|
|
@@ -322,7 +375,7 @@ def _pull_request_tag_fields(repo: Path, ref: str) -> dict[str, str]:
|
|
|
322
375
|
match = re.fullmatch(r"(Mode|Pull-Request|Ship|Landing): (.+)", line)
|
|
323
376
|
if match:
|
|
324
377
|
if match.group(1) in fields:
|
|
325
|
-
raise PipelineGitError("
|
|
378
|
+
raise PipelineGitError("milestone tag repeats metadata")
|
|
326
379
|
fields[match.group(1)] = match.group(2)
|
|
327
380
|
return fields
|
|
328
381
|
|
|
@@ -353,7 +406,15 @@ def _validated_shipped_state(repo: Path) -> dict[str, object]:
|
|
|
353
406
|
return state
|
|
354
407
|
|
|
355
408
|
|
|
356
|
-
|
|
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(
|
|
357
418
|
repo: Path,
|
|
358
419
|
ship: str,
|
|
359
420
|
base: str,
|
|
@@ -364,8 +425,11 @@ def _require_pull_request_integration_proof(
|
|
|
364
425
|
raise PipelineGitError("bind-next requires shipped/done state")
|
|
365
426
|
if state.get("branch") != previous_branch:
|
|
366
427
|
raise PipelineGitError("shipped state does not name the previous branch")
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
)
|
|
369
433
|
archive = state.get("archive")
|
|
370
434
|
if not isinstance(archive, str):
|
|
371
435
|
raise PipelineGitError("shipped state does not name an archive")
|
|
@@ -376,24 +440,23 @@ def _require_pull_request_integration_proof(
|
|
|
376
440
|
):
|
|
377
441
|
raise PipelineGitError("shipped archive does not match the previous branch")
|
|
378
442
|
ref = f"refs/tags/milestone/{match.group(1)}"
|
|
379
|
-
fields =
|
|
380
|
-
if set(fields) !=
|
|
381
|
-
raise PipelineGitError("
|
|
382
|
-
if fields["Mode"] !=
|
|
383
|
-
raise PipelineGitError("
|
|
384
|
-
|
|
385
|
-
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(
|
|
386
449
|
r"https://github\.com/[^/]+/[^/]+/pull/[1-9][0-9]*",
|
|
387
|
-
|
|
450
|
+
fields["Pull-Request"],
|
|
388
451
|
) is None:
|
|
389
452
|
raise PipelineGitError("pull-request integration proof has an invalid PR URL")
|
|
390
453
|
landing = fields["Landing"]
|
|
391
454
|
if re.fullmatch(r"[0-9a-f]{40}", landing) is None:
|
|
392
|
-
raise PipelineGitError("
|
|
455
|
+
raise PipelineGitError(f"{mode} integration proof has an invalid landing")
|
|
393
456
|
local_object = _run_git(repo, "rev-parse", ref).stdout.strip()
|
|
394
457
|
local_target = _run_git(repo, "rev-parse", f"{ref}^{{commit}}").stdout.strip()
|
|
395
458
|
if local_target != landing:
|
|
396
|
-
raise PipelineGitError("
|
|
459
|
+
raise PipelineGitError(f"{mode} integration tag does not point at landing")
|
|
397
460
|
remote = _run_git(
|
|
398
461
|
repo,
|
|
399
462
|
"ls-remote",
|
|
@@ -405,14 +468,14 @@ def _require_pull_request_integration_proof(
|
|
|
405
468
|
check=False,
|
|
406
469
|
)
|
|
407
470
|
if remote.returncode != 0:
|
|
408
|
-
raise PipelineGitError("
|
|
471
|
+
raise PipelineGitError(f"{mode} integration tag is not published")
|
|
409
472
|
rows = dict(
|
|
410
473
|
line.split("\t", 1)[::-1]
|
|
411
474
|
for line in remote.stdout.splitlines()
|
|
412
475
|
if "\t" in line
|
|
413
476
|
)
|
|
414
477
|
if rows != {ref: local_object, f"{ref}^{{}}": landing}:
|
|
415
|
-
raise PipelineGitError("published
|
|
478
|
+
raise PipelineGitError(f"published {mode} integration tag differs")
|
|
416
479
|
parents = _run_git(
|
|
417
480
|
repo,
|
|
418
481
|
"rev-list",
|
|
@@ -422,11 +485,11 @@ def _require_pull_request_integration_proof(
|
|
|
422
485
|
landing,
|
|
423
486
|
).stdout.split()
|
|
424
487
|
if len(parents) != 3 or parents[2] != ship:
|
|
425
|
-
raise PipelineGitError("
|
|
488
|
+
raise PipelineGitError(f"{mode} integration proof has invalid merge topology")
|
|
426
489
|
first_parent = _run_git(repo, "rev-list", "--first-parent", base).stdout.splitlines()
|
|
427
490
|
if landing not in first_parent:
|
|
428
491
|
raise PipelineGitError(
|
|
429
|
-
"
|
|
492
|
+
f"{mode} integration landing is not on the default branch first-parent history"
|
|
430
493
|
)
|
|
431
494
|
return landing
|
|
432
495
|
|
|
@@ -482,6 +545,34 @@ def _write_bind_next_journal(path: Path, value: dict[str, object]) -> None:
|
|
|
482
545
|
temporary.unlink()
|
|
483
546
|
|
|
484
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
|
+
|
|
485
576
|
def _initial_binding_snapshot(repo: Path) -> Optional[tuple]:
|
|
486
577
|
"""Admit only clean work or the validated, untracked initializer state."""
|
|
487
578
|
dirty = _run_git(repo, "status", "--porcelain=v1", "-z", "--untracked-files=all").stdout
|
|
@@ -528,8 +619,7 @@ def bind_initial_milestone_branch(
|
|
|
528
619
|
resolved = _require_worktree_root(repo)
|
|
529
620
|
if not is_bound_branch(branch):
|
|
530
621
|
raise PipelineGitError(f"invalid bound branch: {branch}")
|
|
531
|
-
|
|
532
|
-
raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
|
|
622
|
+
default_name = _require_state_remote_default(resolved, remote_default)
|
|
533
623
|
|
|
534
624
|
default_sha = _run_git(
|
|
535
625
|
resolved,
|
|
@@ -549,7 +639,7 @@ def bind_initial_milestone_branch(
|
|
|
549
639
|
raise PipelineGitError(
|
|
550
640
|
f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
|
|
551
641
|
)
|
|
552
|
-
remote_sha = _remote_ref_sha(resolved, "refs/heads/
|
|
642
|
+
remote_sha = _remote_ref_sha(resolved, f"refs/heads/{default_name}")
|
|
553
643
|
if remote_sha != default_sha:
|
|
554
644
|
raise PipelineGitError(
|
|
555
645
|
f"fetched {remote_default} is stale relative to origin"
|
|
@@ -691,8 +781,7 @@ def bind_next_milestone_branch(
|
|
|
691
781
|
raise PipelineGitError(f"invalid previous bound branch: {previous_branch}")
|
|
692
782
|
if milestone_number(branch) <= milestone_number(previous_branch):
|
|
693
783
|
raise PipelineGitError(f"next branch {branch} must follow {previous_branch}")
|
|
694
|
-
|
|
695
|
-
raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
|
|
784
|
+
default_name = _require_state_remote_default(repo, remote_default)
|
|
696
785
|
|
|
697
786
|
default_sha = _run_git(
|
|
698
787
|
repo,
|
|
@@ -712,7 +801,7 @@ def bind_next_milestone_branch(
|
|
|
712
801
|
raise PipelineGitError(
|
|
713
802
|
f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
|
|
714
803
|
)
|
|
715
|
-
live_default_sha = _remote_ref_sha(repo, "refs/heads/
|
|
804
|
+
live_default_sha = _remote_ref_sha(repo, f"refs/heads/{default_name}")
|
|
716
805
|
if live_default_sha != default_sha:
|
|
717
806
|
raise PipelineGitError(f"fetched {remote_default} is stale relative to origin")
|
|
718
807
|
ship_sha = _run_git(
|
|
@@ -770,7 +859,7 @@ def bind_next_milestone_branch(
|
|
|
770
859
|
detail = landing_integrated.stderr.strip() or landing_integrated.stdout.strip()
|
|
771
860
|
raise PipelineGitError(f"could not verify integrated branch: {detail}")
|
|
772
861
|
if allow_remote_absent:
|
|
773
|
-
proven_landing =
|
|
862
|
+
proven_landing = _require_tag_integration_proof(
|
|
774
863
|
repo,
|
|
775
864
|
ship_sha,
|
|
776
865
|
base_sha,
|
|
@@ -778,7 +867,7 @@ def bind_next_milestone_branch(
|
|
|
778
867
|
)
|
|
779
868
|
if proven_landing != landing_sha:
|
|
780
869
|
raise PipelineGitError(
|
|
781
|
-
"validated landing does not match
|
|
870
|
+
"validated landing does not match the milestone tag integration proof"
|
|
782
871
|
)
|
|
783
872
|
|
|
784
873
|
symbolic_branch = _run_git(
|
|
@@ -91,7 +91,7 @@ STATE_FIELDS = (
|
|
|
91
91
|
"integration_source",
|
|
92
92
|
)
|
|
93
93
|
LEGACY_STATE_FIELDS = STATE_FIELDS[:7]
|
|
94
|
-
INTEGRATION_MODES = ("direct", "pull-request")
|
|
94
|
+
INTEGRATION_MODES = ("direct", "pull-request", "external-landing")
|
|
95
95
|
INTEGRATION_SOURCES = ("default", "milestone")
|
|
96
96
|
NULL = "null"
|
|
97
97
|
SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
|
|
@@ -141,6 +141,8 @@ CROSS_PHASE_TRANSITIONS = {
|
|
|
141
141
|
("decide", "done", "plan", "active"),
|
|
142
142
|
("roadmap", "done", "define", "active"),
|
|
143
143
|
("plan", "done", "build", "active"),
|
|
144
|
+
("plan", "active", "define", "active"),
|
|
145
|
+
("plan", "blocked", "define", "active"),
|
|
144
146
|
("build", "blocked", "define", "active"),
|
|
145
147
|
("build", "blocked", "plan", "active"),
|
|
146
148
|
("build", "active", "ship", "active"),
|
|
@@ -171,9 +173,14 @@ class PipelineState:
|
|
|
171
173
|
integration_default: str
|
|
172
174
|
integration: str
|
|
173
175
|
integration_source: str
|
|
176
|
+
default_branch: str = "main"
|
|
174
177
|
|
|
175
178
|
def json(self) -> dict[str, Optional[str]]:
|
|
176
|
-
|
|
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
|
|
177
184
|
|
|
178
185
|
|
|
179
186
|
@dataclass(frozen=True)
|
|
@@ -193,11 +200,24 @@ def _run_git(
|
|
|
193
200
|
*arguments: str,
|
|
194
201
|
check: bool = True,
|
|
195
202
|
) -> subprocess.CompletedProcess[str]:
|
|
203
|
+
command = _common.git_command(repo, *arguments)
|
|
196
204
|
result = subprocess.run(
|
|
197
|
-
|
|
205
|
+
command,
|
|
198
206
|
capture_output=True,
|
|
199
207
|
encoding="utf-8", errors="replace",
|
|
200
208
|
)
|
|
209
|
+
if (
|
|
210
|
+
check
|
|
211
|
+
and result.returncode != 0
|
|
212
|
+
and arguments
|
|
213
|
+
and arguments[0] == "commit"
|
|
214
|
+
and _common.git_index_lock_retryable(result.stderr)
|
|
215
|
+
):
|
|
216
|
+
result = subprocess.run(
|
|
217
|
+
command,
|
|
218
|
+
capture_output=True,
|
|
219
|
+
encoding="utf-8", errors="replace",
|
|
220
|
+
)
|
|
201
221
|
if check and result.returncode != 0:
|
|
202
222
|
detail = result.stderr.strip() or result.stdout.strip()
|
|
203
223
|
raise PipelineStateError(f"git {' '.join(arguments)} failed: {detail}")
|
|
@@ -275,7 +295,7 @@ def _bound_branch_number(value: str) -> Optional[int]:
|
|
|
275
295
|
def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
|
|
276
296
|
values = _parse_frontmatter(text, label)
|
|
277
297
|
missing = [field for field in LEGACY_STATE_FIELDS if field not in values]
|
|
278
|
-
extra = sorted(set(values) - set(STATE_FIELDS))
|
|
298
|
+
extra = sorted(set(values) - set(STATE_FIELDS) - {"default_branch"})
|
|
279
299
|
if missing:
|
|
280
300
|
raise PipelineStateError(f"{label} is missing fields: {', '.join(missing)}")
|
|
281
301
|
if extra:
|
|
@@ -307,6 +327,7 @@ def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
|
|
|
307
327
|
integration_default=integration_default,
|
|
308
328
|
integration=integration,
|
|
309
329
|
integration_source=integration_source,
|
|
330
|
+
default_branch=values.get("default_branch", "main"),
|
|
310
331
|
)
|
|
311
332
|
_validate_state_values(state, label)
|
|
312
333
|
return state
|
|
@@ -340,6 +361,10 @@ def _validate_state_values(state: PipelineState, label: str) -> None:
|
|
|
340
361
|
raise PipelineStateError(
|
|
341
362
|
f"{label} default-sourced integration must match integration_default"
|
|
342
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
|
+
)
|
|
343
368
|
if state.branch is not None and _bound_branch_number(state.branch) is None:
|
|
344
369
|
raise PipelineStateError(f"{label} has invalid branch: {state.branch}")
|
|
345
370
|
archive_match = ARCHIVE_RE.fullmatch(state.archive or "")
|
|
@@ -401,6 +426,7 @@ def validate_state(repo: Path, project_dir: str = ".project") -> dict[str, objec
|
|
|
401
426
|
"status": "valid",
|
|
402
427
|
"path": str(path),
|
|
403
428
|
"state": state.json(),
|
|
429
|
+
"default_branch": state.default_branch,
|
|
404
430
|
}
|
|
405
431
|
|
|
406
432
|
|
|
@@ -683,8 +709,10 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
683
709
|
if branch
|
|
684
710
|
else None
|
|
685
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
|
|
686
714
|
origin_main = (
|
|
687
|
-
_optional_rev(resolved, "refs/remotes/origin/
|
|
715
|
+
_optional_rev(resolved, f"refs/remotes/origin/{default_branch}") if has_git else None
|
|
688
716
|
)
|
|
689
717
|
pending, pending_error = _pending_answers(resolved)
|
|
690
718
|
lookahead = resolved / ".project" / "next"
|
|
@@ -707,6 +735,7 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
707
735
|
"advance": False,
|
|
708
736
|
"completion": completion,
|
|
709
737
|
"state": state,
|
|
738
|
+
"default_branch": default_branch,
|
|
710
739
|
"route": route,
|
|
711
740
|
"path": str(_track_root(resolved, project_dir) / "STATE.md"),
|
|
712
741
|
"git": {
|
|
@@ -866,7 +895,12 @@ def _route_result(
|
|
|
866
895
|
route["phase"] = phase
|
|
867
896
|
if mode is not None:
|
|
868
897
|
route["mode"] = mode
|
|
869
|
-
return {
|
|
898
|
+
return {
|
|
899
|
+
"schema": ROUTE_SCHEMA,
|
|
900
|
+
"state": state.json(),
|
|
901
|
+
"default_branch": state.default_branch,
|
|
902
|
+
"route": route,
|
|
903
|
+
}
|
|
870
904
|
|
|
871
905
|
|
|
872
906
|
def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
|
|
@@ -911,36 +945,13 @@ def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
|
|
|
911
945
|
return landing
|
|
912
946
|
|
|
913
947
|
|
|
914
|
-
def
|
|
948
|
+
def _bind_next_journal_recovery(
|
|
915
949
|
repo: Path,
|
|
916
950
|
state: PipelineState,
|
|
951
|
+
path: Path,
|
|
952
|
+
transaction: dict[str, object],
|
|
953
|
+
current: Optional[str],
|
|
917
954
|
) -> Optional[dict[str, object]]:
|
|
918
|
-
journal_root = _git_path(repo, BIND_NEXT_JOURNAL_DIR)
|
|
919
|
-
if not journal_root.exists() and not journal_root.is_symlink():
|
|
920
|
-
return None
|
|
921
|
-
if journal_root.is_symlink() or not journal_root.is_dir():
|
|
922
|
-
raise PipelineStateError(f"bind-next journal root must be a real directory: {journal_root}")
|
|
923
|
-
|
|
924
|
-
matches: list[tuple[Path, dict[str, object]]] = []
|
|
925
|
-
for path in sorted(journal_root.glob("*.json")):
|
|
926
|
-
transaction = _read_json(path)
|
|
927
|
-
if transaction.get("schema") not in {
|
|
928
|
-
LEGACY_BIND_NEXT_JOURNAL_SCHEMA,
|
|
929
|
-
BIND_NEXT_JOURNAL_SCHEMA,
|
|
930
|
-
}:
|
|
931
|
-
raise PipelineStateError(f"bind-next journal has invalid schema: {path}")
|
|
932
|
-
if transaction.get("repo") != str(repo):
|
|
933
|
-
raise PipelineStateError(f"bind-next journal belongs to another worktree: {path}")
|
|
934
|
-
if transaction.get("previous_branch") == state.branch:
|
|
935
|
-
matches.append((path, transaction))
|
|
936
|
-
if not matches:
|
|
937
|
-
return None
|
|
938
|
-
if len(matches) != 1:
|
|
939
|
-
raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
|
|
940
|
-
if state.phase != "shipped" or state.status != "done":
|
|
941
|
-
raise PipelineStateError("bind-next journal requires STATE shipped/done")
|
|
942
|
-
|
|
943
|
-
path, transaction = matches[0]
|
|
944
955
|
required = (
|
|
945
956
|
"branch",
|
|
946
957
|
"previous_branch",
|
|
@@ -1017,12 +1028,37 @@ def _bind_next_recovery(
|
|
|
1017
1028
|
raise PipelineStateError("bind-next journal landing does not contain ship")
|
|
1018
1029
|
if not _is_ancestor(repo, landing, base):
|
|
1019
1030
|
raise PipelineStateError("bind-next journal landing is not an ancestor of base")
|
|
1020
|
-
if transaction["remote_default"] != "origin/
|
|
1031
|
+
if transaction["remote_default"] != f"origin/{state.default_branch}":
|
|
1021
1032
|
raise PipelineStateError("bind-next journal has invalid remote default")
|
|
1022
1033
|
if stage not in {"prepared", "switched", "retired"}:
|
|
1023
1034
|
raise PipelineStateError("bind-next journal has invalid stage")
|
|
1024
1035
|
|
|
1025
|
-
|
|
1036
|
+
stale_retired = False
|
|
1037
|
+
if stage == "retired":
|
|
1038
|
+
target_ref = _run_git(
|
|
1039
|
+
repo,
|
|
1040
|
+
"show-ref",
|
|
1041
|
+
"--verify",
|
|
1042
|
+
"--quiet",
|
|
1043
|
+
f"refs/heads/{branch}",
|
|
1044
|
+
check=False,
|
|
1045
|
+
)
|
|
1046
|
+
if target_ref.returncode not in {0, 1}:
|
|
1047
|
+
detail = (target_ref.stderr or target_ref.stdout).strip()
|
|
1048
|
+
raise PipelineStateError(
|
|
1049
|
+
f"could not check bind-next target branch {branch}: {detail}"
|
|
1050
|
+
)
|
|
1051
|
+
target_exists = target_ref.returncode == 0
|
|
1052
|
+
stale_retired = current not in {previous, branch} or not target_exists
|
|
1053
|
+
if stale_retired:
|
|
1054
|
+
# If the completed target is gone while the previous branch is checked
|
|
1055
|
+
# out, still prove that branch has not moved away from the recorded ship.
|
|
1056
|
+
if current == previous:
|
|
1057
|
+
head = _run_git(repo, "rev-parse", "HEAD").stdout.strip()
|
|
1058
|
+
if head != ship:
|
|
1059
|
+
raise PipelineStateError("bind-next previous branch is not at journal ship")
|
|
1060
|
+
return None
|
|
1061
|
+
|
|
1026
1062
|
if current not in {previous, branch}:
|
|
1027
1063
|
raise PipelineStateError("bind-next journal does not own the current branch")
|
|
1028
1064
|
if current == previous and stage != "prepared":
|
|
@@ -1057,6 +1093,52 @@ def _bind_next_recovery(
|
|
|
1057
1093
|
return result
|
|
1058
1094
|
|
|
1059
1095
|
|
|
1096
|
+
def _bind_next_recovery(
|
|
1097
|
+
repo: Path,
|
|
1098
|
+
state: PipelineState,
|
|
1099
|
+
) -> Optional[dict[str, object]]:
|
|
1100
|
+
journal_root = _git_path(repo, BIND_NEXT_JOURNAL_DIR)
|
|
1101
|
+
if not journal_root.exists() and not journal_root.is_symlink():
|
|
1102
|
+
return None
|
|
1103
|
+
if journal_root.is_symlink() or not journal_root.is_dir():
|
|
1104
|
+
raise PipelineStateError(f"bind-next journal root must be a real directory: {journal_root}")
|
|
1105
|
+
|
|
1106
|
+
matches: list[tuple[Path, dict[str, object]]] = []
|
|
1107
|
+
for path in sorted(journal_root.glob("*.json")):
|
|
1108
|
+
transaction = _read_json(path)
|
|
1109
|
+
if transaction.get("schema") not in {
|
|
1110
|
+
LEGACY_BIND_NEXT_JOURNAL_SCHEMA,
|
|
1111
|
+
BIND_NEXT_JOURNAL_SCHEMA,
|
|
1112
|
+
}:
|
|
1113
|
+
raise PipelineStateError(f"bind-next journal has invalid schema: {path}")
|
|
1114
|
+
if transaction.get("repo") != str(repo):
|
|
1115
|
+
raise PipelineStateError(f"bind-next journal belongs to another worktree: {path}")
|
|
1116
|
+
if transaction.get("previous_branch") == state.branch:
|
|
1117
|
+
matches.append((path, transaction))
|
|
1118
|
+
if not matches:
|
|
1119
|
+
return None
|
|
1120
|
+
if state.phase != "shipped" or state.status != "done":
|
|
1121
|
+
if len(matches) != 1:
|
|
1122
|
+
raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
|
|
1123
|
+
raise PipelineStateError("bind-next journal requires STATE shipped/done")
|
|
1124
|
+
|
|
1125
|
+
current = _current_branch(repo)
|
|
1126
|
+
recoveries: list[dict[str, object]] = []
|
|
1127
|
+
for path, transaction in matches:
|
|
1128
|
+
recovery = _bind_next_journal_recovery(
|
|
1129
|
+
repo,
|
|
1130
|
+
state,
|
|
1131
|
+
path,
|
|
1132
|
+
transaction,
|
|
1133
|
+
current,
|
|
1134
|
+
)
|
|
1135
|
+
if recovery is not None:
|
|
1136
|
+
recoveries.append(recovery)
|
|
1137
|
+
if len(recoveries) > 1:
|
|
1138
|
+
raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
|
|
1139
|
+
return recoveries[0] if recoveries else None
|
|
1140
|
+
|
|
1141
|
+
|
|
1060
1142
|
def _pending_discussion_block(repo: Path, phase: Optional[str]) -> Optional[str]:
|
|
1061
1143
|
pending, error = _pending_answers(repo)
|
|
1062
1144
|
if error:
|
|
@@ -1077,7 +1159,7 @@ def route_state(repo: Path, project_dir: str = ".project") -> dict[str, object]:
|
|
|
1077
1159
|
result["recovery"] = recovery
|
|
1078
1160
|
if route.get("phase") == "define":
|
|
1079
1161
|
route["mode"] = "corrections"
|
|
1080
|
-
elif route.get("phase") == "plan":
|
|
1162
|
+
elif route.get("phase") == "plan" and recovery.get("source", "build") == "build":
|
|
1081
1163
|
route["mode"] = "build-repair"
|
|
1082
1164
|
if route["action"] == "run-phase" and result["state"]["archive"] is None:
|
|
1083
1165
|
reason = _pending_discussion_block(_repo_root(repo), route.get("phase"))
|
|
@@ -1668,6 +1750,8 @@ def _validate_transition(
|
|
|
1668
1750
|
},
|
|
1669
1751
|
("define", "done", "plan", "active"): {"planning started"},
|
|
1670
1752
|
("decide", "done", "plan", "active"): {"planning started"},
|
|
1753
|
+
("plan", "active", "define", "active"): {"plan intent corrections requested"},
|
|
1754
|
+
("plan", "blocked", "define", "active"): {"plan intent corrections requested"},
|
|
1671
1755
|
("ship", "blocked", "plan", "active"): {"patch plan reopened"},
|
|
1672
1756
|
("ship", "active", "shipped", "done"): {
|
|
1673
1757
|
"archive preflight passed; shipment recorded",
|
|
@@ -1693,7 +1777,10 @@ def transition_state(
|
|
|
1693
1777
|
if unsupported:
|
|
1694
1778
|
raise PipelineStateError(f"unsupported state fields: {', '.join(sorted(unsupported))}")
|
|
1695
1779
|
if not changes:
|
|
1696
|
-
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
|
+
)
|
|
1697
1784
|
required_expected = {"phase", "status", "branch", "archive"} | set(changes)
|
|
1698
1785
|
missing_expected = sorted(required_expected - set(expected))
|
|
1699
1786
|
if missing_expected:
|
|
@@ -1720,9 +1807,10 @@ def transition_state(
|
|
|
1720
1807
|
)
|
|
1721
1808
|
if reason:
|
|
1722
1809
|
raise PipelineStateError(reason)
|
|
1723
|
-
|
|
1810
|
+
leaving_define = (state.phase, after.phase, after.status) == ("define", "define", "done")
|
|
1811
|
+
if leaving_define or (state.phase in {"research", "decide"} and (
|
|
1724
1812
|
after.phase != state.phase or after.status == "done"
|
|
1725
|
-
):
|
|
1813
|
+
)):
|
|
1726
1814
|
if __package__:
|
|
1727
1815
|
from . import check_handoffs
|
|
1728
1816
|
else:
|
|
@@ -1731,6 +1819,8 @@ def transition_state(
|
|
|
1731
1819
|
except ImportError: # pragma: no cover - package imports used by tests
|
|
1732
1820
|
from scripts import check_handoffs
|
|
1733
1821
|
validator = {
|
|
1822
|
+
# Approved intent carries its spec-reach probe tables complete.
|
|
1823
|
+
"define": check_handoffs.validate_intent_probes,
|
|
1734
1824
|
"research": check_handoffs.validate_research_artifacts,
|
|
1735
1825
|
"decide": check_handoffs.validate_decide_artifacts,
|
|
1736
1826
|
}[state.phase]
|
|
@@ -1747,6 +1837,16 @@ def transition_state(
|
|
|
1747
1837
|
rendered, after.phase,
|
|
1748
1838
|
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1749
1839
|
)
|
|
1840
|
+
elif (
|
|
1841
|
+
state.phase == "plan"
|
|
1842
|
+
and state.status in {"active", "blocked"}
|
|
1843
|
+
and (after.phase, after.status) == ("define", "active")
|
|
1844
|
+
):
|
|
1845
|
+
recovery = _build_recovery().begin(resolved, state, after, event)
|
|
1846
|
+
rendered = _append_event(
|
|
1847
|
+
rendered, after.phase,
|
|
1848
|
+
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1849
|
+
)
|
|
1750
1850
|
_atomic_write(path, rendered)
|
|
1751
1851
|
return {
|
|
1752
1852
|
"schema": STATE_SCHEMA,
|