@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.
Files changed (481) hide show
  1. package/AGENTS.md +2 -207
  2. package/CHANGELOG.md +331 -69
  3. package/DOCS.md +25 -9
  4. package/FULL.md +12 -5
  5. package/GUIDE.md +1 -1
  6. package/HOOKS.md +104 -14
  7. package/QUICK.md +5 -2
  8. package/README.md +131 -111
  9. package/RUNTIME.md +2 -2
  10. package/UPDATE.md +61 -11
  11. package/WORKFLOW.md +44 -7
  12. package/package.json +13 -9
  13. package/platforms/shared-agents/dispatch.md +13 -0
  14. package/scripts/_common.py +619 -18
  15. package/scripts/archive_milestone.py +160 -86
  16. package/scripts/bootstrap_repository.py +8 -5
  17. package/scripts/build_recovery.py +100 -32
  18. package/scripts/build_state.py +143 -24
  19. package/scripts/check_docs_audit.py +91 -7
  20. package/scripts/check_handoffs.py +662 -50
  21. package/scripts/check_task_briefs.py +338 -30
  22. package/scripts/check_update.py +2 -2
  23. package/scripts/core_hook_gate.py +10 -1
  24. package/scripts/core_hook_settings.py +11 -4
  25. package/scripts/detect_project.py +587 -32
  26. package/scripts/discussion_records.py +19 -13
  27. package/scripts/discussion_validate.py +152 -50
  28. package/scripts/dispatch_driver.py +501 -46
  29. package/scripts/git_guard.py +118 -31
  30. package/scripts/guard_hook.py +1567 -194
  31. package/scripts/install.mjs +77 -46
  32. package/scripts/install.py +611 -50
  33. package/scripts/integration.py +580 -25
  34. package/scripts/isolation.py +1438 -57
  35. package/scripts/lean_verification.py +99 -19
  36. package/scripts/loop_run.py +31 -29
  37. package/scripts/members.py +706 -0
  38. package/scripts/migrate_core.py +2 -2
  39. package/scripts/path_config.py +5 -5
  40. package/scripts/pipeline_diagnose.py +82 -31
  41. package/scripts/pipeline_git.py +124 -34
  42. package/scripts/pipeline_state.py +287 -87
  43. package/scripts/pipeline_undo.py +218 -30
  44. package/scripts/promote_lookahead.py +1 -1
  45. package/scripts/review_panel.py +1 -1
  46. package/scripts/runtime_store.py +55 -11
  47. package/scripts/skill-resources.json +37 -5
  48. package/scripts/state_checkpoint.py +135 -44
  49. package/scripts/state_promote.py +2 -0
  50. package/scripts/status_runtime.py +58 -10
  51. package/scripts/sync_skill_resources.py +8 -7
  52. package/scripts/task_context.py +12 -3
  53. package/scripts/token_budget.py +12 -8
  54. package/scripts/wizard.mjs +1 -0
  55. package/scripts/workflow_run.py +107 -30
  56. package/scripts/worktree_paths.py +72 -10
  57. package/skills/gsd-path/BUILD.md +7 -1
  58. package/skills/gsd-path/DECIDE.md +11 -4
  59. package/skills/gsd-path/DEFINE.md +36 -4
  60. package/skills/gsd-path/DOCS-AUDIT.md +6 -4
  61. package/skills/gsd-path/INSPECT.md +5 -4
  62. package/skills/gsd-path/PLAN.md +18 -4
  63. package/skills/gsd-path/RESEARCH.md +13 -1
  64. package/skills/gsd-path/ROADMAP.md +2 -1
  65. package/skills/gsd-path/SHIP.md +33 -11
  66. package/skills/gsd-path/SKILL.md +45 -1
  67. package/skills/gsd-path/references/build-native.md +63 -15
  68. package/skills/gsd-path/references/codebase-mapper.md +2 -1
  69. package/skills/gsd-path/references/dispatch.md +13 -0
  70. package/skills/gsd-path/references/docs-auditor.md +7 -1
  71. package/skills/gsd-path/references/operating-rules.md +214 -0
  72. package/skills/gsd-path/references/planner.md +22 -4
  73. package/skills/gsd-path/references/reviewer-final.md +20 -2
  74. package/skills/gsd-path/references/reviewer-wave.md +18 -5
  75. package/skills/gsd-path/references/reviewer.md +3 -1
  76. package/skills/gsd-path/references/spec-probes.md +150 -0
  77. package/skills/gsd-path/scripts/_common.py +619 -18
  78. package/skills/gsd-path/scripts/archive_milestone.py +160 -86
  79. package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
  80. package/skills/gsd-path/scripts/build_recovery.py +100 -32
  81. package/skills/gsd-path/scripts/build_state.py +143 -24
  82. package/skills/gsd-path/scripts/check_docs_audit.py +91 -7
  83. package/skills/gsd-path/scripts/check_handoffs.py +662 -50
  84. package/skills/gsd-path/scripts/check_task_briefs.py +338 -30
  85. package/skills/gsd-path/scripts/check_update.py +2 -2
  86. package/skills/gsd-path/scripts/detect_project.py +587 -32
  87. package/skills/gsd-path/scripts/discussion_records.py +19 -13
  88. package/skills/gsd-path/scripts/discussion_validate.py +152 -50
  89. package/skills/gsd-path/scripts/dispatch_driver.py +501 -46
  90. package/skills/gsd-path/scripts/git_guard.py +118 -31
  91. package/skills/gsd-path/scripts/integration.py +580 -25
  92. package/skills/gsd-path/scripts/isolation.py +1438 -57
  93. package/skills/gsd-path/scripts/lean_verification.py +99 -19
  94. package/skills/gsd-path/scripts/loop_run.py +31 -29
  95. package/skills/gsd-path/scripts/members.py +706 -0
  96. package/skills/gsd-path/scripts/path_config.py +5 -5
  97. package/skills/gsd-path/scripts/pipeline_git.py +124 -34
  98. package/skills/gsd-path/scripts/pipeline_state.py +287 -87
  99. package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
  100. package/skills/gsd-path/scripts/review_panel.py +1 -1
  101. package/skills/gsd-path/scripts/state_checkpoint.py +135 -44
  102. package/skills/gsd-path/scripts/state_promote.py +2 -0
  103. package/skills/gsd-path/scripts/status_runtime.py +58 -10
  104. package/skills/gsd-path/scripts/task_context.py +12 -3
  105. package/skills/gsd-path/scripts/token_budget.py +12 -8
  106. package/skills/gsd-path/scripts/workflow_run.py +107 -30
  107. package/skills/gsd-path/scripts/worktree_paths.py +72 -10
  108. package/skills/gsd-path/templates/codebase.md +1 -1
  109. package/skills/gsd-path/templates/docs-audit.md +6 -2
  110. package/skills/gsd-path/templates/final-review.md +16 -4
  111. package/skills/gsd-path/templates/gap-review.md +2 -1
  112. package/skills/gsd-path/templates/intent.md +30 -0
  113. package/skills/gsd-path/templates/patch-findings.md +3 -3
  114. package/skills/gsd-path/templates/plan.md +17 -2
  115. package/skills/gsd-path/templates/research-handoff.md +14 -3
  116. package/skills/gsd-path/templates/state.md +1 -1
  117. package/skills/gsd-path/templates/task.md +1 -0
  118. package/skills/gsd-path/templates/wave-review.md +16 -4
  119. package/skills/gsd-path-build/SKILL.md +7 -1
  120. package/skills/gsd-path-build/references/build-native.md +63 -15
  121. package/skills/gsd-path-build/references/dispatch.md +13 -0
  122. package/skills/gsd-path-build/references/operating-rules.md +214 -0
  123. package/skills/gsd-path-build/references/reviewer-final.md +20 -2
  124. package/skills/gsd-path-build/references/reviewer-wave.md +18 -5
  125. package/skills/gsd-path-build/references/reviewer.md +3 -1
  126. package/skills/gsd-path-build/references/spec-probes.md +150 -0
  127. package/skills/gsd-path-build/scripts/_common.py +619 -18
  128. package/skills/gsd-path-build/scripts/archive_milestone.py +160 -86
  129. package/skills/gsd-path-build/scripts/build_recovery.py +100 -32
  130. package/skills/gsd-path-build/scripts/build_state.py +143 -24
  131. package/skills/gsd-path-build/scripts/check_handoffs.py +662 -50
  132. package/skills/gsd-path-build/scripts/check_task_briefs.py +338 -30
  133. package/skills/gsd-path-build/scripts/detect_project.py +587 -32
  134. package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
  135. package/skills/gsd-path-build/scripts/discussion_validate.py +152 -50
  136. package/skills/gsd-path-build/scripts/dispatch_driver.py +501 -46
  137. package/skills/gsd-path-build/scripts/integration.py +580 -25
  138. package/skills/gsd-path-build/scripts/isolation.py +1438 -57
  139. package/skills/gsd-path-build/scripts/loop_run.py +31 -29
  140. package/skills/gsd-path-build/scripts/members.py +706 -0
  141. package/skills/gsd-path-build/scripts/path_config.py +5 -5
  142. package/skills/gsd-path-build/scripts/pipeline_git.py +124 -34
  143. package/skills/gsd-path-build/scripts/pipeline_state.py +287 -87
  144. package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
  145. package/skills/gsd-path-build/scripts/review_panel.py +1 -1
  146. package/skills/gsd-path-build/scripts/state_checkpoint.py +135 -44
  147. package/skills/gsd-path-build/scripts/state_promote.py +2 -0
  148. package/skills/gsd-path-build/scripts/task_context.py +12 -3
  149. package/skills/gsd-path-build/scripts/token_budget.py +12 -8
  150. package/skills/gsd-path-build/scripts/workflow_run.py +107 -30
  151. package/skills/gsd-path-build/scripts/worktree_paths.py +72 -10
  152. package/skills/gsd-path-build/templates/task.md +1 -0
  153. package/skills/gsd-path-build/templates/wave-review.md +16 -4
  154. package/skills/gsd-path-decide/SKILL.md +11 -4
  155. package/skills/gsd-path-decide/references/dispatch.md +13 -0
  156. package/skills/gsd-path-decide/references/operating-rules.md +214 -0
  157. package/skills/gsd-path-decide/scripts/_common.py +619 -18
  158. package/skills/gsd-path-decide/scripts/archive_milestone.py +160 -86
  159. package/skills/gsd-path-decide/scripts/build_recovery.py +100 -32
  160. package/skills/gsd-path-decide/scripts/check_handoffs.py +662 -50
  161. package/skills/gsd-path-decide/scripts/check_task_briefs.py +338 -30
  162. package/skills/gsd-path-decide/scripts/detect_project.py +587 -32
  163. package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
  164. package/skills/gsd-path-decide/scripts/discussion_validate.py +152 -50
  165. package/skills/gsd-path-decide/scripts/integration.py +580 -25
  166. package/skills/gsd-path-decide/scripts/isolation.py +1438 -57
  167. package/skills/gsd-path-decide/scripts/loop_run.py +31 -29
  168. package/skills/gsd-path-decide/scripts/members.py +706 -0
  169. package/skills/gsd-path-decide/scripts/path_config.py +5 -5
  170. package/skills/gsd-path-decide/scripts/pipeline_git.py +124 -34
  171. package/skills/gsd-path-decide/scripts/pipeline_state.py +287 -87
  172. package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
  173. package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
  174. package/skills/gsd-path-decide/scripts/state_checkpoint.py +135 -44
  175. package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
  176. package/skills/gsd-path-decide/scripts/worktree_paths.py +72 -10
  177. package/skills/gsd-path-define/SKILL.md +36 -4
  178. package/skills/gsd-path-define/references/operating-rules.md +214 -0
  179. package/skills/gsd-path-define/references/spec-probes.md +150 -0
  180. package/skills/gsd-path-define/scripts/_common.py +619 -18
  181. package/skills/gsd-path-define/scripts/archive_milestone.py +160 -86
  182. package/skills/gsd-path-define/scripts/build_recovery.py +100 -32
  183. package/skills/gsd-path-define/scripts/check_handoffs.py +662 -50
  184. package/skills/gsd-path-define/scripts/check_task_briefs.py +338 -30
  185. package/skills/gsd-path-define/scripts/detect_project.py +587 -32
  186. package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
  187. package/skills/gsd-path-define/scripts/discussion_validate.py +152 -50
  188. package/skills/gsd-path-define/scripts/integration.py +580 -25
  189. package/skills/gsd-path-define/scripts/isolation.py +1438 -57
  190. package/skills/gsd-path-define/scripts/loop_run.py +31 -29
  191. package/skills/gsd-path-define/scripts/members.py +706 -0
  192. package/skills/gsd-path-define/scripts/path_config.py +5 -5
  193. package/skills/gsd-path-define/scripts/pipeline_git.py +124 -34
  194. package/skills/gsd-path-define/scripts/pipeline_state.py +287 -87
  195. package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
  196. package/skills/gsd-path-define/scripts/review_panel.py +1 -1
  197. package/skills/gsd-path-define/scripts/state_checkpoint.py +135 -44
  198. package/skills/gsd-path-define/scripts/state_promote.py +2 -0
  199. package/skills/gsd-path-define/scripts/worktree_paths.py +72 -10
  200. package/skills/gsd-path-define/templates/intent.md +30 -0
  201. package/skills/gsd-path-define/templates/state.md +1 -1
  202. package/skills/gsd-path-discuss/SKILL.md +2 -1
  203. package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
  204. package/skills/gsd-path-discuss/scripts/_common.py +619 -18
  205. package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
  206. package/skills/gsd-path-discuss/scripts/loop_run.py +31 -29
  207. package/skills/gsd-path-discuss/scripts/path_config.py +5 -5
  208. package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
  209. package/skills/gsd-path-discuss/scripts/worktree_paths.py +72 -10
  210. package/skills/gsd-path-docs-audit/SKILL.md +6 -4
  211. package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
  212. package/skills/gsd-path-docs-audit/references/docs-auditor.md +7 -1
  213. package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
  214. package/skills/gsd-path-docs-audit/scripts/_common.py +619 -18
  215. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +160 -86
  216. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +100 -32
  217. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +91 -7
  218. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +662 -50
  219. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +338 -30
  220. package/skills/gsd-path-docs-audit/scripts/detect_project.py +587 -32
  221. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
  222. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +152 -50
  223. package/skills/gsd-path-docs-audit/scripts/integration.py +580 -25
  224. package/skills/gsd-path-docs-audit/scripts/isolation.py +1438 -57
  225. package/skills/gsd-path-docs-audit/scripts/loop_run.py +31 -29
  226. package/skills/gsd-path-docs-audit/scripts/members.py +706 -0
  227. package/skills/gsd-path-docs-audit/scripts/path_config.py +5 -5
  228. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +124 -34
  229. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +287 -87
  230. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
  231. package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
  232. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +135 -44
  233. package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
  234. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +72 -10
  235. package/skills/gsd-path-docs-audit/templates/docs-audit.md +6 -2
  236. package/skills/gsd-path-forensics/SKILL.md +2 -1
  237. package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
  238. package/skills/gsd-path-forensics/scripts/_common.py +619 -18
  239. package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
  240. package/skills/gsd-path-forensics/scripts/loop_run.py +31 -29
  241. package/skills/gsd-path-forensics/scripts/path_config.py +5 -5
  242. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
  243. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +218 -30
  244. package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
  245. package/skills/gsd-path-forensics/scripts/worktree_paths.py +72 -10
  246. package/skills/gsd-path-inspect/SKILL.md +5 -4
  247. package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
  248. package/skills/gsd-path-inspect/references/dispatch.md +13 -0
  249. package/skills/gsd-path-inspect/references/docs-auditor.md +7 -1
  250. package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
  251. package/skills/gsd-path-inspect/scripts/_common.py +619 -18
  252. package/skills/gsd-path-inspect/scripts/archive_milestone.py +160 -86
  253. package/skills/gsd-path-inspect/scripts/build_recovery.py +100 -32
  254. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +91 -7
  255. package/skills/gsd-path-inspect/scripts/check_handoffs.py +662 -50
  256. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +338 -30
  257. package/skills/gsd-path-inspect/scripts/detect_project.py +587 -32
  258. package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
  259. package/skills/gsd-path-inspect/scripts/discussion_validate.py +152 -50
  260. package/skills/gsd-path-inspect/scripts/integration.py +580 -25
  261. package/skills/gsd-path-inspect/scripts/isolation.py +1438 -57
  262. package/skills/gsd-path-inspect/scripts/loop_run.py +31 -29
  263. package/skills/gsd-path-inspect/scripts/members.py +706 -0
  264. package/skills/gsd-path-inspect/scripts/path_config.py +5 -5
  265. package/skills/gsd-path-inspect/scripts/pipeline_git.py +124 -34
  266. package/skills/gsd-path-inspect/scripts/pipeline_state.py +287 -87
  267. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
  268. package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
  269. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +135 -44
  270. package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
  271. package/skills/gsd-path-inspect/scripts/workflow_run.py +107 -30
  272. package/skills/gsd-path-inspect/scripts/worktree_paths.py +72 -10
  273. package/skills/gsd-path-inspect/templates/codebase.md +1 -1
  274. package/skills/gsd-path-inspect/templates/docs-audit.md +6 -2
  275. package/skills/gsd-path-inspect/templates/state.md +1 -1
  276. package/skills/gsd-path-loop/SKILL.md +2 -1
  277. package/skills/gsd-path-loop/references/operating-rules.md +214 -0
  278. package/skills/gsd-path-loop/scripts/_common.py +619 -18
  279. package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
  280. package/skills/gsd-path-loop/scripts/loop_run.py +31 -29
  281. package/skills/gsd-path-loop/scripts/path_config.py +5 -5
  282. package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
  283. package/skills/gsd-path-loop/scripts/worktree_paths.py +72 -10
  284. package/skills/gsd-path-migrate/SKILL.md +2 -1
  285. package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
  286. package/skills/gsd-path-migrate/scripts/_common.py +619 -18
  287. package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
  288. package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
  289. package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
  290. package/skills/gsd-path-migrate/scripts/loop_run.py +31 -29
  291. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
  292. package/skills/gsd-path-migrate/scripts/path_config.py +5 -5
  293. package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
  294. package/skills/gsd-path-plan/SKILL.md +18 -4
  295. package/skills/gsd-path-plan/references/dispatch.md +13 -0
  296. package/skills/gsd-path-plan/references/operating-rules.md +214 -0
  297. package/skills/gsd-path-plan/references/planner.md +22 -4
  298. package/skills/gsd-path-plan/references/spec-probes.md +150 -0
  299. package/skills/gsd-path-plan/scripts/_common.py +619 -18
  300. package/skills/gsd-path-plan/scripts/archive_milestone.py +160 -86
  301. package/skills/gsd-path-plan/scripts/build_recovery.py +100 -32
  302. package/skills/gsd-path-plan/scripts/check_handoffs.py +662 -50
  303. package/skills/gsd-path-plan/scripts/check_task_briefs.py +338 -30
  304. package/skills/gsd-path-plan/scripts/detect_project.py +587 -32
  305. package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
  306. package/skills/gsd-path-plan/scripts/discussion_validate.py +152 -50
  307. package/skills/gsd-path-plan/scripts/integration.py +580 -25
  308. package/skills/gsd-path-plan/scripts/isolation.py +1438 -57
  309. package/skills/gsd-path-plan/scripts/loop_run.py +31 -29
  310. package/skills/gsd-path-plan/scripts/members.py +706 -0
  311. package/skills/gsd-path-plan/scripts/path_config.py +5 -5
  312. package/skills/gsd-path-plan/scripts/pipeline_git.py +124 -34
  313. package/skills/gsd-path-plan/scripts/pipeline_state.py +287 -87
  314. package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
  315. package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
  316. package/skills/gsd-path-plan/scripts/state_checkpoint.py +135 -44
  317. package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
  318. package/skills/gsd-path-plan/scripts/workflow_run.py +107 -30
  319. package/skills/gsd-path-plan/scripts/worktree_paths.py +72 -10
  320. package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
  321. package/skills/gsd-path-plan/templates/plan.md +17 -2
  322. package/skills/gsd-path-plan/templates/task.md +1 -0
  323. package/skills/gsd-path-research/SKILL.md +13 -1
  324. package/skills/gsd-path-research/references/dispatch.md +13 -0
  325. package/skills/gsd-path-research/references/operating-rules.md +214 -0
  326. package/skills/gsd-path-research/scripts/_common.py +619 -18
  327. package/skills/gsd-path-research/scripts/archive_milestone.py +160 -86
  328. package/skills/gsd-path-research/scripts/build_recovery.py +100 -32
  329. package/skills/gsd-path-research/scripts/check_handoffs.py +662 -50
  330. package/skills/gsd-path-research/scripts/check_task_briefs.py +338 -30
  331. package/skills/gsd-path-research/scripts/detect_project.py +587 -32
  332. package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
  333. package/skills/gsd-path-research/scripts/discussion_validate.py +152 -50
  334. package/skills/gsd-path-research/scripts/integration.py +580 -25
  335. package/skills/gsd-path-research/scripts/isolation.py +1438 -57
  336. package/skills/gsd-path-research/scripts/loop_run.py +31 -29
  337. package/skills/gsd-path-research/scripts/members.py +706 -0
  338. package/skills/gsd-path-research/scripts/path_config.py +5 -5
  339. package/skills/gsd-path-research/scripts/pipeline_git.py +124 -34
  340. package/skills/gsd-path-research/scripts/pipeline_state.py +287 -87
  341. package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
  342. package/skills/gsd-path-research/scripts/review_panel.py +1 -1
  343. package/skills/gsd-path-research/scripts/state_checkpoint.py +135 -44
  344. package/skills/gsd-path-research/scripts/state_promote.py +2 -0
  345. package/skills/gsd-path-research/scripts/worktree_paths.py +72 -10
  346. package/skills/gsd-path-research/templates/research-handoff.md +14 -3
  347. package/skills/gsd-path-roadmap/SKILL.md +2 -1
  348. package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
  349. package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
  350. package/skills/gsd-path-roadmap/scripts/_common.py +619 -18
  351. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +160 -86
  352. package/skills/gsd-path-roadmap/scripts/build_recovery.py +100 -32
  353. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +662 -50
  354. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +338 -30
  355. package/skills/gsd-path-roadmap/scripts/detect_project.py +587 -32
  356. package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
  357. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +152 -50
  358. package/skills/gsd-path-roadmap/scripts/integration.py +580 -25
  359. package/skills/gsd-path-roadmap/scripts/isolation.py +1438 -57
  360. package/skills/gsd-path-roadmap/scripts/loop_run.py +31 -29
  361. package/skills/gsd-path-roadmap/scripts/members.py +706 -0
  362. package/skills/gsd-path-roadmap/scripts/path_config.py +5 -5
  363. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +124 -34
  364. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +287 -87
  365. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
  366. package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
  367. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +135 -44
  368. package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
  369. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +72 -10
  370. package/skills/gsd-path-ship/SKILL.md +33 -11
  371. package/skills/gsd-path-ship/references/dispatch.md +13 -0
  372. package/skills/gsd-path-ship/references/operating-rules.md +214 -0
  373. package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
  374. package/skills/gsd-path-ship/references/reviewer-wave.md +18 -5
  375. package/skills/gsd-path-ship/references/reviewer.md +3 -1
  376. package/skills/gsd-path-ship/references/spec-probes.md +150 -0
  377. package/skills/gsd-path-ship/scripts/_common.py +619 -18
  378. package/skills/gsd-path-ship/scripts/archive_milestone.py +160 -86
  379. package/skills/gsd-path-ship/scripts/build_recovery.py +100 -32
  380. package/skills/gsd-path-ship/scripts/build_state.py +143 -24
  381. package/skills/gsd-path-ship/scripts/check_handoffs.py +662 -50
  382. package/skills/gsd-path-ship/scripts/check_task_briefs.py +338 -30
  383. package/skills/gsd-path-ship/scripts/detect_project.py +587 -32
  384. package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
  385. package/skills/gsd-path-ship/scripts/discussion_validate.py +152 -50
  386. package/skills/gsd-path-ship/scripts/integration.py +580 -25
  387. package/skills/gsd-path-ship/scripts/isolation.py +1438 -57
  388. package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
  389. package/skills/gsd-path-ship/scripts/loop_run.py +31 -29
  390. package/skills/gsd-path-ship/scripts/members.py +706 -0
  391. package/skills/gsd-path-ship/scripts/path_config.py +5 -5
  392. package/skills/gsd-path-ship/scripts/pipeline_git.py +124 -34
  393. package/skills/gsd-path-ship/scripts/pipeline_state.py +287 -87
  394. package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
  395. package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
  396. package/skills/gsd-path-ship/scripts/state_checkpoint.py +135 -44
  397. package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
  398. package/skills/gsd-path-ship/scripts/workflow_run.py +107 -30
  399. package/skills/gsd-path-ship/scripts/worktree_paths.py +72 -10
  400. package/skills/gsd-path-ship/templates/final-review.md +16 -4
  401. package/skills/gsd-path-ship/templates/gap-review.md +2 -1
  402. package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
  403. package/skills/gsd-path-ship/templates/wave-review.md +16 -4
  404. package/skills/gsd-path-undo/SKILL.md +2 -1
  405. package/skills/gsd-path-undo/references/operating-rules.md +214 -0
  406. package/skills/gsd-path-undo/scripts/_common.py +619 -18
  407. package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
  408. package/skills/gsd-path-undo/scripts/loop_run.py +31 -29
  409. package/skills/gsd-path-undo/scripts/path_config.py +5 -5
  410. package/skills/gsd-path-undo/scripts/pipeline_undo.py +218 -30
  411. package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
  412. package/skills/gsd-path-undo/scripts/worktree_paths.py +72 -10
  413. package/skills/path/BUILD.md +7 -1
  414. package/skills/path/DECIDE.md +11 -4
  415. package/skills/path/DEFINE.md +36 -4
  416. package/skills/path/DOCS-AUDIT.md +6 -4
  417. package/skills/path/INSPECT.md +5 -4
  418. package/skills/path/PLAN.md +18 -4
  419. package/skills/path/RESEARCH.md +13 -1
  420. package/skills/path/ROADMAP.md +2 -1
  421. package/skills/path/SHIP.md +33 -11
  422. package/skills/path/SKILL.md +45 -1
  423. package/skills/path/references/build-native.md +63 -15
  424. package/skills/path/references/codebase-mapper.md +2 -1
  425. package/skills/path/references/dispatch.md +13 -0
  426. package/skills/path/references/docs-auditor.md +7 -1
  427. package/skills/path/references/operating-rules.md +214 -0
  428. package/skills/path/references/planner.md +22 -4
  429. package/skills/path/references/reviewer-final.md +20 -2
  430. package/skills/path/references/reviewer-wave.md +18 -5
  431. package/skills/path/references/reviewer.md +3 -1
  432. package/skills/path/references/spec-probes.md +150 -0
  433. package/skills/path/scripts/_common.py +619 -18
  434. package/skills/path/scripts/archive_milestone.py +160 -86
  435. package/skills/path/scripts/bootstrap_repository.py +8 -5
  436. package/skills/path/scripts/build_recovery.py +100 -32
  437. package/skills/path/scripts/build_state.py +143 -24
  438. package/skills/path/scripts/check_docs_audit.py +91 -7
  439. package/skills/path/scripts/check_handoffs.py +662 -50
  440. package/skills/path/scripts/check_task_briefs.py +338 -30
  441. package/skills/path/scripts/check_update.py +2 -2
  442. package/skills/path/scripts/detect_project.py +587 -32
  443. package/skills/path/scripts/discussion_records.py +19 -13
  444. package/skills/path/scripts/discussion_validate.py +152 -50
  445. package/skills/path/scripts/dispatch_driver.py +501 -46
  446. package/skills/path/scripts/git_guard.py +118 -31
  447. package/skills/path/scripts/integration.py +580 -25
  448. package/skills/path/scripts/isolation.py +1438 -57
  449. package/skills/path/scripts/lean_verification.py +99 -19
  450. package/skills/path/scripts/loop_run.py +31 -29
  451. package/skills/path/scripts/members.py +706 -0
  452. package/skills/path/scripts/path_config.py +5 -5
  453. package/skills/path/scripts/pipeline_git.py +124 -34
  454. package/skills/path/scripts/pipeline_state.py +287 -87
  455. package/skills/path/scripts/promote_lookahead.py +1 -1
  456. package/skills/path/scripts/review_panel.py +1 -1
  457. package/skills/path/scripts/state_checkpoint.py +135 -44
  458. package/skills/path/scripts/state_promote.py +2 -0
  459. package/skills/path/scripts/status_runtime.py +58 -10
  460. package/skills/path/scripts/task_context.py +12 -3
  461. package/skills/path/scripts/token_budget.py +12 -8
  462. package/skills/path/scripts/workflow_run.py +107 -30
  463. package/skills/path/scripts/worktree_paths.py +72 -10
  464. package/skills/path/templates/codebase.md +1 -1
  465. package/skills/path/templates/docs-audit.md +6 -2
  466. package/skills/path/templates/final-review.md +16 -4
  467. package/skills/path/templates/gap-review.md +2 -1
  468. package/skills/path/templates/intent.md +30 -0
  469. package/skills/path/templates/patch-findings.md +3 -3
  470. package/skills/path/templates/plan.md +17 -2
  471. package/skills/path/templates/research-handoff.md +14 -3
  472. package/skills/path/templates/state.md +1 -1
  473. package/skills/path/templates/task.md +1 -0
  474. package/skills/path/templates/wave-review.md +16 -4
  475. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  476. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  477. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  478. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  479. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  480. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  481. 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
- try:
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 scripts.pipeline_state import PipelineState
42
- from scripts.isolation import publication_base
43
- from scripts import _common
44
- from scripts import worktree_paths
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, "remote", "get-url", "origin"),
545
- "resolve origin URL",
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 == "pull-request" or (historical and has_origin),
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)}