@opengsd/gsd-path 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -13
- package/CHANGELOG.md +68 -0
- package/DOCS.md +75 -6
- package/FULL.md +12 -15
- package/HOOKS.md +18 -9
- package/QUICK.md +8 -6
- package/README.md +218 -14
- package/RUNTIME.md +16 -5
- package/UPDATE.md +30 -11
- package/WORKFLOW.md +35 -16
- package/package.json +23 -1
- package/platforms/claude/dispatch.md +4 -4
- package/platforms/copilot/dispatch.md +4 -4
- package/platforms/cursor/dispatch.md +5 -10
- package/platforms/grok/dispatch.md +4 -6
- package/platforms/kimi/dispatch.md +4 -4
- package/platforms/kiro/dispatch.md +4 -6
- package/platforms/opencode/dispatch.md +4 -4
- package/platforms/qwen/dispatch.md +4 -4
- package/platforms/shared-agents/dispatch.md +12 -19
- package/scripts/_common.py +5 -0
- package/scripts/archive_milestone.py +30 -124
- package/scripts/bootstrap_repository.py +33 -4
- package/scripts/build_recovery.py +292 -0
- package/scripts/build_state.py +2 -0
- package/scripts/check_docs_audit.py +7 -3
- package/scripts/check_handoffs.py +7 -1
- package/scripts/check_task_briefs.py +3 -0
- package/scripts/detect_project.py +22 -3
- package/scripts/discussion_records.py +3 -0
- package/scripts/discussion_validate.py +8 -3
- package/scripts/dispatch_driver.py +233 -22
- package/scripts/git_guard.py +14 -5
- package/scripts/guard_hook.py +11 -1
- package/scripts/install.mjs +54 -1448
- package/scripts/install.py +155 -188
- package/scripts/integration.py +54 -28
- package/scripts/isolation.py +24 -6
- package/scripts/jev_review.py +138 -0
- package/scripts/lean_verification.py +3 -1
- package/scripts/loop_run.py +2 -0
- package/scripts/model_policy.py +279 -0
- package/scripts/path_config.py +248 -0
- package/scripts/pipeline_diagnose.py +15 -3
- package/scripts/pipeline_git.py +3 -0
- package/scripts/pipeline_state.py +117 -186
- package/scripts/pipeline_undo.py +14 -7
- package/scripts/promote_lookahead.py +33 -162
- package/scripts/review_panel.py +11 -0
- package/scripts/roadmap.py +326 -0
- package/scripts/runtime_store.py +278 -0
- package/scripts/skill-resources.json +182 -4
- package/scripts/state_checkpoint.py +114 -72
- package/scripts/state_promote.py +47 -44
- package/scripts/status_runtime.py +92 -6
- package/scripts/task_context.py +73 -0
- package/scripts/token_budget.py +40 -1
- package/scripts/workflow_run.py +128 -8
- package/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path/BUILD.md +54 -575
- package/skills/gsd-path/DECIDE.md +4 -6
- package/skills/gsd-path/DEFINE.md +23 -21
- package/skills/gsd-path/DOCS-AUDIT.md +2 -0
- package/skills/gsd-path/INSPECT.md +32 -8
- package/skills/gsd-path/PLAN.md +29 -19
- package/skills/gsd-path/RESEARCH.md +5 -6
- package/skills/gsd-path/ROADMAP.md +24 -58
- package/skills/gsd-path/SHIP.md +29 -17
- package/skills/gsd-path/SKILL.md +46 -22
- package/skills/gsd-path/references/build-abandon.md +33 -0
- package/skills/gsd-path/references/build-native.md +502 -0
- package/skills/gsd-path/references/build-recovery.md +78 -0
- package/skills/gsd-path/references/coder.md +8 -3
- package/skills/gsd-path/references/config.md +57 -0
- package/skills/gsd-path/references/dispatch.md +12 -19
- package/skills/gsd-path/references/docs-auditor.md +14 -0
- package/skills/gsd-path/references/jev-review.md +82 -0
- package/skills/gsd-path/references/model-policy.md +139 -0
- package/skills/gsd-path/references/reviewer-final.md +21 -0
- package/skills/gsd-path/references/reviewer-panel.md +17 -0
- package/skills/gsd-path/references/reviewer-skeptic.md +20 -0
- package/skills/gsd-path/references/reviewer-wave.md +89 -0
- package/skills/gsd-path/references/reviewer.md +12 -148
- package/skills/gsd-path/references/runtime-selection.md +25 -0
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/_common.py +5 -0
- package/skills/gsd-path/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path/scripts/bootstrap_repository.py +33 -4
- package/skills/gsd-path/scripts/build_recovery.py +292 -0
- package/skills/gsd-path/scripts/build_state.py +2 -0
- package/skills/gsd-path/scripts/check_docs_audit.py +7 -3
- package/skills/gsd-path/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path/scripts/detect_project.py +22 -3
- package/skills/gsd-path/scripts/discussion_records.py +3 -0
- package/skills/gsd-path/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path/scripts/dispatch_driver.py +233 -22
- package/skills/gsd-path/scripts/git_guard.py +768 -0
- package/skills/gsd-path/scripts/integration.py +54 -28
- package/skills/gsd-path/scripts/isolation.py +24 -6
- package/skills/gsd-path/scripts/jev_review.py +138 -0
- package/skills/gsd-path/scripts/lean_verification.py +3 -1
- package/skills/gsd-path/scripts/loop_run.py +2 -0
- package/skills/gsd-path/scripts/model_policy.py +279 -0
- package/skills/gsd-path/scripts/path_config.py +248 -0
- package/skills/gsd-path/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path/scripts/promote_lookahead.py +33 -162
- package/skills/gsd-path/scripts/review_panel.py +11 -0
- package/skills/gsd-path/scripts/roadmap.py +326 -0
- package/skills/gsd-path/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path/scripts/state_promote.py +47 -44
- package/skills/gsd-path/scripts/status_runtime.py +208 -0
- package/skills/gsd-path/scripts/task_context.py +73 -0
- package/skills/gsd-path/scripts/token_budget.py +40 -1
- package/skills/gsd-path/scripts/workflow_run.py +128 -8
- package/skills/gsd-path/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path/templates/skeptic.md +1 -1
- package/skills/gsd-path/templates/task.md +2 -0
- package/skills/gsd-path/templates/wave-panel.md +1 -1
- package/skills/gsd-path/templates/wave-review.md +1 -1
- package/skills/gsd-path-build/SKILL.md +54 -575
- package/skills/gsd-path-build/references/build-abandon.md +33 -0
- package/skills/gsd-path-build/references/build-native.md +502 -0
- package/skills/gsd-path-build/references/build-recovery.md +78 -0
- package/skills/gsd-path-build/references/coder.md +8 -3
- package/skills/gsd-path-build/references/dispatch.md +12 -19
- package/skills/gsd-path-build/references/jev-review.md +82 -0
- package/skills/gsd-path-build/references/model-policy.md +139 -0
- package/skills/gsd-path-build/references/reviewer-final.md +21 -0
- package/skills/gsd-path-build/references/reviewer-panel.md +17 -0
- package/skills/gsd-path-build/references/reviewer-skeptic.md +20 -0
- package/skills/gsd-path-build/references/reviewer-wave.md +89 -0
- package/skills/gsd-path-build/references/reviewer.md +12 -148
- package/skills/gsd-path-build/references/runtime-selection.md +25 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/_common.py +5 -0
- package/skills/gsd-path-build/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path-build/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-build/scripts/build_state.py +2 -0
- package/skills/gsd-path-build/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-build/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-build/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-build/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-build/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-build/scripts/dispatch_driver.py +233 -22
- package/skills/gsd-path-build/scripts/integration.py +54 -28
- package/skills/gsd-path-build/scripts/isolation.py +24 -6
- package/skills/gsd-path-build/scripts/jev_review.py +138 -0
- package/skills/gsd-path-build/scripts/loop_run.py +2 -0
- package/skills/gsd-path-build/scripts/model_policy.py +279 -0
- package/skills/gsd-path-build/scripts/path_config.py +248 -0
- package/skills/gsd-path-build/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-build/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-build/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-build/scripts/review_panel.py +11 -0
- package/skills/gsd-path-build/scripts/roadmap.py +326 -0
- package/skills/gsd-path-build/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-build/scripts/state_promote.py +47 -44
- package/skills/gsd-path-build/scripts/task_context.py +73 -0
- package/skills/gsd-path-build/scripts/token_budget.py +40 -1
- package/skills/gsd-path-build/scripts/workflow_run.py +128 -8
- package/skills/gsd-path-build/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-build/templates/skeptic.md +1 -1
- package/skills/gsd-path-build/templates/task.md +2 -0
- package/skills/gsd-path-build/templates/wave-panel.md +1 -1
- package/skills/gsd-path-build/templates/wave-review.md +1 -1
- package/skills/gsd-path-decide/SKILL.md +4 -6
- package/skills/gsd-path-decide/references/dispatch.md +12 -19
- package/skills/gsd-path-decide/references/model-policy.md +139 -0
- package/skills/gsd-path-decide/references/runtime-selection.md +25 -0
- package/skills/gsd-path-decide/scripts/_common.py +5 -0
- package/skills/gsd-path-decide/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-decide/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-decide/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-decide/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-decide/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-decide/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-decide/scripts/integration.py +1459 -0
- package/skills/gsd-path-decide/scripts/isolation.py +24 -6
- package/skills/gsd-path-decide/scripts/loop_run.py +839 -0
- package/skills/gsd-path-decide/scripts/model_policy.py +279 -0
- package/skills/gsd-path-decide/scripts/path_config.py +248 -0
- package/skills/gsd-path-decide/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-decide/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-decide/scripts/review_panel.py +564 -0
- package/skills/gsd-path-decide/scripts/roadmap.py +326 -0
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-decide/scripts/state_promote.py +47 -44
- package/skills/gsd-path-decide/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-define/SKILL.md +23 -21
- package/skills/gsd-path-define/references/build-recovery.md +78 -0
- package/skills/gsd-path-define/references/runtime-selection.md +25 -0
- package/skills/gsd-path-define/scripts/_common.py +5 -0
- package/skills/gsd-path-define/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-define/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-define/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-define/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-define/scripts/detect_project.py +22 -3
- package/skills/gsd-path-define/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-define/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-define/scripts/integration.py +1459 -0
- package/skills/gsd-path-define/scripts/isolation.py +24 -6
- package/skills/gsd-path-define/scripts/loop_run.py +839 -0
- package/skills/gsd-path-define/scripts/model_policy.py +279 -0
- package/skills/gsd-path-define/scripts/path_config.py +248 -0
- package/skills/gsd-path-define/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-define/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-define/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-define/scripts/review_panel.py +564 -0
- package/skills/gsd-path-define/scripts/roadmap.py +326 -0
- package/skills/gsd-path-define/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-define/scripts/state_promote.py +47 -44
- package/skills/gsd-path-define/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-discuss/SKILL.md +2 -0
- package/skills/gsd-path-discuss/references/runtime-selection.md +25 -0
- package/skills/gsd-path-discuss/scripts/_common.py +5 -0
- package/skills/gsd-path-discuss/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-discuss/scripts/loop_run.py +839 -0
- package/skills/gsd-path-discuss/scripts/model_policy.py +279 -0
- package/skills/gsd-path-discuss/scripts/path_config.py +248 -0
- package/skills/gsd-path-discuss/scripts/review_panel.py +564 -0
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-docs-audit/SKILL.md +2 -0
- package/skills/gsd-path-docs-audit/references/dispatch.md +12 -19
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +14 -0
- package/skills/gsd-path-docs-audit/references/model-policy.md +139 -0
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +25 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +5 -0
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +7 -3
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-docs-audit/scripts/integration.py +1459 -0
- package/skills/gsd-path-docs-audit/scripts/isolation.py +24 -6
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +839 -0
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +279 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +248 -0
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +564 -0
- package/skills/gsd-path-docs-audit/scripts/roadmap.py +326 -0
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +47 -44
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-forensics/SKILL.md +9 -5
- package/skills/gsd-path-forensics/references/runtime-selection.md +25 -0
- package/skills/gsd-path-forensics/scripts/_common.py +5 -0
- package/skills/gsd-path-forensics/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-forensics/scripts/loop_run.py +839 -0
- package/skills/gsd-path-forensics/scripts/model_policy.py +279 -0
- package/skills/gsd-path-forensics/scripts/path_config.py +248 -0
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +15 -3
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +14 -7
- package/skills/gsd-path-forensics/scripts/review_panel.py +564 -0
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-inspect/SKILL.md +32 -8
- package/skills/gsd-path-inspect/references/dispatch.md +12 -19
- package/skills/gsd-path-inspect/references/docs-auditor.md +14 -0
- package/skills/gsd-path-inspect/references/model-policy.md +139 -0
- package/skills/gsd-path-inspect/references/runtime-selection.md +25 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/_common.py +5 -0
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-inspect/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +7 -3
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-inspect/scripts/detect_project.py +22 -3
- package/skills/gsd-path-inspect/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-inspect/scripts/integration.py +1459 -0
- package/skills/gsd-path-inspect/scripts/isolation.py +24 -6
- package/skills/gsd-path-inspect/scripts/loop_run.py +839 -0
- package/skills/gsd-path-inspect/scripts/model_policy.py +279 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +248 -0
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-inspect/scripts/review_panel.py +564 -0
- package/skills/gsd-path-inspect/scripts/roadmap.py +326 -0
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-inspect/scripts/state_promote.py +47 -44
- package/skills/gsd-path-inspect/scripts/workflow_run.py +237 -0
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-loop/SKILL.md +2 -0
- package/skills/gsd-path-loop/references/runtime-selection.md +25 -0
- package/skills/gsd-path-loop/scripts/_common.py +5 -0
- package/skills/gsd-path-loop/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-loop/scripts/loop_run.py +2 -0
- package/skills/gsd-path-loop/scripts/model_policy.py +279 -0
- package/skills/gsd-path-loop/scripts/path_config.py +248 -0
- package/skills/gsd-path-loop/scripts/review_panel.py +564 -0
- package/skills/gsd-path-loop/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-migrate/SKILL.md +2 -0
- package/skills/gsd-path-migrate/references/runtime-selection.md +25 -0
- package/skills/gsd-path-migrate/scripts/_common.py +185 -0
- package/skills/gsd-path-migrate/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-migrate/scripts/loop_run.py +839 -0
- package/skills/gsd-path-migrate/scripts/model_policy.py +279 -0
- package/skills/gsd-path-migrate/scripts/path_config.py +248 -0
- package/skills/gsd-path-migrate/scripts/review_panel.py +564 -0
- package/skills/gsd-path-plan/SKILL.md +29 -19
- package/skills/gsd-path-plan/references/build-recovery.md +78 -0
- package/skills/gsd-path-plan/references/dispatch.md +12 -19
- package/skills/gsd-path-plan/references/model-policy.md +139 -0
- package/skills/gsd-path-plan/references/runtime-selection.md +25 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/_common.py +5 -0
- package/skills/gsd-path-plan/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-plan/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-plan/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-plan/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-plan/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-plan/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-plan/scripts/integration.py +1459 -0
- package/skills/gsd-path-plan/scripts/isolation.py +24 -6
- package/skills/gsd-path-plan/scripts/loop_run.py +839 -0
- package/skills/gsd-path-plan/scripts/model_policy.py +279 -0
- package/skills/gsd-path-plan/scripts/path_config.py +248 -0
- package/skills/gsd-path-plan/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-plan/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-plan/scripts/review_panel.py +11 -0
- package/skills/gsd-path-plan/scripts/roadmap.py +326 -0
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-plan/scripts/state_promote.py +47 -44
- package/skills/gsd-path-plan/scripts/workflow_run.py +128 -8
- package/skills/gsd-path-plan/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-plan/templates/task.md +2 -0
- package/skills/gsd-path-research/SKILL.md +5 -6
- package/skills/gsd-path-research/references/dispatch.md +12 -19
- package/skills/gsd-path-research/references/model-policy.md +139 -0
- package/skills/gsd-path-research/references/runtime-selection.md +25 -0
- package/skills/gsd-path-research/scripts/_common.py +5 -0
- package/skills/gsd-path-research/scripts/archive_milestone.py +2171 -0
- package/skills/gsd-path-research/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-research/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-research/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-research/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-research/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-research/scripts/discussion_validate.py +1326 -0
- package/skills/gsd-path-research/scripts/integration.py +1459 -0
- package/skills/gsd-path-research/scripts/isolation.py +24 -6
- package/skills/gsd-path-research/scripts/loop_run.py +839 -0
- package/skills/gsd-path-research/scripts/model_policy.py +279 -0
- package/skills/gsd-path-research/scripts/path_config.py +248 -0
- package/skills/gsd-path-research/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-research/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-research/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-research/scripts/review_panel.py +564 -0
- package/skills/gsd-path-research/scripts/roadmap.py +326 -0
- package/skills/gsd-path-research/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-research/scripts/state_promote.py +47 -44
- package/skills/gsd-path-research/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-roadmap/SKILL.md +24 -58
- package/skills/gsd-path-roadmap/references/dispatch.md +12 -19
- package/skills/gsd-path-roadmap/references/model-policy.md +139 -0
- package/skills/gsd-path-roadmap/references/runtime-selection.md +25 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +5 -0
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-roadmap/scripts/detect_project.py +22 -3
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-roadmap/scripts/integration.py +54 -28
- package/skills/gsd-path-roadmap/scripts/isolation.py +24 -6
- package/skills/gsd-path-roadmap/scripts/loop_run.py +839 -0
- package/skills/gsd-path-roadmap/scripts/model_policy.py +279 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +248 -0
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +33 -162
- package/skills/gsd-path-roadmap/scripts/review_panel.py +11 -0
- package/skills/gsd-path-roadmap/scripts/roadmap.py +326 -0
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-roadmap/scripts/state_promote.py +47 -44
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-ship/SKILL.md +29 -17
- package/skills/gsd-path-ship/references/dispatch.md +12 -19
- package/skills/gsd-path-ship/references/jev-review.md +82 -0
- package/skills/gsd-path-ship/references/model-policy.md +139 -0
- package/skills/gsd-path-ship/references/reviewer-final.md +21 -0
- package/skills/gsd-path-ship/references/reviewer-panel.md +17 -0
- package/skills/gsd-path-ship/references/reviewer-skeptic.md +20 -0
- package/skills/gsd-path-ship/references/reviewer-wave.md +89 -0
- package/skills/gsd-path-ship/references/reviewer.md +12 -148
- package/skills/gsd-path-ship/references/runtime-selection.md +25 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/_common.py +5 -0
- package/skills/gsd-path-ship/scripts/archive_milestone.py +30 -124
- package/skills/gsd-path-ship/scripts/build_recovery.py +292 -0
- package/skills/gsd-path-ship/scripts/build_state.py +2 -0
- package/skills/gsd-path-ship/scripts/check_handoffs.py +7 -1
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +3 -0
- package/skills/gsd-path-ship/scripts/detect_project.py +1536 -0
- package/skills/gsd-path-ship/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-ship/scripts/discussion_validate.py +8 -3
- package/skills/gsd-path-ship/scripts/integration.py +54 -28
- package/skills/gsd-path-ship/scripts/isolation.py +24 -6
- package/skills/gsd-path-ship/scripts/jev_review.py +138 -0
- package/skills/gsd-path-ship/scripts/lean_verification.py +3 -1
- package/skills/gsd-path-ship/scripts/loop_run.py +839 -0
- package/skills/gsd-path-ship/scripts/model_policy.py +279 -0
- package/skills/gsd-path-ship/scripts/path_config.py +248 -0
- package/skills/gsd-path-ship/scripts/pipeline_git.py +3 -0
- package/skills/gsd-path-ship/scripts/pipeline_state.py +117 -186
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +286 -0
- package/skills/gsd-path-ship/scripts/review_panel.py +11 -0
- package/skills/gsd-path-ship/scripts/roadmap.py +326 -0
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +114 -72
- package/skills/gsd-path-ship/scripts/state_promote.py +47 -44
- package/skills/gsd-path-ship/scripts/workflow_run.py +128 -8
- package/skills/gsd-path-ship/scripts/worktree_paths.py +94 -0
- package/skills/gsd-path-ship/templates/wave-review.md +1 -1
- package/skills/gsd-path-undo/SKILL.md +2 -0
- package/skills/gsd-path-undo/references/runtime-selection.md +25 -0
- package/skills/gsd-path-undo/scripts/_common.py +5 -0
- package/skills/gsd-path-undo/scripts/discussion_records.py +3 -0
- package/skills/gsd-path-undo/scripts/loop_run.py +839 -0
- package/skills/gsd-path-undo/scripts/model_policy.py +279 -0
- package/skills/gsd-path-undo/scripts/path_config.py +248 -0
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +14 -7
- package/skills/gsd-path-undo/scripts/review_panel.py +564 -0
- package/skills/gsd-path-undo/scripts/worktree_paths.py +94 -0
- package/skills/path/BUILD.md +54 -575
- package/skills/path/DECIDE.md +4 -6
- package/skills/path/DEFINE.md +23 -21
- package/skills/path/DOCS-AUDIT.md +2 -0
- package/skills/path/INSPECT.md +32 -8
- package/skills/path/PLAN.md +29 -19
- package/skills/path/RESEARCH.md +5 -6
- package/skills/path/ROADMAP.md +24 -58
- package/skills/path/SHIP.md +29 -17
- package/skills/path/SKILL.md +46 -22
- package/skills/path/references/build-abandon.md +33 -0
- package/skills/path/references/build-native.md +502 -0
- package/skills/path/references/build-recovery.md +78 -0
- package/skills/path/references/coder.md +8 -3
- package/skills/path/references/config.md +57 -0
- package/skills/path/references/dispatch.md +12 -19
- package/skills/path/references/docs-auditor.md +14 -0
- package/skills/path/references/jev-review.md +82 -0
- package/skills/path/references/model-policy.md +139 -0
- package/skills/path/references/reviewer-final.md +21 -0
- package/skills/path/references/reviewer-panel.md +17 -0
- package/skills/path/references/reviewer-skeptic.md +20 -0
- package/skills/path/references/reviewer-wave.md +89 -0
- package/skills/path/references/reviewer.md +12 -148
- package/skills/path/references/runtime-selection.md +25 -0
- package/skills/path/scripts/_common.py +5 -0
- package/skills/path/scripts/archive_milestone.py +30 -124
- package/skills/path/scripts/bootstrap_repository.py +33 -4
- package/skills/path/scripts/build_recovery.py +292 -0
- package/skills/path/scripts/build_state.py +2 -0
- package/skills/path/scripts/check_docs_audit.py +7 -3
- package/skills/path/scripts/check_handoffs.py +7 -1
- package/skills/path/scripts/check_task_briefs.py +3 -0
- package/skills/path/scripts/detect_project.py +22 -3
- package/skills/path/scripts/discussion_records.py +3 -0
- package/skills/path/scripts/discussion_validate.py +8 -3
- package/skills/path/scripts/dispatch_driver.py +233 -22
- package/skills/path/scripts/git_guard.py +768 -0
- package/skills/path/scripts/integration.py +54 -28
- package/skills/path/scripts/isolation.py +24 -6
- package/skills/path/scripts/jev_review.py +138 -0
- package/skills/path/scripts/lean_verification.py +3 -1
- package/skills/path/scripts/loop_run.py +2 -0
- package/skills/path/scripts/model_policy.py +279 -0
- package/skills/path/scripts/path_config.py +248 -0
- package/skills/path/scripts/pipeline_git.py +3 -0
- package/skills/path/scripts/pipeline_state.py +117 -186
- package/skills/path/scripts/promote_lookahead.py +33 -162
- package/skills/path/scripts/review_panel.py +11 -0
- package/skills/path/scripts/roadmap.py +326 -0
- package/skills/path/scripts/state_checkpoint.py +114 -72
- package/skills/path/scripts/state_promote.py +47 -44
- package/skills/path/scripts/status_runtime.py +208 -0
- package/skills/path/scripts/task_context.py +73 -0
- package/skills/path/scripts/token_budget.py +40 -1
- package/skills/path/scripts/workflow_run.py +128 -8
- package/skills/path/scripts/worktree_paths.py +94 -0
- package/skills/path/templates/skeptic.md +1 -1
- package/skills/path/templates/task.md +2 -0
- package/skills/path/templates/wave-panel.md +1 -1
- package/skills/path/templates/wave-review.md +1 -1
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# gsd-path project runtime
|
|
3
|
+
"""Resolve optional GSD Path cross-model review panels without model reasoning.
|
|
4
|
+
|
|
5
|
+
The orchestrator inspects the host's advertised child-model slugs and passes
|
|
6
|
+
them here. This helper parses PLAN.md / INTENT.md / CHARTER.md (PLAN wins,
|
|
7
|
+
then INTENT, then CHARTER, else off), groups slugs into families, skips the
|
|
8
|
+
parent family, caps the panel at three, and merges family artifacts. It never
|
|
9
|
+
probes a host or overrides the canonical inherit reviewer.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
# Runtime helpers must not modify their immutable installation.
|
|
20
|
+
sys.dont_write_bytecode = True
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Iterable, Optional, Sequence
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
KNOWN_FAMILIES = (
|
|
26
|
+
"claude",
|
|
27
|
+
"gpt",
|
|
28
|
+
"grok",
|
|
29
|
+
"composer",
|
|
30
|
+
"gemini",
|
|
31
|
+
"deepseek",
|
|
32
|
+
"kimi",
|
|
33
|
+
"qwen",
|
|
34
|
+
)
|
|
35
|
+
FAMILY_ALIASES = {
|
|
36
|
+
"claude": "claude",
|
|
37
|
+
"opus": "claude",
|
|
38
|
+
"sonnet": "claude",
|
|
39
|
+
"haiku": "claude",
|
|
40
|
+
"gpt": "gpt",
|
|
41
|
+
"openai": "gpt",
|
|
42
|
+
"grok": "grok",
|
|
43
|
+
"composer": "composer",
|
|
44
|
+
"gemini": "gemini",
|
|
45
|
+
"deepseek": "deepseek",
|
|
46
|
+
"kimi": "kimi",
|
|
47
|
+
"moonshot": "kimi",
|
|
48
|
+
"qwen": "qwen",
|
|
49
|
+
}
|
|
50
|
+
MAX_FAMILIES = 3
|
|
51
|
+
PLAN_LINE = re.compile(
|
|
52
|
+
r"^-\s*review_panel:\s*(\S.*?)\s*(?:<!--.*-->)?\s*$", re.MULTILINE
|
|
53
|
+
)
|
|
54
|
+
INTENT_LINE = re.compile(
|
|
55
|
+
r"^Review panel:\s*(\S.*?)\s*(?:<!--.*-->)?\s*$", re.MULTILINE
|
|
56
|
+
)
|
|
57
|
+
VERSION_NUMBERS = re.compile(r"\d+")
|
|
58
|
+
TOKEN_SPLIT = re.compile(r"[^a-z0-9]+")
|
|
59
|
+
FINDING_BLOCK = re.compile(
|
|
60
|
+
r"^###\s+(?P<id>F\d+)\s*$"
|
|
61
|
+
r"(?P<body>.*?)(?=^###\s+F\d+\s*$|\Z)",
|
|
62
|
+
re.MULTILINE | re.DOTALL,
|
|
63
|
+
)
|
|
64
|
+
FIELD_LINE = re.compile(r"^-\s*(?P<key>[A-Za-z][A-Za-z ]*):\s*(?P<value>.*)\s*$")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ReviewPanelError(RuntimeError):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _first_token(value: str) -> str:
|
|
72
|
+
return value.strip().split()[0].strip().strip("`")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _split_families(value: str) -> tuple[str, ...]:
|
|
76
|
+
tokens = [token.strip().casefold() for token in value.split(",") if token.strip()]
|
|
77
|
+
if not tokens:
|
|
78
|
+
raise ReviewPanelError("review_panel named list is empty")
|
|
79
|
+
unknown = [token for token in tokens if token not in KNOWN_FAMILIES]
|
|
80
|
+
if unknown:
|
|
81
|
+
raise ReviewPanelError(
|
|
82
|
+
f"unknown review_panel families: {', '.join(unknown)}"
|
|
83
|
+
)
|
|
84
|
+
seen = []
|
|
85
|
+
for token in tokens:
|
|
86
|
+
if token not in seen:
|
|
87
|
+
seen.append(token)
|
|
88
|
+
if len(seen) > MAX_FAMILIES:
|
|
89
|
+
raise ReviewPanelError(
|
|
90
|
+
f"review_panel names at most {MAX_FAMILIES} families"
|
|
91
|
+
)
|
|
92
|
+
return tuple(seen)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def parse_review_panel_value(raw: str) -> dict:
|
|
96
|
+
value = raw.strip()
|
|
97
|
+
if not value:
|
|
98
|
+
raise ReviewPanelError("review_panel value is empty")
|
|
99
|
+
token = _first_token(value).casefold()
|
|
100
|
+
if token == "off":
|
|
101
|
+
return {"mode": "off", "families": ()}
|
|
102
|
+
if token == "detected":
|
|
103
|
+
return {"mode": "detected", "families": ()}
|
|
104
|
+
families = _split_families(value)
|
|
105
|
+
return {"mode": "named", "families": families}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def extract_config_value(text: str, pattern: re.Pattern[str], source: str) -> Optional[str]:
|
|
109
|
+
matches = pattern.findall(text)
|
|
110
|
+
if not matches:
|
|
111
|
+
return None
|
|
112
|
+
if len(matches) > 1:
|
|
113
|
+
raise ReviewPanelError(f"{source} has multiple review_panel values")
|
|
114
|
+
return matches[0]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parse_sources(
|
|
118
|
+
plan_text: Optional[str],
|
|
119
|
+
intent_text: Optional[str],
|
|
120
|
+
charter_text: Optional[str] = None,
|
|
121
|
+
) -> dict:
|
|
122
|
+
if plan_text is not None:
|
|
123
|
+
plan_value = extract_config_value(plan_text, PLAN_LINE, "PLAN.md")
|
|
124
|
+
if plan_value is not None:
|
|
125
|
+
parsed = parse_review_panel_value(plan_value)
|
|
126
|
+
parsed["source"] = "plan"
|
|
127
|
+
return parsed
|
|
128
|
+
if intent_text is not None:
|
|
129
|
+
intent_value = extract_config_value(intent_text, INTENT_LINE, "INTENT.md")
|
|
130
|
+
if intent_value is not None:
|
|
131
|
+
parsed = parse_review_panel_value(intent_value)
|
|
132
|
+
parsed["source"] = "intent"
|
|
133
|
+
return parsed
|
|
134
|
+
if charter_text is not None:
|
|
135
|
+
charter_value = extract_config_value(charter_text, INTENT_LINE, "CHARTER.md")
|
|
136
|
+
if charter_value is not None:
|
|
137
|
+
parsed = parse_review_panel_value(charter_value)
|
|
138
|
+
parsed["source"] = "charter"
|
|
139
|
+
return parsed
|
|
140
|
+
return {"mode": "off", "families": (), "source": "default"}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def read_optional(path: Optional[Path]) -> Optional[str]:
|
|
144
|
+
if path is None:
|
|
145
|
+
return None
|
|
146
|
+
resolved = path.resolve()
|
|
147
|
+
if resolved.is_symlink() or not resolved.is_file():
|
|
148
|
+
raise ReviewPanelError(f"path must be a real file: {resolved}")
|
|
149
|
+
return resolved.read_text(encoding="utf-8")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def parse_files(
|
|
153
|
+
plan: Optional[Path],
|
|
154
|
+
intent: Optional[Path],
|
|
155
|
+
charter: Optional[Path] = None,
|
|
156
|
+
) -> dict:
|
|
157
|
+
return parse_sources(
|
|
158
|
+
read_optional(plan), read_optional(intent), read_optional(charter)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def plan_review_panel_enabled(plan_text: str) -> bool:
|
|
163
|
+
"""Archive-safe enabled check: anything other than absent/off is on."""
|
|
164
|
+
try:
|
|
165
|
+
parsed = parse_sources(plan_text, None)
|
|
166
|
+
except ReviewPanelError:
|
|
167
|
+
return True
|
|
168
|
+
return parsed["mode"] != "off"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def family_of_slug(slug: str) -> Optional[str]:
|
|
172
|
+
normalized = slug.strip().casefold()
|
|
173
|
+
if not normalized or normalized == "inherit":
|
|
174
|
+
return None
|
|
175
|
+
tokens = TOKEN_SPLIT.split(normalized)
|
|
176
|
+
for token in tokens:
|
|
177
|
+
if token in FAMILY_ALIASES:
|
|
178
|
+
return FAMILY_ALIASES[token]
|
|
179
|
+
for token in tokens:
|
|
180
|
+
stem = token.rstrip("0123456789")
|
|
181
|
+
if stem and stem in FAMILY_ALIASES:
|
|
182
|
+
return FAMILY_ALIASES[stem]
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def version_key(slug: str) -> tuple[int, ...]:
|
|
187
|
+
numbers = tuple(int(part) for part in VERSION_NUMBERS.findall(slug))
|
|
188
|
+
return numbers if numbers else (0,)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def latest_slug(slugs: Sequence[str]) -> str:
|
|
192
|
+
return max(slugs, key=lambda slug: (version_key(slug), len(slug), slug))
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def group_advertised(slugs: Iterable[str]) -> dict[str, list[str]]:
|
|
196
|
+
grouped: dict[str, list[str]] = {family: [] for family in KNOWN_FAMILIES}
|
|
197
|
+
for slug in slugs:
|
|
198
|
+
cleaned = slug.strip()
|
|
199
|
+
if not cleaned:
|
|
200
|
+
continue
|
|
201
|
+
family = family_of_slug(cleaned)
|
|
202
|
+
if family is None:
|
|
203
|
+
continue
|
|
204
|
+
if cleaned not in grouped[family]:
|
|
205
|
+
grouped[family].append(cleaned)
|
|
206
|
+
return {family: values for family, values in grouped.items() if values}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def split_csv(raw: Optional[str]) -> tuple[str, ...]:
|
|
210
|
+
if not raw:
|
|
211
|
+
return ()
|
|
212
|
+
return tuple(part.strip() for part in raw.split(",") if part.strip())
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def resolve_panel(
|
|
216
|
+
config: dict,
|
|
217
|
+
advertised: Sequence[str],
|
|
218
|
+
parent_family: Optional[str] = None,
|
|
219
|
+
parent_slug: Optional[str] = None,
|
|
220
|
+
excluded_families: Sequence[str] = (),
|
|
221
|
+
) -> dict:
|
|
222
|
+
inferred_parent = parent_family or (
|
|
223
|
+
family_of_slug(parent_slug) if parent_slug else None
|
|
224
|
+
)
|
|
225
|
+
grouped = group_advertised(advertised)
|
|
226
|
+
skipped = []
|
|
227
|
+
selected = []
|
|
228
|
+
|
|
229
|
+
if config["mode"] == "off":
|
|
230
|
+
return {
|
|
231
|
+
"mode": "off",
|
|
232
|
+
"status": "off",
|
|
233
|
+
"reason": "review_panel is off",
|
|
234
|
+
"parent_family": inferred_parent,
|
|
235
|
+
"selected": [],
|
|
236
|
+
"skipped": [],
|
|
237
|
+
"missing": [],
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
wanted = (
|
|
241
|
+
list(config["families"])
|
|
242
|
+
if config["mode"] == "named"
|
|
243
|
+
else [family for family in KNOWN_FAMILIES if family in grouped]
|
|
244
|
+
)
|
|
245
|
+
missing = [family for family in wanted if family not in grouped]
|
|
246
|
+
if config["mode"] == "named" and missing:
|
|
247
|
+
raise ReviewPanelError(
|
|
248
|
+
"named review_panel families are not advertised: " + ", ".join(missing)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if config["mode"] == "named" and set(wanted) & set(excluded_families):
|
|
252
|
+
raise ReviewPanelError("named review_panel must use an independent family")
|
|
253
|
+
for family in wanted:
|
|
254
|
+
if family in excluded_families:
|
|
255
|
+
skipped.append({"family": family, "reason": "excluded reviewer family"})
|
|
256
|
+
continue
|
|
257
|
+
if family == inferred_parent:
|
|
258
|
+
skipped.append({"family": family, "reason": "parent family"})
|
|
259
|
+
continue
|
|
260
|
+
slugs = grouped.get(family)
|
|
261
|
+
if not slugs:
|
|
262
|
+
skipped.append({"family": family, "reason": "not advertised"})
|
|
263
|
+
continue
|
|
264
|
+
selected.append({"family": family, "slug": latest_slug(slugs)})
|
|
265
|
+
if len(selected) >= MAX_FAMILIES:
|
|
266
|
+
overflow = wanted[wanted.index(family) + 1 :]
|
|
267
|
+
for extra in overflow:
|
|
268
|
+
if extra != inferred_parent and extra in grouped:
|
|
269
|
+
skipped.append({"family": extra, "reason": "cap"})
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
if selected:
|
|
273
|
+
return {
|
|
274
|
+
"mode": config["mode"],
|
|
275
|
+
"status": "ready",
|
|
276
|
+
"reason": "panel resolved",
|
|
277
|
+
"parent_family": inferred_parent,
|
|
278
|
+
"selected": selected,
|
|
279
|
+
"skipped": skipped,
|
|
280
|
+
"missing": missing if config["mode"] == "detected" else [],
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if config["mode"] == "named":
|
|
284
|
+
raise ReviewPanelError(
|
|
285
|
+
"named review_panel would only repeat the parent family or is empty"
|
|
286
|
+
)
|
|
287
|
+
return {
|
|
288
|
+
"mode": config["mode"],
|
|
289
|
+
"status": "skipped",
|
|
290
|
+
"reason": "no advertised cross-model families",
|
|
291
|
+
"parent_family": inferred_parent,
|
|
292
|
+
"selected": [],
|
|
293
|
+
"skipped": skipped,
|
|
294
|
+
"missing": missing,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _require_field(body: str, key: str, source: str) -> str:
|
|
299
|
+
for line in body.splitlines():
|
|
300
|
+
match = FIELD_LINE.fullmatch(line.strip())
|
|
301
|
+
if match and match.group("key").casefold() == key.casefold():
|
|
302
|
+
value = match.group("value").strip()
|
|
303
|
+
if not value:
|
|
304
|
+
raise ReviewPanelError(f"{source} has an empty {key} field")
|
|
305
|
+
return value
|
|
306
|
+
raise ReviewPanelError(f"{source} is missing the {key} field")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def parse_findings(text: str, source: str) -> list[dict]:
|
|
310
|
+
findings = []
|
|
311
|
+
for match in FINDING_BLOCK.finditer(text):
|
|
312
|
+
body = match.group("body")
|
|
313
|
+
severity = _require_field(body, "Severity", f"{source} {match.group('id')}").casefold()
|
|
314
|
+
kind = _require_field(body, "Kind", f"{source} {match.group('id')}").casefold()
|
|
315
|
+
criterion = _require_field(body, "Criterion", f"{source} {match.group('id')}")
|
|
316
|
+
evidence = _require_field(body, "Evidence", f"{source} {match.group('id')}")
|
|
317
|
+
found = _require_field(body, "Found", f"{source} {match.group('id')}")
|
|
318
|
+
fix = _require_field(body, "Fix direction", f"{source} {match.group('id')}")
|
|
319
|
+
if severity not in {"high", "medium", "low"}:
|
|
320
|
+
raise ReviewPanelError(f"{source} {match.group('id')} has an invalid Severity")
|
|
321
|
+
if kind not in {"criterion", "preference"}:
|
|
322
|
+
raise ReviewPanelError(f"{source} {match.group('id')} has an invalid Kind")
|
|
323
|
+
if kind == "criterion" and criterion.casefold() == "none":
|
|
324
|
+
raise ReviewPanelError(
|
|
325
|
+
f"{source} {match.group('id')} criterion findings must name a criterion"
|
|
326
|
+
)
|
|
327
|
+
findings.append(
|
|
328
|
+
{
|
|
329
|
+
"id": match.group("id"),
|
|
330
|
+
"severity": severity,
|
|
331
|
+
"kind": kind,
|
|
332
|
+
"criterion": criterion,
|
|
333
|
+
"evidence": evidence,
|
|
334
|
+
"found": found,
|
|
335
|
+
"fix": fix,
|
|
336
|
+
"source": source,
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
if not findings:
|
|
340
|
+
findings_heading = re.search(r"^## Findings\s*$", text, re.MULTILINE)
|
|
341
|
+
if findings_heading:
|
|
342
|
+
rest = text[findings_heading.end() :]
|
|
343
|
+
next_heading = re.search(r"^## ", rest, re.MULTILINE)
|
|
344
|
+
section = rest[: next_heading.start()] if next_heading else rest
|
|
345
|
+
if re.search(r"^-\s*none\s*$", section, re.MULTILINE):
|
|
346
|
+
return []
|
|
347
|
+
raise ReviewPanelError(f"{source} has no findings")
|
|
348
|
+
return findings
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def parse_family_artifact(path: Path, expected_kind: str) -> dict:
|
|
352
|
+
if path.is_symlink() or not path.is_file():
|
|
353
|
+
raise ReviewPanelError(f"panel artifact must be a real file: {path}")
|
|
354
|
+
text = path.read_text(encoding="utf-8")
|
|
355
|
+
lines = text.splitlines()
|
|
356
|
+
if expected_kind == "plan":
|
|
357
|
+
if not lines or not lines[0].startswith("# Plan panel — "):
|
|
358
|
+
raise ReviewPanelError(f"{path.name} heading must start with '# Plan panel — '")
|
|
359
|
+
elif expected_kind == "wave":
|
|
360
|
+
if not lines or not lines[0].startswith("# Panel — wave "):
|
|
361
|
+
raise ReviewPanelError(f"{path.name} heading must start with '# Panel — wave '")
|
|
362
|
+
else:
|
|
363
|
+
raise ReviewPanelError(f"unknown panel kind: {expected_kind}")
|
|
364
|
+
family = _require_field(text, "Family", path.name).casefold()
|
|
365
|
+
model = _require_field(text, "Model", path.name)
|
|
366
|
+
if family not in KNOWN_FAMILIES:
|
|
367
|
+
raise ReviewPanelError(f"{path.name} has an unknown Family")
|
|
368
|
+
if "## Findings" not in text:
|
|
369
|
+
raise ReviewPanelError(f"{path.name} is missing ## Findings")
|
|
370
|
+
findings = parse_findings(text, path.name)
|
|
371
|
+
return {
|
|
372
|
+
"path": str(path),
|
|
373
|
+
"family": family,
|
|
374
|
+
"model": model,
|
|
375
|
+
"text": text.rstrip() + "\n",
|
|
376
|
+
"findings": findings,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def actionable(finding: dict) -> bool:
|
|
381
|
+
return finding["kind"] == "criterion" and finding["severity"] == "high"
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def detect_conflicts(findings: Sequence[dict]) -> list[dict]:
|
|
385
|
+
by_criterion: dict[str, list[dict]] = {}
|
|
386
|
+
for finding in findings:
|
|
387
|
+
key = finding["criterion"].strip().casefold()
|
|
388
|
+
if key in {"", "none"}:
|
|
389
|
+
continue
|
|
390
|
+
by_criterion.setdefault(key, []).append(finding)
|
|
391
|
+
conflicts = []
|
|
392
|
+
for criterion, group in by_criterion.items():
|
|
393
|
+
severities = {item["severity"] for item in group}
|
|
394
|
+
# A conflict is families disagreeing on whether the criterion needs a
|
|
395
|
+
# fix; wording differences in Found are not conflicts.
|
|
396
|
+
if len({actionable(item) for item in group}) > 1:
|
|
397
|
+
conflicts.append(
|
|
398
|
+
{
|
|
399
|
+
"criterion": group[0]["criterion"],
|
|
400
|
+
"families": [item["source"] for item in group],
|
|
401
|
+
"severities": sorted(severities),
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
return conflicts
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def render_finding(finding: dict) -> str:
|
|
408
|
+
return (
|
|
409
|
+
f"- {finding['severity']} / {finding['kind']} — {finding['criterion']}\n"
|
|
410
|
+
f" evidence: {finding['evidence']}\n"
|
|
411
|
+
f" found: {finding['found']}\n"
|
|
412
|
+
f" fix: {finding['fix']}\n"
|
|
413
|
+
f" source: {finding['source']}\n"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def merge_artifacts(
|
|
418
|
+
kind: str,
|
|
419
|
+
inputs: Sequence[Path],
|
|
420
|
+
output: Path,
|
|
421
|
+
header_fields: Sequence[tuple[str, str]],
|
|
422
|
+
) -> dict:
|
|
423
|
+
artifacts = [parse_family_artifact(path, kind) for path in inputs]
|
|
424
|
+
families = [artifact["family"] for artifact in artifacts]
|
|
425
|
+
if len(families) != len(set(families)):
|
|
426
|
+
raise ReviewPanelError("panel merge repeats a family")
|
|
427
|
+
findings = [finding for artifact in artifacts for finding in artifact["findings"]]
|
|
428
|
+
action = [finding for finding in findings if actionable(finding)]
|
|
429
|
+
warnings = [finding for finding in findings if not actionable(finding)]
|
|
430
|
+
conflicts = detect_conflicts(findings)
|
|
431
|
+
title = "# Plan panel" if kind == "plan" else "# Wave panel"
|
|
432
|
+
lines = [title, ""]
|
|
433
|
+
for key, value in header_fields:
|
|
434
|
+
lines.append(f"{key}: {value}")
|
|
435
|
+
lines.extend(
|
|
436
|
+
[
|
|
437
|
+
f"Families: {', '.join(families)}",
|
|
438
|
+
f"Actionable: {len(action)}",
|
|
439
|
+
f"Warnings: {len(warnings)}",
|
|
440
|
+
f"Conflicts: {len(conflicts)}",
|
|
441
|
+
"",
|
|
442
|
+
"## Actionable",
|
|
443
|
+
"",
|
|
444
|
+
]
|
|
445
|
+
)
|
|
446
|
+
if action:
|
|
447
|
+
lines.extend(render_finding(finding) for finding in action)
|
|
448
|
+
else:
|
|
449
|
+
lines.append("- none")
|
|
450
|
+
lines.append("")
|
|
451
|
+
lines.extend(["## Warnings", ""])
|
|
452
|
+
if warnings:
|
|
453
|
+
lines.extend(render_finding(finding) for finding in warnings)
|
|
454
|
+
else:
|
|
455
|
+
lines.append("- none")
|
|
456
|
+
lines.append("")
|
|
457
|
+
lines.extend(["## Conflicts", ""])
|
|
458
|
+
if conflicts:
|
|
459
|
+
for conflict in conflicts:
|
|
460
|
+
lines.append(
|
|
461
|
+
f"- {conflict['criterion']} — families {', '.join(conflict['families'])}; "
|
|
462
|
+
f"severities {', '.join(conflict['severities'])}"
|
|
463
|
+
)
|
|
464
|
+
lines.append("")
|
|
465
|
+
else:
|
|
466
|
+
lines.append("- none")
|
|
467
|
+
lines.append("")
|
|
468
|
+
lines.extend(["## By family", ""])
|
|
469
|
+
for artifact in artifacts:
|
|
470
|
+
lines.append(artifact["text"].rstrip())
|
|
471
|
+
lines.append("")
|
|
472
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
473
|
+
output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
|
|
474
|
+
return {
|
|
475
|
+
"output": str(output.resolve()),
|
|
476
|
+
"families": families,
|
|
477
|
+
"actionable": len(action),
|
|
478
|
+
"warnings": len(warnings),
|
|
479
|
+
"conflicts": len(conflicts),
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def emit(payload: dict) -> int:
|
|
484
|
+
json.dump(payload, sys.stdout, indent=2, sort_keys=True)
|
|
485
|
+
sys.stdout.write("\n")
|
|
486
|
+
return 0
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def parser() -> argparse.ArgumentParser:
|
|
490
|
+
result = argparse.ArgumentParser(description=__doc__)
|
|
491
|
+
result.add_argument(
|
|
492
|
+
"command", choices=("parse", "resolve", "families", "merge", "validate-plan")
|
|
493
|
+
)
|
|
494
|
+
result.add_argument("--plan", type=Path)
|
|
495
|
+
result.add_argument("--intent", type=Path)
|
|
496
|
+
result.add_argument("--charter", type=Path)
|
|
497
|
+
result.add_argument("--advertised", default="")
|
|
498
|
+
result.add_argument("--parent-family")
|
|
499
|
+
result.add_argument("--parent-slug")
|
|
500
|
+
result.add_argument("--exclude-family", action="append", choices=KNOWN_FAMILIES, default=[])
|
|
501
|
+
result.add_argument("--kind", choices=("plan", "wave"))
|
|
502
|
+
result.add_argument("--inputs", default="")
|
|
503
|
+
result.add_argument("--output", type=Path)
|
|
504
|
+
result.add_argument("--wave")
|
|
505
|
+
result.add_argument("--cycle")
|
|
506
|
+
result.add_argument("--mode")
|
|
507
|
+
return result
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
511
|
+
arguments = parser().parse_args(argv)
|
|
512
|
+
try:
|
|
513
|
+
if arguments.command == "families":
|
|
514
|
+
grouped = group_advertised(split_csv(arguments.advertised))
|
|
515
|
+
return emit(
|
|
516
|
+
{
|
|
517
|
+
"families": {
|
|
518
|
+
family: {"slugs": slugs, "latest": latest_slug(slugs)}
|
|
519
|
+
for family, slugs in grouped.items()
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
)
|
|
523
|
+
if arguments.command == "merge":
|
|
524
|
+
if arguments.kind is None or arguments.output is None:
|
|
525
|
+
raise ReviewPanelError("merge requires --kind and --output")
|
|
526
|
+
inputs = [Path(part) for part in split_csv(arguments.inputs)]
|
|
527
|
+
if not inputs:
|
|
528
|
+
raise ReviewPanelError("merge requires --inputs")
|
|
529
|
+
header = []
|
|
530
|
+
if arguments.mode:
|
|
531
|
+
header.append(("Mode", arguments.mode))
|
|
532
|
+
if arguments.kind == "wave":
|
|
533
|
+
if not arguments.wave or not arguments.cycle:
|
|
534
|
+
raise ReviewPanelError("wave merge requires --wave and --cycle")
|
|
535
|
+
header.extend(
|
|
536
|
+
(("Wave", arguments.wave), ("Cycle", arguments.cycle))
|
|
537
|
+
)
|
|
538
|
+
return emit(merge_artifacts(arguments.kind, inputs, arguments.output, header))
|
|
539
|
+
if arguments.plan is None and arguments.command in {"parse", "resolve", "validate-plan"}:
|
|
540
|
+
if arguments.command != "parse" or (
|
|
541
|
+
arguments.intent is None and arguments.charter is None
|
|
542
|
+
):
|
|
543
|
+
raise ReviewPanelError(
|
|
544
|
+
f"{arguments.command} requires --plan, --intent, or --charter"
|
|
545
|
+
)
|
|
546
|
+
config = parse_files(arguments.plan, arguments.intent, arguments.charter)
|
|
547
|
+
if arguments.command in {"parse", "validate-plan"}:
|
|
548
|
+
return emit(config)
|
|
549
|
+
resolved = resolve_panel(
|
|
550
|
+
config,
|
|
551
|
+
split_csv(arguments.advertised),
|
|
552
|
+
arguments.parent_family,
|
|
553
|
+
arguments.parent_slug,
|
|
554
|
+
arguments.exclude_family,
|
|
555
|
+
)
|
|
556
|
+
return emit(resolved)
|
|
557
|
+
except ReviewPanelError as error:
|
|
558
|
+
json.dump({"status": "error", "error": str(error)}, sys.stdout, indent=2)
|
|
559
|
+
sys.stdout.write("\n")
|
|
560
|
+
return 2
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
if __name__ == "__main__":
|
|
564
|
+
sys.exit(main())
|
|
@@ -3,6 +3,8 @@ name: gsd-path-plan
|
|
|
3
3
|
description: Create and validate dependency-ordered GSD Path build waves and complete task contracts. Use only when the user explicitly invokes $gsd-path-plan or an active $gsd-path router explicitly routes to this phase.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
Before executing project helpers, read [runtime selection](references/runtime-selection.md).
|
|
7
|
+
|
|
6
8
|
# GSD Path Planning Phase
|
|
7
9
|
|
|
8
10
|
Dispatch one planner, gate its artifacts, and obtain the single approval that
|
|
@@ -29,8 +31,11 @@ Before dispatch, enter from `decide/done` or `define/done` with
|
|
|
29
31
|
`--set-phase plan --set-status active`, and event `planning started`. Resume
|
|
30
32
|
`plan/blocked` through the same helper with event `planning resumed`. Require
|
|
31
33
|
the returned track state to be `plan/active`; never edit STATE directly.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
For a blocked build, use the [build recovery contract](references/build-recovery.md).
|
|
35
|
+
The runtime's `build-repair` mode owns structural changes to the remaining task
|
|
36
|
+
inventory and the normal approval checkpoint; it is distinct from append-only
|
|
37
|
+
patch mode. Otherwise later phases block instead of replacing an approved plan.
|
|
38
|
+
Patch mode requires an approved plan, no in-progress task, and
|
|
34
39
|
state `plan/done` or `ship/blocked`; any other state blocks. `build/done` is
|
|
35
40
|
reserved exclusively for the build orchestrator's transition recovery and
|
|
36
41
|
must return there instead of reopening planning. When an active router
|
|
@@ -74,10 +79,17 @@ router supplied lookahead mode. Every per-milestone path below, including
|
|
|
74
79
|
review-panel evidence, is rooted there. Program inputs remain at `.project/`
|
|
75
80
|
as stated in Lookahead mode.
|
|
76
81
|
|
|
77
|
-
1.
|
|
78
|
-
|
|
79
|
-
and
|
|
80
|
-
|
|
82
|
+
1. Resolve the local [plan template](templates/plan.md), [task template](templates/task.md),
|
|
83
|
+
`scripts/check_handoffs.py`, and `scripts/review_panel.py` to absolute paths.
|
|
84
|
+
Quick mode reads the plan and task templates because the parent writes
|
|
85
|
+
those artifacts. Other modes pass their paths to the planner. Execute the
|
|
86
|
+
helpers through the commands below and consume their results; their Python
|
|
87
|
+
source is not planning input. Read helper source only when diagnosing a
|
|
88
|
+
concrete failure that the returned error and documented recovery cannot
|
|
89
|
+
explain.
|
|
90
|
+
2. In quick mode, skip planner dispatch and follow Quick mode below; do not
|
|
91
|
+
load the planner role or dispatch contract. In other modes, resolve the
|
|
92
|
+
local [planner role](references/planner.md) for the child, then follow the
|
|
81
93
|
local [runtime dispatch contract](references/dispatch.md) with deterministic
|
|
82
94
|
logical task name `plan`. Give it absolute role, `AGENTS.md`, `WORKFLOW.md`,
|
|
83
95
|
input, template, and output paths, including `.project/LESSONS.md` when it
|
|
@@ -133,7 +145,7 @@ as stated in Lookahead mode.
|
|
|
133
145
|
- Require a `Review depth` value (`full`, `deep`, or `verify-only`) on
|
|
134
146
|
every wave. Wave 1 and any wave touching authentication, authorization,
|
|
135
147
|
payments, data migration, or concurrency requires `full` or `deep`;
|
|
136
|
-
quick-lane single waves
|
|
148
|
+
this includes quick-lane single waves. The planner assigns `deep`
|
|
137
149
|
sparingly to waves where a wrong merge is irreversible or
|
|
138
150
|
security-critical.
|
|
139
151
|
- When INTENT.md `Surfaces:` is not `none`, require a PLAN.md
|
|
@@ -199,11 +211,13 @@ as stated in Lookahead mode.
|
|
|
199
211
|
- `status: error` or exit 2 — use the same guarded transition to set
|
|
200
212
|
`plan/blocked` with an event naming the helper error, link PLAN.md, and stop.
|
|
201
213
|
A named family that is not advertised is an assertion failure.
|
|
202
|
-
- `status: ready` —
|
|
214
|
+
- `status: ready` — resolve the local
|
|
215
|
+
[plan-panel template](templates/plan-panel.md) for the reviewers; other
|
|
216
|
+
statuses do not load this template. For each selected family, spawn one independent child
|
|
203
217
|
with logical task name `review_plan_panel_<family>`, the reviewer role
|
|
204
218
|
in plan-panel mode, the plan-panel template, and the exact helper-returned
|
|
205
|
-
model slug
|
|
206
|
-
|
|
219
|
+
model slug through the dispatch model-policy contract. Planner choices
|
|
220
|
+
use that same contract. Each child stages its family file under a
|
|
207
221
|
disposable root; the parent validates and copies those files, removes
|
|
208
222
|
any stale `<track>/review/PLAN-PANEL.skipped.json`, then runs
|
|
209
223
|
`python3 <absolute review_panel.py> merge --kind plan --inputs <family
|
|
@@ -257,12 +271,8 @@ as stated in Lookahead mode.
|
|
|
257
271
|
(event `patch plan approved`) without a plan checkpoint because build's
|
|
258
272
|
patch re-entry commits the artifacts with its `build/active` transition.
|
|
259
273
|
`pipeline_state.py transition` never approves a plan. Confirm approval,
|
|
260
|
-
link PLAN.md again, and
|
|
261
|
-
|
|
262
|
-
active `$gsd-path`, return control to that router so its bundled build
|
|
263
|
-
contract starts. When invoked directly, stop and tell the user to explicitly
|
|
264
|
-
invoke `$gsd-path`, which routes to build; do not invoke an explicit-only
|
|
265
|
-
sibling skill yourself.
|
|
274
|
+
link PLAN.md again, and use the executable phase handoff in AGENTS.md.
|
|
275
|
+
The plan approval also authorizes build; do not add another approval gate.
|
|
266
276
|
|
|
267
277
|
## Ordering rules
|
|
268
278
|
|
|
@@ -273,8 +283,8 @@ as stated in Lookahead mode.
|
|
|
273
283
|
## Quick mode
|
|
274
284
|
|
|
275
285
|
Legal entry: `define/done` where INTENT.md records `Lane: quick` (transition
|
|
276
|
-
to `plan/active
|
|
277
|
-
|
|
286
|
+
to `plan/active` using exactly the Preconditions event `planning started`;
|
|
287
|
+
record the skipped research and decide phases in the synthesis). Quick mode dispatches no planner agent — the orchestrator writes
|
|
278
288
|
the artifacts directly:
|
|
279
289
|
|
|
280
290
|
1. Write `.project/research/SYNTHESIS.md` containing only `## Settled` lines
|
|
@@ -282,7 +292,7 @@ the artifacts directly:
|
|
|
282
292
|
plus a minimal `## For the planner` naming the walking skeleton. No
|
|
283
293
|
invented decisions or runner-ups.
|
|
284
294
|
2. Write `.project/plan/PLAN.md` with exactly one wave — `Review depth:
|
|
285
|
-
|
|
295
|
+
full` or `deep` — and at most two deliverable-sized task files
|
|
286
296
|
(project policy),
|
|
287
297
|
honoring every task-contract rule above and `.project/LESSONS.md` when it
|
|
288
298
|
exists. Write `review_panel: off` and `finding_skeptics: off` regardless
|