@opengsd/gsd-path 1.0.0 → 1.1.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 +28 -11
- package/DOCS.md +6 -5
- package/FULL.md +9 -13
- package/QUICK.md +3 -3
- package/README.md +151 -14
- package/RUNTIME.md +7 -1
- package/WORKFLOW.md +30 -14
- package/package.json +15 -1
- package/platforms/shared-agents/dispatch.md +6 -0
- package/scripts/archive_milestone.py +10 -118
- package/scripts/bootstrap_repository.py +25 -3
- package/scripts/build_recovery.py +287 -0
- package/scripts/check_docs_audit.py +5 -3
- package/scripts/check_handoffs.py +1 -1
- package/scripts/detect_project.py +11 -2
- package/scripts/dispatch_driver.py +4 -2
- package/scripts/git_guard.py +2 -1
- package/scripts/install.mjs +2 -0
- package/scripts/install.py +2 -0
- package/scripts/integration.py +28 -22
- package/scripts/isolation.py +9 -1
- package/scripts/lean_verification.py +1 -1
- package/scripts/pipeline_diagnose.py +12 -3
- package/scripts/pipeline_state.py +114 -186
- package/scripts/pipeline_undo.py +11 -7
- package/scripts/promote_lookahead.py +30 -162
- package/scripts/roadmap.py +321 -0
- package/scripts/skill-resources.json +79 -1
- package/scripts/state_checkpoint.py +109 -72
- package/scripts/state_promote.py +42 -44
- package/scripts/task_context.py +73 -0
- package/scripts/token_budget.py +40 -1
- package/scripts/workflow_run.py +116 -7
- package/skills/gsd-path/BUILD.md +47 -575
- package/skills/gsd-path/DECIDE.md +2 -6
- package/skills/gsd-path/DEFINE.md +10 -14
- package/skills/gsd-path/INSPECT.md +30 -8
- package/skills/gsd-path/PLAN.md +25 -17
- package/skills/gsd-path/RESEARCH.md +3 -6
- package/skills/gsd-path/ROADMAP.md +22 -58
- package/skills/gsd-path/SHIP.md +5 -4
- package/skills/gsd-path/SKILL.md +24 -14
- package/skills/gsd-path/references/build-abandon.md +33 -0
- package/skills/gsd-path/references/build-native.md +494 -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/dispatch.md +6 -0
- package/skills/gsd-path/references/docs-auditor.md +14 -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 -150
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/discussion_records.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/integration.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/archive_milestone.py +10 -118
- package/skills/gsd-path/scripts/bootstrap_repository.py +25 -3
- package/skills/gsd-path/scripts/build_recovery.py +287 -0
- package/skills/gsd-path/scripts/check_docs_audit.py +5 -3
- package/skills/gsd-path/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path/scripts/detect_project.py +11 -2
- package/skills/gsd-path/scripts/dispatch_driver.py +4 -2
- package/skills/gsd-path/scripts/integration.py +28 -22
- package/skills/gsd-path/scripts/isolation.py +9 -1
- package/skills/gsd-path/scripts/lean_verification.py +1 -1
- package/skills/gsd-path/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path/scripts/promote_lookahead.py +30 -162
- package/skills/gsd-path/scripts/roadmap.py +321 -0
- package/skills/gsd-path/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path/scripts/state_promote.py +42 -44
- 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 +116 -7
- package/skills/gsd-path/templates/wave-review.md +1 -1
- package/skills/gsd-path-build/SKILL.md +47 -575
- package/skills/gsd-path-build/references/build-abandon.md +33 -0
- package/skills/gsd-path-build/references/build-native.md +494 -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 +6 -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 -150
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/build_state.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/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/archive_milestone.py +10 -118
- package/skills/gsd-path-build/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-build/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-build/scripts/detect_project.py +1526 -0
- package/skills/gsd-path-build/scripts/dispatch_driver.py +4 -2
- package/skills/gsd-path-build/scripts/integration.py +28 -22
- package/skills/gsd-path-build/scripts/isolation.py +9 -1
- package/skills/gsd-path-build/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-build/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-build/scripts/roadmap.py +321 -0
- package/skills/gsd-path-build/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-build/scripts/state_promote.py +42 -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 +116 -7
- package/skills/gsd-path-build/templates/wave-review.md +1 -1
- package/skills/gsd-path-decide/SKILL.md +2 -6
- package/skills/gsd-path-decide/references/dispatch.md +6 -0
- package/skills/gsd-path-decide/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-decide/scripts/archive_milestone.py +2157 -0
- package/skills/gsd-path-decide/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-decide/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-decide/scripts/detect_project.py +1526 -0
- package/skills/gsd-path-decide/scripts/discussion_validate.py +1321 -0
- package/skills/gsd-path-decide/scripts/integration.py +1439 -0
- package/skills/gsd-path-decide/scripts/isolation.py +9 -1
- package/skills/gsd-path-decide/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-decide/scripts/review_panel.py +553 -0
- package/skills/gsd-path-decide/scripts/roadmap.py +321 -0
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-decide/scripts/state_promote.py +42 -44
- package/skills/gsd-path-define/SKILL.md +10 -14
- package/skills/gsd-path-define/references/build-recovery.md +78 -0
- package/skills/gsd-path-define/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-define/scripts/archive_milestone.py +2157 -0
- package/skills/gsd-path-define/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-define/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-define/scripts/detect_project.py +11 -2
- package/skills/gsd-path-define/scripts/discussion_validate.py +1321 -0
- package/skills/gsd-path-define/scripts/integration.py +1439 -0
- package/skills/gsd-path-define/scripts/isolation.py +9 -1
- package/skills/gsd-path-define/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-define/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-define/scripts/review_panel.py +553 -0
- package/skills/gsd-path-define/scripts/roadmap.py +321 -0
- package/skills/gsd-path-define/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-define/scripts/state_promote.py +42 -44
- package/skills/gsd-path-docs-audit/references/dispatch.md +6 -0
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +14 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +2157 -0
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +5 -3
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +1526 -0
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +1321 -0
- package/skills/gsd-path-docs-audit/scripts/integration.py +1439 -0
- package/skills/gsd-path-docs-audit/scripts/isolation.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +553 -0
- package/skills/gsd-path-docs-audit/scripts/roadmap.py +321 -0
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +42 -44
- package/skills/gsd-path-forensics/scripts/__pycache__/pipeline_undo.cpython-312.pyc +0 -0
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +12 -3
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +11 -7
- package/skills/gsd-path-inspect/SKILL.md +30 -8
- package/skills/gsd-path-inspect/references/dispatch.md +6 -0
- package/skills/gsd-path-inspect/references/docs-auditor.md +14 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +2157 -0
- package/skills/gsd-path-inspect/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +5 -3
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-inspect/scripts/detect_project.py +11 -2
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +1321 -0
- package/skills/gsd-path-inspect/scripts/integration.py +1439 -0
- package/skills/gsd-path-inspect/scripts/isolation.py +9 -1
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-inspect/scripts/review_panel.py +553 -0
- package/skills/gsd-path-inspect/scripts/roadmap.py +321 -0
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-inspect/scripts/state_promote.py +42 -44
- package/skills/gsd-path-inspect/scripts/workflow_run.py +226 -0
- package/skills/gsd-path-plan/SKILL.md +25 -17
- package/skills/gsd-path-plan/references/build-recovery.md +78 -0
- package/skills/gsd-path-plan/references/dispatch.md +6 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/archive_milestone.py +2157 -0
- package/skills/gsd-path-plan/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-plan/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-plan/scripts/detect_project.py +1526 -0
- package/skills/gsd-path-plan/scripts/discussion_validate.py +1321 -0
- package/skills/gsd-path-plan/scripts/integration.py +1439 -0
- package/skills/gsd-path-plan/scripts/isolation.py +9 -1
- package/skills/gsd-path-plan/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-plan/scripts/roadmap.py +321 -0
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-plan/scripts/state_promote.py +42 -44
- package/skills/gsd-path-plan/scripts/workflow_run.py +116 -7
- package/skills/gsd-path-research/SKILL.md +3 -6
- package/skills/gsd-path-research/references/dispatch.md +6 -0
- package/skills/gsd-path-research/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-research/scripts/archive_milestone.py +2157 -0
- package/skills/gsd-path-research/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-research/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-research/scripts/detect_project.py +1526 -0
- package/skills/gsd-path-research/scripts/discussion_validate.py +1321 -0
- package/skills/gsd-path-research/scripts/integration.py +1439 -0
- package/skills/gsd-path-research/scripts/isolation.py +9 -1
- package/skills/gsd-path-research/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-research/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-research/scripts/review_panel.py +553 -0
- package/skills/gsd-path-research/scripts/roadmap.py +321 -0
- package/skills/gsd-path-research/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-research/scripts/state_promote.py +42 -44
- package/skills/gsd-path-roadmap/SKILL.md +22 -58
- package/skills/gsd-path-roadmap/references/dispatch.md +6 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +10 -118
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-roadmap/scripts/detect_project.py +11 -2
- package/skills/gsd-path-roadmap/scripts/integration.py +28 -22
- package/skills/gsd-path-roadmap/scripts/isolation.py +9 -1
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +30 -162
- package/skills/gsd-path-roadmap/scripts/roadmap.py +321 -0
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-roadmap/scripts/state_promote.py +42 -44
- package/skills/gsd-path-ship/SKILL.md +5 -4
- package/skills/gsd-path-ship/references/dispatch.md +6 -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 -150
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/archive_milestone.py +10 -118
- package/skills/gsd-path-ship/scripts/build_recovery.py +287 -0
- package/skills/gsd-path-ship/scripts/check_handoffs.py +1 -1
- package/skills/gsd-path-ship/scripts/detect_project.py +1526 -0
- package/skills/gsd-path-ship/scripts/integration.py +28 -22
- package/skills/gsd-path-ship/scripts/isolation.py +9 -1
- package/skills/gsd-path-ship/scripts/lean_verification.py +1 -1
- package/skills/gsd-path-ship/scripts/pipeline_state.py +114 -186
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +283 -0
- package/skills/gsd-path-ship/scripts/roadmap.py +321 -0
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +109 -72
- package/skills/gsd-path-ship/scripts/state_promote.py +42 -44
- package/skills/gsd-path-ship/scripts/workflow_run.py +116 -7
- package/skills/gsd-path-ship/templates/wave-review.md +1 -1
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +11 -7
- package/skills/path/BUILD.md +47 -575
- package/skills/path/DECIDE.md +2 -6
- package/skills/path/DEFINE.md +10 -14
- package/skills/path/INSPECT.md +30 -8
- package/skills/path/PLAN.md +25 -17
- package/skills/path/RESEARCH.md +3 -6
- package/skills/path/ROADMAP.md +22 -58
- package/skills/path/SHIP.md +5 -4
- package/skills/path/SKILL.md +24 -14
- package/skills/path/references/build-abandon.md +33 -0
- package/skills/path/references/build-native.md +494 -0
- package/skills/path/references/build-recovery.md +78 -0
- package/skills/path/references/coder.md +8 -3
- package/skills/path/references/dispatch.md +6 -0
- package/skills/path/references/docs-auditor.md +14 -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 -150
- package/skills/path/scripts/archive_milestone.py +10 -118
- package/skills/path/scripts/bootstrap_repository.py +25 -3
- package/skills/path/scripts/build_recovery.py +287 -0
- package/skills/path/scripts/check_docs_audit.py +5 -3
- package/skills/path/scripts/check_handoffs.py +1 -1
- package/skills/path/scripts/detect_project.py +11 -2
- package/skills/path/scripts/dispatch_driver.py +4 -2
- package/skills/path/scripts/integration.py +28 -22
- package/skills/path/scripts/isolation.py +9 -1
- package/skills/path/scripts/lean_verification.py +1 -1
- package/skills/path/scripts/pipeline_state.py +114 -186
- package/skills/path/scripts/promote_lookahead.py +30 -162
- package/skills/path/scripts/roadmap.py +321 -0
- package/skills/path/scripts/state_checkpoint.py +109 -72
- package/skills/path/scripts/state_promote.py +42 -44
- 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 +116 -7
- package/skills/path/templates/wave-review.md +1 -1
package/scripts/pipeline_undo.py
CHANGED
|
@@ -27,18 +27,20 @@ def _load_pipeline_modules():
|
|
|
27
27
|
import isolation
|
|
28
28
|
import pipeline_git
|
|
29
29
|
import pipeline_state
|
|
30
|
-
|
|
30
|
+
import state_checkpoint
|
|
31
|
+
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
31
32
|
except ModuleNotFoundError as error:
|
|
32
33
|
if error.name not in {
|
|
33
34
|
"archive_milestone",
|
|
34
35
|
"isolation",
|
|
35
36
|
"pipeline_git",
|
|
36
37
|
"pipeline_state",
|
|
38
|
+
"state_checkpoint",
|
|
37
39
|
}:
|
|
38
40
|
raise
|
|
39
41
|
try:
|
|
40
|
-
from scripts import archive_milestone, isolation, pipeline_git, pipeline_state
|
|
41
|
-
return archive_milestone, isolation, pipeline_git, pipeline_state
|
|
42
|
+
from scripts import archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
43
|
+
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
42
44
|
except ModuleNotFoundError as error:
|
|
43
45
|
if error.name not in {
|
|
44
46
|
"scripts",
|
|
@@ -46,6 +48,7 @@ def _load_pipeline_modules():
|
|
|
46
48
|
"scripts.isolation",
|
|
47
49
|
"scripts.pipeline_git",
|
|
48
50
|
"scripts.pipeline_state",
|
|
51
|
+
"scripts.state_checkpoint",
|
|
49
52
|
}:
|
|
50
53
|
raise
|
|
51
54
|
shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
|
|
@@ -54,16 +57,17 @@ def _load_pipeline_modules():
|
|
|
54
57
|
import isolation
|
|
55
58
|
import pipeline_git
|
|
56
59
|
import pipeline_state
|
|
57
|
-
|
|
60
|
+
import state_checkpoint
|
|
61
|
+
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
58
62
|
|
|
59
63
|
|
|
60
|
-
archive_milestone, isolation, pipeline_git, pipeline_state = _load_pipeline_modules()
|
|
64
|
+
archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint = _load_pipeline_modules()
|
|
61
65
|
ArchiveError = archive_milestone.ArchiveError
|
|
62
66
|
IsolationError = isolation.IsolationError
|
|
63
67
|
PLAN_APPROVAL_SUBJECT = pipeline_state.PLAN_APPROVAL_SUBJECT
|
|
64
68
|
PipelineStateError = pipeline_state.PipelineStateError
|
|
65
|
-
_activate_roadmap_milestone =
|
|
66
|
-
_approval_details =
|
|
69
|
+
_activate_roadmap_milestone = state_checkpoint._activate_roadmap_milestone
|
|
70
|
+
_approval_details = state_checkpoint._approval_details
|
|
67
71
|
_commit_subject_body = pipeline_state._commit_subject_body
|
|
68
72
|
_optional_rev = pipeline_state._optional_rev
|
|
69
73
|
_render_transition = pipeline_state._render_transition
|
|
@@ -15,24 +15,41 @@ import sys
|
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from typing import Optional, Sequence
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
import archive_milestone
|
|
20
|
-
import
|
|
18
|
+
if __package__:
|
|
19
|
+
from . import _common, archive_milestone, detect_project
|
|
20
|
+
from .roadmap import (
|
|
21
|
+
RoadmapError as LookaheadError,
|
|
22
|
+
NoEligibleMilestone as NoEligibleLookahead,
|
|
23
|
+
roadmap_blocks,
|
|
24
|
+
milestone_block,
|
|
25
|
+
dependency_ids,
|
|
26
|
+
next_eligible_pending,
|
|
27
|
+
selection_payload,
|
|
28
|
+
select_lookahead,
|
|
29
|
+
roadmap_contract,
|
|
30
|
+
compare_roadmap_entry,
|
|
31
|
+
)
|
|
32
|
+
else:
|
|
33
|
+
from roadmap import (
|
|
34
|
+
RoadmapError as LookaheadError,
|
|
35
|
+
NoEligibleMilestone as NoEligibleLookahead,
|
|
36
|
+
roadmap_blocks,
|
|
37
|
+
milestone_block,
|
|
38
|
+
dependency_ids,
|
|
39
|
+
next_eligible_pending,
|
|
40
|
+
selection_payload,
|
|
41
|
+
select_lookahead,
|
|
42
|
+
roadmap_contract,
|
|
43
|
+
compare_roadmap_entry,
|
|
44
|
+
)
|
|
45
|
+
import _common
|
|
46
|
+
import archive_milestone
|
|
47
|
+
import detect_project
|
|
21
48
|
|
|
22
49
|
|
|
23
50
|
PHASES = {"inspect", "define", "research", "decide", "plan"}
|
|
24
51
|
STATUSES = {"active", "blocked", "done"}
|
|
25
52
|
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
53
|
|
|
37
54
|
|
|
38
55
|
def read_text(path: Path, description: str) -> str:
|
|
@@ -76,119 +93,6 @@ def state_fields(content: str) -> dict[str, str]:
|
|
|
76
93
|
return fields
|
|
77
94
|
|
|
78
95
|
|
|
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
96
|
def select_track_branch(content: str, track_state: str) -> dict:
|
|
193
97
|
fields = state_fields(track_state)
|
|
194
98
|
if fields.get("pipeline", "") != archive_milestone.PIPELINE_MARKER:
|
|
@@ -247,42 +151,6 @@ def snapshot_roadmap(source: Path, destination: Path) -> dict:
|
|
|
247
151
|
}
|
|
248
152
|
|
|
249
153
|
|
|
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
154
|
def git_commit_file(root: Path, commit: str, relative: str) -> str:
|
|
287
155
|
require_directory(root, "repository")
|
|
288
156
|
if not SHA_RE.fullmatch(commit):
|
|
@@ -0,0 +1,321 @@
|
|
|
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 re
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RoadmapError(RuntimeError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NoEligibleMilestone(RoadmapError):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ROADMAP_HEADING_RE = re.compile(r"^### (M\d{3,}) — (.+)$")
|
|
19
|
+
STRICT_HEADING_RE = re.compile(r"^### (M\d{3,}) — ([a-z0-9][a-z0-9-]*)\s*$")
|
|
20
|
+
MUTABLE_ROADMAP_FIELDS_RE = re.compile(r"^(Status|Archive|Integrated):")
|
|
21
|
+
|
|
22
|
+
def normalized_slug(value: str) -> str:
|
|
23
|
+
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
|
24
|
+
return slug or "project"
|
|
25
|
+
|
|
26
|
+
def roadmap_blocks(content: str) -> list[dict]:
|
|
27
|
+
lines = content.splitlines(keepends=True)
|
|
28
|
+
headings = []
|
|
29
|
+
for index, line in enumerate(lines):
|
|
30
|
+
match = ROADMAP_HEADING_RE.fullmatch(line.rstrip("\r\n"))
|
|
31
|
+
if match:
|
|
32
|
+
headings.append((index, match.group(1), match.group(2).strip()))
|
|
33
|
+
blocks = []
|
|
34
|
+
for position, (start, milestone_id, title) in enumerate(headings):
|
|
35
|
+
end = headings[position + 1][0] if position + 1 < len(headings) else len(lines)
|
|
36
|
+
fields = {}
|
|
37
|
+
for index in range(start + 1, end):
|
|
38
|
+
match = re.match(
|
|
39
|
+
r"^(Status|Archive|Integrated|Depends on):\s*([^#]*?)\s*(?:#.*)?$",
|
|
40
|
+
lines[index].rstrip("\r\n"),
|
|
41
|
+
)
|
|
42
|
+
if match:
|
|
43
|
+
if match.group(1) in fields:
|
|
44
|
+
raise RoadmapError(
|
|
45
|
+
f"roadmap milestone {milestone_id} has duplicate "
|
|
46
|
+
f"{match.group(1)} field"
|
|
47
|
+
)
|
|
48
|
+
fields[match.group(1)] = (index, match.group(2).strip())
|
|
49
|
+
blocks.append(
|
|
50
|
+
{
|
|
51
|
+
"id": milestone_id,
|
|
52
|
+
"title": title,
|
|
53
|
+
"start": start,
|
|
54
|
+
"end": end,
|
|
55
|
+
"fields": fields,
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
return blocks
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def milestone_block(content: str, milestone: str) -> dict:
|
|
62
|
+
normalized = normalized_slug(milestone)
|
|
63
|
+
matches = [
|
|
64
|
+
block
|
|
65
|
+
for block in roadmap_blocks(content)
|
|
66
|
+
if normalized_slug(block["title"]) == normalized
|
|
67
|
+
]
|
|
68
|
+
if len(matches) != 1:
|
|
69
|
+
raise RoadmapError(f"ROADMAP.md must contain exactly one entry for {milestone}")
|
|
70
|
+
return matches[0]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def dependency_ids(block: dict) -> list[str]:
|
|
74
|
+
dependencies = block["fields"].get("Depends on")
|
|
75
|
+
if dependencies is None:
|
|
76
|
+
raise RoadmapError(f"roadmap milestone {block['id']} lacks dependencies")
|
|
77
|
+
dependency_list = re.fullmatch(
|
|
78
|
+
r"\[\s*(M\d{3,}(?:\s*,\s*M\d{3,})*)?\s*\]",
|
|
79
|
+
dependencies[1],
|
|
80
|
+
)
|
|
81
|
+
if dependency_list is None:
|
|
82
|
+
raise RoadmapError(f"roadmap milestone {block['id']} has invalid dependencies")
|
|
83
|
+
return re.findall(r"M\d{3,}", dependency_list.group(1) or "")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def next_eligible_pending(content: str, active_milestone: Optional[str] = None) -> dict:
|
|
87
|
+
blocks = roadmap_blocks(content)
|
|
88
|
+
blocks_by_id = {block["id"]: block for block in blocks}
|
|
89
|
+
if len(blocks_by_id) != len(blocks):
|
|
90
|
+
raise RoadmapError("ROADMAP.md milestone ids must be unique")
|
|
91
|
+
active_id = None
|
|
92
|
+
if active_milestone is not None:
|
|
93
|
+
active = milestone_block(content, active_milestone)
|
|
94
|
+
if active["fields"].get("Status", (-1, ""))[1] != "active":
|
|
95
|
+
raise RoadmapError(f"roadmap milestone {active_milestone} is not active")
|
|
96
|
+
active_id = active["id"]
|
|
97
|
+
for block in blocks:
|
|
98
|
+
if block["fields"].get("Status", (-1, ""))[1] != "pending":
|
|
99
|
+
continue
|
|
100
|
+
dependencies = dependency_ids(block)
|
|
101
|
+
if all(
|
|
102
|
+
dependency in blocks_by_id
|
|
103
|
+
and (
|
|
104
|
+
blocks_by_id[dependency]["fields"].get("Status", (-1, ""))[1]
|
|
105
|
+
== "shipped"
|
|
106
|
+
or dependency == active_id
|
|
107
|
+
)
|
|
108
|
+
for dependency in dependencies
|
|
109
|
+
):
|
|
110
|
+
return block
|
|
111
|
+
raise NoEligibleMilestone("ROADMAP.md has no dependency-ready pending milestone")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def selection_payload(selected: dict) -> dict:
|
|
115
|
+
milestone = normalized_slug(selected["title"])
|
|
116
|
+
return {
|
|
117
|
+
"status": "selected",
|
|
118
|
+
"id": selected["id"],
|
|
119
|
+
"milestone": milestone,
|
|
120
|
+
"branch": f"gsd-path/{selected['id']}",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def select_lookahead(content: str, active_milestone: Optional[str] = None) -> dict:
|
|
125
|
+
try:
|
|
126
|
+
selected = next_eligible_pending(content, active_milestone)
|
|
127
|
+
except NoEligibleMilestone:
|
|
128
|
+
blocks = roadmap_blocks(content)
|
|
129
|
+
if blocks and all(
|
|
130
|
+
block["fields"].get("Status", (-1, ""))[1]
|
|
131
|
+
in {"shipped", "abandoned"}
|
|
132
|
+
for block in blocks
|
|
133
|
+
):
|
|
134
|
+
return {"status": "complete"}
|
|
135
|
+
return {"status": "none"}
|
|
136
|
+
return selection_payload(selected)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def roadmap_contract(content: str, milestone: str) -> tuple[str, ...]:
|
|
140
|
+
block = milestone_block(content, milestone)
|
|
141
|
+
lines = content.splitlines()
|
|
142
|
+
return tuple(
|
|
143
|
+
line.rstrip()
|
|
144
|
+
for line in lines[block["start"] : block["end"]]
|
|
145
|
+
if not MUTABLE_ROADMAP_FIELDS_RE.match(line)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def compare_roadmap_entry(
|
|
150
|
+
before: str,
|
|
151
|
+
after: str,
|
|
152
|
+
milestone: str,
|
|
153
|
+
active_milestone: Optional[str] = None,
|
|
154
|
+
) -> dict:
|
|
155
|
+
before_contract = roadmap_contract(before, milestone)
|
|
156
|
+
try:
|
|
157
|
+
after_contract = roadmap_contract(after, milestone)
|
|
158
|
+
selected = milestone_block(after, milestone)
|
|
159
|
+
eligible = next_eligible_pending(after, active_milestone)
|
|
160
|
+
except RoadmapError:
|
|
161
|
+
after_contract = ()
|
|
162
|
+
selected = None
|
|
163
|
+
eligible = None
|
|
164
|
+
status = (
|
|
165
|
+
"unchanged"
|
|
166
|
+
if before_contract == after_contract
|
|
167
|
+
and selected is not None
|
|
168
|
+
and eligible is not None
|
|
169
|
+
and selected["id"] == eligible["id"]
|
|
170
|
+
else "changed"
|
|
171
|
+
)
|
|
172
|
+
return {"status": status, "milestone": milestone}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def strict_sections(text: str) -> dict[str, tuple[str, int, int]]:
|
|
176
|
+
lines = text.splitlines(keepends=True)
|
|
177
|
+
headings: list[tuple[str, str, int]] = []
|
|
178
|
+
for index, line in enumerate(lines):
|
|
179
|
+
match = STRICT_HEADING_RE.fullmatch(line.rstrip("\r\n"))
|
|
180
|
+
if match:
|
|
181
|
+
if int(match.group(1).removeprefix("M")) < 1:
|
|
182
|
+
raise RoadmapError(f"ROADMAP.md has invalid milestone id: {match.group(1)}")
|
|
183
|
+
headings.append((match.group(1), match.group(2), index))
|
|
184
|
+
sections: dict[str, tuple[str, int, int]] = {}
|
|
185
|
+
for position, (milestone_id, slug, start) in enumerate(headings):
|
|
186
|
+
end = headings[position + 1][2] if position + 1 < len(headings) else len(lines)
|
|
187
|
+
if slug in sections:
|
|
188
|
+
raise RoadmapError(f"ROADMAP.md repeats milestone slug: {slug}")
|
|
189
|
+
sections[slug] = (milestone_id, start, end)
|
|
190
|
+
return sections
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def replace_field(
|
|
194
|
+
lines: list[str],
|
|
195
|
+
start: int,
|
|
196
|
+
end: int,
|
|
197
|
+
field: str,
|
|
198
|
+
expected: set[str],
|
|
199
|
+
value: str,
|
|
200
|
+
) -> None:
|
|
201
|
+
found: Optional[int] = None
|
|
202
|
+
for index in range(start, end):
|
|
203
|
+
raw = lines[index].rstrip("\r\n")
|
|
204
|
+
ending = lines[index][len(raw) :]
|
|
205
|
+
match = re.match(rf"^({re.escape(field)}:\s*)(\S+)(.*)$", raw)
|
|
206
|
+
if match is None:
|
|
207
|
+
continue
|
|
208
|
+
if found is not None:
|
|
209
|
+
raise RoadmapError(f"ROADMAP.md repeats {field} in one milestone")
|
|
210
|
+
if match.group(2) not in expected:
|
|
211
|
+
raise RoadmapError(
|
|
212
|
+
f"ROADMAP.md {field} is {match.group(2)}, expected {sorted(expected)}"
|
|
213
|
+
)
|
|
214
|
+
lines[index] = f"{match.group(1)}{value}{match.group(3)}{ending}"
|
|
215
|
+
found = index
|
|
216
|
+
if found is None:
|
|
217
|
+
raise RoadmapError(f"ROADMAP.md milestone is missing {field}")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def milestone_status(lines: list[str], start: int, end: int) -> str:
|
|
221
|
+
values = []
|
|
222
|
+
for line in lines[start:end]:
|
|
223
|
+
match = re.fullmatch(r"Status:\s*(\S+)\s*", line.rstrip("\r\n"))
|
|
224
|
+
if match:
|
|
225
|
+
values.append(match.group(1))
|
|
226
|
+
if len(values) != 1:
|
|
227
|
+
raise RoadmapError("ROADMAP.md milestone must have one Status field")
|
|
228
|
+
return values[0]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def activate_milestone(text: str, milestone: str) -> str:
|
|
232
|
+
sections = strict_sections(text)
|
|
233
|
+
if milestone not in sections:
|
|
234
|
+
raise RoadmapError(f"ROADMAP.md is missing selected milestone: {milestone}")
|
|
235
|
+
lines = text.splitlines(keepends=True)
|
|
236
|
+
active = [
|
|
237
|
+
slug
|
|
238
|
+
for slug, (_, start, end) in sections.items()
|
|
239
|
+
if milestone_status(lines, start, end) == "active"
|
|
240
|
+
]
|
|
241
|
+
if active:
|
|
242
|
+
raise RoadmapError(
|
|
243
|
+
"ROADMAP.md already has an active milestone: " + ", ".join(active)
|
|
244
|
+
)
|
|
245
|
+
_, start, end = sections[milestone]
|
|
246
|
+
replace_field(lines, start, end, "Status", {"pending"}, "active")
|
|
247
|
+
return "".join(lines)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
|
|
251
|
+
old_blocks = roadmap_blocks(before)
|
|
252
|
+
before_lines = before.splitlines(keepends=True)
|
|
253
|
+
candidate_lines = candidate.splitlines(keepends=True)
|
|
254
|
+
for block in old_blocks:
|
|
255
|
+
status = block["fields"].get("Status", (-1, ""))[1]
|
|
256
|
+
if status not in {"active", "shipped", "abandoned"}:
|
|
257
|
+
continue
|
|
258
|
+
slug = normalized_slug(block["title"])
|
|
259
|
+
new = milestone_block(candidate, slug)
|
|
260
|
+
if status == "shipped":
|
|
261
|
+
changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
|
|
262
|
+
else:
|
|
263
|
+
changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
|
|
264
|
+
if changed:
|
|
265
|
+
raise RoadmapError(f"{status} roadmap entry changed: {slug}")
|
|
266
|
+
if phase in {"inspect", "define"}:
|
|
267
|
+
active = [b for b in roadmap_blocks(candidate) if b["fields"].get("Status", (-1, ""))[1] == "active"]
|
|
268
|
+
if len(active) != 1 or selection_payload(active[0])["milestone"] != selected:
|
|
269
|
+
raise RoadmapError("roadmap re-slice must retain the active entry")
|
|
270
|
+
return candidate
|
|
271
|
+
if not any(b["fields"].get("Status", (-1, ""))[1] == "abandoned" for b in old_blocks):
|
|
272
|
+
raise RoadmapError("post-abandon re-slice requires an abandoned milestone")
|
|
273
|
+
selection = select_lookahead(candidate)
|
|
274
|
+
if selection.get("milestone") != selected:
|
|
275
|
+
raise RoadmapError("roadmap re-slice must use the selected dependency-ready milestone")
|
|
276
|
+
return activate_milestone(candidate, selected)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def has_questions(text: str, milestone: Optional[str] = None) -> bool:
|
|
280
|
+
"""Report open questions for the active entry, or for ``milestone`` when given."""
|
|
281
|
+
lines = text.splitlines()
|
|
282
|
+
active_section: Optional[list[str]] = None
|
|
283
|
+
for index, line in enumerate(lines):
|
|
284
|
+
heading = STRICT_HEADING_RE.fullmatch(line)
|
|
285
|
+
if heading is None:
|
|
286
|
+
continue
|
|
287
|
+
if int(heading.group(1).removeprefix("M")) < 1:
|
|
288
|
+
raise RoadmapError(
|
|
289
|
+
f"ROADMAP.md has invalid milestone id: {heading.group(1)}"
|
|
290
|
+
)
|
|
291
|
+
end = next(
|
|
292
|
+
(
|
|
293
|
+
cursor
|
|
294
|
+
for cursor in range(index + 1, len(lines))
|
|
295
|
+
if STRICT_HEADING_RE.fullmatch(lines[cursor]) is not None
|
|
296
|
+
),
|
|
297
|
+
len(lines),
|
|
298
|
+
)
|
|
299
|
+
section = lines[index:end]
|
|
300
|
+
if milestone is not None:
|
|
301
|
+
if heading.group(2) == milestone:
|
|
302
|
+
active_section = section
|
|
303
|
+
continue
|
|
304
|
+
if any(re.fullmatch(r"Status:\s*active(?:\s+#.*)?", item) for item in section):
|
|
305
|
+
if active_section is not None:
|
|
306
|
+
raise RoadmapError("ROADMAP.md has more than one active milestone")
|
|
307
|
+
active_section = section
|
|
308
|
+
if active_section is None:
|
|
309
|
+
if milestone is not None:
|
|
310
|
+
raise RoadmapError(f"ROADMAP.md is missing lookahead milestone: {milestone}")
|
|
311
|
+
raise RoadmapError("ROADMAP.md has no active milestone")
|
|
312
|
+
try:
|
|
313
|
+
start = active_section.index("Open questions") + 1
|
|
314
|
+
except ValueError as error:
|
|
315
|
+
raise RoadmapError("active roadmap milestone has no Open questions") from error
|
|
316
|
+
questions = [
|
|
317
|
+
line[2:].strip()
|
|
318
|
+
for line in active_section[start:]
|
|
319
|
+
if line.startswith("- ")
|
|
320
|
+
]
|
|
321
|
+
return any(question.lower() != "none" for question in questions)
|