@opengsd/gsd-path 1.3.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/AGENTS.md +2 -207
  2. package/CHANGELOG.md +331 -69
  3. package/DOCS.md +25 -9
  4. package/FULL.md +12 -5
  5. package/GUIDE.md +1 -1
  6. package/HOOKS.md +104 -14
  7. package/QUICK.md +5 -2
  8. package/README.md +131 -111
  9. package/RUNTIME.md +2 -2
  10. package/UPDATE.md +61 -11
  11. package/WORKFLOW.md +44 -7
  12. package/package.json +13 -9
  13. package/platforms/shared-agents/dispatch.md +13 -0
  14. package/scripts/_common.py +619 -18
  15. package/scripts/archive_milestone.py +160 -86
  16. package/scripts/bootstrap_repository.py +8 -5
  17. package/scripts/build_recovery.py +100 -32
  18. package/scripts/build_state.py +143 -24
  19. package/scripts/check_docs_audit.py +91 -7
  20. package/scripts/check_handoffs.py +662 -50
  21. package/scripts/check_task_briefs.py +338 -30
  22. package/scripts/check_update.py +2 -2
  23. package/scripts/core_hook_gate.py +10 -1
  24. package/scripts/core_hook_settings.py +11 -4
  25. package/scripts/detect_project.py +587 -32
  26. package/scripts/discussion_records.py +19 -13
  27. package/scripts/discussion_validate.py +152 -50
  28. package/scripts/dispatch_driver.py +501 -46
  29. package/scripts/git_guard.py +118 -31
  30. package/scripts/guard_hook.py +1567 -194
  31. package/scripts/install.mjs +77 -46
  32. package/scripts/install.py +611 -50
  33. package/scripts/integration.py +580 -25
  34. package/scripts/isolation.py +1438 -57
  35. package/scripts/lean_verification.py +99 -19
  36. package/scripts/loop_run.py +31 -29
  37. package/scripts/members.py +706 -0
  38. package/scripts/migrate_core.py +2 -2
  39. package/scripts/path_config.py +5 -5
  40. package/scripts/pipeline_diagnose.py +82 -31
  41. package/scripts/pipeline_git.py +124 -34
  42. package/scripts/pipeline_state.py +287 -87
  43. package/scripts/pipeline_undo.py +218 -30
  44. package/scripts/promote_lookahead.py +1 -1
  45. package/scripts/review_panel.py +1 -1
  46. package/scripts/runtime_store.py +55 -11
  47. package/scripts/skill-resources.json +37 -5
  48. package/scripts/state_checkpoint.py +135 -44
  49. package/scripts/state_promote.py +2 -0
  50. package/scripts/status_runtime.py +58 -10
  51. package/scripts/sync_skill_resources.py +8 -7
  52. package/scripts/task_context.py +12 -3
  53. package/scripts/token_budget.py +12 -8
  54. package/scripts/wizard.mjs +1 -0
  55. package/scripts/workflow_run.py +107 -30
  56. package/scripts/worktree_paths.py +72 -10
  57. package/skills/gsd-path/BUILD.md +7 -1
  58. package/skills/gsd-path/DECIDE.md +11 -4
  59. package/skills/gsd-path/DEFINE.md +36 -4
  60. package/skills/gsd-path/DOCS-AUDIT.md +6 -4
  61. package/skills/gsd-path/INSPECT.md +5 -4
  62. package/skills/gsd-path/PLAN.md +18 -4
  63. package/skills/gsd-path/RESEARCH.md +13 -1
  64. package/skills/gsd-path/ROADMAP.md +2 -1
  65. package/skills/gsd-path/SHIP.md +33 -11
  66. package/skills/gsd-path/SKILL.md +45 -1
  67. package/skills/gsd-path/references/build-native.md +63 -15
  68. package/skills/gsd-path/references/codebase-mapper.md +2 -1
  69. package/skills/gsd-path/references/dispatch.md +13 -0
  70. package/skills/gsd-path/references/docs-auditor.md +7 -1
  71. package/skills/gsd-path/references/operating-rules.md +214 -0
  72. package/skills/gsd-path/references/planner.md +22 -4
  73. package/skills/gsd-path/references/reviewer-final.md +20 -2
  74. package/skills/gsd-path/references/reviewer-wave.md +18 -5
  75. package/skills/gsd-path/references/reviewer.md +3 -1
  76. package/skills/gsd-path/references/spec-probes.md +150 -0
  77. package/skills/gsd-path/scripts/_common.py +619 -18
  78. package/skills/gsd-path/scripts/archive_milestone.py +160 -86
  79. package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
  80. package/skills/gsd-path/scripts/build_recovery.py +100 -32
  81. package/skills/gsd-path/scripts/build_state.py +143 -24
  82. package/skills/gsd-path/scripts/check_docs_audit.py +91 -7
  83. package/skills/gsd-path/scripts/check_handoffs.py +662 -50
  84. package/skills/gsd-path/scripts/check_task_briefs.py +338 -30
  85. package/skills/gsd-path/scripts/check_update.py +2 -2
  86. package/skills/gsd-path/scripts/detect_project.py +587 -32
  87. package/skills/gsd-path/scripts/discussion_records.py +19 -13
  88. package/skills/gsd-path/scripts/discussion_validate.py +152 -50
  89. package/skills/gsd-path/scripts/dispatch_driver.py +501 -46
  90. package/skills/gsd-path/scripts/git_guard.py +118 -31
  91. package/skills/gsd-path/scripts/integration.py +580 -25
  92. package/skills/gsd-path/scripts/isolation.py +1438 -57
  93. package/skills/gsd-path/scripts/lean_verification.py +99 -19
  94. package/skills/gsd-path/scripts/loop_run.py +31 -29
  95. package/skills/gsd-path/scripts/members.py +706 -0
  96. package/skills/gsd-path/scripts/path_config.py +5 -5
  97. package/skills/gsd-path/scripts/pipeline_git.py +124 -34
  98. package/skills/gsd-path/scripts/pipeline_state.py +287 -87
  99. package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
  100. package/skills/gsd-path/scripts/review_panel.py +1 -1
  101. package/skills/gsd-path/scripts/state_checkpoint.py +135 -44
  102. package/skills/gsd-path/scripts/state_promote.py +2 -0
  103. package/skills/gsd-path/scripts/status_runtime.py +58 -10
  104. package/skills/gsd-path/scripts/task_context.py +12 -3
  105. package/skills/gsd-path/scripts/token_budget.py +12 -8
  106. package/skills/gsd-path/scripts/workflow_run.py +107 -30
  107. package/skills/gsd-path/scripts/worktree_paths.py +72 -10
  108. package/skills/gsd-path/templates/codebase.md +1 -1
  109. package/skills/gsd-path/templates/docs-audit.md +6 -2
  110. package/skills/gsd-path/templates/final-review.md +16 -4
  111. package/skills/gsd-path/templates/gap-review.md +2 -1
  112. package/skills/gsd-path/templates/intent.md +30 -0
  113. package/skills/gsd-path/templates/patch-findings.md +3 -3
  114. package/skills/gsd-path/templates/plan.md +17 -2
  115. package/skills/gsd-path/templates/research-handoff.md +14 -3
  116. package/skills/gsd-path/templates/state.md +1 -1
  117. package/skills/gsd-path/templates/task.md +1 -0
  118. package/skills/gsd-path/templates/wave-review.md +16 -4
  119. package/skills/gsd-path-build/SKILL.md +7 -1
  120. package/skills/gsd-path-build/references/build-native.md +63 -15
  121. package/skills/gsd-path-build/references/dispatch.md +13 -0
  122. package/skills/gsd-path-build/references/operating-rules.md +214 -0
  123. package/skills/gsd-path-build/references/reviewer-final.md +20 -2
  124. package/skills/gsd-path-build/references/reviewer-wave.md +18 -5
  125. package/skills/gsd-path-build/references/reviewer.md +3 -1
  126. package/skills/gsd-path-build/references/spec-probes.md +150 -0
  127. package/skills/gsd-path-build/scripts/_common.py +619 -18
  128. package/skills/gsd-path-build/scripts/archive_milestone.py +160 -86
  129. package/skills/gsd-path-build/scripts/build_recovery.py +100 -32
  130. package/skills/gsd-path-build/scripts/build_state.py +143 -24
  131. package/skills/gsd-path-build/scripts/check_handoffs.py +662 -50
  132. package/skills/gsd-path-build/scripts/check_task_briefs.py +338 -30
  133. package/skills/gsd-path-build/scripts/detect_project.py +587 -32
  134. package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
  135. package/skills/gsd-path-build/scripts/discussion_validate.py +152 -50
  136. package/skills/gsd-path-build/scripts/dispatch_driver.py +501 -46
  137. package/skills/gsd-path-build/scripts/integration.py +580 -25
  138. package/skills/gsd-path-build/scripts/isolation.py +1438 -57
  139. package/skills/gsd-path-build/scripts/loop_run.py +31 -29
  140. package/skills/gsd-path-build/scripts/members.py +706 -0
  141. package/skills/gsd-path-build/scripts/path_config.py +5 -5
  142. package/skills/gsd-path-build/scripts/pipeline_git.py +124 -34
  143. package/skills/gsd-path-build/scripts/pipeline_state.py +287 -87
  144. package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
  145. package/skills/gsd-path-build/scripts/review_panel.py +1 -1
  146. package/skills/gsd-path-build/scripts/state_checkpoint.py +135 -44
  147. package/skills/gsd-path-build/scripts/state_promote.py +2 -0
  148. package/skills/gsd-path-build/scripts/task_context.py +12 -3
  149. package/skills/gsd-path-build/scripts/token_budget.py +12 -8
  150. package/skills/gsd-path-build/scripts/workflow_run.py +107 -30
  151. package/skills/gsd-path-build/scripts/worktree_paths.py +72 -10
  152. package/skills/gsd-path-build/templates/task.md +1 -0
  153. package/skills/gsd-path-build/templates/wave-review.md +16 -4
  154. package/skills/gsd-path-decide/SKILL.md +11 -4
  155. package/skills/gsd-path-decide/references/dispatch.md +13 -0
  156. package/skills/gsd-path-decide/references/operating-rules.md +214 -0
  157. package/skills/gsd-path-decide/scripts/_common.py +619 -18
  158. package/skills/gsd-path-decide/scripts/archive_milestone.py +160 -86
  159. package/skills/gsd-path-decide/scripts/build_recovery.py +100 -32
  160. package/skills/gsd-path-decide/scripts/check_handoffs.py +662 -50
  161. package/skills/gsd-path-decide/scripts/check_task_briefs.py +338 -30
  162. package/skills/gsd-path-decide/scripts/detect_project.py +587 -32
  163. package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
  164. package/skills/gsd-path-decide/scripts/discussion_validate.py +152 -50
  165. package/skills/gsd-path-decide/scripts/integration.py +580 -25
  166. package/skills/gsd-path-decide/scripts/isolation.py +1438 -57
  167. package/skills/gsd-path-decide/scripts/loop_run.py +31 -29
  168. package/skills/gsd-path-decide/scripts/members.py +706 -0
  169. package/skills/gsd-path-decide/scripts/path_config.py +5 -5
  170. package/skills/gsd-path-decide/scripts/pipeline_git.py +124 -34
  171. package/skills/gsd-path-decide/scripts/pipeline_state.py +287 -87
  172. package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
  173. package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
  174. package/skills/gsd-path-decide/scripts/state_checkpoint.py +135 -44
  175. package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
  176. package/skills/gsd-path-decide/scripts/worktree_paths.py +72 -10
  177. package/skills/gsd-path-define/SKILL.md +36 -4
  178. package/skills/gsd-path-define/references/operating-rules.md +214 -0
  179. package/skills/gsd-path-define/references/spec-probes.md +150 -0
  180. package/skills/gsd-path-define/scripts/_common.py +619 -18
  181. package/skills/gsd-path-define/scripts/archive_milestone.py +160 -86
  182. package/skills/gsd-path-define/scripts/build_recovery.py +100 -32
  183. package/skills/gsd-path-define/scripts/check_handoffs.py +662 -50
  184. package/skills/gsd-path-define/scripts/check_task_briefs.py +338 -30
  185. package/skills/gsd-path-define/scripts/detect_project.py +587 -32
  186. package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
  187. package/skills/gsd-path-define/scripts/discussion_validate.py +152 -50
  188. package/skills/gsd-path-define/scripts/integration.py +580 -25
  189. package/skills/gsd-path-define/scripts/isolation.py +1438 -57
  190. package/skills/gsd-path-define/scripts/loop_run.py +31 -29
  191. package/skills/gsd-path-define/scripts/members.py +706 -0
  192. package/skills/gsd-path-define/scripts/path_config.py +5 -5
  193. package/skills/gsd-path-define/scripts/pipeline_git.py +124 -34
  194. package/skills/gsd-path-define/scripts/pipeline_state.py +287 -87
  195. package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
  196. package/skills/gsd-path-define/scripts/review_panel.py +1 -1
  197. package/skills/gsd-path-define/scripts/state_checkpoint.py +135 -44
  198. package/skills/gsd-path-define/scripts/state_promote.py +2 -0
  199. package/skills/gsd-path-define/scripts/worktree_paths.py +72 -10
  200. package/skills/gsd-path-define/templates/intent.md +30 -0
  201. package/skills/gsd-path-define/templates/state.md +1 -1
  202. package/skills/gsd-path-discuss/SKILL.md +2 -1
  203. package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
  204. package/skills/gsd-path-discuss/scripts/_common.py +619 -18
  205. package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
  206. package/skills/gsd-path-discuss/scripts/loop_run.py +31 -29
  207. package/skills/gsd-path-discuss/scripts/path_config.py +5 -5
  208. package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
  209. package/skills/gsd-path-discuss/scripts/worktree_paths.py +72 -10
  210. package/skills/gsd-path-docs-audit/SKILL.md +6 -4
  211. package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
  212. package/skills/gsd-path-docs-audit/references/docs-auditor.md +7 -1
  213. package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
  214. package/skills/gsd-path-docs-audit/scripts/_common.py +619 -18
  215. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +160 -86
  216. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +100 -32
  217. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +91 -7
  218. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +662 -50
  219. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +338 -30
  220. package/skills/gsd-path-docs-audit/scripts/detect_project.py +587 -32
  221. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
  222. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +152 -50
  223. package/skills/gsd-path-docs-audit/scripts/integration.py +580 -25
  224. package/skills/gsd-path-docs-audit/scripts/isolation.py +1438 -57
  225. package/skills/gsd-path-docs-audit/scripts/loop_run.py +31 -29
  226. package/skills/gsd-path-docs-audit/scripts/members.py +706 -0
  227. package/skills/gsd-path-docs-audit/scripts/path_config.py +5 -5
  228. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +124 -34
  229. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +287 -87
  230. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
  231. package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
  232. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +135 -44
  233. package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
  234. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +72 -10
  235. package/skills/gsd-path-docs-audit/templates/docs-audit.md +6 -2
  236. package/skills/gsd-path-forensics/SKILL.md +2 -1
  237. package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
  238. package/skills/gsd-path-forensics/scripts/_common.py +619 -18
  239. package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
  240. package/skills/gsd-path-forensics/scripts/loop_run.py +31 -29
  241. package/skills/gsd-path-forensics/scripts/path_config.py +5 -5
  242. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
  243. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +218 -30
  244. package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
  245. package/skills/gsd-path-forensics/scripts/worktree_paths.py +72 -10
  246. package/skills/gsd-path-inspect/SKILL.md +5 -4
  247. package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
  248. package/skills/gsd-path-inspect/references/dispatch.md +13 -0
  249. package/skills/gsd-path-inspect/references/docs-auditor.md +7 -1
  250. package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
  251. package/skills/gsd-path-inspect/scripts/_common.py +619 -18
  252. package/skills/gsd-path-inspect/scripts/archive_milestone.py +160 -86
  253. package/skills/gsd-path-inspect/scripts/build_recovery.py +100 -32
  254. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +91 -7
  255. package/skills/gsd-path-inspect/scripts/check_handoffs.py +662 -50
  256. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +338 -30
  257. package/skills/gsd-path-inspect/scripts/detect_project.py +587 -32
  258. package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
  259. package/skills/gsd-path-inspect/scripts/discussion_validate.py +152 -50
  260. package/skills/gsd-path-inspect/scripts/integration.py +580 -25
  261. package/skills/gsd-path-inspect/scripts/isolation.py +1438 -57
  262. package/skills/gsd-path-inspect/scripts/loop_run.py +31 -29
  263. package/skills/gsd-path-inspect/scripts/members.py +706 -0
  264. package/skills/gsd-path-inspect/scripts/path_config.py +5 -5
  265. package/skills/gsd-path-inspect/scripts/pipeline_git.py +124 -34
  266. package/skills/gsd-path-inspect/scripts/pipeline_state.py +287 -87
  267. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
  268. package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
  269. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +135 -44
  270. package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
  271. package/skills/gsd-path-inspect/scripts/workflow_run.py +107 -30
  272. package/skills/gsd-path-inspect/scripts/worktree_paths.py +72 -10
  273. package/skills/gsd-path-inspect/templates/codebase.md +1 -1
  274. package/skills/gsd-path-inspect/templates/docs-audit.md +6 -2
  275. package/skills/gsd-path-inspect/templates/state.md +1 -1
  276. package/skills/gsd-path-loop/SKILL.md +2 -1
  277. package/skills/gsd-path-loop/references/operating-rules.md +214 -0
  278. package/skills/gsd-path-loop/scripts/_common.py +619 -18
  279. package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
  280. package/skills/gsd-path-loop/scripts/loop_run.py +31 -29
  281. package/skills/gsd-path-loop/scripts/path_config.py +5 -5
  282. package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
  283. package/skills/gsd-path-loop/scripts/worktree_paths.py +72 -10
  284. package/skills/gsd-path-migrate/SKILL.md +2 -1
  285. package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
  286. package/skills/gsd-path-migrate/scripts/_common.py +619 -18
  287. package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
  288. package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
  289. package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
  290. package/skills/gsd-path-migrate/scripts/loop_run.py +31 -29
  291. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
  292. package/skills/gsd-path-migrate/scripts/path_config.py +5 -5
  293. package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
  294. package/skills/gsd-path-plan/SKILL.md +18 -4
  295. package/skills/gsd-path-plan/references/dispatch.md +13 -0
  296. package/skills/gsd-path-plan/references/operating-rules.md +214 -0
  297. package/skills/gsd-path-plan/references/planner.md +22 -4
  298. package/skills/gsd-path-plan/references/spec-probes.md +150 -0
  299. package/skills/gsd-path-plan/scripts/_common.py +619 -18
  300. package/skills/gsd-path-plan/scripts/archive_milestone.py +160 -86
  301. package/skills/gsd-path-plan/scripts/build_recovery.py +100 -32
  302. package/skills/gsd-path-plan/scripts/check_handoffs.py +662 -50
  303. package/skills/gsd-path-plan/scripts/check_task_briefs.py +338 -30
  304. package/skills/gsd-path-plan/scripts/detect_project.py +587 -32
  305. package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
  306. package/skills/gsd-path-plan/scripts/discussion_validate.py +152 -50
  307. package/skills/gsd-path-plan/scripts/integration.py +580 -25
  308. package/skills/gsd-path-plan/scripts/isolation.py +1438 -57
  309. package/skills/gsd-path-plan/scripts/loop_run.py +31 -29
  310. package/skills/gsd-path-plan/scripts/members.py +706 -0
  311. package/skills/gsd-path-plan/scripts/path_config.py +5 -5
  312. package/skills/gsd-path-plan/scripts/pipeline_git.py +124 -34
  313. package/skills/gsd-path-plan/scripts/pipeline_state.py +287 -87
  314. package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
  315. package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
  316. package/skills/gsd-path-plan/scripts/state_checkpoint.py +135 -44
  317. package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
  318. package/skills/gsd-path-plan/scripts/workflow_run.py +107 -30
  319. package/skills/gsd-path-plan/scripts/worktree_paths.py +72 -10
  320. package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
  321. package/skills/gsd-path-plan/templates/plan.md +17 -2
  322. package/skills/gsd-path-plan/templates/task.md +1 -0
  323. package/skills/gsd-path-research/SKILL.md +13 -1
  324. package/skills/gsd-path-research/references/dispatch.md +13 -0
  325. package/skills/gsd-path-research/references/operating-rules.md +214 -0
  326. package/skills/gsd-path-research/scripts/_common.py +619 -18
  327. package/skills/gsd-path-research/scripts/archive_milestone.py +160 -86
  328. package/skills/gsd-path-research/scripts/build_recovery.py +100 -32
  329. package/skills/gsd-path-research/scripts/check_handoffs.py +662 -50
  330. package/skills/gsd-path-research/scripts/check_task_briefs.py +338 -30
  331. package/skills/gsd-path-research/scripts/detect_project.py +587 -32
  332. package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
  333. package/skills/gsd-path-research/scripts/discussion_validate.py +152 -50
  334. package/skills/gsd-path-research/scripts/integration.py +580 -25
  335. package/skills/gsd-path-research/scripts/isolation.py +1438 -57
  336. package/skills/gsd-path-research/scripts/loop_run.py +31 -29
  337. package/skills/gsd-path-research/scripts/members.py +706 -0
  338. package/skills/gsd-path-research/scripts/path_config.py +5 -5
  339. package/skills/gsd-path-research/scripts/pipeline_git.py +124 -34
  340. package/skills/gsd-path-research/scripts/pipeline_state.py +287 -87
  341. package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
  342. package/skills/gsd-path-research/scripts/review_panel.py +1 -1
  343. package/skills/gsd-path-research/scripts/state_checkpoint.py +135 -44
  344. package/skills/gsd-path-research/scripts/state_promote.py +2 -0
  345. package/skills/gsd-path-research/scripts/worktree_paths.py +72 -10
  346. package/skills/gsd-path-research/templates/research-handoff.md +14 -3
  347. package/skills/gsd-path-roadmap/SKILL.md +2 -1
  348. package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
  349. package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
  350. package/skills/gsd-path-roadmap/scripts/_common.py +619 -18
  351. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +160 -86
  352. package/skills/gsd-path-roadmap/scripts/build_recovery.py +100 -32
  353. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +662 -50
  354. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +338 -30
  355. package/skills/gsd-path-roadmap/scripts/detect_project.py +587 -32
  356. package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
  357. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +152 -50
  358. package/skills/gsd-path-roadmap/scripts/integration.py +580 -25
  359. package/skills/gsd-path-roadmap/scripts/isolation.py +1438 -57
  360. package/skills/gsd-path-roadmap/scripts/loop_run.py +31 -29
  361. package/skills/gsd-path-roadmap/scripts/members.py +706 -0
  362. package/skills/gsd-path-roadmap/scripts/path_config.py +5 -5
  363. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +124 -34
  364. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +287 -87
  365. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
  366. package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
  367. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +135 -44
  368. package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
  369. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +72 -10
  370. package/skills/gsd-path-ship/SKILL.md +33 -11
  371. package/skills/gsd-path-ship/references/dispatch.md +13 -0
  372. package/skills/gsd-path-ship/references/operating-rules.md +214 -0
  373. package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
  374. package/skills/gsd-path-ship/references/reviewer-wave.md +18 -5
  375. package/skills/gsd-path-ship/references/reviewer.md +3 -1
  376. package/skills/gsd-path-ship/references/spec-probes.md +150 -0
  377. package/skills/gsd-path-ship/scripts/_common.py +619 -18
  378. package/skills/gsd-path-ship/scripts/archive_milestone.py +160 -86
  379. package/skills/gsd-path-ship/scripts/build_recovery.py +100 -32
  380. package/skills/gsd-path-ship/scripts/build_state.py +143 -24
  381. package/skills/gsd-path-ship/scripts/check_handoffs.py +662 -50
  382. package/skills/gsd-path-ship/scripts/check_task_briefs.py +338 -30
  383. package/skills/gsd-path-ship/scripts/detect_project.py +587 -32
  384. package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
  385. package/skills/gsd-path-ship/scripts/discussion_validate.py +152 -50
  386. package/skills/gsd-path-ship/scripts/integration.py +580 -25
  387. package/skills/gsd-path-ship/scripts/isolation.py +1438 -57
  388. package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
  389. package/skills/gsd-path-ship/scripts/loop_run.py +31 -29
  390. package/skills/gsd-path-ship/scripts/members.py +706 -0
  391. package/skills/gsd-path-ship/scripts/path_config.py +5 -5
  392. package/skills/gsd-path-ship/scripts/pipeline_git.py +124 -34
  393. package/skills/gsd-path-ship/scripts/pipeline_state.py +287 -87
  394. package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
  395. package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
  396. package/skills/gsd-path-ship/scripts/state_checkpoint.py +135 -44
  397. package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
  398. package/skills/gsd-path-ship/scripts/workflow_run.py +107 -30
  399. package/skills/gsd-path-ship/scripts/worktree_paths.py +72 -10
  400. package/skills/gsd-path-ship/templates/final-review.md +16 -4
  401. package/skills/gsd-path-ship/templates/gap-review.md +2 -1
  402. package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
  403. package/skills/gsd-path-ship/templates/wave-review.md +16 -4
  404. package/skills/gsd-path-undo/SKILL.md +2 -1
  405. package/skills/gsd-path-undo/references/operating-rules.md +214 -0
  406. package/skills/gsd-path-undo/scripts/_common.py +619 -18
  407. package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
  408. package/skills/gsd-path-undo/scripts/loop_run.py +31 -29
  409. package/skills/gsd-path-undo/scripts/path_config.py +5 -5
  410. package/skills/gsd-path-undo/scripts/pipeline_undo.py +218 -30
  411. package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
  412. package/skills/gsd-path-undo/scripts/worktree_paths.py +72 -10
  413. package/skills/path/BUILD.md +7 -1
  414. package/skills/path/DECIDE.md +11 -4
  415. package/skills/path/DEFINE.md +36 -4
  416. package/skills/path/DOCS-AUDIT.md +6 -4
  417. package/skills/path/INSPECT.md +5 -4
  418. package/skills/path/PLAN.md +18 -4
  419. package/skills/path/RESEARCH.md +13 -1
  420. package/skills/path/ROADMAP.md +2 -1
  421. package/skills/path/SHIP.md +33 -11
  422. package/skills/path/SKILL.md +45 -1
  423. package/skills/path/references/build-native.md +63 -15
  424. package/skills/path/references/codebase-mapper.md +2 -1
  425. package/skills/path/references/dispatch.md +13 -0
  426. package/skills/path/references/docs-auditor.md +7 -1
  427. package/skills/path/references/operating-rules.md +214 -0
  428. package/skills/path/references/planner.md +22 -4
  429. package/skills/path/references/reviewer-final.md +20 -2
  430. package/skills/path/references/reviewer-wave.md +18 -5
  431. package/skills/path/references/reviewer.md +3 -1
  432. package/skills/path/references/spec-probes.md +150 -0
  433. package/skills/path/scripts/_common.py +619 -18
  434. package/skills/path/scripts/archive_milestone.py +160 -86
  435. package/skills/path/scripts/bootstrap_repository.py +8 -5
  436. package/skills/path/scripts/build_recovery.py +100 -32
  437. package/skills/path/scripts/build_state.py +143 -24
  438. package/skills/path/scripts/check_docs_audit.py +91 -7
  439. package/skills/path/scripts/check_handoffs.py +662 -50
  440. package/skills/path/scripts/check_task_briefs.py +338 -30
  441. package/skills/path/scripts/check_update.py +2 -2
  442. package/skills/path/scripts/detect_project.py +587 -32
  443. package/skills/path/scripts/discussion_records.py +19 -13
  444. package/skills/path/scripts/discussion_validate.py +152 -50
  445. package/skills/path/scripts/dispatch_driver.py +501 -46
  446. package/skills/path/scripts/git_guard.py +118 -31
  447. package/skills/path/scripts/integration.py +580 -25
  448. package/skills/path/scripts/isolation.py +1438 -57
  449. package/skills/path/scripts/lean_verification.py +99 -19
  450. package/skills/path/scripts/loop_run.py +31 -29
  451. package/skills/path/scripts/members.py +706 -0
  452. package/skills/path/scripts/path_config.py +5 -5
  453. package/skills/path/scripts/pipeline_git.py +124 -34
  454. package/skills/path/scripts/pipeline_state.py +287 -87
  455. package/skills/path/scripts/promote_lookahead.py +1 -1
  456. package/skills/path/scripts/review_panel.py +1 -1
  457. package/skills/path/scripts/state_checkpoint.py +135 -44
  458. package/skills/path/scripts/state_promote.py +2 -0
  459. package/skills/path/scripts/status_runtime.py +58 -10
  460. package/skills/path/scripts/task_context.py +12 -3
  461. package/skills/path/scripts/token_budget.py +12 -8
  462. package/skills/path/scripts/workflow_run.py +107 -30
  463. package/skills/path/scripts/worktree_paths.py +72 -10
  464. package/skills/path/templates/codebase.md +1 -1
  465. package/skills/path/templates/docs-audit.md +6 -2
  466. package/skills/path/templates/final-review.md +16 -4
  467. package/skills/path/templates/gap-review.md +2 -1
  468. package/skills/path/templates/intent.md +30 -0
  469. package/skills/path/templates/patch-findings.md +3 -3
  470. package/skills/path/templates/plan.md +17 -2
  471. package/skills/path/templates/research-handoff.md +14 -3
  472. package/skills/path/templates/state.md +1 -1
  473. package/skills/path/templates/task.md +1 -0
  474. package/skills/path/templates/wave-review.md +16 -4
  475. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  476. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  477. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  478. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  479. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  480. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  481. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
