@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
|
@@ -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,
|
|
@@ -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:
|
|
@@ -18,14 +18,14 @@ from pathlib import Path, PurePosixPath
|
|
|
18
18
|
from typing import Mapping, Optional, Sequence
|
|
19
19
|
|
|
20
20
|
if __package__:
|
|
21
|
-
from .isolation import IsolationError, checkpoint as isolation_checkpoint,
|
|
21
|
+
from .isolation import IsolationError, checkpoint as isolation_checkpoint, PROJECT_ENTRIES
|
|
22
22
|
else:
|
|
23
23
|
try:
|
|
24
|
-
from isolation import IsolationError, checkpoint as isolation_checkpoint,
|
|
24
|
+
from isolation import IsolationError, checkpoint as isolation_checkpoint, PROJECT_ENTRIES
|
|
25
25
|
except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
|
|
26
26
|
if error.name != "isolation":
|
|
27
27
|
raise
|
|
28
|
-
from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint,
|
|
28
|
+
from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, PROJECT_ENTRIES
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
if __package__:
|
|
@@ -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:
|
|
@@ -444,14 +459,14 @@ def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
|
|
|
444
459
|
# Pre-Git approval defers base-dependent checks to build.
|
|
445
460
|
return
|
|
446
461
|
if __package__:
|
|
447
|
-
from .check_task_briefs import BriefError, _member_bases, validate_task_briefs
|
|
462
|
+
from .check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
|
|
448
463
|
else:
|
|
449
464
|
try:
|
|
450
|
-
from check_task_briefs import BriefError, _member_bases, validate_task_briefs
|
|
465
|
+
from check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
|
|
451
466
|
except ModuleNotFoundError as error: # pragma: no cover - package imports
|
|
452
467
|
if error.name != "check_task_briefs":
|
|
453
468
|
raise
|
|
454
|
-
from scripts.check_task_briefs import BriefError, _member_bases, validate_task_briefs
|
|
469
|
+
from scripts.check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
|
|
455
470
|
if __package__:
|
|
456
471
|
from . import check_handoffs
|
|
457
472
|
else:
|
|
@@ -462,28 +477,10 @@ def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
|
|
|
462
477
|
raise
|
|
463
478
|
from scripts import check_handoffs
|
|
464
479
|
try:
|
|
465
|
-
tasks, dependency_files = check_handoffs.plan_brief_inputs(repo, project_dir)
|
|
466
|
-
landed_bases = {}
|
|
467
480
|
member_base = _member_bases(repo)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
agent = check_handoffs._task_scalar(text, task_id, "agent")
|
|
472
|
-
if agent in {"", "null"}:
|
|
473
|
-
raise BriefError(f"{task_id} landed task has no recorded agent")
|
|
474
|
-
member = check_handoffs._strict_frontmatter(text, task_id).get("repo")
|
|
475
|
-
base_repo = repo
|
|
476
|
-
if member is not None:
|
|
477
|
-
located = member_base(member) if isinstance(member, str) and member else None
|
|
478
|
-
if located is None:
|
|
479
|
-
raise BriefError(f"repo: names no member in MEMBERS.md: {member}")
|
|
480
|
-
base_repo = located[0]
|
|
481
|
-
recorded_base = check_handoffs._task_scalar(text, task_id, "base")
|
|
482
|
-
try:
|
|
483
|
-
landed_bases[task_id] = require_commit(base_repo, require_full_sha(recorded_base))
|
|
484
|
-
except IsolationError as error:
|
|
485
|
-
raise BriefError(f"{task_id} landed task has invalid historical base: {error}") from error
|
|
486
|
-
dependency_files[task_id] = set()
|
|
481
|
+
tasks, dependency_files, landed_bases = plan_validation_bases(
|
|
482
|
+
repo, project_dir, member_base, initial=True
|
|
483
|
+
)
|
|
487
484
|
validate_task_briefs(
|
|
488
485
|
repo, head.stdout.strip(), f"{project_dir}/tasks",
|
|
489
486
|
dependency_files=dependency_files, landed_bases=landed_bases,
|
|
@@ -581,7 +578,10 @@ def checkpoint_approval(
|
|
|
581
578
|
f"{expected_head}^{{commit}}",
|
|
582
579
|
).stdout.strip()
|
|
583
580
|
if resolved_head != expected_head:
|
|
584
|
-
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
|
+
)
|
|
585
585
|
if pipeline_state._run_git(resolved, "rev-parse", "HEAD").stdout.strip() != expected_head:
|
|
586
586
|
raise PipelineStateError("approval must start at expected-head")
|
|
587
587
|
active_state, _, _ = pipeline_state.load_state(resolved)
|
|
@@ -596,8 +596,9 @@ def checkpoint_approval(
|
|
|
596
596
|
state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
|
|
597
597
|
changes, event, subject, body = _approval_details(kind, state, selected_milestone)
|
|
598
598
|
_validate_plan_briefs(resolved, kind, project_dir)
|
|
599
|
+
recovery_reviews = None
|
|
599
600
|
if kind == "plan" and project_dir == ".project":
|
|
600
|
-
pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
601
|
+
recovery_reviews = pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
601
602
|
expected = {
|
|
602
603
|
"phase": state.phase,
|
|
603
604
|
"status": state.status,
|
|
@@ -629,7 +630,7 @@ def checkpoint_approval(
|
|
|
629
630
|
if kind == "roadmap-reslice":
|
|
630
631
|
baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
|
|
631
632
|
roadmap_after = _reslice_roadmap(
|
|
632
|
-
baseline_before, roadmap_before, state, selected_milestone,
|
|
633
|
+
resolved, baseline_before, roadmap_before, state, state_before, selected_milestone,
|
|
633
634
|
) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
634
635
|
roadmap_before,
|
|
635
636
|
selected_milestone,
|
|
@@ -659,6 +660,8 @@ def checkpoint_approval(
|
|
|
659
660
|
}
|
|
660
661
|
if baseline_before is not None:
|
|
661
662
|
journal["baseline_before"] = baseline_before
|
|
663
|
+
if recovery_reviews is not None:
|
|
664
|
+
journal["recovery_reviews"] = recovery_reviews
|
|
662
665
|
pipeline_state._write_json(journal_path, journal)
|
|
663
666
|
return _resume_checkpoint_locked(resolved, project, journal_path, journal)
|
|
664
667
|
|
|
@@ -740,8 +743,8 @@ def defer_approval(
|
|
|
740
743
|
if kind == "roadmap-reslice":
|
|
741
744
|
baseline = project / "ROADMAP.before-reslice.md"
|
|
742
745
|
roadmap_after = _reslice_roadmap(
|
|
743
|
-
pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
744
|
-
roadmap_before, state, selected_milestone,
|
|
746
|
+
resolved, pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
747
|
+
roadmap_before, state, state_before, selected_milestone,
|
|
745
748
|
)
|
|
746
749
|
else:
|
|
747
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}"
|
|
@@ -77,7 +77,7 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
77
77
|
head = subprocess.run(["git", "rev-parse", "--verify", "HEAD"],
|
|
78
78
|
cwd=repo, capture_output=True, encoding="utf-8", errors="replace", check=True).stdout.strip()
|
|
79
79
|
step("check_task_briefs.py", "--repo", str(repo), "--base", head,
|
|
80
|
-
"--tasks-dir", f"{project_dir}/tasks")
|
|
80
|
+
"--tasks-dir", f"{project_dir}/tasks", "--project-dir", project_dir)
|
|
81
81
|
panel = ["validate-plan", "--plan", str(repo / project_dir / "plan/PLAN.md"),
|
|
82
82
|
"--intent", str(repo / project_dir / "intent/INTENT.md")]
|
|
83
83
|
charter = repo / ".project/CHARTER.md"
|
|
@@ -199,7 +199,7 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
199
199
|
head = subprocess.run(["git", "rev-parse", "--verify", "HEAD"],
|
|
200
200
|
cwd=repo, capture_output=True, encoding="utf-8", errors="replace", check=True).stdout.strip()
|
|
201
201
|
step("check_task_briefs.py", "--repo", str(repo), "--base", head,
|
|
202
|
-
"--tasks-dir", f"{project_dir}/tasks")
|
|
202
|
+
"--tasks-dir", f"{project_dir}/tasks", "--project-dir", project_dir)
|
|
203
203
|
step("check_handoffs.py", "plan", *common)
|
|
204
204
|
elif action == "prepare-task" and _task_member(repo, project_dir, task_id):
|
|
205
205
|
# A member task works in a sidecar of its member; its Verify runs there at finish.
|
|
@@ -210,8 +210,14 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
210
210
|
"--base", expected_head, "--task-id", task_id,
|
|
211
211
|
"--round-size", str(round_size))
|
|
212
212
|
if task["mode"] == "serial":
|
|
213
|
+
name = f"task-{task_id.lower()}-verify"
|
|
214
|
+
stale = isolation.sidecar_root(repo, "verify", name)
|
|
215
|
+
if os.path.lexists(stale / ".git"):
|
|
216
|
+
# An earlier prepare-task left this sidecar. Only one that is proven untouched is replaced.
|
|
217
|
+
steps.append({"script": "isolation.py", "result": isolation.retire_stale_verify(
|
|
218
|
+
repo, stale, isolation.verify_branch_name(name))})
|
|
213
219
|
step("isolation.py", "isolate-verify", "--repo", str(repo),
|
|
214
|
-
"--base", expected_head, "--name",
|
|
220
|
+
"--base", expected_head, "--name", name)
|
|
215
221
|
elif action == "build-evidence":
|
|
216
222
|
proof = step("build_state.py", "verify-landed", *common, "--head", expected_head)
|
|
217
223
|
# The landing proof has one canonical home; lean final-review reuse allows only this path.
|
|
@@ -257,6 +263,9 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
257
263
|
ruled.add(row[0])
|
|
258
264
|
if any(len(row) == 6 and row[4] == "NEEDS-USER" and row[0] not in ruled for row in queue):
|
|
259
265
|
raise StepFailed("DOCS-AUDIT.md has NEEDS-USER rows without a user ruling")
|
|
266
|
+
if kind == "intent":
|
|
267
|
+
# Malformed or incomplete spec-probe tables are not a pre-approvable draft.
|
|
268
|
+
step("check_handoffs.py", "intent", *common)
|
|
260
269
|
if kind == "plan":
|
|
261
270
|
if gate_plan()["mode"] != "off":
|
|
262
271
|
raise StepFailed("pre-approval requires review_panel off")
|
|
@@ -275,7 +284,8 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
275
284
|
if action == "approve-plan":
|
|
276
285
|
step("pipeline_state.py", "approve", *common, "--kind", "plan",
|
|
277
286
|
"--expected-head", expected_head)
|
|
278
|
-
except (StepFailed, OSError, subprocess.CalledProcessError,
|
|
287
|
+
except (StepFailed, isolation.IsolationError, OSError, subprocess.CalledProcessError,
|
|
288
|
+
ValueError, KeyError, TypeError) as error:
|
|
279
289
|
return {"status": "blocked", "reason": str(error), "steps": steps,
|
|
280
290
|
"next": next_step}
|
|
281
291
|
return {"status": "complete", "steps": steps, **outputs}
|