@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
|
@@ -27,7 +27,7 @@ import tempfile
|
|
|
27
27
|
import time
|
|
28
28
|
from datetime import datetime, timezone
|
|
29
29
|
from pathlib import Path
|
|
30
|
-
from typing import BinaryIO, Callable, Dict, List, Optional
|
|
30
|
+
from typing import BinaryIO, Callable, Dict, List, Optional, Tuple
|
|
31
31
|
|
|
32
32
|
try:
|
|
33
33
|
from scripts import (_common, archive_milestone, build_state, discussion_validate, isolation,
|
|
@@ -77,6 +77,9 @@ STOP_ERRORS = (DriverStop, isolation.IsolationError, build_state.BuildStateError
|
|
|
77
77
|
pipeline_state.PipelineStateError, archive_milestone.ArchiveError, model_policy.PolicyError)
|
|
78
78
|
_RUNTIME_ACTIVATION_ORDER = (
|
|
79
79
|
"_common",
|
|
80
|
+
"roadmap",
|
|
81
|
+
"review_panel",
|
|
82
|
+
"loop_run",
|
|
80
83
|
"pipeline_git",
|
|
81
84
|
"worktree_paths",
|
|
82
85
|
"build_recovery",
|
|
@@ -87,11 +90,16 @@ _RUNTIME_ACTIVATION_ORDER = (
|
|
|
87
90
|
"build_state",
|
|
88
91
|
"check_handoffs",
|
|
89
92
|
"task_context",
|
|
93
|
+
# discussion_validate and archive_milestone import each other: the second
|
|
94
|
+
# reload binds the reloaded ArchiveError that STOP_ERRORS catches.
|
|
95
|
+
"discussion_validate",
|
|
90
96
|
"archive_milestone",
|
|
97
|
+
"discussion_validate",
|
|
91
98
|
"review_findings",
|
|
92
99
|
"model_policy",
|
|
93
100
|
)
|
|
94
101
|
RECOVERY_BLOCKED = "recovery blocked"
|
|
102
|
+
LEFTOVER_ISOLATE = "task {} is landed; remove its leftover isolate: {}"
|
|
95
103
|
# The stops the build contract answers with build/blocked when the round raises them (steps 1 and 2);
|
|
96
104
|
# the same stops from review, panel, or skeptics stay with the parent.
|
|
97
105
|
BLOCKING_STOPS = {"dependency-deadlock": "dependency deadlock", RECOVERY_BLOCKED: "blocked recovery"}
|
|
@@ -640,6 +648,15 @@ def run_verify(command: str, cwd: Path) -> Dict[str, object]:
|
|
|
640
648
|
return {"exit_code": completed.returncode, "stdout": completed.stdout, "stderr": completed.stderr}
|
|
641
649
|
|
|
642
650
|
|
|
651
|
+
def landing_verify(command: str, regressions: List[Dict[str, object]],
|
|
652
|
+
cwd: Path) -> Tuple[Dict[str, object], List[Dict[str, object]]]:
|
|
653
|
+
"""The task's own Verify, then every regression Verify when the own one passes."""
|
|
654
|
+
execution = run_verify(command, cwd)
|
|
655
|
+
if execution["exit_code"]:
|
|
656
|
+
return execution, []
|
|
657
|
+
return execution, [{**item, **run_verify(str(item["command"]), cwd)} for item in regressions]
|
|
658
|
+
|
|
659
|
+
|
|
643
660
|
def state_from_task(primary: Path, task_id: str) -> Dict[str, object]:
|
|
644
661
|
"""A finish-able state derived from the task frontmatter alone, for coders dispatched by hand."""
|
|
645
662
|
tasks_dir = primary / ".project/tasks"
|
|
@@ -693,6 +710,22 @@ def state_from_task(primary: Path, task_id: str) -> Dict[str, object]:
|
|
|
693
710
|
|
|
694
711
|
def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
|
|
695
712
|
"""Verify in the isolate, land, record the ledger, retire. Returns the landing receipt."""
|
|
713
|
+
landing = land_task(primary, state)
|
|
714
|
+
try:
|
|
715
|
+
if state["mode"] == "member":
|
|
716
|
+
isolation.retire_member_task(primary, str(state["member"]), str(state["task_id"]))
|
|
717
|
+
else:
|
|
718
|
+
isolation.retire(primary, Path(str(state["worktree"])), state.get("task_branch") or None, False)
|
|
719
|
+
except isolation.IsolationError as error:
|
|
720
|
+
# The landing is proven, so a failed removal is a warning; Round.recover checks the isolate again.
|
|
721
|
+
landing["cleanup_warning"] = (f"{error}; the task is landed, but its isolate was not removed. The next "
|
|
722
|
+
"round or finish checks the isolate again and stops if the isolate is "
|
|
723
|
+
"still there")
|
|
724
|
+
return landing
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def land_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
|
|
728
|
+
"""Verify in the isolate, land, record the ledger. Returns the landing receipt."""
|
|
696
729
|
worktree = Path(str(state["worktree"]))
|
|
697
730
|
task_id = str(state["task_id"])
|
|
698
731
|
task_file = str(state["task_file"])
|
|
@@ -701,6 +734,11 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
|
|
|
701
734
|
command = _common.task_verify_command(task_path.read_text(encoding="utf-8"))
|
|
702
735
|
if not command:
|
|
703
736
|
raise DriverStop("task has no Verify command", task=task_id)
|
|
737
|
+
# A later task can break an earlier task that shares a file; that task's Verify also decides this landing.
|
|
738
|
+
regressions = build_state.regression_verifies(primary, task_id, command)
|
|
739
|
+
# A plan-approved `supersedes` retires an earlier Verify; the receipt and the Log show each one.
|
|
740
|
+
retired = build_state.retired_verifies(primary, task_id)
|
|
741
|
+
superseded = {"retired": retired} if retired else {}
|
|
704
742
|
if state["mode"] == "serial":
|
|
705
743
|
sidecar = state["sidecar"]
|
|
706
744
|
ensured = isolation.ensure_verify_sidecar(primary, base, str(sidecar["branch"]))
|
|
@@ -710,30 +748,45 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
|
|
|
710
748
|
isolation.reproduce_verify_sidecar(sidecar_path, tree)
|
|
711
749
|
except isolation.IsolationError as error:
|
|
712
750
|
raise DriverStop(str(error), task=task_id) from error
|
|
713
|
-
execution =
|
|
751
|
+
execution, reruns = landing_verify(command, regressions, sidecar_path)
|
|
714
752
|
location = f"sidecar {sidecar['branch']}"
|
|
715
753
|
isolation.clean_verify(primary, sidecar_path, base, str(sidecar["branch"]))
|
|
716
754
|
isolation.retire(primary, sidecar_path, str(sidecar["branch"]), False)
|
|
717
755
|
else:
|
|
718
|
-
execution =
|
|
756
|
+
execution, reruns = landing_verify(command, regressions, worktree)
|
|
719
757
|
location = f"isolate {state['task_branch']}"
|
|
720
758
|
passed = execution["exit_code"] == 0
|
|
759
|
+
broken = [item for item in reruns if item["exit_code"]]
|
|
721
760
|
execution.update({"worktree": str(worktree), "location": location})
|
|
722
761
|
evidence_dir = (Path(str(state["_path"])).parent if state.get("_path")
|
|
723
762
|
else records_root(primary) / task_id / f"manual-{now().replace(':', '')}")
|
|
724
763
|
evidence_dir.mkdir(parents=True, exist_ok=True)
|
|
725
764
|
_common.atomic_write(evidence_dir / "verify.json",
|
|
726
|
-
json.dumps(execution,
|
|
765
|
+
json.dumps({**execution, "regressions": reruns, **superseded},
|
|
766
|
+
indent=2, sort_keys=True) + "\n")
|
|
727
767
|
execution["evidence"] = str(evidence_dir / "verify.json")
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
+ (f"
|
|
732
|
-
|
|
768
|
+
# The Verify of a superseding task runs in the place of the retired Verify: the label names both.
|
|
769
|
+
for label, run in [("Verify", execution),
|
|
770
|
+
*((f"regression Verify of {', '.join(item['tasks'])}"
|
|
771
|
+
+ (f" (supersedes {', '.join(item['supersedes'])})" if item.get("supersedes") else ""),
|
|
772
|
+
item) for item in reruns)]:
|
|
773
|
+
output = "\n".join(f" {line}" for line in tail(run["stdout"] + run["stderr"]).splitlines())
|
|
774
|
+
append_log(task_path, f"- {now()[:10]} — orchestrator {label} ({location}): "
|
|
775
|
+
f"{'fail' if run['exit_code'] else 'pass'}, exit {run['exit_code']}"
|
|
776
|
+
+ (f"; output tail:\n ```\n{output}\n ```" if output else ""))
|
|
777
|
+
for item in retired:
|
|
778
|
+
part = f": part of {item['part']}" if item.get("part") else ""
|
|
779
|
+
append_log(task_path, f"- {now()[:10]} — orchestrator regression Verify of {item['task']}{part} retired: "
|
|
780
|
+
f"superseded by {item['superseded_by']}")
|
|
781
|
+
if not passed or broken:
|
|
733
782
|
if state["mode"] == "parallel":
|
|
734
783
|
isolation.deactivate_task(worktree, task_id, str(state["task_branch"]))
|
|
735
784
|
elif state["mode"] == "member":
|
|
736
785
|
isolation.deactivate_member_task(primary, str(state["member"]), task_id)
|
|
786
|
+
if passed:
|
|
787
|
+
names = ", ".join(name for item in broken for name in item["tasks"])
|
|
788
|
+
raise DriverStop(f"regression Verify of {names} failed in the isolate", task=task_id,
|
|
789
|
+
execution=execution, regressions=broken)
|
|
737
790
|
raise DriverStop("Verify failed in the isolate", task=task_id, execution=execution)
|
|
738
791
|
if state["mode"] == "member":
|
|
739
792
|
member = str(state["member"])
|
|
@@ -744,9 +797,9 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
|
|
|
744
797
|
ledger = isolation.git_output(worktree, "rev-parse", f"{landing}^") == str(state["member_base"])
|
|
745
798
|
if ledger:
|
|
746
799
|
build_state.verify_record(str(primary), command, landing, "pass", execution=execution, member=member)
|
|
747
|
-
isolation.retire_member_task(primary, member, task_id)
|
|
748
800
|
return {"task": task_id, "commit": str(landed["commit"]), "mode": "member",
|
|
749
|
-
"landing": landing, "verify": execution, "
|
|
801
|
+
"landing": landing, "verify": execution, "regressions": reruns, **superseded,
|
|
802
|
+
"ledger": ledger}
|
|
750
803
|
landed = isolation.land(primary, worktree, base, task_id, str(state["title"]), task_file,
|
|
751
804
|
list(state["files"]))
|
|
752
805
|
commit = str(landed["commit"])
|
|
@@ -754,9 +807,8 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
|
|
|
754
807
|
ledger = isolation.git_output(primary, "rev-parse", f"{commit}^") == base
|
|
755
808
|
if ledger:
|
|
756
809
|
build_state.verify_record(str(primary), command, commit, "pass", execution=execution)
|
|
757
|
-
isolation.retire(primary, worktree, state.get("task_branch") or None, False)
|
|
758
810
|
return {"task": task_id, "commit": commit, "mode": landed["mode"], "verify": execution,
|
|
759
|
-
"ledger": ledger}
|
|
811
|
+
"regressions": reruns, **superseded, "ledger": ledger}
|
|
760
812
|
|
|
761
813
|
|
|
762
814
|
# --- shared by round and review --------------------------------------------
|
|
@@ -772,6 +824,11 @@ def recover_report(primary: Path, project_dir: str, receipt: Dict[str, object])
|
|
|
772
824
|
report = isolation.recover(primary, Path(project_dir) / "tasks")
|
|
773
825
|
receipt["steps"].append({"script": "isolation.py recover", "result": report})
|
|
774
826
|
if report["verdict"] == "block":
|
|
827
|
+
blocked = [task for task in report["tasks"] if task["verdict"] == "block"]
|
|
828
|
+
if blocked and all(task.get("leftover") for task in blocked):
|
|
829
|
+
# Every blocked task is landed: the stop names only the isolates to remove, and the build stays active.
|
|
830
|
+
raise DriverStop("; ".join(LEFTOVER_ISOLATE.format(task["task_id"], task["leftover"])
|
|
831
|
+
for task in blocked), recover=blocked)
|
|
775
832
|
raise DriverStop(RECOVERY_BLOCKED, recover=report)
|
|
776
833
|
return report
|
|
777
834
|
|
|
@@ -907,7 +964,7 @@ class Round:
|
|
|
907
964
|
self.receipt["steps"].append({"script": "isolation.recover_member_landing", "task": journal.stem,
|
|
908
965
|
"result": result})
|
|
909
966
|
if result["state"] == "landed":
|
|
910
|
-
|
|
967
|
+
self.retire_member(str(result["member"]), journal.stem, str(result["landing"]))
|
|
911
968
|
retired_members.add(journal.stem)
|
|
912
969
|
report = recover_report(self.primary, self.project_dir, self.receipt)
|
|
913
970
|
for task in report["tasks"]:
|
|
@@ -936,16 +993,36 @@ class Round:
|
|
|
936
993
|
recover=task,
|
|
937
994
|
)
|
|
938
995
|
if verdict == "recovered" and task.get("member") and task.get("task_id") not in retired_members:
|
|
939
|
-
|
|
996
|
+
self.retire_member(str(task["member"]), str(task["task_id"]), str(task["landing"]))
|
|
940
997
|
if verdict == "recovered" and worktree and worktree["clean"] and task.get("task_branch"):
|
|
941
998
|
isolation.retire(self.primary, Path(str(worktree["path"])),
|
|
942
999
|
str(task["task_branch"]), False)
|
|
943
|
-
elif
|
|
944
|
-
|
|
945
|
-
|
|
1000
|
+
elif verdict == "recovered" and worktree is None and task.get("task_branch"):
|
|
1001
|
+
# The isolate is gone and the landing is proven: retire proves the branch, then deletes it.
|
|
1002
|
+
isolation.retire(self.primary, None, str(task["task_branch"]), True, str(task["commit"]))
|
|
1003
|
+
elif verdict == "resume" and (task.get("landing_retry") or task.get("dispatch_retry")):
|
|
1004
|
+
# ponytail: interrupted retries stay with the parent's documented
|
|
946
1005
|
# procedure; failed/blocked reconciliation surfaces through ready's typed error.
|
|
947
1006
|
raise DriverStop(f"task {task.get('task_id')} needs recovery: {verdict}", recover=task)
|
|
948
1007
|
|
|
1008
|
+
def retire_member(self, member: str, task_id: str, landing: str) -> None:
|
|
1009
|
+
"""Retire a landed member task's sidecar; a proven leftover of it is a stop that names only its path."""
|
|
1010
|
+
checkout, project, _ = isolation._member_context(self.primary, member)
|
|
1011
|
+
sidecar = isolation.sidecar_root(checkout, "task", f"{project}-{task_id}")
|
|
1012
|
+
branch = f"refs/heads/{isolation.TASK_BRANCH_PREFIX}{project}-{task_id}"
|
|
1013
|
+
tip = isolation.run_git(checkout, "rev-parse", "--verify", "--quiet", branch)
|
|
1014
|
+
commits = [landing, *tip.stdout.split()]
|
|
1015
|
+
try:
|
|
1016
|
+
isolation.retire_member_task(self.primary, member, task_id)
|
|
1017
|
+
except isolation.RemovalError as error:
|
|
1018
|
+
# Missing files are a removal that stopped part way; every other state keeps the refusal.
|
|
1019
|
+
if not (isolation.only_missing_files(sidecar)
|
|
1020
|
+
or isolation.holds_only_landed_files(checkout, sidecar, commits)):
|
|
1021
|
+
raise
|
|
1022
|
+
raise DriverStop(LEFTOVER_ISOLATE.format(task_id, sidecar), cleanup_error=str(error))
|
|
1023
|
+
if isolation.holds_only_landed_files(checkout, sidecar, commits):
|
|
1024
|
+
raise DriverStop(LEFTOVER_ISOLATE.format(task_id, sidecar))
|
|
1025
|
+
|
|
949
1026
|
# settle exited children ---------------------------------------------
|
|
950
1027
|
|
|
951
1028
|
def isolate_live(self, state: Dict[str, object]) -> bool:
|
|
@@ -1231,7 +1308,7 @@ class Round:
|
|
|
1231
1308
|
continue
|
|
1232
1309
|
prepared = self.runner("prepare-task", head, task["id"], round_size)
|
|
1233
1310
|
isolate = prepared[0]["result"]
|
|
1234
|
-
sidecar = prepared[1]["result"] if len(prepared) > 1 else None
|
|
1311
|
+
sidecar = prepared[-1]["result"] if len(prepared) > 1 else None # the isolate-verify step is last
|
|
1235
1312
|
worktree = Path(str(isolate["worktree"]))
|
|
1236
1313
|
isolation.activate_task(worktree, head, task["id"], f"build_{task['id'].lower()}",
|
|
1237
1314
|
task["task_file"], isolate["task_branch"])
|
|
@@ -2007,6 +2084,23 @@ def next_task_id(tasks_dir: Path) -> str:
|
|
|
2007
2084
|
return f"T{max(numbers, default=0) + 1:03d}"
|
|
2008
2085
|
|
|
2009
2086
|
|
|
2087
|
+
def replaced_review_base(primary: Path, project_dir: str, wave: int, cycle: int) -> Optional[str]:
|
|
2088
|
+
"""The recovery base when this cycle's review replaces the one a build recovery set aside, else None."""
|
|
2089
|
+
recovery = pipeline_state._build_recovery().context(primary)
|
|
2090
|
+
if not recovery:
|
|
2091
|
+
return None
|
|
2092
|
+
relative = f"{project_dir}/review/wave-{wave}.cycle{cycle}.md"
|
|
2093
|
+
before = isolation.run_git(primary, "show", f"{recovery['base']}:{relative}")
|
|
2094
|
+
current = primary / relative
|
|
2095
|
+
# Plan approval restores the review of an unchanged wave before its checkpoint, so no commit
|
|
2096
|
+
# since the base touches that file. A set-aside review is deleted by that checkpoint.
|
|
2097
|
+
rewritten = isolation.run_git(primary, "log", "-1", "--format=%H", f"{recovery['base']}..HEAD", "--", relative)
|
|
2098
|
+
if (not rewritten.stdout.strip() and before.returncode == 0 and current.is_file()
|
|
2099
|
+
and current.read_text(encoding="utf-8") == before.stdout):
|
|
2100
|
+
return None # the restored review keeps its repairs
|
|
2101
|
+
return recovery["base"]
|
|
2102
|
+
|
|
2103
|
+
|
|
2010
2104
|
def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
|
|
2011
2105
|
"""Step 7: one fix task per findings batch, carrying failed criteria and observations verbatim."""
|
|
2012
2106
|
receipt: Dict[str, object] = {"status": None, "wave": options.wave, "cycle": options.cycle,
|
|
@@ -2015,14 +2109,32 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
|
|
|
2015
2109
|
project = primary / options.project_dir
|
|
2016
2110
|
tasks_dir = project / "tasks"
|
|
2017
2111
|
repairs = []
|
|
2112
|
+
pending = []
|
|
2018
2113
|
for path in sorted(tasks_dir.glob("*-fix-wave-*-cycle-*.md")):
|
|
2019
2114
|
text = path.read_text(encoding="utf-8")
|
|
2020
2115
|
fields, _ = isolation.task_frontmatter(text)
|
|
2021
2116
|
repairs.append({"task": fields["id"], "path": path.relative_to(primary).as_posix(),
|
|
2022
|
-
"wave": int(fields["wave"]), "locators": re.findall(r"(?m)^### (.+)$", _common.section_body(text, "Review findings") or "")
|
|
2117
|
+
"wave": int(fields["wave"]), "locators": re.findall(r"(?m)^### (.+)$", _common.section_body(text, "Review findings") or ""),
|
|
2118
|
+
**({"repo": fields["repo"]} if fields.get("repo") else {})})
|
|
2119
|
+
if fields["status"] != "done":
|
|
2120
|
+
pending.append(repairs[-1])
|
|
2023
2121
|
suffix = f"-fix-wave-{options.wave}-cycle-{options.cycle}.md"
|
|
2024
|
-
|
|
2025
|
-
|
|
2122
|
+
# A build recovery restarts a changed wave at cycle 1, so the repairs and the PLAN heading
|
|
2123
|
+
# of the review it set aside carry the same names. They belong to that review, not this one.
|
|
2124
|
+
replaced = replaced_review_base(primary, options.project_dir, options.wave, options.cycle)
|
|
2125
|
+
|
|
2126
|
+
def of_replaced_review(relative: str) -> bool:
|
|
2127
|
+
if not replaced or isolation.run_git(primary, "show", f"{replaced}:{relative}").returncode != 0:
|
|
2128
|
+
return False
|
|
2129
|
+
return not isolation.run_git(primary, "log", "--diff-filter=AD", "--format=%H",
|
|
2130
|
+
f"{replaced}..HEAD", "--", relative).stdout.strip()
|
|
2131
|
+
|
|
2132
|
+
stale = [item for item in repairs if item["path"].endswith(suffix) and of_replaced_review(item["path"])]
|
|
2133
|
+
existing = [item for item in repairs if item["path"].endswith(suffix) and item not in stale]
|
|
2134
|
+
# A landed repair no longer carries its finding: a repeat proves that fix failed and needs the next one.
|
|
2135
|
+
# Two repos can carry the same locator, so a repair carries a finding only in its own repo.
|
|
2136
|
+
carried = {(item.get("repo"), locator) for item in pending if not item["path"].endswith(suffix)
|
|
2137
|
+
for locator in item["locators"]}
|
|
2026
2138
|
findings = review_findings.compute(primary, options.project_dir, options.wave, options.cycle)
|
|
2027
2139
|
receipt["findings"] = findings
|
|
2028
2140
|
escalation = [key for key in ("structural_blockers", "skeptic_groups", "cap_reached", "all_refuted")
|
|
@@ -2043,8 +2155,10 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
|
|
|
2043
2155
|
repair_waves = {item["wave"] for item in existing}
|
|
2044
2156
|
if len(repair_waves) > 1:
|
|
2045
2157
|
raise DriverStop("repair tasks for this cycle disagree on their wave")
|
|
2046
|
-
|
|
2158
|
+
stale_waves = {item["wave"] for item in stale}
|
|
2159
|
+
repair_heading = next((heading for heading in re.finditer(
|
|
2047
2160
|
rf"(?m)^## Wave (\d+) — fix wave {options.wave} cycle {options.cycle} review findings$", plan_text)
|
|
2161
|
+
if int(heading.group(1)) not in stale_waves), None)
|
|
2048
2162
|
if repair_waves:
|
|
2049
2163
|
fix_wave = next(iter(repair_waves))
|
|
2050
2164
|
elif repair_heading:
|
|
@@ -2055,9 +2169,10 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
|
|
|
2055
2169
|
raise DriverStop("repair tasks disagree with the PLAN repair wave")
|
|
2056
2170
|
receipt["existing"] = existing
|
|
2057
2171
|
for batch in findings["fix_batches"]:
|
|
2058
|
-
|
|
2172
|
+
repo = batch.get("repo") # a member name, or None for the coordinator
|
|
2173
|
+
if any(item.get("repo") == repo and set(batch["locators"]) <= set(item["locators"]) for item in existing):
|
|
2059
2174
|
continue
|
|
2060
|
-
if all(locator in carried for locator in batch["locators"]):
|
|
2175
|
+
if all((repo, locator) in carried for locator in batch["locators"]):
|
|
2061
2176
|
receipt["carried"].append(batch)
|
|
2062
2177
|
continue
|
|
2063
2178
|
batch_groups = [groups[locator] for locator in batch["locators"]]
|
|
@@ -2065,9 +2180,15 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
|
|
|
2065
2180
|
for source in sources:
|
|
2066
2181
|
if source not in texts:
|
|
2067
2182
|
raise DriverStop(f"fix batch names unknown task {source}")
|
|
2183
|
+
# One task changes one repo: the fix depends on, and runs the Verify of, the source tasks of its repo.
|
|
2184
|
+
sources = [source for source in sources if isolation.task_frontmatter(texts[source])[0].get("repo") == repo]
|
|
2185
|
+
# A repeat builds on the landed repairs of its finding: repair-evidence links them by this dependency.
|
|
2186
|
+
deps = sources + [item["task"] for item in repairs
|
|
2187
|
+
if item not in pending and item.get("repo") == repo
|
|
2188
|
+
and f"-fix-wave-{options.wave}-cycle-" in item["path"] and not item["path"].endswith(suffix)
|
|
2189
|
+
and set(item["locators"]) & set(batch["locators"])]
|
|
2068
2190
|
task_id = next_task_id(tasks_dir)
|
|
2069
|
-
verify =
|
|
2070
|
-
f"(\n{command}\n)" for command in dict.fromkeys(_common.task_verify_command(texts[source]) for source in sources))
|
|
2191
|
+
verify = _common.fix_verify(_common.task_verify_command(texts[source]) for source in sources)
|
|
2071
2192
|
heavy = any(re.search(r"(?m)^Heavy:\s*yes", texts[source]) for source in sources)
|
|
2072
2193
|
criteria = "\n".join(f"{index}. {criterion}" for index, criterion in
|
|
2073
2194
|
enumerate(dict.fromkeys(group["criterion"] for group in batch_groups), 1))
|
|
@@ -2075,18 +2196,19 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
|
|
|
2075
2196
|
f"### {group['locator']}\nCriterion: {group['criterion']}\n"
|
|
2076
2197
|
+ "\n".join(f"- {item['text']}" for item in group["observations"]) for group in batch_groups)
|
|
2077
2198
|
files = "\n".join(f" - {path}" for path in batch["files"])
|
|
2199
|
+
member = f"repo: {repo}\n" if repo else "" # the task template puts `repo:` before `files:`
|
|
2078
2200
|
title = f"Fix wave {options.wave} cycle {options.cycle} review findings in {', '.join(sources)}"
|
|
2079
2201
|
text = f"""---
|
|
2080
2202
|
id: {task_id}
|
|
2081
2203
|
title: {title}
|
|
2082
2204
|
wave: {fix_wave}
|
|
2083
|
-
deps: [{', '.join(
|
|
2205
|
+
deps: [{', '.join(deps)}]
|
|
2084
2206
|
status: pending
|
|
2085
2207
|
agent: null
|
|
2086
2208
|
base: null
|
|
2087
2209
|
worktree: null
|
|
2088
2210
|
task_branch: null
|
|
2089
|
-
files:
|
|
2211
|
+
{member}files:
|
|
2090
2212
|
{files}
|
|
2091
2213
|
---
|
|
2092
2214
|
|
|
@@ -2135,8 +2257,8 @@ Heavy: {'yes' if heavy else 'no'}
|
|
|
2135
2257
|
path = tasks_dir / f"{task_id}-fix-wave-{options.wave}-cycle-{options.cycle}.md"
|
|
2136
2258
|
_common.atomic_write(path, text)
|
|
2137
2259
|
receipt["created"].append({"task": task_id, "path": path.relative_to(primary).as_posix(),
|
|
2138
|
-
"wave": fix_wave, "deps":
|
|
2139
|
-
"locators": batch["locators"]})
|
|
2260
|
+
"wave": fix_wave, "deps": deps, "files": batch["files"],
|
|
2261
|
+
"locators": batch["locators"], **({"repo": repo} if repo else {})})
|
|
2140
2262
|
rows = []
|
|
2141
2263
|
for item in [*existing, *receipt["created"]]:
|
|
2142
2264
|
fields, _ = isolation.task_frontmatter((primary / item["path"]).read_text(encoding="utf-8"))
|
|
@@ -2421,6 +2543,10 @@ def main(argv=None) -> int:
|
|
|
2421
2543
|
if native and native["base"] != records[0].get("base"):
|
|
2422
2544
|
records = []
|
|
2423
2545
|
else:
|
|
2546
|
+
if (native and records[0]["outcome"] == "blocked" and records[0].get("native")
|
|
2547
|
+
and not records[0].get("command")):
|
|
2548
|
+
# A failed finish leaves a host-dispatched task active at its base: finish may run again.
|
|
2549
|
+
update_state(records[0], outcome=None, reason=None)
|
|
2424
2550
|
native = None
|
|
2425
2551
|
if arguments.task_id in current.proven:
|
|
2426
2552
|
commit = current.proven[arguments.task_id]
|
|
@@ -19,9 +19,10 @@ _HERE = Path(__file__).resolve().parent
|
|
|
19
19
|
sys.path.insert(0, str(_HERE / "runtime" if (_HERE / "runtime" / "isolation.py").is_file() else _HERE))
|
|
20
20
|
sys.dont_write_bytecode = True # a hook must not leave __pycache__ in the worktree
|
|
21
21
|
try:
|
|
22
|
-
from isolation import BOOKKEEPING_PREFIXES, NULL_SHA, _landing_state, task_frontmatter
|
|
22
|
+
from isolation import BOOKKEEPING_PREFIXES, NULL_SHA, _landing_state, task_frontmatter, undeclared_paths
|
|
23
23
|
from pipeline_git import is_ship_subject, ship_body_matches, ship_commit_body, task_commit_body
|
|
24
24
|
from pipeline_state import _completion_status
|
|
25
|
+
from _common import valid_default_branch
|
|
25
26
|
import members
|
|
26
27
|
except ImportError as error: # pragma: no cover - broken install
|
|
27
28
|
print(
|
|
@@ -240,7 +241,10 @@ def frontmatter_of(content, label):
|
|
|
240
241
|
|
|
241
242
|
|
|
242
243
|
def strict_ship_state(state, archive):
|
|
243
|
-
|
|
244
|
+
# default_branch is optional beside each accepted field set.
|
|
245
|
+
if "default_branch" in state and not valid_default_branch(state["default_branch"]):
|
|
246
|
+
return False
|
|
247
|
+
fields = frozenset(state) - {"default_branch"}
|
|
244
248
|
if fields not in {
|
|
245
249
|
frozenset(LEGACY_STATE_FIELDS),
|
|
246
250
|
frozenset(INTEGRATION_STATE_FIELDS),
|
|
@@ -509,7 +513,7 @@ def product_commit_violations(entries, subject, body):
|
|
|
509
513
|
if error:
|
|
510
514
|
problems.append(error)
|
|
511
515
|
if contract_paths is not None:
|
|
512
|
-
stray =
|
|
516
|
+
stray = undeclared_paths(repo_root(), base, set(staged), contract_paths)
|
|
513
517
|
if stray:
|
|
514
518
|
problems.append("undeclared paths: " + ", ".join(stray))
|
|
515
519
|
if body != task_commit_body(task_file, staged, base).strip():
|
|
@@ -559,8 +563,10 @@ def member_pre_push_violations(lines, role):
|
|
|
559
563
|
"""Member mode: the member's own STATE is ignored; coordinator refs need authorization."""
|
|
560
564
|
checkout, project = repo_root(), role["project"]
|
|
561
565
|
reserved = members.member_ref_prefixes(project)
|
|
562
|
-
|
|
563
|
-
|
|
566
|
+
# The coordinator's MEMBERS.md records this member's default branch.
|
|
567
|
+
default = members.member_default_branch(role["coordinator"], role["name"])
|
|
568
|
+
members.require_origin_branch(checkout, default)
|
|
569
|
+
exclude = ["--not", f"refs/remotes/origin/{default}"]
|
|
564
570
|
member_work = None
|
|
565
571
|
found = []
|
|
566
572
|
for _, local_sha, remote_ref, remote_sha in _push_updates(lines):
|
|
@@ -641,7 +647,20 @@ def abandon_contract_violations(subject, body, abandon):
|
|
|
641
647
|
return []
|
|
642
648
|
|
|
643
649
|
|
|
650
|
+
def recorded_default_branch():
|
|
651
|
+
"""STATE default_branch in the worktree: main without the field; None without a readable STATE."""
|
|
652
|
+
try:
|
|
653
|
+
content = (repo_root() / ".project" / "STATE.md").read_text(encoding="utf-8")
|
|
654
|
+
name = frontmatter_of(content, "STATE.md").get("default_branch", "main")
|
|
655
|
+
except (OSError, ValueError, subprocess.CalledProcessError):
|
|
656
|
+
return None
|
|
657
|
+
return name if valid_default_branch(name) else None
|
|
658
|
+
|
|
659
|
+
|
|
644
660
|
def default_branch():
|
|
661
|
+
recorded = recorded_default_branch()
|
|
662
|
+
if recorded:
|
|
663
|
+
return recorded
|
|
645
664
|
origin_head = subprocess.run(
|
|
646
665
|
["git", "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
|
|
647
666
|
capture_output=True,
|