@opengsd/gsd-core 1.4.4 → 1.5.0-rc.2

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 (197) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +3 -3
  3. package/agents/gsd-code-fixer.md +3 -2
  4. package/agents/gsd-debug-session-manager.md +2 -1
  5. package/agents/gsd-debugger.md +4 -3
  6. package/agents/gsd-executor.md +17 -16
  7. package/agents/gsd-intel-updater.md +38 -41
  8. package/agents/gsd-phase-researcher.md +8 -8
  9. package/agents/gsd-plan-checker.md +23 -13
  10. package/agents/gsd-planner.md +32 -188
  11. package/agents/gsd-project-researcher.md +5 -4
  12. package/agents/gsd-research-synthesizer.md +2 -1
  13. package/agents/gsd-ui-researcher.md +2 -1
  14. package/agents/gsd-verifier.md +12 -11
  15. package/bin/install.js +965 -1486
  16. package/commands/gsd/autonomous.md +5 -1
  17. package/commands/gsd/ns-manage.md +8 -1
  18. package/commands/gsd/ns-project.md +5 -0
  19. package/commands/gsd/ns-review.md +4 -1
  20. package/commands/gsd/ns-workflow.md +7 -1
  21. package/commands/gsd/plan-review-convergence.md +5 -4
  22. package/commands/gsd/surface.md +12 -5
  23. package/gemini-extension.json +1 -1
  24. package/gsd-core/bin/gsd-tools.cjs +198 -101
  25. package/gsd-core/bin/gsd_run +20 -0
  26. package/gsd-core/bin/lib/audit-command-router.cjs +61 -0
  27. package/gsd-core/bin/lib/capability-registry.cjs +2234 -0
  28. package/gsd-core/bin/lib/capability-state.cjs +336 -0
  29. package/gsd-core/bin/lib/check-command-router.cjs +133 -2
  30. package/gsd-core/bin/lib/cli-exit.cjs +22 -3
  31. package/gsd-core/bin/lib/config-loader.cjs +716 -0
  32. package/gsd-core/bin/lib/configuration.cjs +4 -34
  33. package/gsd-core/bin/lib/core-utils.cjs +198 -0
  34. package/gsd-core/bin/lib/core.cjs +107 -1817
  35. package/gsd-core/bin/lib/edge-probe.cjs +173 -0
  36. package/gsd-core/bin/lib/fallow-runner.cjs +63 -25
  37. package/gsd-core/bin/lib/federated-config.cjs +182 -0
  38. package/gsd-core/bin/lib/graphify-command-router.cjs +74 -0
  39. package/gsd-core/bin/lib/init.cjs +58 -12
  40. package/gsd-core/bin/lib/install-profiles.cjs +157 -3
  41. package/gsd-core/bin/lib/intel-command-router.cjs +116 -0
  42. package/gsd-core/bin/lib/intel.cjs +3 -3
  43. package/gsd-core/bin/lib/io.cjs +222 -0
  44. package/gsd-core/bin/lib/loop-host-contract.cjs +105 -0
  45. package/gsd-core/bin/lib/loop-resolver.cjs +460 -0
  46. package/gsd-core/bin/lib/model-resolver.cjs +426 -0
  47. package/gsd-core/bin/lib/phase-command-router.cjs +20 -0
  48. package/gsd-core/bin/lib/phase-id.cjs +215 -0
  49. package/gsd-core/bin/lib/phase-locator.cjs +148 -0
  50. package/gsd-core/bin/lib/phase.cjs +17 -0
  51. package/gsd-core/bin/lib/probe-core.cjs +257 -0
  52. package/gsd-core/bin/lib/profile-pipeline.cjs +2 -2
  53. package/gsd-core/bin/lib/roadmap-parser.cjs +443 -0
  54. package/gsd-core/bin/lib/roadmap.cjs +44 -1
  55. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +92 -95
  56. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +68 -29
  57. package/gsd-core/bin/lib/runtime-homes.cjs +163 -87
  58. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1439 -0
  59. package/gsd-core/bin/lib/runtime-name-policy.cjs +2 -1
  60. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  61. package/gsd-core/bin/lib/shell-command-projection.cjs +13 -0
  62. package/gsd-core/bin/lib/state-document.cjs +8 -0
  63. package/gsd-core/bin/lib/state.cjs +114 -2
  64. package/gsd-core/bin/lib/surface.cjs +66 -14
  65. package/gsd-core/bin/lib/uat-predicate.cjs +329 -0
  66. package/gsd-core/bin/lib/update-context.cjs +4 -1
  67. package/gsd-core/bin/lib/verify.cjs +104 -1
  68. package/gsd-core/bin/lib/worktree-base-ref.cjs +33 -8
  69. package/gsd-core/bin/shared/model-catalog.json +11 -6
  70. package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -1
  71. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
  72. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
  73. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
  74. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
  75. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
  76. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
  77. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
  78. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
  79. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
  80. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
  81. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
  82. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
  83. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
  84. package/gsd-core/references/edge-probe.md +261 -0
  85. package/gsd-core/references/planner-antipatterns.md +41 -0
  86. package/gsd-core/references/planner-guidance.md +186 -0
  87. package/gsd-core/references/planner-reviews.md +5 -2
  88. package/gsd-core/templates/phase-prompt.md +7 -7
  89. package/gsd-core/templates/project.md +19 -2
  90. package/gsd-core/templates/spec.md +12 -0
  91. package/gsd-core/templates/summary-complex.md +1 -0
  92. package/gsd-core/templates/summary-minimal.md +1 -0
  93. package/gsd-core/templates/summary-standard.md +1 -0
  94. package/gsd-core/templates/summary.md +1 -0
  95. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  96. package/gsd-core/workflows/add-backlog.md +1 -1
  97. package/gsd-core/workflows/add-phase.md +1 -1
  98. package/gsd-core/workflows/add-tests.md +1 -1
  99. package/gsd-core/workflows/add-todo.md +1 -1
  100. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  101. package/gsd-core/workflows/audit-fix.md +1 -1
  102. package/gsd-core/workflows/audit-milestone.md +1 -1
  103. package/gsd-core/workflows/audit-uat.md +1 -1
  104. package/gsd-core/workflows/autonomous.md +111 -51
  105. package/gsd-core/workflows/check-todos.md +1 -1
  106. package/gsd-core/workflows/cleanup.md +1 -1
  107. package/gsd-core/workflows/code-review-fix.md +6 -4
  108. package/gsd-core/workflows/code-review.md +53 -17
  109. package/gsd-core/workflows/complete-milestone.md +11 -5
  110. package/gsd-core/workflows/debug.md +1 -1
  111. package/gsd-core/workflows/diagnose-issues.md +1 -1
  112. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  113. package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
  114. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  115. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  116. package/gsd-core/workflows/discuss-phase.md +8 -1
  117. package/gsd-core/workflows/do.md +1 -1
  118. package/gsd-core/workflows/docs-update.md +1 -1
  119. package/gsd-core/workflows/edit-phase.md +1 -1
  120. package/gsd-core/workflows/eval-review.md +4 -1
  121. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  122. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  123. package/gsd-core/workflows/execute-phase.md +8 -1
  124. package/gsd-core/workflows/execute-plan.md +1 -1
  125. package/gsd-core/workflows/explore.md +1 -1
  126. package/gsd-core/workflows/extract-learnings.md +1 -1
  127. package/gsd-core/workflows/forensics.md +1 -1
  128. package/gsd-core/workflows/graduation.md +1 -1
  129. package/gsd-core/workflows/health.md +1 -1
  130. package/gsd-core/workflows/help/modes/full.md +1 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/ingest-docs.md +1 -1
  133. package/gsd-core/workflows/insert-phase.md +1 -1
  134. package/gsd-core/workflows/list-workspaces.md +1 -1
  135. package/gsd-core/workflows/manager.md +1 -1
  136. package/gsd-core/workflows/map-codebase.md +1 -1
  137. package/gsd-core/workflows/milestone-summary.md +1 -1
  138. package/gsd-core/workflows/mvp-phase.md +1 -1
  139. package/gsd-core/workflows/new-milestone.md +9 -1
  140. package/gsd-core/workflows/new-project.md +9 -1
  141. package/gsd-core/workflows/new-workspace.md +1 -1
  142. package/gsd-core/workflows/next.md +1 -1
  143. package/gsd-core/workflows/pause-work.md +1 -1
  144. package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
  145. package/gsd-core/workflows/plan-phase.md +65 -28
  146. package/gsd-core/workflows/plan-review-convergence.md +60 -33
  147. package/gsd-core/workflows/plant-seed.md +1 -1
  148. package/gsd-core/workflows/profile-user.md +1 -1
  149. package/gsd-core/workflows/progress.md +1 -1
  150. package/gsd-core/workflows/quick.md +2 -2
  151. package/gsd-core/workflows/remove-phase.md +1 -1
  152. package/gsd-core/workflows/remove-workspace.md +1 -1
  153. package/gsd-core/workflows/resume-project.md +1 -1
  154. package/gsd-core/workflows/review.md +1 -1
  155. package/gsd-core/workflows/scan.md +1 -1
  156. package/gsd-core/workflows/secure-phase.md +1 -1
  157. package/gsd-core/workflows/settings-advanced.md +7 -7
  158. package/gsd-core/workflows/settings-integrations.md +1 -1
  159. package/gsd-core/workflows/settings.md +2 -2
  160. package/gsd-core/workflows/ship.md +8 -1
  161. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  162. package/gsd-core/workflows/sketch.md +1 -1
  163. package/gsd-core/workflows/spec-phase.md +130 -1
  164. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  165. package/gsd-core/workflows/spike.md +1 -1
  166. package/gsd-core/workflows/stats.md +1 -1
  167. package/gsd-core/workflows/thread.md +1 -1
  168. package/gsd-core/workflows/transition.md +1 -1
  169. package/gsd-core/workflows/ui-phase.md +1 -1
  170. package/gsd-core/workflows/ui-review.md +1 -1
  171. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  172. package/gsd-core/workflows/update.md +2 -2
  173. package/gsd-core/workflows/validate-phase.md +1 -1
  174. package/gsd-core/workflows/verify-phase.md +1 -1
  175. package/gsd-core/workflows/verify-work.md +8 -1
  176. package/package.json +11 -3
  177. package/scripts/base64-scan.sh +1 -1
  178. package/scripts/changeset/cli.cjs +8 -1
  179. package/scripts/changeset/lint.cjs +38 -2
  180. package/scripts/ci-test-scope.cjs +21 -10
  181. package/scripts/gen-capability-registry.cjs +2293 -0
  182. package/scripts/gen-loop-host-contract.cjs +471 -0
  183. package/scripts/lib/allowlist-ratchet.cjs +101 -1
  184. package/scripts/lint-regression-test-names.allowlist.json +269 -0
  185. package/scripts/lint-regression-test-names.cjs +117 -0
  186. package/scripts/lint-test-file-count.allowlist.json +25 -4
  187. package/scripts/lint-windows-test-portability.cjs +178 -0
  188. package/scripts/prompt-injection-scan.sh +4 -4
  189. package/scripts/research-profiles.cjs +10 -10
  190. package/scripts/run-tests.cjs +133 -29
  191. package/scripts/secret-scan.sh +3 -3
  192. package/scripts/sync-next-version.cjs +133 -0
  193. package/scripts/sync-runtime-launcher.cjs +21 -5
  194. package/scripts/update-size-baseline.cjs +68 -0
  195. package/scripts/workflow-policy.cjs +42 -9
  196. package/scripts/workflow-size.cjs +90 -0
  197. package/scripts/run-cross-platform-tests.cjs +0 -67
