@opengsd/gsd-core 1.12.0 → 1.14.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 (455) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-advisor-researcher.compact.md +85 -0
  5. package/agents/gsd-ai-researcher.compact.md +96 -0
  6. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  7. package/agents/gsd-code-fixer.compact.md +458 -0
  8. package/agents/gsd-code-fixer.md +5 -5
  9. package/agents/gsd-code-reviewer.compact.md +269 -0
  10. package/agents/gsd-code-reviewer.md +15 -3
  11. package/agents/gsd-codebase-mapper.compact.md +760 -0
  12. package/agents/gsd-debug-session-manager.compact.md +345 -0
  13. package/agents/gsd-doc-classifier.compact.md +192 -0
  14. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  15. package/agents/gsd-doc-verifier.compact.md +143 -0
  16. package/agents/gsd-doc-writer.compact.md +440 -0
  17. package/agents/gsd-dom-verifier.compact.md +138 -0
  18. package/agents/gsd-domain-researcher.compact.md +141 -0
  19. package/agents/gsd-eval-auditor.compact.md +160 -0
  20. package/agents/gsd-eval-planner.compact.md +137 -0
  21. package/agents/gsd-executor.md +63 -35
  22. package/agents/gsd-framework-selector.compact.md +82 -0
  23. package/agents/gsd-integration-checker.compact.md +245 -0
  24. package/agents/gsd-intel-updater.compact.md +226 -0
  25. package/agents/gsd-mempalace-curator.compact.md +45 -0
  26. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  27. package/agents/gsd-pattern-mapper.compact.md +275 -0
  28. package/agents/gsd-plan-checker.md +76 -57
  29. package/agents/gsd-planner.md +14 -0
  30. package/agents/gsd-project-researcher.compact.md +587 -0
  31. package/agents/gsd-research-synthesizer.compact.md +212 -0
  32. package/agents/gsd-roadmapper.compact.md +454 -0
  33. package/agents/gsd-roadmapper.md +13 -0
  34. package/agents/gsd-security-auditor.compact.md +162 -0
  35. package/agents/gsd-ui-auditor.compact.md +404 -0
  36. package/agents/gsd-ui-checker.compact.md +277 -0
  37. package/agents/gsd-ui-checker.md +19 -3
  38. package/agents/gsd-ui-researcher.compact.md +282 -0
  39. package/agents/gsd-ui-researcher.md +29 -0
  40. package/agents/gsd-user-profiler.compact.md +108 -0
  41. package/agents/gsd-verifier.md +23 -1
  42. package/bin/install.js +444 -134
  43. package/commands/gsd/cleanup.md +1 -0
  44. package/commands/gsd/code-review.md +2 -1
  45. package/commands/gsd/complete-milestone.md +1 -0
  46. package/commands/gsd/config.md +1 -0
  47. package/commands/gsd/debug.md +1 -0
  48. package/commands/gsd/execute-phase.md +1 -1
  49. package/commands/gsd/graphify.md +1 -0
  50. package/commands/gsd/health.md +1 -0
  51. package/commands/gsd/mempalace-capture.md +1 -0
  52. package/commands/gsd/mempalace-recall.md +1 -0
  53. package/commands/gsd/new-milestone.md +1 -0
  54. package/commands/gsd/new-project.md +1 -0
  55. package/commands/gsd/next.md +1 -0
  56. package/commands/gsd/ns-workflow.md +2 -1
  57. package/commands/gsd/pause-work.md +1 -0
  58. package/commands/gsd/phase.md +2 -1
  59. package/commands/gsd/pr-branch.md +1 -0
  60. package/commands/gsd/quick-batch.md +105 -0
  61. package/commands/gsd/resume-work.md +1 -0
  62. package/commands/gsd/review-backlog.md +1 -0
  63. package/commands/gsd/settings.md +2 -1
  64. package/commands/gsd/stats.md +1 -0
  65. package/commands/gsd/surface.md +18 -8
  66. package/commands/gsd/thread.md +1 -0
  67. package/commands/gsd/workspace.md +1 -0
  68. package/commands/gsd/workstreams.md +1 -0
  69. package/gsd-core/bin/check-latest-version.cjs +8 -3
  70. package/gsd-core/bin/gsd-tools.cjs +532 -174
  71. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  72. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  73. package/gsd-core/bin/lib/audit.cjs +39 -22
  74. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  75. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  76. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  77. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  78. package/gsd-core/bin/lib/capability-registry.cjs +528 -116
  79. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  80. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  81. package/gsd-core/bin/lib/capability-validator.cjs +134 -5
  82. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  83. package/gsd-core/bin/lib/check-command-router.cjs +198 -38
  84. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  85. package/gsd-core/bin/lib/clusters.cjs +1 -0
  86. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  87. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  88. package/gsd-core/bin/lib/commands.cjs +981 -79
  89. package/gsd-core/bin/lib/config-loader.cjs +4 -0
  90. package/gsd-core/bin/lib/config.cjs +153 -38
  91. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  92. package/gsd-core/bin/lib/coverage.cjs +1 -1
  93. package/gsd-core/bin/lib/decisions.cjs +343 -28
  94. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  95. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  96. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  97. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  98. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  99. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  100. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  101. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  102. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  103. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
  104. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  105. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  106. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  107. package/gsd-core/bin/lib/init.cjs +539 -60
  108. package/gsd-core/bin/lib/install-engine.cjs +199 -14
  109. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  110. package/gsd-core/bin/lib/install-profiles.cjs +36 -14
  111. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  112. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  113. package/gsd-core/bin/lib/io.cjs +35 -0
  114. package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
  115. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  116. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  117. package/gsd-core/bin/lib/milestone.cjs +41 -10
  118. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  119. package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
  120. package/gsd-core/bin/lib/phase-id.cjs +412 -31
  121. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  122. package/gsd-core/bin/lib/phase.cjs +941 -98
  123. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
  126. package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
  127. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  128. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  129. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  130. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  131. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  132. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  133. package/gsd-core/bin/lib/research-store.cjs +11 -12
  134. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  135. package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
  136. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  137. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  138. package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
  139. package/gsd-core/bin/lib/roadmap.cjs +292 -69
  140. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
  141. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
  142. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
  143. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
  144. package/gsd-core/bin/lib/security.cjs +126 -7
  145. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  146. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  147. package/gsd-core/bin/lib/state-document.cjs +159 -32
  148. package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
  149. package/gsd-core/bin/lib/state-transition.cjs +465 -62
  150. package/gsd-core/bin/lib/state.cjs +906 -151
  151. package/gsd-core/bin/lib/surface.cjs +83 -10
  152. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  153. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  154. package/gsd-core/bin/lib/uat.cjs +1420 -516
  155. package/gsd-core/bin/lib/update-context.cjs +36 -26
  156. package/gsd-core/bin/lib/validate.cjs +230 -12
  157. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  158. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  159. package/gsd-core/bin/lib/verification.cjs +316 -23
  160. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  161. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  162. package/gsd-core/bin/lib/verify.cjs +531 -36
  163. package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
  164. package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
  165. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  166. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  167. package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
  168. package/gsd-core/references/agent-contracts.md +3 -3
  169. package/gsd-core/references/compact-content-gate.md +66 -0
  170. package/gsd-core/references/edge-probe.md +17 -13
  171. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  172. package/gsd-core/references/execute-phase-response-language.md +6 -0
  173. package/gsd-core/references/executor-examples.md +42 -0
  174. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  175. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  176. package/gsd-core/references/model-profiles.md +12 -3
  177. package/gsd-core/references/mvp-concepts.md +2 -2
  178. package/gsd-core/references/plan-checker-examples.md +41 -0
  179. package/gsd-core/references/planner-antipatterns.md +25 -0
  180. package/gsd-core/references/planner-chunked.md +5 -1
  181. package/gsd-core/references/planner-coupling.md +42 -0
  182. package/gsd-core/references/planner-quick-batch.md +71 -0
  183. package/gsd-core/references/planner-reviews.md +47 -0
  184. package/gsd-core/references/planner-revision.md +75 -2
  185. package/gsd-core/references/planning-config.md +5 -1
  186. package/gsd-core/references/response-language-directive.md +9 -0
  187. package/gsd-core/references/revision-loop.md +118 -11
  188. package/gsd-core/references/tdd.md +17 -9
  189. package/gsd-core/references/thinking-models-planning.md +18 -2
  190. package/gsd-core/references/verification-patterns.md +17 -4
  191. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  192. package/gsd-core/references/worktree-path-safety.md +112 -2
  193. package/gsd-core/templates/README.md +7 -1
  194. package/gsd-core/templates/phase-prompt.md +4 -0
  195. package/gsd-core/templates/state.md +6 -3
  196. package/gsd-core/templates/summary.compact.md +212 -0
  197. package/gsd-core/templates/user-setup.compact.md +199 -0
  198. package/gsd-core/templates/user-setup.md +0 -9
  199. package/gsd-core/templates/verification-report.md +5 -0
  200. package/gsd-core/workflows/add-backlog.md +2 -0
  201. package/gsd-core/workflows/add-phase.md +2 -0
  202. package/gsd-core/workflows/add-tests.md +1 -1
  203. package/gsd-core/workflows/add-todo.md +4 -3
  204. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  205. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  206. package/gsd-core/workflows/audit-fix.md +2 -0
  207. package/gsd-core/workflows/audit-milestone.md +2 -0
  208. package/gsd-core/workflows/audit-uat.md +2 -0
  209. package/gsd-core/workflows/autonomous.md +15 -10
  210. package/gsd-core/workflows/check-todos.md +5 -3
  211. package/gsd-core/workflows/cleanup.md +4 -2
  212. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
  213. package/gsd-core/workflows/code-review-fix.md +5 -3
  214. package/gsd-core/workflows/code-review.md +211 -43
  215. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  216. package/gsd-core/workflows/complete-milestone.md +40 -254
  217. package/gsd-core/workflows/debug.md +1 -1
  218. package/gsd-core/workflows/diagnose-issues.md +5 -1
  219. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  220. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  221. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  222. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  223. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  224. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  225. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  226. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  227. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  228. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  229. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  230. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  231. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  232. package/gsd-core/workflows/discuss-phase.md +1 -1
  233. package/gsd-core/workflows/do.md +43 -13
  234. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  235. package/gsd-core/workflows/docs-update.md +15 -156
  236. package/gsd-core/workflows/edit-phase.md +2 -0
  237. package/gsd-core/workflows/eval-review.md +1 -1
  238. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  239. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
  240. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  241. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
  242. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  243. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  245. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  246. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  247. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  248. package/gsd-core/workflows/execute-phase.md +78 -159
  249. package/gsd-core/workflows/execute-plan.md +28 -15
  250. package/gsd-core/workflows/explore.md +2 -0
  251. package/gsd-core/workflows/extract-learnings.md +2 -0
  252. package/gsd-core/workflows/fast.md +6 -0
  253. package/gsd-core/workflows/forensics.md +2 -0
  254. package/gsd-core/workflows/graduation.md +1 -1
  255. package/gsd-core/workflows/health.md +1 -1
  256. package/gsd-core/workflows/help/modes/brief.md +2 -0
  257. package/gsd-core/workflows/help/modes/default.md +2 -0
  258. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  259. package/gsd-core/workflows/help/modes/full.md +12 -0
  260. package/gsd-core/workflows/help/modes/topic.md +2 -0
  261. package/gsd-core/workflows/help.md +3 -1
  262. package/gsd-core/workflows/import.md +3 -3
  263. package/gsd-core/workflows/inbox.md +1 -1
  264. package/gsd-core/workflows/ingest-docs.md +1 -1
  265. package/gsd-core/workflows/insert-phase.md +2 -0
  266. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  267. package/gsd-core/workflows/list-seeds.md +2 -0
  268. package/gsd-core/workflows/list-workspaces.md +2 -0
  269. package/gsd-core/workflows/manager.md +3 -3
  270. package/gsd-core/workflows/map-codebase.md +52 -3
  271. package/gsd-core/workflows/milestone-summary.md +2 -0
  272. package/gsd-core/workflows/mvp-phase.md +1 -1
  273. package/gsd-core/workflows/new-milestone.md +55 -13
  274. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  275. package/gsd-core/workflows/new-project.md +37 -205
  276. package/gsd-core/workflows/new-workspace.md +1 -1
  277. package/gsd-core/workflows/next.md +2 -0
  278. package/gsd-core/workflows/node-repair.md +2 -0
  279. package/gsd-core/workflows/note.md +2 -0
  280. package/gsd-core/workflows/onboard.md +1 -1
  281. package/gsd-core/workflows/pause-work.md +19 -4
  282. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  283. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  284. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  285. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  286. package/gsd-core/workflows/plan-phase.md +144 -185
  287. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  288. package/gsd-core/workflows/plant-seed.md +1 -1
  289. package/gsd-core/workflows/pr-branch.md +30 -10
  290. package/gsd-core/workflows/profile-user.md +1 -1
  291. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  292. package/gsd-core/workflows/progress.md +25 -3
  293. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  294. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  295. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  296. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  297. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  298. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  299. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  300. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  301. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  302. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  303. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  304. package/gsd-core/workflows/quick-batch.md +203 -0
  305. package/gsd-core/workflows/quick.md +21 -4
  306. package/gsd-core/workflows/reapply-patches.md +79 -3
  307. package/gsd-core/workflows/remove-phase.md +2 -0
  308. package/gsd-core/workflows/remove-workspace.md +1 -1
  309. package/gsd-core/workflows/resume-project.md +6 -2
  310. package/gsd-core/workflows/review.md +215 -10
  311. package/gsd-core/workflows/scan.md +2 -0
  312. package/gsd-core/workflows/section-manifest.json +12 -0
  313. package/gsd-core/workflows/secure-phase.md +1 -1
  314. package/gsd-core/workflows/session-report.md +2 -0
  315. package/gsd-core/workflows/settings-advanced.md +2 -0
  316. package/gsd-core/workflows/settings-integrations.md +9 -8
  317. package/gsd-core/workflows/settings.md +19 -6
  318. package/gsd-core/workflows/ship.md +10 -10
  319. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  320. package/gsd-core/workflows/sketch.md +1 -1
  321. package/gsd-core/workflows/smart-entry.md +1 -1
  322. package/gsd-core/workflows/spec-phase.md +24 -19
  323. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  324. package/gsd-core/workflows/spike.md +1 -1
  325. package/gsd-core/workflows/stats.md +2 -0
  326. package/gsd-core/workflows/sync-skills.md +12 -4
  327. package/gsd-core/workflows/thread.md +2 -0
  328. package/gsd-core/workflows/transition.md +2 -0
  329. package/gsd-core/workflows/ui-phase.md +26 -5
  330. package/gsd-core/workflows/ui-review.md +1 -1
  331. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  332. package/gsd-core/workflows/undo.md +1 -1
  333. package/gsd-core/workflows/update.md +48 -43
  334. package/gsd-core/workflows/validate-phase.md +1 -1
  335. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  336. package/gsd-core/workflows/verify-work.md +68 -182
  337. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  338. package/hooks/dist/gsd-check-update-worker.js +19 -2
  339. package/hooks/dist/gsd-context-monitor.js +371 -27
  340. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  341. package/hooks/dist/gsd-node-runner.sh +1 -0
  342. package/hooks/dist/gsd-prompt-guard.js +30 -5
  343. package/hooks/dist/gsd-read-guard.js +2 -0
  344. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  345. package/hooks/dist/gsd-secret-read-guard.js +1105 -0
  346. package/hooks/dist/gsd-statusline.js +18 -10
  347. package/hooks/dist/gsd-validate-commit.sh +474 -7
  348. package/hooks/dist/gsd-workflow-guard.js +2 -1
  349. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  350. package/hooks/dist/gsd-write-guard.js +46 -1
  351. package/hooks/dist/lib/dispatch-identity.js +187 -0
  352. package/hooks/dist/lib/filename-classification.js +64 -0
  353. package/hooks/dist/lib/git-cmd.js +210 -1
  354. package/hooks/dist/lib/injection-patterns.js +36 -6
  355. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  356. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  357. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  358. package/hooks/gsd-agent-isolation-guard.js +42 -16
  359. package/hooks/gsd-check-update-worker.js +19 -2
  360. package/hooks/gsd-context-monitor.js +371 -27
  361. package/hooks/gsd-cursor-subagent-start.js +34 -14
  362. package/hooks/gsd-node-runner.sh +1 -0
  363. package/hooks/gsd-prompt-guard.js +30 -5
  364. package/hooks/gsd-read-guard.js +2 -0
  365. package/hooks/gsd-read-injection-scanner.js +5 -5
  366. package/hooks/gsd-secret-read-guard.js +1105 -0
  367. package/hooks/gsd-statusline.js +18 -10
  368. package/hooks/gsd-validate-commit.sh +474 -7
  369. package/hooks/gsd-workflow-guard.js +2 -1
  370. package/hooks/gsd-worktree-path-guard.js +25 -14
  371. package/hooks/gsd-write-guard.js +46 -1
  372. package/hooks/hooks.json +6 -0
  373. package/hooks/lib/dispatch-identity.js +187 -0
  374. package/hooks/lib/filename-classification.js +64 -0
  375. package/hooks/lib/git-cmd.js +210 -1
  376. package/hooks/lib/injection-patterns.js +36 -6
  377. package/hooks/lib/isolation-deny-reason.js +53 -1
  378. package/hooks/lib/isolation-sentinel.js +58 -19
  379. package/hooks/managed-hooks-registry.cjs +1 -0
  380. package/package.json +13 -9
  381. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  382. package/scripts/benchmark-compact-content.cjs +368 -0
  383. package/scripts/build-hooks.js +11 -4
  384. package/scripts/check-contract-drift.cjs +4 -1
  385. package/scripts/check-env.cjs +36 -8
  386. package/scripts/check-glossary-refs.cjs +25 -21
  387. package/scripts/ci-next-health.cjs +271 -0
  388. package/scripts/ci-prepare-test-scope.cjs +7 -7
  389. package/scripts/ci-test-scope.cjs +133 -20
  390. package/scripts/ci-timeout-report.cjs +1 -1
  391. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  392. package/scripts/docs-guard-registry.cjs +17 -2
  393. package/scripts/gen-adr-index.cjs +8 -2
  394. package/scripts/gen-inventory-manifest.cjs +12 -0
  395. package/scripts/gen-loop-host-contract.cjs +67 -15
  396. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  397. package/scripts/lib/drift-scan.cjs +1 -1
  398. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  399. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  400. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  401. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  402. package/scripts/lib/suite-detection.cjs +32 -0
  403. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  404. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  405. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  406. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  407. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
  408. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  409. package/scripts/lint-phase-id-drift.cjs +465 -15
  410. package/scripts/lint-portable-grep.cjs +176 -0
  411. package/scripts/lint-response-language-coverage.cjs +530 -0
  412. package/scripts/lint-source-test-name-collision.cjs +1 -1
  413. package/scripts/lint-test-file-count.allowlist.json +4 -1
  414. package/scripts/lint-vendored-deps.cjs +128 -17
  415. package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
  416. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  417. package/scripts/npm-audit-baseline.cjs +376 -0
  418. package/scripts/prompt-injection-scan.sh +22 -0
  419. package/scripts/require-issue-link-policy.cjs +16 -1
  420. package/scripts/workflow-size.cjs +139 -0
  421. package/skills/gsd-cleanup/SKILL.md +1 -0
  422. package/skills/gsd-code-review/SKILL.md +2 -1
  423. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  424. package/skills/gsd-config/SKILL.md +1 -0
  425. package/skills/gsd-debug/SKILL.md +1 -0
  426. package/skills/gsd-execute-phase/SKILL.md +1 -1
  427. package/skills/gsd-graphify/SKILL.md +1 -0
  428. package/skills/gsd-health/SKILL.md +1 -0
  429. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  430. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  431. package/skills/gsd-new-milestone/SKILL.md +1 -0
  432. package/skills/gsd-new-project/SKILL.md +1 -0
  433. package/skills/gsd-next/SKILL.md +1 -0
  434. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  435. package/skills/gsd-pause-work/SKILL.md +1 -0
  436. package/skills/gsd-phase/SKILL.md +2 -1
  437. package/skills/gsd-pr-branch/SKILL.md +1 -0
  438. package/skills/gsd-quick-batch/SKILL.md +105 -0
  439. package/skills/gsd-resume-work/SKILL.md +1 -0
  440. package/skills/gsd-review-backlog/SKILL.md +1 -0
  441. package/skills/gsd-settings/SKILL.md +2 -1
  442. package/skills/gsd-stats/SKILL.md +1 -0
  443. package/skills/gsd-surface/SKILL.md +18 -8
  444. package/skills/gsd-thread/SKILL.md +1 -0
  445. package/skills/gsd-workspace/SKILL.md +1 -0
  446. package/skills/gsd-workstreams/SKILL.md +1 -0
  447. package/vscode/package.json +1 -1
  448. package/gsd-core/templates/claude-md.md +0 -145
  449. package/gsd-core/templates/codebase/concerns.md +0 -310
  450. package/gsd-core/templates/codebase/conventions.md +0 -307
  451. package/gsd-core/templates/codebase/integrations.md +0 -280
  452. package/gsd-core/templates/codebase/structure.md +0 -285
  453. package/gsd-core/templates/codebase/testing.md +0 -480
  454. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  455. package/gsd-core/templates/discovery.md +0 -146
