@opengsd/gsd-path 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/AGENTS.md +2 -207
  2. package/CHANGELOG.md +281 -0
  3. package/DOCS.md +13 -7
  4. package/FULL.md +9 -3
  5. package/GUIDE.md +1 -1
  6. package/HOOKS.md +9 -6
  7. package/QUICK.md +4 -1
  8. package/README.md +63 -11
  9. package/RUNTIME.md +1 -1
  10. package/UPDATE.md +57 -8
  11. package/WORKFLOW.md +22 -6
  12. package/package.json +9 -8
  13. package/scripts/_common.py +472 -19
  14. package/scripts/archive_milestone.py +146 -73
  15. package/scripts/bootstrap_repository.py +8 -5
  16. package/scripts/build_recovery.py +4 -1
  17. package/scripts/build_state.py +66 -23
  18. package/scripts/check_docs_audit.py +14 -3
  19. package/scripts/check_handoffs.py +116 -20
  20. package/scripts/check_task_briefs.py +60 -8
  21. package/scripts/check_update.py +2 -2
  22. package/scripts/core_hook_gate.py +10 -1
  23. package/scripts/core_hook_settings.py +11 -4
  24. package/scripts/detect_project.py +345 -15
  25. package/scripts/discussion_records.py +19 -13
  26. package/scripts/discussion_validate.py +66 -21
  27. package/scripts/dispatch_driver.py +111 -26
  28. package/scripts/git_guard.py +116 -29
  29. package/scripts/guard_hook.py +151 -29
  30. package/scripts/install.mjs +54 -38
  31. package/scripts/install.py +562 -41
  32. package/scripts/integration.py +434 -24
  33. package/scripts/isolation.py +1077 -28
  34. package/scripts/lean_verification.py +99 -19
  35. package/scripts/loop_run.py +20 -30
  36. package/scripts/members.py +704 -0
  37. package/scripts/migrate_core.py +2 -2
  38. package/scripts/path_config.py +2 -2
  39. package/scripts/pipeline_diagnose.py +82 -31
  40. package/scripts/pipeline_git.py +61 -22
  41. package/scripts/pipeline_state.py +176 -54
  42. package/scripts/pipeline_undo.py +117 -22
  43. package/scripts/promote_lookahead.py +1 -1
  44. package/scripts/review_panel.py +1 -1
  45. package/scripts/runtime_store.py +35 -9
  46. package/scripts/skill-resources.json +28 -3
  47. package/scripts/state_checkpoint.py +140 -31
  48. package/scripts/state_promote.py +2 -0
  49. package/scripts/status_runtime.py +36 -6
  50. package/scripts/sync_skill_resources.py +8 -7
  51. package/scripts/token_budget.py +12 -8
  52. package/scripts/workflow_run.py +103 -29
  53. package/scripts/worktree_paths.py +18 -8
  54. package/skills/gsd-path/BUILD.md +7 -1
  55. package/skills/gsd-path/DECIDE.md +11 -4
  56. package/skills/gsd-path/DEFINE.md +8 -1
  57. package/skills/gsd-path/DOCS-AUDIT.md +6 -4
  58. package/skills/gsd-path/INSPECT.md +5 -4
  59. package/skills/gsd-path/PLAN.md +7 -1
  60. package/skills/gsd-path/RESEARCH.md +2 -1
  61. package/skills/gsd-path/ROADMAP.md +2 -1
  62. package/skills/gsd-path/SHIP.md +27 -10
  63. package/skills/gsd-path/SKILL.md +45 -1
  64. package/skills/gsd-path/references/build-native.md +47 -14
  65. package/skills/gsd-path/references/codebase-mapper.md +2 -1
  66. package/skills/gsd-path/references/docs-auditor.md +2 -1
  67. package/skills/gsd-path/references/operating-rules.md +214 -0
  68. package/skills/gsd-path/references/planner.md +8 -0
  69. package/skills/gsd-path/references/reviewer-wave.md +3 -1
  70. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  71. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  72. package/skills/gsd-path/scripts/_common.py +472 -19
  73. package/skills/gsd-path/scripts/archive_milestone.py +146 -73
  74. package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
  75. package/skills/gsd-path/scripts/build_recovery.py +4 -1
  76. package/skills/gsd-path/scripts/build_state.py +66 -23
  77. package/skills/gsd-path/scripts/check_docs_audit.py +14 -3
  78. package/skills/gsd-path/scripts/check_handoffs.py +116 -20
  79. package/skills/gsd-path/scripts/check_task_briefs.py +60 -8
  80. package/skills/gsd-path/scripts/check_update.py +2 -2
  81. package/skills/gsd-path/scripts/detect_project.py +345 -15
  82. package/skills/gsd-path/scripts/discussion_records.py +19 -13
  83. package/skills/gsd-path/scripts/discussion_validate.py +66 -21
  84. package/skills/gsd-path/scripts/dispatch_driver.py +111 -26
  85. package/skills/gsd-path/scripts/git_guard.py +116 -29
  86. package/skills/gsd-path/scripts/integration.py +434 -24
  87. package/skills/gsd-path/scripts/isolation.py +1077 -28
  88. package/skills/gsd-path/scripts/lean_verification.py +99 -19
  89. package/skills/gsd-path/scripts/loop_run.py +20 -30
  90. package/skills/gsd-path/scripts/members.py +704 -0
  91. package/skills/gsd-path/scripts/path_config.py +2 -2
  92. package/skills/gsd-path/scripts/pipeline_git.py +61 -22
  93. package/skills/gsd-path/scripts/pipeline_state.py +176 -54
  94. package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
  95. package/skills/gsd-path/scripts/review_panel.py +1 -1
  96. package/skills/gsd-path/scripts/state_checkpoint.py +140 -31
  97. package/skills/gsd-path/scripts/state_promote.py +2 -0
  98. package/skills/gsd-path/scripts/status_runtime.py +36 -6
  99. package/skills/gsd-path/scripts/token_budget.py +12 -8
  100. package/skills/gsd-path/scripts/workflow_run.py +103 -29
  101. package/skills/gsd-path/scripts/worktree_paths.py +18 -8
  102. package/skills/gsd-path/templates/codebase.md +1 -1
  103. package/skills/gsd-path/templates/docs-audit.md +1 -1
  104. package/skills/gsd-path/templates/final-review.md +5 -2
  105. package/skills/gsd-path/templates/gap-review.md +2 -1
  106. package/skills/gsd-path/templates/task.md +1 -0
  107. package/skills/gsd-path/templates/wave-review.md +3 -1
  108. package/skills/gsd-path-build/SKILL.md +7 -1
  109. package/skills/gsd-path-build/references/build-native.md +47 -14
  110. package/skills/gsd-path-build/references/operating-rules.md +214 -0
  111. package/skills/gsd-path-build/references/reviewer-wave.md +3 -1
  112. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  113. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  114. package/skills/gsd-path-build/scripts/_common.py +472 -19
  115. package/skills/gsd-path-build/scripts/archive_milestone.py +146 -73
  116. package/skills/gsd-path-build/scripts/build_recovery.py +4 -1
  117. package/skills/gsd-path-build/scripts/build_state.py +66 -23
  118. package/skills/gsd-path-build/scripts/check_handoffs.py +116 -20
  119. package/skills/gsd-path-build/scripts/check_task_briefs.py +60 -8
  120. package/skills/gsd-path-build/scripts/detect_project.py +345 -15
  121. package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
  122. package/skills/gsd-path-build/scripts/discussion_validate.py +66 -21
  123. package/skills/gsd-path-build/scripts/dispatch_driver.py +111 -26
  124. package/skills/gsd-path-build/scripts/integration.py +434 -24
  125. package/skills/gsd-path-build/scripts/isolation.py +1077 -28
  126. package/skills/gsd-path-build/scripts/loop_run.py +20 -30
  127. package/skills/gsd-path-build/scripts/members.py +704 -0
  128. package/skills/gsd-path-build/scripts/path_config.py +2 -2
  129. package/skills/gsd-path-build/scripts/pipeline_git.py +61 -22
  130. package/skills/gsd-path-build/scripts/pipeline_state.py +176 -54
  131. package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
  132. package/skills/gsd-path-build/scripts/review_panel.py +1 -1
  133. package/skills/gsd-path-build/scripts/state_checkpoint.py +140 -31
  134. package/skills/gsd-path-build/scripts/state_promote.py +2 -0
  135. package/skills/gsd-path-build/scripts/token_budget.py +12 -8
  136. package/skills/gsd-path-build/scripts/workflow_run.py +103 -29
  137. package/skills/gsd-path-build/scripts/worktree_paths.py +18 -8
  138. package/skills/gsd-path-build/templates/task.md +1 -0
  139. package/skills/gsd-path-build/templates/wave-review.md +3 -1
  140. package/skills/gsd-path-decide/SKILL.md +11 -4
  141. package/skills/gsd-path-decide/references/operating-rules.md +214 -0
  142. package/skills/gsd-path-decide/scripts/_common.py +472 -19
  143. package/skills/gsd-path-decide/scripts/archive_milestone.py +146 -73
  144. package/skills/gsd-path-decide/scripts/build_recovery.py +4 -1
  145. package/skills/gsd-path-decide/scripts/check_handoffs.py +116 -20
  146. package/skills/gsd-path-decide/scripts/check_task_briefs.py +60 -8
  147. package/skills/gsd-path-decide/scripts/detect_project.py +345 -15
  148. package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
  149. package/skills/gsd-path-decide/scripts/discussion_validate.py +66 -21
  150. package/skills/gsd-path-decide/scripts/integration.py +434 -24
  151. package/skills/gsd-path-decide/scripts/isolation.py +1077 -28
  152. package/skills/gsd-path-decide/scripts/loop_run.py +20 -30
  153. package/skills/gsd-path-decide/scripts/members.py +704 -0
  154. package/skills/gsd-path-decide/scripts/path_config.py +2 -2
  155. package/skills/gsd-path-decide/scripts/pipeline_git.py +61 -22
  156. package/skills/gsd-path-decide/scripts/pipeline_state.py +176 -54
  157. package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
  158. package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
  159. package/skills/gsd-path-decide/scripts/state_checkpoint.py +140 -31
  160. package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
  161. package/skills/gsd-path-decide/scripts/worktree_paths.py +18 -8
  162. package/skills/gsd-path-define/SKILL.md +8 -1
  163. package/skills/gsd-path-define/references/operating-rules.md +214 -0
  164. package/skills/gsd-path-define/scripts/_common.py +472 -19
  165. package/skills/gsd-path-define/scripts/archive_milestone.py +146 -73
  166. package/skills/gsd-path-define/scripts/build_recovery.py +4 -1
  167. package/skills/gsd-path-define/scripts/check_handoffs.py +116 -20
  168. package/skills/gsd-path-define/scripts/check_task_briefs.py +60 -8
  169. package/skills/gsd-path-define/scripts/detect_project.py +345 -15
  170. package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
  171. package/skills/gsd-path-define/scripts/discussion_validate.py +66 -21
  172. package/skills/gsd-path-define/scripts/integration.py +434 -24
  173. package/skills/gsd-path-define/scripts/isolation.py +1077 -28
  174. package/skills/gsd-path-define/scripts/loop_run.py +20 -30
  175. package/skills/gsd-path-define/scripts/members.py +704 -0
  176. package/skills/gsd-path-define/scripts/path_config.py +2 -2
  177. package/skills/gsd-path-define/scripts/pipeline_git.py +61 -22
  178. package/skills/gsd-path-define/scripts/pipeline_state.py +176 -54
  179. package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
  180. package/skills/gsd-path-define/scripts/review_panel.py +1 -1
  181. package/skills/gsd-path-define/scripts/state_checkpoint.py +140 -31
  182. package/skills/gsd-path-define/scripts/state_promote.py +2 -0
  183. package/skills/gsd-path-define/scripts/worktree_paths.py +18 -8
  184. package/skills/gsd-path-discuss/SKILL.md +2 -1
  185. package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
  186. package/skills/gsd-path-discuss/scripts/_common.py +472 -19
  187. package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
  188. package/skills/gsd-path-discuss/scripts/loop_run.py +20 -30
  189. package/skills/gsd-path-discuss/scripts/path_config.py +2 -2
  190. package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
  191. package/skills/gsd-path-discuss/scripts/worktree_paths.py +18 -8
  192. package/skills/gsd-path-docs-audit/SKILL.md +6 -4
  193. package/skills/gsd-path-docs-audit/references/docs-auditor.md +2 -1
  194. package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
  195. package/skills/gsd-path-docs-audit/scripts/_common.py +472 -19
  196. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +146 -73
  197. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +4 -1
  198. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +14 -3
  199. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +116 -20
  200. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +60 -8
  201. package/skills/gsd-path-docs-audit/scripts/detect_project.py +345 -15
  202. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
  203. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +66 -21
  204. package/skills/gsd-path-docs-audit/scripts/integration.py +434 -24
  205. package/skills/gsd-path-docs-audit/scripts/isolation.py +1077 -28
  206. package/skills/gsd-path-docs-audit/scripts/loop_run.py +20 -30
  207. package/skills/gsd-path-docs-audit/scripts/members.py +704 -0
  208. package/skills/gsd-path-docs-audit/scripts/path_config.py +2 -2
  209. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +61 -22
  210. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +176 -54
  211. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
  212. package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
  213. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +140 -31
  214. package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
  215. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +18 -8
  216. package/skills/gsd-path-docs-audit/templates/docs-audit.md +1 -1
  217. package/skills/gsd-path-forensics/SKILL.md +2 -1
  218. package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
  219. package/skills/gsd-path-forensics/scripts/_common.py +472 -19
  220. package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
  221. package/skills/gsd-path-forensics/scripts/loop_run.py +20 -30
  222. package/skills/gsd-path-forensics/scripts/path_config.py +2 -2
  223. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
  224. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +117 -22
  225. package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
  226. package/skills/gsd-path-forensics/scripts/worktree_paths.py +18 -8
  227. package/skills/gsd-path-inspect/SKILL.md +5 -4
  228. package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
  229. package/skills/gsd-path-inspect/references/docs-auditor.md +2 -1
  230. package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
  231. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  232. package/skills/gsd-path-inspect/scripts/_common.py +472 -19
  233. package/skills/gsd-path-inspect/scripts/archive_milestone.py +146 -73
  234. package/skills/gsd-path-inspect/scripts/build_recovery.py +4 -1
  235. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +14 -3
  236. package/skills/gsd-path-inspect/scripts/check_handoffs.py +116 -20
  237. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +60 -8
  238. package/skills/gsd-path-inspect/scripts/detect_project.py +345 -15
  239. package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
  240. package/skills/gsd-path-inspect/scripts/discussion_validate.py +66 -21
  241. package/skills/gsd-path-inspect/scripts/integration.py +434 -24
  242. package/skills/gsd-path-inspect/scripts/isolation.py +1077 -28
  243. package/skills/gsd-path-inspect/scripts/loop_run.py +20 -30
  244. package/skills/gsd-path-inspect/scripts/members.py +704 -0
  245. package/skills/gsd-path-inspect/scripts/path_config.py +2 -2
  246. package/skills/gsd-path-inspect/scripts/pipeline_git.py +61 -22
  247. package/skills/gsd-path-inspect/scripts/pipeline_state.py +176 -54
  248. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
  249. package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
  250. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +140 -31
  251. package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
  252. package/skills/gsd-path-inspect/scripts/workflow_run.py +103 -29
  253. package/skills/gsd-path-inspect/scripts/worktree_paths.py +18 -8
  254. package/skills/gsd-path-inspect/templates/codebase.md +1 -1
  255. package/skills/gsd-path-inspect/templates/docs-audit.md +1 -1
  256. package/skills/gsd-path-loop/SKILL.md +2 -1
  257. package/skills/gsd-path-loop/references/operating-rules.md +214 -0
  258. package/skills/gsd-path-loop/scripts/_common.py +472 -19
  259. package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
  260. package/skills/gsd-path-loop/scripts/loop_run.py +20 -30
  261. package/skills/gsd-path-loop/scripts/path_config.py +2 -2
  262. package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
  263. package/skills/gsd-path-loop/scripts/worktree_paths.py +18 -8
  264. package/skills/gsd-path-migrate/SKILL.md +2 -1
  265. package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
  266. package/skills/gsd-path-migrate/scripts/_common.py +472 -19
  267. package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
  268. package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
  269. package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
  270. package/skills/gsd-path-migrate/scripts/loop_run.py +20 -30
  271. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
  272. package/skills/gsd-path-migrate/scripts/path_config.py +2 -2
  273. package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
  274. package/skills/gsd-path-plan/SKILL.md +7 -1
  275. package/skills/gsd-path-plan/references/operating-rules.md +214 -0
  276. package/skills/gsd-path-plan/references/planner.md +8 -0
  277. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  278. package/skills/gsd-path-plan/scripts/_common.py +472 -19
  279. package/skills/gsd-path-plan/scripts/archive_milestone.py +146 -73
  280. package/skills/gsd-path-plan/scripts/build_recovery.py +4 -1
  281. package/skills/gsd-path-plan/scripts/check_handoffs.py +116 -20
  282. package/skills/gsd-path-plan/scripts/check_task_briefs.py +60 -8
  283. package/skills/gsd-path-plan/scripts/detect_project.py +345 -15
  284. package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
  285. package/skills/gsd-path-plan/scripts/discussion_validate.py +66 -21
  286. package/skills/gsd-path-plan/scripts/integration.py +434 -24
  287. package/skills/gsd-path-plan/scripts/isolation.py +1077 -28
  288. package/skills/gsd-path-plan/scripts/loop_run.py +20 -30
  289. package/skills/gsd-path-plan/scripts/members.py +704 -0
  290. package/skills/gsd-path-plan/scripts/path_config.py +2 -2
  291. package/skills/gsd-path-plan/scripts/pipeline_git.py +61 -22
  292. package/skills/gsd-path-plan/scripts/pipeline_state.py +176 -54
  293. package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
  294. package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
  295. package/skills/gsd-path-plan/scripts/state_checkpoint.py +140 -31
  296. package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
  297. package/skills/gsd-path-plan/scripts/workflow_run.py +103 -29
  298. package/skills/gsd-path-plan/scripts/worktree_paths.py +18 -8
  299. package/skills/gsd-path-plan/templates/task.md +1 -0
  300. package/skills/gsd-path-research/SKILL.md +2 -1
  301. package/skills/gsd-path-research/references/operating-rules.md +214 -0
  302. package/skills/gsd-path-research/scripts/_common.py +472 -19
  303. package/skills/gsd-path-research/scripts/archive_milestone.py +146 -73
  304. package/skills/gsd-path-research/scripts/build_recovery.py +4 -1
  305. package/skills/gsd-path-research/scripts/check_handoffs.py +116 -20
  306. package/skills/gsd-path-research/scripts/check_task_briefs.py +60 -8
  307. package/skills/gsd-path-research/scripts/detect_project.py +345 -15
  308. package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
  309. package/skills/gsd-path-research/scripts/discussion_validate.py +66 -21
  310. package/skills/gsd-path-research/scripts/integration.py +434 -24
  311. package/skills/gsd-path-research/scripts/isolation.py +1077 -28
  312. package/skills/gsd-path-research/scripts/loop_run.py +20 -30
  313. package/skills/gsd-path-research/scripts/members.py +704 -0
  314. package/skills/gsd-path-research/scripts/path_config.py +2 -2
  315. package/skills/gsd-path-research/scripts/pipeline_git.py +61 -22
  316. package/skills/gsd-path-research/scripts/pipeline_state.py +176 -54
  317. package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
  318. package/skills/gsd-path-research/scripts/review_panel.py +1 -1
  319. package/skills/gsd-path-research/scripts/state_checkpoint.py +140 -31
  320. package/skills/gsd-path-research/scripts/state_promote.py +2 -0
  321. package/skills/gsd-path-research/scripts/worktree_paths.py +18 -8
  322. package/skills/gsd-path-roadmap/SKILL.md +2 -1
  323. package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
  324. package/skills/gsd-path-roadmap/scripts/_common.py +472 -19
  325. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +146 -73
  326. package/skills/gsd-path-roadmap/scripts/build_recovery.py +4 -1
  327. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +116 -20
  328. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +60 -8
  329. package/skills/gsd-path-roadmap/scripts/detect_project.py +345 -15
  330. package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
  331. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +66 -21
  332. package/skills/gsd-path-roadmap/scripts/integration.py +434 -24
  333. package/skills/gsd-path-roadmap/scripts/isolation.py +1077 -28
  334. package/skills/gsd-path-roadmap/scripts/loop_run.py +20 -30
  335. package/skills/gsd-path-roadmap/scripts/members.py +704 -0
  336. package/skills/gsd-path-roadmap/scripts/path_config.py +2 -2
  337. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +61 -22
  338. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +176 -54
  339. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
  340. package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
  341. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +140 -31
  342. package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
  343. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +18 -8
  344. package/skills/gsd-path-ship/SKILL.md +27 -10
  345. package/skills/gsd-path-ship/references/operating-rules.md +214 -0
  346. package/skills/gsd-path-ship/references/reviewer-wave.md +3 -1
  347. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  348. package/skills/gsd-path-ship/scripts/_common.py +472 -19
  349. package/skills/gsd-path-ship/scripts/archive_milestone.py +146 -73
  350. package/skills/gsd-path-ship/scripts/build_recovery.py +4 -1
  351. package/skills/gsd-path-ship/scripts/build_state.py +66 -23
  352. package/skills/gsd-path-ship/scripts/check_handoffs.py +116 -20
  353. package/skills/gsd-path-ship/scripts/check_task_briefs.py +60 -8
  354. package/skills/gsd-path-ship/scripts/detect_project.py +345 -15
  355. package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
  356. package/skills/gsd-path-ship/scripts/discussion_validate.py +66 -21
  357. package/skills/gsd-path-ship/scripts/integration.py +434 -24
  358. package/skills/gsd-path-ship/scripts/isolation.py +1077 -28
  359. package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
  360. package/skills/gsd-path-ship/scripts/loop_run.py +20 -30
  361. package/skills/gsd-path-ship/scripts/members.py +704 -0
  362. package/skills/gsd-path-ship/scripts/path_config.py +2 -2
  363. package/skills/gsd-path-ship/scripts/pipeline_git.py +61 -22
  364. package/skills/gsd-path-ship/scripts/pipeline_state.py +176 -54
  365. package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
  366. package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
  367. package/skills/gsd-path-ship/scripts/state_checkpoint.py +140 -31
  368. package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
  369. package/skills/gsd-path-ship/scripts/workflow_run.py +103 -29
  370. package/skills/gsd-path-ship/scripts/worktree_paths.py +18 -8
  371. package/skills/gsd-path-ship/templates/final-review.md +5 -2
  372. package/skills/gsd-path-ship/templates/gap-review.md +2 -1
  373. package/skills/gsd-path-ship/templates/wave-review.md +3 -1
  374. package/skills/gsd-path-undo/SKILL.md +2 -1
  375. package/skills/gsd-path-undo/references/operating-rules.md +214 -0
  376. package/skills/gsd-path-undo/scripts/_common.py +472 -19
  377. package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
  378. package/skills/gsd-path-undo/scripts/loop_run.py +20 -30
  379. package/skills/gsd-path-undo/scripts/path_config.py +2 -2
  380. package/skills/gsd-path-undo/scripts/pipeline_undo.py +117 -22
  381. package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
  382. package/skills/gsd-path-undo/scripts/worktree_paths.py +18 -8
  383. package/skills/path/BUILD.md +7 -1
  384. package/skills/path/DECIDE.md +11 -4
  385. package/skills/path/DEFINE.md +8 -1
  386. package/skills/path/DOCS-AUDIT.md +6 -4
  387. package/skills/path/INSPECT.md +5 -4
  388. package/skills/path/PLAN.md +7 -1
  389. package/skills/path/RESEARCH.md +2 -1
  390. package/skills/path/ROADMAP.md +2 -1
  391. package/skills/path/SHIP.md +27 -10
  392. package/skills/path/SKILL.md +45 -1
  393. package/skills/path/references/build-native.md +47 -14
  394. package/skills/path/references/codebase-mapper.md +2 -1
  395. package/skills/path/references/docs-auditor.md +2 -1
  396. package/skills/path/references/operating-rules.md +214 -0
  397. package/skills/path/references/planner.md +8 -0
  398. package/skills/path/references/reviewer-wave.md +3 -1
  399. package/skills/path/scripts/_common.py +472 -19
  400. package/skills/path/scripts/archive_milestone.py +146 -73
  401. package/skills/path/scripts/bootstrap_repository.py +8 -5
  402. package/skills/path/scripts/build_recovery.py +4 -1
  403. package/skills/path/scripts/build_state.py +66 -23
  404. package/skills/path/scripts/check_docs_audit.py +14 -3
  405. package/skills/path/scripts/check_handoffs.py +116 -20
  406. package/skills/path/scripts/check_task_briefs.py +60 -8
  407. package/skills/path/scripts/check_update.py +2 -2
  408. package/skills/path/scripts/detect_project.py +345 -15
  409. package/skills/path/scripts/discussion_records.py +19 -13
  410. package/skills/path/scripts/discussion_validate.py +66 -21
  411. package/skills/path/scripts/dispatch_driver.py +111 -26
  412. package/skills/path/scripts/git_guard.py +116 -29
  413. package/skills/path/scripts/integration.py +434 -24
  414. package/skills/path/scripts/isolation.py +1077 -28
  415. package/skills/path/scripts/lean_verification.py +99 -19
  416. package/skills/path/scripts/loop_run.py +20 -30
  417. package/skills/path/scripts/members.py +704 -0
  418. package/skills/path/scripts/path_config.py +2 -2
  419. package/skills/path/scripts/pipeline_git.py +61 -22
  420. package/skills/path/scripts/pipeline_state.py +176 -54
  421. package/skills/path/scripts/promote_lookahead.py +1 -1
  422. package/skills/path/scripts/review_panel.py +1 -1
  423. package/skills/path/scripts/state_checkpoint.py +140 -31
  424. package/skills/path/scripts/state_promote.py +2 -0
  425. package/skills/path/scripts/status_runtime.py +36 -6
  426. package/skills/path/scripts/token_budget.py +12 -8
  427. package/skills/path/scripts/workflow_run.py +103 -29
  428. package/skills/path/scripts/worktree_paths.py +18 -8
  429. package/skills/path/templates/codebase.md +1 -1
  430. package/skills/path/templates/docs-audit.md +1 -1
  431. package/skills/path/templates/final-review.md +5 -2
  432. package/skills/path/templates/gap-review.md +2 -1
  433. package/skills/path/templates/task.md +1 -0
  434. package/skills/path/templates/wave-review.md +3 -1
