@opengsd/gsd-core 1.5.0-rc.1 → 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 (149) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/gsd-code-fixer.md +3 -2
  3. package/agents/gsd-debug-session-manager.md +2 -1
  4. package/agents/gsd-debugger.md +4 -3
  5. package/agents/gsd-executor.md +16 -15
  6. package/agents/gsd-intel-updater.md +38 -41
  7. package/agents/gsd-phase-researcher.md +8 -8
  8. package/agents/gsd-plan-checker.md +12 -11
  9. package/agents/gsd-planner.md +21 -181
  10. package/agents/gsd-project-researcher.md +5 -4
  11. package/agents/gsd-research-synthesizer.md +2 -1
  12. package/agents/gsd-ui-researcher.md +2 -1
  13. package/agents/gsd-verifier.md +9 -8
  14. package/bin/install.js +205 -1402
  15. package/gemini-extension.json +1 -1
  16. package/gsd-core/bin/gsd_run +20 -0
  17. package/gsd-core/bin/lib/capability-registry.cjs +1820 -3
  18. package/gsd-core/bin/lib/check-command-router.cjs +133 -2
  19. package/gsd-core/bin/lib/core.cjs +51 -1
  20. package/gsd-core/bin/lib/edge-probe.cjs +173 -0
  21. package/gsd-core/bin/lib/fallow-runner.cjs +63 -25
  22. package/gsd-core/bin/lib/intel.cjs +3 -3
  23. package/gsd-core/bin/lib/io.cjs +61 -6
  24. package/gsd-core/bin/lib/phase-command-router.cjs +20 -0
  25. package/gsd-core/bin/lib/phase.cjs +17 -0
  26. package/gsd-core/bin/lib/probe-core.cjs +257 -0
  27. package/gsd-core/bin/lib/roadmap.cjs +40 -0
  28. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +51 -141
  29. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +67 -29
  30. package/gsd-core/bin/lib/runtime-homes.cjs +137 -101
  31. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1439 -0
  32. package/gsd-core/bin/lib/runtime-name-policy.cjs +1 -1
  33. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  34. package/gsd-core/bin/lib/state-document.cjs +8 -0
  35. package/gsd-core/bin/lib/uat-predicate.cjs +329 -0
  36. package/gsd-core/bin/lib/update-context.cjs +4 -1
  37. package/gsd-core/bin/lib/verify.cjs +103 -0
  38. package/gsd-core/bin/lib/worktree-base-ref.cjs +33 -8
  39. package/gsd-core/bin/shared/model-catalog.json +11 -6
  40. package/gsd-core/bin/shared/runtime-aliases.manifest.json +2 -1
  41. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
  42. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
  43. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
  44. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
  45. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
  46. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
  47. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
  48. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
  49. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
  50. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
  51. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
  52. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
  53. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
  54. package/gsd-core/references/edge-probe.md +261 -0
  55. package/gsd-core/references/planner-antipatterns.md +41 -0
  56. package/gsd-core/references/planner-guidance.md +186 -0
  57. package/gsd-core/templates/spec.md +12 -0
  58. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  59. package/gsd-core/workflows/add-backlog.md +1 -1
  60. package/gsd-core/workflows/add-phase.md +1 -1
  61. package/gsd-core/workflows/add-tests.md +1 -1
  62. package/gsd-core/workflows/add-todo.md +1 -1
  63. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  64. package/gsd-core/workflows/audit-fix.md +1 -1
  65. package/gsd-core/workflows/audit-milestone.md +1 -1
  66. package/gsd-core/workflows/audit-uat.md +1 -1
  67. package/gsd-core/workflows/autonomous.md +26 -37
  68. package/gsd-core/workflows/check-todos.md +1 -1
  69. package/gsd-core/workflows/cleanup.md +1 -1
  70. package/gsd-core/workflows/code-review-fix.md +1 -1
  71. package/gsd-core/workflows/code-review.md +51 -16
  72. package/gsd-core/workflows/complete-milestone.md +1 -1
  73. package/gsd-core/workflows/debug.md +1 -1
  74. package/gsd-core/workflows/diagnose-issues.md +1 -1
  75. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  76. package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
  77. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  78. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  79. package/gsd-core/workflows/discuss-phase.md +1 -1
  80. package/gsd-core/workflows/do.md +1 -1
  81. package/gsd-core/workflows/docs-update.md +1 -1
  82. package/gsd-core/workflows/edit-phase.md +1 -1
  83. package/gsd-core/workflows/eval-review.md +1 -1
  84. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  85. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  86. package/gsd-core/workflows/execute-phase.md +1 -1
  87. package/gsd-core/workflows/execute-plan.md +1 -1
  88. package/gsd-core/workflows/explore.md +1 -1
  89. package/gsd-core/workflows/extract-learnings.md +1 -1
  90. package/gsd-core/workflows/forensics.md +1 -1
  91. package/gsd-core/workflows/graduation.md +1 -1
  92. package/gsd-core/workflows/health.md +1 -1
  93. package/gsd-core/workflows/import.md +1 -1
  94. package/gsd-core/workflows/ingest-docs.md +1 -1
  95. package/gsd-core/workflows/insert-phase.md +1 -1
  96. package/gsd-core/workflows/list-workspaces.md +1 -1
  97. package/gsd-core/workflows/manager.md +1 -1
  98. package/gsd-core/workflows/map-codebase.md +1 -1
  99. package/gsd-core/workflows/milestone-summary.md +1 -1
  100. package/gsd-core/workflows/mvp-phase.md +1 -1
  101. package/gsd-core/workflows/new-milestone.md +9 -1
  102. package/gsd-core/workflows/new-project.md +9 -1
  103. package/gsd-core/workflows/new-workspace.md +1 -1
  104. package/gsd-core/workflows/next.md +1 -1
  105. package/gsd-core/workflows/pause-work.md +1 -1
  106. package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
  107. package/gsd-core/workflows/plan-phase.md +39 -27
  108. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  109. package/gsd-core/workflows/plant-seed.md +1 -1
  110. package/gsd-core/workflows/profile-user.md +1 -1
  111. package/gsd-core/workflows/progress.md +1 -1
  112. package/gsd-core/workflows/quick.md +1 -1
  113. package/gsd-core/workflows/remove-phase.md +1 -1
  114. package/gsd-core/workflows/remove-workspace.md +1 -1
  115. package/gsd-core/workflows/resume-project.md +1 -1
  116. package/gsd-core/workflows/review.md +1 -1
  117. package/gsd-core/workflows/scan.md +1 -1
  118. package/gsd-core/workflows/secure-phase.md +1 -1
  119. package/gsd-core/workflows/settings-advanced.md +18 -14
  120. package/gsd-core/workflows/settings-integrations.md +1 -1
  121. package/gsd-core/workflows/settings.md +2 -2
  122. package/gsd-core/workflows/ship.md +1 -1
  123. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  124. package/gsd-core/workflows/sketch.md +1 -1
  125. package/gsd-core/workflows/spec-phase.md +130 -1
  126. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  127. package/gsd-core/workflows/spike.md +1 -1
  128. package/gsd-core/workflows/stats.md +1 -1
  129. package/gsd-core/workflows/thread.md +1 -1
  130. package/gsd-core/workflows/transition.md +1 -1
  131. package/gsd-core/workflows/ui-phase.md +1 -1
  132. package/gsd-core/workflows/ui-review.md +1 -1
  133. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  134. package/gsd-core/workflows/update.md +2 -2
  135. package/gsd-core/workflows/validate-phase.md +1 -1
  136. package/gsd-core/workflows/verify-phase.md +1 -1
  137. package/gsd-core/workflows/verify-work.md +1 -1
  138. package/package.json +6 -3
  139. package/scripts/gen-capability-registry.cjs +498 -13
  140. package/scripts/lib/allowlist-ratchet.cjs +101 -1
  141. package/scripts/lint-test-file-count.allowlist.json +15 -2
  142. package/scripts/lint-windows-test-portability.cjs +178 -0
  143. package/scripts/research-profiles.cjs +10 -10
  144. package/scripts/run-tests.cjs +54 -13
  145. package/scripts/sync-next-version.cjs +133 -0
  146. package/scripts/sync-runtime-launcher.cjs +21 -5
  147. package/scripts/update-size-baseline.cjs +68 -0
  148. package/scripts/workflow-policy.cjs +42 -9
  149. package/scripts/workflow-size.cjs +90 -0
