@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
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Benchmarks the token-count effect of every registered `workflow.compact_content`
6
+ * spine/detail split (ADR-4139 Decision 3, Phase 3 #4403's `docs/PARTITION-RULES.md`).
7
+ *
8
+ * For every split it reports the "off" token count (spine + all detail parts —
9
+ * what `workflow.compact_content=false` pays today, since an opted-out project's
10
+ * spine reads every detail part back in) against the "on" token count (spine
11
+ * alone — what `workflow.compact_content=true` pays, since the detail `Read`
12
+ * never fires). See `.gsd/phase/enhance-4404-token-benchmark/40-design.md` for
13
+ * the full design rationale.
14
+ *
15
+ * PROXY-TOKENIZER CAVEAT: Anthropic publishes no tokenizer for Claude 3+, so
16
+ * `gpt-tokenizer` (pinned exact-version devDependency, see ADR-4139
17
+ * Consequences) is a stand-in. The on/off COMPARISON is exact under this one
18
+ * pinned tokenizer applied identically to both sides; the absolute counts are
19
+ * NOT Claude's real token counts. Every output surface says so explicitly —
20
+ * see the `label` field below.
21
+ *
22
+ * Discovery is REIMPLEMENTED here rather than imported from
23
+ * `tests/helpers/compact-content-split.cjs`: a `scripts/` reporting tool
24
+ * depending on a test-only helper module inverts this repo's normal layering,
25
+ * and a test-only module changing shape should never be able to break a
26
+ * benchmark. The discovery logic is intentionally small (see
27
+ * `discoverRegisteredSplits` below) and mirrors `docs/PARTITION-RULES.md`'s
28
+ * own description of what a "registered split" is.
29
+ *
30
+ * Usage:
31
+ * node scripts/benchmark-compact-content.cjs # print JSON to stdout
32
+ * node scripts/benchmark-compact-content.cjs --write # write the committed baseline
33
+ * node scripts/benchmark-compact-content.cjs --check # recompute, diff vs committed baseline, print drift
34
+ * node scripts/benchmark-compact-content.cjs --check --baseline-path=<path>
35
+ * # --check against a DIFFERENT baseline
36
+ * # file (testability seam; defaults to the
37
+ * # committed path when omitted)
38
+ *
39
+ * ============================================================================
40
+ * CRITICAL — READ BEFORE "FIXING" ANYTHING BELOW (this is the issue's own
41
+ * Done-when item; getting it wrong defeats the entire point of this tool):
42
+ *
43
+ * This script — and in particular `--check` — MUST NEVER exit non-zero
44
+ * because a baseline is drifted, stale, or missing entirely. A drifted or
45
+ * absent baseline is REPORTED (printed to stdout as a human-readable diff),
46
+ * never treated as failure. The ONLY thing allowed to make this script exit
47
+ * non-zero is a genuine I/O error reading a SOURCE file the benchmark is
48
+ * measuring (a spine or detail `.md` file that `discoverRegisteredSplits`
49
+ * found on disk but that fails to read) — never anything about the baseline
50
+ * file. This is deliberate: the benchmark is a reporting instrument wired
51
+ * into `npm run benchmark:compact-content`, which is never part of a gate,
52
+ * `lint:ci`, or `pretest` — see `tests/benchmark-compact-content.test.cjs`'s
53
+ * "never-fails-CI" tests, which assert this behavior directly against a
54
+ * deliberately-wrong and a wholly-missing baseline. Do not add
55
+ * `process.exit(1)` (or an `ExitError` with a non-zero code) on drift.
56
+ * ============================================================================
57
+ */
58
+
59
+ const fs = require('node:fs');
60
+ const path = require('node:path');
61
+
62
+ const { countTokens } = require('gpt-tokenizer');
63
+ const { runMain } = require('./lib/cli-exit.cjs');
64
+
65
+ const ROOT = path.resolve(__dirname, '..');
66
+ const WORKFLOWS_DIR = path.join(ROOT, 'gsd-core', 'workflows');
67
+ const BASELINE_PATH = path.join(ROOT, 'tests', 'fixtures', 'compact-content-benchmark-baseline.json');
68
+
69
+ // The tokenizer's own package.json is read live (`require.resolve` + a plain
70
+ // `fs.readFileSync`/JSON.parse — NOT `require('gpt-tokenizer/package.json')`,
71
+ // which would work identically here but would tie this file to Node's CJS
72
+ // JSON-import behavior for no benefit) rather than hardcoding the version as
73
+ // a string literal. This repo's own `package.json` pins `gpt-tokenizer` at an
74
+ // EXACT version (no `^`/`~`), so the two are guaranteed to agree today — but
75
+ // reading the installed package's own version live means a future re-pin of
76
+ // that dependency never requires touching this file too, and the reported
77
+ // version can never silently drift from what actually ran.
78
+ function getTokenizerVersion() {
79
+ const pkgPath = require.resolve('gpt-tokenizer/package.json');
80
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
81
+ return pkg.version;
82
+ }
83
+
84
+ /**
85
+ * Discover every registered spine/detail split under `workflowsDir`.
86
+ *
87
+ * A split is registered by a `<workflowsDir>/<name>/detail/` directory
88
+ * (containing at least one `*.md` file) where `<name>` is exactly ONE path
89
+ * segment directly under `workflowsDir`. Pairs with the spine at
90
+ * `<workflowsDir>/<name>.md`; skipped entirely if that spine does not exist
91
+ * (this benchmark only measures real, complete splits — an orphaned detail
92
+ * directory with no spine is Phase 3's registration guard's problem, not
93
+ * this benchmark's).
94
+ *
95
+ * Mirrors `tests/helpers/compact-content-split.cjs`'s `discoverRegisteredSplits`
96
+ * in shape (same "one segment below workflowsDir, `detail/` dir, at least one
97
+ * `.md` file" rule) but is a from-scratch, self-contained implementation —
98
+ * see the module header for why this is not a shared import.
99
+ *
100
+ * @param {string} [workflowsDir]
101
+ * @returns {Array<{name: string, spinePath: string, detailPaths: string[]}>}
102
+ */
103
+ function discoverRegisteredSplits(workflowsDir = WORKFLOWS_DIR) {
104
+ const found = new Map();
105
+
106
+ function walk(dir) {
107
+ let entries;
108
+ try {
109
+ entries = fs.readdirSync(dir, { withFileTypes: true });
110
+ } catch {
111
+ return; // unreadable directory: nothing to discover under it
112
+ }
113
+ for (const entry of entries) {
114
+ if (!entry.isDirectory()) continue;
115
+ const full = path.join(dir, entry.name);
116
+
117
+ if (entry.name === 'detail') {
118
+ let mdFiles = [];
119
+ try {
120
+ mdFiles = fs
121
+ .readdirSync(full, { withFileTypes: true })
122
+ .filter((e) => e.isFile() && e.name.endsWith('.md'))
123
+ .map((e) => e.name);
124
+ } catch {
125
+ mdFiles = [];
126
+ }
127
+ if (mdFiles.length > 0) {
128
+ const segments = path.relative(workflowsDir, dir).split(path.sep).filter(Boolean);
129
+ if (segments.length === 1) {
130
+ const name = segments[0];
131
+ if (!found.has(name)) {
132
+ const spinePath = path.join(workflowsDir, `${name}.md`);
133
+ if (fs.existsSync(spinePath)) {
134
+ const detailPaths = mdFiles.map((f) => path.join(full, f)).sort();
135
+ found.set(name, { name, spinePath, detailPaths });
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ walk(full);
143
+ }
144
+ }
145
+
146
+ walk(workflowsDir);
147
+ return [...found.values()].sort((a, b) => a.name.localeCompare(b.name));
148
+ }
149
+
150
+ /**
151
+ * Compute the off/on/reduction numbers for ONE registered split.
152
+ *
153
+ * Throws on a genuine read failure of a source file (see the module-header
154
+ * CRITICAL note — this is the one thing allowed to throw). `reductionPct` is
155
+ * reported as `0` rather than `NaN`/`Infinity` when `offTokens` is `0` (an
156
+ * empty spine — should never happen for a real split, but must not crash).
157
+ *
158
+ * @param {{name: string, spinePath: string, detailPaths: string[]}} split
159
+ * @returns {{offTokens: number, onTokens: number, reductionPct: number}}
160
+ */
161
+ function computeSplitTokens(split) {
162
+ const spineContent = fs.readFileSync(split.spinePath, 'utf8');
163
+ const onTokens = countTokens(spineContent);
164
+ let detailTokens = 0;
165
+ for (const detailPath of split.detailPaths) {
166
+ const detailContent = fs.readFileSync(detailPath, 'utf8');
167
+ detailTokens += countTokens(detailContent);
168
+ }
169
+ const offTokens = onTokens + detailTokens;
170
+ const reductionPct = offTokens === 0 ? 0 : round2(((offTokens - onTokens) / offTokens) * 100);
171
+ return { offTokens, onTokens, reductionPct };
172
+ }
173
+
174
+ function round2(n) {
175
+ return Math.round(n * 100) / 100;
176
+ }
177
+
178
+ /**
179
+ * Aggregate per-split numbers. `aggregateReductionPct` is computed from the
180
+ * SUMMED off/on totals, never averaged across per-split percentages — a
181
+ * 2-split fixture with very different sizes is what
182
+ * `tests/benchmark-compact-content.test.cjs` uses to pin this down. Reports
183
+ * `0` (not `NaN`) when there are zero registered splits or `aggregateOff` is
184
+ * `0` — a valid, non-crashing state, not an error.
185
+ *
186
+ * @param {Record<string, {offTokens: number, onTokens: number}>} splitResults
187
+ * @returns {{offTokens: number, onTokens: number, reductionPct: number}}
188
+ */
189
+ function computeAggregate(splitResults) {
190
+ let offTokens = 0;
191
+ let onTokens = 0;
192
+ for (const key of Object.keys(splitResults)) {
193
+ offTokens += splitResults[key].offTokens;
194
+ onTokens += splitResults[key].onTokens;
195
+ }
196
+ const reductionPct = offTokens === 0 ? 0 : round2(((offTokens - onTokens) / offTokens) * 100);
197
+ return { offTokens, onTokens, reductionPct };
198
+ }
199
+
200
+ const LABEL =
201
+ "PROXY-TOKENIZER DELTA — gpt-tokenizer is a stand-in; Anthropic publishes no tokenizer for Claude 3+. " +
202
+ "The on/off COMPARISON is exact under this pinned tokenizer; absolute counts are not Claude's real token counts.";
203
+
204
+ /**
205
+ * Build the full report object — the "committed baseline" shape. Keys of
206
+ * `splits` are sorted (`discoverRegisteredSplits` already returns
207
+ * name-sorted records; `Object.keys` insertion order on a plain object built
208
+ * in that order preserves it, and `--check`'s comparison re-sorts anyway so
209
+ * this is belt-and-suspenders, not load-bearing).
210
+ *
211
+ * @param {string} [workflowsDir]
212
+ * @returns {object}
213
+ */
214
+ function buildReport(workflowsDir = WORKFLOWS_DIR) {
215
+ const splits = discoverRegisteredSplits(workflowsDir);
216
+ const splitReports = {};
217
+ for (const split of splits) {
218
+ splitReports[split.name] = computeSplitTokens(split);
219
+ }
220
+ return {
221
+ schema_version: 1,
222
+ generated_by: 'scripts/benchmark-compact-content.cjs',
223
+ tokenizer: { name: 'gpt-tokenizer', version: getTokenizerVersion() },
224
+ label: LABEL,
225
+ splits: splitReports,
226
+ aggregate: computeAggregate(splitReports),
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Format a human-readable drift summary between a (possibly missing/invalid)
232
+ * committed baseline and a freshly-computed live report. Never throws — a
233
+ * missing or unparseable baseline is reported as "no baseline found" /
234
+ * "baseline could not be parsed", not propagated as an error, per the
235
+ * module's CRITICAL contract.
236
+ *
237
+ * @param {string} baselinePath
238
+ * @param {object} live
239
+ * @returns {string}
240
+ */
241
+ function formatDriftReport(baselinePath, live) {
242
+ const lines = [];
243
+ let baseline = null;
244
+ let baselineReadError = null;
245
+ try {
246
+ const raw = fs.readFileSync(baselinePath, 'utf8');
247
+ try {
248
+ baseline = JSON.parse(raw);
249
+ } catch (parseErr) {
250
+ baselineReadError = `baseline at ${baselinePath} could not be parsed as JSON: ${parseErr.message}`;
251
+ }
252
+ } catch {
253
+ baselineReadError = `no baseline found at ${baselinePath}`;
254
+ }
255
+
256
+ if (baselineReadError) {
257
+ lines.push(`DRIFT: ${baselineReadError} — treating as fully drifted (this is reported, not an error).`);
258
+ lines.push('Live splits:');
259
+ for (const name of Object.keys(live.splits).sort()) {
260
+ const s = live.splits[name];
261
+ lines.push(` + ${name}: off=${s.offTokens} on=${s.onTokens} reduction=${s.reductionPct}%`);
262
+ }
263
+ lines.push(
264
+ `Live aggregate: off=${live.aggregate.offTokens} on=${live.aggregate.onTokens} ` +
265
+ `reduction=${live.aggregate.reductionPct}%`,
266
+ );
267
+ return lines.join('\n');
268
+ }
269
+
270
+ const baselineSplits = (baseline && typeof baseline === 'object' && baseline.splits) || {};
271
+ const liveSplits = live.splits;
272
+ const allNames = new Set([...Object.keys(baselineSplits), ...Object.keys(liveSplits)]);
273
+ let anyDrift = false;
274
+
275
+ if (!baseline || typeof baseline.label !== 'string' || !baseline.label.includes('PROXY-TOKENIZER')) {
276
+ anyDrift = true;
277
+ lines.push('DRIFT: committed baseline is missing the required "PROXY-TOKENIZER" label.');
278
+ }
279
+
280
+ for (const name of [...allNames].sort()) {
281
+ const b = baselineSplits[name];
282
+ const l = liveSplits[name];
283
+ if (!b) {
284
+ anyDrift = true;
285
+ lines.push(`DRIFT: split "${name}" is new (not in committed baseline) — live off=${l.offTokens} on=${l.onTokens} reduction=${l.reductionPct}%`);
286
+ } else if (!l) {
287
+ anyDrift = true;
288
+ lines.push(`DRIFT: split "${name}" was removed (present in committed baseline, not found live) — baseline off=${b.offTokens} on=${b.onTokens} reduction=${b.reductionPct}%`);
289
+ } else if (b.offTokens !== l.offTokens || b.onTokens !== l.onTokens || b.reductionPct !== l.reductionPct) {
290
+ anyDrift = true;
291
+ lines.push(
292
+ `DRIFT: split "${name}": off ${b.offTokens} -> ${l.offTokens} (${l.offTokens - b.offTokens >= 0 ? '+' : ''}${l.offTokens - b.offTokens}), ` +
293
+ `on ${b.onTokens} -> ${l.onTokens} (${l.onTokens - b.onTokens >= 0 ? '+' : ''}${l.onTokens - b.onTokens}), ` +
294
+ `reduction ${b.reductionPct}% -> ${l.reductionPct}% (${round2(l.reductionPct - b.reductionPct) >= 0 ? '+' : ''}${round2(l.reductionPct - b.reductionPct)}pp)`,
295
+ );
296
+ }
297
+ }
298
+
299
+ const ba = (baseline && baseline.aggregate) || {};
300
+ const la = live.aggregate;
301
+ if (ba.offTokens !== la.offTokens || ba.onTokens !== la.onTokens || ba.reductionPct !== la.reductionPct) {
302
+ anyDrift = true;
303
+ lines.push(
304
+ `DRIFT: aggregate: off ${ba.offTokens} -> ${la.offTokens}, on ${ba.onTokens} -> ${la.onTokens}, ` +
305
+ `reduction ${ba.reductionPct}% -> ${la.reductionPct}%`,
306
+ );
307
+ }
308
+
309
+ if (!anyDrift) {
310
+ lines.push(`Baseline at ${baselinePath} is up to date with the live recompute.`);
311
+ } else {
312
+ lines.push('');
313
+ lines.push('Run `node scripts/benchmark-compact-content.cjs --write` to refresh the committed baseline.');
314
+ lines.push('(This is a REPORT, not a gate — exiting 0 regardless of drift, per this script\'s own contract.)');
315
+ }
316
+ return lines.join('\n');
317
+ }
318
+
319
+ function parseArgs(argv) {
320
+ const opts = { write: false, check: false, baselinePath: BASELINE_PATH };
321
+ for (const arg of argv) {
322
+ if (arg === '--write') opts.write = true;
323
+ else if (arg === '--check') opts.check = true;
324
+ else if (arg.startsWith('--baseline-path=')) opts.baselinePath = arg.slice('--baseline-path='.length);
325
+ }
326
+ return opts;
327
+ }
328
+
329
+ function main() {
330
+ const opts = parseArgs(process.argv.slice(2));
331
+
332
+ if (opts.write) {
333
+ const report = buildReport();
334
+ fs.mkdirSync(path.dirname(BASELINE_PATH), { recursive: true });
335
+ fs.writeFileSync(BASELINE_PATH, JSON.stringify(report, null, 2) + '\n');
336
+ process.stdout.write(`Wrote ${BASELINE_PATH}\n`);
337
+ return;
338
+ }
339
+
340
+ if (opts.check) {
341
+ const live = buildReport();
342
+ // See the module-header CRITICAL note: this branch NEVER throws or sets a
343
+ // non-zero exit code on a drifted/missing/invalid baseline — only
344
+ // `buildReport()` above (a genuine source-file read failure) can throw.
345
+ process.stdout.write(formatDriftReport(opts.baselinePath, live) + '\n');
346
+ return;
347
+ }
348
+
349
+ process.stdout.write(JSON.stringify(buildReport(), null, 2) + '\n');
350
+ }
351
+
352
+ /* c8 ignore next 3 -- CLI entry guard; this repo measures coverage with c8, which does not honor istanbul pragmas */
353
+ if (require.main === module) {
354
+ runMain(main);
355
+ }
356
+
357
+ module.exports = {
358
+ discoverRegisteredSplits,
359
+ computeSplitTokens,
360
+ computeAggregate,
361
+ buildReport,
362
+ formatDriftReport,
363
+ getTokenizerVersion,
364
+ parseArgs,
365
+ LABEL,
366
+ BASELINE_PATH,
367
+ WORKFLOWS_DIR,
368
+ };
@@ -61,6 +61,8 @@ const HOOKS_TO_COPY = [
61
61
  'gsd-prompt-guard.js',
62
62
  'gsd-read-guard.js',
63
63
  'gsd-read-injection-scanner.js',
64
+ // Secret-file read guard (#4221) — replaces the installer's Read(.env*) deny rules
65
+ 'gsd-secret-read-guard.js',
64
66
  'gsd-statusline.js',
65
67
  'gsd-update-banner.js',
66
68
  'gsd-workflow-guard.js',
@@ -71,10 +73,15 @@ const HOOKS_TO_COPY = [
71
73
  'gsd-session-state.sh',
72
74
  'gsd-validate-commit.sh',
73
75
  'gsd-phase-boundary.sh',
74
- // Portable node resolver (#3662). Not a registered hook itself: managed JS
75
- // hook commands under --portable-hooks route through it (bash <resolver>
76
- // <baked-node> <script>) so node resolves at hook-fire time in every
77
- // environment sharing the config root. Staged verbatim — no templating.
76
+ // Portable node resolver (#3662). Managed JS hook commands under
77
+ // --portable-hooks route through it (bash <resolver> <baked-node>
78
+ // <script>) so node resolves at hook-fire time in every environment
79
+ // sharing the config root. It IS registered in MANAGED_HOOKS
80
+ // (managed-hooks-registry.cjs) for staleness tracking like every other
81
+ // shipped .sh hook, and install.js stamps its {{GSD_VERSION}} header the
82
+ // same way (#4076 — the prior comment here claimed the opposite on both
83
+ // counts, which is why the header was missing and staleness detection was
84
+ // permanently broken for this file).
78
85
  'gsd-node-runner.sh',
79
86
  // Graphify auto-update hook (#3347 / PR #3557 / #3579). Opt-in via
80
87
  // .planning/config.json graphify.auto_update; off by default.
@@ -159,8 +159,11 @@ function main() {
159
159
  const agentTexts = new Map();
160
160
  const unclosedFenceViolations = [];
161
161
 
162
+ // #4407: .compact.md variant siblings are an alternate rendering of their
163
+ // canonical agent's SAME contract, not a distinct one — excluded so they
164
+ // don't need (and can't drift from) their own registry row.
162
165
  const agentFiles = fs.existsSync(AGENTS_DIR)
163
- ? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'))
166
+ ? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md') && !f.endsWith('.compact.md'))
164
167
  : [];
165
168
 
166
169
  for (const file of agentFiles) {
@@ -30,7 +30,17 @@ const path = require('path');
30
30
  const { spawnSync } = require('child_process');
31
31
 
32
32
  const { ExitError, runMain } = require('./lib/cli-exit.cjs');
33
+ const { describeNpmVersionCheckFailure } = require('./lib/npm-version-check-diagnosis.cjs');
33
34
 
35
+ // #4460 follow-up: check-env.cjs runs as its own standalone CI step BEFORE
36
+ // `npm ci` / `npm run build:lib` (a deliberate pre-flight, run before there
37
+ // is even a node_modules to build with) -- confirmed the hard way, by a
38
+ // MODULE_NOT_FOUND crash on every real CI platform after a first attempt at
39
+ // this fix routed the npm-version check through the canonical execNpm seam
40
+ // (gsd-core/bin/lib/shell-command-projection.cjs), a tsc-compiled artifact
41
+ // that plain does not exist yet at that point in the pipeline. This file
42
+ // must stay self-contained: no requires reaching into gsd-core/bin/lib.
43
+ //
34
44
  // On Windows, npm ships as npm.cmd (a batch wrapper); spawnSync without
35
45
  // shell:true requires the exact filename including extension.
36
46
  const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
@@ -180,18 +190,36 @@ function main() {
180
190
  // Check 2: npm version vs engines.npm (skip if field absent)
181
191
  // ---------------------------------------------------------------------------
182
192
  const enginesNpm = pkgField('engines.npm', PROJECT_ROOT);
183
- let currentNpm = '';
184
- try {
185
- const res = spawnSync(npmCmd, ['--version'], { encoding: 'utf8', timeout: 10_000, shell: process.platform === 'win32' });
186
- if (res.status === 0 && res.stdout) {
187
- currentNpm = res.stdout.trim();
188
- }
189
- } catch { /* ignore */ }
193
+ // #4460: 15_000ms, not the original 10_000 -- matches the default this
194
+ // repo's canonical (but here unusable, see the file-header note above)
195
+ // execNpm seam already uses for npm subprocess calls generally, rather
196
+ // than inventing a new number. Confirmed via real Windows CI: a 10s
197
+ // window was insufficient twice under ~51-file concurrent test load.
198
+ const NPM_VERSION_TIMEOUT_MS = 15_000;
199
+ // No try/catch needed: spawnSync's documented contract routes ENOENT and a
200
+ // timeout-triggered kill through the RETURNED result's `.error` field, not
201
+ // a thrown exception -- there is nothing here for a catch to intercept.
202
+ const npmVersionSpawn = spawnSync(npmCmd, ['--version'], { encoding: 'utf8', timeout: NPM_VERSION_TIMEOUT_MS, shell: process.platform === 'win32' });
203
+ const npmVersionResult = {
204
+ exitCode: npmVersionSpawn.status ?? 1,
205
+ stdout: (npmVersionSpawn.stdout || '').toString().trim(),
206
+ signal: npmVersionSpawn.signal ?? null,
207
+ error: npmVersionSpawn.error ?? null,
208
+ // Canonical cross-platform timeout predicate (matches this repo's
209
+ // execNpm/isSpawnTimeout convention, src/shell-command-projection.cts):
210
+ // error.code === 'ETIMEDOUT', which Node's spawnSync guarantees when its
211
+ // own `timeout` option fires. Checking `signal === 'SIGTERM'` instead
212
+ // (what an earlier version of this fix did) is platform-fragile -- that
213
+ // module's own docstring flags a Windows-specific false-negative risk,
214
+ // the exact platform this bug was discovered on.
215
+ timedOut: (npmVersionSpawn.error && npmVersionSpawn.error.code === 'ETIMEDOUT') === true,
216
+ };
217
+ const currentNpm = npmVersionResult.exitCode === 0 && npmVersionResult.stdout ? npmVersionResult.stdout : '';
190
218
 
191
219
  if (!enginesNpm) {
192
220
  addCheck('npm-version', 'skip', 'engines.npm not set in package.json — skipping');
193
221
  } else if (!currentNpm) {
194
- addCheck('npm-version', 'fail', 'npm binary not found on PATH');
222
+ addCheck('npm-version', 'fail', describeNpmVersionCheckFailure(npmVersionResult));
195
223
  } else {
196
224
  if (satisfiesConstraint(currentNpm, enginesNpm)) {
197
225
  addCheck('npm-version', 'pass', `npm ${currentNpm} satisfies ${enginesNpm}`);
@@ -37,6 +37,7 @@ const fs = require('node:fs');
37
37
  const path = require('node:path');
38
38
 
39
39
  const { ExitError, runMain } = require('./lib/cli-exit.cjs');
40
+ const { tryWithinRoot, PathAcceptance } = require('../gsd-core/bin/lib/security.cjs');
40
41
 
41
42
  const ROOT = path.resolve(__dirname, '..');
42
43
  const CONTEXT_PATH = path.join(ROOT, 'CONTEXT.md');
@@ -131,20 +132,6 @@ function isTracked(token) {
131
132
  return TRACKED_EXACT.has(token) || TRACKED_PREFIXES.some((prefix) => token.startsWith(prefix));
132
133
  }
133
134
 
134
- /**
135
- * True if joining `token` to ROOT stays inside ROOT. `PATH_TOKEN_RE` admits `.`
136
- * inside a segment, so a token like `src/../../../etc/passwd` matches and (via
137
- * the `src/` prefix) reads as "tracked" — `path.join(ROOT, token)` would then
138
- * normalize to an out-of-tree absolute path and `fs.existsSync` would probe it,
139
- * turning a doc lint into a filesystem-existence oracle on the CI host. A
140
- * CONTEXT.md reference is always a plain in-repo path, so a `..` escape is never
141
- * legitimate: confine to ROOT and drop anything that climbs out.
142
- */
143
- function isWithinRoot(token) {
144
- const resolved = path.resolve(ROOT, token);
145
- return resolved === ROOT || resolved.startsWith(ROOT + path.sep);
146
- }
147
-
148
135
  /**
149
136
  * Every distinct, trackable file-path token referenced in `text`, with any
150
137
  * trailing `:<line>` suffix stripped.
@@ -167,7 +154,11 @@ function isWithinRoot(token) {
167
154
  * (CONTRIBUTING's "Do not compute a next number locally"), never a real path.
168
155
  */
169
156
  function extractTrackedRefs(text) {
170
- const tokens = new Set();
157
+ // Maps token -> the ContainedPath tryWithinRoot returned for it. ADR-4650:
158
+ // the value that was validated for containment must be the exact value
159
+ // that gets probed later — never a path re-derived (e.g. re-joined) from
160
+ // the token, which could diverge from what was actually checked.
161
+ const tokens = new Map();
171
162
  const add = (raw) => {
172
163
  if (!PATH_TOKEN_RE.test(raw)) return;
173
164
  const token = raw.replace(/:\d+$/, '');
@@ -177,8 +168,18 @@ function extractTrackedRefs(text) {
177
168
  if (!/[A-Za-z0-9_]$/.test(token)) return;
178
169
  if (token.includes('NNNN')) return;
179
170
  if (!isTracked(token)) return;
180
- if (!isWithinRoot(token)) return;
181
- tokens.add(token);
171
+ // Containment decision is the canonical predicate's, per ADR-4650. Carry
172
+ // the returned ContainedPath forward so checkFileRefs stats the SAME
173
+ // value that was validated, instead of re-joining `token` onto ROOT.
174
+ //
175
+ // The containment ANSWER is unchanged from the retired lexical-only
176
+ // `isWithinRoot`, but the canonical predicate resolves symlinks, so a
177
+ // rejected token is now realpath-resolved before being rejected rather
178
+ // than rejected by string comparison alone; the result is still never
179
+ // surfaced and the token is never stat'd unless it is contained.
180
+ const contained = tryWithinRoot(token, ROOT, PathAcceptance.AbsoluteInsideRoot);
181
+ if (contained === null) return;
182
+ tokens.set(token, contained);
182
183
  };
183
184
  const subTokenRe = /[\w.-]+(?:\/[\w.-]+)*/g;
184
185
  for (const line of text.split(/\r?\n/)) {
@@ -199,14 +200,17 @@ function extractTrackedRefs(text) {
199
200
 
200
201
  /** Check A: every tracked reference must resolve on disk. */
201
202
  function checkFileRefs(contextText) {
202
- const tokens = [...extractTrackedRefs(contextText)].sort();
203
+ const entries = [...extractTrackedRefs(contextText)].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
203
204
  const findings = [];
204
- for (const token of tokens) {
205
- if (!fs.existsSync(path.join(ROOT, token))) {
205
+ for (const [token, contained] of entries) {
206
+ // Stat the ContainedPath returned by tryWithinRoot — NOT a re-joined
207
+ // path.join(ROOT, token) — so the path that was validated for
208
+ // containment is the path that is probed (ADR-4650).
209
+ if (!fs.existsSync(contained)) {
206
210
  findings.push(`CONTEXT.md references \`${token}\` which does not exist in the repo.`);
207
211
  }
208
212
  }
209
- return { findings, checked: tokens.length };
213
+ return { findings, checked: entries.length };
210
214
  }
211
215
 
212
216
  /** The glossary's own claim: `Runtime enum: `allRuntimes` (N values: a, b, c)`. */