@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
|
@@ -100,13 +100,15 @@ def _supplied_contract(repo: Path, task: Path, token: str) -> bool:
|
|
|
100
100
|
return path.is_file() and path.resolve() == repo.resolve() / token
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _pipeline_workspace_path(repo: Path, task: Path, token: str) -> bool:
|
|
104
|
+
track = _project_track(task, repo)
|
|
105
|
+
return track is not None and token.startswith(
|
|
106
|
+
tuple(f"{track}/{name}/" for name in PIPELINE_WORKSPACE_DIRS))
|
|
107
|
+
|
|
108
|
+
|
|
103
109
|
def _pipeline_workspace_file(repo: Path, task: Path, token: str) -> bool:
|
|
104
110
|
"""Pipeline-owned paths may exist only in the working tree before checkpoint."""
|
|
105
|
-
|
|
106
|
-
if track is None:
|
|
107
|
-
return False
|
|
108
|
-
prefixes = tuple(f"{track}/{name}/" for name in PIPELINE_WORKSPACE_DIRS)
|
|
109
|
-
if not any(token.startswith(prefix) for prefix in prefixes):
|
|
111
|
+
if not _pipeline_workspace_path(repo, task, token):
|
|
110
112
|
return False
|
|
111
113
|
path = repo / token
|
|
112
114
|
return path.is_file() and path.resolve() == repo.resolve() / token
|
|
@@ -277,7 +279,7 @@ def _verify_tokens(
|
|
|
277
279
|
|
|
278
280
|
|
|
279
281
|
def _member_bases(root: Path):
|
|
280
|
-
"""Resolve a member name to (checkout,
|
|
282
|
+
"""Resolve a member name to (checkout, base SHA), reading MEMBERS.md once."""
|
|
281
283
|
cache: Dict[str, Optional[Tuple[Path, str]]] = {}
|
|
282
284
|
|
|
283
285
|
def locate(name: str) -> Optional[Tuple[Path, str]]:
|
|
@@ -290,21 +292,22 @@ def _member_bases(root: Path):
|
|
|
290
292
|
except ImportError: # pragma: no cover - package import used by tests
|
|
291
293
|
from scripts import members
|
|
292
294
|
try:
|
|
293
|
-
listed = {member["name"]:
|
|
294
|
-
|
|
295
|
-
if checkout is None:
|
|
295
|
+
listed = {member["name"]: member for member in members.read_members(root)}
|
|
296
|
+
if name not in listed:
|
|
296
297
|
cache[name] = None
|
|
297
298
|
else:
|
|
299
|
+
checkout = Path(listed[name]["checkout"])
|
|
298
300
|
# A build lock selects the bound tip, including during Plan recovery;
|
|
299
|
-
# without the lock, use
|
|
301
|
+
# without the lock, use the member's recorded default branch on origin
|
|
302
|
+
# before the first build start.
|
|
300
303
|
lock = root / members.LOCK_PATH
|
|
301
304
|
locked = [entry for entry in (json.loads(lock.read_text(encoding="utf-8"))["members"]
|
|
302
305
|
if lock.is_file() else []) if entry["name"] == name]
|
|
303
306
|
if locked:
|
|
304
307
|
cache[name] = (checkout, _resolve_base(checkout, f"refs/heads/{locked[0]['branch']}"))
|
|
305
308
|
else:
|
|
306
|
-
members.
|
|
307
|
-
|
|
309
|
+
cache[name] = (checkout, members.require_origin_branch(
|
|
310
|
+
checkout, members.default_branch(listed[name])))
|
|
308
311
|
except (members.MembersError, BriefError) as error:
|
|
309
312
|
raise BriefError(f"member {name} is unavailable: {error}") from error
|
|
310
313
|
return cache[name]
|
|
@@ -328,8 +331,8 @@ def _lint_task(
|
|
|
328
331
|
checked = 0
|
|
329
332
|
if fields is None:
|
|
330
333
|
return task_id, [error or "invalid frontmatter"], None, checked
|
|
331
|
-
# A member task's paths resolve in the member at its origin
|
|
332
|
-
# member bound branch starts; `repo:` absent means the coordinator.
|
|
334
|
+
# A member task's paths resolve in the member at its default branch on origin,
|
|
335
|
+
# where the member bound branch starts; `repo:` absent means the coordinator.
|
|
333
336
|
git_root = repo
|
|
334
337
|
member = fields.get("repo")
|
|
335
338
|
if member is not None:
|
|
@@ -397,6 +400,8 @@ def _lint_task(
|
|
|
397
400
|
and token not in supplied
|
|
398
401
|
and not _supplied_contract(repo, path, token)
|
|
399
402
|
and not _pipeline_workspace_file(repo, path, token)
|
|
403
|
+
# A landed brief is history: the pipeline moves its own files (build recovery, archive) later.
|
|
404
|
+
and not (task_id in (landed_bases or {}) and _pipeline_workspace_path(repo, path, token))
|
|
400
405
|
and not _base_exists(git_root, base, token)
|
|
401
406
|
):
|
|
402
407
|
problems.append(f"## {name} names a path missing at the layer base: {token}")
|
|
@@ -1291,10 +1291,17 @@ def project_slug(root: Path) -> str:
|
|
|
1291
1291
|
return slug or "project"
|
|
1292
1292
|
|
|
1293
1293
|
|
|
1294
|
-
def filled_state_template(
|
|
1294
|
+
def filled_state_template(
|
|
1295
|
+
template: str, slug: str, phase: str, default_branch: str = "main"
|
|
1296
|
+
) -> str:
|
|
1295
1297
|
if "<slug>" not in template:
|
|
1296
1298
|
raise DetectError("state template missing slug placeholder")
|
|
1297
1299
|
text = template.replace("<slug>", slug)
|
|
1300
|
+
text, count = re.subn(
|
|
1301
|
+
r"(?m)^default_branch: main\b", lambda _: f"default_branch: {default_branch}", text, count=1
|
|
1302
|
+
)
|
|
1303
|
+
if count != 1:
|
|
1304
|
+
raise DetectError("state template missing default_branch: main line")
|
|
1298
1305
|
text, count = re.subn(r"(?m)^phase: define\b", f"phase: {phase}", text, count=1)
|
|
1299
1306
|
if count != 1:
|
|
1300
1307
|
raise DetectError("state template missing phase: define line")
|
|
@@ -2030,6 +2037,14 @@ def initialize(
|
|
|
2030
2037
|
)
|
|
2031
2038
|
if git.returncode != 0 or git.stdout.strip() != "true":
|
|
2032
2039
|
return {**payload, "route": "setup-repository", "wrote_state": False}
|
|
2040
|
+
default_branch = _common.origin_default_branch(root)
|
|
2041
|
+
if not _common.valid_default_branch(default_branch):
|
|
2042
|
+
return {**payload, "wrote_state": False, "error": (
|
|
2043
|
+
f"origin default branch {default_branch!r} cannot be recorded in STATE.md: "
|
|
2044
|
+
"it is a GSD Path reserved name or it has a character that STATE frontmatter "
|
|
2045
|
+
"cannot hold. Rename the default branch, then run "
|
|
2046
|
+
"`git remote set-head origin --auto`."
|
|
2047
|
+
)}
|
|
2033
2048
|
expected = "inspect" if payload["verdict"] == "brownfield" else "define"
|
|
2034
2049
|
if phase is None:
|
|
2035
2050
|
phase = expected
|
|
@@ -2043,7 +2058,7 @@ def initialize(
|
|
|
2043
2058
|
return payload
|
|
2044
2059
|
write_state_anchored(
|
|
2045
2060
|
root,
|
|
2046
|
-
filled_state_template(raw, project_slug(root), phase),
|
|
2061
|
+
filled_state_template(raw, project_slug(root), phase, default_branch),
|
|
2047
2062
|
root_status,
|
|
2048
2063
|
project_status,
|
|
2049
2064
|
)
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import shutil
|
|
9
9
|
import sys
|
|
10
|
+
import traceback
|
|
10
11
|
|
|
11
12
|
# Runtime helpers must not modify their immutable installation.
|
|
12
13
|
sys.dont_write_bytecode = True
|
|
@@ -33,7 +34,7 @@ def _load_pipeline_modules():
|
|
|
33
34
|
try:
|
|
34
35
|
from scripts import archive_milestone, pipeline_state, _common
|
|
35
36
|
return _common, archive_milestone, pipeline_state
|
|
36
|
-
except
|
|
37
|
+
except ImportError as error: # a foreign `scripts` package
|
|
37
38
|
if error.name not in {
|
|
38
39
|
"scripts",
|
|
39
40
|
"scripts.archive_milestone",
|
|
@@ -41,6 +42,13 @@ def _load_pipeline_modules():
|
|
|
41
42
|
"scripts._common",
|
|
42
43
|
}:
|
|
43
44
|
raise
|
|
45
|
+
# A plain ImportError raised while a `scripts` module loaded is not a
|
|
46
|
+
# foreign package. A missing module falls back, as before.
|
|
47
|
+
if not isinstance(error, ModuleNotFoundError) and any(
|
|
48
|
+
frame.f_globals.get("__name__", "").startswith("scripts.")
|
|
49
|
+
for frame, _ in traceback.walk_tb(error.__traceback__)
|
|
50
|
+
):
|
|
51
|
+
raise
|
|
44
52
|
shared_scripts = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
|
|
45
53
|
sys.path.insert(0, str(shared_scripts))
|
|
46
54
|
import archive_milestone
|
|
@@ -507,7 +507,8 @@ def archived_intent_criteria(archive: Path) -> dict[int, str]:
|
|
|
507
507
|
criteria = check_handoffs._success_criteria(text)
|
|
508
508
|
except check_handoffs.HandoffError as error:
|
|
509
509
|
raise ArchiveError(str(error)) from error
|
|
510
|
-
|
|
510
|
+
# Each value keeps the form without sub-bullet markers that quotes_criterion reads.
|
|
511
|
+
return {int(key[2:]): value for key, value in criteria.items()}
|
|
511
512
|
|
|
512
513
|
|
|
513
514
|
def parse_final_review(archive: Path) -> tuple:
|
|
@@ -563,7 +564,7 @@ def _final_review(archive: Path) -> tuple:
|
|
|
563
564
|
if len(headings) != len(intent_criteria):
|
|
564
565
|
raise ArchiveError("FINAL.md success criteria do not exactly cover INTENT.md")
|
|
565
566
|
for _, number, criterion in headings:
|
|
566
|
-
if
|
|
567
|
+
if not _common.quotes_criterion(criterion, intent_criteria[number]):
|
|
567
568
|
raise ArchiveError(
|
|
568
569
|
f"FINAL.md SC{number} heading text differs from archived INTENT.md"
|
|
569
570
|
)
|
|
@@ -951,10 +952,10 @@ def validate_wave_review(
|
|
|
951
952
|
(index, match.group(1), " ".join(match.group(2).split()), match.group(3))
|
|
952
953
|
)
|
|
953
954
|
actual = [(sc_id, text) for _, sc_id, text, _ in criterion_headings]
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
955
|
+
if len(actual) != len(expected_criteria) or any(
|
|
956
|
+
sc_id != expected_id or not _common.quotes_criterion(text, expected)
|
|
957
|
+
for (sc_id, text), (expected_id, expected) in zip(actual, expected_criteria)
|
|
958
|
+
):
|
|
958
959
|
raise ArchiveError(
|
|
959
960
|
f"{path.name} Intent coverage does not match its owned success criteria"
|
|
960
961
|
)
|
|
@@ -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]
|