@opengsd/gsd-path 1.4.0 → 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 (347) hide show
  1. package/CHANGELOG.md +325 -344
  2. package/DOCS.md +12 -2
  3. package/FULL.md +3 -2
  4. package/HOOKS.md +95 -8
  5. package/QUICK.md +1 -1
  6. package/README.md +78 -110
  7. package/RUNTIME.md +1 -1
  8. package/UPDATE.md +4 -3
  9. package/WORKFLOW.md +22 -1
  10. package/package.json +5 -2
  11. package/platforms/shared-agents/dispatch.md +13 -0
  12. package/scripts/_common.py +149 -1
  13. package/scripts/archive_milestone.py +14 -13
  14. package/scripts/build_recovery.py +96 -31
  15. package/scripts/build_state.py +79 -3
  16. package/scripts/check_docs_audit.py +77 -4
  17. package/scripts/check_handoffs.py +546 -30
  18. package/scripts/check_task_briefs.py +279 -23
  19. package/scripts/detect_project.py +243 -18
  20. package/scripts/discussion_validate.py +86 -29
  21. package/scripts/dispatch_driver.py +392 -22
  22. package/scripts/git_guard.py +3 -3
  23. package/scripts/guard_hook.py +1416 -165
  24. package/scripts/install.mjs +23 -8
  25. package/scripts/install.py +49 -9
  26. package/scripts/integration.py +146 -1
  27. package/scripts/isolation.py +392 -60
  28. package/scripts/loop_run.py +16 -4
  29. package/scripts/members.py +11 -9
  30. package/scripts/path_config.py +3 -3
  31. package/scripts/pipeline_git.py +63 -12
  32. package/scripts/pipeline_state.py +111 -33
  33. package/scripts/pipeline_undo.py +101 -8
  34. package/scripts/runtime_store.py +20 -2
  35. package/scripts/skill-resources.json +9 -2
  36. package/scripts/state_checkpoint.py +9 -27
  37. package/scripts/status_runtime.py +22 -4
  38. package/scripts/task_context.py +12 -3
  39. package/scripts/wizard.mjs +1 -0
  40. package/scripts/workflow_run.py +5 -2
  41. package/scripts/worktree_paths.py +54 -2
  42. package/skills/gsd-path/DEFINE.md +28 -3
  43. package/skills/gsd-path/PLAN.md +11 -3
  44. package/skills/gsd-path/RESEARCH.md +11 -0
  45. package/skills/gsd-path/SHIP.md +6 -1
  46. package/skills/gsd-path/references/build-native.md +16 -1
  47. package/skills/gsd-path/references/dispatch.md +13 -0
  48. package/skills/gsd-path/references/docs-auditor.md +5 -0
  49. package/skills/gsd-path/references/planner.md +14 -4
  50. package/skills/gsd-path/references/reviewer-final.md +20 -2
  51. package/skills/gsd-path/references/reviewer-wave.md +15 -4
  52. package/skills/gsd-path/references/reviewer.md +3 -1
  53. package/skills/gsd-path/references/spec-probes.md +150 -0
  54. package/skills/gsd-path/scripts/_common.py +149 -1
  55. package/skills/gsd-path/scripts/archive_milestone.py +14 -13
  56. package/skills/gsd-path/scripts/build_recovery.py +96 -31
  57. package/skills/gsd-path/scripts/build_state.py +79 -3
  58. package/skills/gsd-path/scripts/check_docs_audit.py +77 -4
  59. package/skills/gsd-path/scripts/check_handoffs.py +546 -30
  60. package/skills/gsd-path/scripts/check_task_briefs.py +279 -23
  61. package/skills/gsd-path/scripts/detect_project.py +243 -18
  62. package/skills/gsd-path/scripts/discussion_validate.py +86 -29
  63. package/skills/gsd-path/scripts/dispatch_driver.py +392 -22
  64. package/skills/gsd-path/scripts/git_guard.py +3 -3
  65. package/skills/gsd-path/scripts/integration.py +146 -1
  66. package/skills/gsd-path/scripts/isolation.py +392 -60
  67. package/skills/gsd-path/scripts/loop_run.py +16 -4
  68. package/skills/gsd-path/scripts/members.py +11 -9
  69. package/skills/gsd-path/scripts/path_config.py +3 -3
  70. package/skills/gsd-path/scripts/pipeline_git.py +63 -12
  71. package/skills/gsd-path/scripts/pipeline_state.py +111 -33
  72. package/skills/gsd-path/scripts/state_checkpoint.py +9 -27
  73. package/skills/gsd-path/scripts/status_runtime.py +22 -4
  74. package/skills/gsd-path/scripts/task_context.py +12 -3
  75. package/skills/gsd-path/scripts/workflow_run.py +5 -2
  76. package/skills/gsd-path/scripts/worktree_paths.py +54 -2
  77. package/skills/gsd-path/templates/docs-audit.md +5 -1
  78. package/skills/gsd-path/templates/final-review.md +11 -2
  79. package/skills/gsd-path/templates/intent.md +30 -0
  80. package/skills/gsd-path/templates/patch-findings.md +3 -3
  81. package/skills/gsd-path/templates/plan.md +17 -2
  82. package/skills/gsd-path/templates/research-handoff.md +14 -3
  83. package/skills/gsd-path/templates/state.md +1 -1
  84. package/skills/gsd-path/templates/wave-review.md +16 -6
  85. package/skills/gsd-path-build/references/build-native.md +16 -1
  86. package/skills/gsd-path-build/references/dispatch.md +13 -0
  87. package/skills/gsd-path-build/references/reviewer-final.md +20 -2
  88. package/skills/gsd-path-build/references/reviewer-wave.md +15 -4
  89. package/skills/gsd-path-build/references/reviewer.md +3 -1
  90. package/skills/gsd-path-build/references/spec-probes.md +150 -0
  91. package/skills/gsd-path-build/scripts/_common.py +149 -1
  92. package/skills/gsd-path-build/scripts/archive_milestone.py +14 -13
  93. package/skills/gsd-path-build/scripts/build_recovery.py +96 -31
  94. package/skills/gsd-path-build/scripts/build_state.py +79 -3
  95. package/skills/gsd-path-build/scripts/check_handoffs.py +546 -30
  96. package/skills/gsd-path-build/scripts/check_task_briefs.py +279 -23
  97. package/skills/gsd-path-build/scripts/detect_project.py +243 -18
  98. package/skills/gsd-path-build/scripts/discussion_validate.py +86 -29
  99. package/skills/gsd-path-build/scripts/dispatch_driver.py +392 -22
  100. package/skills/gsd-path-build/scripts/integration.py +146 -1
  101. package/skills/gsd-path-build/scripts/isolation.py +392 -60
  102. package/skills/gsd-path-build/scripts/loop_run.py +16 -4
  103. package/skills/gsd-path-build/scripts/members.py +11 -9
  104. package/skills/gsd-path-build/scripts/path_config.py +3 -3
  105. package/skills/gsd-path-build/scripts/pipeline_git.py +63 -12
  106. package/skills/gsd-path-build/scripts/pipeline_state.py +111 -33
  107. package/skills/gsd-path-build/scripts/state_checkpoint.py +9 -27
  108. package/skills/gsd-path-build/scripts/task_context.py +12 -3
  109. package/skills/gsd-path-build/scripts/workflow_run.py +5 -2
  110. package/skills/gsd-path-build/scripts/worktree_paths.py +54 -2
  111. package/skills/gsd-path-build/templates/wave-review.md +16 -6
  112. package/skills/gsd-path-decide/references/dispatch.md +13 -0
  113. package/skills/gsd-path-decide/scripts/_common.py +149 -1
  114. package/skills/gsd-path-decide/scripts/archive_milestone.py +14 -13
  115. package/skills/gsd-path-decide/scripts/build_recovery.py +96 -31
  116. package/skills/gsd-path-decide/scripts/check_handoffs.py +546 -30
  117. package/skills/gsd-path-decide/scripts/check_task_briefs.py +279 -23
  118. package/skills/gsd-path-decide/scripts/detect_project.py +243 -18
  119. package/skills/gsd-path-decide/scripts/discussion_validate.py +86 -29
  120. package/skills/gsd-path-decide/scripts/integration.py +146 -1
  121. package/skills/gsd-path-decide/scripts/isolation.py +392 -60
  122. package/skills/gsd-path-decide/scripts/loop_run.py +16 -4
  123. package/skills/gsd-path-decide/scripts/members.py +11 -9
  124. package/skills/gsd-path-decide/scripts/path_config.py +3 -3
  125. package/skills/gsd-path-decide/scripts/pipeline_git.py +63 -12
  126. package/skills/gsd-path-decide/scripts/pipeline_state.py +111 -33
  127. package/skills/gsd-path-decide/scripts/state_checkpoint.py +9 -27
  128. package/skills/gsd-path-decide/scripts/worktree_paths.py +54 -2
  129. package/skills/gsd-path-define/SKILL.md +28 -3
  130. package/skills/gsd-path-define/references/spec-probes.md +150 -0
  131. package/skills/gsd-path-define/scripts/_common.py +149 -1
  132. package/skills/gsd-path-define/scripts/archive_milestone.py +14 -13
  133. package/skills/gsd-path-define/scripts/build_recovery.py +96 -31
  134. package/skills/gsd-path-define/scripts/check_handoffs.py +546 -30
  135. package/skills/gsd-path-define/scripts/check_task_briefs.py +279 -23
  136. package/skills/gsd-path-define/scripts/detect_project.py +243 -18
  137. package/skills/gsd-path-define/scripts/discussion_validate.py +86 -29
  138. package/skills/gsd-path-define/scripts/integration.py +146 -1
  139. package/skills/gsd-path-define/scripts/isolation.py +392 -60
  140. package/skills/gsd-path-define/scripts/loop_run.py +16 -4
  141. package/skills/gsd-path-define/scripts/members.py +11 -9
  142. package/skills/gsd-path-define/scripts/path_config.py +3 -3
  143. package/skills/gsd-path-define/scripts/pipeline_git.py +63 -12
  144. package/skills/gsd-path-define/scripts/pipeline_state.py +111 -33
  145. package/skills/gsd-path-define/scripts/state_checkpoint.py +9 -27
  146. package/skills/gsd-path-define/scripts/worktree_paths.py +54 -2
  147. package/skills/gsd-path-define/templates/intent.md +30 -0
  148. package/skills/gsd-path-define/templates/state.md +1 -1
  149. package/skills/gsd-path-discuss/scripts/_common.py +149 -1
  150. package/skills/gsd-path-discuss/scripts/loop_run.py +16 -4
  151. package/skills/gsd-path-discuss/scripts/path_config.py +3 -3
  152. package/skills/gsd-path-discuss/scripts/worktree_paths.py +54 -2
  153. package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
  154. package/skills/gsd-path-docs-audit/references/docs-auditor.md +5 -0
  155. package/skills/gsd-path-docs-audit/scripts/_common.py +149 -1
  156. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +14 -13
  157. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +96 -31
  158. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +77 -4
  159. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +546 -30
  160. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +279 -23
  161. package/skills/gsd-path-docs-audit/scripts/detect_project.py +243 -18
  162. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +86 -29
  163. package/skills/gsd-path-docs-audit/scripts/integration.py +146 -1
  164. package/skills/gsd-path-docs-audit/scripts/isolation.py +392 -60
  165. package/skills/gsd-path-docs-audit/scripts/loop_run.py +16 -4
  166. package/skills/gsd-path-docs-audit/scripts/members.py +11 -9
  167. package/skills/gsd-path-docs-audit/scripts/path_config.py +3 -3
  168. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +63 -12
  169. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +111 -33
  170. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +9 -27
  171. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +54 -2
  172. package/skills/gsd-path-docs-audit/templates/docs-audit.md +5 -1
  173. package/skills/gsd-path-forensics/scripts/_common.py +149 -1
  174. package/skills/gsd-path-forensics/scripts/loop_run.py +16 -4
  175. package/skills/gsd-path-forensics/scripts/path_config.py +3 -3
  176. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +101 -8
  177. package/skills/gsd-path-forensics/scripts/worktree_paths.py +54 -2
  178. package/skills/gsd-path-inspect/references/dispatch.md +13 -0
  179. package/skills/gsd-path-inspect/references/docs-auditor.md +5 -0
  180. package/skills/gsd-path-inspect/scripts/_common.py +149 -1
  181. package/skills/gsd-path-inspect/scripts/archive_milestone.py +14 -13
  182. package/skills/gsd-path-inspect/scripts/build_recovery.py +96 -31
  183. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +77 -4
  184. package/skills/gsd-path-inspect/scripts/check_handoffs.py +546 -30
  185. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +279 -23
  186. package/skills/gsd-path-inspect/scripts/detect_project.py +243 -18
  187. package/skills/gsd-path-inspect/scripts/discussion_validate.py +86 -29
  188. package/skills/gsd-path-inspect/scripts/integration.py +146 -1
  189. package/skills/gsd-path-inspect/scripts/isolation.py +392 -60
  190. package/skills/gsd-path-inspect/scripts/loop_run.py +16 -4
  191. package/skills/gsd-path-inspect/scripts/members.py +11 -9
  192. package/skills/gsd-path-inspect/scripts/path_config.py +3 -3
  193. package/skills/gsd-path-inspect/scripts/pipeline_git.py +63 -12
  194. package/skills/gsd-path-inspect/scripts/pipeline_state.py +111 -33
  195. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +9 -27
  196. package/skills/gsd-path-inspect/scripts/workflow_run.py +5 -2
  197. package/skills/gsd-path-inspect/scripts/worktree_paths.py +54 -2
  198. package/skills/gsd-path-inspect/templates/docs-audit.md +5 -1
  199. package/skills/gsd-path-inspect/templates/state.md +1 -1
  200. package/skills/gsd-path-loop/scripts/_common.py +149 -1
  201. package/skills/gsd-path-loop/scripts/loop_run.py +16 -4
  202. package/skills/gsd-path-loop/scripts/path_config.py +3 -3
  203. package/skills/gsd-path-loop/scripts/worktree_paths.py +54 -2
  204. package/skills/gsd-path-migrate/scripts/_common.py +149 -1
  205. package/skills/gsd-path-migrate/scripts/loop_run.py +16 -4
  206. package/skills/gsd-path-migrate/scripts/path_config.py +3 -3
  207. package/skills/gsd-path-plan/SKILL.md +11 -3
  208. package/skills/gsd-path-plan/references/dispatch.md +13 -0
  209. package/skills/gsd-path-plan/references/planner.md +14 -4
  210. package/skills/gsd-path-plan/references/spec-probes.md +150 -0
  211. package/skills/gsd-path-plan/scripts/_common.py +149 -1
  212. package/skills/gsd-path-plan/scripts/archive_milestone.py +14 -13
  213. package/skills/gsd-path-plan/scripts/build_recovery.py +96 -31
  214. package/skills/gsd-path-plan/scripts/check_handoffs.py +546 -30
  215. package/skills/gsd-path-plan/scripts/check_task_briefs.py +279 -23
  216. package/skills/gsd-path-plan/scripts/detect_project.py +243 -18
  217. package/skills/gsd-path-plan/scripts/discussion_validate.py +86 -29
  218. package/skills/gsd-path-plan/scripts/integration.py +146 -1
  219. package/skills/gsd-path-plan/scripts/isolation.py +392 -60
  220. package/skills/gsd-path-plan/scripts/loop_run.py +16 -4
  221. package/skills/gsd-path-plan/scripts/members.py +11 -9
  222. package/skills/gsd-path-plan/scripts/path_config.py +3 -3
  223. package/skills/gsd-path-plan/scripts/pipeline_git.py +63 -12
  224. package/skills/gsd-path-plan/scripts/pipeline_state.py +111 -33
  225. package/skills/gsd-path-plan/scripts/state_checkpoint.py +9 -27
  226. package/skills/gsd-path-plan/scripts/workflow_run.py +5 -2
  227. package/skills/gsd-path-plan/scripts/worktree_paths.py +54 -2
  228. package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
  229. package/skills/gsd-path-plan/templates/plan.md +17 -2
  230. package/skills/gsd-path-research/SKILL.md +11 -0
  231. package/skills/gsd-path-research/references/dispatch.md +13 -0
  232. package/skills/gsd-path-research/scripts/_common.py +149 -1
  233. package/skills/gsd-path-research/scripts/archive_milestone.py +14 -13
  234. package/skills/gsd-path-research/scripts/build_recovery.py +96 -31
  235. package/skills/gsd-path-research/scripts/check_handoffs.py +546 -30
  236. package/skills/gsd-path-research/scripts/check_task_briefs.py +279 -23
  237. package/skills/gsd-path-research/scripts/detect_project.py +243 -18
  238. package/skills/gsd-path-research/scripts/discussion_validate.py +86 -29
  239. package/skills/gsd-path-research/scripts/integration.py +146 -1
  240. package/skills/gsd-path-research/scripts/isolation.py +392 -60
  241. package/skills/gsd-path-research/scripts/loop_run.py +16 -4
  242. package/skills/gsd-path-research/scripts/members.py +11 -9
  243. package/skills/gsd-path-research/scripts/path_config.py +3 -3
  244. package/skills/gsd-path-research/scripts/pipeline_git.py +63 -12
  245. package/skills/gsd-path-research/scripts/pipeline_state.py +111 -33
  246. package/skills/gsd-path-research/scripts/state_checkpoint.py +9 -27
  247. package/skills/gsd-path-research/scripts/worktree_paths.py +54 -2
  248. package/skills/gsd-path-research/templates/research-handoff.md +14 -3
  249. package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
  250. package/skills/gsd-path-roadmap/scripts/_common.py +149 -1
  251. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +14 -13
  252. package/skills/gsd-path-roadmap/scripts/build_recovery.py +96 -31
  253. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +546 -30
  254. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +279 -23
  255. package/skills/gsd-path-roadmap/scripts/detect_project.py +243 -18
  256. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +86 -29
  257. package/skills/gsd-path-roadmap/scripts/integration.py +146 -1
  258. package/skills/gsd-path-roadmap/scripts/isolation.py +392 -60
  259. package/skills/gsd-path-roadmap/scripts/loop_run.py +16 -4
  260. package/skills/gsd-path-roadmap/scripts/members.py +11 -9
  261. package/skills/gsd-path-roadmap/scripts/path_config.py +3 -3
  262. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +63 -12
  263. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +111 -33
  264. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +9 -27
  265. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +54 -2
  266. package/skills/gsd-path-ship/SKILL.md +6 -1
  267. package/skills/gsd-path-ship/references/dispatch.md +13 -0
  268. package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
  269. package/skills/gsd-path-ship/references/reviewer-wave.md +15 -4
  270. package/skills/gsd-path-ship/references/reviewer.md +3 -1
  271. package/skills/gsd-path-ship/references/spec-probes.md +150 -0
  272. package/skills/gsd-path-ship/scripts/_common.py +149 -1
  273. package/skills/gsd-path-ship/scripts/archive_milestone.py +14 -13
  274. package/skills/gsd-path-ship/scripts/build_recovery.py +96 -31
  275. package/skills/gsd-path-ship/scripts/build_state.py +79 -3
  276. package/skills/gsd-path-ship/scripts/check_handoffs.py +546 -30
  277. package/skills/gsd-path-ship/scripts/check_task_briefs.py +279 -23
  278. package/skills/gsd-path-ship/scripts/detect_project.py +243 -18
  279. package/skills/gsd-path-ship/scripts/discussion_validate.py +86 -29
  280. package/skills/gsd-path-ship/scripts/integration.py +146 -1
  281. package/skills/gsd-path-ship/scripts/isolation.py +392 -60
  282. package/skills/gsd-path-ship/scripts/loop_run.py +16 -4
  283. package/skills/gsd-path-ship/scripts/members.py +11 -9
  284. package/skills/gsd-path-ship/scripts/path_config.py +3 -3
  285. package/skills/gsd-path-ship/scripts/pipeline_git.py +63 -12
  286. package/skills/gsd-path-ship/scripts/pipeline_state.py +111 -33
  287. package/skills/gsd-path-ship/scripts/state_checkpoint.py +9 -27
  288. package/skills/gsd-path-ship/scripts/workflow_run.py +5 -2
  289. package/skills/gsd-path-ship/scripts/worktree_paths.py +54 -2
  290. package/skills/gsd-path-ship/templates/final-review.md +11 -2
  291. package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
  292. package/skills/gsd-path-ship/templates/wave-review.md +16 -6
  293. package/skills/gsd-path-undo/scripts/_common.py +149 -1
  294. package/skills/gsd-path-undo/scripts/loop_run.py +16 -4
  295. package/skills/gsd-path-undo/scripts/path_config.py +3 -3
  296. package/skills/gsd-path-undo/scripts/pipeline_undo.py +101 -8
  297. package/skills/gsd-path-undo/scripts/worktree_paths.py +54 -2
  298. package/skills/path/DEFINE.md +28 -3
  299. package/skills/path/PLAN.md +11 -3
  300. package/skills/path/RESEARCH.md +11 -0
  301. package/skills/path/SHIP.md +6 -1
  302. package/skills/path/references/build-native.md +16 -1
  303. package/skills/path/references/dispatch.md +13 -0
  304. package/skills/path/references/docs-auditor.md +5 -0
  305. package/skills/path/references/planner.md +14 -4
  306. package/skills/path/references/reviewer-final.md +20 -2
  307. package/skills/path/references/reviewer-wave.md +15 -4
  308. package/skills/path/references/reviewer.md +3 -1
  309. package/skills/path/references/spec-probes.md +150 -0
  310. package/skills/path/scripts/_common.py +149 -1
  311. package/skills/path/scripts/archive_milestone.py +14 -13
  312. package/skills/path/scripts/build_recovery.py +96 -31
  313. package/skills/path/scripts/build_state.py +79 -3
  314. package/skills/path/scripts/check_docs_audit.py +77 -4
  315. package/skills/path/scripts/check_handoffs.py +546 -30
  316. package/skills/path/scripts/check_task_briefs.py +279 -23
  317. package/skills/path/scripts/detect_project.py +243 -18
  318. package/skills/path/scripts/discussion_validate.py +86 -29
  319. package/skills/path/scripts/dispatch_driver.py +392 -22
  320. package/skills/path/scripts/git_guard.py +3 -3
  321. package/skills/path/scripts/integration.py +146 -1
  322. package/skills/path/scripts/isolation.py +392 -60
  323. package/skills/path/scripts/loop_run.py +16 -4
  324. package/skills/path/scripts/members.py +11 -9
  325. package/skills/path/scripts/path_config.py +3 -3
  326. package/skills/path/scripts/pipeline_git.py +63 -12
  327. package/skills/path/scripts/pipeline_state.py +111 -33
  328. package/skills/path/scripts/state_checkpoint.py +9 -27
  329. package/skills/path/scripts/status_runtime.py +22 -4
  330. package/skills/path/scripts/task_context.py +12 -3
  331. package/skills/path/scripts/workflow_run.py +5 -2
  332. package/skills/path/scripts/worktree_paths.py +54 -2
  333. package/skills/path/templates/docs-audit.md +5 -1
  334. package/skills/path/templates/final-review.md +11 -2
  335. package/skills/path/templates/intent.md +30 -0
  336. package/skills/path/templates/patch-findings.md +3 -3
  337. package/skills/path/templates/plan.md +17 -2
  338. package/skills/path/templates/research-handoff.md +14 -3
  339. package/skills/path/templates/state.md +1 -1
  340. package/skills/path/templates/wave-review.md +16 -6
  341. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  342. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  343. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  344. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  345. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  346. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  347. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
