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.
Files changed (67) hide show
  1. package/dist/cli/analyze.js +255 -21
  2. package/dist/cli/optional-grammars.d.ts +11 -8
  3. package/dist/cli/optional-grammars.js +12 -8
  4. package/dist/core/ingestion/filesystem-walker.js +18 -4
  5. package/dist/core/ingestion/parsing-processor.js +12 -6
  6. package/dist/core/ingestion/pipeline-phases/parse-impl.js +61 -24
  7. package/dist/core/ingestion/scope-extractor.js +7 -1
  8. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +21 -0
  9. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +37 -0
  10. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +7 -1
  11. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +5 -0
  12. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +50 -2
  13. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +28 -0
  14. package/dist/core/ingestion/scope-resolution/scope/walkers.js +75 -23
  15. package/dist/core/ingestion/utils/ast-helpers.d.ts +30 -0
  16. package/dist/core/ingestion/utils/ast-helpers.js +99 -0
  17. package/dist/core/ingestion/workers/parse-worker.js +12 -6
  18. package/dist/core/ingestion/workers/worker-pool.d.ts +4 -0
  19. package/dist/core/ingestion/workers/worker-pool.js +14 -1
  20. package/dist/core/lbug/lbug-adapter.js +183 -39
  21. package/dist/core/lbug/pool-adapter.js +137 -1
  22. package/dist/core/lbug/sidecar-recovery.d.ts +68 -0
  23. package/dist/core/lbug/sidecar-recovery.js +277 -0
  24. package/dist/server/api.js +5 -0
  25. package/package.json +3 -6
  26. package/scripts/build-tree-sitter-dart.cjs +4 -0
  27. package/scripts/build-tree-sitter-proto.cjs +3 -4
  28. package/scripts/build-tree-sitter-swift.cjs +39 -0
  29. package/scripts/build.js +20 -4
  30. package/scripts/materialize-vendor-grammars.cjs +72 -0
  31. package/vendor/tree-sitter-dart/package.json +1 -1
  32. package/vendor/tree-sitter-proto/package.json +1 -1
  33. package/vendor/tree-sitter-swift/package.json +1 -8
  34. package/web/assets/{agent-Warpie3K.js → agent-CBcds30d.js} +101 -76
  35. package/web/assets/{architectureDiagram-UL44E2DR-BVMgctd1.js → architectureDiagram-UL44E2DR-dIoPPr6x.js} +1 -1
  36. package/web/assets/{chunk-LCXTWHL2-D0Ojsf12.js → chunk-LCXTWHL2-B8hbjKUm.js} +1 -1
  37. package/web/assets/{chunk-RG4AUYOV-CGOYR7mA.js → chunk-RG4AUYOV-EfsAenro.js} +1 -1
  38. package/web/assets/{classDiagram-KGZ6W3CR-BWNNFh9P.js → classDiagram-KGZ6W3CR-_hSUwNQJ.js} +1 -1
  39. package/web/assets/{classDiagram-v2-72OJOZXJ-D6den_PS.js → classDiagram-v2-72OJOZXJ-C0NcgLqj.js} +1 -1
  40. package/web/assets/{diagram-3NCE3AQN-fpPDz4AJ.js → diagram-3NCE3AQN-CYrNJJUh.js} +1 -1
  41. package/web/assets/{diagram-GF46GFSD-CBCK4dz-.js → diagram-GF46GFSD-56NpS1jw.js} +1 -1
  42. package/web/assets/{diagram-QXG6HAR7-Dp7brFCQ.js → diagram-QXG6HAR7-DwXkFq_r.js} +1 -1
  43. package/web/assets/{diagram-WEQXMOUZ-CcNoPnrZ.js → diagram-WEQXMOUZ-C6BTq9za.js} +1 -1
  44. package/web/assets/{erDiagram-L5TCEMPS-C7gqztHL.js → erDiagram-L5TCEMPS-BcEjYsUQ.js} +1 -1
  45. package/web/assets/{flowDiagram-H6V6AXG4-AhdoRoMl.js → flowDiagram-H6V6AXG4-DWAVIV6V.js} +1 -1
  46. package/web/assets/{index-DHb4KmNb.js → index-Czp-OFT-.js} +5 -5
  47. package/web/assets/index-nSZgUaIx.css +2 -0
  48. package/web/assets/{infoDiagram-3YFTVSEB-mP4ELRFj.js → infoDiagram-3YFTVSEB-ui-e52GZ.js} +1 -1
  49. package/web/assets/{ishikawaDiagram-BNXS4ZKH-CPiYSQcK.js → ishikawaDiagram-BNXS4ZKH-DGimV4zg.js} +1 -1
  50. package/web/assets/{kanban-definition-75IXJCU3-EI3ocXjG.js → kanban-definition-75IXJCU3-BOyfgvKL.js} +1 -1
  51. package/web/assets/{mindmap-definition-2TDM6QVE-293vIByN.js → mindmap-definition-2TDM6QVE-Ba3QrYSU.js} +1 -1
  52. package/web/assets/{pieDiagram-CU6KROY3-DhdftcYl.js → pieDiagram-CU6KROY3-DMFBXNrM.js} +1 -1
  53. package/web/assets/{requirementDiagram-JXO7QTGE-DdPq7kYS.js → requirementDiagram-JXO7QTGE-bS4xboSz.js} +1 -1
  54. package/web/assets/{sequenceDiagram-VS2MUI6T-CuIcQieZ.js → sequenceDiagram-VS2MUI6T-BqKET_2i.js} +1 -1
  55. package/web/assets/{stateDiagram-7D4R322I-CjVShw_t.js → stateDiagram-7D4R322I-DP9kvX2i.js} +1 -1
  56. package/web/assets/{stateDiagram-v2-36443NZ5-D9CkWeqa.js → stateDiagram-v2-36443NZ5-DB-cZ1VL.js} +1 -1
  57. package/web/assets/{timeline-definition-O6YCAMPW-Dzybzo6D.js → timeline-definition-O6YCAMPW-DNScSOi7.js} +1 -1
  58. package/web/assets/{vennDiagram-MWXL3ELB-CapSiCmK.js → vennDiagram-MWXL3ELB-Bd1zTNWW.js} +1 -1
  59. package/web/assets/{wardleyDiagram-CUQ6CDDI-CohjGBRu.js → wardleyDiagram-CUQ6CDDI-DqCDQKFt.js} +1 -1
  60. package/web/assets/{xychartDiagram-N2JHSOCM-CiYjF6Jz.js → xychartDiagram-N2JHSOCM-B8Cje_Ei.js} +1 -1
  61. package/web/index.html +2 -2
  62. package/vendor/node_modules/node-addon-api/node_addon_api.Makefile +0 -6
  63. package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +0 -106
  64. package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +0 -110
  65. package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +0 -106
  66. package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +0 -106
  67. package/web/assets/index-BleGLU8S.css +0 -2
