@opengsd/gsd-core 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-code-fixer.compact.md +7 -6
  8. package/agents/gsd-code-fixer.md +9 -8
  9. package/agents/gsd-debug-session-manager.compact.md +17 -2
  10. package/agents/gsd-debug-session-manager.md +17 -2
  11. package/agents/gsd-debugger.md +2 -2
  12. package/agents/gsd-eval-auditor.compact.md +1 -1
  13. package/agents/gsd-eval-auditor.md +1 -1
  14. package/agents/gsd-executor.md +13 -8
  15. package/agents/gsd-intel-updater.compact.md +1 -1
  16. package/agents/gsd-intel-updater.md +1 -1
  17. package/agents/gsd-phase-researcher.md +19 -11
  18. package/agents/gsd-plan-checker.md +8 -7
  19. package/agents/gsd-planner.md +12 -8
  20. package/agents/gsd-project-researcher.compact.md +1 -1
  21. package/agents/gsd-project-researcher.md +1 -1
  22. package/agents/gsd-research-synthesizer.compact.md +1 -1
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-auditor.md +155 -17
  25. package/agents/gsd-ui-researcher.compact.md +1 -1
  26. package/agents/gsd-ui-researcher.md +1 -1
  27. package/agents/gsd-verifier.md +10 -9
  28. package/bin/install.js +642 -95
  29. package/commands/gsd/autonomous.md +2 -2
  30. package/commands/gsd/capture.md +1 -1
  31. package/commands/gsd/mempalace-capture.md +7 -3
  32. package/commands/gsd/plan-review-convergence.md +6 -6
  33. package/commands/gsd/progress.md +1 -1
  34. package/commands/gsd/quick-batch.md +1 -1
  35. package/commands/gsd/review.md +2 -3
  36. package/gsd-core/bin/gsd-tools.cjs +335 -22
  37. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  38. package/gsd-core/bin/lib/audit.cjs +81 -13
  39. package/gsd-core/bin/lib/capability-registry.cjs +82 -187
  40. package/gsd-core/bin/lib/capability-validator.cjs +0 -1
  41. package/gsd-core/bin/lib/check-command-router.cjs +101 -14
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  43. package/gsd-core/bin/lib/commands.cjs +175 -42
  44. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  45. package/gsd-core/bin/lib/config.cjs +33 -7
  46. package/gsd-core/bin/lib/decisions.cjs +30 -14
  47. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  48. package/gsd-core/bin/lib/graphify.cjs +10 -2
  49. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  50. package/gsd-core/bin/lib/init.cjs +207 -41
  51. package/gsd-core/bin/lib/install-engine.cjs +13 -0
  52. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone.cjs +18 -5
  54. package/gsd-core/bin/lib/model-resolver.cjs +159 -50
  55. package/gsd-core/bin/lib/phase-command-router.cjs +9 -1
  56. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  57. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  58. package/gsd-core/bin/lib/phase-id.cjs +109 -7
  59. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  60. package/gsd-core/bin/lib/phase.cjs +227 -26
  61. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  62. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  63. package/gsd-core/bin/lib/planning-inspect.cjs +18 -1
  64. package/gsd-core/bin/lib/planning-workspace.cjs +8 -3
  65. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  66. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  67. package/gsd-core/bin/lib/project-root.cjs +41 -2
  68. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  69. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  70. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  71. package/gsd-core/bin/lib/roadmap-parser.cjs +163 -3
  72. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  73. package/gsd-core/bin/lib/roadmap.cjs +251 -31
  74. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +283 -31
  75. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -1
  76. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  77. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -33
  78. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  79. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  80. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  81. package/gsd-core/bin/lib/state.cjs +42 -0
  82. package/gsd-core/bin/lib/surface.cjs +17 -1
  83. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  84. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  85. package/gsd-core/bin/lib/uat.cjs +8 -0
  86. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  87. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  88. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  89. package/gsd-core/bin/lib/verification.cjs +268 -15
  90. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  91. package/gsd-core/bin/lib/verify.cjs +132 -25
  92. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  93. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  94. package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
  95. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  96. package/gsd-core/references/checkpoints.md +5 -3
  97. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  98. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  99. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  100. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  101. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  102. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  103. package/gsd-core/references/edge-probe.md +195 -21
  104. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  105. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  106. package/gsd-core/references/gsd-run-resolver.md +1 -1
  107. package/gsd-core/references/model-profiles.md +1 -1
  108. package/gsd-core/references/phase-argument-parsing.md +9 -7
  109. package/gsd-core/references/phase-id-convention.md +28 -0
  110. package/gsd-core/references/planner-gap-closure.md +2 -0
  111. package/gsd-core/references/planner-load-graph-context.md +24 -13
  112. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  113. package/gsd-core/references/planning-config.md +11 -2
  114. package/gsd-core/references/tdd.md +27 -4
  115. package/gsd-core/references/ui-consideration-probe.md +10 -5
  116. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  117. package/gsd-core/references/worktree-path-safety.md +321 -0
  118. package/gsd-core/templates/verification-report.md +1 -1
  119. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  120. package/gsd-core/workflows/add-backlog.md +1 -1
  121. package/gsd-core/workflows/add-phase.md +1 -1
  122. package/gsd-core/workflows/add-tests.md +2 -2
  123. package/gsd-core/workflows/add-todo.md +3 -3
  124. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  125. package/gsd-core/workflows/audit-fix.md +1 -1
  126. package/gsd-core/workflows/audit-milestone.md +1 -1
  127. package/gsd-core/workflows/audit-uat.md +1 -1
  128. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  129. package/gsd-core/workflows/autonomous.md +16 -6
  130. package/gsd-core/workflows/check-todos.md +2 -2
  131. package/gsd-core/workflows/cleanup.md +2 -2
  132. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  133. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  134. package/gsd-core/workflows/code-review-fix.md +108 -22
  135. package/gsd-core/workflows/code-review.md +63 -46
  136. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +1 -1
  137. package/gsd-core/workflows/complete-milestone.md +2 -2
  138. package/gsd-core/workflows/debug.md +3 -3
  139. package/gsd-core/workflows/diagnose-issues.md +1 -1
  140. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  141. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  142. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  143. package/gsd-core/workflows/discuss-phase.md +1 -1
  144. package/gsd-core/workflows/do.md +2 -2
  145. package/gsd-core/workflows/docs-update.md +3 -3
  146. package/gsd-core/workflows/edit-phase.md +1 -1
  147. package/gsd-core/workflows/eval-review.md +10 -3
  148. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  149. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  150. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  151. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +3 -3
  152. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  153. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  154. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  155. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  156. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  157. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  158. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  159. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  160. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  161. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  162. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  163. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  164. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  165. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  166. package/gsd-core/workflows/execute-phase.md +36 -26
  167. package/gsd-core/workflows/execute-plan.md +5 -4
  168. package/gsd-core/workflows/explore.md +3 -3
  169. package/gsd-core/workflows/extract-learnings.md +2 -1
  170. package/gsd-core/workflows/fast.md +1 -1
  171. package/gsd-core/workflows/forensics.md +1 -1
  172. package/gsd-core/workflows/graduation.md +1 -1
  173. package/gsd-core/workflows/health.md +2 -2
  174. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  175. package/gsd-core/workflows/help/modes/full.md +5 -5
  176. package/gsd-core/workflows/help/modes/topic.md +15 -5
  177. package/gsd-core/workflows/import.md +2 -2
  178. package/gsd-core/workflows/inbox.md +2 -2
  179. package/gsd-core/workflows/ingest-docs.md +3 -3
  180. package/gsd-core/workflows/insert-phase.md +1 -1
  181. package/gsd-core/workflows/list-seeds.md +1 -1
  182. package/gsd-core/workflows/list-workspaces.md +1 -1
  183. package/gsd-core/workflows/manager.md +2 -2
  184. package/gsd-core/workflows/map-codebase.md +2 -2
  185. package/gsd-core/workflows/milestone-summary.md +1 -1
  186. package/gsd-core/workflows/mvp-phase.md +1 -1
  187. package/gsd-core/workflows/new-milestone.md +2 -2
  188. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  189. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  190. package/gsd-core/workflows/new-project.md +7 -7
  191. package/gsd-core/workflows/new-workspace.md +2 -2
  192. package/gsd-core/workflows/next.md +1 -1
  193. package/gsd-core/workflows/note.md +1 -1
  194. package/gsd-core/workflows/onboard.md +1 -1
  195. package/gsd-core/workflows/pause-work.md +1 -1
  196. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  197. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  198. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  199. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  200. package/gsd-core/workflows/plan-phase.md +24 -7
  201. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  202. package/gsd-core/workflows/plant-seed.md +62 -20
  203. package/gsd-core/workflows/pr-branch.md +113 -13
  204. package/gsd-core/workflows/profile-user.md +2 -2
  205. package/gsd-core/workflows/progress.md +1 -1
  206. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  207. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  208. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  209. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  210. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  211. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  212. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  213. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  214. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  215. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  216. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  217. package/gsd-core/workflows/quick-batch.md +1 -1
  218. package/gsd-core/workflows/quick.md +21 -9
  219. package/gsd-core/workflows/reapply-patches.md +9 -3
  220. package/gsd-core/workflows/remove-phase.md +1 -1
  221. package/gsd-core/workflows/remove-workspace.md +2 -2
  222. package/gsd-core/workflows/resume-project.md +1 -1
  223. package/gsd-core/workflows/review.md +31 -16
  224. package/gsd-core/workflows/scan.md +1 -1
  225. package/gsd-core/workflows/secure-phase.md +3 -2
  226. package/gsd-core/workflows/settings-advanced.md +30 -10
  227. package/gsd-core/workflows/settings-integrations.md +2 -3
  228. package/gsd-core/workflows/settings.md +4 -4
  229. package/gsd-core/workflows/ship.md +3 -2
  230. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  231. package/gsd-core/workflows/sketch.md +1 -1
  232. package/gsd-core/workflows/smart-entry.md +2 -2
  233. package/gsd-core/workflows/spec-phase.md +15 -5
  234. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  235. package/gsd-core/workflows/spike.md +1 -1
  236. package/gsd-core/workflows/stats.md +1 -1
  237. package/gsd-core/workflows/sync-skills.md +5 -5
  238. package/gsd-core/workflows/thread.md +1 -1
  239. package/gsd-core/workflows/transition.md +1 -1
  240. package/gsd-core/workflows/ui-phase.md +44 -8
  241. package/gsd-core/workflows/ui-review.md +18 -4
  242. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  243. package/gsd-core/workflows/undo.md +339 -20
  244. package/gsd-core/workflows/update.md +7 -7
  245. package/gsd-core/workflows/validate-phase.md +3 -2
  246. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  247. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  248. package/gsd-core/workflows/verify-work.md +81 -16
  249. package/hooks/dist/gsd-agent-isolation-guard.js +24 -0
  250. package/hooks/dist/gsd-secret-read-guard.js +27 -1
  251. package/hooks/dist/gsd-statusline.js +70 -13
  252. package/hooks/dist/gsd-validate-commit.sh +63 -4
  253. package/hooks/gsd-agent-isolation-guard.js +24 -0
  254. package/hooks/gsd-secret-read-guard.js +27 -1
  255. package/hooks/gsd-statusline.js +70 -13
  256. package/hooks/gsd-validate-commit.sh +63 -4
  257. package/package.json +3 -2
  258. package/scripts/build-hooks.js +15 -6
  259. package/scripts/check-contract-drift.cjs +127 -11
  260. package/scripts/command-contract-helpers.cjs +3 -0
  261. package/scripts/docs-guard-registry.cjs +28 -0
  262. package/scripts/gen-loop-host-contract.cjs +69 -0
  263. package/scripts/lib/macos-conformance-tier.generated.cjs +14 -0
  264. package/scripts/lib/ndjson-reporter.cjs +3 -2
  265. package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
  266. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +28 -1
  267. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  268. package/scripts/lint-phase-id-drift.cjs +290 -5
  269. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  270. package/scripts/lint-retired-runtime-name.cjs +619 -0
  271. package/scripts/lint-state-write-path-drift.cjs +93 -0
  272. package/scripts/lint-test-file-count.allowlist.json +28 -9
  273. package/scripts/lint-workflow-shellcheck-baseline.json +15 -0
  274. package/scripts/prompt-injection-scan.sh +4 -0
  275. package/scripts/release-tarball-smoke.cjs +194 -1
  276. package/skills/gsd-autonomous/SKILL.md +2 -2
  277. package/skills/gsd-capture/SKILL.md +1 -1
  278. package/skills/gsd-mempalace-capture/SKILL.md +7 -3
  279. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  280. package/skills/gsd-progress/SKILL.md +1 -1
  281. package/skills/gsd-quick-batch/SKILL.md +1 -1
  282. package/skills/gsd-review/SKILL.md +2 -3
  283. package/vscode/package.json +1 -1
