ucn 3.8.13 → 3.8.15
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/.claude/skills/ucn/SKILL.md +3 -1
- package/.github/workflows/ci.yml +13 -1
- package/README.md +1 -0
- package/cli/index.js +165 -246
- package/core/analysis.js +1400 -0
- package/core/build-worker.js +194 -0
- package/core/cache.js +105 -7
- package/core/callers.js +194 -64
- package/core/deadcode.js +22 -66
- package/core/discovery.js +9 -54
- package/core/execute.js +139 -54
- package/core/graph.js +615 -0
- package/core/imports.js +50 -16
- package/core/output/analysis-ext.js +271 -0
- package/core/output/analysis.js +491 -0
- package/core/output/extraction.js +188 -0
- package/core/output/find.js +355 -0
- package/core/output/graph.js +399 -0
- package/core/output/refactoring.js +293 -0
- package/core/output/reporting.js +331 -0
- package/core/output/search.js +307 -0
- package/core/output/shared.js +271 -0
- package/core/output/tracing.js +416 -0
- package/core/output.js +15 -3293
- package/core/parallel-build.js +165 -0
- package/core/project.js +299 -3633
- package/core/registry.js +59 -0
- package/core/reporting.js +258 -0
- package/core/search.js +890 -0
- package/core/stacktrace.js +1 -1
- package/core/tracing.js +631 -0
- package/core/verify.js +10 -13
- package/eslint.config.js +43 -0
- package/jsconfig.json +10 -0
- package/languages/go.js +21 -2
- package/languages/html.js +8 -0
- package/languages/index.js +102 -40
- package/languages/java.js +13 -0
- package/languages/javascript.js +17 -1
- package/languages/python.js +14 -0
- package/languages/rust.js +13 -0
- package/languages/utils.js +1 -1
- package/mcp/server.js +45 -28
- package/package.json +8 -3
package/core/discovery.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const { langTraits } = require('../languages');
|
|
9
10
|
|
|
10
11
|
// Always ignore - unambiguous, never user code
|
|
11
12
|
const DEFAULT_IGNORES = [
|
|
@@ -504,46 +505,10 @@ function findTestFileFor(sourceFile, language) {
|
|
|
504
505
|
const ext = path.extname(sourceFile);
|
|
505
506
|
const base = path.basename(sourceFile, ext);
|
|
506
507
|
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
case 'typescript':
|
|
512
|
-
case 'tsx':
|
|
513
|
-
candidates.push(
|
|
514
|
-
`${base}.test${ext}`,
|
|
515
|
-
`${base}.spec${ext}`,
|
|
516
|
-
`${base}.test.ts`,
|
|
517
|
-
`${base}.test.js`,
|
|
518
|
-
`${base}.spec.ts`,
|
|
519
|
-
`${base}.spec.js`
|
|
520
|
-
);
|
|
521
|
-
break;
|
|
522
|
-
case 'python':
|
|
523
|
-
candidates.push(
|
|
524
|
-
`test_${base}.py`,
|
|
525
|
-
`${base}_test.py`
|
|
526
|
-
);
|
|
527
|
-
break;
|
|
528
|
-
case 'go':
|
|
529
|
-
candidates.push(`${base}_test.go`);
|
|
530
|
-
break;
|
|
531
|
-
case 'java':
|
|
532
|
-
candidates.push(
|
|
533
|
-
`${base}Test.java`,
|
|
534
|
-
`${base}Tests.java`,
|
|
535
|
-
`${base}TestCase.java`
|
|
536
|
-
);
|
|
537
|
-
break;
|
|
538
|
-
case 'rust':
|
|
539
|
-
candidates.push(`${base}_test.rs`);
|
|
540
|
-
break;
|
|
541
|
-
default:
|
|
542
|
-
candidates.push(
|
|
543
|
-
`${base}.test${ext}`,
|
|
544
|
-
`${base}.spec${ext}`
|
|
545
|
-
);
|
|
546
|
-
}
|
|
508
|
+
const traits = langTraits(language);
|
|
509
|
+
const candidates = traits?.testFileCandidates
|
|
510
|
+
? traits.testFileCandidates(base, ext)
|
|
511
|
+
: [`${base}.test${ext}`, `${base}.spec${ext}`];
|
|
547
512
|
|
|
548
513
|
// Check in same directory
|
|
549
514
|
for (const candidate of candidates) {
|
|
@@ -553,20 +518,10 @@ function findTestFileFor(sourceFile, language) {
|
|
|
553
518
|
}
|
|
554
519
|
}
|
|
555
520
|
|
|
556
|
-
// Check in
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const testPath = path.join(testsDir, candidate);
|
|
561
|
-
if (fs.existsSync(testPath)) {
|
|
562
|
-
return testPath;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Check in tests subdirectory
|
|
568
|
-
if (language === 'python' || language === 'rust') {
|
|
569
|
-
const testsDir = path.join(dir, 'tests');
|
|
521
|
+
// Check in language-specific test directories
|
|
522
|
+
const testDirs = traits?.testDirs || [];
|
|
523
|
+
for (const testDir of testDirs) {
|
|
524
|
+
const testsDir = path.join(dir, testDir);
|
|
570
525
|
for (const candidate of candidates) {
|
|
571
526
|
const testPath = path.join(testsDir, candidate);
|
|
572
527
|
if (fs.existsSync(testPath)) {
|
package/core/execute.js
CHANGED
|
@@ -86,6 +86,21 @@ function applyTestExclusions(exclude, includeTests) {
|
|
|
86
86
|
return includeTests ? arr : addTestExclusions(arr);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Build common caller/callee analysis options from handler params.
|
|
91
|
+
* Used by about, context, blast, reverseTrace, smart, trace, affectedTests.
|
|
92
|
+
*/
|
|
93
|
+
function buildCallerOptions(p) {
|
|
94
|
+
return {
|
|
95
|
+
file: p.file,
|
|
96
|
+
className: p.className,
|
|
97
|
+
includeMethods: p.includeMethods,
|
|
98
|
+
includeUncertain: p.includeUncertain || false,
|
|
99
|
+
exclude: toExcludeArray(p.exclude),
|
|
100
|
+
minConfidence: num(p.minConfidence, 0),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
/** Check if a file-based result has a file error. */
|
|
90
105
|
function checkFileError(result, file) {
|
|
91
106
|
if (!result) return null;
|
|
@@ -140,6 +155,27 @@ function limitNote(limit, total) {
|
|
|
140
155
|
return `Showing ${limit} of ${total} results. Use --limit N to see more.`;
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
/** Build a truncation warning when index is incomplete */
|
|
159
|
+
function truncationNote(index) {
|
|
160
|
+
if (!index.truncated) return null;
|
|
161
|
+
return `Index limited to ${index.truncated.indexed} files (max ${index.truncated.maxFiles}). Results may be incomplete. Use --max-files N to increase.`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Build notes for tree-based results (blast, trace, reverseTrace, affectedTests). */
|
|
165
|
+
function treeNote(result) {
|
|
166
|
+
const parts = [];
|
|
167
|
+
if (result?.warnings?.length > 0) {
|
|
168
|
+
for (const w of result.warnings) parts.push(w.message || w);
|
|
169
|
+
}
|
|
170
|
+
if (result?.tree?.truncatedChildren > 0) {
|
|
171
|
+
parts.push(`${result.tree.truncatedChildren} children truncated. Use --depth=N or --all to expand.`);
|
|
172
|
+
}
|
|
173
|
+
if (result?.truncatedCallers > 0) {
|
|
174
|
+
parts.push(`${result.truncatedCallers} callers truncated. Use --all to expand.`);
|
|
175
|
+
}
|
|
176
|
+
return parts.length > 0 ? parts.join('\n') : null;
|
|
177
|
+
}
|
|
178
|
+
|
|
143
179
|
/**
|
|
144
180
|
* Check if a --file pattern matches any files in the index.
|
|
145
181
|
* Returns error string if no files match, null otherwise.
|
|
@@ -190,16 +226,11 @@ const HANDLERS = {
|
|
|
190
226
|
if (err) return { ok: false, error: err };
|
|
191
227
|
applyClassMethodSyntax(p);
|
|
192
228
|
const result = index.about(p.name, {
|
|
229
|
+
...buildCallerOptions(p),
|
|
193
230
|
withTypes: p.withTypes || false,
|
|
194
|
-
file: p.file,
|
|
195
|
-
className: p.className,
|
|
196
231
|
all: p.all,
|
|
197
|
-
includeMethods: p.includeMethods,
|
|
198
|
-
includeUncertain: p.includeUncertain || false,
|
|
199
|
-
exclude: toExcludeArray(p.exclude),
|
|
200
232
|
maxCallers: num(p.top, undefined),
|
|
201
233
|
maxCallees: num(p.top, undefined),
|
|
202
|
-
minConfidence: num(p.minConfidence, 0),
|
|
203
234
|
});
|
|
204
235
|
if (!result) {
|
|
205
236
|
// Give better error if file/className filter is the problem
|
|
@@ -218,7 +249,8 @@ const HANDLERS = {
|
|
|
218
249
|
}
|
|
219
250
|
return { ok: false, error: `Symbol "${p.name}" not found.` };
|
|
220
251
|
}
|
|
221
|
-
|
|
252
|
+
const tNote = truncationNote(index);
|
|
253
|
+
return { ok: true, result, showConfidence: !!p.showConfidence, ...(tNote && { note: tNote }) };
|
|
222
254
|
},
|
|
223
255
|
|
|
224
256
|
context: (index, p) => {
|
|
@@ -230,15 +262,11 @@ const HANDLERS = {
|
|
|
230
262
|
const classErr = validateClassName(index, p.name, p.className);
|
|
231
263
|
if (classErr) return { ok: false, error: classErr };
|
|
232
264
|
const result = index.context(p.name, {
|
|
233
|
-
|
|
234
|
-
includeUncertain: p.includeUncertain || false,
|
|
235
|
-
file: p.file,
|
|
236
|
-
className: p.className,
|
|
237
|
-
exclude: toExcludeArray(p.exclude),
|
|
238
|
-
minConfidence: num(p.minConfidence, 0),
|
|
265
|
+
...buildCallerOptions(p),
|
|
239
266
|
});
|
|
240
267
|
if (!result) return { ok: false, error: `Symbol "${p.name}" not found.` };
|
|
241
|
-
|
|
268
|
+
const tNote = truncationNote(index);
|
|
269
|
+
return { ok: true, result, showConfidence: !!p.showConfidence, ...(tNote && { note: tNote }) };
|
|
242
270
|
},
|
|
243
271
|
|
|
244
272
|
impact: (index, p) => {
|
|
@@ -256,7 +284,8 @@ const HANDLERS = {
|
|
|
256
284
|
top: num(p.top, undefined),
|
|
257
285
|
});
|
|
258
286
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
259
|
-
|
|
287
|
+
const tNote = truncationNote(index);
|
|
288
|
+
return { ok: true, result, ...(tNote && { note: tNote }) };
|
|
260
289
|
},
|
|
261
290
|
|
|
262
291
|
blast: (index, p) => {
|
|
@@ -269,16 +298,15 @@ const HANDLERS = {
|
|
|
269
298
|
if (classErr) return { ok: false, error: classErr };
|
|
270
299
|
const depthVal = num(p.depth, undefined);
|
|
271
300
|
const result = index.blast(p.name, {
|
|
301
|
+
...buildCallerOptions(p),
|
|
272
302
|
depth: depthVal ?? 3,
|
|
273
|
-
file: p.file,
|
|
274
|
-
className: p.className,
|
|
275
303
|
all: p.all || depthVal !== undefined,
|
|
276
|
-
exclude: toExcludeArray(p.exclude),
|
|
277
|
-
includeMethods: p.includeMethods,
|
|
278
|
-
includeUncertain: p.includeUncertain || false,
|
|
279
304
|
});
|
|
280
305
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
281
|
-
|
|
306
|
+
const note = treeNote(result);
|
|
307
|
+
const tNote = truncationNote(index);
|
|
308
|
+
const combined = [note, tNote].filter(Boolean).join('\n') || undefined;
|
|
309
|
+
return { ok: true, result, ...(combined && { note: combined }) };
|
|
282
310
|
},
|
|
283
311
|
|
|
284
312
|
reverseTrace: (index, p) => {
|
|
@@ -291,16 +319,15 @@ const HANDLERS = {
|
|
|
291
319
|
if (classErr) return { ok: false, error: classErr };
|
|
292
320
|
const depthVal = num(p.depth, undefined);
|
|
293
321
|
const result = index.reverseTrace(p.name, {
|
|
322
|
+
...buildCallerOptions(p),
|
|
294
323
|
depth: depthVal ?? 5,
|
|
295
|
-
file: p.file,
|
|
296
|
-
className: p.className,
|
|
297
324
|
all: p.all || depthVal !== undefined,
|
|
298
|
-
exclude: toExcludeArray(p.exclude),
|
|
299
|
-
includeMethods: p.includeMethods,
|
|
300
|
-
includeUncertain: p.includeUncertain || false,
|
|
301
325
|
});
|
|
302
326
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
303
|
-
|
|
327
|
+
const note = treeNote(result);
|
|
328
|
+
const tNote = truncationNote(index);
|
|
329
|
+
const combined = [note, tNote].filter(Boolean).join('\n') || undefined;
|
|
330
|
+
return { ok: true, result, ...(combined && { note: combined }) };
|
|
304
331
|
},
|
|
305
332
|
|
|
306
333
|
smart: (index, p) => {
|
|
@@ -309,12 +336,11 @@ const HANDLERS = {
|
|
|
309
336
|
applyClassMethodSyntax(p);
|
|
310
337
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
311
338
|
if (fileErr) return { ok: false, error: fileErr };
|
|
339
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
340
|
+
if (classErr) return { ok: false, error: classErr };
|
|
312
341
|
const result = index.smart(p.name, {
|
|
313
|
-
|
|
314
|
-
className: p.className,
|
|
342
|
+
...buildCallerOptions(p),
|
|
315
343
|
withTypes: p.withTypes || false,
|
|
316
|
-
includeMethods: p.includeMethods,
|
|
317
|
-
includeUncertain: p.includeUncertain || false,
|
|
318
344
|
});
|
|
319
345
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
320
346
|
return { ok: true, result };
|
|
@@ -326,17 +352,19 @@ const HANDLERS = {
|
|
|
326
352
|
applyClassMethodSyntax(p);
|
|
327
353
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
328
354
|
if (fileErr) return { ok: false, error: fileErr };
|
|
355
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
356
|
+
if (classErr) return { ok: false, error: classErr };
|
|
329
357
|
const depthVal = num(p.depth, undefined);
|
|
330
358
|
const result = index.trace(p.name, {
|
|
359
|
+
...buildCallerOptions(p),
|
|
331
360
|
depth: depthVal ?? 3,
|
|
332
|
-
file: p.file,
|
|
333
|
-
className: p.className,
|
|
334
361
|
all: p.all || depthVal !== undefined,
|
|
335
|
-
includeMethods: p.includeMethods,
|
|
336
|
-
includeUncertain: p.includeUncertain || false,
|
|
337
362
|
});
|
|
338
363
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
339
|
-
|
|
364
|
+
const note = treeNote(result);
|
|
365
|
+
const tNote = truncationNote(index);
|
|
366
|
+
const combined = [note, tNote].filter(Boolean).join('\n') || undefined;
|
|
367
|
+
return { ok: true, result, ...(combined && { note: combined }) };
|
|
340
368
|
},
|
|
341
369
|
|
|
342
370
|
example: (index, p) => {
|
|
@@ -345,6 +373,8 @@ const HANDLERS = {
|
|
|
345
373
|
applyClassMethodSyntax(p);
|
|
346
374
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
347
375
|
if (fileErr) return { ok: false, error: fileErr };
|
|
376
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
377
|
+
if (classErr) return { ok: false, error: classErr };
|
|
348
378
|
const result = index.example(p.name, { file: p.file, className: p.className });
|
|
349
379
|
if (!result) return { ok: false, error: `No examples found for "${p.name}".` };
|
|
350
380
|
return { ok: true, result };
|
|
@@ -363,7 +393,17 @@ const HANDLERS = {
|
|
|
363
393
|
all: p.all,
|
|
364
394
|
});
|
|
365
395
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
366
|
-
|
|
396
|
+
const parts = [];
|
|
397
|
+
if (result.similarNamesTotal > result.similarNames.length)
|
|
398
|
+
parts.push(`similar names: showing ${result.similarNames.length} of ${result.similarNamesTotal}`);
|
|
399
|
+
if (result.sharedCallersTotal > result.sharedCallers.length)
|
|
400
|
+
parts.push(`shared callers: showing ${result.sharedCallers.length} of ${result.sharedCallersTotal}`);
|
|
401
|
+
if (result.sharedCalleesTotal > result.sharedCallees.length)
|
|
402
|
+
parts.push(`shared callees: showing ${result.sharedCallees.length} of ${result.sharedCalleesTotal}`);
|
|
403
|
+
const relatedNote = parts.length ? `Truncated: ${parts.join(', ')}. Use --all to show all.` : null;
|
|
404
|
+
const tNote = truncationNote(index);
|
|
405
|
+
const combined = [relatedNote, tNote].filter(Boolean).join('\n') || undefined;
|
|
406
|
+
return { ok: true, result, ...(combined && { note: combined }) };
|
|
367
407
|
},
|
|
368
408
|
|
|
369
409
|
// ── Finding Code ────────────────────────────────────────────────────
|
|
@@ -375,6 +415,10 @@ const HANDLERS = {
|
|
|
375
415
|
// Check if --file pattern matches any files
|
|
376
416
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
377
417
|
if (fileErr) return { ok: false, error: fileErr };
|
|
418
|
+
if (p.className) {
|
|
419
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
420
|
+
if (classErr) return { ok: false, error: classErr };
|
|
421
|
+
}
|
|
378
422
|
// Auto-include tests when pattern clearly targets test functions
|
|
379
423
|
// But only if the user didn't explicitly set include_tests=false
|
|
380
424
|
let includeTests = p.includeTests;
|
|
@@ -401,6 +445,8 @@ const HANDLERS = {
|
|
|
401
445
|
if (limited) notes.push(limitNote(limit, total));
|
|
402
446
|
result = items;
|
|
403
447
|
}
|
|
448
|
+
const tNote = truncationNote(index);
|
|
449
|
+
if (tNote) notes.push(tNote);
|
|
404
450
|
return { ok: true, result, note: notes.length ? notes.join('\n') : undefined };
|
|
405
451
|
},
|
|
406
452
|
|
|
@@ -411,6 +457,10 @@ const HANDLERS = {
|
|
|
411
457
|
const exclude = applyTestExclusions(p.exclude, p.includeTests);
|
|
412
458
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
413
459
|
if (fileErr) return { ok: false, error: fileErr };
|
|
460
|
+
if (p.className) {
|
|
461
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
462
|
+
if (classErr) return { ok: false, error: classErr };
|
|
463
|
+
}
|
|
414
464
|
const result = index.usages(p.name, {
|
|
415
465
|
codeOnly: p.codeOnly || false,
|
|
416
466
|
context: num(p.context, 0),
|
|
@@ -431,6 +481,8 @@ const HANDLERS = {
|
|
|
431
481
|
},
|
|
432
482
|
|
|
433
483
|
toc: (index, p) => {
|
|
484
|
+
const fileErr = checkFilePatternMatch(index, p.file);
|
|
485
|
+
if (fileErr) return { ok: false, error: fileErr };
|
|
434
486
|
const result = index.getToc({
|
|
435
487
|
detailed: p.detailed,
|
|
436
488
|
topLevel: p.topLevel,
|
|
@@ -482,10 +534,14 @@ const HANDLERS = {
|
|
|
482
534
|
note = limitNote(limit, totalEntries);
|
|
483
535
|
}
|
|
484
536
|
}
|
|
537
|
+
const tNote = truncationNote(index);
|
|
538
|
+
if (tNote) note = note ? `${note}\n${tNote}` : tNote;
|
|
485
539
|
return { ok: true, result, note };
|
|
486
540
|
},
|
|
487
541
|
|
|
488
542
|
search: (index, p) => {
|
|
543
|
+
const fileErr = checkFilePatternMatch(index, p.file);
|
|
544
|
+
if (fileErr) return { ok: false, error: fileErr };
|
|
489
545
|
// Detect structural search mode: any of these flags triggers index-based search
|
|
490
546
|
const isStructural = p.type || p.param || p.receiver || p.returns || p.decorator || p.exported || p.unused;
|
|
491
547
|
if (isStructural) {
|
|
@@ -527,13 +583,16 @@ const HANDLERS = {
|
|
|
527
583
|
file: p.file,
|
|
528
584
|
});
|
|
529
585
|
if (result.meta) result.meta.testsExcluded = testsExcluded;
|
|
530
|
-
|
|
586
|
+
const tNote = truncationNote(index);
|
|
587
|
+
return { ok: true, result, ...(tNote && { note: tNote }) };
|
|
531
588
|
},
|
|
532
589
|
|
|
533
590
|
tests: (index, p) => {
|
|
534
591
|
const err = requireName(p.name);
|
|
535
592
|
if (err) return { ok: false, error: err };
|
|
536
593
|
applyClassMethodSyntax(p);
|
|
594
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
595
|
+
if (classErr) return { ok: false, error: classErr };
|
|
537
596
|
const result = index.tests(p.name, {
|
|
538
597
|
callsOnly: p.callsOnly || false,
|
|
539
598
|
className: p.className,
|
|
@@ -551,15 +610,14 @@ const HANDLERS = {
|
|
|
551
610
|
if (classErr) return { ok: false, error: classErr };
|
|
552
611
|
const depthVal = num(p.depth, undefined);
|
|
553
612
|
const result = index.affectedTests(p.name, {
|
|
613
|
+
...buildCallerOptions(p),
|
|
554
614
|
depth: depthVal ?? 3,
|
|
555
|
-
file: p.file,
|
|
556
|
-
className: p.className,
|
|
557
|
-
exclude: toExcludeArray(p.exclude),
|
|
558
|
-
includeMethods: p.includeMethods,
|
|
559
|
-
includeUncertain: p.includeUncertain || false,
|
|
560
615
|
});
|
|
561
616
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
562
|
-
|
|
617
|
+
const note = treeNote(result);
|
|
618
|
+
const tNote = truncationNote(index);
|
|
619
|
+
const combined = [note, tNote].filter(Boolean).join('\n') || undefined;
|
|
620
|
+
return { ok: true, result, ...(combined && { note: combined }) };
|
|
563
621
|
},
|
|
564
622
|
|
|
565
623
|
deadcode: (index, p) => {
|
|
@@ -584,6 +642,8 @@ const HANDLERS = {
|
|
|
584
642
|
if (result.excludedDecorated != null) sliced.excludedDecorated = result.excludedDecorated;
|
|
585
643
|
result = sliced;
|
|
586
644
|
}
|
|
645
|
+
const tNote = truncationNote(index);
|
|
646
|
+
if (tNote) note = note ? `${note}\n${tNote}` : tNote;
|
|
587
647
|
return { ok: true, result, note };
|
|
588
648
|
},
|
|
589
649
|
|
|
@@ -591,13 +651,20 @@ const HANDLERS = {
|
|
|
591
651
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
592
652
|
if (fileErr) return { ok: false, error: fileErr };
|
|
593
653
|
const { detectEntrypoints } = require('./entrypoints');
|
|
594
|
-
const
|
|
654
|
+
const exclude = applyTestExclusions(p.exclude, p.includeTests);
|
|
655
|
+
let result = detectEntrypoints(index, {
|
|
595
656
|
type: p.type,
|
|
596
657
|
framework: p.framework,
|
|
597
658
|
file: p.file,
|
|
598
|
-
exclude
|
|
659
|
+
exclude,
|
|
599
660
|
});
|
|
600
|
-
|
|
661
|
+
const limit = num(p.limit, undefined);
|
|
662
|
+
let note;
|
|
663
|
+
if (limit && limit > 0 && Array.isArray(result) && result.length > limit) {
|
|
664
|
+
note = limitNote(limit, result.length);
|
|
665
|
+
result = result.slice(0, limit);
|
|
666
|
+
}
|
|
667
|
+
return { ok: true, result, note };
|
|
601
668
|
},
|
|
602
669
|
|
|
603
670
|
// ── Extracting Code ─────────────────────────────────────────────────
|
|
@@ -608,6 +675,10 @@ const HANDLERS = {
|
|
|
608
675
|
applyClassMethodSyntax(p);
|
|
609
676
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
610
677
|
if (fileErr) return { ok: false, error: fileErr };
|
|
678
|
+
if (p.className) {
|
|
679
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
680
|
+
if (classErr) return { ok: false, error: classErr };
|
|
681
|
+
}
|
|
611
682
|
|
|
612
683
|
const fnNames = p.name.includes(',')
|
|
613
684
|
? p.name.split(',').map(n => n.trim()).filter(Boolean)
|
|
@@ -662,7 +733,7 @@ const HANDLERS = {
|
|
|
662
733
|
if (entries.length === 0 && notes.length > 0) {
|
|
663
734
|
return { ok: false, error: notes.join('\n') };
|
|
664
735
|
}
|
|
665
|
-
return { ok: true, result: { entries, notes
|
|
736
|
+
return { ok: true, result: { entries }, note: notes.length ? notes.map(n => 'Note: ' + n).join('\n') : undefined };
|
|
666
737
|
},
|
|
667
738
|
|
|
668
739
|
class: (index, p) => {
|
|
@@ -693,7 +764,7 @@ const HANDLERS = {
|
|
|
693
764
|
const totalLines = m.endLine - m.startLine + 1;
|
|
694
765
|
entries.push({ match: m, code, totalLines, summaryMode: false, truncated: false });
|
|
695
766
|
}
|
|
696
|
-
return { ok: true, result: { entries, notes
|
|
767
|
+
return { ok: true, result: { entries }, note: notes.length ? notes.map(n => 'Note: ' + n).join('\n') : undefined };
|
|
697
768
|
}
|
|
698
769
|
|
|
699
770
|
const match = matches.length > 1 && !p.file
|
|
@@ -712,7 +783,7 @@ const HANDLERS = {
|
|
|
712
783
|
if (totalLines > 200 && !maxLines) {
|
|
713
784
|
const methods = index.findMethodsForType(match.name);
|
|
714
785
|
entries.push({ match, code: null, methods, totalLines, summaryMode: true, truncated: false });
|
|
715
|
-
return { ok: true, result: { entries, notes
|
|
786
|
+
return { ok: true, result: { entries }, note: notes.length ? notes.map(n => 'Note: ' + n).join('\n') : undefined };
|
|
716
787
|
}
|
|
717
788
|
|
|
718
789
|
// Truncated mode (maxLines specified and class exceeds it)
|
|
@@ -722,13 +793,13 @@ const HANDLERS = {
|
|
|
722
793
|
const truncated = fileLines.slice(match.startLine - 1, match.startLine - 1 + maxLines);
|
|
723
794
|
const code = cleanHtmlScriptTags(truncated, detectLanguage(match.file)).join('\n');
|
|
724
795
|
entries.push({ match, code, totalLines, summaryMode: false, truncated: true, maxLines });
|
|
725
|
-
return { ok: true, result: { entries, notes
|
|
796
|
+
return { ok: true, result: { entries }, note: notes.length ? notes.map(n => 'Note: ' + n).join('\n') : undefined };
|
|
726
797
|
}
|
|
727
798
|
|
|
728
799
|
// Full extraction
|
|
729
800
|
const code = readAndExtract(match);
|
|
730
801
|
entries.push({ match, code, totalLines, summaryMode: false, truncated: false });
|
|
731
|
-
return { ok: true, result: { entries, notes
|
|
802
|
+
return { ok: true, result: { entries }, note: notes.length ? notes.map(n => 'Note: ' + n).join('\n') : undefined };
|
|
732
803
|
},
|
|
733
804
|
|
|
734
805
|
lines: (index, p) => {
|
|
@@ -870,12 +941,18 @@ const HANDLERS = {
|
|
|
870
941
|
},
|
|
871
942
|
|
|
872
943
|
diffImpact: (index, p) => {
|
|
873
|
-
|
|
944
|
+
let result = index.diffImpact({
|
|
874
945
|
base: p.base || 'HEAD',
|
|
875
946
|
staged: p.staged || false,
|
|
876
947
|
file: p.file,
|
|
877
948
|
});
|
|
878
|
-
|
|
949
|
+
const limit = num(p.limit, undefined);
|
|
950
|
+
let note;
|
|
951
|
+
if (limit && limit > 0 && result && result.changed && result.changed.length > limit) {
|
|
952
|
+
note = limitNote(limit, result.changed.length);
|
|
953
|
+
result = { ...result, changed: result.changed.slice(0, limit) };
|
|
954
|
+
}
|
|
955
|
+
return { ok: true, result, note };
|
|
879
956
|
},
|
|
880
957
|
|
|
881
958
|
// ── Other ───────────────────────────────────────────────────────────
|
|
@@ -884,6 +961,10 @@ const HANDLERS = {
|
|
|
884
961
|
const err = requireName(p.name);
|
|
885
962
|
if (err) return { ok: false, error: err };
|
|
886
963
|
applyClassMethodSyntax(p);
|
|
964
|
+
const fileErr = checkFilePatternMatch(index, p.file);
|
|
965
|
+
if (fileErr) return { ok: false, error: fileErr };
|
|
966
|
+
const classErr = validateClassName(index, p.name, p.className);
|
|
967
|
+
if (classErr) return { ok: false, error: classErr };
|
|
887
968
|
const result = index.typedef(p.name, { exact: p.exact || false, className: p.className, file: p.file });
|
|
888
969
|
return { ok: true, result };
|
|
889
970
|
},
|
|
@@ -897,6 +978,10 @@ const HANDLERS = {
|
|
|
897
978
|
},
|
|
898
979
|
|
|
899
980
|
api: (index, p) => {
|
|
981
|
+
if (p.file) {
|
|
982
|
+
const fileErr = checkFilePatternMatch(index, p.file);
|
|
983
|
+
if (fileErr) return { ok: false, error: fileErr };
|
|
984
|
+
}
|
|
900
985
|
let result = index.api(p.file);
|
|
901
986
|
if (p.file) {
|
|
902
987
|
const fileErr = checkFileError(result, p.file);
|