@@ -14,12 +14,16 @@ from datetime import date
14
14
  from pathlib import Path, PurePosixPath
15
15
  from typing import Optional, Sequence
16
16
 
17
- try:
18
- from pipeline_state import PipelineState
19
- import _common
20
- except ImportError: # pragma: no cover - package import used by tests
21
- from scripts.pipeline_state import PipelineState
22
- from scripts import _common
17
+ if __package__:
18
+ from .pipeline_state import PipelineState
19
+ from . import _common
20
+ else:
21
+ try:
22
+ from pipeline_state import PipelineState
23
+ import _common
24
+ except ImportError: # pragma: no cover - package import used by tests
25
+ from scripts.pipeline_state import PipelineState
26
+ from scripts import _common
23
27
 
24
28
 
25
29
  atomic_replace = _common.atomic_replace
@@ -105,7 +109,7 @@ def validate_discussion_directory(
105
109
  ) -> None:
106
110
  if directory.is_symlink() or not directory.is_dir():
107
111
  raise ArchiveError("discussion archive must be a real directory")
108
- entries = sorted(path.name for path in directory.iterdir())
112
+ entries = sorted(path.name for path in directory.iterdir() if not _common.is_ignored_junk(path))
109
113
  if entries != sorted(DISCUSSION_FILES):