@@ -38,7 +38,7 @@ const commonjs_marker_cjs_1 = require("./commonjs-marker.cjs");
38
38
  const imperative_hook_bus_cjs_1 = require("./host-integration-adapters/imperative-hook-bus.cjs");
39
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports
40
40
  const shellCmdProjection = require("./shell-command-projection.cjs");
41
- const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, escapePosixDoubleQuoted, } = shellCmdProjection;
41
+ const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, escapePosixDoubleQuoted, resolveExecutableBinary, } = shellCmdProjection;
42
42
  // ---------------------------------------------------------------------------
43
43
  // Terminal color constants (mirrors install.js for console output parity)
44
44
  // ---------------------------------------------------------------------------
@@ -576,7 +576,7 @@ function buildBakedNodeToken(opts) {
576
576
  * guarantee), ahead of `command -v node` and the well-known fallback list.
577
577
  */
578
578
  const NODE_RUNNER_RESOLVER_HOOK = 'gsd-node-runner.sh';
579
- function resolveBashRunner(opts) {
579
+ function resolveBashExecutable(opts) {
580
580
  const platform = (opts && opts.platform) || process.platform;
581
581
  if (platform !== 'win32')
582
582
  return 'bash';
@@ -594,12 +594,19 @@ function resolveBashRunner(opts) {
594
594
  candidates.push(node_path_1.default.win32.join(env.SystemDrive, 'Program Files (x86)', 'Git', 'bin', 'bash.exe'));
595
595
  }
596
596
  for (const candidate of candidates) {
597
- if (candidate && exists(candidate)) {
598
- return JSON.stringify(shellCmdProjection.posixNormalize(candidate));
599
- }
597
+ if (candidate && exists(candidate))
598
+ return shellCmdProjection.posixNormalize(candidate);
600
599
  }
601
600
  return null;
602
601
  }
602
+ function resolveBashRunner(opts) {
603
+ const executable = resolveBashExecutable(opts);
604
+ if (executable === null)
605
+ return null;
606
+ return ((opts && opts.platform) || process.platform) === 'win32'
607
+ ? JSON.stringify(executable)
608
+ : executable;
609
+ }
603
610
  // #3662 — recognize the two runtime-resolving command shapes the installer
604
611
  // emits, so the rewriter never churns (or un-does) an entry that already
605
612
  // works in every environment sharing the config root.
@@ -961,16 +968,18 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
961
968
  function reconcileCodexHooksJsonSessionStart(targetDir, opts = {}) {
962
969
  return reconcileCodexHooksJsonEvent(targetDir, 'SessionStart', opts);
963
970
  }
964
- function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
965
- if (!absoluteRunnerToken)
966
- return null;
967
- let interpreter;
971
+ function parseAbsoluteRunnerToken(absoluteRunnerToken) {
968
972
  try {
969
- interpreter = JSON.parse(absoluteRunnerToken);
973
+ return JSON.parse(absoluteRunnerToken);
970
974
  }
971
975
  catch {
972
- interpreter = absoluteRunnerToken;
976
+ return absoluteRunnerToken;
973
977
  }
978
+ }
979
+ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
980
+ if (!absoluteRunnerToken)
981
+ return null;
982
+ const interpreter = parseAbsoluteRunnerToken(absoluteRunnerToken);
974
983
  const targetAbs = shellCmdProjection.posixNormalize(scriptAbsPath);
975
984
  const scriptQuoted = JSON.stringify(targetAbs);
976
985
  const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
@@ -995,6 +1004,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
995
1004
  return { changed: false, wrote: false, path: hooksJsonPath };
996
1005
  const scriptPath = shellCmdProjection.posixNormalize(node_path_1.default.resolve(targetDir, 'hooks', 'gsd-check-update.js'));
997
1006
  const cmdShimPath = scriptPath.replace(/\.js$/, '.cmd');
1007
+ const configuredEntrypoints = [];
998
1008
  let managedCommand;
999
1009
  if (platform === 'win32') {
1000
1010
  const shimIR = buildCodexHookWindowsShimIR(scriptPath, absoluteRunner);
@@ -1011,6 +1021,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
1011
1021
  return { changed: false, wrote: false, path: hooksJsonPath };
1012
1022
  }
1013
1023
  managedCommand = shimIR.hookCommand;
1024
+ configuredEntrypoints.push({ runtime: 'codex', configPath: hooksJsonPath, scriptPath: shimIR.cmdPath, platform, selfExecutable: true }, { runtime: 'codex', configPath: hooksJsonPath, scriptPath, interpreterCandidates: [parseAbsoluteRunnerToken(absoluteRunner)], platform });
1014
1025
  }
