gitnexus 1.6.11-rc.2 → 1.6.11-rc.21

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 (158) hide show
  1. package/README.md +43 -1
  2. package/dist/_shared/impact-risk.d.ts +37 -0
  3. package/dist/_shared/impact-risk.d.ts.map +1 -0
  4. package/dist/_shared/impact-risk.js +92 -0
  5. package/dist/_shared/impact-risk.js.map +1 -0
  6. package/dist/_shared/index.d.ts +2 -0
  7. package/dist/_shared/index.d.ts.map +1 -1
  8. package/dist/_shared/index.js +2 -0
  9. package/dist/_shared/index.js.map +1 -1
  10. package/dist/cli/ai-context.js +4 -4
  11. package/dist/cli/analyze-config.d.ts +2 -0
  12. package/dist/cli/analyze-config.js +16 -0
  13. package/dist/cli/analyze-options.d.ts +4 -0
  14. package/dist/cli/analyze.d.ts +17 -0
  15. package/dist/cli/analyze.js +43 -7
  16. package/dist/cli/eval-server.js +22 -5
  17. package/dist/cli/group.js +7 -1
  18. package/dist/cli/help-i18n.js +2 -0
  19. package/dist/cli/i18n/en.d.ts +12 -2
  20. package/dist/cli/i18n/en.js +12 -2
  21. package/dist/cli/i18n/resources.d.ts +22 -2
  22. package/dist/cli/i18n/zh-CN.d.ts +10 -0
  23. package/dist/cli/i18n/zh-CN.js +12 -2
  24. package/dist/cli/index.js +11 -4
  25. package/dist/cli/status.js +91 -6
  26. package/dist/cli/watch-queue.d.ts +41 -0
  27. package/dist/cli/watch-queue.js +184 -0
  28. package/dist/cli/watch.d.ts +20 -0
  29. package/dist/cli/watch.js +372 -0
  30. package/dist/cli/wiki.js +15 -2
  31. package/dist/config/ignore-service.d.ts +11 -0
  32. package/dist/config/ignore-service.js +41 -4
  33. package/dist/config/repo-control-file.d.ts +3 -0
  34. package/dist/config/repo-control-file.js +115 -0
  35. package/dist/core/group/config-parser.js +20 -2
  36. package/dist/core/group/cross-impact.d.ts +2 -1
  37. package/dist/core/group/cross-impact.js +33 -2
  38. package/dist/core/group/extractors/fs-utils.d.ts +2 -0
  39. package/dist/core/group/extractors/fs-utils.js +86 -0
  40. package/dist/core/group/extractors/graphql-extractor.d.ts +17 -0
  41. package/dist/core/group/extractors/graphql-extractor.js +652 -0
  42. package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
  43. package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
  44. package/dist/core/group/extractors/manifest-extractor.js +1 -1
  45. package/dist/core/group/matching.js +8 -1
  46. package/dist/core/group/service.js +5 -1
  47. package/dist/core/group/storage.js +1 -0
  48. package/dist/core/group/sync.d.ts +3 -1
  49. package/dist/core/group/sync.js +43 -5
  50. package/dist/core/group/types.d.ts +19 -5
  51. package/dist/core/incremental/derived-writeback.d.ts +36 -0
  52. package/dist/core/incremental/derived-writeback.js +68 -0
  53. package/dist/core/incremental/subgraph-extract.d.ts +6 -4
  54. package/dist/core/incremental/subgraph-extract.js +7 -5
  55. package/dist/core/index-content-drift.d.ts +54 -0
  56. package/dist/core/index-content-drift.js +127 -0
  57. package/dist/core/ingestion/filesystem-walker.d.ts +15 -5
  58. package/dist/core/ingestion/filesystem-walker.js +20 -3
  59. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
  60. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
  61. package/dist/core/ingestion/language-provider.d.ts +43 -0
  62. package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
  63. package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
  64. package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
  65. package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
  66. package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
  67. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +4 -0
  68. package/dist/core/ingestion/languages/java/capture-side-channel.js +16 -0
  69. package/dist/core/ingestion/languages/java/captures.js +14 -1
  70. package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
  71. package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
  72. package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
  73. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
  74. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
  75. package/dist/core/ingestion/languages/java.js +2 -0
  76. package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
  77. package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
  78. package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
  79. package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
  80. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +5 -0
  81. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +16 -0
  82. package/dist/core/ingestion/languages/kotlin/captures.js +14 -1
  83. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
  84. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
  85. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +2 -0
  86. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
  87. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
  88. package/dist/core/ingestion/languages/kotlin.js +2 -0
  89. package/dist/core/ingestion/pipeline-phases/di.js +47 -14
  90. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +3 -1
  91. package/dist/core/ingestion/pipeline-phases/parse-impl.js +57 -86
  92. package/dist/core/ingestion/pipeline-phases/parse.d.ts +2 -0
  93. package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
  94. package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
  95. package/dist/core/ingestion/pipeline-phases/scan.js +25 -13
  96. package/dist/core/ingestion/pipeline.d.ts +6 -0
  97. package/dist/core/ingestion/pipeline.js +44 -12
  98. package/dist/core/ingestion/scope-extractor.js +1 -0
  99. package/dist/core/ingestion/utils/symbol-labels.d.ts +2 -2
  100. package/dist/core/ingestion/utils/symbol-labels.js +2 -2
  101. package/dist/core/ingestion/workers/parse-worker.js +28 -2
  102. package/dist/core/lbug/lbug-adapter.d.ts +59 -0
  103. package/dist/core/lbug/lbug-adapter.js +154 -1
  104. package/dist/core/run-analyze.d.ts +17 -0
  105. package/dist/core/run-analyze.js +231 -23
  106. package/dist/core/search/fts-indexes.d.ts +19 -1
  107. package/dist/core/search/fts-indexes.js +28 -1
  108. package/dist/core/wiki/generator.js +8 -0
  109. package/dist/core/wiki/grok-client.d.ts +21 -0
  110. package/dist/core/wiki/grok-client.js +287 -0
  111. package/dist/core/wiki/llm-client.d.ts +1 -1
  112. package/dist/core/wiki/llm-client.js +5 -2
  113. package/dist/core/wiki/local-cli-client.d.ts +11 -0
  114. package/dist/core/wiki/local-cli-client.js +22 -9
  115. package/dist/mcp/local/local-backend.d.ts +24 -7
  116. package/dist/mcp/local/local-backend.js +190 -81
  117. package/dist/mcp/local/pdg-impact.d.ts +8 -4
  118. package/dist/mcp/local/pdg-impact.js +7 -2
  119. package/dist/mcp/repository-policy.d.ts +5 -1
  120. package/dist/mcp/repository-policy.js +48 -4
  121. package/dist/mcp/resources.js +2 -1
  122. package/dist/mcp/server.js +6 -5
  123. package/dist/mcp/tools.js +31 -19
  124. package/dist/server/api.js +23 -64
  125. package/dist/server/grep-params.d.ts +18 -0
  126. package/dist/server/grep-params.js +83 -0
  127. package/dist/server/grep-scan.d.ts +23 -0
  128. package/dist/server/grep-scan.js +107 -0
  129. package/dist/server/grep-worker.d.ts +1 -0
  130. package/dist/server/grep-worker.js +12 -0
  131. package/dist/server/mcp-http.d.ts +8 -0
  132. package/dist/server/mcp-http.js +16 -1
  133. package/dist/storage/file-hash.d.ts +5 -0
  134. package/dist/storage/file-hash.js +16 -6
  135. package/dist/storage/fs-atomic.d.ts +24 -0
  136. package/dist/storage/fs-atomic.js +86 -2
  137. package/dist/storage/git.d.ts +19 -8
  138. package/dist/storage/git.js +83 -24
  139. package/dist/storage/gitnexus-managed-paths.d.ts +36 -0
  140. package/dist/storage/gitnexus-managed-paths.js +46 -0
  141. package/dist/storage/parse-cache.d.ts +22 -4
  142. package/dist/storage/parse-cache.js +106 -29
  143. package/dist/storage/parsedfile-store.d.ts +34 -60
  144. package/dist/storage/parsedfile-store.js +177 -171
  145. package/dist/storage/repo-manager.d.ts +11 -1
  146. package/dist/storage/repo-manager.js +23 -2
  147. package/dist/storage/repo-meta.d.ts +12 -0
  148. package/dist/storage/v8-sidecar.d.ts +48 -0
  149. package/dist/storage/v8-sidecar.js +347 -0
  150. package/dist/types/pipeline.d.ts +14 -0
  151. package/package.json +4 -1
  152. package/scripts/cross-platform-shard.ts +4 -2
  153. package/scripts/cross-platform-tests.ts +7 -1
  154. package/skills/gitnexus-cli.md +11 -3
  155. package/skills/gitnexus-impact-analysis.md +9 -0
  156. package/web/assets/{agent-Dr4l5EOp.js → agent-CFqT4hjR.js} +108 -104
  157. package/web/assets/{index-2zdvEdzg.js → index-BMIniRtX.js} +3 -3
  158. package/web/index.html +1 -1
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Grok Build CLI client for wiki generation.
3
+ *
4
+ * Uses headless `grok --prompt-file` so large wiki prompts are not placed on
5
+ * argv or stdin (Grok does not read stdin as the prompt).
6
+ */
7
+ import { spawn, execFileSync } from 'child_process';
8
+ import { StringDecoder } from 'string_decoder';
9
+ import fs from 'fs/promises';
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import { resolveWindowsCliCommand } from './local-cli-client.js';
13
+ import { logger } from '../logger.js';
14
+ // Verified live: an empty --tools allowlist does NOT disable the shell tool
15
+ // (`run_terminal_cmd`) — Grok still ran `find` across the user's entire home
16
+ // directory looking for project context, taking 10+ minutes per call and
17
+ // then hitting --max-turns anyway. A denylist naming the tool explicitly is
18
+ // what actually blocks it; internal tool IDs, not the CLI-facing names
19
+ // (shell is `run_terminal_cmd`, not `bash`).
20
+ const GROK_DISALLOWED_TOOLS = 'run_terminal_cmd,search_replace,web_search,web_fetch,spawn_subagent';
21
+ // Defense in depth beyond the tool denylist: a kernel-enforced (Landlock/
22
+ // Seatbelt) sandbox so reads/writes stay confined to --cwd (our empty temp
23
+ // dir) + system paths even if a future tool slips past the denylist.
24
+ const GROK_SANDBOX_PROFILE = 'strict';
25
+ // Verified live with the denylist + sandbox above: turn counts vary run to
26
+ // run (observed 3-10 across identical prompts, including the large overview
27
+ // prompt that aggregates every module's summary). 15 gives real headroom
28
+ // over that variance without leaving a runaway session effectively uncapped.
29
+ const GROK_MAX_TURNS = '15';
30
+ let cachedGrokCommand;
31
+ function isVerbose() {
32
+ return process.env.GITNEXUS_VERBOSE === '1';
33
+ }
34
+ function verboseLog(...args) {
35
+ if (isVerbose()) {
36
+ logger.info({ args }, '[grok-cli]');
37
+ }
38
+ }
39
+ function killChildTree(child) {
40
+ if (process.platform === 'win32' && child.pid !== undefined) {
41
+ try {
42
+ execFileSync('taskkill', ['/T', '/F', '/PID', String(child.pid)], {
43
+ stdio: 'ignore',
44
+ windowsHide: true,
45
+ });
46
+ return;
47
+ }
48
+ catch {
49
+ // Process may have already exited — fall through to child.kill()
50
+ }
51
+ }
52
+ child.kill();
53
+ }
54
+ /** Returns `'grok'` when the CLI is on PATH, else null. Cached. */
55
+ export function detectGrokCLI() {
56
+ if (cachedGrokCommand !== undefined)
57
+ return cachedGrokCommand?.displayName ?? null;
58
+ const resolved = resolveWindowsCliCommand('grok');
59
+ try {
60
+ execFileSync(resolved.command, [...resolved.argsPrefix, '--version'], {
61
+ stdio: 'ignore',
62
+ windowsHide: true,
63
+ });
64
+ cachedGrokCommand = resolved;
65
+ }
66
+ catch (err) {
67
+ const isNotFound = err instanceof Error && 'code' in err && err.code === 'ENOENT';
68
+ if (!isNotFound && err instanceof Error) {
69
+ logger.warn(`grok CLI found but --version failed (exit ${err.status ?? '?'}). ` +
70
+ 'Ensure it is authenticated: run `grok --version` manually.');
71
+ }
72
+ cachedGrokCommand = null;
73
+ }
74
+ return cachedGrokCommand?.displayName ?? null;
75
+ }
76
+ function getDetectedGrokCommand() {
77
+ detectGrokCLI();
78
+ return cachedGrokCommand ?? null;
79
+ }
80
+ export function resolveGrokConfig(overrides) {
81
+ return {
82
+ model: overrides?.model,
83
+ requestTimeoutMs: overrides?.requestTimeoutMs,
84
+ };
85
+ }
86
+ function excerpt(raw, max = 200) {
87
+ const trimmed = raw.trim();
88
+ if (trimmed.length <= max)
89
+ return trimmed;
90
+ return `${trimmed.slice(0, max)}…`;
91
+ }
92
+ function parseGrokOutput(stdout) {
93
+ const trimmed = stdout.trim();
94
+ if (!trimmed) {
95
+ throw new Error('grok CLI returned empty output');
96
+ }
97
+ let parsed;
98
+ try {
99
+ parsed = JSON.parse(trimmed);
100
+ }
101
+ catch {
102
+ throw new Error(`grok CLI returned non-JSON output: ${excerpt(trimmed)}`);
103
+ }
104
+ if (!parsed || typeof parsed !== 'object') {
105
+ throw new Error(`grok CLI JSON has no text field: ${excerpt(trimmed)}`);
106
+ }
107
+ const record = parsed;
108
+ if (record.type === 'error') {
109
+ throw new Error(record.message || 'grok CLI returned an error');
110
+ }
111
+ if (typeof record.text !== 'string') {
112
+ throw new Error(`grok CLI JSON has no text field: ${excerpt(trimmed)}`);
113
+ }
114
+ const content = record.text.trim();
115
+ if (!content) {
116
+ throw new Error('grok CLI returned empty text');
117
+ }
118
+ const rawReason = record.stopReason === undefined || record.stopReason === null
119
+ ? ''
120
+ : String(record.stopReason).trim();
121
+ if (rawReason.toLowerCase() !== 'end_turn') {
122
+ throw new Error(rawReason
123
+ ? `grok CLI stopped with stopReason=${rawReason}`
124
+ : 'grok CLI JSON is missing stopReason=end_turn');
125
+ }
126
+ return content;
127
+ }
128
+ /**
129
+ * Call Grok Build in headless mode and return the assistant text.
130
+ *
131
+ * `--cwd` is an empty temp directory (not the repo) so project AGENTS.md
132
+ * files are not injected into wiki generation.
133
+ */
134
+ export async function callGrokLLM(prompt, config, systemPrompt, options) {
135
+ const grokCmd = getDetectedGrokCommand();
136
+ if (!grokCmd) {
137
+ throw new Error('Grok CLI not found. Install Grok Build and ensure `grok` is on PATH. Run `grok login` if unauthenticated.');
138
+ }
139
+ const fullPrompt = systemPrompt ? `${systemPrompt}\n\n---\n\n${prompt}` : prompt;
140
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-wiki-grok-'));
141
+ const promptPath = path.join(tempDir, 'prompt.txt');
142
+ const childLifecycle = { spawned: false, closed: false };
143
+ try {
144
+ await fs.writeFile(promptPath, fullPrompt, 'utf-8');
145
+ const args = [
146
+ '--prompt-file',
147
+ promptPath,
148
+ '--output-format',
149
+ 'json',
150
+ '--max-turns',
151
+ GROK_MAX_TURNS,
152
+ '--no-plan',
153
+ '--no-subagents',
154
+ '--disable-web-search',
155
+ '--disallowed-tools',
156
+ GROK_DISALLOWED_TOOLS,
157
+ '--sandbox',
158
+ GROK_SANDBOX_PROFILE,
159
+ '--cwd',
160
+ tempDir,
161
+ ];
162
+ if (config.model) {
163
+ args.push('--model', config.model);
164
+ }
165
+ verboseLog('Spawning:', grokCmd.command, [...grokCmd.argsPrefix, ...args].join(' ').replace(promptPath, '[prompt-file]'));
166
+ if (config.model) {
167
+ verboseLog('Model:', config.model);
168
+ }
169
+ const content = await runGrok(grokCmd.command, [...grokCmd.argsPrefix, ...args], tempDir, config, options, childLifecycle);
170
+ return { content };
171
+ }
172
+ finally {
173
+ // Skip rm while a live child may still be using cwd/sandbox/prompt-file.
174
+ if (!childLifecycle.spawned || childLifecycle.closed) {
175
+ await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
176
+ }
177
+ }
178
+ }
179
+ function runGrok(command, args, cwd, config, options, lifecycle) {
180
+ const startTime = Date.now();
181
+ return new Promise((resolve, reject) => {
182
+ const child = spawn(command, args, {
183
+ cwd,
184
+ // Prompt is --prompt-file; an unused stdin pipe can EPIPE the wiki process.
185
+ stdio: ['ignore', 'pipe', 'pipe'],
186
+ windowsHide: true,
187
+ env: {
188
+ ...process.env,
189
+ CI: '1',
190
+ },
191
+ });
192
+ lifecycle.spawned = true;
193
+ verboseLog('Process spawned with PID:', child.pid);
194
+ let stdout = '';
195
+ let stderr = '';
196
+ const stdoutDecoder = new StringDecoder('utf8');
197
+ const stderrDecoder = new StringDecoder('utf8');
198
+ let settled = false;
199
+ let timedOut = false;
200
+ let killTimer;
201
+ let killEscalate;
202
+ let hardDeadline;
203
+ const timeoutMs = config.requestTimeoutMs;
204
+ const timeoutError = timeoutMs !== undefined && timeoutMs > 0
205
+ ? new Error(`grok CLI timed out after ${timeoutMs >= 60_000
206
+ ? `${Math.round(timeoutMs / 60_000)}m`
207
+ : `${Math.round(timeoutMs / 1_000)}s`}. Increase --timeout or omit it to disable the request timeout.`)
208
+ : undefined;
209
+ const clearKillTimers = () => {
210
+ if (killTimer !== undefined)
211
+ clearTimeout(killTimer);
212
+ if (killEscalate !== undefined)
213
+ clearTimeout(killEscalate);
214
+ if (hardDeadline !== undefined)
215
+ clearTimeout(hardDeadline);
216
+ };
217
+ const rejectOnce = (error) => {
218
+ if (settled)
219
+ return;
220
+ settled = true;
221
+ clearKillTimers();
222
+ reject(error);
223
+ };
224
+ const resolveOnce = (value) => {
225
+ if (settled)
226
+ return;
227
+ settled = true;
228
+ clearKillTimers();
229
+ resolve(value);
230
+ };
231
+ const KILL_GRACE_MS = 2000;
232
+ if (timeoutMs !== undefined && timeoutMs > 0 && timeoutError) {
233
+ killTimer = setTimeout(() => {
234
+ timedOut = true;
235
+ killChildTree(child);
236
+ killEscalate = setTimeout(() => {
237
+ try {
238
+ child.kill('SIGKILL');
239
+ }
240
+ catch {
241
+ // Process may have already exited.
242
+ }
243
+ hardDeadline = setTimeout(() => {
244
+ rejectOnce(timeoutError);
245
+ }, KILL_GRACE_MS);
246
+ }, KILL_GRACE_MS);
247
+ }, timeoutMs);
248
+ }
249
+ child.stdout.on('data', (chunk) => {
250
+ const chunkStr = stdoutDecoder.write(chunk);
251
+ stdout += chunkStr;
252
+ options?.onChunk?.(stdout.length);
253
+ });
254
+ child.stderr.on('data', (chunk) => {
255
+ stderr += stderrDecoder.write(chunk);
256
+ });
257
+ child.on('close', (code) => {
258
+ const alreadySettled = settled;
259
+ lifecycle.closed = true;
260
+ stdout += stdoutDecoder.end();
261
+ stderr += stderrDecoder.end();
262
+ verboseLog(`Process exited with code ${code} after ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
263
+ if (timedOut && timeoutError) {
264
+ rejectOnce(timeoutError);
265
+ }
266
+ else if (code !== 0) {
267
+ const details = stderr.trim() || stdout.trim();
268
+ rejectOnce(new Error(`grok CLI exited with code ${code}: ${details}`));
269
+ }
270
+ else {
271
+ try {
272
+ resolveOnce(parseGrokOutput(stdout));
273
+ }
274
+ catch (err) {
275
+ rejectOnce(err instanceof Error ? err : new Error(String(err)));
276
+ }
277
+ }
278
+ if (alreadySettled) {
279
+ void fs.rm(cwd, { recursive: true, force: true }).catch(() => undefined);
280
+ }
281
+ });
282
+ child.on('error', (err) => {
283
+ lifecycle.closed = true;
284
+ rejectOnce(new Error(`Failed to spawn grok CLI: ${err.message}`));
285
+ });
286
+ });
287
+ }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Config priority: CLI flags > env vars > defaults
8
8
  */
9
- export type LLMProvider = 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex' | 'opencode' | 'minimax';
9
+ export type LLMProvider = 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex' | 'opencode' | 'grok' | 'minimax';
10
10
  export declare const MINIMAX_OPENAI_BASE_URLS: {
11
11
  readonly global_en: "https://api.minimax.io/v1";
12
12
  readonly cn_zh: "https://api.minimaxi.com/v1";
@@ -33,11 +33,14 @@ export async function resolveLLMConfig(overrides) {
33
33
  ? savedConfig.codexModel
34
34
  : savedProvider === 'opencode'
35
35
  ? savedConfig.opencodeModel
36
- : undefined;
36
+ : savedProvider === 'grok'
37
+ ? savedConfig.grokModel
38
+ : undefined;
37
39
  const localProvider = savedProvider === 'cursor' ||
38
40
  savedProvider === 'claude' ||
39
41
  savedProvider === 'codex' ||
40
- savedProvider === 'opencode';
42
+ savedProvider === 'opencode' ||
43
+ savedProvider === 'grok';
41
44
  const apiKey = overrides?.apiKey ||
42
45
  (savedProvider === 'minimax' ? process.env.MINIMAX_API_KEY : undefined) ||
43
46
  (savedProvider !== 'minimax' ? process.env.GITNEXUS_API_KEY : undefined) ||
@@ -11,8 +11,19 @@ export interface LocalCLIConfig {
11
11
  workingDirectory?: string;
12
12
  requestTimeoutMs?: number;
13
13
  }
14
+ export interface ResolvedCliCommand {
15
+ displayName: string;
16
+ command: string;
17
+ argsPrefix: string[];
18
+ }
14
19
  export declare function detectLocalCLI(provider: LocalAgentProvider): string | null;
15
20
  export declare function resolveLocalCLIConfig(overrides?: Partial<LocalCLIConfig>): LocalCLIConfig;
16
21
  export declare function callClaudeLLM(prompt: string, config: LocalCLIConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
17
22
  export declare function callCodexLLM(prompt: string, config: LocalCLIConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
18
23
  export declare function callOpenCodeLLM(prompt: string, config: LocalCLIConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
24
+ /**
25
+ * Resolve a PATH command for spawn/execFile without a shell.
26
+ * On Windows, npm shims are `.cmd` files that Node cannot execFile/spawn
27
+ * directly — prefer a native `.exe` from `where.exe`, else `cmd.exe /d /s /c`.
28
+ */
29
+ export declare function resolveWindowsCliCommand(displayName: string): ResolvedCliCommand;
@@ -281,10 +281,30 @@ function getDetectedCommand(provider) {
281
281
  detectLocalCLI(provider);
282
282
  return cachedCommands.get(provider) ?? null;
283
283
  }
284
+ /**
285
+ * Resolve a PATH command for spawn/execFile without a shell.
286
+ * On Windows, npm shims are `.cmd` files that Node cannot execFile/spawn
287
+ * directly — prefer a native `.exe` from `where.exe`, else `cmd.exe /d /s /c`.
288
+ */
289
+ export function resolveWindowsCliCommand(displayName) {
290
+ if (process.platform !== 'win32') {
291
+ return { displayName, command: displayName, argsPrefix: [] };
292
+ }
293
+ const located = findWindowsCommand(`${displayName}.cmd`) || findWindowsCommand(displayName);
294
+ if (located && /\.exe$/i.test(located)) {
295
+ return { displayName, command: located, argsPrefix: [] };
296
+ }
297
+ // Last-resort fallback for installations that only expose a .cmd shim.
298
+ return {
299
+ displayName,
300
+ command: process.env.ComSpec || 'cmd.exe',
301
+ argsPrefix: ['/d', '/s', '/c', displayName],
302
+ };
303
+ }
284
304
  function resolveLocalCommand(provider) {
285
305
  const displayName = COMMANDS[provider];
286
306
  if (process.platform !== 'win32') {
287
- return { displayName, command: displayName, argsPrefix: [] };
307
+ return resolveWindowsCliCommand(displayName);
288
308
  }
289
309
  const npmBin = findWindowsCommand(`${displayName}.cmd`) || findWindowsCommand(displayName);
290
310
  if (npmBin) {
@@ -302,14 +322,7 @@ function resolveLocalCommand(provider) {
302
322
  }
303
323
  }
304
324
  }
305
- // Last-resort fallback for non-npm Windows installations that only expose a
306
- // .cmd shim. Prompts are passed via stdin, so repo content is not placed on
307
- // the command line.
308
- return {
309
- displayName,
310
- command: process.env.ComSpec || 'cmd.exe',
311
- argsPrefix: ['/d', '/s', '/c', displayName],
312
- };
325
+ return resolveWindowsCliCommand(displayName);
313
326
  }
314
327
  function findWindowsCommand(command) {
315
328
  try {
@@ -371,10 +371,26 @@ export declare class LocalBackend {
371
371
  * - If only 1 repo, use it
372
372
  * - If 0 or multiple without param, throw with helpful message
373
373
  *
374
- * On a miss, re-reads the registry once in case a new repo was indexed
375
- * while the MCP server was running.
374
+ * Re-reads the registry before an omitted implicit target or after an
375
+ * explicit miss, so long-running servers see newly indexed repositories.
376
376
  */
377
377
  resolveRepo(repoParam?: string, branch?: string): Promise<RepoHandle>;
378
+ /**
379
+ * Internal resolver variant for CLI/MCP tool routing and discovery.
380
+ * - If repoParam is given, match by name or path
381
+ * - If only 1 repo, use it
382
+ * - If multiple repos exist and repoParam is omitted, callers may opt in to
383
+ * the registered repo containing process.cwd()
384
+ * - If 0 repos exist, or cwd cannot disambiguate multiple repos, throw
385
+ *
386
+ * Omitted-repo resolution re-reads the registry before accepting any
387
+ * implicit target, including a cached singleton. A caller that just obtained
388
+ * a fresh registry snapshot may disable that refresh explicitly.
389
+ */
390
+ selectToolRepository(repoParam?: string, branch?: string, options?: {
391
+ allowCwdDefault?: boolean;
392
+ refreshRegistry?: boolean;
393
+ }): Promise<RepoHandle>;
378
394
  /**
379
395
  * Re-point a resolved repo handle at a specific branch index (#2106).
380
396
  *
@@ -409,12 +425,13 @@ export declare class LocalBackend {
409
425
  */
410
426
  private resolveRepoFromCache;
411
427
  /**
412
- * Prefer the indexed repo whose path matches the git root of process.cwd().
428
+ * Match process.cwd() against indexed repositories.
413
429
  *
414
- * In MCP stdio server mode, `process.cwd()` is the server's launch directory,
415
- * not the agent client's cwd. If the server was started from an unrelated
416
- * directory, `getGitRoot` returns null and duplicate-name resolution throws
417
- * {@link RegistryAmbiguousTargetError} — callers should pass an absolute path.
430
+ * Explicit duplicate aliases use exact Git-root matching only. Omitted
431
+ * read-only calls opt into deepest containing-path selection. In that mode a
432
+ * candidate must not sit above cwd's Git root, so an unindexed nested checkout
433
+ * cannot fall through to an indexed ancestor. The `.git` ancestor fallback
434
+ * preserves that boundary when the git executable is unavailable.
418
435
  */
419
436
  private pickRepoHandleForCwd;
420
437
  private handleToRegistryEntry;