@opengsd/gsd-path 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/AGENTS.md +2 -207
  2. package/CHANGELOG.md +294 -0
  3. package/DOCS.md +19 -10
  4. package/FULL.md +9 -3
  5. package/GUIDE.md +1 -1
  6. package/HOOKS.md +9 -6
  7. package/QUICK.md +4 -1
  8. package/README.md +63 -11
  9. package/RUNTIME.md +1 -1
  10. package/UPDATE.md +61 -12
  11. package/WORKFLOW.md +22 -6
  12. package/package.json +9 -8
  13. package/scripts/_common.py +472 -19
  14. package/scripts/archive_milestone.py +146 -73
  15. package/scripts/bootstrap_repository.py +8 -5
  16. package/scripts/build_recovery.py +4 -1
  17. package/scripts/build_state.py +66 -23
  18. package/scripts/check_docs_audit.py +14 -3
  19. package/scripts/check_handoffs.py +116 -20
  20. package/scripts/check_task_briefs.py +60 -8
  21. package/scripts/check_update.py +2 -2
  22. package/scripts/core_hook_gate.py +10 -1
  23. package/scripts/core_hook_settings.py +11 -4
  24. package/scripts/detect_project.py +345 -15
  25. package/scripts/discussion_records.py +19 -13
  26. package/scripts/discussion_validate.py +66 -21
  27. package/scripts/dispatch_driver.py +111 -26
  28. package/scripts/git_guard.py +116 -29
  29. package/scripts/guard_hook.py +151 -29
  30. package/scripts/install.mjs +55 -39
  31. package/scripts/install.py +564 -41
  32. package/scripts/integration.py +434 -24
  33. package/scripts/isolation.py +1077 -28
  34. package/scripts/lean_verification.py +99 -19
  35. package/scripts/loop_run.py +20 -30
  36. package/scripts/members.py +704 -0
  37. package/scripts/migrate_core.py +2 -2
  38. package/scripts/path_config.py +2 -2
  39. package/scripts/pipeline_diagnose.py +82 -31
  40. package/scripts/pipeline_git.py +61 -22
  41. package/scripts/pipeline_state.py +176 -54
  42. package/scripts/pipeline_undo.py +117 -22
  43. package/scripts/promote_lookahead.py +1 -1
  44. package/scripts/review_panel.py +1 -1
  45. package/scripts/runtime_store.py +49 -11
  46. package/scripts/skill-resources.json +28 -3
  47. package/scripts/state_checkpoint.py +140 -31
  48. package/scripts/state_promote.py +2 -0
  49. package/scripts/status_runtime.py +36 -6
  50. package/scripts/sync_skill_resources.py +8 -7
  51. package/scripts/token_budget.py +12 -8
  52. package/scripts/workflow_run.py +103 -29
  53. package/scripts/worktree_paths.py +18 -8
  54. package/skills/gsd-path/BUILD.md +7 -1
  55. package/skills/gsd-path/DECIDE.md +11 -4
  56. package/skills/gsd-path/DEFINE.md +8 -1
  57. package/skills/gsd-path/DOCS-AUDIT.md +6 -4
  58. package/skills/gsd-path/INSPECT.md +5 -4
  59. package/skills/gsd-path/PLAN.md +7 -1
  60. package/skills/gsd-path/RESEARCH.md +2 -1
  61. package/skills/gsd-path/ROADMAP.md +2 -1
  62. package/skills/gsd-path/SHIP.md +27 -10
  63. package/skills/gsd-path/SKILL.md +45 -1
  64. package/skills/gsd-path/references/build-native.md +47 -14
  65. package/skills/gsd-path/references/codebase-mapper.md +2 -1
  66. package/skills/gsd-path/references/docs-auditor.md +2 -1
  67. package/skills/gsd-path/references/operating-rules.md +214 -0
  68. package/skills/gsd-path/references/planner.md +8 -0
  69. package/skills/gsd-path/references/reviewer-wave.md +3 -1
  70. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  71. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  72. package/skills/gsd-path/scripts/_common.py +472 -19
  73. package/skills/gsd-path/scripts/archive_milestone.py +146 -73
  74. package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
  75. package/skills/gsd-path/scripts/build_recovery.py +4 -1
  76. package/skills/gsd-path/scripts/build_state.py +66 -23
  77. package/skills/gsd-path/scripts/check_docs_audit.py +14 -3
  78. package/skills/gsd-path/scripts/check_handoffs.py +116 -20
  79. package/skills/gsd-path/scripts/check_task_briefs.py +60 -8
  80. package/skills/gsd-path/scripts/check_update.py +2 -2
  81. package/skills/gsd-path/scripts/detect_project.py +345 -15
  82. package/skills/gsd-path/scripts/discussion_records.py +19 -13
  83. package/skills/gsd-path/scripts/discussion_validate.py +66 -21
  84. package/skills/gsd-path/scripts/dispatch_driver.py +111 -26
  85. package/skills/gsd-path/scripts/git_guard.py +116 -29
  86. package/skills/gsd-path/scripts/integration.py +434 -24
  87. package/skills/gsd-path/scripts/isolation.py +1077 -28
  88. package/skills/gsd-path/scripts/lean_verification.py +99 -19
  89. package/skills/gsd-path/scripts/loop_run.py +20 -30
  90. package/skills/gsd-path/scripts/members.py +704 -0
  91. package/skills/gsd-path/scripts/path_config.py +2 -2
  92. package/skills/gsd-path/scripts/pipeline_git.py +61 -22
  93. package/skills/gsd-path/scripts/pipeline_state.py +176 -54
  94. package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
  95. package/skills/gsd-path/scripts/review_panel.py +1 -1
  96. package/skills/gsd-path/scripts/state_checkpoint.py +140 -31
  97. package/skills/gsd-path/scripts/state_promote.py +2 -0
  98. package/skills/gsd-path/scripts/status_runtime.py +36 -6
  99. package/skills/gsd-path/scripts/token_budget.py +12 -8
  100. package/skills/gsd-path/scripts/workflow_run.py +103 -29
  101. package/skills/gsd-path/scripts/worktree_paths.py +18 -8
  102. package/skills/gsd-path/templates/codebase.md +1 -1
  103. package/skills/gsd-path/templates/docs-audit.md +1 -1
  104. package/skills/gsd-path/templates/final-review.md +5 -2
  105. package/skills/gsd-path/templates/gap-review.md +2 -1
  106. package/skills/gsd-path/templates/task.md +1 -0
  107. package/skills/gsd-path/templates/wave-review.md +3 -1
  108. package/skills/gsd-path-build/SKILL.md +7 -1
  109. package/skills/gsd-path-build/references/build-native.md +47 -14
  110. package/skills/gsd-path-build/references/operating-rules.md +214 -0
  111. package/skills/gsd-path-build/references/reviewer-wave.md +3 -1
  112. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  113. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  114. package/skills/gsd-path-build/scripts/_common.py +472 -19
  115. package/skills/gsd-path-build/scripts/archive_milestone.py +146 -73
  116. package/skills/gsd-path-build/scripts/build_recovery.py +4 -1
  117. package/skills/gsd-path-build/scripts/build_state.py +66 -23
  118. package/skills/gsd-path-build/scripts/check_handoffs.py +116 -20
  119. package/skills/gsd-path-build/scripts/check_task_briefs.py +60 -8
  120. package/skills/gsd-path-build/scripts/detect_project.py +345 -15
  121. package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
  122. package/skills/gsd-path-build/scripts/discussion_validate.py +66 -21
  123. package/skills/gsd-path-build/scripts/dispatch_driver.py +111 -26
  124. package/skills/gsd-path-build/scripts/integration.py +434 -24
  125. package/skills/gsd-path-build/scripts/isolation.py +1077 -28
  126. package/skills/gsd-path-build/scripts/loop_run.py +20 -30
  127. package/skills/gsd-path-build/scripts/members.py +704 -0
  128. package/skills/gsd-path-build/scripts/path_config.py +2 -2
  129. package/skills/gsd-path-build/scripts/pipeline_git.py +61 -22
  130. package/skills/gsd-path-build/scripts/pipeline_state.py +176 -54
  131. package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
  132. package/skills/gsd-path-build/scripts/review_panel.py +1 -1
  133. package/skills/gsd-path-build/scripts/state_checkpoint.py +140 -31
  134. package/skills/gsd-path-build/scripts/state_promote.py +2 -0
  135. package/skills/gsd-path-build/scripts/token_budget.py +12 -8
  136. package/skills/gsd-path-build/scripts/workflow_run.py +103 -29
  137. package/skills/gsd-path-build/scripts/worktree_paths.py +18 -8
  138. package/skills/gsd-path-build/templates/task.md +1 -0
  139. package/skills/gsd-path-build/templates/wave-review.md +3 -1
  140. package/skills/gsd-path-decide/SKILL.md +11 -4
  141. package/skills/gsd-path-decide/references/operating-rules.md +214 -0
  142. package/skills/gsd-path-decide/scripts/_common.py +472 -19
  143. package/skills/gsd-path-decide/scripts/archive_milestone.py +146 -73
  144. package/skills/gsd-path-decide/scripts/build_recovery.py +4 -1
  145. package/skills/gsd-path-decide/scripts/check_handoffs.py +116 -20
  146. package/skills/gsd-path-decide/scripts/check_task_briefs.py +60 -8
  147. package/skills/gsd-path-decide/scripts/detect_project.py +345 -15
  148. package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
  149. package/skills/gsd-path-decide/scripts/discussion_validate.py +66 -21
  150. package/skills/gsd-path-decide/scripts/integration.py +434 -24
  151. package/skills/gsd-path-decide/scripts/isolation.py +1077 -28
  152. package/skills/gsd-path-decide/scripts/loop_run.py +20 -30
  153. package/skills/gsd-path-decide/scripts/members.py +704 -0
  154. package/skills/gsd-path-decide/scripts/path_config.py +2 -2
  155. package/skills/gsd-path-decide/scripts/pipeline_git.py +61 -22
  156. package/skills/gsd-path-decide/scripts/pipeline_state.py +176 -54
  157. package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
  158. package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
  159. package/skills/gsd-path-decide/scripts/state_checkpoint.py +140 -31
  160. package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
  161. package/skills/gsd-path-decide/scripts/worktree_paths.py +18 -8
  162. package/skills/gsd-path-define/SKILL.md +8 -1
  163. package/skills/gsd-path-define/references/operating-rules.md +214 -0
  164. package/skills/gsd-path-define/scripts/_common.py +472 -19
  165. package/skills/gsd-path-define/scripts/archive_milestone.py +146 -73
  166. package/skills/gsd-path-define/scripts/build_recovery.py +4 -1
  167. package/skills/gsd-path-define/scripts/check_handoffs.py +116 -20
  168. package/skills/gsd-path-define/scripts/check_task_briefs.py +60 -8
  169. package/skills/gsd-path-define/scripts/detect_project.py +345 -15
  170. package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
  171. package/skills/gsd-path-define/scripts/discussion_validate.py +66 -21
  172. package/skills/gsd-path-define/scripts/integration.py +434 -24
  173. package/skills/gsd-path-define/scripts/isolation.py +1077 -28
  174. package/skills/gsd-path-define/scripts/loop_run.py +20 -30
  175. package/skills/gsd-path-define/scripts/members.py +704 -0
  176. package/skills/gsd-path-define/scripts/path_config.py +2 -2
  177. package/skills/gsd-path-define/scripts/pipeline_git.py +61 -22
  178. package/skills/gsd-path-define/scripts/pipeline_state.py +176 -54
  179. package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
  180. package/skills/gsd-path-define/scripts/review_panel.py +1 -1
  181. package/skills/gsd-path-define/scripts/state_checkpoint.py +140 -31
  182. package/skills/gsd-path-define/scripts/state_promote.py +2 -0
  183. package/skills/gsd-path-define/scripts/worktree_paths.py +18 -8
  184. package/skills/gsd-path-discuss/SKILL.md +2 -1
  185. package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
  186. package/skills/gsd-path-discuss/scripts/_common.py +472 -19
  187. package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
  188. package/skills/gsd-path-discuss/scripts/loop_run.py +20 -30
  189. package/skills/gsd-path-discuss/scripts/path_config.py +2 -2
  190. package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
  191. package/skills/gsd-path-discuss/scripts/worktree_paths.py +18 -8
  192. package/skills/gsd-path-docs-audit/SKILL.md +6 -4
  193. package/skills/gsd-path-docs-audit/references/docs-auditor.md +2 -1
  194. package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
  195. package/skills/gsd-path-docs-audit/scripts/_common.py +472 -19
  196. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +146 -73
  197. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +4 -1
  198. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +14 -3
  199. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +116 -20
  200. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +60 -8
  201. package/skills/gsd-path-docs-audit/scripts/detect_project.py +345 -15
  202. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
  203. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +66 -21
  204. package/skills/gsd-path-docs-audit/scripts/integration.py +434 -24
  205. package/skills/gsd-path-docs-audit/scripts/isolation.py +1077 -28
  206. package/skills/gsd-path-docs-audit/scripts/loop_run.py +20 -30
  207. package/skills/gsd-path-docs-audit/scripts/members.py +704 -0
  208. package/skills/gsd-path-docs-audit/scripts/path_config.py +2 -2
  209. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +61 -22
  210. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +176 -54
  211. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
  212. package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
  213. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +140 -31
  214. package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
  215. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +18 -8
  216. package/skills/gsd-path-docs-audit/templates/docs-audit.md +1 -1
  217. package/skills/gsd-path-forensics/SKILL.md +2 -1
  218. package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
  219. package/skills/gsd-path-forensics/scripts/_common.py +472 -19
  220. package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
  221. package/skills/gsd-path-forensics/scripts/loop_run.py +20 -30
  222. package/skills/gsd-path-forensics/scripts/path_config.py +2 -2
  223. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
  224. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +117 -22
  225. package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
  226. package/skills/gsd-path-forensics/scripts/worktree_paths.py +18 -8
  227. package/skills/gsd-path-inspect/SKILL.md +5 -4
  228. package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
  229. package/skills/gsd-path-inspect/references/docs-auditor.md +2 -1
  230. package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
  231. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  232. package/skills/gsd-path-inspect/scripts/_common.py +472 -19
  233. package/skills/gsd-path-inspect/scripts/archive_milestone.py +146 -73
  234. package/skills/gsd-path-inspect/scripts/build_recovery.py +4 -1
  235. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +14 -3
  236. package/skills/gsd-path-inspect/scripts/check_handoffs.py +116 -20
  237. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +60 -8
  238. package/skills/gsd-path-inspect/scripts/detect_project.py +345 -15
  239. package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
  240. package/skills/gsd-path-inspect/scripts/discussion_validate.py +66 -21
  241. package/skills/gsd-path-inspect/scripts/integration.py +434 -24
  242. package/skills/gsd-path-inspect/scripts/isolation.py +1077 -28
  243. package/skills/gsd-path-inspect/scripts/loop_run.py +20 -30
  244. package/skills/gsd-path-inspect/scripts/members.py +704 -0
  245. package/skills/gsd-path-inspect/scripts/path_config.py +2 -2
  246. package/skills/gsd-path-inspect/scripts/pipeline_git.py +61 -22
  247. package/skills/gsd-path-inspect/scripts/pipeline_state.py +176 -54
  248. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
  249. package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
  250. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +140 -31
  251. package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
  252. package/skills/gsd-path-inspect/scripts/workflow_run.py +103 -29
  253. package/skills/gsd-path-inspect/scripts/worktree_paths.py +18 -8
  254. package/skills/gsd-path-inspect/templates/codebase.md +1 -1
  255. package/skills/gsd-path-inspect/templates/docs-audit.md +1 -1
  256. package/skills/gsd-path-loop/SKILL.md +2 -1
  257. package/skills/gsd-path-loop/references/operating-rules.md +214 -0
  258. package/skills/gsd-path-loop/scripts/_common.py +472 -19
  259. package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
  260. package/skills/gsd-path-loop/scripts/loop_run.py +20 -30
  261. package/skills/gsd-path-loop/scripts/path_config.py +2 -2
  262. package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
  263. package/skills/gsd-path-loop/scripts/worktree_paths.py +18 -8
  264. package/skills/gsd-path-migrate/SKILL.md +2 -1
  265. package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
  266. package/skills/gsd-path-migrate/scripts/_common.py +472 -19
  267. package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
  268. package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
  269. package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
  270. package/skills/gsd-path-migrate/scripts/loop_run.py +20 -30
  271. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
  272. package/skills/gsd-path-migrate/scripts/path_config.py +2 -2
  273. package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
  274. package/skills/gsd-path-plan/SKILL.md +7 -1
  275. package/skills/gsd-path-plan/references/operating-rules.md +214 -0
  276. package/skills/gsd-path-plan/references/planner.md +8 -0
  277. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  278. package/skills/gsd-path-plan/scripts/_common.py +472 -19
  279. package/skills/gsd-path-plan/scripts/archive_milestone.py +146 -73
  280. package/skills/gsd-path-plan/scripts/build_recovery.py +4 -1
  281. package/skills/gsd-path-plan/scripts/check_handoffs.py +116 -20
  282. package/skills/gsd-path-plan/scripts/check_task_briefs.py +60 -8
  283. package/skills/gsd-path-plan/scripts/detect_project.py +345 -15
  284. package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
  285. package/skills/gsd-path-plan/scripts/discussion_validate.py +66 -21
  286. package/skills/gsd-path-plan/scripts/integration.py +434 -24
  287. package/skills/gsd-path-plan/scripts/isolation.py +1077 -28
  288. package/skills/gsd-path-plan/scripts/loop_run.py +20 -30
  289. package/skills/gsd-path-plan/scripts/members.py +704 -0
  290. package/skills/gsd-path-plan/scripts/path_config.py +2 -2
  291. package/skills/gsd-path-plan/scripts/pipeline_git.py +61 -22
  292. package/skills/gsd-path-plan/scripts/pipeline_state.py +176 -54
  293. package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
  294. package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
  295. package/skills/gsd-path-plan/scripts/state_checkpoint.py +140 -31
  296. package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
  297. package/skills/gsd-path-plan/scripts/workflow_run.py +103 -29
  298. package/skills/gsd-path-plan/scripts/worktree_paths.py +18 -8
  299. package/skills/gsd-path-plan/templates/task.md +1 -0
  300. package/skills/gsd-path-research/SKILL.md +2 -1
  301. package/skills/gsd-path-research/references/operating-rules.md +214 -0
  302. package/skills/gsd-path-research/scripts/_common.py +472 -19
  303. package/skills/gsd-path-research/scripts/archive_milestone.py +146 -73
  304. package/skills/gsd-path-research/scripts/build_recovery.py +4 -1
  305. package/skills/gsd-path-research/scripts/check_handoffs.py +116 -20
  306. package/skills/gsd-path-research/scripts/check_task_briefs.py +60 -8
  307. package/skills/gsd-path-research/scripts/detect_project.py +345 -15
  308. package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
  309. package/skills/gsd-path-research/scripts/discussion_validate.py +66 -21
  310. package/skills/gsd-path-research/scripts/integration.py +434 -24
  311. package/skills/gsd-path-research/scripts/isolation.py +1077 -28
  312. package/skills/gsd-path-research/scripts/loop_run.py +20 -30
  313. package/skills/gsd-path-research/scripts/members.py +704 -0
  314. package/skills/gsd-path-research/scripts/path_config.py +2 -2
  315. package/skills/gsd-path-research/scripts/pipeline_git.py +61 -22
  316. package/skills/gsd-path-research/scripts/pipeline_state.py +176 -54
  317. package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
  318. package/skills/gsd-path-research/scripts/review_panel.py +1 -1
  319. package/skills/gsd-path-research/scripts/state_checkpoint.py +140 -31
  320. package/skills/gsd-path-research/scripts/state_promote.py +2 -0
  321. package/skills/gsd-path-research/scripts/worktree_paths.py +18 -8
  322. package/skills/gsd-path-roadmap/SKILL.md +2 -1
  323. package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
  324. package/skills/gsd-path-roadmap/scripts/_common.py +472 -19
  325. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +146 -73
  326. package/skills/gsd-path-roadmap/scripts/build_recovery.py +4 -1
  327. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +116 -20
  328. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +60 -8
  329. package/skills/gsd-path-roadmap/scripts/detect_project.py +345 -15
  330. package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
  331. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +66 -21
  332. package/skills/gsd-path-roadmap/scripts/integration.py +434 -24
  333. package/skills/gsd-path-roadmap/scripts/isolation.py +1077 -28
  334. package/skills/gsd-path-roadmap/scripts/loop_run.py +20 -30
  335. package/skills/gsd-path-roadmap/scripts/members.py +704 -0
  336. package/skills/gsd-path-roadmap/scripts/path_config.py +2 -2
  337. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +61 -22
  338. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +176 -54
  339. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
  340. package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
  341. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +140 -31
  342. package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
  343. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +18 -8
  344. package/skills/gsd-path-ship/SKILL.md +27 -10
  345. package/skills/gsd-path-ship/references/operating-rules.md +214 -0
  346. package/skills/gsd-path-ship/references/reviewer-wave.md +3 -1
  347. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  348. package/skills/gsd-path-ship/scripts/_common.py +472 -19
  349. package/skills/gsd-path-ship/scripts/archive_milestone.py +146 -73
  350. package/skills/gsd-path-ship/scripts/build_recovery.py +4 -1
  351. package/skills/gsd-path-ship/scripts/build_state.py +66 -23
  352. package/skills/gsd-path-ship/scripts/check_handoffs.py +116 -20
  353. package/skills/gsd-path-ship/scripts/check_task_briefs.py +60 -8
  354. package/skills/gsd-path-ship/scripts/detect_project.py +345 -15
  355. package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
  356. package/skills/gsd-path-ship/scripts/discussion_validate.py +66 -21
  357. package/skills/gsd-path-ship/scripts/integration.py +434 -24
  358. package/skills/gsd-path-ship/scripts/isolation.py +1077 -28
  359. package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
  360. package/skills/gsd-path-ship/scripts/loop_run.py +20 -30
  361. package/skills/gsd-path-ship/scripts/members.py +704 -0
  362. package/skills/gsd-path-ship/scripts/path_config.py +2 -2
  363. package/skills/gsd-path-ship/scripts/pipeline_git.py +61 -22
  364. package/skills/gsd-path-ship/scripts/pipeline_state.py +176 -54
  365. package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
  366. package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
  367. package/skills/gsd-path-ship/scripts/state_checkpoint.py +140 -31
  368. package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
  369. package/skills/gsd-path-ship/scripts/workflow_run.py +103 -29
  370. package/skills/gsd-path-ship/scripts/worktree_paths.py +18 -8
  371. package/skills/gsd-path-ship/templates/final-review.md +5 -2
  372. package/skills/gsd-path-ship/templates/gap-review.md +2 -1
  373. package/skills/gsd-path-ship/templates/wave-review.md +3 -1
  374. package/skills/gsd-path-undo/SKILL.md +2 -1
  375. package/skills/gsd-path-undo/references/operating-rules.md +214 -0
  376. package/skills/gsd-path-undo/scripts/_common.py +472 -19
  377. package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
  378. package/skills/gsd-path-undo/scripts/loop_run.py +20 -30
  379. package/skills/gsd-path-undo/scripts/path_config.py +2 -2
  380. package/skills/gsd-path-undo/scripts/pipeline_undo.py +117 -22
  381. package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
  382. package/skills/gsd-path-undo/scripts/worktree_paths.py +18 -8
  383. package/skills/path/BUILD.md +7 -1
  384. package/skills/path/DECIDE.md +11 -4
  385. package/skills/path/DEFINE.md +8 -1
  386. package/skills/path/DOCS-AUDIT.md +6 -4
  387. package/skills/path/INSPECT.md +5 -4
  388. package/skills/path/PLAN.md +7 -1
  389. package/skills/path/RESEARCH.md +2 -1
  390. package/skills/path/ROADMAP.md +2 -1
  391. package/skills/path/SHIP.md +27 -10
  392. package/skills/path/SKILL.md +45 -1
  393. package/skills/path/references/build-native.md +47 -14
  394. package/skills/path/references/codebase-mapper.md +2 -1
  395. package/skills/path/references/docs-auditor.md +2 -1
  396. package/skills/path/references/operating-rules.md +214 -0
  397. package/skills/path/references/planner.md +8 -0
  398. package/skills/path/references/reviewer-wave.md +3 -1
  399. package/skills/path/scripts/_common.py +472 -19
  400. package/skills/path/scripts/archive_milestone.py +146 -73
  401. package/skills/path/scripts/bootstrap_repository.py +8 -5
  402. package/skills/path/scripts/build_recovery.py +4 -1
  403. package/skills/path/scripts/build_state.py +66 -23
  404. package/skills/path/scripts/check_docs_audit.py +14 -3
  405. package/skills/path/scripts/check_handoffs.py +116 -20
  406. package/skills/path/scripts/check_task_briefs.py +60 -8
  407. package/skills/path/scripts/check_update.py +2 -2
  408. package/skills/path/scripts/detect_project.py +345 -15
  409. package/skills/path/scripts/discussion_records.py +19 -13
  410. package/skills/path/scripts/discussion_validate.py +66 -21
  411. package/skills/path/scripts/dispatch_driver.py +111 -26
  412. package/skills/path/scripts/git_guard.py +116 -29
  413. package/skills/path/scripts/integration.py +434 -24
  414. package/skills/path/scripts/isolation.py +1077 -28
  415. package/skills/path/scripts/lean_verification.py +99 -19
  416. package/skills/path/scripts/loop_run.py +20 -30
  417. package/skills/path/scripts/members.py +704 -0
  418. package/skills/path/scripts/path_config.py +2 -2
  419. package/skills/path/scripts/pipeline_git.py +61 -22
  420. package/skills/path/scripts/pipeline_state.py +176 -54
  421. package/skills/path/scripts/promote_lookahead.py +1 -1
  422. package/skills/path/scripts/review_panel.py +1 -1
  423. package/skills/path/scripts/state_checkpoint.py +140 -31
  424. package/skills/path/scripts/state_promote.py +2 -0
  425. package/skills/path/scripts/status_runtime.py +36 -6
  426. package/skills/path/scripts/token_budget.py +12 -8
  427. package/skills/path/scripts/workflow_run.py +103 -29
  428. package/skills/path/scripts/worktree_paths.py +18 -8
  429. package/skills/path/templates/codebase.md +1 -1
  430. package/skills/path/templates/docs-audit.md +1 -1
  431. package/skills/path/templates/final-review.md +5 -2
  432. package/skills/path/templates/gap-review.md +2 -1
  433. package/skills/path/templates/task.md +1 -0
  434. package/skills/path/templates/wave-review.md +3 -1