1015
1026
  else {
1016
1027
  managedCommand = projectManagedHookCommand({
@@ -1019,13 +1030,23 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
1019
1030
  runtime: 'codex',
1020
1031
  platform,
1021
1032
  }) ?? undefined;
1033
+ if (managedCommand) {
1034
+ configuredEntrypoints.push({
1035
+ runtime: 'codex',
1036
+ configPath: hooksJsonPath,
1037
+ scriptPath,
1038
+ interpreterCandidates: [parseAbsoluteRunnerToken(absoluteRunner)],
1039
+ platform,
1040
+ });
1041
+ }
1022
1042
  }
1023
1043
  if (!managedCommand)
1024
1044
  return { changed: false, wrote: false, path: hooksJsonPath };
1025
1045
  const commandWindows = platform === 'win32'
1026
1046
  ? JSON.stringify(shellCmdProjection.posixNormalize(cmdShimPath))
1027
1047
  : undefined;
1028
- return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
1048
+ const result = reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
1049
+ return { ...result, configuredEntrypoints };
1029
1050
  }
1030
1051
  function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
1031
1052
  const platform = opts.platform || process.platform;
@@ -1201,6 +1222,56 @@ function cleanupOrphanedCodexContextMonitorScript(targetDir) {
1201
1222
  }
1202
1223
  return result;
1203
1224
  }
