@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
@@ -24,13 +24,15 @@ from datetime import date
24
24
  from pathlib import Path, PurePosixPath
25
25
  from typing import Iterator, NamedTuple, Optional, Sequence
26
26
 
27
- try:
28
- from isolation import (
27
+ if __package__:
28
+ from .isolation import (
29
29
  IsolationError,
30
+ prune_host_scratch,
31
+ PROJECT_ENTRIES,
30
32
  checkpoint as isolation_checkpoint,
31
33
  verify_landed_task_files,
32
34
  )
33
- from pipeline_git import (
35
+ from .pipeline_git import (
34
36
  bound_branch_name,
35
37
  default_branch_name,
36
38
  integrate_commit_body,
@@ -43,45 +45,75 @@ try:
43
45
  ship_commit_body,
44
46
  ship_subject,
45
47
  )
46
- from review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
47
- from pipeline_state import (
48
+ from .review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
49
+ from .pipeline_state import (
48
50
  PipelineState,
49
51
  PipelineStateError,
50
52
  load_state,
51
53
  transition_state,
52
54
  )
53
- import _common
54
- except ImportError: # pragma: no cover - package import used by tests
55
- from scripts.isolation import (
56
- IsolationError,
57
- checkpoint as isolation_checkpoint,
58
- verify_landed_task_files,
59
- )
60
- from scripts.pipeline_git import (
61
- bound_branch_name,
62
- default_branch_name,
63
- integrate_commit_body,
64
- integrate_subject,
65
- is_bound_branch,
66
- is_integrate_subject,
67
- is_ship_subject,
68
- milestone_id,
69
- milestone_number,
70
- ship_commit_body,
71
- ship_subject,
72
- )
73
- from scripts.review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
74
- from scripts.pipeline_state import (
75
- PipelineState,
76
- PipelineStateError,
77
- load_state,
78
- transition_state,
79
- )
80
- from scripts import _common
81
-
82
- if sys.platform == "win32":
83
- import msvcrt
55
+ from . import _common
84
56
  else:
57
+ try:
58
+ from isolation import (
59
+ IsolationError,
60
+ prune_host_scratch,
61
+ PROJECT_ENTRIES,
62
+ checkpoint as isolation_checkpoint,
63
+ verify_landed_task_files,
64
+ )
65
+ from pipeline_git import (
66
+ bound_branch_name,
67
+ default_branch_name,
68
+ integrate_commit_body,
69
+ integrate_subject,
70
+ is_bound_branch,
71
+ is_integrate_subject,
72
+ is_ship_subject,
73
+ milestone_id,
74
+ milestone_number,
75
+ ship_commit_body,
76
+ ship_subject,
77
+ )
78
+ from review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
79
+ from pipeline_state import (
80
+ PipelineState,
81
+ PipelineStateError,
82
+ load_state,
83
+ transition_state,
84
+ )
85
+ import _common
86
+ except ImportError: # pragma: no cover - package import used by tests
87
+ from scripts.isolation import (
88
+ IsolationError,
89
+ prune_host_scratch,
90
+ PROJECT_ENTRIES,
91
+ checkpoint as isolation_checkpoint,
92
+ verify_landed_task_files,
93
+ )
94
+ from scripts.pipeline_git import (
95
+ bound_branch_name,
96
+ default_branch_name,
97
+ integrate_commit_body,
98
+ integrate_subject,
99
+ is_bound_branch,
100
+ is_integrate_subject,
101
+ is_ship_subject,
102
+ milestone_id,
103
+ milestone_number,
104
+ ship_commit_body,
105
+ ship_subject,
106
+ )
107
+ from scripts.review_panel import KNOWN_FAMILIES, ReviewPanelError, parse_sources
108
+ from scripts.pipeline_state import (
109
+ PipelineState,
110
+ PipelineStateError,
111
+ load_state,
112
+ transition_state,
113
+ )
114
+ from scripts import _common
115
+
116
+ if sys.platform != "win32":
85
117
  import fcntl
86
118
 
87
119
 
@@ -350,31 +382,25 @@ else: # standalone script or sibling import
350
382
  @contextmanager
351
383
  def discussion_lock(active_root: Path) -> Iterator[None]:
352
384
  if sys.platform == "win32":
353
- lock_value = require_git_success(
354
- run_git(
355
- active_root.parent,
356
- "rev-parse",
357
- "--git-path",
358
- "gsd-path-discussion.lock",
359
- ),
360
- "resolve discussion lock",
385
+ # Windows cannot flock a directory, so lock a file in the git dir,
386
+ # where it never shows up as an untracked project file.
387
+ resolved = run_git(
388
+ active_root.parent,
389
+ "rev-parse",
390
+ "--git-path",
391
+ "gsd-path-discussion.lock",
361
392
  )
362
- lock_path = Path(lock_value)
393
+ if resolved.returncode != 0 or not resolved.stdout.strip():
394
+ # Outside git, which the POSIX directory lock allows: the same
395
+ # named kernel mutex pipeline_state holds for this directory.
396
+ with _common.directory_mutex(active_root):
397
+ yield
398
+ return
399
+ lock_path = Path(resolved.stdout.strip())
363
400
  if not lock_path.is_absolute():
364
401
  lock_path = active_root.parent / lock_path
365
- lock_path.parent.mkdir(parents=True, exist_ok=True)
366
- with lock_path.open("a+b") as handle:
367
- handle.seek(0, os.SEEK_END)
368
- if handle.tell() == 0:
369
- handle.write(b"\0")
370
- handle.flush()
371
- handle.seek(0)
372
- msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
373
- try:
374
- yield
375
- finally:
376
- handle.seek(0)
377
- msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
402
+ with _common.exclusive_lock(lock_path):
403
+ yield
378
404
  return
379
405
 
380
406
  descriptor = os.open(active_root, os.O_RDONLY)
@@ -400,10 +426,10 @@ def frontmatter_value(content: str, key: str) -> Optional[str]:
400
426
  for line in lines[1:]:
401
427
  if line == "---":
402
428
  break
403
- match = re.match(rf"^{re.escape(key)}:\s*([^#]*?)\s*(?:#.*)?$", line)
429
+ match = re.match(rf"^{re.escape(key)}:(.*)$", line)
404
430
  if match:
405
- value = match.group(1).strip().strip('"').strip("'")
406
- return value
431
+ value = _common.strip_yaml_comment(match.group(1))
432
+ return value.strip().strip('"').strip("'")
407
433
  return None
408
434
 
409
435
 
@@ -535,6 +561,8 @@ def archive_sequence_entries(project: Path) -> dict[int, Path]:
535
561
  archive_root = safe_archive_root(project)
536
562
  entries = {}
537
563
  for candidate in archive_root.iterdir():
564
+ if _common.is_ignored_junk(candidate):
565
+ continue
538
566
  match = ARCHIVE_PATTERN.fullmatch(candidate.name)
539
567
  if match is None or not candidate.is_dir():
540
568
  raise ArchiveError(f"archive root has a noncanonical entry: {candidate.name}")
@@ -558,14 +586,11 @@ def resolved_archive_target(
558
586
  expected_number = milestone_number(branch)
559
587
  configured = parsed_state.archive
560
588
  entries = archive_sequence_entries(project)
561
- missing_prior = [
562
- number for number in range(1, expected_number) if number not in entries
563
- ]
564
589
  later = sorted(number for number in entries if number > expected_number)
565
- if missing_prior or later:
590
+ if later:
566
591
  raise ArchiveError(
567
592
  f"archive sequence does not match bound branch {branch}: "
568
- f"missing={missing_prior}, later={later}"
593
+ f"later={later}"
569
594
  )
570
595
 
571
596
  if configured is not None:
@@ -620,7 +645,7 @@ def pending_ruling_count(audit: Path) -> int:
620
645
 
621
646
 
622
647
  def is_existing_carry_forward(source: Path, archived: Path) -> bool:
623
- entries = list(source.iterdir()) if source.is_dir() else []
648
+ entries = [path for path in source.iterdir() if not _common.is_ignored_junk(path)] if source.is_dir() else []
624
649
  archived_audit = archived / "DOCS-AUDIT.md"
625
650
  if not archived_audit.is_file() or pending_ruling_count(archived_audit) == 0:
626
651
  return False
@@ -681,7 +706,7 @@ def is_real_file(path: Path) -> bool:
681
706
  def canonical_task_files(tasks: Path) -> Sequence[Path]:
682
707
  if tasks.is_symlink() or not tasks.is_dir():
683
708
  return ()
684
- candidates = sorted(tasks.iterdir())
709
+ candidates = sorted(path for path in tasks.iterdir() if not _common.is_ignored_junk(path))
685
710
  if any(
686
711
  not TASK_FILE_PATTERN.fullmatch(path.name) or not is_real_file(path)
687
712
  for path in candidates
@@ -1001,6 +1026,7 @@ def prepare(repo: Path, slug: str) -> dict:
1001
1026
  project = repo.resolve()
1002
1027
  active_root = require_project_layout(project)
1003
1028
  with discussion_lock(active_root):
1029
+ prune_host_scratch(active_root)
1004
1030
  return prepare_locked(project, active_root, slug)
1005
1031
 
1006
1032
 
@@ -1036,9 +1062,10 @@ def prepare_locked(project: Path, active_root: Path, slug: str) -> dict:
1036
1062
  final = active_root / "review" / "FINAL.md"
1037
1063
  reviewed = re.search(r"(?m)^Reviewed HEAD:\s*(\S+)", final.read_text(encoding="utf-8")) if final.is_file() else None
1038
1064
  head = require_git_success(run_git(project, "rev-parse", "HEAD"), "resolve HEAD").strip()
1039
- if reviewed and reviewed.group(1) != head:
1065
+ if reviewed and not _common.reviewed_head_covers(project, reviewed.group(1), head):
1040
1066
  raise ArchiveError(
1041
1067
  f"FINAL.md Reviewed HEAD {reviewed.group(1)} does not match current HEAD {head}; "
1068
+ f"{_common.RUNTIME_PIN_ALLOWANCE}; "
1042
1069
  "re-run the final review on the current commit before archiving"
1043
1070
  )
1044
1071
  target = resolved_archive_target(project, slug, parsed_state)
@@ -1131,13 +1158,7 @@ def require_canonical_abandon_inputs(active_root: Path, archive: Path) -> None:
1131
1158
  def write_abandon_manifest(
1132
1159
  archive: Path, slug: str, ruling: str, abandoned_on: str
1133
1160
  ) -> None:
1134
- contents = []
1135
- for path in archive.rglob("*"):
1136
- if path.is_symlink():
1137
- raise ArchiveError(f"archive contents must not be symlinks: {path}")
1138
- if path.is_file() and path.name != "MANIFEST.md":
1139
- contents.append(path.relative_to(archive).as_posix())
1140
- contents.sort()
1161
+ contents = archive_file_inventory(archive)
1141
1162
  listed_contents = "\n".join(f"- {path}" for path in contents)
1142
1163
  atomic_replace(
1143
1164
  archive / "MANIFEST.md",
@@ -1657,6 +1678,7 @@ def abandon_locked(
1657
1678
 
1658
1679
 
1659
1680
  run_git = _common.run_git
1681
+ reviewed_head_covers = _common.reviewed_head_covers
1660
1682
  run_command = _common.run_command
1661
1683
 
1662
1684
 
@@ -1693,10 +1715,13 @@ def require_clean_older_archives(project: Path, configured: str) -> None:
1693
1715
  current_prefix = f"{configured}/"
1694
1716
  dirty = set()
1695
1717
  ignored_current = set()
1718
+ current_files = {f"{configured}/{name}" for name in archive_file_inventory(project / configured)}
1696
1719
  for record in status.split("\0"):
1697
1720
  has_status = len(record) > 3 and record[2] == " "
1698
1721
  code = record[:2] if has_status else ""
1699
1722
  path = record[3:] if has_status else record
1723
+ if code == "!!" and _common.is_ignored_junk(project / path) and path not in current_files:
1724
+ continue # Ignored OS junk never ships and is not an artifact.
1700
1725
  if code == "!!" and (
1701
1726
  path == ".project/archive/"
1702
1727
  or path == configured
@@ -1745,7 +1770,7 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
1745
1770
  archived_audit = archive / "research" / "DOCS-AUDIT.md"
1746
1771
  carried_forward = pending_ruling_count(archived_audit)
1747
1772
  # Persistent project metadata ships but never archives with a milestone.
1748
- allowed = {"STATE.md", "REPOSITORY.md", "archive", "LESSONS.md", "next"}
1773
+ allowed = {"STATE.md", "REPOSITORY.md", "MEMBERS.md", "archive", "LESSONS.md", "next"}
1749
1774
  for name in ("config.json", "model-policy.json"):
1750
1775
  path = active_root / name
1751
1776
  if path.exists() or path.is_symlink():
@@ -1760,7 +1785,9 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
1760
1785
  if carried_forward:
1761
1786
  allowed.add("research")
1762
1787
  active_audit = active_research / "DOCS-AUDIT.md"
1763
- entries = sorted(path.name for path in active_research.iterdir()) if active_research.is_dir() else []
1788
+ entries = sorted(
1789
+ path.name for path in active_research.iterdir() if not _common.is_ignored_junk(path)
1790
+ ) if active_research.is_dir() else []
1764
1791
  if (
1765
1792
  active_research.is_symlink()
1766
1793
  or entries != ["DOCS-AUDIT.md"]
@@ -1772,7 +1799,12 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
1772
1799
  elif active_research.exists() or active_research.is_symlink():
1773
1800
  raise ArchiveError("active research exists without a pending carry-forward queue")
1774
1801
 
1775
- unexpected = sorted(path.name for path in active_root.iterdir() if path.name not in allowed)
1802
+ # Ignored, untracked non-pipeline entries (.DS_Store) never ship; pipeline entries block even when ignored.
1803
+ visible = _common.git_visible_entries(active_root)
1804
+ unexpected = sorted(
1805
+ path.name for path in active_root.iterdir()
1806
+ if path.name not in allowed and (path.name in PROJECT_ENTRIES or path.name in visible)
1807
+ )
1776
1808
  if unexpected:
1777
1809
  raise ArchiveError(f"unexpected active .project paths remain: {', '.join(unexpected)}")
1778
1810
 
@@ -1848,6 +1880,12 @@ def prepared_transaction(
1848
1880
 
1849
1881
 
1850
1882
  def preflight(repo: Path) -> dict:
1883
+ project = repo.resolve()
1884
+ active_root = require_project_layout(project)
1885
+ configured = strict_state(project)[0].archive
1886
+ current = (archive_from_state(project, configured)
1887
+ if configured and not archive_is_committed(project, configured) else None)
1888
+ prune_host_scratch(active_root, current)
1851
1889
  project, _, state, configured, archive, _, reviewed_head = prepared_transaction(
1852
1890
  repo,
1853
1891
  ("ship", "shipped"),
@@ -1860,9 +1898,10 @@ def preflight(repo: Path) -> dict:
1860
1898
  require_stageable_carry_forward(project, archive)
1861
1899
  require_clean_older_archives(project, configured)
1862
1900
  head = require_git_success(run_git(project, "rev-parse", "HEAD"), "resolve preflight HEAD")
1863
- if reviewed_head != head:
1901
+ if not reviewed_head_covers(project, reviewed_head, head):
1864
1902
  raise ArchiveError(
1865
- f"FINAL.md Reviewed HEAD {reviewed_head} does not match current HEAD {head}"
1903
+ f"FINAL.md Reviewed HEAD {reviewed_head} does not match current HEAD {head}; "
1904
+ f"{_common.RUNTIME_PIN_ALLOWANCE}"
1866
1905
  )
1867
1906
  return {
1868
1907
  "archive": configured,
@@ -1937,6 +1976,28 @@ def validate_shipped_roadmap(text: str, state: PipelineState, archive: str) -> N
1937
1976
  raise ArchiveError("ROADMAP.md must record Status: shipped and the exact Archive")
1938
1977
 
1939
1978
 
1979
+ def member_ship_rows(project: Path, archive: Path, ship_commit: str) -> list:
1980
+ """Ship body member lines, bound to the archived lock order and FINAL.md member heads."""
1981
+ if __package__:
1982
+ from . import check_handoffs
1983
+ from .pipeline_git import ship_member_rows
1984
+ else:
1985
+ import check_handoffs
1986
+ from pipeline_git import ship_member_rows
1987
+ message = require_git_success(run_git(project, "show", "-s", "--format=%B", ship_commit), "inspect ship body")
1988
+ rows = ship_member_rows(message)
1989
+ lock = archive / "build" / "members.json"
1990
+ locked = [entry["name"] for entry in json.loads(lock.read_text(encoding="utf-8"))["members"]] if lock.is_file() else []
1991
+ try:
1992
+ heads = check_handoffs.member_reviewed_heads((archive / "review" / "FINAL.md").read_text(encoding="utf-8"),
1993
+ "FINAL.md")
1994
+ except check_handoffs.HandoffError as error:
1995
+ raise ArchiveError(str(error)) from error
1996
+ if [row["name"] for row in rows] != locked or {row["name"]: row["reviewed_head"] for row in rows} != heads:
1997
+ raise ArchiveError("ship commit member lines must name each locked member at its FINAL.md reviewed HEAD")
1998
+ return rows
1999
+
2000
+
1940
2001
  def validate(repo: Path, *, historical: bool = False) -> dict:
1941
2002
  project, _, state, configured, archive, archived_files, reviewed_head = prepared_transaction(
1942
2003
  repo,
@@ -2030,16 +2091,18 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
2030
2091
  )
2031
2092
  require_committed_carry_forward(project, configured, archive, ship_commit)
2032
2093
 
2094
+ members = member_ship_rows(project, archive, ship_commit)
2033
2095
  ship_parent = require_git_success(run_git(project, "rev-parse", f"{ship_commit}^"), "resolve ship parent")
2034
- if reviewed_head != ship_parent:
2096
+ if not reviewed_head_covers(project, reviewed_head, ship_parent):
2035
2097
  raise ArchiveError(
2036
- f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}"
2098
+ f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}; "
2099
+ f"{_common.RUNTIME_PIN_ALLOWANCE}"
2037
2100
  )
2038
2101
  require_canonical_commit_body(
2039
2102
  project,
2040
2103
  ship_commit,
2041
2104
  ship_subject(archive.name),
2042
- ship_commit_body(configured, reviewed_head),
2105
+ ship_commit_body(configured, reviewed_head, members),
2043
2106
  "ship",
2044
2107
  )
2045
2108
 
@@ -2077,7 +2140,10 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
2077
2140
  + f"; run: git -C {project} revert <commits after {ship_commit[:12]} that touch .project>"
2078
2141
  )
2079
2142
 
2080
- return {"archive": configured, "commit": ship_commit}
2143
+ result = {"archive": configured, "commit": ship_commit}
2144
+ if members:
2145
+ result["members"] = members
2146
+ return result
2081
2147
 
2082
2148
 
2083
2149
  def parser() -> argparse.ArgumentParser:
@@ -2119,6 +2185,12 @@ def parser() -> argparse.ArgumentParser:
2119
2185
  )
2120
2186
  refresh_parser.add_argument("--repo", required=True, type=Path)
2121
2187
 
2188
+ close_members_parser = subparsers.add_parser(
2189
+ "close-members",
2190
+ help="after preflight, integrate each locked member in lock order before the ship commit",
2191
+ )
2192
+ close_members_parser.add_argument("--repo", required=True, type=Path)
2193
+
2122
2194
  integrate_parser = subparsers.add_parser(
2123
2195
  "integrate",
2124
2196
  help="resume and publish the shipped milestone integration",
@@ -2139,7 +2211,7 @@ def parser() -> argparse.ArgumentParser:
2139
2211
  def main(argv: Optional[Sequence[str]] = None) -> int:
2140
2212
  arguments = parser().parse_args(argv)
2141
2213
  try:
2142
- if arguments.command in {"validate-integrated", "refresh-origin", "integrate"}:
2214
+ if arguments.command in {"validate-integrated", "refresh-origin", "integrate", "close-members"}:
2143
2215
  if __package__:
2144
2216
  from . import integration
2145
2217
  else:
@@ -2156,6 +2228,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
2156
2228
  result = integration.validate_integrated(arguments.repo, arguments.slug)
2157
2229
  elif arguments.command == "refresh-origin":
2158
2230
  result = integration.refresh_origin(arguments.repo)
2231
+ elif arguments.command == "close-members":
2232
+ result = integration.close_members(arguments.repo)
2159
2233
  elif arguments.command == "integrate":
2160
2234
  result = integration.integrate(arguments.repo, arguments.slug)
2161
2235
  else:
@@ -1,5 +1,5 @@
1
1
  # gsd-path project runtime
2
- """Guard a blocked build's return to Define or Plan using its committed base."""
2
+ """Guard return to Define or Plan using a committed recovery base."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -17,7 +17,9 @@ EVENTS = {
17
17
  "define": "build intent corrections requested",
18
18
  "plan": "build plan repair requested",
19
19
  }
20
+ PLAN_INTENT_EVENT = "plan intent corrections requested"
20
21
  MARKER = "build recovery: "
22
+ RECOVERY_KEYS = frozenset({"base", "branch", "kind", "source"})
21
23
 
22
24
 
23
25
  def runtime():
@@ -36,9 +38,42 @@ def checkpoint_runtime():
36
38
  return state_checkpoint
37
39
 
38
40
 
41
+ def _parse_recovery_record(value: object) -> dict:
42
+ if (
43
+ not isinstance(value, dict)
44
+ or not {"base", "branch", "kind"}.issubset(value)
45
+ or not set(value).issubset(RECOVERY_KEYS)
46
+ or not isinstance(value["base"], str)
47
+ or not re.fullmatch(r"[0-9a-f]{40}", value["base"])
48
+ ):
49
+ raise runtime().PipelineStateError("invalid build recovery record")
50
+ source = value.get("source", "build")
51
+ if source not in {"build", "plan"}:
52
+ raise runtime().PipelineStateError("invalid build recovery record")
53
+ if source == "plan":
54
+ if value["kind"] != "define":
55
+ raise runtime().PipelineStateError("invalid build recovery record")
56
+ elif value["kind"] not in EVENTS:
57
+ raise runtime().PipelineStateError("invalid build recovery record")
58
+ return {**value, "source": source}
59
+
60
+
61
+ def _recovery_base_proves_milestone(before, current, source: str) -> bool:
62
+ if before.branch != current.branch or before.milestone != current.milestone:
63
+ return False
64
+ if source == "plan":
65
+ return (before.phase, before.status) in {("plan", "active"), ("plan", "blocked")}
66
+ return (before.phase, before.status) == ("build", "blocked")
67
+
68
+
39
69
  def context(repo: Path, text: str | None = None) -> dict | None:
40
70
  state = runtime()
41
- current, loaded, _ = state.load_state(repo)
71
+ try:
72
+ current, loaded, _ = state.load_state(repo)
73
+ except state.PipelineStateError as error:
74
+ if "STATE.md must be a real file" not in str(error):
75
+ raise
76
+ return None
42
77
  text = loaded if text is None else text
43
78
  recovery = None
44
79
  for line in text.splitlines():
@@ -47,26 +82,24 @@ def context(repo: Path, text: str | None = None) -> dict | None:
47
82
  event = line.split(" — ", 2)[-1]
48
83
  if event.startswith(MARKER):
49
84
  try:
50
- value = json.loads(event[len(MARKER):])
85
+ value = _parse_recovery_record(json.loads(event[len(MARKER):]))
51
86
  except ValueError as error:
52
87
  raise state.PipelineStateError("invalid build recovery record") from error
53
- if (not isinstance(value, dict) or set(value) != {"base", "branch", "kind"}
54
- or value["kind"] not in EVENTS
55
- or not isinstance(value["base"], str)
56
- or not re.fullmatch(r"[0-9a-f]{40}", value["base"])):
57
- raise state.PipelineStateError("invalid build recovery record")
58
88
  recovery = {**value, "active": True} if value["branch"] == current.branch else None
59
- elif event == "build started" and recovery:
60
- recovery["active"] = False
89
+ elif recovery and recovery.get("active"):
90
+ source = recovery.get("source", "build")
91
+ if source == "build" and event == "build started":
92
+ recovery["active"] = False
93
+ elif source == "plan" and event == "plan approved":
94
+ recovery["active"] = False
61
95
  if recovery:
62
96
  if not state._is_ancestor(repo, recovery["base"], "HEAD"):
63
97
  raise state.PipelineStateError("build recovery base is not an ancestor of HEAD")
64
- source = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/STATE.md")
65
- if source is None:
98
+ base_text = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/STATE.md")
99
+ if base_text is None:
66
100
  raise state.PipelineStateError("build recovery base has no STATE.md")
67
- before = state._state_from_text(source)
68
- if ((before.phase, before.status) != ("build", "blocked")
69
- or before.branch != current.branch or before.milestone != current.milestone):
101
+ before = state._state_from_text(base_text)
102
+ if not _recovery_base_proves_milestone(before, current, recovery.get("source", "build")):
70
103
  raise state.PipelineStateError("build recovery base does not prove this blocked milestone")
71
104
  return recovery
72
105
 
@@ -90,6 +123,8 @@ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
90
123
  raise state.PipelineStateError("build recovery requires real task artifacts")
91
124
  tasks = {}
92
125
  for path in sorted(directory.iterdir()):
126
+ if state._common.is_ignored_junk(path):
127
+ continue
93
128
  if not re.fullmatch(r"T[0-9]{3}-[a-z0-9][a-z0-9-]*\.md", path.name):
94
129
  raise state.PipelineStateError(f"invalid recovery task path: {path.name}")
95
130
  text = state._read_real_file(path, path.name)
@@ -106,18 +141,20 @@ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
106
141
  return tasks
107
142
 
108
143
 
109
- def begin(repo: Path, before, after, event: str) -> dict:
144
+ def _begin_common(repo: Path, before, event: str) -> str:
110
145
  state = runtime()
111
- if (before.phase, before.status) != ("build", "blocked") or after.phase not in EVENTS:
112
- raise state.PipelineStateError("build recovery requires build/blocked")
113
- if event != EVENTS[after.phase]:
114
- raise state.PipelineStateError(f"state transition requires event: {EVENTS[after.phase]}")
115
146
  if before.archive is not None or before.branch != state._current_branch(repo):
116
147
  raise state.PipelineStateError("build recovery requires the active bound branch")
117
148
  if state._worktree_changes(repo):
118
149
  raise state.PipelineStateError("checkpoint the blocked build before recovery")
119
150
  if any(state.transaction_journals(repo).values()):
120
151
  raise state.PipelineStateError("finish pending transactions before build recovery")
152
+ state._read_real_file(repo / ".project/intent/INTENT.md", "INTENT.md")
153
+ return state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
154
+
155
+
156
+ def _settle_build_dispatch(repo: Path) -> None:
157
+ state = runtime()
121
158
  records = records_root(repo)
122
159
  for directory in {path.parent.parent for path in records.rglob("attempt-*/state.json")}:
123
160
  latest = max(directory.glob("attempt-*/state.json"), key=lambda path: int(path.parent.name.split("-")[1]))
@@ -126,19 +163,49 @@ def begin(repo: Path, before, after, event: str) -> dict:
126
163
  or (record.get("worktree") and record.get("branch") and not record.get("cleanup_complete"))
127
164
  or record.get("outcome") not in {"landed", "collected", "blocked", "resolved", "redispatched"}):
128
165
  raise state.PipelineStateError(f"settle dispatch records before recovery: {latest}")
129
- tasks = settled_tasks(repo)
130
- state._read_real_file(repo / ".project/plan/PLAN.md", "PLAN.md")
131
- state._read_real_file(repo / ".project/intent/INTENT.md", "INTENT.md")
132
- base = state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
166
+
167
+
168
+ def _verify_done_tasks(repo: Path, tasks: dict[str, tuple[str, dict]], base: str) -> None:
169
+ state = runtime()
133
170
  done = [repo / ".project/tasks" / name for name, (_, fields) in tasks.items()
134
171
  if fields["status"] == "done"]
135
- if done:
136
- if __package__:
137
- from scripts.isolation import verify_landed_task_files
138
- else:
139
- from isolation import verify_landed_task_files
140
- verify_landed_task_files(repo, done, ".project/tasks", base)
141
- return {"kind": after.phase, "base": base, "branch": before.branch}
172
+ if not done:
173
+ return
174
+ if __package__:
175
+ from scripts.isolation import verify_landed_task_files
176
+ else:
177
+ from isolation import verify_landed_task_files
178
+ verify_landed_task_files(repo, done, ".project/tasks", base)
179
+
180
+
181
+ def begin(repo: Path, before, after, event: str) -> dict:
182
+ state = runtime()
183
+ if (before.phase, before.status) == ("build", "blocked") and after.phase in EVENTS:
184
+ if event != EVENTS[after.phase]:
185
+ raise state.PipelineStateError(f"state transition requires event: {EVENTS[after.phase]}")
186
+ _settle_build_dispatch(repo)
187
+ tasks = settled_tasks(repo)
188
+ state._read_real_file(repo / ".project/plan/PLAN.md", "PLAN.md")
189
+ base = _begin_common(repo, before, event)
190
+ _verify_done_tasks(repo, tasks, base)
191
+ return {"kind": after.phase, "base": base, "branch": before.branch, "source": "build"}
192
+ if (
193
+ before.phase == "plan"
194
+ and before.status in {"active", "blocked"}
195
+ and (after.phase, after.status) == ("define", "active")
196
+ ):
197
+ if event != PLAN_INTENT_EVENT:
198
+ raise state.PipelineStateError(f"state transition requires event: {PLAN_INTENT_EVENT}")
199
+ state._read_real_file(repo / ".project/plan/PLAN.md", "PLAN.md")
200
+ tasks_dir = repo / ".project/tasks"
201
+ tasks = settled_tasks(repo) if tasks_dir.is_dir() and any(
202
+ path.is_file() and not state._common.is_ignored_junk(path)
203
+ for path in tasks_dir.iterdir()
204
+ ) else {}
205
+ base = _begin_common(repo, before, event)
206
+ _verify_done_tasks(repo, tasks, base)
207
+ return {"kind": "define", "base": base, "branch": before.branch, "source": "plan"}
208
+ raise state.PipelineStateError("build recovery requires build/blocked")
142
209
 
143
210
 
144
211
  def records_root(repo: Path) -> Path:
@@ -165,7 +232,8 @@ def validate_review_backup(repo: Path, recovery: dict, directory: Path) -> None:
165
232
  if path:
166
233
  expected[str(Path(path).relative_to(".project/review"))] = checkpoint_runtime()._git_text_at(repo, recovery["base"], path)
167
234
  actual = {str(path.relative_to(directory)): path.read_text(encoding="utf-8")
168
- for path in directory.rglob("*") if path.is_file()} if directory.exists() else {}
235
+ for path in directory.rglob("*")
236
+ if path.is_file() and not state._common.is_ignored_junk(path)} if directory.exists() else {}
169
237
  if actual != expected:
170
238
  raise state.PipelineStateError("review backup differs from the recovery base")
171
239