@@ -13,12 +13,16 @@ sys.dont_write_bytecode = True
13
13
  from pathlib import Path, PurePosixPath
14
14
  from typing import Dict, List, Optional, Sequence, Set, Tuple
15
15
 
16
- try:
17
- from pipeline_state import PipelineStateError, load_state
18
- import _common
19
- except ImportError: # pragma: no cover - package imports used by tests
20
- from scripts.pipeline_state import PipelineStateError, load_state
21
- from scripts import _common
16
+ if __package__:
17
+ from .pipeline_state import PipelineStateError, load_state
18
+ from . import _common
19
+ else:
20
+ try:
21
+ from pipeline_state import PipelineStateError, load_state
22
+ import _common
23
+ except ImportError: # pragma: no cover - package imports used by tests
24
+ from scripts.pipeline_state import PipelineStateError, load_state
25
+ from scripts import _common
22
26
 
23
27
 
24
28
  PIPELINE = "gsd-path/v2"
@@ -377,7 +381,10 @@ def validate_research(
377
381
  ) -> Dict[str, object]:
378
382
  """Validate the research-to-synthesis hand-off and return its summary."""
379
383
 
380
- _require_state(root, "research", "active", project_dir)
384
+ # Research runs this before closing; decide runs it after its entry transition.
385
+ _require_state_one_of(
386
+ root, {("research", "active"), ("decide", "active")}, project_dir
387
+ )
381
388
  return validate_research_artifacts(root, project_dir)
