@opengsd/gsd-path 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -13
- package/CHANGELOG.md +68 -0
- package/DOCS.md +75 -6
- package/FULL.md +12 -15
- package/HOOKS.md +18 -9
- package/QUICK.md +8 -6
- package/README.md +218 -14
- package/RUNTIME.md +16 -5
- package/UPDATE.md +30 -11
- package/WORKFLOW.md +35 -16
- package/package.json +23 -1
- package/platforms/claude/dispatch.md +4 -4
- package/platforms/copilot/dispatch.md +4 -4
- package/platforms/cursor/dispatch.md +5 -10
- package/platforms/grok/dispatch.md +4 -6
- package/platforms/kimi/dispatch.md +4 -4
- package/platforms/kiro/dispatch.md +4 -6
- package/platforms/opencode/dispatch.md +4 -4
- package/platforms/qwen/dispatch.md +4 -4
- package/platforms/shared-agents/dispatch.md +12 -19
- package/scripts/_common.py +5 -0
- package/scripts/archive_milestone.py +30 -124
- package/scripts/bootstrap_repository.py +33 -4
- package/scripts/build_recovery.py +292 -0
- package/scripts/build_state.py +2 -0
- package/scripts/check_docs_audit.py +7 -3
- package/scripts/check_handoffs.py +7 -1
- package/scripts/check_task_briefs.py +3 -0
- package/scripts/detect_project.py +22 -3
- package/scripts/discussion_records.py +3 -0
- package/scripts/discussion_validate.py +8 -3
- package/scripts/dispatch_driver.py +233 -22
- package/scripts/git_guard.py +14 -5
- package/scripts/guard_hook.py +11 -1
- package/scripts/install.mjs +54 -1448
- package/scripts/install.py +155 -188
- package/scripts/integration.py +54 -28
- package/scripts/isolation.py +24 -6
- package/scripts/jev_review.py +138 -0
- package/scripts/lean_verification.py +3 -1
- package/scripts/loop_run.py +2 -0
- package/scripts/model_policy.py +279 -0
- package/scripts/path_config.py +248 -0
- package/scripts/pipeline_diagnose.py +15 -3
- package/scripts/pipeline_git.py +3 -0
- package/scripts/pipeline_state.py +117 -186
- package/scripts/pipeline_undo.py +14 -7
- package/scripts/promote_lookahead.py +33 -162
- package/scripts/review_panel.py +11 -0
- package/scripts/roadmap.py +326 -0
- package/scripts/runtime_store.py +278 -0
- package/scripts/skill-resources.json +182 -4
- package/scripts/state_checkpoint.py +114 -72
- package/scripts/state_promote.py +47 -44
- package/scripts/status_runtime.py +92 -6
- package/scripts/task_context.py +73 -0
- package/scripts/token_budget.py +40 -1
- package/scripts/workflow_run.py +128 -8
- package/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path/BUILD.md +54 -575
- package/skills/gsd-path/DECIDE.md +4 -6
- package/skills/gsd-path/DEFINE.md +23 -21
- package/skills/gsd-path/DOCS-AUDIT.md +2 -0
- package/skills/gsd-path/INSPECT.md +32 -8
- package/skills/gsd-path/PLAN.md +29 -19
- package/skills/gsd-path/RESEARCH.md +5 -6
- package/skills/gsd-path/ROADMAP.md +24 -58
- package/skills/gsd-path/SHIP.md +29 -17
- package/skills/gsd-path/SKILL.md +46 -22
- package/skills/gsd-path/references/build-abandon.md +33 -0
- package/skills/gsd-path/references/build-native.md +502 -0
- package/skills/gsd-path/references/build-recovery.md +78 -0
- package/skills/gsd-path/references/coder.md +8 -3
- package/skills/gsd-path/references/config.md +57 -0
- package/skills/gsd-path/references/dispatch.md +12 -19
- package/skills/gsd-path/references/docs-auditor.md +14 -0
- package/skills/gsd-path/references/jev-review.md +82 -0
- package/skills/gsd-path/references/model-policy.md +139 -0
- package/skills/gsd-path/references/reviewer-final.md +21 -0
- package/skills/gsd-path/references/reviewer-panel.md +17 -0
- package/skills/gsd-path/references/reviewer-skeptic.md +20 -0
- package/skills/gsd-path/references/reviewer-wave.md +89 -0
- package/skills/gsd-path/references/reviewer.md +12 -148
- package/skills/gsd-path/references/runtime-selection.md +25 -0
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/_common.py +5 -0
- package/skills/gsd-path/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path/scripts/bootstrap_repository.py +33 -4
- package/skills/gsd-path/scripts/build_recovery.py +292 -0
- package/skills/gsd-path/scripts/build_state.py +2 -0
- package/skills/gsd-path/scripts/check_docs_audit.py +7 -3
- package/skills/gsd-path/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path/scripts/detect_project.py +22 -3
- package/skills/gsd-path/scripts/discussion_records.py +3 -0
- package/skills/gsd-path/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path/scripts/dispatch_driver.py +233 -22
- package/skills/gsd-path/scripts/git_guard.py +768 -0
- package/skills/gsd-path/scripts/integration.py +54 -28
- package/skills/gsd-path/scripts/isolation.py +24 -6
- package/skills/gsd-path/scripts/jev_review.py +138 -0
- package/skills/gsd-path/scripts/lean_verification.py +3 -1
- package/skills/gsd-path/scripts/loop_run.py +2 -0
- package/skills/gsd-path/scripts/model_policy.py +279 -0
- package/skills/gsd-path/scripts/path_config.py +248 -0
- package/skills/gsd-path/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path/scripts/promote_lookahead.py +33 -162
- package/skills/gsd-path/scripts/review_panel.py +11 -0
- package/skills/gsd-path/scripts/roadmap.py +326 -0
- package/skills/gsd-path/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path/scripts/state_promote.py +47 -44
- package/skills/gsd-path/scripts/status_runtime.py +208 -0
- package/skills/gsd-path/scripts/task_context.py +73 -0
- package/skills/gsd-path/scripts/token_budget.py +40 -1
- package/skills/gsd-path/scripts/workflow_run.py +128 -8
- package/skills/gsd-path/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path/templates/skeptic.md +1 -1
- package/skills/gsd-path/templates/task.md +2 -0
- package/skills/gsd-path/templates/wave-panel.md +1 -1
- package/skills/gsd-path/templates/wave-review.md +1 -1
- package/skills/gsd-path-build/SKILL.md +54 -575
- package/skills/gsd-path-build/references/build-abandon.md +33 -0
- package/skills/gsd-path-build/references/build-native.md +502 -0
- package/skills/gsd-path-build/references/build-recovery.md +78 -0
- package/skills/gsd-path-build/references/coder.md +8 -3
- package/skills/gsd-path-build/references/dispatch.md +12 -19
- package/skills/gsd-path-build/references/jev-review.md +82 -0
- package/skills/gsd-path-build/references/model-policy.md +139 -0
- package/skills/gsd-path-build/references/reviewer-final.md +21 -0
- package/skills/gsd-path-build/references/reviewer-panel.md +17 -0
- package/skills/gsd-path-build/references/reviewer-skeptic.md +20 -0
- package/skills/gsd-path-build/references/reviewer-wave.md +89 -0
- package/skills/gsd-path-build/references/reviewer.md +12 -148
- package/skills/gsd-path-build/references/runtime-selection.md +25 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/_common.py +5 -0
- package/skills/gsd-path-build/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path-build/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-build/scripts/build_state.py +2 -0
- package/skills/gsd-path-build/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-build/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-build/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-build/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-build/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-build/scripts/dispatch_driver.py +233 -22
- package/skills/gsd-path-build/scripts/integration.py +54 -28
- package/skills/gsd-path-build/scripts/isolation.py +24 -6
- package/skills/gsd-path-build/scripts/jev_review.py +138 -0
- package/skills/gsd-path-build/scripts/loop_run.py +2 -0
- package/skills/gsd-path-build/scripts/model_policy.py +279 -0
- package/skills/gsd-path-build/scripts/path_config.py +248 -0
- package/skills/gsd-path-build/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-build/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-build/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-build/scripts/review_panel.py +11 -0
- package/skills/gsd-path-build/scripts/roadmap.py +326 -0
- package/skills/gsd-path-build/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-build/scripts/state_promote.py +47 -44
- package/skills/gsd-path-build/scripts/task_context.py +73 -0
- package/skills/gsd-path-build/scripts/token_budget.py +40 -1
- package/skills/gsd-path-build/scripts/workflow_run.py +128 -8
- package/skills/gsd-path-build/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-build/templates/skeptic.md +1 -1
- package/skills/gsd-path-build/templates/task.md +2 -0
- package/skills/gsd-path-build/templates/wave-panel.md +1 -1
- package/skills/gsd-path-build/templates/wave-review.md +1 -1
- package/skills/gsd-path-decide/SKILL.md +4 -6
- package/skills/gsd-path-decide/references/dispatch.md +12 -19
- package/skills/gsd-path-decide/references/model-policy.md +139 -0
- package/skills/gsd-path-decide/references/runtime-selection.md +25 -0
- package/skills/gsd-path-decide/scripts/_common.py +5 -0
- package/skills/gsd-path-decide/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-decide/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-decide/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-decide/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-decide/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-decide/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-decide/scripts/integration.py +1459 -0
- package/skills/gsd-path-decide/scripts/isolation.py +24 -6
- package/skills/gsd-path-decide/scripts/loop_run.py +839 -0
- package/skills/gsd-path-decide/scripts/model_policy.py +279 -0
- package/skills/gsd-path-decide/scripts/path_config.py +248 -0
- package/skills/gsd-path-decide/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-decide/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-decide/scripts/review_panel.py +564 -0
- package/skills/gsd-path-decide/scripts/roadmap.py +326 -0
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-decide/scripts/state_promote.py +47 -44
- package/skills/gsd-path-decide/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-define/SKILL.md +23 -21
- package/skills/gsd-path-define/references/build-recovery.md +78 -0
- package/skills/gsd-path-define/references/runtime-selection.md +25 -0
- package/skills/gsd-path-define/scripts/_common.py +5 -0
- package/skills/gsd-path-define/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-define/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-define/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-define/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-define/scripts/detect_project.py +22 -3
- package/skills/gsd-path-define/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-define/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-define/scripts/integration.py +1459 -0
- package/skills/gsd-path-define/scripts/isolation.py +24 -6
- package/skills/gsd-path-define/scripts/loop_run.py +839 -0
- package/skills/gsd-path-define/scripts/model_policy.py +279 -0
- package/skills/gsd-path-define/scripts/path_config.py +248 -0
- package/skills/gsd-path-define/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-define/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-define/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-define/scripts/review_panel.py +564 -0
- package/skills/gsd-path-define/scripts/roadmap.py +326 -0
- package/skills/gsd-path-define/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-define/scripts/state_promote.py +47 -44
- package/skills/gsd-path-define/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-discuss/SKILL.md +2 -0
- package/skills/gsd-path-discuss/references/runtime-selection.md +25 -0
- package/skills/gsd-path-discuss/scripts/_common.py +5 -0
- package/skills/gsd-path-discuss/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-discuss/scripts/loop_run.py +839 -0
- package/skills/gsd-path-discuss/scripts/model_policy.py +279 -0
- package/skills/gsd-path-discuss/scripts/path_config.py +248 -0
- package/skills/gsd-path-discuss/scripts/review_panel.py +564 -0
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-docs-audit/SKILL.md +2 -0
- package/skills/gsd-path-docs-audit/references/dispatch.md +12 -19
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +14 -0
- package/skills/gsd-path-docs-audit/references/model-policy.md +139 -0
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +25 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +7 -3
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-docs-audit/scripts/integration.py +1459 -0
- package/skills/gsd-path-docs-audit/scripts/isolation.py +24 -6
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +839 -0
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +279 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +248 -0
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +564 -0
- package/skills/gsd-path-docs-audit/scripts/roadmap.py +326 -0
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +47 -44
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-forensics/SKILL.md +9 -5
- package/skills/gsd-path-forensics/references/runtime-selection.md +25 -0
- package/skills/gsd-path-forensics/scripts/_common.py +5 -0
- package/skills/gsd-path-forensics/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-forensics/scripts/loop_run.py +839 -0
- package/skills/gsd-path-forensics/scripts/model_policy.py +279 -0
- package/skills/gsd-path-forensics/scripts/path_config.py +248 -0
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +15 -3
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +14 -7
- package/skills/gsd-path-forensics/scripts/review_panel.py +564 -0
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-inspect/SKILL.md +32 -8
- package/skills/gsd-path-inspect/references/dispatch.md +12 -19
- package/skills/gsd-path-inspect/references/docs-auditor.md +14 -0
- package/skills/gsd-path-inspect/references/model-policy.md +139 -0
- package/skills/gsd-path-inspect/references/runtime-selection.md +25 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/_common.py +5 -0
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-inspect/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +7 -3
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-inspect/scripts/detect_project.py +22 -3
- package/skills/gsd-path-inspect/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-inspect/scripts/integration.py +1459 -0
- package/skills/gsd-path-inspect/scripts/isolation.py +24 -6
- package/skills/gsd-path-inspect/scripts/loop_run.py +839 -0
- package/skills/gsd-path-inspect/scripts/model_policy.py +279 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +248 -0
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-inspect/scripts/review_panel.py +564 -0
- package/skills/gsd-path-inspect/scripts/roadmap.py +326 -0
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-inspect/scripts/state_promote.py +47 -44
- package/skills/gsd-path-inspect/scripts/workflow_run.py +237 -0
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-loop/SKILL.md +2 -0
- package/skills/gsd-path-loop/references/runtime-selection.md +25 -0
- package/skills/gsd-path-loop/scripts/_common.py +5 -0
- package/skills/gsd-path-loop/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-loop/scripts/loop_run.py +2 -0
- package/skills/gsd-path-loop/scripts/model_policy.py +279 -0
- package/skills/gsd-path-loop/scripts/path_config.py +248 -0
- package/skills/gsd-path-loop/scripts/review_panel.py +564 -0
- package/skills/gsd-path-loop/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-migrate/SKILL.md +2 -0
- package/skills/gsd-path-migrate/references/runtime-selection.md +25 -0
- package/skills/gsd-path-migrate/scripts/_common.py +185 -0
- package/skills/gsd-path-migrate/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-migrate/scripts/loop_run.py +839 -0
- package/skills/gsd-path-migrate/scripts/model_policy.py +279 -0
- package/skills/gsd-path-migrate/scripts/path_config.py +248 -0
- package/skills/gsd-path-migrate/scripts/review_panel.py +564 -0
- package/skills/gsd-path-plan/SKILL.md +29 -19
- package/skills/gsd-path-plan/references/build-recovery.md +78 -0
- package/skills/gsd-path-plan/references/dispatch.md +12 -19
- package/skills/gsd-path-plan/references/model-policy.md +139 -0
- package/skills/gsd-path-plan/references/runtime-selection.md +25 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/_common.py +5 -0
- package/skills/gsd-path-plan/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-plan/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-plan/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-plan/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-plan/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-plan/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-plan/scripts/integration.py +1459 -0
- package/skills/gsd-path-plan/scripts/isolation.py +24 -6
- package/skills/gsd-path-plan/scripts/loop_run.py +839 -0
- package/skills/gsd-path-plan/scripts/model_policy.py +279 -0
- package/skills/gsd-path-plan/scripts/path_config.py +248 -0
- package/skills/gsd-path-plan/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-plan/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-plan/scripts/review_panel.py +11 -0
- package/skills/gsd-path-plan/scripts/roadmap.py +326 -0
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-plan/scripts/state_promote.py +47 -44
- package/skills/gsd-path-plan/scripts/workflow_run.py +128 -8
- package/skills/gsd-path-plan/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-plan/templates/task.md +2 -0
- package/skills/gsd-path-research/SKILL.md +5 -6
- package/skills/gsd-path-research/references/dispatch.md +12 -19
- package/skills/gsd-path-research/references/model-policy.md +139 -0
- package/skills/gsd-path-research/references/runtime-selection.md +25 -0
- package/skills/gsd-path-research/scripts/_common.py +5 -0
- package/skills/gsd-path-research/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-research/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-research/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-research/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-research/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-research/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-research/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-research/scripts/integration.py +1459 -0
- package/skills/gsd-path-research/scripts/isolation.py +24 -6
- package/skills/gsd-path-research/scripts/loop_run.py +839 -0
- package/skills/gsd-path-research/scripts/model_policy.py +279 -0
- package/skills/gsd-path-research/scripts/path_config.py +248 -0
- package/skills/gsd-path-research/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-research/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-research/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-research/scripts/review_panel.py +564 -0
- package/skills/gsd-path-research/scripts/roadmap.py +326 -0
- package/skills/gsd-path-research/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-research/scripts/state_promote.py +47 -44
- package/skills/gsd-path-research/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-roadmap/SKILL.md +24 -58
- package/skills/gsd-path-roadmap/references/dispatch.md +12 -19
- package/skills/gsd-path-roadmap/references/model-policy.md +139 -0
- package/skills/gsd-path-roadmap/references/runtime-selection.md +25 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +5 -0
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-roadmap/scripts/detect_project.py +22 -3
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-roadmap/scripts/integration.py +54 -28
- package/skills/gsd-path-roadmap/scripts/isolation.py +24 -6
- package/skills/gsd-path-roadmap/scripts/loop_run.py +839 -0
- package/skills/gsd-path-roadmap/scripts/model_policy.py +279 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +248 -0
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +33 -162
- package/skills/gsd-path-roadmap/scripts/review_panel.py +11 -0
- package/skills/gsd-path-roadmap/scripts/roadmap.py +326 -0
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-roadmap/scripts/state_promote.py +47 -44
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-ship/SKILL.md +29 -17
- package/skills/gsd-path-ship/references/dispatch.md +12 -19
- package/skills/gsd-path-ship/references/jev-review.md +82 -0
- package/skills/gsd-path-ship/references/model-policy.md +139 -0
- package/skills/gsd-path-ship/references/reviewer-final.md +21 -0
- package/skills/gsd-path-ship/references/reviewer-panel.md +17 -0
- package/skills/gsd-path-ship/references/reviewer-skeptic.md +20 -0
- package/skills/gsd-path-ship/references/reviewer-wave.md +89 -0
- package/skills/gsd-path-ship/references/reviewer.md +12 -148
- package/skills/gsd-path-ship/references/runtime-selection.md +25 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/_common.py +5 -0
- package/skills/gsd-path-ship/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path-ship/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-ship/scripts/build_state.py +2 -0
- package/skills/gsd-path-ship/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-ship/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-ship/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-ship/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-ship/scripts/integration.py +54 -28
- package/skills/gsd-path-ship/scripts/isolation.py +24 -6
- package/skills/gsd-path-ship/scripts/jev_review.py +138 -0
- package/skills/gsd-path-ship/scripts/lean_verification.py +3 -1
- package/skills/gsd-path-ship/scripts/loop_run.py +839 -0
- package/skills/gsd-path-ship/scripts/model_policy.py +279 -0
- package/skills/gsd-path-ship/scripts/path_config.py +248 -0
- package/skills/gsd-path-ship/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-ship/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-ship/scripts/review_panel.py +11 -0
- package/skills/gsd-path-ship/scripts/roadmap.py +326 -0
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-ship/scripts/state_promote.py +47 -44
- package/skills/gsd-path-ship/scripts/workflow_run.py +128 -8
- package/skills/gsd-path-ship/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-ship/templates/wave-review.md +1 -1
- package/skills/gsd-path-undo/SKILL.md +2 -0
- package/skills/gsd-path-undo/references/runtime-selection.md +25 -0
- package/skills/gsd-path-undo/scripts/_common.py +5 -0
- package/skills/gsd-path-undo/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-undo/scripts/loop_run.py +839 -0
- package/skills/gsd-path-undo/scripts/model_policy.py +279 -0
- package/skills/gsd-path-undo/scripts/path_config.py +248 -0
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +14 -7
- package/skills/gsd-path-undo/scripts/review_panel.py +564 -0
- package/skills/gsd-path-undo/scripts/worktree_paths.py +94 -0
- package/skills/path/BUILD.md +54 -575
- package/skills/path/DECIDE.md +4 -6
- package/skills/path/DEFINE.md +23 -21
- package/skills/path/DOCS-AUDIT.md +2 -0
- package/skills/path/INSPECT.md +32 -8
- package/skills/path/PLAN.md +29 -19
- package/skills/path/RESEARCH.md +5 -6
- package/skills/path/ROADMAP.md +24 -58
- package/skills/path/SHIP.md +29 -17
- package/skills/path/SKILL.md +46 -22
- package/skills/path/references/build-abandon.md +33 -0
- package/skills/path/references/build-native.md +502 -0
- package/skills/path/references/build-recovery.md +78 -0
- package/skills/path/references/coder.md +8 -3
- package/skills/path/references/config.md +57 -0
- package/skills/path/references/dispatch.md +12 -19
- package/skills/path/references/docs-auditor.md +14 -0
- package/skills/path/references/jev-review.md +82 -0
- package/skills/path/references/model-policy.md +139 -0
- package/skills/path/references/reviewer-final.md +21 -0
- package/skills/path/references/reviewer-panel.md +17 -0
- package/skills/path/references/reviewer-skeptic.md +20 -0
- package/skills/path/references/reviewer-wave.md +89 -0
- package/skills/path/references/reviewer.md +12 -148
- package/skills/path/references/runtime-selection.md +25 -0
- package/skills/path/scripts/_common.py +5 -0
- package/skills/path/scripts/archive_milestone.py +30 -124
- package/skills/path/scripts/bootstrap_repository.py +33 -4
- package/skills/path/scripts/build_recovery.py +292 -0
- package/skills/path/scripts/build_state.py +2 -0
- package/skills/path/scripts/check_docs_audit.py +7 -3
- package/skills/path/scripts/check_handoffs.py +7 -1
- package/skills/path/scripts/check_task_briefs.py +3 -0
- package/skills/path/scripts/detect_project.py +22 -3
- package/skills/path/scripts/discussion_records.py +3 -0
- package/skills/path/scripts/discussion_validate.py +8 -3
- package/skills/path/scripts/dispatch_driver.py +233 -22
- package/skills/path/scripts/git_guard.py +768 -0
- package/skills/path/scripts/integration.py +54 -28
- package/skills/path/scripts/isolation.py +24 -6
- package/skills/path/scripts/jev_review.py +138 -0
- package/skills/path/scripts/lean_verification.py +3 -1
- package/skills/path/scripts/loop_run.py +2 -0
- package/skills/path/scripts/model_policy.py +279 -0
- package/skills/path/scripts/path_config.py +248 -0
- package/skills/path/scripts/pipeline_git.py +3 -0
- package/skills/path/scripts/pipeline_state.py +117 -186
- package/skills/path/scripts/promote_lookahead.py +33 -162
- package/skills/path/scripts/review_panel.py +11 -0
- package/skills/path/scripts/roadmap.py +326 -0
- package/skills/path/scripts/state_checkpoint.py +114 -72
- package/skills/path/scripts/state_promote.py +47 -44
- package/skills/path/scripts/status_runtime.py +208 -0
- package/skills/path/scripts/task_context.py +73 -0
- package/skills/path/scripts/token_budget.py +40 -1
- package/skills/path/scripts/workflow_run.py +128 -8
- package/skills/path/scripts/worktree_paths.py +94 -0
- package/skills/path/templates/skeptic.md +1 -1
- package/skills/path/templates/task.md +2 -0
- package/skills/path/templates/wave-panel.md +1 -1
- package/skills/path/templates/wave-review.md +1 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# gsd-path project runtime
|
|
3
|
+
"""Interpret Roadmap text for selection, approval, promotion and milestone close."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
# Runtime helpers must not modify their immutable installation.
|
|
9
|
+
sys.dont_write_bytecode = True
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RoadmapError(RuntimeError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NoEligibleMilestone(RoadmapError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ROADMAP_HEADING_RE = re.compile(r"^### (M\d{3,}) — (.+)$")
|
|
24
|
+
STRICT_HEADING_RE = re.compile(r"^### (M\d{3,}) — ([a-z0-9][a-z0-9-]*)\s*$")
|
|
25
|
+
MUTABLE_ROADMAP_FIELDS_RE = re.compile(r"^(Status|Archive|Integrated):")
|
|
26
|
+
|
|
27
|
+
def normalized_slug(value: str) -> str:
|
|
28
|
+
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
|
29
|
+
return slug or "project"
|
|
30
|
+
|
|
31
|
+
def roadmap_blocks(content: str) -> list[dict]:
|
|
32
|
+
lines = content.splitlines(keepends=True)
|
|
33
|
+
headings = []
|
|
34
|
+
for index, line in enumerate(lines):
|
|
35
|
+
match = ROADMAP_HEADING_RE.fullmatch(line.rstrip("\r\n"))
|
|
36
|
+
if match:
|
|
37
|
+
headings.append((index, match.group(1), match.group(2).strip()))
|
|
38
|
+
blocks = []
|
|
39
|
+
for position, (start, milestone_id, title) in enumerate(headings):
|
|
40
|
+
end = headings[position + 1][0] if position + 1 < len(headings) else len(lines)
|
|
41
|
+
fields = {}
|
|
42
|
+
for index in range(start + 1, end):
|
|
43
|
+
match = re.match(
|
|
44
|
+
r"^(Status|Archive|Integrated|Depends on):\s*([^#]*?)\s*(?:#.*)?$",
|
|
45
|
+
lines[index].rstrip("\r\n"),
|
|
46
|
+
)
|
|
47
|
+
if match:
|
|
48
|
+
if match.group(1) in fields:
|
|
49
|
+
raise RoadmapError(
|
|
50
|
+
f"roadmap milestone {milestone_id} has duplicate "
|
|
51
|
+
f"{match.group(1)} field"
|
|
52
|
+
)
|
|
53
|
+
fields[match.group(1)] = (index, match.group(2).strip())
|
|
54
|
+
blocks.append(
|
|
55
|
+
{
|
|
56
|
+
"id": milestone_id,
|
|
57
|
+
"title": title,
|
|
58
|
+
"start": start,
|
|
59
|
+
"end": end,
|
|
60
|
+
"fields": fields,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
return blocks
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def milestone_block(content: str, milestone: str) -> dict:
|
|
67
|
+
normalized = normalized_slug(milestone)
|
|
68
|
+
matches = [
|
|
69
|
+
block
|
|
70
|
+
for block in roadmap_blocks(content)
|
|
71
|
+
if normalized_slug(block["title"]) == normalized
|
|
72
|
+
]
|
|
73
|
+
if len(matches) != 1:
|
|
74
|
+
raise RoadmapError(f"ROADMAP.md must contain exactly one entry for {milestone}")
|
|
75
|
+
return matches[0]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def dependency_ids(block: dict) -> list[str]:
|
|
79
|
+
dependencies = block["fields"].get("Depends on")
|
|
80
|
+
if dependencies is None:
|
|
81
|
+
raise RoadmapError(f"roadmap milestone {block['id']} lacks dependencies")
|
|
82
|
+
dependency_list = re.fullmatch(
|
|
83
|
+
r"\[\s*(M\d{3,}(?:\s*,\s*M\d{3,})*)?\s*\]",
|
|
84
|
+
dependencies[1],
|
|
85
|
+
)
|
|
86
|
+
if dependency_list is None:
|
|
87
|
+
raise RoadmapError(f"roadmap milestone {block['id']} has invalid dependencies")
|
|
88
|
+
return re.findall(r"M\d{3,}", dependency_list.group(1) or "")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def next_eligible_pending(content: str, active_milestone: Optional[str] = None) -> dict:
|
|
92
|
+
blocks = roadmap_blocks(content)
|
|
93
|
+
blocks_by_id = {block["id"]: block for block in blocks}
|
|
94
|
+
if len(blocks_by_id) != len(blocks):
|
|
95
|
+
raise RoadmapError("ROADMAP.md milestone ids must be unique")
|
|
96
|
+
active_id = None
|
|
97
|
+
if active_milestone is not None:
|
|
98
|
+
active = milestone_block(content, active_milestone)
|
|
99
|
+
if active["fields"].get("Status", (-1, ""))[1] != "active":
|
|
100
|
+
raise RoadmapError(f"roadmap milestone {active_milestone} is not active")
|
|
101
|
+
active_id = active["id"]
|
|
102
|
+
for block in blocks:
|
|
103
|
+
if block["fields"].get("Status", (-1, ""))[1] != "pending":
|
|
104
|
+
continue
|
|
105
|
+
dependencies = dependency_ids(block)
|
|
106
|
+
if all(
|
|
107
|
+
dependency in blocks_by_id
|
|
108
|
+
and (
|
|
109
|
+
blocks_by_id[dependency]["fields"].get("Status", (-1, ""))[1]
|
|
110
|
+
== "shipped"
|
|
111
|
+
or dependency == active_id
|
|
112
|
+
)
|
|
113
|
+
for dependency in dependencies
|
|
114
|
+
):
|
|
115
|
+
return block
|
|
116
|
+
raise NoEligibleMilestone("ROADMAP.md has no dependency-ready pending milestone")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def selection_payload(selected: dict) -> dict:
|
|
120
|
+
milestone = normalized_slug(selected["title"])
|
|
121
|
+
return {
|
|
122
|
+
"status": "selected",
|
|
123
|
+
"id": selected["id"],
|
|
124
|
+
"milestone": milestone,
|
|
125
|
+
"branch": f"gsd-path/{selected['id']}",
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def select_lookahead(content: str, active_milestone: Optional[str] = None) -> dict:
|
|
130
|
+
try:
|
|
131
|
+
selected = next_eligible_pending(content, active_milestone)
|
|
132
|
+
except NoEligibleMilestone:
|
|
133
|
+
blocks = roadmap_blocks(content)
|
|
134
|
+
if blocks and all(
|
|
135
|
+
block["fields"].get("Status", (-1, ""))[1]
|
|
136
|
+
in {"shipped", "abandoned"}
|
|
137
|
+
for block in blocks
|
|
138
|
+
):
|
|
139
|
+
return {"status": "complete"}
|
|
140
|
+
return {"status": "none"}
|
|
141
|
+
return selection_payload(selected)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def roadmap_contract(content: str, milestone: str) -> tuple[str, ...]:
|
|
145
|
+
block = milestone_block(content, milestone)
|
|
146
|
+
lines = content.splitlines()
|
|
147
|
+
return tuple(
|
|
148
|
+
line.rstrip()
|
|
149
|
+
for line in lines[block["start"] : block["end"]]
|
|
150
|
+
if not MUTABLE_ROADMAP_FIELDS_RE.match(line)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def compare_roadmap_entry(
|
|
155
|
+
before: str,
|
|
156
|
+
after: str,
|
|
157
|
+
milestone: str,
|
|
158
|
+
active_milestone: Optional[str] = None,
|
|
159
|
+
) -> dict:
|
|
160
|
+
before_contract = roadmap_contract(before, milestone)
|
|
161
|
+
try:
|
|
162
|
+
after_contract = roadmap_contract(after, milestone)
|
|
163
|
+
selected = milestone_block(after, milestone)
|
|
164
|
+
eligible = next_eligible_pending(after, active_milestone)
|
|
165
|
+
except RoadmapError:
|
|
166
|
+
after_contract = ()
|
|
167
|
+
selected = None
|
|
168
|
+
eligible = None
|
|
169
|
+
status = (
|
|
170
|
+
"unchanged"
|
|
171
|
+
if before_contract == after_contract
|
|
172
|
+
and selected is not None
|
|
173
|
+
and eligible is not None
|
|
174
|
+
and selected["id"] == eligible["id"]
|
|
175
|
+
else "changed"
|
|
176
|
+
)
|
|
177
|
+
return {"status": status, "milestone": milestone}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def strict_sections(text: str) -> dict[str, tuple[str, int, int]]:
|
|
181
|
+
lines = text.splitlines(keepends=True)
|
|
182
|
+
headings: list[tuple[str, str, int]] = []
|
|
183
|
+
for index, line in enumerate(lines):
|
|
184
|
+
match = STRICT_HEADING_RE.fullmatch(line.rstrip("\r\n"))
|
|
185
|
+
if match:
|
|
186
|
+
if int(match.group(1).removeprefix("M")) < 1:
|
|
187
|
+
raise RoadmapError(f"ROADMAP.md has invalid milestone id: {match.group(1)}")
|
|
188
|
+
headings.append((match.group(1), match.group(2), index))
|
|
189
|
+
sections: dict[str, tuple[str, int, int]] = {}
|
|
190
|
+
for position, (milestone_id, slug, start) in enumerate(headings):
|
|
191
|
+
end = headings[position + 1][2] if position + 1 < len(headings) else len(lines)
|
|
192
|
+
if slug in sections:
|
|
193
|
+
raise RoadmapError(f"ROADMAP.md repeats milestone slug: {slug}")
|
|
194
|
+
sections[slug] = (milestone_id, start, end)
|
|
195
|
+
return sections
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def replace_field(
|
|
199
|
+
lines: list[str],
|
|
200
|
+
start: int,
|
|
201
|
+
end: int,
|
|
202
|
+
field: str,
|
|
203
|
+
expected: set[str],
|
|
204
|
+
value: str,
|
|
205
|
+
) -> None:
|
|
206
|
+
found: Optional[int] = None
|
|
207
|
+
for index in range(start, end):
|
|
208
|
+
raw = lines[index].rstrip("\r\n")
|
|
209
|
+
ending = lines[index][len(raw) :]
|
|
210
|
+
match = re.match(rf"^({re.escape(field)}:\s*)(\S+)(.*)$", raw)
|
|
211
|
+
if match is None:
|
|
212
|
+
continue
|
|
213
|
+
if found is not None:
|
|
214
|
+
raise RoadmapError(f"ROADMAP.md repeats {field} in one milestone")
|
|
215
|
+
if match.group(2) not in expected:
|
|
216
|
+
raise RoadmapError(
|
|
217
|
+
f"ROADMAP.md {field} is {match.group(2)}, expected {sorted(expected)}"
|
|
218
|
+
)
|
|
219
|
+
lines[index] = f"{match.group(1)}{value}{match.group(3)}{ending}"
|
|
220
|
+
found = index
|
|
221
|
+
if found is None:
|
|
222
|
+
raise RoadmapError(f"ROADMAP.md milestone is missing {field}")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def milestone_status(lines: list[str], start: int, end: int) -> str:
|
|
226
|
+
values = []
|
|
227
|
+
for line in lines[start:end]:
|
|
228
|
+
match = re.fullmatch(r"Status:\s*(\S+)\s*", line.rstrip("\r\n"))
|
|
229
|
+
if match:
|
|
230
|
+
values.append(match.group(1))
|
|
231
|
+
if len(values) != 1:
|
|
232
|
+
raise RoadmapError("ROADMAP.md milestone must have one Status field")
|
|
233
|
+
return values[0]
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def activate_milestone(text: str, milestone: str) -> str:
|
|
237
|
+
sections = strict_sections(text)
|
|
238
|
+
if milestone not in sections:
|
|
239
|
+
raise RoadmapError(f"ROADMAP.md is missing selected milestone: {milestone}")
|
|
240
|
+
lines = text.splitlines(keepends=True)
|
|
241
|
+
active = [
|
|
242
|
+
slug
|
|
243
|
+
for slug, (_, start, end) in sections.items()
|
|
244
|
+
if milestone_status(lines, start, end) == "active"
|
|
245
|
+
]
|
|
246
|
+
if active:
|
|
247
|
+
raise RoadmapError(
|
|
248
|
+
"ROADMAP.md already has an active milestone: " + ", ".join(active)
|
|
249
|
+
)
|
|
250
|
+
_, start, end = sections[milestone]
|
|
251
|
+
replace_field(lines, start, end, "Status", {"pending"}, "active")
|
|
252
|
+
return "".join(lines)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
|
|
256
|
+
old_blocks = roadmap_blocks(before)
|
|
257
|
+
before_lines = before.splitlines(keepends=True)
|
|
258
|
+
candidate_lines = candidate.splitlines(keepends=True)
|
|
259
|
+
for block in old_blocks:
|
|
260
|
+
status = block["fields"].get("Status", (-1, ""))[1]
|
|
261
|
+
if status not in {"active", "shipped", "abandoned"}:
|
|
262
|
+
continue
|
|
263
|
+
slug = normalized_slug(block["title"])
|
|
264
|
+
new = milestone_block(candidate, slug)
|
|
265
|
+
if status == "shipped":
|
|
266
|
+
changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
|
|
267
|
+
else:
|
|
268
|
+
changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
|
|
269
|
+
if changed:
|
|
270
|
+
raise RoadmapError(f"{status} roadmap entry changed: {slug}")
|
|
271
|
+
if phase in {"inspect", "define"}:
|
|
272
|
+
active = [b for b in roadmap_blocks(candidate) if b["fields"].get("Status", (-1, ""))[1] == "active"]
|
|
273
|
+
if len(active) != 1 or selection_payload(active[0])["milestone"] != selected:
|
|
274
|
+
raise RoadmapError("roadmap re-slice must retain the active entry")
|
|
275
|
+
return candidate
|
|
276
|
+
if not any(b["fields"].get("Status", (-1, ""))[1] == "abandoned" for b in old_blocks):
|
|
277
|
+
raise RoadmapError("post-abandon re-slice requires an abandoned milestone")
|
|
278
|
+
selection = select_lookahead(candidate)
|
|
279
|
+
if selection.get("milestone") != selected:
|
|
280
|
+
raise RoadmapError("roadmap re-slice must use the selected dependency-ready milestone")
|
|
281
|
+
return activate_milestone(candidate, selected)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def has_questions(text: str, milestone: Optional[str] = None) -> bool:
|
|
285
|
+
"""Report open questions for the active entry, or for ``milestone`` when given."""
|
|
286
|
+
lines = text.splitlines()
|
|
287
|
+
active_section: Optional[list[str]] = None
|
|
288
|
+
for index, line in enumerate(lines):
|
|
289
|
+
heading = STRICT_HEADING_RE.fullmatch(line)
|
|
290
|
+
if heading is None:
|
|
291
|
+
continue
|
|
292
|
+
if int(heading.group(1).removeprefix("M")) < 1:
|
|
293
|
+
raise RoadmapError(
|
|
294
|
+
f"ROADMAP.md has invalid milestone id: {heading.group(1)}"
|
|
295
|
+
)
|
|
296
|
+
end = next(
|
|
297
|
+
(
|
|
298
|
+
cursor
|
|
299
|
+
for cursor in range(index + 1, len(lines))
|
|
300
|
+
if STRICT_HEADING_RE.fullmatch(lines[cursor]) is not None
|
|
301
|
+
),
|
|
302
|
+
len(lines),
|
|
303
|
+
)
|
|
304
|
+
section = lines[index:end]
|
|
305
|
+
if milestone is not None:
|
|
306
|
+
if heading.group(2) == milestone:
|
|
307
|
+
active_section = section
|
|
308
|
+
continue
|
|
309
|
+
if any(re.fullmatch(r"Status:\s*active(?:\s+#.*)?", item) for item in section):
|
|
310
|
+
if active_section is not None:
|
|
311
|
+
raise RoadmapError("ROADMAP.md has more than one active milestone")
|
|
312
|
+
active_section = section
|
|
313
|
+
if active_section is None:
|
|
314
|
+
if milestone is not None:
|
|
315
|
+
raise RoadmapError(f"ROADMAP.md is missing lookahead milestone: {milestone}")
|
|
316
|
+
raise RoadmapError("ROADMAP.md has no active milestone")
|
|
317
|
+
try:
|
|
318
|
+
start = active_section.index("Open questions") + 1
|
|
319
|
+
except ValueError as error:
|
|
320
|
+
raise RoadmapError("active roadmap milestone has no Open questions") from error
|
|
321
|
+
questions = [
|
|
322
|
+
line[2:].strip()
|
|
323
|
+
for line in active_section[start:]
|
|
324
|
+
if line.startswith("- ")
|
|
325
|
+
]
|
|
326
|
+
return any(question.lower() != "none" for question in questions)
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# Runtime helpers must not modify their immutable installation.
|
|
10
|
+
sys.dont_write_bytecode = True
|
|
11
|
+
|
|
7
12
|
import hashlib
|
|
8
13
|
import json
|
|
9
14
|
import os
|
|
@@ -20,6 +25,43 @@ except ModuleNotFoundError as error: # pragma: no cover - package imports used
|
|
|
20
25
|
from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
|
|
21
26
|
|
|
22
27
|
|
|
28
|
+
if __package__:
|
|
29
|
+
from . import roadmap
|
|
30
|
+
else:
|
|
31
|
+
import roadmap
|
|
32
|
+
|
|
33
|
+
if __package__: # imported as scripts.state_checkpoint
|
|
34
|
+
from . import pipeline_state
|
|
35
|
+
from .pipeline_state import (
|
|
36
|
+
CHECKPOINT_JOURNAL_NAME,
|
|
37
|
+
CHECKPOINT_KINDS,
|
|
38
|
+
CHECKPOINT_SCHEMA,
|
|
39
|
+
PLAN_APPROVAL_SUBJECT,
|
|
40
|
+
PROMOTION_TRACKS,
|
|
41
|
+
SLUG_RE,
|
|
42
|
+
TASK_FILE_RE,
|
|
43
|
+
TASK_ID_RE,
|
|
44
|
+
ApprovedTaskContract,
|
|
45
|
+
PipelineState,
|
|
46
|
+
PipelineStateError,
|
|
47
|
+
)
|
|
48
|
+
else: # standalone script or sibling import
|
|
49
|
+
import pipeline_state
|
|
50
|
+
from pipeline_state import (
|
|
51
|
+
CHECKPOINT_JOURNAL_NAME,
|
|
52
|
+
CHECKPOINT_KINDS,
|
|
53
|
+
CHECKPOINT_SCHEMA,
|
|
54
|
+
PLAN_APPROVAL_SUBJECT,
|
|
55
|
+
PROMOTION_TRACKS,
|
|
56
|
+
SLUG_RE,
|
|
57
|
+
TASK_FILE_RE,
|
|
58
|
+
TASK_ID_RE,
|
|
59
|
+
ApprovedTaskContract,
|
|
60
|
+
PipelineState,
|
|
61
|
+
PipelineStateError,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
23
65
|
def _sha256(content: str) -> str:
|
|
24
66
|
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
25
67
|
|
|
@@ -65,41 +107,29 @@ def _checkpoint_artifact_digest(project: Path, mutable_paths: set[str]) -> str:
|
|
|
65
107
|
return digest.hexdigest()
|
|
66
108
|
|
|
67
109
|
|
|
68
|
-
def _roadmap_status(lines: list[str], start: int, end: int) -> str:
|
|
69
|
-
values = []
|
|
70
|
-
for line in lines[start:end]:
|
|
71
|
-
match = re.fullmatch(r"Status:\s*(\S+)\s*", line.rstrip("\r\n"))
|
|
72
|
-
if match:
|
|
73
|
-
values.append(match.group(1))
|
|
74
|
-
if len(values) != 1:
|
|
75
|
-
raise PipelineStateError("ROADMAP.md milestone must have one Status field")
|
|
76
|
-
return values[0]
|
|
77
|
-
|
|
78
|
-
|
|
79
110
|
def _activate_roadmap_milestone(text: str, milestone: str) -> str:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
active = [
|
|
85
|
-
slug
|
|
86
|
-
for slug, (_, start, end) in sections.items()
|
|
87
|
-
if _roadmap_status(lines, start, end) == "active"
|
|
88
|
-
]
|
|
89
|
-
if active:
|
|
90
|
-
raise PipelineStateError(
|
|
91
|
-
"ROADMAP.md already has an active milestone: " + ", ".join(active)
|
|
92
|
-
)
|
|
93
|
-
_, start, end = sections[milestone]
|
|
94
|
-
pipeline_state._replace_roadmap_field(lines, start, end, "Status", {"pending"}, "active")
|
|
95
|
-
return "".join(lines)
|
|
96
|
-
|
|
111
|
+
try:
|
|
112
|
+
return roadmap.activate_milestone(text, milestone)
|
|
113
|
+
except roadmap.RoadmapError as error:
|
|
114
|
+
raise PipelineStateError(str(error)) from error
|
|
97
115
|
|
|
98
116
|
def _approval_details(
|
|
99
117
|
kind: str,
|
|
100
118
|
state: PipelineState,
|
|
101
119
|
selected_milestone: Optional[str],
|
|
102
120
|
) -> tuple[dict[str, Optional[str]], str, str, str]:
|
|
121
|
+
if kind == "roadmap-reslice":
|
|
122
|
+
if state.status != "active" or state.archive is not None:
|
|
123
|
+
raise PipelineStateError("roadmap re-slice requires active, unarchived state")
|
|
124
|
+
if state.phase in {"inspect", "define"} and selected_milestone == state.milestone and state.milestone:
|
|
125
|
+
changes = {}
|
|
126
|
+
event = "program roadmap re-slice approved"
|
|
127
|
+
elif state.phase == "roadmap" and state.milestone is None and selected_milestone and SLUG_RE.fullmatch(selected_milestone):
|
|
128
|
+
changes = {"phase": "inspect", "milestone": selected_milestone}
|
|
129
|
+
event = "roadmap approved after abandoned milestone"
|
|
130
|
+
else:
|
|
131
|
+
raise PipelineStateError("roadmap re-slice must preserve the active milestone or select after abandon")
|
|
132
|
+
return changes, event, "roadmap: program roadmap approved", "Why: approved roadmap checkpoint"
|
|
103
133
|
if kind == "plan":
|
|
104
134
|
if selected_milestone is not None:
|
|
105
135
|
raise PipelineStateError("plan approval does not accept a selected milestone")
|
|
@@ -126,6 +156,12 @@ def _approval_details(
|
|
|
126
156
|
raise PipelineStateError(f"unsupported approval kind: {kind}")
|
|
127
157
|
|
|
128
158
|
|
|
159
|
+
def _reslice_roadmap(before: str, candidate: str, state: PipelineState, selected: str) -> str:
|
|
160
|
+
try:
|
|
161
|
+
return roadmap.reslice(before, candidate, state.phase, selected)
|
|
162
|
+
except roadmap.RoadmapError as error:
|
|
163
|
+
raise PipelineStateError(str(error)) from error
|
|
164
|
+
|
|
129
165
|
def _checkpoint_request_fields(
|
|
130
166
|
kind: str,
|
|
131
167
|
project_dir: str,
|
|
@@ -175,6 +211,10 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
|
|
|
175
211
|
"roadmap_before",
|
|
176
212
|
"roadmap_after",
|
|
177
213
|
}
|
|
214
|
+
if journal.get("kind") == "roadmap-reslice":
|
|
215
|
+
expected_keys.add("baseline_before")
|
|
216
|
+
if not isinstance(journal.get("baseline_before"), str):
|
|
217
|
+
raise PipelineStateError("checkpoint journal has an invalid re-slice baseline")
|
|
178
218
|
if set(journal) != expected_keys:
|
|
179
219
|
raise PipelineStateError("checkpoint journal has invalid fields")
|
|
180
220
|
if journal.get("schema") != CHECKPOINT_SCHEMA or journal.get("repo") != str(repo):
|
|
@@ -253,12 +293,14 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
|
|
|
253
293
|
or journal["state_after"] != rendered
|
|
254
294
|
):
|
|
255
295
|
raise PipelineStateError("checkpoint journal approval contract is invalid")
|
|
256
|
-
if
|
|
296
|
+
if journal["kind"] in {"roadmap", "roadmap-reslice"}:
|
|
257
297
|
if project_dir != ".project":
|
|
258
298
|
raise PipelineStateError("roadmap approval is legal only on the active track")
|
|
259
299
|
if journal["roadmap_path"] != ".project/ROADMAP.md":
|
|
260
300
|
raise PipelineStateError("checkpoint journal has an invalid ROADMAP path")
|
|
261
|
-
expected_roadmap =
|
|
301
|
+
expected_roadmap = _reslice_roadmap(
|
|
302
|
+
str(journal["baseline_before"]), str(journal["roadmap_before"]), before, str(selected),
|
|
303
|
+
) if journal["kind"] == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
262
304
|
str(journal["roadmap_before"]),
|
|
263
305
|
str(selected),
|
|
264
306
|
)
|
|
@@ -307,6 +349,12 @@ def _resume_checkpoint_locked(
|
|
|
307
349
|
transaction = _approval_journal(repo, journal)
|
|
308
350
|
state_path = _checkpoint_file(repo, str(transaction["state_path"]), "STATE.md")
|
|
309
351
|
mutable = {state_path.relative_to(project).as_posix()}
|
|
352
|
+
baseline = project / "ROADMAP.before-reslice.md" if transaction["kind"] == "roadmap-reslice" else None
|
|
353
|
+
if baseline is not None:
|
|
354
|
+
mutable.add(baseline.name)
|
|
355
|
+
if baseline.exists() or baseline.is_symlink():
|
|
356
|
+
if pipeline_state._read_real_file(baseline, "re-slice baseline") != transaction["baseline_before"]:
|
|
357
|
+
raise PipelineStateError("re-slice baseline drifted during checkpoint recovery")
|
|
310
358
|
roadmap_path: Optional[Path] = None
|
|
311
359
|
if transaction["roadmap_path"] is not None:
|
|
312
360
|
roadmap_path = _checkpoint_file(
|
|
@@ -354,6 +402,8 @@ def _resume_checkpoint_locked(
|
|
|
354
402
|
raise PipelineStateError("completed checkpoint has the wrong ROADMAP.md")
|
|
355
403
|
|
|
356
404
|
try:
|
|
405
|
+
if baseline is not None and baseline.exists():
|
|
406
|
+
baseline.unlink()
|
|
357
407
|
committed = isolation_checkpoint(
|
|
358
408
|
repo,
|
|
359
409
|
expected_head,
|
|
@@ -362,6 +412,8 @@ def _resume_checkpoint_locked(
|
|
|
362
412
|
[".project"],
|
|
363
413
|
)
|
|
364
414
|
except IsolationError as error:
|
|
415
|
+
if baseline is not None and pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip() == expected_head:
|
|
416
|
+
pipeline_state._atomic_write(baseline, str(transaction["baseline_before"]))
|
|
365
417
|
raise PipelineStateError(f"approval checkpoint failed: {error}") from error
|
|
366
418
|
if committed.get("commit") != pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip():
|
|
367
419
|
raise PipelineStateError("approval checkpoint returned the wrong commit")
|
|
@@ -381,6 +433,8 @@ def _resume_checkpoint_locked(
|
|
|
381
433
|
def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
|
|
382
434
|
if kind != "plan":
|
|
383
435
|
return
|
|
436
|
+
if project_dir == ".project":
|
|
437
|
+
pipeline_state._build_recovery().validate_plan(repo)
|
|
384
438
|
head = pipeline_state._run_git(repo, "rev-parse", "--verify", "HEAD", check=False)
|
|
385
439
|
if head.returncode != 0:
|
|
386
440
|
# Pre-Git approval defers base-dependent checks to build.
|
|
@@ -476,6 +530,8 @@ def checkpoint_approval(
|
|
|
476
530
|
state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
|
|
477
531
|
changes, event, subject, body = _approval_details(kind, state, selected_milestone)
|
|
478
532
|
_validate_plan_briefs(resolved, kind, project_dir)
|
|
533
|
+
if kind == "plan" and project_dir == ".project":
|
|
534
|
+
pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
479
535
|
expected = {
|
|
480
536
|
"phase": state.phase,
|
|
481
537
|
"status": state.status,
|
|
@@ -497,13 +553,18 @@ def checkpoint_approval(
|
|
|
497
553
|
roadmap_path: Optional[Path] = None
|
|
498
554
|
roadmap_before: Optional[str] = None
|
|
499
555
|
roadmap_after: Optional[str] = None
|
|
500
|
-
|
|
556
|
+
baseline_before = None
|
|
557
|
+
if kind in {"roadmap", "roadmap-reslice"}:
|
|
501
558
|
if project_dir != ".project":
|
|
502
559
|
raise PipelineStateError("roadmap approval is legal only on the active track")
|
|
503
560
|
assert selected_milestone is not None
|
|
504
561
|
roadmap_path = project / "ROADMAP.md"
|
|
505
562
|
roadmap_before = pipeline_state._read_real_file(roadmap_path, "ROADMAP.md")
|
|
506
|
-
|
|
563
|
+
if kind == "roadmap-reslice":
|
|
564
|
+
baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
|
|
565
|
+
roadmap_after = _reslice_roadmap(
|
|
566
|
+
baseline_before, roadmap_before, state, selected_milestone,
|
|
567
|
+
) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
507
568
|
roadmap_before,
|
|
508
569
|
selected_milestone,
|
|
509
570
|
)
|
|
@@ -512,6 +573,8 @@ def checkpoint_approval(
|
|
|
512
573
|
mutable = {state_relative}
|
|
513
574
|
if roadmap_path is not None:
|
|
514
575
|
mutable.add(roadmap_path.relative_to(project).as_posix())
|
|
576
|
+
if baseline_before is not None:
|
|
577
|
+
mutable.add("ROADMAP.before-reslice.md")
|
|
515
578
|
journal: dict[str, object] = {
|
|
516
579
|
"schema": CHECKPOINT_SCHEMA,
|
|
517
580
|
"repo": str(resolved),
|
|
@@ -528,6 +591,8 @@ def checkpoint_approval(
|
|
|
528
591
|
"roadmap_before": roadmap_before,
|
|
529
592
|
"roadmap_after": roadmap_after,
|
|
530
593
|
}
|
|
594
|
+
if baseline_before is not None:
|
|
595
|
+
journal["baseline_before"] = baseline_before
|
|
531
596
|
pipeline_state._write_json(journal_path, journal)
|
|
532
597
|
return _resume_checkpoint_locked(resolved, project, journal_path, journal)
|
|
533
598
|
|
|
@@ -576,6 +641,10 @@ def defer_approval(
|
|
|
576
641
|
with pipeline_state._state_lock(project):
|
|
577
642
|
state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
|
|
578
643
|
changes, event, _, _ = _approval_details(kind, state, selected_milestone)
|
|
644
|
+
if project_dir == ".project":
|
|
645
|
+
recovery = pipeline_state._build_recovery().context(resolved, state_before)
|
|
646
|
+
if recovery and recovery["active"]:
|
|
647
|
+
raise PipelineStateError("build recovery requires a plan checkpoint")
|
|
579
648
|
_validate_plan_briefs(resolved, kind, project_dir)
|
|
580
649
|
if patch:
|
|
581
650
|
event = "patch plan approved"
|
|
@@ -595,17 +664,25 @@ def defer_approval(
|
|
|
595
664
|
project_dir,
|
|
596
665
|
approval_kind=kind,
|
|
597
666
|
)
|
|
598
|
-
|
|
667
|
+
baseline = None
|
|
668
|
+
if kind in {"roadmap", "roadmap-reslice"}:
|
|
599
669
|
if project_dir != ".project":
|
|
600
670
|
raise PipelineStateError("roadmap approval is legal only on the active track")
|
|
601
671
|
assert selected_milestone is not None
|
|
602
672
|
roadmap_path = project / "ROADMAP.md"
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
673
|
+
roadmap_before = pipeline_state._read_real_file(roadmap_path, "ROADMAP.md")
|
|
674
|
+
if kind == "roadmap-reslice":
|
|
675
|
+
baseline = project / "ROADMAP.before-reslice.md"
|
|
676
|
+
roadmap_after = _reslice_roadmap(
|
|
677
|
+
pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
678
|
+
roadmap_before, state, selected_milestone,
|
|
679
|
+
)
|
|
680
|
+
else:
|
|
681
|
+
roadmap_after = _activate_roadmap_milestone(roadmap_before, selected_milestone)
|
|
607
682
|
pipeline_state._atomic_write(roadmap_path, roadmap_after)
|
|
608
683
|
pipeline_state._atomic_write(state_path, state_after)
|
|
684
|
+
if baseline is not None:
|
|
685
|
+
baseline.unlink()
|
|
609
686
|
return {
|
|
610
687
|
"schema": CHECKPOINT_SCHEMA,
|
|
611
688
|
"status": "approved",
|
|
@@ -1002,38 +1079,3 @@ def _classify_plan_drift(
|
|
|
1002
1079
|
"contract_paths": sorted(contract_paths),
|
|
1003
1080
|
"reason": "; ".join(reasons) if reasons else "approval contracts and declared paths are unchanged",
|
|
1004
1081
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
# pipeline_state imports this module, so the parent is resolved after the
|
|
1008
|
-
# definitions above. Parent functions are looked up on the module at call time
|
|
1009
|
-
# so patches applied to pipeline_state stay visible here.
|
|
1010
|
-
if __package__: # imported as scripts.state_checkpoint
|
|
1011
|
-
from . import pipeline_state
|
|
1012
|
-
from .pipeline_state import (
|
|
1013
|
-
CHECKPOINT_JOURNAL_NAME,
|
|
1014
|
-
CHECKPOINT_KINDS,
|
|
1015
|
-
CHECKPOINT_SCHEMA,
|
|
1016
|
-
PLAN_APPROVAL_SUBJECT,
|
|
1017
|
-
PROMOTION_TRACKS,
|
|
1018
|
-
SLUG_RE,
|
|
1019
|
-
TASK_FILE_RE,
|
|
1020
|
-
TASK_ID_RE,
|
|
1021
|
-
ApprovedTaskContract,
|
|
1022
|
-
PipelineState,
|
|
1023
|
-
PipelineStateError,
|
|
1024
|
-
)
|
|
1025
|
-
else: # standalone script or sibling import
|
|
1026
|
-
import pipeline_state
|
|
1027
|
-
from pipeline_state import (
|
|
1028
|
-
CHECKPOINT_JOURNAL_NAME,
|
|
1029
|
-
CHECKPOINT_KINDS,
|
|
1030
|
-
CHECKPOINT_SCHEMA,
|
|
1031
|
-
PLAN_APPROVAL_SUBJECT,
|
|
1032
|
-
PROMOTION_TRACKS,
|
|
1033
|
-
SLUG_RE,
|
|
1034
|
-
TASK_FILE_RE,
|
|
1035
|
-
TASK_ID_RE,
|
|
1036
|
-
ApprovedTaskContract,
|
|
1037
|
-
PipelineState,
|
|
1038
|
-
PipelineStateError,
|
|
1039
|
-
)
|