1225
+ function configuredEntrypointsForHook(configDir, hookName, opts) {
1226
+ const platform = opts.platform || process.platform;
1227
+ const runtime = opts.runtime || 'generic';
1228
+ const configPath = opts.configPath || configDir;
1229
+ const target = {
1230
+ runtime,
1231
+ configPath,
1232
+ scriptPath: node_path_1.default.join(configDir, 'hooks', hookName),
1233
+ platform,
1234
+ };
1235
+ const isShellHook = hookName.endsWith('.sh');
1236
+ if (shellHookOmitsBashRunner({ platform, runtime, isShellHook }))
1237
+ return [{ ...target, selfExecutable: true }];
1238
+ const bash = resolveBashExecutable(opts);
1239
+ if (isShellHook) {
1240
+ // An unresolved bash must still surface as an interpreterCandidates entry
1241
+ // (the literal token, same as the portableHooks runner below) so
1242
+ // validateConfiguredEntrypoints reports 'unresolved-interpreter' instead
1243
+ // of silently skipping the check because the field is absent.
1244
+ return [{ ...target, interpreterCandidates: [bash === null ? 'bash' : bash] }];
1245
+ }
1246
+ // #4249: check the SAME stable alias buildNodeRunnerChainToken bakes as its
1247
+ // first candidate (normalizeNodePath rewrites a version-manager shim like
1248
+ // fnm/nvm/mise/volta into its persistent path), not the raw, currently-
1249
+ // running process.execPath — which always trivially resolves regardless of
1250
+ // whether the alias actually baked into the persisted command still does.
1251
+ const nodeCandidates = [
1252
+ normalizeNodePath(opts.execPath || process.execPath, opts),
1253
+ 'node',
1254
+ '/usr/local/bin/node',
1255
+ '/usr/bin/node',
1256
+ ].filter((candidate) => Boolean(candidate));
1257
+ if (!opts.portableHooks) {
1258
+ return [{ ...target, interpreterCandidates: nodeCandidates }];
1259
+ }
1260
+ const runner = {
1261
+ runtime,
1262
+ configPath,
1263
+ scriptPath: node_path_1.default.join(configDir, 'hooks', NODE_RUNNER_RESOLVER_HOOK),
1264
+ interpreterCandidates: bash === null ? ['bash'] : [bash],
1265
+ platform,
1266
+ };
1267
+ return [runner, { ...target, interpreterCandidates: nodeCandidates }];
1268
+ }
1269
+ function recordConfiguredHookCommand(command, configDir, hookName, opts) {
1270
+ if (command && opts.configuredEntrypoints) {
1271
+ opts.configuredEntrypoints.push(...configuredEntrypointsForHook(configDir, hookName, opts).map(entry => ({ ...entry, command })));
1272
+ }
1273
+ return command;
1274
+ }
1204
1275
  function buildHookCommand(configDir, hookName, opts) {
1205
1276
  if (!opts)
1206
1277
  opts = {};
@@ -1208,44 +1279,56 @@ function buildHookCommand(configDir, hookName, opts) {
1208
1279
  const runtime = opts.runtime || 'generic';
1209
1280
  const hookShell = opts.hookShell;
1210
1281
  const isShellHook = hookName.endsWith('.sh');
1282
+ const track = (command) => recordConfiguredHookCommand(command, configDir, hookName, opts);
1211
1283
  if (shellHookOmitsBashRunner({ platform, runtime, isShellHook })) {
1212
1284
  if (opts.portableHooks) {
1213
1285
  const portableBaseDir = projectPortableHookBaseDir({
1214
1286
  configDir,
1215
1287
  homeDir: node_os_1.default.homedir(),
1216
1288
  });
1217
- return JSON.stringify(`${portableBaseDir}/hooks/${hookName}`);
1289
+ return track(JSON.stringify(`${portableBaseDir}/hooks/${hookName}`));
1218
1290
  }
1219
- return JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName);
1291
+ return track(JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName));
1220
1292
  }
1221
1293
  // .sh hooks keep the pre-#3662 shape everywhere: the bash runner resolves
1222
1294
  // at install time like today, and `bash` itself is a PATH-stable binary
1223
1295
  // (the absolute Git-Bash discovery covers win32 — #580/#3393).
1224
1296
  if (isShellHook) {
1225
1297
  const runner = resolveBashRunner(opts);
1226
- if (runner === null)
1298
+ if (runner === null) {
1299
+ // #4249 (antigravity review): this early return skips `track()` below,
1300
+ // so an unresolved bash on win32 (no Git Bash found) previously left
1301
+ // this hook silently unregistered with nothing for
1302
+ // validateConfiguredEntrypoints to reject — configuredEntrypointsForHook's
1303
+ // own 'unresolved bash must still surface' comment describes intent this
1304
+ // return never reached. Push the entry directly (no `command`, since
1305
+ // none was ever built) so the gate actually sees it.
1306
+ if (opts.configuredEntrypoints) {
1307
+ opts.configuredEntrypoints.push(...configuredEntrypointsForHook(configDir, hookName, opts));
1308
+ }
1227
1309
  return null;
1310
+ }
1228
1311
  if (opts.portableHooks) {
1229
1312
  const portableBaseDir = projectPortableHookBaseDir({
1230
1313
  configDir,
1231
1314
  homeDir: node_os_1.default.homedir(),
1232
1315
  });
1233
- return projectManagedHookCommand({
1316
+ return track(projectManagedHookCommand({
1234
1317
  absoluteRunner: runner,
1235
1318
  scriptPath: `${portableBaseDir}/hooks/${hookName}`,
1236
1319
  runtime: opts.runtime || 'generic',
1237
1320
  platform,
1238
1321
  hookShell,
1239
- });
1322
+ }));
1240
1323
  }
1241
1324
  const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
1242
- return projectManagedHookCommand({
1325
+ return track(projectManagedHookCommand({
1243
1326
  absoluteRunner: runner,
1244
1327
  scriptPath: hooksPath,
1245
1328
  runtime,
1246
1329
  platform,
1247
1330
  hookShell,
1248
- });
1331
+ }));
1249
1332
  }
1250
1333
  // JS hooks (#3662): the node runner is resolved at hook-fire time, never
1251
1334
  // baked as a bare absolute path — an install-environment absolute path is