@@ -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"
@@ -50,6 +53,9 @@ COMMENT_PATTERN = re.compile(r"<!--.*?-->", re.DOTALL)
50
53
  BACKTICK_PATTERN = re.compile(r"`([^`\n]+)`")
51
54
  PATH_TOKEN_PATTERN = re.compile(r"[A-Za-z0-9._~/-]+")
52
55
  VERIFY_BLOCK_PATTERN = re.compile(r"```bash[ \t]*\n(?P<block>.*?)```", re.DOTALL)
56
+ TEXT_TOOL_COMMANDS = frozenset({"awk", "egrep", "fgrep", "gawk", "grep", "gsed", "rg", "sed"})
57
+ REGEX_PATTERN_CHARS = frozenset("|*+?[]()^$.\\")
58
+ PIPELINE_WORKSPACE_DIRS = ("review", "discuss", "build", "plan")
53
59
 
54
60
 
55
61
  class BriefError(RuntimeError):
@@ -70,10 +76,20 @@ def _base_exists(repo: Path, base: str, path: str) -> bool:
70
76
  return _run_git(repo, "cat-file", "-e", f"{base}:{path}").returncode == 0
71
77
 
72
78
 
79
+ def _project_track(task: Path, repo: Path) -> Optional[str]:
80
+ try:
81
+ track = task.parent.parent.relative_to(repo).as_posix()
82
+ except ValueError:
83
+ return None
84
+ if track not in {".project", ".project/next"}:
85
+ return None
86
+ return track
87
+
88
+
73
89
  def _supplied_contract(repo: Path, task: Path, token: str) -> bool:
74
90
  """Plan approval checkpoints these supplied inputs after the brief gate."""
75
- track = task.parent.parent.relative_to(repo).as_posix()
76
- if track not in {".project", ".project/next"}:
91
+ track = _project_track(task, repo)
92
+ if track is None:
77
93
  return False
78
94
  if token not in {
79
95
  f"{track}/intent/INTENT.md",
@@ -84,6 +100,18 @@ def _supplied_contract(repo: Path, task: Path, token: str) -> bool:
84
100
  return path.is_file() and path.resolve() == repo.resolve() / token
85
101
 
86
102
 
103
+ def _pipeline_workspace_file(repo: Path, task: Path, token: str) -> bool:
104
+ """Pipeline-owned paths may exist only in the working tree before checkpoint."""
105
+ track = _project_track(task, repo)
106
+ if track is None:
107
+ return False
108
+ prefixes = tuple(f"{track}/{name}/" for name in PIPELINE_WORKSPACE_DIRS)
109
+ if not any(token.startswith(prefix) for prefix in prefixes):
110
+ return False
111
+ path = repo / token
112
+ return path.is_file() and path.resolve() == repo.resolve() / token
113
+
114
+
87
115
  _strip_yaml_comment = _common.strip_yaml_comment
88
116
  _unquote = _common.unquote
89
117
 
@@ -148,7 +176,7 @@ def _clean(body: str) -> str:
148
176
  return COMMENT_PATTERN.sub("", body).strip()
149
177
 
150
178
 
151
- def _looks_like_path(token: str) -> bool:
179
+ def _slash_token_shape(token: str) -> bool:
152
180
  return (
153
181
  "/" in token
154
182
  and PATH_TOKEN_PATTERN.fullmatch(token) is not None
@@ -159,34 +187,136 @@ def _looks_like_path(token: str) -> bool:
159
187
  )
160
188
 
161
189
 
190
+ def _last_segment_has_extension(token: str) -> bool:
191
+ name = PurePosixPath(token).name
192
+ return "." in name and not name.startswith(".")
193
+
194
+
195
+ def _looks_like_path(
196
+ token: str,
197
+ *,
198
+ repo: Path,
199
+ git_root: Path,
200
+ base: str,
201
+ task: Path,
202
+ ) -> bool:
203
+ if not _slash_token_shape(token):
204
+ return False
205
+ if _last_segment_has_extension(token):
206
+ return True
207
+ if _base_exists(git_root, base, token):
208
+ return True
209
+ if _pipeline_workspace_file(repo, task, token):
210
+ return True
211
+ return False
212
+
213
+
214
+ def _looks_like_regex_pattern(token: str) -> bool:
215
+ return any(character in token for character in REGEX_PATTERN_CHARS)
216
+
217
+
218
+ def _text_tool_command(command: str) -> bool:
219
+ return Path(command).name.casefold() in TEXT_TOOL_COMMANDS
220
+
221
+
162
222
  def _normalize(path: str) -> str:
163
223
  return str(PurePosixPath(path))
164
224
 
165
225
 
166
- def _prose_tokens(body: str) -> List[str]:
226
+ def _prose_tokens(
227
+ body: str,
228
+ *,
229
+ repo: Path,
230
+ git_root: Path,
231
+ base: str,
232
+ task: Path,
233
+ ) -> List[str]:
167
234
  cleaned = COMMENT_PATTERN.sub("", body)
168
- return [
169
- _normalize(match.group(1).strip())
170
- for match in BACKTICK_PATTERN.finditer(cleaned)
171
- if _looks_like_path(match.group(1).strip())
172
- ]
235
+ tokens: List[str] = []
236
+ for match in BACKTICK_PATTERN.finditer(cleaned):
237
+ candidate = match.group(1).strip()
238
+ if _looks_like_path(
239
+ candidate, repo=repo, git_root=git_root, base=base, task=task
240
+ ):
241
+ tokens.append(_normalize(candidate))
242
+ return tokens
173
243
 
174
244
 
175
- def _verify_tokens(block: str) -> List[str]:
176
- tokens = []
245
+ def _verify_tokens(
246
+ block: str,
247
+ *,
248
+ repo: Path,
249
+ git_root: Path,
250
+ base: str,
251
+ task: Path,
252
+ ) -> List[str]:
253
+ tokens: List[str] = []
177
254
  for line in block.splitlines():
178
255
  parts = line.split()
179
- for token in parts[1:]:
180
- token = token.strip("\"'")
181
- if _looks_like_path(token):
256
+ if len(parts) < 2:
257
+ continue
258
+ command = parts[0]
259
+ extended_grep = False
260
+ index = 1
261
+ while index < len(parts) and parts[index].startswith("-") and "=" not in parts[index]:
262
+ flag = parts[index]
263
+ if flag in {"-E", "-G"} or flag.startswith("-E"):
264
+ extended_grep = True
265
+ index += 1
266
+ text_tool = _text_tool_command(command)
267
+ for raw in parts[index:]:
268
+ quoted = raw.startswith(("'", '"'))
269
+ token = raw.strip("\"'")
270
+ if text_tool and quoted:
271
+ continue
272
+ if text_tool and command.casefold() == "grep" and extended_grep and _looks_like_regex_pattern(token):
273
+ continue
274
+ if _looks_like_path(token, repo=repo, git_root=git_root, base=base, task=task):
182
275
  tokens.append(_normalize(token))
183
276
  return tokens
184
277
 
185
278
 
279
+ def _member_bases(root: Path):
280
+ """Resolve a member name to (checkout, origin/main SHA), reading MEMBERS.md once."""
281
+ cache: Dict[str, Optional[Tuple[Path, str]]] = {}
282
+
283
+ def locate(name: str) -> Optional[Tuple[Path, str]]:
284
+ if name not in cache:
285
+ if __package__:
286
+ from . import members
287
+ else:
288
+ try:
289
+ import members
290
+ except ImportError: # pragma: no cover - package import used by tests
291
+ from scripts import members
292
+ try:
293
+ listed = {member["name"]: Path(member["checkout"]) for member in members.read_members(root)}
294
+ checkout = listed.get(name)
295
+ if checkout is None:
296
+ cache[name] = None
297
+ else:
298
+ # A build lock selects the bound tip, including during Plan recovery;
299
+ # without the lock, use origin/main before the first build start.
300
+ lock = root / members.LOCK_PATH
301
+ locked = [entry for entry in (json.loads(lock.read_text(encoding="utf-8"))["members"]
302
+ if lock.is_file() else []) if entry["name"] == name]
303
+ if locked:
304
+ cache[name] = (checkout, _resolve_base(checkout, f"refs/heads/{locked[0]['branch']}"))
305
+ else:
306
+ members.require_origin_main(checkout)
307
+ cache[name] = (checkout, _resolve_base(checkout, "refs/remotes/origin/main"))
308
+ except (members.MembersError, BriefError) as error:
309
+ raise BriefError(f"member {name} is unavailable: {error}") from error
310
+ return cache[name]
311
+
312
+ return locate
313
+
314
+
186
315
  def _lint_task(
187
316
  repo: Path, base: str, path: Path,
188
317
  dependency_files: Optional[Dict[str, Set[str]]] = None,
189
318
  landed_bases: Optional[Dict[str, str]] = None,
319
+ member_base=None,
190
320
  ) -> Tuple[str, List[str], Optional[str], int]:
191
321
  text = path.read_text(encoding="utf-8")
192
322
  fields, error = _frontmatter(text)
@@ -198,6 +328,15 @@ def _lint_task(
198
328
  checked = 0
199
329
  if fields is None:
200
330
  return task_id, [error or "invalid frontmatter"], None, checked
331
+ # A member task's paths resolve in the member at its origin/main, where the
332
+ # member bound branch starts; `repo:` absent means the coordinator.
333
+ git_root = repo
334
+ member = fields.get("repo")
335
+ if member is not None:
336
+ located = member_base(member) if isinstance(member, str) and member and member_base else None
337
+ if located is None:
338
+ return task_id, [f"repo: names no member in MEMBERS.md: {member}"], None, checked
339
+ git_root, base = located[0], (landed_bases or {}).get(task_id, located[1])
201
340
 
202
341
  for field in REQUIRED_FIELDS:
203
342
  if field not in fields:
@@ -237,7 +376,7 @@ def _lint_task(
237
376
  for parent in candidate.parents:
238
377
  if str(parent) == ".":
239
378
  break
240
- kind = _run_git(repo, "cat-file", "-t", f"{base}:{parent}")
379
+ kind = _run_git(git_root, "cat-file", "-t", f"{base}:{parent}")
241
380
  if kind.returncode == 0 and kind.stdout.strip() != "tree":
242
381
  problems.append(
243
382
  f"files entry {normalized} has non-directory ancestor "
@@ -249,13 +388,16 @@ def _lint_task(
249
388
  body = sections.get(name)
250
389
  if body is None or not _clean(body):
251
390
  continue
252
- for token in _prose_tokens(body):
391
+ for token in _prose_tokens(
392
+ body, repo=repo, git_root=git_root, base=base, task=path
393
+ ):
253
394
  checked += 1
254
395
  if (
255
396
  token not in declared
256
397
  and token not in supplied
257
398
  and not _supplied_contract(repo, path, token)
258
- and not _base_exists(repo, base, token)
399
+ and not _pipeline_workspace_file(repo, path, token)
400
+ and not _base_exists(git_root, base, token)
259
401
  ):
260
402
  problems.append(f"## {name} names a path missing at the layer base: {token}")
261
403
 
@@ -265,9 +407,20 @@ def _lint_task(
265
407
  if block is None or not block.group("block").strip():
266
408
  problems.append("## Verify must contain a non-empty fenced bash block")
267
409
  else:
268
- for token in _verify_tokens(block.group("block")):
410
+ for token in _verify_tokens(
411
+ block.group("block"),
412
+ repo=repo,
413
+ git_root=git_root,
414
+ base=base,
415
+ task=path,
416
+ ):
269
417
  checked += 1
270
- if token not in declared and token not in supplied and not _base_exists(repo, base, token):
418
+ if (
419
+ token not in declared
420
+ and token not in supplied
421
+ and not _pipeline_workspace_file(repo, path, token)
422
+ and not _base_exists(git_root, base, token)
423
+ ):
271
424
  problems.append(f"## Verify names a path missing at the layer base: {token}")
272
425
 
273
426
  contract = None
@@ -284,12 +437,13 @@ def validate_task_briefs(
284
437
  root: Path, base: str, tasks_dir: str = DEFAULT_TASKS_DIR,
285
438
  *, dependency_files: Optional[Dict[str, Set[str]]] = None,
286
439
  landed_bases: Optional[Dict[str, str]] = None,
440
+ member_base=None,
287
441
  ) -> Dict[str, object]:
288
442
  """Lint task briefs using the caller's validation bases.
289
443
 
290
- Dispatch uses the layer base without overrides. Plan approval supplies
291
- transitive dependency files for pending tasks and resolved historical bases
292
- for landed tasks, whose immutable briefs must survive later file changes.
444
+ Plain lint uses the layer base without overrides. Plan approval and dispatch
445
+ supply transitive dependency files for unlanded tasks and resolved historical
446
+ bases for landed tasks, whose immutable briefs must survive later file changes.
293
447
  The caller validates the task graph and landed metadata before supplying
294
448
  these mappings; landed tasks receive no dependency-file allowance.
295
449
  """
@@ -305,9 +459,11 @@ def validate_task_briefs(
305
459
  task_files = sorted(tasks_path.glob("*.md"))
306
460
  if not task_files:
307
461
  raise BriefError(f"no task briefs found in {tasks_dir}")
462
+ if member_base is None:
463
+ member_base = _member_bases(root)
308
464
  for path in task_files:
309
465
  task_id, task_problems, contract, task_checked = _lint_task(
310
- root, resolved_base, path, dependency_files, landed_bases
466
+ root, resolved_base, path, dependency_files, landed_bases, member_base
311
467
  )
312
468
  problems.extend(f"{task_id}: {problem}" for problem in task_problems)
313
469
  if contract is not None:
@@ -329,6 +485,136 @@ def validate_task_briefs(
329
485
  return {"base": resolved_base, "checked": checked, "tasks": len(task_files)}
330
486
 
331
487
 
488
+ def _resolve_landed_task_base(
489
+ task_id: str,
490
+ recorded_base: str,
491
+ recorded_member_base: object,
492
+ coordinator_repo: Path,
493
+ member_repo: Optional[Path],
494
+ ) -> str:
495
+ """Resolve a landed task's historical base in the repository it names."""
496
+ if __package__:
497
+ from .isolation import IsolationError, require_commit, require_full_sha
498
+ else:
499
+ try:
500
+ from isolation import IsolationError, require_commit, require_full_sha
501
+ except ModuleNotFoundError as error:
502
+ if error.name != "isolation":
503
+ raise
504
+ from scripts.isolation import IsolationError, require_commit, require_full_sha
505
+
506
+ recorded_full = require_full_sha(recorded_base)
507
+ if member_repo is None:
508
+ try:
509
+ return require_commit(coordinator_repo, recorded_full)
510
+ except IsolationError as error:
511
+ raise BriefError(
512
+ f"{task_id} landed task has invalid historical base: {error}"
513
+ ) from error
514
+
515
+ legacy_member_base = (
516
+ recorded_member_base is None
517
+ or (isinstance(recorded_member_base, str)
518
+ and recorded_member_base.strip().casefold() in {"", "null", "~"})
519
+ or (isinstance(recorded_member_base, list) and not recorded_member_base)
520
+ )
521
+ try:
522
+ if not legacy_member_base:
523
+ if not isinstance(recorded_member_base, str):
524
+ raise IsolationError(
525
+ f"member_base must be a full 40-character SHA: {recorded_member_base}"
526
+ )
527
+ member_full = require_full_sha(recorded_member_base)
528
+ return require_commit(member_repo, member_full)
529
+ try:
530
+ return require_commit(coordinator_repo, recorded_full)
531
+ except IsolationError:
532
+ return require_commit(member_repo, recorded_full)
533
+ except IsolationError as error:
534
+ raise BriefError(
535
+ f"{task_id} landed task has invalid historical base: {error}"
536
+ ) from error
537
+
538
+ def plan_validation_bases(
539
+ root: Path, project_dir: str, member_base=None, *, initial: bool = True,
540
+ ) -> Tuple[Dict[str, str], Dict[str, Set[str]], Dict[str, str]]:
541
+ """Share dependency files and landed historical bases across approval and dispatch.
542
+
543
+ Approval requires unlanded tasks to start clean. Dispatch may run beside
544
+ in-flight tasks, while retaining the same graph and path checks.
545
+ """
546
+ if __package__:
547
+ from . import check_handoffs
548
+ from .isolation import IsolationError
549
+ else:
550
+ try:
551
+ import check_handoffs
552
+ from isolation import IsolationError
553
+ except ModuleNotFoundError as error:
554
+ if error.name not in {"check_handoffs", "isolation"}:
555
+ raise
556
+ from scripts import check_handoffs
557
+ from scripts.isolation import IsolationError
558
+ if member_base is None:
559
+ member_base = _member_bases(root)
560
+ try:
561
+ tasks, dependency_files = check_handoffs.plan_brief_inputs(
562
+ root, project_dir, initial=initial
563
+ )
564
+ landed_bases: Dict[str, str] = {}
565
+ for task_id, text in tasks.items():
566
+ if check_handoffs._task_scalar(text, task_id, "status") != "done":
567
+ continue
568
+ agent = check_handoffs._task_scalar(text, task_id, "agent")
569
+ if agent in {"", "null"}:
570
+ raise BriefError(f"{task_id} landed task has no recorded agent")
571
+ fields = check_handoffs._strict_frontmatter(text, task_id)
572
+ member = fields.get("repo")
573
+ base_repo = root
574
+ if member is not None:
575
+ located = member_base(member) if isinstance(member, str) and member else None
576
+ if located is None:
577
+ raise BriefError(f"repo: names no member in MEMBERS.md: {member}")
578
+ base_repo = located[0]
579
+ recorded_base = check_handoffs._task_scalar(text, task_id, "base")
580
+ try:
581
+ landed_bases[task_id] = _resolve_landed_task_base(
582
+ task_id,
583
+ recorded_base,
584
+ fields.get("member_base") if member is not None else None,
585
+ root,
586
+ base_repo if member is not None else None,
587
+ )
588
+ except IsolationError as error:
589
+ raise BriefError(f"{task_id} landed task has invalid historical base: {error}") from error
590
+ dependency_files[task_id] = set()
591
+ except check_handoffs.HandoffError as error:
592
+ raise BriefError(str(error)) from error
593
+ return tasks, dependency_files, landed_bases
594
+
595
+
596
+ def validate_plan_task_briefs(
597
+ root: Path,
598
+ base: str,
599
+ project_dir: str = ".project",
600
+ tasks_dir: Optional[str] = None,
601
+ ) -> Dict[str, object]:
602
+ """Dispatch lint with dependency files and landed historical bases."""
603
+ member_base = _member_bases(root)
604
+ _, dependency_files, landed_bases = plan_validation_bases(
605
+ root, project_dir, member_base, initial=False
606
+ )
607
+ resolved_tasks_dir = tasks_dir or f"{project_dir}/tasks"
608
+ return validate_task_briefs(
609
+ root,
610
+ base,
611
+ resolved_tasks_dir,
612
+ dependency_files=dependency_files,
613
+ landed_bases=landed_bases,
614
+ member_base=member_base,
615
+ )
616
+
617
+
332
618
  def _tasks_dir(value: str) -> str:
333
619
  """Argparse type for --tasks-dir: a relative POSIX-style path."""
334
620
 
@@ -359,15 +645,37 @@ def parser() -> argparse.ArgumentParser:
359
645
  help="relative POSIX-style directory under --repo holding the task files "
360
646
  "(default: .project/tasks)",
361
647
  )
648
+ argument_parser.add_argument(
649
+ "--project-dir",
650
+ default=None,
651
+ help="apply plan dependency files and landed-task bases for this track "
652
+ "(inferred from --tasks-dir when its sibling plan/PLAN.md exists)",
653
+ )
362
654
  return argument_parser
363
655
 
364
656
 
365
657
  def main(argv: Optional[Sequence[str]] = None) -> int:
366
658
  arguments = parser().parse_args(argv)
367
659
  try:
368
- result = validate_task_briefs(
369
- arguments.repo.resolve(), arguments.base, arguments.tasks_dir
370
- )
660
+ root = arguments.repo.resolve()
661
+ project_dir = arguments.project_dir
662
+ if project_dir is None:
663
+ tasks_path = PurePosixPath(arguments.tasks_dir)
664
+ if tasks_path.name == "tasks" and len(tasks_path.parts) > 1:
665
+ track = tasks_path.parent.as_posix()
666
+ if (root / track / "plan/PLAN.md").is_file():
667
+ project_dir = track
668
+ if project_dir is not None:
669
+ result = validate_plan_task_briefs(
670
+ root,
671
+ arguments.base,
672
+ project_dir,
673
+ None if arguments.tasks_dir == DEFAULT_TASKS_DIR else arguments.tasks_dir,
674
+ )
675
+ else:
676
+ result = validate_task_briefs(
677
+ root, arguments.base, arguments.tasks_dir
678
+ )
371
679
  except BriefError as error:
372
680
  print(f"task brief validation failed: {error}", file=sys.stderr)
373
681
  return 1
@@ -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())}