@opengsd/gsd-path 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +460 -341
- package/DOCS.md +16 -5
- package/FULL.md +67 -6
- package/HOOKS.md +158 -10
- package/MIGRATE.md +12 -0
- package/QUICK.md +4 -2
- package/README.md +136 -116
- package/RUNTIME.md +24 -6
- package/UPDATE.md +39 -3
- package/WORKFLOW.md +80 -4
- package/package.json +5 -2
- package/platforms/shared-agents/dispatch.md +13 -0
- package/scripts/_common.py +256 -2
- package/scripts/archive_milestone.py +14 -13
- package/scripts/build_recovery.py +155 -58
- package/scripts/build_state.py +274 -8
- package/scripts/check_docs_audit.py +77 -4
- package/scripts/check_handoffs.py +635 -57
- package/scripts/check_task_briefs.py +293 -32
- package/scripts/detect_project.py +260 -20
- package/scripts/discussion_records.py +9 -1
- package/scripts/discussion_validate.py +93 -35
- package/scripts/dispatch_driver.py +549 -53
- package/scripts/git_guard.py +27 -8
- package/scripts/guard_hook.py +2630 -210
- package/scripts/install.mjs +99 -11
- package/scripts/install.py +231 -46
- package/scripts/integration.py +244 -65
- package/scripts/isolation.py +539 -70
- package/scripts/loop_run.py +16 -4
- package/scripts/members.py +77 -32
- package/scripts/migrate_core.py +2 -0
- package/scripts/model_policy.py +5 -1
- package/scripts/path_config.py +3 -3
- package/scripts/pipeline_diagnose.py +17 -2
- package/scripts/pipeline_git.py +131 -42
- package/scripts/pipeline_state.py +139 -39
- package/scripts/pipeline_undo.py +118 -16
- package/scripts/promote_lookahead.py +3 -3
- package/scripts/review_findings.py +180 -44
- package/scripts/roadmap.py +9 -1
- package/scripts/runtime_store.py +21 -2
- package/scripts/skill-resources.json +9 -2
- package/scripts/state_checkpoint.py +39 -36
- package/scripts/state_promote.py +15 -4
- package/scripts/status_runtime.py +22 -4
- package/scripts/task_context.py +14 -3
- package/scripts/wizard.mjs +1 -0
- package/scripts/workflow_run.py +14 -4
- package/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path/DEFINE.md +34 -5
- package/skills/gsd-path/PLAN.md +14 -3
- package/skills/gsd-path/RESEARCH.md +11 -0
- package/skills/gsd-path/ROADMAP.md +16 -6
- package/skills/gsd-path/SHIP.md +32 -5
- package/skills/gsd-path/SKILL.md +52 -24
- package/skills/gsd-path/references/build-native.md +112 -10
- package/skills/gsd-path/references/build-recovery.md +13 -0
- package/skills/gsd-path/references/coder.md +7 -3
- package/skills/gsd-path/references/dispatch.md +13 -0
- package/skills/gsd-path/references/docs-auditor.md +5 -0
- package/skills/gsd-path/references/operating-rules.md +11 -3
- package/skills/gsd-path/references/planner.md +26 -4
- package/skills/gsd-path/references/reviewer-final.md +20 -2
- package/skills/gsd-path/references/reviewer-wave.md +56 -12
- package/skills/gsd-path/references/reviewer.md +3 -1
- package/skills/gsd-path/references/runtime-selection.md +9 -0
- package/skills/gsd-path/references/spec-probes.md +150 -0
- package/skills/gsd-path/scripts/_common.py +256 -2
- package/skills/gsd-path/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path/scripts/build_recovery.py +155 -58
- package/skills/gsd-path/scripts/build_state.py +274 -8
- package/skills/gsd-path/scripts/check_docs_audit.py +77 -4
- package/skills/gsd-path/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path/scripts/detect_project.py +260 -20
- package/skills/gsd-path/scripts/discussion_records.py +9 -1
- package/skills/gsd-path/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path/scripts/dispatch_driver.py +549 -53
- package/skills/gsd-path/scripts/git_guard.py +27 -8
- package/skills/gsd-path/scripts/integration.py +244 -65
- package/skills/gsd-path/scripts/isolation.py +539 -70
- package/skills/gsd-path/scripts/loop_run.py +16 -4
- package/skills/gsd-path/scripts/members.py +77 -32
- package/skills/gsd-path/scripts/model_policy.py +5 -1
- package/skills/gsd-path/scripts/path_config.py +3 -3
- package/skills/gsd-path/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path/scripts/review_findings.py +180 -44
- package/skills/gsd-path/scripts/roadmap.py +9 -1
- package/skills/gsd-path/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path/scripts/state_promote.py +15 -4
- package/skills/gsd-path/scripts/status_runtime.py +22 -4
- package/skills/gsd-path/scripts/task_context.py +14 -3
- package/skills/gsd-path/scripts/workflow_run.py +14 -4
- package/skills/gsd-path/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path/templates/docs-audit.md +5 -1
- package/skills/gsd-path/templates/final-review.md +11 -2
- package/skills/gsd-path/templates/intent.md +30 -0
- package/skills/gsd-path/templates/patch-findings.md +3 -3
- package/skills/gsd-path/templates/plan.md +17 -2
- package/skills/gsd-path/templates/research-handoff.md +14 -3
- package/skills/gsd-path/templates/state.md +2 -1
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +28 -9
- package/skills/gsd-path-build/references/build-native.md +112 -10
- package/skills/gsd-path-build/references/build-recovery.md +13 -0
- package/skills/gsd-path-build/references/coder.md +7 -3
- package/skills/gsd-path-build/references/dispatch.md +13 -0
- package/skills/gsd-path-build/references/operating-rules.md +11 -3
- package/skills/gsd-path-build/references/reviewer-final.md +20 -2
- package/skills/gsd-path-build/references/reviewer-wave.md +56 -12
- package/skills/gsd-path-build/references/reviewer.md +3 -1
- package/skills/gsd-path-build/references/runtime-selection.md +9 -0
- package/skills/gsd-path-build/references/spec-probes.md +150 -0
- package/skills/gsd-path-build/scripts/_common.py +256 -2
- package/skills/gsd-path-build/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-build/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-build/scripts/build_state.py +274 -8
- package/skills/gsd-path-build/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-build/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-build/scripts/detect_project.py +260 -20
- package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-build/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-build/scripts/dispatch_driver.py +549 -53
- package/skills/gsd-path-build/scripts/integration.py +244 -65
- package/skills/gsd-path-build/scripts/isolation.py +539 -70
- package/skills/gsd-path-build/scripts/loop_run.py +16 -4
- package/skills/gsd-path-build/scripts/members.py +77 -32
- package/skills/gsd-path-build/scripts/model_policy.py +5 -1
- package/skills/gsd-path-build/scripts/path_config.py +3 -3
- package/skills/gsd-path-build/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-build/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-build/scripts/review_findings.py +180 -44
- package/skills/gsd-path-build/scripts/roadmap.py +9 -1
- package/skills/gsd-path-build/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-build/scripts/state_promote.py +15 -4
- package/skills/gsd-path-build/scripts/task_context.py +14 -3
- package/skills/gsd-path-build/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-build/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +28 -9
- package/skills/gsd-path-decide/references/dispatch.md +13 -0
- package/skills/gsd-path-decide/references/operating-rules.md +11 -3
- package/skills/gsd-path-decide/references/runtime-selection.md +9 -0
- package/skills/gsd-path-decide/scripts/_common.py +256 -2
- package/skills/gsd-path-decide/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-decide/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-decide/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-decide/scripts/detect_project.py +260 -20
- package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-decide/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-decide/scripts/integration.py +244 -65
- package/skills/gsd-path-decide/scripts/isolation.py +539 -70
- package/skills/gsd-path-decide/scripts/loop_run.py +16 -4
- package/skills/gsd-path-decide/scripts/members.py +77 -32
- package/skills/gsd-path-decide/scripts/model_policy.py +5 -1
- package/skills/gsd-path-decide/scripts/path_config.py +3 -3
- package/skills/gsd-path-decide/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-decide/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-decide/scripts/roadmap.py +9 -1
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-decide/scripts/state_promote.py +15 -4
- package/skills/gsd-path-decide/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-define/SKILL.md +34 -5
- package/skills/gsd-path-define/references/build-recovery.md +13 -0
- package/skills/gsd-path-define/references/operating-rules.md +11 -3
- package/skills/gsd-path-define/references/runtime-selection.md +9 -0
- package/skills/gsd-path-define/references/spec-probes.md +150 -0
- package/skills/gsd-path-define/scripts/_common.py +256 -2
- package/skills/gsd-path-define/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-define/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-define/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-define/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-define/scripts/detect_project.py +260 -20
- package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-define/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-define/scripts/integration.py +244 -65
- package/skills/gsd-path-define/scripts/isolation.py +539 -70
- package/skills/gsd-path-define/scripts/loop_run.py +16 -4
- package/skills/gsd-path-define/scripts/members.py +77 -32
- package/skills/gsd-path-define/scripts/model_policy.py +5 -1
- package/skills/gsd-path-define/scripts/path_config.py +3 -3
- package/skills/gsd-path-define/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-define/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-define/scripts/roadmap.py +9 -1
- package/skills/gsd-path-define/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-define/scripts/state_promote.py +15 -4
- package/skills/gsd-path-define/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-define/templates/intent.md +30 -0
- package/skills/gsd-path-define/templates/state.md +2 -1
- package/skills/gsd-path-discuss/references/operating-rules.md +11 -3
- package/skills/gsd-path-discuss/references/runtime-selection.md +9 -0
- package/skills/gsd-path-discuss/scripts/_common.py +256 -2
- package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-discuss/scripts/loop_run.py +16 -4
- package/skills/gsd-path-discuss/scripts/model_policy.py +5 -1
- package/skills/gsd-path-discuss/scripts/path_config.py +3 -3
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +5 -0
- package/skills/gsd-path-docs-audit/references/operating-rules.md +11 -3
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +9 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +256 -2
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +77 -4
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +260 -20
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-docs-audit/scripts/integration.py +244 -65
- package/skills/gsd-path-docs-audit/scripts/isolation.py +539 -70
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +16 -4
- package/skills/gsd-path-docs-audit/scripts/members.py +77 -32
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +5 -1
- package/skills/gsd-path-docs-audit/scripts/path_config.py +3 -3
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-docs-audit/scripts/roadmap.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +15 -4
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-docs-audit/templates/docs-audit.md +5 -1
- package/skills/gsd-path-forensics/references/operating-rules.md +11 -3
- package/skills/gsd-path-forensics/references/runtime-selection.md +9 -0
- package/skills/gsd-path-forensics/scripts/_common.py +256 -2
- package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-forensics/scripts/loop_run.py +16 -4
- package/skills/gsd-path-forensics/scripts/model_policy.py +5 -1
- package/skills/gsd-path-forensics/scripts/path_config.py +3 -3
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +118 -16
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-inspect/references/dispatch.md +13 -0
- package/skills/gsd-path-inspect/references/docs-auditor.md +5 -0
- package/skills/gsd-path-inspect/references/operating-rules.md +11 -3
- package/skills/gsd-path-inspect/references/runtime-selection.md +9 -0
- package/skills/gsd-path-inspect/scripts/_common.py +256 -2
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-inspect/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +77 -4
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-inspect/scripts/detect_project.py +260 -20
- package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-inspect/scripts/integration.py +244 -65
- package/skills/gsd-path-inspect/scripts/isolation.py +539 -70
- package/skills/gsd-path-inspect/scripts/loop_run.py +16 -4
- package/skills/gsd-path-inspect/scripts/members.py +77 -32
- package/skills/gsd-path-inspect/scripts/model_policy.py +5 -1
- package/skills/gsd-path-inspect/scripts/path_config.py +3 -3
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-inspect/scripts/roadmap.py +9 -1
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-inspect/scripts/state_promote.py +15 -4
- package/skills/gsd-path-inspect/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-inspect/templates/docs-audit.md +5 -1
- package/skills/gsd-path-inspect/templates/state.md +2 -1
- package/skills/gsd-path-loop/references/operating-rules.md +11 -3
- package/skills/gsd-path-loop/references/runtime-selection.md +9 -0
- package/skills/gsd-path-loop/scripts/_common.py +256 -2
- package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-loop/scripts/loop_run.py +16 -4
- package/skills/gsd-path-loop/scripts/model_policy.py +5 -1
- package/skills/gsd-path-loop/scripts/path_config.py +3 -3
- package/skills/gsd-path-loop/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-migrate/SKILL.md +8 -0
- package/skills/gsd-path-migrate/references/operating-rules.md +11 -3
- package/skills/gsd-path-migrate/references/runtime-selection.md +9 -0
- package/skills/gsd-path-migrate/scripts/_common.py +256 -2
- package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-migrate/scripts/loop_run.py +16 -4
- package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -0
- package/skills/gsd-path-migrate/scripts/model_policy.py +5 -1
- package/skills/gsd-path-migrate/scripts/path_config.py +3 -3
- package/skills/gsd-path-plan/SKILL.md +14 -3
- package/skills/gsd-path-plan/references/build-recovery.md +13 -0
- package/skills/gsd-path-plan/references/dispatch.md +13 -0
- package/skills/gsd-path-plan/references/operating-rules.md +11 -3
- package/skills/gsd-path-plan/references/planner.md +26 -4
- package/skills/gsd-path-plan/references/runtime-selection.md +9 -0
- package/skills/gsd-path-plan/references/spec-probes.md +150 -0
- package/skills/gsd-path-plan/scripts/_common.py +256 -2
- package/skills/gsd-path-plan/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-plan/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-plan/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-plan/scripts/detect_project.py +260 -20
- package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-plan/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-plan/scripts/integration.py +244 -65
- package/skills/gsd-path-plan/scripts/isolation.py +539 -70
- package/skills/gsd-path-plan/scripts/loop_run.py +16 -4
- package/skills/gsd-path-plan/scripts/members.py +77 -32
- package/skills/gsd-path-plan/scripts/model_policy.py +5 -1
- package/skills/gsd-path-plan/scripts/path_config.py +3 -3
- package/skills/gsd-path-plan/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-plan/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-plan/scripts/roadmap.py +9 -1
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-plan/scripts/state_promote.py +15 -4
- package/skills/gsd-path-plan/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-plan/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
- package/skills/gsd-path-plan/templates/plan.md +17 -2
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/SKILL.md +11 -0
- package/skills/gsd-path-research/references/dispatch.md +13 -0
- package/skills/gsd-path-research/references/operating-rules.md +11 -3
- package/skills/gsd-path-research/references/runtime-selection.md +9 -0
- package/skills/gsd-path-research/scripts/_common.py +256 -2
- package/skills/gsd-path-research/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-research/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-research/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-research/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-research/scripts/detect_project.py +260 -20
- package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-research/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-research/scripts/integration.py +244 -65
- package/skills/gsd-path-research/scripts/isolation.py +539 -70
- package/skills/gsd-path-research/scripts/loop_run.py +16 -4
- package/skills/gsd-path-research/scripts/members.py +77 -32
- package/skills/gsd-path-research/scripts/model_policy.py +5 -1
- package/skills/gsd-path-research/scripts/path_config.py +3 -3
- package/skills/gsd-path-research/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-research/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-research/scripts/roadmap.py +9 -1
- package/skills/gsd-path-research/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-research/scripts/state_promote.py +15 -4
- package/skills/gsd-path-research/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-research/templates/research-handoff.md +14 -3
- package/skills/gsd-path-roadmap/SKILL.md +16 -6
- package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
- package/skills/gsd-path-roadmap/references/operating-rules.md +11 -3
- package/skills/gsd-path-roadmap/references/runtime-selection.md +9 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +256 -2
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-roadmap/scripts/detect_project.py +260 -20
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-roadmap/scripts/integration.py +244 -65
- package/skills/gsd-path-roadmap/scripts/isolation.py +539 -70
- package/skills/gsd-path-roadmap/scripts/loop_run.py +16 -4
- package/skills/gsd-path-roadmap/scripts/members.py +77 -32
- package/skills/gsd-path-roadmap/scripts/model_policy.py +5 -1
- package/skills/gsd-path-roadmap/scripts/path_config.py +3 -3
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-roadmap/scripts/roadmap.py +9 -1
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-roadmap/scripts/state_promote.py +15 -4
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-ship/SKILL.md +32 -5
- package/skills/gsd-path-ship/references/dispatch.md +13 -0
- package/skills/gsd-path-ship/references/operating-rules.md +11 -3
- package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
- package/skills/gsd-path-ship/references/reviewer-wave.md +56 -12
- package/skills/gsd-path-ship/references/reviewer.md +3 -1
- package/skills/gsd-path-ship/references/runtime-selection.md +9 -0
- package/skills/gsd-path-ship/references/spec-probes.md +150 -0
- package/skills/gsd-path-ship/scripts/_common.py +256 -2
- package/skills/gsd-path-ship/scripts/archive_milestone.py +14 -13
- package/skills/gsd-path-ship/scripts/build_recovery.py +155 -58
- package/skills/gsd-path-ship/scripts/build_state.py +274 -8
- package/skills/gsd-path-ship/scripts/check_handoffs.py +635 -57
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +293 -32
- package/skills/gsd-path-ship/scripts/detect_project.py +260 -20
- package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-ship/scripts/discussion_validate.py +93 -35
- package/skills/gsd-path-ship/scripts/integration.py +244 -65
- package/skills/gsd-path-ship/scripts/isolation.py +539 -70
- package/skills/gsd-path-ship/scripts/loop_run.py +16 -4
- package/skills/gsd-path-ship/scripts/members.py +77 -32
- package/skills/gsd-path-ship/scripts/model_policy.py +5 -1
- package/skills/gsd-path-ship/scripts/path_config.py +3 -3
- package/skills/gsd-path-ship/scripts/pipeline_git.py +131 -42
- package/skills/gsd-path-ship/scripts/pipeline_state.py +139 -39
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -3
- package/skills/gsd-path-ship/scripts/roadmap.py +9 -1
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +39 -36
- package/skills/gsd-path-ship/scripts/state_promote.py +15 -4
- package/skills/gsd-path-ship/scripts/workflow_run.py +14 -4
- package/skills/gsd-path-ship/scripts/worktree_paths.py +54 -2
- package/skills/gsd-path-ship/templates/final-review.md +11 -2
- package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
- package/skills/gsd-path-ship/templates/wave-review.md +28 -9
- package/skills/gsd-path-undo/references/operating-rules.md +11 -3
- package/skills/gsd-path-undo/references/runtime-selection.md +9 -0
- package/skills/gsd-path-undo/scripts/_common.py +256 -2
- package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-undo/scripts/loop_run.py +16 -4
- package/skills/gsd-path-undo/scripts/model_policy.py +5 -1
- package/skills/gsd-path-undo/scripts/path_config.py +3 -3
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +118 -16
- package/skills/gsd-path-undo/scripts/worktree_paths.py +54 -2
- package/skills/path/DEFINE.md +34 -5
- package/skills/path/PLAN.md +14 -3
- package/skills/path/RESEARCH.md +11 -0
- package/skills/path/ROADMAP.md +16 -6
- package/skills/path/SHIP.md +32 -5
- package/skills/path/SKILL.md +52 -24
- package/skills/path/references/build-native.md +112 -10
- package/skills/path/references/build-recovery.md +13 -0
- package/skills/path/references/coder.md +7 -3
- package/skills/path/references/dispatch.md +13 -0
- package/skills/path/references/docs-auditor.md +5 -0
- package/skills/path/references/operating-rules.md +11 -3
- package/skills/path/references/planner.md +26 -4
- package/skills/path/references/reviewer-final.md +20 -2
- package/skills/path/references/reviewer-wave.md +56 -12
- package/skills/path/references/reviewer.md +3 -1
- package/skills/path/references/runtime-selection.md +9 -0
- package/skills/path/references/spec-probes.md +150 -0
- package/skills/path/scripts/_common.py +256 -2
- package/skills/path/scripts/archive_milestone.py +14 -13
- package/skills/path/scripts/build_recovery.py +155 -58
- package/skills/path/scripts/build_state.py +274 -8
- package/skills/path/scripts/check_docs_audit.py +77 -4
- package/skills/path/scripts/check_handoffs.py +635 -57
- package/skills/path/scripts/check_task_briefs.py +293 -32
- package/skills/path/scripts/detect_project.py +260 -20
- package/skills/path/scripts/discussion_records.py +9 -1
- package/skills/path/scripts/discussion_validate.py +93 -35
- package/skills/path/scripts/dispatch_driver.py +549 -53
- package/skills/path/scripts/git_guard.py +27 -8
- package/skills/path/scripts/integration.py +244 -65
- package/skills/path/scripts/isolation.py +539 -70
- package/skills/path/scripts/loop_run.py +16 -4
- package/skills/path/scripts/members.py +77 -32
- package/skills/path/scripts/model_policy.py +5 -1
- package/skills/path/scripts/path_config.py +3 -3
- package/skills/path/scripts/pipeline_git.py +131 -42
- package/skills/path/scripts/pipeline_state.py +139 -39
- package/skills/path/scripts/promote_lookahead.py +3 -3
- package/skills/path/scripts/review_findings.py +180 -44
- package/skills/path/scripts/roadmap.py +9 -1
- package/skills/path/scripts/state_checkpoint.py +39 -36
- package/skills/path/scripts/state_promote.py +15 -4
- package/skills/path/scripts/status_runtime.py +22 -4
- package/skills/path/scripts/task_context.py +14 -3
- package/skills/path/scripts/workflow_run.py +14 -4
- package/skills/path/scripts/worktree_paths.py +54 -2
- package/skills/path/templates/docs-audit.md +5 -1
- package/skills/path/templates/final-review.md +11 -2
- package/skills/path/templates/intent.md +30 -0
- package/skills/path/templates/patch-findings.md +3 -3
- package/skills/path/templates/plan.md +17 -2
- package/skills/path/templates/research-handoff.md +14 -3
- package/skills/path/templates/state.md +2 -1
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +28 -9
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
package/scripts/build_state.py
CHANGED
|
@@ -24,7 +24,7 @@ if __package__:
|
|
|
24
24
|
)
|
|
25
25
|
from .pipeline_git import task_commit_subject
|
|
26
26
|
from .pipeline_state import PipelineStateError, load_state
|
|
27
|
-
from . import _common
|
|
27
|
+
from . import _common, check_handoffs
|
|
28
28
|
else:
|
|
29
29
|
try:
|
|
30
30
|
from check_task_briefs import _frontmatter, _sections
|
|
@@ -37,6 +37,7 @@ else:
|
|
|
37
37
|
from pipeline_git import task_commit_subject
|
|
38
38
|
from pipeline_state import PipelineStateError, load_state
|
|
39
39
|
import _common
|
|
40
|
+
import check_handoffs
|
|
40
41
|
except ImportError: # pragma: no cover - package imports used by tests
|
|
41
42
|
from scripts.check_task_briefs import _frontmatter, _sections
|
|
42
43
|
from scripts.isolation import (
|
|
@@ -47,7 +48,7 @@ else:
|
|
|
47
48
|
)
|
|
48
49
|
from scripts.pipeline_git import task_commit_subject
|
|
49
50
|
from scripts.pipeline_state import PipelineStateError, load_state
|
|
50
|
-
from scripts import _common
|
|
51
|
+
from scripts import _common, check_handoffs
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
DEFAULT_PROJECT_DIR = ".project"
|
|
@@ -57,6 +58,10 @@ FULL_SHA_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
|
57
58
|
# recover verdicts that count as landed, mapped to their evidence classification.
|
|
58
59
|
LANDED_VERDICTS = {"recovered": "proven-landed", "attested": "attested"}
|
|
59
60
|
WAVE_HEADING_RE = re.compile(r"(?m)^## Wave (?P<wave>\d+)\b.*$")
|
|
61
|
+
REVIEW_VERDICT_RE = re.compile(
|
|
62
|
+
r"(?m)^Wave verdict:\s*(?P<verdict>pass|blocked)\s*(?:<!--.*?-->)?\s*$"
|
|
63
|
+
)
|
|
64
|
+
REVIEW_FINDINGS_HEADING = "Review findings"
|
|
60
65
|
VERIFY_HEAVY_RE = re.compile(r"(?m)^Heavy:\s*(?P<value>yes|no)\s*(?:<!--.*-->)?\s*$")
|
|
61
66
|
VALID_TASK_STATUSES = {"pending", "in-progress", "done", "failed", "blocked"}
|
|
62
67
|
VERIFY_RESULTS = _common.VERIFY_RESULTS
|
|
@@ -88,6 +93,9 @@ class Task:
|
|
|
88
93
|
task_file: str
|
|
89
94
|
verify_heavy: bool
|
|
90
95
|
repo: str = ""
|
|
96
|
+
review_findings: bool = False
|
|
97
|
+
verify: str = ""
|
|
98
|
+
supersedes: Tuple[str, ...] = ()
|
|
91
99
|
|
|
92
100
|
|
|
93
101
|
@dataclass(frozen=True)
|
|
@@ -241,6 +249,9 @@ def _parse_task(path: Path, relative_path: str, task_file: Optional[str] = None)
|
|
|
241
249
|
task_file=task_file or relative_path,
|
|
242
250
|
verify_heavy=_verify_heavy(text, label),
|
|
243
251
|
repo=str(fields.get("repo") or ""),
|
|
252
|
+
review_findings=REVIEW_FINDINGS_HEADING in _sections(text),
|
|
253
|
+
verify=_common.task_verify_command(text),
|
|
254
|
+
supersedes=_normalize_list(fields.get("supersedes", []), f"{label} supersedes", task_ids=True),
|
|
244
255
|
)
|
|
245
256
|
|
|
246
257
|
|
|
@@ -413,7 +424,8 @@ def _recovery_reports(project: Project) -> Dict[str, Mapping[str, object]]:
|
|
|
413
424
|
return reports
|
|
414
425
|
|
|
415
426
|
|
|
416
|
-
def _validate_ready_metadata(project: Project) ->
|
|
427
|
+
def _validate_ready_metadata(project: Project) -> Dict[str, Mapping[str, object]]:
|
|
428
|
+
"""Validate every task against its recovery report; return the reports by task id."""
|
|
417
429
|
by_id = {task.task_id: task for task in project.tasks}
|
|
418
430
|
recovery = _recovery_reports(project)
|
|
419
431
|
seen_worktrees: Dict[str, str] = {}
|
|
@@ -477,12 +489,204 @@ def _validate_ready_metadata(project: Project) -> None:
|
|
|
477
489
|
if report.get("verdict") not in LANDED_VERDICTS:
|
|
478
490
|
reason = report.get("reason", "landing commit is not proven")
|
|
479
491
|
_invalid_state(task, str(reason))
|
|
492
|
+
return recovery
|
|
480
493
|
|
|
481
494
|
|
|
482
495
|
def _overlap(left: Task, right: Task) -> List[str]:
|
|
483
496
|
if left.repo != right.repo:
|
|
484
497
|
return []
|
|
485
|
-
return
|
|
498
|
+
return _common.overlapping_paths(left.files, right.files)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _superseded(tasks: Sequence[Task], task: Task) -> Dict[str, str]:
|
|
502
|
+
"""Earlier task id -> the task that replaces its behavior: this task, or a `done` task."""
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
earlier: other.task_id
|
|
506
|
+
for other in tasks
|
|
507
|
+
if other.status == "done" or other.task_id == task.task_id
|
|
508
|
+
for earlier in other.supersedes
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _held(by_id: Mapping[str, Task], task: Task) -> List[Task]:
|
|
513
|
+
"""The tasks whose protective role this task inherits: each task it supersedes, through a chain."""
|
|
514
|
+
|
|
515
|
+
held: Dict[str, Task] = {}
|
|
516
|
+
pending = list(task.supersedes)
|
|
517
|
+
while pending:
|
|
518
|
+
other = by_id.get(pending.pop())
|
|
519
|
+
if other is not None and other.task_id not in held: # a cycle ends here
|
|
520
|
+
held[other.task_id] = other
|
|
521
|
+
pending.extend(other.supersedes)
|
|
522
|
+
return [by_id[name] for name in sorted(held)]
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _live_verify(
|
|
526
|
+
by_id: Mapping[str, Task], superseded: Mapping[str, str], source: Task
|
|
527
|
+
) -> Tuple[str, Optional[str], List[Tuple[str, str]]]:
|
|
528
|
+
"""The Verify of a source that still runs, or the task that retired all of it, and the drops.
|
|
529
|
+
|
|
530
|
+
The Verify of a fix task is exactly the composition that fix-tasks generates from the
|
|
531
|
+
Verify of each task in its deps, so it carries a copy of each: only the copies that are
|
|
532
|
+
not retired stay in the command. Any other Verify is one command. Each drop is
|
|
533
|
+
(superseded task, superseding task); a copy whose command a live dependency still runs
|
|
534
|
+
is not a drop.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
if source.task_id in superseded:
|
|
538
|
+
return "", superseded[source.task_id], [(source.task_id, superseded[source.task_id])]
|
|
539
|
+
deps = [by_id[dependency] for dependency in source.deps if dependency in by_id]
|
|
540
|
+
if not deps or source.verify != _common.fix_verify(dep.verify for dep in deps):
|
|
541
|
+
return source.verify, None, []
|
|
542
|
+
parts = [_live_verify(by_id, superseded, dep) for dep in deps]
|
|
543
|
+
live = [command for command, retired_by, _ in parts if not retired_by]
|
|
544
|
+
dropped = list(dict.fromkeys(drop for _, _, drops in parts for drop in drops
|
|
545
|
+
if by_id[drop[0]].verify not in live))
|
|
546
|
+
if not live:
|
|
547
|
+
return "", parts[0][1], dropped
|
|
548
|
+
return _common.fix_verify(live), None, dropped
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _regression_sources(
|
|
552
|
+
tasks: Sequence[Task], task: Task
|
|
553
|
+
) -> List[Tuple[Task, List[str], Optional[str], str, List[Tuple[str, str]]]]:
|
|
554
|
+
"""Each `done` task whose Verify protects a file of this task:
|
|
555
|
+
(source, replaced, retired_by, command, dropped).
|
|
556
|
+
|
|
557
|
+
A source is a task of an earlier wave that shares a declared file with this task. A task
|
|
558
|
+
that supersedes others inherits their role: it is also a source, in any wave, for a task
|
|
559
|
+
that shares a file with a task it holds, and `replaced` names those tasks.
|
|
560
|
+
`retired_by` names the task that supersedes the source: this task, or a `done` task. A
|
|
561
|
+
retired Verify does not run, also not as a copy in the Verify of a fix task: `command`
|
|
562
|
+
is the Verify that runs and `dropped` names each retired copy (`_live_verify`). The source still counts for the one-at-a-time
|
|
563
|
+
rule, so the superseding task never runs beside a task that shares a file of the
|
|
564
|
+
superseded task.
|
|
565
|
+
"""
|
|
566
|
+
|
|
567
|
+
by_id = {other.task_id: other for other in tasks}
|
|
568
|
+
superseded = _superseded(tasks, task)
|
|
569
|
+
sources = []
|
|
570
|
+
for other in tasks:
|
|
571
|
+
if other.status != "done" or other.task_id == task.task_id:
|
|
572
|
+
continue
|
|
573
|
+
replaced = [held.task_id for held in _held(by_id, other) if _overlap(task, held)]
|
|
574
|
+
if replaced or (other.wave < task.wave and _overlap(task, other)):
|
|
575
|
+
command, retired_by, dropped = _live_verify(by_id, superseded, other)
|
|
576
|
+
sources.append((other, replaced, retired_by, command, dropped))
|
|
577
|
+
return sources
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _landing_tasks(repo: Path, task_id: str, project_dir: str) -> Tuple[List[Task], Task]:
|
|
581
|
+
tasks = [
|
|
582
|
+
_parse_task(path, path.relative_to(repo).as_posix())
|
|
583
|
+
for path in sorted((repo / project_dir / "tasks").glob("*.md"))
|
|
584
|
+
]
|
|
585
|
+
task = next((item for item in tasks if item.task_id == task_id), None)
|
|
586
|
+
if task is None:
|
|
587
|
+
raise BuildStateError("unknown-task", f"task file is missing: {task_id}")
|
|
588
|
+
return tasks, task
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def retired_verifies(
|
|
592
|
+
repo: Path, task_id: str, project_dir: str = DEFAULT_PROJECT_DIR
|
|
593
|
+
) -> List[Dict[str, str]]:
|
|
594
|
+
"""Return the earlier-wave tasks whose Verify a landing of this task does not run again.
|
|
595
|
+
|
|
596
|
+
A task's plan-approved `supersedes` names them. The Verify is retired for that task, and
|
|
597
|
+
for every task that lands after that task is `done`: the Verify of the superseding task
|
|
598
|
+
then runs in its place (`regression_verifies`, key `supersedes`). A fix task whose
|
|
599
|
+
generated Verify holds only retired copies is here too. A fix task whose generated Verify
|
|
600
|
+
loses some of its copies has one entry for each, with the superseded task under `part`.
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
tasks, task = _landing_tasks(repo, task_id, project_dir)
|
|
604
|
+
retired: List[Dict[str, str]] = []
|
|
605
|
+
for other, _, retired_by, _, dropped in _regression_sources(tasks, task):
|
|
606
|
+
if retired_by:
|
|
607
|
+
if other.verify:
|
|
608
|
+
retired.append({"task": other.task_id, "superseded_by": retired_by})
|
|
609
|
+
else:
|
|
610
|
+
retired.extend({"task": other.task_id, "part": part, "superseded_by": by}
|
|
611
|
+
for part, by in dropped)
|
|
612
|
+
return retired
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def regression_verifies(
|
|
616
|
+
repo: Path, task_id: str, own: str, project_dir: str = DEFAULT_PROJECT_DIR
|
|
617
|
+
) -> List[Dict[str, object]]:
|
|
618
|
+
"""Return the Verify commands of landed tasks that a landing of this task must also pass.
|
|
619
|
+
|
|
620
|
+
One entry per distinct command, with every source task that has it, and under
|
|
621
|
+
`supersedes` the tasks whose retired Verify it replaces for this landing. A command
|
|
622
|
+
that the task's own Verify already runs is left out: an identical command, or
|
|
623
|
+
a dependency's command when the own Verify is exactly the fix-task Verify of
|
|
624
|
+
the task's dependencies. Text that only contains a command proves nothing.
|
|
625
|
+
A retired Verify is not here, also not as a copy in the Verify of a fix task: see
|
|
626
|
+
`retired_verifies`.
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
tasks, task = _landing_tasks(repo, task_id, project_dir)
|
|
630
|
+
by_id = {item.task_id: item for item in tasks}
|
|
631
|
+
composed = [by_id[dependency].verify for dependency in task.deps if dependency in by_id]
|
|
632
|
+
covered = {own, *(composed if own == _common.fix_verify(composed) else ())}
|
|
633
|
+
sources: Dict[str, Dict[str, object]] = {}
|
|
634
|
+
for other, replaced, _, command, _ in _regression_sources(tasks, task):
|
|
635
|
+
if command and command not in covered:
|
|
636
|
+
entry = sources.setdefault(command, {"tasks": [], "command": command})
|
|
637
|
+
entry["tasks"].append(other.task_id)
|
|
638
|
+
if replaced: # this Verify runs in the place of the Verify of each task it replaced
|
|
639
|
+
entry["supersedes"] = sorted({*entry.get("supersedes", ()), *replaced})
|
|
640
|
+
return list(sources.values())
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _blocked_wave_reviews(project: Project) -> Dict[int, int]:
|
|
644
|
+
"""Return waves whose highest canonical review cycle has a blocked lens."""
|
|
645
|
+
|
|
646
|
+
review_dir = project.tasks_dir.parent / "review"
|
|
647
|
+
if review_dir.is_symlink() or not review_dir.is_dir():
|
|
648
|
+
return {}
|
|
649
|
+
|
|
650
|
+
latest: Dict[int, int] = {}
|
|
651
|
+
verdicts: Dict[Tuple[int, int], List[str]] = {}
|
|
652
|
+
for path in sorted(review_dir.iterdir()):
|
|
653
|
+
named = check_handoffs.WAVE_REVIEW_NAME.fullmatch(path.name)
|
|
654
|
+
if named is None or path.is_symlink() or not path.is_file():
|
|
655
|
+
continue
|
|
656
|
+
wave, cycle = int(named.group("wave")), int(named.group("cycle"))
|
|
657
|
+
latest[wave] = max(latest.get(wave, 0), cycle)
|
|
658
|
+
try:
|
|
659
|
+
text = path.read_text(encoding="utf-8")
|
|
660
|
+
except (OSError, UnicodeDecodeError):
|
|
661
|
+
continue
|
|
662
|
+
verdict = REVIEW_VERDICT_RE.search(text)
|
|
663
|
+
if verdict is not None:
|
|
664
|
+
verdicts.setdefault((wave, cycle), []).append(verdict.group("verdict"))
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
wave: cycle
|
|
668
|
+
for wave, cycle in latest.items()
|
|
669
|
+
if "blocked" in verdicts.get((wave, cycle), [])
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def _unreviewed_waves(project: Project, waves: Sequence[int]) -> List[int]:
|
|
674
|
+
"""Return the given waves that still need a canonical review file of their own.
|
|
675
|
+
|
|
676
|
+
A repair wave is left out: the next review cycle of its source wave judges it.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
review_dir = project.tasks_dir.parent / "review"
|
|
680
|
+
reviewed = {
|
|
681
|
+
wave for wave in waves
|
|
682
|
+
if all(task.review_findings for task in project.tasks if task.wave == wave)
|
|
683
|
+
}
|
|
684
|
+
if review_dir.is_dir() and not review_dir.is_symlink():
|
|
685
|
+
for path in review_dir.iterdir():
|
|
686
|
+
named = check_handoffs.WAVE_REVIEW_NAME.fullmatch(path.name)
|
|
687
|
+
if named is not None and path.is_file() and not path.is_symlink():
|
|
688
|
+
reviewed.add(int(named.group("wave")))
|
|
689
|
+
return [wave for wave in waves if wave not in reviewed]
|
|
486
690
|
|
|
487
691
|
|
|
488
692
|
def ready(repo: str, project_dir: str = DEFAULT_PROJECT_DIR, *,
|
|
@@ -490,10 +694,43 @@ def ready(repo: str, project_dir: str = DEFAULT_PROJECT_DIR, *,
|
|
|
490
694
|
"""Return task readiness, optionally accepting build/done for completion checks."""
|
|
491
695
|
|
|
492
696
|
project = _load_project(repo, project_dir, ("active", "done") if allow_done else ("active",))
|
|
493
|
-
_validate_ready_metadata(project)
|
|
697
|
+
reports = _validate_ready_metadata(project)
|
|
494
698
|
by_id = {task.task_id: task for task in project.tasks}
|
|
495
699
|
unfinished_waves = sorted({task.wave for task in project.tasks if task.status != "done"})
|
|
496
700
|
current_wave = unfinished_waves[0] if unfinished_waves else None
|
|
701
|
+
blocked_reviews = _blocked_wave_reviews(project)
|
|
702
|
+
blocked_wave = min(
|
|
703
|
+
(
|
|
704
|
+
wave
|
|
705
|
+
for wave in blocked_reviews
|
|
706
|
+
if (current_wave is None or wave < current_wave)
|
|
707
|
+
and any(task.wave == wave for task in project.tasks)
|
|
708
|
+
and all(task.status == "done" for task in project.tasks if task.wave == wave)
|
|
709
|
+
),
|
|
710
|
+
default=None,
|
|
711
|
+
)
|
|
712
|
+
repair_for_wave: Optional[int] = None
|
|
713
|
+
blocked_review: Optional[Dict[str, object]] = None
|
|
714
|
+
if blocked_wave is not None:
|
|
715
|
+
source_ids = {task.task_id for task in project.tasks if task.wave == blocked_wave}
|
|
716
|
+
repairs = [
|
|
717
|
+
task
|
|
718
|
+
for task in project.tasks
|
|
719
|
+
if task.status != "done"
|
|
720
|
+
and task.wave > blocked_wave
|
|
721
|
+
and task.review_findings
|
|
722
|
+
and source_ids.intersection(task.deps)
|
|
723
|
+
]
|
|
724
|
+
if repairs:
|
|
725
|
+
repair_for_wave = blocked_wave
|
|
726
|
+
current_wave = min(task.wave for task in repairs)
|
|
727
|
+
else:
|
|
728
|
+
current_wave = blocked_wave
|
|
729
|
+
blocked_review = {
|
|
730
|
+
"wave": blocked_wave,
|
|
731
|
+
"cycle": blocked_reviews[blocked_wave],
|
|
732
|
+
"waiting_waves": unfinished_waves,
|
|
733
|
+
}
|
|
497
734
|
active = [task for task in project.tasks if task.status == "in-progress"]
|
|
498
735
|
if current_wave is not None and any(task.wave != current_wave for task in active):
|
|
499
736
|
task = next(task for task in active if task.wave != current_wave)
|
|
@@ -536,7 +773,7 @@ def ready(repo: str, project_dir: str = DEFAULT_PROJECT_DIR, *,
|
|
|
536
773
|
)
|
|
537
774
|
|
|
538
775
|
wave_active = any(task.wave == current_wave for task in active)
|
|
539
|
-
if current_wave is not None and not selectable and not wave_active:
|
|
776
|
+
if current_wave is not None and blocked_review is None and not selectable and not wave_active:
|
|
540
777
|
stalled = [
|
|
541
778
|
{
|
|
542
779
|
"id": task.task_id,
|
|
@@ -556,11 +793,37 @@ def ready(repo: str, project_dir: str = DEFAULT_PROJECT_DIR, *,
|
|
|
556
793
|
{"wave": current_wave, "tasks": stalled},
|
|
557
794
|
)
|
|
558
795
|
|
|
796
|
+
# Tasks that share a regression source land one at a time, each from the landing of the one
|
|
797
|
+
# before, so the source's Verify runs on their combined edits. A parallel round leaves the
|
|
798
|
+
# primary task file pending: a retained isolate shows that the task is already dispatched.
|
|
799
|
+
# A retired source still counts: its superseding task holds its role.
|
|
800
|
+
sources = {task.task_id: {other.task_id for other, _, _, _, _ in _regression_sources(project.tasks, task)}
|
|
801
|
+
for task in concurrent}
|
|
802
|
+
started = [task for task in selectable if reports[task.task_id].get("verdict") != "none"]
|
|
803
|
+
claimed = {source for task in active + started for source in sources[task.task_id]}
|
|
804
|
+
dispatchable = []
|
|
805
|
+
for task in selectable:
|
|
806
|
+
if task in started or not sources[task.task_id] & claimed:
|
|
807
|
+
dispatchable.append(task)
|
|
808
|
+
claimed |= sources[task.task_id]
|
|
809
|
+
|
|
810
|
+
typed: Dict[str, object] = {}
|
|
811
|
+
if repair_for_wave is not None:
|
|
812
|
+
typed["repair_for_wave"] = repair_for_wave
|
|
813
|
+
if blocked_review is not None:
|
|
814
|
+
typed["blocked_review"] = blocked_review
|
|
815
|
+
# Informational: every wave below the lowest unfinished wave is all done.
|
|
816
|
+
unreviewed = _unreviewed_waves(project, sorted(
|
|
817
|
+
{task.wave for task in project.tasks if unfinished_waves and task.wave < unfinished_waves[0]}
|
|
818
|
+
))
|
|
819
|
+
if unreviewed:
|
|
820
|
+
typed["unreviewed_waves"] = unreviewed
|
|
559
821
|
return {
|
|
560
822
|
"command": "ready",
|
|
561
823
|
"branch": project.branch,
|
|
562
824
|
"head": project.head,
|
|
563
825
|
"current_wave": current_wave,
|
|
826
|
+
**typed,
|
|
564
827
|
"ready": [
|
|
565
828
|
{
|
|
566
829
|
"id": task.task_id,
|
|
@@ -570,10 +833,13 @@ def ready(repo: str, project_dir: str = DEFAULT_PROJECT_DIR, *,
|
|
|
570
833
|
"deps": list(task.deps),
|
|
571
834
|
"files": list(task.files),
|
|
572
835
|
"task_file": task.task_file,
|
|
573
|
-
|
|
836
|
+
# The landing also runs each regression Verify, so a heavy one makes the task heavy.
|
|
837
|
+
"verify_heavy": task.verify_heavy
|
|
838
|
+
or any(other.verify_heavy and not retired_by
|
|
839
|
+
for other, _, retired_by, _, _ in _regression_sources(project.tasks, task)),
|
|
574
840
|
**({"repo": task.repo} if task.repo else {}),
|
|
575
841
|
}
|
|
576
|
-
for task in
|
|
842
|
+
for task in dispatchable
|
|
577
843
|
],
|
|
578
844
|
}
|
|
579
845
|
|
|
@@ -8,6 +8,10 @@ with testable claims where every claim row carries a valid type, a valid
|
|
|
8
8
|
verdict, and real evidence, a `## Descriptive docs` list for claimless docs,
|
|
9
9
|
the two sets disjoint and together equal to the frozen inventory, and a
|
|
10
10
|
remediation queue with one classified row per non-verified claim.
|
|
11
|
+
Queue numbers are unique, positive and strictly increasing, not necessarily
|
|
12
|
+
contiguous. With `--prior-audit`, persisting rows keep their number and new
|
|
13
|
+
rows use a number above every prior queue number and User rulings Queue #.
|
|
14
|
+
Numbers stay bound to their original (doc, claim); never reuse a retired one.
|
|
11
15
|
`## User rulings` is fixed-format durable memory. Pass the pre-rewrite audit
|
|
12
16
|
with `--prior-audit FILE`; every prior row, including its Planned value, must
|
|
13
17
|
remain an exact ordered prefix.
|
|
@@ -96,6 +100,12 @@ def _table_rows(body: str, width: int, label: str) -> List[List[str]]:
|
|
|
96
100
|
return rows
|
|
97
101
|
|
|
98
102
|
|
|
103
|
+
def _remediation_queue_rows(body: str, label: str, required: bool = False) -> List[List[str]]:
|
|
104
|
+
if not required and "|" not in body:
|
|
105
|
+
return []
|
|
106
|
+
return _table_rows(body, 6, label)
|
|
107
|
+
|
|
108
|
+
|
|
99
109
|
def _not_placeholder(value: str, label: str) -> None:
|
|
100
110
|
normalized = value.strip().strip("`\"").strip()
|
|
101
111
|
if (
|
|
@@ -247,6 +257,63 @@ def _check_carried(
|
|
|
247
257
|
raise AuditError(f"{label}: doc or evidence changed since the prior audit: {', '.join(moved)}")
|
|
248
258
|
|
|
249
259
|
|
|
260
|
+
def _queue_number(number: str, label: str) -> int:
|
|
261
|
+
if not re.fullmatch(r"[1-9][0-9]*", number):
|
|
262
|
+
raise AuditError(f"{label}: queue numbers must be positive integers")
|
|
263
|
+
return int(number)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _check_queue_number_history(
|
|
267
|
+
queue: Sequence[Sequence[str]],
|
|
268
|
+
prior_sections: Dict[str, str],
|
|
269
|
+
) -> None:
|
|
270
|
+
if "Remediation queue" not in prior_sections:
|
|
271
|
+
raise AuditError("prior audit is missing section: ## Remediation queue")
|
|
272
|
+
if "User rulings" not in prior_sections:
|
|
273
|
+
raise AuditError("prior audit is missing section: ## User rulings")
|
|
274
|
+
|
|
275
|
+
prior_queue = _remediation_queue_rows(
|
|
276
|
+
prior_sections["Remediation queue"], "Prior remediation queue"
|
|
277
|
+
)
|
|
278
|
+
prior_items_by_number: Dict[int, tuple] = {}
|
|
279
|
+
prior_numbers_by_item: Dict[tuple, int] = {}
|
|
280
|
+
for number, doc, claim, *_ in prior_queue:
|
|
281
|
+
numeric_number = _queue_number(number, "Prior remediation queue")
|
|
282
|
+
identity = (doc, claim)
|
|
283
|
+
prior_items_by_number[numeric_number] = identity
|
|
284
|
+
prior_numbers_by_item[identity] = numeric_number
|
|
285
|
+
|
|
286
|
+
prior_rulings = _user_rulings(
|
|
287
|
+
prior_sections["User rulings"], "Prior user rulings"
|
|
288
|
+
)
|
|
289
|
+
prior_ruling_numbers = {int(row[0]) for row in prior_rulings}
|
|
290
|
+
prior_max = max(
|
|
291
|
+
[*prior_items_by_number, *prior_ruling_numbers],
|
|
292
|
+
default=0,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
for number, doc, claim, *_ in queue:
|
|
296
|
+
numeric_number = _queue_number(number, "Remediation queue")
|
|
297
|
+
identity = (doc, claim)
|
|
298
|
+
prior_number = prior_numbers_by_item.get(identity)
|
|
299
|
+
if numeric_number in prior_ruling_numbers and prior_items_by_number.get(numeric_number) != identity:
|
|
300
|
+
raise AuditError(
|
|
301
|
+
f"Remediation queue #{number}: Queue # {number} is already retired by a prior "
|
|
302
|
+
"User rulings row for a different item; give new items a number above every "
|
|
303
|
+
"prior queue number and User rulings Queue #"
|
|
304
|
+
)
|
|
305
|
+
if prior_number is not None and numeric_number != prior_number:
|
|
306
|
+
raise AuditError(
|
|
307
|
+
f"Remediation queue #{number}: persisting item must keep its prior queue "
|
|
308
|
+
f"number #{prior_number}"
|
|
309
|
+
)
|
|
310
|
+
if prior_number is None and numeric_number <= prior_max:
|
|
311
|
+
raise AuditError(
|
|
312
|
+
f"Remediation queue #{number}: new items must use a number above every prior "
|
|
313
|
+
f"queue number and User rulings Queue # (maximum {prior_max})"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
250
317
|
def validate(
|
|
251
318
|
repo: Path,
|
|
252
319
|
audit_relative: str,
|
|
@@ -354,7 +421,9 @@ def validate(
|
|
|
354
421
|
|
|
355
422
|
queue_body = sections["Remediation queue"]
|
|
356
423
|
non_verified = sum(tallies[verdict] for verdict in VERDICTS if verdict != "verified")
|
|
357
|
-
queue =
|
|
424
|
+
queue = _remediation_queue_rows(
|
|
425
|
+
queue_body, "Remediation queue", required=bool(non_verified)
|
|
426
|
+
)
|
|
358
427
|
expected_queue = sorted(
|
|
359
428
|
(doc, claim["claim"], claim["verdict"])
|
|
360
429
|
for doc, claims in docs.items()
|
|
@@ -371,9 +440,13 @@ def validate(
|
|
|
371
440
|
_not_placeholder(action, f"Remediation queue #{number}: action")
|
|
372
441
|
if len(queue) != non_verified:
|
|
373
442
|
raise AuditError(f"Remediation queue has {len(queue)} rows for {non_verified} non-verified claims")
|
|
374
|
-
numbers = [row[0] for row in queue]
|
|
375
|
-
if
|
|
376
|
-
raise AuditError(
|
|
443
|
+
numbers = [_queue_number(row[0], "Remediation queue") for row in queue]
|
|
444
|
+
if any(later <= earlier for earlier, later in zip(numbers, numbers[1:])):
|
|
445
|
+
raise AuditError(
|
|
446
|
+
"Remediation queue numbers must be unique and strictly increasing"
|
|
447
|
+
)
|
|
448
|
+
if prior_sections is not None:
|
|
449
|
+
_check_queue_number_history(queue, prior_sections)
|
|
377
450
|
actual_queue = sorted((doc, claim, verdict) for _, doc, claim, verdict, _, _ in queue)
|
|
378
451
|
if actual_queue != expected_queue:
|
|
379
452
|
raise AuditError(
|