@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
|
@@ -12,27 +12,47 @@ import re
|
|
|
12
12
|
import stat
|
|
13
13
|
import subprocess
|
|
14
14
|
import sys
|
|
15
|
+
|
|
16
|
+
# Runtime helpers must not modify their immutable installation.
|
|
17
|
+
sys.dont_write_bytecode = True
|
|
15
18
|
from pathlib import Path
|
|
16
19
|
from typing import Optional, Sequence
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
import archive_milestone
|
|
20
|
-
import
|
|
21
|
+
if __package__:
|
|
22
|
+
from . import _common, archive_milestone, detect_project
|
|
23
|
+
from .roadmap import (
|
|
24
|
+
RoadmapError as LookaheadError,
|
|
25
|
+
NoEligibleMilestone as NoEligibleLookahead,
|
|
26
|
+
roadmap_blocks,
|
|
27
|
+
milestone_block,
|
|
28
|
+
dependency_ids,
|
|
29
|
+
next_eligible_pending,
|
|
30
|
+
selection_payload,
|
|
31
|
+
select_lookahead,
|
|
32
|
+
roadmap_contract,
|
|
33
|
+
compare_roadmap_entry,
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
from roadmap import (
|
|
37
|
+
RoadmapError as LookaheadError,
|
|
38
|
+
NoEligibleMilestone as NoEligibleLookahead,
|
|
39
|
+
roadmap_blocks,
|
|
40
|
+
milestone_block,
|
|
41
|
+
dependency_ids,
|
|
42
|
+
next_eligible_pending,
|
|
43
|
+
selection_payload,
|
|
44
|
+
select_lookahead,
|
|
45
|
+
roadmap_contract,
|
|
46
|
+
compare_roadmap_entry,
|
|
47
|
+
)
|
|
48
|
+
import _common
|
|
49
|
+
import archive_milestone
|
|
50
|
+
import detect_project
|
|
21
51
|
|
|
22
52
|
|
|
23
53
|
PHASES = {"inspect", "define", "research", "decide", "plan"}
|
|
24
54
|
STATUSES = {"active", "blocked", "done"}
|
|
25
55
|
SHA_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
26
|
-
ROADMAP_HEADING_RE = re.compile(r"^### (M\d{3,}) — (.+)$")
|
|
27
|
-
MUTABLE_ROADMAP_FIELDS_RE = re.compile(r"^(Status|Archive|Integrated):")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class LookaheadError(RuntimeError):
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class NoEligibleLookahead(LookaheadError):
|
|
35
|
-
pass
|
|
36
56
|
|
|
37
57
|
|
|
38
58
|
def read_text(path: Path, description: str) -> str:
|
|
@@ -76,119 +96,6 @@ def state_fields(content: str) -> dict[str, str]:
|
|
|
76
96
|
return fields
|
|
77
97
|
|
|
78
98
|
|
|
79
|
-
def roadmap_blocks(content: str) -> list[dict]:
|
|
80
|
-
lines = content.splitlines(keepends=True)
|
|
81
|
-
headings = []
|
|
82
|
-
for index, line in enumerate(lines):
|
|
83
|
-
match = ROADMAP_HEADING_RE.fullmatch(line.rstrip("\r\n"))
|
|
84
|
-
if match:
|
|
85
|
-
headings.append((index, match.group(1), match.group(2).strip()))
|
|
86
|
-
blocks = []
|
|
87
|
-
for position, (start, milestone_id, title) in enumerate(headings):
|
|
88
|
-
end = headings[position + 1][0] if position + 1 < len(headings) else len(lines)
|
|
89
|
-
fields = {}
|
|
90
|
-
for index in range(start + 1, end):
|
|
91
|
-
match = re.match(
|
|
92
|
-
r"^(Status|Archive|Integrated|Depends on):\s*([^#]*?)\s*(?:#.*)?$",
|
|
93
|
-
lines[index].rstrip("\r\n"),
|
|
94
|
-
)
|
|
95
|
-
if match:
|
|
96
|
-
if match.group(1) in fields:
|
|
97
|
-
raise LookaheadError(
|
|
98
|
-
f"roadmap milestone {milestone_id} has duplicate "
|
|
99
|
-
f"{match.group(1)} field"
|
|
100
|
-
)
|
|
101
|
-
fields[match.group(1)] = (index, match.group(2).strip())
|
|
102
|
-
blocks.append(
|
|
103
|
-
{
|
|
104
|
-
"id": milestone_id,
|
|
105
|
-
"title": title,
|
|
106
|
-
"start": start,
|
|
107
|
-
"end": end,
|
|
108
|
-
"fields": fields,
|
|
109
|
-
}
|
|
110
|
-
)
|
|
111
|
-
return blocks
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def milestone_block(content: str, milestone: str) -> dict:
|
|
115
|
-
normalized = archive_milestone.normalized_slug(milestone)
|
|
116
|
-
matches = [
|
|
117
|
-
block
|
|
118
|
-
for block in roadmap_blocks(content)
|
|
119
|
-
if archive_milestone.normalized_slug(block["title"]) == normalized
|
|
120
|
-
]
|
|
121
|
-
if len(matches) != 1:
|
|
122
|
-
raise LookaheadError(f"ROADMAP.md must contain exactly one entry for {milestone}")
|
|
123
|
-
return matches[0]
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def dependency_ids(block: dict) -> list[str]:
|
|
127
|
-
dependencies = block["fields"].get("Depends on")
|
|
128
|
-
if dependencies is None:
|
|
129
|
-
raise LookaheadError(f"roadmap milestone {block['id']} lacks dependencies")
|
|
130
|
-
dependency_list = re.fullmatch(
|
|
131
|
-
r"\[\s*(M\d{3,}(?:\s*,\s*M\d{3,})*)?\s*\]",
|
|
132
|
-
dependencies[1],
|
|
133
|
-
)
|
|
134
|
-
if dependency_list is None:
|
|
135
|
-
raise LookaheadError(f"roadmap milestone {block['id']} has invalid dependencies")
|
|
136
|
-
return re.findall(r"M\d{3,}", dependency_list.group(1) or "")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def next_eligible_pending(content: str, active_milestone: Optional[str] = None) -> dict:
|
|
140
|
-
blocks = roadmap_blocks(content)
|
|
141
|
-
blocks_by_id = {block["id"]: block for block in blocks}
|
|
142
|
-
if len(blocks_by_id) != len(blocks):
|
|
143
|
-
raise LookaheadError("ROADMAP.md milestone ids must be unique")
|
|
144
|
-
active_id = None
|
|
145
|
-
if active_milestone is not None:
|
|
146
|
-
active = milestone_block(content, active_milestone)
|
|
147
|
-
if active["fields"].get("Status", (-1, ""))[1] != "active":
|
|
148
|
-
raise LookaheadError(f"roadmap milestone {active_milestone} is not active")
|
|
149
|
-
active_id = active["id"]
|
|
150
|
-
for block in blocks:
|
|
151
|
-
if block["fields"].get("Status", (-1, ""))[1] != "pending":
|
|
152
|
-
continue
|
|
153
|
-
dependencies = dependency_ids(block)
|
|
154
|
-
if all(
|
|
155
|
-
dependency in blocks_by_id
|
|
156
|
-
and (
|
|
157
|
-
blocks_by_id[dependency]["fields"].get("Status", (-1, ""))[1]
|
|
158
|
-
== "shipped"
|
|
159
|
-
or dependency == active_id
|
|
160
|
-
)
|
|
161
|
-
for dependency in dependencies
|
|
162
|
-
):
|
|
163
|
-
return block
|
|
164
|
-
raise NoEligibleLookahead("ROADMAP.md has no dependency-ready pending milestone")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def selection_payload(selected: dict) -> dict:
|
|
168
|
-
milestone = archive_milestone.normalized_slug(selected["title"])
|
|
169
|
-
return {
|
|
170
|
-
"status": "selected",
|
|
171
|
-
"id": selected["id"],
|
|
172
|
-
"milestone": milestone,
|
|
173
|
-
"branch": f"gsd-path/{selected['id']}",
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def select_lookahead(content: str, active_milestone: Optional[str] = None) -> dict:
|
|
178
|
-
try:
|
|
179
|
-
selected = next_eligible_pending(content, active_milestone)
|
|
180
|
-
except NoEligibleLookahead:
|
|
181
|
-
blocks = roadmap_blocks(content)
|
|
182
|
-
if blocks and all(
|
|
183
|
-
block["fields"].get("Status", (-1, ""))[1]
|
|
184
|
-
in {"shipped", "abandoned"}
|
|
185
|
-
for block in blocks
|
|
186
|
-
):
|
|
187
|
-
return {"status": "complete"}
|
|
188
|
-
return {"status": "none"}
|
|
189
|
-
return selection_payload(selected)
|
|
190
|
-
|
|
191
|
-
|
|
192
99
|
def select_track_branch(content: str, track_state: str) -> dict:
|
|
193
100
|
fields = state_fields(track_state)
|
|
194
101
|
if fields.get("pipeline", "") != archive_milestone.PIPELINE_MARKER:
|
|
@@ -247,42 +154,6 @@ def snapshot_roadmap(source: Path, destination: Path) -> dict:
|
|
|
247
154
|
}
|
|
248
155
|
|
|
249
156
|
|
|
250
|
-
def roadmap_contract(content: str, milestone: str) -> tuple[str, ...]:
|
|
251
|
-
block = milestone_block(content, milestone)
|
|
252
|
-
lines = content.splitlines()
|
|
253
|
-
return tuple(
|
|
254
|
-
line.rstrip()
|
|
255
|
-
for line in lines[block["start"] : block["end"]]
|
|
256
|
-
if not MUTABLE_ROADMAP_FIELDS_RE.match(line)
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def compare_roadmap_entry(
|
|
261
|
-
before: str,
|
|
262
|
-
after: str,
|
|
263
|
-
milestone: str,
|
|
264
|
-
active_milestone: Optional[str] = None,
|
|
265
|
-
) -> dict:
|
|
266
|
-
before_contract = roadmap_contract(before, milestone)
|
|
267
|
-
try:
|
|
268
|
-
after_contract = roadmap_contract(after, milestone)
|
|
269
|
-
selected = milestone_block(after, milestone)
|
|
270
|
-
eligible = next_eligible_pending(after, active_milestone)
|
|
271
|
-
except LookaheadError:
|
|
272
|
-
after_contract = ()
|
|
273
|
-
selected = None
|
|
274
|
-
eligible = None
|
|
275
|
-
status = (
|
|
276
|
-
"unchanged"
|
|
277
|
-
if before_contract == after_contract
|
|
278
|
-
and selected is not None
|
|
279
|
-
and eligible is not None
|
|
280
|
-
and selected["id"] == eligible["id"]
|
|
281
|
-
else "changed"
|
|
282
|
-
)
|
|
283
|
-
return {"status": status, "milestone": milestone}
|
|
284
|
-
|
|
285
|
-
|
|
286
157
|
def git_commit_file(root: Path, commit: str, relative: str) -> str:
|
|
287
158
|
require_directory(root, "repository")
|
|
288
159
|
if not SHA_RE.fullmatch(commit):
|
package/scripts/review_panel.py
CHANGED
|
@@ -15,6 +15,9 @@ import argparse
|
|
|
15
15
|
import json
|
|
16
16
|
import re
|
|
17
17
|
import sys
|
|
18
|
+
|
|
19
|
+
# Runtime helpers must not modify their immutable installation.
|
|
20
|
+
sys.dont_write_bytecode = True
|
|
18
21
|
from pathlib import Path
|
|
19
22
|
from typing import Iterable, Optional, Sequence
|
|
20
23
|
|
|
@@ -214,6 +217,7 @@ def resolve_panel(
|
|
|
214
217
|
advertised: Sequence[str],
|
|
215
218
|
parent_family: Optional[str] = None,
|
|
216
219
|
parent_slug: Optional[str] = None,
|
|
220
|
+
excluded_families: Sequence[str] = (),
|
|
217
221
|
) -> dict:
|
|
218
222
|
inferred_parent = parent_family or (
|
|
219
223
|
family_of_slug(parent_slug) if parent_slug else None
|
|
@@ -244,7 +248,12 @@ def resolve_panel(
|
|
|
244
248
|
"named review_panel families are not advertised: " + ", ".join(missing)
|
|
245
249
|
)
|
|
246
250
|
|
|
251
|
+
if config["mode"] == "named" and set(wanted) & set(excluded_families):
|
|
252
|
+
raise ReviewPanelError("named review_panel must use an independent family")
|
|
247
253
|
for family in wanted:
|
|
254
|
+
if family in excluded_families:
|
|
255
|
+
skipped.append({"family": family, "reason": "excluded reviewer family"})
|
|
256
|
+
continue
|
|
248
257
|
if family == inferred_parent:
|
|
249
258
|
skipped.append({"family": family, "reason": "parent family"})
|
|
250
259
|
continue
|
|
@@ -488,6 +497,7 @@ def parser() -> argparse.ArgumentParser:
|
|
|
488
497
|
result.add_argument("--advertised", default="")
|
|
489
498
|
result.add_argument("--parent-family")
|
|
490
499
|
result.add_argument("--parent-slug")
|
|
500
|
+
result.add_argument("--exclude-family", action="append", choices=KNOWN_FAMILIES, default=[])
|
|
491
501
|
result.add_argument("--kind", choices=("plan", "wave"))
|
|
492
502
|
result.add_argument("--inputs", default="")
|
|
493
503
|
result.add_argument("--output", type=Path)
|
|
@@ -541,6 +551,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
541
551
|
split_csv(arguments.advertised),
|
|
542
552
|
arguments.parent_family,
|
|
543
553
|
arguments.parent_slug,
|
|
554
|
+
arguments.exclude_family,
|
|
544
555
|
)
|
|
545
556
|
return emit(resolved)
|
|
546
557
|
except ReviewPanelError as error:
|
|
@@ -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)
|