@opengsd/gsd-path 1.3.1 → 1.4.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 +281 -0
- package/DOCS.md +13 -7
- package/FULL.md +9 -3
- package/GUIDE.md +1 -1
- package/HOOKS.md +9 -6
- package/QUICK.md +4 -1
- package/README.md +63 -11
- package/RUNTIME.md +1 -1
- package/UPDATE.md +57 -8
- package/WORKFLOW.md +22 -6
- package/package.json +9 -8
- package/scripts/_common.py +472 -19
- package/scripts/archive_milestone.py +146 -73
- package/scripts/bootstrap_repository.py +8 -5
- package/scripts/build_recovery.py +4 -1
- package/scripts/build_state.py +66 -23
- package/scripts/check_docs_audit.py +14 -3
- package/scripts/check_handoffs.py +116 -20
- package/scripts/check_task_briefs.py +60 -8
- 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 +345 -15
- package/scripts/discussion_records.py +19 -13
- package/scripts/discussion_validate.py +66 -21
- package/scripts/dispatch_driver.py +111 -26
- package/scripts/git_guard.py +116 -29
- package/scripts/guard_hook.py +151 -29
- package/scripts/install.mjs +54 -38
- package/scripts/install.py +562 -41
- package/scripts/integration.py +434 -24
- package/scripts/isolation.py +1077 -28
- package/scripts/lean_verification.py +99 -19
- package/scripts/loop_run.py +20 -30
- package/scripts/members.py +704 -0
- package/scripts/migrate_core.py +2 -2
- package/scripts/path_config.py +2 -2
- package/scripts/pipeline_diagnose.py +82 -31
- package/scripts/pipeline_git.py +61 -22
- package/scripts/pipeline_state.py +176 -54
- package/scripts/pipeline_undo.py +117 -22
- package/scripts/promote_lookahead.py +1 -1
- package/scripts/review_panel.py +1 -1
- package/scripts/runtime_store.py +35 -9
- package/scripts/skill-resources.json +28 -3
- package/scripts/state_checkpoint.py +140 -31
- package/scripts/state_promote.py +2 -0
- package/scripts/status_runtime.py +36 -6
- package/scripts/sync_skill_resources.py +8 -7
- package/scripts/token_budget.py +12 -8
- package/scripts/workflow_run.py +103 -29
- package/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path/BUILD.md +7 -1
- package/skills/gsd-path/DECIDE.md +11 -4
- package/skills/gsd-path/DEFINE.md +8 -1
- package/skills/gsd-path/DOCS-AUDIT.md +6 -4
- package/skills/gsd-path/INSPECT.md +5 -4
- package/skills/gsd-path/PLAN.md +7 -1
- package/skills/gsd-path/RESEARCH.md +2 -1
- package/skills/gsd-path/ROADMAP.md +2 -1
- package/skills/gsd-path/SHIP.md +27 -10
- package/skills/gsd-path/SKILL.md +45 -1
- package/skills/gsd-path/references/build-native.md +47 -14
- package/skills/gsd-path/references/codebase-mapper.md +2 -1
- package/skills/gsd-path/references/docs-auditor.md +2 -1
- package/skills/gsd-path/references/operating-rules.md +214 -0
- package/skills/gsd-path/references/planner.md +8 -0
- package/skills/gsd-path/references/reviewer-wave.md +3 -1
- 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/scripts/_common.py +472 -19
- package/skills/gsd-path/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
- package/skills/gsd-path/scripts/build_recovery.py +4 -1
- package/skills/gsd-path/scripts/build_state.py +66 -23
- package/skills/gsd-path/scripts/check_docs_audit.py +14 -3
- package/skills/gsd-path/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path/scripts/check_update.py +2 -2
- package/skills/gsd-path/scripts/detect_project.py +345 -15
- package/skills/gsd-path/scripts/discussion_records.py +19 -13
- package/skills/gsd-path/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path/scripts/dispatch_driver.py +111 -26
- package/skills/gsd-path/scripts/git_guard.py +116 -29
- package/skills/gsd-path/scripts/integration.py +434 -24
- package/skills/gsd-path/scripts/isolation.py +1077 -28
- package/skills/gsd-path/scripts/lean_verification.py +99 -19
- package/skills/gsd-path/scripts/loop_run.py +20 -30
- package/skills/gsd-path/scripts/members.py +704 -0
- package/skills/gsd-path/scripts/path_config.py +2 -2
- package/skills/gsd-path/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path/scripts/state_promote.py +2 -0
- package/skills/gsd-path/scripts/status_runtime.py +36 -6
- package/skills/gsd-path/scripts/token_budget.py +12 -8
- package/skills/gsd-path/scripts/workflow_run.py +103 -29
- package/skills/gsd-path/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path/templates/codebase.md +1 -1
- package/skills/gsd-path/templates/docs-audit.md +1 -1
- package/skills/gsd-path/templates/final-review.md +5 -2
- package/skills/gsd-path/templates/gap-review.md +2 -1
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +3 -1
- package/skills/gsd-path-build/SKILL.md +7 -1
- package/skills/gsd-path-build/references/build-native.md +47 -14
- package/skills/gsd-path-build/references/operating-rules.md +214 -0
- package/skills/gsd-path-build/references/reviewer-wave.md +3 -1
- 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-build/scripts/_common.py +472 -19
- package/skills/gsd-path-build/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-build/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-build/scripts/build_state.py +66 -23
- package/skills/gsd-path-build/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-build/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-build/scripts/detect_project.py +345 -15
- package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-build/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-build/scripts/dispatch_driver.py +111 -26
- package/skills/gsd-path-build/scripts/integration.py +434 -24
- package/skills/gsd-path-build/scripts/isolation.py +1077 -28
- package/skills/gsd-path-build/scripts/loop_run.py +20 -30
- package/skills/gsd-path-build/scripts/members.py +704 -0
- package/skills/gsd-path-build/scripts/path_config.py +2 -2
- package/skills/gsd-path-build/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-build/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-build/scripts/state_promote.py +2 -0
- package/skills/gsd-path-build/scripts/token_budget.py +12 -8
- package/skills/gsd-path-build/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-build/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +3 -1
- package/skills/gsd-path-decide/SKILL.md +11 -4
- package/skills/gsd-path-decide/references/operating-rules.md +214 -0
- package/skills/gsd-path-decide/scripts/_common.py +472 -19
- package/skills/gsd-path-decide/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-decide/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-decide/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-decide/scripts/detect_project.py +345 -15
- package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-decide/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-decide/scripts/integration.py +434 -24
- package/skills/gsd-path-decide/scripts/isolation.py +1077 -28
- package/skills/gsd-path-decide/scripts/loop_run.py +20 -30
- package/skills/gsd-path-decide/scripts/members.py +704 -0
- package/skills/gsd-path-decide/scripts/path_config.py +2 -2
- package/skills/gsd-path-decide/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-decide/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
- package/skills/gsd-path-decide/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-define/SKILL.md +8 -1
- package/skills/gsd-path-define/references/operating-rules.md +214 -0
- package/skills/gsd-path-define/scripts/_common.py +472 -19
- package/skills/gsd-path-define/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-define/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-define/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-define/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-define/scripts/detect_project.py +345 -15
- package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-define/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-define/scripts/integration.py +434 -24
- package/skills/gsd-path-define/scripts/isolation.py +1077 -28
- package/skills/gsd-path-define/scripts/loop_run.py +20 -30
- package/skills/gsd-path-define/scripts/members.py +704 -0
- package/skills/gsd-path-define/scripts/path_config.py +2 -2
- package/skills/gsd-path-define/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-define/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-define/scripts/state_promote.py +2 -0
- package/skills/gsd-path-define/scripts/worktree_paths.py +18 -8
- 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 +472 -19
- package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-discuss/scripts/loop_run.py +20 -30
- package/skills/gsd-path-discuss/scripts/path_config.py +2 -2
- package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-docs-audit/SKILL.md +6 -4
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +2 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +472 -19
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +14 -3
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +345 -15
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-docs-audit/scripts/integration.py +434 -24
- package/skills/gsd-path-docs-audit/scripts/isolation.py +1077 -28
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +20 -30
- package/skills/gsd-path-docs-audit/scripts/members.py +704 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +2 -2
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-docs-audit/templates/docs-audit.md +1 -1
- 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 +472 -19
- package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-forensics/scripts/loop_run.py +20 -30
- package/skills/gsd-path-forensics/scripts/path_config.py +2 -2
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +117 -22
- package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +18 -8
- 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/docs-auditor.md +2 -1
- package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/_common.py +472 -19
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-inspect/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +14 -3
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-inspect/scripts/detect_project.py +345 -15
- package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-inspect/scripts/integration.py +434 -24
- package/skills/gsd-path-inspect/scripts/isolation.py +1077 -28
- package/skills/gsd-path-inspect/scripts/loop_run.py +20 -30
- package/skills/gsd-path-inspect/scripts/members.py +704 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +2 -2
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
- package/skills/gsd-path-inspect/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-inspect/templates/codebase.md +1 -1
- package/skills/gsd-path-inspect/templates/docs-audit.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 +472 -19
- package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-loop/scripts/loop_run.py +20 -30
- package/skills/gsd-path-loop/scripts/path_config.py +2 -2
- package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
- package/skills/gsd-path-loop/scripts/worktree_paths.py +18 -8
- 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 +472 -19
- 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 +20 -30
- package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
- package/skills/gsd-path-migrate/scripts/path_config.py +2 -2
- package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/SKILL.md +7 -1
- package/skills/gsd-path-plan/references/operating-rules.md +214 -0
- package/skills/gsd-path-plan/references/planner.md +8 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/_common.py +472 -19
- package/skills/gsd-path-plan/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-plan/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-plan/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-plan/scripts/detect_project.py +345 -15
- package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-plan/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-plan/scripts/integration.py +434 -24
- package/skills/gsd-path-plan/scripts/isolation.py +1077 -28
- package/skills/gsd-path-plan/scripts/loop_run.py +20 -30
- package/skills/gsd-path-plan/scripts/members.py +704 -0
- package/skills/gsd-path-plan/scripts/path_config.py +2 -2
- package/skills/gsd-path-plan/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-plan/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
- package/skills/gsd-path-plan/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-plan/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/SKILL.md +2 -1
- package/skills/gsd-path-research/references/operating-rules.md +214 -0
- package/skills/gsd-path-research/scripts/_common.py +472 -19
- package/skills/gsd-path-research/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-research/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-research/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-research/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-research/scripts/detect_project.py +345 -15
- package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-research/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-research/scripts/integration.py +434 -24
- package/skills/gsd-path-research/scripts/isolation.py +1077 -28
- package/skills/gsd-path-research/scripts/loop_run.py +20 -30
- package/skills/gsd-path-research/scripts/members.py +704 -0
- package/skills/gsd-path-research/scripts/path_config.py +2 -2
- package/skills/gsd-path-research/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-research/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-research/scripts/state_promote.py +2 -0
- package/skills/gsd-path-research/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-roadmap/SKILL.md +2 -1
- package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +472 -19
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-roadmap/scripts/detect_project.py +345 -15
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-roadmap/scripts/integration.py +434 -24
- package/skills/gsd-path-roadmap/scripts/isolation.py +1077 -28
- package/skills/gsd-path-roadmap/scripts/loop_run.py +20 -30
- package/skills/gsd-path-roadmap/scripts/members.py +704 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +2 -2
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-ship/SKILL.md +27 -10
- package/skills/gsd-path-ship/references/operating-rules.md +214 -0
- package/skills/gsd-path-ship/references/reviewer-wave.md +3 -1
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/_common.py +472 -19
- package/skills/gsd-path-ship/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-ship/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-ship/scripts/build_state.py +66 -23
- package/skills/gsd-path-ship/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-ship/scripts/detect_project.py +345 -15
- package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-ship/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-ship/scripts/integration.py +434 -24
- package/skills/gsd-path-ship/scripts/isolation.py +1077 -28
- package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
- package/skills/gsd-path-ship/scripts/loop_run.py +20 -30
- package/skills/gsd-path-ship/scripts/members.py +704 -0
- package/skills/gsd-path-ship/scripts/path_config.py +2 -2
- package/skills/gsd-path-ship/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-ship/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
- package/skills/gsd-path-ship/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-ship/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-ship/templates/final-review.md +5 -2
- package/skills/gsd-path-ship/templates/gap-review.md +2 -1
- package/skills/gsd-path-ship/templates/wave-review.md +3 -1
- 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 +472 -19
- package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-undo/scripts/loop_run.py +20 -30
- package/skills/gsd-path-undo/scripts/path_config.py +2 -2
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +117 -22
- package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
- package/skills/gsd-path-undo/scripts/worktree_paths.py +18 -8
- package/skills/path/BUILD.md +7 -1
- package/skills/path/DECIDE.md +11 -4
- package/skills/path/DEFINE.md +8 -1
- package/skills/path/DOCS-AUDIT.md +6 -4
- package/skills/path/INSPECT.md +5 -4
- package/skills/path/PLAN.md +7 -1
- package/skills/path/RESEARCH.md +2 -1
- package/skills/path/ROADMAP.md +2 -1
- package/skills/path/SHIP.md +27 -10
- package/skills/path/SKILL.md +45 -1
- package/skills/path/references/build-native.md +47 -14
- package/skills/path/references/codebase-mapper.md +2 -1
- package/skills/path/references/docs-auditor.md +2 -1
- package/skills/path/references/operating-rules.md +214 -0
- package/skills/path/references/planner.md +8 -0
- package/skills/path/references/reviewer-wave.md +3 -1
- package/skills/path/scripts/_common.py +472 -19
- package/skills/path/scripts/archive_milestone.py +146 -73
- package/skills/path/scripts/bootstrap_repository.py +8 -5
- package/skills/path/scripts/build_recovery.py +4 -1
- package/skills/path/scripts/build_state.py +66 -23
- package/skills/path/scripts/check_docs_audit.py +14 -3
- package/skills/path/scripts/check_handoffs.py +116 -20
- package/skills/path/scripts/check_task_briefs.py +60 -8
- package/skills/path/scripts/check_update.py +2 -2
- package/skills/path/scripts/detect_project.py +345 -15
- package/skills/path/scripts/discussion_records.py +19 -13
- package/skills/path/scripts/discussion_validate.py +66 -21
- package/skills/path/scripts/dispatch_driver.py +111 -26
- package/skills/path/scripts/git_guard.py +116 -29
- package/skills/path/scripts/integration.py +434 -24
- package/skills/path/scripts/isolation.py +1077 -28
- package/skills/path/scripts/lean_verification.py +99 -19
- package/skills/path/scripts/loop_run.py +20 -30
- package/skills/path/scripts/members.py +704 -0
- package/skills/path/scripts/path_config.py +2 -2
- package/skills/path/scripts/pipeline_git.py +61 -22
- package/skills/path/scripts/pipeline_state.py +176 -54
- 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 +140 -31
- package/skills/path/scripts/state_promote.py +2 -0
- package/skills/path/scripts/status_runtime.py +36 -6
- package/skills/path/scripts/token_budget.py +12 -8
- package/skills/path/scripts/workflow_run.py +103 -29
- package/skills/path/scripts/worktree_paths.py +18 -8
- package/skills/path/templates/codebase.md +1 -1
- package/skills/path/templates/docs-audit.md +1 -1
- package/skills/path/templates/final-review.md +5 -2
- package/skills/path/templates/gap-review.md +2 -1
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +3 -1
|
@@ -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)
|
|
@@ -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}")
|
|
@@ -620,7 +648,7 @@ def pending_ruling_count(audit: Path) -> int:
|
|
|
620
648
|
|
|
621
649
|
|
|
622
650
|
def is_existing_carry_forward(source: Path, archived: Path) -> bool:
|
|
623
|
-
entries =
|
|
651
|
+
entries = [path for path in source.iterdir() if not _common.is_ignored_junk(path)] if source.is_dir() else []
|
|
624
652
|
archived_audit = archived / "DOCS-AUDIT.md"
|
|
625
653
|
if not archived_audit.is_file() or pending_ruling_count(archived_audit) == 0:
|
|
626
654
|
return False
|
|
@@ -681,7 +709,7 @@ def is_real_file(path: Path) -> bool:
|
|
|
681
709
|
def canonical_task_files(tasks: Path) -> Sequence[Path]:
|
|
682
710
|
if tasks.is_symlink() or not tasks.is_dir():
|
|
683
711
|
return ()
|
|
684
|
-
candidates = sorted(tasks.iterdir())
|
|
712
|
+
candidates = sorted(path for path in tasks.iterdir() if not _common.is_ignored_junk(path))
|
|
685
713
|
if any(
|
|
686
714
|
not TASK_FILE_PATTERN.fullmatch(path.name) or not is_real_file(path)
|
|
687
715
|
for path in candidates
|
|
@@ -1001,6 +1029,7 @@ def prepare(repo: Path, slug: str) -> dict:
|
|
|
1001
1029
|
project = repo.resolve()
|
|
1002
1030
|
active_root = require_project_layout(project)
|
|
1003
1031
|
with discussion_lock(active_root):
|
|
1032
|
+
prune_host_scratch(active_root)
|
|
1004
1033
|
return prepare_locked(project, active_root, slug)
|
|
1005
1034
|
|
|
1006
1035
|
|
|
@@ -1131,13 +1160,7 @@ def require_canonical_abandon_inputs(active_root: Path, archive: Path) -> None:
|
|
|
1131
1160
|
def write_abandon_manifest(
|
|
1132
1161
|
archive: Path, slug: str, ruling: str, abandoned_on: str
|
|
1133
1162
|
) -> 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()
|
|
1163
|
+
contents = archive_file_inventory(archive)
|
|
1141
1164
|
listed_contents = "\n".join(f"- {path}" for path in contents)
|
|
1142
1165
|
atomic_replace(
|
|
1143
1166
|
archive / "MANIFEST.md",
|
|
@@ -1693,10 +1716,13 @@ def require_clean_older_archives(project: Path, configured: str) -> None:
|
|
|
1693
1716
|
current_prefix = f"{configured}/"
|
|
1694
1717
|
dirty = set()
|
|
1695
1718
|
ignored_current = set()
|
|
1719
|
+
current_files = {f"{configured}/{name}" for name in archive_file_inventory(project / configured)}
|
|
1696
1720
|
for record in status.split("\0"):
|
|
1697
1721
|
has_status = len(record) > 3 and record[2] == " "
|
|
1698
1722
|
code = record[:2] if has_status else ""
|
|
1699
1723
|
path = record[3:] if has_status else record
|
|
1724
|
+
if code == "!!" and _common.is_ignored_junk(project / path) and path not in current_files:
|
|
1725
|
+
continue # Ignored OS junk never ships and is not an artifact.
|
|
1700
1726
|
if code == "!!" and (
|
|
1701
1727
|
path == ".project/archive/"
|
|
1702
1728
|
or path == configured
|
|
@@ -1745,7 +1771,7 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
|
|
|
1745
1771
|
archived_audit = archive / "research" / "DOCS-AUDIT.md"
|
|
1746
1772
|
carried_forward = pending_ruling_count(archived_audit)
|
|
1747
1773
|
# Persistent project metadata ships but never archives with a milestone.
|
|
1748
|
-
allowed = {"STATE.md", "REPOSITORY.md", "archive", "LESSONS.md", "next"}
|
|
1774
|
+
allowed = {"STATE.md", "REPOSITORY.md", "MEMBERS.md", "archive", "LESSONS.md", "next"}
|
|
1749
1775
|
for name in ("config.json", "model-policy.json"):
|
|
1750
1776
|
path = active_root / name
|
|
1751
1777
|
if path.exists() or path.is_symlink():
|
|
@@ -1760,7 +1786,9 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
|
|
|
1760
1786
|
if carried_forward:
|
|
1761
1787
|
allowed.add("research")
|
|
1762
1788
|
active_audit = active_research / "DOCS-AUDIT.md"
|
|
1763
|
-
entries = sorted(
|
|
1789
|
+
entries = sorted(
|
|
1790
|
+
path.name for path in active_research.iterdir() if not _common.is_ignored_junk(path)
|
|
1791
|
+
) if active_research.is_dir() else []
|
|
1764
1792
|
if (
|
|
1765
1793
|
active_research.is_symlink()
|
|
1766
1794
|
or entries != ["DOCS-AUDIT.md"]
|
|
@@ -1772,7 +1800,12 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
|
|
|
1772
1800
|
elif active_research.exists() or active_research.is_symlink():
|
|
1773
1801
|
raise ArchiveError("active research exists without a pending carry-forward queue")
|
|
1774
1802
|
|
|
1775
|
-
|
|
1803
|
+
# Ignored, untracked non-pipeline entries (.DS_Store) never ship; pipeline entries block even when ignored.
|
|
1804
|
+
visible = _common.git_visible_entries(active_root)
|
|
1805
|
+
unexpected = sorted(
|
|
1806
|
+
path.name for path in active_root.iterdir()
|
|
1807
|
+
if path.name not in allowed and (path.name in PROJECT_ENTRIES or path.name in visible)
|
|
1808
|
+
)
|
|
1776
1809
|
if unexpected:
|
|
1777
1810
|
raise ArchiveError(f"unexpected active .project paths remain: {', '.join(unexpected)}")
|
|
1778
1811
|
|
|
@@ -1848,6 +1881,12 @@ def prepared_transaction(
|
|
|
1848
1881
|
|
|
1849
1882
|
|
|
1850
1883
|
def preflight(repo: Path) -> dict:
|
|
1884
|
+
project = repo.resolve()
|
|
1885
|
+
active_root = require_project_layout(project)
|
|
1886
|
+
configured = strict_state(project)[0].archive
|
|
1887
|
+
current = (archive_from_state(project, configured)
|
|
1888
|
+
if configured and not archive_is_committed(project, configured) else None)
|
|
1889
|
+
prune_host_scratch(active_root, current)
|
|
1851
1890
|
project, _, state, configured, archive, _, reviewed_head = prepared_transaction(
|
|
1852
1891
|
repo,
|
|
1853
1892
|
("ship", "shipped"),
|
|
@@ -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,6 +2091,7 @@ 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
2096
|
if reviewed_head != ship_parent:
|
|
2035
2097
|
raise ArchiveError(
|
|
@@ -2039,7 +2101,7 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
|
2039
2101
|
project,
|
|
2040
2102
|
ship_commit,
|
|
2041
2103
|
ship_subject(archive.name),
|
|
2042
|
-
ship_commit_body(configured, reviewed_head),
|
|
2104
|
+
ship_commit_body(configured, reviewed_head, members),
|
|
2043
2105
|
"ship",
|
|
2044
2106
|
)
|
|
2045
2107
|
|
|
@@ -2077,7 +2139,10 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
|
2077
2139
|
+ f"; run: git -C {project} revert <commits after {ship_commit[:12]} that touch .project>"
|
|
2078
2140
|
)
|
|
2079
2141
|
|
|
2080
|
-
|
|
2142
|
+
result = {"archive": configured, "commit": ship_commit}
|
|
2143
|
+
if members:
|
|
2144
|
+
result["members"] = members
|
|
2145
|
+
return result
|
|
2081
2146
|
|
|
2082
2147
|
|
|
2083
2148
|
def parser() -> argparse.ArgumentParser:
|
|
@@ -2119,6 +2184,12 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2119
2184
|
)
|
|
2120
2185
|
refresh_parser.add_argument("--repo", required=True, type=Path)
|
|
2121
2186
|
|
|
2187
|
+
close_members_parser = subparsers.add_parser(
|
|
2188
|
+
"close-members",
|
|
2189
|
+
help="after preflight, integrate each locked member in lock order before the ship commit",
|
|
2190
|
+
)
|
|
2191
|
+
close_members_parser.add_argument("--repo", required=True, type=Path)
|
|
2192
|
+
|
|
2122
2193
|
integrate_parser = subparsers.add_parser(
|
|
2123
2194
|
"integrate",
|
|
2124
2195
|
help="resume and publish the shipped milestone integration",
|
|
@@ -2139,7 +2210,7 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2139
2210
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
2140
2211
|
arguments = parser().parse_args(argv)
|
|
2141
2212
|
try:
|
|
2142
|
-
if arguments.command in {"validate-integrated", "refresh-origin", "integrate"}:
|
|
2213
|
+
if arguments.command in {"validate-integrated", "refresh-origin", "integrate", "close-members"}:
|
|
2143
2214
|
if __package__:
|
|
2144
2215
|
from . import integration
|
|
2145
2216
|
else:
|
|
@@ -2156,6 +2227,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2156
2227
|
result = integration.validate_integrated(arguments.repo, arguments.slug)
|
|
2157
2228
|
elif arguments.command == "refresh-origin":
|
|
2158
2229
|
result = integration.refresh_origin(arguments.repo)
|
|
2230
|
+
elif arguments.command == "close-members":
|
|
2231
|
+
result = integration.close_members(arguments.repo)
|
|
2159
2232
|
elif arguments.command == "integrate":
|
|
2160
2233
|
result = integration.integrate(arguments.repo, arguments.slug)
|
|
2161
2234
|
else:
|
|
@@ -90,6 +90,8 @@ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
|
|
|
90
90
|
raise state.PipelineStateError("build recovery requires real task artifacts")
|
|
91
91
|
tasks = {}
|
|
92
92
|
for path in sorted(directory.iterdir()):
|
|
93
|
+
if state._common.is_ignored_junk(path):
|
|
94
|
+
continue
|
|
93
95
|
if not re.fullmatch(r"T[0-9]{3}-[a-z0-9][a-z0-9-]*\.md", path.name):
|
|
94
96
|
raise state.PipelineStateError(f"invalid recovery task path: {path.name}")
|
|
95
97
|
text = state._read_real_file(path, path.name)
|
|
@@ -165,7 +167,8 @@ def validate_review_backup(repo: Path, recovery: dict, directory: Path) -> None:
|
|
|
165
167
|
if path:
|
|
166
168
|
expected[str(Path(path).relative_to(".project/review"))] = checkpoint_runtime()._git_text_at(repo, recovery["base"], path)
|
|
167
169
|
actual = {str(path.relative_to(directory)): path.read_text(encoding="utf-8")
|
|
168
|
-
for path in directory.rglob("*")
|
|
170
|
+
for path in directory.rglob("*")
|
|
171
|
+
if path.is_file() and not state._common.is_ignored_junk(path)} if directory.exists() else {}
|
|
169
172
|
if actual != expected:
|
|
170
173
|
raise state.PipelineStateError("review backup differs from the recovery base")
|
|
171
174
|
|
|
@@ -13,12 +13,16 @@ sys.dont_write_bytecode = True
|
|
|
13
13
|
from pathlib import Path, PurePosixPath
|
|
14
14
|
from typing import Dict, List, Optional, Sequence, Set, Tuple
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
from pipeline_state import PipelineStateError, load_state
|
|
18
|
-
import _common
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
if __package__:
|
|
17
|
+
from .pipeline_state import PipelineStateError, load_state
|
|
18
|
+
from . import _common
|
|
19
|
+
else:
|
|
20
|
+
try:
|
|
21
|
+
from pipeline_state import PipelineStateError, load_state
|
|
22
|
+
import _common
|
|
23
|
+
except ImportError: # pragma: no cover - package imports used by tests
|
|
24
|
+
from scripts.pipeline_state import PipelineStateError, load_state
|
|
25
|
+
from scripts import _common
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
PIPELINE = "gsd-path/v2"
|
|
@@ -377,7 +381,10 @@ def validate_research(
|
|
|
377
381
|
) -> Dict[str, object]:
|
|
378
382
|
"""Validate the research-to-synthesis hand-off and return its summary."""
|
|
379
383
|
|
|
380
|
-
|
|
384
|
+
# Research runs this before closing; decide runs it after its entry transition.
|
|
385
|
+
_require_state_one_of(
|
|
386
|
+
root, {("research", "active"), ("decide", "active")}, project_dir
|
|
387
|
+
)
|
|
381
388
|
return validate_research_artifacts(root, project_dir)
|
|
382
389
|
|
|
383
390
|
|
|
@@ -548,6 +555,45 @@ def _reviewed_head(text: str, source: str) -> str:
|
|
|
548
555
|
return value
|
|
549
556
|
|
|
550
557
|
|
|
558
|
+
MEMBER_HEAD_PATTERN = re.compile(r"(?m)^Member reviewed HEAD:(.*)$")
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def member_reviewed_heads(text: str, source: str) -> Dict[str, str]:
|
|
562
|
+
"""`Member reviewed HEAD: <member> <sha>` lines, one per member of a multi-repo milestone."""
|
|
563
|
+
heads: Dict[str, str] = {}
|
|
564
|
+
# Only the header binds heads; command output quoted in later sections cannot add one.
|
|
565
|
+
for value in MEMBER_HEAD_PATTERN.findall(_strip_comments(text).split("\n## ", 1)[0]):
|
|
566
|
+
name, _, sha = value.strip().partition(" ")
|
|
567
|
+
if not name or not SHA_PATTERN.fullmatch(sha.strip()):
|
|
568
|
+
raise HandoffError(
|
|
569
|
+
f"{source} Member reviewed HEAD must be '<member> <full commit SHA>', not {value.strip() or 'empty'}"
|
|
570
|
+
)
|
|
571
|
+
if name in heads:
|
|
572
|
+
raise HandoffError(f"{source} repeats Member reviewed HEAD for {name}")
|
|
573
|
+
heads[name] = sha.strip()
|
|
574
|
+
return heads
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def _member_tips(root: Path) -> Dict[str, str]:
|
|
578
|
+
"""Each locked member's bound branch tip; empty when the milestone locks no member."""
|
|
579
|
+
try:
|
|
580
|
+
import isolation
|
|
581
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
582
|
+
from scripts import isolation
|
|
583
|
+
lock = root / ".project/build/members.json"
|
|
584
|
+
if not lock.is_file():
|
|
585
|
+
return {}
|
|
586
|
+
tips: Dict[str, str] = {}
|
|
587
|
+
try:
|
|
588
|
+
for item in json.loads(lock.read_text(encoding="utf-8"))["members"]:
|
|
589
|
+
checkout, _, entry = isolation._member_context(root, item["name"])
|
|
590
|
+
tips[entry["name"]] = isolation.git_output(
|
|
591
|
+
checkout, "rev-parse", "--verify", f"refs/heads/{entry['branch']}^{{commit}}")
|
|
592
|
+
except isolation.IsolationError as error:
|
|
593
|
+
raise HandoffError(str(error)) from error
|
|
594
|
+
return tips
|
|
595
|
+
|
|
596
|
+
|
|
551
597
|
def _strip_comments(text: str) -> str:
|
|
552
598
|
return COMMENT_PATTERN.sub("", text)
|
|
553
599
|
|
|
@@ -750,6 +796,8 @@ def _task_texts(root: Path, project_dir: str) -> Dict[str, str]:
|
|
|
750
796
|
raise HandoffError(f"missing {project_dir}/tasks")
|
|
751
797
|
tasks: Dict[str, str] = {}
|
|
752
798
|
for path in sorted(tasks_dir.iterdir()):
|
|
799
|
+
if _common.is_ignored_junk(path):
|
|
800
|
+
continue
|
|
753
801
|
named = TASK_FILE_NAME.fullmatch(path.name)
|
|
754
802
|
if named is None or not path.is_file() or path.is_symlink():
|
|
755
803
|
raise HandoffError(f"non-canonical task artifact: {path.name}")
|
|
@@ -1008,7 +1056,10 @@ def _validate_task_graph(
|
|
|
1008
1056
|
graph: Dict[str, List[str]] = {}
|
|
1009
1057
|
task_waves: Dict[str, int] = {}
|
|
1010
1058
|
task_files: Dict[str, List[str]] = {}
|
|
1059
|
+
# `repo:` names the member a task changes; absent means the coordinator.
|
|
1060
|
+
task_repos: Dict[str, str] = {}
|
|
1011
1061
|
for task_id, text in tasks.items():
|
|
1062
|
+
task_repos[task_id] = str(_strict_frontmatter(text, task_id).get("repo") or "")
|
|
1012
1063
|
# Patch mode reopens planning beside landed tasks; only new tasks start clean.
|
|
1013
1064
|
landed = _task_scalar(text, task_id, "status") == "done"
|
|
1014
1065
|
_require_task_structure(task_id, text, initial=initial and not landed)
|
|
@@ -1028,7 +1079,7 @@ def _validate_task_graph(
|
|
|
1028
1079
|
|
|
1029
1080
|
for index, left in enumerate(task_ids):
|
|
1030
1081
|
for right in task_ids[index + 1 :]:
|
|
1031
|
-
if task_waves[left] != task_waves[right]:
|
|
1082
|
+
if task_waves[left] != task_waves[right] or task_repos[left] != task_repos[right]:
|
|
1032
1083
|
continue
|
|
1033
1084
|
overlap = sorted(set(task_files[left]) & set(task_files[right]))
|
|
1034
1085
|
if overlap:
|
|
@@ -1039,6 +1090,7 @@ def _validate_task_graph(
|
|
|
1039
1090
|
visiting: Set[str] = set()
|
|
1040
1091
|
visited: Set[str] = set()
|
|
1041
1092
|
dependency_files: Dict[str, Set[str]] = {}
|
|
1093
|
+
dependency_pairs: Dict[str, Set[Tuple[str, str]]] = {}
|
|
1042
1094
|
|
|
1043
1095
|
def visit(task_id: str) -> None:
|
|
1044
1096
|
if task_id in visiting:
|
|
@@ -1046,11 +1098,15 @@ def _validate_task_graph(
|
|
|
1046
1098
|
if task_id in visited:
|
|
1047
1099
|
return
|
|
1048
1100
|
visiting.add(task_id)
|
|
1049
|
-
|
|
1101
|
+
dependency_pairs[task_id] = set()
|
|
1050
1102
|
for dependency in graph[task_id]:
|
|
1051
1103
|
visit(dependency)
|
|
1052
|
-
|
|
1053
|
-
|
|
1104
|
+
dependency_pairs[task_id].update((task_repos[dependency], name) for name in task_files[dependency])
|
|
1105
|
+
dependency_pairs[task_id].update(dependency_pairs[dependency])
|
|
1106
|
+
# A dependency's files are supplied only within the same repo.
|
|
1107
|
+
dependency_files[task_id] = {
|
|
1108
|
+
name for repo, name in dependency_pairs[task_id] if repo == task_repos[task_id]
|
|
1109
|
+
}
|
|
1054
1110
|
visiting.remove(task_id)
|
|
1055
1111
|
visited.add(task_id)
|
|
1056
1112
|
|
|
@@ -1212,7 +1268,7 @@ def _head_file(root: Path, relative: str) -> Optional[str]:
|
|
|
1212
1268
|
["git", "show", f"HEAD:{relative}"],
|
|
1213
1269
|
cwd=root,
|
|
1214
1270
|
capture_output=True,
|
|
1215
|
-
|
|
1271
|
+
encoding="utf-8", errors="replace",
|
|
1216
1272
|
check=False,
|
|
1217
1273
|
)
|
|
1218
1274
|
return result.stdout if result.returncode == 0 else None
|
|
@@ -1313,6 +1369,21 @@ def validate_roadmap(
|
|
|
1313
1369
|
return {"phase": "roadmap", "milestones": ids, "dependencies": dependencies}
|
|
1314
1370
|
|
|
1315
1371
|
|
|
1372
|
+
def _integrated_members(root: Path, state: Dict[str, str]) -> Set[str]:
|
|
1373
|
+
"""Members this milestone already closed; a new task for one would need a second merge."""
|
|
1374
|
+
match = re.fullmatch(r"gsd-path/M(\d{3,})", state.get("branch") or "")
|
|
1375
|
+
common = subprocess.run(["git", "-C", str(root), "rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
1376
|
+
capture_output=True, text=True, check=False)
|
|
1377
|
+
if match is None or common.returncode != 0:
|
|
1378
|
+
return set()
|
|
1379
|
+
if __package__:
|
|
1380
|
+
from . import members
|
|
1381
|
+
else:
|
|
1382
|
+
import members
|
|
1383
|
+
rows = members.milestone_member_close(Path(common.stdout.strip()), int(match.group(1)))
|
|
1384
|
+
return {row["name"] for row in rows if row.get("status") == "integrated"}
|
|
1385
|
+
|
|
1386
|
+
|
|
1316
1387
|
def validate_plan(
|
|
1317
1388
|
root: Path, project_dir: str = DEFAULT_PROJECT_DIR
|
|
1318
1389
|
) -> Dict[str, object]:
|
|
@@ -1332,6 +1403,14 @@ def validate_plan(
|
|
|
1332
1403
|
# Build readiness owns task lifecycle validation; coverage must survive landings.
|
|
1333
1404
|
state = _require_pipeline(root, project_dir)
|
|
1334
1405
|
_validate_task_graph(wave_depths, tasks, initial=state["phase"] == "plan")
|
|
1406
|
+
integrated = _integrated_members(root, state)
|
|
1407
|
+
for task_id, text in tasks.items():
|
|
1408
|
+
repo = str(_strict_frontmatter(text, task_id).get("repo") or "")
|
|
1409
|
+
if repo in integrated and _task_scalar(text, task_id, "status") != "done":
|
|
1410
|
+
raise HandoffError(
|
|
1411
|
+
f"{task_id} changes member {repo}, which is already integrated in this milestone; "
|
|
1412
|
+
"plan that change in a new milestone"
|
|
1413
|
+
)
|
|
1335
1414
|
assigned: Dict[str, Set[str]] = {task_id: set() for task_id in tasks}
|
|
1336
1415
|
covered = set()
|
|
1337
1416
|
for criterion, task_id, acceptance in rows:
|
|
@@ -1591,6 +1670,19 @@ def validate_wave_evidence(
|
|
|
1591
1670
|
}
|
|
1592
1671
|
|
|
1593
1672
|
|
|
1673
|
+
def criterion_surfaces(root: Path, project_dir: str = DEFAULT_PROJECT_DIR) -> Dict[str, str]:
|
|
1674
|
+
"""Map each surface-owned INTENT SC to the surface PLAN.md's contract lists it under."""
|
|
1675
|
+
|
|
1676
|
+
surface_of: Dict[str, str] = {}
|
|
1677
|
+
surfaces = _surfaces(_read(root, _intent_path(project_dir)), "INTENT.md")
|
|
1678
|
+
if surfaces:
|
|
1679
|
+
plan = _read(root, f"{project_dir}/plan/PLAN.md")
|
|
1680
|
+
for surface, (_task, owned) in _surface_contract(plan, surfaces).items():
|
|
1681
|
+
for criterion in owned:
|
|
1682
|
+
surface_of[criterion] = surface
|
|
1683
|
+
return surface_of
|
|
1684
|
+
|
|
1685
|
+
|
|
1594
1686
|
def validate_final(
|
|
1595
1687
|
root: Path, project_dir: str = DEFAULT_PROJECT_DIR, *, final_text: Optional[str] = None
|
|
1596
1688
|
) -> Dict[str, object]:
|
|
@@ -1599,20 +1691,14 @@ def validate_final(
|
|
|
1599
1691
|
_require_state(root, "ship", "active", project_dir)
|
|
1600
1692
|
intent = _read(root, _intent_path(project_dir))
|
|
1601
1693
|
criteria = _success_criteria(intent)
|
|
1602
|
-
surface_of
|
|
1603
|
-
surfaces = _surfaces(intent, "INTENT.md")
|
|
1604
|
-
if surfaces:
|
|
1605
|
-
plan = _read(root, f"{project_dir}/plan/PLAN.md")
|
|
1606
|
-
for surface, (_task, owned) in _surface_contract(plan, surfaces).items():
|
|
1607
|
-
for criterion in owned:
|
|
1608
|
-
surface_of[criterion] = surface
|
|
1694
|
+
surface_of = criterion_surfaces(root, project_dir)
|
|
1609
1695
|
relative = f"{project_dir}/review/FINAL.md"
|
|
1610
1696
|
text = _read(root, relative) if final_text is None else final_text
|
|
1611
1697
|
reviewed_head = _reviewed_head(text, relative)
|
|
1612
1698
|
head_result = subprocess.run(
|
|
1613
1699
|
("git", "-C", str(root), "rev-parse", "--verify", "HEAD"),
|
|
1614
1700
|
capture_output=True,
|
|
1615
|
-
|
|
1701
|
+
encoding="utf-8", errors="replace",
|
|
1616
1702
|
check=False,
|
|
1617
1703
|
)
|
|
1618
1704
|
if head_result.returncode != 0:
|
|
@@ -1623,6 +1709,14 @@ def validate_final(
|
|
|
1623
1709
|
f"FINAL.md Reviewed HEAD {reviewed_head} is not the current HEAD "
|
|
1624
1710
|
f"{current_head}; re-run the final review on the current commit"
|
|
1625
1711
|
)
|
|
1712
|
+
member_heads = member_reviewed_heads(text, relative)
|
|
1713
|
+
tips = _member_tips(root)
|
|
1714
|
+
if member_heads != tips:
|
|
1715
|
+
expected = ", ".join(f"{name} {tip}" for name, tip in tips.items()) or "none"
|
|
1716
|
+
raise HandoffError(
|
|
1717
|
+
f"FINAL.md Member reviewed HEAD lines must name each locked member head ({expected}); "
|
|
1718
|
+
"re-run the final review on the current member heads"
|
|
1719
|
+
)
|
|
1626
1720
|
overall = _line_value(text, "Overall verdict:")
|
|
1627
1721
|
if overall not in {"pass", "blocked"}:
|
|
1628
1722
|
raise HandoffError("FINAL.md Overall verdict is invalid")
|
|
@@ -1697,6 +1791,8 @@ def validate_final(
|
|
|
1697
1791
|
raise HandoffError(f"{gap_relative} heading does not match its file number")
|
|
1698
1792
|
if _reviewed_head(gap, gap_relative) != reviewed_head:
|
|
1699
1793
|
raise HandoffError(f"{gap_relative} Reviewed HEAD differs from FINAL.md")
|
|
1794
|
+
if member_reviewed_heads(gap, gap_relative) != member_heads:
|
|
1795
|
+
raise HandoffError(f"{gap_relative} Member reviewed HEAD lines differ from FINAL.md")
|
|
1700
1796
|
verdict = _line_value(gap, "Gap verdict:")
|
|
1701
1797
|
if verdict not in {"pass", "blocked"}:
|
|
1702
1798
|
raise HandoffError(f"{gap_relative} Gap verdict is invalid")
|