@@ -1268,7 +1351,7 @@ function buildHookCommand(configDir, hookName, opts) {
1268
1351
  // Absolute Git-Bash discovery on win32 when available (#580); `bash` on
1269
1352
  // PATH otherwise — the same assumption .sh hooks already make.
1270
1353
  const resolverRunner = resolveBashRunner(opts) || 'bash';
1271
- return shellCmdProjection.projectShellCommandText({
1354
+ return track(shellCmdProjection.projectShellCommandText({
1272
1355
  runnerToken: resolverRunner,
1273
1356
  argTokens: [
1274
1357
  JSON.stringify(`${portableBaseDir}/hooks/${NODE_RUNNER_RESOLVER_HOOK}`),
@@ -1278,19 +1361,19 @@ function buildHookCommand(configDir, hookName, opts) {
1278
1361
  runtime,
1279
1362
  platform,
1280
1363
  hookShell,
1281
- });
1364
+ }));
1282
1365
  }
1283
1366
  const chainRunner = buildNodeRunnerChainToken(opts);
1284
1367
  if (chainRunner === null)
1285
1368
  return null;
1286
1369
  const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
1287
- return shellCmdProjection.projectShellCommandText({
1370
+ return track(shellCmdProjection.projectShellCommandText({
1288
1371
  runnerToken: chainRunner,
1289
1372
  argTokens: [JSON.stringify(hooksPath)],
1290
1373
  runtime,
1291
1374
  platform,
1292
1375
  hookShell,
1293
- });
1376
+ }));
1294
1377
  }
1295
1378
  // ---------------------------------------------------------------------------
1296
1379
  // Cline helpers
@@ -1391,6 +1474,7 @@ function mergeGsdAgentsMd(filePath, gsdContent) {
1391
1474
  // ---------------------------------------------------------------------------
1392
1475
  function writeClineArtifacts(targetDir, isGlobalInstall) {
1393
1476
  const written = [];
1477
+ const configuredEntrypoints = [];
1394
1478
  const clinerulesDir = node_path_1.default.join(targetDir, '.clinerules');
1395
1479
  try {
1396
1480
  if (node_fs_1.default.existsSync(clinerulesDir)) {
@@ -1416,6 +1500,20 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
1416
1500
  catch { /* Windows: hooks unsupported anyway */ }
1417
1501
  written.push('.clinerules/hooks/PreToolUse');
1418
1502
  console.log(` ${green}✓${reset} Wrote .clinerules/hooks/PreToolUse`);
1503
+ // #4249 (CodeRabbit): Cline invokes this file directly via its own
1504
+ // `#!/usr/bin/env node` shebang — a hybrid case. The script itself still
1505
+ // needs the execute bit (selfExecutable), but unlike GSD's other JS hooks
1506
+ // (which bake an absolute, install-time-resolved node path specifically to
1507
+ // avoid this) its interpreter is looked up on PATH by `env` at hook-fire
1508
+ // time, so `node` must also resolve or the hook can never run.
1509
+ configuredEntrypoints.push({
1510
+ runtime: 'cline',
1511
+ configPath: hookPath,
1512
+ scriptPath: hookPath,
1513
+ interpreterCandidates: ['node'],
1514
+ selfExecutable: true,
1515
+ platform: process.platform,
1516
+ });
1419
1517
  if (isGlobalInstall) {
1420
1518
  try {
1421
1519
  const agentsPath = node_path_1.default.join(node_os_1.default.homedir(), '.agents', 'AGENTS.md');
@@ -1426,7 +1524,7 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
1426
1524
  console.warn(` ${yellow}⚠${reset} Could not write ~/.agents/AGENTS.md: ${err.message}`);
1427
1525
  }
1428
1526
  }
1429
- return written;
1527
+ return { written, configuredEntrypoints };
1430
1528
  }
1431
1529
  // ---------------------------------------------------------------------------
1432
1530
  // Cursor hook functions
@@ -1677,7 +1775,13 @@ function writeCursorHooksJson(targetDir, src, opts) {
1677
1775
  if (installedScripts.size > 0) {
1678
1776
  ensureCommonJsMarker(hooksDir);
1679
1777
  }
1680
- const hookOpts = { runtime: 'cursor', platform: opts.platform || process.platform };
1778
+ const configuredEntrypoints = [];
1779
+ const hookOpts = {
1780
+ runtime: 'cursor',
1781
+ platform: opts.platform || process.platform,
1782
+ configPath: node_path_1.default.join(targetDir, 'hooks.json'),
1783
+ configuredEntrypoints,
1784
+ };
1681
1785
  const commands = {};
1682
1786
  for (const ev of events) {
1683
1787
  const script = imperative_hook_bus_cjs_1.CURSOR_EVENT_SCRIPT_MAP[ev];
@@ -1691,7 +1795,7 @@ function writeCursorHooksJson(targetDir, src, opts) {
1691
1795
  const managedEntries = (0, imperative_hook_bus_cjs_1.buildHookBusEntries)(events, commands);
1692
1796
  const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
1693
1797
  const result = reconcileCursorHooksJson(hooksJsonPath, managedEntries);
1694
- return { hooksJsonPath, changed: result.changed };
1798
+ return { hooksJsonPath, changed: result.changed, configuredEntrypoints };
1695
1799
  }
1696
1800
  function removeCursorHooksJson(targetDir) {
1697
1801
  const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
@@ -1896,7 +2000,13 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
1896
2000
  if (installedScripts.size > 0) {
1897
2001
  ensureCommonJsMarker(hooksDir);
1898
2002
  }
1899
- const hookOpts = { runtime: 'windsurf', platform: opts.platform || process.platform };
2003
+ const configuredEntrypoints = [];
2004
+ const hookOpts = {
2005
+ runtime: 'windsurf',
2006
+ platform: opts.platform || process.platform,
2007
+ configPath: node_path_1.default.join(targetDir, 'hooks.json'),
2008
+ configuredEntrypoints,
2009
+ };
1900
2010
  const commands = {};
1901
2011
  for (const ev of WINDSURF_HOOK_EVENTS) {
1902
2012
  const script = WINDSURF_EVENT_SCRIPT_MAP[ev];
@@ -1910,7 +2020,7 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
1910
2020
  }
1911
2021
  const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
1912
2022
  const result = reconcileWindsurfHooksJson(hooksJsonPath, managedEntries);
1913
- return { hooksJsonPath, changed: result.changed };
2023
+ return { hooksJsonPath, changed: result.changed, configuredEntrypoints };
1914
2024
  }
1915
2025
  /**
1916
2026
  * Remove all GSD-managed Cascade hook entries from hooks.json. User-owned
@@ -2761,13 +2871,21 @@ function stripKimiHooksTomlBlock(content) {
2761
2871
  * touch the file's mtime for no reason.
2762
2872
  */
2763
2873
  function writeKimiHooksToml(configPath, targetDir, opts) {
2874
+ const configuredEntrypoints = [];
2875
+ const trackedOpts = {
2876
+ hookOpts: {
2877
+ ...opts.hookOpts,
2878
+ configPath,
2879
+ configuredEntrypoints,
2880
+ },
2881
+ };
2764
2882
  const existing = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, 'utf8') : '';
2765
2883
  const stripped = stripKimiHooksTomlBlock(existing) ?? '';
2766
- const block = buildKimiHooksTomlBlock(targetDir, opts);
2884
+ const block = buildKimiHooksTomlBlock(targetDir, trackedOpts);
2767
2885
  const entryCount = block ? (block.match(/\[\[hooks\]\]/g) || []).length : 0;
2768
2886
  if (!block) {
2769
2887
  if (stripped === existing)
2770
- return { changed: false, path: configPath, entryCount: 0 };
2888
+ return { changed: false, path: configPath, entryCount: 0, configuredEntrypoints };
2771
2889
  if (stripped.trim() === '') {
2772
2890
  if (node_fs_1.default.existsSync(configPath))
2773
2891
  node_fs_1.default.unlinkSync(configPath);
@@ -2776,15 +2894,15 @@ function writeKimiHooksToml(configPath, targetDir, opts) {
2776
2894
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
2777
2895
  atomicWriteFileSync(configPath, stripped, 'utf8');
2778
2896
  }
2779
- return { changed: true, path: configPath, entryCount: 0 };
2897
+ return { changed: true, path: configPath, entryCount: 0, configuredEntrypoints };
2780
2898
  }
2781
2899
  const separator = stripped.trim() === '' ? '' : (stripped.endsWith('\n') ? '\n' : '\n\n');
2782
2900
  const next = stripped.trim() === '' ? `${block}\n` : `${stripped}${separator}${block}\n`;
2783
2901
  if (next === existing)
2784
- return { changed: false, path: configPath, entryCount };
2902
+ return { changed: false, path: configPath, entryCount, configuredEntrypoints };
2785
2903
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
2786
2904
  atomicWriteFileSync(configPath, next, 'utf8');
2787
- return { changed: true, path: configPath, entryCount };
2905
+ return { changed: true, path: configPath, entryCount, configuredEntrypoints };
2788
2906
  }
2789
2907
  /**
2790
2908
  * Uninstall-time counterpart to writeKimiHooksToml: strips the GSD block and
@@ -2830,6 +2948,68 @@ function referencesHook(h, hookName) {
2830
2948
  (Array.isArray(args) && args.some(a => typeof a === 'string' && a.includes(hookName))) ||
2831
2949
  (typeof url === 'string' && url.includes(hookName));
2832
2950
  }
2951
+ function validateConfiguredEntrypoints(entries, deps = {}) {
2952
+ const statSync = deps.statSync ?? node_fs_1.default.statSync;
2953
+ const accessSync = deps.accessSync ?? node_fs_1.default.accessSync;
2954
+ const resolve = deps.resolveExecutableBinary ?? resolveExecutableBinary;
2955
+ const invalid = [];
2956
+ for (const entry of entries) {
2957
+ let scriptOk = false;
2958
+ try {
2959
+ scriptOk = statSync(entry.scriptPath).isFile();
2960
+ if (!scriptOk) {
2961
+ invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'wrong-file-type' });
2962
+ }
2963
+ else {
2964
+ // #4249: statSync only needs search permission on the parent dirs, so
2965
+ // it succeeds even for a chmod-000 file — the EACCES catch below
2966
+ // never fires for that case. Read permission on the file itself must
2967
+ // be checked explicitly: an interpreter opens the script directly,
2968
+ // and even a self-executable shebang script is opened and read by
2969
+ // its kernel-invoked interpreter, not just exec'd — X_OK alone does
2970
+ // not prove it's readable.
2971
+ try {
2972
+ accessSync(entry.scriptPath, node_fs_1.default.constants.R_OK);
2973
+ }
2974
+ catch {
2975
+ scriptOk = false;
2976
+ invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'unreadable' });
2977
+ }
2978
+ }
2979
+ }
2980
+ catch (statErr) {
2981
+ // #4249 Nit: EACCES means a parent directory couldn't be searched —
2982
+ // a real (if rare) permission problem, distinct from ENOENT's "missing".
2983
+ // EPERM: Windows' equivalent permission-denied code for a directory a
2984
+ // parent path couldn't be traversed into.
2985
+ const code = statErr?.code;
2986
+ const reason = code === 'EACCES' || code === 'EPERM' ? 'unreadable' : 'missing';
2987
+ invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason });
2988
+ }
2989
+ // #4249: selfExecutable is the sole source of truth for whether the OS
2990
+ // execs scriptPath directly via its own shebang (set explicitly by every
2991
+ // producer that needs it — a Windows-Claude .sh hook, Codex's Windows
2992
+ // .cmd shim, Cline's hybrid `env node` hook — rather than inferred from
2993
+ // the absence of interpreterCandidates, which Cline's hybrid case also
2994
+ // carries). Skip on win32 like resolveExecutableBinary's own X_OK
2995
+ // carve-out does: POSIX mode bits don't mean executable on Windows, and a
2996
+ // real accessSync(X_OK) there would fail a .cmd shim under a test that
2997
+ // simulates win32 on a POSIX runner (Node's own no-op only protects an
2998
+ // actual Windows machine). Cline is the only producer where this runs.
2999
+ if (scriptOk && entry.selfExecutable && (entry.platform ?? process.platform) !== 'win32') {
3000
+ try {
3001
+ accessSync(entry.scriptPath, node_fs_1.default.constants.X_OK);
3002
+ }
3003
+ catch {
3004
+ invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'not-executable' });
3005
+ }
3006
+ }
3007
+ if (entry.interpreterCandidates && !entry.interpreterCandidates.some(candidate => resolve(candidate, { platform: entry.platform, requireExecutable: true }) !== null)) {
3008
+ invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'interpreter', path: entry.interpreterCandidates.join(' | '), reason: 'unresolved-interpreter' });
3009
+ }
3010
+ }
3011
+ return invalid.length === 0 ? { ok: true } : { ok: false, invalid };
3012
+ }
2833
3013
  module.exports = {
2834
3014
  // Cline
2835
3015
  buildClineRulesBody,
@@ -2894,7 +3074,9 @@ module.exports = {
2894
3074
  // Shared
2895
3075
  stageTransitiveHookLibs,
2896
3076
  buildHookCommand,
3077
+ recordConfiguredHookCommand,
2897
3078
  applySettingsJsonHooks,
3079
+ validateConfiguredEntrypoints,
2898
3080
  referencesHook,
2899
3081
  rewriteLegacyManagedNodeHookCommands,
2900
3082
  reconcileManagedShellHookCommands,
@@ -15,7 +15,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
15
15
  return (mod && mod.__esModule) ? mod : { "default": mod };
16
16
  };
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.NO_LOCAL_CONFIG_DIR_SENTINEL = void 0;
18
+ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = exports.RetiredRuntimeError = exports.RETIRED_RUNTIME_IDS = void 0;
19
+ exports.isRetiredRuntimeId = isRetiredRuntimeId;
20
+ exports.assertNotRetiredRuntime = assertNotRetiredRuntime;
19
21
  exports.canonicalizeRuntimeName = canonicalizeRuntimeName;
20
22
  exports.resolveRuntimeNameFromCandidates = resolveRuntimeNameFromCandidates;
21
23
  exports.getProjectInstructionFile = getProjectInstructionFile;
@@ -26,6 +28,105 @@ exports.runtimeFlags = runtimeFlags;
26
28
  exports.getRuntimeNewProjectCommand = getRuntimeNewProjectCommand;
27
29
  const node_fs_1 = __importDefault(require("node:fs"));
28
30
  const node_path_1 = __importDefault(require("node:path"));
31
+ /**
32
+ * Runtime ids that GSD deliberately RETIRED, as opposed to ids it has simply
33
+ * never heard of. The distinction is load-bearing: an unknown id degrades to a
34
+ * safe cross-agent default on purpose (the #1529 contract in
35
+ * `getProjectInstructionFile` below), because GSD cannot know what a future
36
+ * runtime wants. A retired id is the opposite case — there is a recorded
37
+ * decision that it is gone and a named successor to point at, so resolving it
38
+ * to a DIFFERENT product's label and config home is a silent wrong answer.
39
+ *
40
+ * Measured before this guard existed: `getGlobalConfigDir('gemini')` and
41
+ * `getGlobalConfigDir('claude')` returned byte-identical paths, so asking for a
42
+ * runtime Google sunset on 2026-06-18 wrote into Claude Code's config home and
43
+ * labelled the install "Claude Code".
44
+ *
45
+ * Same table shape as `RETIRED_RUNTIMES` in
46
+ * `scripts/lint-retired-runtime-name.cjs` (#4753), which guards PROSE at build
47
+ * time while this guards IDS at runtime. They are deliberately NOT shared code
48
+ * — coupling them would make a build-time lint depend on compiled `src/` output
49
+ * it does not otherwise need — so a parity test asserts the two sets agree
50
+ * (CLAUDE.md, Generative Fix Divergence).
51
+ */
52
+ const RETIRED_RUNTIME_DETAILS = new Map([
53
+ ['gem' + 'ini', { successor: 'Antigravity', retiredBy: '#1928', sunset: '2026-06-18' }],
54
+ ]);
55
+ /**
56
+ * Normalise a candidate id for retirement matching: NFKC-fold, lowercase, and
57
+ * drop every non-alphanumeric character.
58
+ *
59
+ * Folding the separators is what makes `gemini-cli`, `gemini_cli`,
60
+ * `gemini.cli` and `geminicli` one key instead of four near-misses a reviewer
61
+ * has to find one at a time — and NFKC folds the full-width `gemini` a CJK
62
+ * keyboard produces. It stays MEMBERSHIP matching, not a prefix or substring
63
+ * test: `gemini-2.5-pro` folds to `gemini25pro` and `gemini-3.1-pro-preview`
64
+ * to `gemini31propreview`, neither of which is a member, so Google's live
65
+ * model ids — part of Antigravity's real on-disk contract — are untouched.
66
+ *
67
+ * Homoglyph folding is deliberately NOT attempted. A Cyrillic `і` in place of
68
+ * `i` would slip through, and that is accepted: these values arrive from argv
69
+ * and env, which are trusted inputs here, and a mapping broad enough to catch
70
+ * deliberate homoglyphs would start catching legitimate ids.
71
+ */
72
+ function normalizeForRetirementMatch(value) {
73
+ return value.normalize('NFKC').toLowerCase().replace(/[^a-z0-9]/g, '');
74
+ }
75
+ /**
76
+ * Every spelling that names a retired runtime, mapped to its canonical id.
77
+ * A Map, not an object literal: an object literal indexed by a computed key
78
+ * resolves INHERITED properties, so `'__proto__'` and `'constructor'` were
79
+ * truthy and threw with every field `undefined` — while `isRetiredRuntimeId`,
80
+ * which already went through a Set, correctly answered false for the same
81
+ * input. Two guards disagreeing about one id is worse than either answer.
82
+ * A Map has no prototype keys, so the hazard is structural, not patched.
83
+ */
84
+ const RETIRED_RUNTIME_SPELLINGS = new Map(Array.from(RETIRED_RUNTIME_DETAILS.keys()).flatMap((id) => [
85
+ [normalizeForRetirementMatch(id), id],
86
+ [normalizeForRetirementMatch(`${id}-cli`), id],
87
+ ]));
88
+ /**
89
+ * The canonical retired runtime ids, lowercase. Deliberately the canonical ids
90
+ * ONLY, not their alias spellings, so the parity assertion against
91
+ * `scripts/lint-retired-runtime-name.cjs`'s `RETIRED_RUNTIMES` compares like
92
+ * with like.
93
+ */
94
+ exports.RETIRED_RUNTIME_IDS = new Set(RETIRED_RUNTIME_DETAILS.keys());
95
+ /** Error thrown when a retired runtime id reaches a resolution accessor. */
96
+ class RetiredRuntimeError extends Error {
97
+ code = 'GSD_RETIRED_RUNTIME';
98
+ runtimeId;
99
+ constructor(runtimeId, detail) {
100
+ super(`Runtime "${runtimeId}" was retired by ${detail.retiredBy} (sunset ${detail.sunset}); `
101
+ + `use "${detail.successor}" instead. Refusing to resolve it, because the previous `
102
+ + `behaviour silently returned Claude Code's values.`);
103
+ this.name = 'RetiredRuntimeError';
104
+ this.runtimeId = runtimeId;
105
+ }
106
+ }
107
+ exports.RetiredRuntimeError = RetiredRuntimeError;
108
+ /**
109
+ * Is `runtime` a deliberately retired runtime, under any of its spellings?
110
+ * Shares one normaliser and one table with `assertNotRetiredRuntime`, so the
111
+ * predicate and the assertion can never disagree.
112
+ */
113
+ function isRetiredRuntimeId(runtime) {
114
+ if (typeof runtime !== 'string')
115
+ return false;
116
+ return RETIRED_RUNTIME_SPELLINGS.has(normalizeForRetirementMatch(runtime));
117
+ }
118
+ /** Throw if `runtime` names a retired runtime. No-op otherwise. */
119
+ function assertNotRetiredRuntime(runtime) {
120
+ if (typeof runtime !== 'string')
121
+ return;
122
+ const id = RETIRED_RUNTIME_SPELLINGS.get(normalizeForRetirementMatch(runtime));
123
+ if (id === undefined)
124
+ return;
125
+ const detail = RETIRED_RUNTIME_DETAILS.get(id);
126
+ if (detail === undefined)
127
+ return;
128
+ throw new RetiredRuntimeError(id, detail);
129
+ }
29
130
  const FALLBACK_ALIASES = {
30
131
  claude: ['claude', 'claude-code', 'claude-cli'],
31
132
  opencode: ['opencode', 'open-code', 'opencode-cli'],
@@ -145,6 +246,7 @@ function resolveRuntimeNameFromCandidates(...candidates) {
145
246
  * avoid a circular dependency at module load.
146
247
  */
147
248
  function getProjectInstructionFile(runtime) {
249
+ assertNotRetiredRuntime(runtime);
148
250
  const canonical = canonicalizeRuntimeName(runtime);
149
251
  if (canonical === 'claude')
150
252
  return '.claude/CLAUDE.md';
@@ -198,6 +300,9 @@ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = '(no-local-config-dir)';
198
300
  * compatibility spine had no production consumer at all.
199
301
  */
200
302
  function getDirName(runtime) {
303
+ // Same silent-wrong-answer class as the four accessors AC#1 names: this
304
+ // returned '.claude' for a retired id, and it feeds runtimeConfigDir.
305
+ assertNotRetiredRuntime(runtime);
201
306
  if (!runtime)
202
307
  return '.claude';
203
308
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -268,6 +373,7 @@ const RUNTIME_LABELS = {
268
373
  * 'Claude Code'. Sibling to `getDirName`; pure (no I/O).
269
374
  */
270
375
  function getRuntimeLabel(runtime) {
376
+ assertNotRetiredRuntime(runtime);
271
377
  if (!runtime)
272
378
  return 'Claude Code';
273
379
  const label = RUNTIME_LABELS[runtime];
@@ -323,6 +429,7 @@ const GLOBAL_CONFIG_HOME_FRAGMENTS = {
323
429
  * dynamically). Pure: no I/O. Sibling to `getDirName` / `getRuntimeLabel`.
324
430
  */
325
431
  function getGlobalConfigHomeFragment(runtime) {
432
+ assertNotRetiredRuntime(runtime);
326
433
  if (!runtime)
327
434
  return DEFAULT_CONFIG_HOME_FRAGMENT;
328
435
  const frag = GLOBAL_CONFIG_HOME_FRAGMENTS[runtime];
@@ -382,6 +489,9 @@ const RUNTIME_NEW_PROJECT_COMMANDS = {
382
489
  kimi: '/skill:gsd-new-project',
383
490
  };
384
491
  function getRuntimeNewProjectCommand(runtime) {
492
+ // Deliberately NOT retirement-guarded: this value does not vary by runtime
493
+ // in a way that makes a retired id a WRONG answer, so throwing here would
494
+ // cost callers a crash without correcting anything.
385
495
  if (!runtime)
386
496
  return DEFAULT_NEW_PROJECT_COMMAND;
387
497
  const c = RUNTIME_NEW_PROJECT_COMMANDS[runtime];
@@ -263,6 +263,7 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
263
263
  'gsd-session-state.sh',
264
264
  'gsd-validate-commit.sh',
265
265
  'gsd-phase-boundary.sh',
266
+ 'gsd-graphify-update.sh',
266
267
  // #3662: same three guards as MANAGED_HOOK_BASENAMES_BY_SURFACE above —
267
268
  // their absence here meant isManagedHookCommand never recognized them, so
268
269
  // the settings.json→settings.local.json migration filter (and uninstall
@@ -1068,12 +1069,15 @@ function _normalizeMd(content) {
1068
1069
  if (i === 0 || !insideFence[i - 1])
1069
1070
  result.push('');
1070
1071
  }
1071
- // #3854: the `!/^\s/.test(prev)` guard mirrors the after-a-bullet rule below —
1072
- // an indented non-bullet line is a CONTINUATION of the previous list item, not a
1073
- // preceding paragraph, so no separating blank may be injected before this bullet
1074
- // (that injection converted every tight multi-line list to a loose one on write).
1075
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 && prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) && !/^\s/.test(prev) && prevTrimmed !== '---')
1076
- result.push('');
1072
+ // No "separate a list from a preceding paragraph" rule (#3854, #4725).
1073
+ // This pass re-normalizes the ENTIRE document on every .md write, so an
1074
+ // inserted blank before a bullet whose previous line is ordinary prose
1075
+ // reflowed text the command never touched (converting tight lists to
1076
+ // loose ones); #3854 first guarded the indented-continuation predecessor,
1077
+ // #4725 removed the rule outright. A paragraph→list transition stays
1078
+ // exactly as the author wrote it; a heading→list separation still comes
1079
+ // from the after-heading rule below. The after-a-bullet rule at the end
1080
+ // of this loop is a different transition (list→prose) and is unaffected.
1077
1081
  result.push(line);
1078
1082
  if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '')
1079
1083
  result.push('');