ucn 5.3.6 → 5.3.8
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 +25 -1
- package/.claude/skills/ucn/references/commands.md +18 -1
- package/README.md +17 -0
- package/cli/index.js +31 -19
- package/core/analysis.js +12 -2
- package/core/cache.js +15 -1
- package/core/check.js +7 -0
- package/core/discovery.js +15 -5
- package/core/execute.js +55 -19
- package/core/graph.js +6 -2
- package/core/output/check.js +5 -1
- package/core/output/doctor.js +1 -1
- package/core/output/lines.js +8 -2
- package/core/output/public.js +8 -0
- package/core/output/reporting.js +1 -1
- package/core/output/search.js +6 -1
- package/core/project.js +3 -1
- package/core/public-command.js +1 -1
- package/core/registry.js +6 -5
- package/core/reporting.js +2 -0
- package/core/search.js +17 -4
- package/languages/c-family.js +9 -8
- package/languages/csharp.js +11 -10
- package/languages/go.js +8 -7
- package/languages/java.js +3 -2
- package/languages/javascript.js +2 -1
- package/languages/python.js +3 -2
- package/languages/rust.js +3 -2
- package/languages/utils.js +51 -29
- package/mcp/server.js +12 -8
- package/package.json +2 -2
|
@@ -41,6 +41,12 @@ three. The text block is the whole response on every surface.
|
|
|
41
41
|
|
|
42
42
|
Persistent indexes live in a per-user, project-keyed cache rather than the analyzed repository. Set `UCN_CACHE_DIR` to override the cache root; CLI `--no-cache` bypasses persistence and `--clear-cache` removes the current project's cache. Legacy `<project>/.ucn-cache` directories are migrated on first use. Concurrent invocations on a cold cache share one build: the first process takes a build lock, the others wait for its cache (`Waiting for another ucn process building the index...` on stderr) instead of rebuilding; a lock left by a dead process is broken automatically.
|
|
43
43
|
|
|
44
|
+
Files named `*.min.js`, `*.bundle.js`, and `*.map` are disclosed as skipped
|
|
45
|
+
bundled sources; they make observed-text completeness partial. Pass
|
|
46
|
+
`--include-bundled` (MCP `include_bundled=true`) to index the JavaScript files.
|
|
47
|
+
This option bypasses the shared cache and still respects user exclusions.
|
|
48
|
+
Source maps remain disclosed but unindexed; inspect them with text tools.
|
|
49
|
+
|
|
44
50
|
Supported source families are JavaScript/TypeScript/TSX, Python, Go, Rust, Java, C, C++, C#, and HTML inline JavaScript/event handlers. C/C++ uses `compile_commands.json` when available to classify headers and resolve include paths. Recoverable preprocessor branches contribute AST-proven source facts, so a single selected configuration does not silently erase definitions or calls; disagreeing conditional macro identities stay visible as unverified. C++ resolution uses namespace ownership, static overload shape (including arrays), and macro-parameter requalification. C# resolution uses declared property/field receiver types plus overload and hiding discipline. This is portable AST analysis, not a compiler build; macros, templates, generated code, reflection, and external dependency semantics can remain unverified.
|
|
45
51
|
|
|
46
52
|
`repo` refines its HOT list exactly for up to 400 candidate definitions per
|
|
@@ -78,6 +84,7 @@ counts them; `CALL SITES` stays call-shaped.
|
|
|
78
84
|
Target-less `impact` and `check` diff the working tree against `HEAD` AND
|
|
79
85
|
include untracked, non-ignored source files as whole-file additions, so new
|
|
80
86
|
modules are checked before `git add`. `--staged` keeps its index-only meaning.
|
|
87
|
+
The changed-path note also counts untracked documentation and configuration.
|
|
81
88
|
|
|
82
89
|
An observed-text zero is not semantic zero or safe-delete proof. Numeric evidence values are ordinal ranking weights, not probabilities.
|
|
83
90
|
|
|
@@ -119,7 +126,7 @@ answers:
|
|
|
119
126
|
ucn find handleRequest --lines # path:line:signature # kind
|
|
120
127
|
ucn show handleRequest --lines # callers as path:line:text; unverified ones end in "\t# unverified: <reason>"
|
|
121
128
|
ucn show handleRequest --lines --sections=callees
|
|
122
|
-
ucn usages handleRequest --lines # every literal-name
|
|
129
|
+
ucn usages handleRequest --lines # every literal-name occurrence; non-call kinds tagged "# import" / "# definition"
|
|
123
130
|
ucn search 'retry(' --lines # grep -n output, code-aware scope
|
|
124
131
|
ucn impact handleRequest --lines
|
|
125
132
|
ucn source handleRequest --raw # the code and nothing else, ready for an exact-string edit
|
|
@@ -129,6 +136,12 @@ ucn source src/server.js:40-80 --raw
|
|
|
129
136
|
Records go to stdout; the `ACCOUNT` / `CONTRACT` lines, notes, and the
|
|
130
137
|
same-name disambiguation go to stderr prefixed `# ` (MCP keeps them in the one
|
|
131
138
|
text block, and `--raw` appends its note as one trailing `# ` line there).
|
|
139
|
+
`usages` emits one record per occurrence, so a source line may repeat; deduplicate
|
|
140
|
+
`path:line` values for line counts. Definition handles start at decorators when
|
|
141
|
+
present, while usages point at token lines (`nameLine` identifies the declaration
|
|
142
|
+
token when it differs from `startLine`). Structural `search --unused` keeps its
|
|
143
|
+
safety note and decorator tags in shell output; runtime registrations can appear
|
|
144
|
+
and zero call edges do not prove a symbol is safe to delete.
|
|
132
145
|
`--lines` lists the whole band without default row/character caps, so pipe through
|
|
133
146
|
`grep -v '# unverified'` for the confirmed tier or `cut -d: -f1 | sort | uniq -c`
|
|
134
147
|
for callers per file. Nothing to list prints nothing and exits 1, grep's
|
|
@@ -143,6 +156,11 @@ characters (backslash/tab/CR/LF) are escaped; use JSON for exact filenames.
|
|
|
143
156
|
languages; use `ucn ... --lines` when the question is a symbol, a caller, or a
|
|
144
157
|
definition, and `--raw` when the next step is an edit.
|
|
145
158
|
|
|
159
|
+
Command errors exit 2 in ordinary text mode as well. CLI JSON preserves its
|
|
160
|
+
own contract: successful empty results exit 0; command errors exit 1 with
|
|
161
|
+
`meta.ok: false` and an `error` field. A target-less `check` exits 1 when
|
|
162
|
+
`TRUST` is `BLOCKED`, 0 otherwise; a check that could not run exits 2.
|
|
163
|
+
|
|
146
164
|
## Breaking-change protocol
|
|
147
165
|
|
|
148
166
|
1. Pin the exact definition with `find`.
|
|
@@ -183,6 +201,12 @@ rejected, and unsupported advanced syntax should be handed to ripgrep.
|
|
|
183
201
|
`find` activity counts are definition-pinned: confirmed plus visible unverified
|
|
184
202
|
call candidates. Calls proved to belong to another same-name target are
|
|
185
203
|
disclosed separately and excluded from the activity total.
|
|
204
|
+
Broad queries rank candidates by inexpensive approximate usage totals before
|
|
205
|
+
applying the row limit; only returned definitions receive caller adjudication.
|
|
206
|
+
The selection note discloses that approximation. `find`, text `search`,
|
|
207
|
+
`deadcode`, `api`, and `repo --sections=files` default to at most 500 results
|
|
208
|
+
(structural `search`: 50). Use an explicit `--limit=N` to request more;
|
|
209
|
+
`usages` and `--lines` have no default row cap.
|
|
186
210
|
|
|
187
211
|
For `plan --rename-to`, the selected declaration is only the starting point.
|
|
188
212
|
When the index proves the relationship, the rename unit closes over
|
|
@@ -33,7 +33,7 @@ structural or code-only search.
|
|
|
33
33
|
|---|---|
|
|
34
34
|
| `repo` | Repository orientation. Select `summary,files,stats,health` with `--sections`; `--deep` includes readiness evidence. Skipped unsupported source is listed with a grep/language-tool handoff. |
|
|
35
35
|
| `deps <file>` | File dependency graph. Use `--direction=imports\|importers\|both`, `--detailed`, or `--cycles`. Cycles distinguish eager edges from function-local, Python typing-guarded, and TypeScript type-only edges. Complete cycle groups remain visible when enumeration is capped. |
|
|
36
|
-
| `api [file]` | Static exported/public surface for a project or file. |
|
|
36
|
+
| `api [file]` | Static exported/public surface for a project or file. An exact file includes tests; broader scans exclude tests with a count. Use `--include-tests` to include them. |
|
|
37
37
|
| `entrypoints` | Framework, route, task, test, and runtime entry points. |
|
|
38
38
|
| `endpoints` | Server/client HTTP surface; `--bridge` adds advisory matching. |
|
|
39
39
|
|
|
@@ -49,6 +49,14 @@ structural or code-only search.
|
|
|
49
49
|
|
|
50
50
|
Symbol-listing commands emit handles such as `src/api.ts:42:handler`. Pass the full handle to symbol commands. `path:line` also works. Handles prevent same-named definitions from being silently combined.
|
|
51
51
|
|
|
52
|
+
Definition handles and source spans start at the first decorator or annotation when present; literal usages point at the actual token line. Use a symbol's `nameLine` (when present, otherwise `startLine`) to compare declaration tokens with usages.
|
|
53
|
+
|
|
54
|
+
Structural `search --param` matches parameter names, types, and defaults; `--returns` matches return annotations. Both exclude AST comments and preserve string contents. `--unused` lists callable symbols without call edges, not safe-delete candidates; decorated runtime registrations may still appear. Its safety note and decorator tags are retained in `--lines` output. Use `deadcode` and `usages` before deletion.
|
|
55
|
+
|
|
56
|
+
`repo` summary/stats `buildTime` is the duration of the last index build (discovery, parsing, and graphs), retained in the cache. It excludes cache loading/saving and query execution, so it is not command wall time; `buildTimeNote` states this boundary.
|
|
57
|
+
|
|
58
|
+
`--lines` writes one record per output line. `usages` records occurrences, so multiple tokens on the same source line can produce repeated `path:line` values. Deduplicate those values when counting source lines.
|
|
59
|
+
|
|
52
60
|
## Common flags
|
|
53
61
|
|
|
54
62
|
| Flag | Meaning |
|
|
@@ -58,6 +66,8 @@ Symbol-listing commands emit handles such as `src/api.ts:42:handler`. Pass the f
|
|
|
58
66
|
| `--class-name=<name>` | Scope a member when no handle is available. |
|
|
59
67
|
| `--in=<directory>` | Limit query scope to a directory. |
|
|
60
68
|
| `--exclude=<patterns>` | Exclude matching paths. |
|
|
69
|
+
| `--limit=N` | Default maximum of 500 results for `find`, text `search`, `deadcode`, `api`, and `repo` files; structural `search` defaults to 50. Explicit limits override these caps; `usages` and `--lines` are uncapped by default. |
|
|
70
|
+
| `--include-bundled` | Include `*.min.js` and `*.bundle.js` in discovery, respecting user exclusions and bypassing the shared cache. By default these and `*.map` are disclosed as skipped sources and completeness is partial. Source maps remain unindexed. MCP: `include_bundled=true`. |
|
|
61
71
|
| `--depth=N` | Set trace/dependency/test traversal depth. |
|
|
62
72
|
| `--direction=<value>` | Select trace or dependency direction. |
|
|
63
73
|
| `--all` | Lift result and formatter caps where supported. It is recommended only for commands that accept it. |
|
|
@@ -84,6 +94,13 @@ ucn [target] <command> [argument] [flags]
|
|
|
84
94
|
|
|
85
95
|
Omit the target for the current project. A target may be a file, directory, or quoted glob such as `"src/**/*.py"`.
|
|
86
96
|
|
|
97
|
+
Ordinary text and shell command errors exit 2. JSON command errors retain
|
|
98
|
+
exit 1 with `meta.ok: false` and an `error` field; successful empty JSON
|
|
99
|
+
results exit 0. Target-less `check` exits 1 for `TRUST: BLOCKED`, 0 for other
|
|
100
|
+
completed checks, and 2 when the check could not run. Working-tree `impact`
|
|
101
|
+
and `check` count untracked documentation/configuration in their non-source
|
|
102
|
+
path note; `--staged` excludes all untracked paths.
|
|
103
|
+
|
|
87
104
|
## Language notes
|
|
88
105
|
|
|
89
106
|
Supported source families are JavaScript/TypeScript/TSX, Python, Go, Rust, Java, C, C++, C#, and HTML inline JavaScript/event handlers. C/C++ consumes `compile_commands.json` when present for header-language and include-path context, and retains AST-proven facts across recoverable preprocessor branches. C++ call identity uses namespace ownership, static overload shape (including arrays), and macro requalification; disagreeing conditional macro definitions remain visible as unverified. C# uses declared property/field receiver types and overload/hiding discipline. UCN remains portable AST analysis: it does not run a compiler, preprocessor, Roslyn, or an LSP during normal queries, and it does not assert which conditional branch a build activates.
|
package/README.md
CHANGED
|
@@ -213,6 +213,23 @@ and a shell-mode character budget fails before writing partial output.
|
|
|
213
213
|
`source --raw` extracts complete functions and classes unless an explicit
|
|
214
214
|
line limit is requested; any resulting truncation is reported on stderr.
|
|
215
215
|
|
|
216
|
+
Ordinary text-mode command errors also exit 2. JSON keeps exit 0 for successful
|
|
217
|
+
empty results and exit 1 for command errors (`meta.ok: false` plus `error`).
|
|
218
|
+
Target-less `check` exits 1 when `TRUST` is `BLOCKED`, 0 for other completed
|
|
219
|
+
checks, and 2 if it could not run.
|
|
220
|
+
|
|
221
|
+
Outside `--lines`, `find`, text `search`, `deadcode`, `api`, and
|
|
222
|
+
`repo --sections=files` default to a maximum of 500 results. Use `--limit=N`
|
|
223
|
+
to request more; `usages` lists every site unless a limit is given. Broad `find` queries select candidates by approximate usage
|
|
224
|
+
totals before calculating definition-pinned caller activity, and disclose
|
|
225
|
+
that selection when limited.
|
|
226
|
+
|
|
227
|
+
Files named `*.min.js`, `*.bundle.js`, and `*.map` are reported as skipped
|
|
228
|
+
sources and make completeness partial. `--include-bundled` (MCP
|
|
229
|
+
`include_bundled=true`) indexes the JavaScript bundles while respecting user
|
|
230
|
+
exclusions; it bypasses the shared cache. Source maps remain disclosed but
|
|
231
|
+
unindexed.
|
|
232
|
+
|
|
216
233
|
## AI setup
|
|
217
234
|
|
|
218
235
|
One tool, 18 commands, compact source-linked answers that keep their trust
|
package/cli/index.js
CHANGED
|
@@ -48,7 +48,7 @@ let activeCanonicalCommand = null;
|
|
|
48
48
|
class CommandError extends Error { constructor() { super(); } }
|
|
49
49
|
|
|
50
50
|
// Thrown by validateNumericFlags when a numeric flag has a bad value.
|
|
51
|
-
// The CLI top-level catches this, prints the message, and exits 1. Interactive
|
|
51
|
+
// The CLI top-level catches this, prints the message, and exits 2 (JSON: 1). Interactive
|
|
52
52
|
// mode catches it inside its REPL try/catch and continues the session.
|
|
53
53
|
class FlagValidationError extends Error {
|
|
54
54
|
constructor(msg) { super(msg); this.name = 'FlagValidationError'; }
|
|
@@ -363,6 +363,7 @@ function parseFlags(tokens) {
|
|
|
363
363
|
hideUncertain: tokens.includes('--hide-uncertain') || tokens.includes('--no-uncertain') || undefined,
|
|
364
364
|
stack: getValueFlag('--stack'),
|
|
365
365
|
workersRaw: getValueFlag('--workers'),
|
|
366
|
+
includeBundled: tokens.includes('--include-bundled') || undefined,
|
|
366
367
|
workers: (() => {
|
|
367
368
|
const v = getValueFlag('--workers');
|
|
368
369
|
if (v === null) return undefined;
|
|
@@ -376,7 +377,7 @@ function parseFlags(tokens) {
|
|
|
376
377
|
const flags = parseFlags(args);
|
|
377
378
|
flags.json = args.includes('--json');
|
|
378
379
|
flags.quiet = !args.includes('--verbose') && !args.includes('--no-quiet');
|
|
379
|
-
flags.cache = !args.includes('--no-cache');
|
|
380
|
+
flags.cache = !args.includes('--no-cache') && !flags.includeBundled;
|
|
380
381
|
flags.clearCache = args.includes('--clear-cache');
|
|
381
382
|
flags.interactive = args.includes('--interactive') || args.includes('-i');
|
|
382
383
|
flags.followSymlinks = !args.includes('--no-follow-symlinks');
|
|
@@ -408,7 +409,7 @@ if (unknownFlags.length > 0) {
|
|
|
408
409
|
emitCliError(
|
|
409
410
|
`Unknown flag(s): ${unknownFlags.join(', ')}. Use --help to see available flags.`,
|
|
410
411
|
);
|
|
411
|
-
process.exit(flags.
|
|
412
|
+
process.exit(flags.json ? 1 : 2);
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
// Validate numeric flag values up front so bad input fails before we build
|
|
@@ -419,7 +420,7 @@ try {
|
|
|
419
420
|
} catch (e) {
|
|
420
421
|
if (e instanceof FlagValidationError) {
|
|
421
422
|
emitCliError(e.message);
|
|
422
|
-
process.exit(flags.
|
|
423
|
+
process.exit(flags.json ? 1 : 2);
|
|
423
424
|
}
|
|
424
425
|
throw e;
|
|
425
426
|
}
|
|
@@ -607,7 +608,7 @@ function main() {
|
|
|
607
608
|
if (!(e instanceof CommandError)) {
|
|
608
609
|
emitCliError(`Error: ${e.message}`);
|
|
609
610
|
}
|
|
610
|
-
process.exitCode = flags.
|
|
611
|
+
process.exitCode = flags.json ? 1 : 2;
|
|
611
612
|
}
|
|
612
613
|
}
|
|
613
614
|
|
|
@@ -625,7 +626,7 @@ function printTieredNoOpNotes(canonical, flags, print) {
|
|
|
625
626
|
}
|
|
626
627
|
|
|
627
628
|
const GLOBAL_FLAG_KEYS = new Set([
|
|
628
|
-
'json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'maxFiles',
|
|
629
|
+
'json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'includeBundled', 'maxFiles',
|
|
629
630
|
'verbose', 'interactive', '_fileFromFileMode', 'topRaw',
|
|
630
631
|
'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'depthRaw', 'contextRaw',
|
|
631
632
|
'workers', 'workersRaw', 'lineRaw', 'maxChars', 'maxCharsRaw', 'minConfidenceRaw',
|
|
@@ -752,7 +753,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
752
753
|
if (flags.cache && !flags.clearCache) {
|
|
753
754
|
const loaded = index.loadCache();
|
|
754
755
|
cacheWasLoaded = !!loaded;
|
|
755
|
-
if (loaded && !flags.maxFiles) {
|
|
756
|
+
if (loaded && !index.includeBundled && !flags.maxFiles) {
|
|
756
757
|
if (!index.isCacheStale()) {
|
|
757
758
|
usedCache = true;
|
|
758
759
|
if (!flags.quiet) {
|
|
@@ -766,7 +767,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
766
767
|
// If cache was loaded but stale, force rebuild to avoid duplicates
|
|
767
768
|
let needsCacheSave = false;
|
|
768
769
|
if (!usedCache) {
|
|
769
|
-
const buildOpts = { quiet: flags.quiet, forceRebuild: cacheWasLoaded, followSymlinks: flags.followSymlinks, maxFiles: flags.maxFiles, workers: flags.workers };
|
|
770
|
+
const buildOpts = { quiet: flags.quiet, forceRebuild: cacheWasLoaded, followSymlinks: flags.followSymlinks, includeBundled: flags.includeBundled, maxFiles: flags.maxFiles, workers: flags.workers };
|
|
770
771
|
if (flags.cache && !flags.maxFiles) {
|
|
771
772
|
// Cross-process build lock (fix #354): concurrent cold-cache
|
|
772
773
|
// invocations share one build instead of each rebuilding.
|
|
@@ -813,7 +814,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
813
814
|
if (!(e instanceof CommandError)) {
|
|
814
815
|
emitCliError(`Error: ${e.message}`);
|
|
815
816
|
}
|
|
816
|
-
process.exitCode = flags.
|
|
817
|
+
process.exitCode = flags.json ? 1 : 2;
|
|
817
818
|
} finally {
|
|
818
819
|
// Save cache after command execution so callsCache populated
|
|
819
820
|
// by findCallers/findCallees gets persisted to disk.
|
|
@@ -834,7 +835,11 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
834
835
|
// ============================================================================
|
|
835
836
|
|
|
836
837
|
function runGlobCommand(pattern, command, arg) {
|
|
837
|
-
const
|
|
838
|
+
const discoveryIssues = [];
|
|
839
|
+
const files = expandGlob(pattern, {
|
|
840
|
+
includeBundled: flags.includeBundled,
|
|
841
|
+
onDiscoveryIssue: issue => discoveryIssues.push(issue),
|
|
842
|
+
});
|
|
838
843
|
|
|
839
844
|
if (files.length === 0) {
|
|
840
845
|
fail(`No files match pattern: ${pattern}`, command);
|
|
@@ -849,6 +854,9 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
849
854
|
const rootDir = findProjectRoot(path.dirname(files[0]));
|
|
850
855
|
const index = new ProjectIndex(rootDir);
|
|
851
856
|
index.build(files, { quiet: true });
|
|
857
|
+
index.discoveryIssues.push(...discoveryIssues.map(issue => ({
|
|
858
|
+
...issue, relativePath: path.relative(index.root, issue.path), path: undefined,
|
|
859
|
+
})));
|
|
852
860
|
|
|
853
861
|
if (!isPublicCommand(canonical)) {
|
|
854
862
|
fail(unknownCommandMessage(command));
|
|
@@ -909,7 +917,8 @@ Commands:
|
|
|
909
917
|
deps <file> File graph; --direction=imports|importers|both
|
|
910
918
|
--detailed Include import declarations
|
|
911
919
|
deps --cycles Report circular dependencies (no file target)
|
|
912
|
-
api [file] Project or file public API
|
|
920
|
+
api [file] Project or file public API (exact files include tests;
|
|
921
|
+
broader scans exclude tests unless --include-tests)
|
|
913
922
|
check [symbol] Signature check; without symbol, precommit check
|
|
914
923
|
plan <symbol> Preview rename or parameter edits
|
|
915
924
|
entrypoints Runtime and framework entry points
|
|
@@ -925,7 +934,8 @@ Common flags:
|
|
|
925
934
|
--base=REF --staged --no-cache --clear-cache [--all] --max-files=N --workers=N
|
|
926
935
|
--max-chars=N (text output; default 10K targeted / 3K broad, ceiling 100K)
|
|
927
936
|
--lines find/usages/search/show/impact: grep -n shape, one path:line:text
|
|
928
|
-
record per line
|
|
937
|
+
record per output line; usages may repeat a source line per occurrence
|
|
938
|
+
(tags after a tab: # unverified: <reason>, # import,
|
|
929
939
|
# callee); accounting and notes go to stderr as "# " lines; exit 1
|
|
930
940
|
when nothing matched; exit 2 on errors. No default result cap.
|
|
931
941
|
show defaults to callers; --sections=callers,callees selects bands.
|
|
@@ -938,14 +948,14 @@ ${perCommandFlags}
|
|
|
938
948
|
|
|
939
949
|
Global/build/output flags:
|
|
940
950
|
--help -h --version -v --mcp --json --verbose --no-quiet --quiet
|
|
941
|
-
--interactive -i --no-cache --clear-cache --no-follow-symlinks
|
|
951
|
+
--interactive -i --no-cache --clear-cache --no-follow-symlinks --include-bundled
|
|
942
952
|
--max-files=N --max-chars=N --workers=N
|
|
943
953
|
--clear-cache --all clears every bounded per-user UCN project cache.
|
|
944
954
|
|
|
945
955
|
Exit codes:
|
|
946
956
|
0 Command completed successfully; check found no blocking issues.
|
|
947
|
-
1
|
|
948
|
-
2
|
|
957
|
+
1 Empty --lines listing, blocking check findings, or JSON command error.
|
|
958
|
+
2 Text/shell command error, or a check that could not run.
|
|
949
959
|
|
|
950
960
|
Boolean aliases:
|
|
951
961
|
--no-include-methods --no-regex --show-confidence --hide-confidence
|
|
@@ -995,14 +1005,14 @@ function runInteractive(rootDir) {
|
|
|
995
1005
|
let iCacheFresh;
|
|
996
1006
|
if (flags.cache) {
|
|
997
1007
|
const loaded = !flags.clearCache && index.loadCache();
|
|
998
|
-
iCacheFresh = loaded && !index.isCacheStale();
|
|
1008
|
+
iCacheFresh = loaded && !index.includeBundled && !index.isCacheStale();
|
|
999
1009
|
if (!iCacheFresh) {
|
|
1000
1010
|
index.buildCached({ quiet: true, forceRebuild: !!loaded, workers: flags.workers }, {
|
|
1001
1011
|
onWait: () => console.log('Waiting for another ucn process building the index...'),
|
|
1002
1012
|
});
|
|
1003
1013
|
}
|
|
1004
1014
|
} else {
|
|
1005
|
-
index.build(null, { quiet: true, workers: flags.workers });
|
|
1015
|
+
index.build(null, { quiet: true, workers: flags.workers, includeBundled: flags.includeBundled });
|
|
1006
1016
|
}
|
|
1007
1017
|
console.log(`Index ready: ${index.files.size} files, ${index.symbols.size} unique symbol names`);
|
|
1008
1018
|
console.log('Type commands (e.g., "find parseFile", "show main", "repo")');
|
|
@@ -1118,16 +1128,18 @@ Flags can be added per-command: show myFunc --sections=source,callers
|
|
|
1118
1128
|
// refreshes individual call records lazily; without this matching
|
|
1119
1129
|
// rebuild a newly added definition was invisible while its calls
|
|
1120
1130
|
// appeared in neighbouring answers (UCN5-044).
|
|
1121
|
-
|
|
1131
|
+
const includeBundled = !!(iflags.includeBundled || flags.includeBundled);
|
|
1132
|
+
if (!!index.includeBundled !== includeBundled || index.isCacheStale()) {
|
|
1122
1133
|
console.log('Source changed; rebuilding index...');
|
|
1123
1134
|
const rebuildOpts = {
|
|
1124
1135
|
quiet: true,
|
|
1125
1136
|
forceRebuild: true,
|
|
1126
1137
|
followSymlinks: flags.followSymlinks,
|
|
1138
|
+
includeBundled,
|
|
1127
1139
|
maxFiles: flags.maxFiles,
|
|
1128
1140
|
workers: flags.workers,
|
|
1129
1141
|
};
|
|
1130
|
-
if (flags.cache && !flags.maxFiles) {
|
|
1142
|
+
if (flags.cache && !flags.maxFiles && !includeBundled) {
|
|
1131
1143
|
index.buildCached(rebuildOpts, {
|
|
1132
1144
|
onWait: () => console.log('Waiting for another ucn process building the index...'),
|
|
1133
1145
|
});
|
package/core/analysis.js
CHANGED
|
@@ -2052,7 +2052,14 @@ function diffImpact(index, options = {}) {
|
|
|
2052
2052
|
if (known.has(localRel)) continue;
|
|
2053
2053
|
const filePath = path.join(index.root, localRel);
|
|
2054
2054
|
const fileEntry = index.files.get(filePath);
|
|
2055
|
-
if (!fileEntry || !detectLanguage(filePath))
|
|
2055
|
+
if (!fileEntry || !detectLanguage(filePath)) {
|
|
2056
|
+
changes.push({
|
|
2057
|
+
filePath, relativePath: localRel,
|
|
2058
|
+
gitRelativePath: projectPrefix ? `${projectPrefix}/${localRel}` : localRel,
|
|
2059
|
+
addedLines: [], deletedLines: [], untracked: true,
|
|
2060
|
+
});
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2056
2063
|
let lineCount = fileEntry.lines;
|
|
2057
2064
|
if (!Number.isFinite(lineCount)) {
|
|
2058
2065
|
try { lineCount = fs.readFileSync(filePath, 'utf-8').split('\n').length; } catch (_) { continue; }
|
|
@@ -2096,7 +2103,10 @@ function diffImpact(index, options = {}) {
|
|
|
2096
2103
|
|
|
2097
2104
|
for (const change of changes) {
|
|
2098
2105
|
const lang = detectLanguage(change.filePath);
|
|
2099
|
-
if (!lang
|
|
2106
|
+
if (!lang || (change.untracked && !index.files.has(change.filePath))) {
|
|
2107
|
+
nonSourcePaths++;
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2100
2110
|
|
|
2101
2111
|
const fileEntry = index.files.get(change.filePath);
|
|
2102
2112
|
|
package/core/cache.js
CHANGED
|
@@ -711,7 +711,9 @@ function clearAllCaches() {
|
|
|
711
711
|
// v224 (#355 recovery): Go declaration origins; Rust aliases, wrapper patterns,
|
|
712
712
|
// copied bindings and qualified macro receivers; TS indexed array evidence.
|
|
713
713
|
// v225 (fix #357): Rust `use path::name as local` bindings record the original name with a paired `renames` alias.
|
|
714
|
-
|
|
714
|
+
// v226: bundled/minified filename exclusions are disclosed in discoveryIssues.
|
|
715
|
+
// v227: signature parameter/return text excludes AST comments in every language.
|
|
716
|
+
const CACHE_FORMAT_VERSION = 227;
|
|
715
717
|
const USAGE_CACHE_FILE = 'usage-results.json';
|
|
716
718
|
|
|
717
719
|
/**
|
|
@@ -935,6 +937,7 @@ function saveCache(index, cachePath) {
|
|
|
935
937
|
unsupportedFiles: Array.isArray(index.unsupportedFiles)
|
|
936
938
|
? index.unsupportedFiles
|
|
937
939
|
: [],
|
|
940
|
+
includeBundled: index.includeBundled === true,
|
|
938
941
|
discoveryIssues: Array.isArray(index.discoveryIssues)
|
|
939
942
|
? index.discoveryIssues
|
|
940
943
|
: [],
|
|
@@ -1164,6 +1167,7 @@ function loadCache(index, cachePath) {
|
|
|
1164
1167
|
index.unsupportedFiles = Array.isArray(cacheData.unsupportedFiles)
|
|
1165
1168
|
? cacheData.unsupportedFiles
|
|
1166
1169
|
: [];
|
|
1170
|
+
index.includeBundled = cacheData.includeBundled === true;
|
|
1167
1171
|
index.discoveryIssues = Array.isArray(cacheData.discoveryIssues)
|
|
1168
1172
|
? cacheData.discoveryIssues
|
|
1169
1173
|
: [];
|
|
@@ -1302,8 +1306,13 @@ function isCacheStale(index) {
|
|
|
1302
1306
|
// Only reached when all cached files are unchanged.
|
|
1303
1307
|
const pattern = detectProjectPattern(index.root);
|
|
1304
1308
|
const currentUnsupported = [];
|
|
1309
|
+
const currentBundled = [];
|
|
1305
1310
|
const globOpts = {
|
|
1306
1311
|
root: index.root,
|
|
1312
|
+
includeBundled: index.includeBundled === true,
|
|
1313
|
+
onDiscoveryIssue: issue => {
|
|
1314
|
+
if (issue.reason === 'bundled') currentBundled.push(path.relative(index.root, issue.path));
|
|
1315
|
+
},
|
|
1307
1316
|
onSkippedFile: (filePath) => {
|
|
1308
1317
|
const kind = classifyUnsupportedSourceFile(filePath);
|
|
1309
1318
|
if (!kind) return;
|
|
@@ -1321,6 +1330,11 @@ function isCacheStale(index) {
|
|
|
1321
1330
|
globOpts.ignores = [...DEFAULT_IGNORES, ...configExclude];
|
|
1322
1331
|
}
|
|
1323
1332
|
const currentFiles = expandGlob(pattern, globOpts);
|
|
1333
|
+
const cachedBundled = (index.discoveryIssues || [])
|
|
1334
|
+
.filter(issue => issue.reason === 'bundled').map(issue => issue.relativePath).sort();
|
|
1335
|
+
currentBundled.sort();
|
|
1336
|
+
if (cachedBundled.length !== currentBundled.length ||
|
|
1337
|
+
cachedBundled.some((value, i) => value !== currentBundled[i])) return true;
|
|
1324
1338
|
const cachedPaths = new Set(index.files.keys());
|
|
1325
1339
|
const currentPaths = new Set(currentFiles);
|
|
1326
1340
|
|
package/core/check.js
CHANGED
|
@@ -73,6 +73,11 @@ function check(index, options = {}) {
|
|
|
73
73
|
const modified = (dr && Array.isArray(dr.functions)) ? dr.functions : [];
|
|
74
74
|
const added = (dr && Array.isArray(dr.newFunctions)) ? dr.newFunctions : [];
|
|
75
75
|
const deleted = (dr && Array.isArray(dr.deletedFunctions)) ? dr.deletedFunctions : [];
|
|
76
|
+
const pathCounts = {
|
|
77
|
+
changedPaths: dr?.changedPaths || 0,
|
|
78
|
+
nonSourcePaths: dr?.nonSourcePaths || 0,
|
|
79
|
+
untrackedPaths: dr?.untrackedPaths || 0,
|
|
80
|
+
};
|
|
76
81
|
|
|
77
82
|
const allChanged = [
|
|
78
83
|
...modified.map(f => ({ ...f, _kind: 'modified' })),
|
|
@@ -99,6 +104,7 @@ function check(index, options = {}) {
|
|
|
99
104
|
status: 'clean',
|
|
100
105
|
empty: true,
|
|
101
106
|
reason,
|
|
107
|
+
...pathCounts,
|
|
102
108
|
};
|
|
103
109
|
}
|
|
104
110
|
|
|
@@ -293,6 +299,7 @@ function check(index, options = {}) {
|
|
|
293
299
|
staged: !!options.staged,
|
|
294
300
|
ok: true,
|
|
295
301
|
status: 'checked',
|
|
302
|
+
...pathCounts,
|
|
296
303
|
changed: items,
|
|
297
304
|
totalChanged: allChanged.length + deleted.length,
|
|
298
305
|
truncated: !!(limit && allChanged.length > limit),
|
package/core/discovery.js
CHANGED
|
@@ -52,16 +52,15 @@ const DEFAULT_IGNORES = [
|
|
|
52
52
|
'.pytest_cache',
|
|
53
53
|
'.mypy_cache',
|
|
54
54
|
|
|
55
|
-
// Bundled/minified
|
|
56
|
-
'*.min.js',
|
|
57
|
-
'*.bundle.js',
|
|
58
|
-
'*.map',
|
|
59
|
-
|
|
60
55
|
// System
|
|
61
56
|
'.DS_Store',
|
|
62
57
|
'.ucn-cache'
|
|
63
58
|
];
|
|
64
59
|
|
|
60
|
+
// These can contain user code: disclose their exclusion, unlike dependency
|
|
61
|
+
// and VCS directories. Source maps are data and cannot be parsed as code.
|
|
62
|
+
const BUNDLED_PATTERNS = ['*.min.js', '*.bundle.js', '*.map'];
|
|
63
|
+
|
|
65
64
|
// Conditional ignores - only ignore when marker file exists in same directory
|
|
66
65
|
// Maps directory name -> array of marker files that indicate it's a vendor dir
|
|
67
66
|
const CONDITIONAL_IGNORES = {
|
|
@@ -340,6 +339,7 @@ function expandGlob(pattern, options = {}) {
|
|
|
340
339
|
maxDepth,
|
|
341
340
|
maxFileSize,
|
|
342
341
|
followSymlinks,
|
|
342
|
+
includeBundled: options.includeBundled === true,
|
|
343
343
|
// Anchored gitignore patterns ('/name') apply only to entries directly
|
|
344
344
|
// under the project root — the .gitignore's own directory (fix #226).
|
|
345
345
|
anchorRoot: root,
|
|
@@ -530,6 +530,16 @@ function walkDir(dir, options, depth = 0, visited = new Set()) {
|
|
|
530
530
|
walkDir(fullPath, options, depth + 1, visited);
|
|
531
531
|
}
|
|
532
532
|
} else if (isFile) {
|
|
533
|
+
if (shouldIgnore(entry.name, BUNDLED_PATTERNS) &&
|
|
534
|
+
(!options.includeBundled || entry.name.endsWith('.map'))) {
|
|
535
|
+
options.onDiscoveryIssue?.({
|
|
536
|
+
path: fullPath, kind: 'file', reason: 'bundled',
|
|
537
|
+
detail: entry.name.endsWith('.map')
|
|
538
|
+
? 'source maps are not supported source files'
|
|
539
|
+
: 'bundled/minified filename; use --include-bundled to index',
|
|
540
|
+
});
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
533
543
|
if (options.filePattern.test(entry.name)) {
|
|
534
544
|
let size;
|
|
535
545
|
try { size = fs.statSync(fullPath).size; } catch (e) {
|
package/core/execute.js
CHANGED
|
@@ -292,6 +292,11 @@ function num(val, fallback) {
|
|
|
292
292
|
return isNaN(n) ? fallback : n;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
/** Shared listing bound; shell listings remain uncapped unless requested. */
|
|
296
|
+
function listingLimit(p) {
|
|
297
|
+
return num(p.limit, undefined) || (p.lines || p.all ? undefined : 500);
|
|
298
|
+
}
|
|
299
|
+
|
|
295
300
|
/**
|
|
296
301
|
* Apply limit to an array result.
|
|
297
302
|
* Returns { items, total, limited } where limited is true if truncated.
|
|
@@ -771,7 +776,7 @@ const HANDLERS = {
|
|
|
771
776
|
...p,
|
|
772
777
|
// repo's public limit caps the file result set. Direct toc's
|
|
773
778
|
// legacy symbol-list cap is intentionally not composed here.
|
|
774
|
-
top: p.top || p
|
|
779
|
+
top: p.top || listingLimit(p),
|
|
775
780
|
limit: undefined,
|
|
776
781
|
});
|
|
777
782
|
if (!failure && selected.has('stats')) failure = collect('stats', 'stats', p);
|
|
@@ -1147,10 +1152,13 @@ const HANDLERS = {
|
|
|
1147
1152
|
if (!p.withSource && Array.isArray(response.result)) {
|
|
1148
1153
|
response.result = response.result.map(({ code, ...item }) => item);
|
|
1149
1154
|
}
|
|
1150
|
-
const limit =
|
|
1155
|
+
const limit = listingLimit(p);
|
|
1151
1156
|
if (limit && limit > 0 && Array.isArray(response.result)) {
|
|
1152
1157
|
const { items, total, limited } = applyLimit(response.result, limit);
|
|
1153
1158
|
response.result = items;
|
|
1159
|
+
Object.defineProperty(items, 'findInfo', {
|
|
1160
|
+
value: { total, shown: items.length }, enumerable: false,
|
|
1161
|
+
});
|
|
1154
1162
|
if (limited) {
|
|
1155
1163
|
const note = limitNote(limit, total);
|
|
1156
1164
|
response.note = response.note ? `${response.note}\n${note}` : note;
|
|
@@ -1184,9 +1192,9 @@ const HANDLERS = {
|
|
|
1184
1192
|
file: p.file,
|
|
1185
1193
|
className: p.className,
|
|
1186
1194
|
exact: p.exact || false,
|
|
1187
|
-
//
|
|
1188
|
-
//
|
|
1189
|
-
skipCounts:
|
|
1195
|
+
// Filter and bound the inventory before resolving any callers.
|
|
1196
|
+
// Shell listings do not need the activity pass at all.
|
|
1197
|
+
skipCounts: true,
|
|
1190
1198
|
exclude,
|
|
1191
1199
|
in: p.in,
|
|
1192
1200
|
});
|
|
@@ -1204,9 +1212,6 @@ const HANDLERS = {
|
|
|
1204
1212
|
const kinds = kindGroups[p.type] || new Set([p.type]);
|
|
1205
1213
|
result = result.filter(item => kinds.has(item.type));
|
|
1206
1214
|
}
|
|
1207
|
-
if (p.withSource) {
|
|
1208
|
-
result = result.map(item => ({ ...item, code: readAndExtract(item) }));
|
|
1209
|
-
}
|
|
1210
1215
|
const fullFindCount = result.length;
|
|
1211
1216
|
const nameWideDefinitionCounts = Object.fromEntries(
|
|
1212
1217
|
[...new Set(result.map(item => item.name))].map(name => [
|
|
@@ -1234,11 +1239,21 @@ const HANDLERS = {
|
|
|
1234
1239
|
}
|
|
1235
1240
|
}
|
|
1236
1241
|
// Apply limit
|
|
1237
|
-
const limit =
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
+
const limit = listingLimit(p);
|
|
1243
|
+
if (!p.lines) {
|
|
1244
|
+
index._beginOp();
|
|
1245
|
+
try {
|
|
1246
|
+
result = index._applyFindFilters(result, { limit });
|
|
1247
|
+
} finally { index._endOp(); }
|
|
1248
|
+
} else {
|
|
1249
|
+
result = applyLimit(result, limit).items;
|
|
1250
|
+
}
|
|
1251
|
+
if (result.length < fullFindCount) {
|
|
1252
|
+
notes.push(limitNote(result.length, fullFindCount));
|
|
1253
|
+
if (!p.lines) notes.push('Selection ranked by approximate usage totals; returned activity counts use definition-pinned caller evidence.');
|
|
1254
|
+
}
|
|
1255
|
+
if (p.withSource) {
|
|
1256
|
+
result = result.map(item => ({ ...item, code: readAndExtract(item) }));
|
|
1242
1257
|
}
|
|
1243
1258
|
Object.defineProperty(result, 'findInfo', {
|
|
1244
1259
|
value: {
|
|
@@ -1299,12 +1314,18 @@ const HANDLERS = {
|
|
|
1299
1314
|
const hidden = unfiltered.length - result.length;
|
|
1300
1315
|
if (hidden > 0) notes.push(`${hidden} test-file usage(s) hidden by default — pass --include-tests to include them.`);
|
|
1301
1316
|
}
|
|
1302
|
-
// Apply limit to total usages (result is a flat array)
|
|
1317
|
+
// Apply limit to total usages (result is a flat array). usages is the
|
|
1318
|
+
// escape-hatch listing (fix #284): only an EXPLICIT limit caps it; the
|
|
1319
|
+
// release command-surface gate reads every oracle reference from it.
|
|
1303
1320
|
const limit = num(p.limit, undefined);
|
|
1304
1321
|
let limited = result;
|
|
1305
1322
|
if (limit && limit > 0 && Array.isArray(result) && result.length > limit) {
|
|
1306
1323
|
notes.push(limitNote(limit, result.length));
|
|
1307
1324
|
limited = result.slice(0, limit);
|
|
1325
|
+
Object.defineProperty(limited, 'limitInfo', {
|
|
1326
|
+
value: { total: result.length, shown: limited.length },
|
|
1327
|
+
enumerable: false,
|
|
1328
|
+
});
|
|
1308
1329
|
// Summary counts describe the FULL result set — the limit applies
|
|
1309
1330
|
// to listed entries only (fix #237: the header claimed '0 calls'
|
|
1310
1331
|
// for a called function whenever the definition filled the limit).
|
|
@@ -1640,7 +1661,7 @@ const HANDLERS = {
|
|
|
1640
1661
|
file: p.file,
|
|
1641
1662
|
});
|
|
1642
1663
|
// Apply limit to dead code results (result is an array with custom properties)
|
|
1643
|
-
const limit =
|
|
1664
|
+
const limit = listingLimit(p);
|
|
1644
1665
|
let note;
|
|
1645
1666
|
if (limit && limit > 0 && Array.isArray(result) && result.length > limit) {
|
|
1646
1667
|
note = limitNote(limit, result.length);
|
|
@@ -2309,7 +2330,7 @@ const HANDLERS = {
|
|
|
2309
2330
|
},
|
|
2310
2331
|
|
|
2311
2332
|
api: (index, p) => {
|
|
2312
|
-
if (p.file) {
|
|
2333
|
+
if (p.file && typeof index.resolveFilePathForQuery(p.file) !== 'string') {
|
|
2313
2334
|
const fileErr = checkFilePatternMatch(index, p.file);
|
|
2314
2335
|
if (fileErr) return { ok: false, error: fileErr };
|
|
2315
2336
|
}
|
|
@@ -2325,13 +2346,13 @@ const HANDLERS = {
|
|
|
2325
2346
|
return { ok: false, error: `No files matched the 'in' directory filter '${p.in}'.` };
|
|
2326
2347
|
}
|
|
2327
2348
|
}
|
|
2328
|
-
let result = index.api(p.file, { in: p.in });
|
|
2349
|
+
let result = index.api(p.file, { in: p.in, includeTests: p.includeTests });
|
|
2329
2350
|
if (p.file) {
|
|
2330
2351
|
const fileErr = checkFileError(result, p.file, index);
|
|
2331
2352
|
if (fileErr) return { ok: false, error: fileErr };
|
|
2332
2353
|
}
|
|
2333
2354
|
// Apply limit to api results (api returns an array)
|
|
2334
|
-
const limit =
|
|
2355
|
+
const limit = listingLimit(p);
|
|
2335
2356
|
let note;
|
|
2336
2357
|
if (limit && limit > 0 && Array.isArray(result)) {
|
|
2337
2358
|
const { items, total, limited } = applyLimit(result, limit);
|
|
@@ -2353,6 +2374,10 @@ const HANDLERS = {
|
|
|
2353
2374
|
}
|
|
2354
2375
|
result = items;
|
|
2355
2376
|
}
|
|
2377
|
+
if (result.apiInfo?.excludedTestFiles > 0) {
|
|
2378
|
+
const excluded = `${result.apiInfo.excludedTestFiles} test file(s) excluded from API; use --include-tests to include them, or name an exact file.`;
|
|
2379
|
+
note = note ? `${note}\n${excluded}` : excluded;
|
|
2380
|
+
}
|
|
2356
2381
|
return { ok: true, result, note };
|
|
2357
2382
|
},
|
|
2358
2383
|
|
|
@@ -2494,7 +2519,18 @@ function execute(index, command, params = {}) {
|
|
|
2494
2519
|
}
|
|
2495
2520
|
}
|
|
2496
2521
|
}
|
|
2497
|
-
|
|
2522
|
+
const response = handler(index, params);
|
|
2523
|
+
const bundled = (index.discoveryIssues || []).filter(issue => issue.reason === 'bundled');
|
|
2524
|
+
if (bundled.length > 0) {
|
|
2525
|
+
const files = bundled.slice(0, 5).map(issue => issue.relativePath).join(', ');
|
|
2526
|
+
const note = `UCN skipped ${bundled.length} bundled/minified or source-map file(s): ${files}` +
|
|
2527
|
+
(bundled.length > 5 ? ', ...' : '') +
|
|
2528
|
+
'. This is not a repository-wide semantic zero; use --include-bundled for JavaScript bundles ' +
|
|
2529
|
+
'(source maps remain unindexed), or grep/ripgrep to inspect skipped files.';
|
|
2530
|
+
if (response.ok) response.note = combineNotes([response.note, note]);
|
|
2531
|
+
else response.error = `${response.error}\n${note}`;
|
|
2532
|
+
}
|
|
2533
|
+
return response;
|
|
2498
2534
|
} catch (e) {
|
|
2499
2535
|
return { ok: false, error: e.message };
|
|
2500
2536
|
}
|