110
114
  raise ArchiveError("discussion archive must contain exactly DIALOGUE.md and ANSWERS.md")
111
115
  dialogue, answers = (directory / name for name in DISCUSSION_FILES)
@@ -490,10 +494,13 @@ def archived_intent_criteria(archive: Path) -> dict[int, str]:
490
494
  if not archive_milestone.is_real_file(intent):
491
495
  raise ArchiveError("archived intent must be a real INTENT.md file")
492
496
  text = HTML_COMMENT_PATTERN.sub("", intent.read_text(encoding="utf-8"))
493
- try:
494
- import check_handoffs
495
- except ImportError:
496
- from scripts import check_handoffs
497
+ if __package__:
498
+ from . import check_handoffs
499
+ else:
500
+ try:
501
+ import check_handoffs
502
+ except ImportError:
503
+ from scripts import check_handoffs
497
504
  try:
498
505
  criteria = check_handoffs._success_criteria(text)
499
506
  except check_handoffs.HandoffError as error:
@@ -502,10 +509,13 @@ def archived_intent_criteria(archive: Path) -> dict[int, str]:
502
509
 
503
510
 
504
511
  def parse_final_review(archive: Path) -> tuple:
505
- try:
506
- import check_handoffs
507
- except ImportError: # pragma: no cover - package import used by tests
508
- from scripts import check_handoffs
512
+ if __package__:
513
+ from . import check_handoffs
514
+ else:
515
+ try:
516
+ import check_handoffs
517
+ except ImportError: # pragma: no cover - package import used by tests
518
+ from scripts import check_handoffs
509
519
 
