@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -41,7 +41,15 @@ const planningWorkspace = require("./planning-workspace.cjs");
41
41
  // `phase_id_convention` reader, from the same §7 owner module `planningPaths`
42
42
  // comes from. Resolved once in `buildPlanningSnapshot` — see the
43
43
  // `phaseIdConvention` field's comment for why one resolution point matters.
44
- const { planningPaths, planningRoot, resolvePhaseIdConvention } = planningWorkspace;
44
+ // #4257: `resolveEnvWorkstream` is that same module's ONE owner of the env
45
+ // workstream discriminator `planningDir` applies — the name W002's scope
46
+ // clause prints comes from the same resolution point that scoped the reads.
47
+ const { planningPaths, planningRoot, resolvePhaseIdConvention, resolveEnvWorkstream } = planningWorkspace;
48
+ // #4257: canonical CommonMark code strippers (markdown-sectionizer is the
49
+ // repo's T0 structural seam, adopted per the #2365 composition order — fenced
50
+ // blocks first, then inline spans) so the `statePhaseTokens` harvest sees
51
+ // PROSE, not quoted literals.
52
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
45
53
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
46
54
  // eslint-disable-next-line @typescript-eslint/no-require-imports
47
55
  const frontmatterMod = require("./frontmatter.cjs");
