@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -0,0 +1,891 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gen-exit-code-registry.cjs — generates FIVE byte-identical/derived
4
+ * artifacts from the declaration at gsd-core/bin/shared/exit-codes.json:
5
+ * - gsd-core/bin/lib/exit-code-registry.cjs (tsc-adjacent build tree)
6
+ * - scripts/lib/exit-code-registry.cjs (committed, for scripts/
7
+ * consumers that must work on an unbuilt clone — same reason
8
+ * scripts/lib/cli-exit.cjs exists alongside gsd-core/bin/lib/cli-exit.cjs;
9
+ * see scripts/gen-scripts-cli-exit.cjs).
10
+ * - hooks/lib/exit-code-registry.js (committed, for hooks/
11
+ * consumers that must work on a raw, unbuilt clone — same reason as the
12
+ * scripts/ copy above; see scripts/gen-hooks-cli-exit.cjs, which emits
13
+ * hooks/lib/cli-exit.js's sibling `require('./exit-code-registry.js')`.
14
+ * `.js`, not `.cjs`, to match the hooks/lib/*.js convention — ADR-3889
15
+ * Phase 7, #3911).
16
+ * - src/exit-code-registry.d.cts (the ambient type declaration
17
+ * tsc uses to typecheck src/cli-exit.cts's `require('./exit-code-registry.cjs')`
18
+ * against the shape the .cjs artifacts above actually export — generated
19
+ * from the SAME ENTRY_FIELD_TYPES table serializeRegistry() uses, so the
20
+ * two can never independently drift).
21
+ * - gsd-core/bin/shared/exit-codes.sh (POSIX sh, safe under
22
+ * `set -u`: one `export EXIT_<NAME>=<code>` per entry, sourced by the
23
+ * bash scanners under scripts/ so a shell caller never re-invents a
24
+ * literal exit-code integer — ADR-3889 Phase 4, #3908).
25
+ *
26
+ * ADR-3889 ("One exit-code registry — 0 and 1 are free, everything else is
27
+ * allocated") Phase 1 (#3905) built the single-output allocator; Phase 2
28
+ * (#3906) added the second .cjs emission so scripts/ has its own committed
29
+ * copy instead of reaching into gitignored build output; a follow-up closed
30
+ * the review finding that the .d.cts was hand-maintained with no gate by
31
+ * generating it here too; Phase 4 (#3908) added the shell fragment so the
32
+ * three bash scanners can source symbolic names instead of hardcoding
33
+ * integers; Phase 7 (#3911) added the hooks/lib/ copy so a shipped hook can
34
+ * terminate through `terminateNow` without depending on any build artifact.
35
+ *
36
+ * The three .cjs/.js artifacts (primary, scripts, hooks) are byte-identical:
37
+ * serializeRegistry() only encodes the DECLARATION path (for the banner
38
+ * comment), never the output path, so one generated string is written to all
39
+ * three locations unchanged. The .d.cts and .sh artifacts are separate,
40
+ * smaller derivations but are generated and --check-gated exactly the same
41
+ * way.
42
+ *
43
+ * Nothing in this script emits a registered exit code itself; wiring
44
+ * consumers onto the registry is separate work.
45
+ *
46
+ * Usage:
47
+ * node scripts/gen-exit-code-registry.cjs # same as --write
48
+ * node scripts/gen-exit-code-registry.cjs --write # write all five artifacts
49
+ * node scripts/gen-exit-code-registry.cjs --check # exit 1 if ANY committed artifact is stale
50
+ * node scripts/gen-exit-code-registry.cjs --declaration <path> --out <path> --scripts-out <path> --hooks-out <path> --dts-out <path> --sh-out <path> # override for tests
51
+ * node scripts/gen-exit-code-registry.cjs --json # emit ONE JSON report on stdout instead of human prose
52
+ */
53
+
54
+ 'use strict';
55
+
56
+ const fs = require('node:fs');
57
+ const path = require('node:path');
58
+
59
+ const REPO_ROOT = path.resolve(__dirname, '..');
60
+ const DEFAULT_DECLARATION_PATH = path.join(REPO_ROOT, 'gsd-core', 'bin', 'shared', 'exit-codes.json');
61
+ const DEFAULT_OUTPUT_PATH = path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib', 'exit-code-registry.cjs');
62
+ const DEFAULT_SCRIPTS_OUTPUT_PATH = path.join(REPO_ROOT, 'scripts', 'lib', 'exit-code-registry.cjs');
63
+ const DEFAULT_HOOKS_OUTPUT_PATH = path.join(REPO_ROOT, 'hooks', 'lib', 'exit-code-registry.js');
64
+ const DEFAULT_DTS_OUTPUT_PATH = path.join(REPO_ROOT, 'src', 'exit-code-registry.d.cts');
65
+ const DEFAULT_SH_OUTPUT_PATH = path.join(REPO_ROOT, 'gsd-core', 'bin', 'shared', 'exit-codes.sh');
66
+
67
+ /**
68
+ * Single source of the entry field list (name -> TS type), in emission order.
69
+ * serializeRegistry()'s per-entry object literal and serializeDts()'s
70
+ * ExitCodeEntry interface are BOTH derived from this one table, so the two
71
+ * artifacts cannot independently drift out of shape with each other — closing
72
+ * the review finding that the ambient .d.cts was a hand-maintained guess at
73
+ * what serializeRegistry() emits.
74
+ */
75
+ const ENTRY_FIELD_TYPES = Object.freeze({
76
+ code: 'number',
77
+ name: 'string',
78
+ meaning: 'string',
79
+ owner: 'string',
80
+ authorizedBy: 'string',
81
+ });
82
+
83
+ /** Frozen reason codes so tests assert on structure, not prose. */
84
+ const REASON = Object.freeze({
85
+ OK: 'ok_generated_sync',
86
+ DRIFTED: 'fail_generated_drifted',
87
+ USAGE: 'fail_usage',
88
+ MISSING_DECLARATION: 'fail_missing_declaration',
89
+ MALFORMED_DECLARATION: 'fail_malformed_declaration',
90
+ NOT_AN_ARRAY: 'fail_not_an_array',
91
+ EMPTY_DECLARATION: 'fail_empty_declaration',
92
+ INVALID_ENTRY: 'fail_invalid_entry',
93
+ DUPLICATE_CODE: 'fail_duplicate_code',
94
+ DUPLICATE_NAME: 'fail_duplicate_name',
95
+ RESERVED_CODE: 'fail_reserved_code',
96
+ FORBIDDEN_OWNER: 'fail_forbidden_owner',
97
+ MISSING_ARTIFACT: 'fail_missing_artifact',
98
+ INVALID_CHARACTERS: 'fail_invalid_characters',
99
+ });
100
+
101
+ const USAGE_MESSAGE = [
102
+ 'Usage: node scripts/gen-exit-code-registry.cjs [--write|--check] [--declaration <path>] [--out <path>] [--scripts-out <path>] [--hooks-out <path>] [--dts-out <path>] [--sh-out <path>] [--json]',
103
+ ' (no flag) same as --write',
104
+ ' --write write all five generated registry artifacts',
105
+ ' --check exit 1 if ANY committed artifact is stale',
106
+ ' --declaration override the declaration path (default: gsd-core/bin/shared/exit-codes.json)',
107
+ ' --out override the primary output artifact path (default: gsd-core/bin/lib/exit-code-registry.cjs)',
108
+ ' --scripts-out override the secondary output artifact path (default: scripts/lib/exit-code-registry.cjs)',
109
+ ' --hooks-out override the hooks output artifact path (default: hooks/lib/exit-code-registry.js)',
110
+ ' --dts-out override the ambient type declaration path (default: src/exit-code-registry.d.cts)',
111
+ ' --sh-out override the shell-sourceable fragment path (default: gsd-core/bin/shared/exit-codes.sh)',
112
+ ' --json emit ONE JSON report ({ok, reason, context, detail?}) on stdout instead of human-readable prose',
113
+ ].join('\n');
114
+
115
+ /** SCREAMING_SNAKE_CASE: starts with a letter, only uppercase letters/digits/underscores. */
116
+ const NAME_RE = /^[A-Z][A-Z0-9_]*$/;
117
+
118
+ /** Fields every entry must carry as a non-empty, non-whitespace-only string. */
119
+ const REQUIRED_STRING_FIELDS = ['meaning', 'owner', 'authorizedBy'];
120
+
121
+ /**
122
+ * Characters forbidden in any declaration string field: a literal pipe `|`
123
+ * (the Markdown table cell delimiter `gen-exit-code-docs.cjs`'s
124
+ * `renderRegisteredTable` interpolates these fields into — an unescaped `|`
125
+ * breaks the row and everything after it lands verbatim in the rendered
126
+ * page, including a forged Markdown heading), and any C0 control character
127
+ * (`\x00`-`\x1F`, `\x7F`) — which subsumes CR (`\r`) and LF (`\n`), both of
128
+ * which would otherwise let a single declaration entry inject an entire
129
+ * extra row (or non-table content) into the generated table.
130
+ *
131
+ * Enforced HERE, at the validator both generators share (`gen-exit-code-registry.cjs`'s
132
+ * `validateEntry`, called by `gen-exit-code-docs.cjs`'s `loadEntries`), not
133
+ * at the docs renderer — failing closed at the declaration source is
134
+ * correct: an escaping fix at render time would let a malformed declaration
135
+ * through validation and only cosmetically repair the symptom (#3913 P9
136
+ * SEC-3).
137
+ *
138
+ * Checked via char codes rather than a literal control-character regex
139
+ * range — same approach as `scripts/registry-schema.cjs`'s
140
+ * `hasDisallowedControlChar` — so this never trips ESLint's `no-control-regex`.
141
+ *
142
+ * @param {string} v
143
+ * @returns {boolean}
144
+ */
145
+ function hasForbiddenDeclarationChar(v) {
146
+ for (let i = 0; i < v.length; i += 1) {
147
+ const code = v.charCodeAt(i);
148
+ if (code === 0x7c) return true; // '|'
149
+ if (code < 0x20 || code === 0x7f) return true; // C0 control chars (incl. CR/LF/tab) + DEL
150
+ }
151
+ return false;
152
+ }
153
+
154
+ /**
155
+ * Bands, per ADR-3889 §1:
156
+ * 0, 1 free (not allocatable here)
157
+ * 2 hook-adapter only
158
+ * 3-13 Node-reserved
159
+ * 14-63, 79, 126+ outside every band
160
+ * 64-78 generic
161
+ * 80-125 domain
162
+ *
163
+ * SINGLE SOURCE for every band boundary in this file: `isAllocatableCode`,
164
+ * `bandFor`, and (via scripts/gen-exit-code-docs.cjs's `classifyBand`) the
165
+ * generated "Reserved bands" table are ALL derived from this one ordered
166
+ * list of `{category, allocatable, test}` predicates — there is no second
167
+ * hand-typed range anywhere else. Widening or narrowing a band means editing
168
+ * a `test` function here; every consumer (validation, the docs page) picks
169
+ * that change up with nothing else to keep in sync.
170
+ */
171
+ const BANDS = Object.freeze([
172
+ { category: 'free', allocatable: false, test: (code) => code === 0 || code === 1 },
173
+ { category: 'hook-only', allocatable: true, test: (code) => code === 2 },
174
+ { category: 'node-reserved', allocatable: false, test: (code) => code >= 3 && code <= 13 },
175
+ { category: 'generic', allocatable: true, test: (code) => code >= 64 && code <= 78 },
176
+ { category: 'domain', allocatable: true, test: (code) => code >= 80 && code <= 125 },
177
+ { category: 'shell-signal', allocatable: false, test: (code) => code >= 126 },
178
+ ]);
179
+
180
+ /** The band a code falls into, or `null` for the residual "outside every band" gap (14-63, 79) that no BANDS entry above claims. */
181
+ function bandEntryFor(code) {
182
+ return BANDS.find((band) => band.test(code)) || null;
183
+ }
184
+
185
+ function isAllocatableCode(code) {
186
+ const band = bandEntryFor(code);
187
+ return band !== null && band.allocatable;
188
+ }
189
+
190
+ /**
191
+ * Label the non-allocatable band a rejected code falls into, per the same
192
+ * BANDS table isAllocatableCode reads. Only called for codes that already
193
+ * failed isAllocatableCode, so an allocatable band's category is never
194
+ * returned here.
195
+ * @returns {string}
196
+ */
197
+ function bandFor(code) {
198
+ const band = bandEntryFor(code);
199
+ return band ? band.category : 'outside-every-band'; // 14-63, 79
200
+ }
201
+
202
+ /**
203
+ * Validate a single declaration entry's shape and band membership.
204
+ * @returns {{ok:true}|{ok:false,reason:string,message:string,context:object}}
205
+ */
206
+ function validateEntry(entry, index) {
207
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
208
+ return {
209
+ ok: false,
210
+ reason: REASON.INVALID_ENTRY,
211
+ message: `entry[${index}] is not an object: ${JSON.stringify(entry)}`,
212
+ context: { field: 'entry', index },
213
+ };
214
+ }
215
+
216
+ const { code, name } = entry;
217
+ if (!Number.isInteger(code) || code < 0) {
218
+ return {
219
+ ok: false,
220
+ reason: REASON.INVALID_ENTRY,
221
+ message: `entry[${index}].code must be a non-negative integer (no coercion), received ${JSON.stringify(code)}`,
222
+ context: { field: 'code', index, code },
223
+ };
224
+ }
225
+
226
+ if (typeof name !== 'string' || name.trim() === '' || !NAME_RE.test(name)) {
227
+ return {
228
+ ok: false,
229
+ reason: REASON.INVALID_ENTRY,
230
+ message: `entry[${index}].name must be a non-empty SCREAMING_SNAKE_CASE string, received ${JSON.stringify(name)}`,
231
+ context: { field: 'name', index, code, name },
232
+ };
233
+ }
234
+
235
+ for (const field of REQUIRED_STRING_FIELDS) {
236
+ const value = entry[field];
237
+ if (typeof value !== 'string' || value.trim() === '') {
238
+ return {
239
+ ok: false,
240
+ reason: REASON.INVALID_ENTRY,
241
+ message: `entry[${index}] (${name}).${field} must be a non-empty string, received ${JSON.stringify(value)}`,
242
+ context: { field, index, code, name },
243
+ };
244
+ }
245
+ if (hasForbiddenDeclarationChar(value)) {
246
+ return {
247
+ ok: false,
248
+ reason: REASON.INVALID_CHARACTERS,
249
+ message: `entry[${index}] (${name}).${field} must not contain a "|", CR, LF, or other control character ` +
250
+ `(these are interpolated into a Markdown table cell by gen-exit-code-docs.cjs), received ${JSON.stringify(value)}`,
251
+ context: { field, index, code, name },
252
+ };
253
+ }
254
+ }
255
+
256
+ if (!isAllocatableCode(code)) {
257
+ return {
258
+ ok: false,
259
+ reason: REASON.RESERVED_CODE,
260
+ message: `entry[${index}] (${name}) declares code ${code}, which is outside every allocatable band ` +
261
+ `(2 hook-adapter only; 64-78 generic; 80-125 domain) — see ADR-3889 §1`,
262
+ context: { code, band: bandFor(code), index, name },
263
+ };
264
+ }
265
+
266
+ if (code === 2 && entry.owner !== 'hook-adapter') {
267
+ return {
268
+ ok: false,
269
+ reason: REASON.FORBIDDEN_OWNER,
270
+ message: `entry[${index}] (${name}) declares code 2 with owner "${entry.owner}" — code 2 is reserved to ` +
271
+ `the Claude Code hook protocol and may only be owned by "hook-adapter"`,
272
+ context: { code, owner: entry.owner, requiredOwner: 'hook-adapter', index, name },
273
+ };
274
+ }
275
+
276
+ return { ok: true };
277
+ }
278
+
279
+ /**
280
+ * Validate the whole declaration: every entry individually, then the
281
+ * cross-entry invariants (one number one meaning; one owner emits a given
282
+ * code — but the SAME owner may legitimately own several distinct codes).
283
+ * @returns {{ok:true}|{ok:false,reason:string,message:string,context:object}}
284
+ */
285
+ function validateEntries(entries) {
286
+ for (let i = 0; i < entries.length; i++) {
287
+ const result = validateEntry(entries[i], i);
288
+ if (!result.ok) return result;
289
+ }
290
+
291
+ const byCode = new Map();
292
+ const byName = new Map();
293
+ for (const entry of entries) {
294
+ if (byCode.has(entry.code)) {
295
+ const other = byCode.get(entry.code);
296
+ return {
297
+ ok: false,
298
+ reason: REASON.DUPLICATE_CODE,
299
+ message: `code ${entry.code} is declared twice: "${other.name}" and "${entry.name}"`,
300
+ context: { code: entry.code, names: [other.name, entry.name] },
301
+ };
302
+ }
303
+ byCode.set(entry.code, entry);
304
+
305
+ if (byName.has(entry.name)) {
306
+ const other = byName.get(entry.name);
307
+ return {
308
+ ok: false,
309
+ reason: REASON.DUPLICATE_NAME,
310
+ message: `name "${entry.name}" is declared twice: code ${other.code} and code ${entry.code}`,
311
+ context: { name: entry.name, codes: [other.code, entry.code] },
312
+ };
313
+ }
314
+ byName.set(entry.name, entry);
315
+ }
316
+
317
+ return { ok: true };
318
+ }
319
+
320
+ /**
321
+ * Load and parse the declaration file.
322
+ * @returns {{ok:true,entries:Array}|{ok:false,reason:string,message:string}}
323
+ */
324
+ function loadDeclaration(declarationPath) {
325
+ if (!fs.existsSync(declarationPath)) {
326
+ return {
327
+ ok: false,
328
+ reason: REASON.MISSING_DECLARATION,
329
+ message: `declaration not found at ${declarationPath}`,
330
+ context: { path: declarationPath },
331
+ };
332
+ }
333
+
334
+ let raw;
335
+ try {
336
+ raw = fs.readFileSync(declarationPath, 'utf8');
337
+ } catch (err) {
338
+ return {
339
+ ok: false,
340
+ reason: REASON.MISSING_DECLARATION,
341
+ message: `could not read ${declarationPath}: ${err.message}`,
342
+ context: { path: declarationPath },
343
+ };
344
+ }
345
+
346
+ let parsed;
347
+ try {
348
+ parsed = JSON.parse(raw);
349
+ } catch (err) {
350
+ return {
351
+ ok: false,
352
+ reason: REASON.MALFORMED_DECLARATION,
353
+ message: `${declarationPath} is not valid JSON: ${err.message}`,
354
+ context: { path: declarationPath },
355
+ };
356
+ }
357
+
358
+ if (!Array.isArray(parsed)) {
359
+ return {
360
+ ok: false,
361
+ reason: REASON.NOT_AN_ARRAY,
362
+ message: `${declarationPath} must be a JSON array, received ${parsed === null ? 'null' : typeof parsed}`,
363
+ context: { path: declarationPath },
364
+ };
365
+ }
366
+
367
+ if (parsed.length === 0) {
368
+ return {
369
+ ok: false,
370
+ reason: REASON.EMPTY_DECLARATION,
371
+ message: `${declarationPath} is an empty array — declare at least one exit code`,
372
+ context: { path: declarationPath },
373
+ };
374
+ }
375
+
376
+ return { ok: true, entries: parsed };
377
+ }
378
+
379
+ /**
380
+ * Hand-serialize the generated registry module (string concatenation, like
381
+ * gsd-core/bin/lib/capability-registry.cjs — no build step, no template
382
+ * engine, so the emitted bytes are exactly what `--check` re-derives).
383
+ */
384
+ function serializeRegistry(entries, declarationPath) {
385
+ const relDeclaration = path.relative(REPO_ROOT, declarationPath).split(path.sep).join('/');
386
+ const banner = [
387
+ '\'use strict\';',
388
+ '',
389
+ '// GENERATED FILE — DO NOT EDIT BY HAND.',
390
+ `// Source of truth: ${relDeclaration}. Regenerate with:`,
391
+ '// node scripts/gen-exit-code-registry.cjs --write',
392
+ '// This exact content is emitted to THREE locations — gsd-core/bin/lib/exit-code-registry.cjs,',
393
+ '// scripts/lib/exit-code-registry.cjs, and hooks/lib/exit-code-registry.js (the latter two',
394
+ '// committed so scripts/ and hooks/ consumers work on an unbuilt clone) — all byte-compared by',
395
+ '// `npm run lint:generated-sync` (#3905 ADR-3889 Phase 1; #3906 Phase 2 added the second copy;',
396
+ '// #3911 ADR-3889 Phase 7 added the hooks/lib/ copy).',
397
+ '//',
398
+ '// exitCodeFor(name) / nameForExitCode(code) are pure and total over this',
399
+ '// closed table — each throws for anything not registered here.',
400
+ '',
401
+ ].join('\n');
402
+
403
+ const entryFieldNames = Object.keys(ENTRY_FIELD_TYPES);
404
+ const entryLiterals = entries.map((e) => {
405
+ const fieldLines = entryFieldNames.map((field) => ` ${field}: ${JSON.stringify(e[field])},`).join('\n');
406
+ return ` Object.freeze({\n${fieldLines}\n })`;
407
+ }).join(',\n');
408
+
409
+ const body = [
410
+ 'const EXIT_CODES = Object.freeze([',
411
+ entryLiterals,
412
+ ']);',
413
+ '',
414
+ 'const NAME_TO_CODE = new Map(EXIT_CODES.map((entry) => [entry.name, entry.code]));',
415
+ 'const CODE_TO_NAME = new Map(EXIT_CODES.map((entry) => [entry.code, entry.name]));',
416
+ '',
417
+ '/**',
418
+ ' * Resolve the registered exit code for a symbolic name. Pure, total: throws',
419
+ ' * for anything not an exact, registered, exact-case key — including',
420
+ ' * non-strings, the empty string, untrimmed strings, wrong case, and',
421
+ ' * prototype-chain names like `__proto__`/`constructor`/`toString` (a Map',
422
+ ' * lookup never touches the prototype chain, so these are indistinguishable',
423
+ ' * from any other unregistered name).',
424
+ ' *',
425
+ ' * @param {string} name',
426
+ ' * @returns {number}',
427
+ ' */',
428
+ 'function exitCodeFor(name) {',
429
+ ' if (typeof name !== \'string\' || name.length === 0) {',
430
+ ' throw new Error(`exitCodeFor: name must be a non-empty string, received ${JSON.stringify(name)}`);',
431
+ ' }',
432
+ ' if (!NAME_TO_CODE.has(name)) {',
433
+ ' throw new Error(`exitCodeFor: unregistered exit code name: ${JSON.stringify(name)}`);',
434
+ ' }',
435
+ ' return NAME_TO_CODE.get(name);',
436
+ '}',
437
+ '',
438
+ '/**',
439
+ ' * Reverse of exitCodeFor: resolve the symbolic name for a registered exit',
440
+ ' * code. Pure, total: throws for anything not an exact, registered code.',
441
+ ' *',
442
+ ' * @param {number} code',
443
+ ' * @returns {string}',
444
+ ' */',
445
+ 'function nameForExitCode(code) {',
446
+ ' if (!CODE_TO_NAME.has(code)) {',
447
+ ' throw new Error(`nameForExitCode: unregistered exit code: ${JSON.stringify(code)}`);',
448
+ ' }',
449
+ ' return CODE_TO_NAME.get(code);',
450
+ '}',
451
+ '',
452
+ 'module.exports = { EXIT_CODES, exitCodeFor, nameForExitCode };',
453
+ '',
454
+ ].join('\n');
455
+
456
+ return banner + '\n' + body;
457
+ }
458
+
459
+ /**
460
+ * Generate the ambient type declaration for the generated .cjs registry
461
+ * artifacts. Derived from the SAME ENTRY_FIELD_TYPES table serializeRegistry()
462
+ * iterates for its per-entry object literals, and from serializeRegistry()'s
463
+ * own fixed `module.exports = { EXIT_CODES, exitCodeFor, nameForExitCode }`
464
+ * shape — so this declaration cannot drift out of step with what the sibling
465
+ * .cjs artifacts actually export without both call sites being edited
466
+ * together. Structural only (no per-entry data): the type is the same
467
+ * regardless of how many rows the declaration has.
468
+ */
469
+ function serializeDts(declarationPath) {
470
+ const relDeclaration = path.relative(REPO_ROOT, declarationPath).split(path.sep).join('/');
471
+ const fieldLines = Object.entries(ENTRY_FIELD_TYPES)
472
+ .map(([field, type]) => ` readonly ${field}: ${type};`)
473
+ .join('\n');
474
+
475
+ return [
476
+ '// GENERATED FILE — DO NOT EDIT BY HAND.',
477
+ `// Source of truth: ${relDeclaration} + the ENTRY_FIELD_TYPES table in`,
478
+ '// scripts/gen-exit-code-registry.cjs. Regenerate with:',
479
+ '// node scripts/gen-exit-code-registry.cjs --write',
480
+ '//',
481
+ '// Ambient type declaration for exit-code-registry.cjs — a GENERATED,',
482
+ '// committed artifact with no `.cts` source of its own (it is hand-serialized',
483
+ '// from gsd-core/bin/shared/exit-codes.json by scripts/gen-exit-code-registry.cjs,',
484
+ '// ADR-3889 §2, #3905/#3906), so tsc has nothing to compile for it. This file',
485
+ "// exists purely so `src/cli-exit.cts`'s `require('./exit-code-registry.cjs')`",
486
+ '// type-checks against the SAME shape the generated artifact actually exports',
487
+ '// at runtime — mirroring the src/vendor/*.d.cts pattern already used for',
488
+ '// other verbatim/generated JS this tree resolves types for without compiling.',
489
+ '//',
490
+ '// This declaration is generated from the same ENTRY_FIELD_TYPES table',
491
+ "// serializeRegistry()'s per-entry object literals iterate, and is",
492
+ '// byte-compared by `node scripts/gen-exit-code-registry.cjs --check`',
493
+ '// (the same check that already covers the two sibling .cjs artifacts) so a',
494
+ '// shape drift here fails the build instead of surfacing at a destructuring',
495
+ '// call site.',
496
+ '',
497
+ 'export interface ExitCodeEntry {',
498
+ fieldLines,
499
+ '}',
500
+ '',
501
+ 'declare const exitCodeRegistry: {',
502
+ ' readonly EXIT_CODES: readonly ExitCodeEntry[];',
503
+ ' // Property-typed function signatures (`name: (args) => ret`), NOT method',
504
+ ' // shorthand (`name(args): ret`) — the latter is a TS "method" and trips',
505
+ ' // @typescript-eslint/unbound-method at every destructuring call site',
506
+ ' // (`const { exitCodeFor } = ...`), since a method may implicitly use',
507
+ ' // `this`. These are pure functions that never do, so they are typed as',
508
+ ' // plain function-valued properties instead.',
509
+ ' exitCodeFor: (name: string) => number;',
510
+ ' nameForExitCode: (code: number) => string;',
511
+ '};',
512
+ '',
513
+ 'export = exitCodeRegistry;',
514
+ '',
515
+ ].join('\n');
516
+ }
517
+
518
+ /**
519
+ * Generate the shell-sourceable fragment: one `export EXIT_<NAME>=<code>`
520
+ * line per declared entry, POSIX sh, safe under `set -u` (a sourced file that
521
+ * only ever ASSIGNS variables can never trip an unset-variable check,
522
+ * regardless of what the caller's shell had in scope beforehand).
523
+ *
524
+ * Consumed by the bash scanners under scripts/ via `. gsd-core/bin/shared/
525
+ * exit-codes.sh` (ADR-3889 Phase 4, #3908) so a shell caller resolves a
526
+ * symbolic name instead of hardcoding a literal integer that can silently
527
+ * drift from the registry.
528
+ */
529
+ function serializeSh(entries, declarationPath) {
530
+ const relDeclaration = path.relative(REPO_ROOT, declarationPath).split(path.sep).join('/');
531
+ const banner = [
532
+ '#!/bin/sh',
533
+ '# GENERATED FILE — DO NOT EDIT BY HAND.',
534
+ `# Source of truth: ${relDeclaration}. Regenerate with:`,
535
+ '# node scripts/gen-exit-code-registry.cjs --write',
536
+ '#',
537
+ '# One `export EXIT_<NAME>=<code>` per gsd-core/bin/shared/exit-codes.json',
538
+ '# entry (ADR-3889 §2, #3905/#3906/#3908). POSIX sh, safe under `set -u`:',
539
+ '# sourcing this file only ever ASSIGNS variables, never reads one, so it',
540
+ '# cannot trip an unset-variable check regardless of the caller\'s existing',
541
+ '# environment.',
542
+ '#',
543
+ '# Usage (from a scanner under scripts/):',
544
+ '# . "$(dirname "$0")/../gsd-core/bin/shared/exit-codes.sh"',
545
+ '# exit "$EXIT_UNAVAILABLE"',
546
+ '',
547
+ ].join('\n');
548
+
549
+ const lines = entries.map((e) => `export EXIT_${e.name}=${e.code}`);
550
+
551
+ return banner + lines.join('\n') + '\n';
552
+ }
553
+
554
+ /**
555
+ * Load, validate, and serialize the declaration in one step.
556
+ * @returns {{ok:true,content:string}|{ok:false,reason:string,message:string}}
557
+ */
558
+ function buildRegistryContent(declarationPath) {
559
+ const loaded = loadDeclaration(declarationPath);
560
+ if (!loaded.ok) return loaded;
561
+
562
+ const validated = validateEntries(loaded.entries);
563
+ if (!validated.ok) return validated;
564
+
565
+ return { ok: true, content: serializeRegistry(loaded.entries, declarationPath), entries: loaded.entries };
566
+ }
567
+
568
+ function printFail(result) {
569
+ console.error(`FAIL gen-exit-code-registry: ${result.reason}`);
570
+ console.error(` ${result.message}`);
571
+ }
572
+
573
+ /**
574
+ * Emit a failure outcome: structured JSON on stdout (and NO stderr prose)
575
+ * when `json` is set, otherwise the legacy human-readable stderr report.
576
+ * `context` carries the specifics (offending code/name/field/path) that the
577
+ * `detail` prose currently embeds, so a `--json` consumer never needs to
578
+ * parse prose to recover them — it defaults to `null` for reasons (USAGE,
579
+ * DRIFTED, MISSING_ARTIFACT) that carry no structured specifics.
580
+ * @param {{reason:string, message?:string, context?:object}} result
581
+ * @param {boolean} json
582
+ */
583
+ function emitFail(result, json) {
584
+ if (json) {
585
+ process.stdout.write(JSON.stringify({ ok: false, reason: result.reason, context: result.context ?? null, detail: result.message }) + '\n');
586
+ return;
587
+ }
588
+ printFail(result);
589
+ }
590
+
591
+ /**
592
+ * Emit a success outcome: structured JSON on stdout when `json` is set,
593
+ * otherwise the legacy human-readable stdout line.
594
+ * @param {string} reason
595
+ * @param {string} humanMessage
596
+ * @param {boolean} json
597
+ */
598
+ function emitOk(reason, humanMessage, json) {
599
+ if (json) {
600
+ process.stdout.write(JSON.stringify({ ok: true, reason }) + '\n');
601
+ return;
602
+ }
603
+ console.log(humanMessage);
604
+ }
605
+
606
+ function doWrite(declarationPath, outPath, scriptsOutPath, hooksOutPath, dtsPath, shPath, json) {
607
+ const result = buildRegistryContent(declarationPath);
608
+ if (!result.ok) {
609
+ emitFail(result, json);
610
+ return 1;
611
+ }
612
+ // The three .cjs/.js artifacts are byte-identical copies of the same
613
+ // generated content (serializeRegistry never encodes the output path), so
614
+ // the same string is written to all three locations unchanged.
615
+ for (const target of [outPath, scriptsOutPath, hooksOutPath]) {
616
+ fs.mkdirSync(path.dirname(target), { recursive: true });
617
+ fs.writeFileSync(target, result.content, 'utf8');
618
+ }
619
+ const dtsContent = serializeDts(declarationPath);
620
+ fs.mkdirSync(path.dirname(dtsPath), { recursive: true });
621
+ fs.writeFileSync(dtsPath, dtsContent, 'utf8');
622
+ const shContent = serializeSh(result.entries, declarationPath);
623
+ fs.mkdirSync(path.dirname(shPath), { recursive: true });
624
+ fs.writeFileSync(shPath, shContent, 'utf8');
625
+ emitOk(
626
+ REASON.OK,
627
+ `ok gen-exit-code-registry: wrote ${outPath}\nok gen-exit-code-registry: wrote ${scriptsOutPath}\n`
628
+ + `ok gen-exit-code-registry: wrote ${hooksOutPath}\n`
629
+ + `ok gen-exit-code-registry: wrote ${dtsPath}\nok gen-exit-code-registry: wrote ${shPath}`,
630
+ json,
631
+ );
632
+ return 0;
633
+ }
634
+
635
+ /**
636
+ * Verify one committed artifact against the freshly generated content.
637
+ * @returns {{ok:true}|{ok:false,reason:string,message:string,context:object}}
638
+ */
639
+ function checkOneArtifact(artifactLabel, artifactPath, content) {
640
+ if (!fs.existsSync(artifactPath)) {
641
+ return {
642
+ ok: false,
643
+ reason: REASON.MISSING_ARTIFACT,
644
+ message: `${artifactPath} (${artifactLabel}) does not exist. Run:\n node scripts/gen-exit-code-registry.cjs --write`,
645
+ context: { artifact: artifactLabel, path: artifactPath },
646
+ };
647
+ }
648
+
649
+ const committed = fs.readFileSync(artifactPath, 'utf8');
650
+ if (committed !== content) {
651
+ return {
652
+ ok: false,
653
+ reason: REASON.DRIFTED,
654
+ message:
655
+ `${artifactPath} (${artifactLabel}, ${committed.length} bytes) != freshly generated content (${content.length} bytes)\n\n`
656
+ + 'Regenerate with:\n node scripts/gen-exit-code-registry.cjs --write',
657
+ context: { artifact: artifactLabel, path: artifactPath },
658
+ };
659
+ }
660
+
661
+ return { ok: true };
662
+ }
663
+
664
+ /**
665
+ * --check verifies ALL FIVE committed artifacts against the same freshly
666
+ * generated content and fails naming which one drifted (or is missing) if
667
+ * any does. Checked in a fixed order (primary, secondary, hooks, dts, sh) so
668
+ * a single-artifact failure is always reported deterministically.
669
+ */
670
+ function doCheck(declarationPath, outPath, scriptsOutPath, hooksOutPath, dtsPath, shPath, json) {
671
+ const result = buildRegistryContent(declarationPath);
672
+ if (!result.ok) {
673
+ emitFail(result, json);
674
+ return 1;
675
+ }
676
+ const dtsContent = serializeDts(declarationPath);
677
+ const shContent = serializeSh(result.entries, declarationPath);
678
+
679
+ const artifacts = [
680
+ ['primary', outPath, result.content],
681
+ ['secondary', scriptsOutPath, result.content],
682
+ ['hooks', hooksOutPath, result.content],
683
+ ['dts', dtsPath, dtsContent],
684
+ ['sh', shPath, shContent],
685
+ ];
686
+ for (const [artifactLabel, artifactPath, content] of artifacts) {
687
+ const checked = checkOneArtifact(artifactLabel, artifactPath, content);
688
+ if (!checked.ok) {
689
+ emitFail(checked, json);
690
+ return 1;
691
+ }
692
+ }
693
+
694
+ emitOk(
695
+ REASON.OK,
696
+ `ok gen-exit-code-registry: ${outPath} matches ${declarationPath}\n`
697
+ + `ok gen-exit-code-registry: ${scriptsOutPath} matches ${declarationPath}\n`
698
+ + `ok gen-exit-code-registry: ${hooksOutPath} matches ${declarationPath}\n`
699
+ + `ok gen-exit-code-registry: ${dtsPath} matches ${declarationPath}\n`
700
+ + `ok gen-exit-code-registry: ${shPath} matches ${declarationPath}`,
701
+ json,
702
+ );
703
+ return 0;
704
+ }
705
+
706
+ /**
707
+ * @returns {{mode:'write'|'check', declarationPath:?string, outPath:?string, scriptsOutPath:?string, hooksOutPath:?string, dtsPath:?string, shPath:?string, json:boolean}}
708
+ */
709
+ function parseArgs(argv) {
710
+ let mode = null;
711
+ let declarationPath = null;
712
+ let outPath = null;
713
+ let scriptsOutPath = null;
714
+ let hooksOutPath = null;
715
+ let dtsPath = null;
716
+ let shPath = null;
717
+ let json = false;
718
+
719
+ for (let i = 0; i < argv.length; i++) {
720
+ const arg = argv[i];
721
+ if (arg === '--write' || arg === '--check') {
722
+ if (mode !== null) {
723
+ throw new Error(`conflicting mode flags: --${mode} and ${arg}`);
724
+ }
725
+ mode = arg === '--write' ? 'write' : 'check';
726
+ } else if (arg === '--json') {
727
+ json = true;
728
+ } else if (arg === '--declaration') {
729
+ const value = argv[++i];
730
+ if (value === undefined) throw new Error('--declaration requires a value');
731
+ declarationPath = value;
732
+ } else if (arg.startsWith('--declaration=')) {
733
+ declarationPath = arg.slice('--declaration='.length);
734
+ } else if (arg === '--out') {
735
+ const value = argv[++i];
736
+ if (value === undefined) throw new Error('--out requires a value');
737
+ outPath = value;
738
+ } else if (arg.startsWith('--out=')) {
739
+ outPath = arg.slice('--out='.length);
740
+ } else if (arg === '--scripts-out') {
741
+ const value = argv[++i];
742
+ if (value === undefined) throw new Error('--scripts-out requires a value');
743
+ scriptsOutPath = value;
744
+ } else if (arg.startsWith('--scripts-out=')) {
745
+ scriptsOutPath = arg.slice('--scripts-out='.length);
746
+ } else if (arg === '--hooks-out') {
747
+ const value = argv[++i];
748
+ if (value === undefined) throw new Error('--hooks-out requires a value');
749
+ hooksOutPath = value;
750
+ } else if (arg.startsWith('--hooks-out=')) {
751
+ hooksOutPath = arg.slice('--hooks-out='.length);
752
+ } else if (arg === '--dts-out') {
753
+ const value = argv[++i];
754
+ if (value === undefined) throw new Error('--dts-out requires a value');
755
+ dtsPath = value;
756
+ } else if (arg.startsWith('--dts-out=')) {
757
+ dtsPath = arg.slice('--dts-out='.length);
758
+ } else if (arg === '--sh-out') {
759
+ const value = argv[++i];
760
+ if (value === undefined) throw new Error('--sh-out requires a value');
761
+ shPath = value;
762
+ } else if (arg.startsWith('--sh-out=')) {
763
+ shPath = arg.slice('--sh-out='.length);
764
+ } else {
765
+ throw new Error(`unrecognized argument: ${arg}`);
766
+ }
767
+ }
768
+
769
+ return { mode: mode || 'write', declarationPath, outPath, scriptsOutPath, hooksOutPath, dtsPath, shPath, json };
770
+ }
771
+
772
+ function main() {
773
+ // --json must be honored even on a parse failure (e.g. an unrecognized
774
+ // flag alongside --json), so it is detected from the raw argv rather
775
+ // than from parseArgs's return value, which may never be produced.
776
+ const rawArgv = process.argv.slice(2);
777
+ const jsonRequested = rawArgv.includes('--json');
778
+
779
+ let args;
780
+ try {
781
+ args = parseArgs(rawArgv);
782
+ } catch (err) {
783
+ emitFail({ reason: REASON.USAGE, message: jsonRequested ? err.message : `${err.message}\n${USAGE_MESSAGE}` }, jsonRequested);
784
+ return 1;
785
+ }
786
+
787
+ const declarationPath = args.declarationPath || DEFAULT_DECLARATION_PATH;
788
+ const outPath = args.outPath || DEFAULT_OUTPUT_PATH;
789
+ const scriptsOutPath = args.scriptsOutPath || DEFAULT_SCRIPTS_OUTPUT_PATH;
790
+ const hooksOutPath = args.hooksOutPath || DEFAULT_HOOKS_OUTPUT_PATH;
791
+ const dtsPath = args.dtsPath || DEFAULT_DTS_OUTPUT_PATH;
792
+ const shPath = args.shPath || DEFAULT_SH_OUTPUT_PATH;
793
+
794
+ return args.mode === 'check'
795
+ ? doCheck(declarationPath, outPath, scriptsOutPath, hooksOutPath, dtsPath, shPath, args.json)
796
+ : doWrite(declarationPath, outPath, scriptsOutPath, hooksOutPath, dtsPath, shPath, args.json);
797
+ }
798
+
799
+ if (require.main === module) process.exitCode = main();
800
+
801
+ /**
802
+ * Classify a single code into one of the band categories the docs page
803
+ * renders a row for. Reads the SAME `bandEntryFor`/BANDS table that
804
+ * `isAllocatableCode`/`bandFor` are themselves derived from — there is no
805
+ * second, independently-typed band boundary anywhere in this classification,
806
+ * so widening or narrowing a band (editing a `test` in BANDS) changes what
807
+ * this returns too, with nothing else to keep in sync.
808
+ * @param {number} code
809
+ * @returns {'free'|'hook-only'|'node-reserved'|'outside-every-band'|'generic'|'domain'|'shell-signal'}
810
+ */
811
+ function classifyBand(code) {
812
+ const band = bandEntryFor(code);
813
+ return band ? band.category : 'outside-every-band'; // 14-63, 79
814
+ }
815
+
816
+ /**
817
+ * Scan the code space [0, maxCode] and group it into maximal contiguous
818
+ * runs of the same classifyBand() category, in the order those categories
819
+ * first appear. The run touching `maxCode` is marked `openEnded: true` for
820
+ * any category whose classification never changes past that point
821
+ * (currently only 'shell-signal', since bandFor(code >= 126) is constant),
822
+ * so the docs renderer can print it as `126+` instead of a false upper
823
+ * bound. This is what makes the docs page's band table a DERIVED artifact:
824
+ * widening a band in isAllocatableCode/bandFor changes what this function
825
+ * returns, which changes the rendered table, with no second literal to
826
+ * keep in sync.
827
+ * @param {number} maxCode
828
+ * @returns {Array<{category:string, ranges:Array<{start:number,end:number,openEnded:boolean}>}>}
829
+ */
830
+ function computeBandRanges(maxCode) {
831
+ /** @type {Map<string, Array<{start:number,end:number,openEnded:boolean}>>} */
832
+ const byCategory = new Map();
833
+ const order = [];
834
+
835
+ let runCategory = null;
836
+ let runStart = null;
837
+ for (let code = 0; code <= maxCode; code += 1) {
838
+ const category = classifyBand(code);
839
+ if (category !== runCategory) {
840
+ if (runCategory !== null) {
841
+ pushRun(byCategory, order, runCategory, runStart, code - 1, false);
842
+ }
843
+ runCategory = category;
844
+ runStart = code;
845
+ }
846
+ }
847
+ // Close the final run. It is open-ended (unbounded above) exactly when its
848
+ // category classification is constant for every code beyond maxCode too —
849
+ // true today only for 'shell-signal', since bandFor treats every code
850
+ // >= 126 identically with no further upper boundary.
851
+ const openEnded = classifyBand(maxCode) === classifyBand(maxCode + 1);
852
+ pushRun(byCategory, order, runCategory, runStart, maxCode, openEnded);
853
+
854
+ return order.map((category) => ({ category, ranges: byCategory.get(category) }));
855
+ }
856
+
857
+ function pushRun(byCategory, order, category, start, end, openEnded) {
858
+ if (!byCategory.has(category)) {
859
+ byCategory.set(category, []);
860
+ order.push(category);
861
+ }
862
+ byCategory.get(category).push({ start, end, openEnded });
863
+ }
864
+
865
+ module.exports = {
866
+ REASON,
867
+ USAGE_MESSAGE,
868
+ DEFAULT_DECLARATION_PATH,
869
+ DEFAULT_OUTPUT_PATH,
870
+ DEFAULT_SCRIPTS_OUTPUT_PATH,
871
+ DEFAULT_HOOKS_OUTPUT_PATH,
872
+ DEFAULT_DTS_OUTPUT_PATH,
873
+ DEFAULT_SH_OUTPUT_PATH,
874
+ ENTRY_FIELD_TYPES,
875
+ BANDS,
876
+ bandEntryFor,
877
+ isAllocatableCode,
878
+ bandFor,
879
+ classifyBand,
880
+ computeBandRanges,
881
+ hasForbiddenDeclarationChar,
882
+ validateEntry,
883
+ validateEntries,
884
+ loadDeclaration,
885
+ serializeRegistry,
886
+ serializeDts,
887
+ serializeSh,
888
+ buildRegistryContent,
889
+ parseArgs,
890
+ main,
891
+ };