@opengsd/gsd-path 1.5.0 → 1.6.1

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 (363) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +169 -0
  3. package/DOCS.md +4 -3
  4. package/FULL.md +68 -7
  5. package/HOOKS.md +81 -4
  6. package/MIGRATE.md +12 -0
  7. package/QUICK.md +4 -2
  8. package/README.md +69 -16
  9. package/RUNTIME.md +23 -5
  10. package/UPDATE.md +35 -0
  11. package/WORKFLOW.md +66 -7
  12. package/package.json +1 -1
  13. package/scripts/_common.py +150 -1
  14. package/scripts/build_recovery.py +62 -30
  15. package/scripts/build_state.py +195 -5
  16. package/scripts/check_handoffs.py +92 -30
  17. package/scripts/check_task_briefs.py +27 -19
  18. package/scripts/detect_project.py +17 -2
  19. package/scripts/discussion_records.py +9 -1
  20. package/scripts/discussion_validate.py +7 -6
  21. package/scripts/dispatch_driver.py +174 -36
  22. package/scripts/git_guard.py +42 -7
  23. package/scripts/guard_hook.py +1216 -47
  24. package/scripts/install.mjs +76 -3
  25. package/scripts/install.py +182 -37
  26. package/scripts/integration.py +115 -68
  27. package/scripts/isolation.py +192 -23
  28. package/scripts/members.py +68 -25
  29. package/scripts/migrate_core.py +2 -0
  30. package/scripts/model_policy.py +13 -2
  31. package/scripts/pipeline_diagnose.py +17 -2
  32. package/scripts/pipeline_git.py +87 -35
  33. package/scripts/pipeline_state.py +31 -7
  34. package/scripts/pipeline_undo.py +17 -8
  35. package/scripts/promote_lookahead.py +3 -3
  36. package/scripts/review_findings.py +180 -44
  37. package/scripts/roadmap.py +9 -1
  38. package/scripts/runtime_store.py +1 -0
  39. package/scripts/state_checkpoint.py +30 -9
  40. package/scripts/state_promote.py +92 -5
  41. package/scripts/task_context.py +2 -0
  42. package/scripts/workflow_run.py +9 -2
  43. package/skills/gsd-path/DEFINE.md +6 -2
  44. package/skills/gsd-path/PLAN.md +6 -2
  45. package/skills/gsd-path/ROADMAP.md +16 -6
  46. package/skills/gsd-path/SHIP.md +38 -11
  47. package/skills/gsd-path/SKILL.md +52 -24
  48. package/skills/gsd-path/references/build-native.md +96 -9
  49. package/skills/gsd-path/references/build-recovery.md +13 -0
  50. package/skills/gsd-path/references/coder.md +7 -3
  51. package/skills/gsd-path/references/model-policy.md +3 -1
  52. package/skills/gsd-path/references/operating-rules.md +14 -5
  53. package/skills/gsd-path/references/planner.md +13 -1
  54. package/skills/gsd-path/references/reviewer-final.md +5 -5
  55. package/skills/gsd-path/references/reviewer-wave.md +46 -13
  56. package/skills/gsd-path/references/runtime-selection.md +10 -0
  57. package/skills/gsd-path/scripts/_common.py +150 -1
  58. package/skills/gsd-path/scripts/build_recovery.py +62 -30
  59. package/skills/gsd-path/scripts/build_state.py +195 -5
  60. package/skills/gsd-path/scripts/check_handoffs.py +92 -30
  61. package/skills/gsd-path/scripts/check_task_briefs.py +27 -19
  62. package/skills/gsd-path/scripts/detect_project.py +17 -2
  63. package/skills/gsd-path/scripts/discussion_records.py +9 -1
  64. package/skills/gsd-path/scripts/discussion_validate.py +7 -6
  65. package/skills/gsd-path/scripts/dispatch_driver.py +174 -36
  66. package/skills/gsd-path/scripts/git_guard.py +42 -7
  67. package/skills/gsd-path/scripts/integration.py +115 -68
  68. package/skills/gsd-path/scripts/isolation.py +192 -23
  69. package/skills/gsd-path/scripts/members.py +68 -25
  70. package/skills/gsd-path/scripts/model_policy.py +13 -2
  71. package/skills/gsd-path/scripts/pipeline_git.py +87 -35
  72. package/skills/gsd-path/scripts/pipeline_state.py +31 -7
  73. package/skills/gsd-path/scripts/promote_lookahead.py +3 -3
  74. package/skills/gsd-path/scripts/review_findings.py +180 -44
  75. package/skills/gsd-path/scripts/roadmap.py +9 -1
  76. package/skills/gsd-path/scripts/state_checkpoint.py +30 -9
  77. package/skills/gsd-path/scripts/state_promote.py +92 -5
  78. package/skills/gsd-path/scripts/task_context.py +2 -0
  79. package/skills/gsd-path/scripts/workflow_run.py +9 -2
  80. package/skills/gsd-path/templates/final-review.md +5 -5
  81. package/skills/gsd-path/templates/state.md +1 -0
  82. package/skills/gsd-path/templates/task.md +1 -0
  83. package/skills/gsd-path/templates/wave-review.md +18 -9
  84. package/skills/gsd-path-build/references/build-native.md +96 -9
  85. package/skills/gsd-path-build/references/build-recovery.md +13 -0
  86. package/skills/gsd-path-build/references/coder.md +7 -3
  87. package/skills/gsd-path-build/references/model-policy.md +3 -1
  88. package/skills/gsd-path-build/references/operating-rules.md +14 -5
  89. package/skills/gsd-path-build/references/reviewer-final.md +5 -5
  90. package/skills/gsd-path-build/references/reviewer-wave.md +46 -13
  91. package/skills/gsd-path-build/references/runtime-selection.md +10 -0
  92. package/skills/gsd-path-build/scripts/_common.py +150 -1
  93. package/skills/gsd-path-build/scripts/build_recovery.py +62 -30
  94. package/skills/gsd-path-build/scripts/build_state.py +195 -5
  95. package/skills/gsd-path-build/scripts/check_handoffs.py +92 -30
  96. package/skills/gsd-path-build/scripts/check_task_briefs.py +27 -19
  97. package/skills/gsd-path-build/scripts/detect_project.py +17 -2
  98. package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
  99. package/skills/gsd-path-build/scripts/discussion_validate.py +7 -6
  100. package/skills/gsd-path-build/scripts/dispatch_driver.py +174 -36
  101. package/skills/gsd-path-build/scripts/integration.py +115 -68
  102. package/skills/gsd-path-build/scripts/isolation.py +192 -23
  103. package/skills/gsd-path-build/scripts/members.py +68 -25
  104. package/skills/gsd-path-build/scripts/model_policy.py +13 -2
  105. package/skills/gsd-path-build/scripts/pipeline_git.py +87 -35
  106. package/skills/gsd-path-build/scripts/pipeline_state.py +31 -7
  107. package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -3
  108. package/skills/gsd-path-build/scripts/review_findings.py +180 -44
  109. package/skills/gsd-path-build/scripts/roadmap.py +9 -1
  110. package/skills/gsd-path-build/scripts/state_checkpoint.py +30 -9
  111. package/skills/gsd-path-build/scripts/state_promote.py +92 -5
  112. package/skills/gsd-path-build/scripts/task_context.py +2 -0
  113. package/skills/gsd-path-build/scripts/workflow_run.py +9 -2
  114. package/skills/gsd-path-build/templates/task.md +1 -0
  115. package/skills/gsd-path-build/templates/wave-review.md +18 -9
  116. package/skills/gsd-path-decide/references/model-policy.md +3 -1
  117. package/skills/gsd-path-decide/references/operating-rules.md +14 -5
  118. package/skills/gsd-path-decide/references/runtime-selection.md +10 -0
  119. package/skills/gsd-path-decide/scripts/_common.py +150 -1
  120. package/skills/gsd-path-decide/scripts/build_recovery.py +62 -30
  121. package/skills/gsd-path-decide/scripts/check_handoffs.py +92 -30
  122. package/skills/gsd-path-decide/scripts/check_task_briefs.py +27 -19
  123. package/skills/gsd-path-decide/scripts/detect_project.py +17 -2
  124. package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
  125. package/skills/gsd-path-decide/scripts/discussion_validate.py +7 -6
  126. package/skills/gsd-path-decide/scripts/integration.py +115 -68
  127. package/skills/gsd-path-decide/scripts/isolation.py +192 -23
  128. package/skills/gsd-path-decide/scripts/members.py +68 -25
  129. package/skills/gsd-path-decide/scripts/model_policy.py +13 -2
  130. package/skills/gsd-path-decide/scripts/pipeline_git.py +87 -35
  131. package/skills/gsd-path-decide/scripts/pipeline_state.py +31 -7
  132. package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -3
  133. package/skills/gsd-path-decide/scripts/roadmap.py +9 -1
  134. package/skills/gsd-path-decide/scripts/state_checkpoint.py +30 -9
  135. package/skills/gsd-path-decide/scripts/state_promote.py +92 -5
  136. package/skills/gsd-path-define/SKILL.md +6 -2
  137. package/skills/gsd-path-define/references/build-recovery.md +13 -0
  138. package/skills/gsd-path-define/references/operating-rules.md +14 -5
  139. package/skills/gsd-path-define/references/runtime-selection.md +10 -0
  140. package/skills/gsd-path-define/scripts/_common.py +150 -1
  141. package/skills/gsd-path-define/scripts/build_recovery.py +62 -30
  142. package/skills/gsd-path-define/scripts/check_handoffs.py +92 -30
  143. package/skills/gsd-path-define/scripts/check_task_briefs.py +27 -19
  144. package/skills/gsd-path-define/scripts/detect_project.py +17 -2
  145. package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
  146. package/skills/gsd-path-define/scripts/discussion_validate.py +7 -6
  147. package/skills/gsd-path-define/scripts/integration.py +115 -68
  148. package/skills/gsd-path-define/scripts/isolation.py +192 -23
  149. package/skills/gsd-path-define/scripts/members.py +68 -25
  150. package/skills/gsd-path-define/scripts/model_policy.py +13 -2
  151. package/skills/gsd-path-define/scripts/pipeline_git.py +87 -35
  152. package/skills/gsd-path-define/scripts/pipeline_state.py +31 -7
  153. package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -3
  154. package/skills/gsd-path-define/scripts/roadmap.py +9 -1
  155. package/skills/gsd-path-define/scripts/state_checkpoint.py +30 -9
  156. package/skills/gsd-path-define/scripts/state_promote.py +92 -5
  157. package/skills/gsd-path-define/templates/state.md +1 -0
  158. package/skills/gsd-path-discuss/references/operating-rules.md +14 -5
  159. package/skills/gsd-path-discuss/references/runtime-selection.md +10 -0
  160. package/skills/gsd-path-discuss/scripts/_common.py +150 -1
  161. package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
  162. package/skills/gsd-path-discuss/scripts/model_policy.py +13 -2
  163. package/skills/gsd-path-docs-audit/references/model-policy.md +3 -1
  164. package/skills/gsd-path-docs-audit/references/operating-rules.md +14 -5
  165. package/skills/gsd-path-docs-audit/references/runtime-selection.md +10 -0
  166. package/skills/gsd-path-docs-audit/scripts/_common.py +150 -1
  167. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +62 -30
  168. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +92 -30
  169. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +27 -19
  170. package/skills/gsd-path-docs-audit/scripts/detect_project.py +17 -2
  171. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
  172. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +7 -6
  173. package/skills/gsd-path-docs-audit/scripts/integration.py +115 -68
  174. package/skills/gsd-path-docs-audit/scripts/isolation.py +192 -23
  175. package/skills/gsd-path-docs-audit/scripts/members.py +68 -25
  176. package/skills/gsd-path-docs-audit/scripts/model_policy.py +13 -2
  177. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +87 -35
  178. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +31 -7
  179. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -3
  180. package/skills/gsd-path-docs-audit/scripts/roadmap.py +9 -1
  181. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +30 -9
  182. package/skills/gsd-path-docs-audit/scripts/state_promote.py +92 -5
  183. package/skills/gsd-path-forensics/references/operating-rules.md +14 -5
  184. package/skills/gsd-path-forensics/references/runtime-selection.md +10 -0
  185. package/skills/gsd-path-forensics/scripts/_common.py +150 -1
  186. package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
  187. package/skills/gsd-path-forensics/scripts/model_policy.py +13 -2
  188. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
  189. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +17 -8
  190. package/skills/gsd-path-inspect/references/model-policy.md +3 -1
  191. package/skills/gsd-path-inspect/references/operating-rules.md +14 -5
  192. package/skills/gsd-path-inspect/references/runtime-selection.md +10 -0
  193. package/skills/gsd-path-inspect/scripts/_common.py +150 -1
  194. package/skills/gsd-path-inspect/scripts/build_recovery.py +62 -30
  195. package/skills/gsd-path-inspect/scripts/check_handoffs.py +92 -30
  196. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +27 -19
  197. package/skills/gsd-path-inspect/scripts/detect_project.py +17 -2
  198. package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
  199. package/skills/gsd-path-inspect/scripts/discussion_validate.py +7 -6
  200. package/skills/gsd-path-inspect/scripts/integration.py +115 -68
  201. package/skills/gsd-path-inspect/scripts/isolation.py +192 -23
  202. package/skills/gsd-path-inspect/scripts/members.py +68 -25
  203. package/skills/gsd-path-inspect/scripts/model_policy.py +13 -2
  204. package/skills/gsd-path-inspect/scripts/pipeline_git.py +87 -35
  205. package/skills/gsd-path-inspect/scripts/pipeline_state.py +31 -7
  206. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -3
  207. package/skills/gsd-path-inspect/scripts/roadmap.py +9 -1
  208. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +30 -9
  209. package/skills/gsd-path-inspect/scripts/state_promote.py +92 -5
  210. package/skills/gsd-path-inspect/scripts/workflow_run.py +9 -2
  211. package/skills/gsd-path-inspect/templates/state.md +1 -0
  212. package/skills/gsd-path-loop/references/operating-rules.md +14 -5
  213. package/skills/gsd-path-loop/references/runtime-selection.md +10 -0
  214. package/skills/gsd-path-loop/scripts/_common.py +150 -1
  215. package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
  216. package/skills/gsd-path-loop/scripts/model_policy.py +13 -2
  217. package/skills/gsd-path-migrate/SKILL.md +8 -0
  218. package/skills/gsd-path-migrate/references/operating-rules.md +14 -5
  219. package/skills/gsd-path-migrate/references/runtime-selection.md +10 -0
  220. package/skills/gsd-path-migrate/scripts/_common.py +150 -1
  221. package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
  222. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -0
  223. package/skills/gsd-path-migrate/scripts/model_policy.py +13 -2
  224. package/skills/gsd-path-plan/SKILL.md +6 -2
  225. package/skills/gsd-path-plan/references/build-recovery.md +13 -0
  226. package/skills/gsd-path-plan/references/model-policy.md +3 -1
  227. package/skills/gsd-path-plan/references/operating-rules.md +14 -5
  228. package/skills/gsd-path-plan/references/planner.md +13 -1
  229. package/skills/gsd-path-plan/references/runtime-selection.md +10 -0
  230. package/skills/gsd-path-plan/scripts/_common.py +150 -1
  231. package/skills/gsd-path-plan/scripts/build_recovery.py +62 -30
  232. package/skills/gsd-path-plan/scripts/check_handoffs.py +92 -30
  233. package/skills/gsd-path-plan/scripts/check_task_briefs.py +27 -19
  234. package/skills/gsd-path-plan/scripts/detect_project.py +17 -2
  235. package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
  236. package/skills/gsd-path-plan/scripts/discussion_validate.py +7 -6
  237. package/skills/gsd-path-plan/scripts/integration.py +115 -68
  238. package/skills/gsd-path-plan/scripts/isolation.py +192 -23
  239. package/skills/gsd-path-plan/scripts/members.py +68 -25
  240. package/skills/gsd-path-plan/scripts/model_policy.py +13 -2
  241. package/skills/gsd-path-plan/scripts/pipeline_git.py +87 -35
  242. package/skills/gsd-path-plan/scripts/pipeline_state.py +31 -7
  243. package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -3
  244. package/skills/gsd-path-plan/scripts/roadmap.py +9 -1
  245. package/skills/gsd-path-plan/scripts/state_checkpoint.py +30 -9
  246. package/skills/gsd-path-plan/scripts/state_promote.py +92 -5
  247. package/skills/gsd-path-plan/scripts/workflow_run.py +9 -2
  248. package/skills/gsd-path-plan/templates/task.md +1 -0
  249. package/skills/gsd-path-research/references/model-policy.md +3 -1
  250. package/skills/gsd-path-research/references/operating-rules.md +14 -5
  251. package/skills/gsd-path-research/references/runtime-selection.md +10 -0
  252. package/skills/gsd-path-research/scripts/_common.py +150 -1
  253. package/skills/gsd-path-research/scripts/build_recovery.py +62 -30
  254. package/skills/gsd-path-research/scripts/check_handoffs.py +92 -30
  255. package/skills/gsd-path-research/scripts/check_task_briefs.py +27 -19
  256. package/skills/gsd-path-research/scripts/detect_project.py +17 -2
  257. package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
  258. package/skills/gsd-path-research/scripts/discussion_validate.py +7 -6
  259. package/skills/gsd-path-research/scripts/integration.py +115 -68
  260. package/skills/gsd-path-research/scripts/isolation.py +192 -23
  261. package/skills/gsd-path-research/scripts/members.py +68 -25
  262. package/skills/gsd-path-research/scripts/model_policy.py +13 -2
  263. package/skills/gsd-path-research/scripts/pipeline_git.py +87 -35
  264. package/skills/gsd-path-research/scripts/pipeline_state.py +31 -7
  265. package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -3
  266. package/skills/gsd-path-research/scripts/roadmap.py +9 -1
  267. package/skills/gsd-path-research/scripts/state_checkpoint.py +30 -9
  268. package/skills/gsd-path-research/scripts/state_promote.py +92 -5
  269. package/skills/gsd-path-roadmap/SKILL.md +16 -6
  270. package/skills/gsd-path-roadmap/references/model-policy.md +3 -1
  271. package/skills/gsd-path-roadmap/references/operating-rules.md +14 -5
  272. package/skills/gsd-path-roadmap/references/runtime-selection.md +10 -0
  273. package/skills/gsd-path-roadmap/scripts/_common.py +150 -1
  274. package/skills/gsd-path-roadmap/scripts/build_recovery.py +62 -30
  275. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +92 -30
  276. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +27 -19
  277. package/skills/gsd-path-roadmap/scripts/detect_project.py +17 -2
  278. package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
  279. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +7 -6
  280. package/skills/gsd-path-roadmap/scripts/integration.py +115 -68
  281. package/skills/gsd-path-roadmap/scripts/isolation.py +192 -23
  282. package/skills/gsd-path-roadmap/scripts/members.py +68 -25
  283. package/skills/gsd-path-roadmap/scripts/model_policy.py +13 -2
  284. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +87 -35
  285. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +31 -7
  286. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -3
  287. package/skills/gsd-path-roadmap/scripts/roadmap.py +9 -1
  288. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +30 -9
  289. package/skills/gsd-path-roadmap/scripts/state_promote.py +92 -5
  290. package/skills/gsd-path-ship/SKILL.md +38 -11
  291. package/skills/gsd-path-ship/references/model-policy.md +3 -1
  292. package/skills/gsd-path-ship/references/operating-rules.md +14 -5
  293. package/skills/gsd-path-ship/references/reviewer-final.md +5 -5
  294. package/skills/gsd-path-ship/references/reviewer-wave.md +46 -13
  295. package/skills/gsd-path-ship/references/runtime-selection.md +10 -0
  296. package/skills/gsd-path-ship/scripts/_common.py +150 -1
  297. package/skills/gsd-path-ship/scripts/build_recovery.py +62 -30
  298. package/skills/gsd-path-ship/scripts/build_state.py +195 -5
  299. package/skills/gsd-path-ship/scripts/check_handoffs.py +92 -30
  300. package/skills/gsd-path-ship/scripts/check_task_briefs.py +27 -19
  301. package/skills/gsd-path-ship/scripts/detect_project.py +17 -2
  302. package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
  303. package/skills/gsd-path-ship/scripts/discussion_validate.py +7 -6
  304. package/skills/gsd-path-ship/scripts/integration.py +115 -68
  305. package/skills/gsd-path-ship/scripts/isolation.py +192 -23
  306. package/skills/gsd-path-ship/scripts/members.py +68 -25
  307. package/skills/gsd-path-ship/scripts/model_policy.py +13 -2
  308. package/skills/gsd-path-ship/scripts/pipeline_git.py +87 -35
  309. package/skills/gsd-path-ship/scripts/pipeline_state.py +31 -7
  310. package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -3
  311. package/skills/gsd-path-ship/scripts/roadmap.py +9 -1
  312. package/skills/gsd-path-ship/scripts/state_checkpoint.py +30 -9
  313. package/skills/gsd-path-ship/scripts/state_promote.py +92 -5
  314. package/skills/gsd-path-ship/scripts/workflow_run.py +9 -2
  315. package/skills/gsd-path-ship/templates/final-review.md +5 -5
  316. package/skills/gsd-path-ship/templates/wave-review.md +18 -9
  317. package/skills/gsd-path-undo/references/operating-rules.md +14 -5
  318. package/skills/gsd-path-undo/references/runtime-selection.md +10 -0
  319. package/skills/gsd-path-undo/scripts/_common.py +150 -1
  320. package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
  321. package/skills/gsd-path-undo/scripts/model_policy.py +13 -2
  322. package/skills/gsd-path-undo/scripts/pipeline_undo.py +17 -8
  323. package/skills/path/DEFINE.md +6 -2
  324. package/skills/path/PLAN.md +6 -2
  325. package/skills/path/ROADMAP.md +16 -6
  326. package/skills/path/SHIP.md +38 -11
  327. package/skills/path/SKILL.md +52 -24
  328. package/skills/path/references/build-native.md +96 -9
  329. package/skills/path/references/build-recovery.md +13 -0
  330. package/skills/path/references/coder.md +7 -3
  331. package/skills/path/references/model-policy.md +3 -1
  332. package/skills/path/references/operating-rules.md +14 -5
  333. package/skills/path/references/planner.md +13 -1
  334. package/skills/path/references/reviewer-final.md +5 -5
  335. package/skills/path/references/reviewer-wave.md +46 -13
  336. package/skills/path/references/runtime-selection.md +10 -0
  337. package/skills/path/scripts/_common.py +150 -1
  338. package/skills/path/scripts/build_recovery.py +62 -30
  339. package/skills/path/scripts/build_state.py +195 -5
  340. package/skills/path/scripts/check_handoffs.py +92 -30
  341. package/skills/path/scripts/check_task_briefs.py +27 -19
  342. package/skills/path/scripts/detect_project.py +17 -2
  343. package/skills/path/scripts/discussion_records.py +9 -1
  344. package/skills/path/scripts/discussion_validate.py +7 -6
  345. package/skills/path/scripts/dispatch_driver.py +174 -36
  346. package/skills/path/scripts/git_guard.py +42 -7
  347. package/skills/path/scripts/integration.py +115 -68
  348. package/skills/path/scripts/isolation.py +192 -23
  349. package/skills/path/scripts/members.py +68 -25
  350. package/skills/path/scripts/model_policy.py +13 -2
  351. package/skills/path/scripts/pipeline_git.py +87 -35
  352. package/skills/path/scripts/pipeline_state.py +31 -7
  353. package/skills/path/scripts/promote_lookahead.py +3 -3
  354. package/skills/path/scripts/review_findings.py +180 -44
  355. package/skills/path/scripts/roadmap.py +9 -1
  356. package/skills/path/scripts/state_checkpoint.py +30 -9
  357. package/skills/path/scripts/state_promote.py +92 -5
  358. package/skills/path/scripts/task_context.py +2 -0
  359. package/skills/path/scripts/workflow_run.py +9 -2
  360. package/skills/path/templates/final-review.md +5 -5
  361. package/skills/path/templates/state.md +1 -0
  362. package/skills/path/templates/task.md +1 -0
  363. package/skills/path/templates/wave-review.md +18 -9
