@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
|
@@ -24,13 +24,15 @@ from datetime import date
|
|
|
24
24
|
from pathlib import Path, PurePosixPath
|
|
25
25
|
from typing import Iterator, NamedTuple, Optional, Sequence
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
from isolation import (
|
|
27
|
+
if __package__:
|
|
28
|
+
from .isolation import (
|
|
29
29
|
IsolationError,
|
|
30
|
+
prune_host_scratch,
|
|
31
|
+
PROJECT_ENTRIES,
|
|
30
32
|
checkpoint as isolation_checkpoint,
|
|
31
33
|
verify_landed_task_files,
|
|
32
34
|
)
|
|
33
|
-
from pipeline_git import (
|
|
35
|
+
from .pipeline_git import (
|
|
34
36
|
bound_branch_name,
|
|
35
37
|
default_branch_name,
|
|
36
38
|
integrate_commit_body,
|
|
@@ -43,45 +45,75 @@ try:
|
|
|
43
45
|
ship_commit_body,
|
|
44
46
|
ship_subject,
|
|
45
47
|
)
|
|
46
|
-
from review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
|
|
47
|
-
from pipeline_state import (
|
|
48
|
+
from .review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
|
|
49
|
+
from .pipeline_state import (
|
|
48
50
|
PipelineState,
|
|
49
51
|
PipelineStateError,
|
|
50
52
|
load_state,
|
|
51
53
|
transition_state,
|
|
52
54
|
)
|
|
53
|
-
import _common
|
|
54
|
-
except ImportError: # pragma: no cover - package import used by tests
|
|
55
|
-
from scripts.isolation import (
|
|
56
|
-
IsolationError,
|
|
57
|
-
checkpoint as isolation_checkpoint,
|
|
58
|
-
verify_landed_task_files,
|
|
59
|
-
)
|
|
60
|
-
from scripts.pipeline_git import (
|
|
61
|
-
bound_branch_name,
|
|
62
|
-
default_branch_name,
|
|
63
|
-
integrate_commit_body,
|
|
64
|
-
integrate_subject,
|
|
65
|
-
is_bound_branch,
|
|
66
|
-
is_integrate_subject,
|
|
67
|
-
is_ship_subject,
|
|
68
|
-
milestone_id,
|
|
69
|
-
milestone_number,
|
|
70
|
-
ship_commit_body,
|
|
71
|
-
ship_subject,
|
|
72
|
-
)
|
|
73
|
-
from scripts.review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
|
|
74
|
-
from scripts.pipeline_state import (
|
|
75
|
-
PipelineState,
|
|
76
|
-
PipelineStateError,
|
|
77
|
-
load_state,
|
|
78
|
-
transition_state,
|
|
79
|
-
)
|
|
80
|
-
from scripts import _common
|
|
81
|
-
|
|
82
|
-
if sys.platform == "win32":
|
|
83
|
-
import msvcrt
|
|
55
|
+
from . import _common
|
|
84
56
|
else:
|
|
57
|
+
try:
|
|
58
|
+
from isolation import (
|
|
59
|
+
IsolationError,
|
|
60
|
+
prune_host_scratch,
|
|
61
|
+
PROJECT_ENTRIES,
|
|
62
|
+
checkpoint as isolation_checkpoint,
|
|
63
|
+
verify_landed_task_files,
|
|
64
|
+
)
|
|
65
|
+
from pipeline_git import (
|
|
66
|
+
bound_branch_name,
|
|
67
|
+
default_branch_name,
|
|
68
|
+
integrate_commit_body,
|
|
69
|
+
integrate_subject,
|
|
70
|
+
is_bound_branch,
|
|
71
|
+
is_integrate_subject,
|
|
72
|
+
is_ship_subject,
|
|
73
|
+
milestone_id,
|
|
74
|
+
milestone_number,
|
|
75
|
+
ship_commit_body,
|
|
76
|
+
ship_subject,
|
|
77
|
+
)
|
|
78
|
+
from review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
|
|
79
|
+
from pipeline_state import (
|
|
80
|
+
PipelineState,
|
|
81
|
+
PipelineStateError,
|
|
82
|
+
load_state,
|
|
83
|
+
transition_state,
|
|
84
|
+
)
|
|
85
|
+
import _common
|
|
86
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
87
|
+
from scripts.isolation import (
|
|
88
|
+
IsolationError,
|
|
89
|
+
prune_host_scratch,
|
|
90
|
+
PROJECT_ENTRIES,
|
|
91
|
+
checkpoint as isolation_checkpoint,
|
|
92
|
+
verify_landed_task_files,
|
|
93
|
+
)
|
|
94
|
+
from scripts.pipeline_git import (
|
|
95
|
+
bound_branch_name,
|
|
96
|
+
default_branch_name,
|
|
97
|
+
integrate_commit_body,
|
|
98
|
+
integrate_subject,
|
|
99
|
+
is_bound_branch,
|
|
100
|
+
is_integrate_subject,
|
|
101
|
+
is_ship_subject,
|
|
102
|
+
milestone_id,
|
|
103
|
+
milestone_number,
|
|
104
|
+
ship_commit_body,
|
|
105
|
+
ship_subject,
|
|
106
|
+
)
|
|
107
|
+
from scripts.review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
|
|
108
|
+
from scripts.pipeline_state import (
|
|
109
|
+
PipelineState,
|
|
110
|
+
PipelineStateError,
|
|
111
|
+
load_state,
|
|
112
|
+
transition_state,
|
|
113
|
+
)
|
|
114
|
+
from scripts import _common
|
|
115
|
+
|
|
116
|
+
if sys.platform != "win32":
|
|
85
117
|
import fcntl
|
|
86
118
|
|
|
87
119
|
|
|
@@ -350,31 +382,25 @@ else: # standalone script or sibling import
|
|
|
350
382
|
@contextmanager
|
|
351
383
|
def discussion_lock(active_root: Path) -> Iterator[None]:
|
|
352
384
|
if sys.platform == "win32":
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
"resolve discussion lock",
|
|
385
|
+
# Windows cannot flock a directory, so lock a file in the git dir,
|
|
386
|
+
# where it never shows up as an untracked project file.
|
|
387
|
+
resolved = run_git(
|
|
388
|
+
active_root.parent,
|
|
389
|
+
"rev-parse",
|
|
390
|
+
"--git-path",
|
|
391
|
+
"gsd-path-discussion.lock",
|
|
361
392
|
)
|
|
362
|
-
|
|
393
|
+
if resolved.returncode != 0 or not resolved.stdout.strip():
|
|
394
|
+
# Outside git, which the POSIX directory lock allows: the same
|
|
395
|
+
# named kernel mutex pipeline_state holds for this directory.
|
|
396
|
+
with _common.directory_mutex(active_root):
|
|
397
|
+
yield
|
|
398
|
+
return
|
|
399
|
+
lock_path = Path(resolved.stdout.strip())
|
|
363
400
|
if not lock_path.is_absolute():
|
|
364
401
|
lock_path = active_root.parent / lock_path
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
handle.seek(0, os.SEEK_END)
|
|
368
|
-
if handle.tell() == 0:
|
|
369
|
-
handle.write(b"\0")
|
|
370
|
-
handle.flush()
|
|
371
|
-
handle.seek(0)
|
|
372
|
-
msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
|
|
373
|
-
try:
|
|
374
|
-
yield
|
|
375
|
-
finally:
|
|
376
|
-
handle.seek(0)
|
|
377
|
-
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
|
|
402
|
+
with _common.exclusive_lock(lock_path):
|
|
403
|
+
yield
|
|
378
404
|
return
|
|
379
405
|
|
|
380
406
|
descriptor = os.open(active_root, os.O_RDONLY)
|
|
@@ -400,10 +426,10 @@ def frontmatter_value(content: str, key: str) -> Optional[str]:
|
|
|
400
426
|
for line in lines[1:]:
|
|
401
427
|
if line == "---":
|
|
402
428
|
break
|
|
403
|
-
match = re.match(rf"^{re.escape(key)}
|
|
429
|
+
match = re.match(rf"^{re.escape(key)}:(.*)$", line)
|
|
404
430
|
if match:
|
|
405
|
-
value = match.group(1)
|
|
406
|
-
return value
|
|
431
|
+
value = _common.strip_yaml_comment(match.group(1))
|
|
432
|
+
return value.strip().strip('"').strip("'")
|
|
407
433
|
return None
|
|
408
434
|
|
|
409
435
|
|
|
@@ -535,6 +561,8 @@ def archive_sequence_entries(project: Path) -> dict[int, Path]:
|
|
|
535
561
|
archive_root = safe_archive_root(project)
|
|
536
562
|
entries = {}
|
|
537
563
|
for candidate in archive_root.iterdir():
|
|
564
|
+
if _common.is_ignored_junk(candidate):
|
|
565
|
+
continue
|
|
538
566
|
match = ARCHIVE_PATTERN.fullmatch(candidate.name)
|
|
539
567
|
if match is None or not candidate.is_dir():
|
|
540
568
|
raise ArchiveError(f"archive root has a noncanonical entry: {candidate.name}")
|
|
@@ -558,14 +586,11 @@ def resolved_archive_target(
|
|
|
558
586
|
expected_number = milestone_number(branch)
|
|
559
587
|
configured = parsed_state.archive
|
|
560
588
|
entries = archive_sequence_entries(project)
|
|
561
|
-
missing_prior = [
|
|
562
|
-
number for number in range(1, expected_number) if number not in entries
|
|
563
|
-
]
|
|
564
589
|
later = sorted(number for number in entries if number > expected_number)
|
|
565
|
-
if
|
|
590
|
+
if later:
|
|
566
591
|
raise ArchiveError(
|
|
567
592
|
f"archive sequence does not match bound branch {branch}: "
|
|
568
|
-
f"
|
|
593
|
+
f"later={later}"
|
|
569
594
|
)
|
|
570
595
|
|
|
571
596
|
if configured is not None:
|
|
@@ -620,7 +645,7 @@ def pending_ruling_count(audit: Path) -> int:
|
|
|
620
645
|
|
|
621
646
|
|
|
622
647
|
def is_existing_carry_forward(source: Path, archived: Path) -> bool:
|
|
623
|
-
entries =
|
|
648
|
+
entries = [path for path in source.iterdir() if not _common.is_ignored_junk(path)] if source.is_dir() else []
|
|
624
649
|
archived_audit = archived / "DOCS-AUDIT.md"
|
|
625
650
|
if not archived_audit.is_file() or pending_ruling_count(archived_audit) == 0:
|
|
626
651
|
return False
|
|
@@ -681,7 +706,7 @@ def is_real_file(path: Path) -> bool:
|
|
|
681
706
|
def canonical_task_files(tasks: Path) -> Sequence[Path]:
|
|
682
707
|
if tasks.is_symlink() or not tasks.is_dir():
|
|
683
708
|
return ()
|
|
684
|
-
candidates = sorted(tasks.iterdir())
|
|
709
|
+
candidates = sorted(path for path in tasks.iterdir() if not _common.is_ignored_junk(path))
|
|
685
710
|
if any(
|
|
686
711
|
not TASK_FILE_PATTERN.fullmatch(path.name) or not is_real_file(path)
|
|
687
712
|
for path in candidates
|
|
@@ -1001,6 +1026,7 @@ def prepare(repo: Path, slug: str) -> dict:
|
|
|
1001
1026
|
project = repo.resolve()
|
|
1002
1027
|
active_root = require_project_layout(project)
|
|
1003
1028
|
with discussion_lock(active_root):
|
|
1029
|
+
prune_host_scratch(active_root)
|
|
1004
1030
|
return prepare_locked(project, active_root, slug)
|
|
1005
1031
|
|
|
1006
1032
|
|
|
@@ -1036,9 +1062,10 @@ def prepare_locked(project: Path, active_root: Path, slug: str) -> dict:
|
|
|
1036
1062
|
final = active_root / "review" / "FINAL.md"
|
|
1037
1063
|
reviewed = re.search(r"(?m)^Reviewed HEAD:\s*(\S+)", final.read_text(encoding="utf-8")) if final.is_file() else None
|
|
1038
1064
|
head = require_git_success(run_git(project, "rev-parse", "HEAD"), "resolve HEAD").strip()
|
|
1039
|
-
if reviewed and reviewed.group(1)
|
|
1065
|
+
if reviewed and not _common.reviewed_head_covers(project, reviewed.group(1), head):
|
|
1040
1066
|
raise ArchiveError(
|
|
1041
1067
|
f"FINAL.md Reviewed HEAD {reviewed.group(1)} does not match current HEAD {head}; "
|
|
1068
|
+
f"{_common.RUNTIME_PIN_ALLOWANCE}; "
|
|
1042
1069
|
"re-run the final review on the current commit before archiving"
|
|
1043
1070
|
)
|
|
1044
1071
|
target = resolved_archive_target(project, slug, parsed_state)
|
|
@@ -1131,13 +1158,7 @@ def require_canonical_abandon_inputs(active_root: Path, archive: Path) -> None:
|
|
|
1131
1158
|
def write_abandon_manifest(
|
|
1132
1159
|
archive: Path, slug: str, ruling: str, abandoned_on: str
|
|
1133
1160
|
) -> None:
|
|
1134
|
-
contents =
|
|
1135
|
-
for path in archive.rglob("*"):
|
|
1136
|
-
if path.is_symlink():
|
|
1137
|
-
raise ArchiveError(f"archive contents must not be symlinks: {path}")
|
|
1138
|
-
if path.is_file() and path.name != "MANIFEST.md":
|
|
1139
|
-
contents.append(path.relative_to(archive).as_posix())
|
|
1140
|
-
contents.sort()
|
|
1161
|
+
contents = archive_file_inventory(archive)
|
|
1141
1162
|
listed_contents = "\n".join(f"- {path}" for path in contents)
|
|
1142
1163
|
atomic_replace(
|
|
1143
1164
|
archive / "MANIFEST.md",
|
|
@@ -1657,6 +1678,7 @@ def abandon_locked(
|
|
|
1657
1678
|
|
|
1658
1679
|
|
|
1659
1680
|
run_git = _common.run_git
|
|
1681
|
+
reviewed_head_covers = _common.reviewed_head_covers
|
|
1660
1682
|
run_command = _common.run_command
|
|
1661
1683
|
|
|
1662
1684
|
|
|
@@ -1693,10 +1715,13 @@ def require_clean_older_archives(project: Path, configured: str) -> None:
|
|
|
1693
1715
|
current_prefix = f"{configured}/"
|
|
1694
1716
|
dirty = set()
|
|
1695
1717
|
ignored_current = set()
|
|
1718
|
+
current_files = {f"{configured}/{name}" for name in archive_file_inventory(project / configured)}
|
|
1696
1719
|
for record in status.split("\0"):
|
|
1697
1720
|
has_status = len(record) > 3 and record[2] == " "
|
|
1698
1721
|
code = record[:2] if has_status else ""
|
|
1699
1722
|
path = record[3:] if has_status else record
|
|
1723
|
+
if code == "!!" and _common.is_ignored_junk(project / path) and path not in current_files:
|
|
1724
|
+
continue # Ignored OS junk never ships and is not an artifact.
|
|
1700
1725
|
if code == "!!" and (
|
|
1701
1726
|
path == ".project/archive/"
|
|
1702
1727
|
or path == configured
|
|
@@ -1745,7 +1770,7 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
|
|
|
1745
1770
|
archived_audit = archive / "research" / "DOCS-AUDIT.md"
|
|
1746
1771
|
carried_forward = pending_ruling_count(archived_audit)
|
|
1747
1772
|
# Persistent project metadata ships but never archives with a milestone.
|
|
1748
|
-
allowed = {"STATE.md", "REPOSITORY.md", "archive", "LESSONS.md", "next"}
|
|
1773
|
+
allowed = {"STATE.md", "REPOSITORY.md", "MEMBERS.md", "archive", "LESSONS.md", "next"}
|
|
1749
1774
|
for name in ("config.json", "model-policy.json"):
|
|
1750
1775
|
path = active_root / name
|
|
1751
1776
|
if path.exists() or path.is_symlink():
|
|
@@ -1760,7 +1785,9 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
|
|
|
1760
1785
|
if carried_forward:
|
|
1761
1786
|
allowed.add("research")
|
|
1762
1787
|
active_audit = active_research / "DOCS-AUDIT.md"
|
|
1763
|
-
entries = sorted(
|
|
1788
|
+
entries = sorted(
|
|
1789
|
+
path.name for path in active_research.iterdir() if not _common.is_ignored_junk(path)
|
|
1790
|
+
) if active_research.is_dir() else []
|
|
1764
1791
|
if (
|
|
1765
1792
|
active_research.is_symlink()
|
|
1766
1793
|
or entries != ["DOCS-AUDIT.md"]
|
|
@@ -1772,7 +1799,12 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
|
|
|
1772
1799
|
elif active_research.exists() or active_research.is_symlink():
|
|
1773
1800
|
raise ArchiveError("active research exists without a pending carry-forward queue")
|
|
1774
1801
|
|
|
1775
|
-
|
|
1802
|
+
# Ignored, untracked non-pipeline entries (.DS_Store) never ship; pipeline entries block even when ignored.
|
|
1803
|
+
visible = _common.git_visible_entries(active_root)
|
|
1804
|
+
unexpected = sorted(
|
|
1805
|
+
path.name for path in active_root.iterdir()
|
|
1806
|
+
if path.name not in allowed and (path.name in PROJECT_ENTRIES or path.name in visible)
|
|
1807
|
+
)
|
|
1776
1808
|
if unexpected:
|
|
1777
1809
|
raise ArchiveError(f"unexpected active .project paths remain: {', '.join(unexpected)}")
|
|
1778
1810
|
|
|
@@ -1848,6 +1880,12 @@ def prepared_transaction(
|
|
|
1848
1880
|
|
|
1849
1881
|
|
|
1850
1882
|
def preflight(repo: Path) -> dict:
|
|
1883
|
+
project = repo.resolve()
|
|
1884
|
+
active_root = require_project_layout(project)
|
|
1885
|
+
configured = strict_state(project)[0].archive
|
|
1886
|
+
current = (archive_from_state(project, configured)
|
|
1887
|
+
if configured and not archive_is_committed(project, configured) else None)
|
|
1888
|
+
prune_host_scratch(active_root, current)
|
|
1851
1889
|
project, _, state, configured, archive, _, reviewed_head = prepared_transaction(
|
|
1852
1890
|
repo,
|
|
1853
1891
|
("ship", "shipped"),
|
|
@@ -1860,9 +1898,10 @@ def preflight(repo: Path) -> dict:
|
|
|
1860
1898
|
require_stageable_carry_forward(project, archive)
|
|
1861
1899
|
require_clean_older_archives(project, configured)
|
|
1862
1900
|
head = require_git_success(run_git(project, "rev-parse", "HEAD"), "resolve preflight HEAD")
|
|
1863
|
-
if reviewed_head
|
|
1901
|
+
if not reviewed_head_covers(project, reviewed_head, head):
|
|
1864
1902
|
raise ArchiveError(
|
|
1865
|
-
f"FINAL.md Reviewed HEAD {reviewed_head} does not match current HEAD {head}"
|
|
1903
|
+
f"FINAL.md Reviewed HEAD {reviewed_head} does not match current HEAD {head}; "
|
|
1904
|
+
f"{_common.RUNTIME_PIN_ALLOWANCE}"
|
|
1866
1905
|
)
|
|
1867
1906
|
return {
|
|
1868
1907
|
"archive": configured,
|
|
@@ -1937,6 +1976,28 @@ def validate_shipped_roadmap(text: str, state: PipelineState, archive: str) -> N
|
|
|
1937
1976
|
raise ArchiveError("ROADMAP.md must record Status: shipped and the exact Archive")
|
|
1938
1977
|
|
|
1939
1978
|
|
|
1979
|
+
def member_ship_rows(project: Path, archive: Path, ship_commit: str) -> list:
|
|
1980
|
+
"""Ship body member lines, bound to the archived lock order and FINAL.md member heads."""
|
|
1981
|
+
if __package__:
|
|
1982
|
+
from . import check_handoffs
|
|
1983
|
+
from .pipeline_git import ship_member_rows
|
|
1984
|
+
else:
|
|
1985
|
+
import check_handoffs
|
|
1986
|
+
from pipeline_git import ship_member_rows
|
|
1987
|
+
message = require_git_success(run_git(project, "show", "-s", "--format=%B", ship_commit), "inspect ship body")
|
|
1988
|
+
rows = ship_member_rows(message)
|
|
1989
|
+
lock = archive / "build" / "members.json"
|
|
1990
|
+
locked = [entry["name"] for entry in json.loads(lock.read_text(encoding="utf-8"))["members"]] if lock.is_file() else []
|
|
1991
|
+
try:
|
|
1992
|
+
heads = check_handoffs.member_reviewed_heads((archive / "review" / "FINAL.md").read_text(encoding="utf-8"),
|
|
1993
|
+
"FINAL.md")
|
|
1994
|
+
except check_handoffs.HandoffError as error:
|
|
1995
|
+
raise ArchiveError(str(error)) from error
|
|
1996
|
+
if [row["name"] for row in rows] != locked or {row["name"]: row["reviewed_head"] for row in rows} != heads:
|
|
1997
|
+
raise ArchiveError("ship commit member lines must name each locked member at its FINAL.md reviewed HEAD")
|
|
1998
|
+
return rows
|
|
1999
|
+
|
|
2000
|
+
|
|
1940
2001
|
def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
1941
2002
|
project, _, state, configured, archive, archived_files, reviewed_head = prepared_transaction(
|
|
1942
2003
|
repo,
|
|
@@ -2030,16 +2091,18 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
|
2030
2091
|
)
|
|
2031
2092
|
require_committed_carry_forward(project, configured, archive, ship_commit)
|
|
2032
2093
|
|
|
2094
|
+
members = member_ship_rows(project, archive, ship_commit)
|
|
2033
2095
|
ship_parent = require_git_success(run_git(project, "rev-parse", f"{ship_commit}^"), "resolve ship parent")
|
|
2034
|
-
if reviewed_head
|
|
2096
|
+
if not reviewed_head_covers(project, reviewed_head, ship_parent):
|
|
2035
2097
|
raise ArchiveError(
|
|
2036
|
-
f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}"
|
|
2098
|
+
f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}; "
|
|
2099
|
+
f"{_common.RUNTIME_PIN_ALLOWANCE}"
|
|
2037
2100
|
)
|
|
2038
2101
|
require_canonical_commit_body(
|
|
2039
2102
|
project,
|
|
2040
2103
|
ship_commit,
|
|
2041
2104
|
ship_subject(archive.name),
|
|
2042
|
-
ship_commit_body(configured, reviewed_head),
|
|
2105
|
+
ship_commit_body(configured, reviewed_head, members),
|
|
2043
2106
|
"ship",
|
|
2044
2107
|
)
|
|
2045
2108
|
|
|
@@ -2077,7 +2140,10 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
|
2077
2140
|
+ f"; run: git -C {project} revert <commits after {ship_commit[:12]} that touch .project>"
|
|
2078
2141
|
)
|
|
2079
2142
|
|
|
2080
|
-
|
|
2143
|
+
result = {"archive": configured, "commit": ship_commit}
|
|
2144
|
+
if members:
|
|
2145
|
+
result["members"] = members
|
|
2146
|
+
return result
|
|
2081
2147
|
|
|
2082
2148
|
|
|
2083
2149
|
def parser() -> argparse.ArgumentParser:
|
|
@@ -2119,6 +2185,12 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2119
2185
|
)
|
|
2120
2186
|
refresh_parser.add_argument("--repo", required=True, type=Path)
|
|
2121
2187
|
|
|
2188
|
+
close_members_parser = subparsers.add_parser(
|
|
2189
|
+
"close-members",
|
|
2190
|
+
help="after preflight, integrate each locked member in lock order before the ship commit",
|
|
2191
|
+
)
|
|
2192
|
+
close_members_parser.add_argument("--repo", required=True, type=Path)
|
|
2193
|
+
|
|
2122
2194
|
integrate_parser = subparsers.add_parser(
|
|
2123
2195
|
"integrate",
|
|
2124
2196
|
help="resume and publish the shipped milestone integration",
|
|
@@ -2139,7 +2211,7 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2139
2211
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
2140
2212
|
arguments = parser().parse_args(argv)
|
|
2141
2213
|
try:
|
|
2142
|
-
if arguments.command in {"validate-integrated", "refresh-origin", "integrate"}:
|
|
2214
|
+
if arguments.command in {"validate-integrated", "refresh-origin", "integrate", "close-members"}:
|
|
2143
2215
|
if __package__:
|
|
2144
2216
|
from . import integration
|
|
2145
2217
|
else:
|
|
@@ -2156,6 +2228,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2156
2228
|
result = integration.validate_integrated(arguments.repo, arguments.slug)
|
|
2157
2229
|
elif arguments.command == "refresh-origin":
|
|
2158
2230
|
result = integration.refresh_origin(arguments.repo)
|
|
2231
|
+
elif arguments.command == "close-members":
|
|
2232
|
+
result = integration.close_members(arguments.repo)
|
|
2159
2233
|
elif arguments.command == "integrate":
|
|
2160
2234
|
result = integration.integrate(arguments.repo, arguments.slug)
|
|
2161
2235
|
else:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# gsd-path project runtime
|
|
2
|
-
"""Guard
|
|
2
|
+
"""Guard return to Define or Plan using a committed recovery base."""
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
@@ -17,7 +17,9 @@ EVENTS = {
|
|
|
17
17
|
"define": "build intent corrections requested",
|
|
18
18
|
"plan": "build plan repair requested",
|
|
19
19
|
}
|
|
20
|
+
PLAN_INTENT_EVENT = "plan intent corrections requested"
|
|
20
21
|
MARKER = "build recovery: "
|
|
22
|
+
RECOVERY_KEYS = frozenset({"base", "branch", "kind", "source"})
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def runtime():
|
|
@@ -36,9 +38,42 @@ def checkpoint_runtime():
|
|
|
36
38
|
return state_checkpoint
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
def _parse_recovery_record(value: object) -> dict:
|
|
42
|
+
if (
|
|
43
|
+
not isinstance(value, dict)
|
|
44
|
+
or not {"base", "branch", "kind"}.issubset(value)
|
|
45
|
+
or not set(value).issubset(RECOVERY_KEYS)
|
|
46
|
+
or not isinstance(value["base"], str)
|
|
47
|
+
or not re.fullmatch(r"[0-9a-f]{40}", value["base"])
|
|
48
|
+
):
|
|
49
|
+
raise runtime().PipelineStateError("invalid build recovery record")
|
|
50
|
+
source = value.get("source", "build")
|
|
51
|
+
if source not in {"build", "plan"}:
|
|
52
|
+
raise runtime().PipelineStateError("invalid build recovery record")
|
|
53
|
+
if source == "plan":
|
|
54
|
+
if value["kind"] != "define":
|
|
55
|
+
raise runtime().PipelineStateError("invalid build recovery record")
|
|
56
|
+
elif value["kind"] not in EVENTS:
|
|
57
|
+
raise runtime().PipelineStateError("invalid build recovery record")
|
|
58
|
+
return {**value, "source": source}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _recovery_base_proves_milestone(before, current, source: str) -> bool:
|
|
62
|
+
if before.branch != current.branch or before.milestone != current.milestone:
|
|
63
|
+
return False
|
|
64
|
+
if source == "plan":
|
|
65
|
+
return (before.phase, before.status) in {("plan", "active"), ("plan", "blocked")}
|
|
66
|
+
return (before.phase, before.status) == ("build", "blocked")
|
|
67
|
+
|
|
68
|
+
|
|
39
69
|
def context(repo: Path, text: str | None = None) -> dict | None:
|
|
40
70
|
state = runtime()
|
|
41
|
-
|
|
71
|
+
try:
|
|
72
|
+
current, loaded, _ = state.load_state(repo)
|
|
73
|
+
except state.PipelineStateError as error:
|
|
74
|
+
if "STATE.md must be a real file" not in str(error):
|
|
75
|
+
raise
|
|
76
|
+
return None
|
|
42
77
|
text = loaded if text is None else text
|
|
43
78
|
recovery = None
|
|
44
79
|
for line in text.splitlines():
|
|
@@ -47,26 +82,24 @@ def context(repo: Path, text: str | None = None) -> dict | None:
|
|
|
47
82
|
event = line.split(" — ", 2)[-1]
|
|
48
83
|
if event.startswith(MARKER):
|
|
49
84
|
try:
|
|
50
|
-
value = json.loads(event[len(MARKER):])
|
|
85
|
+
value = _parse_recovery_record(json.loads(event[len(MARKER):]))
|
|
51
86
|
except ValueError as error:
|
|
52
87
|
raise state.PipelineStateError("invalid build recovery record") from error
|
|
53
|
-
if (not isinstance(value, dict) or set(value) != {"base", "branch", "kind"}
|
|
54
|
-
or value["kind"] not in EVENTS
|
|
55
|
-
or not isinstance(value["base"], str)
|
|
56
|
-
or not re.fullmatch(r"[0-9a-f]{40}", value["base"])):
|
|
57
|
-
raise state.PipelineStateError("invalid build recovery record")
|
|
58
88
|
recovery = {**value, "active": True} if value["branch"] == current.branch else None
|
|
59
|
-
elif
|
|
60
|
-
recovery
|
|
89
|
+
elif recovery and recovery.get("active"):
|
|
90
|
+
source = recovery.get("source", "build")
|
|
91
|
+
if source == "build" and event == "build started":
|
|
92
|
+
recovery["active"] = False
|
|
93
|
+
elif source == "plan" and event == "plan approved":
|
|
94
|
+
recovery["active"] = False
|
|
61
95
|
if recovery:
|
|
62
96
|
if not state._is_ancestor(repo, recovery["base"], "HEAD"):
|
|
63
97
|
raise state.PipelineStateError("build recovery base is not an ancestor of HEAD")
|
|
64
|
-
|
|
65
|
-
if
|
|
98
|
+
base_text = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/STATE.md")
|
|
99
|
+
if base_text is None:
|
|
66
100
|
raise state.PipelineStateError("build recovery base has no STATE.md")
|
|
67
|
-
before = state._state_from_text(
|
|
68
|
-
if (
|
|
69
|
-
or before.branch != current.branch or before.milestone != current.milestone):
|
|
101
|
+
before = state._state_from_text(base_text)
|
|
102
|
+
if not _recovery_base_proves_milestone(before, current, recovery.get("source", "build")):
|
|
70
103
|
raise state.PipelineStateError("build recovery base does not prove this blocked milestone")
|
|
71
104
|
return recovery
|
|
72
105
|
|
|
@@ -90,6 +123,8 @@ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
|
|
|
90
123
|
raise state.PipelineStateError("build recovery requires real task artifacts")
|
|
91
124
|
tasks = {}
|
|
92
125
|
for path in sorted(directory.iterdir()):
|
|
126
|
+
if state._common.is_ignored_junk(path):
|
|
127
|
+
continue
|
|
93
128
|
if not re.fullmatch(r"T[0-9]{3}-[a-z0-9][a-z0-9-]*\.md", path.name):
|
|
94
129
|
raise state.PipelineStateError(f"invalid recovery task path: {path.name}")
|
|
95
130
|
text = state._read_real_file(path, path.name)
|
|
@@ -106,18 +141,20 @@ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
|
|
|
106
141
|
return tasks
|
|
107
142
|
|
|
108
143
|
|
|
109
|
-
def
|
|
144
|
+
def _begin_common(repo: Path, before, event: str) -> str:
|
|
110
145
|
state = runtime()
|
|
111
|
-
if (before.phase, before.status) != ("build", "blocked") or after.phase not in EVENTS:
|
|
112
|
-
raise state.PipelineStateError("build recovery requires build/blocked")
|
|
113
|
-
if event != EVENTS[after.phase]:
|
|
114
|
-
raise state.PipelineStateError(f"state transition requires event: {EVENTS[after.phase]}")
|
|
115
146
|
if before.archive is not None or before.branch != state._current_branch(repo):
|
|
116
147
|
raise state.PipelineStateError("build recovery requires the active bound branch")
|
|
117
148
|
if state._worktree_changes(repo):
|
|
118
149
|
raise state.PipelineStateError("checkpoint the blocked build before recovery")
|
|
119
150
|
if any(state.transaction_journals(repo).values()):
|
|
120
151
|
raise state.PipelineStateError("finish pending transactions before build recovery")
|
|
152
|
+
state._read_real_file(repo / ".project/intent/INTENT.md", "INTENT.md")
|
|
153
|
+
return state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _settle_build_dispatch(repo: Path) -> None:
|
|
157
|
+
state = runtime()
|
|
121
158
|
records = records_root(repo)
|
|
122
159
|
for directory in {path.parent.parent for path in records.rglob("attempt-*/state.json")}:
|
|
123
160
|
latest = max(directory.glob("attempt-*/state.json"), key=lambda path: int(path.parent.name.split("-")[1]))
|
|
@@ -126,19 +163,49 @@ def begin(repo: Path, before, after, event: str) -> dict:
|
|
|
126
163
|
or (record.get("worktree") and record.get("branch") and not record.get("cleanup_complete"))
|
|
127
164
|
or record.get("outcome") not in {"landed", "collected", "blocked", "resolved", "redispatched"}):
|
|
128
165
|
raise state.PipelineStateError(f"settle dispatch records before recovery: {latest}")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _verify_done_tasks(repo: Path, tasks: dict[str, tuple[str, dict]], base: str) -> None:
|
|
169
|
+
state = runtime()
|
|
133
170
|
done = [repo / ".project/tasks" / name for name, (_, fields) in tasks.items()
|
|
134
171
|
if fields["status"] == "done"]
|
|
135
|
-
if done:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
172
|
+
if not done:
|
|
173
|
+
return
|
|
174
|
+
if __package__:
|
|
175
|
+
from scripts.isolation import verify_landed_task_files
|
|
176
|
+
else:
|
|
177
|
+
from isolation import verify_landed_task_files
|
|
178
|
+
verify_landed_task_files(repo, done, ".project/tasks", base)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def begin(repo: Path, before, after, event: str) -> dict:
|
|
182
|
+
state = runtime()
|
|
183
|
+
if (before.phase, before.status) == ("build", "blocked") and after.phase in EVENTS:
|
|
184
|
+
if event != EVENTS[after.phase]:
|
|
185
|
+
raise state.PipelineStateError(f"state transition requires event: {EVENTS[after.phase]}")
|
|
186
|
+
_settle_build_dispatch(repo)
|
|
187
|
+
tasks = settled_tasks(repo)
|
|
188
|
+
state._read_real_file(repo / ".project/plan/PLAN.md", "PLAN.md")
|
|
189
|
+
base = _begin_common(repo, before, event)
|
|
190
|
+
_verify_done_tasks(repo, tasks, base)
|
|
191
|
+
return {"kind": after.phase, "base": base, "branch": before.branch, "source": "build"}
|
|
192
|
+
if (
|
|
193
|
+
before.phase == "plan"
|
|
194
|
+
and before.status in {"active", "blocked"}
|
|
195
|
+
and (after.phase, after.status) == ("define", "active")
|
|
196
|
+
):
|
|
197
|
+
if event != PLAN_INTENT_EVENT:
|
|
198
|
+
raise state.PipelineStateError(f"state transition requires event: {PLAN_INTENT_EVENT}")
|
|
199
|
+
state._read_real_file(repo / ".project/plan/PLAN.md", "PLAN.md")
|
|
200
|
+
tasks_dir = repo / ".project/tasks"
|
|
201
|
+
tasks = settled_tasks(repo) if tasks_dir.is_dir() and any(
|
|
202
|
+
path.is_file() and not state._common.is_ignored_junk(path)
|
|
203
|
+
for path in tasks_dir.iterdir()
|
|
204
|
+
) else {}
|
|
205
|
+
base = _begin_common(repo, before, event)
|
|
206
|
+
_verify_done_tasks(repo, tasks, base)
|
|
207
|
+
return {"kind": "define", "base": base, "branch": before.branch, "source": "plan"}
|
|
208
|
+
raise state.PipelineStateError("build recovery requires build/blocked")
|
|
142
209
|
|
|
143
210
|
|
|
144
211
|
def records_root(repo: Path) -> Path:
|
|
@@ -165,7 +232,8 @@ def validate_review_backup(repo: Path, recovery: dict, directory: Path) -> None:
|
|
|
165
232
|
if path:
|
|
166
233
|
expected[str(Path(path).relative_to(".project/review"))] = checkpoint_runtime()._git_text_at(repo, recovery["base"], path)
|
|
167
234
|
actual = {str(path.relative_to(directory)): path.read_text(encoding="utf-8")
|
|
168
|
-
for path in directory.rglob("*")
|
|
235
|
+
for path in directory.rglob("*")
|
|
236
|
+
if path.is_file() and not state._common.is_ignored_junk(path)} if directory.exists() else {}
|
|
169
237
|
if actual != expected:
|
|
170
238
|
raise state.PipelineStateError("review backup differs from the recovery base")
|
|
171
239
|
|