@@ -21,6 +21,9 @@ const node_path_1 = __importDefault(require("node:path"));
21
21
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
22
22
  const clock_cjs_1 = require("./clock.cjs");
23
23
  // eslint-disable-next-line @typescript-eslint/no-require-imports
24
+ const planningScopeMod = require("./planning-scope.cjs");
25
+ const { SCOPE } = planningScopeMod;
26
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
24
27
  const activeWorkstreamStore = require("./active-workstream-store.cjs");
25
28
  const { createSharedPointerAdapter, createSessionScopedPointerAdapter, createMemoryPointerAdapter, getActiveWorkstream: getStoredActiveWorkstream, peekActiveWorkstream: peekStoredActiveWorkstream, setActiveWorkstream: setStoredActiveWorkstream, clearActiveWorkstream: clearStoredActiveWorkstream, diagnoseUnresolvedActiveWorkstream: diagnoseUnresolvedStoredActiveWorkstream, } = activeWorkstreamStore;
26
29
  // Track .planning/.lock files held by this process so they can be removed on exit.
@@ -97,11 +100,27 @@ const PLANNING_LOCK_RETRY_ERRNOS = new Set([
97
100
  'ENOENT', // Docker overlay-fs: parent dir transiently missing during race
98
101
  'ESTALE', // NFS: stale file handle (self-resolves on retry)
99
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
+ return process.env['GSD_WORKSTREAM'] ?? null;
118
+ }
100
119
  function planningDir(cwd, ws, project) {
101
120
  if (project === undefined)
102
121
  project = process.env['GSD_PROJECT'] ?? null;
103
122
  if (ws === undefined)
104
- ws = process.env['GSD_WORKSTREAM'] ?? null;
123
+ ws = resolveEnvWorkstream();
105
124
  // Reject path separators and traversal components in project/workstream names
106
125
  const BAD_SEGMENT = /[/\\]|\.\./;
107
126
  if (project && BAD_SEGMENT.test(project)) {
@@ -175,6 +194,87 @@ function worktreesOptedOutUnguarded(cwd) {
175
194
  }
176
195
  return false;
177
196
  }
197
+ /**
198
+ * #612: resolve `phase_id_convention` with the SAME workstream->root federation
199
+ * config-loader uses (config-loader.cts:618/:649) — the workstream config wins,
200
+ * the root config is the fallback.
201
+ *
202
+ * Why this exists rather than `loadConfig(cwd)['phase_id_convention']`: as of
203
+ * #2997 (aa7697fe, in `next`), loadConfig surfaces `phase_id_convention` in
204
+ * its resolved `_baseConfig` — the "loadConfig drops keys it does not know"
205
+ * rationale this comment used to give is stale. The surviving reasons for the
206
+ * direct read are (1) the workstream->root federation below, a standalone
207
+ * resolution this function needs to run against a GIVEN cwd rather than
208
+ * whatever base a `loadConfig(cwd)` call elsewhere would federate from, and
209
+ * (2) convention-ENUM validation, which is still #612 PR-4 work — this
210
+ * function returns the raw string unvalidated, same as the now-surfaced
211
+ * resolved key would. #2997 surfacing the key makes consuming it from
212
+ * resolved config (instead of re-reading config.json here) a natural PR-4
213
+ * consolidation, not this PR's scope. Cycles were never the obstacle.
214
+ *
215
+ * Why federation matters here specifically: the phase-id readers were splitting
216
+ * on this value from two different bases — one resolving from the workstream
217
+ * directory, one from the root — so a workstream repo got the widened ROADMAP
218
+ * read with the narrow directory read, or the reverse, and reported every phase
219
+ * either missing from disk or malformed on disk. One resolver, one answer.
220
+ *
221
+ * The workstream is `planningDir`'s own `ws` parameter, forwarded, so this
222
+ * shares the canonical resolution (and its GSD_PROJECT/GSD_WORKSTREAM
223
+ * handling). Root is consulted as a fallback only when a workstream is active,
224
+ * matching config-loader; a project-scoped directory stands alone. Returns null
225
+ * when unset, absent, or unreadable — every caller treats null as "not the
226
+ * bracket convention".
227
+ *
228
+ * #2761 B1 (trek-e review): `ws` is a PARAMETER, not read from the environment
229
+ * here. It was omitted at first on the reasoning that "the active workstream is
230
+ * whatever planningDir resolves" — true only for the env-driven caller. A
231
+ * caller that iterates workstreams passes the name as an ARGUMENT (it cannot
232
+ * set `GSD_WORKSTREAM` per iteration), and `planningDir` falls back to the env
233
+ * only when `ws` is `undefined`, so an argument-driven call resolved this
234
+ * convention from the ROOT config while reading that workstream's ROADMAP. Two
235
+ * consequences, both reproduced: a workstream that explicitly declares its OWN
236
+ * convention had it ignored — the root's value decided how the workstream's
237
+ * roadmap was parsed, so flipping ONLY the root config changed which milestone
238
+ * a workstream extracted; and `--workstream foo` disagreed with
239
+ * `GSD_WORKSTREAM=foo` on the same repo.
240
+ *
241
+ * `undefined` (the default) keeps `planningDir`'s env fallback, so every
242
+ * pre-#2761 call site is byte-identical; `null` means "explicitly no
243
+ * workstream". Same discriminator `planningDir` and `getMilestonePhaseFilter`
244
+ * already carry.
245
+ *
246
+ * SCOPE: this governs the #612 bracket-selection reads ONLY. The shipped
247
+ * milestone-prefixed W021 gate keeps its own root-only read — re-basing a
248
+ * legacy convention's gate onto a different config is a behaviour change to a
249
+ * shipped check, in both directions, and is not part of read tolerance.
250
+ */
251
+ function resolvePhaseIdConvention(cwd, ws) {
252
+ const readFrom = (dir) => {
253
+ const configPath = node_path_1.default.join(dir, 'config.json');
254
+ if (!node_fs_1.default.existsSync(configPath))
255
+ return null;
256
+ try {
257
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
258
+ const value = parsed['phase_id_convention'];
259
+ return typeof value === 'string' && value !== '' ? value : null;
260
+ }
261
+ catch {
262
+ return null;
263
+ }
264
+ };
265
+ const scoped = planningDir(cwd, ws);
266
+ const root = planningRoot(cwd);
267
+ if (scoped === root)
268
+ return readFrom(root);
269
+ // Root is a fallback only when a WORKSTREAM is active — config-loader falls
270
+ // back to the root config under `if (ws)` and not otherwise, so a
271
+ // project-scoped directory stands alone. Detected by suppressing the
272
+ // workstream segment rather than re-reading the environment.
273
+ const projectOnly = planningDir(cwd, null);
274
+ if (scoped === projectOnly)
275
+ return readFrom(scoped);
276
+ return readFrom(scoped) ?? readFrom(root);
277
+ }
178
278
  // Sorted list of workstream directory names under `<root>/.planning/workstreams`,
179
279
  // or `[]` when the project is flat (no workstreams dir). Single source of truth
180
280
  // for the "workstream mode" detection shared by the #1912/#2028 fail-safe guards
@@ -200,6 +300,31 @@ function listAvailableWorkstreams(cwd) {
200
300
  function quickDirFrom(planningBase) {
201
301
  return node_path_1.default.join(planningBase, 'quick');
202
302
  }
303
+ // #4256: the todos directory — deliberately ROOT-SCOPED, unlike every other
304
+ // planningPaths key. Todos are shared project state by construction: the
305
+ // migrateToWorkstreams contract keeps them among the shared files that "stay
306
+ // in place" at .planning/todos/ (workstream.cts), and every workflow writer
307
+ // writes that literal cwd-relative root path. The six todos readers
308
+ // previously hand-composed `path.join(planningDir(cwd), 'todos', ...)`,
309
+ // which silently re-scoped to .planning/workstreams/<ws>/todos/ — a
310
+ // directory nothing creates — under a workstream, so todos went invisible
311
+ // and audit-open passed the milestone-close gate vacuously. Same
312
+ // two-composers-of-one-path shape the `debug` (#3149) and `quick` (#2142)
313
+ // keys were introduced to eliminate (DEFECT.GENERATIVE-FIX).
314
+ //
315
+ // Exported as its own function pair (not only as a `planningPaths` key)
316
+ // because `audit.cts`'s `scanTodos`/`cmdAuditAcknowledge` consume an
317
+ // already-resolved todos base rather than a `cwd`, mirroring how #2142
318
+ // exported `quickDirFrom` for `scanQuickTasks`. `todosDir` takes NO ws/project
319
+ // parameter — todos have no workstream- or project-scoped form anywhere, so
320
+ // there is no discriminator to thread. This is also the single root #4327's
321
+ // future filename-containment guard should enforce against.
322
+ function todosDirFrom(planningBase) {
323
+ return node_path_1.default.join(planningBase, 'todos');
324
+ }
325
+ function todosDir(cwd) {
326
+ return todosDirFrom(planningRoot(cwd));
327
+ }
203
328
  function planningPaths(cwd, ws) {
204
329
  const base = planningDir(cwd, ws);
205
330
  return {
@@ -216,6 +341,11 @@ function planningPaths(cwd, ws) {
216
341
  debug: node_path_1.default.join(base, 'debug'),
217
342
  // #2142: quick-task directory, composed via the shared quickDirFrom helper.
218
343
  quick: quickDirFrom(base),
344
+ // #4256: todos directory — deliberately ROOT-scoped while the rest of
345
+ // this record follows the active workstream/project (todos are shared
346
+ // project state per the migrateToWorkstreams contract), composed via the
347
+ // shared todosDir helper so this key and every direct caller agree.
348
+ todos: todosDir(cwd),
219
349
  };
220
350
  }
221
351
  /**
@@ -449,40 +579,30 @@ function describeUnresolvedWorkstreamReason(reason) {
449
579
  return 'the name is not a valid workstream name';
450
580
  return "its workstream directory doesn't exist (it may have been renamed or removed)";
451
581
  }
452
- /**
453
- * Locate the CONTEXT.md file in a phase directory, handling both the bare
454
- * form (`CONTEXT.md`) and the padded-prefix convention (`NN-CONTEXT.md`,
455
- * `NN.N-CONTEXT.md`, etc.) used by gsd-discuss-phase output.
456
- *
457
- * Returns the filename (not the full path) of the first match, or null if
458
- * no CONTEXT.md exists in the directory.
459
- *
460
- * Canonical dual-form predicate extracted here to eliminate the 5-site
461
- * duplication that previously existed across init.cjs, roadmap.cjs,
462
- * core.cjs, gap-checker.cjs (#3739).
463
- *
464
- * @param absDirOrFiles - Absolute path to the phase directory,
465
- * OR an already-read files array (avoids a redundant readdirSync at call sites
466
- * that already hold a directory listing).
467
- */
468
582
  function findContextMdIn(absDirOrFiles) {
469
- try {
470
- const files = Array.isArray(absDirOrFiles)
471
- ? absDirOrFiles
472
- : node_fs_1.default.readdirSync(absDirOrFiles);
583
+ const matchIn = (files) => {
473
584
  if (files.includes('CONTEXT.md'))
474
585
  return 'CONTEXT.md';
475
586
  return files.find((f) => f.endsWith('-CONTEXT.md')) ?? null;
587
+ };
588
+ if (Array.isArray(absDirOrFiles)) {
589
+ return matchIn(absDirOrFiles);
590
+ }
591
+ try {
592
+ const files = node_fs_1.default.readdirSync(absDirOrFiles);
593
+ return { file: matchIn(files), files, scope: SCOPE.COMPLETE };
476
594
  }
477
595
  catch (err) {
478
- // #1883: distinguish genuine absence from a permission/I-O failure. ENOENT
479
- // ("nothing there") keeps the long-standing null contract the callers rely
480
- // on; every other error (EACCES, EIO, …) is a real read failure that must
481
- // propagate — otherwise an unreadable phase dir is silently reported as
482
- // "no CONTEXT.md" and the discuss/plan gates wrongly skip context.
483
- if (err.code === 'ENOENT')
484
- return null;
485
- throw err;
596
+ // #1883 / #4014: distinguish genuine absence from a permission/I-O
597
+ // failure. ENOENT ("nothing there") keeps the long-standing "real empty"
598
+ // contract callers rely on; every other error (EACCES, EIO, …) is a real
599
+ // read failure — reported as SCOPE.UNREADABLE rather than thrown, so a
600
+ // caller no longer needs its own try/catch to keep an unreadable phase
601
+ // dir from being silently reported the same as "no CONTEXT.md".
602
+ if (err.code === 'ENOENT') {
603
+ return { file: null, files: [], scope: SCOPE.COMPLETE };
604
+ }
605
+ return { file: null, files: [], scope: SCOPE.UNREADABLE };
486
606
  }
487
607
  }
488
608
  module.exports = {
@@ -493,9 +613,13 @@ module.exports = {
493
613
  createMemoryPointerAdapter,
494
614
  planningDir,
495
615
  planningRoot,
616
+ resolveEnvWorkstream,
617
+ resolvePhaseIdConvention,
496
618
  listAvailableWorkstreams,
497
619
  planningPaths,
498
620
  quickDirFrom,
621
+ todosDirFrom,
622
+ todosDir,
499
623
  withPlanningLock,
500
624
  getActiveWorkstream,
501
625
  peekActiveWorkstream,
@@ -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
+ }
@@ -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'],