@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
@@ -25,6 +25,7 @@ import json
25
25
  import os
26
26
  import re
27
27
  import shlex
28
+ import stat
28
29
  import subprocess
29
30
  import sys
30
31
  from pathlib import Path, PurePosixPath
@@ -474,6 +475,20 @@ def collect(
474
475
  )
475
476
 
476
477
 
478
+ def is_link_like(path):
479
+ """A symlink, or on Windows any name-surrogate reparse point such as a junction.
480
+
481
+ Path.is_symlink() is false for a junction, which still redirects the path.
482
+ """
483
+ try:
484
+ status = os.lstat(path)
485
+ except OSError:
486
+ return False
487
+ return stat.S_ISLNK(status.st_mode) or bool(
488
+ getattr(status, "st_reparse_tag", 0) & 0x20000000 # IO_REPARSE_TAG name surrogate
489
+ )
490
+
491
+
477
492
  def normalize_posix(path):
478
493
  text = path.replace("\\", "/").strip()
479
494
  if not text:
@@ -536,7 +551,7 @@ def repository_root():
536
551
  result = subprocess.run(
537
552
  ["git", "rev-parse", "--show-toplevel"],
538
553
  cwd=candidate,
539
- text=True,
554
+ encoding="utf-8", errors="replace",
540
555
  capture_output=True,
541
556
  check=False,
542
557
  )
@@ -544,7 +559,7 @@ def repository_root():
544
559
  state = candidate / ".project" / "STATE.md"
545
560
  if (
546
561
  candidate.is_dir()
547
- and not candidate.is_symlink()
562
+ and not is_link_like(candidate)
548
563
  and os.path.lexists(state)
549
564
  ):
550
565
  return candidate
@@ -560,7 +575,7 @@ def project_status(repo):
560
575
  result = subprocess.run(
561
576
  [sys.executable, "-B", str(launcher), "--repo", str(repo)],
562
577
  cwd=repo,
563
- text=True,
578
+ encoding="utf-8", errors="replace",
564
579
  capture_output=True,
565
580
  check=False,
566
581
  )
@@ -697,13 +712,29 @@ def valid_status_branch(value):
697
712
 
698
713
  def target_paths(path, working_directories, repo):
699
714
  repo = repo.resolve()
700
- candidate = Path(path)
715
+ candidate = Path(native_path_text(path))
701
716
  if not candidate.is_absolute():
702
717
  base = Path(working_directories[0]) if working_directories else repo
703
718
  if not base.is_absolute():
704
719
  base = repo / base
705
720
  candidate = base / candidate
706
- return Path(os.path.abspath(candidate)), candidate.resolve(strict=False)
721
+ return Path(os.path.abspath(candidate)), resolve_lenient(candidate)
722
+
723
+
724
+ def resolve_lenient(path):
725
+ """Path.resolve(strict=False) that still follows links on Windows.
726
+
727
+ Python 3.9 raises OSError on Windows for names it cannot open (rep*) and
728
+ for some links to missing files; the archive checks must not skip those
729
+ (a symlinked archive path would be allowed), so fall back to
730
+ os.path.realpath, which resolves what it can and keeps the remainder.
731
+ """
732
+ try:
733
+ return path.resolve(strict=False)
734
+ except OSError:
735
+ if os.name != "nt":
736
+ raise
737
+ return Path(os.path.realpath(path))
707
738
 
708
739
 
709
740
  @lru_cache(maxsize=None)