382
389
 
383
390
 
@@ -548,6 +555,45 @@ def _reviewed_head(text: str, source: str) -> str:
548
555
  return value
549
556
 
550
557
 
558
+ MEMBER_HEAD_PATTERN = re.compile(r"(?m)^Member reviewed HEAD:(.*)$")
559
+
560
+
561
+ def member_reviewed_heads(text: str, source: str) -> Dict[str, str]:
562
+ """`Member reviewed HEAD: <member> <sha>` lines, one per member of a multi-repo milestone."""
563
+ heads: Dict[str, str] = {}
564
+ # Only the header binds heads; command output quoted in later sections cannot add one.
565
+ for value in MEMBER_HEAD_PATTERN.findall(_strip_comments(text).split("\n## ", 1)[0]):
566
+ name, _, sha = value.strip().partition(" ")
567
+ if not name or not SHA_PATTERN.fullmatch(sha.strip()):
568
+ raise HandoffError(
569
+ f"{source} Member reviewed HEAD must be '<member> <full commit SHA>', not {value.strip() or 'empty'}"
570
+ )
571
+ if name in heads:
572
+ raise HandoffError(f"{source} repeats Member reviewed HEAD for {name}")
573
+ heads[name] = sha.strip()
574
+ return heads
575
+
576
+
577
+ def _member_tips(root: Path) -> Dict[str, str]:
578
+ """Each locked member's bound branch tip; empty when the milestone locks no member."""
579
+ try:
580
+ import isolation
581
+ except ImportError: # pragma: no cover - package import used by tests
582
+ from scripts import isolation
583
+ lock = root / ".project/build/members.json"
584
+ if not lock.is_file():
585
+ return {}
586
+ tips: Dict[str, str] = {}
587
+ try:
588
+ for item in json.loads(lock.read_text(encoding="utf-8"))["members"]:
589
+ checkout, _, entry = isolation._member_context(root, item["name"])
590
+ tips[entry["name"]] = isolation.git_output(
591
+ checkout, "rev-parse", "--verify", f"refs/heads/{entry['branch']}^{{commit}}")
592
+ except isolation.IsolationError as error:
593
+ raise HandoffError(str(error)) from error
594
+ return tips
595
+
596
+
551
597
  def _strip_comments(text: str) -> str:
