@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
|
@@ -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":
|
package/scripts/pipeline_undo.py
CHANGED
|
@@ -25,22 +25,30 @@ from pathlib import Path, PurePosixPath
|
|
|
25
25
|
from typing import Optional, Sequence
|
|
26
26
|
|
|
27
27
|
def _load_pipeline_modules():
|
|
28
|
-
|
|
29
|
-
import archive_milestone
|
|
30
|
-
import isolation
|
|
31
|
-
import pipeline_git
|
|
32
|
-
import pipeline_state
|
|
33
|
-
import state_checkpoint
|
|
28
|
+
if __package__:
|
|
29
|
+
from . import archive_milestone
|
|
30
|
+
from . import isolation
|
|
31
|
+
from . import pipeline_git
|
|
32
|
+
from . import pipeline_state
|
|
33
|
+
from . import state_checkpoint
|
|
34
34
|
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
import archive_milestone
|
|
38
|
+
import isolation
|
|
39
|
+
import pipeline_git
|
|
40
|
+
import pipeline_state
|
|
41
|
+
import state_checkpoint
|
|
42
|
+
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
43
|
+
except ModuleNotFoundError as error:
|
|
44
|
+
if error.name not in {
|
|
45
|
+
"archive_milestone",
|
|
46
|
+
"isolation",
|
|
47
|
+
"pipeline_git",
|
|
48
|
+
"pipeline_state",
|
|
49
|
+
"state_checkpoint",
|
|
50
|
+
}:
|
|
51
|
+
raise
|
|
44
52
|
try:
|
|
45
53
|
from scripts import archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
46
54
|
return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
|
|
@@ -94,7 +102,7 @@ UNDO_TRANSACTION_SCHEMA = pipeline_state.UNDO_TRANSACTION_SCHEMA
|
|
|
94
102
|
UNDO_TRANSACTION_NAME = pipeline_state.UNDO_TRANSACTION_NAME
|
|
95
103
|
ROADMAP_APPROVAL_SUBJECT = "roadmap: program roadmap approved"
|
|
96
104
|
TASK_SUBJECT_RE = re.compile(r"^(T\d{3,}): .+")
|
|
97
|
-
KINDS = ("checkpoint", "task", "uncommitted-archive", "lookahead")
|
|
105
|
+
KINDS = ("checkpoint", "task", "member-task", "uncommitted-archive", "lookahead")
|
|
98
106
|
PROMOTION_PREFIX = "router: promote lookahead milestone "
|
|
99
107
|
ABANDON_PREFIX = "build: abandon milestone "
|
|
100
108
|
INTEGRATE_PREFIX = "integrate: "
|
|
@@ -528,6 +536,7 @@ def _prepare_transaction(
|
|
|
528
536
|
branch: str,
|
|
529
537
|
archive: Optional[str],
|
|
530
538
|
discussion: Optional[dict[str, bytes]],
|
|
539
|
+
member_fields: Optional[dict[str, str]] = None,
|
|
531
540
|
) -> dict[str, object]:
|
|
532
541
|
transaction = {
|
|
533
542
|
"schema": UNDO_TRANSACTION_SCHEMA,
|
|
@@ -545,6 +554,8 @@ def _prepare_transaction(
|
|
|
545
554
|
),
|
|
546
555
|
"discussion": _encode_discussion(discussion),
|
|
547
556
|
}
|
|
557
|
+
if member_fields is not None:
|
|
558
|
+
transaction.update(member_fields)
|
|
548
559
|
_write_json(_git_path(repo, UNDO_TRANSACTION_NAME), transaction)
|
|
549
560
|
return transaction
|
|
550
561
|
|
|
@@ -641,7 +652,7 @@ def _transaction_recovery_stage(
|
|
|
641
652
|
expected_head = str(transaction["expected_head"])
|
|
642
653
|
parent = str(transaction["parent"])
|
|
643
654
|
current = _optional_rev(repo, "HEAD")
|
|
644
|
-
if transaction["kind"]
|
|
655
|
+
if transaction["kind"] in {"checkpoint", "member-task"}:
|
|
645
656
|
allowed = {expected_head, parent}
|
|
646
657
|
else:
|
|
647
658
|
allowed = {expected_head}
|
|
@@ -662,8 +673,60 @@ def _transaction_recovery_stage(
|
|
|
662
673
|
return "reset"
|
|
663
674
|
|
|
664
675
|
|
|
676
|
+
def _member_undo_parent(repo: Path, member: str, landing: str) -> str:
|
|
677
|
+
"""The member landing's parent, while that landing is the unpublished bound tip."""
|
|
678
|
+
checkout, _, entry = isolation._member_context(repo, member)
|
|
679
|
+
bound = entry["branch"]
|
|
680
|
+
if _run_git(checkout, "rev-parse", f"refs/heads/{bound}").stdout.strip() != landing:
|
|
681
|
+
raise UndoError(f"member {member} {bound} moved past this landing")
|
|
682
|
+
_require_member_unpublished(checkout, member, landing, bound)
|
|
683
|
+
return _run_git(checkout, "rev-parse", f"{landing}^").stdout.strip()
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _require_member_unpublished(checkout: Path, member: str, landing: str, bound: str) -> None:
|
|
687
|
+
for ref, where in (("refs/remotes/origin/main", "origin/main"), (f"refs/remotes/origin/{bound}", f"origin/{bound}")):
|
|
688
|
+
if _run_git(checkout, "merge-base", "--is-ancestor", landing, ref, check=False).returncode == 0:
|
|
689
|
+
raise UndoError(f"member {member} landing is already on {where}")
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _require_no_member_landing_journal(repo: Path) -> None:
|
|
693
|
+
directory = isolation.common_git_dir(repo).joinpath(*isolation.MEMBER_LANDING_DIR)
|
|
694
|
+
if any(directory.glob("*.json")):
|
|
695
|
+
raise UndoError("member landing journal is pending; recover member landing first")
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _undo_member_landing(repo: Path, transaction: dict[str, object]) -> None:
|
|
699
|
+
"""Reset the member bound branch first; a resumed undo finds it already reset."""
|
|
700
|
+
member = str(transaction["member"])
|
|
701
|
+
checkout, _, entry = isolation._member_context(repo, member)
|
|
702
|
+
tip = _run_git(checkout, "rev-parse", f"refs/heads/{entry['branch']}").stdout.strip()
|
|
703
|
+
if tip == transaction["member_parent"]:
|
|
704
|
+
_require_member_unpublished(checkout, member, str(transaction["landing"]), entry["branch"])
|
|
705
|
+
return
|
|
706
|
+
if tip != transaction["landing"]:
|
|
707
|
+
raise UndoError(f"member {member} bound branch moved during undo")
|
|
708
|
+
if _member_undo_parent(repo, member, str(transaction["landing"])) != transaction["member_parent"]:
|
|
709
|
+
raise UndoError(f"member {member} landing parent changed during undo")
|
|
710
|
+
bound = Path(isolation.member_bound_checkout(repo, member)["checkout"])
|
|
711
|
+
if _run_git(bound, "status", "--porcelain", "--untracked-files=all").stdout:
|
|
712
|
+
raise UndoError(f"member {member} bound checkout is dirty")
|
|
713
|
+
_run_git(bound, "reset", "--hard", str(transaction["member_parent"]))
|
|
714
|
+
|
|
715
|
+
|
|
665
716
|
def _finish_transaction(repo: Path, transaction: dict[str, object]) -> None:
|
|
717
|
+
if transaction["kind"] == "member-task":
|
|
718
|
+
with isolation._member_landing_lock(repo):
|
|
719
|
+
_finish_transaction_locked(repo, transaction)
|
|
720
|
+
return
|
|
721
|
+
_finish_transaction_locked(repo, transaction)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def _finish_transaction_locked(repo: Path, transaction: dict[str, object]) -> None:
|
|
725
|
+
if transaction["kind"] == "member-task":
|
|
726
|
+
_require_no_member_landing_journal(repo)
|
|
666
727
|
stage = _transaction_recovery_stage(repo, transaction)
|
|
728
|
+
if transaction["kind"] == "member-task":
|
|
729
|
+
_undo_member_landing(repo, transaction)
|
|
667
730
|
if stage == "prepared":
|
|
668
731
|
_require_unpublished_reset(repo, str(transaction["branch"]))
|
|
669
732
|
_reset_to(repo, str(transaction["parent"]))
|
|
@@ -675,11 +738,7 @@ def _finish_transaction(repo: Path, transaction: dict[str, object]) -> None:
|
|
|
675
738
|
_remove_untracked_tree(repo, archive)
|
|
676
739
|
path = _git_path(repo, UNDO_TRANSACTION_NAME)
|
|
677
740
|
path.unlink()
|
|
678
|
-
|
|
679
|
-
try:
|
|
680
|
-
os.fsync(descriptor)
|
|
681
|
-
finally:
|
|
682
|
-
os.close(descriptor)
|
|
741
|
+
pipeline_state._common.fsync_directory(path.parent)
|
|
683
742
|
|
|
684
743
|
|
|
685
744
|
def classify_undo(repo: Path) -> dict[str, object]:
|
|
@@ -821,6 +880,28 @@ def classify_undo(repo: Path) -> dict[str, object]:
|
|
|
821
880
|
proof_error = _task_proof_error(resolved, head)
|
|
822
881
|
if proof_error:
|
|
823
882
|
return _blocked([f"task landing ownership is unproven: {proof_error}"])
|
|
883
|
+
record = isolation.MEMBER_RECORD_RE.search(body)
|
|
884
|
+
if record is not None:
|
|
885
|
+
try:
|
|
886
|
+
_require_no_member_landing_journal(resolved)
|
|
887
|
+
member_parent = _member_undo_parent(resolved, record["member"], record["landing"])
|
|
888
|
+
except (IsolationError, UndoError, PipelineStateError) as error:
|
|
889
|
+
return _blocked([f"member landing undo is unsafe: {error}"])
|
|
890
|
+
return {
|
|
891
|
+
"kind": "member-task",
|
|
892
|
+
"branch": recorded_branch,
|
|
893
|
+
"head": head,
|
|
894
|
+
"parent": parent,
|
|
895
|
+
"subject": subject,
|
|
896
|
+
"member": record["member"],
|
|
897
|
+
"landing": record["landing"],
|
|
898
|
+
"member_parent": member_parent,
|
|
899
|
+
"effects": [
|
|
900
|
+
f"reset member {record['member']} bound branch to {member_parent}",
|
|
901
|
+
f"reset {recorded_branch} to {parent}",
|
|
902
|
+
],
|
|
903
|
+
"blocked": [],
|
|
904
|
+
}
|
|
824
905
|
return {
|
|
825
906
|
"kind": "task",
|
|
826
907
|
"branch": recorded_branch,
|
|
@@ -978,6 +1059,20 @@ def apply_undo(repo: Path, kind: str, expected_head: str) -> dict[str, object]:
|
|
|
978
1059
|
discussion,
|
|
979
1060
|
)
|
|
980
1061
|
_finish_transaction(resolved, transaction)
|
|
1062
|
+
elif kind == "member-task":
|
|
1063
|
+
with isolation._member_landing_lock(resolved):
|
|
1064
|
+
_require_no_member_landing_journal(resolved)
|
|
1065
|
+
if preview(resolved)["target"] != target:
|
|
1066
|
+
raise UndoError("member task undo target changed after preview")
|
|
1067
|
+
if _worktree_changes(resolved):
|
|
1068
|
+
raise UndoError("worktree changed after member task undo preview")
|
|
1069
|
+
_require_unpublished_reset(resolved, str(target["branch"]))
|
|
1070
|
+
transaction = _prepare_transaction(
|
|
1071
|
+
resolved, kind, expected_head, str(target["parent"]), str(target["branch"]), None, None,
|
|
1072
|
+
{"member": str(target["member"]), "landing": str(target["landing"]),
|
|
1073
|
+
"member_parent": str(target["member_parent"])},
|
|
1074
|
+
)
|
|
1075
|
+
_finish_transaction_locked(resolved, transaction)
|
|
981
1076
|
elif kind == "task":
|
|
982
1077
|
parent = target["parent"]
|
|
983
1078
|
if not parent:
|
|
@@ -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:
|
package/scripts/review_panel.py
CHANGED
|
@@ -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,
|