@opengsd/gsd-path 1.5.0 → 1.6.1
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/AGENTS.md +1 -1
- package/CHANGELOG.md +169 -0
- package/DOCS.md +4 -3
- package/FULL.md +68 -7
- package/HOOKS.md +81 -4
- package/MIGRATE.md +12 -0
- package/QUICK.md +4 -2
- package/README.md +69 -16
- package/RUNTIME.md +23 -5
- package/UPDATE.md +35 -0
- package/WORKFLOW.md +66 -7
- package/package.json +1 -1
- package/scripts/_common.py +150 -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 +27 -19
- 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 +174 -36
- package/scripts/git_guard.py +42 -7
- package/scripts/guard_hook.py +1216 -47
- package/scripts/install.mjs +76 -3
- package/scripts/install.py +182 -37
- package/scripts/integration.py +115 -68
- package/scripts/isolation.py +192 -23
- package/scripts/members.py +68 -25
- package/scripts/migrate_core.py +2 -0
- package/scripts/model_policy.py +13 -2
- package/scripts/pipeline_diagnose.py +17 -2
- package/scripts/pipeline_git.py +87 -35
- package/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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 +6 -2
- package/skills/gsd-path/ROADMAP.md +16 -6
- package/skills/gsd-path/SHIP.md +38 -11
- 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/model-policy.md +3 -1
- package/skills/gsd-path/references/operating-rules.md +14 -5
- 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 +10 -0
- package/skills/gsd-path/scripts/_common.py +150 -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 +27 -19
- 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 +174 -36
- package/skills/gsd-path/scripts/git_guard.py +42 -7
- package/skills/gsd-path/scripts/integration.py +115 -68
- package/skills/gsd-path/scripts/isolation.py +192 -23
- package/skills/gsd-path/scripts/members.py +68 -25
- package/skills/gsd-path/scripts/model_policy.py +13 -2
- package/skills/gsd-path/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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/model-policy.md +3 -1
- package/skills/gsd-path-build/references/operating-rules.md +14 -5
- 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 +10 -0
- package/skills/gsd-path-build/scripts/_common.py +150 -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 +27 -19
- 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 +174 -36
- package/skills/gsd-path-build/scripts/integration.py +115 -68
- package/skills/gsd-path-build/scripts/isolation.py +192 -23
- package/skills/gsd-path-build/scripts/members.py +68 -25
- package/skills/gsd-path-build/scripts/model_policy.py +13 -2
- package/skills/gsd-path-build/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-build/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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/model-policy.md +3 -1
- package/skills/gsd-path-decide/references/operating-rules.md +14 -5
- package/skills/gsd-path-decide/references/runtime-selection.md +10 -0
- package/skills/gsd-path-decide/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-decide/scripts/isolation.py +192 -23
- package/skills/gsd-path-decide/scripts/members.py +68 -25
- package/skills/gsd-path-decide/scripts/model_policy.py +13 -2
- package/skills/gsd-path-decide/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-decide/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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 +14 -5
- package/skills/gsd-path-define/references/runtime-selection.md +10 -0
- package/skills/gsd-path-define/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-define/scripts/isolation.py +192 -23
- package/skills/gsd-path-define/scripts/members.py +68 -25
- package/skills/gsd-path-define/scripts/model_policy.py +13 -2
- package/skills/gsd-path-define/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-define/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- package/skills/gsd-path-define/templates/state.md +1 -0
- package/skills/gsd-path-discuss/references/operating-rules.md +14 -5
- package/skills/gsd-path-discuss/references/runtime-selection.md +10 -0
- package/skills/gsd-path-discuss/scripts/_common.py +150 -1
- package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-discuss/scripts/model_policy.py +13 -2
- package/skills/gsd-path-docs-audit/references/model-policy.md +3 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +14 -5
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +10 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-docs-audit/scripts/isolation.py +192 -23
- package/skills/gsd-path-docs-audit/scripts/members.py +68 -25
- package/skills/gsd-path-docs-audit/scripts/model_policy.py +13 -2
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- package/skills/gsd-path-forensics/references/operating-rules.md +14 -5
- package/skills/gsd-path-forensics/references/runtime-selection.md +10 -0
- package/skills/gsd-path-forensics/scripts/_common.py +150 -1
- package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-forensics/scripts/model_policy.py +13 -2
- 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/model-policy.md +3 -1
- package/skills/gsd-path-inspect/references/operating-rules.md +14 -5
- package/skills/gsd-path-inspect/references/runtime-selection.md +10 -0
- package/skills/gsd-path-inspect/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-inspect/scripts/isolation.py +192 -23
- package/skills/gsd-path-inspect/scripts/members.py +68 -25
- package/skills/gsd-path-inspect/scripts/model_policy.py +13 -2
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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 +14 -5
- package/skills/gsd-path-loop/references/runtime-selection.md +10 -0
- package/skills/gsd-path-loop/scripts/_common.py +150 -1
- package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-loop/scripts/model_policy.py +13 -2
- package/skills/gsd-path-migrate/SKILL.md +8 -0
- package/skills/gsd-path-migrate/references/operating-rules.md +14 -5
- package/skills/gsd-path-migrate/references/runtime-selection.md +10 -0
- package/skills/gsd-path-migrate/scripts/_common.py +150 -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 +13 -2
- package/skills/gsd-path-plan/SKILL.md +6 -2
- package/skills/gsd-path-plan/references/build-recovery.md +13 -0
- package/skills/gsd-path-plan/references/model-policy.md +3 -1
- package/skills/gsd-path-plan/references/operating-rules.md +14 -5
- package/skills/gsd-path-plan/references/planner.md +13 -1
- package/skills/gsd-path-plan/references/runtime-selection.md +10 -0
- package/skills/gsd-path-plan/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-plan/scripts/isolation.py +192 -23
- package/skills/gsd-path-plan/scripts/members.py +68 -25
- package/skills/gsd-path-plan/scripts/model_policy.py +13 -2
- package/skills/gsd-path-plan/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-plan/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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/model-policy.md +3 -1
- package/skills/gsd-path-research/references/operating-rules.md +14 -5
- package/skills/gsd-path-research/references/runtime-selection.md +10 -0
- package/skills/gsd-path-research/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-research/scripts/isolation.py +192 -23
- package/skills/gsd-path-research/scripts/members.py +68 -25
- package/skills/gsd-path-research/scripts/model_policy.py +13 -2
- package/skills/gsd-path-research/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-research/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- package/skills/gsd-path-roadmap/SKILL.md +16 -6
- package/skills/gsd-path-roadmap/references/model-policy.md +3 -1
- package/skills/gsd-path-roadmap/references/operating-rules.md +14 -5
- package/skills/gsd-path-roadmap/references/runtime-selection.md +10 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-roadmap/scripts/isolation.py +192 -23
- package/skills/gsd-path-roadmap/scripts/members.py +68 -25
- package/skills/gsd-path-roadmap/scripts/model_policy.py +13 -2
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- package/skills/gsd-path-ship/SKILL.md +38 -11
- package/skills/gsd-path-ship/references/model-policy.md +3 -1
- package/skills/gsd-path-ship/references/operating-rules.md +14 -5
- 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 +10 -0
- package/skills/gsd-path-ship/scripts/_common.py +150 -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 +27 -19
- 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 +115 -68
- package/skills/gsd-path-ship/scripts/isolation.py +192 -23
- package/skills/gsd-path-ship/scripts/members.py +68 -25
- package/skills/gsd-path-ship/scripts/model_policy.py +13 -2
- package/skills/gsd-path-ship/scripts/pipeline_git.py +87 -35
- package/skills/gsd-path-ship/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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 +14 -5
- package/skills/gsd-path-undo/references/runtime-selection.md +10 -0
- package/skills/gsd-path-undo/scripts/_common.py +150 -1
- package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
- package/skills/gsd-path-undo/scripts/model_policy.py +13 -2
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +17 -8
- package/skills/path/DEFINE.md +6 -2
- package/skills/path/PLAN.md +6 -2
- package/skills/path/ROADMAP.md +16 -6
- package/skills/path/SHIP.md +38 -11
- 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/model-policy.md +3 -1
- package/skills/path/references/operating-rules.md +14 -5
- 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 +10 -0
- package/skills/path/scripts/_common.py +150 -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 +27 -19
- 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 +174 -36
- package/skills/path/scripts/git_guard.py +42 -7
- package/skills/path/scripts/integration.py +115 -68
- package/skills/path/scripts/isolation.py +192 -23
- package/skills/path/scripts/members.py +68 -25
- package/skills/path/scripts/model_policy.py +13 -2
- package/skills/path/scripts/pipeline_git.py +87 -35
- package/skills/path/scripts/pipeline_state.py +31 -7
- 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 +92 -5
- 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
|
@@ -343,6 +343,67 @@ def _completed_promotion(
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
|
|
346
|
+
def _ruling_rows(text: str) -> dict[str, list[str]]:
|
|
347
|
+
"""Map Queue # to the cells of each `## User rulings` table row."""
|
|
348
|
+
rows: dict[str, list[str]] = {}
|
|
349
|
+
in_rulings = False
|
|
350
|
+
for line in text.splitlines():
|
|
351
|
+
if line == "## User rulings":
|
|
352
|
+
in_rulings = True
|
|
353
|
+
continue
|
|
354
|
+
if in_rulings and line.startswith("## "):
|
|
355
|
+
break
|
|
356
|
+
if in_rulings and line.lstrip().startswith("|"):
|
|
357
|
+
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
|
358
|
+
if len(cells) == 4 and cells[0].isdigit():
|
|
359
|
+
rows[cells[0]] = cells
|
|
360
|
+
return rows
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _superseded_carry_forward(
|
|
364
|
+
repo: Path,
|
|
365
|
+
active_state: PipelineState,
|
|
366
|
+
source: Path,
|
|
367
|
+
destination: Path,
|
|
368
|
+
) -> bool:
|
|
369
|
+
"""True when active research is only the shipped DOCS-AUDIT carry-forward
|
|
370
|
+
and the lookahead audit keeps every one of its rulings."""
|
|
371
|
+
if not active_state.archive or destination.is_symlink() or not destination.is_dir():
|
|
372
|
+
return False
|
|
373
|
+
archived = repo / active_state.archive / "research" / "DOCS-AUDIT.md"
|
|
374
|
+
active = destination / "DOCS-AUDIT.md"
|
|
375
|
+
lookahead = source / "DOCS-AUDIT.md"
|
|
376
|
+
entries = [
|
|
377
|
+
path for path in destination.iterdir() if not state_checkpoint._is_junk_file(path)
|
|
378
|
+
]
|
|
379
|
+
if entries != [active] or any(
|
|
380
|
+
path.is_symlink() or not path.is_file() for path in (archived, active, lookahead)
|
|
381
|
+
):
|
|
382
|
+
return False
|
|
383
|
+
carried = active.read_bytes()
|
|
384
|
+
if carried != archived.read_bytes():
|
|
385
|
+
return False
|
|
386
|
+
rulings = _ruling_rows(carried.decode("utf-8"))
|
|
387
|
+
if not any(cells[3] == "no" for cells in rulings.values()):
|
|
388
|
+
return False
|
|
389
|
+
kept = _ruling_rows(lookahead.read_text(encoding="utf-8"))
|
|
390
|
+
for number, cells in rulings.items():
|
|
391
|
+
other = kept.get(number)
|
|
392
|
+
# Plan may record a T### for a pending ruling; nothing else may change.
|
|
393
|
+
if (
|
|
394
|
+
other is None
|
|
395
|
+
or other[:3] != cells[:3]
|
|
396
|
+
or (
|
|
397
|
+
other[3] != cells[3]
|
|
398
|
+
and not (cells[3] == "no" and re.fullmatch(r"T\d{3}", other[3]))
|
|
399
|
+
)
|
|
400
|
+
):
|
|
401
|
+
raise PipelineStateError(
|
|
402
|
+
f"lookahead DOCS-AUDIT.md does not keep carry-forward ruling #{number}"
|
|
403
|
+
)
|
|
404
|
+
return True
|
|
405
|
+
|
|
406
|
+
|
|
346
407
|
def _prepare_promotion(
|
|
347
408
|
repo: Path,
|
|
348
409
|
project: Path,
|
|
@@ -377,17 +438,22 @@ def _prepare_promotion(
|
|
|
377
438
|
head = pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
|
|
378
439
|
if head != base:
|
|
379
440
|
raise PipelineStateError(f"promotion must start at base SHA: {head} != {base}")
|
|
380
|
-
|
|
441
|
+
active_state, active_text, _ = pipeline_state.load_state(repo)
|
|
442
|
+
remote_default = f"origin/{active_state.default_branch}"
|
|
443
|
+
remote = pipeline_state._run_git(
|
|
444
|
+
repo, "rev-parse", "--verify", f"{remote_default}^{{commit}}"
|
|
445
|
+
).stdout.strip()
|
|
381
446
|
if remote != base:
|
|
382
447
|
raise PipelineStateError(
|
|
383
|
-
f"base SHA is not current
|
|
448
|
+
f"base SHA is not current {remote_default}: {base} != {remote}"
|
|
384
449
|
)
|
|
385
450
|
if not pipeline_state._is_ancestor(repo, landing, base):
|
|
386
|
-
raise PipelineStateError(
|
|
451
|
+
raise PipelineStateError(
|
|
452
|
+
f"landing is not an ancestor of the current {active_state.default_branch} base"
|
|
453
|
+
)
|
|
387
454
|
if pipeline_state._run_git(repo, "status", "--porcelain", "--untracked-files=all").stdout:
|
|
388
455
|
raise PipelineStateError("promotion must start from a clean worktree")
|
|
389
456
|
|
|
390
|
-
active_state, active_text, _ = pipeline_state.load_state(repo)
|
|
391
457
|
if active_state.phase != "shipped" or active_state.status != "done":
|
|
392
458
|
raise PipelineStateError("promotion requires active STATE shipped/done")
|
|
393
459
|
if active_state.milestone is None:
|
|
@@ -456,6 +522,12 @@ def _prepare_promotion(
|
|
|
456
522
|
raise PipelineStateError(
|
|
457
523
|
"lookahead integration_default does not match the active project"
|
|
458
524
|
)
|
|
525
|
+
if next_state.default_branch != active_state.default_branch:
|
|
526
|
+
raise PipelineStateError(
|
|
527
|
+
"lookahead default_branch does not match the active project: copy "
|
|
528
|
+
f"`default_branch: {active_state.default_branch}` from .project/STATE.md "
|
|
529
|
+
"into .project/next/STATE.md"
|
|
530
|
+
)
|
|
459
531
|
if next_state.milestone != milestone:
|
|
460
532
|
raise PipelineStateError(
|
|
461
533
|
f"requested milestone {milestone} != next STATE milestone {next_state.milestone}"
|
|
@@ -464,12 +536,17 @@ def _prepare_promotion(
|
|
|
464
536
|
raise PipelineStateError("lookahead STATE branch and archive must be null")
|
|
465
537
|
|
|
466
538
|
tracks: dict[str, str] = {}
|
|
539
|
+
carry_forward: Optional[str] = None
|
|
467
540
|
for name in PROMOTION_TRACKS:
|
|
468
541
|
source = next_root / name
|
|
469
542
|
destination = project / name
|
|
470
543
|
if source.exists() or source.is_symlink():
|
|
471
544
|
if destination.exists() or destination.is_symlink():
|
|
472
|
-
|
|
545
|
+
if name != "research" or not _superseded_carry_forward(
|
|
546
|
+
repo, active_state, source, destination
|
|
547
|
+
):
|
|
548
|
+
raise PipelineStateError(f"promotion destination already exists: {destination}")
|
|
549
|
+
carry_forward = state_checkpoint._tree_digest(destination)
|
|
473
550
|
tracks[name] = state_checkpoint._tree_digest(source)
|
|
474
551
|
if not tracks:
|
|
475
552
|
raise PipelineStateError("lookahead track has no promotable artifacts")
|
|
@@ -496,6 +573,7 @@ def _prepare_promotion(
|
|
|
496
573
|
"landing": landing,
|
|
497
574
|
"base": base,
|
|
498
575
|
"tracks": tracks,
|
|
576
|
+
"carry_forward_sha256": carry_forward,
|
|
499
577
|
"residual_sha256": state_checkpoint._tree_digest(next_root, tuple(tracks)),
|
|
500
578
|
"next_state_sha256": state_checkpoint._sha256(next_text),
|
|
501
579
|
"active_state_sha256": state_checkpoint._sha256(active_text),
|
|
@@ -548,6 +626,15 @@ def _resume_track_moves(project: Path, journal: dict[str, object], path: Path) -
|
|
|
548
626
|
destination = project / name
|
|
549
627
|
source_exists = source.exists() or source.is_symlink()
|
|
550
628
|
destination_exists = destination.exists() or destination.is_symlink()
|
|
629
|
+
carry_forward = journal.get("carry_forward_sha256")
|
|
630
|
+
if name == "research" and source_exists and destination_exists and carry_forward:
|
|
631
|
+
if state_checkpoint._tree_digest(destination) != carry_forward:
|
|
632
|
+
raise PipelineStateError("DOCS-AUDIT carry-forward drifted during promotion")
|
|
633
|
+
# The archive and the ship commit keep the carry-forward copy.
|
|
634
|
+
shutil.rmtree(destination)
|
|
635
|
+
journal["stage"] = "removed:carry-forward"
|
|
636
|
+
pipeline_state._write_json(path, journal)
|
|
637
|
+
destination_exists = False
|
|
551
638
|
if source_exists and destination_exists:
|
|
552
639
|
raise PipelineStateError(f"promotion has both source and destination: {name}")
|
|
553
640
|
if not source_exists and not destination_exists:
|
|
@@ -267,7 +267,11 @@ The persisted `STATE.archive` field is the transaction identity.
|
|
|
267
267
|
`python3 <absolute archive_milestone.py> close-members --repo <root>`
|
|
268
268
|
after preflight passes and before recording shipment. It closes each
|
|
269
269
|
locked member in lock order in that member's integration mode and
|
|
270
|
-
journals each one.
|
|
270
|
+
journals each one. Each member closes onto the default branch that
|
|
271
|
+
MEMBERS.md records for it (`main` when its section has no
|
|
272
|
+
`Default branch`); when the command reports
|
|
273
|
+
`remote default must be <recorded>`, stop and report it. When it returns
|
|
274
|
+
`awaiting-merge`, report the member PR
|
|
271
275
|
link, stop, and rerun the same command after the user merges it; never
|
|
272
276
|
merge a member PR yourself. When it returns `integrated`, keep its `body`
|
|
273
277
|
for the ship commit below.
|
|
@@ -310,8 +314,10 @@ The persisted `STATE.archive` field is the transaction identity.
|
|
|
310
314
|
milestone is not shipped until integration below passes.
|
|
311
315
|
7. Integrate only after the postcommit `validate` passes, with the recorded
|
|
312
316
|
ship commit and exact reviewed HEAD unchanged. Run `python3 <absolute archive_milestone.py> integrate --repo <root> --slug <STATE.milestone>`.
|
|
313
|
-
This helper validates the ship commit, fetches origin, requires remote
|
|
314
|
-
default `main
|
|
317
|
+
This helper validates the ship commit, fetches origin, requires the remote
|
|
318
|
+
default to equal STATE `default_branch` (`main` when the field is absent),
|
|
319
|
+
and follows the locked `STATE.integration` mode. `main` and `origin/main`
|
|
320
|
+
below mean that recorded branch:
|
|
315
321
|
|
|
316
322
|
- `direct` owns the resumable merge transaction. It creates the canonical
|
|
317
323
|
named integration worktree, performs the hook-verified `--no-ff` merge,
|
|
@@ -335,15 +341,36 @@ The persisted `STATE.archive` field is the transaction identity.
|
|
|
335
341
|
PR is open, the helper returns `status: awaiting-merge`. Present
|
|
336
342
|
**Outcome**, link
|
|
337
343
|
the returned PR as **Review**, and state in **Next** that the user must
|
|
338
|
-
merge it with GitHub's merge-commit method. Stop this ship
|
|
339
|
-
Path never enables auto-merge or merges the PR. On rerun after
|
|
340
|
-
GitHub user merges it, the helper requires the PR head to remain
|
|
341
|
-
commit and its landing to be
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
merge it with GitHub's merge-commit or rebase method. Stop this ship
|
|
345
|
+
invocation; Path never enables auto-merge or merges the PR. On rerun after
|
|
346
|
+
a human GitHub user merges it, the helper requires the PR head to remain
|
|
347
|
+
the ship commit and its landing to be on `origin/main` first-parent
|
|
348
|
+
history as either a two-parent merge with that ship commit as second
|
|
349
|
+
parent, or a rebase merge: the last of a first-parent run of single-parent
|
|
350
|
+
commits whose `git patch-id --stable` values match, in order, the
|
|
351
|
+
non-merge commits from the landing to the ship commit (with equal trees
|
|
352
|
+
when `main` did not move). It then writes and pushes the annotated
|
|
353
|
+
milestone tag containing the PR URL, ship SHA, and landing SHA. A
|
|
354
|
+
milestone that was rebase-merged before this support existed recovers by
|
|
355
|
+
rerunning ship. A closed unmerged PR, squash, merge queue, moved
|
|
345
356
|
head, duplicate PR, or non-GitHub.com origin blocks. The remote bound
|
|
346
357
|
branch may be absent after a valid merge because GitHub may auto-delete it.
|
|
358
|
+
- `external-landing` is for an origin where Path cannot open the review
|
|
359
|
+
itself, such as GitLab, Gitea, or GitHub Enterprise Server. It needs no
|
|
360
|
+
forge CLI. The helper publishes the bound branch at the exact ship commit,
|
|
361
|
+
never pushes `main`, and returns `status: awaiting-merge` with `branch`,
|
|
362
|
+
`merge_subject`, and `merge_body`. Present **Outcome**, and state in
|
|
363
|
+
**Next** that the user must open a merge request from `branch` to `main`
|
|
364
|
+
in their own forge and merge it with a merge commit. The merge commit
|
|
365
|
+
message must be exactly `merge_subject`, a blank line, then `merge_body`;
|
|
366
|
+
show both values verbatim in a code block. Stop this ship invocation;
|
|
367
|
+
Path never opens or merges the request. The forge may delete the source
|
|
368
|
+
branch after the merge. On rerun after the merge, the helper requires exactly
|
|
369
|
+
one commit with that subject on `origin/main` first-parent history, that
|
|
370
|
+
exact body, and two parents with the ship commit as second parent. It
|
|
371
|
+
then writes and pushes the annotated milestone tag containing the ship
|
|
372
|
+
SHA and landing SHA. A squash, rebase, fast-forward, or different merge
|
|
373
|
+
message blocks.
|
|
347
374
|
|
|
348
375
|
A passing run returns the same proof exposed by `validate-integrated`.
|
|
349
376
|
A non-zero result blocks; rerun the exact `integrate` command to resume a
|
|
@@ -354,7 +381,7 @@ The persisted `STATE.archive` field is the transaction identity.
|
|
|
354
381
|
continuing. Never merge into or re-ship the closed branch.
|
|
355
382
|
For a later validation recheck with origin network access, run
|
|
356
383
|
`python3 <absolute archive_milestone.py> validate-integrated --repo <root> --slug <STATE.milestone>`.
|
|
357
|
-
|
|
384
|
+
All integration modes refresh the remote-default and mirrored milestone-tag
|
|
358
385
|
refs when an `origin` remote exists. Without that remote, validation resolves
|
|
359
386
|
the existing remote-default refs locally, preserving offline direct-mode
|
|
360
387
|
fixtures. PR mode also revalidates the PR identity, state, merge provenance,
|
|
@@ -70,7 +70,9 @@ settings block; their default inheritance remains usable.
|
|
|
70
70
|
Resolve the bundled helper to an absolute path. Use one record under the Git
|
|
71
71
|
common directory's `gsd-path/model-selection/` directory per assignment. Scope
|
|
72
72
|
the path and `--scope` to the milestone, active/lookahead track, and review cycle
|
|
73
|
-
where applicable; later work must not reuse an earlier record.
|
|
73
|
+
where applicable; later work must not reuse an earlier record. The helper
|
|
74
|
+
refuses a relative `--record`, and `release` of a missing record names the
|
|
75
|
+
path and creates nothing.
|
|
74
76
|
|
|
75
77
|
```text
|
|
76
78
|
python3 -B <helper> resolve --repo <primary> --scope <milestone/track/cycle> --assignment <logical-name> --role <role> --record <absolute-record> --capabilities <advertised-json>
|
|
@@ -38,8 +38,8 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
38
38
|
the lookahead planning track for the next milestone; it follows the same
|
|
39
39
|
marker rule, owns no task frontmatter, and never binds a branch.
|
|
40
40
|
- STATE.integration_default is the project closeout choice and
|
|
41
|
-
STATE.integration is the current milestone choice. Both are `direct
|
|
42
|
-
`pull-request`; STATE.integration_source is `default` or `milestone` and
|
|
41
|
+
STATE.integration is the current milestone choice. Both are `direct`,
|
|
42
|
+
`pull-request`, or `external-landing`; STATE.integration_source is `default` or `milestone` and
|
|
43
43
|
preserves explicit override provenance. Older v2 state defaults to `direct`,
|
|
44
44
|
and modes may be changed only through `pipeline_state.py
|
|
45
45
|
configure-integration` before build. A next milestone resets its current
|
|
@@ -82,7 +82,9 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
82
82
|
`isolation.py attest` may stand in for it: one `.project/`-only commit that
|
|
83
83
|
names the base, the attested HEAD, the declared files that changed, the
|
|
84
84
|
task Verify recorded as passing at that HEAD, and the ruling verbatim.
|
|
85
|
-
`recover` reports it as `attested`, never as a proven landing.
|
|
85
|
+
`recover` reports it as `attested`, never as a proven landing. Attestation
|
|
86
|
+
matches the declared files literally: work that is only below a declared
|
|
87
|
+
directory can land but cannot be attested.
|
|
86
88
|
- All pipeline work for a milestone lives on `gsd-path/M00N` (M001, M002,
|
|
87
89
|
…) bound in STATE.branch. The next milestone binds a new unused
|
|
88
90
|
`gsd-path/M00N` at the remote default after the previous ship integrates.
|
|
@@ -90,6 +92,12 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
90
92
|
branch — either Path's direct merge or a Path-owned PR merged by the user.
|
|
91
93
|
The bound branch never receives merges or back-merges and is never the
|
|
92
94
|
GitHub default.
|
|
95
|
+
- The default branch is the one that STATE `default_branch` records at
|
|
96
|
+
initialization; a STATE file without the field means `main`. `main` and
|
|
97
|
+
`origin/main` in these rules mean that branch. Never change the record and
|
|
98
|
+
never take the name from a later `origin/HEAD` lookup. For a member
|
|
99
|
+
repository, the branch is the one that its `.project/MEMBERS.md` section
|
|
100
|
+
records as `Default branch`; a section without the field means `main`.
|
|
93
101
|
- After initialization writes STATE.md, and before entering the first pipeline
|
|
94
102
|
phase in an existing Git repository, the router calls
|
|
95
103
|
`scripts/pipeline_git.py bind-initial` with the selected M00N and exact
|
|
@@ -128,8 +136,9 @@ bundles this file; spawned agents follow their role brief instead.
|
|
|
128
136
|
for the current STATE.branch milestone must be an ancestor of origin/main;
|
|
129
137
|
if it is not, that milestone's integration is incomplete and control routes
|
|
130
138
|
to ship, not build. Direct mode requires the matching canonical `integrate:`
|
|
131
|
-
commit; PR mode requires tag metadata and a two-parent merge whose
|
|
132
|
-
parent is the ship commit
|
|
139
|
+
commit; PR mode requires tag metadata and either a two-parent merge whose
|
|
140
|
+
second parent is the ship commit or a proven rebase merge of the ship
|
|
141
|
+
commits. A null branch returns
|
|
133
142
|
to the router; build never creates, selects, switches, or rebinds it.
|
|
134
143
|
- The ship phase makes exactly one commit on the bound branch — the
|
|
135
144
|
`.project/`-only ship commit recording
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
## Final integration mode
|
|
2
2
|
|
|
3
3
|
Use only final-review.md. Treat INTENT.md success criteria as the rubric.
|
|
4
|
-
Each `### SCn —` heading must
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Each `### SCn —` heading must have every word of the criterion in INTENT.md
|
|
5
|
+
`## Success criteria`: join indented continuation lines and sub-bullets
|
|
6
|
+
with a single space. You can keep or remove the `- ` markers of the
|
|
7
|
+
sub-bullets; the gate compares every other word. Exercise the running
|
|
8
|
+
system and record `met`, `not-met`, or
|
|
9
9
|
`unverifiable` with checked command output or a precise file reference.
|
|
10
10
|
When PLAN.md carries
|
|
11
11
|
a `## Surface contract`, every criterion it lists under a surface is checked
|
|
@@ -17,19 +17,21 @@ tasks in this wave as the rubric. For each task:
|
|
|
17
17
|
criterion. Never use the primary worktree or branch tip as task
|
|
18
18
|
evidence. Do not create or remove Git worktrees yourself.
|
|
19
19
|
4. Mark `pass` or `fail`. Before the evidence separator (` — `), copy the
|
|
20
|
-
complete task acceptance or interface criterion
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
complete task acceptance or interface criterion (see Review file format
|
|
21
|
+
below). Do not substitute an AC id or summary: finding grouping binds
|
|
22
|
+
this text to the task. Give the observed result, `file:line`, and
|
|
23
23
|
concrete fix direction for each failure.
|
|
24
24
|
|
|
25
|
+
A Log entry `orchestrator regression Verify of <task id> retired: superseded
|
|
26
|
+
by <task id>` is not evidence: judge the superseding task's acceptance
|
|
27
|
+
criteria. The same applies to the entry `orchestrator regression Verify of
|
|
28
|
+
<fix task id>: part of <task id> retired: superseded by <task id>`.
|
|
29
|
+
|
|
25
30
|
Then check each INTENT.md success criterion owned by a task in this wave
|
|
26
31
|
against that same isolated product. In each `### SCn —` heading, copy the
|
|
27
|
-
criterion text
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
only whitespace is normalized at the gate. Do not paraphrase or drop
|
|
31
|
-
sub-bullets. Mark `pass` or `fail` with evidence; a failed owned SC blocks
|
|
32
|
-
the wave. Omit the Intent coverage section when no task in the wave owns
|
|
32
|
+
complete criterion text from INTENT.md `## Success criteria` (see Review
|
|
33
|
+
file format below). Mark `pass` or `fail` with evidence; a failed owned SC
|
|
34
|
+
blocks the wave. Omit the Intent coverage section when no task in the wave owns
|
|
33
35
|
an SC. When INTENT.md `## Edge coverage` or `## Prohibitions` has rows for
|
|
34
36
|
an owned SC, judge it against those rulings too: cite each `held-out` edge
|
|
35
37
|
id with its held-out test's recorded output and each `judgment` prohibition
|
|
@@ -48,6 +50,30 @@ For cycle greater than 1, read the previous review, re-check every failure,
|
|
|
48
50
|
and regression-check affected passes. State when the same criterion fails for
|
|
49
51
|
a different reason. Use only the wave-review template.
|
|
50
52
|
|
|
53
|
+
### Review file format
|
|
54
|
+
|
|
55
|
+
`scripts/check_handoffs.py wave` rejects a review file that breaks one of
|
|
56
|
+
these rules, and `scripts/review_findings.py` cannot group a failure whose
|
|
57
|
+
criterion text does not match. Apply the rules before you write the file.
|
|
58
|
+
|
|
59
|
+
- **Section order.** Put the `## T### —` sections in task id order (T001
|
|
60
|
+
before T002). The order of the PLAN.md wave table does not apply. A `pass`
|
|
61
|
+
review has one section for each task of the wave.
|
|
62
|
+
- **Section heading.** Write `## T### — <title>: pass` or `: fail`, where
|
|
63
|
+
`<title>` is the exact `title:` value of the task file.
|
|
64
|
+
- **Evidence lines.** An evidence line starts with `- ✅ ` for pass or
|
|
65
|
+
`- ❌ ` for fail. Each task section and each `### SCn —` block needs one
|
|
66
|
+
or more lines with the marker of its own verdict. A failed task needs a
|
|
67
|
+
`- ❌ ` criterion line, also when other text describes the failure.
|
|
68
|
+
- **Criterion text.** In a task evidence line and in a `### SCn —` heading,
|
|
69
|
+
copy every word of the criterion. Join wrapped lines and sub-bullets with
|
|
70
|
+
one space. You can keep or remove the `- ` markers of the sub-bullets.
|
|
71
|
+
A changed, added, removed or moved word does not match.
|
|
72
|
+
- **Angle brackets.** Text such as `<b>` or `<path>` outside backticks reads
|
|
73
|
+
as an unfilled template placeholder and is rejected. Write literal markup
|
|
74
|
+
and code in backticks inside a sentence, for example: the output has
|
|
75
|
+
`<b>hello</b>`.
|
|
76
|
+
|
|
51
77
|
### Re-review after a proven repair
|
|
52
78
|
|
|
53
79
|
When the brief supplies a `review_findings.py repair-evidence` receipt, use
|
|
@@ -55,14 +81,21 @@ its isolated repair product for the linked original criteria, including owned
|
|
|
55
81
|
INTENT criteria. The original faulty landing and earlier verdicts remain
|
|
56
82
|
historical evidence; they do not force the later verdict to remain failed.
|
|
57
83
|
Reconstruct the receipt's repair `base` plus only its `commit^..commit` patch
|
|
58
|
-
for `files` inside the supplied sidecar.
|
|
59
|
-
|
|
84
|
+
for `files` inside the supplied sidecar. For a member repair (the receipt
|
|
85
|
+
names `member`), use `member_base` and `member_commit` in that member's
|
|
86
|
+
repository. `earlier_repairs` are linked repairs of the same findings that
|
|
87
|
+
are already in the repair base. When the receipt has `later_repairs`, linked
|
|
88
|
+
repairs changed the same files after this repair: apply their patches in
|
|
89
|
+
order too. Read the repair task's recorded
|
|
90
|
+
Verify, or with `later_repairs` that of the last one, which is the receipt's
|
|
91
|
+
`verification`; inspect the patch against the original unchanged contract, and reuse
|
|
60
92
|
settled checks. Check unaffected criteria from their prior evidence plus the
|
|
61
93
|
repair diff for regressions. Cite the receipt, repair landing and task Log in
|
|
62
94
|
the new review. A receipt proves provenance, not acceptance: judge whether the
|
|
63
95
|
repair actually satisfies the criterion. A missing or rejected receipt blocks
|
|
64
|
-
using that repair; the parent resolves it
|
|
65
|
-
|
|
96
|
+
using that repair; the parent resolves it, or briefs this review in full at
|
|
97
|
+
the current HEAD without a receipt, rather than creating another product fix
|
|
98
|
+
for the unchanged historical failure.
|
|
66
99
|
|
|
67
100
|
## Final scope in a quick full wave
|
|
68
101
|
|
|
@@ -20,6 +20,16 @@ references to bundled copies of those helpers. Other skill helpers, templates,
|
|
|
20
20
|
and role briefs remain in the installed skill bundle. Pass `-B` to Python helper
|
|
21
21
|
invocations so execution does not create bytecode in the runtime store.
|
|
22
22
|
|
|
23
|
+
The selected runtime is authoritative for each helper in its `manifest.json`;
|
|
24
|
+
this includes `review_findings.py` and `task_context.py`. A selected runtime
|
|
25
|
+
that was published before a helper became a runtime file does not list that
|
|
26
|
+
helper: run the bundled copy until the project runtime is upgraded.
|
|
27
|
+
`dispatch_driver.py` and `workflow_run.py` always run from the installed skill
|
|
28
|
+
bundle, because they read role briefs and templates from it; a skill update
|
|
29
|
+
replaces them, a runtime upgrade does not. The driver reloads the helper modules
|
|
30
|
+
that it imports from the selected runtime, and runs the subprocess helpers that
|
|
31
|
+
the runtime lists (such as the fix-task brief lint) from it.
|
|
32
|
+
|
|
23
33
|
With no declaration, retain the legacy helper lookup. An existing
|
|
24
34
|
`.gsd-path/runtime/` requires the explicit `--runtime-migrate --project PATH`
|
|
25
35
|
operation before adopting external storage; review the resulting Git diff.
|
|
@@ -25,7 +25,8 @@ import signal
|
|
|
25
25
|
import subprocess
|
|
26
26
|
import time
|
|
27
27
|
from pathlib import Path
|
|
28
|
-
from typing import BinaryIO, Iterator, List, Optional, Sequence
|
|
28
|
+
from typing import BinaryIO, Iterable, Iterator, List, Optional, Sequence, Tuple
|
|
29
|
+
|
|
29
30
|
|
|
30
31
|
# Chosen by what imports, not os.name, so tests can emulate Windows on POSIX.
|
|
31
32
|
try:
|
|
@@ -161,6 +162,33 @@ def project_ignore_error(repo: Path) -> Optional[str]:
|
|
|
161
162
|
+ ". Anchor the product rule (for example `/build/`) in a task that owns it.")
|
|
162
163
|
|
|
163
164
|
|
|
165
|
+
_DEFAULT_BRANCH_RE = re.compile(
|
|
166
|
+
r"[^\s#\"'\\~^:?*\[\x00-\x1f\x7f-][^\s#\"'\\~^:?*\[\x00-\x1f\x7f]*"
|
|
167
|
+
)
|
|
168
|
+
_RESERVED_BRANCH_PREFIXES = ("gsd-path/", "gsd-path-integrate/", "gsd-path-task/")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def valid_default_branch(name: str) -> bool:
|
|
172
|
+
"""A git branch name that STATE frontmatter can hold and Path does not reserve."""
|
|
173
|
+
return (
|
|
174
|
+
bool(_DEFAULT_BRANCH_RE.fullmatch(name))
|
|
175
|
+
and name != "null"
|
|
176
|
+
and not name.startswith(_RESERVED_BRANCH_PREFIXES)
|
|
177
|
+
and not name.endswith(("/", ".", ".lock"))
|
|
178
|
+
and not any(part in name for part in ("..", "//", "/.", "@{"))
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def origin_default_branch(repo: Path) -> str:
|
|
183
|
+
"""The origin default branch that initialization records; an unset origin/HEAD means main."""
|
|
184
|
+
result = run_git(repo, "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD")
|
|
185
|
+
prefix = "refs/remotes/origin/"
|
|
186
|
+
remote_ref = result.stdout.strip()
|
|
187
|
+
if result.returncode != 0 or not remote_ref.startswith(prefix):
|
|
188
|
+
return "main"
|
|
189
|
+
return remote_ref.removeprefix(prefix)
|
|
190
|
+
|
|
191
|
+
|
|
164
192
|
def section_body(text: str, heading: str) -> Optional[str]:
|
|
165
193
|
"""The text under `## <heading>` up to the next `## `, or None when absent."""
|
|
166
194
|
match = re.search(
|
|
@@ -169,6 +197,79 @@ def section_body(text: str, heading: str) -> Optional[str]:
|
|
|
169
197
|
return match.group("body") if match else None
|
|
170
198
|
|
|
171
199
|
|
|
200
|
+
def overlapping_paths(left: Sequence[str], right: Sequence[str]) -> List[str]:
|
|
201
|
+
"""Return the sorted paths that two task `files` lists both cover.
|
|
202
|
+
|
|
203
|
+
An entry covers itself and the paths below it. For a declared directory
|
|
204
|
+
and a path below it, the result names the lower path. The callers have no
|
|
205
|
+
task base, so this does not ask if an entry is a file: the task-brief
|
|
206
|
+
lint rejects a path below a file at the base.
|
|
207
|
+
"""
|
|
208
|
+
return sorted(
|
|
209
|
+
{
|
|
210
|
+
max(one, other, key=len)
|
|
211
|
+
for one in left
|
|
212
|
+
for other in right
|
|
213
|
+
if one == other or one.startswith(other + "/") or other.startswith(one + "/")
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# The `- ` of an indented sub-bullet line. A line that would then read as a
|
|
219
|
+
# numbered item (`- 2. text`) keeps its marker, so both readings of a source
|
|
220
|
+
# have the same criteria.
|
|
221
|
+
SUB_BULLET_MARKER = re.compile(r"(?m)^([ \t]+)-[ \t]+(?![ \t]*\d+\.\s)")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def without_sub_bullet_markers(source: str) -> str:
|
|
225
|
+
"""`source` with the `- ` marker of each indented sub-bullet line removed."""
|
|
226
|
+
return SUB_BULLET_MARKER.sub(r"\1", source)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class Criterion(str):
|
|
230
|
+
"""A criterion as its parser joins it, with the `- ` markers of its sub-bullet lines.
|
|
231
|
+
|
|
232
|
+
`bare` is the same criterion parsed from `without_sub_bullet_markers(source)`.
|
|
233
|
+
The joined text cannot tell a marker from a minus sign; the source lines can.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
bare: str
|
|
237
|
+
|
|
238
|
+
def __new__(cls, text: str, bare: str) -> "Criterion":
|
|
239
|
+
criterion = super().__new__(cls, text)
|
|
240
|
+
criterion.bare = bare
|
|
241
|
+
return criterion
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def quotes_criterion(quote: str, criterion: str) -> bool:
|
|
245
|
+
"""Whether a review quote is `criterion`, for every reader.
|
|
246
|
+
|
|
247
|
+
Only whitespace is collapsed. The quote must equal the criterion with all
|
|
248
|
+
the markers of its sub-bullet lines or with none of them; every other
|
|
249
|
+
character, a hyphen inside a line too, is compared.
|
|
250
|
+
"""
|
|
251
|
+
forms = (criterion, getattr(criterion, "bare", criterion))
|
|
252
|
+
return " ".join(quote.split()) in {" ".join(form.split()) for form in forms}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def quoted_criterion(item: str, criteria: Sequence[str]) -> Optional[Tuple[int, int]]:
|
|
256
|
+
"""Index of the criterion quoted at the start of `item`, and where the quote ends.
|
|
257
|
+
|
|
258
|
+
A criterion can contain ` — ` itself, so each separator and the end of
|
|
259
|
+
`item` is a candidate, the longest quote first. A criterion as written
|
|
260
|
+
wins over any criterion read without its sub-bullet markers.
|
|
261
|
+
"""
|
|
262
|
+
ends = [match.start() for match in re.finditer("(?= —(?: |$))|$", item)][::-1]
|
|
263
|
+
for bare in (False, True):
|
|
264
|
+
for end in ends:
|
|
265
|
+
quote = " ".join(item[:end].split())
|
|
266
|
+
for index, criterion in enumerate(criteria):
|
|
267
|
+
form = getattr(criterion, "bare", criterion) if bare else criterion
|
|
268
|
+
if quote == " ".join(form.split()):
|
|
269
|
+
return index, end
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
172
273
|
def task_verify_command(task_text: str) -> str:
|
|
173
274
|
"""The Verify shell text, excluding the closing fence's separator newline."""
|
|
174
275
|
body = section_body(task_text, "Verify")
|
|
@@ -176,6 +277,11 @@ def task_verify_command(task_text: str) -> str:
|
|
|
176
277
|
return block.group("block").removesuffix("\n") if block else ""
|
|
177
278
|
|
|
178
279
|
|
|
280
|
+
def fix_verify(commands: Iterable[str]) -> str:
|
|
281
|
+
"""The Verify of a fix task: each distinct source Verify in its own subshell, under `set -e`."""
|
|
282
|
+
return "set -e\n" + "\n".join(f"(\n{command}\n)" for command in dict.fromkeys(commands))
|
|
283
|
+
|
|
284
|
+
|
|
179
285
|
def latest_verify_entry(entries: list, command: str, commit: str,
|
|
180
286
|
repo: Optional[str] = None) -> Optional[dict]:
|
|
181
287
|
"""Rows are keyed by (command, repo, commit); a row without `repo` is a coordinator row."""
|
|
@@ -255,6 +361,49 @@ def run_git(
|
|
|
255
361
|
return _run_exact(git_command(repo, *arguments), input=input)
|
|
256
362
|
|
|
257
363
|
|
|
364
|
+
def _patch_ids(repo: Path, commits: Sequence[str]) -> list[Optional[str]]:
|
|
365
|
+
"""`git patch-id --stable` per commit, in order; None for an empty commit."""
|
|
366
|
+
log = run_git(repo, "log", "--no-walk=unsorted", "-p", "--binary", "--no-color",
|
|
367
|
+
"--no-ext-diff", *commits)
|
|
368
|
+
if log.returncode != 0:
|
|
369
|
+
raise ValueError("could not read commit patches")
|
|
370
|
+
ids = run_git(repo, "patch-id", "--stable", input=log.stdout)
|
|
371
|
+
if ids.returncode != 0:
|
|
372
|
+
raise ValueError("could not compute patch ids")
|
|
373
|
+
by_commit = {commit: patch for patch, commit in (line.split() for line in ids.stdout.splitlines())}
|
|
374
|
+
return [by_commit.get(commit) for commit in commits]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def rebase_landing_problem(repo: Path, ship: str, landing: str) -> Optional[str]:
|
|
378
|
+
"""Why landing is not a forge rebase merge of ship, or None when it is (#418).
|
|
379
|
+
|
|
380
|
+
A rebase merge replays ship's own commits onto the default branch. Proof:
|
|
381
|
+
the first-parent run that ends at landing has one non-merge commit per
|
|
382
|
+
non-merge commit of landing..ship, with equal patch ids in the same order;
|
|
383
|
+
when that run starts at ship's fork point, landing's tree equals ship's.
|
|
384
|
+
"""
|
|
385
|
+
listed = run_git(repo, "rev-list", "--reverse", "--topo-order", "--no-merges", f"{landing}..{ship}")
|
|
386
|
+
if listed.returncode != 0:
|
|
387
|
+
return "could not list the ship commits"
|
|
388
|
+
originals = listed.stdout.split()
|
|
389
|
+
if not originals:
|
|
390
|
+
return "landing already contains the ship commit"
|
|
391
|
+
walked = run_git(repo, "rev-list", "--first-parent", "--parents", "-n", str(len(originals)), landing)
|
|
392
|
+
rows = [line.split() for line in walked.stdout.splitlines()][::-1]
|
|
393
|
+
if walked.returncode != 0 or len(rows) != len(originals) or any(len(row) != 2 for row in rows):
|
|
394
|
+
return "landing is not a linear run of rebased commits"
|
|
395
|
+
try:
|
|
396
|
+
if _patch_ids(repo, originals) != _patch_ids(repo, [row[0] for row in rows]):
|
|
397
|
+
return "rebased commits do not replay the ship commits in order"
|
|
398
|
+
except ValueError as error:
|
|
399
|
+
return str(error)
|
|
400
|
+
fork = run_git(repo, "merge-base", ship, landing).stdout.strip()
|
|
401
|
+
trees = run_git(repo, "rev-parse", f"{ship}^{{tree}}", f"{landing}^{{tree}}").stdout.split()
|
|
402
|
+
if rows[0][1] == fork and (len(trees) != 2 or trees[0] != trees[1]):
|
|
403
|
+
return "rebased tree differs from the ship tree"
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
|
|
258
407
|
def git_command(repo: Path, *arguments: str) -> tuple[str, ...]:
|
|
259
408
|
"""Git argv for repo, including WSL drvfs workarounds when needed."""
|
|
260
409
|
return ("git", "-C", str(repo), *git_drvfs_config_flags(repo), *arguments)
|