@@ -16,6 +16,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
16
16
  */
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const node_fs_1 = __importDefault(require("node:fs"));
19
+ const node_os_1 = __importDefault(require("node:os"));
19
20
  // eslint-disable-next-line @typescript-eslint/no-require-imports
20
21
  const installProfiles = require("./install-profiles.cjs");
21
22
  const { stageSkillsForProfile, stageAgentsForProfile, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
@@ -129,7 +130,7 @@ function findAgentsSourceRoot(runtimeConfigDir) {
129
130
  const ALLOWED_RUNTIMES = new Set([
130
131
  'claude', 'cursor', 'gemini', 'codex', 'copilot', 'antigravity',
131
132
  'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy',
132
- 'cline', 'opencode', 'kilo',
133
+ 'cline', 'kimi', 'opencode', 'kilo',
133
134
  ]);
134
135
  // ---------------------------------------------------------------------------
135
136
  // Layout table builders
@@ -150,6 +151,43 @@ function agentsKind(destSubpath, prefix, configDir) {
150
151
  stage: (resolved) => stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved),
151
152
  };
152
153
  }
154
+ function kimiAgentsKind(destSubpath, prefix, configDir) {
155
+ return {
156
+ kind: 'kimi-agents',
157
+ destSubpath,
158
+ prefix,
159
+ stage: (resolved) => {
160
+ const installExports = getInstallExports();
161
+ const buildKimiAgentArtifacts = installExports['buildKimiAgentArtifacts'];
162
+ const stagedAgents = stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved);
163
+ const subagents = [];
164
+ if (node_fs_1.default.existsSync(stagedAgents)) {
165
+ for (const entry of node_fs_1.default.readdirSync(stagedAgents, { withFileTypes: true })) {
166
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
167
+ continue;
168
+ const agentPath = node_path_1.default.join(stagedAgents, entry.name);
169
+ subagents.push({
170
+ path: node_path_1.default.join('agents', entry.name).replace(/\\/g, '/'),
171
+ content: node_fs_1.default.readFileSync(agentPath, 'utf8'),
172
+ });
173
+ }
174
+ }
175
+ const rootAgent = `---\nname: gsd\ndescription: Run GSD workflows in Kimi CLI.\ntools: Agent\n---\n\n# GSD for Kimi CLI\n\nCoordinate installed /skill:gsd-* workflows and route work to generated GSD subagents when a workflow requires an agent handoff.\n`;
176
+ const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents });
177
+ const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-kimi-agents-'));
178
+ installProfiles.STAGED_DIRS.add(stageDir);
179
+ node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.yaml'), artifacts.root.yaml);
180
+ node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.md'), artifacts.root.prompt);
181
+ const subagentsDir = node_path_1.default.join(stageDir, 'subagents');
182
+ node_fs_1.default.mkdirSync(subagentsDir, { recursive: true });
183
+ for (const artifact of artifacts.subagents) {
184
+ node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
185
+ node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.md`), artifact.prompt);
186
+ }
187
+ return stageDir;
188
+ },
189
+ };
190
+ }
153
191
  /**
154
192
  * Build a skills kind descriptor.
155
193
  *
@@ -158,8 +196,13 @@ function agentsKind(destSubpath, prefix, configDir) {
158
196
  * @param converterName name of converter function in bin/install.js exports
159
197
  * @param runtime canonical runtime ID (gates Hermes/Qwen branding in converter)
160
198
  * @param configDir runtime config dir (for .gsd-source marker resolution)
199
+ * @param nested if true, nest concrete skills under their ns-* routers (#69)
200
+ * @param scope install scope; converted to isGlobal and passed as 5th positional
201
+ * arg so scope-aware converters (antigravity, copilot) can choose
202
+ * between global home paths and workspace-relative paths without
203
+ * colliding with the `runtime` string at position 3.
161
204
  */
162
- function skillsKind(destSubpath, prefix, converterName, runtime, configDir) {
205
+ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false, scope = 'global') {
163
206
  return {
164
207
  kind: 'skills',
165
208
  destSubpath,
@@ -168,10 +211,15 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir) {
168
211
  const installExports = getInstallExports();
169
212
  const realConverter = installExports[converterName];
170
213
  // Compute cmdNames once per stage call for performance (#3583).
171
- // Extra args are ignored by converters that don't need runtime/cmdNames.
214
+ // Extra trailing args are ignored by converters that don't need them. The
215
+ // isGlobal flag is the 5th positional (NOT the 3rd): the 3rd positional is
216
+ // `runtime` for the claude/kimi/cline converters, so the scope-aware
217
+ // converters (antigravity, copilot) read isGlobal from position 5 to avoid
218
+ // colliding with `runtime` and always taking the global branch.
172
219
  const cmdNames = installExports.readGsdCommandNames();
173
- const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames);
174
- return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix);
220
+ const isGlobal = scope === 'global';
221
+ const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames, isGlobal);
222
+ return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested);
175
223
  },
176
224
  };
177
225
  }
@@ -203,11 +251,41 @@ function convertedCommandsKind(destSubpath, prefix, converterName, configDir) {
203
251
  },
204
252
  };
205
253
  }
206
- // ---------------------------------------------------------------------------
207
- // Public API
208
- // ---------------------------------------------------------------------------
254
+ /** Lazy registry accessor — mirrors pattern from 5b/5c (runtime-homes.cts). */
255
+ function getRegistry() {
256
+ return _require('./capability-registry.cjs');
257
+ }
258
+ /**
259
+ * Map a single ArtifactKindDescriptor entry to an ArtifactKind using the
260
+ * matching builder function. Mirrors the hand-built calls in the old switch.
261
+ */
262
+ function dispatchKindEntry(entry, runtime, configDir, scope) {
263
+ const { kind, destSubpath, prefix, nesting, converter } = entry;
264
+ const nested = nesting === 'nested';
265
+ switch (kind) {
266
+ case 'commands':
267
+ if (converter == null) {
268
+ return commandsKind(destSubpath, prefix, configDir);
269
+ }
270
+ return convertedCommandsKind(destSubpath, prefix, converter, configDir);
271
+ case 'agents':
272
+ return agentsKind(destSubpath, prefix, configDir);
273
+ case 'skills':
274
+ if (converter == null) {
275
+ throw new TypeError(`resolveRuntimeArtifactLayout: skills entry for '${runtime}' has converter=null (converter is required for skills)`);
276
+ }
277
+ return skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope);
278
+ case 'kimi-agents':
279
+ return kimiAgentsKind(destSubpath, prefix, configDir);
280
+ default:
281
+ throw new TypeError(`resolveRuntimeArtifactLayout: unknown kind '${kind}' in descriptor for runtime '${runtime}'`);
282
+ }
283
+ }
209
284
  /**
210
285
  * Resolve the artifact layout for a given runtime and config directory.
286
+ *
287
+ * ADR-857 phase 5d: driven by the capability-registry artifactLayout descriptor
288
+ * instead of a hardcoded switch statement.
211
289
  */
212
290
  function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
213
291
  if (typeof configDir !== 'string' || configDir === '') {
@@ -219,94 +297,13 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
219
297
  if (!ALLOWED_RUNTIMES.has(runtime)) {
220
298
  throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
221
299
  }
222
- let kinds;
223
- switch (runtime) {
224
- case 'claude':
225
- if (scope === 'local') {
226
- kinds = [
227
- commandsKind('commands/gsd', 'gsd-', configDir),
228
- agentsKind('agents', 'gsd-', configDir),
229
- ];
230
- }
231
- else {
232
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClaudeSkill', 'claude', configDir)];
233
- }
234
- break;
235
- case 'cursor':
236
- // Cursor 1.6+ supports two artifact surfaces:
237
- // 1. skills/gsd-<name>/SKILL.md — rich skills with frontmatter + adapter header
238
- // 2. commands/gsd-<name>.md — plain markdown slash commands (no frontmatter)
239
- // accessed via '/' in the Agent input (#785)
240
- kinds = [
241
- skillsKind('skills', 'gsd-', 'convertClaudeCommandToCursorSkill', 'cursor', configDir),
242
- convertedCommandsKind('commands', 'gsd-', 'convertClaudeCommandToCursorCommand', configDir),
243
- ];
244
- break;
245
- case 'gemini':
246
- kinds = [commandsKind('commands/gsd', 'gsd-', configDir)];
247
- break;
248
- case 'codex':
249
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToCodexSkill', 'codex', configDir)];
250
- break;
251
- case 'copilot':
252
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToCopilotSkill', 'copilot', configDir)];
253
- break;
254
- case 'antigravity':
255
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToAntigravitySkill', 'antigravity', configDir)];
256
- break;
257
- case 'windsurf':
258
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToWindsurfSkill', 'windsurf', configDir)];
259
- break;
260
- case 'augment':
261
- kinds = [
262
- commandsKind('commands', 'gsd-', configDir),
263
- skillsKind('skills', 'gsd-', 'convertClaudeCommandToAugmentSkill', 'augment', configDir),
264
- ];
265
- break;
266
- case 'trae':
267
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToTraeSkill', 'trae', configDir)];
268
- break;
269
- case 'qwen':
270
- kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClaudeSkill', 'qwen', configDir)];
271
- break;
272
- case 'hermes':
273
- kinds = [skillsKind('skills/gsd', '', 'convertClaudeCommandToClaudeSkill', 'hermes', configDir)];
274
- break;
275
- case 'codebuddy':
276
- // CodeBuddy (Tencent) reads two user-level surfaces (codebuddy.ai/docs/cli):
277
- // 1. commands/gsd-<name>.md — slash commands shown in the '/' menu (#789)
278
- // 2. skills/gsd-<name>/SKILL.md — model-invocable skills, emitted with
279
- // user-invocable:false so they stay OUT of '/' (the commands surface is
280
- // the sole '/' entry point) — avoids a duplicated /gsd-* per workflow.
281
- // Subagents (~/.codebuddy/agents/) are already emitted by the generic agents
282
- // block in bin/install.js; MCP is excluded (gsd ships no MCP server).
283
- kinds = [
284
- convertedCommandsKind('commands', 'gsd-', 'convertClaudeCommandToCodebuddyCommand', configDir),
285
- skillsKind('skills', 'gsd-', 'convertClaudeCommandToCodebuddySkill', 'codebuddy', configDir),
286
- ];
287
- break;
288
- case 'cline':
289
- kinds = scope === 'global' ? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClineSkill', 'cline', configDir)] : [];
290
- break;
291
- case 'opencode':
292
- // OpenCode reads flat slash commands from command/ and on-demand skills
293
- // from skills/<name>/SKILL.md (https://opencode.ai/docs/skills). Emit both.
294
- kinds = [
295
- commandsKind('command', 'gsd-', configDir),
296
- skillsKind('skills', 'gsd-', 'convertClaudeCommandToOpencodeSkill', 'opencode', configDir),
297
- ];
298
- break;
299
- case 'kilo':
300
- // Kilo derives from OpenCode and shares the skills/<name>/SKILL.md layout
301
- // (https://kilo.ai/docs/customize/skills). Emit flat commands + skills.
302
- kinds = [
303
- commandsKind('command', 'gsd-', configDir),
304
- skillsKind('skills', 'gsd-', 'convertClaudeCommandToKiloSkill', 'kilo', configDir),
305
- ];
306
- break;
307
- default:
308
- throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
300
+ const desc = getRegistry().runtimes[runtime]?.runtime?.artifactLayout;
301
+ if (!desc) {
302
+ // Runtime is in ALLOWED_RUNTIMES but has no descriptor — reproduce old default: throw.
303
+ throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
309
304
  }
305
+ const entries = desc[scope] ?? [];
306
+ const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope));
310
307
  return { runtime, configDir, scope, kinds };
311
308
  }
312
309
  module.exports = { resolveRuntimeArtifactLayout, findInstallSourceRoot, getInstallExports };
@@ -1,29 +1,39 @@
1
1
  'use strict';
2
- // ---------------------------------------------------------------------------
3
- // Registry
4
- // ---------------------------------------------------------------------------
5
- const REGISTRY = Object.freeze({
6
- claude: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
7
- gemini: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
8
- antigravity: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
9
- augment: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
10
- qwen: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
11
- hermes: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
12
- codebuddy: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null }),
13
- opencode: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: 'opencode' }),
14
- kilo: Object.freeze({ installSurface: 'settings-json', writesSharedSettings: false, finishPermissionWriter: 'kilo' }),
15
- codex: Object.freeze({ installSurface: 'codex-toml', writesSharedSettings: false, finishPermissionWriter: null }),
16
- copilot: Object.freeze({ installSurface: 'copilot-instructions', writesSharedSettings: false, finishPermissionWriter: null }),
17
- cline: Object.freeze({ installSurface: 'cline-rules', writesSharedSettings: false, finishPermissionWriter: null }),
18
- cursor: Object.freeze({ installSurface: 'cursor-hooks-json', writesSharedSettings: false, finishPermissionWriter: null }),
19
- windsurf: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
20
- trae: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
21
- });
2
+ /**
3
+ * Runtime config adapter registry — dispatch table for install-phase config
4
+ * mutations (issue #60), replacing inline `runtime === '...'` branching in
5
+ * bin/install.js.
6
+ *
7
+ * ADR-857 phase 5g drive 2: The hand-kept REGISTRY const has been retired.
8
+ * Values are now read directly from the capability-registry.cjs descriptor
9
+ * (capabilities/<id>/capability.json runtime block) so a single source of
10
+ * truth drives all surfaces.
11
+ *
12
+ * Design notes:
13
+ * - `installSurface` selects which config handler install() runs:
14
+ * 'settings-json' → fall through to the shared settings.json accumulation.
15
+ * 'codex-toml' → early-return after writing codex.toml.
16
+ * 'copilot-instructions' → early-return after writing .github/copilot-instructions.md.
17
+ * 'cline-rules' → early-return after writing .clinerules.
18
+ * 'cursor-hooks-json' → early-return after writing .cursor/hooks.json (issue #777).
19
+ * 'profile-marker-only' → early-return after writing only the profile marker.
20
+ * - `writesSharedSettings` is the finishInstall writeSettings gate:
21
+ * false for codex / copilot / kilo / cursor / windsurf / trae / cline / kimi (legacy exclusion list).
22
+ * true for all other runtimes.
23
+ * - `finishPermissionWriter` names the finishInstall-phase dedicated config writer:
24
+ * 'opencode' → writes BOTH shared settings AND its own permissions file.
25
+ * 'kilo' → writes only its own permissions file.
26
+ * null → no dedicated permission writer.
27
+ */
28
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
29
+ const { runtimes } = require('./capability-registry.cjs');
22
30
  // ---------------------------------------------------------------------------
23
31
  // Exports
24
32
  // ---------------------------------------------------------------------------
25
- /** The complete set of 15 supported runtimes for config-adapter dispatch. */
26
- const ALLOWED_CONFIG_RUNTIMES = new Set(Object.keys(REGISTRY));
33
+ /** The complete set of 16 supported runtimes for config-adapter dispatch. */
34
+ const ALLOWED_CONFIG_RUNTIMES = new Set(Object.entries(runtimes)
35
+ .filter(([, cap]) => cap && cap.runtime && typeof cap.runtime['installSurface'] === 'string')
36
+ .map(([id]) => id));
27
37
  /** All valid installSurface values. */
28
38
  const INSTALL_SURFACES = Object.freeze([
29
39
  'settings-json',
@@ -42,15 +52,44 @@ const INSTALL_SURFACES = Object.freeze([
42
52
  * @throws {TypeError} if runtime is not a known supported runtime.
43
53
  */
44
54
  function resolveRuntimeConfigIntent(runtime) {
45
- if (!Object.hasOwn(REGISTRY, runtime)) {
55
+ const entry = runtimes[runtime]?.runtime;
56
+ if (!entry)
46
57
  throw new TypeError(`Unknown runtime for config adapter: ${runtime}`);
47
- }
48
- const entry = REGISTRY[runtime];
58
+ const permissionWriter = entry['permissionWriter'];
59
+ return {
60
+ runtime,
61
+ installSurface: entry['installSurface'],
62
+ writesSharedSettings: entry['writesSharedSettings'],
63
+ finishPermissionWriter: permissionWriter == null ? null : permissionWriter,
64
+ };
65
+ }
66
+ /**
67
+ * Resolve the complete install plan for a given runtime.
68
+ *
69
+ * Composes the config-intent axes from resolveRuntimeConfigIntent PLUS the
70
+ * three hook axes (hookEvents / extendedHookEvents / hooksSurface) that
71
+ * install() previously read scattered from the capability registry.
72
+ *
73
+ * ADR-857 phase 5g capstone — single typed seam for all install-level
74
+ * descriptor reads. Returns a fresh object each call.
75
+ *
76
+ * @throws {TypeError} if runtime is not a known supported runtime.
77
+ */
78
+ function resolveInstallPlan(runtime) {
79
+ const desc = runtimes[runtime]?.runtime;
80
+ if (!desc)
81
+ throw new TypeError(`Unknown runtime for install plan: ${runtime}`);
82
+ const configIntent = resolveRuntimeConfigIntent(runtime);
49
83
  return {
50
84
  runtime,
51
- installSurface: entry.installSurface,
52
- writesSharedSettings: entry.writesSharedSettings,
53
- finishPermissionWriter: entry.finishPermissionWriter,
85
+ installSurface: configIntent.installSurface,
86
+ writesSharedSettings: configIntent.writesSharedSettings,
87
+ finishPermissionWriter: configIntent.finishPermissionWriter,
88
+ hookEvents: desc['hookEvents'],
89
+ extendedHookEvents: Array.isArray(desc['extendedHookEvents']) ? desc['extendedHookEvents'] : [],
90
+ hooksSurface: desc['hooksSurface'] != null
91
+ ? desc['hooksSurface']
92
+ : ((runtime === 'opencode' || runtime === 'kilo') ? 'none' : 'settings-json'),
54
93
  };
55
94
  }
56
- module.exports = { resolveRuntimeConfigIntent, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
95
+ module.exports = { resolveRuntimeConfigIntent, resolveInstallPlan, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
@@ -15,12 +15,28 @@
15
15
  * cline — Skills-capable since v3.48.0 (#782). SKILL.md files live at
16
16
  * ~/.cline/skills/<skillName>/SKILL.md (same flat layout as cursor/codex).
17
17
  * .clinerules is also emitted (rules-based compatibility layer).
18
+ * kimi — Agent Skills are discovered from Kimi's generic user roots:
19
+ * ~/.config/agents/skills (recommended) then ~/.agents/skills,
20
+ * with Kimi selecting the first existing generic skills directory.
21
+ * ~/.kimi-code/skills is brand-specific and can be selected as a
22
+ * GSD write target with --config-dir or KIMI_CONFIG_DIR.
23
+ * trae — Targets Trae IDE (trae.ai), the Electron-based IDE — NOT
24
+ * trae-agent (github.com/bytedance/trae-agent), a Python CLI that
25
+ * uses trae_config.yaml, has no ~/.trae directory, and has no
26
+ * skills system. Both are ByteDance "Trae" products; they are
27
+ * entirely distinct. The global ~/.trae/skills/ path is
28
+ * community-soft-confirmed: docs.trae.ai/ide/skills documents the
29
+ * SKILL.md format and project-level .trae/skills/, but does NOT
30
+ * publish the global on-disk path; ~/.trae/skills/ rests on
31
+ * community evidence incl. Trae-AI/TRAE#2253. Best-effort only.
18
32
  */
19
33
  var __importDefault = (this && this.__importDefault) || function (mod) {
20
34
  return (mod && mod.__esModule) ? mod : { "default": mod };
21
35
  };
22
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
23
38
  exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
39
+ exports.resolveKimiGlobalDir = resolveKimiGlobalDir;
24
40
  exports.getGlobalConfigDir = getGlobalConfigDir;
25
41
  exports.getGlobalSkillsBase = getGlobalSkillsBase;
26
42
  exports.getGlobalSkillDir = getGlobalSkillDir;
@@ -38,26 +54,142 @@ function expandTilde(p) {
38
54
  return node_path_1.default.join(node_os_1.default.homedir(), p.slice(1));
39
55
  return p;
40
56
  }
57
+ /**
58
+ * Resolve a configHome descriptor to an absolute directory path.
59
+ *
60
+ * Implements the four descriptor kinds:
61
+ * - dot-home: env-override → path.join(home, name)
62
+ * - dot-home-nested: env-override → probed subdir of path.join(home, parent)
63
+ * - xdg: env[0] → env[1](dirname) → env[2](XDG subdir) → ~/.config/<name>
64
+ * - generic-agents-root:env[0] → first probe where probeExists exists → probe[0]
65
+ */
66
+ function resolveConfigHomeFromDescriptor(configHome, opts = {}) {
67
+ const env = opts.env ?? process.env;
68
+ const home = opts.home ?? node_os_1.default.homedir();
69
+ const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
70
+ switch (configHome.kind) {
71
+ case 'dot-home': {
72
+ // First env var that is set wins
73
+ for (const varName of configHome.env) {
74
+ const val = env[varName];
75
+ if (val)
76
+ return expandTilde(val);
77
+ }
78
+ return node_path_1.default.join(home, configHome.name);
79
+ }
80
+ case 'dot-home-nested': {
81
+ // env override
82
+ const nestedEnv0Val = env[configHome.env[0]];
83
+ if (configHome.env[0] && nestedEnv0Val) {
84
+ return expandTilde(nestedEnv0Val);
85
+ }
86
+ const base = node_path_1.default.join(home, configHome.parent);
87
+ if (configHome.probe && configHome.probe.length > 0) {
88
+ // probe each candidate under base; return first that exists
89
+ for (const candidate of configHome.probe) {
90
+ const resolved = node_path_1.default.join(base, candidate);
91
+ if (existsSyncFn(resolved))
92
+ return resolved;
93
+ }
94
+ // fallback: first probe candidate
95
+ return node_path_1.default.join(base, configHome.probe[0]);
96
+ }
97
+ // no probe (e.g. windsurf): always name under parent
98
+ return node_path_1.default.join(base, configHome.name);
99
+ }
100
+ case 'xdg': {
101
+ // env[0]: direct override dir
102
+ const xdgEnv0Val = env[configHome.env[0]];
103
+ if (configHome.env[0] && xdgEnv0Val) {
104
+ return expandTilde(xdgEnv0Val);
105
+ }
106
+ // env[1]: FILE path → dirname
107
+ const xdgEnv1Val = env[configHome.env[1]];
108
+ if (configHome.env[1] && xdgEnv1Val) {
109
+ return node_path_1.default.dirname(expandTilde(xdgEnv1Val));
110
+ }
111
+ // env[2]: XDG_CONFIG_HOME → subdir
112
+ const xdgEnv2Val = env[configHome.env[2]];
113
+ if (configHome.env[2] && xdgEnv2Val) {
114
+ return node_path_1.default.join(expandTilde(xdgEnv2Val), configHome.name);
115
+ }
116
+ return node_path_1.default.join(home, '.config', configHome.name);
117
+ }
118
+ case 'generic-agents-root': {
119
+ // env override
120
+ const garEnv0Val = env[configHome.env[0]];
121
+ if (configHome.env[0] && garEnv0Val) {
122
+ return expandTilde(garEnv0Val);
123
+ }
124
+ // probe each candidate; return first where probeExists subpath exists
125
+ for (const candidate of configHome.probe) {
126
+ const resolved = expandTildeWithHome(candidate, home);
127
+ if (existsSyncFn(node_path_1.default.join(resolved, configHome.probeExists))) {
128
+ return resolved;
129
+ }
130
+ }
131
+ // fallback: first probe candidate
132
+ return expandTildeWithHome(configHome.probe[0], home);
133
+ }
134
+ }
135
+ }
136
+ /**
137
+ * Expand ~ using an explicit home directory (for hermetic testing).
138
+ */
139
+ function expandTildeWithHome(p, home) {
140
+ if (!p)
141
+ return p;
142
+ if (p.startsWith('~/') || p === '~')
143
+ return node_path_1.default.join(home, p.slice(1));
144
+ return p;
145
+ }
41
146
  /**
42
147
  * Resolve Antigravity global config dir across 1.x and 2.x layouts.
148
+ *
149
+ * Thin wrapper delegating to resolveConfigHomeFromDescriptor with the
150
+ * antigravity descriptor shape. Preserved for external callers and tests.
43
151
  */
44
152
  function resolveAntigravityGlobalDir(opts = {}) {
45
153
  const env = opts.env ?? process.env;
46
154
  const home = opts.home ?? node_os_1.default.homedir();
47
155
  const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
48
- if (env['ANTIGRAVITY_CONFIG_DIR'])
49
- return expandTilde(env['ANTIGRAVITY_CONFIG_DIR']);
50
- const base = node_path_1.default.join(home, '.gemini');
51
- const candidates = [
52
- node_path_1.default.join(base, 'antigravity'),
53
- node_path_1.default.join(base, 'antigravity-ide'),
54
- node_path_1.default.join(base, 'antigravity-cli'),
55
- ];
56
- for (const candidate of candidates) {
57
- if (existsSyncFn(candidate))
58
- return candidate;
59
- }
60
- return node_path_1.default.join(base, 'antigravity');
156
+ return resolveConfigHomeFromDescriptor({
157
+ kind: 'dot-home-nested',
158
+ name: 'antigravity',
159
+ parent: '.gemini',
160
+ env: ['ANTIGRAVITY_CONFIG_DIR'],
161
+ probe: ['antigravity', 'antigravity-ide', 'antigravity-cli'],
162
+ }, { env, home, existsSync: existsSyncFn });
163
+ }
164
+ /**
165
+ * Resolve Kimi's generic user root using Kimi CLI's documented first-existing
166
+ * generic skills directory policy:
167
+ *
168
+ * 1. ~/.config/agents/skills (recommended)
169
+ * 2. ~/.agents/skills
170
+ *
171
+ * If neither generic skills directory exists yet, install to the recommended
172
+ * ~/.config/agents root so the generated skills become the first generic
173
+ * candidate Kimi discovers.
174
+ *
175
+ * KIMI_CONFIG_DIR is a GSD installer write-location override. It is not Kimi's
176
+ * upstream data-root variable, and arbitrary roots are discoverable by Kimi only
177
+ * when the user also configures Kimi --skills-dir or extra_skill_dirs.
178
+ *
179
+ * Thin wrapper delegating to resolveConfigHomeFromDescriptor with the
180
+ * kimi descriptor shape. Preserved for external callers and tests.
181
+ */
182
+ function resolveKimiGlobalDir(opts = {}) {
183
+ const env = opts.env ?? process.env;
184
+ const home = opts.home ?? node_os_1.default.homedir();
185
+ const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
186
+ return resolveConfigHomeFromDescriptor({
187
+ kind: 'generic-agents-root',
188
+ name: 'agents',
189
+ env: ['KIMI_CONFIG_DIR'],
190
+ probe: ['~/.config/agents', '~/.agents'],
191
+ probeExists: 'skills',
192
+ }, { env, home, existsSync: existsSyncFn });
61
193
  }
62
194
  /**
63
195
  * Return the global config base directory for the given runtime.
@@ -71,81 +203,25 @@ function resolveAntigravityGlobalDir(opts = {}) {
71
203
  function getGlobalConfigDir(runtime, explicitDir) {
72
204
  if (explicitDir)
73
205
  return expandTilde(explicitDir);
74
- const home = node_os_1.default.homedir();
75
- const env = process.env;
76
- switch (runtime) {
77
- // ── Claude Code ──────────────────────────────────────────────────────────
78
- case 'claude':
79
- return env['CLAUDE_CONFIG_DIR'] ? expandTilde(env['CLAUDE_CONFIG_DIR']) : node_path_1.default.join(home, '.claude');
80
- // ── Cursor ───────────────────────────────────────────────────────────────
81
- case 'cursor':
82
- return env['CURSOR_CONFIG_DIR'] ? expandTilde(env['CURSOR_CONFIG_DIR']) : node_path_1.default.join(home, '.cursor');
83
- // ── Gemini CLI ───────────────────────────────────────────────────────────
84
- case 'gemini':
85
- return env['GEMINI_CONFIG_DIR'] ? expandTilde(env['GEMINI_CONFIG_DIR']) : node_path_1.default.join(home, '.gemini');
86
- // ── Codex ────────────────────────────────────────────────────────────────
87
- case 'codex':
88
- return env['CODEX_HOME'] ? expandTilde(env['CODEX_HOME']) : node_path_1.default.join(home, '.codex');
89
- // ── Grok Build ───────────────────────────────────────────────────────────
90
- case 'grok':
91
- return env['GROK_AGENTS_HOME'] ? expandTilde(env['GROK_AGENTS_HOME']) : node_path_1.default.join(home, '.agents');
92
- // ── Copilot (VS Code) ────────────────────────────────────────────────────
93
- case 'copilot':
94
- if (env['COPILOT_CONFIG_DIR'])
95
- return expandTilde(env['COPILOT_CONFIG_DIR']);
96
- if (env['COPILOT_HOME'])
97
- return expandTilde(env['COPILOT_HOME']);
98
- return node_path_1.default.join(home, '.copilot');
99
- // ── Antigravity ──────────────────────────────────────────────────────────
100
- case 'antigravity':
101
- return resolveAntigravityGlobalDir({ env, home });
102
- // ── Windsurf ─────────────────────────────────────────────────────────────
103
- case 'windsurf':
104
- return env['WINDSURF_CONFIG_DIR']
105
- ? expandTilde(env['WINDSURF_CONFIG_DIR'])
106
- : node_path_1.default.join(home, '.codeium', 'windsurf');
107
- // ── Augment ──────────────────────────────────────────────────────────────
108
- case 'augment':
109
- return env['AUGMENT_CONFIG_DIR'] ? expandTilde(env['AUGMENT_CONFIG_DIR']) : node_path_1.default.join(home, '.augment');
110
- // ── Trae ─────────────────────────────────────────────────────────────────
111
- case 'trae':
112
- return env['TRAE_CONFIG_DIR'] ? expandTilde(env['TRAE_CONFIG_DIR']) : node_path_1.default.join(home, '.trae');
113
- // ── Qwen Code ────────────────────────────────────────────────────────────
114
- case 'qwen':
115
- return env['QWEN_CONFIG_DIR'] ? expandTilde(env['QWEN_CONFIG_DIR']) : node_path_1.default.join(home, '.qwen');
116
- // ── Hermes Agent ─────────────────────────────────────────────────────────
117
- case 'hermes':
118
- return env['HERMES_HOME'] ? expandTilde(env['HERMES_HOME']) : node_path_1.default.join(home, '.hermes');
119
- // ── CodeBuddy ────────────────────────────────────────────────────────────
120
- case 'codebuddy':
121
- return env['CODEBUDDY_CONFIG_DIR'] ? expandTilde(env['CODEBUDDY_CONFIG_DIR']) : node_path_1.default.join(home, '.codebuddy');
122
- // ── Cline ────────────────────────────────────────────────────────────────
123
- case 'cline':
124
- return env['CLINE_CONFIG_DIR'] ? expandTilde(env['CLINE_CONFIG_DIR']) : node_path_1.default.join(home, '.cline');
125
- // ── OpenCode (XDG) ───────────────────────────────────────────────────────
126
- case 'opencode': {
127
- if (env['OPENCODE_CONFIG_DIR'])
128
- return expandTilde(env['OPENCODE_CONFIG_DIR']);
129
- if (env['OPENCODE_CONFIG'])
130
- return node_path_1.default.dirname(expandTilde(env['OPENCODE_CONFIG']));
131
- if (env['XDG_CONFIG_HOME'])
132
- return node_path_1.default.join(expandTilde(env['XDG_CONFIG_HOME']), 'opencode');
133
- return node_path_1.default.join(home, '.config', 'opencode');
134
- }
135
- // ── Kilo (XDG) ───────────────────────────────────────────────────────────
136
- case 'kilo': {
137
- if (env['KILO_CONFIG_DIR'])
138
- return expandTilde(env['KILO_CONFIG_DIR']);
139
- if (env['KILO_CONFIG'])
140
- return node_path_1.default.dirname(expandTilde(env['KILO_CONFIG']));
141
- if (env['XDG_CONFIG_HOME'])
142
- return node_path_1.default.join(expandTilde(env['XDG_CONFIG_HOME']), 'kilo');
143
- return node_path_1.default.join(home, '.config', 'kilo');
144
- }
145
- // ── Default (Claude fallback) ─────────────────────────────────────────────
146
- default:
147
- return env['CLAUDE_CONFIG_DIR'] ? expandTilde(env['CLAUDE_CONFIG_DIR']) : node_path_1.default.join(home, '.claude');
206
+ // ── Grok: not in the registry — hardcoded branch ─────────────────────────
207
+ if (runtime === 'grok') {
208
+ const env = process.env;
209
+ return env['GROK_AGENTS_HOME'] ? expandTilde(env['GROK_AGENTS_HOME']) : node_path_1.default.join(node_os_1.default.homedir(), '.agents');
148
210
  }
211
+ // ── Descriptor-driven: look up in capability-registry ────────────────────
212
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
213
+ const { runtimes } = require('./capability-registry.cjs');
214
+ const runtimeEntry = runtimes[runtime];
215
+ if (runtimeEntry?.runtime?.configHome) {
216
+ return resolveConfigHomeFromDescriptor(runtimeEntry.runtime.configHome, {
217
+ env: process.env,
218
+ home: node_os_1.default.homedir(),
219
+ existsSync: node_fs_1.default.existsSync,
220
+ });
221
+ }
222
+ // ── Default (unknown runtime → Claude fallback) ───────────────────────────
223
+ const env = process.env;
224
+ return env['CLAUDE_CONFIG_DIR'] ? expandTilde(env['CLAUDE_CONFIG_DIR']) : node_path_1.default.join(node_os_1.default.homedir(), '.claude');
149
225
  }
150
226
  /**
151
227
  * Return the global skills base directory for the given runtime.