@@ -1,30 +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
- kimi: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
22
- });
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');
23
30
  // ---------------------------------------------------------------------------
24
31
  // Exports
25
32
  // ---------------------------------------------------------------------------
26
33
  /** The complete set of 16 supported runtimes for config-adapter dispatch. */
27
- const ALLOWED_CONFIG_RUNTIMES = new Set(Object.keys(REGISTRY));
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));
28
37
  /** All valid installSurface values. */
29
38
  const INSTALL_SURFACES = Object.freeze([
30
39
  'settings-json',
@@ -43,15 +52,44 @@ const INSTALL_SURFACES = Object.freeze([
43
52
  * @throws {TypeError} if runtime is not a known supported runtime.
44
53
  */
45
54
  function resolveRuntimeConfigIntent(runtime) {
46
- if (!Object.hasOwn(REGISTRY, runtime)) {
55
+ const entry = runtimes[runtime]?.runtime;
56
+ if (!entry)
47
57
  throw new TypeError(`Unknown runtime for config adapter: ${runtime}`);
48
- }
49
- 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);
50
83
  return {
51
84
  runtime,
52
- installSurface: entry.installSurface,
53
- writesSharedSettings: entry.writesSharedSettings,
54
- 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'),
55
93
  };
56
94
  }
57
- module.exports = { resolveRuntimeConfigIntent, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
95
+ module.exports = { resolveRuntimeConfigIntent, resolveInstallPlan, ALLOWED_CONFIG_RUNTIMES, INSTALL_SURFACES };
@@ -20,11 +20,21 @@
20
20
  * with Kimi selecting the first existing generic skills directory.
21
21
  * ~/.kimi-code/skills is brand-specific and can be selected as a
22
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.
23
32
  */
24
33
  var __importDefault = (this && this.__importDefault) || function (mod) {
25
34
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
35
  };
27
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
28
38
  exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
29
39
  exports.resolveKimiGlobalDir = resolveKimiGlobalDir;
30
40
  exports.getGlobalConfigDir = getGlobalConfigDir;
@@ -44,26 +54,112 @@ function expandTilde(p) {
44
54
  return node_path_1.default.join(node_os_1.default.homedir(), p.slice(1));
45
55
  return p;
46
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
+ }
47
146
  /**
48
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.
49
151
  */
50
152
  function resolveAntigravityGlobalDir(opts = {}) {
51
153
  const env = opts.env ?? process.env;
52
154
  const home = opts.home ?? node_os_1.default.homedir();
53
155
  const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
54
- if (env['ANTIGRAVITY_CONFIG_DIR'])
55
- return expandTilde(env['ANTIGRAVITY_CONFIG_DIR']);
56
- const base = node_path_1.default.join(home, '.gemini');
57
- const candidates = [
58
- node_path_1.default.join(base, 'antigravity'),
59
- node_path_1.default.join(base, 'antigravity-ide'),
60
- node_path_1.default.join(base, 'antigravity-cli'),
61
- ];
62
- for (const candidate of candidates) {
63
- if (existsSyncFn(candidate))
64
- return candidate;
65
- }
66
- 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 });
67
163
  }
68
164
  /**
69
165
  * Resolve Kimi's generic user root using Kimi CLI's documented first-existing
@@ -79,21 +175,21 @@ function resolveAntigravityGlobalDir(opts = {}) {
79
175
  * KIMI_CONFIG_DIR is a GSD installer write-location override. It is not Kimi's
80
176
  * upstream data-root variable, and arbitrary roots are discoverable by Kimi only
81
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.
82
181
  */
83
182
  function resolveKimiGlobalDir(opts = {}) {
84
183
  const env = opts.env ?? process.env;
85
184
  const home = opts.home ?? node_os_1.default.homedir();
86
185
  const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
87
- if (env['KIMI_CONFIG_DIR'])
88
- return expandTilde(env['KIMI_CONFIG_DIR']);
89
- const recommendedRoot = node_path_1.default.join(home, '.config', 'agents');
90
- const fallbackRoot = node_path_1.default.join(home, '.agents');
91
- const candidates = [recommendedRoot, fallbackRoot];
92
- for (const candidate of candidates) {
93
- if (existsSyncFn(node_path_1.default.join(candidate, 'skills')))
94
- return candidate;
95
- }
96
- return recommendedRoot;
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 });
97
193
  }
