@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
|
@@ -16,14 +16,20 @@ from pathlib import Path
|
|
|
16
16
|
from typing import Iterator, Optional, Sequence
|
|
17
17
|
|
|
18
18
|
def _load_pipeline_modules():
|
|
19
|
-
|
|
20
|
-
import archive_milestone
|
|
21
|
-
import pipeline_state
|
|
22
|
-
import _common
|
|
19
|
+
if __package__:
|
|
20
|
+
from . import archive_milestone
|
|
21
|
+
from . import pipeline_state
|
|
22
|
+
from . import _common
|
|
23
23
|
return _common, archive_milestone, pipeline_state
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
else:
|
|
25
|
+
try:
|
|
26
|
+
import archive_milestone
|
|
27
|
+
import pipeline_state
|
|
28
|
+
import _common
|
|
29
|
+
return _common, archive_milestone, pipeline_state
|
|
30
|
+
except ModuleNotFoundError as error:
|
|
31
|
+
if error.name not in {"archive_milestone", "pipeline_state", "_common"}:
|
|
32
|
+
raise
|
|
27
33
|
try:
|
|
28
34
|
from scripts import archive_milestone, pipeline_state, _common
|
|
29
35
|
return _common, archive_milestone, pipeline_state
|
|
@@ -111,7 +117,7 @@ def record_count(directory: Path) -> int:
|
|
|
111
117
|
def validate_or_empty(directory: Path) -> None:
|
|
112
118
|
if directory.is_symlink() or not directory.is_dir():
|
|
113
119
|
raise DiscussionError("discussion records must be a real directory")
|
|
114
|
-
if sorted(path.name for path in directory.iterdir()) != sorted(FILES):
|
|
120
|
+
if sorted(path.name for path in directory.iterdir() if not _common.is_ignored_junk(path)) != sorted(FILES):
|
|
115
121
|
raise DiscussionError("discussion directory must contain exactly DIALOGUE.md and ANSWERS.md")
|
|
116
122
|
if record_count(directory) == 0:
|
|
117
123
|
if "### D" in (directory / "DIALOGUE.md").read_text(encoding="utf-8"):
|
|
@@ -170,7 +176,7 @@ def validate_contents(project: Path, files: dict) -> None:
|
|
|
170
176
|
staging.mkdir()
|
|
171
177
|
try:
|
|
172
178
|
for name in FILES:
|
|
173
|
-
(staging / name).
|
|
179
|
+
(staging / name).write_bytes(files[name].encode("utf-8"))
|
|
174
180
|
validate_or_empty(staging)
|
|
175
181
|
finally:
|
|
176
182
|
shutil.rmtree(staging)
|
|
@@ -187,11 +193,11 @@ def initialize_records(discussion: Path, dialogue_template: Path, answers_templa
|
|
|
187
193
|
shutil.rmtree(staging)
|
|
188
194
|
staging.mkdir()
|
|
189
195
|
try:
|
|
190
|
-
(staging / "DIALOGUE.md").
|
|
191
|
-
template_header(dialogue_template, "### D001")
|
|
196
|
+
(staging / "DIALOGUE.md").write_bytes(
|
|
197
|
+
template_header(dialogue_template, "### D001").encode("utf-8")
|
|
192
198
|
)
|
|
193
|
-
(staging / "ANSWERS.md").
|
|
194
|
-
template_header(answers_template, "## Answer A001")
|
|
199
|
+
(staging / "ANSWERS.md").write_bytes(
|
|
200
|
+
template_header(answers_template, "## Answer A001").encode("utf-8")
|
|
195
201
|
)
|
|
196
202
|
os.replace(staging, discussion)
|
|
197
203
|
finally:
|
|
@@ -32,10 +32,10 @@ from datetime import datetime, timedelta, timezone
|
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import Iterator, Optional, Sequence
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
import
|
|
37
|
-
|
|
38
|
-
import
|
|
35
|
+
try:
|
|
36
|
+
from . import _common
|
|
37
|
+
except ImportError:
|
|
38
|
+
import _common
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class LoopError(RuntimeError):
|
|
@@ -119,25 +119,8 @@ def log_path(fields: dict) -> Path:
|
|
|
119
119
|
def log_lock(fields: dict) -> Iterator[None]:
|
|
120
120
|
path = log_path(fields)
|
|
121
121
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if sys.platform == "win32":
|
|
125
|
-
handle.seek(0, os.SEEK_END)
|
|
126
|
-
if handle.tell() == 0:
|
|
127
|
-
handle.write(b"\0")
|
|
128
|
-
handle.flush()
|
|
129
|
-
handle.seek(0)
|
|
130
|
-
msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
|
|
131
|
-
else:
|
|
132
|
-
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
133
|
-
try:
|
|
134
|
-
yield
|
|
135
|
-
finally:
|
|
136
|
-
if sys.platform == "win32":
|
|
137
|
-
handle.seek(0)
|
|
138
|
-
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
|
|
139
|
-
else:
|
|
140
|
-
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
122
|
+
with _common.exclusive_lock(path.with_name(f".{path.name}.lock")):
|
|
123
|
+
yield
|
|
141
124
|
|
|
142
125
|
|
|
143
126
|
def load_log(fields: dict) -> list[dict]:
|
|
@@ -528,15 +511,34 @@ def command_claim(fields: dict) -> dict:
|
|
|
528
511
|
|
|
529
512
|
|
|
530
513
|
def run_shell(command: str, timeout: float) -> tuple[Optional[int], str]:
|
|
531
|
-
"""Run one spec command; a hung command counts as failed (exit None).
|
|
514
|
+
"""Run one spec command; a hung command counts as failed (exit None).
|
|
515
|
+
|
|
516
|
+
POSIX runs /bin/sh as shell=True did; Windows runs Git for Windows bash, not
|
|
517
|
+
cmd.exe. A timeout kills the whole process group when possible; if output
|
|
518
|
+
pipes stay open, drain them with a short grace timeout and then close them.
|
|
519
|
+
"""
|
|
520
|
+
argv = _common.bash_argv(command) if os.name == "nt" else ["/bin/sh", "-c", command]
|
|
521
|
+
process = _common.popen_group(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
522
|
+
stdout = b""
|
|
523
|
+
stderr = b""
|
|
532
524
|
try:
|
|
533
|
-
|
|
534
|
-
command, shell=True, capture_output=True, text=True, check=False, timeout=timeout
|
|
535
|
-
)
|
|
525
|
+
stdout, stderr = process.communicate(timeout=timeout)
|
|
536
526
|
except subprocess.TimeoutExpired as expired:
|
|
537
|
-
|
|
527
|
+
_common.kill_tree(process)
|
|
528
|
+
try:
|
|
529
|
+
stdout, stderr = process.communicate(timeout=5)
|
|
530
|
+
except subprocess.TimeoutExpired as drained:
|
|
531
|
+
stdout = drained.stdout or expired.stdout or b""
|
|
532
|
+
stderr = drained.stderr or expired.stderr or b""
|
|
533
|
+
if process.stdout:
|
|
534
|
+
process.stdout.close()
|
|
535
|
+
if process.stderr:
|
|
536
|
+
process.stderr.close()
|
|
537
|
+
process.kill()
|
|
538
|
+
process.wait(timeout=5)
|
|
539
|
+
output = stdout.decode("utf-8", errors="replace") + stderr.decode("utf-8", errors="replace")
|
|
538
540
|
return None, f"{output}\ntimed out at the admitted wall-clock deadline"
|
|
539
|
-
return
|
|
541
|
+
return process.returncode, (stdout + stderr).decode("utf-8", errors="replace")
|
|
540
542
|
|
|
541
543
|
|
|
542
544
|
def command_verify(fields: dict, claim_identifier: Optional[str]) -> dict:
|
|
@@ -13,7 +13,7 @@ sys.dont_write_bytecode = True
|
|
|
13
13
|
ROLES = ('coder', 'reviewer', 'review_panel', 'skeptic', 'inspect_codebase',
|
|
14
14
|
'inspect_docs', 'docs_audit', 'research', 'decide', 'roadmap', 'plan', 'plan_patch')
|
|
15
15
|
HOSTS = ('codex', 'claude', 'grok', 'opencode', 'copilot', 'qwen', 'antigravity',
|
|
16
|
-
'cursor', 'zed', 'kiro', 'kimi')
|
|
16
|
+
'cursor', 'zed', 'kiro', 'kimi', 'muse')
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def sibling(name):
|
|
@@ -73,8 +73,8 @@ def validate_value(key, value):
|
|
|
73
73
|
if not isinstance(value, str) or not value or value != value.strip():
|
|
74
74
|
raise ValueError(f'{key} requires a nonempty string')
|
|
75
75
|
if key == 'integration':
|
|
76
|
-
if value not in ('direct', 'pull-request'):
|
|
77
|
-
raise ValueError('integration must be direct or
|
|
76
|
+
if value not in ('direct', 'pull-request', 'external-landing'):
|
|
77
|
+
raise ValueError('integration must be direct, pull-request, or external-landing')
|
|
78
78
|
elif key == 'review_panel':
|
|
79
79
|
if any(char.isspace() for char in value):
|
|
80
80
|
raise ValueError('use off, detected, or comma-separated families without spaces')
|
|
@@ -174,13 +174,13 @@ def set_value(repo, scope, key, value, reset=False):
|
|
|
174
174
|
if scope == 'project':
|
|
175
175
|
state, _, _ = sibling('pipeline_state').load_state(repo)
|
|
176
176
|
branch = subprocess.run(['git', '-C', str(repo), 'branch', '--show-current'],
|
|
177
|
-
capture_output=True,
|
|
177
|
+
capture_output=True, encoding="utf-8", errors="replace")
|
|
178
178
|
if branch.returncode or not state.branch or branch.stdout.strip() != state.branch:
|
|
179
179
|
raise ValueError('Run the router to bind the project branch before editing settings.')
|
|
180
180
|
head = subprocess.run(['git', '-C', str(repo), 'rev-parse', '--verify', 'HEAD'], capture_output=True)
|
|
181
181
|
if head.returncode == 0:
|
|
182
182
|
closed = subprocess.run([sys.executable, '-B', str(Path(__file__).with_name('git_guard.py')),
|
|
183
|
-
'closed-milestone'], cwd=repo, capture_output=True,
|
|
183
|
+
'closed-milestone'], cwd=repo, capture_output=True, encoding="utf-8", errors="replace")
|
|
184
184
|
if closed.returncode:
|
|
185
185
|
raise ValueError(closed.stderr.strip() or 'Cannot verify the milestone branch is open.')
|
|
186
186
|
if scope == 'project' and key == 'integration':
|
|
@@ -17,6 +17,7 @@ import os
|
|
|
17
17
|
import re
|
|
18
18
|
import shutil
|
|
19
19
|
import sys
|
|
20
|
+
from datetime import date
|
|
20
21
|
|
|
21
22
|
# Runtime helpers must not modify their immutable installation.
|
|
22
23
|
sys.dont_write_bytecode = True
|
|
@@ -25,22 +26,30 @@ from pathlib import Path, PurePosixPath
|
|
|
25
26
|
from typing import Optional, Sequence
|
|
26
27
|
|
|
27
28
|
def _load_pipeline_modules():
|
|
28
|
-
|
|
29
|
-
import archive_milestone
|
|
30
|
-
import isolation
|
|
31
|
-
import pipeline_git
|
|
32
|
-
import pipeline_state
|
|
33
|
-
import state_checkpoint
|
|
29
|
+
if __package__:
|
|
30
|
+
from . import archive_milestone
|
|
31
|
+
from . import isolation
|
|
32
|
+
from . import pipeline_git
|
|
33
|
+
from . import pipeline_state
|
|
34
|
+
from . import state_checkpoint
|
|
34
35
|
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
else:
|
|
37
|
+
try:
|
|
38
|
+
import archive_milestone
|
|
39
|
+
import isolation
|
|
40
|
+
import pipeline_git
|
|
41
|
+
import pipeline_state
|
|
42
|
+
import state_checkpoint
|
|
43
|
+
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
44
|
+
except ModuleNotFoundError as error:
|
|
45
|
+
if error.name not in {
|
|
46
|
+
"archive_milestone",
|
|
47
|
+
"isolation",
|
|
48
|
+
"pipeline_git",
|
|
49
|
+
"pipeline_state",
|
|
50
|
+
"state_checkpoint",
|
|
51
|
+
}:
|
|
52
|
+
raise
|
|
44
53
|
try:
|
|
45
54
|
from scripts import archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
46
55
|
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
@@ -94,10 +103,20 @@ UNDO_TRANSACTION_SCHEMA = pipeline_state.UNDO_TRANSACTION_SCHEMA
|
|
|
94
103
|
UNDO_TRANSACTION_NAME = pipeline_state.UNDO_TRANSACTION_NAME
|
|
95
104
|
ROADMAP_APPROVAL_SUBJECT = "roadmap: program roadmap approved"
|
|
96
105
|
TASK_SUBJECT_RE = re.compile(r"^(T\d{3,}): .+")
|
|
97
|
-
KINDS = ("checkpoint", "task", "uncommitted-archive", "lookahead")
|
|
106
|
+
KINDS = ("checkpoint", "task", "member-task", "uncommitted-archive", "lookahead")
|
|
98
107
|
PROMOTION_PREFIX = "router: promote lookahead milestone "
|
|
99
108
|
ABANDON_PREFIX = "build: abandon milestone "
|
|
100
109
|
INTEGRATE_PREFIX = "integrate: "
|
|
110
|
+
SHIP_LOG_ONLY_LINE = re.compile(
|
|
111
|
+
r"- (?P<date>\d{4}-\d{2}-\d{2}) — ship — (?P<event>[^\r\n]+)\n\Z"
|
|
112
|
+
)
|
|
113
|
+
SHIPMENT_RECORD_LINE = re.compile(
|
|
114
|
+
r"- (?P<date>\d{4}-\d{2}-\d{2}) — shipped — (?P<event>[^\r\n]+)\n\Z"
|
|
115
|
+
)
|
|
116
|
+
SHIPMENT_EVENTS = {
|
|
117
|
+
"archive preflight passed; shipment recorded",
|
|
118
|
+
"archive preflight passed; shipment recorded; program complete",
|
|
119
|
+
}
|
|
101
120
|
|
|
102
121
|
|
|
103
122
|
class UndoError(RuntimeError):
|
|
@@ -367,6 +386,80 @@ def pending_transaction(repo: Path) -> Optional[dict[str, object]]:
|
|
|
367
386
|
raise UndoError(str(error)) from error
|
|
368
387
|
|
|
369
388
|
|
|
389
|
+
def _valid_state_event_line(line: str, phase: str) -> bool:
|
|
390
|
+
pattern = SHIP_LOG_ONLY_LINE if phase == "ship" else SHIPMENT_RECORD_LINE
|
|
391
|
+
match = pattern.fullmatch(line)
|
|
392
|
+
if match is None:
|
|
393
|
+
return False
|
|
394
|
+
try:
|
|
395
|
+
event_date = date.fromisoformat(match.group("date"))
|
|
396
|
+
except ValueError:
|
|
397
|
+
return False
|
|
398
|
+
event = match.group("event")
|
|
399
|
+
return event_date.isoformat() == match.group("date") and event == " ".join(event.split())
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _owned_ship_log_events(
|
|
403
|
+
prepared_state: str,
|
|
404
|
+
current_state: str,
|
|
405
|
+
phase: object,
|
|
406
|
+
status: object,
|
|
407
|
+
) -> Optional[str]:
|
|
408
|
+
"""Return only ship log lines appended around a helper-owned archive event.
|
|
409
|
+
|
|
410
|
+
The prepared text must remain byte-for-byte unchanged. For shipped/done,
|
|
411
|
+
record-shipment changes only phase and status before appending its one
|
|
412
|
+
generated shipment event, so validate that exact prefix and event shape.
|
|
413
|
+
"""
|
|
414
|
+
if (phase, status) == ("ship", "active"):
|
|
415
|
+
if current_state == prepared_state:
|
|
416
|
+
return ""
|
|
417
|
+
if not current_state.startswith(prepared_state):
|
|
418
|
+
return None
|
|
419
|
+
suffix = current_state[len(prepared_state):]
|
|
420
|
+
separator = "" if prepared_state.endswith("\n") else "\n"
|
|
421
|
+
if separator:
|
|
422
|
+
if not suffix.startswith(separator):
|
|
423
|
+
return None
|
|
424
|
+
suffix = suffix[len(separator):]
|
|
425
|
+
lines = suffix.splitlines(keepends=True)
|
|
426
|
+
if not lines or "".join(lines) != suffix:
|
|
427
|
+
return None
|
|
428
|
+
if not all(_valid_state_event_line(line, "ship") for line in lines):
|
|
429
|
+
return None
|
|
430
|
+
return separator + suffix
|
|
431
|
+
|
|
432
|
+
if (phase, status) != ("shipped", "done"):
|
|
433
|
+
return None
|
|
434
|
+
try:
|
|
435
|
+
shipped_prefix = pipeline_state._set_frontmatter(
|
|
436
|
+
prepared_state,
|
|
437
|
+
{"phase": "shipped", "status": "done"},
|
|
438
|
+
)
|
|
439
|
+
except PipelineStateError:
|
|
440
|
+
return None
|
|
441
|
+
if not current_state.startswith(shipped_prefix):
|
|
442
|
+
return None
|
|
443
|
+
suffix = current_state[len(shipped_prefix):]
|
|
444
|
+
separator = "" if shipped_prefix.endswith("\n") else "\n"
|
|
445
|
+
if separator:
|
|
446
|
+
if not suffix.startswith(separator):
|
|
447
|
+
return None
|
|
448
|
+
suffix = suffix[len(separator):]
|
|
449
|
+
lines = suffix.splitlines(keepends=True)
|
|
450
|
+
if not lines or "".join(lines) != suffix:
|
|
451
|
+
return None
|
|
452
|
+
shipment_event = lines[-1]
|
|
453
|
+
if not _valid_state_event_line(shipment_event, "shipped"):
|
|
454
|
+
return None
|
|
455
|
+
if SHIPMENT_RECORD_LINE.fullmatch(shipment_event).group("event") not in SHIPMENT_EVENTS:
|
|
456
|
+
return None
|
|
457
|
+
ship_events = lines[:-1]
|
|
458
|
+
if not all(_valid_state_event_line(line, "ship") for line in ship_events):
|
|
459
|
+
return None
|
|
460
|
+
return separator + "".join(ship_events) if ship_events else ""
|
|
461
|
+
|
|
462
|
+
|
|
370
463
|
def _archive_metadata_error(
|
|
371
464
|
repo: Path,
|
|
372
465
|
state_fields: dict[str, object],
|
|
@@ -412,8 +505,13 @@ def _archive_metadata_error(
|
|
|
412
505
|
if final_review.exists() or gap_reviews:
|
|
413
506
|
reviewed_head, _ = archive_milestone.parse_final_review(archive_path)
|
|
414
507
|
archive_milestone.validate_gap_reviews(archive_path, reviewed_head)
|
|
415
|
-
if
|
|
416
|
-
|
|
508
|
+
if not archive_milestone.reviewed_head_covers(
|
|
509
|
+
repo, reviewed_head, _optional_rev(repo, "HEAD")
|
|
510
|
+
):
|
|
511
|
+
return (
|
|
512
|
+
"archive undo final review does not match current HEAD; "
|
|
513
|
+
f"{archive_milestone._common.RUNTIME_PIN_ALLOWANCE}"
|
|
514
|
+
)
|
|
417
515
|
expected_inventory.add("review/FINAL.md")
|
|
418
516
|
expected_inventory.update(
|
|
419
517
|
path.relative_to(archive_path).as_posix() for path in gap_reviews
|
|
@@ -468,10 +566,14 @@ def _archive_metadata_error(
|
|
|
468
566
|
"archive",
|
|
469
567
|
configured,
|
|
470
568
|
)
|
|
471
|
-
current_state = (repo / ".project" / "STATE.md").
|
|
569
|
+
current_state = (repo / ".project" / "STATE.md").read_bytes().decode("utf-8")
|
|
472
570
|
phase = state_fields.get("phase")
|
|
473
571
|
status = state_fields.get("status")
|
|
572
|
+
ship_events = _owned_ship_log_events(prepared_state, current_state, phase, status)
|
|
474
573
|
if (phase, status) == ("ship", "active"):
|
|
574
|
+
if ship_events is None:
|
|
575
|
+
return "archive undo found unowned STATE.md changes"
|
|
576
|
+
prepared_state += ship_events
|
|
475
577
|
if current_state != prepared_state:
|
|
476
578
|
return "archive undo found unowned STATE.md changes"
|
|
477
579
|
if ".project/ROADMAP.md" in dirty:
|
|
@@ -479,14 +581,13 @@ def _archive_metadata_error(
|
|
|
479
581
|
return None
|
|
480
582
|
if (phase, status) != ("shipped", "done"):
|
|
481
583
|
return "archive undo requires ship/active or shipped/done"
|
|
584
|
+
if ship_events is None:
|
|
585
|
+
return "archive undo found unowned shipment STATE.md changes"
|
|
586
|
+
prepared_state += ship_events
|
|
482
587
|
|
|
483
588
|
prepared = _state_from_text(prepared_state, "prepared archive STATE.md")
|
|
484
|
-
events = (
|
|
485
|
-
"archive preflight passed; shipment recorded",
|
|
486
|
-
"archive preflight passed; shipment recorded; program complete",
|
|
487
|
-
)
|
|
488
589
|
rendered_states = []
|
|
489
|
-
for event in
|
|
590
|
+
for event in SHIPMENT_EVENTS:
|
|
490
591
|
try:
|
|
491
592
|
_, _, rendered = _render_transition(
|
|
492
593
|
prepared,
|
|
@@ -528,6 +629,7 @@ def _prepare_transaction(
|
|
|
528
629
|
branch: str,
|
|
529
630
|
archive: Optional[str],
|
|
530
631
|
discussion: Optional[dict[str, bytes]],
|
|
632
|
+
member_fields: Optional[dict[str, str]] = None,
|
|
531
633
|
) -> dict[str, object]:
|
|
532
634
|
transaction = {
|
|
533
635
|
"schema": UNDO_TRANSACTION_SCHEMA,
|
|
@@ -545,6 +647,8 @@ def _prepare_transaction(
|
|
|
545
647
|
),
|
|
546
648
|
"discussion": _encode_discussion(discussion),
|
|
547
649
|
}
|
|
650
|
+
if member_fields is not None:
|
|
651
|
+
transaction.update(member_fields)
|
|
548
652
|
_write_json(_git_path(repo, UNDO_TRANSACTION_NAME), transaction)
|
|
549
653
|
return transaction
|
|
550
654
|
|
|
@@ -641,7 +745,7 @@ def _transaction_recovery_stage(
|
|
|
641
745
|
expected_head = str(transaction["expected_head"])
|
|
642
746
|
parent = str(transaction["parent"])
|
|
643
747
|
current = _optional_rev(repo, "HEAD")
|
|
644
|
-
if transaction["kind"]
|
|
748
|
+
if transaction["kind"] in {"checkpoint", "member-task"}:
|
|
645
749
|
allowed = {expected_head, parent}
|
|
646
750
|
else:
|
|
647
751
|
allowed = {expected_head}
|
|
@@ -662,8 +766,60 @@ def _transaction_recovery_stage(
|
|
|
662
766
|
return "reset"
|
|
663
767
|
|
|
664
768
|
|
|
769
|
+
def _member_undo_parent(repo: Path, member: str, landing: str) -> str:
|
|
770
|
+
"""The member landing's parent, while that landing is the unpublished bound tip."""
|
|
771
|
+
checkout, _, entry = isolation._member_context(repo, member)
|
|
772
|
+
bound = entry["branch"]
|
|
773
|
+
if _run_git(checkout, "rev-parse", f"refs/heads/{bound}").stdout.strip() != landing:
|
|
774
|
+
raise UndoError(f"member {member} {bound} moved past this landing")
|
|
775
|
+
_require_member_unpublished(checkout, member, landing, bound)
|
|
776
|
+
return _run_git(checkout, "rev-parse", f"{landing}^").stdout.strip()
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _require_member_unpublished(checkout: Path, member: str, landing: str, bound: str) -> None:
|
|
780
|
+
for ref, where in (("refs/remotes/origin/main", "origin/main"), (f"refs/remotes/origin/{bound}", f"origin/{bound}")):
|
|
781
|
+
if _run_git(checkout, "merge-base", "--is-ancestor", landing, ref, check=False).returncode == 0:
|
|
782
|
+
raise UndoError(f"member {member} landing is already on {where}")
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _require_no_member_landing_journal(repo: Path) -> None:
|
|
786
|
+
directory = isolation.common_git_dir(repo).joinpath(*isolation.MEMBER_LANDING_DIR)
|
|
787
|
+
if any(directory.glob("*.json")):
|
|
788
|
+
raise UndoError("member landing journal is pending; recover member landing first")
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def _undo_member_landing(repo: Path, transaction: dict[str, object]) -> None:
|
|
792
|
+
"""Reset the member bound branch first; a resumed undo finds it already reset."""
|
|
793
|
+
member = str(transaction["member"])
|
|
794
|
+
checkout, _, entry = isolation._member_context(repo, member)
|
|
795
|
+
tip = _run_git(checkout, "rev-parse", f"refs/heads/{entry['branch']}").stdout.strip()
|
|
796
|
+
if tip == transaction["member_parent"]:
|
|
797
|
+
_require_member_unpublished(checkout, member, str(transaction["landing"]), entry["branch"])
|
|
798
|
+
return
|
|
799
|
+
if tip != transaction["landing"]:
|
|
800
|
+
raise UndoError(f"member {member} bound branch moved during undo")
|
|
801
|
+
if _member_undo_parent(repo, member, str(transaction["landing"])) != transaction["member_parent"]:
|
|
802
|
+
raise UndoError(f"member {member} landing parent changed during undo")
|
|
803
|
+
bound = Path(isolation.member_bound_checkout(repo, member)["checkout"])
|
|
804
|
+
if _run_git(bound, "status", "--porcelain", "--untracked-files=all").stdout:
|
|
805
|
+
raise UndoError(f"member {member} bound checkout is dirty")
|
|
806
|
+
_run_git(bound, "reset", "--hard", str(transaction["member_parent"]))
|
|
807
|
+
|
|
808
|
+
|
|
665
809
|
def _finish_transaction(repo: Path, transaction: dict[str, object]) -> None:
|
|
810
|
+
if transaction["kind"] == "member-task":
|
|
811
|
+
with isolation._member_landing_lock(repo):
|
|
812
|
+
_finish_transaction_locked(repo, transaction)
|
|
813
|
+
return
|
|
814
|
+
_finish_transaction_locked(repo, transaction)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def _finish_transaction_locked(repo: Path, transaction: dict[str, object]) -> None:
|
|
818
|
+
if transaction["kind"] == "member-task":
|
|
819
|
+
_require_no_member_landing_journal(repo)
|
|
666
820
|
stage = _transaction_recovery_stage(repo, transaction)
|
|
821
|
+
if transaction["kind"] == "member-task":
|
|
822
|
+
_undo_member_landing(repo, transaction)
|
|
667
823
|
if stage == "prepared":
|
|
668
824
|
_require_unpublished_reset(repo, str(transaction["branch"]))
|
|
669
825
|
_reset_to(repo, str(transaction["parent"]))
|
|
@@ -675,11 +831,7 @@ def _finish_transaction(repo: Path, transaction: dict[str, object]) -> None:
|
|
|
675
831
|
_remove_untracked_tree(repo, archive)
|
|
676
832
|
path = _git_path(repo, UNDO_TRANSACTION_NAME)
|
|
677
833
|
path.unlink()
|
|
678
|
-
|
|
679
|
-
try:
|
|
680
|
-
os.fsync(descriptor)
|
|
681
|
-
finally:
|
|
682
|
-
os.close(descriptor)
|
|
834
|
+
pipeline_state._common.fsync_directory(path.parent)
|
|
683
835
|
|
|
684
836
|
|
|
685
837
|
def classify_undo(repo: Path) -> dict[str, object]:
|
|
@@ -821,6 +973,28 @@ def classify_undo(repo: Path) -> dict[str, object]:
|
|
|
821
973
|
proof_error = _task_proof_error(resolved, head)
|
|
822
974
|
if proof_error:
|
|
823
975
|
return _blocked([f"task landing ownership is unproven: {proof_error}"])
|
|
976
|
+
record = isolation.MEMBER_RECORD_RE.search(body)
|
|
977
|
+
if record is not None:
|
|
978
|
+
try:
|
|
979
|
+
_require_no_member_landing_journal(resolved)
|
|
980
|
+
member_parent = _member_undo_parent(resolved, record["member"], record["landing"])
|
|
981
|
+
except (IsolationError, UndoError, PipelineStateError) as error:
|
|
982
|
+
return _blocked([f"member landing undo is unsafe: {error}"])
|
|
983
|
+
return {
|
|
984
|
+
"kind": "member-task",
|
|
985
|
+
"branch": recorded_branch,
|
|
986
|
+
"head": head,
|
|
987
|
+
"parent": parent,
|
|
988
|
+
"subject": subject,
|
|
989
|
+
"member": record["member"],
|
|
990
|
+
"landing": record["landing"],
|
|
991
|
+
"member_parent": member_parent,
|
|
992
|
+
"effects": [
|
|
993
|
+
f"reset member {record['member']} bound branch to {member_parent}",
|
|
994
|
+
f"reset {recorded_branch} to {parent}",
|
|
995
|
+
],
|
|
996
|
+
"blocked": [],
|
|
997
|
+
}
|
|
824
998
|
return {
|
|
825
999
|
"kind": "task",
|
|
826
1000
|
"branch": recorded_branch,
|
|
@@ -978,6 +1152,20 @@ def apply_undo(repo: Path, kind: str, expected_head: str) -> dict[str, object]:
|
|
|
978
1152
|
discussion,
|
|
979
1153
|
)
|
|
980
1154
|
_finish_transaction(resolved, transaction)
|
|
1155
|
+
elif kind == "member-task":
|
|
1156
|
+
with isolation._member_landing_lock(resolved):
|
|
1157
|
+
_require_no_member_landing_journal(resolved)
|
|
1158
|
+
if preview(resolved)["target"] != target:
|
|
1159
|
+
raise UndoError("member task undo target changed after preview")
|
|
1160
|
+
if _worktree_changes(resolved):
|
|
1161
|
+
raise UndoError("worktree changed after member task undo preview")
|
|
1162
|
+
_require_unpublished_reset(resolved, str(target["branch"]))
|
|
1163
|
+
transaction = _prepare_transaction(
|
|
1164
|
+
resolved, kind, expected_head, str(target["parent"]), str(target["branch"]), None, None,
|
|
1165
|
+
{"member": str(target["member"]), "landing": str(target["landing"]),
|
|
1166
|
+
"member_parent": str(target["member_parent"])},
|
|
1167
|
+
)
|
|
1168
|
+
_finish_transaction_locked(resolved, transaction)
|
|
981
1169
|
elif kind == "task":
|
|
982
1170
|
parent = target["parent"]
|
|
983
1171
|
if not parent:
|
|
@@ -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,
|
|
@@ -13,10 +13,34 @@ import os
|
|
|
13
13
|
import tempfile
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
import _common
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if __package__:
|
|
17
|
+
from . import _common
|
|
18
|
+
else:
|
|
19
|
+
try:
|
|
20
|
+
import _common
|
|
21
|
+
except ImportError:
|
|
22
|
+
from scripts import _common
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _worktree_list_porcelain(primary: Path) -> tuple[str, bool]:
|
|
26
|
+
try:
|
|
27
|
+
if __package__:
|
|
28
|
+
from . import pipeline_git as pg
|
|
29
|
+
else:
|
|
30
|
+
from scripts import pipeline_git as pg
|
|
31
|
+
except ImportError:
|
|
32
|
+
try:
|
|
33
|
+
import pipeline_git as pg
|
|
34
|
+
except ImportError:
|
|
35
|
+
pg = None
|
|
36
|
+
if pg is not None:
|
|
37
|
+
return pg.worktree_list_porcelain(primary)
|
|
38
|
+
try:
|
|
39
|
+
output = _git(primary, "worktree", "list", "--porcelain", "-z")
|
|
40
|
+
return output, True
|
|
41
|
+
except ValueError:
|
|
42
|
+
output = _git(primary, "worktree", "list", "--porcelain")
|
|
43
|
+
return output, False
|
|
20
44
|
|
|
21
45
|
|
|
22
46
|
def _git(primary: Path, *args: str) -> str:
|
|
@@ -27,7 +51,10 @@ def _git(primary: Path, *args: str) -> str:
|
|
|
27
51
|
|
|
28
52
|
|
|
29
53
|
def _identity(path: Path) -> str:
|
|
30
|
-
|
|
54
|
+
digest = hashlib.sha256(os.fsencode(path)).hexdigest()
|
|
55
|
+
# Two identities nest in every managed path; full digests would use about
|
|
56
|
+
# 130 of Windows' 260-character MAX_PATH before any repository content.
|
|
57
|
+
return digest[:16] if os.name == "nt" else digest
|
|
31
58
|
|
|
32
59
|
|
|
33
60
|
def _workspace(primary: Path, pin: bool) -> Path:
|
|
@@ -65,7 +92,7 @@ def _workspace(primary: Path, pin: bool) -> Path:
|
|
|
65
92
|
receipt.parent.mkdir(parents=True, exist_ok=True)
|
|
66
93
|
fd, temporary = tempfile.mkstemp(dir=receipt.parent)
|
|
67
94
|
try:
|
|
68
|
-
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
95
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
|
|
69
96
|
json.dump({"primary": str(primary), "root": str(workspace)}, handle)
|
|
70
97
|
try:
|
|
71
98
|
os.link(temporary, receipt)
|
|
@@ -76,17 +103,52 @@ def _workspace(primary: Path, pin: bool) -> Path:
|
|
|
76
103
|
return workspace
|
|
77
104
|
|
|
78
105
|
|
|
106
|
+
def release_workspace(primary: Path) -> bool:
|
|
107
|
+
"""Forget the pinned placement receipt once no linked worktrees remain.
|
|
108
|
+
|
|
109
|
+
Lets GSD_PATH_WORKTREE_ROOT (or a new default) apply again at the next
|
|
110
|
+
milestone's first allocation. Returns True when a receipt was removed;
|
|
111
|
+
any remaining linked worktree (including one only still registered) keeps
|
|
112
|
+
the pin.
|
|
113
|
+
"""
|
|
114
|
+
primary = primary.resolve()
|
|
115
|
+
common = Path(_git(primary, "rev-parse", "--git-common-dir"))
|
|
116
|
+
common = (primary / common).resolve()
|
|
117
|
+
receipt = common / "gsd-path" / "workspaces" / f"{_identity(primary)}.json"
|
|
118
|
+
if not receipt.is_file():
|
|
119
|
+
return False
|
|
120
|
+
output, nul_separated = _worktree_list_porcelain(primary)
|
|
121
|
+
records = output.split("\0\0") if nul_separated else [
|
|
122
|
+
block for block in output.strip().split("\n\n") if block.strip()
|
|
123
|
+
]
|
|
124
|
+
for record in records:
|
|
125
|
+
fields = record.split("\0") if nul_separated else record.splitlines()
|
|
126
|
+
for field in fields:
|
|
127
|
+
if field.startswith("worktree ") and Path(field[len("worktree "):]).resolve() != primary:
|
|
128
|
+
return False
|
|
129
|
+
receipt.unlink()
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
|
|
79
133
|
def worktree_path(primary: Path, kind: str, name: str, *, pin: bool = False) -> Path:
|
|
80
134
|
primary = primary.resolve()
|
|
81
|
-
if kind not in {"task", "verify", "integrate"} or not name or Path(name).name != name or name in {".", ".."}:
|
|
135
|
+
if kind not in {"task", "verify", "integrate", "bound"} or not name or Path(name).name != name or name in {".", ".."}:
|
|
82
136
|
raise ValueError("invalid managed worktree kind or name")
|
|
137
|
+
if kind == "bound":
|
|
138
|
+
return _workspace(primary, pin) / kind / name
|
|
83
139
|
legacy = (primary.parent / f".{primary.name}-gsd-path-integrate-{name}"
|
|
84
140
|
if kind == "integrate" else primary.parent / f"{primary.name}.gsd-path" / kind / name)
|
|
85
141
|
branch = f"refs/heads/gsd-path-{kind}/{name}"
|
|
86
|
-
|
|
142
|
+
output, nul_separated = _worktree_list_porcelain(primary)
|
|
143
|
+
if nul_separated:
|
|
144
|
+
records = output.split("\0\0")
|
|
145
|
+
else:
|
|
146
|
+
records = [block for block in output.strip().split("\n\n") if block.strip()]
|
|
87
147
|
for record in records:
|
|
88
|
-
fields = record.split("\0")
|
|
89
|
-
|
|
148
|
+
fields = record.split("\0") if nul_separated else record.splitlines()
|
|
149
|
+
# Compare as paths: git prints forward slashes on Windows.
|
|
150
|
+
worktrees = [Path(field[len("worktree "):]) for field in fields if field.startswith("worktree ")]
|
|
151
|
+
if legacy in worktrees and f"branch {branch}" in fields:
|
|
90
152
|
return legacy
|
|
91
153
|
# Keep collision checks and missing-directory recovery at the old location.
|
|
92
154
|
if os.path.lexists(legacy):
|