@opengsd/gsd-core 1.9.0 → 1.10.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 (223) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +131 -34
  5. package/agents/gsd-debugger.md +12 -246
  6. package/agents/gsd-executor.md +7 -5
  7. package/agents/gsd-integration-checker.md +3 -0
  8. package/agents/gsd-plan-checker.md +9 -0
  9. package/agents/gsd-planner.md +5 -8
  10. package/agents/gsd-roadmapper.md +21 -3
  11. package/agents/gsd-verifier.md +14 -70
  12. package/bin/install.js +503 -341
  13. package/commands/gsd/mempalace-capture.md +1 -1
  14. package/commands/gsd/new-milestone.md +1 -1
  15. package/commands/gsd/plan-phase.md +1 -1
  16. package/gsd-core/bin/gsd-tools.cjs +607 -63
  17. package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
  18. package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
  19. package/gsd-core/bin/lib/api-coverage.cjs +120 -0
  20. package/gsd-core/bin/lib/audit.cjs +89 -1
  21. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  22. package/gsd-core/bin/lib/capability-registry.cjs +96 -110
  23. package/gsd-core/bin/lib/capability-validator.cjs +12 -2
  24. package/gsd-core/bin/lib/check-command-router.cjs +43 -1
  25. package/gsd-core/bin/lib/command-aliases.cjs +72 -0
  26. package/gsd-core/bin/lib/commands.cjs +26 -25
  27. package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
  28. package/gsd-core/bin/lib/config-loader.cjs +1 -0
  29. package/gsd-core/bin/lib/config.cjs +12 -1
  30. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  31. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  32. package/gsd-core/bin/lib/core-utils.cjs +91 -12
  33. package/gsd-core/bin/lib/docs.cjs +3 -2
  34. package/gsd-core/bin/lib/external-job.cjs +19 -4
  35. package/gsd-core/bin/lib/frontmatter.cjs +84 -12
  36. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  37. package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
  38. package/gsd-core/bin/lib/graphify.cjs +142 -27
  39. package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
  40. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  41. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  42. package/gsd-core/bin/lib/init.cjs +1021 -57
  43. package/gsd-core/bin/lib/install-engine.cjs +64 -10
  44. package/gsd-core/bin/lib/install-profiles.cjs +27 -1
  45. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  47. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  48. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  49. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  50. package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
  51. package/gsd-core/bin/lib/io.cjs +28 -3
  52. package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
  53. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  54. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  55. package/gsd-core/bin/lib/milestone.cjs +106 -51
  56. package/gsd-core/bin/lib/phase-id.cjs +63 -0
  57. package/gsd-core/bin/lib/phase-locator.cjs +138 -45
  58. package/gsd-core/bin/lib/phase.cjs +260 -25
  59. package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
  60. package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
  61. package/gsd-core/bin/lib/project-root.cjs +48 -0
  62. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  63. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
  64. package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
  65. package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
  66. package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
  67. package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
  68. package/gsd-core/bin/lib/roadmap.cjs +37 -7
  69. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
  70. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
  71. package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
  72. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
  73. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  74. package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
  75. package/gsd-core/bin/lib/smart-entry.cjs +12 -0
  76. package/gsd-core/bin/lib/state-transition.cjs +73 -8
  77. package/gsd-core/bin/lib/state.cjs +151 -62
  78. package/gsd-core/bin/lib/surface.cjs +12 -1
  79. package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
  80. package/gsd-core/bin/lib/uat.cjs +320 -21
  81. package/gsd-core/bin/lib/unusable-input.cjs +9 -0
  82. package/gsd-core/bin/lib/verification.cjs +29 -12
  83. package/gsd-core/bin/lib/verify.cjs +29 -5
  84. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  85. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
  86. package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
  87. package/gsd-core/bin/lib/workstream.cjs +6 -0
  88. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  89. package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
  90. package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
  91. package/gsd-core/references/artifact-types.md +10 -3
  92. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  93. package/gsd-core/references/debugger-techniques.md +255 -0
  94. package/gsd-core/references/research-documentation-lookup.md +5 -3
  95. package/gsd-core/references/specless-probe-fallback.md +7 -6
  96. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  97. package/gsd-core/references/worktree-branch-check.md +2 -2
  98. package/gsd-core/templates/summary-complex.md +2 -0
  99. package/gsd-core/templates/summary-minimal.md +2 -0
  100. package/gsd-core/templates/summary-standard.md +2 -0
  101. package/gsd-core/templates/summary.md +2 -0
  102. package/gsd-core/workflows/audit-milestone.md +3 -0
  103. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  104. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  105. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  106. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  107. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  108. package/gsd-core/workflows/autonomous.md +32 -69
  109. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  110. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
  111. package/gsd-core/workflows/code-review.md +42 -145
  112. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  113. package/gsd-core/workflows/complete-milestone.md +23 -81
  114. package/gsd-core/workflows/debug.md +9 -12
  115. package/gsd-core/workflows/diagnose-issues.md +22 -0
  116. package/gsd-core/workflows/discovery-phase.md +4 -4
  117. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  118. package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
  119. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  120. package/gsd-core/workflows/docs-update.md +8 -51
  121. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
  122. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  123. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  124. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  126. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  127. package/gsd-core/workflows/execute-phase.md +65 -137
  128. package/gsd-core/workflows/execute-plan.md +1 -1
  129. package/gsd-core/workflows/help/modes/full.md +6 -1
  130. package/gsd-core/workflows/ingest-docs.md +2 -1
  131. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  132. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  133. package/gsd-core/workflows/new-milestone.md +21 -38
  134. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  135. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  136. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  137. package/gsd-core/workflows/new-project.md +13 -226
  138. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  139. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  140. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  141. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  142. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  143. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  144. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  145. package/gsd-core/workflows/plan-phase.md +49 -193
  146. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  147. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  148. package/gsd-core/workflows/progress.md +11 -153
  149. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  150. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  151. package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
  152. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  153. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  154. package/gsd-core/workflows/quick.md +20 -390
  155. package/gsd-core/workflows/resume-project.md +3 -0
  156. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  157. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  158. package/gsd-core/workflows/review.md +15 -8
  159. package/gsd-core/workflows/section-manifest.json +219 -0
  160. package/gsd-core/workflows/sketch.md +1 -1
  161. package/gsd-core/workflows/spec-phase.md +17 -14
  162. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  163. package/gsd-core/workflows/spike.md +50 -16
  164. package/gsd-core/workflows/sync-skills.md +49 -11
  165. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  166. package/gsd-core/workflows/transition.md +8 -21
  167. package/gsd-core/workflows/ui-phase.md +8 -7
  168. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  169. package/gsd-core/workflows/update.md +18 -7
  170. package/gsd-core/workflows/verify-phase.md +4 -7
  171. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  172. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  173. package/gsd-core/workflows/verify-work.md +8 -58
  174. package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
  175. package/hooks/dist/gsd-check-update-worker.js +14 -5
  176. package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
  177. package/hooks/dist/gsd-read-injection-scanner.js +7 -0
  178. package/hooks/dist/gsd-statusline.js +72 -6
  179. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  180. package/hooks/dist/gsd-write-guard.js +359 -0
  181. package/hooks/dist/lib/isolation-sentinel.js +268 -0
  182. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  183. package/hooks/gsd-agent-isolation-guard.js +428 -0
  184. package/hooks/gsd-check-update-worker.js +14 -5
  185. package/hooks/gsd-cursor-subagent-start.js +532 -26
  186. package/hooks/gsd-read-injection-scanner.js +7 -0
  187. package/hooks/gsd-statusline.js +72 -6
  188. package/hooks/gsd-worktree-path-guard.js +2 -1
  189. package/hooks/gsd-write-guard.js +359 -0
  190. package/hooks/hooks.json +12 -0
  191. package/hooks/lib/isolation-sentinel.js +268 -0
  192. package/hooks/managed-hooks-registry.cjs +2 -0
  193. package/package.json +14 -5
  194. package/pi/gsd.cjs +57 -12
  195. package/scripts/build-hooks.js +9 -0
  196. package/scripts/changeset/lint.cjs +9 -2
  197. package/scripts/changeset/serialize.cjs +5 -1
  198. package/scripts/gen-capability-matrix.cjs +1 -1
  199. package/scripts/gen-context-index.cjs +448 -0
  200. package/scripts/gen-inventory-manifest.cjs +101 -1
  201. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  202. package/scripts/gen-registry.cjs +39 -15
  203. package/scripts/gen-section-manifest.cjs +638 -0
  204. package/scripts/generate-package-identity.cjs +4 -2
  205. package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
  206. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  207. package/scripts/lint-docs-command-form.cjs +195 -0
  208. package/scripts/lint-docs-required.cjs +9 -1
  209. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  210. package/scripts/lint-example-parser-parity.cjs +395 -0
  211. package/scripts/lint-test-file-count.allowlist.json +27 -1
  212. package/scripts/mutation-matrix.cjs +13 -0
  213. package/scripts/prompt-injection-scan.sh +27 -6
  214. package/scripts/registry-schema.cjs +323 -94
  215. package/scripts/run-tests.cjs +3 -2
  216. package/scripts/validate-registry.cjs +10 -6
  217. package/skills/gsd-autonomous/SKILL.md +1 -1
  218. package/skills/gsd-execute-phase/SKILL.md +1 -1
  219. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  220. package/skills/gsd-new-milestone/SKILL.md +1 -1
  221. package/skills/gsd-plan-phase/SKILL.md +2 -2
  222. package/vscode/package.json +1 -1
  223. package/scripts/gen-emitted-baseline.cjs +0 -145