98
194
  /**
99
195
  * Return the global config base directory for the given runtime.
@@ -107,85 +203,25 @@ function resolveKimiGlobalDir(opts = {}) {
107
203
  function getGlobalConfigDir(runtime, explicitDir) {
108
204
  if (explicitDir)
109
205
  return expandTilde(explicitDir);
110
- const home = node_os_1.default.homedir();
111
- const env = process.env;
112
- switch (runtime) {
113
- // ── Claude Code ──────────────────────────────────────────────────────────
114
- case 'claude':
115
- return env['CLAUDE_CONFIG_DIR'] ? expandTilde(env['CLAUDE_CONFIG_DIR']) : node_path_1.default.join(home, '.claude');
116
- // ── Cursor ───────────────────────────────────────────────────────────────
117
- case 'cursor':
118
- return env['CURSOR_CONFIG_DIR'] ? expandTilde(env['CURSOR_CONFIG_DIR']) : node_path_1.default.join(home, '.cursor');
119
- // ── Gemini CLI ───────────────────────────────────────────────────────────
120
- case 'gemini':
121
- return env['GEMINI_CONFIG_DIR'] ? expandTilde(env['GEMINI_CONFIG_DIR']) : node_path_1.default.join(home, '.gemini');
122
- // ── Codex ────────────────────────────────────────────────────────────────
123
- case 'codex':
124
- return env['CODEX_HOME'] ? expandTilde(env['CODEX_HOME']) : node_path_1.default.join(home, '.codex');
125
- // ── Grok Build ───────────────────────────────────────────────────────────
126
- case 'grok':
127
- return env['GROK_AGENTS_HOME'] ? expandTilde(env['GROK_AGENTS_HOME']) : node_path_1.default.join(home, '.agents');
128
- // ── Copilot (VS Code) ────────────────────────────────────────────────────
129
- case 'copilot':
130
- if (env['COPILOT_CONFIG_DIR'])
131
- return expandTilde(env['COPILOT_CONFIG_DIR']);
132
- if (env['COPILOT_HOME'])
133
- return expandTilde(env['COPILOT_HOME']);
134
- return node_path_1.default.join(home, '.copilot');
135
- // ── Antigravity ──────────────────────────────────────────────────────────
136
- case 'antigravity':
137
- return resolveAntigravityGlobalDir({ env, home });
138
- // ── Windsurf ─────────────────────────────────────────────────────────────
139
- case 'windsurf':
140
- return env['WINDSURF_CONFIG_DIR']
141
- ? expandTilde(env['WINDSURF_CONFIG_DIR'])
142
- : node_path_1.default.join(home, '.codeium', 'windsurf');
143
- // ── Augment ──────────────────────────────────────────────────────────────
144
- case 'augment':
145
- return env['AUGMENT_CONFIG_DIR'] ? expandTilde(env['AUGMENT_CONFIG_DIR']) : node_path_1.default.join(home, '.augment');
146
- // ── Trae ─────────────────────────────────────────────────────────────────
147
- case 'trae':
148
- return env['TRAE_CONFIG_DIR'] ? expandTilde(env['TRAE_CONFIG_DIR']) : node_path_1.default.join(home, '.trae');
149
- // ── Qwen Code ────────────────────────────────────────────────────────────
150
- case 'qwen':
151
- return env['QWEN_CONFIG_DIR'] ? expandTilde(env['QWEN_CONFIG_DIR']) : node_path_1.default.join(home, '.qwen');
152
- // ── Hermes Agent ─────────────────────────────────────────────────────────
153
- case 'hermes':
154
- return env['HERMES_HOME'] ? expandTilde(env['HERMES_HOME']) : node_path_1.default.join(home, '.hermes');
155
- // ── CodeBuddy ────────────────────────────────────────────────────────────
156
- case 'codebuddy':
157
- return env['CODEBUDDY_CONFIG_DIR'] ? expandTilde(env['CODEBUDDY_CONFIG_DIR']) : node_path_1.default.join(home, '.codebuddy');
158
- // ── Cline ────────────────────────────────────────────────────────────────
159
- case 'cline':
160
- return env['CLINE_CONFIG_DIR'] ? expandTilde(env['CLINE_CONFIG_DIR']) : node_path_1.default.join(home, '.cline');
161
- // ── Kimi CLI (generic agents user root) ────────────────────────────────
162
- case 'kimi': {
163
- return resolveKimiGlobalDir({ env, home });
164
- }
165
- // ── OpenCode (XDG) ───────────────────────────────────────────────────────
166
- case 'opencode': {
167
- if (env['OPENCODE_CONFIG_DIR'])
168
- return expandTilde(env['OPENCODE_CONFIG_DIR']);
169
- if (env['OPENCODE_CONFIG'])
170
- return node_path_1.default.dirname(expandTilde(env['OPENCODE_CONFIG']));
171
- if (env['XDG_CONFIG_HOME'])
172
- return node_path_1.default.join(expandTilde(env['XDG_CONFIG_HOME']), 'opencode');
173
- return node_path_1.default.join(home, '.config', 'opencode');
174
- }
175
- // ── Kilo (XDG) ───────────────────────────────────────────────────────────
176
- case 'kilo': {
177
- if (env['KILO_CONFIG_DIR'])
178
- return expandTilde(env['KILO_CONFIG_DIR']);
179
- if (env['KILO_CONFIG'])
180
- return node_path_1.default.dirname(expandTilde(env['KILO_CONFIG']));
181
- if (env['XDG_CONFIG_HOME'])
182
- return node_path_1.default.join(expandTilde(env['XDG_CONFIG_HOME']), 'kilo');
183
- return node_path_1.default.join(home, '.config', 'kilo');
184
- }
185
- // ── Default (Claude fallback) ─────────────────────────────────────────────
186
- default:
187
- 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');
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
+ });
188
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');
189
225
  }
190
226
  /**
191
227
  * Return the global skills base directory for the given runtime.