ucn 3.8.23 → 3.8.25
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 +114 -11
- package/README.md +152 -156
- package/cli/index.js +363 -37
- package/core/analysis.js +936 -32
- package/core/bridge.js +1111 -0
- package/core/brief.js +408 -0
- package/core/cache.js +105 -5
- package/core/callers.js +72 -18
- package/core/check.js +200 -0
- package/core/discovery.js +57 -34
- package/core/entrypoints.js +638 -4
- package/core/execute.js +304 -5
- package/core/git-enrich.js +130 -0
- package/core/graph.js +24 -2
- package/core/output/analysis.js +157 -25
- package/core/output/brief.js +100 -0
- package/core/output/check.js +79 -0
- package/core/output/doctor.js +85 -0
- package/core/output/endpoints.js +239 -0
- package/core/output/extraction.js +2 -0
- package/core/output/find.js +126 -39
- package/core/output/graph.js +48 -15
- package/core/output/refactoring.js +103 -5
- package/core/output/reporting.js +63 -23
- package/core/output/search.js +110 -17
- package/core/output/shared.js +56 -2
- package/core/output.js +4 -0
- package/core/parser.js +8 -2
- package/core/project.js +39 -3
- package/core/registry.js +30 -14
- package/core/reporting.js +465 -2
- package/core/search.js +130 -10
- package/core/shared.js +101 -5
- package/core/tracing.js +16 -6
- package/core/verify.js +982 -95
- package/languages/go.js +91 -6
- package/languages/html.js +10 -0
- package/languages/java.js +151 -35
- package/languages/javascript.js +290 -33
- package/languages/python.js +78 -11
- package/languages/rust.js +267 -12
- package/languages/utils.js +315 -3
- package/mcp/server.js +91 -16
- package/package.json +9 -1
package/cli/index.js
CHANGED
|
@@ -15,13 +15,132 @@ const { ProjectIndex } = require('../core/project');
|
|
|
15
15
|
const { expandGlob, findProjectRoot } = require('../core/discovery');
|
|
16
16
|
const output = require('../core/output');
|
|
17
17
|
const { getCliCommandSet, resolveCommand, FLAG_APPLICABILITY, toCliName, FILE_LOCAL_COMMANDS } = require('../core/registry');
|
|
18
|
+
const { looksLikeHandle, parseSymbolHandle } = require('../core/shared');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert a CLI argument that may be a stable handle into the symbol name
|
|
22
|
+
* that's appropriate for headers / "Usages of X" / "find Y" displays.
|
|
23
|
+
* Plain names pass through unchanged.
|
|
24
|
+
*/
|
|
25
|
+
function nameForDisplay(arg) {
|
|
26
|
+
if (typeof arg !== 'string') return arg;
|
|
27
|
+
if (!looksLikeHandle(arg)) return arg;
|
|
28
|
+
const h = parseSymbolHandle(arg);
|
|
29
|
+
return h && h.name ? h.name : arg;
|
|
30
|
+
}
|
|
18
31
|
const { execute } = require('../core/execute');
|
|
19
32
|
const { ExpandCache } = require('../core/expand-cache');
|
|
20
33
|
|
|
21
34
|
// Sentinel error for command failures that have already printed their message.
|
|
22
35
|
// Thrown instead of process.exit(1) so finally blocks can run (cache save).
|
|
23
36
|
class CommandError extends Error { constructor() { super(); } }
|
|
24
|
-
|
|
37
|
+
|
|
38
|
+
// Thrown by validateNumericFlags when a numeric flag has a bad value.
|
|
39
|
+
// The CLI top-level catches this, prints the message, and exits 1. Interactive
|
|
40
|
+
// mode catches it inside its REPL try/catch and continues the session.
|
|
41
|
+
class FlagValidationError extends Error {
|
|
42
|
+
constructor(msg) { super(msg); this.name = 'FlagValidationError'; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validate that a raw flag value is a positive integer. Returns the parsed
|
|
47
|
+
* number when valid, or throws FlagValidationError. Callers pass `null`/`undefined`
|
|
48
|
+
* raw values through unchanged (no flag → no validation).
|
|
49
|
+
*
|
|
50
|
+
* @param {string|null|undefined} raw - The raw string captured from the CLI/interactive token.
|
|
51
|
+
* @param {string} flagName - The CLI flag name including dashes (e.g. "--top") for error messages.
|
|
52
|
+
* @param {object} [opts]
|
|
53
|
+
* @param {boolean} [opts.allowZero=false] - Whether 0 is a valid value (e.g. depth=0 may be meaningful).
|
|
54
|
+
* @param {number} [opts.cap=10000000] - Maximum accepted value (rejects 1e100 etc).
|
|
55
|
+
* @returns {number|undefined} The validated integer, or undefined when raw is null/undefined.
|
|
56
|
+
*/
|
|
57
|
+
function validatePositiveInt(raw, flagName, { allowZero = false, cap = 10000000 } = {}) {
|
|
58
|
+
if (raw == null) return undefined;
|
|
59
|
+
const label = allowZero ? 'non-negative integer' : 'positive integer';
|
|
60
|
+
const trimmed = String(raw).trim();
|
|
61
|
+
if (trimmed === '') {
|
|
62
|
+
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got "${raw}")`);
|
|
63
|
+
}
|
|
64
|
+
const n = Number(trimmed);
|
|
65
|
+
if (!isFinite(n) || isNaN(n)) {
|
|
66
|
+
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got "${raw}")`);
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isInteger(n)) {
|
|
69
|
+
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got ${n})`);
|
|
70
|
+
}
|
|
71
|
+
if (allowZero) {
|
|
72
|
+
if (n < 0) {
|
|
73
|
+
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got ${n})`);
|
|
74
|
+
}
|
|
75
|
+
} else if (n <= 0) {
|
|
76
|
+
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got ${n})`);
|
|
77
|
+
}
|
|
78
|
+
if (n > cap) {
|
|
79
|
+
throw new FlagValidationError(`Invalid ${flagName} value: ${n} exceeds maximum (${cap})`);
|
|
80
|
+
}
|
|
81
|
+
return n;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate all numeric flags on a parsed flags object. Looks at the *Raw
|
|
86
|
+
* companion strings preserved by parseFlags so we catch user-supplied bad
|
|
87
|
+
* values regardless of whether the parsed numeric form happened to be falsy.
|
|
88
|
+
* Mutates `flags` to hold the validated numeric values.
|
|
89
|
+
*
|
|
90
|
+
* Throws FlagValidationError on the first invalid flag.
|
|
91
|
+
*/
|
|
92
|
+
function validateNumericFlags(flags) {
|
|
93
|
+
// --top: positive integer, no zero. Used by stats/find/context/etc.
|
|
94
|
+
if (flags.topRaw != null) {
|
|
95
|
+
flags.top = validatePositiveInt(flags.topRaw, '--top');
|
|
96
|
+
}
|
|
97
|
+
// --limit: positive integer, no zero. Reject "0 = no limit" silent coercion.
|
|
98
|
+
if (flags.limitRaw != null) {
|
|
99
|
+
flags.limit = validatePositiveInt(flags.limitRaw, '--limit');
|
|
100
|
+
}
|
|
101
|
+
// --max-files: positive integer, no zero.
|
|
102
|
+
if (flags.maxFilesRaw != null) {
|
|
103
|
+
flags.maxFiles = validatePositiveInt(flags.maxFilesRaw, '--max-files');
|
|
104
|
+
}
|
|
105
|
+
// --max-lines: positive integer, no zero. Used by class command.
|
|
106
|
+
if (flags.maxLinesRaw != null) {
|
|
107
|
+
flags.maxLines = validatePositiveInt(flags.maxLinesRaw, '--max-lines');
|
|
108
|
+
}
|
|
109
|
+
// --depth: non-negative integer (0 is meaningful: "this symbol only").
|
|
110
|
+
if (flags.depthRaw != null) {
|
|
111
|
+
flags.depth = validatePositiveInt(flags.depthRaw, '--depth', { allowZero: true });
|
|
112
|
+
}
|
|
113
|
+
// --context: non-negative integer (0 = no surrounding lines).
|
|
114
|
+
if (flags.contextRaw != null) {
|
|
115
|
+
flags.context = validatePositiveInt(flags.contextRaw, '--context', { allowZero: true });
|
|
116
|
+
}
|
|
117
|
+
// --workers: non-negative integer (0 disables parallel build).
|
|
118
|
+
if (flags.workersRaw != null) {
|
|
119
|
+
flags.workers = validatePositiveInt(flags.workersRaw, '--workers', { allowZero: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Print an error message and abort. When `--json` is in effect, write a JSON
|
|
125
|
+
* error envelope to stdout (so JSON-consuming pipelines see structured output)
|
|
126
|
+
* and write the same plain message to stderr (for humans piping to a TTY).
|
|
127
|
+
*/
|
|
128
|
+
function fail(msg) {
|
|
129
|
+
// Honor --json by writing a structured envelope to stdout for pipelines.
|
|
130
|
+
// We use try/catch around symbol lookups because `flags` may not be initialized
|
|
131
|
+
// yet when fail() is called from the early arg-parsing path (TDZ).
|
|
132
|
+
let wantsJson = false;
|
|
133
|
+
try { if (typeof flags !== 'undefined' && flags && flags.json) wantsJson = true; } catch (_) {}
|
|
134
|
+
if (!wantsJson) {
|
|
135
|
+
try { if (Array.isArray(process.argv) && process.argv.includes('--json')) wantsJson = true; } catch (_) {}
|
|
136
|
+
}
|
|
137
|
+
if (wantsJson) {
|
|
138
|
+
const env = { meta: { ok: false }, error: typeof msg === 'string' ? msg : String(msg) };
|
|
139
|
+
try { process.stdout.write(JSON.stringify(env) + '\n'); } catch (_) {}
|
|
140
|
+
}
|
|
141
|
+
console.error(msg);
|
|
142
|
+
throw new CommandError();
|
|
143
|
+
}
|
|
25
144
|
|
|
26
145
|
// ============================================================================
|
|
27
146
|
// ARGUMENT PARSING
|
|
@@ -74,10 +193,11 @@ function parseFlags(tokens) {
|
|
|
74
193
|
exclude: parseExclude(),
|
|
75
194
|
in: getValueFlag('--in'),
|
|
76
195
|
includeTests: tokens.includes('--include-tests') ? true : undefined,
|
|
196
|
+
excludeTests: tokens.includes('--exclude-tests') ? true : undefined,
|
|
77
197
|
includeExported: tokens.includes('--include-exported') || undefined,
|
|
78
198
|
includeDecorated: tokens.includes('--include-decorated') || undefined,
|
|
79
199
|
includeUncertain: tokens.includes('--include-uncertain') || undefined,
|
|
80
|
-
includeMethods: tokens.some(a => a === '--include-methods=false') ? false : tokens.some(a => a === '--include-methods' || (a.startsWith('--include-methods=') && a !== '--include-methods=false')) ? true : undefined,
|
|
200
|
+
includeMethods: tokens.some(a => a === '--include-methods=false' || a === '--no-include-methods') ? false : tokens.some(a => a === '--include-methods' || (a.startsWith('--include-methods=') && a !== '--include-methods=false')) ? true : undefined,
|
|
81
201
|
detailed: tokens.includes('--detailed') || undefined,
|
|
82
202
|
topLevel: tokens.includes('--top-level') || undefined,
|
|
83
203
|
all: tokens.includes('--all') || undefined,
|
|
@@ -88,8 +208,14 @@ function parseFlags(tokens) {
|
|
|
88
208
|
withTypes: tokens.includes('--with-types') || undefined,
|
|
89
209
|
expand: tokens.includes('--expand') || undefined,
|
|
90
210
|
depth: getValueFlag('--depth'),
|
|
211
|
+
depthRaw: getValueFlag('--depth'),
|
|
212
|
+
// `top` is the parsed numeric value (NaN/0 default → falsy). `topRaw`
|
|
213
|
+
// preserves the original string so downstream validators can produce
|
|
214
|
+
// helpful errors for "abc"/"-1"/"0" instead of silently defaulting.
|
|
91
215
|
top: parseInt(getValueFlag('--top') || '0'),
|
|
216
|
+
topRaw: getValueFlag('--top'),
|
|
92
217
|
context: parseInt(getValueFlag('--context') || '0'),
|
|
218
|
+
contextRaw: getValueFlag('--context'),
|
|
93
219
|
direction: getValueFlag('--direction'),
|
|
94
220
|
addParam: getValueFlag('--add-param'),
|
|
95
221
|
removeParam: getValueFlag('--remove-param'),
|
|
@@ -97,12 +223,20 @@ function parseFlags(tokens) {
|
|
|
97
223
|
defaultValue: getValueFlag('--default'),
|
|
98
224
|
base: getValueFlag('--base'),
|
|
99
225
|
staged: tokens.includes('--staged') || undefined,
|
|
226
|
+
deep: tokens.includes('--deep') || undefined,
|
|
227
|
+
compact: tokens.includes('--compact') || undefined,
|
|
100
228
|
maxLines: getValueFlag('--max-lines') || null,
|
|
229
|
+
maxLinesRaw: getValueFlag('--max-lines'),
|
|
101
230
|
regex: tokens.includes('--no-regex') ? false : undefined,
|
|
102
231
|
functions: tokens.includes('--functions') || undefined,
|
|
232
|
+
hot: tokens.includes('--hot') || undefined,
|
|
233
|
+
diverse: tokens.includes('--diverse') || undefined,
|
|
234
|
+
git: tokens.includes('--git') || undefined,
|
|
103
235
|
className: getValueFlag('--class-name'),
|
|
104
236
|
limit: parseInt(getValueFlag('--limit') || '0') || undefined,
|
|
237
|
+
limitRaw: getValueFlag('--limit'),
|
|
105
238
|
maxFiles: parseInt(getValueFlag('--max-files') || '0') || undefined,
|
|
239
|
+
maxFilesRaw: getValueFlag('--max-files'),
|
|
106
240
|
// Structural search flags
|
|
107
241
|
type: getValueFlag('--type'),
|
|
108
242
|
param: getValueFlag('--param'),
|
|
@@ -111,10 +245,20 @@ function parseFlags(tokens) {
|
|
|
111
245
|
decorator: getValueFlag('--decorator'),
|
|
112
246
|
exported: tokens.includes('--exported') || undefined,
|
|
113
247
|
unused: tokens.includes('--unused') || undefined,
|
|
114
|
-
showConfidence: tokens.includes('--no-confidence') ? false : undefined,
|
|
248
|
+
showConfidence: (tokens.includes('--hide-confidence') || tokens.includes('--no-confidence')) ? false : undefined,
|
|
115
249
|
minConfidence: parseFloat(getValueFlag('--min-confidence') || '0') || 0,
|
|
250
|
+
unreachableOnly: tokens.includes('--unreachable-only') || undefined,
|
|
116
251
|
framework: getValueFlag('--framework'),
|
|
252
|
+
// endpoints command flags
|
|
253
|
+
bridge: tokens.includes('--bridge') || undefined,
|
|
254
|
+
serverOnly: tokens.includes('--server-only') || undefined,
|
|
255
|
+
clientOnly: tokens.includes('--client-only') || undefined,
|
|
256
|
+
unmatched: tokens.includes('--unmatched') || undefined,
|
|
257
|
+
method: getValueFlag('--method'),
|
|
258
|
+
prefix: getValueFlag('--prefix'),
|
|
259
|
+
hideUncertain: tokens.includes('--hide-uncertain') || tokens.includes('--no-uncertain') || undefined,
|
|
117
260
|
stack: getValueFlag('--stack'),
|
|
261
|
+
workersRaw: getValueFlag('--workers'),
|
|
118
262
|
workers: (() => {
|
|
119
263
|
const v = getValueFlag('--workers');
|
|
120
264
|
if (v === null) return undefined;
|
|
@@ -138,17 +282,19 @@ const knownFlags = new Set([
|
|
|
138
282
|
'--help', '-h', '--mcp',
|
|
139
283
|
'--json', '--verbose', '--no-quiet', '--quiet',
|
|
140
284
|
'--code-only', '--with-types', '--top-level', '--exact', '--case-sensitive',
|
|
141
|
-
'--no-cache', '--clear-cache', '--include-tests',
|
|
142
|
-
'--include-exported', '--include-decorated', '--expand', '--interactive', '-i', '--all', '--include-methods', '--include-uncertain', '--detailed', '--calls-only',
|
|
285
|
+
'--no-cache', '--clear-cache', '--include-tests', '--exclude-tests',
|
|
286
|
+
'--include-exported', '--include-decorated', '--expand', '--interactive', '-i', '--all', '--include-methods', '--no-include-methods', '--include-uncertain', '--detailed', '--calls-only',
|
|
143
287
|
'--file', '--context', '--exclude', '--not', '--in',
|
|
144
288
|
'--depth', '--direction', '--add-param', '--remove-param', '--rename-to',
|
|
145
289
|
'--default', '--top', '--no-follow-symlinks',
|
|
146
290
|
'--base', '--staged', '--stack',
|
|
147
|
-
'--regex', '--no-regex', '--functions',
|
|
291
|
+
'--regex', '--no-regex', '--functions', '--hot', '--diverse', '--git',
|
|
148
292
|
'--max-lines', '--class-name', '--limit', '--max-files',
|
|
149
293
|
'--type', '--param', '--receiver', '--returns', '--decorator', '--exported', '--unused',
|
|
150
|
-
'--
|
|
151
|
-
'--framework', '--workers'
|
|
294
|
+
'--hide-confidence', '--no-confidence', '--min-confidence', '--unreachable-only',
|
|
295
|
+
'--framework', '--workers', '--deep', '--compact',
|
|
296
|
+
'--bridge', '--server-only', '--client-only', '--unmatched',
|
|
297
|
+
'--method', '--prefix', '--hide-uncertain', '--no-uncertain'
|
|
152
298
|
]);
|
|
153
299
|
|
|
154
300
|
// Handle help flag
|
|
@@ -171,6 +317,23 @@ if (unknownFlags.length > 0) {
|
|
|
171
317
|
process.exit(1);
|
|
172
318
|
}
|
|
173
319
|
|
|
320
|
+
// Validate numeric flag values up front so bad input fails before we build
|
|
321
|
+
// any indexes. Applies to --top, --limit, --max-files, --max-lines, --depth,
|
|
322
|
+
// --context, --workers. Throws FlagValidationError with a helpful message.
|
|
323
|
+
try {
|
|
324
|
+
validateNumericFlags(flags);
|
|
325
|
+
} catch (e) {
|
|
326
|
+
if (e instanceof FlagValidationError) {
|
|
327
|
+
if (flags.json) {
|
|
328
|
+
const env = { meta: { ok: false }, error: e.message };
|
|
329
|
+
try { process.stdout.write(JSON.stringify(env) + '\n'); } catch (_) {}
|
|
330
|
+
}
|
|
331
|
+
console.error(e.message);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
throw e;
|
|
335
|
+
}
|
|
336
|
+
|
|
174
337
|
// Value flags that consume the next token (space form: --flag value)
|
|
175
338
|
const VALUE_FLAGS = new Set([
|
|
176
339
|
'--file', '--depth', '--top', '--context', '--direction',
|
|
@@ -178,7 +341,7 @@ const VALUE_FLAGS = new Set([
|
|
|
178
341
|
'--base', '--exclude', '--not', '--in', '--max-lines', '--class-name',
|
|
179
342
|
'--type', '--param', '--receiver', '--returns', '--decorator',
|
|
180
343
|
'--limit', '--max-files', '--min-confidence', '--stack', '--framework',
|
|
181
|
-
'--workers'
|
|
344
|
+
'--workers', '--method', '--prefix'
|
|
182
345
|
]);
|
|
183
346
|
|
|
184
347
|
// Remove flags from args, then add args after -- (which are all positional)
|
|
@@ -479,7 +642,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
479
642
|
// Map from camelCase flag name to CLI flag string
|
|
480
643
|
const flagToCli = (f) => '--' + f.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
481
644
|
// Flags that are global (not command-specific) — skip warning for these
|
|
482
|
-
const globalFlags = new Set(['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles', 'verbose', 'expand', 'interactive', '_fileFromFileMode']);
|
|
645
|
+
const globalFlags = new Set(['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles', 'verbose', 'expand', 'interactive', '_fileFromFileMode', 'topRaw', 'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'depthRaw', 'contextRaw', 'workersRaw']);
|
|
483
646
|
for (const [key, value] of Object.entries(flags)) {
|
|
484
647
|
if (globalFlags.has(key)) continue;
|
|
485
648
|
// Skip unset values (undefined, null, 0, empty array) — but NOT false (explicit negation)
|
|
@@ -512,7 +675,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
512
675
|
if (note) console.error(note);
|
|
513
676
|
printOutput(result,
|
|
514
677
|
r => output.formatSymbolJson(r, arg),
|
|
515
|
-
r => output.formatFindDetailed(r, arg, { depth: flags.depth, top: flags.top, all: flags.all })
|
|
678
|
+
r => output.formatFindDetailed(r, arg, { depth: flags.depth, top: flags.top, all: flags.all, compact: flags.compact })
|
|
516
679
|
);
|
|
517
680
|
break;
|
|
518
681
|
}
|
|
@@ -521,19 +684,29 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
521
684
|
const { ok, result, error, note } = execute(index, 'usages', { name: arg, ...flags });
|
|
522
685
|
if (!ok) fail(error);
|
|
523
686
|
if (note) console.error(note);
|
|
687
|
+
const displayName = nameForDisplay(arg);
|
|
524
688
|
printOutput(result,
|
|
525
|
-
r => output.formatUsagesJson(r,
|
|
526
|
-
r => output.formatUsages(r,
|
|
689
|
+
r => output.formatUsagesJson(r, displayName),
|
|
690
|
+
r => output.formatUsages(r, displayName, { compact: flags.compact })
|
|
527
691
|
);
|
|
528
692
|
break;
|
|
529
693
|
}
|
|
530
694
|
|
|
531
695
|
case 'example': {
|
|
532
|
-
const { ok, result, error } = execute(index, 'example', {
|
|
696
|
+
const { ok, result, error, note } = execute(index, 'example', {
|
|
697
|
+
name: arg,
|
|
698
|
+
file: flags.file,
|
|
699
|
+
className: flags.className,
|
|
700
|
+
diverse: flags.diverse,
|
|
701
|
+
top: flags.top || undefined,
|
|
702
|
+
includeTests: flags.includeTests,
|
|
703
|
+
});
|
|
533
704
|
if (!ok) fail(error);
|
|
705
|
+
if (note) console.error(note);
|
|
706
|
+
const displayName = nameForDisplay(arg);
|
|
534
707
|
printOutput(result,
|
|
535
|
-
r => output.formatExampleJson(r,
|
|
536
|
-
r => output.formatExample(r,
|
|
708
|
+
r => output.formatExampleJson(r, displayName),
|
|
709
|
+
r => output.formatExample(r, displayName)
|
|
537
710
|
);
|
|
538
711
|
break;
|
|
539
712
|
}
|
|
@@ -549,6 +722,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
549
722
|
expandHint: 'Use "expand <N>" or --expand to see code for items',
|
|
550
723
|
uncertainHint: 'use --include-uncertain to include all',
|
|
551
724
|
showConfidence: flags.showConfidence !== false,
|
|
725
|
+
compact: !!flags.compact,
|
|
552
726
|
});
|
|
553
727
|
console.log(text);
|
|
554
728
|
|
|
@@ -577,7 +751,29 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
577
751
|
match, itemNum: expandNum, itemCount: items.length, validateRoot: true
|
|
578
752
|
});
|
|
579
753
|
if (!ok) fail(error);
|
|
580
|
-
|
|
754
|
+
if (flags.json) {
|
|
755
|
+
// Honor --json: structured output with the expanded code + metadata.
|
|
756
|
+
const env = {
|
|
757
|
+
meta: { command: 'expand', item: expandNum },
|
|
758
|
+
data: {
|
|
759
|
+
item: expandNum,
|
|
760
|
+
...(match && {
|
|
761
|
+
name: match.name,
|
|
762
|
+
type: match.type,
|
|
763
|
+
file: match.relativePath || match.file,
|
|
764
|
+
startLine: match.startLine,
|
|
765
|
+
endLine: match.endLine,
|
|
766
|
+
handle: match.relativePath && match.startLine && match.name
|
|
767
|
+
? `${match.relativePath}:${match.startLine}:${match.name}`
|
|
768
|
+
: null,
|
|
769
|
+
}),
|
|
770
|
+
text: result.text,
|
|
771
|
+
},
|
|
772
|
+
};
|
|
773
|
+
console.log(JSON.stringify(env, null, 2));
|
|
774
|
+
} else {
|
|
775
|
+
console.log(result.text);
|
|
776
|
+
}
|
|
581
777
|
break;
|
|
582
778
|
}
|
|
583
779
|
|
|
@@ -596,7 +792,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
596
792
|
if (!ok) fail(error);
|
|
597
793
|
printOutput(result,
|
|
598
794
|
output.formatAboutJson,
|
|
599
|
-
r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth, showConfidence: flags.showConfidence !== false })
|
|
795
|
+
r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth, showConfidence: flags.showConfidence !== false, compact: !!flags.compact, git: !!flags.git })
|
|
600
796
|
);
|
|
601
797
|
if (note) console.error(note);
|
|
602
798
|
break;
|
|
@@ -605,7 +801,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
605
801
|
case 'impact': {
|
|
606
802
|
const { ok, result, error, note } = execute(index, 'impact', { name: arg, ...flags });
|
|
607
803
|
if (!ok) fail(error);
|
|
608
|
-
printOutput(result, output.formatImpactJson, output.formatImpact);
|
|
804
|
+
printOutput(result, output.formatImpactJson, r => output.formatImpact(r, { compact: flags.compact }));
|
|
609
805
|
if (note) console.error(note);
|
|
610
806
|
break;
|
|
611
807
|
}
|
|
@@ -663,6 +859,34 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
663
859
|
break;
|
|
664
860
|
}
|
|
665
861
|
|
|
862
|
+
case 'brief': {
|
|
863
|
+
requireArg(arg, 'Usage: ucn . brief <name>');
|
|
864
|
+
const { ok, result, error } = execute(index, 'brief', { name: arg, file: flags.file, className: flags.className, git: flags.git });
|
|
865
|
+
if (!ok) fail(error);
|
|
866
|
+
printOutput(result, output.formatBriefJson, output.formatBrief);
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
case 'doctor': {
|
|
871
|
+
const { ok, result, error } = execute(index, 'doctor', {
|
|
872
|
+
file: flags.file, in: flags.in,
|
|
873
|
+
limit: flags.limit, deep: flags.deep,
|
|
874
|
+
});
|
|
875
|
+
if (!ok) fail(error);
|
|
876
|
+
printOutput(result, output.formatDoctorJson, output.formatDoctor);
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
case 'check': {
|
|
881
|
+
const { ok, result, error } = execute(index, 'check', {
|
|
882
|
+
base: flags.base, staged: flags.staged,
|
|
883
|
+
file: flags.file, limit: flags.limit,
|
|
884
|
+
});
|
|
885
|
+
if (!ok) fail(error);
|
|
886
|
+
printOutput(result, output.formatCheckJson, output.formatCheck);
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
|
|
666
890
|
// ── Extraction commands (via execute) ────────────────────────────
|
|
667
891
|
|
|
668
892
|
case 'fn': {
|
|
@@ -760,9 +984,10 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
760
984
|
case 'tests': {
|
|
761
985
|
const { ok, result, error } = execute(index, 'tests', { name: arg, callsOnly: flags.callsOnly, className: flags.className, file: flags.file, exclude: flags.exclude });
|
|
762
986
|
if (!ok) fail(error);
|
|
987
|
+
const displayName = nameForDisplay(arg);
|
|
763
988
|
printOutput(result,
|
|
764
|
-
r => output.formatTestsJson(r,
|
|
765
|
-
r => output.formatTests(r,
|
|
989
|
+
r => output.formatTestsJson(r, displayName),
|
|
990
|
+
r => output.formatTests(r, displayName)
|
|
766
991
|
);
|
|
767
992
|
break;
|
|
768
993
|
}
|
|
@@ -817,7 +1042,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
817
1042
|
}
|
|
818
1043
|
|
|
819
1044
|
case 'entrypoints': {
|
|
820
|
-
const { ok, result, error, note } = execute(index, 'entrypoints', { type: flags.type, framework: flags.framework, file: flags.file, exclude: flags.exclude, includeTests: flags.includeTests, limit: flags.limit });
|
|
1045
|
+
const { ok, result, error, note } = execute(index, 'entrypoints', { type: flags.type, framework: flags.framework, file: flags.file, exclude: flags.exclude, includeTests: flags.includeTests, excludeTests: flags.excludeTests, limit: flags.limit });
|
|
821
1046
|
if (!ok) fail(error);
|
|
822
1047
|
if (note) console.error(note);
|
|
823
1048
|
printOutput(result,
|
|
@@ -827,9 +1052,42 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
827
1052
|
break;
|
|
828
1053
|
}
|
|
829
1054
|
|
|
1055
|
+
case 'endpoints': {
|
|
1056
|
+
const { ok, result, error, note } = execute(index, 'endpoints', {
|
|
1057
|
+
file: flags.file,
|
|
1058
|
+
exclude: flags.exclude,
|
|
1059
|
+
limit: flags.limit,
|
|
1060
|
+
framework: flags.framework,
|
|
1061
|
+
bridge: flags.bridge,
|
|
1062
|
+
serverOnly: flags.serverOnly,
|
|
1063
|
+
clientOnly: flags.clientOnly,
|
|
1064
|
+
unmatched: flags.unmatched,
|
|
1065
|
+
method: flags.method,
|
|
1066
|
+
prefix: flags.prefix,
|
|
1067
|
+
hideUncertain: flags.hideUncertain,
|
|
1068
|
+
});
|
|
1069
|
+
if (!ok) fail(error);
|
|
1070
|
+
if (note) console.error(note);
|
|
1071
|
+
printOutput(result,
|
|
1072
|
+
output.formatEndpointsJson,
|
|
1073
|
+
r => output.formatEndpoints(r, { bridge: r._bridge, unmatched: r._unmatched })
|
|
1074
|
+
);
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
830
1078
|
case 'stats': {
|
|
831
|
-
|
|
1079
|
+
// MEDIUM-7: pass the raw --top value when present so the executor
|
|
1080
|
+
// can validate it and surface "Invalid --top" errors. Without
|
|
1081
|
+
// this, --top=abc is silently coerced to NaN → undefined and
|
|
1082
|
+
// the user gets the default (10) with no warning.
|
|
1083
|
+
const topVal = flags.topRaw != null ? flags.topRaw : (flags.top || undefined);
|
|
1084
|
+
const { ok, result, error, note } = execute(index, 'stats', {
|
|
1085
|
+
functions: flags.functions,
|
|
1086
|
+
hot: flags.hot,
|
|
1087
|
+
top: topVal,
|
|
1088
|
+
});
|
|
832
1089
|
if (!ok) fail(error);
|
|
1090
|
+
if (note) console.error(note);
|
|
833
1091
|
printOutput(result,
|
|
834
1092
|
output.formatStatsJson,
|
|
835
1093
|
r => output.formatStats(r, { top: flags.top })
|
|
@@ -845,6 +1103,18 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
845
1103
|
break;
|
|
846
1104
|
}
|
|
847
1105
|
|
|
1106
|
+
case 'auditAsync': {
|
|
1107
|
+
const { ok, result, error, note } = execute(index, 'auditAsync', {
|
|
1108
|
+
file: flags.file,
|
|
1109
|
+
exclude: flags.exclude,
|
|
1110
|
+
limit: flags.limit,
|
|
1111
|
+
});
|
|
1112
|
+
if (!ok) fail(error);
|
|
1113
|
+
if (note) console.error(note);
|
|
1114
|
+
printOutput(result, output.formatAuditAsyncJson, output.formatAuditAsync);
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
848
1118
|
default:
|
|
849
1119
|
console.error(`Unknown command: ${canonical}`);
|
|
850
1120
|
printUsage();
|
|
@@ -858,8 +1128,10 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
858
1128
|
} finally {
|
|
859
1129
|
// Save cache after command execution so callsCache populated
|
|
860
1130
|
// by findCallers/findCallees gets persisted to disk.
|
|
861
|
-
// On cache-hit runs, only re-save if callsCache was mutated
|
|
862
|
-
|
|
1131
|
+
// On cache-hit runs, only re-save if callsCache was mutated OR
|
|
1132
|
+
// reachability was computed (MED-1: persists the BFS result so
|
|
1133
|
+
// subsequent cold invocations don't repeat the 7-11s tax).
|
|
1134
|
+
if (flags.cache && (needsCacheSave || index.callsCacheDirty || index.reachabilityDirty)) {
|
|
863
1135
|
try { index.saveCache(); } catch (e) { /* best-effort */ }
|
|
864
1136
|
}
|
|
865
1137
|
}
|
|
@@ -966,7 +1238,7 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
966
1238
|
const applicableFlags = FLAG_APPLICABILITY[canonical];
|
|
967
1239
|
if (applicableFlags) {
|
|
968
1240
|
const flagToCli = (f) => '--' + f.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
969
|
-
const globalFlags = new Set(['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles', 'verbose', 'expand', 'interactive', '_fileFromFileMode']);
|
|
1241
|
+
const globalFlags = new Set(['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles', 'verbose', 'expand', 'interactive', '_fileFromFileMode', 'topRaw', 'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'depthRaw', 'contextRaw', 'workersRaw']);
|
|
970
1242
|
for (const [key, value] of Object.entries(flags)) {
|
|
971
1243
|
if (globalFlags.has(key)) continue;
|
|
972
1244
|
if (value === undefined || value === null || value === 0 || (Array.isArray(value) && value.length === 0)) continue;
|
|
@@ -1032,7 +1304,7 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
1032
1304
|
break;
|
|
1033
1305
|
case 'about':
|
|
1034
1306
|
printOutput(result, output.formatAboutJson,
|
|
1035
|
-
r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth, showConfidence: flags.showConfidence !== false }));
|
|
1307
|
+
r => output.formatAbout(r, { expand: flags.expand, root: index.root, depth: flags.depth, showConfidence: flags.showConfidence !== false, compact: !!flags.compact }));
|
|
1036
1308
|
break;
|
|
1037
1309
|
case 'context':
|
|
1038
1310
|
if (flags.json) {
|
|
@@ -1043,6 +1315,7 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
1043
1315
|
uncertainHint: 'use --include-uncertain to include all',
|
|
1044
1316
|
expandHint: 'Use --expand to see inline callee previews',
|
|
1045
1317
|
showConfidence: flags.showConfidence !== false,
|
|
1318
|
+
compact: !!flags.compact,
|
|
1046
1319
|
});
|
|
1047
1320
|
console.log(text);
|
|
1048
1321
|
if (flags.expand) {
|
|
@@ -1060,6 +1333,15 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
1060
1333
|
printOutput(result, output.formatRelatedJson,
|
|
1061
1334
|
r => output.formatRelated(r, { all: flags.all, top: flags.top }));
|
|
1062
1335
|
break;
|
|
1336
|
+
case 'brief':
|
|
1337
|
+
printOutput(result, output.formatBriefJson, output.formatBrief);
|
|
1338
|
+
break;
|
|
1339
|
+
case 'doctor':
|
|
1340
|
+
printOutput(result, output.formatDoctorJson, output.formatDoctor);
|
|
1341
|
+
break;
|
|
1342
|
+
case 'check':
|
|
1343
|
+
printOutput(result, output.formatCheckJson, output.formatCheck);
|
|
1344
|
+
break;
|
|
1063
1345
|
case 'trace':
|
|
1064
1346
|
printOutput(result, output.formatTraceJson, output.formatTrace);
|
|
1065
1347
|
break;
|
|
@@ -1115,9 +1397,15 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
1115
1397
|
case 'entrypoints':
|
|
1116
1398
|
printOutput(result, output.formatEntrypointsJson, output.formatEntrypoints);
|
|
1117
1399
|
break;
|
|
1400
|
+
case 'endpoints':
|
|
1401
|
+
printOutput(result, output.formatEndpointsJson, r => output.formatEndpoints(r, { bridge: r._bridge, unmatched: r._unmatched }));
|
|
1402
|
+
break;
|
|
1118
1403
|
case 'diffImpact':
|
|
1119
1404
|
printOutput(result, output.formatDiffImpactJson, output.formatDiffImpact);
|
|
1120
1405
|
break;
|
|
1406
|
+
case 'auditAsync':
|
|
1407
|
+
printOutput(result, output.formatAuditAsyncJson, output.formatAuditAsync);
|
|
1408
|
+
break;
|
|
1121
1409
|
case 'stacktrace':
|
|
1122
1410
|
printOutput(result, output.formatStackTraceJson, output.formatStackTrace);
|
|
1123
1411
|
break;
|
|
@@ -1141,6 +1429,8 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
1141
1429
|
// ============================================================================
|
|
1142
1430
|
|
|
1143
1431
|
|
|
1432
|
+
// Single source of truth for the public CLI help. README points here ("Run `ucn --help`")
|
|
1433
|
+
// rather than carrying a copy — keep it that way.
|
|
1144
1434
|
function printUsage() {
|
|
1145
1435
|
console.log(`UCN - Universal Code Navigator
|
|
1146
1436
|
|
|
@@ -1157,6 +1447,7 @@ Usage:
|
|
|
1157
1447
|
UNDERSTAND CODE
|
|
1158
1448
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1159
1449
|
about <name> Full picture (definition, callers, callees, tests, code)
|
|
1450
|
+
brief <name> One-screen summary (signature, docstring, side effects, complexity)
|
|
1160
1451
|
context <name> Who calls this + what it calls (numbered for expand)
|
|
1161
1452
|
smart <name> Function + all dependencies inline
|
|
1162
1453
|
impact <name> What breaks if changed (call sites grouped by file)
|
|
@@ -1200,16 +1491,22 @@ REFACTORING HELPERS
|
|
|
1200
1491
|
plan <name> Preview refactoring (--add-param, --remove-param, --rename-to)
|
|
1201
1492
|
verify <name> Check all call sites match signature
|
|
1202
1493
|
diff-impact What changed in git diff and who calls it (--base, --staged)
|
|
1494
|
+
check Pre-commit summary: diff-impact + verify + affected-tests in one shot
|
|
1203
1495
|
deadcode Find unused functions/classes
|
|
1204
1496
|
entrypoints Detect framework entry points (routes, DI, tasks)
|
|
1497
|
+
endpoints HTTP API: list server routes + client requests; --bridge to match
|
|
1498
|
+
--bridge --server-only --client-only --unmatched
|
|
1499
|
+
--method=GET --prefix=/api --hide-uncertain
|
|
1205
1500
|
|
|
1206
1501
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1207
1502
|
OTHER
|
|
1208
1503
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1209
1504
|
api Show exported/public symbols
|
|
1210
1505
|
typedef <name> Find type definitions
|
|
1211
|
-
stats Project statistics (--functions for per-function line counts)
|
|
1506
|
+
stats Project statistics (--functions for per-function line counts, --hot for top callers)
|
|
1507
|
+
doctor Project trust report (counts, blind spots, parse failures, verdict; --deep for resolution coverage)
|
|
1212
1508
|
stacktrace <text> Parse stack trace, show code at each frame (alias: stack)
|
|
1509
|
+
audit-async Find calls in async functions that are likely missing await (JS/TS/Python)
|
|
1213
1510
|
|
|
1214
1511
|
Common Flags:
|
|
1215
1512
|
--file <pattern> Filter by file path (e.g., --file=routes)
|
|
@@ -1228,18 +1525,30 @@ Common Flags:
|
|
|
1228
1525
|
--with-types Include type definitions (about, smart)
|
|
1229
1526
|
--detailed Show all symbols in toc (not just counts)
|
|
1230
1527
|
--include-tests Include test files in usage counts (about) and results (find, usages, deadcode)
|
|
1528
|
+
--exclude-tests Exclude test files (entrypoints — tests are included by default)
|
|
1231
1529
|
--class-name=X Scope to specific class (e.g., --class-name=Repository)
|
|
1232
1530
|
--include-methods Include method calls (obj.fn) in caller/callee analysis
|
|
1233
1531
|
--include-uncertain Include ambiguous/uncertain matches
|
|
1234
|
-
--
|
|
1532
|
+
--hide-confidence Hide confidence scores (shown by default in about, context)
|
|
1235
1533
|
--min-confidence=N Filter low-confidence edges (about, context, blast, trace,
|
|
1236
1534
|
reverse-trace, smart, affected-tests)
|
|
1237
|
-
--
|
|
1535
|
+
--unreachable-only Show only callers/callees that are unreachable from entry points (about, context, impact)
|
|
1238
1536
|
--include-exported Include exported symbols in deadcode
|
|
1239
1537
|
--no-regex Force plain text search (regex is default)
|
|
1240
1538
|
--functions Show per-function line counts (stats command)
|
|
1539
|
+
--hot Show top N most-called functions (stats command, pair with --top=N)
|
|
1540
|
+
--diverse Cluster call sites by argument shape (example command, pair with --top=N)
|
|
1541
|
+
--git Attach git enrichment (last modified, author, recent commits) to about/brief
|
|
1241
1542
|
--include-decorated Include decorated/annotated symbols in deadcode
|
|
1242
1543
|
--framework=X Filter entrypoints by framework (e.g., --framework=express,spring)
|
|
1544
|
+
--bridge Match server routes to client requests (endpoints command).
|
|
1545
|
+
Confidence tiers: EXACT, PARTIAL, UNCERTAIN
|
|
1546
|
+
--server-only Only list server routes (endpoints command)
|
|
1547
|
+
--client-only Only list client requests (endpoints command)
|
|
1548
|
+
--unmatched Only show routes/requests with no match (endpoints, pair with --bridge)
|
|
1549
|
+
--method=X Filter by HTTP method (endpoints, e.g., --method=POST)
|
|
1550
|
+
--prefix=X Filter routes/requests by path prefix (endpoints, e.g., --prefix=/api)
|
|
1551
|
+
--hide-uncertain Hide UNCERTAIN-confidence bridges (endpoints command)
|
|
1243
1552
|
--exact Exact name match only (find, typedef)
|
|
1244
1553
|
--calls-only Only show call/test-case matches (tests)
|
|
1245
1554
|
--case-sensitive Case-sensitive text search (search)
|
|
@@ -1357,7 +1666,7 @@ Flags can be added per-command: context myFunc --include-methods
|
|
|
1357
1666
|
const tokens = input.split(/\s+/);
|
|
1358
1667
|
const command = tokens[0];
|
|
1359
1668
|
// Flags that take a space-separated value (--flag value)
|
|
1360
|
-
const valueFlagNames = new Set(['--file', '--in', '--base', '--add-param', '--remove-param', '--rename-to', '--default', '--depth', '--top', '--context', '--max-lines', '--direction', '--exclude', '--not', '--stack', '--type', '--param', '--receiver', '--returns', '--decorator', '--limit', '--max-files', '--min-confidence', '--class-name', '--framework']);
|
|
1669
|
+
const valueFlagNames = new Set(['--file', '--in', '--base', '--add-param', '--remove-param', '--rename-to', '--default', '--depth', '--top', '--context', '--max-lines', '--direction', '--exclude', '--not', '--stack', '--type', '--param', '--receiver', '--returns', '--decorator', '--limit', '--max-files', '--min-confidence', '--class-name', '--framework', '--method', '--prefix']);
|
|
1361
1670
|
const flagTokens = [];
|
|
1362
1671
|
const argTokens = [];
|
|
1363
1672
|
const skipNext = new Set();
|
|
@@ -1378,10 +1687,18 @@ Flags can be added per-command: context myFunc --include-methods
|
|
|
1378
1687
|
const iflags = parseFlags(flagTokens);
|
|
1379
1688
|
|
|
1380
1689
|
try {
|
|
1690
|
+
// Validate numeric flags (--top, --limit, etc) — same rules as
|
|
1691
|
+
// global CLI mode. MED-2/MED-3/MED-5: bad values are rejected with
|
|
1692
|
+
// a helpful message instead of being silently coerced.
|
|
1693
|
+
validateNumericFlags(iflags);
|
|
1381
1694
|
const iCanonical = resolveCommand(command, 'cli') || command;
|
|
1382
1695
|
executeInteractiveCommand(index, iCanonical, arg, iflags, iExpandCache);
|
|
1383
1696
|
} catch (e) {
|
|
1384
|
-
|
|
1697
|
+
if (e instanceof FlagValidationError) {
|
|
1698
|
+
console.log(e.message);
|
|
1699
|
+
} else {
|
|
1700
|
+
console.error(`Error: ${e.message}`);
|
|
1701
|
+
}
|
|
1385
1702
|
}
|
|
1386
1703
|
|
|
1387
1704
|
rl.prompt();
|
|
@@ -1406,14 +1723,15 @@ Flags can be added per-command: context myFunc --include-methods
|
|
|
1406
1723
|
|
|
1407
1724
|
const INTERACTIVE_DISPATCH = {
|
|
1408
1725
|
// ── Understanding Code ───────────────────────────────────────────
|
|
1409
|
-
about: { params: 'name', format: (r, _a, f, idx) => output.formatAbout(r, { expand: f.expand, root: idx.root, showAll: f.all, depth: f.depth, showConfidence: f.showConfidence !== false }) },
|
|
1726
|
+
about: { params: 'name', format: (r, _a, f, idx) => output.formatAbout(r, { expand: f.expand, root: idx.root, showAll: f.all, depth: f.depth, showConfidence: f.showConfidence !== false, git: !!f.git }) },
|
|
1410
1727
|
smart: { params: 'name', format: (r) => output.formatSmart(r, { uncertainHint: 'use --include-uncertain to include all' }) },
|
|
1411
1728
|
impact: { params: 'name', format: (r) => output.formatImpact(r) },
|
|
1412
1729
|
blast: { params: 'name', format: (r) => output.formatBlast(r) },
|
|
1413
1730
|
trace: { params: 'name', format: (r) => output.formatTrace(r) },
|
|
1414
1731
|
reverseTrace: { params: 'name', format: (r) => output.formatReverseTrace(r) },
|
|
1415
1732
|
related: { params: 'name', format: (r, _a, f) => output.formatRelated(r, { all: f.all, top: f.top }) },
|
|
1416
|
-
example: { params:
|
|
1733
|
+
example: { params: (a, f) => ({ name: a, file: f.file, className: f.className, diverse: f.diverse, top: f.top || undefined, includeTests: f.includeTests }), format: (r, a) => output.formatExample(r, a) },
|
|
1734
|
+
brief: { params: 'name', format: (r) => output.formatBrief(r) },
|
|
1417
1735
|
|
|
1418
1736
|
// ── Finding Code ─────────────────────────────────────────────────
|
|
1419
1737
|
find: { params: 'name', format: (r, a, f) => output.formatFindDetailed(r, a, { depth: f.depth, top: f.top, all: f.all }) },
|
|
@@ -1434,12 +1752,20 @@ const INTERACTIVE_DISPATCH = {
|
|
|
1434
1752
|
plan: { params: 'name', format: (r) => output.formatPlan(r) },
|
|
1435
1753
|
verify: { params: 'name', format: (r) => output.formatVerify(r) },
|
|
1436
1754
|
diffImpact: { params: (a, f) => ({ base: f.base, staged: f.staged, file: f.file, limit: f.limit, all: f.all }), format: (r, _a, f) => output.formatDiffImpact(r, { all: f.all }) },
|
|
1437
|
-
|
|
1755
|
+
check: { params: (a, f) => ({ base: f.base, staged: f.staged, file: f.file, limit: f.limit }), format: (r) => output.formatCheck(r) },
|
|
1756
|
+
entrypoints: { params: (a, f) => ({ type: f.type, framework: f.framework, file: f.file, exclude: f.exclude, includeTests: f.includeTests, excludeTests: f.excludeTests, limit: f.limit }), format: (r) => output.formatEntrypoints(r) },
|
|
1757
|
+
endpoints: { params: (a, f) => ({ file: f.file, exclude: f.exclude, limit: f.limit, framework: f.framework, bridge: f.bridge, serverOnly: f.serverOnly, clientOnly: f.clientOnly, unmatched: f.unmatched, method: f.method, prefix: f.prefix, hideUncertain: f.hideUncertain }), format: (r) => output.formatEndpoints(r, { bridge: r._bridge, unmatched: r._unmatched }) },
|
|
1438
1758
|
|
|
1439
1759
|
// ── Other ────────────────────────────────────────────────────────
|
|
1440
1760
|
api: { params: (a, f) => ({ file: a || f.file, limit: f.limit }), format: (r, a, f) => output.formatApi(r, a || f.file || '.') },
|
|
1441
1761
|
stacktrace: { params: (a, f) => ({ stack: f.stack || a }), format: (r) => output.formatStackTrace(r) },
|
|
1442
|
-
|
|
1762
|
+
doctor: { params: (a, f) => ({ file: f.file, in: f.in, limit: f.limit, deep: f.deep }), format: (r) => output.formatDoctor(r) },
|
|
1763
|
+
// MED-2: stats handler in execute.js rejects top<=0; without explicit
|
|
1764
|
+
// coercion, parseFlags's `top: 0` default would surface as
|
|
1765
|
+
// "Invalid --top value" on bare `stats`. Mirror the project-mode top
|
|
1766
|
+
// coercion (topRaw when present, else undefined for default-10).
|
|
1767
|
+
stats: { params: (a, f) => ({ functions: f.functions, hot: f.hot, top: f.topRaw != null ? f.topRaw : (f.top || undefined) }), format: (r, _a, f) => output.formatStats(r, { top: f.top }) },
|
|
1768
|
+
auditAsync: { params: (a, f) => ({ file: f.file, exclude: f.exclude, limit: f.limit }), format: (r) => output.formatAuditAsync(r) },
|
|
1443
1769
|
};
|
|
1444
1770
|
|
|
1445
1771
|
/**
|
|
@@ -1464,7 +1790,7 @@ function executeInteractiveCommand(index, command, arg, iflags = {}, cache = nul
|
|
|
1464
1790
|
const applicableFlags = FLAG_APPLICABILITY[command];
|
|
1465
1791
|
if (applicableFlags) {
|
|
1466
1792
|
const flagToCli = (f) => '--' + f.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
1467
|
-
const globalFlags = new Set(['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles', 'verbose', 'expand', 'interactive', '_fileFromFileMode']);
|
|
1793
|
+
const globalFlags = new Set(['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles', 'verbose', 'expand', 'interactive', '_fileFromFileMode', 'topRaw', 'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'depthRaw', 'contextRaw', 'workersRaw']);
|
|
1468
1794
|
for (const [key, value] of Object.entries(iflags)) {
|
|
1469
1795
|
if (globalFlags.has(key)) continue;
|
|
1470
1796
|
if (value === undefined || value === null || value === 0 || (Array.isArray(value) && value.length === 0)) continue;
|