@opengsd/gsd-path 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -207
- package/CHANGELOG.md +331 -69
- package/DOCS.md +25 -9
- package/FULL.md +12 -5
- package/GUIDE.md +1 -1
- package/HOOKS.md +104 -14
- package/QUICK.md +5 -2
- package/README.md +131 -111
- package/RUNTIME.md +2 -2
- package/UPDATE.md +61 -11
- package/WORKFLOW.md +44 -7
- package/package.json +13 -9
- package/platforms/shared-agents/dispatch.md +13 -0
- package/scripts/_common.py +619 -18
- package/scripts/archive_milestone.py +160 -86
- package/scripts/bootstrap_repository.py +8 -5
- package/scripts/build_recovery.py +100 -32
- package/scripts/build_state.py +143 -24
- package/scripts/check_docs_audit.py +91 -7
- package/scripts/check_handoffs.py +662 -50
- package/scripts/check_task_briefs.py +338 -30
- package/scripts/check_update.py +2 -2
- package/scripts/core_hook_gate.py +10 -1
- package/scripts/core_hook_settings.py +11 -4
- package/scripts/detect_project.py +587 -32
- package/scripts/discussion_records.py +19 -13
- package/scripts/discussion_validate.py +152 -50
- package/scripts/dispatch_driver.py +501 -46
- package/scripts/git_guard.py +118 -31
- package/scripts/guard_hook.py +1567 -194
- package/scripts/install.mjs +77 -46
- package/scripts/install.py +611 -50
- package/scripts/integration.py +580 -25
- package/scripts/isolation.py +1438 -57
- package/scripts/lean_verification.py +99 -19
- package/scripts/loop_run.py +31 -29
- package/scripts/members.py +706 -0
- package/scripts/migrate_core.py +2 -2
- package/scripts/path_config.py +5 -5
- package/scripts/pipeline_diagnose.py +82 -31
- package/scripts/pipeline_git.py +124 -34
- package/scripts/pipeline_state.py +287 -87
- package/scripts/pipeline_undo.py +218 -30
- package/scripts/promote_lookahead.py +1 -1
- package/scripts/review_panel.py +1 -1
- package/scripts/runtime_store.py +55 -11
- package/scripts/skill-resources.json +37 -5
- package/scripts/state_checkpoint.py +135 -44
- package/scripts/state_promote.py +2 -0
- package/scripts/status_runtime.py +58 -10
- package/scripts/sync_skill_resources.py +8 -7
- package/scripts/task_context.py +12 -3
- package/scripts/token_budget.py +12 -8
- package/scripts/wizard.mjs +1 -0
- package/scripts/workflow_run.py +107 -30
- package/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path/BUILD.md +7 -1
- package/skills/gsd-path/DECIDE.md +11 -4
- package/skills/gsd-path/DEFINE.md +36 -4
- package/skills/gsd-path/DOCS-AUDIT.md +6 -4
- package/skills/gsd-path/INSPECT.md +5 -4
- package/skills/gsd-path/PLAN.md +18 -4
- package/skills/gsd-path/RESEARCH.md +13 -1
- package/skills/gsd-path/ROADMAP.md +2 -1
- package/skills/gsd-path/SHIP.md +33 -11
- package/skills/gsd-path/SKILL.md +45 -1
- package/skills/gsd-path/references/build-native.md +63 -15
- package/skills/gsd-path/references/codebase-mapper.md +2 -1
- package/skills/gsd-path/references/dispatch.md +13 -0
- package/skills/gsd-path/references/docs-auditor.md +7 -1
- package/skills/gsd-path/references/operating-rules.md +214 -0
- package/skills/gsd-path/references/planner.md +22 -4
- package/skills/gsd-path/references/reviewer-final.md +20 -2
- package/skills/gsd-path/references/reviewer-wave.md +18 -5
- package/skills/gsd-path/references/reviewer.md +3 -1
- package/skills/gsd-path/references/spec-probes.md +150 -0
- package/skills/gsd-path/scripts/_common.py +619 -18
- package/skills/gsd-path/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
- package/skills/gsd-path/scripts/build_recovery.py +100 -32
- package/skills/gsd-path/scripts/build_state.py +143 -24
- package/skills/gsd-path/scripts/check_docs_audit.py +91 -7
- package/skills/gsd-path/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path/scripts/check_update.py +2 -2
- package/skills/gsd-path/scripts/detect_project.py +587 -32
- package/skills/gsd-path/scripts/discussion_records.py +19 -13
- package/skills/gsd-path/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path/scripts/dispatch_driver.py +501 -46
- package/skills/gsd-path/scripts/git_guard.py +118 -31
- package/skills/gsd-path/scripts/integration.py +580 -25
- package/skills/gsd-path/scripts/isolation.py +1438 -57
- package/skills/gsd-path/scripts/lean_verification.py +99 -19
- package/skills/gsd-path/scripts/loop_run.py +31 -29
- package/skills/gsd-path/scripts/members.py +706 -0
- package/skills/gsd-path/scripts/path_config.py +5 -5
- package/skills/gsd-path/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path/scripts/review_panel.py +1 -1
- package/skills/gsd-path/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path/scripts/state_promote.py +2 -0
- package/skills/gsd-path/scripts/status_runtime.py +58 -10
- package/skills/gsd-path/scripts/task_context.py +12 -3
- package/skills/gsd-path/scripts/token_budget.py +12 -8
- package/skills/gsd-path/scripts/workflow_run.py +107 -30
- package/skills/gsd-path/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path/templates/codebase.md +1 -1
- package/skills/gsd-path/templates/docs-audit.md +6 -2
- package/skills/gsd-path/templates/final-review.md +16 -4
- package/skills/gsd-path/templates/gap-review.md +2 -1
- package/skills/gsd-path/templates/intent.md +30 -0
- package/skills/gsd-path/templates/patch-findings.md +3 -3
- package/skills/gsd-path/templates/plan.md +17 -2
- package/skills/gsd-path/templates/research-handoff.md +14 -3
- package/skills/gsd-path/templates/state.md +1 -1
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +16 -4
- package/skills/gsd-path-build/SKILL.md +7 -1
- package/skills/gsd-path-build/references/build-native.md +63 -15
- package/skills/gsd-path-build/references/dispatch.md +13 -0
- package/skills/gsd-path-build/references/operating-rules.md +214 -0
- package/skills/gsd-path-build/references/reviewer-final.md +20 -2
- package/skills/gsd-path-build/references/reviewer-wave.md +18 -5
- package/skills/gsd-path-build/references/reviewer.md +3 -1
- package/skills/gsd-path-build/references/spec-probes.md +150 -0
- package/skills/gsd-path-build/scripts/_common.py +619 -18
- package/skills/gsd-path-build/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-build/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-build/scripts/build_state.py +143 -24
- package/skills/gsd-path-build/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-build/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-build/scripts/detect_project.py +587 -32
- package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-build/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-build/scripts/dispatch_driver.py +501 -46
- package/skills/gsd-path-build/scripts/integration.py +580 -25
- package/skills/gsd-path-build/scripts/isolation.py +1438 -57
- package/skills/gsd-path-build/scripts/loop_run.py +31 -29
- package/skills/gsd-path-build/scripts/members.py +706 -0
- package/skills/gsd-path-build/scripts/path_config.py +5 -5
- package/skills/gsd-path-build/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-build/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-build/scripts/review_panel.py +1 -1
- package/skills/gsd-path-build/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-build/scripts/state_promote.py +2 -0
- package/skills/gsd-path-build/scripts/task_context.py +12 -3
- package/skills/gsd-path-build/scripts/token_budget.py +12 -8
- package/skills/gsd-path-build/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-build/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +16 -4
- package/skills/gsd-path-decide/SKILL.md +11 -4
- package/skills/gsd-path-decide/references/dispatch.md +13 -0
- package/skills/gsd-path-decide/references/operating-rules.md +214 -0
- package/skills/gsd-path-decide/scripts/_common.py +619 -18
- package/skills/gsd-path-decide/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-decide/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-decide/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-decide/scripts/detect_project.py +587 -32
- package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-decide/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-decide/scripts/integration.py +580 -25
- package/skills/gsd-path-decide/scripts/isolation.py +1438 -57
- package/skills/gsd-path-decide/scripts/loop_run.py +31 -29
- package/skills/gsd-path-decide/scripts/members.py +706 -0
- package/skills/gsd-path-decide/scripts/path_config.py +5 -5
- package/skills/gsd-path-decide/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-decide/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
- package/skills/gsd-path-decide/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-define/SKILL.md +36 -4
- package/skills/gsd-path-define/references/operating-rules.md +214 -0
- package/skills/gsd-path-define/references/spec-probes.md +150 -0
- package/skills/gsd-path-define/scripts/_common.py +619 -18
- package/skills/gsd-path-define/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-define/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-define/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-define/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-define/scripts/detect_project.py +587 -32
- package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-define/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-define/scripts/integration.py +580 -25
- package/skills/gsd-path-define/scripts/isolation.py +1438 -57
- package/skills/gsd-path-define/scripts/loop_run.py +31 -29
- package/skills/gsd-path-define/scripts/members.py +706 -0
- package/skills/gsd-path-define/scripts/path_config.py +5 -5
- package/skills/gsd-path-define/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-define/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-define/scripts/review_panel.py +1 -1
- package/skills/gsd-path-define/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-define/scripts/state_promote.py +2 -0
- package/skills/gsd-path-define/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-define/templates/intent.md +30 -0
- package/skills/gsd-path-define/templates/state.md +1 -1
- package/skills/gsd-path-discuss/SKILL.md +2 -1
- package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
- package/skills/gsd-path-discuss/scripts/_common.py +619 -18
- package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-discuss/scripts/loop_run.py +31 -29
- package/skills/gsd-path-discuss/scripts/path_config.py +5 -5
- package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-docs-audit/SKILL.md +6 -4
- package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +7 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +619 -18
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +91 -7
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +587 -32
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-docs-audit/scripts/integration.py +580 -25
- package/skills/gsd-path-docs-audit/scripts/isolation.py +1438 -57
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +31 -29
- package/skills/gsd-path-docs-audit/scripts/members.py +706 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +5 -5
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-docs-audit/templates/docs-audit.md +6 -2
- package/skills/gsd-path-forensics/SKILL.md +2 -1
- package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
- package/skills/gsd-path-forensics/scripts/_common.py +619 -18
- package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-forensics/scripts/loop_run.py +31 -29
- package/skills/gsd-path-forensics/scripts/path_config.py +5 -5
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +218 -30
- package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-inspect/SKILL.md +5 -4
- package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
- package/skills/gsd-path-inspect/references/dispatch.md +13 -0
- package/skills/gsd-path-inspect/references/docs-auditor.md +7 -1
- package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
- package/skills/gsd-path-inspect/scripts/_common.py +619 -18
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-inspect/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +91 -7
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-inspect/scripts/detect_project.py +587 -32
- package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-inspect/scripts/integration.py +580 -25
- package/skills/gsd-path-inspect/scripts/isolation.py +1438 -57
- package/skills/gsd-path-inspect/scripts/loop_run.py +31 -29
- package/skills/gsd-path-inspect/scripts/members.py +706 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +5 -5
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
- package/skills/gsd-path-inspect/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-inspect/templates/codebase.md +1 -1
- package/skills/gsd-path-inspect/templates/docs-audit.md +6 -2
- package/skills/gsd-path-inspect/templates/state.md +1 -1
- package/skills/gsd-path-loop/SKILL.md +2 -1
- package/skills/gsd-path-loop/references/operating-rules.md +214 -0
- package/skills/gsd-path-loop/scripts/_common.py +619 -18
- package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-loop/scripts/loop_run.py +31 -29
- package/skills/gsd-path-loop/scripts/path_config.py +5 -5
- package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
- package/skills/gsd-path-loop/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-migrate/SKILL.md +2 -1
- package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
- package/skills/gsd-path-migrate/scripts/_common.py +619 -18
- package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
- package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
- package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-migrate/scripts/loop_run.py +31 -29
- package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
- package/skills/gsd-path-migrate/scripts/path_config.py +5 -5
- package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/SKILL.md +18 -4
- package/skills/gsd-path-plan/references/dispatch.md +13 -0
- package/skills/gsd-path-plan/references/operating-rules.md +214 -0
- package/skills/gsd-path-plan/references/planner.md +22 -4
- package/skills/gsd-path-plan/references/spec-probes.md +150 -0
- package/skills/gsd-path-plan/scripts/_common.py +619 -18
- package/skills/gsd-path-plan/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-plan/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-plan/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-plan/scripts/detect_project.py +587 -32
- package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-plan/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-plan/scripts/integration.py +580 -25
- package/skills/gsd-path-plan/scripts/isolation.py +1438 -57
- package/skills/gsd-path-plan/scripts/loop_run.py +31 -29
- package/skills/gsd-path-plan/scripts/members.py +706 -0
- package/skills/gsd-path-plan/scripts/path_config.py +5 -5
- package/skills/gsd-path-plan/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-plan/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
- package/skills/gsd-path-plan/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-plan/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
- package/skills/gsd-path-plan/templates/plan.md +17 -2
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/SKILL.md +13 -1
- package/skills/gsd-path-research/references/dispatch.md +13 -0
- package/skills/gsd-path-research/references/operating-rules.md +214 -0
- package/skills/gsd-path-research/scripts/_common.py +619 -18
- package/skills/gsd-path-research/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-research/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-research/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-research/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-research/scripts/detect_project.py +587 -32
- package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-research/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-research/scripts/integration.py +580 -25
- package/skills/gsd-path-research/scripts/isolation.py +1438 -57
- package/skills/gsd-path-research/scripts/loop_run.py +31 -29
- package/skills/gsd-path-research/scripts/members.py +706 -0
- package/skills/gsd-path-research/scripts/path_config.py +5 -5
- package/skills/gsd-path-research/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-research/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-research/scripts/review_panel.py +1 -1
- package/skills/gsd-path-research/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-research/scripts/state_promote.py +2 -0
- package/skills/gsd-path-research/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-research/templates/research-handoff.md +14 -3
- package/skills/gsd-path-roadmap/SKILL.md +2 -1
- package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
- package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +619 -18
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-roadmap/scripts/detect_project.py +587 -32
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-roadmap/scripts/integration.py +580 -25
- package/skills/gsd-path-roadmap/scripts/isolation.py +1438 -57
- package/skills/gsd-path-roadmap/scripts/loop_run.py +31 -29
- package/skills/gsd-path-roadmap/scripts/members.py +706 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +5 -5
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-ship/SKILL.md +33 -11
- package/skills/gsd-path-ship/references/dispatch.md +13 -0
- package/skills/gsd-path-ship/references/operating-rules.md +214 -0
- package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
- package/skills/gsd-path-ship/references/reviewer-wave.md +18 -5
- package/skills/gsd-path-ship/references/reviewer.md +3 -1
- package/skills/gsd-path-ship/references/spec-probes.md +150 -0
- package/skills/gsd-path-ship/scripts/_common.py +619 -18
- package/skills/gsd-path-ship/scripts/archive_milestone.py +160 -86
- package/skills/gsd-path-ship/scripts/build_recovery.py +100 -32
- package/skills/gsd-path-ship/scripts/build_state.py +143 -24
- package/skills/gsd-path-ship/scripts/check_handoffs.py +662 -50
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +338 -30
- package/skills/gsd-path-ship/scripts/detect_project.py +587 -32
- package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-ship/scripts/discussion_validate.py +152 -50
- package/skills/gsd-path-ship/scripts/integration.py +580 -25
- package/skills/gsd-path-ship/scripts/isolation.py +1438 -57
- package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
- package/skills/gsd-path-ship/scripts/loop_run.py +31 -29
- package/skills/gsd-path-ship/scripts/members.py +706 -0
- package/skills/gsd-path-ship/scripts/path_config.py +5 -5
- package/skills/gsd-path-ship/scripts/pipeline_git.py +124 -34
- package/skills/gsd-path-ship/scripts/pipeline_state.py +287 -87
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +135 -44
- package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
- package/skills/gsd-path-ship/scripts/workflow_run.py +107 -30
- package/skills/gsd-path-ship/scripts/worktree_paths.py +72 -10
- package/skills/gsd-path-ship/templates/final-review.md +16 -4
- package/skills/gsd-path-ship/templates/gap-review.md +2 -1
- package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
- package/skills/gsd-path-ship/templates/wave-review.md +16 -4
- package/skills/gsd-path-undo/SKILL.md +2 -1
- package/skills/gsd-path-undo/references/operating-rules.md +214 -0
- package/skills/gsd-path-undo/scripts/_common.py +619 -18
- package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-undo/scripts/loop_run.py +31 -29
- package/skills/gsd-path-undo/scripts/path_config.py +5 -5
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +218 -30
- package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
- package/skills/gsd-path-undo/scripts/worktree_paths.py +72 -10
- package/skills/path/BUILD.md +7 -1
- package/skills/path/DECIDE.md +11 -4
- package/skills/path/DEFINE.md +36 -4
- package/skills/path/DOCS-AUDIT.md +6 -4
- package/skills/path/INSPECT.md +5 -4
- package/skills/path/PLAN.md +18 -4
- package/skills/path/RESEARCH.md +13 -1
- package/skills/path/ROADMAP.md +2 -1
- package/skills/path/SHIP.md +33 -11
- package/skills/path/SKILL.md +45 -1
- package/skills/path/references/build-native.md +63 -15
- package/skills/path/references/codebase-mapper.md +2 -1
- package/skills/path/references/dispatch.md +13 -0
- package/skills/path/references/docs-auditor.md +7 -1
- package/skills/path/references/operating-rules.md +214 -0
- package/skills/path/references/planner.md +22 -4
- package/skills/path/references/reviewer-final.md +20 -2
- package/skills/path/references/reviewer-wave.md +18 -5
- package/skills/path/references/reviewer.md +3 -1
- package/skills/path/references/spec-probes.md +150 -0
- package/skills/path/scripts/_common.py +619 -18
- package/skills/path/scripts/archive_milestone.py +160 -86
- package/skills/path/scripts/bootstrap_repository.py +8 -5
- package/skills/path/scripts/build_recovery.py +100 -32
- package/skills/path/scripts/build_state.py +143 -24
- package/skills/path/scripts/check_docs_audit.py +91 -7
- package/skills/path/scripts/check_handoffs.py +662 -50
- package/skills/path/scripts/check_task_briefs.py +338 -30
- package/skills/path/scripts/check_update.py +2 -2
- package/skills/path/scripts/detect_project.py +587 -32
- package/skills/path/scripts/discussion_records.py +19 -13
- package/skills/path/scripts/discussion_validate.py +152 -50
- package/skills/path/scripts/dispatch_driver.py +501 -46
- package/skills/path/scripts/git_guard.py +118 -31
- package/skills/path/scripts/integration.py +580 -25
- package/skills/path/scripts/isolation.py +1438 -57
- package/skills/path/scripts/lean_verification.py +99 -19
- package/skills/path/scripts/loop_run.py +31 -29
- package/skills/path/scripts/members.py +706 -0
- package/skills/path/scripts/path_config.py +5 -5
- package/skills/path/scripts/pipeline_git.py +124 -34
- package/skills/path/scripts/pipeline_state.py +287 -87
- package/skills/path/scripts/promote_lookahead.py +1 -1
- package/skills/path/scripts/review_panel.py +1 -1
- package/skills/path/scripts/state_checkpoint.py +135 -44
- package/skills/path/scripts/state_promote.py +2 -0
- package/skills/path/scripts/status_runtime.py +58 -10
- package/skills/path/scripts/task_context.py +12 -3
- package/skills/path/scripts/token_budget.py +12 -8
- package/skills/path/scripts/workflow_run.py +107 -30
- package/skills/path/scripts/worktree_paths.py +72 -10
- package/skills/path/templates/codebase.md +1 -1
- package/skills/path/templates/docs-audit.md +6 -2
- package/skills/path/templates/final-review.md +16 -4
- package/skills/path/templates/gap-review.md +2 -1
- package/skills/path/templates/intent.md +30 -0
- package/skills/path/templates/patch-findings.md +3 -3
- package/skills/path/templates/plan.md +17 -2
- package/skills/path/templates/research-handoff.md +14 -3
- package/skills/path/templates/state.md +1 -1
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +16 -4
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
|
@@ -9,26 +9,12 @@ sys.dont_write_bytecode = True
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import re
|
|
12
|
+
import contextlib
|
|
12
13
|
from pathlib import Path, PurePosixPath
|
|
13
14
|
from typing import Optional
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
from pipeline_git import (
|
|
17
|
-
bound_branch_name,
|
|
18
|
-
default_branch_name,
|
|
19
|
-
integrate_commit_body,
|
|
20
|
-
integrate_subject,
|
|
21
|
-
is_bound_branch,
|
|
22
|
-
milestone_id,
|
|
23
|
-
milestone_number,
|
|
24
|
-
ship_subject,
|
|
25
|
-
)
|
|
26
|
-
from pipeline_state import PipelineState
|
|
27
|
-
from isolation import publication_base
|
|
28
|
-
import _common
|
|
29
|
-
import worktree_paths
|
|
30
|
-
except ImportError: # pragma: no cover - package import used by tests
|
|
31
|
-
from scripts.pipeline_git import (
|
|
16
|
+
if __package__:
|
|
17
|
+
from .pipeline_git import (
|
|
32
18
|
bound_branch_name,
|
|
33
19
|
default_branch_name,
|
|
34
20
|
integrate_commit_body,
|
|
@@ -38,10 +24,41 @@ except ImportError: # pragma: no cover - package import used by tests
|
|
|
38
24
|
milestone_number,
|
|
39
25
|
ship_subject,
|
|
40
26
|
)
|
|
41
|
-
from
|
|
42
|
-
from
|
|
43
|
-
from
|
|
44
|
-
from
|
|
27
|
+
from .pipeline_state import PipelineState
|
|
28
|
+
from .isolation import publication_base
|
|
29
|
+
from . import _common
|
|
30
|
+
from . import worktree_paths
|
|
31
|
+
else:
|
|
32
|
+
try:
|
|
33
|
+
from pipeline_git import (
|
|
34
|
+
bound_branch_name,
|
|
35
|
+
default_branch_name,
|
|
36
|
+
integrate_commit_body,
|
|
37
|
+
integrate_subject,
|
|
38
|
+
is_bound_branch,
|
|
39
|
+
milestone_id,
|
|
40
|
+
milestone_number,
|
|
41
|
+
ship_subject,
|
|
42
|
+
)
|
|
43
|
+
from pipeline_state import PipelineState
|
|
44
|
+
from isolation import publication_base
|
|
45
|
+
import _common
|
|
46
|
+
import worktree_paths
|
|
47
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
48
|
+
from scripts.pipeline_git import (
|
|
49
|
+
bound_branch_name,
|
|
50
|
+
default_branch_name,
|
|
51
|
+
integrate_commit_body,
|
|
52
|
+
integrate_subject,
|
|
53
|
+
is_bound_branch,
|
|
54
|
+
milestone_id,
|
|
55
|
+
milestone_number,
|
|
56
|
+
ship_subject,
|
|
57
|
+
)
|
|
58
|
+
from scripts.pipeline_state import PipelineState
|
|
59
|
+
from scripts.isolation import publication_base
|
|
60
|
+
from scripts import _common
|
|
61
|
+
from scripts import worktree_paths
|
|
45
62
|
|
|
46
63
|
|
|
47
64
|
if __package__:
|
|
@@ -541,8 +558,8 @@ def ensure_integration_tag(
|
|
|
541
558
|
|
|
542
559
|
def github_repository(project: Path) -> str:
|
|
543
560
|
remote = archive_milestone.require_git_success(
|
|
544
|
-
run_git(project, "
|
|
545
|
-
"
|
|
561
|
+
run_git(project, "config", "--get", "remote.origin.url"),
|
|
562
|
+
"read configured origin URL",
|
|
546
563
|
)
|
|
547
564
|
patterns = (
|
|
548
565
|
r"https://github\.com/([^/]+/[^/]+?)(?:\.git)?$",
|
|
@@ -701,8 +718,9 @@ def update_pull_request_body(
|
|
|
701
718
|
archive_path: str,
|
|
702
719
|
ship_commit: str,
|
|
703
720
|
branch: str,
|
|
721
|
+
expected_body: Optional[str] = None,
|
|
704
722
|
) -> dict:
|
|
705
|
-
expected = pull_request_body(archive_path, ship_commit, branch)
|
|
723
|
+
expected = expected_body if expected_body is not None else pull_request_body(archive_path, ship_commit, branch)
|
|
706
724
|
current = pull.get("body")
|
|
707
725
|
if isinstance(current, str) and current.rstrip().endswith(PR_CREDIT_LINE):
|
|
708
726
|
return pull
|
|
@@ -780,6 +798,36 @@ def pull_request_tag_metadata(project: Path, tag_ref: str) -> dict[str, str]:
|
|
|
780
798
|
return fields
|
|
781
799
|
|
|
782
800
|
|
|
801
|
+
def external_landing_tag_message(
|
|
802
|
+
archive_name: str, ship_commit: str, landing: str
|
|
803
|
+
) -> str:
|
|
804
|
+
return (
|
|
805
|
+
f"milestone {archive_name}\n\n"
|
|
806
|
+
"Mode: external-landing\n"
|
|
807
|
+
f"Ship: {ship_commit}\n"
|
|
808
|
+
f"Landing: {landing}"
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def external_landing_tag_metadata(project: Path, tag_ref: str) -> dict[str, str]:
|
|
813
|
+
message = archive_milestone.require_git_success(
|
|
814
|
+
run_git(project, "for-each-ref", "--format=%(contents)", tag_ref),
|
|
815
|
+
"inspect external-landing milestone tag",
|
|
816
|
+
)
|
|
817
|
+
fields: dict[str, str] = {}
|
|
818
|
+
for line in message.splitlines():
|
|
819
|
+
match = re.fullmatch(r"(Mode|Ship|Landing): (.+)", line)
|
|
820
|
+
if match:
|
|
821
|
+
if match.group(1) in fields:
|
|
822
|
+
raise ArchiveError("external-landing milestone tag repeats metadata")
|
|
823
|
+
fields[match.group(1)] = match.group(2)
|
|
824
|
+
if set(fields) != {"Mode", "Ship", "Landing"}:
|
|
825
|
+
raise ArchiveError("external-landing milestone tag is missing metadata")
|
|
826
|
+
if fields["Mode"] != "external-landing":
|
|
827
|
+
raise ArchiveError("external-landing milestone tag has the wrong mode")
|
|
828
|
+
return fields
|
|
829
|
+
|
|
830
|
+
|
|
783
831
|
def require_pull_request_merge(
|
|
784
832
|
project: Path,
|
|
785
833
|
merge_commit: str,
|
|
@@ -1004,6 +1052,78 @@ def integrate_pull_request(
|
|
|
1004
1052
|
return validate_integrated(project, archive_milestone.milestone_slug(state))
|
|
1005
1053
|
|
|
1006
1054
|
|
|
1055
|
+
def integrate_external_landing(
|
|
1056
|
+
project: Path,
|
|
1057
|
+
state: PipelineState,
|
|
1058
|
+
archive_path: str,
|
|
1059
|
+
archive_name: str,
|
|
1060
|
+
ship_commit: str,
|
|
1061
|
+
remote_default: str,
|
|
1062
|
+
default_name: str,
|
|
1063
|
+
bound_branch: str,
|
|
1064
|
+
) -> dict:
|
|
1065
|
+
if state.branch is None:
|
|
1066
|
+
raise ArchiveError("external-landing integration requires a bound branch")
|
|
1067
|
+
publishable_bound_branch(project, bound_branch, ship_commit)
|
|
1068
|
+
contains_ship = run_git(
|
|
1069
|
+
project, "merge-base", "--is-ancestor", ship_commit, remote_default
|
|
1070
|
+
)
|
|
1071
|
+
if contains_ship.returncode == 1:
|
|
1072
|
+
publish_bound_branch(project, bound_branch, ship_commit)
|
|
1073
|
+
return {
|
|
1074
|
+
"status": "awaiting-merge",
|
|
1075
|
+
"mode": "external-landing",
|
|
1076
|
+
"archive": archive_path,
|
|
1077
|
+
"commit": ship_commit,
|
|
1078
|
+
}
|
|
1079
|
+
if contains_ship.returncode != 0:
|
|
1080
|
+
archive_milestone.require_git_success(
|
|
1081
|
+
contains_ship, "inspect ship ancestry on the remote default"
|
|
1082
|
+
)
|
|
1083
|
+
raise AssertionError("unreachable")
|
|
1084
|
+
|
|
1085
|
+
merge_commit = find_integrate_commit(
|
|
1086
|
+
project, remote_default, archive_name, ship_commit
|
|
1087
|
+
)
|
|
1088
|
+
require_generated_integration_commit(
|
|
1089
|
+
project,
|
|
1090
|
+
merge_commit,
|
|
1091
|
+
archive_path,
|
|
1092
|
+
archive_name,
|
|
1093
|
+
ship_commit,
|
|
1094
|
+
default_name,
|
|
1095
|
+
bound_branch,
|
|
1096
|
+
)
|
|
1097
|
+
tag_name = f"milestone/{archive_name}"
|
|
1098
|
+
message = external_landing_tag_message(archive_name, ship_commit, merge_commit)
|
|
1099
|
+
tag_object, tag_published = ensure_integration_tag(
|
|
1100
|
+
project,
|
|
1101
|
+
tag_name,
|
|
1102
|
+
merge_commit,
|
|
1103
|
+
message,
|
|
1104
|
+
)
|
|
1105
|
+
if not tag_published:
|
|
1106
|
+
archive_milestone.require_git_success(
|
|
1107
|
+
run_git(
|
|
1108
|
+
project,
|
|
1109
|
+
"push",
|
|
1110
|
+
"origin",
|
|
1111
|
+
f"refs/tags/{tag_name}:refs/tags/{tag_name}",
|
|
1112
|
+
),
|
|
1113
|
+
"push milestone tag",
|
|
1114
|
+
)
|
|
1115
|
+
archive_milestone.require_git_success(
|
|
1116
|
+
run_git(
|
|
1117
|
+
project,
|
|
1118
|
+
"update-ref",
|
|
1119
|
+
f"refs/remotes/origin/tags/{tag_name}",
|
|
1120
|
+
tag_object,
|
|
1121
|
+
),
|
|
1122
|
+
"refresh local milestone-tag ref",
|
|
1123
|
+
)
|
|
1124
|
+
return validate_integrated(project, archive_milestone.milestone_slug(state))
|
|
1125
|
+
|
|
1126
|
+
|
|
1007
1127
|
def integrate(repo: Path, slug: str) -> dict:
|
|
1008
1128
|
project = repo.resolve()
|
|
1009
1129
|
active_root = archive_milestone.require_project_layout(project)
|
|
@@ -1053,6 +1173,17 @@ def integrate(repo: Path, slug: str) -> dict:
|
|
|
1053
1173
|
archive_name,
|
|
1054
1174
|
ship_commit,
|
|
1055
1175
|
)
|
|
1176
|
+
if state.integration == "external-landing":
|
|
1177
|
+
return integrate_external_landing(
|
|
1178
|
+
project,
|
|
1179
|
+
state,
|
|
1180
|
+
archive_path,
|
|
1181
|
+
archive_name,
|
|
1182
|
+
ship_commit,
|
|
1183
|
+
remote_default,
|
|
1184
|
+
default_name,
|
|
1185
|
+
bound_branch,
|
|
1186
|
+
)
|
|
1056
1187
|
publishable_bound_branch(project, bound_branch, ship_commit)
|
|
1057
1188
|
integration_branch, worktree = integration_names(project, archive_name, pin=True)
|
|
1058
1189
|
interrupted_worktree = registered_worktree(project, integration_branch)
|
|
@@ -1328,6 +1459,32 @@ def validate_integrated(
|
|
|
1328
1459
|
merge_commit,
|
|
1329
1460
|
),
|
|
1330
1461
|
)
|
|
1462
|
+
elif state.integration == "external-landing":
|
|
1463
|
+
merge_commit = find_integrate_commit(
|
|
1464
|
+
project, remote_default, archive_name, ship_commit
|
|
1465
|
+
)
|
|
1466
|
+
archive_milestone.require_canonical_commit_body(
|
|
1467
|
+
project,
|
|
1468
|
+
merge_commit,
|
|
1469
|
+
integrate_subject(archive_name, default_name),
|
|
1470
|
+
integrate_commit_body(configured, ship_commit, default_name, bound_branch),
|
|
1471
|
+
"integration",
|
|
1472
|
+
)
|
|
1473
|
+
require_pull_request_merge(project, merge_commit, ship_commit, remote_default)
|
|
1474
|
+
if run_git(project, "rev-parse", "--verify", "--quiet", tag_ref).returncode != 0:
|
|
1475
|
+
raise ArchiveError(f"missing milestone tag: {tag_name}")
|
|
1476
|
+
metadata = external_landing_tag_metadata(project, tag_ref)
|
|
1477
|
+
if metadata["Ship"] != ship_commit:
|
|
1478
|
+
raise ArchiveError("external-landing milestone tag names the wrong ship commit")
|
|
1479
|
+
if metadata["Landing"] != merge_commit:
|
|
1480
|
+
raise ArchiveError("external-landing milestone tag names the wrong landing")
|
|
1481
|
+
require_annotated_tag(
|
|
1482
|
+
project,
|
|
1483
|
+
tag_ref,
|
|
1484
|
+
merge_commit,
|
|
1485
|
+
f"milestone tag {tag_name}",
|
|
1486
|
+
external_landing_tag_message(archive_name, ship_commit, merge_commit),
|
|
1487
|
+
)
|
|
1331
1488
|
else:
|
|
1332
1489
|
merge_commit = find_integrate_commit(
|
|
1333
1490
|
project, remote_default, archive_name, ship_commit
|
|
@@ -1364,7 +1521,8 @@ def validate_integrated(
|
|
|
1364
1521
|
ship_commit,
|
|
1365
1522
|
tag_name,
|
|
1366
1523
|
merge_commit,
|
|
1367
|
-
allow_missing_bound=state.integration
|
|
1524
|
+
allow_missing_bound=state.integration in ("pull-request", "external-landing")
|
|
1525
|
+
or (historical and has_origin),
|
|
1368
1526
|
retired=historical,
|
|
1369
1527
|
)
|
|
1370
1528
|
|
|
@@ -1394,6 +1552,12 @@ def validate_integrated(
|
|
|
1394
1552
|
raise ArchiveError(f"published {ref} differs from local integration evidence")
|
|
1395
1553
|
if pull_request is not None:
|
|
1396
1554
|
result["pull_request"] = pull_request
|
|
1555
|
+
for row in shipped.get("members", []):
|
|
1556
|
+
proven = validate_member_integrated(project, row["name"], configured, row["reviewed_head"], refresh=refresh)
|
|
1557
|
+
if (proven["merge"], proven["tag"]) != (row["merge"], row["tag"]):
|
|
1558
|
+
raise ArchiveError(f"member {row['name']} integration differs from the ship commit")
|
|
1559
|
+
if "members" in shipped:
|
|
1560
|
+
result["members"] = shipped["members"]
|
|
1397
1561
|
return result
|
|
1398
1562
|
|
|
1399
1563
|
|
|
@@ -1457,3 +1621,394 @@ def require_published_integration(
|
|
|
1457
1621
|
raise ArchiveError(
|
|
1458
1622
|
f"published milestone tag {tag_name} does not point at the integration merge"
|
|
1459
1623
|
)
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
def _member_names(coordinator: Path, member: str, archive_path: str) -> tuple[Path, str, str, str, str]:
|
|
1627
|
+
"""A member's checkout, coordinator project, bound branch, merge subject, and tag."""
|
|
1628
|
+
try:
|
|
1629
|
+
import isolation
|
|
1630
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
1631
|
+
from scripts import isolation
|
|
1632
|
+
try:
|
|
1633
|
+
checkout, project = isolation.member_checkout(coordinator.resolve(), member)
|
|
1634
|
+
except isolation.IsolationError as error:
|
|
1635
|
+
raise ArchiveError(str(error)) from error
|
|
1636
|
+
archive_name = PurePosixPath(archive_path).name
|
|
1637
|
+
identifier = milestone_id(milestone_number(archive_name))
|
|
1638
|
+
bound = f"gsd-path/{project}-{identifier}"
|
|
1639
|
+
subject = f"integrate: {project} {identifier} — merge {bound} into main"
|
|
1640
|
+
return checkout, project, bound, subject, f"milestone/{project}-{archive_name}"
|
|
1641
|
+
|
|
1642
|
+
|
|
1643
|
+
def _member_merge_body(archive_path: str, member: str, reviewed_head: str) -> str:
|
|
1644
|
+
return f"Archive: {archive_path}\nMember: {member}\nReviewed-HEAD: {reviewed_head}"
|
|
1645
|
+
|
|
1646
|
+
|
|
1647
|
+
def member_pull_request_tag_message(tag_name: str, pull_request: str, reviewed_head: str, landing: str) -> str:
|
|
1648
|
+
return (f"milestone {tag_name.removeprefix('milestone/')}\n\nMode: pull-request\n"
|
|
1649
|
+
f"Pull-Request: {pull_request}\nReviewed-HEAD: {reviewed_head}\nLanding: {landing}")
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def member_pull_request_tag_metadata(contents: str) -> dict[str, str]:
|
|
1653
|
+
fields: dict[str, str] = {}
|
|
1654
|
+
for line in contents.splitlines():
|
|
1655
|
+
match = re.fullmatch(r"(Mode|Pull-Request|Reviewed-HEAD|Landing): (.+)", line)
|
|
1656
|
+
if match:
|
|
1657
|
+
if match.group(1) in fields:
|
|
1658
|
+
raise ArchiveError("pull-request member tag repeats metadata")
|
|
1659
|
+
fields[match.group(1)] = match.group(2)
|
|
1660
|
+
if set(fields) != {"Mode", "Pull-Request", "Reviewed-HEAD", "Landing"}:
|
|
1661
|
+
raise ArchiveError("pull-request member tag is missing metadata")
|
|
1662
|
+
if fields["Mode"] != "pull-request":
|
|
1663
|
+
raise ArchiveError("pull-request member tag has the wrong mode")
|
|
1664
|
+
return fields
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def _find_member_merge(checkout: Path, subject: str, body: str, reviewed_head: str) -> Optional[str]:
|
|
1668
|
+
"""The member merge in origin/main's first-parent history, if published."""
|
|
1669
|
+
log = archive_milestone.require_git_success(
|
|
1670
|
+
run_git(checkout, "log", "--first-parent", "--format=%H%x00%s", "origin/main"),
|
|
1671
|
+
"inspect member origin/main first-parent history",
|
|
1672
|
+
)
|
|
1673
|
+
matches = [commit for commit, _, text in (line.partition("\x00") for line in log.splitlines())
|
|
1674
|
+
if text == subject]
|
|
1675
|
+
if len(matches) > 1:
|
|
1676
|
+
raise ArchiveError(f"multiple member merges named {subject!r} on origin/main")
|
|
1677
|
+
if not matches:
|
|
1678
|
+
return None
|
|
1679
|
+
_require_member_merge(checkout, matches[0], subject, body, reviewed_head)
|
|
1680
|
+
return matches[0]
|
|
1681
|
+
|
|
1682
|
+
|
|
1683
|
+
def _require_member_merge(checkout: Path, commit: str, subject: str, body: str, reviewed_head: str) -> str:
|
|
1684
|
+
"""Check a canonical member merge; return its first parent."""
|
|
1685
|
+
archive_milestone.require_canonical_commit_body(checkout, commit, subject, body, "member integration")
|
|
1686
|
+
parents = archive_milestone.require_git_success(
|
|
1687
|
+
run_git(checkout, "rev-list", "--parents", "-n", "1", commit), "inspect member merge parents"
|
|
1688
|
+
).split()
|
|
1689
|
+
if len(parents) != 3 or parents[2] != reviewed_head:
|
|
1690
|
+
raise ArchiveError("member merge second parent is not the member reviewed HEAD")
|
|
1691
|
+
return parents[1]
|
|
1692
|
+
|
|
1693
|
+
|
|
1694
|
+
def _create_member_merge(checkout: Path, project: str, archive_path: str, subject: str, body: str,
|
|
1695
|
+
reviewed_head: str, origin_main: str) -> str:
|
|
1696
|
+
identifier = milestone_id(milestone_number(PurePosixPath(archive_path).name))
|
|
1697
|
+
branch = f"gsd-path-integrate/{project}-{identifier}"
|
|
1698
|
+
try:
|
|
1699
|
+
worktree = worktree_paths.worktree_path(checkout, "integrate", f"{project}-{identifier}", pin=True)
|
|
1700
|
+
except (ValueError, OSError) as error:
|
|
1701
|
+
raise ArchiveError(str(error)) from error
|
|
1702
|
+
interrupted = registered_worktree(checkout, branch)
|
|
1703
|
+
if interrupted is not None and optional_ref(interrupted, "MERGE_HEAD") is not None:
|
|
1704
|
+
archive_milestone.require_git_success(run_git(interrupted, "merge", "--abort"),
|
|
1705
|
+
"abort interrupted member merge")
|
|
1706
|
+
remove_registered_worktree(checkout, branch, worktree)
|
|
1707
|
+
tip = optional_ref(checkout, f"refs/heads/{branch}")
|
|
1708
|
+
if tip is not None:
|
|
1709
|
+
base_tip = run_git(checkout, "merge-base", "--is-ancestor", tip, origin_main)
|
|
1710
|
+
if base_tip.returncode != 0:
|
|
1711
|
+
if base_tip.returncode != 1:
|
|
1712
|
+
archive_milestone.require_git_success(base_tip, "inspect interrupted member integration base")
|
|
1713
|
+
if _require_member_merge(checkout, tip, subject, body, reviewed_head) == origin_main:
|
|
1714
|
+
return tip
|
|
1715
|
+
delete_integration_branch(checkout, branch, tip)
|
|
1716
|
+
archive_milestone.require_git_success(
|
|
1717
|
+
run_git(checkout, "worktree", "add", "--quiet", "-b", branch, str(worktree), origin_main),
|
|
1718
|
+
"create member integration worktree",
|
|
1719
|
+
)
|
|
1720
|
+
merged = run_git(worktree, "merge", "--no-ff", "-m", subject, "-m", body, reviewed_head)
|
|
1721
|
+
if merged.returncode != 0:
|
|
1722
|
+
conflicts = run_git(worktree, "diff", "--name-only", "--diff-filter=U").stdout.split()
|
|
1723
|
+
if optional_ref(worktree, "MERGE_HEAD") is not None:
|
|
1724
|
+
run_git(worktree, "merge", "--abort")
|
|
1725
|
+
remove_registered_worktree(checkout, branch, worktree)
|
|
1726
|
+
delete_integration_branch(checkout, branch, origin_main)
|
|
1727
|
+
raise ArchiveError("member integration merge failed; Git aborted it without resolving files: "
|
|
1728
|
+
+ (", ".join(conflicts) or (merged.stderr or merged.stdout).strip()))
|
|
1729
|
+
merge = archive_milestone.require_git_success(run_git(worktree, "rev-parse", "HEAD"), "resolve member merge")
|
|
1730
|
+
if _require_member_merge(worktree, merge, subject, body, reviewed_head) != origin_main:
|
|
1731
|
+
raise ArchiveError("member merge first parent is not origin/main")
|
|
1732
|
+
remove_registered_worktree(checkout, branch, worktree)
|
|
1733
|
+
return merge
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
def _push_member_ref(checkout: Path, project: str, source: str, ref: str, sha: str, lease: Optional[str]) -> None:
|
|
1737
|
+
"""Authorize `ref` at `sha` for the member pre-push hook, then publish it."""
|
|
1738
|
+
try:
|
|
1739
|
+
import members
|
|
1740
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
1741
|
+
from scripts import members
|
|
1742
|
+
members.authorize_push(checkout, project, ref, sha)
|
|
1743
|
+
arguments = ["push"] + ([f"--force-with-lease={ref}:{lease}"] if lease is not None else [])
|
|
1744
|
+
archive_milestone.require_git_success(
|
|
1745
|
+
run_git(checkout, *arguments, "origin", f"{source}:{ref}"), f"publish member {ref}"
|
|
1746
|
+
)
|
|
1747
|
+
|
|
1748
|
+
|
|
1749
|
+
def _publish_member_bound(checkout: Path, project: str, member: str, bound: str, reviewed_head: str) -> None:
|
|
1750
|
+
bound_ref = f"refs/heads/{bound}"
|
|
1751
|
+
remote_bound = live_remote_ref(checkout, bound_ref)
|
|
1752
|
+
if remote_bound is None:
|
|
1753
|
+
_push_member_ref(checkout, project, reviewed_head, bound_ref, reviewed_head, "")
|
|
1754
|
+
elif remote_bound != reviewed_head:
|
|
1755
|
+
raise ArchiveError(f"member {member} origin/{bound} moved: {remote_bound}")
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
def _require_member_close(checkout: Path, member: str, bound: str, reviewed_head: str) -> None:
|
|
1759
|
+
if optional_ref(checkout, f"refs/heads/{bound}") != reviewed_head:
|
|
1760
|
+
raise ArchiveError(f"member {member} {bound} is not at its reviewed HEAD {reviewed_head}")
|
|
1761
|
+
if default_branch_name(refresh_origin(checkout)["remote_default"]) != "main":
|
|
1762
|
+
raise ArchiveError(f"member {member} remote default must be main")
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
def integrate_member(coordinator: Path, member: str, archive_path: str, reviewed_head: str) -> dict:
|
|
1766
|
+
"""Direct-mode member close: publish the bound branch, merge it into main, then tag."""
|
|
1767
|
+
checkout, project, bound, subject, tag_name = _member_names(coordinator, member, archive_path)
|
|
1768
|
+
body = _member_merge_body(archive_path, member, reviewed_head)
|
|
1769
|
+
_require_member_close(checkout, member, bound, reviewed_head)
|
|
1770
|
+
merge = _find_member_merge(checkout, subject, body, reviewed_head)
|
|
1771
|
+
if merge is None:
|
|
1772
|
+
origin_main = archive_milestone.require_git_success(
|
|
1773
|
+
run_git(checkout, "rev-parse", "origin/main"), "resolve member origin/main")
|
|
1774
|
+
ancestry = run_git(checkout, "merge-base", "--is-ancestor", reviewed_head, origin_main)
|
|
1775
|
+
if ancestry.returncode == 0:
|
|
1776
|
+
raise ArchiveError("member reviewed HEAD reached origin/main outside Path; user-approved recovery required")
|
|
1777
|
+
if ancestry.returncode != 1:
|
|
1778
|
+
archive_milestone.require_git_success(ancestry, "inspect member reviewed HEAD ancestry")
|
|
1779
|
+
merge = _create_member_merge(checkout, project, archive_path, subject, body, reviewed_head, origin_main)
|
|
1780
|
+
# Publish in order: bound branch, main, tag; a rerun publishes only what is missing.
|
|
1781
|
+
_publish_member_bound(checkout, project, member, bound, reviewed_head)
|
|
1782
|
+
if run_git(checkout, "merge-base", "--is-ancestor", merge, "origin/main").returncode != 0:
|
|
1783
|
+
_push_member_ref(checkout, project, merge, "refs/heads/main", merge, None)
|
|
1784
|
+
archive_milestone.require_git_success(
|
|
1785
|
+
run_git(checkout, "update-ref", "refs/remotes/origin/main", merge), "refresh member origin/main")
|
|
1786
|
+
tag_object, tag_published = ensure_integration_tag(checkout, tag_name, merge)
|
|
1787
|
+
if not tag_published:
|
|
1788
|
+
_push_member_ref(checkout, project, f"refs/tags/{tag_name}", f"refs/tags/{tag_name}", tag_object, None)
|
|
1789
|
+
archive_milestone.require_git_success(
|
|
1790
|
+
run_git(checkout, "update-ref", f"refs/remotes/origin/tags/{tag_name}", tag_object),
|
|
1791
|
+
"refresh member milestone-tag ref")
|
|
1792
|
+
identifier = milestone_id(milestone_number(PurePosixPath(archive_path).name))
|
|
1793
|
+
delete_integration_branch(checkout, f"gsd-path-integrate/{project}-{identifier}", merge)
|
|
1794
|
+
return validate_member_integrated(coordinator, member, archive_path, reviewed_head)
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
def integrate_member_pull_request(coordinator: Path, member: str, archive_path: str, reviewed_head: str) -> dict:
|
|
1798
|
+
"""Pull-request member close: open one PR for the bound branch; after a human merge, tag it."""
|
|
1799
|
+
checkout, project, bound, subject, tag_name = _member_names(coordinator, member, archive_path)
|
|
1800
|
+
body = _member_merge_body(archive_path, member, reviewed_head)
|
|
1801
|
+
_require_member_close(checkout, member, bound, reviewed_head)
|
|
1802
|
+
require_github_authentication()
|
|
1803
|
+
repository = github_repository(checkout)
|
|
1804
|
+
pull = find_pull_request(repository, bound, reviewed_head)
|
|
1805
|
+
if pull is None or pull["merged_at"] is None:
|
|
1806
|
+
if pull is not None and pull["state"] != "open":
|
|
1807
|
+
raise ArchiveError(f"member {member} pull request was closed without merging")
|
|
1808
|
+
_publish_member_bound(checkout, project, member, bound, reviewed_head)
|
|
1809
|
+
if pull is None:
|
|
1810
|
+
pull = require_pull_request_shape(github_api_json(
|
|
1811
|
+
f"repos/{repository}/pulls", "--method", "POST", "-f", f"title={subject}",
|
|
1812
|
+
"-f", f"head={bound}", "-f", "base=main", "-f", f"body={body}\n\n---\n{PR_CREDIT_LINE}"))
|
|
1813
|
+
require_pull_request_identity(repository, pull, bound, reviewed_head)
|
|
1814
|
+
else:
|
|
1815
|
+
pull = update_pull_request_body(
|
|
1816
|
+
repository, pull, archive_path, reviewed_head, bound,
|
|
1817
|
+
f"{body}\n\n---\n{PR_CREDIT_LINE}",
|
|
1818
|
+
)
|
|
1819
|
+
require_pull_request_merge_provenance(repository, pull["number"])
|
|
1820
|
+
return {"status": "awaiting-merge", "mode": "pull-request", "member": member,
|
|
1821
|
+
"pull_request": pull["html_url"]}
|
|
1822
|
+
merge = pull["merge_commit_sha"]
|
|
1823
|
+
if pull["state"] != "closed" or not isinstance(merge, str):
|
|
1824
|
+
raise ArchiveError(f"member {member} pull request merge metadata is invalid")
|
|
1825
|
+
require_pull_request_merge_provenance(repository, pull["number"], merge)
|
|
1826
|
+
refresh_origin(checkout)
|
|
1827
|
+
require_pull_request_merge(checkout, merge, reviewed_head, "origin/main")
|
|
1828
|
+
pull = update_pull_request_body(
|
|
1829
|
+
repository, pull, archive_path, reviewed_head, bound,
|
|
1830
|
+
f"{body}\n\n---\n{PR_CREDIT_LINE}",
|
|
1831
|
+
)
|
|
1832
|
+
message = member_pull_request_tag_message(tag_name, pull["html_url"], reviewed_head, merge)
|
|
1833
|
+
tag_object, tag_published = ensure_integration_tag(checkout, tag_name, merge, message)
|
|
1834
|
+
if not tag_published:
|
|
1835
|
+
_push_member_ref(checkout, project, f"refs/tags/{tag_name}", f"refs/tags/{tag_name}", tag_object, None)
|
|
1836
|
+
archive_milestone.require_git_success(
|
|
1837
|
+
run_git(checkout, "update-ref", f"refs/remotes/origin/tags/{tag_name}", tag_object),
|
|
1838
|
+
"refresh member milestone-tag ref")
|
|
1839
|
+
return validate_member_integrated(coordinator, member, archive_path, reviewed_head)
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
def validate_member_integrated(
|
|
1843
|
+
coordinator: Path, member: str, archive_path: str, reviewed_head: str, *, refresh: bool = True
|
|
1844
|
+
) -> dict:
|
|
1845
|
+
"""Prove the member merge is on origin/main and its published tag points at it."""
|
|
1846
|
+
checkout, _, bound, subject, tag_name = _member_names(coordinator, member, archive_path)
|
|
1847
|
+
tracking = f"refs/remotes/origin/tags/{tag_name}"
|
|
1848
|
+
if refresh:
|
|
1849
|
+
refresh_origin(checkout)
|
|
1850
|
+
published = live_remote_ref(checkout, f"refs/tags/{tag_name}")
|
|
1851
|
+
else:
|
|
1852
|
+
published = optional_ref(checkout, tracking)
|
|
1853
|
+
contents = ""
|
|
1854
|
+
if refresh and published is not None:
|
|
1855
|
+
archive_milestone.require_git_success(
|
|
1856
|
+
run_git(checkout, "update-ref", tracking, published), "refresh member milestone-tag ref")
|
|
1857
|
+
if published is not None:
|
|
1858
|
+
contents = run_git(checkout, "for-each-ref", "--format=%(contents)", tracking).stdout
|
|
1859
|
+
if "Mode: pull-request" in contents.splitlines():
|
|
1860
|
+
metadata = member_pull_request_tag_metadata(contents)
|
|
1861
|
+
if metadata["Reviewed-HEAD"] != reviewed_head:
|
|
1862
|
+
raise ArchiveError(f"member {member} tag {tag_name} names a different reviewed HEAD")
|
|
1863
|
+
merge = archive_milestone.require_git_success(
|
|
1864
|
+
run_git(checkout, "rev-parse", f"{tracking}^{{commit}}"), "resolve member tag target")
|
|
1865
|
+
if metadata["Landing"] != merge:
|
|
1866
|
+
raise ArchiveError(f"member {member} tag {tag_name} names a different landing")
|
|
1867
|
+
require_github_authentication()
|
|
1868
|
+
repository = github_repository(checkout)
|
|
1869
|
+
pull = find_pull_request(repository, bound, reviewed_head)
|
|
1870
|
+
if pull is None:
|
|
1871
|
+
raise ArchiveError(f"member {member} GitHub pull request is missing")
|
|
1872
|
+
if pull["html_url"] != metadata["Pull-Request"]:
|
|
1873
|
+
raise ArchiveError(f"member {member} tag {tag_name} names the wrong pull request")
|
|
1874
|
+
if (pull["state"] != "closed" or pull["merged_at"] is None
|
|
1875
|
+
or pull["merge_commit_sha"] != merge):
|
|
1876
|
+
raise ArchiveError(f"member {member} GitHub pull request is not merged at the tagged landing")
|
|
1877
|
+
require_pull_request_merge_provenance(repository, pull["number"], merge)
|
|
1878
|
+
require_pull_request_merge(checkout, merge, reviewed_head, "origin/main")
|
|
1879
|
+
require_annotated_tag(
|
|
1880
|
+
checkout, tracking, merge, f"member tag {tag_name}",
|
|
1881
|
+
member_pull_request_tag_message(tag_name, pull["html_url"], reviewed_head, merge),
|
|
1882
|
+
)
|
|
1883
|
+
else:
|
|
1884
|
+
merge = _find_member_merge(checkout, subject, _member_merge_body(archive_path, member, reviewed_head),
|
|
1885
|
+
reviewed_head)
|
|
1886
|
+
if merge is None:
|
|
1887
|
+
raise ArchiveError(f"member {member} has no {subject!r} merge on origin/main")
|
|
1888
|
+
if published is None:
|
|
1889
|
+
raise ArchiveError(f"member {member} tag {tag_name} is not published")
|
|
1890
|
+
require_annotated_tag(checkout, tracking, merge, f"member tag {tag_name}")
|
|
1891
|
+
return {"member": member, "merge": merge, "tag": tag_name, "tag_object": published}
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
def retire_member(coordinator: Path, member: str, archive_path: str, reviewed_head: str,
|
|
1895
|
+
merge: str, tag: str) -> dict:
|
|
1896
|
+
"""After integration, delete a member's bound branch everywhere and clear its authorizations."""
|
|
1897
|
+
try:
|
|
1898
|
+
import members
|
|
1899
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
1900
|
+
from scripts import members
|
|
1901
|
+
checkout, project, bound, _, tag_name = _member_names(coordinator, member, archive_path)
|
|
1902
|
+
proven = validate_member_integrated(coordinator, member, archive_path, reviewed_head)
|
|
1903
|
+
if (proven["merge"], proven["tag"]) != (merge, tag):
|
|
1904
|
+
raise ArchiveError(f"member {member} integration differs from the ship commit")
|
|
1905
|
+
bound_ref = f"refs/heads/{bound}"
|
|
1906
|
+
local = optional_ref(checkout, bound_ref)
|
|
1907
|
+
if local is not None and local != reviewed_head:
|
|
1908
|
+
raise ArchiveError(f"member {member} {bound} moved: {local}")
|
|
1909
|
+
holder = registered_worktree(checkout, bound)
|
|
1910
|
+
if holder is not None:
|
|
1911
|
+
try:
|
|
1912
|
+
expected = worktree_paths.worktree_path(
|
|
1913
|
+
checkout, "bound", bound.removeprefix("gsd-path/")
|
|
1914
|
+
).resolve()
|
|
1915
|
+
except (ValueError, OSError) as error:
|
|
1916
|
+
raise ArchiveError(str(error)) from error
|
|
1917
|
+
if holder != expected:
|
|
1918
|
+
raise ArchiveError(f"member {member} bound checkout is outside Path's workspace: {holder}")
|
|
1919
|
+
status = archive_milestone.require_git_success(
|
|
1920
|
+
run_git(holder, "status", "--porcelain", "--untracked-files=all"),
|
|
1921
|
+
"inspect member bound checkout",
|
|
1922
|
+
)
|
|
1923
|
+
if status:
|
|
1924
|
+
raise ArchiveError(f"member {member} bound checkout is not clean: {holder}")
|
|
1925
|
+
remote = live_remote_ref(checkout, bound_ref)
|
|
1926
|
+
if remote is not None:
|
|
1927
|
+
if remote != reviewed_head:
|
|
1928
|
+
raise ArchiveError(f"member {member} origin/{bound} moved: {remote}")
|
|
1929
|
+
members.authorize_delete(checkout, project, bound_ref, reviewed_head)
|
|
1930
|
+
archive_milestone.require_git_success(
|
|
1931
|
+
run_git(checkout, "push", f"--force-with-lease={bound_ref}:{reviewed_head}", "origin", "--delete", bound),
|
|
1932
|
+
f"delete member origin/{bound}")
|
|
1933
|
+
if holder is not None:
|
|
1934
|
+
archive_milestone.require_git_success(run_git(checkout, "worktree", "remove", str(holder)),
|
|
1935
|
+
"remove member bound checkout")
|
|
1936
|
+
if local is not None:
|
|
1937
|
+
archive_milestone.require_git_success(run_git(checkout, "update-ref", "-d", bound_ref, reviewed_head),
|
|
1938
|
+
"delete member bound branch")
|
|
1939
|
+
run_git(checkout, "update-ref", "-d", f"refs/remotes/origin/{bound}")
|
|
1940
|
+
for kind, ref in (("push", bound_ref), ("push", "refs/heads/main"), ("push", f"refs/tags/{tag_name}"),
|
|
1941
|
+
("delete", bound_ref)):
|
|
1942
|
+
members.clear_authorization(checkout, project, kind, ref)
|
|
1943
|
+
# The last sidecar retires here at milestone end; isolation's retires also
|
|
1944
|
+
# release the pin whenever the workspace is idle, so a new root can apply.
|
|
1945
|
+
with contextlib.suppress(ValueError, OSError):
|
|
1946
|
+
worktree_paths.release_workspace(checkout)
|
|
1947
|
+
return {"member": member, "retired": bound}
|
|
1948
|
+
|
|
1949
|
+
|
|
1950
|
+
def close_members(repo: Path) -> dict:
|
|
1951
|
+
"""Close each locked member in lock order before the coordinator ship commit; resumable."""
|
|
1952
|
+
try:
|
|
1953
|
+
import check_handoffs
|
|
1954
|
+
import members
|
|
1955
|
+
from pipeline_git import ship_commit_body
|
|
1956
|
+
except ImportError: # pragma: no cover - package import used by tests
|
|
1957
|
+
from scripts import check_handoffs, members
|
|
1958
|
+
from scripts.pipeline_git import ship_commit_body
|
|
1959
|
+
project = repo.resolve()
|
|
1960
|
+
state, _, _ = archive_milestone.strict_state(project)
|
|
1961
|
+
if (state.phase, state.status) != ("ship", "active") or state.archive is None:
|
|
1962
|
+
raise ArchiveError("close-members requires ship/active with a prepared archive")
|
|
1963
|
+
archive = project / state.archive
|
|
1964
|
+
final_text = (archive / "review" / "FINAL.md").read_text(encoding="utf-8")
|
|
1965
|
+
reviewed = check_handoffs._reviewed_head(final_text, "FINAL.md")
|
|
1966
|
+
lock = archive / "build" / "members.json"
|
|
1967
|
+
locked = json.loads(lock.read_text(encoding="utf-8"))["members"] if lock.is_file() else []
|
|
1968
|
+
try:
|
|
1969
|
+
heads = check_handoffs.member_reviewed_heads(final_text, "FINAL.md")
|
|
1970
|
+
except check_handoffs.HandoffError as error:
|
|
1971
|
+
raise ArchiveError(str(error)) from error
|
|
1972
|
+
if sorted(heads) != sorted(entry["name"] for entry in locked):
|
|
1973
|
+
raise ArchiveError("FINAL.md Member reviewed HEAD lines must name each locked member")
|
|
1974
|
+
common = Path(archive_milestone.require_git_success(
|
|
1975
|
+
run_git(project, "rev-parse", "--path-format=absolute", "--git-common-dir"), "resolve Git common dir"))
|
|
1976
|
+
archive_name = PurePosixPath(state.archive).name
|
|
1977
|
+
try:
|
|
1978
|
+
modes = {item["name"]: item["integration"] for item in members.read_members(project)} if locked else {}
|
|
1979
|
+
rows = {row["name"]: row for row in members.read_member_close(common, archive_name)}
|
|
1980
|
+
except members.MembersError as error:
|
|
1981
|
+
raise ArchiveError(str(error)) from error
|
|
1982
|
+
def save() -> None:
|
|
1983
|
+
members.write_member_close(common, archive_name, [rows[item["name"]] for item in locked if item["name"] in rows])
|
|
1984
|
+
|
|
1985
|
+
closed = []
|
|
1986
|
+
for entry in locked:
|
|
1987
|
+
name = entry["name"]
|
|
1988
|
+
mode = modes.get(name, "default")
|
|
1989
|
+
mode = state.integration if mode == "default" else mode
|
|
1990
|
+
row = rows.get(name) or {"name": name, "mode": mode, "reviewed_head": heads[name],
|
|
1991
|
+
"status": "pending", "merge": None, "tag": None}
|
|
1992
|
+
if row["reviewed_head"] != heads[name] or row["mode"] != mode:
|
|
1993
|
+
raise ArchiveError(f"member-close journal for {name} differs from FINAL.md or MEMBERS.md")
|
|
1994
|
+
if row["status"] == "integrated":
|
|
1995
|
+
result = validate_member_integrated(project, name, state.archive, heads[name])
|
|
1996
|
+
if (result["merge"], result["tag"]) != (row["merge"], row["tag"]):
|
|
1997
|
+
raise ArchiveError(f"member {name} integration differs from its member-close journal")
|
|
1998
|
+
closed.append(row)
|
|
1999
|
+
continue
|
|
2000
|
+
# Journal pending before the member moves; a later member waits for this one.
|
|
2001
|
+
rows[name] = row
|
|
2002
|
+
save()
|
|
2003
|
+
if mode == "direct":
|
|
2004
|
+
result = integrate_member(project, name, state.archive, heads[name])
|
|
2005
|
+
else:
|
|
2006
|
+
result = integrate_member_pull_request(project, name, state.archive, heads[name])
|
|
2007
|
+
if result.get("status") == "awaiting-merge":
|
|
2008
|
+
row["pull_request"] = result["pull_request"]
|
|
2009
|
+
save()
|
|
2010
|
+
return {"status": "awaiting-merge", "member": name, "pull_request": result["pull_request"]}
|
|
2011
|
+
row.update(status="integrated", merge=result["merge"], tag=result["tag"])
|
|
2012
|
+
save()
|
|
2013
|
+
closed.append(row)
|
|
2014
|
+
return {"status": "integrated", "members": closed, "body": ship_commit_body(state.archive, reviewed, closed)}
|