@opengsd/gsd-path 1.5.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 +138 -0
- package/DOCS.md +4 -3
- package/FULL.md +64 -4
- package/HOOKS.md +63 -2
- package/MIGRATE.md +12 -0
- package/QUICK.md +4 -2
- package/README.md +67 -15
- package/RUNTIME.md +23 -5
- package/UPDATE.md +35 -0
- package/WORKFLOW.md +58 -3
- package/package.json +1 -1
- package/scripts/_common.py +107 -1
- package/scripts/build_recovery.py +62 -30
- package/scripts/build_state.py +195 -5
- package/scripts/check_handoffs.py +92 -30
- package/scripts/check_task_briefs.py +19 -14
- package/scripts/detect_project.py +17 -2
- package/scripts/discussion_records.py +9 -1
- package/scripts/discussion_validate.py +7 -6
- package/scripts/dispatch_driver.py +157 -31
- package/scripts/git_guard.py +24 -5
- package/scripts/guard_hook.py +1216 -47
- package/scripts/install.mjs +76 -3
- package/scripts/install.py +182 -37
- package/scripts/integration.py +98 -64
- package/scripts/isolation.py +148 -11
- package/scripts/members.py +68 -25
- package/scripts/migrate_core.py +2 -0
- package/scripts/model_policy.py +5 -1
- package/scripts/pipeline_diagnose.py +17 -2
- package/scripts/pipeline_git.py +68 -30
- package/scripts/pipeline_state.py +28 -6
- package/scripts/pipeline_undo.py +17 -8
- 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 +1 -0
- package/scripts/state_checkpoint.py +30 -9
- package/scripts/state_promote.py +15 -4
- package/scripts/task_context.py +2 -0
- package/scripts/workflow_run.py +9 -2
- package/skills/gsd-path/DEFINE.md +6 -2
- package/skills/gsd-path/PLAN.md +3 -0
- package/skills/gsd-path/ROADMAP.md +16 -6
- package/skills/gsd-path/SHIP.md +26 -4
- package/skills/gsd-path/SKILL.md +52 -24
- package/skills/gsd-path/references/build-native.md +96 -9
- package/skills/gsd-path/references/build-recovery.md +13 -0
- package/skills/gsd-path/references/coder.md +7 -3
- package/skills/gsd-path/references/operating-rules.md +11 -3
- package/skills/gsd-path/references/planner.md +13 -1
- package/skills/gsd-path/references/reviewer-final.md +5 -5
- package/skills/gsd-path/references/reviewer-wave.md +46 -13
- package/skills/gsd-path/references/runtime-selection.md +9 -0
- package/skills/gsd-path/scripts/_common.py +107 -1
- package/skills/gsd-path/scripts/build_recovery.py +62 -30
- package/skills/gsd-path/scripts/build_state.py +195 -5
- package/skills/gsd-path/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path/scripts/detect_project.py +17 -2
- package/skills/gsd-path/scripts/discussion_records.py +9 -1
- package/skills/gsd-path/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path/scripts/dispatch_driver.py +157 -31
- package/skills/gsd-path/scripts/git_guard.py +24 -5
- package/skills/gsd-path/scripts/integration.py +98 -64
- package/skills/gsd-path/scripts/isolation.py +148 -11
- package/skills/gsd-path/scripts/members.py +68 -25
- package/skills/gsd-path/scripts/model_policy.py +5 -1
- package/skills/gsd-path/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path/scripts/state_promote.py +15 -4
- package/skills/gsd-path/scripts/task_context.py +2 -0
- package/skills/gsd-path/scripts/workflow_run.py +9 -2
- package/skills/gsd-path/templates/final-review.md +5 -5
- package/skills/gsd-path/templates/state.md +1 -0
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +18 -9
- package/skills/gsd-path-build/references/build-native.md +96 -9
- 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/operating-rules.md +11 -3
- package/skills/gsd-path-build/references/reviewer-final.md +5 -5
- package/skills/gsd-path-build/references/reviewer-wave.md +46 -13
- package/skills/gsd-path-build/references/runtime-selection.md +9 -0
- package/skills/gsd-path-build/scripts/_common.py +107 -1
- package/skills/gsd-path-build/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-build/scripts/build_state.py +195 -5
- package/skills/gsd-path-build/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-build/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-build/scripts/detect_project.py +17 -2
- package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-build/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-build/scripts/dispatch_driver.py +157 -31
- package/skills/gsd-path-build/scripts/integration.py +98 -64
- package/skills/gsd-path-build/scripts/isolation.py +148 -11
- package/skills/gsd-path-build/scripts/members.py +68 -25
- package/skills/gsd-path-build/scripts/model_policy.py +5 -1
- package/skills/gsd-path-build/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-build/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-build/scripts/state_promote.py +15 -4
- package/skills/gsd-path-build/scripts/task_context.py +2 -0
- package/skills/gsd-path-build/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +18 -9
- 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 +107 -1
- package/skills/gsd-path-decide/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-decide/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-decide/scripts/detect_project.py +17 -2
- package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-decide/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-decide/scripts/integration.py +98 -64
- package/skills/gsd-path-decide/scripts/isolation.py +148 -11
- package/skills/gsd-path-decide/scripts/members.py +68 -25
- package/skills/gsd-path-decide/scripts/model_policy.py +5 -1
- package/skills/gsd-path-decide/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-decide/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-decide/scripts/state_promote.py +15 -4
- package/skills/gsd-path-define/SKILL.md +6 -2
- 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/scripts/_common.py +107 -1
- package/skills/gsd-path-define/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-define/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-define/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-define/scripts/detect_project.py +17 -2
- package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-define/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-define/scripts/integration.py +98 -64
- package/skills/gsd-path-define/scripts/isolation.py +148 -11
- package/skills/gsd-path-define/scripts/members.py +68 -25
- package/skills/gsd-path-define/scripts/model_policy.py +5 -1
- package/skills/gsd-path-define/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-define/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-define/scripts/state_promote.py +15 -4
- package/skills/gsd-path-define/templates/state.md +1 -0
- 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 +107 -1
- package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-discuss/scripts/model_policy.py +5 -1
- 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 +107 -1
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +17 -2
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-docs-audit/scripts/integration.py +98 -64
- package/skills/gsd-path-docs-audit/scripts/isolation.py +148 -11
- package/skills/gsd-path-docs-audit/scripts/members.py +68 -25
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +5 -1
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +15 -4
- 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 +107 -1
- package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-forensics/scripts/model_policy.py +5 -1
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +17 -8
- 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 +107 -1
- package/skills/gsd-path-inspect/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-inspect/scripts/detect_project.py +17 -2
- package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-inspect/scripts/integration.py +98 -64
- package/skills/gsd-path-inspect/scripts/isolation.py +148 -11
- package/skills/gsd-path-inspect/scripts/members.py +68 -25
- package/skills/gsd-path-inspect/scripts/model_policy.py +5 -1
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-inspect/scripts/state_promote.py +15 -4
- package/skills/gsd-path-inspect/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-inspect/templates/state.md +1 -0
- 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 +107 -1
- package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-loop/scripts/model_policy.py +5 -1
- 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 +107 -1
- package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
- 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-plan/SKILL.md +3 -0
- package/skills/gsd-path-plan/references/build-recovery.md +13 -0
- package/skills/gsd-path-plan/references/operating-rules.md +11 -3
- package/skills/gsd-path-plan/references/planner.md +13 -1
- package/skills/gsd-path-plan/references/runtime-selection.md +9 -0
- package/skills/gsd-path-plan/scripts/_common.py +107 -1
- package/skills/gsd-path-plan/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-plan/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-plan/scripts/detect_project.py +17 -2
- package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-plan/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-plan/scripts/integration.py +98 -64
- package/skills/gsd-path-plan/scripts/isolation.py +148 -11
- package/skills/gsd-path-plan/scripts/members.py +68 -25
- package/skills/gsd-path-plan/scripts/model_policy.py +5 -1
- package/skills/gsd-path-plan/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-plan/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-plan/scripts/state_promote.py +15 -4
- package/skills/gsd-path-plan/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-plan/templates/task.md +1 -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 +107 -1
- package/skills/gsd-path-research/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-research/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-research/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-research/scripts/detect_project.py +17 -2
- package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-research/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-research/scripts/integration.py +98 -64
- package/skills/gsd-path-research/scripts/isolation.py +148 -11
- package/skills/gsd-path-research/scripts/members.py +68 -25
- package/skills/gsd-path-research/scripts/model_policy.py +5 -1
- package/skills/gsd-path-research/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-research/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-research/scripts/state_promote.py +15 -4
- package/skills/gsd-path-roadmap/SKILL.md +16 -6
- 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 +107 -1
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-roadmap/scripts/detect_project.py +17 -2
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-roadmap/scripts/integration.py +98 -64
- package/skills/gsd-path-roadmap/scripts/isolation.py +148 -11
- package/skills/gsd-path-roadmap/scripts/members.py +68 -25
- package/skills/gsd-path-roadmap/scripts/model_policy.py +5 -1
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-roadmap/scripts/state_promote.py +15 -4
- package/skills/gsd-path-ship/SKILL.md +26 -4
- package/skills/gsd-path-ship/references/operating-rules.md +11 -3
- package/skills/gsd-path-ship/references/reviewer-final.md +5 -5
- package/skills/gsd-path-ship/references/reviewer-wave.md +46 -13
- package/skills/gsd-path-ship/references/runtime-selection.md +9 -0
- package/skills/gsd-path-ship/scripts/_common.py +107 -1
- package/skills/gsd-path-ship/scripts/build_recovery.py +62 -30
- package/skills/gsd-path-ship/scripts/build_state.py +195 -5
- package/skills/gsd-path-ship/scripts/check_handoffs.py +92 -30
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +19 -14
- package/skills/gsd-path-ship/scripts/detect_project.py +17 -2
- package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-ship/scripts/discussion_validate.py +7 -6
- package/skills/gsd-path-ship/scripts/integration.py +98 -64
- package/skills/gsd-path-ship/scripts/isolation.py +148 -11
- package/skills/gsd-path-ship/scripts/members.py +68 -25
- package/skills/gsd-path-ship/scripts/model_policy.py +5 -1
- package/skills/gsd-path-ship/scripts/pipeline_git.py +68 -30
- package/skills/gsd-path-ship/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/gsd-path-ship/scripts/state_promote.py +15 -4
- package/skills/gsd-path-ship/scripts/workflow_run.py +9 -2
- package/skills/gsd-path-ship/templates/final-review.md +5 -5
- package/skills/gsd-path-ship/templates/wave-review.md +18 -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 +107 -1
- package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-undo/scripts/model_policy.py +5 -1
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +17 -8
- package/skills/path/DEFINE.md +6 -2
- package/skills/path/PLAN.md +3 -0
- package/skills/path/ROADMAP.md +16 -6
- package/skills/path/SHIP.md +26 -4
- package/skills/path/SKILL.md +52 -24
- package/skills/path/references/build-native.md +96 -9
- package/skills/path/references/build-recovery.md +13 -0
- package/skills/path/references/coder.md +7 -3
- package/skills/path/references/operating-rules.md +11 -3
- package/skills/path/references/planner.md +13 -1
- package/skills/path/references/reviewer-final.md +5 -5
- package/skills/path/references/reviewer-wave.md +46 -13
- package/skills/path/references/runtime-selection.md +9 -0
- package/skills/path/scripts/_common.py +107 -1
- package/skills/path/scripts/build_recovery.py +62 -30
- package/skills/path/scripts/build_state.py +195 -5
- package/skills/path/scripts/check_handoffs.py +92 -30
- package/skills/path/scripts/check_task_briefs.py +19 -14
- package/skills/path/scripts/detect_project.py +17 -2
- package/skills/path/scripts/discussion_records.py +9 -1
- package/skills/path/scripts/discussion_validate.py +7 -6
- package/skills/path/scripts/dispatch_driver.py +157 -31
- package/skills/path/scripts/git_guard.py +24 -5
- package/skills/path/scripts/integration.py +98 -64
- package/skills/path/scripts/isolation.py +148 -11
- package/skills/path/scripts/members.py +68 -25
- package/skills/path/scripts/model_policy.py +5 -1
- package/skills/path/scripts/pipeline_git.py +68 -30
- package/skills/path/scripts/pipeline_state.py +28 -6
- 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 +30 -9
- package/skills/path/scripts/state_promote.py +15 -4
- package/skills/path/scripts/task_context.py +2 -0
- package/skills/path/scripts/workflow_run.py +9 -2
- package/skills/path/templates/final-review.md +5 -5
- package/skills/path/templates/state.md +1 -0
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +18 -9
|
@@ -8,20 +8,29 @@ canonical review files, and every skeptic file for this wave. It groups
|
|
|
8
8
|
blocking findings by canonical criterion locator (`t001_ac2`, `sc3`,
|
|
9
9
|
`t001_files`, `t001_interface_contract`), collapses true duplicate
|
|
10
10
|
observations, selects the groups that need a skeptic, carries refutations
|
|
11
|
-
forward across cycles, batches fix-task groups by disjoint file
|
|
12
|
-
structural blockers out of skeptic accounting, and reports
|
|
13
|
-
cap is reached. It never decides the wave verdict.
|
|
11
|
+
forward across cycles, batches fix-task groups by repo and disjoint file
|
|
12
|
+
scope, keeps structural blockers out of skeptic accounting, and reports
|
|
13
|
+
whether the cycle cap is reached. It never decides the wave verdict.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
|
+
import functools
|
|
19
20
|
import json
|
|
20
21
|
import re
|
|
21
22
|
import sys
|
|
23
|
+
|
|
24
|
+
# Runtime helpers must not modify their immutable installation.
|
|
25
|
+
sys.dont_write_bytecode = True
|
|
22
26
|
from pathlib import Path
|
|
23
27
|
from typing import Dict, List, Optional, Sequence
|
|
24
28
|
|
|
29
|
+
try:
|
|
30
|
+
from . import _common
|
|
31
|
+
except ImportError: # installed standalone helper
|
|
32
|
+
import _common
|
|
33
|
+
|
|
25
34
|
DEFAULT_PROJECT_DIR = ".project"
|
|
26
35
|
DEFAULT_MAX_REVIEW_CYCLES = 3
|
|
27
36
|
CONFIG_KEYS = ("max_review_cycles", "wave_budget", "review_panel", "finding_skeptics")
|
|
@@ -143,15 +152,25 @@ def _frontmatter_list(frontmatter: str, key: str) -> List[str]:
|
|
|
143
152
|
|
|
144
153
|
|
|
145
154
|
def _numbered_items(section: Optional[str]) -> List[str]:
|
|
146
|
-
|
|
155
|
+
"""Numbered criteria; each also carries its text without sub-bullet markers (issue #392)."""
|
|
147
156
|
if section is None:
|
|
148
|
-
return
|
|
157
|
+
return []
|
|
158
|
+
bare = _numbered_texts(_common.without_sub_bullet_markers(section))
|
|
159
|
+
return [_common.Criterion(text, other) for text, other in zip(_numbered_texts(section), bare)]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _numbered_texts(section: str) -> List[str]:
|
|
163
|
+
items: List[str] = []
|
|
164
|
+
open_item = False
|
|
149
165
|
for line in _strip_comments(section).splitlines():
|
|
150
166
|
match = NUMBERED_ITEM.match(line.strip())
|
|
151
167
|
if match:
|
|
152
168
|
items.append(_normalize(match.group(2)))
|
|
153
|
-
|
|
169
|
+
open_item = True
|
|
170
|
+
elif open_item and line[:1].isspace() and line.strip():
|
|
154
171
|
items[-1] = _normalize(items[-1] + " " + line.strip())
|
|
172
|
+
elif line.strip():
|
|
173
|
+
open_item = False
|
|
155
174
|
return items
|
|
156
175
|
|
|
157
176
|
|
|
@@ -183,9 +202,11 @@ def load_wave_tasks(project: Path, wave: int) -> Dict[str, dict]:
|
|
|
183
202
|
if not task_wave or int(task_wave.group(1)) != wave:
|
|
184
203
|
continue
|
|
185
204
|
body = parts[2]
|
|
205
|
+
member = re.search(r"(?m)^repo:[ \t]*(?P<name>[^\s#]+)", frontmatter)
|
|
186
206
|
tasks[named.group("id")] = {
|
|
187
207
|
"id": named.group("id"),
|
|
188
208
|
"path": str(path),
|
|
209
|
+
"repo": member.group("name").strip("\"'") if member else None, # None is the coordinator
|
|
189
210
|
"files": _frontmatter_list(frontmatter, "files"),
|
|
190
211
|
"acceptance": _numbered_items(_section(body, "Acceptance criteria")),
|
|
191
212
|
"interface": [
|
|
@@ -270,17 +291,20 @@ def parse_lens(text: str, lens: str, tasks: Dict[str, dict]) -> dict:
|
|
|
270
291
|
continue
|
|
271
292
|
zones[zone].append(line)
|
|
272
293
|
for observation in _fail_items(zones["criteria"]):
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
elif criterion in task["interface"]:
|
|
278
|
-
locator = f"{task_id.lower()}_interface_contract"
|
|
279
|
-
if locator is None:
|
|
294
|
+
# Issue #392: match as the wave gate does, then report the task's own text.
|
|
295
|
+
criteria = task["acceptance"] + task["interface"]
|
|
296
|
+
quoted = _common.quoted_criterion(observation, criteria)
|
|
297
|
+
if quoted is None:
|
|
280
298
|
problems.append(
|
|
281
|
-
f"{lens} lens {task_id} finding names no task criterion:
|
|
299
|
+
f"{lens} lens {task_id} finding names no task criterion: "
|
|
300
|
+
f"{_criterion_of(observation)}"
|
|
282
301
|
)
|
|
283
302
|
continue
|
|
303
|
+
criterion = criteria[quoted[0]]
|
|
304
|
+
if quoted[0] < len(task["acceptance"]):
|
|
305
|
+
locator = f"{task_id.lower()}_ac{quoted[0] + 1}"
|
|
306
|
+
else:
|
|
307
|
+
locator = f"{task_id.lower()}_interface_contract"
|
|
284
308
|
findings.append(
|
|
285
309
|
{"locator": locator, "criterion": criterion, "task": task_id,
|
|
286
310
|
"lens": lens, "observation": observation}
|
|
@@ -433,21 +457,27 @@ def _earlier_lens_locators(project: Path, wave: int, cycle: int, depth: str, tas
|
|
|
433
457
|
|
|
434
458
|
|
|
435
459
|
def _fix_batches(groups: Sequence[dict], tasks: Dict[str, dict]) -> List[dict]:
|
|
436
|
-
"""One batch per disjoint file scope: groups whose task files overlap share a batch.
|
|
460
|
+
"""One batch per repo and disjoint file scope: groups whose task files overlap in one repo share a batch.
|
|
461
|
+
|
|
462
|
+
A member batch names its `repo`; a coordinator batch has no such key.
|
|
463
|
+
"""
|
|
437
464
|
|
|
438
465
|
batches: List[dict] = []
|
|
439
466
|
for group in groups:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
467
|
+
owners = [tasks.get(task_id, {}) for task_id in group["tasks"]]
|
|
468
|
+
for repo in sorted({task.get("repo") or "" for task in owners}) or [""]:
|
|
469
|
+
files = sorted({path for task in owners if (task.get("repo") or "") == repo
|
|
470
|
+
for path in task.get("files", [])})
|
|
471
|
+
merged = {"locators": [group["locator"]], "files": files, **({"repo": repo} if repo else {})}
|
|
472
|
+
keep: List[dict] = []
|
|
473
|
+
for batch in batches:
|
|
474
|
+
if batch.get("repo") == merged.get("repo") and _common.overlapping_paths(batch["files"], files):
|
|
475
|
+
merged["locators"] = batch["locators"] + merged["locators"]
|
|
476
|
+
merged["files"] = sorted(set(batch["files"]) | set(merged["files"]))
|
|
477
|
+
else:
|
|
478
|
+
keep.append(batch)
|
|
479
|
+
keep.append(merged)
|
|
480
|
+
batches = keep
|
|
451
481
|
return batches
|
|
452
482
|
|
|
453
483
|
|
|
@@ -604,7 +634,7 @@ def repair_evidence(repo: Path, project_dir: str, wave: int, cycle: int, task_id
|
|
|
604
634
|
)["tasks"][0]
|
|
605
635
|
if proof["verdict"] != "recovered":
|
|
606
636
|
raise ReviewFindingsError("repair requires a proven landing")
|
|
607
|
-
base
|
|
637
|
+
base = proof["base"] # in the coordinator, where the source review is
|
|
608
638
|
findings = _section(text, "Review findings") or ""
|
|
609
639
|
headings = list(re.finditer(r"(?m)^### ([a-z0-9_]+)\s*$", findings))
|
|
610
640
|
locators = [heading.group(1) for heading in headings]
|
|
@@ -613,10 +643,28 @@ def repair_evidence(repo: Path, project_dir: str, wave: int, cycle: int, task_id
|
|
|
613
643
|
source = compute(repo, project_dir, wave, cycle)
|
|
614
644
|
if source["structural_blockers"]:
|
|
615
645
|
raise ReviewFindingsError("source findings have structural blockers")
|
|
646
|
+
member = fields.get("repo") # a finding in two repos has one batch per repo with the same locators
|
|
616
647
|
batches = [batch for batch in source["fix_batches"]
|
|
617
|
-
if set(batch["locators"]) == set(locators)]
|
|
648
|
+
if batch.get("repo") == member and set(batch["locators"]) == set(locators)]
|
|
618
649
|
if len(batches) != 1:
|
|
619
650
|
raise ReviewFindingsError("repair must carry one eligible source finding batch")
|
|
651
|
+
# A member task's product is in the member repository: its commits, its bound branch, its Verify rows.
|
|
652
|
+
where, tip = repo, [] # the coordinator product is judged in the working tree
|
|
653
|
+
if member:
|
|
654
|
+
where, _, lock = isolation._member_context(repo, member)
|
|
655
|
+
tip = [f"refs/heads/{lock['branch']}"]
|
|
656
|
+
|
|
657
|
+
def entry(landing: dict, path: Path, paths: List[str]) -> dict:
|
|
658
|
+
"""A proven landing as the receipt shows it; a member landing adds its commits in the member."""
|
|
659
|
+
item = {"task": landing["task_id"], "base": landing["base"], "commit": landing["commit"], "files": paths}
|
|
660
|
+
if member:
|
|
661
|
+
item.update(member_base=isolation.task_frontmatter(_read(path, "task"))[0]["member_base"],
|
|
662
|
+
member_commit=landing["landing"])
|
|
663
|
+
return item
|
|
664
|
+
|
|
665
|
+
def at(item: dict, point: str = "commit") -> str:
|
|
666
|
+
return item.get(f"member_{point}", item[point])
|
|
667
|
+
|
|
620
668
|
groups = [group for group in source["groups"] if group["locator"] in locators]
|
|
621
669
|
for index, heading in enumerate(headings):
|
|
622
670
|
end = headings[index + 1].start() if index + 1 < len(headings) else len(findings)
|
|
@@ -627,22 +675,101 @@ def repair_evidence(repo: Path, project_dir: str, wave: int, cycle: int, task_id
|
|
|
627
675
|
raise ReviewFindingsError("repair does not carry the exact criterion and observations")
|
|
628
676
|
frontmatter = text.split("---", 2)[1]
|
|
629
677
|
files = _frontmatter_list(frontmatter, "files")
|
|
630
|
-
|
|
678
|
+
sources = load_wave_tasks(project, wave)
|
|
679
|
+
originals = sorted({task for group in groups for task in group["tasks"] if sources[task]["repo"] == member})
|
|
680
|
+
# A repair names the files it needs. They can be fewer than the batch files, and a plan repair
|
|
681
|
+
# can add a fix site that the source tasks did not declare.
|
|
631
682
|
if (task_id in originals or not set(originals) <= set(_frontmatter_list(frontmatter, "deps"))
|
|
632
|
-
or
|
|
683
|
+
or not files):
|
|
633
684
|
raise ReviewFindingsError("repair dependencies/files do not match its source batch")
|
|
634
|
-
|
|
685
|
+
repair = {"task_file": repair_path.relative_to(repo).as_posix(), **entry(proof, repair_path, files),
|
|
686
|
+
**({"member": member} if member else {})}
|
|
687
|
+
|
|
688
|
+
def differ(start: str, end: List[str], paths: List[str]) -> bool:
|
|
689
|
+
return bool(isolation.run_git(where, "diff", "--quiet", start, *end, "--", *paths).returncode)
|
|
690
|
+
|
|
691
|
+
def before(start: str, end: str) -> bool:
|
|
692
|
+
return not isolation.run_git(where, "merge-base", "--is-ancestor", start, end).returncode
|
|
693
|
+
|
|
694
|
+
@functools.lru_cache(maxsize=None)
|
|
695
|
+
def linked() -> tuple:
|
|
696
|
+
"""Landed repairs of the same findings that `deps` join to this repair, in landing order.
|
|
697
|
+
|
|
698
|
+
A linked task is done, is in the same repo, carries a finding of this repair in its
|
|
699
|
+
`## Review findings`, and depends on this repair or on a linked task, or the reverse.
|
|
700
|
+
"""
|
|
701
|
+
tasks, carried = {task_id: (repair_path, fields)}, {}
|
|
702
|
+
for path in (project / "tasks").glob("T*-*.md"):
|
|
703
|
+
other = _read(path, "task")
|
|
704
|
+
other_fields = isolation.task_frontmatter(other)[0] or {}
|
|
705
|
+
tasks[str(other_fields.get("id"))] = (path, other_fields)
|
|
706
|
+
blocks = re.split(r"(?m)^### ([a-z0-9_]+)\s*$", _section(other, "Review findings") or "")
|
|
707
|
+
carried[str(other_fields.get("id"))] = {
|
|
708
|
+
(locator, _normalize(line)) for locator, block in zip(blocks[1::2], blocks[2::2])
|
|
709
|
+
for line in re.findall(r"(?m)^Criterion:(.*)$", block)}
|
|
710
|
+
|
|
711
|
+
def traced(finding: tuple, seen: frozenset = frozenset()) -> set:
|
|
712
|
+
"""The finding as it started: its locator and criterion in the review of the source tasks.
|
|
713
|
+
|
|
714
|
+
A finding on an acceptance criterion of a fix task (`fix-tasks` copies the carried criteria
|
|
715
|
+
there) is the finding with the same criterion that this fix task carries. Two findings with
|
|
716
|
+
the same criterion text and other locators stay two findings.
|
|
717
|
+
"""
|
|
718
|
+
owner = re.fullmatch(r"t(\d{3})_ac\d+", finding[0])
|
|
719
|
+
sources = () if not owner else {
|
|
720
|
+
item for item in carried.get(f"T{owner.group(1)}", ()) if item[1] == finding[1]} - seen
|
|
721
|
+
return {origin for item in sources for origin in traced(item, seen | {finding})} or {finding}
|
|
722
|
+
|
|
723
|
+
def origins(other: str) -> set:
|
|
724
|
+
return {origin for finding in carried.get(other, ()) for origin in traced(finding)}
|
|
725
|
+
|
|
726
|
+
repairs = {other: task for other, task in tasks.items() if other == task_id or (
|
|
727
|
+
task[1].get("status") == "done" and task[1].get("repo") == member
|
|
728
|
+
and origins(task_id) & origins(other))}
|
|
729
|
+
joined, more = set(), {task_id}
|
|
730
|
+
while more:
|
|
731
|
+
joined |= more
|
|
732
|
+
more = {other for other, (_, other_fields) in repairs.items() if other not in joined and (
|
|
733
|
+
joined & set(other_fields.get("deps") or [])
|
|
734
|
+
or any(other in (repairs[item][1].get("deps") or []) for item in joined))}
|
|
735
|
+
others = sorted(joined - {task_id})
|
|
736
|
+
proofs = isolation.verify_landed_task_files(
|
|
737
|
+
repo, [repairs[other][0] for other in others], f"{project_dir}/tasks", head)["tasks"] if others else []
|
|
738
|
+
links = []
|
|
739
|
+
for landing in proofs:
|
|
740
|
+
path, other_fields = repairs[landing["task_id"]]
|
|
741
|
+
if landing["verdict"] == "recovered":
|
|
742
|
+
links.append({"task_file": path.relative_to(repo).as_posix(),
|
|
743
|
+
**entry(landing, path, list(other_fields.get("files") or []))})
|
|
744
|
+
return tuple(sorted(links, key=lambda link: int(isolation.git_output(where, "rev-list", "--count", at(link)))))
|
|
745
|
+
|
|
746
|
+
def linked_changes(start: str, end: List[str], paths: List[str]) -> Optional[List[dict]]:
|
|
747
|
+
"""The linked repairs whose landings are every change to `paths` from `start` to `end`, else None."""
|
|
748
|
+
if not differ(start, end, paths):
|
|
749
|
+
return []
|
|
750
|
+
links = [link for link in linked()
|
|
751
|
+
if before(start, f"{at(link)}^") and (not end or before(at(link), end[0]))]
|
|
752
|
+
for link in links:
|
|
753
|
+
if differ(start, [f"{at(link)}^"], paths):
|
|
754
|
+
return None
|
|
755
|
+
start = at(link)
|
|
756
|
+
return None if differ(start, end, paths) else links
|
|
757
|
+
|
|
635
758
|
original_paths = [Path(sources[task]["path"]) for task in originals]
|
|
636
759
|
original_proofs = isolation.verify_landed_task_files(
|
|
637
760
|
repo, original_paths, f"{project_dir}/tasks", head
|
|
638
761
|
)["tasks"]
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
762
|
+
landed, earlier = [], set()
|
|
763
|
+
for landing in original_proofs:
|
|
764
|
+
links = None
|
|
765
|
+
if landing["verdict"] == "recovered":
|
|
766
|
+
original = entry(landing, Path(sources[landing["task_id"]]["path"]), sources[landing["task_id"]]["files"])
|
|
767
|
+
if before(at(original), at(repair, "base")):
|
|
768
|
+
links = linked_changes(at(original), [at(repair, "base")], original["files"])
|
|
769
|
+
if links is None:
|
|
645
770
|
raise ReviewFindingsError("repair base contains unlinked changes to original product")
|
|
771
|
+
landed.append(original)
|
|
772
|
+
earlier.update(link["task"] for link in links)
|
|
646
773
|
source_paths = list(review_paths(project, wave, cycle, source["depth"]).values())
|
|
647
774
|
source_paths.extend(Path(group["skeptic"]["path"]) for group in groups if group["skeptic"])
|
|
648
775
|
for path in set(source_paths):
|
|
@@ -650,19 +777,28 @@ def repair_evidence(repo: Path, project_dir: str, wave: int, cycle: int, task_id
|
|
|
650
777
|
at_base = isolation.run_git(repo, "show", f"{base}:{relative}")
|
|
651
778
|
if at_base.returncode or at_base.stdout != _read(path, "source review"):
|
|
652
779
|
raise ReviewFindingsError("source review must be unchanged and precede the repair")
|
|
653
|
-
|
|
780
|
+
# A later linked repair supersedes this one: the last one is the product at the tip, and its
|
|
781
|
+
# landing and its Verify are the evidence. The reviewer applies every patch of the chain, so the
|
|
782
|
+
# files of every later linked repair must also change only through the landings of the chain.
|
|
783
|
+
later = linked_changes(at(repair), tip, files)
|
|
784
|
+
chain_files = sorted({*files, *(path for link in later or [] for path in link["files"])})
|
|
785
|
+
last = later[-1] if later else repair
|
|
786
|
+
verification = build_state.verify_lookup(
|
|
787
|
+
str(repo), _common.task_verify_command(_read(repo / last["task_file"], "repair task")), at(last), member)
|
|
654
788
|
if not verification["reuse"]:
|
|
655
789
|
raise ReviewFindingsError("repair has no reusable passing Verify at its landing")
|
|
656
|
-
if
|
|
790
|
+
if later is None or (later and linked_changes(at(repair), tip, chain_files) is None):
|
|
657
791
|
raise ReviewFindingsError("product changed after the repair landing")
|
|
792
|
+
# A parallel landing can have a parent that is newer than the repair base. The reviewer builds the
|
|
793
|
+
# product from the base, so no other landing between the two can change a file of the chain.
|
|
794
|
+
if differ(at(repair, "base"), [f"{at(repair)}^"], chain_files):
|
|
795
|
+
raise ReviewFindingsError("product changed between the repair base and the repair landing")
|
|
658
796
|
return {
|
|
659
797
|
"status": "ok", "command": "repair-evidence", "head": head,
|
|
660
798
|
"source": {"wave": wave, "cycle": cycle}, "groups": groups,
|
|
661
|
-
"originals":
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
"repair": {"task": task_id, "task_file": repair_path.relative_to(repo).as_posix(),
|
|
665
|
-
"base": base, "commit": commit, "files": files},
|
|
799
|
+
"originals": landed, "repair": repair,
|
|
800
|
+
**({"earlier_repairs": [link for link in linked() if link["task"] in earlier]} if earlier else {}),
|
|
801
|
+
**({"later_repairs": later, "chain_files": chain_files} if later else {}),
|
|
666
802
|
"verification": verification,
|
|
667
803
|
"verdict": "evidence-ready; reviewer judges the original unchanged criteria",
|
|
668
804
|
}
|
package/scripts/roadmap.py
CHANGED
|
@@ -252,7 +252,12 @@ def activate_milestone(text: str, milestone: str) -> str:
|
|
|
252
252
|
return "".join(lines)
|
|
253
253
|
|
|
254
254
|
|
|
255
|
-
def
|
|
255
|
+
def _entry_identity(block: dict) -> tuple:
|
|
256
|
+
"""Heading, Status, Depends on, Archive and Integrated of one entry."""
|
|
257
|
+
return block["id"], block["title"], {name: value for name, (_, value) in block["fields"].items()}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def reslice(before: str, candidate: str, phase: str, selected: str, amend_active: bool = False) -> str:
|
|
256
261
|
old_blocks = roadmap_blocks(before)
|
|
257
262
|
before_lines = before.splitlines(keepends=True)
|
|
258
263
|
candidate_lines = candidate.splitlines(keepends=True)
|
|
@@ -264,6 +269,9 @@ def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
|
|
|
264
269
|
new = milestone_block(candidate, slug)
|
|
265
270
|
if status == "shipped":
|
|
266
271
|
changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
|
|
272
|
+
elif status == "active" and amend_active:
|
|
273
|
+
# Intent is not approved: the scope may change, the identity may not.
|
|
274
|
+
changed = _entry_identity(block) != _entry_identity(new)
|
|
267
275
|
else:
|
|
268
276
|
changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
|
|
269
277
|
if changed:
|
package/scripts/runtime_store.py
CHANGED
|
@@ -24,6 +24,7 @@ RUNTIME_FILES = (
|
|
|
24
24
|
"state_checkpoint.py", "state_promote.py", "discussion_validate.py", "integration.py",
|
|
25
25
|
"build_state.py", "lean_verification.py", "check_docs_audit.py",
|
|
26
26
|
"path_config.py", "model_policy.py", "loop_run.py", "members.py",
|
|
27
|
+
"review_findings.py", "task_context.py",
|
|
27
28
|
)
|
|
28
29
|
GUARDS = ("guard_hook.py", "git_guard.py")
|
|
29
30
|
|
|
@@ -164,9 +164,16 @@ def _approval_details(
|
|
|
164
164
|
raise PipelineStateError(f"unsupported approval kind: {kind}")
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
def _reslice_roadmap(
|
|
167
|
+
def _reslice_roadmap(
|
|
168
|
+
repo: Path, before: str, candidate: str, state: PipelineState, state_text: str, selected: str,
|
|
169
|
+
) -> str:
|
|
170
|
+
# Define may amend its active entry until intent approval. A return to
|
|
171
|
+
# define after that approval always leaves an active recovery record.
|
|
172
|
+
amend_active = state.phase == "define" and not (
|
|
173
|
+
pipeline_state._build_recovery().context(repo, state_text) or {}
|
|
174
|
+
).get("active")
|
|
168
175
|
try:
|
|
169
|
-
return roadmap.reslice(before, candidate, state.phase, selected)
|
|
176
|
+
return roadmap.reslice(before, candidate, state.phase, selected, amend_active)
|
|
170
177
|
except roadmap.RoadmapError as error:
|
|
171
178
|
raise PipelineStateError(str(error)) from error
|
|
172
179
|
|
|
@@ -223,6 +230,10 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
|
|
|
223
230
|
expected_keys.add("baseline_before")
|
|
224
231
|
if not isinstance(journal.get("baseline_before"), str):
|
|
225
232
|
raise PipelineStateError("checkpoint journal has an invalid re-slice baseline")
|
|
233
|
+
if "recovery_reviews" in journal:
|
|
234
|
+
expected_keys.add("recovery_reviews")
|
|
235
|
+
if journal.get("kind") != "plan" or not isinstance(journal["recovery_reviews"], dict):
|
|
236
|
+
raise PipelineStateError("checkpoint journal has an invalid recovery review report")
|
|
226
237
|
if set(journal) != expected_keys:
|
|
227
238
|
raise PipelineStateError("checkpoint journal has invalid fields")
|
|
228
239
|
if journal.get("schema") != CHECKPOINT_SCHEMA or journal.get("repo") != str(repo):
|
|
@@ -307,7 +318,8 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
|
|
|
307
318
|
if journal["roadmap_path"] != ".project/ROADMAP.md":
|
|
308
319
|
raise PipelineStateError("checkpoint journal has an invalid ROADMAP path")
|
|
309
320
|
expected_roadmap = _reslice_roadmap(
|
|
310
|
-
str(journal["baseline_before"]), str(journal["roadmap_before"]), before,
|
|
321
|
+
repo, str(journal["baseline_before"]), str(journal["roadmap_before"]), before,
|
|
322
|
+
str(journal["state_before"]), str(selected),
|
|
311
323
|
) if journal["kind"] == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
312
324
|
str(journal["roadmap_before"]),
|
|
313
325
|
str(selected),
|
|
@@ -423,7 +435,7 @@ def _resume_checkpoint_locked(
|
|
|
423
435
|
raise PipelineStateError("approval checkpoint returned the wrong commit")
|
|
424
436
|
_unlink_checkpoint_journal(journal_path)
|
|
425
437
|
state = pipeline_state._state_from_text(str(transaction["state_after"]))
|
|
426
|
-
|
|
438
|
+
approved: dict[str, object] = {
|
|
427
439
|
"schema": CHECKPOINT_SCHEMA,
|
|
428
440
|
"status": "approved",
|
|
429
441
|
"kind": transaction["kind"],
|
|
@@ -432,6 +444,9 @@ def _resume_checkpoint_locked(
|
|
|
432
444
|
"paths": committed.get("paths"),
|
|
433
445
|
"state": state.json(),
|
|
434
446
|
}
|
|
447
|
+
if "recovery_reviews" in transaction:
|
|
448
|
+
approved["recovery_reviews"] = transaction["recovery_reviews"]
|
|
449
|
+
return approved
|
|
435
450
|
|
|
436
451
|
|
|
437
452
|
def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
|
|
@@ -563,7 +578,10 @@ def checkpoint_approval(
|
|
|
563
578
|
f"{expected_head}^{{commit}}",
|
|
564
579
|
).stdout.strip()
|
|
565
580
|
if resolved_head != expected_head:
|
|
566
|
-
raise PipelineStateError(
|
|
581
|
+
raise PipelineStateError(
|
|
582
|
+
"expected-head must be an exact full commit SHA; "
|
|
583
|
+
"resolve a ref or a short SHA with git rev-parse first"
|
|
584
|
+
)
|
|
567
585
|
if pipeline_state._run_git(resolved, "rev-parse", "HEAD").stdout.strip() != expected_head:
|
|
568
586
|
raise PipelineStateError("approval must start at expected-head")
|
|
569
587
|
active_state, _, _ = pipeline_state.load_state(resolved)
|
|
@@ -578,8 +596,9 @@ def checkpoint_approval(
|
|
|
578
596
|
state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
|
|
579
597
|
changes, event, subject, body = _approval_details(kind, state, selected_milestone)
|
|
580
598
|
_validate_plan_briefs(resolved, kind, project_dir)
|
|
599
|
+
recovery_reviews = None
|
|
581
600
|
if kind == "plan" and project_dir == ".project":
|
|
582
|
-
pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
601
|
+
recovery_reviews = pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
|
|
583
602
|
expected = {
|
|
584
603
|
"phase": state.phase,
|
|
585
604
|
"status": state.status,
|
|
@@ -611,7 +630,7 @@ def checkpoint_approval(
|
|
|
611
630
|
if kind == "roadmap-reslice":
|
|
612
631
|
baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
|
|
613
632
|
roadmap_after = _reslice_roadmap(
|
|
614
|
-
baseline_before, roadmap_before, state, selected_milestone,
|
|
633
|
+
resolved, baseline_before, roadmap_before, state, state_before, selected_milestone,
|
|
615
634
|
) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
|
|
616
635
|
roadmap_before,
|
|
617
636
|
selected_milestone,
|
|
@@ -641,6 +660,8 @@ def checkpoint_approval(
|
|
|
641
660
|
}
|
|
642
661
|
if baseline_before is not None:
|
|
643
662
|
journal["baseline_before"] = baseline_before
|
|
663
|
+
if recovery_reviews is not None:
|
|
664
|
+
journal["recovery_reviews"] = recovery_reviews
|
|
644
665
|
pipeline_state._write_json(journal_path, journal)
|
|
645
666
|
return _resume_checkpoint_locked(resolved, project, journal_path, journal)
|
|
646
667
|
|
|
@@ -722,8 +743,8 @@ def defer_approval(
|
|
|
722
743
|
if kind == "roadmap-reslice":
|
|
723
744
|
baseline = project / "ROADMAP.before-reslice.md"
|
|
724
745
|
roadmap_after = _reslice_roadmap(
|
|
725
|
-
pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
726
|
-
roadmap_before, state, selected_milestone,
|
|
746
|
+
resolved, pipeline_state._read_real_file(baseline, "re-slice baseline"),
|
|
747
|
+
roadmap_before, state, state_before, selected_milestone,
|
|
727
748
|
)
|
|
728
749
|
else:
|
|
729
750
|
roadmap_after = _activate_roadmap_milestone(roadmap_before, selected_milestone)
|
package/scripts/state_promote.py
CHANGED
|
@@ -377,17 +377,22 @@ def _prepare_promotion(
|
|
|
377
377
|
head = pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
|
|
378
378
|
if head != base:
|
|
379
379
|
raise PipelineStateError(f"promotion must start at base SHA: {head} != {base}")
|
|
380
|
-
|
|
380
|
+
active_state, active_text, _ = pipeline_state.load_state(repo)
|
|
381
|
+
remote_default = f"origin/{active_state.default_branch}"
|
|
382
|
+
remote = pipeline_state._run_git(
|
|
383
|
+
repo, "rev-parse", "--verify", f"{remote_default}^{{commit}}"
|
|
384
|
+
).stdout.strip()
|
|
381
385
|
if remote != base:
|
|
382
386
|
raise PipelineStateError(
|
|
383
|
-
f"base SHA is not current
|
|
387
|
+
f"base SHA is not current {remote_default}: {base} != {remote}"
|
|
384
388
|
)
|
|
385
389
|
if not pipeline_state._is_ancestor(repo, landing, base):
|
|
386
|
-
raise PipelineStateError(
|
|
390
|
+
raise PipelineStateError(
|
|
391
|
+
f"landing is not an ancestor of the current {active_state.default_branch} base"
|
|
392
|
+
)
|
|
387
393
|
if pipeline_state._run_git(repo, "status", "--porcelain", "--untracked-files=all").stdout:
|
|
388
394
|
raise PipelineStateError("promotion must start from a clean worktree")
|
|
389
395
|
|
|
390
|
-
active_state, active_text, _ = pipeline_state.load_state(repo)
|
|
391
396
|
if active_state.phase != "shipped" or active_state.status != "done":
|
|
392
397
|
raise PipelineStateError("promotion requires active STATE shipped/done")
|
|
393
398
|
if active_state.milestone is None:
|
|
@@ -456,6 +461,12 @@ def _prepare_promotion(
|
|
|
456
461
|
raise PipelineStateError(
|
|
457
462
|
"lookahead integration_default does not match the active project"
|
|
458
463
|
)
|
|
464
|
+
if next_state.default_branch != active_state.default_branch:
|
|
465
|
+
raise PipelineStateError(
|
|
466
|
+
"lookahead default_branch does not match the active project: copy "
|
|
467
|
+
f"`default_branch: {active_state.default_branch}` from .project/STATE.md "
|
|
468
|
+
"into .project/next/STATE.md"
|
|
469
|
+
)
|
|
459
470
|
if next_state.milestone != milestone:
|
|
460
471
|
raise PipelineStateError(
|
|
461
472
|
f"requested milestone {milestone} != next STATE milestone {next_state.milestone}"
|
package/scripts/task_context.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# gsd-path project runtime
|
|
2
3
|
"""Render a read-only coder intent view from the approved task and intent."""
|
|
3
4
|
|
|
4
5
|
import argparse
|
|
5
6
|
import hashlib
|
|
6
7
|
import re
|
|
7
8
|
import sys
|
|
9
|
+
sys.dont_write_bytecode = True
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
|
|
10
12
|
try:
|
package/scripts/workflow_run.py
CHANGED
|
@@ -210,8 +210,14 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
210
210
|
"--base", expected_head, "--task-id", task_id,
|
|
211
211
|
"--round-size", str(round_size))
|
|
212
212
|
if task["mode"] == "serial":
|
|
213
|
+
name = f"task-{task_id.lower()}-verify"
|
|
214
|
+
stale = isolation.sidecar_root(repo, "verify", name)
|
|
215
|
+
if os.path.lexists(stale / ".git"):
|
|
216
|
+
# An earlier prepare-task left this sidecar. Only one that is proven untouched is replaced.
|
|
217
|
+
steps.append({"script": "isolation.py", "result": isolation.retire_stale_verify(
|
|
218
|
+
repo, stale, isolation.verify_branch_name(name))})
|
|
213
219
|
step("isolation.py", "isolate-verify", "--repo", str(repo),
|
|
214
|
-
"--base", expected_head, "--name",
|
|
220
|
+
"--base", expected_head, "--name", name)
|
|
215
221
|
elif action == "build-evidence":
|
|
216
222
|
proof = step("build_state.py", "verify-landed", *common, "--head", expected_head)
|
|
217
223
|
# The landing proof has one canonical home; lean final-review reuse allows only this path.
|
|
@@ -278,7 +284,8 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
278
284
|
if action == "approve-plan":
|
|
279
285
|
step("pipeline_state.py", "approve", *common, "--kind", "plan",
|
|
280
286
|
"--expected-head", expected_head)
|
|
281
|
-
except (StepFailed, OSError, subprocess.CalledProcessError,
|
|
287
|
+
except (StepFailed, isolation.IsolationError, OSError, subprocess.CalledProcessError,
|
|
288
|
+
ValueError, KeyError, TypeError) as error:
|
|
282
289
|
return {"status": "blocked", "reason": str(error), "steps": steps,
|
|
283
290
|
"next": next_step}
|
|
284
291
|
return {"status": "complete", "steps": steps, **outputs}
|
|
@@ -168,8 +168,12 @@ State ownership. There is no re-interview of charter or roadmap scope:
|
|
|
168
168
|
and prohibition ruling for the user to accept or change; it never rules on
|
|
169
169
|
them silently. A requested change that contradicts the
|
|
170
170
|
charter or the resolved roadmap entry is a scope change — this phase never
|
|
171
|
-
edits the roadmap; surface it to the user for a `$gsd-path-roadmap`
|
|
172
|
-
|
|
171
|
+
edits the roadmap; surface it to the user for a `$gsd-path-roadmap`
|
|
172
|
+
re-slice. Before intent approval, that re-slice keeps `define/active` and
|
|
173
|
+
can change the scope of this milestone's `active` entry; after its
|
|
174
|
+
approval, draft INTENT.md again from the changed entry. In `corrections`
|
|
175
|
+
mode the intent is already approved, so the re-slice waits for the next
|
|
176
|
+
milestone boundary.
|
|
173
177
|
4. On approval, finalize per the Output contract, setting `milestone` to the
|
|
174
178
|
resolved entry's slug. The router continues with milestone-scoped research
|
|
175
179
|
when the entry lists open questions, otherwise planning.
|
package/skills/gsd-path/PLAN.md
CHANGED
|
@@ -119,6 +119,9 @@ as stated in Lookahead mode.
|
|
|
119
119
|
in the same wave. Same-wave tasks may share files only when one
|
|
120
120
|
transitively depends on the other through `deps`; the build executes
|
|
121
121
|
them in dependency layers.
|
|
122
|
+
- Require an optional `supersedes` field to be a list of different task
|
|
123
|
+
ids; each one names an existing task of an earlier wave, in the same
|
|
124
|
+
repo, that shares a declared file with the task.
|
|
122
125
|
- Reject a declared dependency that carries neither data nor a prerequisite
|
|
123
126
|
effect. A `deps` edge is valid only when the dependent consumes a symbol,
|
|
124
127
|
signature, schema, endpoint, file format, or path named in that
|
|
@@ -27,7 +27,8 @@ return to `$gsd-path` for ownership checking. Legal entry is
|
|
|
27
27
|
yet, `roadmap/active|blocked`, or a
|
|
28
28
|
milestone-boundary re-slice: `inspect/active` or `define/active` with no
|
|
29
29
|
approved INTENT.md for the next milestone and a user request to re-scope the
|
|
30
|
-
remaining `pending` entries,
|
|
30
|
+
remaining `pending` entries or, at `define/active` only, to change the scope
|
|
31
|
+
of the `active` entry,
|
|
31
32
|
or `roadmap/active` with STATE.milestone null and an `abandoned` entry in
|
|
32
33
|
ROADMAP.md (the post-abandon re-slice the build orchestrator hands off).
|
|
33
34
|
Enter from `decide/done` only with `pipeline_state.py transition`, exact
|
|
@@ -35,6 +36,8 @@ expected phase/status/milestone/branch/archive fields, event `roadmap started`,
|
|
|
35
36
|
and `--set-phase roadmap --set-status active`; never edit STATE directly. In a
|
|
36
37
|
milestone-boundary re-slice, preserve the entering `inspect/active` or
|
|
37
38
|
`define/active` state throughout the re-slice.
|
|
39
|
+
A `define/active` state that the router reports in `corrections` mode already
|
|
40
|
+
has an approved INTENT.md; it blocks.
|
|
38
41
|
Any later phase blocks; mid-milestone
|
|
39
42
|
re-scope is never legal — finish or ship the active milestone first. A
|
|
40
43
|
`decide/done` state with ROADMAP.md already present is milestone-scope decide;
|
|
@@ -71,8 +74,11 @@ precondition fails.
|
|
|
71
74
|
give `.project/ROADMAP.before-reslice.md` with the instruction to preserve
|
|
72
75
|
`shipped` entries byte-for-byte except Status/Archive/Integrated fields and
|
|
73
76
|
to never modify `abandoned` entries at all. For a milestone-boundary
|
|
74
|
-
re-slice
|
|
75
|
-
`pending` entries are in scope.
|
|
77
|
+
re-slice entered at `inspect/active`, also preserve the existing `active`
|
|
78
|
+
entry byte-for-byte; only `pending` entries are in scope. Entered at
|
|
79
|
+
`define/active`, the `active` entry may also change as the user ruled, but
|
|
80
|
+
its heading (id and slug) and its Status, Depends on, Archive, and
|
|
81
|
+
Integrated fields stay unchanged.
|
|
76
82
|
The output is exactly `.project/ROADMAP.md` — no other location is
|
|
77
83
|
canonical.
|
|
78
84
|
3. Gate ROADMAP.md:
|
|
@@ -96,8 +102,11 @@ precondition fails.
|
|
|
96
102
|
- On a re-slice, `shipped` entries differ from
|
|
97
103
|
`.project/ROADMAP.before-reslice.md` only in Status/Archive/Integrated;
|
|
98
104
|
`abandoned` entries are byte-for-byte identical.
|
|
99
|
-
- On a milestone-boundary re-slice
|
|
100
|
-
identical to
|
|
105
|
+
- On a milestone-boundary re-slice entered at `inspect/active`, the prior
|
|
106
|
+
`active` entry is byte-for-byte identical to
|
|
107
|
+
`.project/ROADMAP.before-reslice.md`. Entered at `define/active`, it
|
|
108
|
+
stays the only `active` entry and keeps its heading and its Status,
|
|
109
|
+
Depends on, Archive, and Integrated fields.
|
|
101
110
|
4. Redispatch one complete corrected brief under logical task name `roadmap`,
|
|
102
111
|
following the runtime dispatch contract and including all gate failures.
|
|
103
112
|
Allow one revision round. If it still fails, keep the entering
|
|
@@ -160,7 +169,8 @@ precondition fails.
|
|
|
160
169
|
|
|
161
170
|
The helper owns roadmap selection, expected state, baseline cleanup and
|
|
162
171
|
the approval checkpoint. It journals before mutation, retains the baseline
|
|
163
|
-
bytes for recovery, and refuses changes to settled
|
|
172
|
+
bytes for recovery, and refuses changes to settled entries and every
|
|
173
|
+
`active` entry change that step 2 does not permit.
|
|
164
174
|
A milestone-boundary re-slice retains Inspect or Define; a post-abandon
|
|
165
175
|
re-slice enters Inspect. Require its typed result to report `status:
|
|
166
176
|
approved`, the requested kind, milestone and returned commit. After an
|