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.
- package/README.md +43 -1
- package/dist/_shared/impact-risk.d.ts +37 -0
- package/dist/_shared/impact-risk.d.ts.map +1 -0
- package/dist/_shared/impact-risk.js +92 -0
- package/dist/_shared/impact-risk.js.map +1 -0
- package/dist/_shared/index.d.ts +2 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +2 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/cli/ai-context.js +4 -4
- package/dist/cli/analyze-config.d.ts +2 -0
- package/dist/cli/analyze-config.js +16 -0
- package/dist/cli/analyze-options.d.ts +4 -0
- package/dist/cli/analyze.d.ts +17 -0
- package/dist/cli/analyze.js +43 -7
- package/dist/cli/eval-server.js +22 -5
- package/dist/cli/group.js +7 -1
- package/dist/cli/help-i18n.js +2 -0
- package/dist/cli/i18n/en.d.ts +12 -2
- package/dist/cli/i18n/en.js +12 -2
- package/dist/cli/i18n/resources.d.ts +22 -2
- package/dist/cli/i18n/zh-CN.d.ts +10 -0
- package/dist/cli/i18n/zh-CN.js +12 -2
- package/dist/cli/index.js +11 -4
- package/dist/cli/status.js +91 -6
- package/dist/cli/watch-queue.d.ts +41 -0
- package/dist/cli/watch-queue.js +184 -0
- package/dist/cli/watch.d.ts +20 -0
- package/dist/cli/watch.js +372 -0
- package/dist/cli/wiki.js +15 -2
- package/dist/config/ignore-service.d.ts +11 -0
- package/dist/config/ignore-service.js +41 -4
- package/dist/config/repo-control-file.d.ts +3 -0
- package/dist/config/repo-control-file.js +115 -0
- package/dist/core/group/config-parser.js +20 -2
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +33 -2
- package/dist/core/group/extractors/fs-utils.d.ts +2 -0
- package/dist/core/group/extractors/fs-utils.js +86 -0
- package/dist/core/group/extractors/graphql-extractor.d.ts +17 -0
- package/dist/core/group/extractors/graphql-extractor.js +652 -0
- package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +1 -1
- package/dist/core/group/matching.js +8 -1
- package/dist/core/group/service.js +5 -1
- package/dist/core/group/storage.js +1 -0
- package/dist/core/group/sync.d.ts +3 -1
- package/dist/core/group/sync.js +43 -5
- package/dist/core/group/types.d.ts +19 -5
- package/dist/core/incremental/derived-writeback.d.ts +36 -0
- package/dist/core/incremental/derived-writeback.js +68 -0
- package/dist/core/incremental/subgraph-extract.d.ts +6 -4
- package/dist/core/incremental/subgraph-extract.js +7 -5
- package/dist/core/index-content-drift.d.ts +54 -0
- package/dist/core/index-content-drift.js +127 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +15 -5
- package/dist/core/ingestion/filesystem-walker.js +20 -3
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
- package/dist/core/ingestion/language-provider.d.ts +43 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +4 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +16 -0
- package/dist/core/ingestion/languages/java/captures.js +14 -1
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
- package/dist/core/ingestion/languages/java.js +2 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +5 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +16 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +14 -1
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
- package/dist/core/ingestion/languages/kotlin.js +2 -0
- package/dist/core/ingestion/pipeline-phases/di.js +47 -14
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +3 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +57 -86
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
- package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
- package/dist/core/ingestion/pipeline-phases/scan.js +25 -13
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +44 -12
- package/dist/core/ingestion/scope-extractor.js +1 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +2 -2
- package/dist/core/ingestion/utils/symbol-labels.js +2 -2
- package/dist/core/ingestion/workers/parse-worker.js +28 -2
- package/dist/core/lbug/lbug-adapter.d.ts +59 -0
- package/dist/core/lbug/lbug-adapter.js +154 -1
- package/dist/core/run-analyze.d.ts +17 -0
- package/dist/core/run-analyze.js +231 -23
- package/dist/core/search/fts-indexes.d.ts +19 -1
- package/dist/core/search/fts-indexes.js +28 -1
- package/dist/core/wiki/generator.js +8 -0
- package/dist/core/wiki/grok-client.d.ts +21 -0
- package/dist/core/wiki/grok-client.js +287 -0
- package/dist/core/wiki/llm-client.d.ts +1 -1
- package/dist/core/wiki/llm-client.js +5 -2
- package/dist/core/wiki/local-cli-client.d.ts +11 -0
- package/dist/core/wiki/local-cli-client.js +22 -9
- package/dist/mcp/local/local-backend.d.ts +24 -7
- package/dist/mcp/local/local-backend.js +190 -81
- package/dist/mcp/local/pdg-impact.d.ts +8 -4
- package/dist/mcp/local/pdg-impact.js +7 -2
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +2 -1
- package/dist/mcp/server.js +6 -5
- package/dist/mcp/tools.js +31 -19
- package/dist/server/api.js +23 -64
- package/dist/server/grep-params.d.ts +18 -0
- package/dist/server/grep-params.js +83 -0
- package/dist/server/grep-scan.d.ts +23 -0
- package/dist/server/grep-scan.js +107 -0
- package/dist/server/grep-worker.d.ts +1 -0
- package/dist/server/grep-worker.js +12 -0
- package/dist/server/mcp-http.d.ts +8 -0
- package/dist/server/mcp-http.js +16 -1
- package/dist/storage/file-hash.d.ts +5 -0
- package/dist/storage/file-hash.js +16 -6
- package/dist/storage/fs-atomic.d.ts +24 -0
- package/dist/storage/fs-atomic.js +86 -2
- package/dist/storage/git.d.ts +19 -8
- package/dist/storage/git.js +83 -24
- package/dist/storage/gitnexus-managed-paths.d.ts +36 -0
- package/dist/storage/gitnexus-managed-paths.js +46 -0
- package/dist/storage/parse-cache.d.ts +22 -4
- package/dist/storage/parse-cache.js +106 -29
- package/dist/storage/parsedfile-store.d.ts +34 -60
- package/dist/storage/parsedfile-store.js +177 -171
- package/dist/storage/repo-manager.d.ts +11 -1
- package/dist/storage/repo-manager.js +23 -2
- package/dist/storage/repo-meta.d.ts +12 -0
- package/dist/storage/v8-sidecar.d.ts +48 -0
- package/dist/storage/v8-sidecar.js +347 -0
- package/dist/types/pipeline.d.ts +14 -0
- package/package.json +4 -1
- package/scripts/cross-platform-shard.ts +4 -2
- package/scripts/cross-platform-tests.ts +7 -1
- package/skills/gitnexus-cli.md +11 -3
- package/skills/gitnexus-impact-analysis.md +9 -0
- package/web/assets/{agent-Dr4l5EOp.js → agent-CFqT4hjR.js} +108 -104
- package/web/assets/{index-2zdvEdzg.js → index-BMIniRtX.js} +3 -3
- 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
|
-
:
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
375
|
-
*
|
|
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
|
-
*
|
|
428
|
+
* Match process.cwd() against indexed repositories.
|
|
413
429
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
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;
|