@@ -712,7 +743,7 @@ def repository_control_roots(repo):
712
743
  result = subprocess.run(
713
744
  ["git", "rev-parse", "--git-common-dir"],
714
745
  cwd=repo,
715
- text=True,
746
+ encoding="utf-8", errors="replace",
716
747
  capture_output=True,
717
748
  env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
718
749
  check=False,
@@ -789,6 +820,51 @@ def path_kind(candidate, repo, control_roots=()):
789
820
  return "product"
790
821
 
791
822
 
823
+ def member_target_kind(candidate, repo):
824
+ """'product' when the path lies in a verified member of this coordinator.
825
+
826
+ A member follows its coordinator's phase. A stale member of this
827
+ coordinator raises MembersError, so the caller denies the write.
828
+ """
829
+ directory = candidate if candidate.is_dir() else candidate.parent
830
+ while not directory.is_dir() and directory != directory.parent:
831
+ directory = directory.parent
832
+ found = subprocess.run(
833
+ ["git", "-C", str(directory), "rev-parse", "--path-format=absolute",
834
+ "--show-toplevel", "--git-common-dir"],
835
+ encoding="utf-8", errors="replace", capture_output=True, check=False,
836
+ env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
837
+ )
838
+ lines = found.stdout.splitlines()
839
+ if found.returncode != 0:
840
+ if found.stderr.strip().startswith("fatal: not a git repository"):
841
+ return None
842
+ raise RuntimeError(found.stderr.strip() or "Git inspection failed")
843
+ if len(lines) != 2:
844
+ raise RuntimeError("Git inspection returned an unexpected result")
845
+ toplevel, common = Path(lines[0]), Path(lines[1])
846
+ if not os.path.lexists(common / "gsd-path" / "member.json"):
847
+ return None
848
+ import members # the guard's runtime directory; only paths in a member need it
849
+ coordinator = members.marker_coordinator(toplevel)
850
+ if coordinator is None:
851
+ return None
852
+ if coordinator.resolve() != repo.resolve():
853
+ for member in members.read_members(repo):
854
+ try:
855
+ listed_common = members._common_dir(Path(member["checkout"]))
856
+ except (members.MembersError, OSError):
857
+ continue
858
+ if listed_common == common.resolve():
859
+ raise members.MembersError(
860
+ "member marker names an old coordinator; "
861
+ "run members.py repair --repo <coordinator>"
862
+ )
863
+ return None
864
+ members.member_role(toplevel)
865
+ return "product"
866
+
867
+
792
868
  def target_kind(path, working_directories, repo, control_roots=None):
793
869
  lexical, resolved = target_paths(path, working_directories, repo)
794
870
  repo = repo.resolve()
@@ -798,10 +874,21 @@ def target_kind(path, working_directories, repo, control_roots=None):
798
874
  _canonical_control_alias(lexical, repo, control_roots),
799
875
  _canonical_control_alias(resolved, repo, control_roots),
800
876
  )
801
- kinds = {
877
+ resolved_kind = path_kind(resolved, repo, control_roots)
878
+ kinds = {resolved_kind} | {
802
879
  path_kind(candidate, repo, control_roots)
803
- for candidate in (lexical, resolved, *aliases)
880
+ for candidate in (lexical, *aliases)
804
881
  }
882
+ if resolved_kind == "external" and "protected" not in kinds:
883
+ try:
884
+ member_kind = member_target_kind(resolved, repo)
885
+ except Exception as error:
886
+ deny(
887
+ f"member write refused: {error}; "
888
+ "run members.py repair --repo <coordinator>"
889
+ )
890
+ if member_kind:
891
+ kinds.add(member_kind)
805
892
  for kind in ("protected", "product", "artifact", "external"):
806
893
  if kind in kinds:
807
894
  return kind
@@ -824,12 +911,12 @@ def pipeline_target_kinds(targets):
824
911
  def closed_target_reason(targets):
825
912
  for path, working_directories in targets:
826
913
  for target in dict.fromkeys(target_paths(path, working_directories, repository_root())):
827
- directory = target if target.is_dir() and not target.is_symlink() else target.parent
914
+ directory = target if target.is_dir() and not is_link_like(target) else target.parent
828
915
  while not directory.exists() and directory != directory.parent:
829
916
  directory = directory.parent
830
917
  root = subprocess.run(
831
918
  ["git", "-C", str(directory), "rev-parse", "--show-toplevel"],
832
- capture_output=True, text=True,
919
+ capture_output=True, encoding="utf-8", errors="replace",
833
920
  )
834
921
  if root.returncode == 0:
835
922
  reason = closed_milestone_reason(Path(root.stdout.strip()))
@@ -919,10 +1006,35 @@ def expansion_can_match_archive(path):
919
1006
  )
920
1007
 
921
1008
 