@@ -10,10 +10,11 @@
10
10
  with actual values, otherwise omit them:
11
11
  Reviewed HEAD: <full review commit SHA>
12
12
  Review scope: final
13
- In each surface SC block also include Surface, Check, and Observed fields
14
- using the final-review format. Surface holds exactly the surface name from
15
- PLAN.md's Surface contract; put walkthrough detail in Check and Observed.
16
- Record the actual walkthrough evidence once. -->
13
+ In each surface SC block keep the mandatory `- ✅` / `- ❌` evidence line
14
+ (the wave gate requires it). Also include Surface, Check, and Observed
15
+ fields using the final-review format. Surface holds exactly the surface name
16
+ from PLAN.md's Surface contract; put walkthrough detail in Check and Observed.
17
+ The `- ✅` line may summarize Observed; do not omit it. -->
17
18
 
18
19
  Wave verdict: <pass | blocked — blocked if any task fails>
19
20
  Cycle: <C>
@@ -45,12 +46,21 @@ Contract violations (blocking):
45
46
  <!-- One heading per INTENT.md success criterion owned by a task in this
46
47
  wave, ending `: pass` or `: fail`. `scripts/check_handoffs.py wave
47
48
  --review <this file>` gates the SC id set and verdicts. Omit this
48
- section when no task in the wave owns an SC. -->
49
+ section when no task in the wave owns an SC.
50
+ Copy the criterion text from INTENT.md `## Success criteria` exactly as
51
+ `_success_criteria` stores it: indented continuation lines join with a
52
+ single space and keep their leading `- ` markers; the gate compares
53
+ heading text with `_normalize_ws` only (no paraphrase or stripped
54
+ sub-bullets). -->
49
55
 