@@ -343,6 +343,67 @@ def _completed_promotion(
343
343
  }
344
344
 
345
345
 
346
+ def _ruling_rows(text: str) -> dict[str, list[str]]:
347
+ """Map Queue # to the cells of each `## User rulings` table row."""
348
+ rows: dict[str, list[str]] = {}
349
+ in_rulings = False
350
+ for line in text.splitlines():
351
+ if line == "## User rulings":
352
+ in_rulings = True
353
+ continue
354
+ if in_rulings and line.startswith("## "):
355
+ break
356
+ if in_rulings and line.lstrip().startswith("|"):
357
+ cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
358
+ if len(cells) == 4 and cells[0].isdigit():
359
+ rows[cells[0]] = cells
360
+ return rows
361
+
362
+
363
+ def _superseded_carry_forward(
364
+ repo: Path,
365
+ active_state: PipelineState,
366
+ source: Path,
367
+ destination: Path,
368
+ ) -> bool:
369
+ """True when active research is only the shipped DOCS-AUDIT carry-forward
370
+ and the lookahead audit keeps every one of its rulings."""
371
+ if not active_state.archive or destination.is_symlink() or not destination.is_dir():
372
+ return False
373
+ archived = repo / active_state.archive / "research" / "DOCS-AUDIT.md"
374
+ active = destination / "DOCS-AUDIT.md"
375
+ lookahead = source / "DOCS-AUDIT.md"
376
+ entries = [
377
+ path for path in destination.iterdir() if not state_checkpoint._is_junk_file(path)
378
+ ]
379
+ if entries != [active] or any(
380
+ path.is_symlink() or not path.is_file() for path in (archived, active, lookahead)
381
+ ):
382
+ return False
383
+ carried = active.read_bytes()
384
+ if carried != archived.read_bytes():
385
+ return False
386
+ rulings = _ruling_rows(carried.decode("utf-8"))
387
+ if not any(cells[3] == "no" for cells in rulings.values()):
388
+ return False
389
+ kept = _ruling_rows(lookahead.read_text(encoding="utf-8"))
390
+ for number, cells in rulings.items():
391
+ other = kept.get(number)
392
+ # Plan may record a T### for a pending ruling; nothing else may change.
393
+ if (
394
+ other is None
395
+ or other[:3] != cells[:3]
396
+ or (
397
+ other[3] != cells[3]
398
+ and not (cells[3] == "no" and re.fullmatch(r"T\d{3}", other[3]))
399
+ )
400
+ ):
401
+ raise PipelineStateError(
402
+ f"lookahead DOCS-AUDIT.md does not keep carry-forward ruling #{number}"
403
+ )
404
+ return True
405
+
406
+
346
407
  def _prepare_promotion(
347
408
  repo: Path,
348
409
  project: Path,
@@ -377,17 +438,22 @@ def _prepare_promotion(
377
438
  head = pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
378
439
  if head != base:
379
440
  raise PipelineStateError(f"promotion must start at base SHA: {head} != {base}")
380
- remote = pipeline_state._run_git(repo, "rev-parse", "--verify", "origin/main^{commit}").stdout.strip()
441
+ active_state, active_text, _ = pipeline_state.load_state(repo)
442
+ remote_default = f"origin/{active_state.default_branch}"
443
+ remote = pipeline_state._run_git(
444
+ repo, "rev-parse", "--verify", f"{remote_default}^{{commit}}"
445
+ ).stdout.strip()
381
446
  if remote != base:
382
447
  raise PipelineStateError(
383
- f"base SHA is not current origin/main: {base} != {remote}"
448
+ f"base SHA is not current {remote_default}: {base} != {remote}"
384
449
  )
385
450
  if not pipeline_state._is_ancestor(repo, landing, base):
386
- raise PipelineStateError("landing is not an ancestor of the current main base")
451
+ raise PipelineStateError(
452
+ f"landing is not an ancestor of the current {active_state.default_branch} base"
453
+ )
387
454
  if pipeline_state._run_git(repo, "status", "--porcelain", "--untracked-files=all").stdout:
388
455
  raise PipelineStateError("promotion must start from a clean worktree")
389
456
 
390
- active_state, active_text, _ = pipeline_state.load_state(repo)
391
457
  if active_state.phase != "shipped" or active_state.status != "done":
392
458
  raise PipelineStateError("promotion requires active STATE shipped/done")
393
459
  if active_state.milestone is None:
@@ -456,6 +522,12 @@ def _prepare_promotion(
456
522
  raise PipelineStateError(
457
523
  "lookahead integration_default does not match the active project"
458
524
  )
525
+ if next_state.default_branch != active_state.default_branch:
526
+ raise PipelineStateError(
527
+ "lookahead default_branch does not match the active project: copy "
528
+ f"`default_branch: {active_state.default_branch}` from .project/STATE.md "
529
+ "into .project/next/STATE.md"
530
+ )
459
531
  if next_state.milestone != milestone:
460
532
  raise PipelineStateError(
461
533
  f"requested milestone {milestone} != next STATE milestone {next_state.milestone}"
@@ -464,12 +536,17 @@ def _prepare_promotion(
464
536
  raise PipelineStateError("lookahead STATE branch and archive must be null")
465
537
 
466
538
  tracks: dict[str, str] = {}
539
+ carry_forward: Optional[str] = None
467
540
  for name in PROMOTION_TRACKS:
468
541
  source = next_root / name
469
542
  destination = project / name
470
543
  if source.exists() or source.is_symlink():
471
544
  if destination.exists() or destination.is_symlink():
472
- raise PipelineStateError(f"promotion destination already exists: {destination}")
545
+ if name != "research" or not _superseded_carry_forward(
546
+ repo, active_state, source, destination
547
+ ):
548
+ raise PipelineStateError(f"promotion destination already exists: {destination}")
549
+ carry_forward = state_checkpoint._tree_digest(destination)
473
550
  tracks[name] = state_checkpoint._tree_digest(source)
474
551
  if not tracks:
475
552
  raise PipelineStateError("lookahead track has no promotable artifacts")
@@ -496,6 +573,7 @@ def _prepare_promotion(
496
573
  "landing": landing,
497
574
  "base": base,
498
575
  "tracks": tracks,
576
+ "carry_forward_sha256": carry_forward,
499
577
  "residual_sha256": state_checkpoint._tree_digest(next_root, tuple(tracks)),
500
578
  "next_state_sha256": state_checkpoint._sha256(next_text),
501
579
  "active_state_sha256": state_checkpoint._sha256(active_text),
@@ -548,6 +626,15 @@ def _resume_track_moves(project: Path, journal: dict[str, object], path: Path) -
548
626
  destination = project / name
549
627
  source_exists = source.exists() or source.is_symlink()
550
628
  destination_exists = destination.exists() or destination.is_symlink()
629
+ carry_forward = journal.get("carry_forward_sha256")
630
+ if name == "research" and source_exists and destination_exists and carry_forward:
631
+ if state_checkpoint._tree_digest(destination) != carry_forward:
632
+ raise PipelineStateError("DOCS-AUDIT carry-forward drifted during promotion")
633
+ # The archive and the ship commit keep the carry-forward copy.
634
+ shutil.rmtree(destination)
635
+ journal["stage"] = "removed:carry-forward"
636
+ pipeline_state._write_json(path, journal)
637
+ destination_exists = False
551
638
  if source_exists and destination_exists:
552
639
  raise PipelineStateError(f"promotion has both source and destination: {name}")
553
640
  if not source_exists and not destination_exists:
@@ -267,7 +267,11 @@ The persisted `STATE.archive` field is the transaction identity.
267
267
  `python3 <absolute archive_milestone.py> close-members --repo <root>`
268
268
  after preflight passes and before recording shipment. It closes each
269
269
  locked member in lock order in that member's integration mode and
270
- journals each one. When it returns `awaiting-merge`, report the member PR
270
+ journals each one. Each member closes onto the default branch that
271
+ MEMBERS.md records for it (`main` when its section has no
272
+ `Default branch`); when the command reports
273
+ `remote default must be <recorded>`, stop and report it. When it returns
274
+ `awaiting-merge`, report the member PR
271
275
  link, stop, and rerun the same command after the user merges it; never
272
276
  merge a member PR yourself. When it returns `integrated`, keep its `body`
273
277
  for the ship commit below.
@@ -310,8 +314,10 @@ The persisted `STATE.archive` field is the transaction identity.
310
314
  milestone is not shipped until integration below passes.
311
315
  7. Integrate only after the postcommit `validate` passes, with the recorded
312
316
  ship commit and exact reviewed HEAD unchanged. Run `python3 <absolute archive_milestone.py> integrate --repo <root> --slug <STATE.milestone>`.
313
- This helper validates the ship commit, fetches origin, requires remote
314
- default `main`, and follows the locked `STATE.integration` mode:
317
+ This helper validates the ship commit, fetches origin, requires the remote
318
+ default to equal STATE `default_branch` (`main` when the field is absent),
319
+ and follows the locked `STATE.integration` mode. `main` and `origin/main`
320
+ below mean that recorded branch:
315
321
 
316
322
  - `direct` owns the resumable merge transaction. It creates the canonical
317
323
  named integration worktree, performs the hook-verified `--no-ff` merge,
@@ -335,15 +341,36 @@ The persisted `STATE.archive` field is the transaction identity.
335
341
  PR is open, the helper returns `status: awaiting-merge`. Present
336
342
  **Outcome**, link
337
343
  the returned PR as **Review**, and state in **Next** that the user must
338
- merge it with GitHub's merge-commit method. Stop this ship invocation;
339
- Path never enables auto-merge or merges the PR. On rerun after a human
340
- GitHub user merges it, the helper requires the PR head to remain the ship
341
- commit and its landing to be a two-parent merge with that ship commit as
342
- second parent on `origin/main` first-parent history. It then writes and
343
- pushes the annotated milestone tag containing the PR URL, ship SHA, and
344
- landing SHA. A closed unmerged PR, squash, rebase, merge queue, moved
344
+ merge it with GitHub's merge-commit or rebase method. Stop this ship
345
+ invocation; Path never enables auto-merge or merges the PR. On rerun after
346
+ a human GitHub user merges it, the helper requires the PR head to remain
347
+ the ship commit and its landing to be on `origin/main` first-parent
348
+ history as either a two-parent merge with that ship commit as second
349
+ parent, or a rebase merge: the last of a first-parent run of single-parent
350
+ commits whose `git patch-id --stable` values match, in order, the
351
+ non-merge commits from the landing to the ship commit (with equal trees
352
+ when `main` did not move). It then writes and pushes the annotated
353
+ milestone tag containing the PR URL, ship SHA, and landing SHA. A
354
+ milestone that was rebase-merged before this support existed recovers by
355
+ rerunning ship. A closed unmerged PR, squash, merge queue, moved
345
356
  head, duplicate PR, or non-GitHub.com origin blocks. The remote bound
346
357
  branch may be absent after a valid merge because GitHub may auto-delete it.
358
+ - `external-landing` is for an origin where Path cannot open the review
359
+ itself, such as GitLab, Gitea, or GitHub Enterprise Server. It needs no
360
+ forge CLI. The helper publishes the bound branch at the exact ship commit,
361
+ never pushes `main`, and returns `status: awaiting-merge` with `branch`,
362
+ `merge_subject`, and `merge_body`. Present **Outcome**, and state in
363
+ **Next** that the user must open a merge request from `branch` to `main`
364
+ in their own forge and merge it with a merge commit. The merge commit
365
+ message must be exactly `merge_subject`, a blank line, then `merge_body`;
366
+ show both values verbatim in a code block. Stop this ship invocation;
367
+ Path never opens or merges the request. The forge may delete the source
368
+ branch after the merge. On rerun after the merge, the helper requires exactly
369
+ one commit with that subject on `origin/main` first-parent history, that
370
+ exact body, and two parents with the ship commit as second parent. It
371
+ then writes and pushes the annotated milestone tag containing the ship
372
+ SHA and landing SHA. A squash, rebase, fast-forward, or different merge
373
+ message blocks.
347
374
 
348
375
  A passing run returns the same proof exposed by `validate-integrated`.
349
376
  A non-zero result blocks; rerun the exact `integrate` command to resume a
@@ -354,7 +381,7 @@ The persisted `STATE.archive` field is the transaction identity.
354
381
  continuing. Never merge into or re-ship the closed branch.
355
382
  For a later validation recheck with origin network access, run
356
383
  `python3 <absolute archive_milestone.py> validate-integrated --repo <root> --slug <STATE.milestone>`.
357
- Both integration modes refresh the remote-default and mirrored milestone-tag
384
+ All integration modes refresh the remote-default and mirrored milestone-tag
358
385
  refs when an `origin` remote exists. Without that remote, validation resolves
359
386
  the existing remote-default refs locally, preserving offline direct-mode
360
387
  fixtures. PR mode also revalidates the PR identity, state, merge provenance,
@@ -70,7 +70,9 @@ settings block; their default inheritance remains usable.
70
70
  Resolve the bundled helper to an absolute path. Use one record under the Git
71
71
  common directory's `gsd-path/model-selection/` directory per assignment. Scope
72
72
  the path and `--scope` to the milestone, active/lookahead track, and review cycle
73
- where applicable; later work must not reuse an earlier record.
73
+ where applicable; later work must not reuse an earlier record. The helper
74
+ refuses a relative `--record`, and `release` of a missing record names the
75
+ path and creates nothing.
74
76
 
75
77
  ```text
76
78
  python3 -B <helper> resolve --repo <primary> --scope <milestone/track/cycle> --assignment <logical-name> --role <role> --record <absolute-record> --capabilities <advertised-json>
@@ -38,8 +38,8 @@ bundles this file; spawned agents follow their role brief instead.
38
38
  the lookahead planning track for the next milestone; it follows the same
39
39
  marker rule, owns no task frontmatter, and never binds a branch.
40
40
  - STATE.integration_default is the project closeout choice and
41
- STATE.integration is the current milestone choice. Both are `direct` or
42
- `pull-request`; STATE.integration_source is `default` or `milestone` and
41
+ STATE.integration is the current milestone choice. Both are `direct`,
42
+ `pull-request`, or `external-landing`; STATE.integration_source is `default` or `milestone` and
43
43
  preserves explicit override provenance. Older v2 state defaults to `direct`,
44
44
  and modes may be changed only through `pipeline_state.py
45
45
  configure-integration` before build. A next milestone resets its current
@@ -82,7 +82,9 @@ bundles this file; spawned agents follow their role brief instead.
82
82
  `isolation.py attest` may stand in for it: one `.project/`-only commit that
83
83
  names the base, the attested HEAD, the declared files that changed, the
84
84
  task Verify recorded as passing at that HEAD, and the ruling verbatim.
85
- `recover` reports it as `attested`, never as a proven landing.
85
+ `recover` reports it as `attested`, never as a proven landing. Attestation
86
+ matches the declared files literally: work that is only below a declared
87
+ directory can land but cannot be attested.
86
88
  - All pipeline work for a milestone lives on `gsd-path/M00N` (M001, M002,
87
89
  …) bound in STATE.branch. The next milestone binds a new unused
88
90
  `gsd-path/M00N` at the remote default after the previous ship integrates.
@@ -90,6 +92,12 @@ bundles this file; spawned agents follow their role brief instead.
90
92
  branch — either Path's direct merge or a Path-owned PR merged by the user.
91
93
  The bound branch never receives merges or back-merges and is never the
92
94
  GitHub default.
95
+ - The default branch is the one that STATE `default_branch` records at
96
+ initialization; a STATE file without the field means `main`. `main` and
97
+ `origin/main` in these rules mean that branch. Never change the record and
98
+ never take the name from a later `origin/HEAD` lookup. For a member
99
+ repository, the branch is the one that its `.project/MEMBERS.md` section
100
+ records as `Default branch`; a section without the field means `main`.
93
101
  - After initialization writes STATE.md, and before entering the first pipeline
94
102
  phase in an existing Git repository, the router calls
95
103
  `scripts/pipeline_git.py bind-initial` with the selected M00N and exact
@@ -128,8 +136,9 @@ bundles this file; spawned agents follow their role brief instead.
128
136
  for the current STATE.branch milestone must be an ancestor of origin/main;
129
137
  if it is not, that milestone's integration is incomplete and control routes
130
138
  to ship, not build. Direct mode requires the matching canonical `integrate:`
131
- commit; PR mode requires tag metadata and a two-parent merge whose second
132
- parent is the ship commit. A null branch returns
139
+ commit; PR mode requires tag metadata and either a two-parent merge whose
140
+ second parent is the ship commit or a proven rebase merge of the ship
141
+ commits. A null branch returns
133
142
  to the router; build never creates, selects, switches, or rebinds it.
134
143
  - The ship phase makes exactly one commit on the bound branch — the
135
144
  `.project/`-only ship commit recording
@@ -1,11 +1,11 @@
1
1
  ## Final integration mode
2
2
 
3
3
  Use only final-review.md. Treat INTENT.md success criteria as the rubric.
4
- Each `### SCn —` heading must match the criterion text as
5
- `check_handoffs._success_criteria` stores it from INTENT.md `## Success
6
- criteria`: indented continuation lines join with a single space and keep
7
- their `- ` list markers; the gate compares titles with `_normalize_ws`
8
- only. Exercise the running system and record `met`, `not-met`, or
4
+ Each `### SCn —` heading must have every word of the criterion in INTENT.md
5
+ `## Success criteria`: join indented continuation lines and sub-bullets
6
+ with a single space. You can keep or remove the `- ` markers of the
7
+ sub-bullets; the gate compares every other word. Exercise the running
8
+ system and record `met`, `not-met`, or
9
9
  `unverifiable` with checked command output or a precise file reference.
10
10
  When PLAN.md carries
11
11
  a `## Surface contract`, every criterion it lists under a surface is checked
@@ -17,19 +17,21 @@ tasks in this wave as the rubric. For each task:
17
17
  criterion. Never use the primary worktree or branch tip as task
18
18
  evidence. Do not create or remove Git worktrees yourself.
19
19
  4. Mark `pass` or `fail`. Before the evidence separator (` — `), copy the
20
- complete task acceptance or interface criterion verbatim, joining wrapped
21
- lines with spaces. Do not substitute an AC id or summary: finding grouping
22
- binds this text to the task. Give the observed result, `file:line`, and
20
+ complete task acceptance or interface criterion (see Review file format
21
+ below). Do not substitute an AC id or summary: finding grouping binds
22
+ this text to the task. Give the observed result, `file:line`, and
23
23
  concrete fix direction for each failure.
24
24
 
25
+ A Log entry `orchestrator regression Verify of <task id> retired: superseded
26
+ by <task id>` is not evidence: judge the superseding task's acceptance
27
+ criteria. The same applies to the entry `orchestrator regression Verify of
28
+ <fix task id>: part of <task id> retired: superseded by <task id>`.
29
+
25
30
  Then check each INTENT.md success criterion owned by a task in this wave
26
31
  against that same isolated product. In each `### SCn —` heading, copy the
27
- criterion text exactly as `check_handoffs._success_criteria` stores it from
28
- INTENT.md `## Success criteria`: indented continuation lines are joined
29
- with a single space and keep their `- ` list markers in the compared text;
30
- only whitespace is normalized at the gate. Do not paraphrase or drop
31
- sub-bullets. Mark `pass` or `fail` with evidence; a failed owned SC blocks
32
- the wave. Omit the Intent coverage section when no task in the wave owns
32
+ complete criterion text from INTENT.md `## Success criteria` (see Review
33
+ file format below). Mark `pass` or `fail` with evidence; a failed owned SC
34
+ blocks the wave. Omit the Intent coverage section when no task in the wave owns
33
35
  an SC. When INTENT.md `## Edge coverage` or `## Prohibitions` has rows for
34
36
  an owned SC, judge it against those rulings too: cite each `held-out` edge
35
37
  id with its held-out test's recorded output and each `judgment` prohibition
@@ -48,6 +50,30 @@ For cycle greater than 1, read the previous review, re-check every failure,
48
50
  and regression-check affected passes. State when the same criterion fails for
49
51
  a different reason. Use only the wave-review template.
50
52
 
53
+ ### Review file format
54
+
55
+ `scripts/check_handoffs.py wave` rejects a review file that breaks one of
56
+ these rules, and `scripts/review_findings.py` cannot group a failure whose
57
+ criterion text does not match. Apply the rules before you write the file.
58
+
59
+ - **Section order.** Put the `## T### —` sections in task id order (T001
60
+ before T002). The order of the PLAN.md wave table does not apply. A `pass`
61
+ review has one section for each task of the wave.
62
+ - **Section heading.** Write `## T### — <title>: pass` or `: fail`, where
63
+ `<title>` is the exact `title:` value of the task file.
64
+ - **Evidence lines.** An evidence line starts with `- ✅ ` for pass or
65
+ `- ❌ ` for fail. Each task section and each `### SCn —` block needs one
66
+ or more lines with the marker of its own verdict. A failed task needs a
67
+ `- ❌ ` criterion line, also when other text describes the failure.
68
+ - **Criterion text.** In a task evidence line and in a `### SCn —` heading,
69
+ copy every word of the criterion. Join wrapped lines and sub-bullets with
70
+ one space. You can keep or remove the `- ` markers of the sub-bullets.
71
+ A changed, added, removed or moved word does not match.
72
+ - **Angle brackets.** Text such as `<b>` or `<path>` outside backticks reads
73
+ as an unfilled template placeholder and is rejected. Write literal markup
74
+ and code in backticks inside a sentence, for example: the output has
75
+ `<b>hello</b>`.
76
+
51
77
  ### Re-review after a proven repair
52
78
 
53
79
  When the brief supplies a `review_findings.py repair-evidence` receipt, use
@@ -55,14 +81,21 @@ its isolated repair product for the linked original criteria, including owned
55
81
  INTENT criteria. The original faulty landing and earlier verdicts remain
56
82
  historical evidence; they do not force the later verdict to remain failed.
57
83
  Reconstruct the receipt's repair `base` plus only its `commit^..commit` patch
58
- for `files` inside the supplied sidecar. Read the repair task's recorded
59
- Verify, inspect the patch against the original unchanged contract, and reuse
84
+ for `files` inside the supplied sidecar. For a member repair (the receipt
85
+ names `member`), use `member_base` and `member_commit` in that member's
86
+ repository. `earlier_repairs` are linked repairs of the same findings that
87
+ are already in the repair base. When the receipt has `later_repairs`, linked
88
+ repairs changed the same files after this repair: apply their patches in
89
+ order too. Read the repair task's recorded
90
+ Verify, or with `later_repairs` that of the last one, which is the receipt's
91
+ `verification`; inspect the patch against the original unchanged contract, and reuse
60
92
  settled checks. Check unaffected criteria from their prior evidence plus the
61
93
  repair diff for regressions. Cite the receipt, repair landing and task Log in
62
94
  the new review. A receipt proves provenance, not acceptance: judge whether the
63
95
  repair actually satisfies the criterion. A missing or rejected receipt blocks
64
- using that repair; the parent resolves it rather than creating another product
65
- fix for the unchanged historical failure.
96
+ using that repair; the parent resolves it, or briefs this review in full at
97
+ the current HEAD without a receipt, rather than creating another product fix
98
+ for the unchanged historical failure.
66
99
 
67
100
  ## Final scope in a quick full wave
68
101
 
@@ -20,6 +20,16 @@ references to bundled copies of those helpers. Other skill helpers, templates,
20
20
  and role briefs remain in the installed skill bundle. Pass `-B` to Python helper
21
21
  invocations so execution does not create bytecode in the runtime store.
22
22
 
23
+ The selected runtime is authoritative for each helper in its `manifest.json`;
24
+ this includes `review_findings.py` and `task_context.py`. A selected runtime
25
+ that was published before a helper became a runtime file does not list that
26
+ helper: run the bundled copy until the project runtime is upgraded.
27
+ `dispatch_driver.py` and `workflow_run.py` always run from the installed skill
28
+ bundle, because they read role briefs and templates from it; a skill update
29
+ replaces them, a runtime upgrade does not. The driver reloads the helper modules
30
+ that it imports from the selected runtime, and runs the subprocess helpers that
31
+ the runtime lists (such as the fix-task brief lint) from it.
32
+
23
33
  With no declaration, retain the legacy helper lookup. An existing
24
34
  `.gsd-path/runtime/` requires the explicit `--runtime-migrate --project PATH`
25
35
  operation before adopting external storage; review the resulting Git diff.
@@ -25,7 +25,8 @@ import signal
25
25
  import subprocess
26
26
  import time
27
27
  from pathlib import Path
28
- from typing import BinaryIO, Iterator, List, Optional, Sequence
28
+ from typing import BinaryIO, Iterable, Iterator, List, Optional, Sequence, Tuple
29
+
29
30
 
30
31
  # Chosen by what imports, not os.name, so tests can emulate Windows on POSIX.
31
32
  try:
@@ -161,6 +162,33 @@ def project_ignore_error(repo: Path) -> Optional[str]:
161
162
  + ". Anchor the product rule (for example `/build/`) in a task that owns it.")
162
163
 
163
164
 
165
+ _DEFAULT_BRANCH_RE = re.compile(
166
+ r"[^\s#\"'\\~^:?*\[\x00-\x1f\x7f-][^\s#\"'\\~^:?*\[\x00-\x1f\x7f]*"
167
+ )
168
+ _RESERVED_BRANCH_PREFIXES = ("gsd-path/", "gsd-path-integrate/", "gsd-path-task/")
169
+
170
+
171
+ def valid_default_branch(name: str) -> bool:
172
+ """A git branch name that STATE frontmatter can hold and Path does not reserve."""
173
+ return (
174
+ bool(_DEFAULT_BRANCH_RE.fullmatch(name))
175
+ and name != "null"
176
+ and not name.startswith(_RESERVED_BRANCH_PREFIXES)
177
+ and not name.endswith(("/", ".", ".lock"))
178
+ and not any(part in name for part in ("..", "//", "/.", "@{"))
179
+ )
180
+
181
+
182
+ def origin_default_branch(repo: Path) -> str:
183
+ """The origin default branch that initialization records; an unset origin/HEAD means main."""
184
+ result = run_git(repo, "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD")
185
+ prefix = "refs/remotes/origin/"
186
+ remote_ref = result.stdout.strip()
187
+ if result.returncode != 0 or not remote_ref.startswith(prefix):
188
+ return "main"
189
+ return remote_ref.removeprefix(prefix)
190
+
191
+
164
192
  def section_body(text: str, heading: str) -> Optional[str]:
165
193
  """The text under `## <heading>` up to the next `## `, or None when absent."""
166
194
  match = re.search(
@@ -169,6 +197,79 @@ def section_body(text: str, heading: str) -> Optional[str]:
169
197
  return match.group("body") if match else None
170
198
 
171
199
 
200
+ def overlapping_paths(left: Sequence[str], right: Sequence[str]) -> List[str]:
201
+ """Return the sorted paths that two task `files` lists both cover.
202
+
203
+ An entry covers itself and the paths below it. For a declared directory
204
+ and a path below it, the result names the lower path. The callers have no
205
+ task base, so this does not ask if an entry is a file: the task-brief
206
+ lint rejects a path below a file at the base.
207
+ """
208
+ return sorted(
209
+ {
210
+ max(one, other, key=len)
211
+ for one in left
212
+ for other in right
213
+ if one == other or one.startswith(other + "/") or other.startswith(one + "/")
214
+ }
215
+ )
216
+
217
+
218
+ # The `- ` of an indented sub-bullet line. A line that would then read as a
219
+ # numbered item (`- 2. text`) keeps its marker, so both readings of a source
220
+ # have the same criteria.
221
+ SUB_BULLET_MARKER = re.compile(r"(?m)^([ \t]+)-[ \t]+(?![ \t]*\d+\.\s)")
222
+
223
+
224
+ def without_sub_bullet_markers(source: str) -> str:
225
+ """`source` with the `- ` marker of each indented sub-bullet line removed."""
226
+ return SUB_BULLET_MARKER.sub(r"\1", source)
227
+
228
+
229
+ class Criterion(str):
230
+ """A criterion as its parser joins it, with the `- ` markers of its sub-bullet lines.
231
+
232
+ `bare` is the same criterion parsed from `without_sub_bullet_markers(source)`.
233
+ The joined text cannot tell a marker from a minus sign; the source lines can.
234
+ """
235
+
236
+ bare: str
237
+
238
+ def __new__(cls, text: str, bare: str) -> "Criterion":
239
+ criterion = super().__new__(cls, text)
240
+ criterion.bare = bare
241
+ return criterion
242
+
243
+
244
+ def quotes_criterion(quote: str, criterion: str) -> bool:
245
+ """Whether a review quote is `criterion`, for every reader.
246
+
247
+ Only whitespace is collapsed. The quote must equal the criterion with all
248
+ the markers of its sub-bullet lines or with none of them; every other
249
+ character, a hyphen inside a line too, is compared.
250
+ """
251
+ forms = (criterion, getattr(criterion, "bare", criterion))
252
+ return " ".join(quote.split()) in {" ".join(form.split()) for form in forms}
253
+
254
+
255
+ def quoted_criterion(item: str, criteria: Sequence[str]) -> Optional[Tuple[int, int]]:
256
+ """Index of the criterion quoted at the start of `item`, and where the quote ends.
257
+
258
+ A criterion can contain ` — ` itself, so each separator and the end of
259
+ `item` is a candidate, the longest quote first. A criterion as written
260
+ wins over any criterion read without its sub-bullet markers.
261
+ """
262
+ ends = [match.start() for match in re.finditer("(?= —(?: |$))|$", item)][::-1]
263
+ for bare in (False, True):
264
+ for end in ends:
265
+ quote = " ".join(item[:end].split())
266
+ for index, criterion in enumerate(criteria):
267
+ form = getattr(criterion, "bare", criterion) if bare else criterion
268
+ if quote == " ".join(form.split()):
269
+ return index, end
270
+ return None
271
+
272
+
172
273
  def task_verify_command(task_text: str) -> str:
173
274
  """The Verify shell text, excluding the closing fence's separator newline."""
174
275
  body = section_body(task_text, "Verify")
@@ -176,6 +277,11 @@ def task_verify_command(task_text: str) -> str:
176
277
  return block.group("block").removesuffix("\n") if block else ""
177
278
 
178
279
 
280
+ def fix_verify(commands: Iterable[str]) -> str:
281
+ """The Verify of a fix task: each distinct source Verify in its own subshell, under `set -e`."""
282
+ return "set -e\n" + "\n".join(f"(\n{command}\n)" for command in dict.fromkeys(commands))
283
+
284
+
179
285
  def latest_verify_entry(entries: list, command: str, commit: str,
180
286
  repo: Optional[str] = None) -> Optional[dict]:
181
287
  """Rows are keyed by (command, repo, commit); a row without `repo` is a coordinator row."""
@@ -255,6 +361,49 @@ def run_git(
255
361
  return _run_exact(git_command(repo, *arguments), input=input)
256
362
 
257
363
 
364
+ def _patch_ids(repo: Path, commits: Sequence[str]) -> list[Optional[str]]:
365
+ """`git patch-id --stable` per commit, in order; None for an empty commit."""
366
+ log = run_git(repo, "log", "--no-walk=unsorted", "-p", "--binary", "--no-color",
367
+ "--no-ext-diff", *commits)
368
+ if log.returncode != 0:
369
+ raise ValueError("could not read commit patches")
370
+ ids = run_git(repo, "patch-id", "--stable", input=log.stdout)
371
+ if ids.returncode != 0:
372
+ raise ValueError("could not compute patch ids")
373
+ by_commit = {commit: patch for patch, commit in (line.split() for line in ids.stdout.splitlines())}
374
+ return [by_commit.get(commit) for commit in commits]
375
+
376
+
377
+ def rebase_landing_problem(repo: Path, ship: str, landing: str) -> Optional[str]:
378
+ """Why landing is not a forge rebase merge of ship, or None when it is (#418).
379
+
380
+ A rebase merge replays ship's own commits onto the default branch. Proof:
381
+ the first-parent run that ends at landing has one non-merge commit per
382
+ non-merge commit of landing..ship, with equal patch ids in the same order;
383
+ when that run starts at ship's fork point, landing's tree equals ship's.
384
+ """
385
+ listed = run_git(repo, "rev-list", "--reverse", "--topo-order", "--no-merges", f"{landing}..{ship}")
386
+ if listed.returncode != 0:
387
+ return "could not list the ship commits"
388
+ originals = listed.stdout.split()
389
+ if not originals:
390
+ return "landing already contains the ship commit"
391
+ walked = run_git(repo, "rev-list", "--first-parent", "--parents", "-n", str(len(originals)), landing)
392
+ rows = [line.split() for line in walked.stdout.splitlines()][::-1]
393
+ if walked.returncode != 0 or len(rows) != len(originals) or any(len(row) != 2 for row in rows):
394
+ return "landing is not a linear run of rebased commits"
395
+ try:
396
+ if _patch_ids(repo, originals) != _patch_ids(repo, [row[0] for row in rows]):
397
+ return "rebased commits do not replay the ship commits in order"
398
+ except ValueError as error:
399
+ return str(error)
400
+ fork = run_git(repo, "merge-base", ship, landing).stdout.strip()
401
+ trees = run_git(repo, "rev-parse", f"{ship}^{{tree}}", f"{landing}^{{tree}}").stdout.split()
402
+ if rows[0][1] == fork and (len(trees) != 2 or trees[0] != trees[1]):
403
+ return "rebased tree differs from the ship tree"
404
+ return None
405
+
406
+
258
407
  def git_command(repo: Path, *arguments: str) -> tuple[str, ...]:
259
408
  """Git argv for repo, including WSL drvfs workarounds when needed."""
260
409
  return ("git", "-C", str(repo), *git_drvfs_config_flags(repo), *arguments)