1009
+ # Git Bash (Claude Code's shell on Windows) names drive C: as /c or /cygdrive/c.
1010
+ MSYS_DRIVE_PATH = re.compile(r"^/(?:cygdrive/)?([A-Za-z])(?=/|$)")
1011
+
1012
+
1013
+ def native_path_text(value):
1014
+ """On Windows, the drive path that a Git Bash path such as /c/repo names.
1015
+
1016
+ Python reads /c/repo as a folder named c at the root of the current drive,
1017
+ which would hide an archive from the containment checks.
1018
+ """
1019
+ if os.name != "nt":
1020
+ return value
1021
+ text = value.replace("\\", "/")
1022
+ match = MSYS_DRIVE_PATH.match(text)
1023
+ if match is None:
1024
+ return value
1025
+ return f"{match.group(1).upper()}:{text[match.end():] or '/'}"
1026
+
1027
+
922
1028
  def path_values(value):
923
- yield value
1029
+ values = [value]
924
1030
  if value.startswith("-") and "=" in value:
925
- yield value.split("=", 1)[1]
1031
+ values.append(value.split("=", 1)[1])
1032
+ for candidate in values:
1033
+ yield candidate
1034
+ # Deny when either reading of an ambiguous Windows path reaches an archive.
1035
+ native = native_path_text(candidate)
1036
+ if native != candidate:
1037
+ yield native
926
1038
 
927
1039
 
928
1040
  def is_absolute_path(path):
@@ -932,7 +1044,7 @@ def is_absolute_path(path):
932
1044
 
933
1045
  def contains_existing_archive(path, working_directories):
934
1046
  try:
935
- target = Path(path).resolve(strict=False)
1047
+ target = resolve_lenient(Path(path))
936
1048
  except (OSError, RuntimeError):
937
1049
  return False
938
1050
  search_paths = [target, Path.cwd(), *(Path(value) for value in working_directories)]
@@ -940,7 +1052,7 @@ def contains_existing_archive(path, working_directories):
940
1052
  if os.name != "nt" and re.match(r"^[A-Za-z]:[/\\]", str(search_path)):
941
1053
  continue
942
1054
  try:
943
- resolved = search_path.resolve(strict=False)
1055
+ resolved = resolve_lenient(search_path)
944
1056
  except (OSError, RuntimeError):
945
1057
  continue
946
1058
  for parent in (resolved, *resolved.parents):
@@ -973,7 +1085,7 @@ def path_in_archive(path, working_directories=(), ancestors=True):
973
1085
  if os.name != "nt" and re.match(r"^[A-Za-z]:[/\\]", combined):
974
1086
  continue
975
1087
  try:
976
- resolved = Path(combined).resolve(strict=False)
1088
+ resolved = resolve_lenient(Path(combined))
977
1089
  except (OSError, RuntimeError):
978
1090
  continue
979
1091
  if in_archive(resolved.as_posix()) or (
@@ -997,7 +1109,7 @@ def working_directory_in_archive(path):
997
1109
  if os.name != "nt" and re.match(r"^[A-Za-z]:[/\\]", value):
998
1110
  continue
999
1111
  try:
1000
- resolved = Path(value).resolve(strict=False)
1112
+ resolved = resolve_lenient(Path(value))
1001
1113
  except (OSError, RuntimeError):
1002
1114
  continue
1003
1115
  if in_archive(resolved.as_posix()):
@@ -1159,7 +1271,7 @@ def copy_destinations(arguments, directories, assignments=None):
1159
1271
  lexical, source_path = target_paths(source, directories, repository_root())
1160
1272
  if source_path.is_dir():
1161
1273
  # ponytail: reject ambiguous directory links instead of emulating cp flags.
1162
- if lexical.is_symlink():
1274
+ if is_link_like(lexical):
1163
1275
  raise ValueError("cp directory source is a symlink; pass a literal directory")
1164
1276
 
1165
1277
  def unreadable(error):
@@ -1168,7 +1280,7 @@ def copy_destinations(arguments, directories, assignments=None):
1168
1280
  for root, folders, files in os.walk(source_path, onerror=unreadable):
1169
1281
  for name in folders + files:
1170
1282
  entry = Path(root) / name
1171
- if entry.is_symlink() and entry.is_dir():
1283
+ if is_link_like(entry) and entry.is_dir():
1172
1284
  raise ValueError("cp source contains a directory symlink; copy it separately")
1173
1285
  for output in outputs:
1174
1286
  yield str(output / entry.relative_to(source_path))
@@ -1648,7 +1760,7 @@ def has_short_option(arguments, option):
1648
1760
  def git_alias(command, git_options):
1649
1761
  result = subprocess.run(
1650
1762
  ["git", *git_options, "config", "--get", f"alias.{command}"],
1651
- text=True,
1763
+ encoding="utf-8", errors="replace",
1652
1764
  capture_output=True,
1653
1765
  check=False,
1654
1766
  )
@@ -1791,13 +1903,13 @@ def closed_milestone_reason(repo=None):
1791
1903
  repo = repo or repository_root()
1792
1904
  branch = subprocess.run(
1793
1905
  ["git", "-C", str(repo), "branch", "--show-current"],
1794
- capture_output=True, text=True,
1906
+ capture_output=True, encoding="utf-8", errors="replace",
1795
1907
  )
1796
1908
  if not re.fullmatch(r"gsd-path/M\d{3,}", branch.stdout.strip()):
1797
1909
  return None
1798
1910
  result = subprocess.run(
1799
1911
  [sys.executable, "-B", str(Path(__file__).with_name("git_guard.py")),
1800
- "closed-milestone"], cwd=repo, capture_output=True, text=True,
1912
+ "closed-milestone"], cwd=repo, capture_output=True, encoding="utf-8", errors="replace",
1801
1913
  )