50
- ### SC1 — <criterion>: <pass | fail>
56
+ ### SC1 — <criterion as stored by the gate>: <pass | fail>
51
57
  - ✅ <evidence: verify output / file:line checked>
52
58
  - ❌ <criterion> — found: <what exists instead, file:line>
53
59
  fix: <concrete direction a coder can execute without re-investigating>
60
+ <!-- Quick-lane final scope (Review scope: final): add after the evidence line:
61
+ - **Surface**: <surface name from PLAN.md>
62
+ - **Check**: `<walkthrough command>`
63
+ - **Observed**: <what was seen> -->
54
64
 
55
65
  ## Fixed since last cycle
56
66
 
@@ -65,6 +65,49 @@ VERIFY_RESULTS = ("pass", "fail")
65
65
  VERIFY_LEDGER_SCHEMA = "gsd-path/verify-ledger/v2"
66
66
  VERIFY_BLOCK_PATTERN = re.compile(r"```bash[ \t]*\n(?P<block>.*?)```", re.DOTALL)
67
67
 
68
+ REVIEW_BULLET_HEADING_PATTERN = re.compile(r"^#{2,3} ")
69
+ REVIEW_BULLET_FIELD_LABEL_PATTERN = re.compile(r"^- \*\*[^*]+\*\*:")
70
+
71
+
72
+ def review_bullet_field_label_pattern(field: str) -> re.Pattern[str]:
73
+ return re.compile(rf"^- \*\*{re.escape(field)}\*\*:[ \t]*(.*)$")
74
+
75
+
76
+ def _review_bullet_field_value(lines: Sequence[str], index: int, inline: str) -> str:
77
+ if inline:
78
+ return inline
79
+ collected: list[str] = []
80
+ for line in lines[index + 1 :]:
81
+ if REVIEW_BULLET_FIELD_LABEL_PATTERN.match(line) or REVIEW_BULLET_HEADING_PATTERN.match(
82
+ line
83
+ ):
84
+ break
85
+ if line and not line[0].isspace():
86
+ break
87
+ stripped = line.strip()
88
+ if stripped:
89
+ collected.append(stripped)
90
+ return "\n".join(collected)
91
+
92
+
93
+ def find_review_bullet_field_values(lines: Sequence[str], field: str) -> list[str]:
94
+ pattern = review_bullet_field_label_pattern(field)
95
+ values: list[str] = []
96
+ for index, line in enumerate(lines):
97
+ match = pattern.fullmatch(line)
98
+ if match is None:
99
+ continue
100
+ values.append(_review_bullet_field_value(lines, index, match.group(1)))
101
+ return values
102
+
103
+
104
+ def parse_review_bullet_field(lines: Sequence[str], field: str) -> str:
105
+ """Parse one `- **Field**:` review bullet, including indented continuations."""
106
+ values = find_review_bullet_field_values(lines, field)
107
+ if len(values) != 1:
108
+ raise ValueError(field)
109
+ return values[0]
110
+
68
111
 