@@ -109,9 +117,12 @@ function worstScope(...scopes) {
109
117
  * uncorrelated (isPhaseComplete's readability check never re-derives or
110
118
  * requires scanPhasePlans, and vice versa).
111
119
  */
112
- function buildPhaseSnapshot(phasesDir, dir) {
120
+ function buildPhaseSnapshot(phasesDir, dir, convention) {
113
121
  const fullPhaseDir = node_path_1.default.join(phasesDir, dir);
114
- const completionResult = isPhaseComplete(fullPhaseDir);
122
+ // #612: the snapshot's single federated convention resolution rides into
123
+ // completion, so a bracket phase dir resolves and scopes its verification
124
+ // report exactly like its legacy twin.
125
+ const completionResult = isPhaseComplete(fullPhaseDir, { convention });
115
126
  const scanResult = scanPhasePlans(fullPhaseDir);
116
127
  return {
117
128
  dir,
@@ -160,9 +171,11 @@ function buildPhaseSnapshot(phasesDir, dir) {
160
171
  * a whole-body fallback, together.
161
172
  * - `statePhaseTokens` scans the WHOLE document (`verify.cts`'s exact
162
173
  * `PHASE_NUMBER_TOKEN_SOURCE` regex, relocated verbatim from
163
- * `verify.cts:1731-1735`), not just the Current Position section, so it is
164
- * NOT degraded to `TRUNCATED` by a missing section header — it stays
165
- * `COMPLETE` whenever the file itself was read successfully.
174
+ * `verify.cts:1731-1735`; #4257 adds the left word boundary and the
175
+ * fenced-block/inline-span strip — see the harvest site's comment), not
176
+ * just the Current Position section, so it is NOT degraded to `TRUNCATED`
177
+ * by a missing section header — it stays `COMPLETE` whenever the file
178
+ * itself was read successfully.
166
179
  */
167
180
  function buildStateFields(statePath) {
168
181
  let content;
@@ -222,7 +235,30 @@ function buildStateFields(statePath) {
222
235
  scope: currentPositionScope,
223
236
  });
224
237
  const statePhaseTokens = {
225
- value: [...content.matchAll(new RegExp(`[Pp]hase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})`, 'g'))].map((m) => m[1]),
238
+ // #4257: harvest PROSE phase references, not every literal token match.
239
+ // Two precisions over the pre-#4257 verbatim relocation of verify.cts's
240
+ // scan (which was `[Pp]hase\s+(TOKEN)`, unanchored, over the raw file):
241
+ //
242
+ // 1. Strip fenced code blocks, then inline code spans (the #2365
243
+ // composition order, via the canonical markdown-sectionizer seam) —
244
+ // a token inside backticks is a QUOTED LITERAL (a ledger row quoting
245
+ // `` `/gsd-execute-phase 5` `` or `` `- [ ] **Phase 40:` `` from a
246
+ // sibling roadmap), not a reference. Pinned tradeoff: a GENUINE
247
+ // reference written in backticks stops counting too — a quoted
248
+ // literal and a reference are indistinguishable inside a code span.
249
+ // 2. Left word boundary `(?<![-\w])` — the `-phase 5` tail of GSD's own
250
+ // command names (`/gsd-execute-phase 5`, bare or in prose) is a
251
+ // command mention, not a reference, and word-suffixed carriers
252
+ // (`myphase 5`) never were references. `Phase 5` at line start,
253
+ // `**Phase 5:**`, `(Phase 5)`, `[Phase 5]`, and `### Phase 5:` all
254
+ // still harvest — the char before `Phase` is not in `[-\w]`.
255
+ //
256
+ // Still scans the WHOLE document (frontmatter included — the `Phase: 3`
257
+ // field syntax never matched, `phase` is followed by a colon, not `\s`),
258
+ // still `COMPLETE` whenever the file itself was read successfully.
259
+ value: [
260
+ ...(0, markdown_sectionizer_cjs_1.stripInlineCode)((0, markdown_sectionizer_cjs_1.stripFencedCode)(content).text).matchAll(new RegExp(`(?<![-\\w])[Pp]hase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})`, 'g')),
261
+ ].map((m) => m[1]),
226
262
  scope: SCOPE.COMPLETE,
227
263
  };
228
264
  return { currentPhaseLabel, statePhaseTokens, stateStatus };
@@ -632,7 +668,7 @@ function buildRoadmapBracketIncoherencesField(roadmapPath, convention) {
632
668
  * deliberate fail-open match to the pre-migration behavior, not a scope
633
669
  * degradation, since the original never surfaced these failures either.
634
670
  */
635
- function buildResearchValidationStatusField(phasesDir, phaseDirNames, enumerationScope) {
671
+ function buildResearchValidationStatusField(phasesDir, phaseDirNames, enumerationScope, convention) {
636
672
  const value = phaseDirNames.map((dir) => {
637
673
  const fullPhaseDir = node_path_1.default.join(phasesDir, dir);
638
674
  let files;
@@ -646,7 +682,9 @@ function buildResearchValidationStatusField(phasesDir, phaseDirNames, enumeratio
646
682
  // phase-numbered-artifact predicates, so a stray cross-phase
647
683
  // -RESEARCH.md/-VALIDATION.md sitting in the wrong directory cannot flip
648
684
  // this phase's flags — mirrors core-utils.cts's getPhaseFileStats.
649
- const scopedFiles = scopeToPhase(files, dir);
685
+ // #612: the snapshot's federated convention threaded, so a bracket dir
686
+ // scopes by its real token instead of the include-everything fail-safe.
687
+ const scopedFiles = scopeToPhase(files, dir, convention);
650
688
  const researchFile = scopedFiles.find((f) => f.endsWith('-RESEARCH.md'));
651
689
  const hasValidationMd = scopedFiles.some((f) => f.endsWith('-VALIDATION.md'));
652
690
  let hasValidationArchitecture = false;
@@ -963,6 +1001,11 @@ function buildPlanningSnapshot(cwd) {
963
1001
  // See the `phaseIdConvention` field's comment for why one resolution point is
964
1002
  // load-bearing rather than a micro-optimisation.
965
1003
  const phaseIdConvention = resolvePhaseIdConvention(cwd) ?? null;
1004
+ // #4257: the workstream `planningPaths(cwd)` just scoped every read to
1005
+ // (its `planningDir` call applies this exact discriminator when handed no
1006
+ // `ws`), resolved through the same owner so W002's scope clause names the
1007
+ // scope the valid set was ACTUALLY built from.
1008
+ const workstream = resolveEnvWorkstream();
966
1009
  const milestone = getMilestoneInfo(cwd);
967
1010
  // #612: deliberately LEFT to `listMilestonePhaseDirs`'s own lazy resolve —
968
1011
  // this call is byte-identical to upstream's.
@@ -976,9 +1019,14 @@ function buildPlanningSnapshot(cwd) {
976
1019
  // genuinely differ, and substituting one for the other would silently re-scope
977
1020
  // `phaseDirs` — a change this PR does not need and no test covers. The
978
1021
  // federation guarantee PR-2 exists to deliver is delivered where it is
979
- // observable: in the rules that read `snapshot.phaseIdConvention`.
1022
+ // observable: in the rules that read `snapshot.phaseIdConvention`. Per-phase
1023
+ // completion and the research/validation scoping below deliberately use the
1024
+ // FEDERATED `phaseIdConvention` (the same value `snapshot.phaseIdConvention`
1025
+ // publishes), while `phaseDirs` keeps `listMilestonePhaseDirs`'s lazy
1026
+ // PROJECT-only resolve — the divergence documented above is unchanged by
1027
+ // this thread. No behavior change.
980
1028
  const phaseDirs = listMilestonePhaseDirs(paths.phases, { cwd });
981
- const phasesValue = phaseDirs.value.map((dir) => buildPhaseSnapshot(paths.phases, dir));
1029
+ const phasesValue = phaseDirs.value.map((dir) => buildPhaseSnapshot(paths.phases, dir, phaseIdConvention));
982
1030
  const stateFields = buildStateFields(paths.state);
983
1031
  const allPhaseDirNames = buildAllPhaseDirNamesField(paths.phases);
984
1032
  const roadmapDeclared = buildRoadmapDeclaredPhasesField(paths.roadmap, phaseIdConvention);
@@ -1001,7 +1049,7 @@ function buildPlanningSnapshot(cwd) {
1001
1049
  stateStatus: stateFields.stateStatus,
1002
1050
  roadmapDeclaredPhases: roadmapDeclared.declared,
1003
1051
  roadmapPhaseCheckboxes: buildRoadmapPhaseCheckboxesField(paths.roadmap, phaseIdConvention),
1004
- researchValidationStatus: buildResearchValidationStatusField(paths.phases, phaseDirs.value, phaseDirs.scope),
1052
+ researchValidationStatus: buildResearchValidationStatusField(paths.phases, phaseDirs.value, phaseDirs.scope, phaseIdConvention),
1005
1053
  milestoneArchiveStatus: buildMilestoneArchiveStatusField(cwd),
1006
1054
  planningRootFiles: buildPlanningRootFilesField(cwd),
1007
1055
  allPhaseDirNames,
@@ -1013,6 +1061,7 @@ function buildPlanningSnapshot(cwd) {
1013
1061
  phaseIdConvention,
1014
1062
  roadmapSentinelPhaseTokens: roadmapDeclared.sentinelTokens,
1015
1063
  roadmapBracketIncoherences: buildRoadmapBracketIncoherencesField(paths.roadmap, phaseIdConvention),
1064
+ workstream,
1016
1065
  };
1017
1066
  }
1018
1067
  module.exports = {
@@ -100,11 +100,32 @@ const PLANNING_LOCK_RETRY_ERRNOS = new Set([
100
100
  'ENOENT', // Docker overlay-fs: parent dir transiently missing during race
101
101
  'ESTALE', // NFS: stale file handle (self-resolves on retry)
102
102
  ]);
103
+ /**
104
+ * #4257: the ONE owner of the env workstream discriminator `planningDir`
105
+ * itself applies when handed no `ws` argument. `planningPaths(cwd)` — and
106
+ * therefore every workstream-scoped `PlanningSnapshot` read — resolves its
107
+ * base through exactly this read, and the CLI bootstrap has already folded
108
+ * the stored active-workstream pointer into the env by the time any
109
+ * diagnostic runs (`resolveActiveWorkstream` → `applyResolvedWorkstreamEnv`,
110
+ * `active-workstream-store.cjs`). Exposed so a consumer that needs to NAME
111
+ * the scope those reads used (W002's warning message, via the snapshot's
112
+ * `workstream` field) derives it from the same resolution point instead of
113
+ * growing a second env read site that can drift (the #612 PR-2
114
+ * two-readers-two-bases lesson).
115
+ */
116
+ function resolveEnvWorkstream() {
117
+ const value = process.env['GSD_WORKSTREAM']?.trim();
118
+ return value || null;
119
+ }
103
120
  function planningDir(cwd, ws, project) {
104
121
  if (project === undefined)
105
- project = process.env['GSD_PROJECT'] ?? null;
122
+ project = process.env['GSD_PROJECT']?.trim() || null;
123
+ else if (typeof project === 'string')
124
+ project = project.trim() || null;
106
125
  if (ws === undefined)
107
- ws = process.env['GSD_WORKSTREAM'] ?? null;
126
+ ws = resolveEnvWorkstream();
127
+ else if (typeof ws === 'string')
128
+ ws = ws.trim() || null;
108
129
  // Reject path separators and traversal components in project/workstream names
109
130
  const BAD_SEGMENT = /[/\\]|\.\./;
110
131
  if (project && BAD_SEGMENT.test(project)) {
@@ -171,7 +192,7 @@ function worktreesOptedOutUnguarded(cwd) {
171
192
  const scoped = ownKey(readCfg(node_path_1.default.join(planningDir(cwd), 'config.json')));
172
193
  if (scoped.present)
173
194
  return scoped.value === false;
174
- if (process.env['GSD_WORKSTREAM']) {
195
+ if (resolveEnvWorkstream() !== null) {
175
196
  const root = ownKey(readCfg(node_path_1.default.join(planningRoot(cwd), 'config.json')));
176
197
  if (root.present)
177
198
  return root.value === false;
@@ -284,6 +305,31 @@ function listAvailableWorkstreams(cwd) {
284
305
  function quickDirFrom(planningBase) {
285
306
  return node_path_1.default.join(planningBase, 'quick');
286
307
  }
308
+ // #4256: the todos directory — deliberately ROOT-SCOPED, unlike every other
309
+ // planningPaths key. Todos are shared project state by construction: the
310
+ // migrateToWorkstreams contract keeps them among the shared files that "stay
311
+ // in place" at .planning/todos/ (workstream.cts), and every workflow writer
312
+ // writes that literal cwd-relative root path. The six todos readers
313
+ // previously hand-composed `path.join(planningDir(cwd), 'todos', ...)`,
314
+ // which silently re-scoped to .planning/workstreams/<ws>/todos/ — a
315
+ // directory nothing creates — under a workstream, so todos went invisible
316
+ // and audit-open passed the milestone-close gate vacuously. Same
317
+ // two-composers-of-one-path shape the `debug` (#3149) and `quick` (#2142)
318
+ // keys were introduced to eliminate (DEFECT.GENERATIVE-FIX).
319
+ //
320
+ // Exported as its own function pair (not only as a `planningPaths` key)
321
+ // because `audit.cts`'s `scanTodos`/`cmdAuditAcknowledge` consume an
322
+ // already-resolved todos base rather than a `cwd`, mirroring how #2142
323
+ // exported `quickDirFrom` for `scanQuickTasks`. `todosDir` takes NO ws/project
324
+ // parameter — todos have no workstream- or project-scoped form anywhere, so
325
+ // there is no discriminator to thread. This is also the single root #4327's
326
+ // future filename-containment guard should enforce against.
327
+ function todosDirFrom(planningBase) {
328
+ return node_path_1.default.join(planningBase, 'todos');
329
+ }
330
+ function todosDir(cwd) {
331
+ return todosDirFrom(planningRoot(cwd));
332
+ }
287
333
  function planningPaths(cwd, ws) {
288
334
  const base = planningDir(cwd, ws);
289
335
  return {
@@ -300,6 +346,11 @@ function planningPaths(cwd, ws) {
300
346
  debug: node_path_1.default.join(base, 'debug'),
301
347
  // #2142: quick-task directory, composed via the shared quickDirFrom helper.
302
348
  quick: quickDirFrom(base),
349
+ // #4256: todos directory — deliberately ROOT-scoped while the rest of
350
+ // this record follows the active workstream/project (todos are shared
351
+ // project state per the migrateToWorkstreams contract), composed via the
352
+ // shared todosDir helper so this key and every direct caller agree.
353
+ todos: todosDir(cwd),
303
354
  };
304
355
  }
305
356
  /**
@@ -567,10 +618,13 @@ module.exports = {
567
618
  createMemoryPointerAdapter,
568
619
  planningDir,
569
620
  planningRoot,
621
+ resolveEnvWorkstream,
570
622
  resolvePhaseIdConvention,
571
623
  listAvailableWorkstreams,
572
624
  planningPaths,
573
625
  quickDirFrom,
626
+ todosDirFrom,
627
+ todosDir,
574
628
  withPlanningLock,
575
629
  getActiveWorkstream,
576
630
  peekActiveWorkstream,
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * PR-branch `.planning/` path classification patterns — single source of
4
+ * truth (ADR-4910 §8, epic #4906 Phase 5; issues #4605, #4606).
5
+ *
6
+ * `gsd-core/workflows/pr-branch.md` cannot `require()` this module at
7
+ * runtime — a workflow markdown file has no module system, and its bash
8
+ * steps are copy-pasted text executed by whatever shell interprets the
9
+ * workflow. So the three exported constants below are mirrored VERBATIM
10
+ * into that file's `TRANSIENT_DIRS=`, `STRUCTURAL_RE=`, and
11
+ * `MILESTONE_PHASES_RE=` bash variable assignments, and
12
+ * `scripts/lint-pr-branch-pattern-drift.cjs` asserts the mirror never
13
+ * drifts from this file (compiled to `gsd-core/bin/lib/pr-branch-patterns.cjs`)
14
+ * as part of `npm run lint:ci`. Change a value here, then update
15
+ * `gsd-core/workflows/pr-branch.md` to match, or the drift guard fails.
16
+ *
17
+ * This module does not read the filesystem and has no dependencies (a leaf
18
+ * module, mirroring `src/secrets.cts` / `src/planning-scope.cts`).
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.MILESTONE_PHASES_RE_SRC = exports.STRUCTURAL_RE_SRC = exports.TRANSIENT_DIRS_SRC = void 0;
22
+ /**
23
+ * Space-separated list of `.planning/` subdirectory names that are reviewer
24
+ * noise (PLAN.md, SUMMARY.md, CONTEXT.md, RESEARCH.md, and friends) —
25
+ * filtered out of a PR branch in BOTH default and strict mode.
26
+ *
27
+ * Mirrored verbatim into `gsd-core/workflows/pr-branch.md`'s
28
+ * `TRANSIENT_DIRS="..."` assignment.
29
+ */
30
+ exports.TRANSIENT_DIRS_SRC = 'phases quick research threads todos debug seeds codebase ui-reviews';
31
+ /**
32
+ * Structural planning files — repository planning state, preserved in
33
+ * default mode and filtered out in strict mode. Anchored on both
34
+ * alternatives so `.planning/STATEX.md` and `.planning/STATE.md.bak` are
35
+ * NOT treated as structural. The `milestones` alternative matches only
36
+ * FILES directly under `.planning/milestones/` (e.g. `v1.0-ROADMAP.md`) —
37
+ * not a `<milestone>-phases/` subdirectory nested there. That subdirectory
38
+ * is reviewer noise, not structural state (#4605); it falls through to
39
+ * `MILESTONE_PHASES_RE_SRC` below instead.
40
+ *
41
+ * Mirrored verbatim into `gsd-core/workflows/pr-branch.md`'s
42
+ * `STRUCTURAL_RE="..."` assignment.
43
+ */
44
+ exports.STRUCTURAL_RE_SRC = '^\\.planning/(STATE|ROADMAP|MILESTONES|PROJECT|REQUIREMENTS)\\.md$|^\\.planning/milestones/[^/]+\\.md$';
45
+ /**
46
+ * Milestone-scoped phase-plan directories — the same reviewer noise as
47
+ * `TRANSIENT_DIRS_SRC`'s `phases` entry, but nested per-milestone once a
48
+ * project has passed at least one milestone:
49
+ * `.planning/milestones/<milestone>-phases/`. The milestone slug (`v1.0`,
50
+ * `m2`, ...) varies per project, so this is declared as a shape, not a
51
+ * literal path — a single path segment standing in for the slug, anchored
52
+ * the same way `STRUCTURAL_RE_SRC`'s alternatives are (#4605).
53
+ *
54
+ * Mirrored verbatim into `gsd-core/workflows/pr-branch.md`'s
55
+ * `MILESTONE_PHASES_RE="..."` assignment.
56
+ */
57
+ exports.MILESTONE_PHASES_RE_SRC = '^\\.planning/milestones/[^/]+-phases/';
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /**
3
+ * #4145: hash-first recovery for gsd-pristine/ baselines stored at an
4
+ * unexpected path.
5
+ *
6
+ * Some installs hold a pristine snapshot whose SHA-256 equals the hash recorded
7
+ * in backup-meta.json.pristine_hashes for a manifest-keyed file, but at a path
8
+ * that is not `path.join(pristineDir, relPath)` — e.g. stored without the
9
+ * `gsd-core/` top-level segment by an earlier release's writer. Both readers
10
+ * (verify-reapply-patches.cjs verifyFile and install.js saveLocalPatches)
11
+ * resolved strictly by that join, missed the snapshot, and reported
12
+ * ok_no_baseline / fell into regeneration that can never satisfy the recorded
13
+ * outgoing hash — a self-perpetuating gap.
14
+ *
15
+ * Hash equality with the recorded pristine_hashes entry is the same authority
16
+ * the #3657 drift guard already trusts, so a match cannot be the wrong
17
+ * baseline regardless of which release wrote it or where under gsd-pristine/
18
+ * it lives. This module owns the shared scan so the two readers cannot drift
19
+ * apart again (two private strict joins drifting is exactly the bug class).
20
+ *
21
+ * ADR-457: runtime module in src/*.cts, compiled to
22
+ * gsd-core/bin/lib/pristine-baseline.cjs.
23
+ */
24
+ var __importDefault = (this && this.__importDefault) || function (mod) {
25
+ return (mod && mod.__esModule) ? mod : { "default": mod };
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.sha256File = sha256File;
29
+ exports.findPristineByHash = findPristineByHash;
30
+ exports.findPristineInGit = findPristineInGit;
31
+ const node_fs_1 = __importDefault(require("node:fs"));
32
+ const node_path_1 = __importDefault(require("node:path"));
33
+ const node_crypto_1 = __importDefault(require("node:crypto"));
34
+ const node_child_process_1 = require("node:child_process");
35
+ /**
36
+ * SHA-256 hex digest of a file's raw bytes. Byte-for-byte the same digest
37
+ * install.js fileHash() records into manifests and backup-meta.json.
38
+ */
39
+ function sha256File(absPath) {
40
+ return node_crypto_1.default.createHash('sha256').update(node_fs_1.default.readFileSync(absPath)).digest('hex');
41
+ }
42
+ function walkSorted(dir, relPrefix, results) {
43
+ let entries;
44
+ try {
45
+ entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
46
+ }
47
+ catch {
48
+ return; // absent or unreadable — nothing to scan here
49
+ }
50
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
51
+ for (const entry of entries) {
52
+ // Never follow symlinks: gsd-pristine/ is installer-authored plain files;
53
+ // a link here is not a baseline and must not redirect the walk out of the
54
+ // tree (same posture as migration 004's walker).
55
+ if (entry.isSymbolicLink())
56
+ continue;
57
+ const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
58
+ if (entry.isDirectory()) {
59
+ walkSorted(node_path_1.default.join(dir, entry.name), rel, results);
60
+ }
61
+ else if (entry.isFile()) {
62
+ results.push(rel);
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Find the first file under `pristineDir` (deterministic sorted walk) whose
68
+ * SHA-256 equals `recordedHash`, as a pristineDir-relative POSIX path.
69
+ *
70
+ * - `skip` is never returned — a single POSIX relPath string or a Set of them.
71
+ * Callers pass the canonical path(s) they (or other files in the same run)
72
+ * already own, so a file sitting at a canonical path is never adopted
73
+ * through the scan. For the installer's relocation this is what prevents a
74
+ * byte-identical canonical belonging to ANOTHER modified file from being
75
+ * "rescued" away (relocated and deleted at its home path).
76
+ * - Multiple matches are byte-identical by sha-256 authority; sorted order
77
+ * makes the choice deterministic.
78
+ * - Returns null when pristineDir is absent/unreadable or nothing matches.
79
+ */
80
+ function findPristineByHash(pristineDir, recordedHash, skip) {
81
+ if (!pristineDir || typeof recordedHash !== 'string' || recordedHash.length === 0) {
82
+ return null;
83
+ }
84
+ const skipSet = skip instanceof Set ? skip : new Set(skip !== undefined ? [skip] : []);
85
+ const rels = [];
86
+ walkSorted(pristineDir, '', rels);
87
+ for (const rel of rels) {
88
+ if (skipSet.has(rel))
89
+ continue;
90
+ try {
91
+ if (sha256File(node_path_1.default.join(pristineDir, rel)) === recordedHash) {
92
+ return rel;
93
+ }
94
+ }
95
+ catch {
96
+ // unreadable candidate — keep scanning
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * #4135: recover a pristine baseline from the config dir's OWN git history,
103
+ * anchored by the recorded pristine_hashes entry.
104
+ *
105
+ * The #3407 promotion rule keeps only regeneration candidates byte-identical
106
+ * across the whole version span, so a multi-version update leaves
107
+ * gsd-pristine/ holding exactly the files upstream did NOT change — near-zero
108
+ * coverage precisely where upstream churned the most. On a git-managed config
109
+ * dir the outgoing bytes often still exist in history (the workflow's
110
+ * documented Option A), and pristine_hashes is the same authority every other
111
+ * resolution tier trusts: a blob whose SHA-256 equals the recorded hash cannot
112
+ * be the wrong baseline. This is read-only recovery (git log / git show only).
113
+ *
114
+ * Guarantees:
115
+ * - Only an EXACT sha-256 match with the recorded hash is ever returned.
116
+ * - Newest-first commit order (git log default) makes multi-match resolution
117
+ * deterministic; byte-identical matches are interchangeable anyway.
118
+ * - Any failure (git absent, not a repository, empty history, unreadable
119
+ * blob, subprocess timeout) yields null — never a throw — so the caller's
120
+ * OK_NO_BASELINE posture is the universal fallback.
121
+ * - The walk is bounded: at most GIT_MAX_COMMITS_PER_FILE commits per file.
122
+ */
123
+ const GIT_MAX_COMMITS_PER_FILE = 100;
124
+ /** Per-subprocess bound in ms — an unbounded git call is an indefinite hang. */
125
+ const GIT_SUBPROCESS_TIMEOUT_MS = 10_000;
126
+ /** git log --format=%H output cap; 100 full shas are ~4 KB, this is headroom. */
127
+ const GIT_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
128
+ function isCleanRelativePosixPath(relPath) {
129
+ if (!relPath || relPath.startsWith('/') || relPath.includes('\\') || relPath.includes('\0')) {
130
+ return false;
131
+ }
132
+ const segments = relPath.split('/');
133
+ return segments.every((seg) => seg.length > 0 && seg !== '.' && seg !== '..');
134
+ }
135
+ function gitExec(gitDir, args) {
136
+ return (0, node_child_process_1.execFileSync)('git', args, {
137
+ cwd: gitDir,
138
+ encoding: 'utf8',
139
+ timeout: GIT_SUBPROCESS_TIMEOUT_MS,
140
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
141
+ // windowsHide (#685): a console-window flash per git call would spam the
142
+ // user on Windows for what is a background, read-only history walk.
143
+ windowsHide: true,
144
+ // stderr is discarded: "file absent in commit" is an expected walk outcome,
145
+ // not operator-visible diagnostics.
146
+ stdio: ['ignore', 'pipe', 'ignore'],
147
+ });
148
+ }
149
+ function findPristineInGit(gitDir, relPath, recordedHash) {
150
+ if (!gitDir || typeof relPath !== 'string' || typeof recordedHash !== 'string'
151
+ || recordedHash.length === 0 || !isCleanRelativePosixPath(relPath)) {
152
+ return null;
153
+ }
154
+ let commits;
155
+ try {
156
+ const logOutput = gitExec(gitDir, ['log', '--format=%H', '--', relPath]).trim();
157
+ if (!logOutput)
158
+ return null;
159
+ commits = logOutput.split('\n').slice(0, GIT_MAX_COMMITS_PER_FILE);
160
+ }
161
+ catch {
162
+ return null; // git absent, not a repository, or the walk failed
163
+ }
164
+ for (const commit of commits) {
165
+ if (!/^[0-9a-f]{40}$/i.test(commit))
166
+ continue;
167
+ try {
168
+ const blob = gitExec(gitDir, ['show', `${commit}:${relPath}`]);
169
+ if (sha256String(blob) === recordedHash) {
170
+ return blob;
171
+ }
172
+ }
173
+ catch {
174
+ // blob absent in this commit (rename/add boundary) — keep walking
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+ /** sha256 of a utf8 string, matching how manifest hashes are recorded. */
180
+ function sha256String(content) {
181
+ return node_crypto_1.default.createHash('sha256').update(content, 'utf8').digest('hex');
182
+ }
@@ -234,6 +234,12 @@ function analyzeCoverage(items, resolutions = [], validators) {
234
234
  const unresolved = merged.filter((i) => i.status === 'unresolved').length;
235
235
  const applicable = merged.length;
236
236
  const resolved = applicable - unresolved; // closed set: resolved-status + dismissed
237
+ // #4656: the unclassified soft-signal rows count toward `applicable` (the
238
+ // rollup is count-preserving and `resolved = applicable - unresolved` is a
239
+ // documented identity), so the count is exposed as a SIBLING field — the
240
+ // zero-applicable guards can then also fire when EVERY requirement is
241
+ // unclassified, the case the spec-phase/ui-phase docs promise to catch.
242
+ const unclassified = merged.filter((i) => i.category === 'unclassified').length;
237
243
  const byVerification = {};
238
244
  for (const tier of validators.verification)
239
245
  byVerification[tier] = 0;
@@ -242,7 +248,7 @@ function analyzeCoverage(items, resolutions = [], validators) {
242
248
  byVerification[i.verification] = (byVerification[i.verification] ?? 0) + 1;
243
249
  }
244
250
  }
245
- return { items: merged, coverage: { applicable, resolved, unresolved, byVerification } };
251
+ return { items: merged, coverage: { applicable, resolved, unresolved, unclassified, byVerification } };
246
252
  }
247
253
  /**
248
254
  * The prohibition adapter's injected runtime validators (ADR-550 #5). There is no closed
@@ -362,6 +362,93 @@ function childEnv() {
362
362
  const NODE_TEST_TIMEOUT_MS = 30_000;
363
363
  const ESLINT_TIMEOUT_MS = 60_000;
364
364
  const CHECK_MAX_BUFFER = 16 * 1024 * 1024;
365
+ /** Windows `taskkill` resolved by ABSOLUTE path — never a bare PATH-resolved name. A project
366
+ * directory used as the child's `cwd` could otherwise contain a planted `taskkill.exe`/`.bat`
367
+ * that Windows executable resolution picks up ahead of the real one (#3660 review, minor-9).
368
+ * Returns null (never a hardcoded fallback, per tests/hardcoded-paths.test.cjs) when neither env
369
+ * var is set -- not expected on a real Windows host (both are set by the OS itself), but a
370
+ * hostile/stripped env should degrade to "skip the reap" rather than guess a system path. */
371
+ function taskkillPath() {
372
+ const root = process.env.SystemRoot || process.env.windir;
373
+ return root ? node_path_1.default.join(root, 'System32', 'taskkill.exe') : null;
374
+ }
375
+ /**
376
+ * Reap the process TREE rooted at `pid` after THIS call's own bound killed it (#3660: `node --test`
377
+ * forks a per-file WORKER by default since Node 22 — `execFileSync`'s `timeout` signals only the
378
+ * direct child/runner, never the worker, which is reparented to PID 1 and can busy-loop forever).
379
+ *
380
+ * POSIX: the child was spawned `detached` (its own process group, pgid === pid), so `-pid` addresses
381
+ * the whole group. ESRCH (group already gone) is swallowed — the subject may have exited on its own
382
+ * between the timeout firing and this call.
383
+ *
384
+ * Windows has no process-group equivalent; `taskkill /PID <pid> /T /F` walks the live process tree by
385
+ * parent-PID instead, which does not require the parent PID to still be alive. A non-zero exit means
386
+ * "nothing left to kill" (already gone, or never had descendants) — not a failure, so it is never
387
+ * escalated; there is no portable stronger primitive to escalate TO.
388
+ *
389
+ * Never throws — this runs from a `catch`/`finally` path and must not itself become the error.
390
+ */
391
+ function reapDescendants(pid) {
392
+ if (typeof pid !== 'number' || pid <= 0)
393
+ return; // defensive: never signal pid 0 (self) or negative
394
+ if (process.platform === 'win32') {
395
+ const exe = taskkillPath();
396
+ if (!exe)
397
+ return; // no safe absolute path available -- best-effort, skip rather than guess
398
+ try {
399
+ // 5s bound (DEFECT.UNBOUNDED-SUBPROCESS): a local OS command, not a network call -- if
400
+ // taskkill itself hangs, this function's own "never throws" contract already treats that
401
+ // identically to any other failure (swallowed below), so a bounded timeout costs nothing
402
+ // and just prevents a stuck taskkill from blocking the caller forever.
403
+ (0, node_child_process_1.spawnSync)(exe, ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true, timeout: 5_000 });
404
+ }
405
+ catch {
406
+ // best-effort: a missing taskkill.exe (or a timeout) is not this call's problem to escalate
407
+ }
408
+ return;
409
+ }
410
+ try {
411
+ process.kill(-pid, 'SIGKILL');
412
+ }
413
+ catch {
414
+ // Swallows ESRCH (group already gone -- nothing to reap) and any other errno (e.g. EPERM) --
415
+ // this helper never throws regardless of cause; see the function's own doc comment above.
416
+ }
417
+ }
418
+ /**
419
+ * `execFileSync`, with descendant reaping layered on top. Same contract (same return value, throws
420
+ * the identical error) EXCEPT that when — and ONLY when — this call's OWN timeout killed the child,
421
+ * any descendants the child forked are also reaped.
422
+ *
423
+ * Gated on `error.code === 'ETIMEDOUT'`, NOT `error.signal`. `signal` is the field Node's own docs
424
+ * describe for this purpose, but it is not reliably populated across platforms/Node versions: on one
425
+ * real Linux CI run (Node 24) a genuine timeout-kill threw `{ signal: null, code: 'ETIMEDOUT',
426
+ * status: 7 }` — `signal` was simply absent, `code` was the only reliable marker (confirmed empirically
427
+ * before landing this; a macOS/Node run separately showed `signal: 'SIGTERM'` for the identical
428
+ * scenario, so neither field alone is safe to rely on everywhere — `code` was the one constant).
429
+ * Gating strictly on the timeout code (rather than reaping on every throw) matters: an ordinary
430
+ * non-zero exit (a real test/lint failure) has no `ETIMEDOUT` code — the child exited on its own, so a
431
+ * reap there would fire on every red run for no reason and, on POSIX, risks signalling a process group
432
+ * whose pgid was *already* recycled by something unrelated in the time since (the #3660 review's
433
+ * Blocker-3 defect in the prior attempt at this fix, PR #3681). Only the timeout-kill path is targeted.
434
+ *
435
+ * Spawns `detached` on POSIX so the reap above can address the whole process group; omitted on
436
+ * Windows (no such flag there — `@types/node`'s `ExecFileSyncOptions` doesn't declare `detached`
437
+ * either, hence the cast below, though libuv honors it identically to `spawnSync`).
438
+ */
439
+ function execFileSyncReaping(file, args, options) {
440
+ const spawnOptions = process.platform === 'win32' ? options : { ...options, detached: true };
441
+ try {
442
+ return (0, node_child_process_1.execFileSync)(file, args, spawnOptions);
443
+ }
444
+ catch (e) {
445
+ const err = e;
446
+ if (typeof err.pid === 'number' && err.code === 'ETIMEDOUT') {
447
+ reapDescendants(err.pid);
448
+ }
449
+ throw e;
450
+ }
451
+ }
365
452
  /** Resolve the effective timeout: only a POSITIVE override is honored — `0` (which Node treats as
366
453
  * "no timeout") or a negative value falls back to the bounded default, so the subprocess is ALWAYS
367
454
  * bounded (a `timeoutMs: 0` injection can never disable the bound). */
@@ -378,7 +465,7 @@ function posTimeout(timeoutMs, def) {
378
465
  */
379
466
  function runNodeTestWithSubject(check, cwd, subject, timeoutMs) {
380
467
  try {
381
- return (0, node_child_process_1.execFileSync)(process.execPath, buildNodeTestArgs(check), {
468
+ return execFileSyncReaping(process.execPath, buildNodeTestArgs(check), {
382
469
  cwd,
383
470
  encoding: 'utf-8',
384
471
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -398,7 +485,7 @@ function defaultRunCheck(check, cwd, timeoutMs) {
398
485
  if (check.kind === 'node-test') {
399
486
  let out = '';
400
487
  try {
401
- out = (0, node_child_process_1.execFileSync)(process.execPath, buildNodeTestArgs(check), {
488
+ out = execFileSyncReaping(process.execPath, buildNodeTestArgs(check), {
402
489
  cwd,
403
490
  encoding: 'utf-8',
404
491
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -422,7 +509,7 @@ function defaultRunCheck(check, cwd, timeoutMs) {
422
509
  return { passed: false }; // eslint not installed -> fail closed, never throw
423
510
  let json = '';
424
511
  try {
425
- json = (0, node_child_process_1.execFileSync)(process.execPath, [eslintCli, ...buildLintArgs(check)], {
512
+ json = execFileSyncReaping(process.execPath, [eslintCli, ...buildLintArgs(check)], {
426
513
  cwd,
427
514
  encoding: 'utf-8',
428
515
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -482,7 +569,7 @@ function defaultProveFailFirst(check, cwd, timeoutMs) {
482
569
  return { provenFailFirst: false }; // eslint not installed -> fail closed, never throw
483
570
  let json = '';
484
571
  try {
485
- json = (0, node_child_process_1.execFileSync)(process.execPath, [eslintCli, ...buildLintArgs({ ...check, target: fixture })], {
572
+ json = execFileSyncReaping(process.execPath, [eslintCli, ...buildLintArgs({ ...check, target: fixture })], {
486
573
  cwd,
487
574
  encoding: 'utf-8',
488
575
  stdio: ['ignore', 'pipe', 'pipe'],