@opengsd/gsd-path 1.3.0 → 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 +294 -0
- package/DOCS.md +19 -10
- 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 +61 -12
- 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 +55 -39
- package/scripts/install.py +564 -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 +49 -11
- 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
|
@@ -174,13 +174,13 @@ def set_value(repo, scope, key, value, reset=False):
|
|
|
174
174
|
if scope == 'project':
|
|
175
175
|
state, _, _ = sibling('pipeline_state').load_state(repo)
|
|
176
176
|
branch = subprocess.run(['git', '-C', str(repo), 'branch', '--show-current'],
|
|
177
|
-
capture_output=True,
|
|
177
|
+
capture_output=True, encoding="utf-8", errors="replace")
|
|
178
178
|
if branch.returncode or not state.branch or branch.stdout.strip() != state.branch:
|
|
179
179
|
raise ValueError('Run the router to bind the project branch before editing settings.')
|
|
180
180
|
head = subprocess.run(['git', '-C', str(repo), 'rev-parse', '--verify', 'HEAD'], capture_output=True)
|
|
181
181
|
if head.returncode == 0:
|
|
182
182
|
closed = subprocess.run([sys.executable, '-B', str(Path(__file__).with_name('git_guard.py')),
|
|
183
|
-
'closed-milestone'], cwd=repo, capture_output=True,
|
|
183
|
+
'closed-milestone'], cwd=repo, capture_output=True, encoding="utf-8", errors="replace")
|
|
184
184
|
if closed.returncode:
|
|
185
185
|
raise ValueError(closed.stderr.strip() or 'Cannot verify the milestone branch is open.')
|
|
186
186
|
if scope == 'project' and key == 'integration':
|
|
@@ -23,10 +23,13 @@ sys.dont_write_bytecode = True
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Optional, Sequence
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
import _common
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
if __package__:
|
|
27
|
+
from . import _common
|
|
28
|
+
else:
|
|
29
|
+
try:
|
|
30
|
+
import _common
|
|
31
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
32
|
+
from scripts import _common
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
ARCHIVE_NAME_RE = re.compile(r"^(\d{3,})-([a-z0-9][a-z0-9-]*)$")
|
|
@@ -123,8 +126,24 @@ def is_integrate_subject(subject: str, archive_name: str, default_branch: str) -
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
MEMBER_SHIP_ROW = re.compile(
|
|
130
|
+
r"(?m)^Member-Reviewed-HEAD: (?P<name>[^@\s]+)@(?P<reviewed_head>[0-9a-f]{40})\n"
|
|
131
|
+
r"Member: (?P=name) (?P<merge>[0-9a-f]{40}) (?P<tag>\S+)$"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def ship_member_rows(body: str) -> list:
|
|
136
|
+
"""Closed-member rows named by a ship body; callers compare the rebuilt body exactly."""
|
|
137
|
+
return [match.groupdict() for match in MEMBER_SHIP_ROW.finditer(body)]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def ship_commit_body(archive_path: str, reviewed_head: str, members: Sequence[dict] = ()) -> str:
|
|
141
|
+
"""The ship body; a multi-repo milestone adds each closed member in lock order."""
|
|
142
|
+
return f"Archive: {archive_path}\nReviewed-HEAD: {reviewed_head}\n" + "".join(
|
|
143
|
+
f"Member-Reviewed-HEAD: {row['name']}@{row['reviewed_head']}\n"
|
|
144
|
+
f"Member: {row['name']} {row['merge']} {row['tag']}\n"
|
|
145
|
+
for row in members
|
|
146
|
+
)
|
|
128
147
|
|
|
129
148
|
|
|
130
149
|
def integrate_commit_body(
|
|
@@ -200,7 +219,7 @@ def _run_git(
|
|
|
200
219
|
result = subprocess.run(
|
|
201
220
|
["git", "-C", str(repo), *args],
|
|
202
221
|
capture_output=True,
|
|
203
|
-
|
|
222
|
+
encoding="utf-8", errors="replace",
|
|
204
223
|
)
|
|
205
224
|
if check and result.returncode != 0:
|
|
206
225
|
detail = result.stderr.strip() or result.stdout.strip()
|
|
@@ -319,7 +338,7 @@ def _validated_shipped_state(repo: Path) -> dict[str, object]:
|
|
|
319
338
|
str(repo),
|
|
320
339
|
],
|
|
321
340
|
capture_output=True,
|
|
322
|
-
|
|
341
|
+
encoding="utf-8", errors="replace",
|
|
323
342
|
)
|
|
324
343
|
if result.returncode != 0:
|
|
325
344
|
detail = result.stderr.strip() or result.stdout.strip()
|
|
@@ -446,19 +465,16 @@ def _write_bind_next_journal(path: Path, value: dict[str, object]) -> None:
|
|
|
446
465
|
raise PipelineGitError(f"bind-next journal temporary path exists: {temporary}")
|
|
447
466
|
descriptor: Optional[int] = None
|
|
448
467
|
try:
|
|
449
|
-
descriptor = os.open(
|
|
450
|
-
|
|
468
|
+
descriptor = os.open(
|
|
469
|
+
temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | _common.O_BINARY, 0o600
|
|
470
|
+
)
|
|
471
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
451
472
|
descriptor = None
|
|
452
|
-
json.
|
|
453
|
-
handle.write("\n")
|
|
473
|
+
handle.write((json.dumps(value, indent=2, sort_keys=True) + "\n").encode("utf-8"))
|
|
454
474
|
handle.flush()
|
|
455
475
|
os.fsync(handle.fileno())
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
try:
|
|
459
|
-
os.fsync(directory)
|
|
460
|
-
finally:
|
|
461
|
-
os.close(directory)
|
|
476
|
+
_common.replace(temporary, path)
|
|
477
|
+
_common.fsync_directory(path.parent)
|
|
462
478
|
finally:
|
|
463
479
|
if descriptor is not None:
|
|
464
480
|
os.close(descriptor)
|
|
@@ -473,10 +489,13 @@ def _initial_binding_snapshot(repo: Path) -> Optional[tuple]:
|
|
|
473
489
|
return None
|
|
474
490
|
if dirty != "?? .project/STATE.md\0":
|
|
475
491
|
raise PipelineGitError("primary worktree is not clean")
|
|
476
|
-
|
|
477
|
-
import pipeline_state
|
|
478
|
-
|
|
479
|
-
|
|
492
|
+
if __package__:
|
|
493
|
+
from . import pipeline_state
|
|
494
|
+
else:
|
|
495
|
+
try:
|
|
496
|
+
import pipeline_state
|
|
497
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
498
|
+
from scripts import pipeline_state
|
|
480
499
|
|
|
481
500
|
project = repo / ".project"
|
|
482
501
|
path = project / "STATE.md"
|
|
@@ -635,6 +654,25 @@ def retire_previous_branch(
|
|
|
635
654
|
_run_git(repo, "branch", "-d", previous_branch)
|
|
636
655
|
|
|
637
656
|
|
|
657
|
+
def _retire_members(repo: Path, ship_sha: str) -> None:
|
|
658
|
+
"""Retire each member the ship commit names; each step is idempotent for a resumed bind-next."""
|
|
659
|
+
message = _run_git(repo, "show", "-s", "--format=%B", ship_sha).stdout
|
|
660
|
+
rows = ship_member_rows(message)
|
|
661
|
+
if not rows:
|
|
662
|
+
return
|
|
663
|
+
if __package__:
|
|
664
|
+
from . import integration
|
|
665
|
+
else:
|
|
666
|
+
import integration
|
|
667
|
+
archive = re.search(r"(?m)^Archive: (\S+)$", message)
|
|
668
|
+
try:
|
|
669
|
+
for row in rows:
|
|
670
|
+
integration.retire_member(repo, row["name"], archive.group(1), row["reviewed_head"],
|
|
671
|
+
row["merge"], row["tag"])
|
|
672
|
+
except integration.ArchiveError as error:
|
|
673
|
+
raise PipelineGitError(f"member retirement failed: {error}") from error
|
|
674
|
+
|
|
675
|
+
|
|
638
676
|
def bind_next_milestone_branch(
|
|
639
677
|
repo: Path,
|
|
640
678
|
branch: str,
|
|
@@ -868,6 +906,7 @@ def bind_next_milestone_branch(
|
|
|
868
906
|
allow_remote_absent or journal["stage"] in {"switched", "retired"}
|
|
869
907
|
),
|
|
870
908
|
)
|
|
909
|
+
_retire_members(repo, ship_sha)
|
|
871
910
|
journal["stage"] = "retired"
|
|
872
911
|
_write_bind_next_journal(journal_path, journal)
|
|
873
912
|
return {
|
|
@@ -27,25 +27,35 @@ from datetime import date
|
|
|
27
27
|
from pathlib import Path, PurePosixPath
|
|
28
28
|
from typing import Iterator, Mapping, Optional, Sequence
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
from isolation import (
|
|
30
|
+
if __package__:
|
|
31
|
+
from .isolation import (
|
|
32
32
|
IsolationError,
|
|
33
33
|
authorized_task_worktree,
|
|
34
34
|
checkpoint as isolation_checkpoint,
|
|
35
35
|
collect_artifact_recoveries,
|
|
36
36
|
)
|
|
37
|
-
import _common
|
|
38
|
-
import roadmap
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
from . import _common
|
|
38
|
+
from . import roadmap
|
|
39
|
+
else:
|
|
40
|
+
try:
|
|
41
|
+
from isolation import (
|
|
42
|
+
IsolationError,
|
|
43
|
+
authorized_task_worktree,
|
|
44
|
+
checkpoint as isolation_checkpoint,
|
|
45
|
+
collect_artifact_recoveries,
|
|
46
|
+
)
|
|
47
|
+
import _common
|
|
48
|
+
import roadmap
|
|
49
|
+
except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
|
|
50
|
+
if error.name not in {"isolation", "_common", "roadmap"}:
|
|
51
|
+
raise
|
|
52
|
+
from scripts.isolation import (
|
|
53
|
+
IsolationError,
|
|
54
|
+
authorized_task_worktree,
|
|
55
|
+
checkpoint as isolation_checkpoint,
|
|
56
|
+
collect_artifact_recoveries,
|
|
57
|
+
)
|
|
58
|
+
from scripts import _common, roadmap
|
|
49
59
|
|
|
50
60
|
try: # pragma: no cover - exercised only on Windows
|
|
51
61
|
import fcntl
|
|
@@ -66,6 +76,8 @@ PHASES = (
|
|
|
66
76
|
"shipped",
|
|
67
77
|
)
|
|
68
78
|
STATUSES = ("active", "done", "blocked")
|
|
79
|
+
PRE_APPROVAL_KINDS = ("intent", "plan")
|
|
80
|
+
PRE_APPROVAL_MARKER = "pre-approval: "
|
|
69
81
|
STATE_FIELDS = (
|
|
70
82
|
"pipeline",
|
|
71
83
|
"project",
|
|
@@ -184,7 +196,7 @@ def _run_git(
|
|
|
184
196
|
result = subprocess.run(
|
|
185
197
|
["git", "-C", str(repo), *arguments],
|
|
186
198
|
capture_output=True,
|
|
187
|
-
|
|
199
|
+
encoding="utf-8", errors="replace",
|
|
188
200
|
)
|
|
189
201
|
if check and result.returncode != 0:
|
|
190
202
|
detail = result.stderr.strip() or result.stdout.strip()
|
|
@@ -410,14 +422,17 @@ def undo_transaction(repo: Path) -> Optional[dict[str, object]]:
|
|
|
410
422
|
"archive_fingerprint",
|
|
411
423
|
"discussion",
|
|
412
424
|
}
|
|
425
|
+
if value.get("kind") == "member-task":
|
|
426
|
+
# A member landing undo also names the member reset it performs first.
|
|
427
|
+
expected |= {"member", "landing", "member_parent"}
|
|
413
428
|
if set(value) != expected or value.get("schema") != UNDO_TRANSACTION_SCHEMA:
|
|
414
429
|
raise PipelineStateError("undo transaction has invalid fields")
|
|
415
430
|
if value.get("repo") != str(resolved):
|
|
416
431
|
raise PipelineStateError("undo transaction belongs to another worktree")
|
|
417
432
|
kind = value.get("kind")
|
|
418
|
-
if kind not in {"checkpoint", "uncommitted-archive"}:
|
|
433
|
+
if kind not in {"checkpoint", "uncommitted-archive", "member-task"}:
|
|
419
434
|
raise PipelineStateError("undo transaction has invalid kind")
|
|
420
|
-
for field in ("expected_head", "parent"):
|
|
435
|
+
for field in ("expected_head", "parent") + (("landing", "member_parent") if kind == "member-task" else ()):
|
|
421
436
|
field_value = value.get(field)
|
|
422
437
|
if not isinstance(field_value, str) or not re.fullmatch(
|
|
423
438
|
r"[0-9a-f]{40,64}", field_value
|
|
@@ -433,10 +448,12 @@ def undo_transaction(repo: Path) -> Optional[dict[str, object]]:
|
|
|
433
448
|
raise PipelineStateError("undo transaction has invalid worktree fingerprint")
|
|
434
449
|
archive = value.get("archive")
|
|
435
450
|
archive_fingerprint = value.get("archive_fingerprint")
|
|
436
|
-
if kind
|
|
451
|
+
if kind in {"checkpoint", "member-task"} and (
|
|
437
452
|
archive is not None or archive_fingerprint is not None
|
|
438
453
|
):
|
|
439
|
-
raise PipelineStateError("
|
|
454
|
+
raise PipelineStateError(f"{kind} undo transaction cannot name an archive")
|
|
455
|
+
if kind == "member-task" and not isinstance(value.get("member"), str):
|
|
456
|
+
raise PipelineStateError("member-task undo transaction has invalid member")
|
|
440
457
|
if kind == "uncommitted-archive" and (
|
|
441
458
|
not isinstance(archive, str) or ARCHIVE_RE.fullmatch(archive) is None
|
|
442
459
|
):
|
|
@@ -563,12 +580,15 @@ def _pending_answers(repo: Path) -> tuple[list[dict[str, str]], Optional[str]]:
|
|
|
563
580
|
if not answers.exists() and not answers.is_symlink():
|
|
564
581
|
return [], None
|
|
565
582
|
try:
|
|
566
|
-
|
|
567
|
-
from discussion_records import DiscussionError, pending_records
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
583
|
+
if __package__:
|
|
584
|
+
from .discussion_records import DiscussionError, pending_records
|
|
585
|
+
else:
|
|
586
|
+
try:
|
|
587
|
+
from discussion_records import DiscussionError, pending_records
|
|
588
|
+
except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
|
|
589
|
+
if error.name != "discussion_records":
|
|
590
|
+
raise
|
|
591
|
+
from scripts.discussion_records import DiscussionError, pending_records
|
|
572
592
|
except ModuleNotFoundError as error:
|
|
573
593
|
if error.name not in {"scripts", "scripts.discussion_records"}:
|
|
574
594
|
raise
|
|
@@ -624,6 +644,29 @@ def _completion_status(repo: Path, state: Mapping[str, object], project_dir: str
|
|
|
624
644
|
return {"status": "verified", "proof": proof}
|
|
625
645
|
|
|
626
646
|
|
|
647
|
+
def _member_status(repo: Path, state: Mapping[str, object]) -> Optional[list]:
|
|
648
|
+
"""Locked members and their ship close, when this milestone has a member lock."""
|
|
649
|
+
if __package__:
|
|
650
|
+
from scripts import members
|
|
651
|
+
else:
|
|
652
|
+
import members
|
|
653
|
+
archive = state.get("archive")
|
|
654
|
+
locks = [repo / ".project" / "build" / "members.json"]
|
|
655
|
+
if isinstance(archive, str) and archive not in {"", "null"}:
|
|
656
|
+
locks.insert(0, repo / archive.rstrip("/") / "build" / "members.json")
|
|
657
|
+
lock = next((path for path in locks if path.is_file()), None)
|
|
658
|
+
if lock is None:
|
|
659
|
+
return None
|
|
660
|
+
closed = {}
|
|
661
|
+
match = re.fullmatch(r"gsd-path/M(\d{3,})", str(state.get("branch") or ""))
|
|
662
|
+
if match is not None:
|
|
663
|
+
common = Path(_run_git(repo, "rev-parse", "--path-format=absolute", "--git-common-dir").stdout.strip())
|
|
664
|
+
closed = {row["name"]: row.get("status") for row in members.milestone_member_close(common, int(match.group(1)))}
|
|
665
|
+
entries = json.loads(lock.read_text(encoding="utf-8"))["members"]
|
|
666
|
+
return [{"name": entry["name"], "branch": entry["branch"], "close": closed.get(entry["name"])}
|
|
667
|
+
for entry in entries]
|
|
668
|
+
|
|
669
|
+
|
|
627
670
|
def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]:
|
|
628
671
|
"""Report owned state, route, and git facts without advancing a phase."""
|
|
629
672
|
resolved = _repo_root(repo)
|
|
@@ -658,7 +701,8 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
658
701
|
"collect_artifact": None,
|
|
659
702
|
}
|
|
660
703
|
completion = _completion_status(resolved, state, project_dir)
|
|
661
|
-
|
|
704
|
+
member_rows = _member_status(resolved, state) if has_git and project_dir == ".project" else None
|
|
705
|
+
result = {
|
|
662
706
|
"schema": STATUS_SCHEMA,
|
|
663
707
|
"advance": False,
|
|
664
708
|
"completion": completion,
|
|
@@ -688,6 +732,9 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
688
732
|
"next_skill": _next_skill(route if isinstance(route, dict) else {}),
|
|
689
733
|
"handoff": phase_handoff(state, route, _track_root(resolved, project_dir) / "STATE.md", completion),
|
|
690
734
|
}
|
|
735
|
+
if member_rows is not None:
|
|
736
|
+
result["members"] = member_rows
|
|
737
|
+
return result
|
|
691
738
|
|
|
692
739
|
|
|
693
740
|
def phase_handoff(state: Mapping[str, object], route: Mapping[str, object], path: Path,
|
|
@@ -759,7 +806,7 @@ def _valid_patch_findings(repo: Path) -> bool:
|
|
|
759
806
|
str(repo),
|
|
760
807
|
],
|
|
761
808
|
capture_output=True,
|
|
762
|
-
|
|
809
|
+
encoding="utf-8", errors="replace",
|
|
763
810
|
)
|
|
764
811
|
return result.returncode == 0
|
|
765
812
|
|
|
@@ -791,6 +838,8 @@ def _selected_initial_branch(project: Path, state: PipelineState) -> str:
|
|
|
791
838
|
if archive.is_symlink() or not archive.is_dir():
|
|
792
839
|
raise PipelineStateError(f"archive root must be a real directory: {archive}")
|
|
793
840
|
for child in archive.iterdir():
|
|
841
|
+
if _common.is_ignored_junk(child):
|
|
842
|
+
continue
|
|
794
843
|
match = re.fullmatch(r"(\d{3,})-[a-z0-9][a-z0-9-]*", child.name)
|
|
795
844
|
if (
|
|
796
845
|
child.is_symlink()
|
|
@@ -1381,8 +1430,11 @@ def _append_event(
|
|
|
1381
1430
|
|
|
1382
1431
|
@contextlib.contextmanager
|
|
1383
1432
|
def _state_lock(project: Path) -> Iterator[None]:
|
|
1384
|
-
if fcntl is None:
|
|
1385
|
-
|
|
1433
|
+
if fcntl is None:
|
|
1434
|
+
# Windows cannot flock a directory.
|
|
1435
|
+
with _common.directory_mutex(project):
|
|
1436
|
+
yield
|
|
1437
|
+
return
|
|
1386
1438
|
descriptor = os.open(project, os.O_RDONLY)
|
|
1387
1439
|
try:
|
|
1388
1440
|
fcntl.flock(descriptor, fcntl.LOCK_EX)
|
|
@@ -1408,27 +1460,21 @@ def _atomic_write(path: Path, content: str) -> None:
|
|
|
1408
1460
|
and os.path.samestat(path_status, temporary_status)
|
|
1409
1461
|
):
|
|
1410
1462
|
temporary.unlink()
|
|
1411
|
-
|
|
1412
|
-
try:
|
|
1413
|
-
os.fsync(directory)
|
|
1414
|
-
finally:
|
|
1415
|
-
os.close(directory)
|
|
1463
|
+
_common.fsync_directory(path.parent)
|
|
1416
1464
|
if temporary.exists() or temporary.is_symlink():
|
|
1417
1465
|
raise PipelineStateError(f"atomic-write temporary path already exists: {temporary}")
|
|
1418
1466
|
descriptor: Optional[int] = None
|
|
1419
1467
|
try:
|
|
1420
|
-
descriptor = os.open(
|
|
1421
|
-
|
|
1468
|
+
descriptor = os.open(
|
|
1469
|
+
temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | _common.O_BINARY, 0o666
|
|
1470
|
+
)
|
|
1471
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
1422
1472
|
descriptor = None
|
|
1423
|
-
handle.write(content)
|
|
1473
|
+
handle.write(content.encode("utf-8"))
|
|
1424
1474
|
handle.flush()
|
|
1425
1475
|
os.fsync(handle.fileno())
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
try:
|
|
1429
|
-
os.fsync(directory)
|
|
1430
|
-
finally:
|
|
1431
|
-
os.close(directory)
|
|
1476
|
+
_common.replace(temporary, path)
|
|
1477
|
+
_common.fsync_directory(path.parent)
|
|
1432
1478
|
finally:
|
|
1433
1479
|
if descriptor is not None:
|
|
1434
1480
|
os.close(descriptor)
|
|
@@ -1668,12 +1714,6 @@ def transition_state(
|
|
|
1668
1714
|
if recovery_context and recovery_context["active"]:
|
|
1669
1715
|
if (state.milestone, state.branch, state.archive) != (after.milestone, after.branch, after.archive):
|
|
1670
1716
|
raise PipelineStateError("build recovery must preserve milestone identity")
|
|
1671
|
-
if state.phase == "build" and after.phase in {"define", "plan"}:
|
|
1672
|
-
recovery = _build_recovery().begin(resolved, state, after, event)
|
|
1673
|
-
rendered = _append_event(
|
|
1674
|
-
rendered, after.phase,
|
|
1675
|
-
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1676
|
-
)
|
|
1677
1717
|
if after.status != "blocked":
|
|
1678
1718
|
reason = _pending_discussion_block(
|
|
1679
1719
|
resolved, after.phase if after.status == "active" else None
|
|
@@ -1683,10 +1723,13 @@ def transition_state(
|
|
|
1683
1723
|
if state.phase in {"research", "decide"} and (
|
|
1684
1724
|
after.phase != state.phase or after.status == "done"
|
|
1685
1725
|
):
|
|
1686
|
-
|
|
1687
|
-
import check_handoffs
|
|
1688
|
-
|
|
1689
|
-
|
|
1726
|
+
if __package__:
|
|
1727
|
+
from . import check_handoffs
|
|
1728
|
+
else:
|
|
1729
|
+
try:
|
|
1730
|
+
import check_handoffs
|
|
1731
|
+
except ImportError: # pragma: no cover - package imports used by tests
|
|
1732
|
+
from scripts import check_handoffs
|
|
1690
1733
|
validator = {
|
|
1691
1734
|
"research": check_handoffs.validate_research_artifacts,
|
|
1692
1735
|
"decide": check_handoffs.validate_decide_artifacts,
|
|
@@ -1695,6 +1738,15 @@ def transition_state(
|
|
|
1695
1738
|
validator(resolved, project_dir)
|
|
1696
1739
|
except check_handoffs.HandoffError as error:
|
|
1697
1740
|
raise PipelineStateError(f"{state.phase} handoff failed: {error}") from error
|
|
1741
|
+
if (project_dir == ".project" and (state.phase, state.status) == ("plan", "done")
|
|
1742
|
+
and (after.phase, after.status) == ("build", "active")):
|
|
1743
|
+
_lock_build_members(resolved)
|
|
1744
|
+
if state.phase == "build" and after.phase in {"define", "plan"}:
|
|
1745
|
+
recovery = _build_recovery().begin(resolved, state, after, event)
|
|
1746
|
+
rendered = _append_event(
|
|
1747
|
+
rendered, after.phase,
|
|
1748
|
+
_build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
|
|
1749
|
+
)
|
|
1698
1750
|
_atomic_write(path, rendered)
|
|
1699
1751
|
return {
|
|
1700
1752
|
"schema": STATE_SCHEMA,
|
|
@@ -1705,6 +1757,17 @@ def transition_state(
|
|
|
1705
1757
|
}
|
|
1706
1758
|
|
|
1707
1759
|
|
|
1760
|
+
def _lock_build_members(repo: Path) -> None:
|
|
1761
|
+
if __package__:
|
|
1762
|
+
from scripts import members
|
|
1763
|
+
else:
|
|
1764
|
+
import members
|
|
1765
|
+
try:
|
|
1766
|
+
members.lock_build_members(repo)
|
|
1767
|
+
except members.MembersError as error:
|
|
1768
|
+
raise PipelineStateError(f"build start: {error}") from error
|
|
1769
|
+
|
|
1770
|
+
|
|
1708
1771
|
def _build_recovery():
|
|
1709
1772
|
if __package__:
|
|
1710
1773
|
from scripts import build_recovery
|
|
@@ -1776,6 +1839,55 @@ def configure_integration(
|
|
|
1776
1839
|
}
|
|
1777
1840
|
|
|
1778
1841
|
|
|
1842
|
+
def _log_events(text: str) -> list[str]:
|
|
1843
|
+
return [line.split(" — ", 2)[-1] for line in text.splitlines()
|
|
1844
|
+
if line.startswith("- ") and " — " in line]
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
def pre_approve(repo: Path, grant: Optional[str], use: Optional[str]) -> dict[str, object]:
|
|
1848
|
+
"""Record an owner's pre-approval grant, or record one granted gate's use."""
|
|
1849
|
+
resolved = _repo_root(repo)
|
|
1850
|
+
project = _track_root(resolved, ".project")
|
|
1851
|
+
with _state_lock(project):
|
|
1852
|
+
state, text, path = load_state(resolved)
|
|
1853
|
+
if grant is not None:
|
|
1854
|
+
kinds = grant.split(",")
|
|
1855
|
+
if not kinds or len(set(kinds)) != len(kinds) or not set(kinds) <= set(PRE_APPROVAL_KINDS):
|
|
1856
|
+
raise PipelineStateError(
|
|
1857
|
+
f"pre-approval kinds must be distinct values from: {', '.join(PRE_APPROVAL_KINDS)}"
|
|
1858
|
+
)
|
|
1859
|
+
if state.milestone is not None:
|
|
1860
|
+
raise PipelineStateError("pre-approval is granted only before milestone intent approval")
|
|
1861
|
+
event = PRE_APPROVAL_MARKER + json.dumps({"kinds": kinds}, sort_keys=True)
|
|
1862
|
+
else:
|
|
1863
|
+
events = _log_events(text)
|
|
1864
|
+
starts = [index for index, event in enumerate(events) if event.startswith(PRE_APPROVAL_MARKER)]
|
|
1865
|
+
if not starts:
|
|
1866
|
+
raise PipelineStateError("no pre-approval grant is recorded")
|
|
1867
|
+
try:
|
|
1868
|
+
recorded = json.loads(events[starts[-1]][len(PRE_APPROVAL_MARKER):])
|
|
1869
|
+
kinds = recorded["kinds"]
|
|
1870
|
+
except (ValueError, TypeError, KeyError) as error:
|
|
1871
|
+
raise PipelineStateError("invalid pre-approval record") from error
|
|
1872
|
+
if not isinstance(kinds, list):
|
|
1873
|
+
raise PipelineStateError("invalid pre-approval record")
|
|
1874
|
+
if (_build_recovery().context(resolved, text) or {}).get("active") or (project / "review/PATCH-FINDINGS.md").exists():
|
|
1875
|
+
raise PipelineStateError("pre-approval never covers build recovery or patch planning")
|
|
1876
|
+
if use not in kinds:
|
|
1877
|
+
raise PipelineStateError(f"pre-approval does not grant {use}")
|
|
1878
|
+
# The canonical approval event, not this record, consumes the grant.
|
|
1879
|
+
approvals = events[starts[-1] + 1:].count("milestone intent approved")
|
|
1880
|
+
expected = {"intent": ("define", "active", 0), "plan": ("plan", "active", 1)}[use]
|
|
1881
|
+
if (state.phase, state.status, approvals) != expected or (
|
|
1882
|
+
(state.milestone is None) != (use == "intent")
|
|
1883
|
+
):
|
|
1884
|
+
raise PipelineStateError(f"pre-approval for {use} is used or out of scope")
|
|
1885
|
+
event = f"pre-authorized approval: {use}"
|
|
1886
|
+
rendered = _append_event(text, state.phase, event)
|
|
1887
|
+
_atomic_write(path, rendered)
|
|
1888
|
+
return {"schema": STATE_SCHEMA, "status": "recorded", "event": event, "state": state.json()}
|
|
1889
|
+
|
|
1890
|
+
|
|
1779
1891
|
def _shipment_roadmap(text: str, state: PipelineState, archive: str) -> str:
|
|
1780
1892
|
if state.milestone is None or state.branch is None:
|
|
1781
1893
|
raise PipelineStateError("shipment state must name milestone and branch")
|
|
@@ -2016,7 +2128,8 @@ def _commit_message(subject: str, body_fields: Mapping[str, str]) -> str:
|
|
|
2016
2128
|
def _validate_next_layout(next_root: Path, state: PipelineState) -> None:
|
|
2017
2129
|
phase_index = LOOKAHEAD_PHASES.index(state.phase)
|
|
2018
2130
|
for path in next_root.rglob("*"):
|
|
2019
|
-
|
|
2131
|
+
# POSIX form: a WindowsPath never equals the PurePosixPath layout below.
|
|
2132
|
+
relative = PurePosixPath(path.relative_to(next_root).as_posix())
|
|
2020
2133
|
if path.is_symlink():
|
|
2021
2134
|
raise PipelineStateError(f"lookahead artifacts must not be symlinks: {path}")
|
|
2022
2135
|
if path.is_dir():
|
|
@@ -2028,6 +2141,8 @@ def _validate_next_layout(next_root: Path, state: PipelineState) -> None:
|
|
|
2028
2141
|
continue
|
|
2029
2142
|
if not path.is_file():
|
|
2030
2143
|
raise PipelineStateError(f"lookahead has unsafe artifact: {path}")
|
|
2144
|
+
if _common.is_ignored_junk(path):
|
|
2145
|
+
continue
|
|
2031
2146
|
artifact = relative.as_posix()
|
|
2032
2147
|
required_phase = LOOKAHEAD_FIXED_ARTIFACTS.get(artifact)
|
|
2033
2148
|
if required_phase is None and LOOKAHEAD_EVIDENCE_RE.fullmatch(artifact):
|
|
@@ -2112,6 +2227,11 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
|
|
|
2112
2227
|
configure.add_argument("--project-dir", default=".project")
|
|
2113
2228
|
configure.add_argument("--scope", required=True, choices=("default", "milestone"))
|
|
2114
2229
|
configure.add_argument("--mode", required=True, choices=INTEGRATION_MODES)
|
|
2230
|
+
pre = subparsers.add_parser("pre-approve")
|
|
2231
|
+
pre.add_argument("--repo", required=True, type=Path)
|
|
2232
|
+
pre_mode = pre.add_mutually_exclusive_group(required=True)
|
|
2233
|
+
pre_mode.add_argument("--grant")
|
|
2234
|
+
pre_mode.add_argument("--use", choices=PRE_APPROVAL_KINDS)
|
|
2115
2235
|
shipment = subparsers.add_parser("record-shipment")
|
|
2116
2236
|
shipment.add_argument("--repo", required=True, type=Path)
|
|
2117
2237
|
shipment.add_argument("--archive", required=True)
|
|
@@ -2200,6 +2320,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2200
2320
|
base,
|
|
2201
2321
|
landing,
|
|
2202
2322
|
)
|
|
2323
|
+
elif args.command == "pre-approve":
|
|
2324
|
+
result = pre_approve(args.repo, args.grant, args.use)
|
|
2203
2325
|
elif args.command == "record-shipment":
|
|
2204
2326
|
result = record_shipment(args.repo, args.archive, args.event)
|
|
2205
2327
|
elif args.command == "configure-integration":
|
|
@@ -205,7 +205,7 @@ def select_fetched_base(
|
|
|
205
205
|
resolved = subprocess.run(
|
|
206
206
|
["git", "-C", str(root), "rev-parse", "--verify", f"{remote_default}^{{commit}}"],
|
|
207
207
|
capture_output=True,
|
|
208
|
-
|
|
208
|
+
encoding="utf-8", errors="replace",
|
|
209
209
|
check=False,
|
|
210
210
|
)
|
|
211
211
|
if resolved.returncode != 0 or resolved.stdout.strip() != base:
|
|
@@ -470,7 +470,7 @@ def merge_artifacts(
|
|
|
470
470
|
lines.append(artifact["text"].rstrip())
|
|
471
471
|
lines.append("")
|
|
472
472
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
473
|
-
output.
|
|
473
|
+
output.write_bytes(("\n".join(lines).rstrip() + "\n").encode("utf-8"))
|
|
474
474
|
return {
|
|
475
475
|
"output": str(output.resolve()),
|
|
476
476
|
"families": families,
|