69
112
  # Sample paths the pipeline must commit. A product rule such as an unanchored
70
113
  # `build/` would otherwise drop them silently from every commit.
@@ -150,6 +193,11 @@ def verify_ledger_entries(path: Path) -> list:
150
193
  return parse_verify_ledger(path.read_text(encoding="utf-8"))
151
194
 
152
195
 
196
+ def read_user_text(path: Path) -> str:
197
+ """Read a user-edited text file with CRLF normalized to LF."""
198
+ return path.read_text(encoding="utf-8").replace("\r\n", "\n").replace("\r", "\n")
199
+
200
+
153
201
  def parse_verify_ledger(text: str) -> list:
154
202
  entries = []
155
203
  for number, line in enumerate(text.splitlines(), start=1):
@@ -204,7 +252,43 @@ def run_command(
204
252
  def run_git(
205
253
  repo: Path, *arguments: str, input: Optional[str] = None
206
254
  ) -> subprocess.CompletedProcess[str]:
207
- return _run_exact(("git", "-C", str(repo), *arguments), input=input)
255
+ return _run_exact(git_command(repo, *arguments), input=input)
256
+
257
+
258
+ def git_command(repo: Path, *arguments: str) -> tuple[str, ...]:
259
+ """Git argv for repo, including WSL drvfs workarounds when needed."""
260
+ return ("git", "-C", str(repo), *git_drvfs_config_flags(repo), *arguments)
261
+
262
+
263
+ def git_drvfs_config_flags(repo: Path) -> tuple[str, ...]:
264
+ """Disable index preload on 9p/drvfs mounts where git commit races on index.lock (#207)."""
265
+ if os.name != "posix":
266
+ return ()
267
+ try:
268
+ root = repo.resolve()
269
+ mounts = Path("/proc/mounts").read_text(encoding="utf-8", errors="replace").splitlines()
270
+ except OSError:
271
+ return ()
272
+ best = ""
273
+ fstype = ""
274
+ for line in mounts:
275
+ parts = line.split()
276
+ if len(parts) < 3:
277
+ continue
278
+ mount_point, mount_type = parts[1], parts[2]
279
+ if mount_type not in {"9p", "drvfs"}:
280
+ continue
281
+ if root == Path(mount_point) or root.is_relative_to(mount_point):
282
+ if len(mount_point) >= len(best):
283
+ best, fstype = mount_point, mount_type
284
+ if not fstype:
285
+ return ()
286
+ return ("-c", "core.preloadindex=false", "-c", "index.threads=1")
287
+
288
+
289
+ def git_index_lock_retryable(stderr: str) -> bool:
290
+ text = stderr.casefold()
291
+ return "index.lock" in text and "file exists" in text
208
292
 
209
293
 
210
294
  # OS files that are never pipeline artifacts when git ignores them.
@@ -227,6 +311,70 @@ def git_visible_entries(directory: Path) -> set:
227
311
  return {path.split("/", 1)[0] for path in result.stdout.split("\0") if path}
228
312
 
229
313
 
314
+ RUNTIME_PIN_PATH = ".gsd-path/runtime.json"
315
+ RUNTIME_PIN_ALLOWANCE = (
316
+ "only runtime-pin commits (.gsd-path/runtime.json) may follow the reviewed HEAD"
317
+ )
318
+
319
+
320
+ def reviewed_head_covers(repo: Path, reviewed: str, head: str) -> bool:
321
+ """True when a review of `reviewed` still covers `head`.
322
+
323
+ That is the same commit, or `reviewed` is an ancestor of `head` and every
324
+ commit on the first-parent path in ``reviewed..head`` is a non-merge commit
325
+ whose only change is ``.gsd-path/runtime.json``. An empty commit does not
326
+ qualify. Any other path, any merge commit, an unresolvable SHA, or a
327
+ non-ancestor returns False.
328
+ """
329
+ if not reviewed or not head:
330
+ return False
331
+ resolved_repo = Path(repo)
332
+ resolved: List[str] = []
333
+ for value in (reviewed, head):
334
+ result = run_git(
335
+ resolved_repo, "rev-parse", "--verify", "--quiet", f"{value}^{{commit}}"
336
+ )
337
+ if result.returncode != 0 or not result.stdout.strip():
338
+ return False
339
+ resolved.append(result.stdout.strip())
340
+ reviewed_sha, head_sha = resolved
341
+ if reviewed_sha == head_sha:
342
+ return True
343
+ if (
344
+ run_git(resolved_repo, "merge-base", "--is-ancestor", reviewed_sha, head_sha).returncode
345
+ != 0
346
+ ):
347
+ return False
348
+ listing = run_git(
349
+ resolved_repo,
350
+ "rev-list",
351
+ "--first-parent",
352
+ "--parents",
353
+ f"{reviewed_sha}..{head_sha}",
354
+ )
355
+ if listing.returncode != 0:
356
+ return False
357
+ for line in listing.stdout.splitlines():
358
+ fields = line.split()
359
+ if len(fields) != 2:
360
+ return False
361
+ commit_sha, parent_sha = fields[0], fields[1]
362
+ changed = run_git(
363
+ resolved_repo,
364
+ "diff",
365
+ "--name-only",
366
+ "--no-renames",
367
+ "--no-ext-diff",
368
+ parent_sha,
369
+ commit_sha,
370
+ )
371
+ if changed.returncode != 0:
372
+ return False
373
+ if {path for path in changed.stdout.splitlines() if path} != {RUNTIME_PIN_PATH}:
374
+ return False
375
+ return True
376
+
377
+
230
378
  def atomic_replace(path: Path, temporary_path: Path, content: str) -> None:
231
379
  temporary_path.parent.mkdir(parents=True, exist_ok=True)
232
380
  descriptor = None
@@ -514,16 +514,28 @@ def run_shell(command: str, timeout: float) -> tuple[Optional[int], str]:
514
514
  """Run one spec command; a hung command counts as failed (exit None).
515
515
 
516
516
  POSIX runs /bin/sh as shell=True did; Windows runs Git for Windows bash, not
517
- cmd.exe. A timeout kills the whole process group, so a grandchild cannot
518
- keep the output pipes open.
517
+ cmd.exe. A timeout kills the whole process group when possible; if output
518
+ pipes stay open, drain them with a short grace timeout and then close them.
519
519
  """
520
520
  argv = _common.bash_argv(command) if os.name == "nt" else ["/bin/sh", "-c", command]
521
521
  process = _common.popen_group(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
522
+ stdout = b""
523
+ stderr = b""
522
524
  try:
523
525
  stdout, stderr = process.communicate(timeout=timeout)
524
- except subprocess.TimeoutExpired:
526
+ except subprocess.TimeoutExpired as expired:
525
527
  _common.kill_tree(process)
526
- stdout, stderr = process.communicate()
528
+ try:
529
+ stdout, stderr = process.communicate(timeout=5)
530
+ except subprocess.TimeoutExpired as drained:
531
+ stdout = drained.stdout or expired.stdout or b""
532
+ stderr = drained.stderr or expired.stderr or b""
533
+ if process.stdout:
534
+ process.stdout.close()
535
+ if process.stderr:
536
+ process.stderr.close()
537
+ process.kill()
538
+ process.wait(timeout=5)
527
539
  output = stdout.decode("utf-8", errors="replace") + stderr.decode("utf-8", errors="replace")
528
540
  return None, f"{output}\ntimed out at the admitted wall-clock deadline"
529
541
  return process.returncode, (stdout + stderr).decode("utf-8", errors="replace")
@@ -13,7 +13,7 @@ sys.dont_write_bytecode = True
13
13
  ROLES = ('coder', 'reviewer', 'review_panel', 'skeptic', 'inspect_codebase',
14
14
  'inspect_docs', 'docs_audit', 'research', 'decide', 'roadmap', 'plan', 'plan_patch')
15
15
  HOSTS = ('codex', 'claude', 'grok', 'opencode', 'copilot', 'qwen', 'antigravity',
16
- 'cursor', 'zed', 'kiro', 'kimi')
16
+ 'cursor', 'zed', 'kiro', 'kimi', 'muse')
17
17
 
18
18
 
19
19
  def sibling(name):
@@ -73,8 +73,8 @@ def validate_value(key, value):
73
73
  if not isinstance(value, str) or not value or value != value.strip():
74
74
  raise ValueError(f'{key} requires a nonempty string')
75
75
  if key == 'integration':
76
- if value not in ('direct', 'pull-request'):
77
- raise ValueError('integration must be direct or pull-request')
76
+ if value not in ('direct', 'pull-request', 'external-landing'):
77
+ raise ValueError('integration must be direct, pull-request, or external-landing')
78
78
  elif key == 'review_panel':
79
79
  if any(char.isspace() for char in value):
80
80
  raise ValueError('use off, detected, or comma-separated families without spaces')
@@ -17,6 +17,7 @@ import os
17
17
  import re
18
18
  import shutil
19
19
  import sys
20
+ from datetime import date
20
21
 
21
22
  # Runtime helpers must not modify their immutable installation.
22
23
  sys.dont_write_bytecode = True
@@ -106,6 +107,16 @@ KINDS = ("checkpoint", "task", "member-task", "uncommitted-archive", "lookahead"
106
107
  PROMOTION_PREFIX = "router: promote lookahead milestone "
107
108
  ABANDON_PREFIX = "build: abandon milestone "
108
109
  INTEGRATE_PREFIX = "integrate: "
110
+ SHIP_LOG_ONLY_LINE = re.compile(
111
+ r"- (?P<date>\d{4}-\d{2}-\d{2}) — ship — (?P<event>[^\r\n]+)\n\Z"
112
+ )
113
+ SHIPMENT_RECORD_LINE = re.compile(
114
+ r"- (?P<date>\d{4}-\d{2}-\d{2}) — shipped — (?P<event>[^\r\n]+)\n\Z"
115
+ )
116
+ SHIPMENT_EVENTS = {
117
+ "archive preflight passed; shipment recorded",
118
+ "archive preflight passed; shipment recorded; program complete",
119
+ }
109
120
 
110
121
 
111
122
  class UndoError(RuntimeError):
@@ -375,6 +386,80 @@ def pending_transaction(repo: Path) -> Optional[dict[str, object]]:
375
386
  raise UndoError(str(error)) from error
376
387
 
377
388
 
389
+ def _valid_state_event_line(line: str, phase: str) -> bool:
390
+ pattern = SHIP_LOG_ONLY_LINE if phase == "ship" else SHIPMENT_RECORD_LINE
391
+ match = pattern.fullmatch(line)
392
+ if match is None:
393
+ return False
394
+ try:
395
+ event_date = date.fromisoformat(match.group("date"))
396
+ except ValueError:
397
+ return False
398
+ event = match.group("event")
399
+ return event_date.isoformat() == match.group("date") and event == " ".join(event.split())
400
+
401
+
402
+ def _owned_ship_log_events(
403
+ prepared_state: str,
404
+ current_state: str,
405
+ phase: object,
406
+ status: object,
407
+ ) -> Optional[str]:
408
+ """Return only ship log lines appended around a helper-owned archive event.
409
+
410
+ The prepared text must remain byte-for-byte unchanged. For shipped/done,
411
+ record-shipment changes only phase and status before appending its one
412
+ generated shipment event, so validate that exact prefix and event shape.
413
+ """
414
+ if (phase, status) == ("ship", "active"):
415
+ if current_state == prepared_state:
416
+ return ""
417
+ if not current_state.startswith(prepared_state):
418
+ return None
419
+ suffix = current_state[len(prepared_state):]
420
+ separator = "" if prepared_state.endswith("\n") else "\n"
421
+ if separator:
422
+ if not suffix.startswith(separator):
423
+ return None
424
+ suffix = suffix[len(separator):]
425
+ lines = suffix.splitlines(keepends=True)
426
+ if not lines or "".join(lines) != suffix:
427
+ return None
428
+ if not all(_valid_state_event_line(line, "ship") for line in lines):
429
+ return None
430
+ return separator + suffix
431
+
432
+ if (phase, status) != ("shipped", "done"):
433
+ return None
434
+ try:
435
+ shipped_prefix = pipeline_state._set_frontmatter(
436
+ prepared_state,
437
+ {"phase": "shipped", "status": "done"},
438
+ )
439
+ except PipelineStateError:
440
+ return None
441
+ if not current_state.startswith(shipped_prefix):
442
+ return None
443
+ suffix = current_state[len(shipped_prefix):]
444
+ separator = "" if shipped_prefix.endswith("\n") else "\n"
445
+ if separator:
446
+ if not suffix.startswith(separator):
447
+ return None
448
+ suffix = suffix[len(separator):]
449
+ lines = suffix.splitlines(keepends=True)
450
+ if not lines or "".join(lines) != suffix:
451
+ return None
452
+ shipment_event = lines[-1]
453
+ if not _valid_state_event_line(shipment_event, "shipped"):
454
+ return None
455
+ if SHIPMENT_RECORD_LINE.fullmatch(shipment_event).group("event") not in SHIPMENT_EVENTS:
456
+ return None
457
+ ship_events = lines[:-1]
458
+ if not all(_valid_state_event_line(line, "ship") for line in ship_events):
459
+ return None
460
+ return separator + "".join(ship_events) if ship_events else ""
461
+
462
+
378
463
  def _archive_metadata_error(
379
464
  repo: Path,
380
465
  state_fields: dict[str, object],
@@ -420,8 +505,13 @@ def _archive_metadata_error(
420
505
  if final_review.exists() or gap_reviews:
421
506
  reviewed_head, _ = archive_milestone.parse_final_review(archive_path)
422
507
  archive_milestone.validate_gap_reviews(archive_path, reviewed_head)
423
- if reviewed_head != _optional_rev(repo, "HEAD"):
424
- return "archive undo final review does not match current HEAD"
508
+ if not archive_milestone.reviewed_head_covers(
509
+ repo, reviewed_head, _optional_rev(repo, "HEAD")
510
+ ):
511
+ return (
512
+ "archive undo final review does not match current HEAD; "
513
+ f"{archive_milestone._common.RUNTIME_PIN_ALLOWANCE}"
514
+ )
425
515
  expected_inventory.add("review/FINAL.md")
426
516
  expected_inventory.update(
427
517
  path.relative_to(archive_path).as_posix() for path in gap_reviews
@@ -476,10 +566,14 @@ def _archive_metadata_error(
476
566
  "archive",
477
567
  configured,
478
568
  )
479
- current_state = (repo / ".project" / "STATE.md").read_text(encoding="utf-8")
569
+ current_state = (repo / ".project" / "STATE.md").read_bytes().decode("utf-8")
480
570
  phase = state_fields.get("phase")
481
571
  status = state_fields.get("status")
572
+ ship_events = _owned_ship_log_events(prepared_state, current_state, phase, status)
482
573
  if (phase, status) == ("ship", "active"):
574
+ if ship_events is None:
575
+ return "archive undo found unowned STATE.md changes"
576
+ prepared_state += ship_events
483
577
  if current_state != prepared_state:
484
578
  return "archive undo found unowned STATE.md changes"
485
579
  if ".project/ROADMAP.md" in dirty:
@@ -487,14 +581,13 @@ def _archive_metadata_error(
487
581
  return None
488
582
  if (phase, status) != ("shipped", "done"):
489
583
  return "archive undo requires ship/active or shipped/done"
584
+ if ship_events is None:
585
+ return "archive undo found unowned shipment STATE.md changes"
586
+ prepared_state += ship_events
490
587
 
491
588
  prepared = _state_from_text(prepared_state, "prepared archive STATE.md")
492
- events = (
493
- "archive preflight passed; shipment recorded",
494
- "archive preflight passed; shipment recorded; program complete",
495
- )
496
589
  rendered_states = []
497
- for event in events:
590
+ for event in SHIPMENT_EVENTS:
498
591
  try:
499
592
  _, _, rendered = _render_transition(
500
593
  prepared,
@@ -22,6 +22,27 @@ else:
22
22
  from scripts import _common
23
23
 
24
24
 
25
+ def _worktree_list_porcelain(primary: Path) -> tuple[str, bool]:
26
+ try:
27
+ if __package__:
28
+ from . import pipeline_git as pg
29
+ else:
30
+ from scripts import pipeline_git as pg
31
+ except ImportError:
32
+ try:
33
+ import pipeline_git as pg
34
+ except ImportError:
35
+ pg = None
36
+ if pg is not None:
37
+ return pg.worktree_list_porcelain(primary)
38
+ try:
39
+ output = _git(primary, "worktree", "list", "--porcelain", "-z")
40
+ return output, True
41
+ except ValueError:
42
+ output = _git(primary, "worktree", "list", "--porcelain")
43
+ return output, False
44
+
45
+
25
46
  def _git(primary: Path, *args: str) -> str:
26
47
  result = _common.run_git(primary, *args)
27
48
  if result.returncode:
@@ -82,6 +103,33 @@ def _workspace(primary: Path, pin: bool) -> Path:
82
103
  return workspace
83
104
 
84
105
 
106
+ def release_workspace(primary: Path) -> bool:
107
+ """Forget the pinned placement receipt once no linked worktrees remain.
108
+
109
+ Lets GSD_PATH_WORKTREE_ROOT (or a new default) apply again at the next
110
+ milestone's first allocation. Returns True when a receipt was removed;
111
+ any remaining linked worktree (including one only still registered) keeps
112
+ the pin.
113
+ """
114
+ primary = primary.resolve()
115
+ common = Path(_git(primary, "rev-parse", "--git-common-dir"))
116
+ common = (primary / common).resolve()
117
+ receipt = common / "gsd-path" / "workspaces" / f"{_identity(primary)}.json"
118
+ if not receipt.is_file():
119
+ return False
120
+ output, nul_separated = _worktree_list_porcelain(primary)
121
+ records = output.split("\0\0") if nul_separated else [
122
+ block for block in output.strip().split("\n\n") if block.strip()
123
+ ]
124
+ for record in records:
125
+ fields = record.split("\0") if nul_separated else record.splitlines()
126
+ for field in fields:
127
+ if field.startswith("worktree ") and Path(field[len("worktree "):]).resolve() != primary:
128
+ return False
129
+ receipt.unlink()
130
+ return True
131
+
132
+
85
133
  def worktree_path(primary: Path, kind: str, name: str, *, pin: bool = False) -> Path:
86
134
  primary = primary.resolve()
87
135
  if kind not in {"task", "verify", "integrate", "bound"} or not name or Path(name).name != name or name in {".", ".."}:
@@ -91,9 +139,13 @@ def worktree_path(primary: Path, kind: str, name: str, *, pin: bool = False) ->
91
139
  legacy = (primary.parent / f".{primary.name}-gsd-path-integrate-{name}"
92
140
  if kind == "integrate" else primary.parent / f"{primary.name}.gsd-path" / kind / name)
93
141
  branch = f"refs/heads/gsd-path-{kind}/{name}"
94
- records = _git(primary, "worktree", "list", "--porcelain", "-z").split("\0\0")
142
+ output, nul_separated = _worktree_list_porcelain(primary)
143
+ if nul_separated:
144
+ records = output.split("\0\0")
145
+ else:
146
+ records = [block for block in output.strip().split("\n\n") if block.strip()]
95
147
  for record in records:
96
- fields = record.split("\0")
148
+ fields = record.split("\0") if nul_separated else record.splitlines()
97
149
  # Compare as paths: git prints forward slashes on Windows.
98
150
  worktrees = [Path(field[len("worktree "):]) for field in fields if field.startswith("worktree ")]
99
151
  if legacy in worktrees and f"branch {branch}" in fields:
@@ -100,6 +100,10 @@ Obtain a confident answer in every area before finishing:
100
100
  give every named surface at least one success criterion observable
101
101
  there. A criterion satisfied by a passing test alone does not cover a
102
102
  surface.
103
+ 9. **Edges and prohibitions** — once the success criteria are drafted, run
104
+ the [spec-reach probes](references/spec-probes.md): every applicable edge
105
+ and every kept prohibition gets the user's ruling in INTENT.md
106
+ `## Edge coverage` and `## Prohibitions`.
103
107
 
104
108
  ## Program mode
105
109
 
@@ -149,7 +153,10 @@ State ownership. There is no re-interview of charter or roadmap scope:
149
153
  `Lane: milestone`, `Surfaces:` copied verbatim from the entry, and
150
154
  `Review panel:` copied from CHARTER.md (default
151
155
  the configured preference when CHARTER omits it). The confirmation may override the copied
152
- panel value; do not invent `detected` or a named list. When brownfield,
156
+ panel value; do not invent `detected` or a named list. Run the
157
+ [spec-reach probes](references/spec-probes.md) over the derived criteria
158
+ and draft `## Edge coverage` and `## Prohibitions` with recommended
159
+ rulings. When brownfield,
153
160
  fill `## Current state` from the map, record doc-vs-code rulings as in
154
161
  Brownfield mode, and add protected existing behavior under Scope out.
155
162
  Write Ground truth paths from the track root: `.project` normally, or
@@ -157,7 +164,9 @@ State ownership. There is no re-interview of charter or roadmap scope:
157
164
  active `research/` paths.
158
165
  3. Present one confirmation, not an interview: playback the derivation (and
159
166
  brownfield ground truth when present), link the resolved absolute INTENT.md
160
- path, and ask approve or adjust. A requested change that contradicts the
167
+ path, and ask approve or adjust. The confirmation lists each proposed edge
168
+ and prohibition ruling for the user to accept or change; it never rules on
169
+ them silently. A requested change that contradicts the
161
170
  charter or the resolved roadmap entry is a scope change — this phase never
162
171
  edits the roadmap; surface it to the user for a `$gsd-path-roadmap` re-slice
163
172
  at the next milestone boundary.
@@ -254,7 +263,15 @@ question. The rules change for every primary mode:
254
263
  the brownfield map already established. Milestone mode skips this step.
255
264
  5. Stop when coverage is complete or the user says `enough`. Record remaining
256
265
  uncertainty under `## Open questions` with `RESEARCH` or `NEEDS-USER`.
257
- 6. Classify the proposed milestone lane before writing the approval draft.
266
+ `enough` does not skip the next step.
267
+ 6. In standard mode, run the [spec-reach probes](references/spec-probes.md)
268
+ over the drafted success criteria: one question round for edges, then one
269
+ for kept prohibitions, each with a recommended ruling first. A ruling
270
+ observable where the user sees the work becomes or amends a success
271
+ criterion; write every ruling into `## Edge coverage` and
272
+ `## Prohibitions`. Program mode skips this step (each milestone runs it
273
+ in milestone mode); milestone mode runs it inside its confirmation.
274
+ 7. Classify the proposed milestone lane before writing the approval draft.
258
275
  Milestone mode keeps `Lane: milestone` and skips this classification.
259
276
  Program mode writes CHARTER.md instead of INTENT.md. Otherwise choose
260
277
  `quick` when scope fits at most two deliverable-sized tasks (project
@@ -275,6 +292,11 @@ question. The rules change for every primary mode:
275
292
  ## Output contract
276
293
 
277
294
  At approval, retain the already reviewed `Lane:` value and its one-line reason.
295
+ Before every approval question on INTENT.md, run the bundled `python3
296
+ <absolute-bundled-script> intent --repo <absolute-root>` helper
297
+ (`scripts/check_handoffs.py`, plus `--project-dir .project/next` in
298
+ lookahead). It checks the success criteria, Surfaces, and both probe tables;
299
+ fix a failure in the draft before asking. It approves nothing.
278
300
  Before presenting an approval draft without a CHARTER or explicit panel choice,
279
301
  read `settings.review_panel.value` from the selected runtime's `path_config.py
280
302
  show --repo <absolute-root>` (bundle for legacy projects; off for older selected
@@ -302,5 +324,8 @@ Use the executable phase handoff in AGENTS.md for the next action.
302
324
  - In brownfield mode, state mapped facts for correction; never re-ask them.
303
325
  - Treat solution-shaped problems as hypotheses and uncover the underlying pain.
304
326
  - Preserve vetoes and corrections verbatim.
327
+ - Never rule on an edge's behavior or a prohibition yourself; recommend,
328
+ then record the user's ruling. An edge the user has not ruled on is a
329
+ required answer, including under pre-approval.
305
330
  - Wrap up when every coverage-checklist area is answered or the user signals
306
331
  done, and tag remaining gaps.
@@ -115,9 +115,10 @@ as stated in Lookahead mode.
115
115
  In patch mode, tasks already `done` keep their landed metadata; only
116
116
  the appended patch tasks must start clean.
117
117
  - Require every dependency id to exist, forbid later-wave dependencies,
118
- detect cycles, and forbid file overlap between planned tasks in the same
119
- wave. Same-wave dependency chains are allowed only when their files do
120
- not overlap; the build executes them in dependency layers.
118
+ detect cycles, and forbid unordered file overlap between planned tasks
119
+ in the same wave. Same-wave tasks may share files only when one
120
+ transitively depends on the other through `deps`; the build executes
121
+ them in dependency layers.
121
122
  - Reject a declared dependency that carries neither data nor a prerequisite
122
123
  effect. A `deps` edge is valid only when the dependent consumes a symbol,
123
124
  signature, schema, endpoint, file format, or path named in that
@@ -157,6 +158,13 @@ as stated in Lookahead mode.
157
158
  command's output — not an internal unit test.
158
159
  Require the surface to land in the same wave as the capability behind
159
160
  it, never in a later polish wave.
161
+ - When INTENT.md has `## Edge coverage`, require a PLAN.md
162
+ `## Held-out checks` row for every `held-out` edge and for no other
163
+ edge. The named test is in that task's `files`, the task's Verify runs
164
+ it, and the task's Acceptance criteria state the edge id and its ruling
165
+ (see [spec-reach probes](references/spec-probes.md)). An edge or
166
+ prohibition ruled `criterion SCn` needs no extra row; it is covered as
167
+ SCn. Put each `judgment` prohibition in its owning task's Approach.
160
168
  - Prove every intent constraint and synthesis decision is covered, that no
161
169
  scope-out veto appears in a task, and that `Project verify` is a real,
162
170
  non-placeholder command in PLAN.md. Every Verify — task and project —
@@ -84,6 +84,17 @@ settled brownfield input.
84
84
  template before dispatch. Record every standard dimension exactly once as
85
85
  `dispatched` or `skipped` with a reason, and assign every research question
86
86
  to at least one dispatched dimension. No question may remain unassigned.
87
+ Copy each question to exactly one `Question assignments` row. Assign one or
88
+ more dispatched dimension slugs there, each in its own code span and
89
+ separated by commas, as in:
90
+
91
+ ```md
92
+ - `[RESEARCH] Which approach is viable?` → `stack`, `pitfalls`
93
+ ```
94
+
95
+ List a dimension only once per row. Every dispatched dimension must own at
96
+ least one question, and its evidence file must answer every question
97
+ assigned to it. Use `- none` only when there are no research questions.
87
98
  2. Decide which standard dimensions have work: dispatch a dimension only when
88
99
  it has at least one assigned `RESEARCH` question from INTENT.md or
89
100
  CHARTER.md. Unsettled choices and risks need that source question before
@@ -159,7 +159,12 @@ so the position is unchanged. Then:
159
159
  then stop and do not invent a patch finding. A `not-met`,
160
160
  `unverifiable`, or blocked gap with valid evidence sets `ship/blocked`,
161
161
  writes `.project/review/PATCH-FINDINGS.md` from the patch-findings template,
162
- and runs `python3 <absolute check_handoffs.py> patch --repo <absolute repo
162
+ copying the full `Risk: ...` line into each gap finding's Locator, its
163
+ `Found` field into Evidence, and its `Fix direction` field verbatim. For a
164
+ `FINAL.md` finding, use its `SC<n>` id as Locator and copy its `Finding` and
165
+ `Fix direction` fields verbatim. The patch validator requires these source
166
+ field values to match exactly; do not shorten or paraphrase them. Run
167
+ `python3 <absolute check_handoffs.py> patch --repo <absolute repo
163
168
  root>`. Present the blocked outcome, link the resolved absolute
164
169
  PATCH-FINDINGS.md path, and state that patch planning is next. The manifest
165
170
  contains exactly those FINAL.md and gap rows. When routed by an active