@opengsd/gsd-path 1.3.1 → 1.5.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/AGENTS.md +2 -207
- package/CHANGELOG.md +331 -69
- package/DOCS.md +25 -9
- package/FULL.md +12 -5
- package/GUIDE.md +1 -1
- package/HOOKS.md +104 -14
- package/QUICK.md +5 -2
- package/README.md +131 -111
- package/RUNTIME.md +2 -2
- package/UPDATE.md +61 -11
- package/WORKFLOW.md +44 -7
- package/package.json +13 -9
- package/platforms/shared-agents/dispatch.md +13 -0
- package/scripts/_common.py +619 -18
- package/scripts/archive_milestone.py +160 -86
- package/scripts/bootstrap_repository.py +8 -5
- package/scripts/build_recovery.py +100 -32
- package/scripts/build_state.py +143 -24
- package/scripts/check_docs_audit.py +91 -7
- package/scripts/check_handoffs.py +662 -50
- package/scripts/check_task_briefs.py +338 -30
- package/scripts/check_update.py +2 -2
- package/scripts/core_hook_gate.py +10 -1
- package/scripts/core_hook_settings.py +11 -4
- package/scripts/detect_project.py +587 -32
- package/scripts/discussion_records.py +19 -13
- package/scripts/discussion_validate.py +152 -50
- package/scripts/dispatch_driver.py +501 -46
- package/scripts/git_guard.py +118 -31
- package/scripts/guard_hook.py +1567 -194
- package/scripts/install.mjs +77 -46
- package/scripts/install.py +611 -50
- package/scripts/integration.py +580 -25
- package/scripts/isolation.py +1438 -57
- package/scripts/lean_verification.py +99 -19
- package/scripts/loop_run.py +31 -29
- package/scripts/members.py +706 -0
- package/scripts/migrate_core.py +2 -2
- package/scripts/path_config.py +5 -5
- package/scripts/pipeline_diagnose.py +82 -31
- package/scripts/pipeline_git.py +124 -34
- package/scripts/pipeline_state.py +287 -87
- package/scripts/pipeline_undo.py +218 -30
- package/scripts/promote_lookahead.py +1 -1
- package/scripts/review_panel.py +1 -1
- package/scripts/runtime_store.py +55 -11
- package/scripts/skill-resources.json +37 -5
- package/scripts/state_checkpoint.py +135 -44
- package/scripts/state_promote.py +2 -0
- package/scripts/status_runtime.py +58 -10
- package/scripts/sync_skill_resources.py +8 -7
- package/scripts/task_context.py +12 -3
- package/scripts/token_budget.py +12 -8
- package/scripts/wizard.mjs +1 -0
- package/scripts/workflow_run.py +107 -30
- package/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path/BUILD.md +7 -1
- package/skills/gsd-path/DECIDE.md +11 -4
- package/skills/gsd-path/DEFINE.md +36 -4
- package/skills/gsd-path/DOCS-AUDIT.md +6 -4
- package/skills/gsd-path/INSPECT.md +5 -4
- package/skills/gsd-path/PLAN.md +18 -4
- package/skills/gsd-path/RESEARCH.md +13 -1
- package/skills/gsd-path/ROADMAP.md +2 -1
- package/skills/gsd-path/SHIP.md +33 -11
- package/skills/gsd-path/SKILL.md +45 -1
- package/skills/gsd-path/references/build-native.md +63 -15
- package/skills/gsd-path/references/codebase-mapper.md +2 -1
- package/skills/gsd-path/references/dispatch.md +13 -0
- package/skills/gsd-path/references/docs-auditor.md +7 -1
- package/skills/gsd-path/references/operating-rules.md +214 -0
- package/skills/gsd-path/references/planner.md +22 -4
- package/skills/gsd-path/references/reviewer-final.md +20 -2
- package/skills/gsd-path/references/reviewer-wave.md +18 -5
- package/skills/gsd-path/references/reviewer.md +3 -1
- package/skills/gsd-path/references/spec-probes.md +150 -0
- package/skills/gsd-path/scripts/_common.py +619 -18
- package/skills/gsd-path/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
- package/skills/gsd-path/scripts/build_recovery.py +100 -32
- package/skills/gsd-path/scripts/build_state.py +143 -24
- package/skills/gsd-path/scripts/check_docs_audit.py +91 -7
- package/skills/gsd-path/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path/scripts/check_update.py +2 -2
- package/skills/gsd-path/scripts/detect_project.py +587 -32
- package/skills/gsd-path/scripts/discussion_records.py +19 -13
- package/skills/gsd-path/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path/scripts/dispatch_driver.py +501 -46
- package/skills/gsd-path/scripts/git_guard.py +118 -31
- package/skills/gsd-path/scripts/integration.py +580 -25
- package/skills/gsd-path/scripts/isolation.py +1438 -57
- package/skills/gsd-path/scripts/lean_verification.py +99 -19
- package/skills/gsd-path/scripts/loop_run.py +31 -29
- package/skills/gsd-path/scripts/members.py +706 -0
- package/skills/gsd-path/scripts/path_config.py +5 -5
- package/skills/gsd-path/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path/scripts/review_panel.py +1 -1
- package/skills/gsd-path/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path/scripts/state_promote.py +2 -0
- package/skills/gsd-path/scripts/status_runtime.py +58 -10
- package/skills/gsd-path/scripts/task_context.py +12 -3
- package/skills/gsd-path/scripts/token_budget.py +12 -8
- package/skills/gsd-path/scripts/workflow_run.py +107 -30
- package/skills/gsd-path/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path/templates/codebase.md +1 -1
- package/skills/gsd-path/templates/docs-audit.md +6 -2
- package/skills/gsd-path/templates/final-review.md +16 -4
- package/skills/gsd-path/templates/gap-review.md +2 -1
- package/skills/gsd-path/templates/intent.md +30 -0
- package/skills/gsd-path/templates/patch-findings.md +3 -3
- package/skills/gsd-path/templates/plan.md +17 -2
- package/skills/gsd-path/templates/research-handoff.md +14 -3
- package/skills/gsd-path/templates/state.md +1 -1
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +16 -4
- package/skills/gsd-path-build/SKILL.md +7 -1
- package/skills/gsd-path-build/references/build-native.md +63 -15
- package/skills/gsd-path-build/references/dispatch.md +13 -0
- package/skills/gsd-path-build/references/operating-rules.md +214 -0
- package/skills/gsd-path-build/references/reviewer-final.md +20 -2
- package/skills/gsd-path-build/references/reviewer-wave.md +18 -5
- package/skills/gsd-path-build/references/reviewer.md +3 -1
- package/skills/gsd-path-build/references/spec-probes.md +150 -0
- package/skills/gsd-path-build/scripts/_common.py +619 -18
- package/skills/gsd-path-build/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-build/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-build/scripts/build_state.py +143 -24
- package/skills/gsd-path-build/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-build/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-build/scripts/detect_project.py +587 -32
- package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-build/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-build/scripts/dispatch_driver.py +501 -46
- package/skills/gsd-path-build/scripts/integration.py +580 -25
- package/skills/gsd-path-build/scripts/isolation.py +1438 -57
- package/skills/gsd-path-build/scripts/loop_run.py +31 -29
- package/skills/gsd-path-build/scripts/members.py +706 -0
- package/skills/gsd-path-build/scripts/path_config.py +5 -5
- package/skills/gsd-path-build/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-build/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-build/scripts/review_panel.py +1 -1
- package/skills/gsd-path-build/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-build/scripts/state_promote.py +2 -0
- package/skills/gsd-path-build/scripts/task_context.py +12 -3
- package/skills/gsd-path-build/scripts/token_budget.py +12 -8
- package/skills/gsd-path-build/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-build/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +16 -4
- package/skills/gsd-path-decide/SKILL.md +11 -4
- package/skills/gsd-path-decide/references/dispatch.md +13 -0
- package/skills/gsd-path-decide/references/operating-rules.md +214 -0
- package/skills/gsd-path-decide/scripts/_common.py +619 -18
- package/skills/gsd-path-decide/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-decide/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-decide/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-decide/scripts/detect_project.py +587 -32
- package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-decide/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-decide/scripts/integration.py +580 -25
- package/skills/gsd-path-decide/scripts/isolation.py +1438 -57
- package/skills/gsd-path-decide/scripts/loop_run.py +31 -29
- package/skills/gsd-path-decide/scripts/members.py +706 -0
- package/skills/gsd-path-decide/scripts/path_config.py +5 -5
- package/skills/gsd-path-decide/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-decide/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
- package/skills/gsd-path-decide/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-define/SKILL.md +36 -4
- package/skills/gsd-path-define/references/operating-rules.md +214 -0
- package/skills/gsd-path-define/references/spec-probes.md +150 -0
- package/skills/gsd-path-define/scripts/_common.py +619 -18
- package/skills/gsd-path-define/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-define/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-define/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-define/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-define/scripts/detect_project.py +587 -32
- package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-define/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-define/scripts/integration.py +580 -25
- package/skills/gsd-path-define/scripts/isolation.py +1438 -57
- package/skills/gsd-path-define/scripts/loop_run.py +31 -29
- package/skills/gsd-path-define/scripts/members.py +706 -0
- package/skills/gsd-path-define/scripts/path_config.py +5 -5
- package/skills/gsd-path-define/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-define/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-define/scripts/review_panel.py +1 -1
- package/skills/gsd-path-define/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-define/scripts/state_promote.py +2 -0
- package/skills/gsd-path-define/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-define/templates/intent.md +30 -0
- package/skills/gsd-path-define/templates/state.md +1 -1
- package/skills/gsd-path-discuss/SKILL.md +2 -1
- package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
- package/skills/gsd-path-discuss/scripts/_common.py +619 -18
- package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-discuss/scripts/loop_run.py +31 -29
- package/skills/gsd-path-discuss/scripts/path_config.py +5 -5
- package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-docs-audit/SKILL.md +6 -4
- package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +7 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +619 -18
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +91 -7
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +587 -32
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-docs-audit/scripts/integration.py +580 -25
- package/skills/gsd-path-docs-audit/scripts/isolation.py +1438 -57
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +31 -29
- package/skills/gsd-path-docs-audit/scripts/members.py +706 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +5 -5
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-docs-audit/templates/docs-audit.md +6 -2
- package/skills/gsd-path-forensics/SKILL.md +2 -1
- package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
- package/skills/gsd-path-forensics/scripts/_common.py +619 -18
- package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-forensics/scripts/loop_run.py +31 -29
- package/skills/gsd-path-forensics/scripts/path_config.py +5 -5
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +218 -30
- package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-inspect/SKILL.md +5 -4
- package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
- package/skills/gsd-path-inspect/references/dispatch.md +13 -0
- package/skills/gsd-path-inspect/references/docs-auditor.md +7 -1
- package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
- package/skills/gsd-path-inspect/scripts/_common.py +619 -18
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-inspect/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +91 -7
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-inspect/scripts/detect_project.py +587 -32
- package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-inspect/scripts/integration.py +580 -25
- package/skills/gsd-path-inspect/scripts/isolation.py +1438 -57
- package/skills/gsd-path-inspect/scripts/loop_run.py +31 -29
- package/skills/gsd-path-inspect/scripts/members.py +706 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +5 -5
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
- package/skills/gsd-path-inspect/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-inspect/templates/codebase.md +1 -1
- package/skills/gsd-path-inspect/templates/docs-audit.md +6 -2
- package/skills/gsd-path-inspect/templates/state.md +1 -1
- package/skills/gsd-path-loop/SKILL.md +2 -1
- package/skills/gsd-path-loop/references/operating-rules.md +214 -0
- package/skills/gsd-path-loop/scripts/_common.py +619 -18
- package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-loop/scripts/loop_run.py +31 -29
- package/skills/gsd-path-loop/scripts/path_config.py +5 -5
- package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
- package/skills/gsd-path-loop/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-migrate/SKILL.md +2 -1
- package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
- package/skills/gsd-path-migrate/scripts/_common.py +619 -18
- package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
- package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
- package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-migrate/scripts/loop_run.py +31 -29
- package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
- package/skills/gsd-path-migrate/scripts/path_config.py +5 -5
- package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/SKILL.md +18 -4
- package/skills/gsd-path-plan/references/dispatch.md +13 -0
- package/skills/gsd-path-plan/references/operating-rules.md +214 -0
- package/skills/gsd-path-plan/references/planner.md +22 -4
- package/skills/gsd-path-plan/references/spec-probes.md +150 -0
- package/skills/gsd-path-plan/scripts/_common.py +619 -18
- package/skills/gsd-path-plan/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-plan/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-plan/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-plan/scripts/detect_project.py +587 -32
- package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-plan/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-plan/scripts/integration.py +580 -25
- package/skills/gsd-path-plan/scripts/isolation.py +1438 -57
- package/skills/gsd-path-plan/scripts/loop_run.py +31 -29
- package/skills/gsd-path-plan/scripts/members.py +706 -0
- package/skills/gsd-path-plan/scripts/path_config.py +5 -5
- package/skills/gsd-path-plan/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-plan/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
- package/skills/gsd-path-plan/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-plan/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
- package/skills/gsd-path-plan/templates/plan.md +17 -2
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/SKILL.md +13 -1
- package/skills/gsd-path-research/references/dispatch.md +13 -0
- package/skills/gsd-path-research/references/operating-rules.md +214 -0
- package/skills/gsd-path-research/scripts/_common.py +619 -18
- package/skills/gsd-path-research/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-research/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-research/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-research/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-research/scripts/detect_project.py +587 -32
- package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-research/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-research/scripts/integration.py +580 -25
- package/skills/gsd-path-research/scripts/isolation.py +1438 -57
- package/skills/gsd-path-research/scripts/loop_run.py +31 -29
- package/skills/gsd-path-research/scripts/members.py +706 -0
- package/skills/gsd-path-research/scripts/path_config.py +5 -5
- package/skills/gsd-path-research/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-research/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-research/scripts/review_panel.py +1 -1
- package/skills/gsd-path-research/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-research/scripts/state_promote.py +2 -0
- package/skills/gsd-path-research/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-research/templates/research-handoff.md +14 -3
- package/skills/gsd-path-roadmap/SKILL.md +2 -1
- package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
- package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +619 -18
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-roadmap/scripts/detect_project.py +587 -32
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-roadmap/scripts/integration.py +580 -25
- package/skills/gsd-path-roadmap/scripts/isolation.py +1438 -57
- package/skills/gsd-path-roadmap/scripts/loop_run.py +31 -29
- package/skills/gsd-path-roadmap/scripts/members.py +706 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +5 -5
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-ship/SKILL.md +33 -11
- package/skills/gsd-path-ship/references/dispatch.md +13 -0
- package/skills/gsd-path-ship/references/operating-rules.md +214 -0
- package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
- package/skills/gsd-path-ship/references/reviewer-wave.md +18 -5
- package/skills/gsd-path-ship/references/reviewer.md +3 -1
- package/skills/gsd-path-ship/references/spec-probes.md +150 -0
- package/skills/gsd-path-ship/scripts/_common.py +619 -18
- package/skills/gsd-path-ship/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-ship/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-ship/scripts/build_state.py +143 -24
- package/skills/gsd-path-ship/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-ship/scripts/detect_project.py +587 -32
- package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-ship/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-ship/scripts/integration.py +580 -25
- package/skills/gsd-path-ship/scripts/isolation.py +1438 -57
- package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
- package/skills/gsd-path-ship/scripts/loop_run.py +31 -29
- package/skills/gsd-path-ship/scripts/members.py +706 -0
- package/skills/gsd-path-ship/scripts/path_config.py +5 -5
- package/skills/gsd-path-ship/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-ship/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
- package/skills/gsd-path-ship/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-ship/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-ship/templates/final-review.md +16 -4
- package/skills/gsd-path-ship/templates/gap-review.md +2 -1
- package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
- package/skills/gsd-path-ship/templates/wave-review.md +16 -4
- package/skills/gsd-path-undo/SKILL.md +2 -1
- package/skills/gsd-path-undo/references/operating-rules.md +214 -0
- package/skills/gsd-path-undo/scripts/_common.py +619 -18
- package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-undo/scripts/loop_run.py +31 -29
- package/skills/gsd-path-undo/scripts/path_config.py +5 -5
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +218 -30
- package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
- package/skills/gsd-path-undo/scripts/worktree_paths.py +72 -10
- package/skills/path/BUILD.md +7 -1
- package/skills/path/DECIDE.md +11 -4
- package/skills/path/DEFINE.md +36 -4
- package/skills/path/DOCS-AUDIT.md +6 -4
- package/skills/path/INSPECT.md +5 -4
- package/skills/path/PLAN.md +18 -4
- package/skills/path/RESEARCH.md +13 -1
- package/skills/path/ROADMAP.md +2 -1
- package/skills/path/SHIP.md +33 -11
- package/skills/path/SKILL.md +45 -1
- package/skills/path/references/build-native.md +63 -15
- package/skills/path/references/codebase-mapper.md +2 -1
- package/skills/path/references/dispatch.md +13 -0
- package/skills/path/references/docs-auditor.md +7 -1
- package/skills/path/references/operating-rules.md +214 -0
- package/skills/path/references/planner.md +22 -4
- package/skills/path/references/reviewer-final.md +20 -2
- package/skills/path/references/reviewer-wave.md +18 -5
- package/skills/path/references/reviewer.md +3 -1
- package/skills/path/references/spec-probes.md +150 -0
- package/skills/path/scripts/_common.py +619 -18
- package/skills/path/scripts/archive_milestone.py +160 -86
- package/skills/path/scripts/bootstrap_repository.py +8 -5
- package/skills/path/scripts/build_recovery.py +100 -32
- package/skills/path/scripts/build_state.py +143 -24
- package/skills/path/scripts/check_docs_audit.py +91 -7
- package/skills/path/scripts/check_handoffs.py +662 -50
- package/skills/path/scripts/check_task_briefs.py +338 -30
- package/skills/path/scripts/check_update.py +2 -2
- package/skills/path/scripts/detect_project.py +587 -32
- package/skills/path/scripts/discussion_records.py +19 -13
- package/skills/path/scripts/discussion_validate.py +152 -50
- package/skills/path/scripts/dispatch_driver.py +501 -46
- package/skills/path/scripts/git_guard.py +118 -31
- package/skills/path/scripts/integration.py +580 -25
- package/skills/path/scripts/isolation.py +1438 -57
- package/skills/path/scripts/lean_verification.py +99 -19
- package/skills/path/scripts/loop_run.py +31 -29
- package/skills/path/scripts/members.py +706 -0
- package/skills/path/scripts/path_config.py +5 -5
- package/skills/path/scripts/pipeline_git.py +124 -34
- package/skills/path/scripts/pipeline_state.py +287 -87
- package/skills/path/scripts/promote_lookahead.py +1 -1
- package/skills/path/scripts/review_panel.py +1 -1
- package/skills/path/scripts/state_checkpoint.py +135 -44
- package/skills/path/scripts/state_promote.py +2 -0
- package/skills/path/scripts/status_runtime.py +58 -10
- package/skills/path/scripts/task_context.py +12 -3
- package/skills/path/scripts/token_budget.py +12 -8
- package/skills/path/scripts/workflow_run.py +107 -30
- package/skills/path/scripts/worktree_paths.py +72 -10
- package/skills/path/templates/codebase.md +1 -1
- package/skills/path/templates/docs-audit.md +6 -2
- package/skills/path/templates/final-review.md +16 -4
- package/skills/path/templates/gap-review.md +2 -1
- package/skills/path/templates/intent.md +30 -0
- package/skills/path/templates/patch-findings.md +3 -3
- package/skills/path/templates/plan.md +17 -2
- package/skills/path/templates/research-handoff.md +14 -3
- package/skills/path/templates/state.md +1 -1
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +16 -4
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
|
@@ -27,25 +27,35 @@ from datetime import date
|
|
|
27
27
|
from pathlib import Path, PurePosixPath
|
|
28
28
|
from typing import Iterator, Mapping, Optional, Sequence
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
from isolation import (
|
|
30
|
+
if __package__:
|
|
31
|
+
from .isolation import (
|
|
32
32
|
IsolationError,
|
|
33
33
|
authorized_task_worktree,
|
|
34
34
|
checkpoint as isolation_checkpoint,
|
|
35
35
|
collect_artifact_recoveries,
|
|
36
36
|
)
|
|
37
|
-
import _common
|
|
38
|
-
import roadmap
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
from . import _common
|
|
38
|
+
from . import roadmap
|
|
39
|
+
else:
|
|
40
|
+
try:
|
|
41
|
+
from isolation import (
|
|
42
|
+
IsolationError,
|
|
43
|
+
authorized_task_worktree,
|
|
44
|
+
checkpoint as isolation_checkpoint,
|
|
45
|
+
collect_artifact_recoveries,
|
|
46
|
+
)
|
|
47
|
+
import _common
|
|
48
|
+
import roadmap
|
|
49
|
+
except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
|
|
50
|
+
if error.name not in {"isolation", "_common", "roadmap"}:
|
|
51
|
+
raise
|
|
52
|
+
from scripts.isolation import (
|
|
53
|
+
IsolationError,
|
|
54
|
+
authorized_task_worktree,
|
|
55
|
+
checkpoint as isolation_checkpoint,
|
|
56
|
+
collect_artifact_recoveries,
|
|
57
|
+
)
|
|
58
|
+
from scripts import _common, roadmap
|
|
49
59
|
|
|
50
60
|
try: # pragma: no cover - exercised only on Windows
|
|
51
61
|
import fcntl
|
|
@@ -66,6 +76,8 @@ PHASES = (
|
|
|
66
76
|
"shipped",
|
|
67
77
|
)
|
|
68
78
|
STATUSES = ("active", "done", "blocked")
|
|
79
|
+
PRE_APPROVAL_KINDS = ("intent", "plan")
|
|
80
|
+
PRE_APPROVAL_MARKER = "pre-approval: "
|
|
69
81
|
STATE_FIELDS = (
|
|
70
82
|
"pipeline",
|
|
71
83
|
"project",
|
|
@@ -79,7 +91,7 @@ STATE_FIELDS = (
|
|
|
79
91
|
"integration_source",
|
|
80
92
|
)
|
|
81
93
|
LEGACY_STATE_FIELDS = STATE_FIELDS[:7]
|
|
82
|
-
INTEGRATION_MODES = ("direct", "pull-request")
|
|
94
|
+
INTEGRATION_MODES = ("direct", "pull-request", "external-landing")
|
|
83
95
|
INTEGRATION_SOURCES = ("default", "milestone")
|
|
84
96
|
NULL = "null"
|
|
85
97
|
SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
|
|
@@ -129,6 +141,8 @@ CROSS_PHASE_TRANSITIONS = {
|
|
|
129
141
|
("decide", "done", "plan", "active"),
|
|
130
142
|
("roadmap", "done", "define", "active"),
|
|
131
143
|
("plan", "done", "build", "active"),
|
|
144
|
+
("plan", "active", "define", "active"),
|
|
145
|
+
("plan", "blocked", "define", "active"),
|
|
132
146
|
("build", "blocked", "define", "active"),
|
|
133
147
|
("build", "blocked", "plan", "active"),
|
|
134
148
|
("build", "active", "ship", "active"),
|
|
@@ -181,11 +195,24 @@ def _run_git(
|
|
|
181
195
|
*arguments: str,
|
|
182
196
|
check: bool = True,
|
|
183
197
|
) -> subprocess.CompletedProcess[str]:
|
|
198
|
+
command = _common.git_command(repo, *arguments)
|
|
184
199
|
result = subprocess.run(
|
|
185
|
-
|
|
200
|
+
command,
|
|
186
201
|
capture_output=True,
|
|
187
|
-
|
|
202
|
+
encoding="utf-8", errors="replace",
|
|
188
203
|
)
|
|
204
|
+
if (
|
|
205
|
+
check
|
|
206
|
+
and result.returncode != 0
|
|
207
|
+
and arguments
|
|
208
|
+
and arguments[0] == "commit"
|
|
209
|
+
and _common.git_index_lock_retryable(result.stderr)
|
|
210
|
+
):
|
|
211
|
+
result = subprocess.run(
|
|
212
|
+
command,
|
|
213
|
+
capture_output=True,
|
|
214
|
+
encoding="utf-8", errors="replace",
|
|
215
|
+
)
|
|
189
216
|
if check and result.returncode != 0:
|
|
190
217
|
detail = result.stderr.strip() or result.stdout.strip()
|
|
191
218
|
raise PipelineStateError(f"git {' '.join(arguments)} failed: {detail}")
|
|
@@ -410,14 +437,17 @@ def undo_transaction(repo: Path) -> Optional[dict[str, object]]:
|
|
|
410
437
|
"archive_fingerprint",
|
|
411
438
|
"discussion",
|
|
412
439
|
}
|
|
440
|
+
if value.get("kind") == "member-task":
|
|
441
|
+
# A member landing undo also names the member reset it performs first.
|
|
442
|
+
expected |= {"member", "landing", "member_parent"}
|
|
413
443
|
if set(value) != expected or value.get("schema") != UNDO_TRANSACTION_SCHEMA:
|
|
414
444
|
raise PipelineStateError("undo transaction has invalid fields")
|
|
415
445
|
if value.get("repo") != str(resolved):
|
|
416
446
|
raise PipelineStateError("undo transaction belongs to another worktree")
|
|
417
447
|
kind = value.get("kind")
|
|
418
|
-
if kind not in {"checkpoint", "uncommitted-archive"}:
|
|
448
|
+
if kind not in {"checkpoint", "uncommitted-archive", "member-task"}:
|
|
419
449
|
raise PipelineStateError("undo transaction has invalid kind")
|
|
420
|
-
for field in ("expected_head", "parent"):
|
|
450
|
+
for field in ("expected_head", "parent") + (("landing", "member_parent") if kind == "member-task" else ()):
|
|
421
451
|
field_value = value.get(field)
|
|
422
452
|
if not isinstance(field_value, str) or not re.fullmatch(
|
|
423
453
|
r"[0-9a-f]{40,64}", field_value
|
|
@@ -433,10 +463,12 @@ def undo_transaction(repo: Path) -> Optional[dict[str, object]]:
|
|
|
433
463
|
raise PipelineStateError("undo transaction has invalid worktree fingerprint")
|
|
434
464
|
archive = value.get("archive")
|
|
435
465
|
archive_fingerprint = value.get("archive_fingerprint")
|
|
436
|
-
if kind
|
|
466
|
+
if kind in {"checkpoint", "member-task"} and (
|
|
437
467
|
archive is not None or archive_fingerprint is not None
|
|
438
468
|
):
|
|
439
|
-
raise PipelineStateError("
|
|
469
|
+
raise PipelineStateError(f"{kind} undo transaction cannot name an archive")
|
|
470
|
+
if kind == "member-task" and not isinstance(value.get("member"), str):
|
|
471
|
+
raise PipelineStateError("member-task undo transaction has invalid member")
|
|
440
472
|
if kind == "uncommitted-archive" and (
|
|
441
473
|
not isinstance(archive, str) or ARCHIVE_RE.fullmatch(archive) is None
|
|
442
474
|
):
|
|
@@ -563,12 +595,15 @@ def _pending_answers(repo: Path) -> tuple[list[dict[str, str]], Optional[str]]:
|
|
|
563
595
|
if not answers.exists() and not answers.is_symlink():
|
|
564
596
|
return [], None
|
|
565
597
|
try:
|
|
566
|
-
|
|
567
|
-
from discussion_records import DiscussionError, pending_records
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
598
|
+
if __package__:
|
|
599
|
+
from .discussion_records import DiscussionError, pending_records
|
|
600
|
+
else:
|
|
601
|
+
try:
|
|
602
|
+
from discussion_records import DiscussionError, pending_records
|
|
603
|
+
except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
|
|
604
|
+
if error.name != "discussion_records":
|
|
605
|
+
raise
|
|
606
|
+
from scripts.discussion_records import DiscussionError, pending_records
|
|
572
607
|
except ModuleNotFoundError as error:
|
|
573
608
|
if error.name not in {"scripts", "scripts.discussion_records"}:
|
|
574
609
|
raise
|
|
@@ -624,6 +659,29 @@ def _completion_status(repo: Path, state: Mapping[str, object], project_dir: str
|
|
|
624
659
|
return {"status": "verified", "proof": proof}
|
|
625
660
|
|
|
626
661
|
|
|
662
|
+
def _member_status(repo: Path, state: Mapping[str, object]) -> Optional[list]:
|
|
663
|
+
"""Locked members and their ship close, when this milestone has a member lock."""
|
|
664
|
+
if __package__:
|
|
665
|
+
from scripts import members
|
|
666
|
+
else:
|
|
667
|
+
import members
|
|
668
|
+
archive = state.get("archive")
|
|
669
|
+
locks = [repo / ".project" / "build" / "members.json"]
|
|
670
|
+
if isinstance(archive, str) and archive not in {"", "null"}:
|
|
671
|
+
locks.insert(0, repo / archive.rstrip("/") / "build" / "members.json")
|
|
672
|
+
lock = next((path for path in locks if path.is_file()), None)
|
|
673
|
+
if lock is None:
|
|
674
|
+
return None
|
|
675
|
+
closed = {}
|
|
676
|
+
match = re.fullmatch(r"gsd-path/M(\d{3,})", str(state.get("branch") or ""))
|
|
677
|
+
if match is not None:
|
|
678
|
+
common = Path(_run_git(repo, "rev-parse", "--path-format=absolute", "--git-common-dir").stdout.strip())
|
|
679
|
+
closed = {row["name"]: row.get("status") for row in members.milestone_member_close(common, int(match.group(1)))}
|
|
680
|
+
entries = json.loads(lock.read_text(encoding="utf-8"))["members"]
|
|
681
|
+
return [{"name": entry["name"], "branch": entry["branch"], "close": closed.get(entry["name"])}
|
|
682
|
+
for entry in entries]
|
|
683
|
+
|
|
684
|
+
|
|
627
685
|
def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]:
|
|
628
686
|
"""Report owned state, route, and git facts without advancing a phase."""
|
|
629
687
|
resolved = _repo_root(repo)
|
|
@@ -658,7 +716,8 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
658
716
|
"collect_artifact": None,
|
|
659
717
|
}
|
|
660
718
|
completion = _completion_status(resolved, state, project_dir)
|
|
661
|
-
|
|
719
|
+
member_rows = _member_status(resolved, state) if has_git and project_dir == ".project" else None
|
|
720
|
+
result = {
|
|
662
721
|
"schema": STATUS_SCHEMA,
|
|
663
722
|
"advance": False,
|
|
664
723
|
"completion": completion,
|
|
@@ -688,6 +747,9 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
688
747
|
"next_skill": _next_skill(route if isinstance(route, dict) else {}),
|
|
689
748
|
"handoff": phase_handoff(state, route, _track_root(resolved, project_dir) / "STATE.md", completion),
|
|
690
749
|
}
|
|
750
|
+
if member_rows is not None:
|
|
751
|
+
result["members"] = member_rows
|
|
752
|
+
return result
|
|
691
753
|
|
|
692
754
|
|
|
693
755
|
def phase_handoff(state: Mapping[str, object], route: Mapping[str, object], path: Path,
|
|
@@ -759,7 +821,7 @@ def _valid_patch_findings(repo: Path) -> bool:
|
|
|
759
821
|
str(repo),
|
|
760
822
|
],
|
|
761
823
|
capture_output=True,
|
|
762
|
-
|
|
824
|
+
encoding="utf-8", errors="replace",
|
|
763
825
|
)
|
|
764
826
|
return result.returncode == 0
|
|
765
827
|
|
|
@@ -791,6 +853,8 @@ def _selected_initial_branch(project: Path, state: PipelineState) -> str:
|
|
|
791
853
|
if archive.is_symlink() or not archive.is_dir():
|
|
792
854
|
raise PipelineStateError(f"archive root must be a real directory: {archive}")
|
|
793
855
|
for child in archive.iterdir():
|
|
856
|
+
if _common.is_ignored_junk(child):
|
|
857
|
+
continue
|
|
794
858
|
match = re.fullmatch(r"(\d{3,})-[a-z0-9][a-z0-9-]*", child.name)
|
|
795
859
|
if (
|
|
796
860
|
child.is_symlink()
|
|
@@ -862,36 +926,13 @@ def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
|
|
|
862
926
|
return landing
|
|
863
927
|
|
|
864
928
|
|
|
865
|
-
def
|
|
929
|
+
def _bind_next_journal_recovery(
|
|
866
930
|
repo: Path,
|
|
867
931
|
state: PipelineState,
|
|
932
|
+
path: Path,
|
|
933
|
+
transaction: dict[str, object],
|
|
934
|
+
current: Optional[str],
|
|
868
935
|
) -> Optional[dict[str, object]]:
|
|
869
|
-
journal_root = _git_path(repo, BIND_NEXT_JOURNAL_DIR)
|
|
870
|
-
if not journal_root.exists() and not journal_root.is_symlink():
|
|
871
|
-
return None
|
|
872
|
-
if journal_root.is_symlink() or not journal_root.is_dir():
|
|
873
|
-
raise PipelineStateError(f"bind-next journal root must be a real directory: {journal_root}")
|
|
874
|
-
|
|
875
|
-
matches: list[tuple[Path, dict[str, object]]] = []
|
|
876
|
-
for path in sorted(journal_root.glob("*.json")):
|
|
877
|
-
transaction = _read_json(path)
|
|
878
|
-
if transaction.get("schema") not in {
|
|
879
|
-
LEGACY_BIND_NEXT_JOURNAL_SCHEMA,
|
|
880
|
-
BIND_NEXT_JOURNAL_SCHEMA,
|
|
881
|
-
}:
|
|
882
|
-
raise PipelineStateError(f"bind-next journal has invalid schema: {path}")
|
|
883
|
-
if transaction.get("repo") != str(repo):
|
|
884
|
-
raise PipelineStateError(f"bind-next journal belongs to another worktree: {path}")
|
|
885
|
-
if transaction.get("previous_branch") == state.branch:
|
|
886
|
-
matches.append((path, transaction))
|
|
887
|
-
if not matches:
|
|
888
|
-
return None
|
|
889
|
-
if len(matches) != 1:
|
|
890
|
-
raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
|
|
891
|
-
if state.phase != "shipped" or state.status != "done":
|
|
892
|
-
raise PipelineStateError("bind-next journal requires STATE shipped/done")
|
|
893
|
-
|
|
894
|
-
path, transaction = matches[0]
|
|
895
936
|
required = (
|
|
896
937
|
"branch",
|
|
897
938
|
"previous_branch",
|
|
@@ -973,7 +1014,32 @@ def _bind_next_recovery(
|
|
|
973
1014
|
if stage not in {"prepared", "switched", "retired"}:
|
|
974
1015
|
raise PipelineStateError("bind-next journal has invalid stage")
|
|
975
1016
|
|
|
976
|
-
|
|
1017
|
+
stale_retired = False
|
|
1018
|
+
if stage == "retired":
|
|
1019
|
+
target_ref = _run_git(
|
|
1020
|
+
repo,
|
|
1021
|
+
"show-ref",
|
|
1022
|
+
"--verify",
|
|
1023
|
+
"--quiet",
|
|
1024
|
+
f"refs/heads/{branch}",
|
|
1025
|
+
check=False,
|
|
1026
|
+
)
|
|
1027
|
+
if target_ref.returncode not in {0, 1}:
|
|
1028
|
+
detail = (target_ref.stderr or target_ref.stdout).strip()
|
|
1029
|
+
raise PipelineStateError(
|
|
1030
|
+
f"could not check bind-next target branch {branch}: {detail}"
|
|
1031
|
+
)
|
|
1032
|
+
target_exists = target_ref.returncode == 0
|
|
1033
|
+
stale_retired = current not in {previous, branch} or not target_exists
|
|
1034
|
+
if stale_retired:
|
|
1035
|
+
# If the completed target is gone while the previous branch is checked
|
|
1036
|
+
# out, still prove that branch has not moved away from the recorded ship.
|
|
1037
|
+
if current == previous:
|
|
1038
|
+
head = _run_git(repo, "rev-parse", "HEAD").stdout.strip()
|
|
1039
|
+
if head != ship:
|
|
1040
|
+
raise PipelineStateError("bind-next previous branch is not at journal ship")
|
|
1041
|
+
return None
|
|
1042
|
+
|
|
977
1043
|
if current not in {previous, branch}:
|
|
978
1044
|
raise PipelineStateError("bind-next journal does not own the current branch")
|
|
979
1045
|
if current == previous and stage != "prepared":
|
|
@@ -1008,6 +1074,52 @@ def _bind_next_recovery(
|
|
|
1008
1074
|
return result
|
|
1009
1075
|
|
|
1010
1076
|
|
|
1077
|
+
def _bind_next_recovery(
|
|
1078
|
+
repo: Path,
|
|
1079
|
+
state: PipelineState,
|
|
1080
|
+
) -> Optional[dict[str, object]]:
|
|
1081
|
+
journal_root = _git_path(repo, BIND_NEXT_JOURNAL_DIR)
|
|
1082
|
+
if not journal_root.exists() and not journal_root.is_symlink():
|
|
1083
|
+
return None
|
|
1084
|
+
if journal_root.is_symlink() or not journal_root.is_dir():
|
|
1085
|
+
raise PipelineStateError(f"bind-next journal root must be a real directory: {journal_root}")
|
|
1086
|
+
|
|
1087
|
+
matches: list[tuple[Path, dict[str, object]]] = []
|
|
1088
|
+
for path in sorted(journal_root.glob("*.json")):
|
|
1089
|
+
transaction = _read_json(path)
|
|
1090
|
+
if transaction.get("schema") not in {
|
|
1091
|
+
LEGACY_BIND_NEXT_JOURNAL_SCHEMA,
|
|
1092
|
+
BIND_NEXT_JOURNAL_SCHEMA,
|
|
1093
|
+
}:
|
|
1094
|
+
raise PipelineStateError(f"bind-next journal has invalid schema: {path}")
|
|
1095
|
+
if transaction.get("repo") != str(repo):
|
|
1096
|
+
raise PipelineStateError(f"bind-next journal belongs to another worktree: {path}")
|
|
1097
|
+
if transaction.get("previous_branch") == state.branch:
|
|
1098
|
+
matches.append((path, transaction))
|
|
1099
|
+
if not matches:
|
|
1100
|
+
return None
|
|
1101
|
+
if state.phase != "shipped" or state.status != "done":
|
|
1102
|
+
if len(matches) != 1:
|
|
1103
|
+
raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
|
|
1104
|
+
raise PipelineStateError("bind-next journal requires STATE shipped/done")
|
|
1105
|
+
|
|
1106
|
+
current = _current_branch(repo)
|
|
1107
|
+
recoveries: list[dict[str, object]] = []
|
|
1108
|
+
for path, transaction in matches:
|
|
1109
|
+
recovery = _bind_next_journal_recovery(
|
|
1110
|
+
repo,
|
|
1111
|
+
state,
|
|
1112
|
+
path,
|
|
1113
|
+
transaction,
|
|
1114
|
+
current,
|
|
1115
|
+
)
|
|
1116
|
+
if recovery is not None:
|
|
1117
|
+
recoveries.append(recovery)
|
|
1118
|
+
if len(recoveries) > 1:
|
|
1119
|
+
raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
|
|
1120
|
+
return recoveries[0] if recoveries else None
|
|
1121
|
+
|
|
1122
|
+
|
|
1011
1123
|
def _pending_discussion_block(repo: Path, phase: Optional[str]) -> Optional[str]:
|
|
1012
1124
|
pending, error = _pending_answers(repo)
|
|
1013
1125
|
if error:
|
|
@@ -1028,7 +1140,7 @@ def route_state(repo: Path, project_dir: str = ".project") -> dict[str, object]:
|
|
|
1028
1140
|
result["recovery"] = recovery
|
|
1029
1141
|
if route.get("phase") == "define":
|
|
1030
1142
|
route["mode"] = "corrections"
|
|
1031
|
-
elif route.get("phase") == "plan":
|
|
1143
|
+
elif route.get("phase") == "plan" and recovery.get("source", "build") == "build":
|
|
1032
1144
|
route["mode"] = "build-repair"
|
|
1033
1145
|
if route["action"] == "run-phase" and result["state"]["archive"] is None:
|
|
1034
1146
|
reason = _pending_discussion_block(_repo_root(repo), route.get("phase"))
|
|
@@ -1381,8 +1493,11 @@ def _append_event(
|
|
|
1381
1493
|
|
|
1382
1494
|
@contextlib.contextmanager
|
|
1383
1495
|
def _state_lock(project: Path) -> Iterator[None]:
|
|
1384
|
-
if fcntl is None:
|
|
1385
|
-
|
|
1496
|
+
if fcntl is None:
|
|
1497
|
+
# Windows cannot flock a directory.
|
|
1498
|
+
with _common.directory_mutex(project):
|
|
1499
|
+
yield
|
|
1500
|
+
return
|
|
1386
1501
|
descriptor = os.open(project, os.O_RDONLY)
|
|
1387
1502
|
try:
|
|
1388
1503
|
fcntl.flock(descriptor, fcntl.LOCK_EX)
|
|
@@ -1408,27 +1523,21 @@ def _atomic_write(path: Path, content: str) -> None:
|
|
|
1408
1523
|
and os.path.samestat(path_status, temporary_status)
|
|
1409
1524
|
):
|
|
1410
1525
|
temporary.unlink()
|
|
1411
|
-
|
|
1412
|
-
try:
|
|
1413
|
-
os.fsync(directory)
|
|
1414
|
-
finally:
|
|
1415
|
-
os.close(directory)
|
|
1526
|
+
_common.fsync_directory(path.parent)
|
|
1416
1527
|
if temporary.exists() or temporary.is_symlink():
|
|
1417
1528
|
raise PipelineStateError(f"atomic-write temporary path already exists: {temporary}")
|
|
1418
1529
|
descriptor: Optional[int] = None
|
|
1419
1530
|
try:
|
|
1420
|
-
descriptor = os.open(
|
|
1421
|
-
|
|
1531
|
+
descriptor = os.open(
|
|
1532
|
+
temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | _common.O_BINARY, 0o666
|
|
1533
|
+
)
|
|
1534
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
1422
1535
|
descriptor = None
|
|
1423
|
-
handle.write(content)
|
|
1536
|
+
handle.write(content.encode("utf-8"))
|
|
1424
1537
|
handle.flush()
|
|
1425
1538
|
os.fsync(handle.fileno())
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
try:
|
|
1429
|
-
os.fsync(directory)
|
|
1430
|
-
finally:
|
|
1431
|
-
os.close(directory)
|
|
1539
|
+
_common.replace(temporary, path)
|
|
1540
|
+
_common.fsync_directory(path.parent)
|
|
1432
1541
|
finally:
|
|
1433
1542
|
if descriptor is not None:
|
|
1434
1543
|
os.close(descriptor)
|
|
@@ -1622,6 +1731,8 @@ def _validate_transition(
|
|
|
1622
1731
|
},
|
|
1623
1732
|
("define", "done", "plan", "active"): {"planning started"},
|
|
1624
1733
|
("decide", "done", "plan", "active"): {"planning started"},
|
|
1734
|
+
("plan", "active", "define", "active"): {"plan intent corrections requested"},
|
|
1735
|
+
("plan", "blocked", "define", "active"): {"plan intent corrections requested"},
|
|
1625
1736
|
("ship", "blocked", "plan", "active"): {"patch plan reopened"},
|
|
1626
1737
|
("ship", "active", "shipped", "done"): {
|
|
1627
1738
|
"archive preflight passed; shipment recorded",
|
|
@@ -1668,26 +1779,26 @@ def transition_state(
|
|
|
1668
1779
|
if recovery_context and recovery_context["active"]:
|
|
1669
1780
|
if (state.milestone, state.branch, state.archive) != (after.milestone, after.branch, after.archive):
|
|
1670
1781
|
raise PipelineStateError("build recovery must preserve milestone identity")
|
|
1671
|
-
if state.phase == "build" and after.phase in {"define", "plan"}:
|
|
1672
|
-
recovery = _build_recovery().begin(resolved, state, after, event)
|
|
1673
|
-
rendered = _append_event(
|
|
1674
|
-
rendered, after.phase,
|
|
1675
|
-
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1676
|
-
)
|
|
1677
1782
|
if after.status != "blocked":
|
|
1678
1783
|
reason = _pending_discussion_block(
|
|
1679
1784
|
resolved, after.phase if after.status == "active" else None
|
|
1680
1785
|
)
|
|
1681
1786
|
if reason:
|
|
1682
1787
|
raise PipelineStateError(reason)
|
|
1683
|
-
|
|
1788
|
+
leaving_define = (state.phase, after.phase, after.status) == ("define", "define", "done")
|
|
1789
|
+
if leaving_define or (state.phase in {"research", "decide"} and (
|
|
1684
1790
|
after.phase != state.phase or after.status == "done"
|
|
1685
|
-
):
|
|
1686
|
-
|
|
1687
|
-
import check_handoffs
|
|
1688
|
-
|
|
1689
|
-
|
|
1791
|
+
)):
|
|
1792
|
+
if __package__:
|
|
1793
|
+
from . import check_handoffs
|
|
1794
|
+
else:
|
|
1795
|
+
try:
|
|
1796
|
+
import check_handoffs
|
|
1797
|
+
except ImportError: # pragma: no cover - package imports used by tests
|
|
1798
|
+
from scripts import check_handoffs
|
|
1690
1799
|
validator = {
|
|
1800
|
+
# Approved intent carries its spec-reach probe tables complete.
|
|
1801
|
+
"define": check_handoffs.validate_intent_probes,
|
|
1691
1802
|
"research": check_handoffs.validate_research_artifacts,
|
|
1692
1803
|
"decide": check_handoffs.validate_decide_artifacts,
|
|
1693
1804
|
}[state.phase]
|
|
@@ -1695,6 +1806,25 @@ def transition_state(
|
|
|
1695
1806
|
validator(resolved, project_dir)
|
|
1696
1807
|
except check_handoffs.HandoffError as error:
|
|
1697
1808
|
raise PipelineStateError(f"{state.phase} handoff failed: {error}") from error
|
|
1809
|
+
if (project_dir == ".project" and (state.phase, state.status) == ("plan", "done")
|
|
1810
|
+
and (after.phase, after.status) == ("build", "active")):
|
|
1811
|
+
_lock_build_members(resolved)
|
|
1812
|
+
if state.phase == "build" and after.phase in {"define", "plan"}:
|
|
1813
|
+
recovery = _build_recovery().begin(resolved, state, after, event)
|
|
1814
|
+
rendered = _append_event(
|
|
1815
|
+
rendered, after.phase,
|
|
1816
|
+
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1817
|
+
)
|
|
1818
|
+
elif (
|
|
1819
|
+
state.phase == "plan"
|
|
1820
|
+
and state.status in {"active", "blocked"}
|
|
1821
|
+
and (after.phase, after.status) == ("define", "active")
|
|
1822
|
+
):
|
|
1823
|
+
recovery = _build_recovery().begin(resolved, state, after, event)
|
|
1824
|
+
rendered = _append_event(
|
|
1825
|
+
rendered, after.phase,
|
|
1826
|
+
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1827
|
+
)
|
|
1698
1828
|
_atomic_write(path, rendered)
|
|
1699
1829
|
return {
|
|
1700
1830
|
"schema": STATE_SCHEMA,
|
|
@@ -1705,6 +1835,17 @@ def transition_state(
|
|
|
1705
1835
|
}
|
|
1706
1836
|
|
|
1707
1837
|
|
|
1838
|
+
def _lock_build_members(repo: Path) -> None:
|
|
1839
|
+
if __package__:
|
|
1840
|
+
from scripts import members
|
|
1841
|
+
else:
|
|
1842
|
+
import members
|
|
1843
|
+
try:
|
|
1844
|
+
members.lock_build_members(repo)
|
|
1845
|
+
except members.MembersError as error:
|
|
1846
|
+
raise PipelineStateError(f"build start: {error}") from error
|
|
1847
|
+
|
|
1848
|
+
|
|
1708
1849
|
def _build_recovery():
|
|
1709
1850
|
if __package__:
|
|
1710
1851
|
from scripts import build_recovery
|
|
@@ -1776,6 +1917,55 @@ def configure_integration(
|
|
|
1776
1917
|
}
|
|
1777
1918
|
|
|
1778
1919
|
|
|
1920
|
+
def _log_events(text: str) -> list[str]:
|
|
1921
|
+
return [line.split(" — ", 2)[-1] for line in text.splitlines()
|
|
1922
|
+
if line.startswith("- ") and " — " in line]
|
|
1923
|
+
|
|
1924
|
+
|
|
1925
|
+
def pre_approve(repo: Path, grant: Optional[str], use: Optional[str]) -> dict[str, object]:
|
|
1926
|
+
"""Record an owner's pre-approval grant, or record one granted gate's use."""
|
|
1927
|
+
resolved = _repo_root(repo)
|
|
1928
|
+
project = _track_root(resolved, ".project")
|
|
1929
|
+
with _state_lock(project):
|
|
1930
|
+
state, text, path = load_state(resolved)
|
|
1931
|
+
if grant is not None:
|
|
1932
|
+
kinds = grant.split(",")
|
|
1933
|
+
if not kinds or len(set(kinds)) != len(kinds) or not set(kinds) <= set(PRE_APPROVAL_KINDS):
|
|
1934
|
+
raise PipelineStateError(
|
|
1935
|
+
f"pre-approval kinds must be distinct values from: {', '.join(PRE_APPROVAL_KINDS)}"
|
|
1936
|
+
)
|
|
1937
|
+
if state.milestone is not None:
|
|
1938
|
+
raise PipelineStateError("pre-approval is granted only before milestone intent approval")
|
|
1939
|
+
event = PRE_APPROVAL_MARKER + json.dumps({"kinds": kinds}, sort_keys=True)
|
|
1940
|
+
else:
|
|
1941
|
+
events = _log_events(text)
|
|
1942
|
+
starts = [index for index, event in enumerate(events) if event.startswith(PRE_APPROVAL_MARKER)]
|
|
1943
|
+
if not starts:
|
|
1944
|
+
raise PipelineStateError("no pre-approval grant is recorded")
|
|
1945
|
+
try:
|
|
1946
|
+
recorded = json.loads(events[starts[-1]][len(PRE_APPROVAL_MARKER):])
|
|
1947
|
+
kinds = recorded["kinds"]
|
|
1948
|
+
except (ValueError, TypeError, KeyError) as error:
|
|
1949
|
+
raise PipelineStateError("invalid pre-approval record") from error
|
|
1950
|
+
if not isinstance(kinds, list):
|
|
1951
|
+
raise PipelineStateError("invalid pre-approval record")
|
|
1952
|
+
if (_build_recovery().context(resolved, text) or {}).get("active") or (project / "review/PATCH-FINDINGS.md").exists():
|
|
1953
|
+
raise PipelineStateError("pre-approval never covers build recovery or patch planning")
|
|
1954
|
+
if use not in kinds:
|
|
1955
|
+
raise PipelineStateError(f"pre-approval does not grant {use}")
|
|
1956
|
+
# The canonical approval event, not this record, consumes the grant.
|
|
1957
|
+
approvals = events[starts[-1] + 1:].count("milestone intent approved")
|
|
1958
|
+
expected = {"intent": ("define", "active", 0), "plan": ("plan", "active", 1)}[use]
|
|
1959
|
+
if (state.phase, state.status, approvals) != expected or (
|
|
1960
|
+
(state.milestone is None) != (use == "intent")
|
|
1961
|
+
):
|
|
1962
|
+
raise PipelineStateError(f"pre-approval for {use} is used or out of scope")
|
|
1963
|
+
event = f"pre-authorized approval: {use}"
|
|
1964
|
+
rendered = _append_event(text, state.phase, event)
|
|
1965
|
+
_atomic_write(path, rendered)
|
|
1966
|
+
return {"schema": STATE_SCHEMA, "status": "recorded", "event": event, "state": state.json()}
|
|
1967
|
+
|
|
1968
|
+
|
|
1779
1969
|
def _shipment_roadmap(text: str, state: PipelineState, archive: str) -> str:
|
|
1780
1970
|
if state.milestone is None or state.branch is None:
|
|
1781
1971
|
raise PipelineStateError("shipment state must name milestone and branch")
|
|
@@ -2016,7 +2206,8 @@ def _commit_message(subject: str, body_fields: Mapping[str, str]) -> str:
|
|
|
2016
2206
|
def _validate_next_layout(next_root: Path, state: PipelineState) -> None:
|
|
2017
2207
|
phase_index = LOOKAHEAD_PHASES.index(state.phase)
|
|
2018
2208
|
for path in next_root.rglob("*"):
|
|
2019
|
-
|
|
2209
|
+
# POSIX form: a WindowsPath never equals the PurePosixPath layout below.
|
|
2210
|
+
relative = PurePosixPath(path.relative_to(next_root).as_posix())
|
|
2020
2211
|
if path.is_symlink():
|
|
2021
2212
|
raise PipelineStateError(f"lookahead artifacts must not be symlinks: {path}")
|
|
2022
2213
|
if path.is_dir():
|
|
@@ -2028,6 +2219,8 @@ def _validate_next_layout(next_root: Path, state: PipelineState) -> None:
|
|
|
2028
2219
|
continue
|
|
2029
2220
|
if not path.is_file():
|
|
2030
2221
|
raise PipelineStateError(f"lookahead has unsafe artifact: {path}")
|
|
2222
|
+
if _common.is_ignored_junk(path):
|
|
2223
|
+
continue
|
|
2031
2224
|
artifact = relative.as_posix()
|
|
2032
2225
|
required_phase = LOOKAHEAD_FIXED_ARTIFACTS.get(artifact)
|
|
2033
2226
|
if required_phase is None and LOOKAHEAD_EVIDENCE_RE.fullmatch(artifact):
|
|
@@ -2112,6 +2305,11 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
|
|
|
2112
2305
|
configure.add_argument("--project-dir", default=".project")
|
|
2113
2306
|
configure.add_argument("--scope", required=True, choices=("default", "milestone"))
|
|
2114
2307
|
configure.add_argument("--mode", required=True, choices=INTEGRATION_MODES)
|
|
2308
|
+
pre = subparsers.add_parser("pre-approve")
|
|
2309
|
+
pre.add_argument("--repo", required=True, type=Path)
|
|
2310
|
+
pre_mode = pre.add_mutually_exclusive_group(required=True)
|
|
2311
|
+
pre_mode.add_argument("--grant")
|
|
2312
|
+
pre_mode.add_argument("--use", choices=PRE_APPROVAL_KINDS)
|
|
2115
2313
|
shipment = subparsers.add_parser("record-shipment")
|
|
2116
2314
|
shipment.add_argument("--repo", required=True, type=Path)
|
|
2117
2315
|
shipment.add_argument("--archive", required=True)
|
|
@@ -2200,6 +2398,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2200
2398
|
base,
|
|
2201
2399
|
landing,
|
|
2202
2400
|
)
|
|
2401
|
+
elif args.command == "pre-approve":
|
|
2402
|
+
result = pre_approve(args.repo, args.grant, args.use)
|
|
2203
2403
|
elif args.command == "record-shipment":
|
|
2204
2404
|
result = record_shipment(args.repo, args.archive, args.event)
|
|
2205
2405
|
elif args.command == "configure-integration":
|
|
@@ -205,7 +205,7 @@ def select_fetched_base(
|
|
|
205
205
|
resolved = subprocess.run(
|
|
206
206
|
["git", "-C", str(root), "rev-parse", "--verify", f"{remote_default}^{{commit}}"],
|
|
207
207
|
capture_output=True,
|
|
208
|
-
|
|
208
|
+
encoding="utf-8", errors="replace",
|
|
209
209
|
check=False,
|
|
210
210
|
)
|
|
211
211
|
if resolved.returncode != 0 or resolved.stdout.strip() != base:
|
|
@@ -470,7 +470,7 @@ def merge_artifacts(
|
|
|
470
470
|
lines.append(artifact["text"].rstrip())
|
|
471
471
|
lines.append("")
|
|
472
472
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
473
|
-
output.
|
|
473
|
+
output.write_bytes(("\n".join(lines).rstrip() + "\n").encode("utf-8"))
|
|
474
474
|
return {
|
|
475
475
|
"output": str(output.resolve()),
|
|
476
476
|
"families": families,
|