@@ -8,7 +8,7 @@
8
8
  * skill generation (--skills), summary output, and process.exit().
9
9
  */
10
10
  import path from 'path';
11
- import { execFileSync } from 'child_process';
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
- * stable heap-exhaustion phrases in stderr/message across Linux/macOS/Windows
75
- * (for example "JavaScript heap out of memory" or "Reached heap limit"),
76
- * while some environments only expose status/signal (e.g. 134/SIGABRT).
77
- * We combine both text signatures and process-exit signatures.
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
- try {
123
- execFileSync(process.execPath, [...cliFlags, ...process.argv.slice(1)], {
124
- stdio: 'inherit',
125
- env: { ...process.env, NODE_OPTIONS: `${nodeOpts} ${HEAP_FLAG}`.trim() },
126
- });
127
- }
128
- catch (e) {
129
- if (childProcessLikelyOom(e)) {
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
- const status = typeof e === 'object' && e !== null && 'status' in e && typeof e.status === 'number'
137
- ? e.status
138
- : 1;
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 bar = new cliProgress.SingleBar({
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
- }, cliProgress.Presets.shades_grey);
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-proto are optionalDependencies that
5
- * require a `node-gyp rebuild` at install time. The build can be skipped
6
- * via GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or it can
7
- * silently soft-fail when the C++ toolchain is missing.
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 support
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
- * `file:` package directory is installed regardless of postinstall
23
- * outcome), giving false negatives for the exact users we want to warn:
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-proto are optionalDependencies that
5
- * require a `node-gyp rebuild` at install time. The build can be skipped
6
- * via GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or it can
7
- * silently soft-fail when the C++ toolchain is missing.
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 support
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
- * `file:` package directory is installed regardless of postinstall
26
- * outcome), giving false negatives for the exact users we want to warn:
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
- logger.warn(` Skipped ${skippedLarge} large files (>${maxFileSizeBytes / 1024}KB${suffix})`);
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
- logger.warn(` - ${p}`);
78
+ warnLargeFileSkip(` - ${p}`);
65
79
  }
66
80
  if (!showAll) {
67
81
  const remaining = skippedLargePaths.length - SKIPPED_PREVIEW_CAP;
68
- logger.warn(` ...and ${remaining} more (set GITNEXUS_VERBOSE=1 to list them all)`);
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
- logger.warn(` Set GITNEXUS_MAX_FILE_SIZE=<KB> to include files above the default cap.`);
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
- if (enclosingClassId) {
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, `${enclosingClassId}->${nodeId}`),
637
- sourceId: enclosingClassId,
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
  });