@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
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  import shutil
9
9
  import shlex
10
10
  import tempfile
11
+ import time
11
12
  from contextlib import contextmanager
12
13
 
13
14
  try:
@@ -22,10 +23,33 @@ RUNTIME_FILES = (
22
23
  "review_panel.py", "_common.py", "worktree_paths.py", "build_recovery.py",
23
24
  "state_checkpoint.py", "state_promote.py", "discussion_validate.py", "integration.py",
24
25
  "build_state.py", "lean_verification.py", "check_docs_audit.py",
25
- "path_config.py", "model_policy.py", "loop_run.py",
26
+ "path_config.py", "model_policy.py", "loop_run.py", "members.py",
26
27
  )
27
28
  GUARDS = ("guard_hook.py", "git_guard.py")
28
29
 
30
+ # Access denied and sharing violation: on Windows a scanner or indexer that
31
+ # briefly opens a freshly written file blocks renaming the directory holding it.
32
+ _WINDOWS_BUSY_ERRORS = (5, 32)
33
+ _RENAME_ATTEMPTS = 20
34
+
35
+
36
+ def _rename_directory(source, destination):
37
+ """Path.rename, retried while Windows briefly holds a file inside ``source``."""
38
+ if os.name != "nt":
39
+ source.rename(destination)
40
+ return
41
+ for attempt in range(_RENAME_ATTEMPTS):
42
+ try:
43
+ source.rename(destination)
44
+ return
45
+ except PermissionError as error:
46
+ # Never retry onto a directory that appeared meanwhile.
47
+ if (getattr(error, "winerror", None) not in _WINDOWS_BUSY_ERRORS
48
+ or os.path.lexists(destination)
49
+ or attempt == _RENAME_ATTEMPTS - 1):
50
+ raise
51
+ time.sleep(min(0.01 * 2 ** attempt, 0.25))
52
+
29
53
 
30
54
  def guard_launcher(name):
31
55
  if name not in GUARDS:
@@ -41,7 +65,7 @@ def guard_launcher(name):
41
65
 
42
66
 
43
67
  def source_manifest(source):
44
- version = json.loads((source / "package.json").read_text())["version"]
68
+ version = json.loads((source / "package.json").read_text(encoding="utf-8"))["version"]
45
69
  files = {}
46
70
  for name in (*RUNTIME_FILES, *GUARDS, "status_runtime.py"):
47
71
  path = source / "scripts" / name
@@ -107,7 +131,7 @@ def publish(source, *, expected=None, dry_run=False, repair=False):
107
131
  shutil.copyfile(source / "scripts" / name, staging / name)
108
132
  if hashlib.sha256((staging / name).read_bytes()).hexdigest() != digest:
109
133
  raise ValueError(f"runtime source changed while installing: {name}")
110
- (staging / "manifest.json").write_text(pin_text(manifest), encoding="utf-8")
134
+ (staging / "manifest.json").write_bytes(pin_text(manifest).encode("utf-8"))
111
135
  # Serialize publication/repair, including two explicit restorations.
112
136
  previous = Path(temporary) / "previous"
113
137
  with publication_lock(parent, pin["digest"]):
@@ -118,13 +142,13 @@ def publish(source, *, expected=None, dry_run=False, repair=False):
118
142
  except ValueError:
119
143
  if not repair:
120
144
  raise
121
- destination.rename(previous)
145
+ _rename_directory(destination, previous)
122
146
  try:
123
- staging.rename(destination)
147
+ _rename_directory(staging, destination)
124
148
  status_runtime.validate_runtime(pin)
125
149
  except BaseException:
126
150
  if previous.exists() and not destination.exists():
127
- previous.rename(destination)
151
+ _rename_directory(previous, destination)
128
152
  raise
129
153
  return pin
130
154
 
@@ -167,7 +191,7 @@ def recover_migration(project):
167
191
  return
168
192
  if journal.is_symlink():
169
193
  raise ValueError(f"unsafe runtime migration journal: {journal}")
170
- data = json.loads(journal.read_text())
194
+ data = json.loads(journal.read_text(encoding="utf-8"))
171
195
  recorded_project = data.get("project")
