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/cli/index.js
CHANGED
|
@@ -30,13 +30,22 @@ const {
|
|
|
30
30
|
formatSurfaceMessage,
|
|
31
31
|
getCliFlagsForCommand,
|
|
32
32
|
getCliAcceptedFlags,
|
|
33
|
+
getCliValueFlags,
|
|
34
|
+
generateCliParamReference,
|
|
35
|
+
commandMode,
|
|
36
|
+
paramDefault,
|
|
37
|
+
paramFact,
|
|
38
|
+
paramRange,
|
|
39
|
+
paramValues,
|
|
40
|
+
GLOBAL_PARAMS,
|
|
33
41
|
} = require('../core/registry');
|
|
34
|
-
const {
|
|
42
|
+
const { FlagValidationError, parseFlags, parseProcessFlags, validateFlags, processInteger } = require('./flags');
|
|
43
|
+
const { buildPublicParams, isPublicCommand, noOpNotes } = require('../core/public-command');
|
|
35
44
|
const { execute } = require('../core/execute');
|
|
36
45
|
const { describeError } = require('../core/errors');
|
|
37
|
-
const { applyOutputBudget
|
|
46
|
+
const { applyOutputBudget } = require('../core/output-budget');
|
|
38
47
|
const { clearAllCaches } = require('../core/cache');
|
|
39
|
-
const { commentLines } = require('../core/output/lines');
|
|
48
|
+
const { commentLines, budgetLineListing } = require('../core/output/lines');
|
|
40
49
|
|
|
41
50
|
// A downstream consumer such as head may finish before our write drains.
|
|
42
51
|
// Handle the pipe closure without an unhandled Node error or bypassing cache
|
|
@@ -53,13 +62,6 @@ let activeCanonicalCommand = null;
|
|
|
53
62
|
// Thrown instead of process.exit(1) so finally blocks can run (cache save).
|
|
54
63
|
class CommandError extends Error { constructor() { super(); } }
|
|
55
64
|
|
|
56
|
-
// Thrown by validateNumericFlags when a numeric flag has a bad value.
|
|
57
|
-
// The CLI top-level catches this, prints the message, and exits 2 (JSON: 1). Interactive
|
|
58
|
-
// mode catches it inside its REPL try/catch and continues the session.
|
|
59
|
-
class FlagValidationError extends Error {
|
|
60
|
-
constructor(msg) { super(msg); this.name = 'FlagValidationError'; }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
65
|
function unknownCommandMessage(command, { interactive = false } = {}) {
|
|
64
66
|
const migration = v4MigrationHint(command, 'cli');
|
|
65
67
|
if (migration) return `Unknown command: ${command}. ${migration}`;
|
|
@@ -77,11 +79,9 @@ function resultExitCode(command, result) {
|
|
|
77
79
|
if (result && result.ok === false) return 2;
|
|
78
80
|
if (command !== 'check' || !result) return 0;
|
|
79
81
|
if (result.trust) {
|
|
80
|
-
// Target-less pre-commit check
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
// body edit fail CI; incomplete accounts and signature drift do.
|
|
84
|
-
return result.trust.status === 'BLOCKED' ? 1 : 0;
|
|
82
|
+
// Target-less pre-commit check: a definite break fails, something
|
|
83
|
+
// to look at does not, unless the caller asked for --strict.
|
|
84
|
+
return result.trust.passes === false ? 1 : 0;
|
|
85
85
|
}
|
|
86
86
|
// Symbol-mode check (verify): a mismatch or unresolved call-site tier is
|
|
87
87
|
// review-required and must fail a CI gate.
|
|
@@ -89,113 +89,11 @@ function resultExitCode(command, result) {
|
|
|
89
89
|
Number(result.unverifiedCount || 0) > 0) ? 1 : 0;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/**
|
|
93
|
-
* Validate that a raw flag value is a positive integer. Returns the parsed
|
|
94
|
-
* number when valid, or throws FlagValidationError. Callers pass `null`/`undefined`
|
|
95
|
-
* raw values through unchanged (no flag → no validation).
|
|
96
|
-
*
|
|
97
|
-
* @param {string|null|undefined} raw - The raw string captured from the CLI/interactive token.
|
|
98
|
-
* @param {string} flagName - The CLI flag name including dashes (e.g. "--top") for error messages.
|
|
99
|
-
* @param {object} [opts]
|
|
100
|
-
* @param {boolean} [opts.allowZero=false] - Whether 0 is a valid value (e.g. depth=0 may be meaningful).
|
|
101
|
-
* @param {number} [opts.cap=10000000] - Maximum accepted value (rejects 1e100 etc).
|
|
102
|
-
* @returns {number|undefined} The validated integer, or undefined when raw is null/undefined.
|
|
103
|
-
*/
|
|
104
|
-
function validatePositiveInt(raw, flagName, { allowZero = false, cap = 10000000 } = {}) {
|
|
105
|
-
if (raw == null) return undefined;
|
|
106
|
-
const label = allowZero ? 'non-negative integer' : 'positive integer';
|
|
107
|
-
const trimmed = String(raw).trim();
|
|
108
|
-
if (trimmed === '') {
|
|
109
|
-
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got "${raw}")`);
|
|
110
|
-
}
|
|
111
|
-
// CLI integer flags use canonical base-10 spelling. Number()/parseInt()
|
|
112
|
-
// accept exponent, decimal and hexadecimal forms (`1e1`, `2.5`, `0x10`),
|
|
113
|
-
// which is especially dangerous for source-line identity because a
|
|
114
|
-
// malformed pin can accidentally select a real definition.
|
|
115
|
-
if (!/^\d+$/.test(trimmed)) {
|
|
116
|
-
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got "${raw}")`);
|
|
117
|
-
}
|
|
118
|
-
const n = Number(trimmed);
|
|
119
|
-
if (!isFinite(n) || isNaN(n)) {
|
|
120
|
-
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got "${raw}")`);
|
|
121
|
-
}
|
|
122
|
-
if (!Number.isInteger(n)) {
|
|
123
|
-
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got ${n})`);
|
|
124
|
-
}
|
|
125
|
-
if (allowZero) {
|
|
126
|
-
if (n < 0) {
|
|
127
|
-
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got ${n})`);
|
|
128
|
-
}
|
|
129
|
-
} else if (n <= 0) {
|
|
130
|
-
throw new FlagValidationError(`Invalid ${flagName} value: must be a ${label} (got ${n})`);
|
|
131
|
-
}
|
|
132
|
-
if (n > cap) {
|
|
133
|
-
throw new FlagValidationError(`Invalid ${flagName} value: ${n} exceeds maximum (${cap})`);
|
|
134
|
-
}
|
|
135
|
-
return n;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Validate all numeric flags on a parsed flags object. Looks at the *Raw
|
|
140
|
-
* companion strings preserved by parseFlags so we catch user-supplied bad
|
|
141
|
-
* values regardless of whether the parsed numeric form happened to be falsy.
|
|
142
|
-
* Mutates `flags` to hold the validated numeric values.
|
|
143
|
-
*
|
|
144
|
-
* Throws FlagValidationError on the first invalid flag.
|
|
145
|
-
*/
|
|
146
|
-
function validateNumericFlags(flags) {
|
|
147
|
-
// --top: positive integer, no zero. Used by show/find/repo/etc.
|
|
148
|
-
if (flags.topRaw != null) {
|
|
149
|
-
flags.top = validatePositiveInt(flags.topRaw, '--top');
|
|
150
|
-
}
|
|
151
|
-
// --limit: positive integer, no zero. Reject "0 = no limit" silent coercion.
|
|
152
|
-
if (flags.limitRaw != null) {
|
|
153
|
-
flags.limit = validatePositiveInt(flags.limitRaw, '--limit');
|
|
154
|
-
}
|
|
155
|
-
// --max-files: positive integer, no zero.
|
|
156
|
-
if (flags.maxFilesRaw != null) {
|
|
157
|
-
flags.maxFiles = validatePositiveInt(flags.maxFilesRaw, '--max-files');
|
|
158
|
-
}
|
|
159
|
-
if (flags.lineRaw != null) {
|
|
160
|
-
flags.line = validatePositiveInt(flags.lineRaw, '--line');
|
|
161
|
-
}
|
|
162
|
-
// --max-lines: positive integer, no zero. Used by source.
|
|
163
|
-
if (flags.maxLinesRaw != null) {
|
|
164
|
-
flags.maxLines = validatePositiveInt(flags.maxLinesRaw, '--max-lines');
|
|
165
|
-
}
|
|
166
|
-
// --depth: non-negative integer (0 is meaningful: "this symbol only").
|
|
167
|
-
if (flags.depthRaw != null) {
|
|
168
|
-
flags.depth = validatePositiveInt(flags.depthRaw, '--depth', { allowZero: true });
|
|
169
|
-
}
|
|
170
|
-
// --context: non-negative integer (0 = no surrounding lines).
|
|
171
|
-
if (flags.contextRaw != null) {
|
|
172
|
-
flags.context = validatePositiveInt(flags.contextRaw, '--context', { allowZero: true });
|
|
173
|
-
}
|
|
174
|
-
// --workers: non-negative integer (0 disables parallel build).
|
|
175
|
-
if (flags.workersRaw != null) {
|
|
176
|
-
flags.workers = validatePositiveInt(flags.workersRaw, '--workers', { allowZero: true });
|
|
177
|
-
}
|
|
178
|
-
if (flags.maxCharsRaw != null) {
|
|
179
|
-
flags.maxChars = validatePositiveInt(flags.maxCharsRaw, '--max-chars', {
|
|
180
|
-
cap: MAX_OUTPUT_CHARS,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
// --min-confidence: number in [0,1] (ordinal evidence weight). Anything
|
|
184
|
-
// else used to coerce to 0 silently — "abc" behaved like no filter.
|
|
185
|
-
if (flags.minConfidenceRaw != null) {
|
|
186
|
-
const value = Number(flags.minConfidenceRaw);
|
|
187
|
-
if (flags.minConfidenceRaw === '' || !Number.isFinite(value) ||
|
|
188
|
-
value < 0 || value > 1) {
|
|
189
|
-
throw new FlagValidationError(
|
|
190
|
-
`Invalid --min-confidence value: "${flags.minConfidenceRaw}" — must be a number between 0 and 1.`);
|
|
191
|
-
}
|
|
192
|
-
flags.minConfidence = value;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
92
|
/** Emit the one CLI error contract, including failures before main dispatch. */
|
|
197
93
|
function emitCliError(msg, command = activeCanonicalCommand, { internal = false } = {}) {
|
|
198
|
-
const
|
|
94
|
+
const argv = process.argv.slice(2);
|
|
95
|
+
const end = argv.indexOf('--');
|
|
96
|
+
const wantsJson = parseProcessFlags(end === -1 ? argv : argv.slice(0, end)).json;
|
|
199
97
|
const error = typeof msg === 'string' ? msg : String(msg);
|
|
200
98
|
if (wantsJson) {
|
|
201
99
|
const canonical = command && isPublicCommand(command)
|
|
@@ -254,9 +152,17 @@ function emitCaughtError(e) {
|
|
|
254
152
|
// ============================================================================
|
|
255
153
|
|
|
256
154
|
const rawArgs = process.argv.slice(2);
|
|
155
|
+
const processArgs = rawArgs.includes('--') ? rawArgs.slice(0, rawArgs.indexOf('--')) : rawArgs;
|
|
156
|
+
const processFlags = parseProcessFlags(processArgs);
|
|
157
|
+
try {
|
|
158
|
+
validateFlags(processFlags);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
emitCliError(e.message);
|
|
161
|
+
process.exit(processFlags.json ? 1 : 2);
|
|
162
|
+
}
|
|
257
163
|
|
|
258
164
|
// MCP server mode — launch server and skip CLI
|
|
259
|
-
if (
|
|
165
|
+
if (processFlags.mcp) {
|
|
260
166
|
require('../mcp/server.js');
|
|
261
167
|
} else {
|
|
262
168
|
// Support -- to separate flags from positional arguments
|
|
@@ -264,156 +170,28 @@ const doubleDashIdx = rawArgs.indexOf('--');
|
|
|
264
170
|
const args = doubleDashIdx === -1 ? rawArgs : rawArgs.slice(0, doubleDashIdx);
|
|
265
171
|
const argsAfterDoubleDash = doubleDashIdx === -1 ? [] : rawArgs.slice(doubleDashIdx + 1);
|
|
266
172
|
|
|
267
|
-
// Parse flags
|
|
268
|
-
/**
|
|
269
|
-
* Parse flags from an array of tokens. Supports both --flag=value and --flag value forms.
|
|
270
|
-
* Shared between global CLI mode and interactive mode.
|
|
271
|
-
*/
|
|
272
|
-
function parseFlags(tokens) {
|
|
273
|
-
function getValueFlag(flagName) {
|
|
274
|
-
const eqForm = tokens.find(a => a.startsWith(flagName + '='));
|
|
275
|
-
if (eqForm) return eqForm.split('=').slice(1).join('=');
|
|
276
|
-
const idx = tokens.indexOf(flagName);
|
|
277
|
-
if (idx !== -1 && idx + 1 < tokens.length && !tokens[idx + 1].startsWith('-')) {
|
|
278
|
-
return tokens[idx + 1];
|
|
279
|
-
}
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
function parseExclude() {
|
|
283
|
-
const result = [];
|
|
284
|
-
for (const a of tokens) {
|
|
285
|
-
if (a.startsWith('--exclude=') || a.startsWith('--not=')) {
|
|
286
|
-
result.push(...a.split('=').slice(1).join('=').split(','));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
for (const flag of ['--exclude', '--not']) {
|
|
290
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
291
|
-
if (tokens[i] === flag && i + 1 < tokens.length && !tokens[i + 1].startsWith('-')) {
|
|
292
|
-
result.push(...tokens[i + 1].split(','));
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return result;
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
file: getValueFlag('--file'),
|
|
300
|
-
exclude: parseExclude(),
|
|
301
|
-
in: getValueFlag('--in'),
|
|
302
|
-
includeTests: tokens.includes('--include-tests') ? true : undefined,
|
|
303
|
-
excludeTests: tokens.includes('--exclude-tests') ? true : undefined,
|
|
304
|
-
includeExported: tokens.includes('--include-exported') || undefined,
|
|
305
|
-
includeDecorated: tokens.includes('--include-decorated') || undefined,
|
|
306
|
-
expandUnverified: tokens.includes('--expand-unverified') || undefined,
|
|
307
|
-
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,
|
|
308
|
-
detailed: tokens.includes('--detailed') || undefined,
|
|
309
|
-
topLevel: tokens.includes('--top-level') || undefined,
|
|
310
|
-
all: tokens.includes('--all') || undefined,
|
|
311
|
-
exact: tokens.includes('--exact') || undefined,
|
|
312
|
-
callsOnly: tokens.includes('--calls-only') || undefined,
|
|
313
|
-
codeOnly: tokens.includes('--code-only') || undefined,
|
|
314
|
-
caseSensitive: tokens.includes('--case-sensitive') || undefined,
|
|
315
|
-
withTypes: tokens.includes('--with-types') || undefined,
|
|
316
|
-
withSource: tokens.includes('--with-source') || undefined,
|
|
317
|
-
cycles: tokens.includes('--cycles') || undefined,
|
|
318
|
-
depth: getValueFlag('--depth'),
|
|
319
|
-
depthRaw: getValueFlag('--depth'),
|
|
320
|
-
// `top` is the parsed numeric value (NaN/0 default → falsy). `topRaw`
|
|
321
|
-
// preserves the original string so downstream validators can produce
|
|
322
|
-
// helpful errors for "abc"/"-1"/"0" instead of silently defaulting.
|
|
323
|
-
top: parseInt(getValueFlag('--top') || '0'),
|
|
324
|
-
topRaw: getValueFlag('--top'),
|
|
325
|
-
context: parseInt(getValueFlag('--context') || '0'),
|
|
326
|
-
contextRaw: getValueFlag('--context'),
|
|
327
|
-
direction: getValueFlag('--direction'),
|
|
328
|
-
to: getValueFlag('--to'),
|
|
329
|
-
sections: getValueFlag('--sections'),
|
|
330
|
-
range: getValueFlag('--range'),
|
|
331
|
-
addParam: getValueFlag('--add-param'),
|
|
332
|
-
removeParam: getValueFlag('--remove-param'),
|
|
333
|
-
renameTo: getValueFlag('--rename-to'),
|
|
334
|
-
defaultValue: getValueFlag('--default-value') ?? getValueFlag('--default'),
|
|
335
|
-
base: getValueFlag('--base'),
|
|
336
|
-
staged: tokens.includes('--staged') || undefined,
|
|
337
|
-
deep: tokens.includes('--deep') || undefined,
|
|
338
|
-
compact: tokens.includes('--no-compact')
|
|
339
|
-
? false
|
|
340
|
-
: (tokens.includes('--compact') ? true : undefined),
|
|
341
|
-
maxLines: getValueFlag('--max-lines') || null,
|
|
342
|
-
maxLinesRaw: getValueFlag('--max-lines'),
|
|
343
|
-
regex: tokens.includes('--regex')
|
|
344
|
-
? true
|
|
345
|
-
: (tokens.includes('--no-regex') ? false : undefined),
|
|
346
|
-
functions: tokens.includes('--functions') || undefined,
|
|
347
|
-
hot: tokens.includes('--hot') || undefined,
|
|
348
|
-
diverse: tokens.includes('--diverse') || undefined,
|
|
349
|
-
raw: tokens.includes('--raw') || undefined,
|
|
350
|
-
lines: tokens.includes('--lines') || undefined,
|
|
351
|
-
git: tokens.includes('--git') || undefined,
|
|
352
|
-
className: getValueFlag('--class-name'),
|
|
353
|
-
// Explicit line pin (fix #249: our own disambiguation notes advertise
|
|
354
|
-
// line= but no surface accepted it).
|
|
355
|
-
line: parseInt(getValueFlag('--line') || '0', 10) || undefined,
|
|
356
|
-
lineRaw: getValueFlag('--line'),
|
|
357
|
-
limit: parseInt(getValueFlag('--limit') || '0') || undefined,
|
|
358
|
-
limitRaw: getValueFlag('--limit'),
|
|
359
|
-
maxFiles: parseInt(getValueFlag('--max-files') || '0') || undefined,
|
|
360
|
-
maxFilesRaw: getValueFlag('--max-files'),
|
|
361
|
-
maxChars: parseInt(getValueFlag('--max-chars') || '0') || undefined,
|
|
362
|
-
maxCharsRaw: getValueFlag('--max-chars'),
|
|
363
|
-
// Structural search flags
|
|
364
|
-
type: getValueFlag('--type'),
|
|
365
|
-
param: getValueFlag('--param'),
|
|
366
|
-
receiver: getValueFlag('--receiver'),
|
|
367
|
-
returns: getValueFlag('--returns'),
|
|
368
|
-
decorator: getValueFlag('--decorator'),
|
|
369
|
-
exported: tokens.includes('--exported') || undefined,
|
|
370
|
-
unused: tokens.includes('--unused') || undefined,
|
|
371
|
-
showConfidence: (tokens.includes('--hide-confidence') || tokens.includes('--no-confidence')) ? false
|
|
372
|
-
: tokens.includes('--show-confidence') ? true : undefined,
|
|
373
|
-
minConfidence: 0,
|
|
374
|
-
minConfidenceRaw: getValueFlag('--min-confidence'),
|
|
375
|
-
unreachableOnly: tokens.includes('--unreachable-only') || undefined,
|
|
376
|
-
framework: getValueFlag('--framework'),
|
|
377
|
-
// endpoints command flags
|
|
378
|
-
bridge: tokens.includes('--bridge') || undefined,
|
|
379
|
-
serverOnly: tokens.includes('--server-only') || undefined,
|
|
380
|
-
clientOnly: tokens.includes('--client-only') || undefined,
|
|
381
|
-
unmatched: tokens.includes('--unmatched') || undefined,
|
|
382
|
-
method: getValueFlag('--method'),
|
|
383
|
-
prefix: getValueFlag('--prefix'),
|
|
384
|
-
hideUncertain: tokens.includes('--hide-uncertain') || tokens.includes('--no-uncertain') || undefined,
|
|
385
|
-
stack: getValueFlag('--stack'),
|
|
386
|
-
workersRaw: getValueFlag('--workers'),
|
|
387
|
-
includeBundled: tokens.includes('--include-bundled') || undefined,
|
|
388
|
-
workers: (() => {
|
|
389
|
-
const v = getValueFlag('--workers');
|
|
390
|
-
if (v === null) return undefined;
|
|
391
|
-
const n = parseInt(v, 10);
|
|
392
|
-
return isNaN(n) ? undefined : n;
|
|
393
|
-
})(),
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
173
|
// Parse shared flags from CLI args, then add global-only flags
|
|
174
|
+
// Parameters come from the registry's table (cli/flags.js); the flags of the
|
|
175
|
+
// CLI process itself are read here.
|
|
398
176
|
const flags = parseFlags(args);
|
|
399
|
-
flags.json =
|
|
400
|
-
flags.quiet =
|
|
401
|
-
flags.cache =
|
|
402
|
-
flags.clearCache =
|
|
403
|
-
flags.interactive =
|
|
404
|
-
flags.followSymlinks =
|
|
177
|
+
flags.json = processFlags.json ?? false;
|
|
178
|
+
flags.quiet = processFlags.quiet ?? true;
|
|
179
|
+
flags.cache = (processFlags.cache ?? true) && !flags.includeBundled;
|
|
180
|
+
flags.clearCache = processFlags.clearCache ?? false;
|
|
181
|
+
flags.interactive = processFlags.interactive ?? false;
|
|
182
|
+
flags.followSymlinks = flags.followSymlinks ?? paramDefault('followSymlinks');
|
|
405
183
|
|
|
406
184
|
// Known flags for validation
|
|
407
185
|
const knownFlags = getCliAcceptedFlags();
|
|
408
186
|
|
|
409
187
|
// Handle help flag
|
|
410
|
-
if (
|
|
188
|
+
if (processFlags.help) {
|
|
411
189
|
printUsage();
|
|
412
190
|
process.exit(0);
|
|
413
191
|
}
|
|
414
192
|
|
|
415
193
|
// Handle version flag — read from package.json (single source of truth, shared with MCP serverInfo)
|
|
416
|
-
if (
|
|
194
|
+
if (processFlags.version) {
|
|
417
195
|
console.log(require('../package.json').version);
|
|
418
196
|
process.exit(0);
|
|
419
197
|
}
|
|
@@ -433,11 +211,11 @@ if (unknownFlags.length > 0) {
|
|
|
433
211
|
process.exit(flags.json ? 1 : 2);
|
|
434
212
|
}
|
|
435
213
|
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
// --context, --workers. Throws FlagValidationError with a helpful message.
|
|
214
|
+
// Refuse a flag value the parameter table does not take before any index is
|
|
215
|
+
// built: a number outside its bounds, a flag written without its value.
|
|
439
216
|
try {
|
|
440
|
-
|
|
217
|
+
validateFlags(flags);
|
|
218
|
+
flags.workers = processInteger(args, '--workers');
|
|
441
219
|
} catch (e) {
|
|
442
220
|
if (e instanceof FlagValidationError) {
|
|
443
221
|
emitCliError(e.message);
|
|
@@ -447,20 +225,12 @@ try {
|
|
|
447
225
|
}
|
|
448
226
|
|
|
449
227
|
// Value flags that consume the next token (space form: --flag value)
|
|
450
|
-
const VALUE_FLAGS =
|
|
451
|
-
'--file', '--depth', '--top', '--context', '--direction', '--to', '--sections', '--range',
|
|
452
|
-
'--add-param', '--remove-param', '--rename-to', '--default', '--default-value',
|
|
453
|
-
'--base', '--exclude', '--not', '--in', '--max-lines', '--class-name', '--line',
|
|
454
|
-
'--type', '--param', '--receiver', '--returns', '--decorator',
|
|
455
|
-
'--limit', '--max-files', '--max-chars', '--min-confidence', '--stack', '--framework',
|
|
456
|
-
'--workers', '--method', '--prefix'
|
|
457
|
-
]);
|
|
228
|
+
const VALUE_FLAGS = getCliValueFlags();
|
|
458
229
|
|
|
459
230
|
// Remove flags from args, then add args after -- (which are all positional)
|
|
460
231
|
const positionalArgs = [
|
|
461
232
|
...args.filter((a, idx) =>
|
|
462
|
-
!a.startsWith('
|
|
463
|
-
a !== '-i' &&
|
|
233
|
+
!a.startsWith('-') &&
|
|
464
234
|
!(idx > 0 && VALUE_FLAGS.has(args[idx - 1]) && !args[idx - 1].includes('='))
|
|
465
235
|
),
|
|
466
236
|
...argsAfterDoubleDash
|
|
@@ -485,18 +255,37 @@ function requireArg(arg, usage) {
|
|
|
485
255
|
}
|
|
486
256
|
}
|
|
487
257
|
|
|
258
|
+
/** Exact raw source and mandatory note bytes, shared by budgets and emitters. */
|
|
259
|
+
function rawCliOutput(text, note, interactive = false) {
|
|
260
|
+
const body = String(text);
|
|
261
|
+
return {
|
|
262
|
+
source: body + (interactive || !body.endsWith('\n') ? '\n' : ''),
|
|
263
|
+
note: note ? commentLines(formatSurfaceMessage(note, 'cli')).join('\n') + '\n' : '',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
488
267
|
function formatCliText(command, result, params, execution, displayFlags) {
|
|
489
268
|
const text = output.formatPublicText(command, result, params, {
|
|
490
269
|
...execution,
|
|
491
270
|
surface: 'cli',
|
|
492
271
|
});
|
|
493
|
-
// --lines / --raw are pipe surfaces
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
272
|
+
// --lines / --raw are pipe surfaces with no default budget, and an empty
|
|
273
|
+
// answer must stay empty (grep prints nothing and exits 1). Under an
|
|
274
|
+
// explicit --max-chars a line listing prints the rows that fit and one
|
|
275
|
+
// line counting the rest, as `| head` would; a record is never cut. Raw
|
|
276
|
+
// source is never cut either, since a truncated function body is worse
|
|
277
|
+
// than a long one: there the budget is a fail-before-output guard.
|
|
497
278
|
if (params?.lines || params?.raw) {
|
|
498
|
-
|
|
499
|
-
|
|
279
|
+
const rawOutput = params.raw ? rawCliOutput(text, execution?.note, displayFlags?.interactive) : null;
|
|
280
|
+
const emittedChars = params.lines
|
|
281
|
+
? (text.split('\n').filter(Boolean).join('\n').length + (text ? 1 : 0))
|
|
282
|
+
: rawOutput.source.length + rawOutput.note.length;
|
|
283
|
+
if (displayFlags?.maxChars && emittedChars > displayFlags.maxChars) {
|
|
284
|
+
const listing = params.lines ? budgetLineListing(text, displayFlags.maxChars - 1, 'cli') : null;
|
|
285
|
+
if (listing) return listing.text;
|
|
286
|
+
fail(params.lines
|
|
287
|
+
? `Output exceeds --max-chars=${displayFlags.maxChars} and not one row fits beside the accounting lines. Raise --max-chars or omit it.`
|
|
288
|
+
: `Output exceeds --max-chars=${displayFlags.maxChars}; shell output cannot be truncated. Narrow the query, use --limit/--max-lines, or omit --max-chars.`);
|
|
500
289
|
}
|
|
501
290
|
return text;
|
|
502
291
|
}
|
|
@@ -507,6 +296,7 @@ function formatCliText(command, result, params, execution, displayFlags) {
|
|
|
507
296
|
surface: 'cli',
|
|
508
297
|
params,
|
|
509
298
|
trailingChars: 1, // emitCliText appends the terminal newline.
|
|
299
|
+
peerText: output.formatPublicText(command, result, params, { ...execution, surface: 'mcp' }),
|
|
510
300
|
}).text;
|
|
511
301
|
}
|
|
512
302
|
|
|
@@ -530,11 +320,11 @@ function emitCliText(text, params, json, note) {
|
|
|
530
320
|
return;
|
|
531
321
|
}
|
|
532
322
|
if (!json && params?.raw) {
|
|
533
|
-
const
|
|
534
|
-
process.stdout.write(
|
|
323
|
+
const rawOutput = rawCliOutput(text, note);
|
|
324
|
+
process.stdout.write(rawOutput.source);
|
|
535
325
|
// Code lines are never reinterpreted (a Python comment starts with
|
|
536
326
|
// "# " too), so the note travels on its own channel.
|
|
537
|
-
if (note) process.stderr.write(
|
|
327
|
+
if (rawOutput.note) process.stderr.write(rawOutput.note);
|
|
538
328
|
return;
|
|
539
329
|
}
|
|
540
330
|
console.log(text);
|
|
@@ -638,20 +428,14 @@ function main() {
|
|
|
638
428
|
* Tiered-output contract notes shared by one-shot and interactive mode.
|
|
639
429
|
*/
|
|
640
430
|
function printTieredNoOpNotes(canonical, flags, print) {
|
|
641
|
-
|
|
642
|
-
if (['impact', 'trace', 'tests', 'check'].includes(canonical) && flags.includeMethods) {
|
|
643
|
-
print(`Note: --include-methods has no effect on '${toCliName(canonical)}' — method calls are always tiered by receiver evidence.`);
|
|
644
|
-
}
|
|
645
|
-
if (canonical === 'show' && flags.includeMethods) {
|
|
646
|
-
print(`Note: --include-methods on '${toCliName(canonical)}' affects only method-callee display for standalone-function targets — caller tiers are always evidence-based, and method targets analyze method calls by default.`);
|
|
647
|
-
}
|
|
431
|
+
for (const note of noOpNotes(canonical, flags)) print(`Note: ${formatSurfaceMessage(note, 'cli')}`);
|
|
648
432
|
}
|
|
649
433
|
|
|
434
|
+
// Keys of a parsed flag object that no command's applicability decides: the
|
|
435
|
+
// CLI process flags and the parameters every command accepts.
|
|
650
436
|
const GLOBAL_FLAG_KEYS = new Set([
|
|
651
|
-
'json', 'quiet', 'cache', 'clearCache', '
|
|
652
|
-
'
|
|
653
|
-
'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'depthRaw', 'contextRaw',
|
|
654
|
-
'workers', 'workersRaw', 'lineRaw', 'maxChars', 'maxCharsRaw', 'minConfidenceRaw',
|
|
437
|
+
'json', 'quiet', 'cache', 'clearCache', 'verbose', 'interactive', 'workers',
|
|
438
|
+
'_fileFromFileMode', ...GLOBAL_PARAMS,
|
|
655
439
|
]);
|
|
656
440
|
|
|
657
441
|
/** Apply one command/flag policy across project, file, glob, and REPL modes. */
|
|
@@ -715,7 +499,7 @@ function runFileCommand(filePath, command, arg) {
|
|
|
715
499
|
tests: 'tests <name>', plan: 'plan <name>',
|
|
716
500
|
};
|
|
717
501
|
// Structural search doesn't require term
|
|
718
|
-
const isStructural =
|
|
502
|
+
const isStructural = commandMode('search', flags) === 'structural';
|
|
719
503
|
const hasFlagTarget = canonical === 'source' && flags.range;
|
|
720
504
|
if (needsArg[canonical] && !(canonical === 'search' && isStructural) && !hasFlagTarget) {
|
|
721
505
|
requireArg(arg, `Usage: ucn <file> ${needsArg[canonical]}`);
|
|
@@ -854,6 +638,10 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
854
638
|
if (flags.cache && index.usageCacheDirty) {
|
|
855
639
|
try { index.saveUsageCache(); } catch (e) { /* best-effort */ }
|
|
856
640
|
}
|
|
641
|
+
// A HOT ranking computed by this command, for the next one.
|
|
642
|
+
if (flags.cache && index.hotSummaryDirty) {
|
|
643
|
+
try { index.saveHotSummaries(); } catch (e) { /* best-effort */ }
|
|
644
|
+
}
|
|
857
645
|
}
|
|
858
646
|
}
|
|
859
647
|
|
|
@@ -884,6 +672,7 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
884
672
|
index.discoveryIssues.push(...discoveryIssues.map(issue => ({
|
|
885
673
|
...issue, relativePath: path.relative(index.root, issue.path), path: undefined,
|
|
886
674
|
})));
|
|
675
|
+
index._sourceUniverse = null; // classify with the glob walk's facts included
|
|
887
676
|
|
|
888
677
|
if (!isPublicCommand(canonical)) {
|
|
889
678
|
fail(unknownCommandMessage(command));
|
|
@@ -916,6 +705,13 @@ function printUsage() {
|
|
|
916
705
|
const flags = getCliFlagsForCommand(command);
|
|
917
706
|
return ` ${command.padEnd(14)} ${flags.join(' ')}`;
|
|
918
707
|
}).join('\n');
|
|
708
|
+
// Value lists, defaults and bounds are the registry's parameter table.
|
|
709
|
+
const values = (param, command, separator = '|') => paramValues(param, command).join(separator);
|
|
710
|
+
const budget = {
|
|
711
|
+
max: paramRange('maxChars').max,
|
|
712
|
+
targeted: paramDefault('maxChars', 'show'),
|
|
713
|
+
broad: paramDefault('maxChars', 'repo'),
|
|
714
|
+
};
|
|
919
715
|
console.log(`UCN - Universal Code Navigator
|
|
920
716
|
|
|
921
717
|
Supported: JavaScript/TypeScript, Python, Go, Rust, Java, C, C++, C#, HTML
|
|
@@ -927,12 +723,12 @@ Usage:
|
|
|
927
723
|
|
|
928
724
|
Commands:
|
|
929
725
|
repo Repository overview
|
|
930
|
-
--sections
|
|
726
|
+
--sections=${values('sections', 'repo', ',')} Select repository sections
|
|
931
727
|
--deep Include deep readiness evidence
|
|
932
728
|
show <symbol> Symbol summary and relationships
|
|
933
|
-
--sections
|
|
729
|
+
--sections=${values('sections', 'show', ',')}
|
|
934
730
|
find <name> Definitions; --type=type, --with-source
|
|
935
|
-
text: top
|
|
731
|
+
text: top ${paramFact('limit', 'findDetail')} in detail + a count of the rest; --json: up to ${paramDefault('limit', 'find')};
|
|
936
732
|
--limit=N raises both
|
|
937
733
|
usages <name> Calls, imports, definitions, references (test files
|
|
938
734
|
included; --exclude-tests hides them with a count)
|
|
@@ -940,11 +736,11 @@ Commands:
|
|
|
940
736
|
literal text by default; --regex enables regular-expression syntax
|
|
941
737
|
source <symbol|file:range> Exact function, class, or line extraction
|
|
942
738
|
trace <symbol> Call graph
|
|
943
|
-
--direction
|
|
944
|
-
--to
|
|
739
|
+
--direction=${values('direction', 'trace')} Downstream or upstream (default: ${paramDefault('direction', 'trace')})
|
|
740
|
+
--to=${values('to', 'trace')} Follow callers toward entry points
|
|
945
741
|
impact [symbol] Symbol impact; without symbol, Git-diff impact
|
|
946
742
|
tests <symbol> Direct tests; --depth=N adds transitive impact
|
|
947
|
-
deps <file> File graph; --direction
|
|
743
|
+
deps <file> File graph; --direction=${values('direction', 'deps')}
|
|
948
744
|
--detailed Include import declarations
|
|
949
745
|
deps --cycles Report circular dependencies (no file target)
|
|
950
746
|
api [file] Project or file public API (exact files include tests;
|
|
@@ -961,21 +757,27 @@ Common flags:
|
|
|
961
757
|
--file=PATH --exclude=a,b --in=PATH --depth=N --top=N --limit=N
|
|
962
758
|
--all --compact --no-compact --json --include-tests --class-name=X --line=N
|
|
963
759
|
--range=N-M (source <file> --range=N-M, or --file=PATH)
|
|
964
|
-
--base=REF --staged --no-cache --clear-cache [--all] --max-files=N --workers=N
|
|
965
|
-
--max-chars=N (text characters, not UTF-8 bytes; default
|
|
760
|
+
--base=REF --staged --strict --no-cache --clear-cache [--all] --max-files=N --workers=N
|
|
761
|
+
--max-chars=N (text characters, not UTF-8 bytes; default ${budget.targeted} targeted / ${budget.broad} broad, ceiling ${budget.max})
|
|
762
|
+
JSON is uncapped; meta.maxChars is "not-applied", even with an explicit budget.
|
|
966
763
|
--lines find/usages/search/show/impact: grep -n shape, one path:line:text
|
|
967
764
|
record per output line; usages combines occurrences on each source line
|
|
968
765
|
(tags after a tab: # unverified: <reason>, # import,
|
|
969
766
|
# callee); accounting and notes go to stderr as "# " lines; exit 1
|
|
970
767
|
when nothing matched; exit 2 on errors. No default result cap.
|
|
971
|
-
|
|
768
|
+
Under an explicit --max-chars the rows that fit are printed whole
|
|
769
|
+
and one "# truncated: X of Y rows omitted" line counts the rest.
|
|
770
|
+
show defaults to ${paramDefault('sections', 'show', 'lines').join(',')}; --sections=${paramValues('sections', 'show', 'lines').join(',')} selects bands.
|
|
972
771
|
--raw source: full code, no header or gutter (including large classes).
|
|
973
|
-
|
|
772
|
+
Never cut: an explicit --max-chars it exceeds fails before output.
|
|
974
773
|
Cache: per-user by default; set UCN_CACHE_DIR to override the cache root.
|
|
975
774
|
|
|
976
775
|
Accepted flags by command:
|
|
977
776
|
${perCommandFlags}
|
|
978
777
|
|
|
778
|
+
Values, bounds and defaults:
|
|
779
|
+
${generateCliParamReference()}
|
|
780
|
+
|
|
979
781
|
Global/build/output flags:
|
|
980
782
|
--help -h --version -v --mcp --json --verbose --no-quiet --quiet
|
|
981
783
|
--interactive -i --no-cache --clear-cache --no-follow-symlinks --include-bundled
|
|
@@ -1072,12 +874,12 @@ function runInteractive(rootDir) {
|
|
|
1072
874
|
if (input === 'help') {
|
|
1073
875
|
console.log(`
|
|
1074
876
|
Commands:
|
|
1075
|
-
repo Repository overview (--sections
|
|
877
|
+
repo Repository overview (--sections=${paramValues('sections', 'repo').join(',')})
|
|
1076
878
|
show <name> Symbol summary + relationships (--sections=...)
|
|
1077
|
-
find <name> Find definitions (--type=type, --with-source; top
|
|
879
|
+
find <name> Find definitions (--type=type, --with-source; top ${paramFact('limit', 'findDetail')} shown, --limit=N)
|
|
1078
880
|
usages <name> All usages grouped by type
|
|
1079
881
|
source <target> Extract a symbol or file:line-range
|
|
1080
|
-
trace <name> Call tree (--direction
|
|
882
|
+
trace <name> Call tree (--direction=${paramValues('direction', 'trace').join('|')}, --to=${paramValues('to', 'trace').join('|')})
|
|
1081
883
|
impact [name] Symbol impact, or Git diff impact without a name
|
|
1082
884
|
tests <name> Direct tests; --depth=N includes transitive impact
|
|
1083
885
|
deps <file> File graph (--direction=, --depth=, --detailed, --cycles)
|
|
@@ -1087,7 +889,7 @@ Commands:
|
|
|
1087
889
|
entrypoints Detect runtime, framework, task, and test entry points
|
|
1088
890
|
endpoints List server/client HTTP endpoints (--bridge to match)
|
|
1089
891
|
check [name] Symbol signature check, or pre-commit check without a name
|
|
1090
|
-
plan <name> Preview refactoring (--add-param=, --remove-param=, --rename-to=, --default-value
|
|
892
|
+
plan <name> Preview refactoring (--add-param=, --remove-param=, --rename-to=, --default-value=, --delete)
|
|
1091
893
|
stacktrace <text> Parse a stack trace
|
|
1092
894
|
api Show public symbols
|
|
1093
895
|
audit-async Find likely missing-await calls (JS/TS/Python/C#)
|
|
@@ -1112,13 +914,13 @@ Flags can be added per-command: show myFunc --sections=source,callers
|
|
|
1112
914
|
const tokens = input.split(/\s+/);
|
|
1113
915
|
const command = tokens[0];
|
|
1114
916
|
// Flags that take a space-separated value (--flag value)
|
|
1115
|
-
const valueFlagNames =
|
|
917
|
+
const valueFlagNames = VALUE_FLAGS;
|
|
1116
918
|
const flagTokens = [];
|
|
1117
919
|
const argTokens = [];
|
|
1118
920
|
const skipNext = new Set();
|
|
1119
921
|
for (let i = 1; i < tokens.length; i++) {
|
|
1120
922
|
if (skipNext.has(i)) { continue; }
|
|
1121
|
-
if (tokens[i].startsWith('
|
|
923
|
+
if (tokens[i].startsWith('-')) {
|
|
1122
924
|
flagTokens.push(tokens[i]);
|
|
1123
925
|
// If it's a value-flag without = and next token exists and isn't a flag, consume it too
|
|
1124
926
|
if (valueFlagNames.has(tokens[i]) && !tokens[i].includes('=') && i + 1 < tokens.length && !tokens[i + 1].startsWith('--')) {
|
|
@@ -1134,24 +936,28 @@ Flags can be added per-command: show myFunc --sections=source,callers
|
|
|
1134
936
|
// Unknown flags error instead of folding their value into the symbol
|
|
1135
937
|
// name. Same vocabulary as one-shot mode.
|
|
1136
938
|
const unknown = flagTokens.filter(t =>
|
|
1137
|
-
t.startsWith('
|
|
939
|
+
t.startsWith('-') && !knownFlags.has(t.split('=')[0]));
|
|
1138
940
|
if (unknown.length > 0) {
|
|
1139
941
|
console.log(`Unknown flag(s): ${unknown.join(', ')}`);
|
|
1140
942
|
rl.prompt();
|
|
1141
943
|
return;
|
|
1142
944
|
}
|
|
1143
945
|
const iflags = parseFlags(flagTokens);
|
|
1144
|
-
|
|
1145
|
-
// check the raw tokens.
|
|
1146
|
-
if (flagTokens.includes('--json')) {
|
|
1147
|
-
console.log('Note: interactive mode prints text output — run `ucn . <command> --json` one-shot for JSON.');
|
|
1148
|
-
}
|
|
946
|
+
const iprocess = parseProcessFlags(flagTokens);
|
|
1149
947
|
|
|
1150
948
|
try {
|
|
1151
|
-
//
|
|
1152
|
-
//
|
|
1153
|
-
|
|
1154
|
-
|
|
949
|
+
// The same value rules as one-shot mode: a bad value is refused
|
|
950
|
+
// with a message, never silently coerced.
|
|
951
|
+
validateFlags(iflags);
|
|
952
|
+
validateFlags(iprocess);
|
|
953
|
+
if (iprocess.json) {
|
|
954
|
+
console.log('Note: interactive mode prints text output — run `ucn . <command> --json` one-shot for JSON.');
|
|
955
|
+
}
|
|
956
|
+
const startupOnly = Object.keys(iprocess).filter(key => key !== 'json');
|
|
957
|
+
if (startupOnly.length > 0 || flagTokens.some(token => token === '--workers' || token.startsWith('--workers='))) {
|
|
958
|
+
processInteger(flagTokens, '--workers');
|
|
959
|
+
console.log('Note: CLI process controls apply when starting UCN; they do not change an active interactive session.');
|
|
960
|
+
}
|
|
1155
961
|
// The REPL is an edit/query loop, not a frozen snapshot. Keep the
|
|
1156
962
|
// symbol table and call cache on the same generation by checking
|
|
1157
963
|
// source staleness before every command. getCachedCalls already
|
|
@@ -1183,7 +989,7 @@ Flags can be added per-command: show myFunc --sections=source,callers
|
|
|
1183
989
|
} catch (e) {
|
|
1184
990
|
if (e instanceof FlagValidationError) {
|
|
1185
991
|
console.log(e.message);
|
|
1186
|
-
} else {
|
|
992
|
+
} else if (!(e instanceof CommandError)) {
|
|
1187
993
|
const described = describeError(e);
|
|
1188
994
|
console.error(described.internalError ? described.error : `Error: ${described.error}`);
|
|
1189
995
|
}
|
|
@@ -1211,13 +1017,21 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
1211
1017
|
: formatSurfaceMessage(publicExecution.error, 'cli'));
|
|
1212
1018
|
return;
|
|
1213
1019
|
}
|
|
1214
|
-
|
|
1020
|
+
const text = formatCliText(
|
|
1215
1021
|
command,
|
|
1216
1022
|
publicExecution.result,
|
|
1217
1023
|
publicParams,
|
|
1218
1024
|
publicExecution,
|
|
1219
|
-
iflags,
|
|
1220
|
-
)
|
|
1025
|
+
{ ...iflags, interactive: true },
|
|
1026
|
+
);
|
|
1027
|
+
if (publicParams.raw) {
|
|
1028
|
+
// Keep the REPL's existing source/newline and prompt layout; the
|
|
1029
|
+
// mandatory disclosure follows it in the same text stream.
|
|
1030
|
+
const rawOutput = rawCliOutput(text, publicExecution.note, true);
|
|
1031
|
+
process.stdout.write(rawOutput.source + rawOutput.note);
|
|
1032
|
+
} else {
|
|
1033
|
+
console.log(text);
|
|
1034
|
+
}
|
|
1221
1035
|
}
|
|
1222
1036
|
|
|
1223
1037
|
// ============================================================================
|