510
520
  final = archive / "review" / "FINAL.md"
511
521
  if not archive_milestone.is_real_file(final):
@@ -597,8 +607,29 @@ def parse_final_review(archive: Path) -> tuple:
597
607
  return reviewed_head.lower(), criteria
598
608
 
599
609
 
610
+ def _member_heads(text: str, source: str) -> dict:
611
+ if __package__:
612
+ from . import check_handoffs
613
+ else:
614
+ try:
615
+ import check_handoffs
616
+ except ImportError: # pragma: no cover - package import used by tests
617
+ from scripts import check_handoffs
618
+ try:
619
+ return check_handoffs.member_reviewed_heads(text, source)
620
+ except check_handoffs.HandoffError as error:
621
+ raise ArchiveError(str(error)) from error
622
+
623
+
600
624
  def validate_gap_reviews(archive: Path, reviewed_head: str) -> None:
601
625
  review = archive / "review"
626
+ member_heads = _member_heads((review / "FINAL.md").read_text(encoding="utf-8"), "FINAL.md")
627
+ lock = archive / "build" / "members.json"
628
+ locked = [item["name"] for item in json.loads(lock.read_text(encoding="utf-8"))["members"]] if lock.is_file() else []
629
+ if sorted(member_heads) != sorted(locked):
630
+ raise ArchiveError(
631
+ "FINAL.md Member reviewed HEAD lines must name each locked member: " + (", ".join(locked) or "none")
632
+ )
602
633
  candidates = sorted(path for path in review.iterdir() if path.name.startswith("final-gap-"))