552
598
  return COMMENT_PATTERN.sub("", text)
553
599
 
@@ -750,6 +796,8 @@ def _task_texts(root: Path, project_dir: str) -> Dict[str, str]:
750
796
  raise HandoffError(f"missing {project_dir}/tasks")
751
797
  tasks: Dict[str, str] = {}
752
798
  for path in sorted(tasks_dir.iterdir()):
799
+ if _common.is_ignored_junk(path):
800
+ continue
753
801
  named = TASK_FILE_NAME.fullmatch(path.name)
754
802
  if named is None or not path.is_file() or path.is_symlink():
755
803
  raise HandoffError(f"non-canonical task artifact: {path.name}")
@@ -1008,7 +1056,10 @@ def _validate_task_graph(
1008
1056
  graph: Dict[str, List[str]] = {}
1009
1057
  task_waves: Dict[str, int] = {}
1010
1058
  task_files: Dict[str, List[str]] = {}
1059
+ # `repo:` names the member a task changes; absent means the coordinator.
1060
+ task_repos: Dict[str, str] = {}
1011
1061
  for task_id, text in tasks.items():
1062
+ task_repos[task_id] = str(_strict_frontmatter(text, task_id).get("repo") or "")
1012
1063
  # Patch mode reopens planning beside landed tasks; only new tasks start clean.
1013
1064
  landed = _task_scalar(text, task_id, "status") == "done"
1014
1065
  _require_task_structure(task_id, text, initial=initial and not landed)
@@ -1028,7 +1079,7 @@ def _validate_task_graph(
1028
1079
 
1029
1080
  for index, left in enumerate(task_ids):
1030
1081
  for right in task_ids[index + 1 :]:
1031
- if task_waves[left] != task_waves[right]:
1082
+ if task_waves[left] != task_waves[right] or task_repos[left] != task_repos[right]:
1032
1083
  continue
1033
1084
  overlap = sorted(set(task_files[left]) & set(task_files[right]))
1034
1085
  if overlap:
@@ -1039,6 +1090,7 @@ def _validate_task_graph(
1039
1090
  visiting: Set[str] = set()
1040
1091
  visited: Set[str] = set()
1041
1092
  dependency_files: Dict[str, Set[str]] = {}
1093
+ dependency_pairs: Dict[str, Set[Tuple[str, str]]] = {}
1042
1094
 
1043
1095
  def visit(task_id: str) -> None:
1044
1096
  if task_id in visiting:
@@ -1046,11 +1098,15 @@ def _validate_task_graph(
1046
1098
  if task_id in visited:
1047
1099
  return
1048
1100
  visiting.add(task_id)
1049
- dependency_files[task_id] = set()
1101
+ dependency_pairs[task_id] = set()
1050
1102
  for dependency in graph[task_id]:
1051
1103
  visit(dependency)
1052
- dependency_files[task_id].update(task_files[dependency])
1053
- dependency_files[task_id].update(dependency_files[dependency])
1104
+ dependency_pairs[task_id].update((task_repos[dependency], name) for name in task_files[dependency])
1105
+ dependency_pairs[task_id].update(dependency_pairs[dependency])
1106
+ # A dependency's files are supplied only within the same repo.
1107
+ dependency_files[task_id] = {
1108
+ name for repo, name in dependency_pairs[task_id] if repo == task_repos[task_id]
1109
+ }
1054
1110
  visiting.remove(task_id)
1055
1111
  visited.add(task_id)
1056
1112
 
@@ -1212,7 +1268,7 @@ def _head_file(root: Path, relative: str) -> Optional[str]:
1212
1268
  ["git", "show", f"HEAD:{relative}"],
1213
1269
  cwd=root,
1214
1270
  capture_output=True,
1215
- text=True,
1271
+ encoding="utf-8", errors="replace",
1216
1272
  check=False,
1217
1273
  )
1218
1274
  return result.stdout if result.returncode == 0 else None
@@ -1313,6 +1369,21 @@ def validate_roadmap(
1313
1369
  return {"phase": "roadmap", "milestones": ids, "dependencies": dependencies}
1314
1370
 
1315
1371
 
1372
+ def _integrated_members(root: Path, state: Dict[str, str]) -> Set[str]:
1373
+ """Members this milestone already closed; a new task for one would need a second merge."""
1374
+ match = re.fullmatch(r"gsd-path/M(\d{3,})", state.get("branch") or "")
1375
+ common = subprocess.run(["git", "-C", str(root), "rev-parse", "--path-format=absolute", "--git-common-dir"],
1376
+ capture_output=True, text=True, check=False)
1377
+ if match is None or common.returncode != 0:
1378
+ return set()
1379
+ if __package__:
1380
+ from . import members
1381
+ else:
1382
+ import members
1383
+ rows = members.milestone_member_close(Path(common.stdout.strip()), int(match.group(1)))
1384
+ return {row["name"] for row in rows if row.get("status") == "integrated"}
1385
+
1386
+
1316
1387
  def validate_plan(
1317
1388
  root: Path, project_dir: str = DEFAULT_PROJECT_DIR
1318
1389
  ) -> Dict[str, object]:
@@ -1332,6 +1403,14 @@ def validate_plan(
1332
1403
  # Build readiness owns task lifecycle validation; coverage must survive landings.
1333
1404
  state = _require_pipeline(root, project_dir)
1334
1405
  _validate_task_graph(wave_depths, tasks, initial=state["phase"] == "plan")
1406
+ integrated = _integrated_members(root, state)
1407
+ for task_id, text in tasks.items():
1408
+ repo = str(_strict_frontmatter(text, task_id).get("repo") or "")
1409
+ if repo in integrated and _task_scalar(text, task_id, "status") != "done":
1410
+ raise HandoffError(
1411
+ f"{task_id} changes member {repo}, which is already integrated in this milestone; "
1412
+ "plan that change in a new milestone"
1413
+ )
1335
1414
  assigned: Dict[str, Set[str]] = {task_id: set() for task_id in tasks}
1336
1415
  covered = set()
1337
1416
  for criterion, task_id, acceptance in rows:
@@ -1591,6 +1670,19 @@ def validate_wave_evidence(
1591
1670
  }
1592
1671
 
1593
1672
 
1673
+ def criterion_surfaces(root: Path, project_dir: str = DEFAULT_PROJECT_DIR) -> Dict[str, str]:
1674
+ """Map each surface-owned INTENT SC to the surface PLAN.md's contract lists it under."""
1675
+
1676
+ surface_of: Dict[str, str] = {}
1677
+ surfaces = _surfaces(_read(root, _intent_path(project_dir)), "INTENT.md")
1678
+ if surfaces:
1679
+ plan = _read(root, f"{project_dir}/plan/PLAN.md")
1680
+ for surface, (_task, owned) in _surface_contract(plan, surfaces).items():
1681
+ for criterion in owned:
1682
+ surface_of[criterion] = surface
1683
+ return surface_of
1684
+
1685
+
1594
1686
  def validate_final(
1595
1687
  root: Path, project_dir: str = DEFAULT_PROJECT_DIR, *, final_text: Optional[str] = None
1596
1688
  ) -> Dict[str, object]:
@@ -1599,20 +1691,14 @@ def validate_final(
1599
1691
  _require_state(root, "ship", "active", project_dir)
1600
1692
  intent = _read(root, _intent_path(project_dir))
1601
1693
  criteria = _success_criteria(intent)
1602
- surface_of: Dict[str, str] = {}
1603
- surfaces = _surfaces(intent, "INTENT.md")
1604
- if surfaces:
1605
- plan = _read(root, f"{project_dir}/plan/PLAN.md")
1606
- for surface, (_task, owned) in _surface_contract(plan, surfaces).items():
1607
- for criterion in owned:
1608
- surface_of[criterion] = surface
1694
+ surface_of = criterion_surfaces(root, project_dir)
1609
1695
  relative = f"{project_dir}/review/FINAL.md"
1610
1696
  text = _read(root, relative) if final_text is None else final_text
1611
1697
  reviewed_head = _reviewed_head(text, relative)
1612
1698
  head_result = subprocess.run(
1613
1699
  ("git", "-C", str(root), "rev-parse", "--verify", "HEAD"),
1614
1700
  capture_output=True,
1615
- text=True,
1701
+ encoding="utf-8", errors="replace",
1616
1702
  check=False,
1617
1703
  )
1618
1704
  if head_result.returncode != 0:
@@ -1623,6 +1709,14 @@ def validate_final(
1623
1709
  f"FINAL.md Reviewed HEAD {reviewed_head} is not the current HEAD "
1624
1710
  f"{current_head}; re-run the final review on the current commit"
1625
1711
  )
1712
+ member_heads = member_reviewed_heads(text, relative)
1713
+ tips = _member_tips(root)
1714
+ if member_heads != tips:
1715
+ expected = ", ".join(f"{name} {tip}" for name, tip in tips.items()) or "none"
1716
+ raise HandoffError(
1717
+ f"FINAL.md Member reviewed HEAD lines must name each locked member head ({expected}); "
1718
+ "re-run the final review on the current member heads"
1719
+ )
1626
1720
  overall = _line_value(text, "Overall verdict:")
1627
1721
  if overall not in {"pass", "blocked"}:
1628
1722
  raise HandoffError("FINAL.md Overall verdict is invalid")
@@ -1697,6 +1791,8 @@ def validate_final(
1697
1791
  raise HandoffError(f"{gap_relative} heading does not match its file number")
1698
1792
  if _reviewed_head(gap, gap_relative) != reviewed_head:
1699
1793
  raise HandoffError(f"{gap_relative} Reviewed HEAD differs from FINAL.md")
1794
+ if member_reviewed_heads(gap, gap_relative) != member_heads:
1795
+ raise HandoffError(f"{gap_relative} Member reviewed HEAD lines differ from FINAL.md")
1700
1796
  verdict = _line_value(gap, "Gap verdict:")
1701
1797
  if verdict not in {"pass", "blocked"}:
1702
1798
  raise HandoffError(f"{gap_relative} Gap verdict is invalid")
@@ -12,10 +12,13 @@ sys.dont_write_bytecode = True
12
12
  from pathlib import Path, PurePosixPath
13
13
  from typing import Dict, List, Optional, Sequence, Set, Tuple
14
14
 
15
- try:
16
- import _common
17
- except ImportError: # pragma: no cover - package import used by tests
18
- from scripts import _common
15
+ if __package__:
16
+ from . import _common
17
+ else:
18
+ try:
19
+ import _common
20
+ except ImportError: # pragma: no cover - package import used by tests
21
+ from scripts import _common
19
22
 
20
23
 
21
24
  DEFAULT_TASKS_DIR = ".project/tasks"
@@ -183,10 +186,47 @@ def _verify_tokens(block: str) -> List[str]:
183
186
  return tokens
184
187
 
185
188
 
189
+ def _member_bases(root: Path):
190
+ """Resolve a member name to (checkout, origin/main SHA), reading MEMBERS.md once."""
191
+ cache: Dict[str, Optional[Tuple[Path, str]]] = {}
192
+
193
+ def locate(name: str) -> Optional[Tuple[Path, str]]:
194
+ if name not in cache:
195
+ if __package__:
196
+ from . import members
197
+ else:
198
+ try:
199
+ import members
200
+ except ImportError: # pragma: no cover - package import used by tests
201
+ from scripts import members
202
+ try:
203
+ listed = {member["name"]: Path(member["checkout"]) for member in members.read_members(root)}
204
+ checkout = listed.get(name)
205
+ if checkout is None:
206
+ cache[name] = None
207
+ else:
208
+ # A build lock selects the bound tip, including during Plan recovery;
209
+ # without the lock, use origin/main before the first build start.
210
+ lock = root / members.LOCK_PATH
211
+ locked = [entry for entry in (json.loads(lock.read_text(encoding="utf-8"))["members"]
212
+ if lock.is_file() else []) if entry["name"] == name]
213
+ if locked:
214
+ cache[name] = (checkout, _resolve_base(checkout, f"refs/heads/{locked[0]['branch']}"))
215
+ else:
216
+ members.require_origin_main(checkout)
217
+ cache[name] = (checkout, _resolve_base(checkout, "refs/remotes/origin/main"))
218
+ except (members.MembersError, BriefError) as error:
219
+ raise BriefError(f"member {name} is unavailable: {error}") from error
220
+ return cache[name]
221
+
222
+ return locate
223
+
224
+
186
225
  def _lint_task(
187
226
  repo: Path, base: str, path: Path,
188
227
  dependency_files: Optional[Dict[str, Set[str]]] = None,
189
228
  landed_bases: Optional[Dict[str, str]] = None,
229
+ member_base=None,
190
230
  ) -> Tuple[str, List[str], Optional[str], int]:
191
231
  text = path.read_text(encoding="utf-8")
192
232
  fields, error = _frontmatter(text)
@@ -198,6 +238,15 @@ def _lint_task(
198
238
  checked = 0
199
239
  if fields is None:
200
240
  return task_id, [error or "invalid frontmatter"], None, checked
241
+ # A member task's paths resolve in the member at its origin/main, where the
242
+ # member bound branch starts; `repo:` absent means the coordinator.
243
+ git_root = repo
244
+ member = fields.get("repo")
245
+ if member is not None:
246
+ located = member_base(member) if isinstance(member, str) and member and member_base else None
247
+ if located is None:
248
+ return task_id, [f"repo: names no member in MEMBERS.md: {member}"], None, checked
249
+ git_root, base = located[0], (landed_bases or {}).get(task_id, located[1])
201
250
 
202
251
  for field in REQUIRED_FIELDS:
203
252
  if field not in fields:
@@ -237,7 +286,7 @@ def _lint_task(
237
286
  for parent in candidate.parents:
238
287
  if str(parent) == ".":
239
288
  break
240
- kind = _run_git(repo, "cat-file", "-t", f"{base}:{parent}")
289
+ kind = _run_git(git_root, "cat-file", "-t", f"{base}:{parent}")
241
290
  if kind.returncode == 0 and kind.stdout.strip() != "tree":
242
291
  problems.append(
243
292
  f"files entry {normalized} has non-directory ancestor "
@@ -255,7 +304,7 @@ def _lint_task(
255
304
  token not in declared
256
305
  and token not in supplied
257
306
  and not _supplied_contract(repo, path, token)
258
- and not _base_exists(repo, base, token)
307
+ and not _base_exists(git_root, base, token)
259
308
  ):
260
309
  problems.append(f"## {name} names a path missing at the layer base: {token}")
261
310
 
@@ -267,7 +316,7 @@ def _lint_task(
267
316
  else:
268
317
  for token in _verify_tokens(block.group("block")):
269
318
  checked += 1
270
- if token not in declared and token not in supplied and not _base_exists(repo, base, token):
319
+ if token not in declared and token not in supplied and not _base_exists(git_root, base, token):
271
320
  problems.append(f"## Verify names a path missing at the layer base: {token}")
272
321
 
273
322
  contract = None
@@ -284,6 +333,7 @@ def validate_task_briefs(
284
333
  root: Path, base: str, tasks_dir: str = DEFAULT_TASKS_DIR,
285
334
  *, dependency_files: Optional[Dict[str, Set[str]]] = None,
286
335
  landed_bases: Optional[Dict[str, str]] = None,
336
+ member_base=None,
287
337
  ) -> Dict[str, object]:
288
338
  """Lint task briefs using the caller's validation bases.
289
339
 
@@ -305,9 +355,11 @@ def validate_task_briefs(
305
355
  task_files = sorted(tasks_path.glob("*.md"))
306
356
  if not task_files:
307
357
  raise BriefError(f"no task briefs found in {tasks_dir}")
358
+ if member_base is None:
359
+ member_base = _member_bases(root)
308
360
  for path in task_files:
309
361
  task_id, task_problems, contract, task_checked = _lint_task(
310
- root, resolved_base, path, dependency_files, landed_bases
362
+ root, resolved_base, path, dependency_files, landed_bases, member_base
311
363
  )
312
364
  problems.extend(f"{task_id}: {problem}" for problem in task_problems)
313
365
  if contract is not None:
@@ -37,8 +37,8 @@ def is_newer(latest, installed):
37
37
  def _write_cache(now, latest):
38
38
  try:
39
39
  CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
40
- CACHE_PATH.write_text(
41
- json.dumps({"checked_at": now, "latest": latest}), encoding="utf-8"
40
+ CACHE_PATH.write_bytes(
41
+ json.dumps({"checked_at": now, "latest": latest}).encode("utf-8")
42
42
  )
43
43
  except OSError:
44
44
  pass
@@ -12,6 +12,9 @@ def main():
12
12
  parser = argparse.ArgumentParser(description=__doc__)
13
13
  parser.add_argument("--repo", required=True, action="append", type=Path)
14
14
  parser.add_argument("--command", required=True)
15
+ # Windows wrappers record bash: the host ran the original through Git Bash,
16
+ # and shell=True would run it through cmd.exe instead.
17
+ parser.add_argument("--shell", choices=("bash",))
15
18
  args = parser.parse_args()
16
19
  payload = sys.stdin.buffer.read()
17
20
  try:
@@ -26,7 +29,13 @@ def main():
26
29
  except (OSError, ValueError, RuntimeError):
27
30
  # An uncertain event keeps the original hook's protection and errors.
28
31
  pass
29
- result = subprocess.run(args.command, shell=True, input=payload)
32
+ if args.shell == "bash":
33
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
34
+ import _common
35
+
36
+ result = subprocess.run(_common.bash_argv(args.command), input=payload)
37
+ else:
38
+ result = subprocess.run(args.command, shell=True, input=payload)
30
39
  return result.returncode if result.returncode >= 0 else 128 - result.returncode
31
40
 
32
41
 
@@ -8,7 +8,6 @@ import os
8
8
  from pathlib import Path
9
9
  import shlex
10
10
  import stat
11
- import subprocess
12
11
  import sys
13
12
  import tempfile
14
13
 
@@ -47,8 +46,15 @@ def plan(args):
47
46
  gate = Path(__file__).resolve().with_name("core_hook_gate.py")
48
47
  if not gate.is_file():
49
48
  raise ValueError(f"missing installed hook gate: {gate}")
50
- argv = [sys.executable, "-B", str(gate), "--repo", str(repo), "--command", original]
51
- replacement = subprocess.list2cmdline(argv) if os.name == "nt" else shlex.join(argv)
49
+ if os.name == "nt":
50
+ # Claude Code runs Windows hook commands in Git Bash: quote for bash,
51
+ # use forward slashes, and have the gate run the original through bash
52
+ # rather than cmd.exe.
53
+ argv = [Path(sys.executable).as_posix(), "-B", gate.as_posix(), "--repo", repo.as_posix(),
54
+ "--shell", "bash", "--command", original]
55
+ else:
56
+ argv = [sys.executable, "-B", str(gate), "--repo", str(repo), "--command", original]
57
+ replacement = shlex.join(argv)
52
58
  entry["command"] = replacement
53
59
  after = (json.dumps(data, indent=2) + "\n").encode()
54
60
  receipt = {
@@ -60,7 +66,8 @@ def plan(args):
60
66
  "after": base64.b64encode(after).decode(),
61
67
  }
62
68
  # Settings may contain secrets; the review/rollback copy is owner-only.
63
- with open(args.receipt, "x", encoding="utf-8", opener=lambda path, flags: os.open(path, flags, 0o600)) as handle:
69
+ with open(args.receipt, "x", encoding="utf-8", newline="\n",
70
+ opener=lambda path, flags: os.open(path, flags, 0o600)) as handle:
64
71
  json.dump(receipt, handle, indent=2)
65
72
  handle.write("\n")
66
73
  return {"status": "planned", "receipt": str(args.receipt.absolute())}