172
196
  if (data.get("schema") != "gsd-path/runtime-migration/v1"
173
197
  or not isinstance(recorded_project, str)
@@ -208,8 +232,10 @@ def operate(source, project, action, *, dry_run=False):
208
232
  pin = publish(source, dry_run=dry_run)
209
233
  if not dry_run:
210
234
  launcher = project / ".gsd-path/status_runtime.py"
211
- original = launcher.read_text(encoding="utf-8")
212
- replacement = (source / "scripts/status_runtime.py").read_text(encoding="utf-8")
235
+ # Exact bytes: read_text() would turn CRLF into LF, leaving the
236
+ # launcher unequal to the runtime's byte-for-byte copy.
237
+ original = launcher.read_bytes().decode("utf-8")
238
+ replacement = (source / "scripts/status_runtime.py").read_bytes().decode("utf-8")
213
239
  # Both launchers understand the same declaration schema. Publishing
214
240
  # the compatible launcher first leaves the old selection usable.
215
241
  if replacement != original:
@@ -235,6 +261,7 @@ def operate(source, project, action, *, dry_run=False):
235
261
  raise ValueError("legacy runtime contains unknown files; preserve and resolve them before migration")
236
262
  managed = entries + [project / ".gsd-path/status_runtime.py"]
237
263
  managed += [project / ".gsd-path" / n for n in GUARDS if (project / ".gsd-path" / n).exists()]
264
+ divergent = []
238
265
  for path in managed:
239
266
  if path.is_symlink() or not path.is_file():
240
267
  raise ValueError(f"unsafe migration file: {path}")
@@ -249,7 +276,7 @@ def operate(source, project, action, *, dry_run=False):
249
276
  if clean.stdout:
250
277
  raise ValueError(f"locally modified migration file: {path}")
251
278
  elif path.read_bytes() != (source / "scripts" / path.name).read_bytes():
252
- raise ValueError(f"untracked migration file differs from source: {path}")
279
+ divergent.append(path)
253
280
  pin = publish(source, dry_run=dry_run)
254
281
  if dry_run:
255
282
  return pin
@@ -257,6 +284,17 @@ def operate(source, project, action, *, dry_run=False):
257
284
  declaration = project / ".gsd-path/runtime.json"
258
285
  if os.path.lexists(declaration):
259
286
  raise ValueError("runtime declaration already exists; refusing legacy migration")
287
+ backup = None
288
+ if divergent:
289
+ backup_root = migration_journal(project).parent / "backups"
290
+ if backup_root.is_symlink():
291
+ raise ValueError(f"unsafe runtime migration backup directory: {backup_root}")
292
+ backup_root.mkdir(parents=True, exist_ok=True)
293
+ backup = Path(tempfile.mkdtemp(prefix="legacy-", dir=backup_root))
294
+ for path in divergent:
295
+ saved = backup / path.relative_to(project / ".gsd-path")
296
+ saved.parent.mkdir(parents=True, exist_ok=True)
297
+ saved.write_bytes(originals[path])
260
298
  updates = {project / ".gsd-path/status_runtime.py": (source / "scripts/status_runtime.py").read_bytes(),
261
299
  declaration: pin_text(pin).encode()}
262
300
  updates.update({p: None for p in entries})
@@ -278,4 +316,4 @@ def operate(source, project, action, *, dry_run=False):
278
316
  recover_migration(project)
279
317
  raise
280
318
  journal.unlink()
281
- return pin
319
+ return {**pin, "backup": str(backup)} if backup else pin
@@ -55,6 +55,7 @@
55
55
  "scripts/integration.py",
56
56
  "scripts/archive_milestone.py",
57
57
  "scripts/bootstrap_repository.py",
58
+ "scripts/members.py",
58
59
  "scripts/detect_project.py",
59
60
  "scripts/promote_lookahead.py",
60
61
  "scripts/discussion_records.py",
@@ -100,6 +101,7 @@
100
101
  "phase_resources": {
101
102
  "gsd-path-build": [
102
103
  "references/runtime-selection.md",
104
+ "references/operating-rules.md",
103
105
  "references/build-recovery.md",
104
106
  "references/build-native.md",
105
107
  "references/build-abandon.md",
@@ -120,11 +122,13 @@
120
122
  ],
121
123
  "gsd-path-discuss": [
122
124
  "references/runtime-selection.md",
125
+ "references/operating-rules.md",
123
126
  "templates/answers.md",
124
127
  "templates/dialogue.md"
125
128
  ],
126
129
  "gsd-path-docs-audit": [
127
130
  "references/runtime-selection.md",
131
+ "references/operating-rules.md",
128
132
  "references/dispatch.md",
129
133
  "references/model-policy.md",
130
134
  "references/docs-auditor.md",
@@ -132,6 +136,7 @@
132
136
  ],
133
137
  "gsd-path-define": [
134
138
  "references/runtime-selection.md",
139
+ "references/operating-rules.md",
135
140
  "references/build-recovery.md",
136
141
  "templates/charter.md",
137
142
  "templates/intent.md",
@@ -139,6 +144,7 @@
139
144
  ],
140
145
  "gsd-path-inspect": [
141
146
  "references/runtime-selection.md",
147
+ "references/operating-rules.md",
142
148
  "references/codebase-mapper.md",
143
149
  "references/dispatch.md",
144
150
  "references/model-policy.md",
@@ -148,17 +154,21 @@
148
154
  "templates/state.md"
149
155
  ],
150
156
  "gsd-path-forensics": [
151
- "references/runtime-selection.md"
157
+ "references/runtime-selection.md",
158
+ "references/operating-rules.md"
152
159
  ],
153
160
  "gsd-path-migrate": [
154
- "references/runtime-selection.md"
161
+ "references/runtime-selection.md",
162
+ "references/operating-rules.md"
155
163
  ],
156
164
  "gsd-path-loop": [
157
165
  "references/runtime-selection.md",
166
+ "references/operating-rules.md",
158
167
  "templates/loop.md"
159
168
  ],
160
169
  "gsd-path-plan": [
161
170
  "references/runtime-selection.md",
171
+ "references/operating-rules.md",
162
172
  "references/build-recovery.md",
163
173
  "references/dispatch.md",
164
174
  "references/model-policy.md",
@@ -170,6 +180,7 @@
170
180
  ],
171
181
  "gsd-path-research": [
172
182
  "references/runtime-selection.md",
183
+ "references/operating-rules.md",
173
184
  "references/dispatch.md",
174
185
  "references/model-policy.md",
175
186
  "references/researcher.md",
@@ -178,6 +189,7 @@
178
189
  ],
179
190
  "gsd-path-roadmap": [
180
191
  "references/runtime-selection.md",
192
+ "references/operating-rules.md",
181
193
  "references/dispatch.md",
182
194
  "references/model-policy.md",
183
195
  "references/roadmapper.md",
@@ -185,6 +197,7 @@
185
197
  ],
186
198
  "gsd-path-ship": [
187
199
  "references/runtime-selection.md",
200
+ "references/operating-rules.md",
188
201
  "references/dispatch.md",
189
202
  "references/model-policy.md",
190
203
  "references/reviewer.md",
@@ -202,13 +215,15 @@
202
215
  ],
203
216
  "gsd-path-decide": [
204
217
  "references/runtime-selection.md",
218
+ "references/operating-rules.md",
205
219
  "references/dispatch.md",
206
220
  "references/model-policy.md",
207
221
  "references/decider.md",
208
222
  "templates/synthesis.md"
209
223
  ],
210
224
  "gsd-path-undo": [
211
- "references/runtime-selection.md"
225
+ "references/runtime-selection.md",
226
+ "references/operating-rules.md"
212
227
  ]
213
228
  },
214
229
  "script_targets": [
@@ -259,6 +274,16 @@
259
274
  ["scripts/discussion_validate.py", "skills/gsd-path-ship/scripts/discussion_validate.py"],
260
275
  ["scripts/integration.py", "skills/gsd-path-ship/scripts/integration.py"],
261
276
  ["scripts/bootstrap_repository.py", "skills/gsd-path/scripts/bootstrap_repository.py"],
277
+ ["scripts/members.py", "skills/gsd-path/scripts/members.py"],
278
+ ["scripts/members.py", "skills/gsd-path-build/scripts/members.py"],
279
+ ["scripts/members.py", "skills/gsd-path-decide/scripts/members.py"],
280
+ ["scripts/members.py", "skills/gsd-path-define/scripts/members.py"],
281
+ ["scripts/members.py", "skills/gsd-path-docs-audit/scripts/members.py"],
282
+ ["scripts/members.py", "skills/gsd-path-inspect/scripts/members.py"],
283
+ ["scripts/members.py", "skills/gsd-path-plan/scripts/members.py"],
284
+ ["scripts/members.py", "skills/gsd-path-research/scripts/members.py"],
285
+ ["scripts/members.py", "skills/gsd-path-roadmap/scripts/members.py"],
286
+ ["scripts/members.py", "skills/gsd-path-ship/scripts/members.py"],
262
287
  ["scripts/detect_project.py", "skills/gsd-path/scripts/detect_project.py"],
263
288
  ["scripts/detect_project.py", "skills/gsd-path-define/scripts/detect_project.py"],
264
289
  ["scripts/detect_project.py", "skills/gsd-path-inspect/scripts/detect_project.py"],
@@ -17,12 +17,15 @@ from datetime import date
17
17
  from pathlib import Path, PurePosixPath
18
18
  from typing import Mapping, Optional, Sequence
19
19
 
20
- try:
21
- from isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
22
- except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
23
- if error.name != "isolation":
24
- raise
25
- from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
20
+ if __package__:
21
+ from .isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
22
+ else:
23
+ try:
24
+ from isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
25
+ except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
26
+ if error.name != "isolation":
27
+ raise
28
+ from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
26
29
 
27
30
 
28
31
  if __package__:
@@ -66,6 +69,11 @@ def _sha256(content: str) -> str:
66
69
  return hashlib.sha256(content.encode("utf-8")).hexdigest()
67
70
 
68
71
 
72
+ def _is_junk_file(path: Path) -> bool:
73
+ # Drift digests skip OS junk by name: Finder rewrites it whatever its ignore status.
74
+ return path.name in pipeline_state._common.OS_JUNK_NAMES and path.is_file() and not path.is_symlink()
75
+
76
+
69
77
  def _tree_digest(path: Path, excluded: Sequence[str] = ()) -> str:
70
78
  if path.is_symlink() or not path.is_dir():
71
79
  raise PipelineStateError(f"promotion path must be a real directory: {path}")
@@ -74,7 +82,7 @@ def _tree_digest(path: Path, excluded: Sequence[str] = ()) -> str:
74
82
  if child.is_symlink():
75
83
  raise PipelineStateError(f"promotion path contains a symlink: {child}")
76
84
  relative = child.relative_to(path).as_posix()
77
- if PurePosixPath(relative).parts[0] in excluded:
85
+ if PurePosixPath(relative).parts[0] in excluded or _is_junk_file(child):
78
86
  continue
79
87
  if child.is_dir():
80
88
  digest.update(f"d\0{relative}\0".encode())
@@ -87,15 +95,15 @@ def _tree_digest(path: Path, excluded: Sequence[str] = ()) -> str:
87
95
 
88
96
 
89
97
  def _checkpoint_artifact_digest(project: Path, mutable_paths: set[str]) -> str:
90
- """Hash every approval artifact except the helper-owned metadata files."""
98
+ """Hash approval artifacts except helper-owned metadata and OS junk."""
91
99
  digest = hashlib.sha256()
92
100
  for child in sorted(project.rglob("*")):
93
101
  if child.is_symlink():
94
102
  raise PipelineStateError(f"checkpoint path contains a symlink: {child}")
95
103
  relative = child.relative_to(project).as_posix()
96
- # Unsupported entries (OS junk) are refused by the checkpoint itself; leaving them
97
- # out lets a refused approval resume after the owner removes them.
98
- if relative in mutable_paths or relative.split("/", 1)[0] not in PROJECT_ENTRIES:
104
+ # Finder can rewrite OS junk during approval; it must not cause artifact drift.
105
+ if (relative in mutable_paths or relative.split("/", 1)[0] not in PROJECT_ENTRIES
106
+ or _is_junk_file(child)):
99
107
  continue
100
108
  if child.is_dir():
101
109
  digest.update(f"d\0{relative}\0".encode())
@@ -333,11 +341,7 @@ def _converge_checkpoint_file(path: Path, before: str, after: str, label: str) -
333
341
 
334
342
  def _unlink_checkpoint_journal(path: Path) -> None:
335
343
  path.unlink()
336
- descriptor = os.open(path.parent, os.O_RDONLY)
337
- try:
338
- os.fsync(descriptor)
339
- finally:
340
- os.close(descriptor)
344
+ pipeline_state._common.fsync_directory(path.parent)
341
345
 
342
346
 
343
347
  def _resume_checkpoint_locked(
@@ -439,41 +443,103 @@ def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
439
443
  if head.returncode != 0:
440
444
  # Pre-Git approval defers base-dependent checks to build.
441
445
  return
442
- try:
443
- from check_task_briefs import BriefError, validate_task_briefs
444
- except ModuleNotFoundError as error: # pragma: no cover - package imports
445
- if error.name != "check_task_briefs":
446
- raise
447
- from scripts.check_task_briefs import BriefError, validate_task_briefs
448
- try:
449
- import check_handoffs
450
- except ModuleNotFoundError as error:
451
- if error.name != "check_handoffs":
452
- raise
453
- from scripts import check_handoffs
446
+ if __package__:
447
+ from .check_task_briefs import BriefError, _member_bases, validate_task_briefs
448
+ else:
449
+ try:
450
+ from check_task_briefs import BriefError, _member_bases, validate_task_briefs
451
+ except ModuleNotFoundError as error: # pragma: no cover - package imports
452
+ if error.name != "check_task_briefs":
453
+ raise
454
+ from scripts.check_task_briefs import BriefError, _member_bases, validate_task_briefs
455
+ if __package__:
456
+ from . import check_handoffs
457
+ else:
458
+ try:
459
+ import check_handoffs
460
+ except ModuleNotFoundError as error:
461
+ if error.name != "check_handoffs":
462
+ raise
463
+ from scripts import check_handoffs
454
464
  try:
455
465
  tasks, dependency_files = check_handoffs.plan_brief_inputs(repo, project_dir)
456
466
  landed_bases = {}
467
+ member_base = _member_bases(repo)
457
468
  for task_id, text in tasks.items():
458
469
  if check_handoffs._task_scalar(text, task_id, "status") != "done":
459
470
  continue
460
471
  agent = check_handoffs._task_scalar(text, task_id, "agent")
461
472
  if agent in {"", "null"}:
462
473
  raise BriefError(f"{task_id} landed task has no recorded agent")
474
+ member = check_handoffs._strict_frontmatter(text, task_id).get("repo")
475
+ base_repo = repo
476
+ if member is not None:
477
+ located = member_base(member) if isinstance(member, str) and member else None
478
+ if located is None:
479
+ raise BriefError(f"repo: names no member in MEMBERS.md: {member}")
480
+ base_repo = located[0]
463
481
  recorded_base = check_handoffs._task_scalar(text, task_id, "base")
464
482
  try:
465
- landed_bases[task_id] = require_commit(repo, require_full_sha(recorded_base))
483
+ landed_bases[task_id] = require_commit(base_repo, require_full_sha(recorded_base))
466
484
  except IsolationError as error:
467
485
  raise BriefError(f"{task_id} landed task has invalid historical base: {error}") from error
468
486
  dependency_files[task_id] = set()
469
487
  validate_task_briefs(
470
488
  repo, head.stdout.strip(), f"{project_dir}/tasks",
471
489
  dependency_files=dependency_files, landed_bases=landed_bases,
490
+ member_base=member_base,
472
491
  )
492
+ if project_dir == ".project/next":
493
+ # Promotion checks lookahead member tasks from the bases their briefs were checked at.
494
+ named = {
495
+ str(check_handoffs._strict_frontmatter(text, task_id).get("repo"))
496
+ for task_id, text in tasks.items()
497
+ if check_handoffs._strict_frontmatter(text, task_id).get("repo")
498
+ and check_handoffs._task_scalar(text, task_id, "status") != "done"
499
+ }
500
+ if named:
501
+ bases = {name: member_base(name)[1] for name in sorted(named)}
502
+ milestone = pipeline_state.load_state(repo, project_dir)[0].milestone
503
+ path = _lookahead_member_bases_path(repo, str(milestone))
504
+ path.parent.mkdir(parents=True, exist_ok=True)
505
+ pipeline_state._common.atomic_write(path, json.dumps(
506
+ {"schema": LOOKAHEAD_MEMBER_BASES_SCHEMA, "milestone": milestone, "bases": bases},
507
+ indent=2, sort_keys=True) + "\n")
473
508
  except (BriefError, check_handoffs.HandoffError) as error:
474
509
  raise PipelineStateError(f"task brief validation failed: {error}") from error
475
510
 
476
511
 
512
+ LOOKAHEAD_MEMBER_BASES_SCHEMA = "gsd-path/lookahead-member-bases/v1"
513
+
514
+
515
+ def _lookahead_member_bases_path(repo: Path, milestone: str) -> Path:
516
+ common = pipeline_state._run_git(repo, "rev-parse", "--path-format=absolute", "--git-common-dir").stdout.strip()
517
+ return Path(common) / "gsd-path" / "lookahead-member-bases" / f"{milestone}.json"
518
+
519
+
520
+ def _lookahead_member_bases(repo: Path, milestone: str) -> Optional[dict]:
521
+ path = _lookahead_member_bases_path(repo, milestone)
522
+ if not path.is_file():
523
+ return None
524
+ value = json.loads(path.read_text(encoding="utf-8"))
525
+ if value.get("schema") != LOOKAHEAD_MEMBER_BASES_SCHEMA or value.get("milestone") != milestone:
526
+ return None
527
+ return value.get("bases")
528
+
529
+
530
+ def _shipped_member_merges(repo: Path, landing: str) -> dict:
531
+ """Member merges named by the ship commit the integration landing merged."""
532
+ if __package__:
533
+ from .pipeline_git import ship_member_rows
534
+ else:
535
+ from pipeline_git import ship_member_rows
536
+ ship = pipeline_state._run_git(repo, "rev-parse", "--verify", "--quiet", f"{landing}^2", check=False)
537
+ if ship.returncode != 0:
538
+ return {}
539
+ message = pipeline_state._run_git(repo, "show", "-s", "--format=%B", ship.stdout.strip()).stdout
540
+ return {row["name"]: row["merge"] for row in ship_member_rows(message)}
541
+
542
+
477
543
  def checkpoint_approval(
478
544
  repo: Path,
479
545
  kind: str,
@@ -1027,6 +1093,46 @@ def _classify_plan_drift(
1027
1093
  }
1028
1094
  approved = _approved_task_contracts(repo, checkpoint)
1029
1095
  current = _task_contracts_at(repo, revision)
1096
+ if __package__:
1097
+ from . import check_handoffs
1098
+ else:
1099
+ import check_handoffs
1100
+ # Member task paths live in member repos: compare each member from the base its brief
1101
+ # was checked at to the member merge the shipped milestone made (both fixed commits).
1102
+ task_repos = {
1103
+ task_id: str(check_handoffs._strict_frontmatter(contract.text, contract.path).get("repo") or "")
1104
+ for task_id, contract in approved.items()
1105
+ }
1106
+ active_member_tasks = {
1107
+ task_id for task_id, name in task_repos.items()
1108
+ if name and check_handoffs._task_scalar(approved[task_id].text, task_id, "status") != "done"
1109
+ }
1110
+ active_members = {task_repos[task_id] for task_id in active_member_tasks}
1111
+ member_changes: dict[str, list] = {}
1112
+ if active_members:
1113
+ bases = _lookahead_member_bases(repo, str(state.milestone)) or {}
1114
+ if any(name not in bases for name in active_members):
1115
+ return {
1116
+ "class": "unverifiable",
1117
+ "checkpoint": checkpoint,
1118
+ "task_ids": [],
1119
+ "changed_paths": [],
1120
+ "contract_paths": [],
1121
+ "member_tasks": True,
1122
+ "reason": "member bases were not recorded at plan approval",
1123
+ }
1124
+ if __package__:
1125
+ from . import members
1126
+ else:
1127
+ import members
1128
+ checkouts = {item["name"]: Path(item["checkout"]) for item in members.read_members(repo)}
1129
+ merges = _shipped_member_merges(repo, landing or revision)
1130
+ for name in active_members:
1131
+ member_changes[name] = [
1132
+ item for item in pipeline_state._run_git(
1133
+ checkouts[name], "diff", "--no-renames", "--name-only", "-z", bases[name], merges[name]
1134
+ ).stdout.split("\0") if item
1135
+ ] if name in merges else []
1030
1136
 
1031
1137
  contract_paths: set[str] = set()
1032
1138
  flagged_ids: set[str] = set()
@@ -1050,9 +1156,12 @@ def _classify_plan_drift(
1050
1156
 
1051
1157
  product_paths: set[str] = set()
1052
1158
  for task_id, contract in approved.items():
1159
+ member = task_repos.get(task_id)
1160
+ if member and task_id not in active_member_tasks:
1161
+ continue
1053
1162
  matches = sorted(
1054
- changed_path
1055
- for changed_path in changed
1163
+ (f"{member}:{changed_path}" if member else changed_path)
1164
+ for changed_path in (member_changes[member] if member else changed)
1056
1165
  if any(
1057
1166
  changed_path == path
1058
1167
  or changed_path.startswith(f"{path.rstrip('/')}/")
@@ -82,6 +82,8 @@ def _promotion_event(
82
82
  if isinstance(task_ids, list) and task_ids:
83
83
  return f"{prefix}; plan drift flagged tasks {', '.join(task_ids)}"
84
84
  return f"{prefix}; plan contract changed after approval"
85
+ if drift_class == "unverifiable" and drift.get("member_tasks"):
86
+ return f"{prefix}; plan drift unverifiable because member bases were not recorded at plan approval"
85
87
  if drift_class == "unverifiable":
86
88
  return f"{prefix}; plan drift unverifiable because approval checkpoint is missing"
87
89
  return prefix
@@ -29,7 +29,7 @@ def process_identity(pid: int):
29
29
  else:
30
30
  command = ["ps", "-o", "lstart=", "-p", str(pid)]
31
31
  try:
32
- result = subprocess.run(command, capture_output=True, text=True, check=False)
32
+ result = subprocess.run(command, capture_output=True, encoding="utf-8", errors="replace", check=False)
33
33
  except OSError:
34
34
  return None
35
35
  value = result.stdout.strip()
@@ -45,11 +45,39 @@ def runtime_identity(runtime: Path):
45
45
 
46
46
 
47
47
  def process_alive(pid: int) -> bool:
48
+ """Whether pid names a running process, without signalling it.
49
+
50
+ Windows os.kill(pid, 0) sends CTRL_C_EVENT, so Windows asks the kernel.
51
+ status_runtime and install carry verbatim copies (they cannot import this
52
+ module); tests/test_common_platform.py keeps them identical.
53
+ """
54
+ if pid <= 0:
55
+ return False
56
+ if os.name != "nt":
57
+ try:
58
+ os.kill(pid, 0)
59
+ except OSError as error:
60
+ return error.errno == errno.EPERM
61
+ return True
62
+ import ctypes
63
+ from ctypes import wintypes
64
+
65
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
66
+ kernel32.OpenProcess.restype = wintypes.HANDLE
67
+ kernel32.OpenProcess.argtypes = (wintypes.DWORD, wintypes.BOOL, wintypes.DWORD)
68
+ kernel32.GetExitCodeProcess.argtypes = (wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD))
69
+ kernel32.CloseHandle.argtypes = (wintypes.HANDLE,)
70
+ handle = kernel32.OpenProcess(0x1000, False, pid) # PROCESS_QUERY_LIMITED_INFORMATION
71
+ if not handle:
72
+ # ERROR_ACCESS_DENIED: a protected process exists but refuses the query.
73
+ return ctypes.get_last_error() == 5
48
74
  try:
49
- os.kill(pid, 0)
50
- except OSError as error:
51
- return error.errno == errno.EPERM
52
- return True
75
+ code = wintypes.DWORD()
76
+ if not kernel32.GetExitCodeProcess(handle, ctypes.byref(code)):
77
+ return True
78
+ return code.value == 259 # STILL_ACTIVE
79
+ finally:
80
+ kernel32.CloseHandle(handle)
53
81
 
54
82
 
55
83
  def install_lock_active(lock: Path) -> bool:
@@ -196,7 +224,9 @@ def main(argv: Sequence[str] = sys.argv[1:]) -> int:
196
224
  return 2
197
225
  if args.runtime_path:
198
226
  try:
199
- print(resolve_runtime(args.repo.resolve()))
227
+ # Forward slashes: agents paste this into Git Bash on Windows, where
228
+ # unquoted backslashes are escapes.
229
+ print(resolve_runtime(args.repo.resolve()).as_posix())
200
230
  return 0
201
231
  except (OSError, ValueError) as error:
202
232
  print(str(error), file=sys.stderr)
@@ -129,8 +129,8 @@ def divergence_warning(source: Path, destination: Path, root: Path) -> Optional[
129
129
  if destination.stat().st_mtime > source.stat().st_mtime:
130
130
  return (
131
131
  f"generated resource diverges from canonical and is newer "
132
- f"(possible wrong-direction edit; edit {source.relative_to(root)} "
133
- f"instead): {destination.relative_to(root)}"
132
+ f"(possible wrong-direction edit; edit {source.relative_to(root).as_posix()} "
133
+ f"instead): {destination.relative_to(root).as_posix()}"
134
134
  )
135
135
  return None
136
136
 
@@ -166,9 +166,9 @@ def mismatches(root: Path) -> Sequence[str]:
166
166
  problems.append("package.json files do not match manifest package_files")
167
167
  for source, destination in resource_pairs(root):
168
168
  if not source.is_file():
169
- problems.append(f"missing canonical resource: {source.relative_to(root)}")
169
+ problems.append(f"missing canonical resource: {source.relative_to(root).as_posix()}")
170
170
  elif not destination.is_file():
171
- problems.append(f"missing generated resource: {destination.relative_to(root)}")
171
+ problems.append(f"missing generated resource: {destination.relative_to(root).as_posix()}")
172
172
  else:
173
173
  try:
174
174
  expected = materialized_resource_bytes(source, destination)
@@ -181,10 +181,10 @@ def mismatches(root: Path) -> Sequence[str]:
181
181
  problems.append(warning)
182
182
  else:
183
183
  problems.append(
184
- f"stale generated resource: {destination.relative_to(root)}"
184
+ f"stale generated resource: {destination.relative_to(root).as_posix()}"
185
185
  )
186
186
  for path in package_metadata(root):
187
- problems.append(f"unexpected package metadata: {path.relative_to(root)}")
187
+ problems.append(f"unexpected package metadata: {path.relative_to(root).as_posix()}")
188
188
  return problems
189
189
 
190
190
 
@@ -216,7 +216,8 @@ def synchronize(root: Path) -> int:
216
216
  package_path = root / "package.json"
217
217
  package = json.loads(package_path.read_text(encoding="utf-8"))
218
218
  package["files"] = list(PACKAGE_FILES)
219
- package_path.write_text(json.dumps(package, indent=2) + "\n", encoding="utf-8")
219
+ # Bytes, so Windows text mode never writes CRLF.
220
+ package_path.write_bytes((json.dumps(package, indent=2) + "\n").encode("utf-8"))
220
221
  print(
221
222
  json.dumps(
222
223
  {