603
634
  if not candidates:
604
635
  raise ArchiveError("final review requires at least one final-gap-N.md artifact")
@@ -626,6 +657,8 @@ def validate_gap_reviews(archive: Path, reviewed_head: str) -> None:
626
657
  gap_head = completed_field(lines, "Reviewed HEAD:", path.name).lower()
627
658
  if gap_head != reviewed_head:
628
659
  raise ArchiveError(f"{path.name} Reviewed HEAD does not match FINAL.md")
660
+ if _member_heads("\n".join(lines), path.name) != member_heads:
661
+ raise ArchiveError(f"{path.name} Member reviewed HEAD lines do not match FINAL.md")
629
662
  if completed_field(lines, "Gap verdict:", path.name) != "pass":
630
663
  raise ArchiveError(f"{path.name} gap review did not pass")
631
664
  risk = completed_field(lines, "Risk:", path.name)
@@ -655,10 +688,13 @@ def validate_gap_reviews(archive: Path, reviewed_head: str) -> None:
655
688
 
656
689
 
657
690
  def meaningful_review_evidence(lines: Sequence[str], marker: str, task_text: str = "") -> bool:
658
- try:
659
- from check_handoffs import task_review_observation
660
- except ImportError: # pragma: no cover - package import used by tests
661
- from scripts.check_handoffs import task_review_observation
691
+ if __package__:
692
+ from .check_handoffs import task_review_observation
693
+ else:
694
+ try:
695
+ from check_handoffs import task_review_observation
696
+ except ImportError: # pragma: no cover - package import used by tests
697
+ from scripts.check_handoffs import task_review_observation
662
698
 
663
699
  prefix = f"- {marker} "
664
700
  evidence = []
@@ -1071,8 +1107,17 @@ def archive_file_inventory(archive: Path) -> Sequence[str]:
1071
1107
  for path in archive.rglob("*"):
1072
1108
  if path.is_symlink():
1073
1109
  raise ArchiveError(f"archive contents must not be symlinks: {path}")
1074
- if path.is_file() and path.name != "MANIFEST.md":
1075
- contents.append(path.relative_to(archive).as_posix())
1110
+ if not path.is_file() or path.name == "MANIFEST.md":
1111
+ continue
1112
+ relative = path.relative_to(archive)
1113
+ if _common.is_ignored_junk(path):
1114
+ if len(relative.parts) == 1:
1115
+ continue
1116
+ original = archive.parent.parent / relative
1117
+ project = archive.parent.parent.parent
1118
+ if run_git(project, "check-ignore", "-q", "--", original.relative_to(project).as_posix()).returncode == 0:
1119
+ continue
1120
+ contents.append(relative.as_posix())
1076
1121
  return sorted(contents)
1077
1122
 
1078
1123
 
@@ -12,23 +12,8 @@ import re
12
12
  from pathlib import Path, PurePosixPath
13
13
  from typing import Optional