1802
1914
  if result.returncode:
1803
1915
  return result.stderr.strip() or "closed milestone inspection failed"
@@ -1807,7 +1919,7 @@ def closed_milestone_reason(repo=None):
1807
1919
  def bundled_helper_invocation(command, tokens, working_directories, helpers=PIPELINE_HELPERS):
1808
1920
  """Allow one plain ``python[3] [-B] <script>`` from the guard-owned runtime.
1809
1921
 
1810
- The interpreter token must be exactly python or python3, and the script
1922
+ The interpreter must be exactly python, python3, or py -3, and the script
1811
1923
  operand is the exact parsed token, with expansions and parent traversal refused.
1812
1924
  The resolved helper must be a regular file inside runtime/ beside this guard,
1813
1925
  or beside the guard itself in the repository layout. Resolve only in supplied
@@ -1826,9 +1938,12 @@ def bundled_helper_invocation(command, tokens, working_directories, helpers=PIPE
1826
1938
  _, substitutions = split_command_substitutions(command)
1827
1939
  if substitutions or wrapped_command_tokens(segments[0]) is not None:
1828
1940
  return False
1829
- if tokens[0] not in ("python", "python3"):
1941
+ if tokens[:2] == ["py", "-3"]:
1942
+ rest = tokens[2:]
1943
+ elif tokens[0] in ("python", "python3"):
1944
+ rest = tokens[1:]
1945
+ else:
1830
1946
  return False
1831
- rest = tokens[1:]
1832
1947
  if rest[:1] == ["-B"]:
1833
1948
  rest = rest[1:]
1834
1949
  if not rest:
@@ -1853,7 +1968,8 @@ def bundled_helper_invocation(command, tokens, working_directories, helpers=PIPE
1853
1968
  runtime = runtime.resolve()
1854
1969
  for base in working_directories or [os.getcwd()]:
1855
1970
  try:
1856
- script = (Path(base) / operand).resolve()
1971
+ # A Git Bash /c/... operand names the C: drive, not a folder named c.
1972
+ script = (Path(base) / native_path_text(operand)).resolve()
1857
1973
  if (
1858
1974
  script.name in helpers
1859
1975
  and (script.is_relative_to(runtime) or (
@@ -2035,7 +2151,7 @@ def closed_shell_execution_reason(command, tokens, working_directories):
2035
2151
  for directory in directories:
2036
2152
  result = subprocess.run(
2037
2153
  ["git", *options, "rev-parse", "--show-toplevel", "--absolute-git-dir"],
2038
- cwd=directory, capture_output=True, text=True,
2154
+ cwd=directory, capture_output=True, encoding="utf-8", errors="replace",
2039
2155
  )
2040
2156
  if result.returncode:
2041
2157
  raise ValueError("git target repository cannot be resolved")
@@ -2068,8 +2184,8 @@ def command_denial(command, working_directories, allow_destructive=True):
2068
2184
  try:
2069
2185
  tokens = shell_tokens(outer)
2070
2186
  if tokens in (
2071
- [interpreter, "-B", "-c", "import sys; raise SystemExit(sys.version_info < (3, 9))"]
2072
- for interpreter in ("python3", "python")
2187
+ [*interpreter.split(), "-B", "-c", "import sys; raise SystemExit(sys.version_info < (3, 9))"]
2188
+ for interpreter in ("python3", "python", "py -3")
2073
2189
  ):
2074
2190
  return None
2075
2191
  destructive = list(destructive_shell_invocations(tokens))
@@ -2116,8 +2232,14 @@ def allow(event):
2116
2232
 
2117
2233
 
2118
2234
  def main():
2235
+ # Hosts exchange hook input and output as UTF-8; a Windows pipe would
2236
+ # default to cp1252. In-process callers may substitute text streams.
2237
+ for stream in (sys.stdout, sys.stderr):
2238
+ if hasattr(stream, "reconfigure"):
2239
+ stream.reconfigure(encoding="utf-8", errors="replace")
2119
2240
  try:
2120
- event = json.load(sys.stdin)
2241
+ stdin = getattr(sys.stdin, "buffer", None)
2242
+ event = json.loads(stdin.read().decode("utf-8") if stdin is not None else sys.stdin.read())
2121
2243
  if not isinstance(event, dict):
2122
2244
  raise ValueError("hook event must be an object")
2123
2245
  evaluate(event)
@@ -14,32 +14,12 @@ export const CLAUDE_BRIDGE = "@../AGENTS.md\n@../WORKFLOW.md\n";
14
14
  export const HOOKS_DIRECTORY = ".gsd-path";
15
15
  export const GUARD_SCRIPTS = ["guard_hook.py", "git_guard.py"];
16
16
  export const GUARD_MARKER = "gsd-path guard";
17
- export const PROJECT_RUNTIME_SCRIPTS = [
18
- "pipeline_state.py",
19
- "roadmap.py",
20
- "check_handoffs.py",
21
- "check_task_briefs.py",
22
- "isolation.py",
23
- "discussion_records.py",
24
- "pipeline_git.py",
25
- "promote_lookahead.py",
26
- "detect_project.py",
27
- "pipeline_diagnose.py",
28
- "pipeline_undo.py",
29
- "archive_milestone.py",
30
- "review_panel.py",
31
- "_common.py",
32
- "worktree_paths.py",
33
- "build_recovery.py",
34
- "state_checkpoint.py",
35
- "state_promote.py",
36
- "discussion_validate.py",
37
- "integration.py",
38
- ];
39
17
  export const PROJECT_RUNTIME_MARKER = "gsd-path project runtime";
40
18
  export const PROJECT_STATUS_LAUNCHER = "status_runtime.py";
41
19
  export const PROJECT_STATUS_MARKER = "gsd-path project status launcher";
42
20
  const INSTALL_LOCK_NAME = ".gsd-path-install-lock";
21
+ const LOCAL_EDITS_NOTE =
22
+ "; local edits inside these skills are not carried forward (see UPDATE.md#local-additions)";
43
23
  const INSTALL_LOCK_OWNER = "owner.json";
44
24
  const INSTALL_LOCK_SCHEMA = "gsd-path/install-lock/v2";
45
25
  export const CLAUDE_MATCHER = ".*";
@@ -151,15 +131,22 @@ export const GIT_HOOKS = [
151
131
  ];
152
132
  export const GIT_HOOK_NAMES = GIT_HOOKS.map(([name]) => name);
153
133
 
154
- // Every interpreter a managed hook may legitimately be pinned to.
155
- const INTERPRETER_CANDIDATES = ["python3", "python"];
134
+ // Every interpreter a managed hook may legitimately be pinned to. Each is a
135
+ // command prefix: python.org Windows installs may put only `py` on PATH.
136
+ const INTERPRETER_CANDIDATES = ["python3", "python", "py -3"];
137
+
138
+ // Run a Python interpreter command prefix (for example "py -3") with args.
139
+ function spawnPython(interpreter, args, options) {
140
+ const [command, ...prefix] = interpreter.split(" ");
141
+ return spawnSync(command, [...prefix, ...args], options);
142
+ }
156
143
 
157
- // Probe for a runnable Python interpreter (python3, then python) so emitted
158
- // hooks never hard-code an interpreter that does not exist on this machine
159
- // (python3 is typically absent on Windows).
144
+ // Probe for a runnable Python interpreter (python3, then python, then py -3)
145
+ // so emitted hooks never hard-code an interpreter that does not exist on this
146
+ // machine (python3 is typically absent on Windows).
160
147
  export function detectPythonInterpreter() {
161
148
  for (const candidate of INTERPRETER_CANDIDATES) {
162
- const result = spawnSync(
149
+ const result = spawnPython(
163
150
  candidate,
164
151
  ["-B", "-c", "import sys; raise SystemExit(sys.version_info < (3, 9))"],
165
152
  { stdio: "ignore" }
@@ -475,12 +462,17 @@ function materializedResourceBytes(source, destination) {
475
462
  return Buffer.from(rewriteRouterAliasSkill(data.toString("utf8"), alias, canonical));
476
463
  }
477
464
 
465
+ // The JavaScript realpath keeps Windows 8.3 short names (C:\Users\LONGNA~1);
466
+ // Python's resolve() expands them, and both installers must hash one lock
467
+ // identity per target, so Windows asks the OS for the final path as Python does.
468
+ const realpath = process.platform === "win32" ? fs.realpathSync.native : fs.realpathSync;
469
+
478
470
  function resolveNonStrict(value) {
479
471
  let prefix = path.resolve(value);
480
472
  const suffix = [];
481
473
  for (;;) {
482
474
  try {
483
- return path.join(fs.realpathSync(prefix), ...suffix);
475
+ return path.join(realpath(prefix), ...suffix);
484
476
  } catch {
485
477
  const parent = path.dirname(prefix);
486
478
  if (parent === prefix) return path.join(prefix, ...suffix);
@@ -1090,7 +1082,7 @@ function rollbackTarget(transaction) {
1090
1082
  removePath(destination);
1091
1083
  }
1092
1084
  for (const [original, stored] of [...transaction.moved].reverse()) {
1093
- if (lexists(stored)) fs.renameSync(stored, original);
1085
+ if (lexists(stored)) renameRetrying(stored, original);
1094
1086
  }
1095
1087
  if (transaction.backup !== null) {
1096
1088
  try {
@@ -1314,13 +1306,34 @@ function plannedBackupRoots(legacyRoot, deployments) {
1314
1306
  return planned;
1315
1307
  }
1316
1308
 
1309
+ // Access denied and busy: on Windows a scanner or indexer that briefly opens a
1310
+ // freshly written file blocks moving it or the directory holding it.
1311
+ const WINDOWS_BUSY_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
1312
+ const RENAME_ATTEMPTS = 20;
1313
+
1314
+ function renameRetrying(from, to) {
1315
+ if (process.platform !== "win32") return fs.renameSync(from, to);
1316
+ for (let attempt = 0; ; attempt += 1) {
1317
+ try {
1318
+ return fs.renameSync(from, to);
1319
+ } catch (error) {
1320
+ // A directory moved onto an existing directory also reports EPERM; that
1321
+ // is not transient, so never retry it.
1322
+ if (!WINDOWS_BUSY_CODES.has(error.code)
1323
+ || fs.lstatSync(to, { throwIfNoEntry: false })?.isDirectory()
1324
+ || attempt === RENAME_ATTEMPTS - 1) throw error;
1325
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.min(10 * 2 ** attempt, 250));
1326
+ }
1327
+ }
1328
+ }
1329
+
1317
1330
  // Test-injection points; production callers never touch these.
1318
1331
  export const hooks = {
1319
1332
  mismatches,
1320
1333
  applyTarget,
1321
- rename: fs.renameSync.bind(fs),
1334
+ rename: renameRetrying,
1322
1335
  renameInstallLock: fs.renameSync.bind(fs),
1323
- renameInstallStage: fs.renameSync.bind(fs),
1336
+ renameInstallStage: renameRetrying,
1324
1337
  processIdentity,
1325
1338
  reserveDirectory,
1326
1339
  reserveFile,
@@ -1332,7 +1345,7 @@ export const hooks = {
1332
1345
  // Project installation already requires Python; keep runtime lifecycle ownership there.
1333
1346
  function projectAdapter(source, project, action, payload = {}, env = process.env) {
1334
1347
  const interpreter = requiredPythonRuntime("project runtime");
1335
- const result = spawnSync(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"), "--adapter-request"], {
1348
+ const result = spawnPython(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"), "--adapter-request"], {
1336
1349
  input: JSON.stringify({ source, project, action, ...payload }), encoding: "utf8", env: { ...process.env, ...env },
1337
1350
  });
1338
1351
  if (result.error || result.status !== 0) {
@@ -1484,7 +1497,7 @@ export async function install(sourceRoot, plans, options = {}) {
1484
1497
  if (legacyTransaction.backup !== null) {
1485
1498
  results.push(
1486
1499
  `codex-legacy: backed up ${legacyTransaction.moved.length} entries to ` +
1487
- `${legacyTransaction.backup}`
1500
+ `${legacyTransaction.backup}${LOCAL_EDITS_NOTE}`
1488
1501
  );
1489
1502
  }
1490
1503
  }
@@ -1503,7 +1516,7 @@ export async function install(sourceRoot, plans, options = {}) {
1503
1516
  if (transaction.backup !== null) {
1504
1517
  results.push(
1505
1518
  `${plan.targets.join("+")}: backed up ${transaction.moved.length} entries ` +
1506
- `to ${transaction.backup}`
1519
+ `to ${transaction.backup}${LOCAL_EDITS_NOTE}`
1507
1520
  );
1508
1521
  }
1509
1522
  }
@@ -1559,6 +1572,7 @@ export function parseCli(argv) {
1559
1572
  "dry-run": { type: "boolean", default: false },
1560
1573
  local: { type: "boolean", default: false },
1561
1574
  project: { type: "string" },
1575
+ "member-of": { type: "string" },
1562
1576
  doctor: { type: "boolean", default: false },
1563
1577
  hooks: { type: "boolean", default: false },
1564
1578
  "hooks-init": { type: "boolean", default: false },
@@ -1653,9 +1667,10 @@ function usage() {
1653
1667
  " --doctor read-only health check of installs, hooks, and state\n" +
1654
1668
  " --hooks with --project: install guard hooks (see HOOKS.md)\n" +
1655
1669
  " --hooks-init add guards to an existing project without changing its contracts\n" +
1670
+ " --member-of PATH with --project: install member git hooks that run PATH's guard\n" +
1656
1671
  " --runtime-restore restore the exact declared runtime from --source-root\n" +
1657
1672
  " --runtime-upgrade explicitly select the supplied package runtime\n" +
1658
- " --runtime-migrate migrate a tracked legacy runtime for review; add --update to continue upgrading\n" +
1673
+ " --runtime-migrate migrate a legacy runtime for review; add --update to continue upgrading\n" +
1659
1674
  " --hooks-refresh validate the selected runtime; keep its version\n" +
1660
1675
  " --hooks-refresh-full refresh native settings/git hooks; target flags create missing configs\n" +
1661
1676
  " --dry-run preview without writing\n" +
@@ -1702,9 +1717,10 @@ export async function main(argv, env = process.env) {
1702
1717
  ui.error("--runtime-migrate --update requires --project and cannot be combined with another runtime, doctor, or hook operation");
1703
1718
  return 2;
1704
1719
  }
1705
- if (!migrateAndUpdate && ["runtime-restore", "runtime-upgrade", "runtime-migrate"].some(name => values[name])) {
1720
+ if (!migrateAndUpdate && (values["member-of"] !== undefined
1721
+ || ["runtime-restore", "runtime-upgrade", "runtime-migrate"].some(name => values[name]))) {
1706
1722
  const interpreter = requiredPythonRuntime("project runtime");
1707
- const result = spawnSync(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
1723
+ const result = spawnPython(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
1708
1724
  ...argv.filter(arg => arg !== "--no-color")], { stdio: "inherit", env });
1709
1725
  return result.status ?? 1;
1710
1726
  }
@@ -1809,7 +1825,7 @@ export async function main(argv, env = process.env) {
1809
1825
  }
1810
1826
  if (migrateAndUpdate) {
1811
1827
  const interpreter = requiredPythonRuntime("project runtime");
1812
- const migration = spawnSync(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
1828
+ const migration = spawnPython(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
1813
1829
  "--runtime-migrate", "--project", project, "--source-root", sourceRoot,
1814
1830
  ...(values["dry-run"] ? ["--dry-run"] : [])], { stdio: "inherit", env });
1815
1831
  if (migration.error || migration.status !== 0) {