@@ -34,6 +34,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34
34
  return (mod && mod.__esModule) ? mod : { "default": mod };
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.LEGACY_NON_REGISTRY_RUNTIME_IDS = void 0;
38
+ exports.isRegisteredRuntimeId = isRegisteredRuntimeId;
37
39
  exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
38
40
  exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
39
41
  exports.detectAntigravityDirAmbiguity = detectAntigravityDirAmbiguity;
@@ -48,15 +50,36 @@ const node_os_1 = __importDefault(require("node:os"));
48
50
  const node_path_1 = __importDefault(require("node:path"));
49
51
  const node_fs_1 = __importDefault(require("node:fs"));
50
52
  /**
51
- * Expand a leading ~ to os.homedir().
53
+ * Expand a leading ~ to the given home directory (defaults to os.homedir()).
54
+ * Every call site inside resolveConfigHomeFromDescriptor threads its
55
+ * resolved `home` local through here so an injected opts.home (used by
56
+ * hermetic tests) is honored instead of silently falling back to the real
57
+ * home directory.
52
58
  */
53
- function expandTilde(p) {
59
+ function expandTilde(p, home = node_os_1.default.homedir()) {
54
60
  if (!p)
55
61
  return p;
56
62
  if (p.startsWith('~/') || p === '~')
57
- return node_path_1.default.join(node_os_1.default.homedir(), p.slice(1));
63
+ return node_path_1.default.join(home, p.slice(1));
58
64
  return p;
59
65
  }
66
+ /**
67
+ * True when `val` is a usable env-var override: a real string that contains
68
+ * at least one non-whitespace character. Every env-override consumption site
69
+ * in resolveConfigHomeFromDescriptor gates on this instead of a bare truthy
70
+ * check, so `FOO_DIR=''` (empty), `FOO_DIR` unset (`undefined`), and
71
+ * `FOO_DIR=' '` (whitespace-only — e.g. from a shell templating bug that
72
+ * leaves a variable substitution blank but quoted) all fall back to the
73
+ * descriptor default identically. Deliberately does NOT trim: a value that
74
+ * merely has leading/trailing whitespace around otherwise-real content (or
75
+ * interior whitespace, e.g. `~/My Agent Dir`) is passed through byte-for-byte
76
+ * unchanged, exactly as this module already treats every other env-var
77
+ * override (no site here or elsewhere in this file trims a path value) — so
78
+ * default behavior for every non-whitespace value is unaffected by this guard.
79
+ */
80
+ function hasNonBlankOverride(val) {
81
+ return typeof val === 'string' && val.trim() !== '';
82
+ }
60
83
  function resolveDescriptorWithOptions(configHome) {
61
84
  return resolveConfigHomeFromDescriptor(configHome, {
62
85
  env: process.env,
@@ -68,6 +91,64 @@ function getRegistry() {
68
91
  // eslint-disable-next-line @typescript-eslint/no-require-imports
69
92
  return require('./capability-registry.cjs');
70
93
  }
94
+ /**
95
+ * Legacy runtime ids that have a genuine, dedicated resolution branch
96
+ * elsewhere in this module but no capability-registry descriptor (#3024
97
+ * review BLOCKER, finding 1).
98
+ *
99
+ * `isRegisteredRuntimeId`'s real contract is "does this id resolve to a
100
+ * real, runtime-specific path?", not "is it a key in the registry map" —
101
+ * registry membership is only a proxy for that, and the proxy is wrong for
102
+ * `grok`: `getGlobalConfigDir` has a live hardcoded branch for it (`~/.agents`,
103
+ * overridable via `GROK_AGENTS_HOME`), predating the capability-registry
104
+ * descriptor system, and it is documented end-user-facing surface
105
+ * (pre-fix `gsd-core/workflows/sync-skills.md` listed it explicitly, and
106
+ * `docs/discussions/grok-build-support-2026-05.md` records the support
107
+ * decision). Rejecting it here would silently remove working, documented
108
+ * `--skills-root`/`sync-skills` support for a runtime that never regressed.
109
+ *
110
+ * This set is enumerated by hand, not derived, and MUST stay in lockstep
111
+ * with `getGlobalConfigDir`'s hardcoded branches: as of #3024, `grok` is
112
+ * the ONLY id in this position (verified by reading the full function body
113
+ * — every other unregistered id there falls through to the claude
114
+ * fallback, which is the behavior this validator exists to reject). Do
115
+ * NOT add an id here just because it "should" work — add it only after
116
+ * confirming `getGlobalConfigDir` has a real dedicated branch for it, the
117
+ * way `grok`'s is confirmed above. Do not "clean this up" by removing it;
118
+ * that is exactly the regression this constant guards against.
119
+ */
120
+ exports.LEGACY_NON_REGISTRY_RUNTIME_IDS = new Set(['grok']);
121
+ /**
122
+ * True when `runtime` is a real runtime id with a genuine, runtime-specific
123
+ * resolution path — either a registered id in the capability registry
124
+ * (`capability-registry.cjs`'s `runtimes` object) or one of the small,
125
+ * explicitly named `LEGACY_NON_REGISTRY_RUNTIME_IDS` set (currently just
126
+ * `grok`) that resolves via a dedicated hardcoded branch instead of a
127
+ * registry descriptor.
128
+ *
129
+ * Guarded with an own-property lookup — never a bare index — so a
130
+ * prototype-chain id (`__proto__`, `constructor`, `prototype`, `toString`,
131
+ * …) can never resolve to an inherited value and be mistaken for a real
132
+ * entry. Without this, `getGlobalSkillsBase`/`getGlobalConfigDir`'s bare
133
+ * `runtimes[runtime]` lookups fall through the prototype chain for those
134
+ * ids, find no usable descriptor, and silently resolve to claude's
135
+ * fallback path instead of failing loudly (#3024).
136
+ *
137
+ * Single shared validator for every `--skills-root` entry point
138
+ * (`gsd-tools query skills-root`'s `routeSkillsRoot`, `install.js
139
+ * --skills-root`) so the two surfaces can never diverge on which runtime
140
+ * ids they accept.
141
+ */
142
+ function isRegisteredRuntimeId(runtime) {
143
+ if (typeof runtime !== 'string')
144
+ return false;
145
+ const trimmed = runtime.trim();
146
+ if (!trimmed)
147
+ return false;
148
+ if (Object.prototype.hasOwnProperty.call(getRegistry().runtimes, trimmed))
149
+ return true;
150
+ return exports.LEGACY_NON_REGISTRY_RUNTIME_IDS.has(trimmed);
151
+ }
71
152
  /**
72
153
  * Resolve a configHome descriptor to an absolute directory path.
73
154
  *
@@ -86,16 +167,16 @@ function resolveConfigHomeFromDescriptor(configHome, opts = {}) {
86
167
  // First env var that is set wins
87
168
  for (const varName of configHome.env) {
88
169
  const val = env[varName];
89
- if (val)
90
- return expandTilde(val);
170
+ if (hasNonBlankOverride(val))
171
+ return expandTilde(val, home);
91
172
  }
92
173
  return node_path_1.default.join(home, configHome.name);
93
174
  }
94
175
  case 'dot-home-nested': {
95
176
  // env override
96
177
  const nestedEnv0Val = env[configHome.env[0]];
97
- if (configHome.env[0] && nestedEnv0Val) {
98
- return expandTilde(nestedEnv0Val);
178
+ if (configHome.env[0] && hasNonBlankOverride(nestedEnv0Val)) {
179
+ return expandTilde(nestedEnv0Val, home);
99
180
  }
100
181
  const base = node_path_1.default.join(home, configHome.parent);
101
182
  if (configHome.probe && configHome.probe.length > 0) {
@@ -127,49 +208,50 @@ function resolveConfigHomeFromDescriptor(configHome, opts = {}) {
127
208
  case 'xdg': {
128
209
  // env[0]: direct override dir
129
210
  const xdgEnv0Val = env[configHome.env[0]];
130
- if (configHome.env[0] && xdgEnv0Val) {
131
- return expandTilde(xdgEnv0Val);
211
+ if (configHome.env[0] && hasNonBlankOverride(xdgEnv0Val)) {
212
+ return expandTilde(xdgEnv0Val, home);
132
213
  }
133
214
  // env[1]: FILE path → dirname
134
215
  const xdgEnv1Val = env[configHome.env[1]];
135
- if (configHome.env[1] && xdgEnv1Val) {
136
- return node_path_1.default.dirname(expandTilde(xdgEnv1Val));
216
+ if (configHome.env[1] && hasNonBlankOverride(xdgEnv1Val)) {
217
+ return node_path_1.default.dirname(expandTilde(xdgEnv1Val, home));
137
218
  }
138
219
  // env[2]: XDG_CONFIG_HOME → subdir
139
220
  const xdgEnv2Val = env[configHome.env[2]];
140
- if (configHome.env[2] && xdgEnv2Val) {
141
- return node_path_1.default.join(expandTilde(xdgEnv2Val), configHome.name);
221
+ if (configHome.env[2] && hasNonBlankOverride(xdgEnv2Val)) {
222
+ return node_path_1.default.join(expandTilde(xdgEnv2Val, home), configHome.name);
142
223
  }
143
224
  return node_path_1.default.join(home, '.config', configHome.name);
144
225
  }
145
226
  case 'generic-agents-root': {
146
227
  // env override
147
228
  const garEnv0Val = env[configHome.env[0]];
148
- if (configHome.env[0] && garEnv0Val) {
149
- return expandTilde(garEnv0Val);
229
+ if (configHome.env[0] && hasNonBlankOverride(garEnv0Val)) {
230
+ return expandTilde(garEnv0Val, home);
150
231
  }
151
232
  // probe each candidate; return first where probeExists subpath exists
152
233
  for (const candidate of configHome.probe) {
153
- const resolved = expandTildeWithHome(candidate, home);
234
+ const resolved = expandTilde(candidate, home);
154
235
  if (existsSyncFn(node_path_1.default.join(resolved, configHome.probeExists))) {
155
236
  return resolved;
156
237
  }
157
238
  }
158
239
  // fallback: first probe candidate
159
- return expandTildeWithHome(configHome.probe[0], home);
240
+ return expandTilde(configHome.probe[0], home);
241
+ }
242
+ case 'none': {
243
+ // #2103: no file-projected config directory exists for this runtime
244
+ // (e.g. vscode). Previously this kind had no matching case, so the
245
+ // switch fell through and implicitly returned `undefined` — which
246
+ // then crashed a downstream `path.join(undefined, ...)` with a
247
+ // cryptic `TypeError [ERR_INVALID_ARG_TYPE]` far from the real cause.
248
+ // Throwing here makes the failure mode explicit; callers that need a
249
+ // nullable result (getGlobalSkillsBase) check `configHome.kind` and
250
+ // short-circuit BEFORE ever reaching this function.
251
+ throw new Error(`Runtime "${configHome.name}" has no config-home directory (configHome.kind === "none")`);
160
252
  }
161
253
  }
162
254
  }
163
- /**
164
- * Expand ~ using an explicit home directory (for hermetic testing).
165
- */
166
- function expandTildeWithHome(p, home) {
167
- if (!p)
168
- return p;
169
- if (p.startsWith('~/') || p === '~')
170
- return node_path_1.default.join(home, p.slice(1));
171
- return p;
172
- }
173
255
  /**
174
256
  * Resolve Antigravity global config dir across 1.x and 2.x layouts.
175
257
  *
@@ -257,25 +339,45 @@ function resolveKimiGlobalDir(opts = {}) {
257
339
  }, { env, home, existsSync: existsSyncFn });
258
340
  }
259
341
  /**
260
- * Resolve the directory holding Kimi CLI's OWN native config.toml (the file
261
- * Kimi itself reads for providers/models/hooks/etc — see
262
- * moonshotai.github.io/kimi-cli/en/configuration/data-locations.html and
263
- * .../reference/kimi-command.html). Default `~/.kimi`, overridden by
264
- * `KIMI_SHARE_DIR` per Kimi's own upstream env-var (NOT `KIMI_CONFIG_DIR`,
265
- * which is a GSD-installer write-location override for the unrelated generic
266
- * Agent-Skills root resolved by resolveKimiGlobalDir above).
342
+ * Resolve the directory holding the Kimi product's OWN native config.toml —
343
+ * the file that product itself reads for providers/models/hooks/etc, and the
344
+ * one GSD writes its `[[hooks]]` block, hooks bundle and CommonJS marker into.
345
+ *
346
+ * The two Kimi runtimes share `hooksSurface: "kimi-hooks-toml"` but are
347
+ * different products with different roots, and this must be selected by
348
+ * `runtime` (#2755). Before that fix this function was unparameterized and a
349
+ * `--kimi-code` install wrote its hooks into Kimi CLI's `~/.kimi`, leaving Kimi
350
+ * Code with none:
351
+ *
352
+ * kimi → `~/.kimi`, overridden by `KIMI_SHARE_DIR`
353
+ * (moonshotai.github.io/kimi-cli/en/configuration/data-locations.html)
354
+ * kimi-code → `~/.kimi-code`, overridden by `KIMI_CODE_HOME`
355
+ * (moonshotai/kimi-code docs/en/configuration/data-locations.md;
356
+ * its hooks doc places `[[hooks]]` in `~/.kimi-code/config.toml`)
267
357
  *
268
- * This is deliberately a SEPARATE directory from GSD's kimi configHome
269
- * (~/.config/agents): Kimi's own docs confirm the Agent-Skills search path is
270
- * independent of KIMI_SHARE_DIR ("This variable does not affect Agent Skills
271
- * search paths, which are handled separately"). #2095 Upgrade 1 writes GSD's
272
- * native [[hooks]] entries into `<this dir>/config.toml`, never into the
273
- * skills configDir.
358
+ * Each product's env var is scoped to that product: `KIMI_SHARE_DIR` is Kimi
359
+ * CLI's own upstream variable and must NOT redirect kimi-code, nor vice versa.
360
+ *
361
+ * An unrecognised `runtime` (and an omitted one) falls back to `~/.kimi`, which
362
+ * preserves the pre-#2755 behaviour for every existing caller that passes no
363
+ * runtime — this function is exported, so that default is a contract.
364
+ *
365
+ * For BOTH runtimes this is deliberately a SEPARATE directory from the generic
366
+ * Agent-Skills root resolved by `resolveKimiGlobalDir` (`~/.config/agents`):
367
+ * both vendors' docs confirm the Agent-Skills search path is independent of the
368
+ * data-root env var. GSD's native `[[hooks]]` entries go in
369
+ * `<this dir>/config.toml`, never into the skills configDir.
274
370
  */
275
371
  function resolveKimiHooksTomlDir(opts = {}) {
276
372
  const env = opts.env ?? process.env;
277
373
  const home = opts.home ?? node_os_1.default.homedir();
278
- return resolveConfigHomeFromDescriptor({ kind: 'dot-home', name: '.kimi', env: ['KIMI_SHARE_DIR'] }, { env, home });
374
+ // Explicit comparison rather than an object lookup keyed on `runtime`: the
375
+ // value originates from argv, and an index would resolve inherited keys
376
+ // (`constructor`, `__proto__`) to something that is not a descriptor.
377
+ const descriptor = opts.runtime === 'kimi-code'
378
+ ? { kind: 'dot-home', name: '.kimi-code', env: ['KIMI_CODE_HOME'] }
379
+ : { kind: 'dot-home', name: '.kimi', env: ['KIMI_SHARE_DIR'] };
380
+ return resolveConfigHomeFromDescriptor(descriptor, { env, home });
279
381
  }
280
382
  /**
281
383
  * Return the global config base directory for the given runtime.
@@ -317,6 +419,17 @@ function resolveSkillsBaseFromDescriptor(configHome, opts = {}, skillsDestSubpat
317
419
  function getGlobalSkillsBase(runtime) {
318
420
  const runtimeEntry = getRegistry().runtimes[runtime];
319
421
  const descriptor = runtimeEntry?.runtime;
422
+ // #2103: a runtime with `configHome.kind === 'none'` (e.g. vscode —
423
+ // Marketplace/VSIX extension, installSurface:'none') has no file-projected
424
+ // config directory at all, and therefore no skills root. Short-circuit to
425
+ // null BEFORE falling through to getGlobalConfigDir below, which would
426
+ // otherwise throw resolving a 'none' configHome (see
427
+ // resolveConfigHomeFromDescriptor's 'none' case). null is the correct
428
+ // answer here, not a crash — the `=== null` guard at every call site
429
+ // (bin/install.js --skills-root, gsd-tools routeSkillsRoot) already
430
+ // handles it as "this runtime does not use a skills directory".
431
+ if (descriptor?.configHome?.kind === 'none')
432
+ return null;
320
433
  const globalSkillsKind = descriptor?.artifactLayout?.global?.find((entry) => entry.kind === 'skills');
321
434
  // ADR-1239 upgrade 3 (#2088): honor a skills-kind `home` override (e.g. Codex
322
435
  // → $HOME/.agents/skills, independent of $CODEX_HOME) so the reported skills
@@ -28,6 +28,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  const node_fs_1 = __importDefault(require("node:fs"));
29
29
  const node_path_1 = __importDefault(require("node:path"));
30
30
  const node_os_1 = __importDefault(require("node:os"));
31
+ // #2544: the single source of truth for the CommonJS module-type marker. The
32
+ // two helpers below are thin boolean-returning shims over these — see the
33
+ // marker section for why this file no longer carries its own copy.
34
+ const commonjs_marker_cjs_1 = require("./commonjs-marker.cjs");
31
35
  const imperative_hook_bus_cjs_1 = require("./host-integration-adapters/imperative-hook-bus.cjs");
32
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports
33
37
  const shellCmdProjection = require("./shell-command-projection.cjs");
@@ -166,62 +170,51 @@ function atomicWriteFileSync(target, data, options) {
166
170
  //
167
171
  // The marker content is byte-identical to installSharedHooksBundle's
168
172
  // (bin/install.js installSharedHooksBundle): {"type":"commonjs"}\n.
173
+ //
174
+ // #2544: the two helpers below no longer carry their own copy of the write and
175
+ // remove rules — they DELEGATE to src/commonjs-marker.cts, which #2544 makes the
176
+ // single place both rules are enforced. Keeping a second copy here was not
177
+ // merely redundant; the copies had drifted apart on exactly the two properties
178
+ // that matter:
179
+ //
180
+ // - ownership probe: `fs.existsSync` FOLLOWS symlinks and reports `false` for
181
+ // a DANGLING one, so a dangling `package.json` symlink classified as absent
182
+ // and the write below followed the link outside the directory GSD owns.
183
+ // `classifyMarker` uses `lstat` + `isFile()`, so a symlink or a directory at
184
+ // the marker path is classified `foreign` and left strictly alone.
185
+ // - create: a plain `writeFileSync` leaves the classify->write window open.
186
+ // `ensureCommonJsMarker` creates with `flag: 'wx'` (O_EXCL), so anything
187
+ // that appears at the path in between fails with EEXIST instead of being
188
+ // followed or overwritten.
189
+ //
190
+ // The exported signatures are unchanged (both still return a boolean), so every
191
+ // caller and the #2717 tests are unaffected.
169
192
  // ---------------------------------------------------------------------------
170
- /** The exact marker content GSD writes, matching installSharedHooksBundle. */
171
- const COMMONJS_MARKER_CONTENT = '{"type":"commonjs"}\n';
172
193
  /**
173
194
  * Ensure a `package.json` forcing CommonJS mode exists in `dir` (the directory
174
195
  * holding GSD-staged `.js` hook scripts). Idempotent: a no-op if the marker is
175
- * already present with GSD's content. Overwrites only when the file is absent
176
- * or already carries GSD's exact marker — it never clobbers a distinct
177
- * user-authored package.json (it leaves such a file in place; the user owns it).
196
+ * already present with GSD's content. Never clobbers a distinct user-authored
197
+ * package.json (it leaves such a file in place; the user owns it).
178
198
  *
179
199
  * @param dir - absolute path to the directory holding the staged .js hooks
180
- * @returns `true` if the marker is present after the call (written or already there)
200
+ * @returns `true` if GSD's marker is present after the call (written or already there)
181
201
  */
182
202
  function ensureCommonJsMarker(dir) {
183
- const markerPath = node_path_1.default.join(dir, 'package.json');
184
- try {
185
- if (node_fs_1.default.existsSync(markerPath)) {
186
- const existing = node_fs_1.default.readFileSync(markerPath, 'utf8');
187
- // Already GSD's marker (tolerant of trailing-whitespace variants) — done.
188
- if (existing.trim() === '{"type":"commonjs"}')
189
- return true;
190
- // A distinct package.json the user owns — do NOT clobber. The hook will
191
- // load as whatever type the user declared; that is the user's choice.
192
- return false;
193
- }
194
- node_fs_1.default.writeFileSync(markerPath, COMMONJS_MARKER_CONTENT);
195
- return true;
196
- }
197
- catch {
198
- // Best-effort: a marker write failure must not fail the whole install.
199
- return false;
200
- }
203
+ // 'written' | 'unchanged' -> the marker is ours and present.
204
+ // 'preserved-foreign' -> a file GSD does not own is there; left untouched.
205
+ // 'failed' -> environmental (EACCES/EROFS/ENOSPC); best-effort.
206
+ const outcome = (0, commonjs_marker_cjs_1.ensureCommonJsMarker)(dir);
207
+ return outcome === 'written' || outcome === 'unchanged';
201
208
  }
202
209
  /**
203
210
  * Remove the CommonJS marker from `dir` on uninstall — but ONLY if it carries
204
211
  * GSD's exact marker content. A user-authored package.json is never deleted.
205
- * Mirrors the kimi uninstall guard in bin/install.js.
206
212
  *
207
213
  * @param dir - absolute path to the directory that held the staged .js hooks
208
214
  * @returns `true` if a GSD-owned marker was removed
209
215
  */
210
216
  function removeCommonJsMarkerIfGsdOwned(dir) {
211
- const markerPath = node_path_1.default.join(dir, 'package.json');
212
- try {
213
- if (!node_fs_1.default.existsSync(markerPath))
214
- return false;
215
- const content = node_fs_1.default.readFileSync(markerPath, 'utf8').trim();
216
- if (content === '{"type":"commonjs"}') {
217
- node_fs_1.default.unlinkSync(markerPath);
218
- return true;
219
- }
220
- return false;
221
- }
222
- catch {
223
- return false;
224
- }
217
+ return (0, commonjs_marker_cjs_1.removeCommonJsMarker)(dir);
225
218
  }
226
219
  // ---------------------------------------------------------------------------
227
220
  // parseTomlValue + findMultilineBasicStringClose
@@ -1072,7 +1065,15 @@ function writeCursorHooksJson(targetDir, src, opts) {
1072
1065
  // installSharedHooksBundle (the only other writer of this marker); without
1073
1066
  // it, a ~/.cursor/package.json declaring {"type":"module"} makes Node load
1074
1067
  // these require()-using scripts as ESM and every Cursor hook fails silently.
1075
- ensureCommonJsMarker(hooksDir);
1068
+ //
1069
+ // #2544: gated on having actually staged a script, mirroring
1070
+ // installSharedHooksBundle's `stagedHooks` gate. hooks/ is shared space, and
1071
+ // this function mkdirs it unconditionally — so an ungated write drops a GSD
1072
+ // marker into a directory GSD created but did not fill, which is the same
1073
+ // write-into-someone-else's-territory this issue is about.
1074
+ if (installedScripts.size > 0) {
1075
+ ensureCommonJsMarker(hooksDir);
1076
+ }
1076
1077
  const hookOpts = { runtime: 'cursor', platform: opts.platform || process.platform };
1077
1078
  const commands = {};
1078
1079
  for (const ev of events) {
@@ -1266,7 +1267,12 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
1266
1267
  // installSharedHooksBundle (the only other writer of this marker); without
1267
1268
  // it, a config-root package.json declaring {"type":"module"} makes Node load
1268
1269
  // these require()-using scripts as ESM and the Windsurf hooks fail silently.
1269
- ensureCommonJsMarker(hooksDir);
1270
+ //
1271
+ // #2544: gated on having actually staged a script — see the identical gate in
1272
+ // the Cursor writer above and `stagedHooks` in installSharedHooksBundle.
1273
+ if (installedScripts.size > 0) {
1274
+ ensureCommonJsMarker(hooksDir);
1275
+ }
1270
1276
  const hookOpts = { runtime: 'windsurf', platform: opts.platform || process.platform };
1271
1277
  const commands = {};
1272
1278
  for (const ev of WINDSURF_HOOK_EVENTS) {
@@ -1597,6 +1603,63 @@ function applySettingsJsonHooks(settings, opts) {
1597
1603
  else if (!hasWorktreePathGuardHook && !node_fs_1.default.existsSync(worktreePathGuardFile)) {
1598
1604
  console.warn(` ${yellow}⚠${reset} Skipped worktree path guard hook — gsd-worktree-path-guard.js not found at target`);
1599
1605
  }
1606
+ // Configure PreToolUse hook for Agent-dispatch isolation (#3045)
1607
+ // Hard-blocks an executor Agent() dispatch (subagent_type="gsd-executor")
1608
+ // missing its harness isolation parameter when this project's resolved
1609
+ // dispatch isolation is harness-worktree. Prevents the executor from
1610
+ // silently running and committing in the primary checkout when the
1611
+ // model-authored dispatch omits isolation="worktree".
1612
+ const agentIsolationGuardCommand = isGlobal
1613
+ ? buildHookCommand(targetDir, 'gsd-agent-isolation-guard.js', hookOpts)
1614
+ : localCmd('gsd-agent-isolation-guard.js');
1615
+ const hasAgentIsolationGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-agent-isolation-guard')));
1616
+ const agentIsolationGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-agent-isolation-guard.js');
1617
+ if (!hasAgentIsolationGuardHook && node_fs_1.default.existsSync(agentIsolationGuardFile) && agentIsolationGuardCommand) {
1618
+ settings.hooks[preToolEvent].push({
1619
+ // #3045 MAJOR 1: widened from "Agent"-only — hooks.json's own
1620
+ // PostToolUse precedent (context-monitor) already hedges both names,
1621
+ // and the hook itself now accepts tool_name "Task" too.
1622
+ matcher: 'Agent|Task',
1623
+ hooks: [
1624
+ {
1625
+ type: 'command',
1626
+ command: agentIsolationGuardCommand,
1627
+ timeout: 5
1628
+ }
1629
+ ]
1630
+ });
1631
+ console.log(` ${green}✓${reset} Configured agent isolation dispatch guard hook`);
1632
+ }
1633
+ else if (!hasAgentIsolationGuardHook && !node_fs_1.default.existsSync(agentIsolationGuardFile)) {
1634
+ console.warn(` ${yellow}⚠${reset} Skipped agent isolation guard hook — gsd-agent-isolation-guard.js not found at target`);
1635
+ }
1636
+ // Configure PreToolUse hook for catastrophic-shrink protection (#2255, fix 3 of #973)
1637
+ // Hard-blocks a whole-file Write that collapses a curated .planning/ artifact
1638
+ // (ROADMAP.md, milestone roadmaps, STATE.md) far below its on-disk size.
1639
+ // Escape hatches (both named in the block message): the single-use
1640
+ // sentinel .planning/.gsd-allow-shrink (workflow steps — a per-step env
1641
+ // cannot reach a hook) and GSD_ALLOW_PLANNING_SHRINK=1 (interactive).
1642
+ const writeGuardCommand = isGlobal
1643
+ ? buildHookCommand(targetDir, 'gsd-write-guard.js', hookOpts)
1644
+ : localCmd('gsd-write-guard.js');
1645
+ const hasWriteGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-write-guard')));
1646
+ const writeGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-write-guard.js');
1647
+ if (!hasWriteGuardHook && node_fs_1.default.existsSync(writeGuardFile) && writeGuardCommand) {
1648
+ settings.hooks[preToolEvent].push({
1649
+ matcher: 'Write',
1650
+ hooks: [
1651
+ {
1652
+ type: 'command',
1653
+ command: writeGuardCommand,
1654
+ timeout: 5
1655
+ }
1656
+ ]
1657
+ });
1658
+ console.log(` ${green}✓${reset} Configured write guard hook (catastrophic-shrink protection)`);
1659
+ }
1660
+ else if (!hasWriteGuardHook && !node_fs_1.default.existsSync(writeGuardFile)) {
1661
+ console.warn(` ${yellow}⚠${reset} Skipped write guard hook — gsd-write-guard.js not found at target`);
1662
+ }
1600
1663
  // Configure commit validation hook (Conventional Commits enforcement, opt-in)
1601
1664
  const validateCommitCommand = isGlobal
1602
1665
  ? buildHookCommand(targetDir, 'gsd-validate-commit.sh', hookOpts)
@@ -1925,6 +1988,7 @@ function buildKimiHooksTomlBlock(targetDir, opts) {
1925
1988
  { event: 'PreToolUse', command: cmd('gsd-prompt-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
1926
1989
  { event: 'PreToolUse', command: cmd('gsd-read-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
1927
1990
  { event: 'PreToolUse', command: cmd('gsd-worktree-path-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
1991
+ { event: 'PreToolUse', command: cmd('gsd-write-guard.js'), matcher: 'WriteFile', timeout: 5 },
1928
1992
  { event: 'PreToolUse', command: cmd('gsd-workflow-guard.js'), matcher: 'Shell|WriteFile|StrReplaceFile', timeout: 5 },
1929
1993
  { event: 'PreToolUse', command: cmd('gsd-validate-commit.sh'), matcher: 'Shell', timeout: 5 },
1930
1994
  // PostToolUse