@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
|
}
|
|
@@ -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:
|
|
@@ -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)
|
|
@@ -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}"
|
|
@@ -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:
|
|
@@ -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}
|
|
@@ -11,6 +11,7 @@ task_branch: null # orchestrator-owned: gsd-path-task/<id> while parallel; nul
|
|
|
11
11
|
# Optional explicit dispatch overrides: model: <advertised slug|inherit>
|
|
12
12
|
# effort: <advertised effort|inherit>
|
|
13
13
|
# repo: <member> # multi-repo only: the MEMBERS.md member this task changes; omit for the coordinator
|
|
14
|
+
# supersedes: [<task id>] # only when INTENT requires it: earlier-wave tasks whose behavior this task replaces; retires their Verify
|
|
14
15
|
files: # every file this task may touch — dispatch checks overlap
|
|
15
16
|
- <path>
|
|
16
17
|
---
|
|
@@ -26,9 +26,17 @@ Tasks reviewed: <count>
|
|
|
26
26
|
|
|
27
27
|
## T001 — <title>: <pass | fail>
|
|
28
28
|
|
|
29
|
-
<!--
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
<!-- Task sections go in task id order (T001 before T002), not in the order
|
|
30
|
+
of the PLAN.md wave table. The heading title is the exact `title:` of
|
|
31
|
+
the task file. An evidence line starts with `- ✅ ` (pass) or `- ❌ `
|
|
32
|
+
(fail). A section needs one or more lines with the marker of its own
|
|
33
|
+
verdict, so a failed task needs a `- ❌ ` criterion line.
|
|
34
|
+
Per task criterion: copy every word of the acceptance/interface
|
|
35
|
+
criterion before the em dash; join wrapped lines and sub-bullets with
|
|
36
|
+
spaces (the `- ` markers of sub-bullets are optional). An AC id or
|
|
37
|
+
summary alone cannot be matched by review_findings.py.
|
|
38
|
+
Write literal markup and code in backticks inside a sentence: text
|
|
39
|
+
such as <b> outside backticks reads as an unfilled placeholder. -->
|
|
32
40
|
- ✅ <criterion> — <evidence: verify output / file:line checked>
|
|
33
41
|
- ❌ <criterion> — found: <what exists instead, file:line>
|
|
34
42
|
fix: <concrete direction a coder can execute without re-investigating>
|
|
@@ -47,13 +55,14 @@ Contract violations (blocking):
|
|
|
47
55
|
wave, ending `: pass` or `: fail`. `scripts/check_handoffs.py wave
|
|
48
56
|
--review <this file>` gates the SC id set and verdicts. Omit this
|
|
49
57
|
section when no task in the wave owns an SC.
|
|
50
|
-
Copy the criterion
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
Copy every word of the criterion from INTENT.md `## Success criteria`:
|
|
59
|
+
join indented continuation lines and sub-bullets with a single space
|
|
60
|
+
(the `- ` markers of sub-bullets are optional). The gate compares every
|
|
61
|
+
other word: no paraphrase, no dropped sub-bullet.
|
|
62
|
+
Each SC block needs an evidence line with the marker of its own
|
|
63
|
+
verdict: `- ✅ ` (pass) or `- ❌ ` (fail). -->
|
|
55
64
|
|
|
56
|
-
### SC1 — <criterion
|
|
65
|
+
### SC1 — <criterion from INTENT.md>: <pass | fail>
|
|
57
66
|
- ✅ <evidence: verify output / file:line checked>
|
|
58
67
|
- ❌ <criterion> — found: <what exists instead, file:line>
|
|
59
68
|
fix: <concrete direction a coder can execute without re-investigating>
|
|
@@ -38,8 +38,8 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
38
38
|
the lookahead planning track for the next milestone; it follows the same
|
|
39
39
|
marker rule, owns no task frontmatter, and never binds a branch.
|
|
40
40
|
- STATE.integration_default is the project closeout choice and
|
|
41
|
-
STATE.integration is the current milestone choice. Both are `direct
|
|
42
|
-
`pull-request`; STATE.integration_source is `default` or `milestone` and
|
|
41
|
+
STATE.integration is the current milestone choice. Both are `direct`,
|
|
42
|
+
`pull-request`, or `external-landing`; STATE.integration_source is `default` or `milestone` and
|
|
43
43
|
preserves explicit override provenance. Older v2 state defaults to `direct`,
|
|
44
44
|
and modes may be changed only through `pipeline_state.py
|
|
45
45
|
configure-integration` before build. A next milestone resets its current
|
|
@@ -82,7 +82,9 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
82
82
|
`isolation.py attest` may stand in for it: one `.project/`-only commit that
|
|
83
83
|
names the base, the attested HEAD, the declared files that changed, the
|
|
84
84
|
task Verify recorded as passing at that HEAD, and the ruling verbatim.
|
|
85
|
-
`recover` reports it as `attested`, never as a proven landing.
|
|
85
|
+
`recover` reports it as `attested`, never as a proven landing. Attestation
|
|
86
|
+
matches the declared files literally: work that is only below a declared
|
|
87
|
+
directory can land but cannot be attested.
|
|
86
88
|
- All pipeline work for a milestone lives on `gsd-path/M00N` (M001, M002,
|
|
87
89
|
…) bound in STATE.branch. The next milestone binds a new unused
|
|
88
90
|
`gsd-path/M00N` at the remote default after the previous ship integrates.
|
|
@@ -90,6 +92,12 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
90
92
|
branch — either Path's direct merge or a Path-owned PR merged by the user.
|
|
91
93
|
The bound branch never receives merges or back-merges and is never the
|
|
92
94
|
GitHub default.
|
|
95
|
+
- The default branch is the one that STATE `default_branch` records at
|
|
96
|
+
initialization; a STATE file without the field means `main`. `main` and
|
|
97
|
+
`origin/main` in these rules mean that branch. Never change the record and
|
|
98
|
+
never take the name from a later `origin/HEAD` lookup. For a member
|
|
99
|
+
repository, the branch is the one that its `.project/MEMBERS.md` section
|
|
100
|
+
records as `Default branch`; a section without the field means `main`.
|
|
93
101
|
- After initialization writes STATE.md, and before entering the first pipeline
|
|
94
102
|
phase in an existing Git repository, the router calls
|
|
95
103
|
`scripts/pipeline_git.py bind-initial` with the selected M00N and exact
|