14
14
 
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 (
15
+ if __package__:
16
+ from .pipeline_git import (
32
17
  bound_branch_name,
33
18
  default_branch_name,
34
19
  integrate_commit_body,
@@ -38,10 +23,41 @@ except ImportError: # pragma: no cover - package import used by tests
38
23
  milestone_number,
39
24
  ship_subject,
40
25
  )
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
26
+ from .pipeline_state import PipelineState
27
+ from .isolation import publication_base
28
+ from . import _common
29
+ from . import worktree_paths
30
+ else:
31
+ try:
32
+ from pipeline_git import (
33
+ bound_branch_name,
34
+ default_branch_name,
35
+ integrate_commit_body,
36
+ integrate_subject,
37
+ is_bound_branch,
38
+ milestone_id,
39
+ milestone_number,
40
+ ship_subject,
41
+ )
42
+ from pipeline_state import PipelineState
43
+ from isolation import publication_base
44
+ import _common
45
+ import worktree_paths
46
+ except ImportError: # pragma: no cover - package import used by tests
47
+ from scripts.pipeline_git import (
48
+ bound_branch_name,
49
+ default_branch_name,
50
+ integrate_commit_body,
51
+ integrate_subject,
52
+ is_bound_branch,
53
+ milestone_id,
54
+ milestone_number,
55
+ ship_subject,
56
+ )
57
+ from scripts.pipeline_state import PipelineState
58
+ from scripts.isolation import publication_base
59
+ from scripts import _common
60
+ from scripts import worktree_paths
45
61
 
46
62
 
47
63
  if __package__:
@@ -541,8 +557,8 @@ def ensure_integration_tag(
541
557
 
542
558
  def github_repository(project: Path) -> str:
543
559
  remote = archive_milestone.require_git_success(
544
- run_git(project, "remote", "get-url", "origin"),
545
- "resolve origin URL",
560
+ run_git(project, "config", "--get", "remote.origin.url"),
561
+ "read configured origin URL",
546
562
  )
547
563
  patterns = (
548
564
  r"https://github\.com/([^/]+/[^/]+?)(?:\.git)?$",
@@ -701,8 +717,9 @@ def update_pull_request_body(
701
717
  archive_path: str,
702
718
  ship_commit: str,
703
719
  branch: str,
720
+ expected_body: Optional[str] = None,
704
721
  ) -> dict:
705
- expected = pull_request_body(archive_path, ship_commit, branch)
722
+ expected = expected_body if expected_body is not None else pull_request_body(archive_path, ship_commit, branch)
706
723
  current = pull.get("body")
707
724
  if isinstance(current, str) and current.rstrip().endswith(PR_CREDIT_LINE):
708
725
  return pull
@@ -1394,6 +1411,12 @@ def validate_integrated(
1394
1411
  raise ArchiveError(f"published {ref} differs from local integration evidence")
1395
1412
  if pull_request is not None:
1396
1413
  result["pull_request"] = pull_request
1414
+ for row in shipped.get("members", []):
1415
+ proven = validate_member_integrated(project, row["name"], configured, row["reviewed_head"], refresh=refresh)
1416
+ if (proven["merge"], proven["tag"]) != (row["merge"], row["tag"]):
1417
+ raise ArchiveError(f"member {row['name']} integration differs from the ship commit")
1418
+ if "members" in shipped:
1419
+ result["members"] = shipped["members"]
1397
1420
  return result
1398
1421
 
1399
1422
 
@@ -1457,3 +1480,390 @@ def require_published_integration(
1457
1480
  raise ArchiveError(
1458
1481
  f"published milestone tag {tag_name} does not point at the integration merge"
1459
1482
  )
1483
+
1484
+
1485
+ def _member_names(coordinator: Path, member: str, archive_path: str) -> tuple[Path, str, str, str, str]:
1486
+ """A member's checkout, coordinator project, bound branch, merge subject, and tag."""
1487
+ try:
1488
+ import isolation
1489
+ except ImportError: # pragma: no cover - package import used by tests
1490
+ from scripts import isolation
1491
+ try:
1492
+ checkout, project = isolation.member_checkout(coordinator.resolve(), member)
1493
+ except isolation.IsolationError as error:
1494
+ raise ArchiveError(str(error)) from error
1495
+ archive_name = PurePosixPath(archive_path).name
1496
+ identifier = milestone_id(milestone_number(archive_name))
1497
+ bound = f"gsd-path/{project}-{identifier}"
1498
+ subject = f"integrate: {project} {identifier} — merge {bound} into main"
1499
+ return checkout, project, bound, subject, f"milestone/{project}-{archive_name}"
1500
+
1501
+
1502
+ def _member_merge_body(archive_path: str, member: str, reviewed_head: str) -> str:
1503
+ return f"Archive: {archive_path}\nMember: {member}\nReviewed-HEAD: {reviewed_head}"
1504
+
1505
+
1506
+ def member_pull_request_tag_message(tag_name: str, pull_request: str, reviewed_head: str, landing: str) -> str:
1507
+ return (f"milestone {tag_name.removeprefix('milestone/')}\n\nMode: pull-request\n"
1508
+ f"Pull-Request: {pull_request}\nReviewed-HEAD: {reviewed_head}\nLanding: {landing}")
1509
+
1510
+
1511
+ def member_pull_request_tag_metadata(contents: str) -> dict[str, str]:
1512
+ fields: dict[str, str] = {}
1513
+ for line in contents.splitlines():
1514
+ match = re.fullmatch(r"(Mode|Pull-Request|Reviewed-HEAD|Landing): (.+)", line)
1515
+ if match:
1516
+ if match.group(1) in fields:
1517
+ raise ArchiveError("pull-request member tag repeats metadata")
1518
+ fields[match.group(1)] = match.group(2)
1519
+ if set(fields) != {"Mode", "Pull-Request", "Reviewed-HEAD", "Landing"}:
1520
+ raise ArchiveError("pull-request member tag is missing metadata")
1521
+ if fields["Mode"] != "pull-request":
1522
+ raise ArchiveError("pull-request member tag has the wrong mode")
1523
+ return fields
1524
+
1525
+
1526
+ def _find_member_merge(checkout: Path, subject: str, body: str, reviewed_head: str) -> Optional[str]:
1527
+ """The member merge in origin/main's first-parent history, if published."""
1528
+ log = archive_milestone.require_git_success(
1529
+ run_git(checkout, "log", "--first-parent", "--format=%H%x00%s", "origin/main"),
1530
+ "inspect member origin/main first-parent history",
1531
+ )
1532
+ matches = [commit for commit, _, text in (line.partition("\x00") for line in log.splitlines())
1533
+ if text == subject]
1534
+ if len(matches) > 1:
1535
+ raise ArchiveError(f"multiple member merges named {subject!r} on origin/main")
1536
+ if not matches:
1537
+ return None
1538
+ _require_member_merge(checkout, matches[0], subject, body, reviewed_head)
1539
+ return matches[0]
1540
+
1541
+
1542
+ def _require_member_merge(checkout: Path, commit: str, subject: str, body: str, reviewed_head: str) -> str:
1543
+ """Check a canonical member merge; return its first parent."""
1544
+ archive_milestone.require_canonical_commit_body(checkout, commit, subject, body, "member integration")
1545
+ parents = archive_milestone.require_git_success(
1546
+ run_git(checkout, "rev-list", "--parents", "-n", "1", commit), "inspect member merge parents"
1547
+ ).split()
1548
+ if len(parents) != 3 or parents[2] != reviewed_head:
1549
+ raise ArchiveError("member merge second parent is not the member reviewed HEAD")
1550
+ return parents[1]
1551
+
1552
+
1553
+ def _create_member_merge(checkout: Path, project: str, archive_path: str, subject: str, body: str,
1554
+ reviewed_head: str, origin_main: str) -> str:
1555
+ identifier = milestone_id(milestone_number(PurePosixPath(archive_path).name))
1556
+ branch = f"gsd-path-integrate/{project}-{identifier}"
1557
+ try:
1558
+ worktree = worktree_paths.worktree_path(checkout, "integrate", f"{project}-{identifier}", pin=True)
1559
+ except (ValueError, OSError) as error:
1560
+ raise ArchiveError(str(error)) from error
1561
+ interrupted = registered_worktree(checkout, branch)
1562
+ if interrupted is not None and optional_ref(interrupted, "MERGE_HEAD") is not None:
1563
+ archive_milestone.require_git_success(run_git(interrupted, "merge", "--abort"),
1564
+ "abort interrupted member merge")
1565
+ remove_registered_worktree(checkout, branch, worktree)
1566
+ tip = optional_ref(checkout, f"refs/heads/{branch}")
1567
+ if tip is not None:
1568
+ base_tip = run_git(checkout, "merge-base", "--is-ancestor", tip, origin_main)
1569
+ if base_tip.returncode != 0:
1570
+ if base_tip.returncode != 1:
1571
+ archive_milestone.require_git_success(base_tip, "inspect interrupted member integration base")
1572
+ if _require_member_merge(checkout, tip, subject, body, reviewed_head) == origin_main:
1573
+ return tip
1574
+ delete_integration_branch(checkout, branch, tip)
1575
+ archive_milestone.require_git_success(
1576
+ run_git(checkout, "worktree", "add", "--quiet", "-b", branch, str(worktree), origin_main),
1577
+ "create member integration worktree",
1578
+ )
1579
+ merged = run_git(worktree, "merge", "--no-ff", "-m", subject, "-m", body, reviewed_head)
1580
+ if merged.returncode != 0:
1581
+ conflicts = run_git(worktree, "diff", "--name-only", "--diff-filter=U").stdout.split()
1582
+ if optional_ref(worktree, "MERGE_HEAD") is not None:
1583
+ run_git(worktree, "merge", "--abort")
1584
+ remove_registered_worktree(checkout, branch, worktree)
1585
+ delete_integration_branch(checkout, branch, origin_main)
1586
+ raise ArchiveError("member integration merge failed; Git aborted it without resolving files: "
1587
+ + (", ".join(conflicts) or (merged.stderr or merged.stdout).strip()))
1588
+ merge = archive_milestone.require_git_success(run_git(worktree, "rev-parse", "HEAD"), "resolve member merge")
1589
+ if _require_member_merge(worktree, merge, subject, body, reviewed_head) != origin_main:
1590
+ raise ArchiveError("member merge first parent is not origin/main")
1591
+ remove_registered_worktree(checkout, branch, worktree)
1592
+ return merge
1593
+
1594
+
1595
+ def _push_member_ref(checkout: Path, project: str, source: str, ref: str, sha: str, lease: Optional[str]) -> None:
1596
+ """Authorize `ref` at `sha` for the member pre-push hook, then publish it."""
1597
+ try:
1598
+ import members
1599
+ except ImportError: # pragma: no cover - package import used by tests
1600
+ from scripts import members
1601
+ members.authorize_push(checkout, project, ref, sha)
1602
+ arguments = ["push"] + ([f"--force-with-lease={ref}:{lease}"] if lease is not None else [])
1603
+ archive_milestone.require_git_success(
1604
+ run_git(checkout, *arguments, "origin", f"{source}:{ref}"), f"publish member {ref}"
1605
+ )
1606
+
1607
+
1608
+ def _publish_member_bound(checkout: Path, project: str, member: str, bound: str, reviewed_head: str) -> None:
1609
+ bound_ref = f"refs/heads/{bound}"
1610
+ remote_bound = live_remote_ref(checkout, bound_ref)
1611
+ if remote_bound is None:
1612
+ _push_member_ref(checkout, project, reviewed_head, bound_ref, reviewed_head, "")
1613
+ elif remote_bound != reviewed_head:
1614
+ raise ArchiveError(f"member {member} origin/{bound} moved: {remote_bound}")
1615
+
1616
+
1617
+ def _require_member_close(checkout: Path, member: str, bound: str, reviewed_head: str) -> None:
1618
+ if optional_ref(checkout, f"refs/heads/{bound}") != reviewed_head:
1619
+ raise ArchiveError(f"member {member} {bound} is not at its reviewed HEAD {reviewed_head}")
1620
+ if default_branch_name(refresh_origin(checkout)["remote_default"]) != "main":
1621
+ raise ArchiveError(f"member {member} remote default must be main")
1622
+
1623
+
1624
+ def integrate_member(coordinator: Path, member: str, archive_path: str, reviewed_head: str) -> dict:
1625
+ """Direct-mode member close: publish the bound branch, merge it into main, then tag."""
1626
+ checkout, project, bound, subject, tag_name = _member_names(coordinator, member, archive_path)
1627
+ body = _member_merge_body(archive_path, member, reviewed_head)
1628
+ _require_member_close(checkout, member, bound, reviewed_head)
1629
+ merge = _find_member_merge(checkout, subject, body, reviewed_head)
1630
+ if merge is None:
1631
+ origin_main = archive_milestone.require_git_success(
1632
+ run_git(checkout, "rev-parse", "origin/main"), "resolve member origin/main")
1633
+ ancestry = run_git(checkout, "merge-base", "--is-ancestor", reviewed_head, origin_main)
1634
+ if ancestry.returncode == 0:
1635
+ raise ArchiveError("member reviewed HEAD reached origin/main outside Path; user-approved recovery required")
1636
+ if ancestry.returncode != 1:
1637
+ archive_milestone.require_git_success(ancestry, "inspect member reviewed HEAD ancestry")
1638
+ merge = _create_member_merge(checkout, project, archive_path, subject, body, reviewed_head, origin_main)
1639
+ # Publish in order: bound branch, main, tag; a rerun publishes only what is missing.
1640
+ _publish_member_bound(checkout, project, member, bound, reviewed_head)
1641
+ if run_git(checkout, "merge-base", "--is-ancestor", merge, "origin/main").returncode != 0:
1642
+ _push_member_ref(checkout, project, merge, "refs/heads/main", merge, None)
1643
+ archive_milestone.require_git_success(
1644
+ run_git(checkout, "update-ref", "refs/remotes/origin/main", merge), "refresh member origin/main")
1645
+ tag_object, tag_published = ensure_integration_tag(checkout, tag_name, merge)
1646
+ if not tag_published:
1647
+ _push_member_ref(checkout, project, f"refs/tags/{tag_name}", f"refs/tags/{tag_name}", tag_object, None)
1648
+ archive_milestone.require_git_success(
1649
+ run_git(checkout, "update-ref", f"refs/remotes/origin/tags/{tag_name}", tag_object),
1650
+ "refresh member milestone-tag ref")
1651
+ identifier = milestone_id(milestone_number(PurePosixPath(archive_path).name))
1652
+ delete_integration_branch(checkout, f"gsd-path-integrate/{project}-{identifier}", merge)
1653
+ return validate_member_integrated(coordinator, member, archive_path, reviewed_head)
1654
+
1655
+
1656
+ def integrate_member_pull_request(coordinator: Path, member: str, archive_path: str, reviewed_head: str) -> dict:
1657
+ """Pull-request member close: open one PR for the bound branch; after a human merge, tag it."""
1658
+ checkout, project, bound, subject, tag_name = _member_names(coordinator, member, archive_path)
1659
+ body = _member_merge_body(archive_path, member, reviewed_head)
1660
+ _require_member_close(checkout, member, bound, reviewed_head)
1661
+ require_github_authentication()
1662
+ repository = github_repository(checkout)
1663
+ pull = find_pull_request(repository, bound, reviewed_head)
1664
+ if pull is None or pull["merged_at"] is None:
1665
+ if pull is not None and pull["state"] != "open":
1666
+ raise ArchiveError(f"member {member} pull request was closed without merging")
1667
+ _publish_member_bound(checkout, project, member, bound, reviewed_head)
1668
+ if pull is None:
1669
+ pull = require_pull_request_shape(github_api_json(
1670
+ f"repos/{repository}/pulls", "--method", "POST", "-f", f"title={subject}",
1671
+ "-f", f"head={bound}", "-f", "base=main", "-f", f"body={body}\n\n---\n{PR_CREDIT_LINE}"))
1672
+ require_pull_request_identity(repository, pull, bound, reviewed_head)
1673
+ else:
1674
+ pull = update_pull_request_body(
1675
+ repository, pull, archive_path, reviewed_head, bound,
1676
+ f"{body}\n\n---\n{PR_CREDIT_LINE}",
1677
+ )
1678
+ require_pull_request_merge_provenance(repository, pull["number"])
1679
+ return {"status": "awaiting-merge", "mode": "pull-request", "member": member,
1680
+ "pull_request": pull["html_url"]}
1681
+ merge = pull["merge_commit_sha"]
1682
+ if pull["state"] != "closed" or not isinstance(merge, str):
1683
+ raise ArchiveError(f"member {member} pull request merge metadata is invalid")
1684
+ require_pull_request_merge_provenance(repository, pull["number"], merge)
1685
+ refresh_origin(checkout)
1686
+ require_pull_request_merge(checkout, merge, reviewed_head, "origin/main")
1687
+ pull = update_pull_request_body(
1688
+ repository, pull, archive_path, reviewed_head, bound,
1689
+ f"{body}\n\n---\n{PR_CREDIT_LINE}",
1690
+ )
1691
+ message = member_pull_request_tag_message(tag_name, pull["html_url"], reviewed_head, merge)
1692
+ tag_object, tag_published = ensure_integration_tag(checkout, tag_name, merge, message)
1693
+ if not tag_published:
1694
+ _push_member_ref(checkout, project, f"refs/tags/{tag_name}", f"refs/tags/{tag_name}", tag_object, None)
1695
+ archive_milestone.require_git_success(
1696
+ run_git(checkout, "update-ref", f"refs/remotes/origin/tags/{tag_name}", tag_object),
1697
+ "refresh member milestone-tag ref")
1698
+ return validate_member_integrated(coordinator, member, archive_path, reviewed_head)
1699
+
1700
+
1701
+ def validate_member_integrated(
1702
+ coordinator: Path, member: str, archive_path: str, reviewed_head: str, *, refresh: bool = True
1703
+ ) -> dict:
1704
+ """Prove the member merge is on origin/main and its published tag points at it."""
1705
+ checkout, _, bound, subject, tag_name = _member_names(coordinator, member, archive_path)
1706
+ tracking = f"refs/remotes/origin/tags/{tag_name}"
1707
+ if refresh:
1708
+ refresh_origin(checkout)
1709
+ published = live_remote_ref(checkout, f"refs/tags/{tag_name}")
1710
+ else:
1711
+ published = optional_ref(checkout, tracking)
1712
+ contents = ""
1713
+ if refresh and published is not None:
1714
+ archive_milestone.require_git_success(
1715
+ run_git(checkout, "update-ref", tracking, published), "refresh member milestone-tag ref")
1716
+ if published is not None:
1717
+ contents = run_git(checkout, "for-each-ref", "--format=%(contents)", tracking).stdout
1718
+ if "Mode: pull-request" in contents.splitlines():
1719
+ metadata = member_pull_request_tag_metadata(contents)
1720
+ if metadata["Reviewed-HEAD"] != reviewed_head:
1721
+ raise ArchiveError(f"member {member} tag {tag_name} names a different reviewed HEAD")
1722
+ merge = archive_milestone.require_git_success(
1723
+ run_git(checkout, "rev-parse", f"{tracking}^{{commit}}"), "resolve member tag target")
1724
+ if metadata["Landing"] != merge:
1725
+ raise ArchiveError(f"member {member} tag {tag_name} names a different landing")
1726
+ require_github_authentication()
1727
+ repository = github_repository(checkout)
1728
+ pull = find_pull_request(repository, bound, reviewed_head)
1729
+ if pull is None:
1730
+ raise ArchiveError(f"member {member} GitHub pull request is missing")
1731
+ if pull["html_url"] != metadata["Pull-Request"]:
1732
+ raise ArchiveError(f"member {member} tag {tag_name} names the wrong pull request")
1733
+ if (pull["state"] != "closed" or pull["merged_at"] is None
1734
+ or pull["merge_commit_sha"] != merge):
1735
+ raise ArchiveError(f"member {member} GitHub pull request is not merged at the tagged landing")
1736
+ require_pull_request_merge_provenance(repository, pull["number"], merge)
1737
+ require_pull_request_merge(checkout, merge, reviewed_head, "origin/main")
1738
+ require_annotated_tag(
1739
+ checkout, tracking, merge, f"member tag {tag_name}",
1740
+ member_pull_request_tag_message(tag_name, pull["html_url"], reviewed_head, merge),
1741
+ )
1742
+ else:
1743
+ merge = _find_member_merge(checkout, subject, _member_merge_body(archive_path, member, reviewed_head),
1744
+ reviewed_head)
1745
+ if merge is None:
1746
+ raise ArchiveError(f"member {member} has no {subject!r} merge on origin/main")
1747
+ if published is None:
1748
+ raise ArchiveError(f"member {member} tag {tag_name} is not published")
1749
+ require_annotated_tag(checkout, tracking, merge, f"member tag {tag_name}")
1750
+ return {"member": member, "merge": merge, "tag": tag_name, "tag_object": published}
1751
+
1752
+
1753
+ def retire_member(coordinator: Path, member: str, archive_path: str, reviewed_head: str,
1754
+ merge: str, tag: str) -> dict:
1755
+ """After integration, delete a member's bound branch everywhere and clear its authorizations."""
1756
+ try:
1757
+ import members
1758
+ except ImportError: # pragma: no cover - package import used by tests
1759
+ from scripts import members
1760
+ checkout, project, bound, _, tag_name = _member_names(coordinator, member, archive_path)
1761
+ proven = validate_member_integrated(coordinator, member, archive_path, reviewed_head)
1762
+ if (proven["merge"], proven["tag"]) != (merge, tag):
1763
+ raise ArchiveError(f"member {member} integration differs from the ship commit")
1764
+ bound_ref = f"refs/heads/{bound}"
1765
+ local = optional_ref(checkout, bound_ref)
1766
+ if local is not None and local != reviewed_head:
1767
+ raise ArchiveError(f"member {member} {bound} moved: {local}")
1768
+ holder = registered_worktree(checkout, bound)
1769
+ if holder is not None:
1770
+ try:
1771
+ expected = worktree_paths.worktree_path(
1772
+ checkout, "bound", bound.removeprefix("gsd-path/")
1773
+ ).resolve()
1774
+ except (ValueError, OSError) as error:
1775
+ raise ArchiveError(str(error)) from error
1776
+ if holder != expected:
1777
+ raise ArchiveError(f"member {member} bound checkout is outside Path's workspace: {holder}")
1778
+ status = archive_milestone.require_git_success(
1779
+ run_git(holder, "status", "--porcelain", "--untracked-files=all"),
1780
+ "inspect member bound checkout",
1781
+ )
1782
+ if status:
1783
+ raise ArchiveError(f"member {member} bound checkout is not clean: {holder}")
1784
+ remote = live_remote_ref(checkout, bound_ref)
1785
+ if remote is not None:
1786
+ if remote != reviewed_head:
1787
+ raise ArchiveError(f"member {member} origin/{bound} moved: {remote}")
1788
+ members.authorize_delete(checkout, project, bound_ref, reviewed_head)
1789
+ archive_milestone.require_git_success(
1790
+ run_git(checkout, "push", f"--force-with-lease={bound_ref}:{reviewed_head}", "origin", "--delete", bound),
1791
+ f"delete member origin/{bound}")
1792
+ if holder is not None:
1793
+ archive_milestone.require_git_success(run_git(checkout, "worktree", "remove", str(holder)),
1794
+ "remove member bound checkout")
1795
+ if local is not None:
1796
+ archive_milestone.require_git_success(run_git(checkout, "update-ref", "-d", bound_ref, reviewed_head),
1797
+ "delete member bound branch")
1798
+ run_git(checkout, "update-ref", "-d", f"refs/remotes/origin/{bound}")
1799
+ for kind, ref in (("push", bound_ref), ("push", "refs/heads/main"), ("push", f"refs/tags/{tag_name}"),
1800
+ ("delete", bound_ref)):
1801
+ members.clear_authorization(checkout, project, kind, ref)
1802
+ return {"member": member, "retired": bound}
1803
+
1804
+
1805
+ def close_members(repo: Path) -> dict:
1806
+ """Close each locked member in lock order before the coordinator ship commit; resumable."""
1807
+ try:
1808
+ import check_handoffs
1809
+ import members
1810
+ from pipeline_git import ship_commit_body
1811
+ except ImportError: # pragma: no cover - package import used by tests
1812
+ from scripts import check_handoffs, members
1813
+ from scripts.pipeline_git import ship_commit_body
1814
+ project = repo.resolve()
1815
+ state, _, _ = archive_milestone.strict_state(project)
1816
+ if (state.phase, state.status) != ("ship", "active") or state.archive is None:
1817
+ raise ArchiveError("close-members requires ship/active with a prepared archive")
1818
+ archive = project / state.archive
1819
+ final_text = (archive / "review" / "FINAL.md").read_text(encoding="utf-8")
1820
+ reviewed = check_handoffs._reviewed_head(final_text, "FINAL.md")
1821
+ lock = archive / "build" / "members.json"
1822
+ locked = json.loads(lock.read_text(encoding="utf-8"))["members"] if lock.is_file() else []
1823
+ try:
1824
+ heads = check_handoffs.member_reviewed_heads(final_text, "FINAL.md")
1825
+ except check_handoffs.HandoffError as error:
1826
+ raise ArchiveError(str(error)) from error
1827
+ if sorted(heads) != sorted(entry["name"] for entry in locked):
1828
+ raise ArchiveError("FINAL.md Member reviewed HEAD lines must name each locked member")
1829
+ common = Path(archive_milestone.require_git_success(
1830
+ run_git(project, "rev-parse", "--path-format=absolute", "--git-common-dir"), "resolve Git common dir"))
1831
+ archive_name = PurePosixPath(state.archive).name
1832
+ try:
1833
+ modes = {item["name"]: item["integration"] for item in members.read_members(project)} if locked else {}
1834
+ rows = {row["name"]: row for row in members.read_member_close(common, archive_name)}
1835
+ except members.MembersError as error:
1836
+ raise ArchiveError(str(error)) from error
1837
+ def save() -> None:
1838
+ members.write_member_close(common, archive_name, [rows[item["name"]] for item in locked if item["name"] in rows])
1839
+
1840
+ closed = []
1841
+ for entry in locked:
1842
+ name = entry["name"]
1843
+ mode = modes.get(name, "default")
1844
+ mode = state.integration if mode == "default" else mode
1845
+ row = rows.get(name) or {"name": name, "mode": mode, "reviewed_head": heads[name],
1846
+ "status": "pending", "merge": None, "tag": None}
1847
+ if row["reviewed_head"] != heads[name] or row["mode"] != mode:
1848
+ raise ArchiveError(f"member-close journal for {name} differs from FINAL.md or MEMBERS.md")
1849
+ if row["status"] == "integrated":
1850
+ result = validate_member_integrated(project, name, state.archive, heads[name])
1851
+ if (result["merge"], result["tag"]) != (row["merge"], row["tag"]):
1852
+ raise ArchiveError(f"member {name} integration differs from its member-close journal")
1853
+ closed.append(row)
1854
+ continue
1855
+ # Journal pending before the member moves; a later member waits for this one.
1856
+ rows[name] = row
1857
+ save()
1858
+ if mode == "direct":
1859
+ result = integrate_member(project, name, state.archive, heads[name])
1860
+ else:
1861
+ result = integrate_member_pull_request(project, name, state.archive, heads[name])
1862
+ if result.get("status") == "awaiting-merge":
1863
+ row["pull_request"] = result["pull_request"]
1864
+ save()
1865
+ return {"status": "awaiting-merge", "member": name, "pull_request": result["pull_request"]}
1866
+ row.update(status="integrated", merge=result["merge"], tag=result["tag"])
1867
+ save()
1868
+ closed.append(row)
1869
+ return {"status": "integrated", "members": closed, "body": ship_commit_body(state.archive, reviewed, closed)}