gitnexus 1.6.6-rc.30 → 1.6.6-rc.32
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/dist/cli/analyze.js +255 -21
- package/dist/cli/optional-grammars.d.ts +11 -8
- package/dist/cli/optional-grammars.js +12 -8
- package/dist/core/ingestion/filesystem-walker.js +18 -4
- package/dist/core/ingestion/parsing-processor.js +12 -6
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +61 -24
- package/dist/core/ingestion/scope-extractor.js +7 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +21 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +7 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +5 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +50 -2
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +28 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +75 -23
- package/dist/core/ingestion/utils/ast-helpers.d.ts +30 -0
- package/dist/core/ingestion/utils/ast-helpers.js +99 -0
- package/dist/core/ingestion/workers/parse-worker.js +12 -6
- package/dist/core/ingestion/workers/worker-pool.d.ts +4 -0
- package/dist/core/ingestion/workers/worker-pool.js +14 -1
- package/dist/core/lbug/lbug-adapter.js +183 -39
- package/dist/core/lbug/pool-adapter.js +137 -1
- package/dist/core/lbug/sidecar-recovery.d.ts +68 -0
- package/dist/core/lbug/sidecar-recovery.js +277 -0
- package/dist/server/api.js +5 -0
- package/package.json +3 -6
- package/scripts/build-tree-sitter-dart.cjs +4 -0
- package/scripts/build-tree-sitter-proto.cjs +3 -4
- package/scripts/build-tree-sitter-swift.cjs +39 -0
- package/scripts/build.js +20 -4
- package/scripts/materialize-vendor-grammars.cjs +72 -0
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -8
- package/web/assets/{agent-Warpie3K.js → agent-CBcds30d.js} +101 -76
- package/web/assets/{architectureDiagram-UL44E2DR-BVMgctd1.js → architectureDiagram-UL44E2DR-dIoPPr6x.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-D0Ojsf12.js → chunk-LCXTWHL2-B8hbjKUm.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CGOYR7mA.js → chunk-RG4AUYOV-EfsAenro.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-BWNNFh9P.js → classDiagram-KGZ6W3CR-_hSUwNQJ.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-D6den_PS.js → classDiagram-v2-72OJOZXJ-C0NcgLqj.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-fpPDz4AJ.js → diagram-3NCE3AQN-CYrNJJUh.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CBCK4dz-.js → diagram-GF46GFSD-56NpS1jw.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-Dp7brFCQ.js → diagram-QXG6HAR7-DwXkFq_r.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-CcNoPnrZ.js → diagram-WEQXMOUZ-C6BTq9za.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-C7gqztHL.js → erDiagram-L5TCEMPS-BcEjYsUQ.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-AhdoRoMl.js → flowDiagram-H6V6AXG4-DWAVIV6V.js} +1 -1
- package/web/assets/{index-DHb4KmNb.js → index-Czp-OFT-.js} +5 -5
- package/web/assets/index-nSZgUaIx.css +2 -0
- package/web/assets/{infoDiagram-3YFTVSEB-mP4ELRFj.js → infoDiagram-3YFTVSEB-ui-e52GZ.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-CPiYSQcK.js → ishikawaDiagram-BNXS4ZKH-DGimV4zg.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-EI3ocXjG.js → kanban-definition-75IXJCU3-BOyfgvKL.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-293vIByN.js → mindmap-definition-2TDM6QVE-Ba3QrYSU.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-DhdftcYl.js → pieDiagram-CU6KROY3-DMFBXNrM.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-DdPq7kYS.js → requirementDiagram-JXO7QTGE-bS4xboSz.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-CuIcQieZ.js → sequenceDiagram-VS2MUI6T-BqKET_2i.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-CjVShw_t.js → stateDiagram-7D4R322I-DP9kvX2i.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-D9CkWeqa.js → stateDiagram-v2-36443NZ5-DB-cZ1VL.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-Dzybzo6D.js → timeline-definition-O6YCAMPW-DNScSOi7.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-CapSiCmK.js → vennDiagram-MWXL3ELB-Bd1zTNWW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-CohjGBRu.js → wardleyDiagram-CUQ6CDDI-DqCDQKFt.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-CiYjF6Jz.js → xychartDiagram-N2JHSOCM-B8Cje_Ei.js} +1 -1
- package/web/index.html +2 -2
- package/vendor/node_modules/node-addon-api/node_addon_api.Makefile +0 -6
- package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +0 -106
- package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +0 -110
- package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +0 -106
- package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +0 -106
- package/web/assets/index-BleGLU8S.css +0 -2
package/dist/cli/analyze.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* skill generation (--skills), summary output, and process.exit().
|
|
9
9
|
*/
|
|
10
10
|
import path from 'path';
|
|
11
|
-
import {
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
12
|
import v8 from 'v8';
|
|
13
13
|
import cliProgress from 'cli-progress';
|
|
14
14
|
import { closeLbug } from '../core/lbug/lbug-adapter.js';
|
|
@@ -29,6 +29,7 @@ import { isHfDownloadFailure } from '../core/embeddings/hf-env.js';
|
|
|
29
29
|
// previous behaviour silently swallowed stack traces and made #1169
|
|
30
30
|
// indistinguishable from a no-op success on Windows.
|
|
31
31
|
const realStderrWrite = process.stderr.write.bind(process.stderr);
|
|
32
|
+
const realStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
32
33
|
const writeFatalToStderr = (label, err) => {
|
|
33
34
|
const isErr = err instanceof Error;
|
|
34
35
|
const message = isErr ? err.message : String(err);
|
|
@@ -67,14 +68,212 @@ const HEAP_FLAG = `--max-old-space-size=${RESPAWN_HEAP_MB}`;
|
|
|
67
68
|
/** Increase default stack size (KB) to prevent stack overflow on deep class hierarchies. */
|
|
68
69
|
const STACK_KB = 4096;
|
|
69
70
|
const STACK_FLAG = `--stack-size=${STACK_KB}`;
|
|
71
|
+
const RESPAWN_OUTPUT_TAIL_CHARS = 1024 * 1024;
|
|
72
|
+
const RESPAWN_PROGRESS_ENV = 'GITNEXUS_RESPAWN_PROGRESS_TTY';
|
|
73
|
+
const terminalColumns = () => {
|
|
74
|
+
const parsed = Number(process.env.COLUMNS);
|
|
75
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 80;
|
|
76
|
+
};
|
|
77
|
+
const ANSI_ESCAPE_PATTERN = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\)|[PX^_][\s\S]*?\x1B\\|[78]|[@-Z\\-_])/y;
|
|
78
|
+
const splitGraphemes = (text) => {
|
|
79
|
+
const Segmenter = Intl.Segmenter;
|
|
80
|
+
if (Segmenter) {
|
|
81
|
+
return Array.from(new Segmenter(undefined, { granularity: 'grapheme' }).segment(text), (s) => s.segment);
|
|
82
|
+
}
|
|
83
|
+
return Array.from(text);
|
|
84
|
+
};
|
|
85
|
+
const isZeroWidthCodePoint = (codePoint) => codePoint === 0x200d ||
|
|
86
|
+
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
87
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
88
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
89
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
90
|
+
(codePoint >= 0xfe00 && codePoint <= 0xfe0f) ||
|
|
91
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f);
|
|
92
|
+
const isWideCodePoint = (codePoint) => codePoint >= 0x1100 &&
|
|
93
|
+
(codePoint <= 0x115f ||
|
|
94
|
+
codePoint === 0x2329 ||
|
|
95
|
+
codePoint === 0x232a ||
|
|
96
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
97
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
98
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
99
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
100
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
101
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
102
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
103
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
|
|
104
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
105
|
+
const visibleColumns = (text) => {
|
|
106
|
+
let columns = 0;
|
|
107
|
+
for (const char of Array.from(text)) {
|
|
108
|
+
const codePoint = char.codePointAt(0);
|
|
109
|
+
if (codePoint === undefined || isZeroWidthCodePoint(codePoint))
|
|
110
|
+
continue;
|
|
111
|
+
columns += isWideCodePoint(codePoint) ? 2 : 1;
|
|
112
|
+
}
|
|
113
|
+
return columns;
|
|
114
|
+
};
|
|
115
|
+
const readAnsiEscapeAt = (text, index) => {
|
|
116
|
+
ANSI_ESCAPE_PATTERN.lastIndex = index;
|
|
117
|
+
return ANSI_ESCAPE_PATTERN.exec(text)?.[0];
|
|
118
|
+
};
|
|
119
|
+
const truncateAnsiToColumns = (text, maxColumns) => {
|
|
120
|
+
if (!Number.isFinite(maxColumns) || maxColumns <= 0)
|
|
121
|
+
return '';
|
|
122
|
+
let output = '';
|
|
123
|
+
let columns = 0;
|
|
124
|
+
let index = 0;
|
|
125
|
+
while (index < text.length) {
|
|
126
|
+
const escape = readAnsiEscapeAt(text, index);
|
|
127
|
+
if (escape) {
|
|
128
|
+
output += escape;
|
|
129
|
+
index += escape.length;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const nextEscapeIndex = text.indexOf('\x1B', index);
|
|
133
|
+
const plainEnd = nextEscapeIndex === -1 ? text.length : nextEscapeIndex;
|
|
134
|
+
const plainText = text.slice(index, plainEnd);
|
|
135
|
+
for (const segment of splitGraphemes(plainText)) {
|
|
136
|
+
const width = visibleColumns(segment);
|
|
137
|
+
if (width > 0 && columns + width > maxColumns)
|
|
138
|
+
return output;
|
|
139
|
+
output += segment;
|
|
140
|
+
columns += width;
|
|
141
|
+
}
|
|
142
|
+
index = plainEnd;
|
|
143
|
+
}
|
|
144
|
+
return output;
|
|
145
|
+
};
|
|
146
|
+
const createAnsiPipeTerminal = (stream) => {
|
|
147
|
+
let linewrap = true;
|
|
148
|
+
let dy = 0;
|
|
149
|
+
const write = (s) => {
|
|
150
|
+
stream.write(s);
|
|
151
|
+
};
|
|
152
|
+
const moveVertical = (delta) => {
|
|
153
|
+
if (delta > 0)
|
|
154
|
+
write(`\x1B[${delta}B`);
|
|
155
|
+
else if (delta < 0)
|
|
156
|
+
write(`\x1B[${Math.abs(delta)}A`);
|
|
157
|
+
};
|
|
158
|
+
return {
|
|
159
|
+
cursorSave: () => write('\x1B7'),
|
|
160
|
+
cursorRestore: () => write('\x1B8'),
|
|
161
|
+
cursor: (enabled) => write(enabled ? '\x1B[?25h' : '\x1B[?25l'),
|
|
162
|
+
lineWrapping: (enabled) => {
|
|
163
|
+
linewrap = enabled;
|
|
164
|
+
write(enabled ? '\x1B[?7h' : '\x1B[?7l');
|
|
165
|
+
},
|
|
166
|
+
cursorTo: (x = null, y = null) => {
|
|
167
|
+
if (typeof y === 'number' && typeof x === 'number') {
|
|
168
|
+
write(`\x1B[${y + 1};${x + 1}H`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (typeof x === 'number') {
|
|
172
|
+
write(x === 0 ? '\r' : `\x1B[${x + 1}G`);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
cursorRelative: (dx = null, nextDy = null) => {
|
|
176
|
+
if (typeof dx === 'number' && dx !== 0) {
|
|
177
|
+
write(dx > 0 ? `\x1B[${dx}C` : `\x1B[${Math.abs(dx)}D`);
|
|
178
|
+
}
|
|
179
|
+
if (typeof nextDy === 'number' && nextDy !== 0) {
|
|
180
|
+
dy += nextDy;
|
|
181
|
+
moveVertical(nextDy);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
cursorRelativeReset: () => {
|
|
185
|
+
moveVertical(-dy);
|
|
186
|
+
write('\r');
|
|
187
|
+
dy = 0;
|
|
188
|
+
},
|
|
189
|
+
clearRight: () => write('\x1B[0K'),
|
|
190
|
+
clearLine: () => write('\x1B[2K'),
|
|
191
|
+
clearBottom: () => write('\x1B[0J'),
|
|
192
|
+
newline: () => {
|
|
193
|
+
write('\n');
|
|
194
|
+
dy++;
|
|
195
|
+
},
|
|
196
|
+
write: (s, rawWrite = false) => {
|
|
197
|
+
const width = terminalColumns();
|
|
198
|
+
write(linewrap && rawWrite === false ? truncateAnsiToColumns(s, width) : s);
|
|
199
|
+
},
|
|
200
|
+
isTTY: () => true,
|
|
201
|
+
getWidth: terminalColumns,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const shouldBridgeRespawnProgressTty = () => process.stderr.isTTY === true || process.stdout.isTTY === true;
|
|
205
|
+
const appendOutputTail = (tail, chunk) => {
|
|
206
|
+
const text = Buffer.isBuffer(chunk)
|
|
207
|
+
? chunk.toString('utf8')
|
|
208
|
+
: typeof chunk === 'string'
|
|
209
|
+
? chunk
|
|
210
|
+
: String(chunk ?? '');
|
|
211
|
+
if (!text)
|
|
212
|
+
return tail;
|
|
213
|
+
const next = tail + text;
|
|
214
|
+
return next.length > RESPAWN_OUTPUT_TAIL_CHARS ? next.slice(-RESPAWN_OUTPUT_TAIL_CHARS) : next;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Run the respawned analyzer while teeing child output through to the parent
|
|
218
|
+
* and keeping a bounded tail for crash classification.
|
|
219
|
+
*
|
|
220
|
+
* `execFileSync(..., { stdio: 'inherit' })` preserved live progress but hid
|
|
221
|
+
* stderr/stdout from the parent on abnormal exits. That made every
|
|
222
|
+
* SIGABRT/status-134 child look like an output-less V8 heap OOM, even when the
|
|
223
|
+
* terminal had already shown a native crash such as
|
|
224
|
+
* `libc++abi: ... Napi::Error`. Piped streams plus an explicit tee keeps the UX
|
|
225
|
+
* and gives `childProcessLikelyOom` the evidence it needs.
|
|
226
|
+
*/
|
|
227
|
+
const runRespawnedAnalyze = (args, env) => new Promise((resolve) => {
|
|
228
|
+
let stdout = '';
|
|
229
|
+
let stderr = '';
|
|
230
|
+
let settled = false;
|
|
231
|
+
const finish = (exit) => {
|
|
232
|
+
if (settled)
|
|
233
|
+
return;
|
|
234
|
+
settled = true;
|
|
235
|
+
resolve(exit);
|
|
236
|
+
};
|
|
237
|
+
const child = spawn(process.execPath, [...args], {
|
|
238
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
239
|
+
env,
|
|
240
|
+
});
|
|
241
|
+
child.stdout?.on('data', (chunk) => {
|
|
242
|
+
stdout = appendOutputTail(stdout, chunk);
|
|
243
|
+
realStdoutWrite(chunk);
|
|
244
|
+
});
|
|
245
|
+
child.stderr?.on('data', (chunk) => {
|
|
246
|
+
stderr = appendOutputTail(stderr, chunk);
|
|
247
|
+
realStderrWrite(chunk);
|
|
248
|
+
});
|
|
249
|
+
child.on('error', (err) => {
|
|
250
|
+
finish({
|
|
251
|
+
status: 1,
|
|
252
|
+
signal: null,
|
|
253
|
+
stdout,
|
|
254
|
+
stderr,
|
|
255
|
+
message: err instanceof Error ? err.message : String(err),
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
child.on('close', (status, signal) => {
|
|
259
|
+
finish({
|
|
260
|
+
status,
|
|
261
|
+
signal,
|
|
262
|
+
stdout,
|
|
263
|
+
stderr,
|
|
264
|
+
message: `Command failed: ${process.execPath} ${args.join(' ')}`,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
70
268
|
/**
|
|
71
269
|
* Heuristic for "child re-exec likely died from V8 OOM".
|
|
72
270
|
*
|
|
73
|
-
* Platform-independent detection is best-effort: V8/Node usually emit
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
271
|
+
* Platform-independent detection is best-effort: V8/Node usually emit stable
|
|
272
|
+
* heap-exhaustion phrases in stderr/message across Linux/macOS/Windows (for
|
|
273
|
+
* example "JavaScript heap out of memory" or "Reached heap limit"). When the
|
|
274
|
+
* child produced no output at all, we still treat status 134/SIGABRT as likely
|
|
275
|
+
* heap OOM. If stderr/stdout contains a native crash diagnostic, the output
|
|
276
|
+
* evidence wins and we do not print heap guidance.
|
|
78
277
|
*/
|
|
79
278
|
const childProcessLikelyOom = (err) => {
|
|
80
279
|
if (!err || typeof err !== 'object')
|
|
@@ -97,6 +296,23 @@ const childProcessLikelyOom = (err) => {
|
|
|
97
296
|
return false;
|
|
98
297
|
return e.status === 134 || e.signal === 'SIGABRT';
|
|
99
298
|
};
|
|
299
|
+
const childProcessLikelyNativeAbort = (err) => {
|
|
300
|
+
if (!err || typeof err !== 'object')
|
|
301
|
+
return false;
|
|
302
|
+
const e = err;
|
|
303
|
+
const hasNativeAbortSignature = (v) => {
|
|
304
|
+
const text = (Buffer.isBuffer(v) ? v.toString('utf8') : typeof v === 'string' ? v : '').toLowerCase();
|
|
305
|
+
if (!text)
|
|
306
|
+
return false;
|
|
307
|
+
return (text.includes('napi::error') ||
|
|
308
|
+
text.includes('libc++abi: terminating') ||
|
|
309
|
+
text.includes('abort trap') ||
|
|
310
|
+
text.includes('native stack') ||
|
|
311
|
+
text.includes('native worker') ||
|
|
312
|
+
text.includes('native binding'));
|
|
313
|
+
};
|
|
314
|
+
return [e.message, e.stderr, e.stdout].some((v) => hasNativeAbortSignature(v));
|
|
315
|
+
};
|
|
100
316
|
const forceHeapOOMForTestIfEnabled = () => {
|
|
101
317
|
if (process.env.GITNEXUS_TEST_FORCE_HEAP_OOM !== '1')
|
|
102
318
|
return;
|
|
@@ -107,7 +323,7 @@ const forceHeapOOMForTestIfEnabled = () => {
|
|
|
107
323
|
chunks.push('x'.repeat(1024 * 1024));
|
|
108
324
|
};
|
|
109
325
|
/** Re-exec the process with a 16GB heap and larger stack if we're currently below that. */
|
|
110
|
-
function ensureHeap() {
|
|
326
|
+
async function ensureHeap() {
|
|
111
327
|
const nodeOpts = process.env.NODE_OPTIONS || '';
|
|
112
328
|
if (nodeOpts.includes('--max-old-space-size'))
|
|
113
329
|
return false;
|
|
@@ -119,23 +335,30 @@ function ensureHeap() {
|
|
|
119
335
|
const cliFlags = [HEAP_FLAG];
|
|
120
336
|
if (!nodeOpts.includes('--stack-size'))
|
|
121
337
|
cliFlags.push(STACK_FLAG);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
338
|
+
const childArgs = [...cliFlags, ...process.argv.slice(1)];
|
|
339
|
+
const childEnv = {
|
|
340
|
+
...process.env,
|
|
341
|
+
NODE_OPTIONS: `${nodeOpts} ${HEAP_FLAG}`.trim(),
|
|
342
|
+
};
|
|
343
|
+
if (shouldBridgeRespawnProgressTty())
|
|
344
|
+
childEnv[RESPAWN_PROGRESS_ENV] = '1';
|
|
345
|
+
const childExit = await runRespawnedAnalyze(childArgs, childEnv);
|
|
346
|
+
if (childExit.status !== 0 || childExit.signal) {
|
|
347
|
+
if (childProcessLikelyOom(childExit)) {
|
|
130
348
|
cliError(` Analysis likely ran out of memory.\n` +
|
|
131
349
|
` Retry with a larger heap if your machine allows it:\n` +
|
|
132
350
|
` NODE_OPTIONS="--max-old-space-size=24576" gitnexus analyze [your-args]\n` +
|
|
133
351
|
` (Windows: set NODE_OPTIONS=--max-old-space-size=24576 && gitnexus analyze [your-args])\n` +
|
|
134
352
|
` If this persists, it may be a native crash unrelated to heap size.\n`, { recoveryHint: 'heap-oom-respawn' });
|
|
135
353
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
354
|
+
else if (childProcessLikelyNativeAbort(childExit)) {
|
|
355
|
+
cliError(` Analysis aborted in a native worker or native binding path.\n` +
|
|
356
|
+
` Try one of these recovery paths:\n` +
|
|
357
|
+
` gitnexus analyze --workers 0\n` +
|
|
358
|
+
` npm uninstall -g gitnexus && npm install -g gitnexus@latest\n` +
|
|
359
|
+
` Use Node 22 LTS if you are on a newer non-LTS runtime.\n`, { recoveryHint: 'native-worker-abort' });
|
|
360
|
+
}
|
|
361
|
+
const status = typeof childExit.status === 'number' && childExit.status !== 0 ? childExit.status : 1;
|
|
139
362
|
process.exitCode = status;
|
|
140
363
|
}
|
|
141
364
|
return true;
|
|
@@ -157,6 +380,7 @@ const ANALYZE_CLI_ENV_KEYS = [
|
|
|
157
380
|
'GITNEXUS_EMBEDDING_BATCH_SIZE',
|
|
158
381
|
'GITNEXUS_EMBEDDING_SUB_BATCH_SIZE',
|
|
159
382
|
'GITNEXUS_EMBEDDING_DEVICE',
|
|
383
|
+
'GITNEXUS_ANALYZE_PROGRESS_ACTIVE',
|
|
160
384
|
];
|
|
161
385
|
const snapshotAnalyzeEnv = () => {
|
|
162
386
|
const snap = {};
|
|
@@ -188,7 +412,7 @@ const restoreAnalyzeEnv = (snap) => {
|
|
|
188
412
|
*/
|
|
189
413
|
export const shouldGenerateCommunitySkillFiles = (options, pipelineResult) => Boolean(options?.skills && pipelineResult && !options?.indexOnly);
|
|
190
414
|
export const analyzeCommand = async (inputPath, options) => {
|
|
191
|
-
if (ensureHeap())
|
|
415
|
+
if (await ensureHeap())
|
|
192
416
|
return;
|
|
193
417
|
forceHeapOOMForTestIfEnabled();
|
|
194
418
|
// Install fatal handlers immediately after re-exec resolution so any
|
|
@@ -362,7 +586,7 @@ const analyzeCommandImpl = async (inputPath, options) => {
|
|
|
362
586
|
console.log(`${maxFileSizeBanner}\n`);
|
|
363
587
|
}
|
|
364
588
|
// ── CLI progress bar setup ─────────────────────────────────────────
|
|
365
|
-
const
|
|
589
|
+
const barOptions = {
|
|
366
590
|
format: ' {bar} {percentage}% | {phase}',
|
|
367
591
|
barCompleteChar: '\u2588',
|
|
368
592
|
barIncompleteChar: '\u2591',
|
|
@@ -371,7 +595,16 @@ const analyzeCommandImpl = async (inputPath, options) => {
|
|
|
371
595
|
autopadding: true,
|
|
372
596
|
clearOnComplete: false,
|
|
373
597
|
stopOnComplete: false,
|
|
374
|
-
}
|
|
598
|
+
};
|
|
599
|
+
if (process.env[RESPAWN_PROGRESS_ENV] === '1' && process.stderr.isTTY !== true) {
|
|
600
|
+
// Heap respawn pipes stderr so the parent can classify native/OOM crashes.
|
|
601
|
+
// The parent was a real TTY when it opted into this env var, so forward
|
|
602
|
+
// ANSI cursor controls through the pipe instead of cli-progress' non-TTY
|
|
603
|
+
// newline mode. That keeps one-line redraw UX while retaining stderr tail
|
|
604
|
+
// capture for diagnostics.
|
|
605
|
+
barOptions.terminal = createAnsiPipeTerminal(process.stderr);
|
|
606
|
+
}
|
|
607
|
+
const bar = new cliProgress.SingleBar(barOptions, cliProgress.Presets.shades_grey);
|
|
375
608
|
bar.start(100, 0, { phase: 'Initializing...' });
|
|
376
609
|
// Graceful SIGINT handling. Pino's default destination is `sync: false`
|
|
377
610
|
// (buffered) — flush before exit so in-flight records reach stderr.
|
|
@@ -413,6 +646,7 @@ const analyzeCommandImpl = async (inputPath, options) => {
|
|
|
413
646
|
console.warn = barLog;
|
|
414
647
|
// eslint-disable-next-line no-console -- intentional console-routing for progress bar UX
|
|
415
648
|
console.error = barLog;
|
|
649
|
+
process.env.GITNEXUS_ANALYZE_PROGRESS_ACTIVE = '1';
|
|
416
650
|
// Track elapsed time per phase
|
|
417
651
|
let lastPhaseLabel = 'Initializing...';
|
|
418
652
|
let phaseStart = Date.now();
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optional grammar availability check.
|
|
3
3
|
*
|
|
4
|
-
* tree-sitter-dart and tree-sitter-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* tree-sitter-dart, tree-sitter-proto, and tree-sitter-swift are vendored
|
|
5
|
+
* under vendor/ and materialized into node_modules/ at postinstall. Dart
|
|
6
|
+
* and Proto are built from source with node-gyp; Swift ships platform
|
|
7
|
+
* prebuilds activated via node-gyp-build. All three can be skipped via
|
|
8
|
+
* GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
|
|
9
|
+
* soft-fail when the toolchain is missing (Dart/Proto) or no prebuild
|
|
10
|
+
* matches the host platform (Swift).
|
|
8
11
|
*
|
|
9
12
|
* Either path produces the same observable: the .node binding is absent
|
|
10
13
|
* at runtime. This helper detects that condition and surfaces a single
|
|
11
|
-
* stderr line per missing grammar so users learn why .dart/.proto
|
|
12
|
-
* is unavailable instead of silently getting a degraded index.
|
|
14
|
+
* stderr line per missing grammar so users learn why .dart/.proto/.swift
|
|
15
|
+
* support is unavailable instead of silently getting a degraded index.
|
|
13
16
|
*/
|
|
14
17
|
export interface MissingGrammar {
|
|
15
18
|
name: string;
|
|
@@ -19,8 +22,8 @@ export interface MissingGrammar {
|
|
|
19
22
|
* Returns the list of optional grammars whose native binding cannot be
|
|
20
23
|
* loaded. Actually `require()`s the package — `require.resolve` would
|
|
21
24
|
* locate the entry path even when the `.node` binding is absent (the
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* package directory exists without a working `.node` binding), giving false
|
|
26
|
+
* negatives for the exact users we want to warn:
|
|
24
27
|
* those who installed with `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` or whose
|
|
25
28
|
* native rebuild soft-failed for missing toolchain.
|
|
26
29
|
*
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optional grammar availability check.
|
|
3
3
|
*
|
|
4
|
-
* tree-sitter-dart and tree-sitter-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* tree-sitter-dart, tree-sitter-proto, and tree-sitter-swift are vendored
|
|
5
|
+
* under vendor/ and materialized into node_modules/ at postinstall. Dart
|
|
6
|
+
* and Proto are built from source with node-gyp; Swift ships platform
|
|
7
|
+
* prebuilds activated via node-gyp-build. All three can be skipped via
|
|
8
|
+
* GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
|
|
9
|
+
* soft-fail when the toolchain is missing (Dart/Proto) or no prebuild
|
|
10
|
+
* matches the host platform (Swift).
|
|
8
11
|
*
|
|
9
12
|
* Either path produces the same observable: the .node binding is absent
|
|
10
13
|
* at runtime. This helper detects that condition and surfaces a single
|
|
11
|
-
* stderr line per missing grammar so users learn why .dart/.proto
|
|
12
|
-
* is unavailable instead of silently getting a degraded index.
|
|
14
|
+
* stderr line per missing grammar so users learn why .dart/.proto/.swift
|
|
15
|
+
* support is unavailable instead of silently getting a degraded index.
|
|
13
16
|
*/
|
|
14
17
|
import { createRequire } from 'module';
|
|
15
18
|
import { cliWarn } from './cli-message.js';
|
|
@@ -17,13 +20,14 @@ const _require = createRequire(import.meta.url);
|
|
|
17
20
|
const OPTIONAL_GRAMMARS = [
|
|
18
21
|
{ name: 'tree-sitter-dart', pkg: 'tree-sitter-dart', extensions: ['.dart'] },
|
|
19
22
|
{ name: 'tree-sitter-proto', pkg: 'tree-sitter-proto', extensions: ['.proto'] },
|
|
23
|
+
{ name: 'tree-sitter-swift', pkg: 'tree-sitter-swift', extensions: ['.swift'] },
|
|
20
24
|
];
|
|
21
25
|
/**
|
|
22
26
|
* Returns the list of optional grammars whose native binding cannot be
|
|
23
27
|
* loaded. Actually `require()`s the package — `require.resolve` would
|
|
24
28
|
* locate the entry path even when the `.node` binding is absent (the
|
|
25
|
-
*
|
|
26
|
-
*
|
|
29
|
+
* package directory exists without a working `.node` binding), giving false
|
|
30
|
+
* negatives for the exact users we want to warn:
|
|
27
31
|
* those who installed with `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` or whose
|
|
28
32
|
* native rebuild soft-failed for missing toolchain.
|
|
29
33
|
*
|
|
@@ -6,6 +6,20 @@ import { glob } from 'glob';
|
|
|
6
6
|
import { createIgnoreFilter } from '../../config/ignore-service.js';
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
8
|
const READ_CONCURRENCY = 32;
|
|
9
|
+
const ANALYZE_PROGRESS_ACTIVE_ENV = 'GITNEXUS_ANALYZE_PROGRESS_ACTIVE';
|
|
10
|
+
const warnLargeFileSkip = (message) => {
|
|
11
|
+
if (process.env[ANALYZE_PROGRESS_ACTIVE_ENV] === '1') {
|
|
12
|
+
// analyze.ts routes console.warn through the progress bar logger while
|
|
13
|
+
// the bar is active. Emitting the operator-facing large-file notice there
|
|
14
|
+
// avoids raw pino NDJSON corrupting the one-line progress display in the
|
|
15
|
+
// heap-respawn child, whose stderr is intentionally piped for crash
|
|
16
|
+
// classification.
|
|
17
|
+
// eslint-disable-next-line no-console -- intentionally routed by analyze progress UI
|
|
18
|
+
console.warn(message);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
logger.warn(message);
|
|
22
|
+
};
|
|
9
23
|
/**
|
|
10
24
|
* Phase 1: Scan repository — stat files to get paths + sizes, no content loaded.
|
|
11
25
|
* Memory: ~10MB for 100K files vs ~1GB+ with content.
|
|
@@ -50,7 +64,7 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
|
|
|
50
64
|
const isDefault = maxFileSizeBytes === DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
51
65
|
const isOverrideUnset = !process.env.GITNEXUS_MAX_FILE_SIZE;
|
|
52
66
|
const suffix = isDefault ? ', likely generated/vendored' : '';
|
|
53
|
-
|
|
67
|
+
warnLargeFileSkip(` Skipped ${skippedLarge} large files (>${maxFileSizeBytes / 1024}KB${suffix})`);
|
|
54
68
|
// Always show at least the first few paths so users can diagnose why
|
|
55
69
|
// edges are missing from a specific file (issue #1659). The full list is
|
|
56
70
|
// gated behind GITNEXUS_VERBOSE=1 to avoid flooding output on repos with
|
|
@@ -61,17 +75,17 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
|
|
|
61
75
|
const showAll = isVerboseIngestionEnabled() || skippedLargePaths.length <= SKIPPED_PREVIEW_CAP;
|
|
62
76
|
const preview = showAll ? skippedLargePaths : skippedLargePaths.slice(0, SKIPPED_PREVIEW_CAP);
|
|
63
77
|
for (const p of preview) {
|
|
64
|
-
|
|
78
|
+
warnLargeFileSkip(` - ${p}`);
|
|
65
79
|
}
|
|
66
80
|
if (!showAll) {
|
|
67
81
|
const remaining = skippedLargePaths.length - SKIPPED_PREVIEW_CAP;
|
|
68
|
-
|
|
82
|
+
warnLargeFileSkip(` ...and ${remaining} more (set GITNEXUS_VERBOSE=1 to list them all)`);
|
|
69
83
|
}
|
|
70
84
|
// Only hint about the env var when the user has not set it at all. An
|
|
71
85
|
// explicit GITNEXUS_MAX_FILE_SIZE=512 happens to resolve to the same
|
|
72
86
|
// bytes as the default but the operator clearly already knows the knob.
|
|
73
87
|
if (isDefault && isOverrideUnset) {
|
|
74
|
-
|
|
88
|
+
warnLargeFileSkip(` Set GITNEXUS_MAX_FILE_SIZE=<KB> to include files above the default cap.`);
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
return entries;
|
|
@@ -7,7 +7,7 @@ import { extractVueScript, isVueSetupTopLevel } from './vue-sfc-extractor.js';
|
|
|
7
7
|
import { yieldToEventLoop } from './utils/event-loop.js';
|
|
8
8
|
import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
|
|
9
9
|
import { isVerboseIngestionEnabled } from './utils/verbose.js';
|
|
10
|
-
import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCaptures, CLASS_CONTAINER_TYPES, } from './utils/ast-helpers.js';
|
|
10
|
+
import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, getLabelFromCaptures, CLASS_CONTAINER_TYPES, } from './utils/ast-helpers.js';
|
|
11
11
|
import { detectFrameworkFromAST } from './framework-detection.js';
|
|
12
12
|
import { buildTypeEnv } from './type-env.js';
|
|
13
13
|
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from './utils/method-props.js';
|
|
@@ -400,6 +400,9 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
400
400
|
? cachedFindEnclosingClassInfo(nameNode || definitionNodeForRange, file.path, provider.resolveEnclosingOwner)
|
|
401
401
|
: null;
|
|
402
402
|
const enclosingClassId = enclosingClassInfo?.classId ?? null;
|
|
403
|
+
const objectLiteralOwnerInfo = !enclosingClassId && nodeLabel === 'Method' && definitionNode
|
|
404
|
+
? findObjectLiteralBindingInfo(definitionNode, file.path)
|
|
405
|
+
: null;
|
|
403
406
|
// Qualify method/property IDs with enclosing class name to avoid collisions
|
|
404
407
|
// e.g. "Method:animal.dart:Animal.speak" vs "Method:animal.dart:Dog.speak"
|
|
405
408
|
const qualifiedName = enclosingClassInfo
|
|
@@ -615,7 +618,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
615
618
|
returnType: methodProps.returnType,
|
|
616
619
|
declaredType,
|
|
617
620
|
templateArguments: classTemplateArguments,
|
|
618
|
-
ownerId: enclosingClassId ?? undefined,
|
|
621
|
+
ownerId: enclosingClassId ?? objectLiteralOwnerInfo?.ownerId ?? undefined,
|
|
619
622
|
qualifiedName: qualifiedTypeName,
|
|
620
623
|
});
|
|
621
624
|
const fileId = generateId('File', file.path);
|
|
@@ -630,15 +633,18 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
630
633
|
};
|
|
631
634
|
graph.addRelationship(relationship);
|
|
632
635
|
// ── HAS_METHOD / HAS_PROPERTY: link member to enclosing class ──
|
|
633
|
-
|
|
636
|
+
const ownerIdForMemberEdge = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId ?? null;
|
|
637
|
+
if (ownerIdForMemberEdge) {
|
|
634
638
|
const memberEdgeType = nodeLabel === 'Property' ? 'HAS_PROPERTY' : 'HAS_METHOD';
|
|
635
639
|
graph.addRelationship({
|
|
636
|
-
id: generateId(memberEdgeType, `${
|
|
637
|
-
sourceId:
|
|
640
|
+
id: generateId(memberEdgeType, `${ownerIdForMemberEdge}->${nodeId}`),
|
|
641
|
+
sourceId: ownerIdForMemberEdge,
|
|
638
642
|
targetId: nodeId,
|
|
639
643
|
type: memberEdgeType,
|
|
640
644
|
confidence: 1.0,
|
|
641
|
-
reason:
|
|
645
|
+
reason: objectLiteralOwnerInfo
|
|
646
|
+
? 'object literal method belongs to exported object binding'
|
|
647
|
+
: '',
|
|
642
648
|
});
|
|
643
649
|
}
|
|
644
650
|
});
|