ucn 5.5.2 → 5.6.0
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 +35 -12
- package/.claude/skills/ucn/references/commands.md +21 -17
- package/.claude/skills/ucn/references/resolution-rules.md +4 -0
- package/.claude/skills/ucn/references/trust-contract.md +4 -4
- package/CHANGELOG.md +474 -0
- package/README.md +71 -29
- package/cli/flags.js +184 -0
- package/cli/index.js +138 -324
- package/core/account.js +44 -87
- package/core/analysis.js +273 -107
- package/core/ast-analysis.js +2 -0
- package/core/bridge.js +29 -65
- package/core/cache.js +101 -16
- package/core/callers.js +352 -70
- package/core/capture.js +331 -0
- package/core/change-kinds.js +888 -0
- package/core/check.js +1117 -139
- package/core/client-receivers.js +189 -0
- package/core/command-contracts.js +7 -6
- package/core/constructed-class.js +1195 -0
- package/core/contract-membership.js +142 -0
- package/core/deadcode.js +388 -85
- package/core/deletion.js +401 -0
- package/core/discovery.js +104 -9
- package/core/entrypoints.js +12 -6
- package/core/evidence-bands.js +68 -0
- package/core/evidence-bands.json +44 -0
- package/core/execute.js +172 -161
- package/core/hot-summary.js +241 -0
- package/core/index-ir.js +3 -1
- package/core/inprocess-clients.js +41 -15
- package/core/ir.js +5 -1
- package/core/member-signatures.js +789 -0
- package/core/output/analysis-ext.js +20 -10
- package/core/output/analysis.js +98 -60
- package/core/output/check.js +98 -37
- package/core/output/doctor.js +34 -1
- package/core/output/find.js +2 -1
- package/core/output/lines.js +63 -3
- package/core/output/public.js +30 -11
- package/core/output/refactoring.js +32 -0
- package/core/output/reporting.js +21 -2
- package/core/output/search.js +11 -2
- package/core/output/tracing.js +35 -7
- package/core/output-budget.js +124 -57
- package/core/parallel-build.js +3 -3
- package/core/project.js +106 -23
- package/core/provenance-type.js +12 -5
- package/core/public-command.js +35 -9
- package/core/python-modules.js +6 -0
- package/core/reflection.js +166 -23
- package/core/registry.js +623 -72
- package/core/reporting.js +69 -34
- package/core/request-helpers.js +63 -7
- package/core/review-items.js +75 -0
- package/core/search.js +89 -128
- package/core/shared.js +0 -45
- package/core/snapshot.js +350 -0
- package/core/source-universe.js +707 -0
- package/core/test-units.js +3624 -0
- package/core/tracing.js +150 -96
- package/core/verify.js +27 -11
- package/languages/adapter.js +8 -3
- package/languages/c-family.js +12 -0
- package/languages/csharp.js +634 -0
- package/languages/go.js +120 -0
- package/languages/index.js +422 -4
- package/languages/java.js +712 -0
- package/languages/javascript.js +1460 -2
- package/languages/lexical-scope.js +68 -0
- package/languages/python.js +1672 -1
- package/languages/rust.js +91 -0
- package/mcp/server.js +44 -110
- package/package.json +11 -6
package/core/execute.js
CHANGED
|
@@ -15,7 +15,7 @@ const path = require('path');
|
|
|
15
15
|
const { addTestExclusions, pickBestDefinition, parseSymbolHandle, looksLikeHandle } = require('./shared');
|
|
16
16
|
const { cleanHtmlScriptTags, detectLanguage } = require('./parser');
|
|
17
17
|
const { renderExpandItem } = require('./expand-cache');
|
|
18
|
-
const { CANONICAL_COMMANDS } = require('./registry');
|
|
18
|
+
const { CANONICAL_COMMANDS, FLAG_APPLICABILITY, commandMode, paramDefault, paramTableError, toCliName } = require('./registry');
|
|
19
19
|
const { sourceRangeTarget } = require('./public-command');
|
|
20
20
|
const { isAccessorDefinition } = require('./accessors');
|
|
21
21
|
const { describeError } = require('./errors');
|
|
@@ -24,11 +24,11 @@ const { describeError } = require('./errors');
|
|
|
24
24
|
// HELPERS
|
|
25
25
|
// ============================================================================
|
|
26
26
|
|
|
27
|
+
// A state, said once and short: what the absence never means is in the
|
|
28
|
+
// tool description and the usage guide.
|
|
27
29
|
const NO_STATIC_TEST_LINK_NOTE =
|
|
28
|
-
'No
|
|
29
|
-
'subprocess
|
|
30
|
-
'exercise the target. Inspect the project test runner and search for the defining module/file ' +
|
|
31
|
-
'before concluding it is untested.';
|
|
30
|
+
'No static test link found; this is not runtime coverage evidence ' +
|
|
31
|
+
'(tests that reach the target by subprocess, reflection or generated code are not seen).';
|
|
32
32
|
|
|
33
33
|
function requireName(name) {
|
|
34
34
|
if (!name || (typeof name === 'string' && !name.trim())) {
|
|
@@ -99,17 +99,20 @@ function symbolNotFound(index, name, label = 'Symbol') {
|
|
|
99
99
|
} else {
|
|
100
100
|
message = `${label} "${query}" not found. Use find with a shorter name, or search for a literal occurrence.`;
|
|
101
101
|
}
|
|
102
|
+
// Unanalyzed source is searched for this name; it is mentioned only
|
|
103
|
+
// when the name occurs in it, or when part of the project could not be
|
|
104
|
+
// searched at all.
|
|
105
|
+
const universeTools = require('./source-universe');
|
|
102
106
|
const unsupported = require('./account').scanUnsupportedFiles(index, query);
|
|
103
107
|
if (unsupported.lines > 0) {
|
|
104
|
-
const
|
|
108
|
+
const labels = universeTools.matchLabels(unsupported);
|
|
105
109
|
message += ` ${unsupported.lines} literal match(es) exist in ` +
|
|
106
|
-
`${unsupported.fileCount}
|
|
107
|
-
`${
|
|
108
|
-
|
|
109
|
-
} else if (index.unsupportedFiles?.length > 0) {
|
|
110
|
-
message += ` UCN skipped ${index.unsupportedFiles.length} unsupported source ` +
|
|
111
|
-
`file(s), so this is not a repository-wide semantic zero; verify with grep/ripgrep.`;
|
|
110
|
+
`${unsupported.fileCount} unanalyzed file(s)` +
|
|
111
|
+
`${labels ? ` (${labels})` : ''}; this is not a semantic zero. ` +
|
|
112
|
+
universeTools.matchAdvice(unsupported);
|
|
112
113
|
}
|
|
114
|
+
const unsearched = universeTools.unsearchedNote(index);
|
|
115
|
+
if (unsearched) message += ` ${unsearched}`;
|
|
113
116
|
return message;
|
|
114
117
|
}
|
|
115
118
|
|
|
@@ -213,7 +216,7 @@ function buildCallerOptions(p) {
|
|
|
213
216
|
includeUncertain: p.includeUncertain || false,
|
|
214
217
|
includeTests: p.includeTests,
|
|
215
218
|
exclude: toExcludeArray(p.exclude),
|
|
216
|
-
minConfidence: num(p.minConfidence,
|
|
219
|
+
minConfidence: num(p.minConfidence, paramDefault('minConfidence')),
|
|
217
220
|
};
|
|
218
221
|
}
|
|
219
222
|
|
|
@@ -294,9 +297,12 @@ function num(val, fallback) {
|
|
|
294
297
|
return isNaN(n) ? fallback : n;
|
|
295
298
|
}
|
|
296
299
|
|
|
297
|
-
/**
|
|
298
|
-
|
|
299
|
-
|
|
300
|
+
/**
|
|
301
|
+
* The listing bound of `command`: an explicit limit, else the command's
|
|
302
|
+
* default limit. Shell listings and `all` have no default bound.
|
|
303
|
+
*/
|
|
304
|
+
function listingLimit(p, command) {
|
|
305
|
+
return num(p.limit, undefined) || (p.lines || p.all ? undefined : paramDefault('limit', command));
|
|
300
306
|
}
|
|
301
307
|
|
|
302
308
|
/**
|
|
@@ -421,31 +427,17 @@ function readAndExtract(match) {
|
|
|
421
427
|
return cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
|
|
422
428
|
}
|
|
423
429
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
function parseSections(value, defaults, allowed, command) {
|
|
432
|
-
const supplied = value != null && value !== '';
|
|
430
|
+
/**
|
|
431
|
+
* The sections a `show` or `repo` request selects: the ones it names, else
|
|
432
|
+
* the command's default for its mode. The parameter table has already
|
|
433
|
+
* refused a name outside the command's set.
|
|
434
|
+
*/
|
|
435
|
+
function selectedSections(command, p) {
|
|
436
|
+
const supplied = p.sections != null && p.sections !== '';
|
|
433
437
|
const raw = !supplied
|
|
434
|
-
?
|
|
435
|
-
: (Array.isArray(
|
|
436
|
-
|
|
437
|
-
if (supplied && sections.length === 0) {
|
|
438
|
-
return {
|
|
439
|
-
error: `No valid ${command} sections were provided. Available: ${[...allowed].join(', ')}.`,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
const invalid = sections.filter(s => !allowed.has(s));
|
|
443
|
-
if (invalid.length > 0) {
|
|
444
|
-
return {
|
|
445
|
-
error: `Unknown ${command} section(s): ${invalid.join(', ')}. Available: ${[...allowed].join(', ')}.`,
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
return { sections };
|
|
438
|
+
? paramDefault('sections', command, commandMode(command, p))
|
|
439
|
+
: (Array.isArray(p.sections) ? p.sections : String(p.sections).split(','));
|
|
440
|
+
return [...new Set(raw.map(s => String(s).trim().toLowerCase()).filter(Boolean))];
|
|
449
441
|
}
|
|
450
442
|
|
|
451
443
|
function addMode(result, mode) {
|
|
@@ -471,14 +463,17 @@ const PUBLIC_COMMAND_SET = new Set(CANONICAL_COMMANDS);
|
|
|
471
463
|
* glob, file, and programmatic callers receive the same answer.
|
|
472
464
|
*/
|
|
473
465
|
function validatePublicParams(command, p) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
466
|
+
// The shell modes change the shape of the whole answer: on a command
|
|
467
|
+
// that has none they are refused, not ignored.
|
|
468
|
+
for (const shape of ['raw', 'lines']) {
|
|
469
|
+
if (!p[shape] || FLAG_APPLICABILITY[command].includes(shape)) continue;
|
|
470
|
+
const takers = CANONICAL_COMMANDS.filter(name => FLAG_APPLICABILITY[name].includes(shape)).map(toCliName);
|
|
471
|
+
return takers.length === 1
|
|
472
|
+
? `--${shape} is supported only by ${takers[0]}.`
|
|
473
|
+
: `--${shape} is supported by ${takers.slice(0, -1).join(', ')}, and ${takers.at(-1)}.`;
|
|
481
474
|
}
|
|
475
|
+
const tableError = paramTableError(command, p);
|
|
476
|
+
if (tableError) return tableError;
|
|
482
477
|
const hasName = typeof p.name === 'string' && p.name.trim() !== '';
|
|
483
478
|
const gitScope = p.staged || (typeof p.base === 'string' && p.base.trim() !== '');
|
|
484
479
|
if (command === 'impact' && hasName && gitScope) {
|
|
@@ -504,14 +499,14 @@ function validatePublicParams(command, p) {
|
|
|
504
499
|
return 'search receiver filtering requires type=call (or omit type to select call mode).';
|
|
505
500
|
}
|
|
506
501
|
if (command === 'trace' && p.to === 'entrypoints' &&
|
|
507
|
-
(p.direction || '
|
|
502
|
+
(p.direction || paramDefault('direction', 'trace')) !== 'callers') {
|
|
508
503
|
return 'trace --to=entrypoints requires --direction=callers.';
|
|
509
504
|
}
|
|
510
505
|
if (command === 'deps' && p.cycles &&
|
|
511
506
|
(p.file || p.direction || p.depth != null || p.detailed || p.all)) {
|
|
512
507
|
return 'deps cycles mode cannot be combined with file, direction, depth, detailed, or all.';
|
|
513
508
|
}
|
|
514
|
-
if (command === 'tests' && Number(p.depth ||
|
|
509
|
+
if (command === 'tests' && Number(p.depth || paramDefault('depth', 'tests')) > 0 && p.callsOnly) {
|
|
515
510
|
return 'tests callsOnly applies to direct mode and cannot be combined with depth>0.';
|
|
516
511
|
}
|
|
517
512
|
if (command === 'entrypoints' && p.includeTests === true && p.excludeTests === true) {
|
|
@@ -523,8 +518,9 @@ function validatePublicParams(command, p) {
|
|
|
523
518
|
if (command === 'plan') {
|
|
524
519
|
const operations = [p.addParam, p.removeParam, p.renameTo]
|
|
525
520
|
.filter(value => value != null && String(value).trim() !== '');
|
|
521
|
+
if (p.delete) operations.push('delete');
|
|
526
522
|
if (operations.length !== 1) {
|
|
527
|
-
return 'plan requires exactly one operation: addParam, removeParam, or
|
|
523
|
+
return 'plan requires exactly one operation: addParam, removeParam, renameTo, or delete.';
|
|
528
524
|
}
|
|
529
525
|
if (p.defaultValue != null && !p.addParam) {
|
|
530
526
|
return 'plan defaultValue is valid only with addParam.';
|
|
@@ -587,13 +583,7 @@ const HANDLERS = {
|
|
|
587
583
|
line: resolved.def.nameLine || resolved.def.startLine,
|
|
588
584
|
...(resolved.def.className && { className: resolved.def.className }),
|
|
589
585
|
} : query;
|
|
590
|
-
const parsed =
|
|
591
|
-
p.sections,
|
|
592
|
-
p.lines ? ['callers'] : ['summary', 'callers', 'callees'],
|
|
593
|
-
SHOW_SECTIONS,
|
|
594
|
-
'show',
|
|
595
|
-
);
|
|
596
|
-
if (parsed.error) return { ok: false, error: parsed.error };
|
|
586
|
+
const parsed = { sections: selectedSections('show', p) };
|
|
597
587
|
if (p.withTypes && !parsed.sections.includes('types')) parsed.sections.push('types');
|
|
598
588
|
|
|
599
589
|
const selected = new Set(parsed.sections);
|
|
@@ -729,10 +719,7 @@ const HANDLERS = {
|
|
|
729
719
|
}
|
|
730
720
|
const err = requireFile(p.file);
|
|
731
721
|
if (err) return { ok: false, error: err };
|
|
732
|
-
const direction = p.direction || '
|
|
733
|
-
if (!['imports', 'importers', 'both'].includes(direction)) {
|
|
734
|
-
return { ok: false, error: 'deps direction must be imports, importers, or both.' };
|
|
735
|
-
}
|
|
722
|
+
const direction = p.direction || paramDefault('direction', 'deps');
|
|
736
723
|
const graphResponse = HANDLERS.graph(index, {
|
|
737
724
|
file: p.file,
|
|
738
725
|
direction,
|
|
@@ -764,9 +751,7 @@ const HANDLERS = {
|
|
|
764
751
|
if (fileErr) return { ok: false, error: fileErr };
|
|
765
752
|
const inErr = validateInFilter(index, p.in);
|
|
766
753
|
if (inErr) return { ok: false, error: inErr };
|
|
767
|
-
const
|
|
768
|
-
const parsed = parseSections(p.sections, defaults, REPO_SECTIONS, 'repo');
|
|
769
|
-
if (parsed.error) return { ok: false, error: parsed.error };
|
|
754
|
+
const parsed = { sections: selectedSections('repo', p) };
|
|
770
755
|
const selected = new Set(parsed.sections);
|
|
771
756
|
const result = { sections: parsed.sections };
|
|
772
757
|
const notes = [];
|
|
@@ -783,7 +768,7 @@ const HANDLERS = {
|
|
|
783
768
|
...p,
|
|
784
769
|
// repo's public limit caps the file result set. Direct toc's
|
|
785
770
|
// legacy symbol-list cap is intentionally not composed here.
|
|
786
|
-
top: p.top || listingLimit(p),
|
|
771
|
+
top: p.top || listingLimit(p, 'repo'),
|
|
787
772
|
limit: undefined,
|
|
788
773
|
});
|
|
789
774
|
if (!failure && selected.has('stats')) failure = collect('stats', 'stats', p);
|
|
@@ -880,6 +865,7 @@ const HANDLERS = {
|
|
|
880
865
|
line: p.line,
|
|
881
866
|
exclude: toExcludeArray(p.exclude),
|
|
882
867
|
top: num(p.limit, undefined) || num(p.top, undefined),
|
|
868
|
+
all: !!p.all,
|
|
883
869
|
unreachableOnly: !!p.unreachableOnly,
|
|
884
870
|
// BUG-H3: pass through user-supplied flags. impact defaults to including
|
|
885
871
|
// method calls because "what breaks if I change this" should include
|
|
@@ -907,7 +893,7 @@ const HANDLERS = {
|
|
|
907
893
|
const depthVal = num(p.depth, undefined);
|
|
908
894
|
const result = index.blast(p.name, {
|
|
909
895
|
...buildCallerOptions(p),
|
|
910
|
-
depth: depthVal ??
|
|
896
|
+
depth: depthVal ?? paramDefault('depth', 'trace'),
|
|
911
897
|
all: !!p.all,
|
|
912
898
|
expandUnverified: !!p.expandUnverified,
|
|
913
899
|
});
|
|
@@ -931,7 +917,7 @@ const HANDLERS = {
|
|
|
931
917
|
const depthVal = num(p.depth, undefined);
|
|
932
918
|
const result = index.reverseTrace(p.name, {
|
|
933
919
|
...buildCallerOptions(p),
|
|
934
|
-
depth: depthVal ??
|
|
920
|
+
depth: depthVal ?? paramDefault('depth', 'trace', 'entrypoints'),
|
|
935
921
|
all: !!p.all,
|
|
936
922
|
expandUnverified: !!p.expandUnverified,
|
|
937
923
|
});
|
|
@@ -962,13 +948,7 @@ const HANDLERS = {
|
|
|
962
948
|
},
|
|
963
949
|
|
|
964
950
|
trace: (index, p) => {
|
|
965
|
-
const direction = p.direction || '
|
|
966
|
-
if (!['callees', 'callers'].includes(direction)) {
|
|
967
|
-
return { ok: false, error: 'trace direction must be callees or callers.' };
|
|
968
|
-
}
|
|
969
|
-
if (p.to && p.to !== 'entrypoints') {
|
|
970
|
-
return { ok: false, error: 'trace --to currently accepts only entrypoints.' };
|
|
971
|
-
}
|
|
951
|
+
const direction = p.direction || paramDefault('direction', 'trace');
|
|
972
952
|
if (direction === 'callers') {
|
|
973
953
|
const response = p.to === 'entrypoints'
|
|
974
954
|
? HANDLERS.reverseTrace(index, { ...p, direction: undefined, to: undefined })
|
|
@@ -991,7 +971,7 @@ const HANDLERS = {
|
|
|
991
971
|
const depthVal = num(p.depth, undefined);
|
|
992
972
|
const result = index.trace(p.name, {
|
|
993
973
|
...buildCallerOptions(p),
|
|
994
|
-
depth: depthVal ??
|
|
974
|
+
depth: depthVal ?? paramDefault('depth', 'trace'),
|
|
995
975
|
all: !!p.all,
|
|
996
976
|
expandUnverified: !!p.expandUnverified,
|
|
997
977
|
});
|
|
@@ -1128,10 +1108,11 @@ const HANDLERS = {
|
|
|
1128
1108
|
const { check } = require('./check');
|
|
1129
1109
|
try {
|
|
1130
1110
|
const result = check(index, {
|
|
1131
|
-
base: p.base || '
|
|
1111
|
+
base: p.base || paramDefault('base'),
|
|
1132
1112
|
staged: !!p.staged,
|
|
1133
1113
|
file: p.file,
|
|
1134
1114
|
limit: num(p.limit, undefined),
|
|
1115
|
+
strict: !!p.strict,
|
|
1135
1116
|
});
|
|
1136
1117
|
addMode(result, 'diff');
|
|
1137
1118
|
return { ok: true, result };
|
|
@@ -1145,21 +1126,13 @@ const HANDLERS = {
|
|
|
1145
1126
|
find: (index, p) => {
|
|
1146
1127
|
const inErr = validateInFilter(index, p.in);
|
|
1147
1128
|
if (inErr) return { ok: false, error: inErr };
|
|
1148
|
-
const validFindTypes = new Set([
|
|
1149
|
-
'type', 'function', 'class', 'variable', 'state', 'constant',
|
|
1150
|
-
'field', 'macro', 'method', 'constructor', 'interface', 'enum',
|
|
1151
|
-
'struct', 'trait', 'record', 'namespace',
|
|
1152
|
-
]);
|
|
1153
|
-
if (p.type && !validFindTypes.has(p.type)) {
|
|
1154
|
-
return { ok: false, error: `Invalid find type "${p.type}". Valid types: ${[...validFindTypes].join(', ')}.` };
|
|
1155
|
-
}
|
|
1156
1129
|
if (p.type === 'type') {
|
|
1157
1130
|
const response = HANDLERS.typedef(index, { ...p });
|
|
1158
1131
|
if (response.ok) {
|
|
1159
1132
|
if (!p.withSource && Array.isArray(response.result)) {
|
|
1160
1133
|
response.result = response.result.map(({ code, ...item }) => item);
|
|
1161
1134
|
}
|
|
1162
|
-
const limit = listingLimit(p);
|
|
1135
|
+
const limit = listingLimit(p, 'find');
|
|
1163
1136
|
if (limit && limit > 0 && Array.isArray(response.result)) {
|
|
1164
1137
|
const { items, total, limited } = applyLimit(response.result, limit);
|
|
1165
1138
|
response.result = items;
|
|
@@ -1211,12 +1184,14 @@ const HANDLERS = {
|
|
|
1211
1184
|
item.startLine === line || item.nameLine === line);
|
|
1212
1185
|
}
|
|
1213
1186
|
if (p.type) {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1187
|
+
// A Map: a kind may be named like an inherited object property
|
|
1188
|
+
// (`constructor`), which a plain object lookup would return.
|
|
1189
|
+
const kindGroups = new Map([
|
|
1190
|
+
['function', new Set(['function', 'method', 'constructor', 'get', 'set'])],
|
|
1191
|
+
['class', new Set(CLASS_KIND_TYPES)],
|
|
1192
|
+
['variable', new Set(['state', 'constant', 'field'])],
|
|
1193
|
+
]);
|
|
1194
|
+
const kinds = kindGroups.get(p.type) || new Set([p.type]);
|
|
1220
1195
|
result = result.filter(item => kinds.has(item.type));
|
|
1221
1196
|
}
|
|
1222
1197
|
const fullFindCount = result.length;
|
|
@@ -1232,21 +1207,24 @@ const HANDLERS = {
|
|
|
1232
1207
|
notes.push(`Note: exact=true treats "${p.name}" as a literal name (glob expansion disabled).`);
|
|
1233
1208
|
}
|
|
1234
1209
|
let unsupportedMatches = null;
|
|
1235
|
-
if (fullFindCount === 0
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1210
|
+
if (fullFindCount === 0) {
|
|
1211
|
+
const universeTools = require('./source-universe');
|
|
1212
|
+
if (universeTools.hasTextOnly(index)) {
|
|
1213
|
+
const matches = require('./account').scanUnsupportedFiles(index, p.name);
|
|
1214
|
+
if (matches.lines > 0) {
|
|
1215
|
+
unsupportedMatches = matches;
|
|
1216
|
+
const labels = universeTools.matchLabels(matches);
|
|
1217
|
+
notes.push(`${matches.lines} literal match(es) exist in ` +
|
|
1218
|
+
`${matches.fileCount} unanalyzed file(s)` +
|
|
1219
|
+
`${labels ? ` (${labels})` : ''}; this is not a semantic zero. ` +
|
|
1220
|
+
universeTools.matchAdvice(matches));
|
|
1221
|
+
}
|
|
1246
1222
|
}
|
|
1223
|
+
const unsearched = universeTools.unsearchedNote(index);
|
|
1224
|
+
if (unsearched) notes.push(unsearched);
|
|
1247
1225
|
}
|
|
1248
1226
|
// Apply limit
|
|
1249
|
-
const limit = listingLimit(p);
|
|
1227
|
+
const limit = listingLimit(p, 'find');
|
|
1250
1228
|
if (!p.lines) {
|
|
1251
1229
|
index._beginOp();
|
|
1252
1230
|
try {
|
|
@@ -1305,7 +1283,7 @@ const HANDLERS = {
|
|
|
1305
1283
|
const userExclude = toExcludeArray(p.exclude);
|
|
1306
1284
|
const unfiltered = index.usages(p.name, {
|
|
1307
1285
|
codeOnly: p.codeOnly || false,
|
|
1308
|
-
context: num(p.context,
|
|
1286
|
+
context: num(p.context, paramDefault('context')),
|
|
1309
1287
|
className: p.className,
|
|
1310
1288
|
// A stable handle pins which definition is meant; it must not
|
|
1311
1289
|
// silently turn into a same-file-only usage scan. An explicit
|
|
@@ -1363,15 +1341,20 @@ const HANDLERS = {
|
|
|
1363
1341
|
const accountTools = require('./account');
|
|
1364
1342
|
const unsupportedMatches = accountTools.scanUnsupportedFiles(index, p.name);
|
|
1365
1343
|
if (unsupportedMatches.lines > 0) {
|
|
1366
|
-
const langs =
|
|
1344
|
+
const langs = require('./source-universe').matchLabels(unsupportedMatches);
|
|
1367
1345
|
notes.push(`${unsupportedMatches.lines} line(s) in ${unsupportedMatches.fileCount} ` +
|
|
1368
|
-
`
|
|
1346
|
+
`unanalyzed file(s)${langs ? ` (${langs})` : ''} also match — ` +
|
|
1369
1347
|
'NOT analyzed by UCN; verify with grep/ripgrep.');
|
|
1370
1348
|
Object.defineProperty(limited, 'unsupportedMatches', {
|
|
1371
1349
|
value: unsupportedMatches,
|
|
1372
1350
|
enumerable: false, writable: true, configurable: true,
|
|
1373
1351
|
});
|
|
1374
1352
|
}
|
|
1353
|
+
// An inventory is incomplete wherever a path could not be searched
|
|
1354
|
+
// (asked after the scan above: a file that fails to read while being
|
|
1355
|
+
// scanned becomes such a path).
|
|
1356
|
+
const unsearched = require('./source-universe').unsearchedNote(index);
|
|
1357
|
+
if (unsearched) notes.push(unsearched);
|
|
1375
1358
|
const failed = accountTools.scanFailedFiles(index, p.name);
|
|
1376
1359
|
if (failed.unparsed.lines > 0) {
|
|
1377
1360
|
notes.push(`${failed.unparsed.lines} matching line(s) in ${failed.unparsed.fileCount} unparsed file(s) were not classified: ${failed.unparsed.files.join(', ')}.`);
|
|
@@ -1455,7 +1438,7 @@ const HANDLERS = {
|
|
|
1455
1438
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
1456
1439
|
if (fileErr) return { ok: false, error: fileErr };
|
|
1457
1440
|
// Detect structural search mode: any of these flags triggers index-based search
|
|
1458
|
-
const isStructural =
|
|
1441
|
+
const isStructural = commandMode('search', p) === 'structural';
|
|
1459
1442
|
if (isStructural) {
|
|
1460
1443
|
const exclude = applyTestExclusions(p.exclude, p.includeTests);
|
|
1461
1444
|
const topVal = num(p.top, undefined) || num(p.limit, undefined);
|
|
@@ -1473,7 +1456,7 @@ const HANDLERS = {
|
|
|
1473
1456
|
testExclude: p.includeTests ? undefined : ['test files'],
|
|
1474
1457
|
in: p.in,
|
|
1475
1458
|
file: p.file,
|
|
1476
|
-
top: topVal || (p.lines ? undefined :
|
|
1459
|
+
top: topVal || (p.lines ? undefined : paramDefault('limit', 'search', 'structural')),
|
|
1477
1460
|
});
|
|
1478
1461
|
if (result.meta.error) return { ok: false, error: result.meta.error };
|
|
1479
1462
|
const unsupported = (!p.regex && (p.term || p.name))
|
|
@@ -1487,7 +1470,7 @@ const HANDLERS = {
|
|
|
1487
1470
|
value: unsupported,
|
|
1488
1471
|
enumerable: false, writable: true, configurable: true,
|
|
1489
1472
|
});
|
|
1490
|
-
note = combineNotes([note, `${unsupported.lines} matching line(s) in ${unsupported.fileCount}
|
|
1473
|
+
note = combineNotes([note, `${unsupported.lines} matching line(s) in ${unsupported.fileCount} unanalyzed file(s) were not structurally analyzed; verify with grep/ripgrep.`]);
|
|
1491
1474
|
}
|
|
1492
1475
|
return { ok: true, result, structural: true, note };
|
|
1493
1476
|
}
|
|
@@ -1497,14 +1480,14 @@ const HANDLERS = {
|
|
|
1497
1480
|
const testsExcluded = !p.includeTests;
|
|
1498
1481
|
const exclude = applyTestExclusions(p.exclude, p.includeTests);
|
|
1499
1482
|
// JSON is intentionally structured rather than byte-truncated, so
|
|
1500
|
-
// text search needs a row bound of its own
|
|
1501
|
-
//
|
|
1502
|
-
//
|
|
1483
|
+
// text search needs a row bound of its own: the default limit, with
|
|
1484
|
+
// total/truncated metadata for controlled expansion by an explicit
|
|
1485
|
+
// top/limit.
|
|
1503
1486
|
const topVal = num(p.top, undefined) || num(p.limit, undefined);
|
|
1504
|
-
const effectiveLimit = topVal || (p.lines ? undefined :
|
|
1487
|
+
const effectiveLimit = topVal || (p.lines ? undefined : paramDefault('limit', 'search'));
|
|
1505
1488
|
const result = index.search(p.term, {
|
|
1506
1489
|
codeOnly: p.codeOnly || false,
|
|
1507
|
-
context: num(p.context,
|
|
1490
|
+
context: num(p.context, paramDefault('context')),
|
|
1508
1491
|
caseSensitive: p.caseSensitive || false,
|
|
1509
1492
|
exclude,
|
|
1510
1493
|
in: p.in,
|
|
@@ -1521,8 +1504,10 @@ const HANDLERS = {
|
|
|
1521
1504
|
value: unsupported,
|
|
1522
1505
|
enumerable: false, writable: true, configurable: true,
|
|
1523
1506
|
});
|
|
1524
|
-
notes.push(`${unsupported.lines} matching line(s) in ${unsupported.fileCount}
|
|
1507
|
+
notes.push(`${unsupported.lines} matching line(s) in ${unsupported.fileCount} unanalyzed file(s) were not searched; verify with grep/ripgrep.`);
|
|
1525
1508
|
}
|
|
1509
|
+
const unsearched = require('./source-universe').unsearchedNote(index);
|
|
1510
|
+
if (unsearched) notes.push(unsearched);
|
|
1526
1511
|
}
|
|
1527
1512
|
const tNote = truncationNote(index);
|
|
1528
1513
|
if (tNote) notes.push(tNote);
|
|
@@ -1530,7 +1515,7 @@ const HANDLERS = {
|
|
|
1530
1515
|
},
|
|
1531
1516
|
|
|
1532
1517
|
tests: (index, p) => {
|
|
1533
|
-
const depth = num(p.depth,
|
|
1518
|
+
const depth = num(p.depth, paramDefault('depth', 'tests'));
|
|
1534
1519
|
if (depth > 0) {
|
|
1535
1520
|
const response = HANDLERS.affectedTests(index, { ...p, depth });
|
|
1536
1521
|
if (response.ok) addMode(response.result, 'affected');
|
|
@@ -1614,15 +1599,17 @@ const HANDLERS = {
|
|
|
1614
1599
|
if (!testsTargetIsFile) {
|
|
1615
1600
|
const unsupportedMatches = require('./account').scanUnsupportedFiles(index, p.name);
|
|
1616
1601
|
if (unsupportedMatches.lines > 0) {
|
|
1617
|
-
const langs =
|
|
1602
|
+
const langs = require('./source-universe').matchLabels(unsupportedMatches);
|
|
1618
1603
|
testNotes.push(`"${p.name}" also occurs on ${unsupportedMatches.lines} line(s) in ` +
|
|
1619
|
-
`${unsupportedMatches.fileCount}
|
|
1604
|
+
`${unsupportedMatches.fileCount} unanalyzed file(s)${langs ? ` (${langs})` : ''} — ` +
|
|
1620
1605
|
'their test files are invisible to UCN; verify with grep/ripgrep.');
|
|
1621
1606
|
Object.defineProperty(result, 'unsupportedMatches', {
|
|
1622
1607
|
value: unsupportedMatches,
|
|
1623
1608
|
enumerable: false, writable: true, configurable: true,
|
|
1624
1609
|
});
|
|
1625
1610
|
}
|
|
1611
|
+
const unsearched = require('./source-universe').unsearchedNote(index);
|
|
1612
|
+
if (unsearched) testNotes.push(unsearched);
|
|
1626
1613
|
}
|
|
1627
1614
|
return {
|
|
1628
1615
|
ok: true,
|
|
@@ -1675,7 +1662,7 @@ const HANDLERS = {
|
|
|
1675
1662
|
file: p.file,
|
|
1676
1663
|
});
|
|
1677
1664
|
// Apply limit to dead code results (result is an array with custom properties)
|
|
1678
|
-
const limit = listingLimit(p);
|
|
1665
|
+
const limit = listingLimit(p, 'deadcode');
|
|
1679
1666
|
let note;
|
|
1680
1667
|
if (limit && limit > 0 && Array.isArray(result) && result.length > limit) {
|
|
1681
1668
|
note = limitNote(limit, result.length);
|
|
@@ -1687,6 +1674,8 @@ const HANDLERS = {
|
|
|
1687
1674
|
if (result.excludedRuntimeContract != null) sliced.excludedRuntimeContract = result.excludedRuntimeContract;
|
|
1688
1675
|
if (result.excludedConstructors != null) sliced.excludedConstructors = result.excludedConstructors;
|
|
1689
1676
|
if (result.excludedDynamicDispatch != null) sliced.excludedDynamicDispatch = result.excludedDynamicDispatch;
|
|
1677
|
+
if (result.excludedRequiredSlot != null) sliced.excludedRequiredSlot = result.excludedRequiredSlot;
|
|
1678
|
+
if (result.projectKind != null) sliced.projectKind = result.projectKind;
|
|
1690
1679
|
if (result.pythonImplicitExportFiles != null) sliced.pythonImplicitExportFiles = result.pythonImplicitExportFiles;
|
|
1691
1680
|
if (result.computedDispatch != null) sliced.computedDispatch = result.computedDispatch;
|
|
1692
1681
|
if (result.reflection != null) sliced.reflection = result.reflection;
|
|
@@ -1780,21 +1769,12 @@ const HANDLERS = {
|
|
|
1780
1769
|
endpoints: (index, p) => {
|
|
1781
1770
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
1782
1771
|
if (fileErr) return { ok: false, error: fileErr };
|
|
1783
|
-
//
|
|
1784
|
-
//
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
normMethod = String(p.method).trim().toUpperCase();
|
|
1790
|
-
if (!HTTP_METHODS.has(normMethod)) {
|
|
1791
|
-
return {
|
|
1792
|
-
ok: false,
|
|
1793
|
-
error: `Invalid --method value: "${p.method}". Expected one of ${[...HTTP_METHODS].join(', ')}.`,
|
|
1794
|
-
};
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
const { endpoints } = require('./bridge');
|
|
1772
|
+
// The method is one of the table's verbs in any letter case; a typo
|
|
1773
|
+
// is refused there instead of filtering everything out.
|
|
1774
|
+
const normMethod = p.method != null && String(p.method).trim() !== ''
|
|
1775
|
+
? String(p.method).trim().toUpperCase()
|
|
1776
|
+
: null;
|
|
1777
|
+
const { endpoints, ENDPOINT_FRAMEWORKS } = require('./bridge');
|
|
1798
1778
|
// HIGH-2: --unmatched implies --bridge (we need bridges to know what's
|
|
1799
1779
|
// unmatched). Without bridges computed, we can't separate matched from
|
|
1800
1780
|
// unmatched on either side.
|
|
@@ -1813,16 +1793,14 @@ const HANDLERS = {
|
|
|
1813
1793
|
});
|
|
1814
1794
|
if (p.framework != null && String(p.framework).trim() !== '') {
|
|
1815
1795
|
const framework = String(p.framework).trim().toLowerCase();
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
]);
|
|
1822
|
-
if (!known.has(framework)) {
|
|
1796
|
+
// Known: a label the endpoint detectors declare, or one a row of
|
|
1797
|
+
// this answer carries. A label the answer prints always filters.
|
|
1798
|
+
const present = [...result.routes, ...result.requests].map(row => row.framework).filter(Boolean);
|
|
1799
|
+
const known = [...new Set([...ENDPOINT_FRAMEWORKS, ...present])].sort();
|
|
1800
|
+
if (!known.includes(framework)) {
|
|
1823
1801
|
return {
|
|
1824
1802
|
ok: false,
|
|
1825
|
-
error: `Invalid
|
|
1803
|
+
error: `Invalid endpoints framework "${p.framework}". Valid: ${known.join(', ')}.`,
|
|
1826
1804
|
};
|
|
1827
1805
|
}
|
|
1828
1806
|
result.routes = result.routes.filter(route => route.framework === framework);
|
|
@@ -2208,8 +2186,8 @@ const HANDLERS = {
|
|
|
2208
2186
|
const err = requireFile(p.file);
|
|
2209
2187
|
if (err) return { ok: false, error: err };
|
|
2210
2188
|
const result = index.graph(p.file, {
|
|
2211
|
-
direction: p.direction || '
|
|
2212
|
-
maxDepth: num(p.depth,
|
|
2189
|
+
direction: p.direction || paramDefault('direction', 'deps'),
|
|
2190
|
+
maxDepth: num(p.depth, paramDefault('depth', 'deps')),
|
|
2213
2191
|
});
|
|
2214
2192
|
if (result && result.error === 'invalid-direction') {
|
|
2215
2193
|
return { ok: false, error: result.message };
|
|
@@ -2275,8 +2253,23 @@ const HANDLERS = {
|
|
|
2275
2253
|
if (classErr) return { ok: false, error: classErr };
|
|
2276
2254
|
const pinErr = checkDefinitionPin(index, p);
|
|
2277
2255
|
if (pinErr) return { ok: false, error: pinErr };
|
|
2256
|
+
if (p.delete) {
|
|
2257
|
+
// The same eligibility rule the dead-code audit runs over every
|
|
2258
|
+
// name, asked about this one declaration (core/deletion.js).
|
|
2259
|
+
const resolved = index.resolveSymbol(p.name, {
|
|
2260
|
+
file: p.file, className: p.className, line: p.line,
|
|
2261
|
+
});
|
|
2262
|
+
if (!resolved.def) return { ok: false, error: symbolNotFound(index, p.name) };
|
|
2263
|
+
const assessment = require('./deletion').assessDeletion(index, resolved.def);
|
|
2264
|
+
return { ok: true, result: {
|
|
2265
|
+
found: true, operation: 'delete', ...assessment,
|
|
2266
|
+
// Which declaration a bare name selected, and the others it
|
|
2267
|
+
// could have meant: the verdict is about one of them.
|
|
2268
|
+
...(resolved.warnings.length > 0 && { warnings: resolved.warnings }),
|
|
2269
|
+
} };
|
|
2270
|
+
}
|
|
2278
2271
|
if (!p.addParam && !p.removeParam && !p.renameTo) {
|
|
2279
|
-
return { ok: false, error: 'Plan requires an operation: add_param, remove_param, or
|
|
2272
|
+
return { ok: false, error: 'Plan requires an operation: add_param, remove_param, rename_to, or delete.' };
|
|
2280
2273
|
}
|
|
2281
2274
|
const result = index.plan(p.name, {
|
|
2282
2275
|
addParam: p.addParam,
|
|
@@ -2296,11 +2289,39 @@ const HANDLERS = {
|
|
|
2296
2289
|
},
|
|
2297
2290
|
|
|
2298
2291
|
diffImpact: (index, p) => {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2292
|
+
// A staged diff describes the git index: its new side is read from
|
|
2293
|
+
// the tree that holds it (core/snapshot.js), never from a worktree
|
|
2294
|
+
// that has moved on.
|
|
2295
|
+
const { resolveTarget, rebasePaths } = require('./snapshot');
|
|
2296
|
+
let target = null;
|
|
2297
|
+
try {
|
|
2298
|
+
target = resolveTarget(index, { staged: !!p.staged });
|
|
2299
|
+
} catch (e) {
|
|
2300
|
+
if (!e || e.code !== 'staged-snapshot-unavailable') throw e;
|
|
2301
|
+
// Outside a repository the diff itself says so, below. Anything
|
|
2302
|
+
// else is a staged tree that could not be read: an answer from
|
|
2303
|
+
// the worktree would be an answer about another tree.
|
|
2304
|
+
if (e.reason !== 'not-a-repository') {
|
|
2305
|
+
return { ok: false, error: `The staged tree could not be exported and indexed (${e.message}); nothing was analyzed.` };
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
let result;
|
|
2309
|
+
try {
|
|
2310
|
+
const view = target ? target.index : index;
|
|
2311
|
+
result = view.diffImpact({
|
|
2312
|
+
base: p.base || paramDefault('base'),
|
|
2313
|
+
staged: p.staged || false,
|
|
2314
|
+
file: p.file,
|
|
2315
|
+
all: !!p.all,
|
|
2316
|
+
top: num(p.limit, undefined) || num(p.top, undefined),
|
|
2317
|
+
...(target && { repoRoot: target.repoRoot }),
|
|
2318
|
+
});
|
|
2319
|
+
// Paths of an exported snapshot are reported where the
|
|
2320
|
+
// repository has them.
|
|
2321
|
+
if (target && target.ephemeral) rebasePaths(result, target.index.root, target.repoRoot);
|
|
2322
|
+
} finally {
|
|
2323
|
+
if (target) target.dispose();
|
|
2324
|
+
}
|
|
2304
2325
|
const limit = num(p.limit, undefined);
|
|
2305
2326
|
let note;
|
|
2306
2327
|
if (limit && limit > 0 && result) {
|
|
@@ -2373,7 +2394,7 @@ const HANDLERS = {
|
|
|
2373
2394
|
if (fileErr) return { ok: false, error: fileErr };
|
|
2374
2395
|
}
|
|
2375
2396
|
// Apply limit to api results (api returns an array)
|
|
2376
|
-
const limit = listingLimit(p);
|
|
2397
|
+
const limit = listingLimit(p, 'api');
|
|
2377
2398
|
let note;
|
|
2378
2399
|
if (limit && limit > 0 && Array.isArray(result)) {
|
|
2379
2400
|
const { items, total, limited } = applyLimit(result, limit);
|
|
@@ -2563,16 +2584,6 @@ function execute(index, command, params = {}) {
|
|
|
2563
2584
|
}
|
|
2564
2585
|
const response = handler(index, params);
|
|
2565
2586
|
response.projectRoot = index.root;
|
|
2566
|
-
const bundled = (index.discoveryIssues || []).filter(issue => issue.reason === 'bundled');
|
|
2567
|
-
if (bundled.length > 0) {
|
|
2568
|
-
const files = bundled.slice(0, 5).map(issue => issue.relativePath).join(', ');
|
|
2569
|
-
const note = `UCN skipped ${bundled.length} bundled/minified or source-map file(s): ${files}` +
|
|
2570
|
-
(bundled.length > 5 ? ', ...' : '') +
|
|
2571
|
-
'. This is not a repository-wide semantic zero; use --include-bundled for JavaScript bundles ' +
|
|
2572
|
-
'(source maps remain unindexed), or grep/ripgrep to inspect skipped files.';
|
|
2573
|
-
if (response.ok) response.note = combineNotes([response.note, note]);
|
|
2574
|
-
else response.error = `${response.error}\n${note}`;
|
|
2575
|
-
}
|
|
2576
2587
|
return response;
|
|
2577
2588
|
} catch (e) {
|
|
2578
2589
|
// A deliberate refusal keeps its message; an exception inside the
|