ucn 5.0.6 → 5.2.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 +12 -5
- package/.claude/skills/ucn/references/commands.md +2 -2
- package/.claude/skills/ucn/references/trust-contract.md +3 -2
- package/README.md +31 -14
- package/core/analysis.js +66 -1
- package/core/bridge.js +2 -1
- package/core/cache.js +64 -8
- package/core/callers.js +1722 -90
- package/core/graph-build.js +6 -0
- package/core/index-ir.js +16 -5
- package/core/ir.js +50 -3
- package/core/output/analysis.js +25 -0
- package/core/output/refactoring.js +1 -1
- package/core/output/shared.js +2 -2
- package/core/project.js +32 -0
- package/core/search.js +9 -0
- package/core/verify.js +1084 -36
- package/languages/c-family.js +231 -9
- package/languages/csharp.js +14 -3
- package/languages/go.js +473 -71
- package/languages/javascript.js +288 -21
- package/languages/python.js +443 -97
- package/languages/rust.js +87 -4
- package/languages/utils.js +11 -0
- package/mcp/server.js +100 -104
- package/mcp/stdio-server.js +296 -0
- package/package.json +10 -8
|
@@ -41,7 +41,7 @@ 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.
|
|
43
43
|
|
|
44
|
-
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;
|
|
44
|
+
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
45
|
|
|
46
46
|
`repo` readiness is task-specific. Its headline is navigation readiness;
|
|
47
47
|
refactor, deletion, semantic recall, and the sampled evidence mix are separate
|
|
@@ -53,7 +53,7 @@ not an accuracy grade.
|
|
|
53
53
|
For caller-bearing `show`, `impact`, `trace`, `tests`, and `check` views:
|
|
54
54
|
|
|
55
55
|
- `CONFIRMED` has binding, receiver, import, or ownership evidence for the pinned target.
|
|
56
|
-
- `UNVERIFIED` is a possible target with insufficient identity evidence. Review it before a breaking change.
|
|
56
|
+
- `UNVERIFIED` is a possible target with insufficient identity evidence. Review it before a breaking change. When same-name definitions cause the ambiguity, `show` lists their stable handles once above the sites so the competing targets are explicit.
|
|
57
57
|
- `ACCOUNT` partitions observed literal-name lines into confirmed, unverified, non-call, excluded, and unresolved buckets. In mixed-language repositories it also counts occurrences in unsupported-language source files.
|
|
58
58
|
- `CONTRACT` states the scope and completeness of that observed-text partition. When unsupported-language files contain the name, it says so explicitly and the partition-complete claim is limited to supported languages.
|
|
59
59
|
- `WARNING` identifies unreadable, unparsed, or partially indexed files, and lists unsupported-language occurrence sites (file:line plus the line text) so nothing grep would show is hidden. `usages` and `tests` carry the same disclosure as a note.
|
|
@@ -114,9 +114,16 @@ rejected, and unsupported advanced syntax should be handed to ripgrep.
|
|
|
114
114
|
call candidates. Calls proved to belong to another same-name target are
|
|
115
115
|
disclosed separately and excluded from the activity total.
|
|
116
116
|
|
|
117
|
-
`plan
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
For `plan --rename-to`, the selected declaration is only the starting point.
|
|
118
|
+
When the index proves the relationship, the rename unit closes over
|
|
119
|
+
overload/signature groups, base and override declarations, Rust trait slots,
|
|
120
|
+
Go interface slots and their satisfiers, exact call and value-reference tokens,
|
|
121
|
+
imports/exports, Python `__all__` strings, and module-attribute references.
|
|
122
|
+
Exact token/expression spans keep a foreign same-named occurrence on the same
|
|
123
|
+
line unchanged. Open external interfaces, incomplete ownership, unresolved
|
|
124
|
+
dispatch, or an inexact token route to `needsReview` instead of a synthesized
|
|
125
|
+
edit. Read `changeSummary` and every review item. `plan` previews only: it does
|
|
126
|
+
not modify files, run a compiler, or prove runtime compatibility.
|
|
120
127
|
|
|
121
128
|
## Efficient use
|
|
122
129
|
|
|
@@ -25,7 +25,7 @@ structural or code-only search.
|
|
|
25
25
|
| `impact [handle]` | Show direct symbol impact when given a handle; without one, analyze the Git diff. |
|
|
26
26
|
| `tests <handle\|file>` | Find statically linked direct tests. Set `--depth=N` for transitive affected tests. Empty results are not runtime coverage proof. |
|
|
27
27
|
| `check [handle]` | Validate confirmed call-site arity for a symbol; without one, run the composed pre-commit diagnostic. |
|
|
28
|
-
| `plan <handle>` | Preview `--rename-to`, `--add-param`, or `--remove-param` edits,
|
|
28
|
+
| `plan <handle>` | Preview `--rename-to`, `--add-param`, or `--remove-param` edits. For renames, the proven closure can include overload/signature groups, inheritance/trait/Go-interface slots, exact call/reference tokens, imports/exports, Python `__all__` strings, and module-attribute references. Open ownership or method sets stay `needsReview`; the command never applies or compiles edits. |
|
|
29
29
|
|
|
30
30
|
## Repository and architecture
|
|
31
31
|
|
|
@@ -84,4 +84,4 @@ Omit the target for the current project. A target may be a file, directory, or q
|
|
|
84
84
|
|
|
85
85
|
## Language notes
|
|
86
86
|
|
|
87
|
-
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. 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.
|
|
87
|
+
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.
|
|
@@ -41,16 +41,17 @@ Even when all eight hold, `account.contract.semanticComplete` remains false. Use
|
|
|
41
41
|
|
|
42
42
|
Semantic recall can be complete while the result is still impractical: a true edge may be present only in a large unverified set. Release evaluation therefore measures both placement and the amount of review an agent must perform.
|
|
43
43
|
|
|
44
|
-
- `trueEdgeUnverifiedRate` is the share of
|
|
44
|
+
- `trueEdgeUnverifiedRate` is the share of statically exact oracle edges found only in the unverified tier. Runtime-polymorphic, compiler-dependent template, and oracle-unresolved references remain in the all-oracle view but never inflate the exact denominator.
|
|
45
45
|
- `zeroActionableUnverifiedTargetRate` is the share of reviewed targets with no actionable ambiguity.
|
|
46
46
|
- `actionableUnverifiedCandidatesP50`, `actionableUnverifiedCandidatesP95`, and `actionableUnverifiedCandidatesMax` measure actionable candidate-set size per target.
|
|
47
47
|
- `unverifiedReviewItemsPerOracleEdge` measures effective review work relative to the oracle workload. Actionable false candidates count individually; each named runtime-dispatch family counts once.
|
|
48
48
|
- `rawFalseUnverifiedPerOracleEdge` preserves the ungrouped false-candidate amplification for auditability.
|
|
49
|
+
- `runtimeDependentOracleEdges`, `compilerDependentOracleEdges`, and `oracleAbstentionEdges` disclose why all-oracle coverage is broader than exact target identity.
|
|
49
50
|
- `unverifiedReasons` groups candidates by the engine reason that kept them out of the confirmed tier.
|
|
50
51
|
|
|
51
52
|
Configuration-gated candidates remain visible but are not labeled false when the compiler/LSP oracle did not score them. The raw JSON rollup is the source of truth for these fields; the Markdown report is generated from the same data.
|
|
52
53
|
|
|
53
|
-
The publish-blocking portable-AST ceilings are: at most 10% of exact true edges left unverified, at most 20% within any sufficiently sampled symbol kind, at least 80% of targets with zero actionable ambiguity, actionable-candidate p95 at most five, and at most 0.10 effective review items per oracle edge. The semantic gate separately requires 100% in-scope recall, at least 98% confirmed-tier precision, full conservation, and 100% public-command proof recall. Passing these sampled floors is release evidence, not runtime-completeness proof.
|
|
54
|
+
The publish-blocking portable-AST ceilings are: at most 10% of exact true edges left unverified, at most 20% within any sufficiently sampled symbol kind, at least 80% of targets with zero actionable ambiguity, actionable-candidate p95 at most five, and at most 0.10 effective review items per oracle edge. The semantic gate separately requires 100% in-scope recall, at least 98% confirmed-tier precision, full conservation, and 100% public-command proof recall. Three class-aware floors gate at zero: unaccounted oracle edges of the exact, runtime-dispatch, and oracle-unresolved classes (caller and callee arms; compile-time-dispatch attributions stay report-only because their non-exact band varies with the compiler oracle's environment), runtime-dispatch oracle edges excluded with reason (possible-dispatch routing is demote-only), and deferred oracle-unresolved edges that exact definition lookup pins to the target re-enter the gate-bearing universe. Passing these sampled floors is release evidence, not runtime-completeness proof.
|
|
54
55
|
|
|
55
56
|
The performance gate runs each pinned repository in three independent processes with a fixed worker shape. Publish and PR gates require both CPU and wall-throughput floors on the median run, pin the expected file/LOC workload, and use the worst observed build/full-board peak RSS so memory failures cannot be averaged away. Exploratory one-process runs keep wall throughput diagnostic. Scoped reports are separate from the full release artifact; the dated rollup is release-qualified only when every required row came from a full release invocation. The composite release gate runs this stage first so verbose compiler-oracle output and report consumers cannot perturb its samples.
|
|
56
57
|
|
package/README.md
CHANGED
|
@@ -39,6 +39,8 @@ It's deliberately lightweight:
|
|
|
39
39
|
|
|
40
40
|
- **No required background process** - the CLI parses on demand, answers, and
|
|
41
41
|
exits. MCP stays warm only when you choose to run it.
|
|
42
|
+
- **No HTTP or network stack** - MCP uses a local, dependency-free stdio
|
|
43
|
+
transport; UCN never opens a port.
|
|
42
44
|
- **No language servers, no compilation** - tree-sitter does the analysis
|
|
43
45
|
without building the project.
|
|
44
46
|
- **No config** - point it at a directory and ask.
|
|
@@ -200,7 +202,7 @@ ACCOUNT, CONTRACT, and WARNING lines survive the cut.
|
|
|
200
202
|
Don't take the tiers on faith. Release gates re-derive UCN's answers from real
|
|
201
203
|
compilers and language servers on a ten-repository board of pinned production
|
|
202
204
|
codebases, and publishing is blocked unless they pass. The latest full
|
|
203
|
-
release-board run (2026-08-
|
|
205
|
+
release-board run (2026-08-24):
|
|
204
206
|
|
|
205
207
|
| Repository | Pinned commit | Oracle | Caller precision | Caller recall | Callee prec / recall | Command checks |
|
|
206
208
|
|---|---|---|---:|---:|---:|---:|
|
|
@@ -211,16 +213,16 @@ release-board run (2026-08-11):
|
|
|
211
213
|
| [ripgrep](https://github.com/BurntSushi/ripgrep) | [`82313cf9`](https://github.com/BurntSushi/ripgrep/commit/82313cf95849bfe425109ad9506a52154879b1b1) | rust-analyzer | 100% | 100% | 100% / 100% | 100% |
|
|
212
214
|
| [clap](https://github.com/clap-rs/clap) | [`d3e59a9a`](https://github.com/clap-rs/clap/commit/d3e59a9ab214910b9dad02921b7ef42c6400de9b) | rust-analyzer | 100% | 100% | 100% / 100% | 100% |
|
|
213
215
|
| [javapoet](https://github.com/square/javapoet) | [`b9017a95`](https://github.com/square/javapoet/commit/b9017a9503b76e11b4ad4c1a9f050e2d29112cb0) | JDT LS | 100% | 100% | 100% / 100% | 100% |
|
|
214
|
-
| [newtonsoft-json](https://github.com/JamesNK/Newtonsoft.Json) | [`4f73e743`](https://github.com/JamesNK/Newtonsoft.Json/commit/4f73e74372445108d2c1bda37b36e6f5e43402e0) | Roslyn |
|
|
216
|
+
| [newtonsoft-json](https://github.com/JamesNK/Newtonsoft.Json) | [`4f73e743`](https://github.com/JamesNK/Newtonsoft.Json/commit/4f73e74372445108d2c1bda37b36e6f5e43402e0) | Roslyn | 100% | 100% | 100% / 100% | 100% |
|
|
215
217
|
| [cjson](https://github.com/DaveGamble/cJSON) | [`c859b25d`](https://github.com/DaveGamble/cJSON/commit/c859b25da02955fef659d658b8f324b5cde87be3) | clangd | 100% | 100% | 100% / 100% | 100% |
|
|
216
|
-
| [fmt](https://github.com/fmtlib/fmt) | [`e424e3f2`](https://github.com/fmtlib/fmt/commit/e424e3f2e607da02742f73db84873b8084fc714c) | clangd |
|
|
218
|
+
| [fmt](https://github.com/fmtlib/fmt) | [`e424e3f2`](https://github.com/fmtlib/fmt/commit/e424e3f2e607da02742f73db84873b8084fc714c) | clangd | 100% | 100% | 100% / 100% | 100% |
|
|
217
219
|
|
|
218
220
|
On the same run: **zero** in-scope oracle call edges missing from the answer
|
|
219
221
|
(the release gate) on every repository, **zero** false-dead `deadcode` claims
|
|
220
222
|
in the oracle-visible sample, **8,000 / 8,000** cross-command consistency
|
|
221
223
|
comparisons in agreement, **10 / 10** repositories inside the performance
|
|
222
|
-
budget (slowest median cold build
|
|
223
|
-
p95
|
|
224
|
+
budget (slowest normalized median cold build 17.4K lines/second by wall time,
|
|
225
|
+
worst query p95 83.0 ms, highest peak RSS 908.5 MB), and 3,609 automated tests with no
|
|
224
226
|
failures or skips. The same gates run in CI (the scheduled
|
|
225
227
|
[Eval workflow](https://github.com/mleoca/ucn/actions/workflows/eval.yml) and
|
|
226
228
|
every release tag), and `npm run trust:gate` reproduces the release board
|
|
@@ -234,10 +236,11 @@ external population. Unverified precision is reported separately and is
|
|
|
234
236
|
intentionally much lower on dispatch-heavy code: those entries are review
|
|
235
237
|
candidates, never confirmed claims.
|
|
236
238
|
|
|
237
|
-
Beyond the publish gate, a scheduled board re-checks
|
|
239
|
+
Beyond the publish gate, a scheduled board re-checks 24 pinned repositories
|
|
238
240
|
across every supported oracle language (zod, express, hono, zustand, fastify,
|
|
239
|
-
rich, click, grpc-go, chi, cursive, gson, jsoup, and
|
|
240
|
-
fresh-repo arm of codebases the engine was never
|
|
241
|
+
rich, click, attrs, grpc-go, chi, cursive, itertools, gson, jsoup, and
|
|
242
|
+
friends), plus a rotating fresh-repo arm of codebases the engine was never
|
|
243
|
+
tuned on. Repositories that
|
|
241
244
|
expose a gap stay on the board; they don't get removed to keep a table pretty.
|
|
242
245
|
These are measured results on pinned code, not a claim of universal program
|
|
243
246
|
understanding or identical performance on every machine.
|
|
@@ -299,8 +302,17 @@ cli/index.js (2 changes)
|
|
|
299
302
|
... (more changes in core/discovery.js, core/cache.js, core/project.js, test/integration.test.js)
|
|
300
303
|
```
|
|
301
304
|
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
For a rename, `plan` closes the change over every relationship the index can
|
|
306
|
+
prove: overload/signature groups, base and override declarations, Rust trait
|
|
307
|
+
slots, Go interface slots and their satisfiers, exact call and value-reference
|
|
308
|
+
tokens, imports/exports, Python `__all__` strings, and module-attribute
|
|
309
|
+
references. It edits exact token or expression spans, so another same-named
|
|
310
|
+
call on the same line is not swept up accidentally.
|
|
311
|
+
|
|
312
|
+
Open external interfaces, incomplete ownership, unresolved dispatch, or an
|
|
313
|
+
inexact token are marked `needsReview` instead of receiving a synthesized
|
|
314
|
+
edit. `plan` previews changes; it does not modify files or replace the
|
|
315
|
+
compiler and test suite. Before committing, point the same machinery at your
|
|
304
316
|
Git diff:
|
|
305
317
|
|
|
306
318
|
```bash
|
|
@@ -518,10 +530,13 @@ evidence.
|
|
|
518
530
|
- **C** - functions, structs, macros, includes, calls, entry points, API
|
|
519
531
|
analysis.
|
|
520
532
|
- **C++** - C coverage plus classes, methods, constructors, inheritance,
|
|
521
|
-
namespaces, overloads, templates, typed field receivers
|
|
533
|
+
namespaces, overloads, templates, typed field receivers, static array-shape
|
|
534
|
+
selection, and macro requalification. Conditional macro disagreement stays
|
|
535
|
+
visible as unverified.
|
|
522
536
|
- **C#** - namespaces, classes/interfaces/records, fields/properties,
|
|
523
|
-
attributes,
|
|
524
|
-
|
|
537
|
+
attributes, declared property/field receiver types, overload and hiding
|
|
538
|
+
discipline, async flow, top-level programs, .NET stack frames, and
|
|
539
|
+
ASP.NET/HttpClient endpoints.
|
|
525
540
|
- **HTML** - inline JavaScript and `on*` event handlers.
|
|
526
541
|
|
|
527
542
|
For C and C++, a `compile_commands.json` improves header-language,
|
|
@@ -638,7 +653,9 @@ answers. It's guidance over the same engine, not a second implementation.
|
|
|
638
653
|
and external consumers can be invisible. UCN reports these blind spots
|
|
639
654
|
(`repo --sections=health --deep`) rather than pretending they don't exist.
|
|
640
655
|
- Interface, trait, template, overload, and untyped-receiver dispatch may stay
|
|
641
|
-
in the UNVERIFIED tier with a reason instead of being guessed.
|
|
656
|
+
in the UNVERIFIED tier with a reason instead of being guessed. When
|
|
657
|
+
same-name definitions compete, `show` lists their stable handles once so
|
|
658
|
+
agents can see exactly what needs disambiguation.
|
|
642
659
|
- C/C++ analysis doesn't run the preprocessor or compiler; build-specific
|
|
643
660
|
branches, advanced templates, and macro expansion can remain unresolved. C#
|
|
644
661
|
analysis doesn't run Roslyn; source generators and external assembly
|
package/core/analysis.js
CHANGED
|
@@ -12,7 +12,8 @@ const path = require('path');
|
|
|
12
12
|
const { execFileSync } = require('child_process');
|
|
13
13
|
const { parse } = require('./parser');
|
|
14
14
|
const { detectLanguage, langTraits } = require('../languages');
|
|
15
|
-
const { NON_CALLABLE_TYPES, addTestExclusions, countTextBlindspots,
|
|
15
|
+
const { NON_CALLABLE_TYPES, addTestExclusions, countTextBlindspots,
|
|
16
|
+
codeUnitCompare, formatSymbolHandle } = require('./shared');
|
|
16
17
|
const { isTestFile } = require('./discovery');
|
|
17
18
|
const { computeReachability, symbolKey } = require('./entrypoints');
|
|
18
19
|
const { getLanguageAdapter } = require('../languages');
|
|
@@ -23,6 +24,62 @@ const { projectComputedDispatch } = require('./ast-analysis');
|
|
|
23
24
|
// passed to one of these (the common pattern in mocha/jest/vitest).
|
|
24
25
|
const _JS_TEST_FRAMEWORK_CALLS = new Set(['describe', 'it', 'test', 'spec', 'context', 'suite']);
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Give agents the exact indexed definitions behind a same-name ambiguity.
|
|
29
|
+
* Candidate handles live once on the context result rather than being copied
|
|
30
|
+
* onto every unverified site (a single broad name can have thousands).
|
|
31
|
+
*/
|
|
32
|
+
function buildAmbiguityCandidates(index, name, selected, unverified) {
|
|
33
|
+
const relevant = (unverified || []).filter(site =>
|
|
34
|
+
site.reason === 'method-ambiguous' || site.reason === 'ambiguous-binding');
|
|
35
|
+
if (relevant.length === 0) return null;
|
|
36
|
+
const byHandle = new Map();
|
|
37
|
+
for (const definition of (index.symbols.get(name) || [])) {
|
|
38
|
+
if (!definition.file || !definition.startLine) continue;
|
|
39
|
+
const handle = formatSymbolHandle(definition);
|
|
40
|
+
if (handle && !byHandle.has(handle)) byHandle.set(handle, definition);
|
|
41
|
+
}
|
|
42
|
+
const definitions = [...byHandle.values()]
|
|
43
|
+
.sort((a, b) => {
|
|
44
|
+
const aSelected = a.bindingId === selected.bindingId ||
|
|
45
|
+
(a.file === selected.file && a.startLine === selected.startLine);
|
|
46
|
+
const bSelected = b.bindingId === selected.bindingId ||
|
|
47
|
+
(b.file === selected.file && b.startLine === selected.startLine);
|
|
48
|
+
if (aSelected !== bSelected) return aSelected ? -1 : 1;
|
|
49
|
+
const fileCmp = codeUnitCompare(a.relativePath || a.file, b.relativePath || b.file);
|
|
50
|
+
if (fileCmp !== 0) return fileCmp;
|
|
51
|
+
if (a.startLine !== b.startLine) return a.startLine - b.startLine;
|
|
52
|
+
return codeUnitCompare(a.type, b.type);
|
|
53
|
+
});
|
|
54
|
+
if (definitions.length < 2) return null;
|
|
55
|
+
const limit = 12;
|
|
56
|
+
const items = definitions.slice(0, limit).map(definition => ({
|
|
57
|
+
handle: formatSymbolHandle(definition),
|
|
58
|
+
type: definition.type,
|
|
59
|
+
...(definition.className && { owner: definition.className }),
|
|
60
|
+
...(!definition.className && definition.receiver && {
|
|
61
|
+
owner: definition.receiver.replace(/^\*/, ''),
|
|
62
|
+
}),
|
|
63
|
+
...(!definition.className && !definition.receiver && definition.assignedReceiver && {
|
|
64
|
+
owner: definition.assignedReceiver,
|
|
65
|
+
memberAssignment: true,
|
|
66
|
+
}),
|
|
67
|
+
selected: definition.bindingId === selected.bindingId ||
|
|
68
|
+
(definition.file === selected.file && definition.startLine === selected.startLine),
|
|
69
|
+
}));
|
|
70
|
+
// reduce, not Math.max(...spread): a broad name on a monorepo can carry
|
|
71
|
+
// enough unverified sites to overflow the argument list.
|
|
72
|
+
const dispatchOwners = relevant.reduce(
|
|
73
|
+
(max, site) => Math.max(max, Number(site.dispatchCandidates) || 0), 0);
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
totalDefinitions: definitions.length,
|
|
77
|
+
...(dispatchOwners > 0 && { dispatchOwners }),
|
|
78
|
+
items,
|
|
79
|
+
truncated: definitions.length > items.length,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
26
83
|
/**
|
|
27
84
|
* Tag each call site with `inTestCase` based on its enclosing function's
|
|
28
85
|
* entry-point classification. Uses each language's `getEntryPointKind`
|
|
@@ -338,6 +395,8 @@ function context(index, name, options = {}) {
|
|
|
338
395
|
|
|
339
396
|
typeCallers = [...typeCallers].sort(byFileLine);
|
|
340
397
|
typeUnverified = [...typeUnverified].sort(byFileLine);
|
|
398
|
+
const typeAmbiguityCandidates = buildAmbiguityCandidates(
|
|
399
|
+
index, name, def, typeUnverified);
|
|
341
400
|
|
|
342
401
|
const callerTotal = typeCallers.length;
|
|
343
402
|
if (options.maxCallers && options.maxCallers > 0) {
|
|
@@ -366,6 +425,9 @@ function context(index, name, options = {}) {
|
|
|
366
425
|
// Also include places where the type is used in function parameters/returns
|
|
367
426
|
callers: typeCallers,
|
|
368
427
|
unverifiedCallers: typeUnverified,
|
|
428
|
+
...(typeAmbiguityCandidates && {
|
|
429
|
+
ambiguityCandidates: typeAmbiguityCandidates,
|
|
430
|
+
}),
|
|
369
431
|
meta: { account: typeAccount, callerTotal }
|
|
370
432
|
};
|
|
371
433
|
|
|
@@ -485,6 +547,8 @@ function context(index, name, options = {}) {
|
|
|
485
547
|
|
|
486
548
|
const callerHistogram = buildHistogram(callers);
|
|
487
549
|
const calleeHistogram = buildHistogram(callees);
|
|
550
|
+
const ambiguityCandidates = buildAmbiguityCandidates(
|
|
551
|
+
index, name, def, unverifiedCallers);
|
|
488
552
|
const callerTotal = callers.length;
|
|
489
553
|
const calleeTotal = callees.length;
|
|
490
554
|
if (options.maxCallers && options.maxCallers > 0) {
|
|
@@ -512,6 +576,7 @@ function context(index, name, options = {}) {
|
|
|
512
576
|
returnType: def.returnType,
|
|
513
577
|
callers,
|
|
514
578
|
unverifiedCallers,
|
|
579
|
+
...(ambiguityCandidates && { ambiguityCandidates }),
|
|
515
580
|
callees,
|
|
516
581
|
unverifiedCallees: rawCallees.unverifiedCallees || [],
|
|
517
582
|
callerHistogram,
|
package/core/bridge.js
CHANGED
|
@@ -835,7 +835,8 @@ function collectProjectRouterMounts(index) {
|
|
|
835
835
|
const targetEntry = index.files.get(targetFile);
|
|
836
836
|
if (targetEntry) {
|
|
837
837
|
const exportedReceivers = (targetEntry.exportDetails || [])
|
|
838
|
-
.filter(exp => exp.type === 'module.exports'
|
|
838
|
+
.filter(exp => (exp.type === 'module.exports' && exp.defaultLike) ||
|
|
839
|
+
exp.isDefault ||
|
|
839
840
|
exp.kind === 'default' || exp.type === 'export-default' ||
|
|
840
841
|
exp.name === ref.name)
|
|
841
842
|
.map(exp => exp.localName || exp.name).filter(Boolean);
|
package/core/cache.js
CHANGED
|
@@ -599,7 +599,52 @@ function clearAllCaches() {
|
|
|
599
599
|
// v166: C++ nested aliases persist their lexical owner ranges, and `auto`
|
|
600
600
|
// return functions persist a unanimously inferred local concrete type. v165
|
|
601
601
|
// was used during prerelease development before both fields were complete.
|
|
602
|
-
|
|
602
|
+
// v173: JS/TS fluent methods persist AST-proven `this` return identity, and
|
|
603
|
+
// nested const/arrow callables persist their enclosing lexical owner range.
|
|
604
|
+
// v172: immutable JS/TS class-member aliases materialize their local and
|
|
605
|
+
// exported callable identities, preserving declared return types through
|
|
606
|
+
// factory aliases and chained receivers.
|
|
607
|
+
// v171: import bindings persist their syntactic kind (named/default/namespace)
|
|
608
|
+
// so an exported ESM namespace object can carry exact member ownership through
|
|
609
|
+
// a downstream named or default import.
|
|
610
|
+
// v170: statically-owned CommonJS property assignments persist their local
|
|
611
|
+
// callable identity, allowing exact module ownership to exclude a different
|
|
612
|
+
// same-name export without treating all CJS surfaces as opaque.
|
|
613
|
+
// v169: CommonJS export details distinguish the value assigned directly to
|
|
614
|
+
// module.exports (`defaultLike`) from property exports; this prevents a
|
|
615
|
+
// namespace require from becoming a confirmed call to every exported member.
|
|
616
|
+
// v168: JS/TS one-hop member-assignment defs persist assignedReceiver — the
|
|
617
|
+
// object they patch (`console.log = fn` → 'console') — so the builtin-global
|
|
618
|
+
// exclusion can see cross-file that a project def rebinds the global's
|
|
619
|
+
// member (fix #286a); impl-kind symbols leave the bindings table (#286b).
|
|
620
|
+
// v177: C++ `template <>` full-specialization defs persist isSpecialization
|
|
621
|
+
// (fix #299A — identity closure with the primary template), and C++
|
|
622
|
+
// argument-position calls through a local callable variable record kind
|
|
623
|
+
// 'expr' instead of call:NAME (fix #299B shadow guard).
|
|
624
|
+
// v178: extendsGraph entries persist the defining symbol's startLine so
|
|
625
|
+
// scope-aware consumers can pick the right def when several same-name
|
|
626
|
+
// classes live in one file — function-local test subclasses (fix #300,
|
|
627
|
+
// attrs-measured); Go range VALUE variables are typed from the container's
|
|
628
|
+
// element type (declared fields/params/vars/literals — `for _, route :=
|
|
629
|
+
// range r.routes` types route as Route; fix #300, mux-measured), changing
|
|
630
|
+
// persisted call-record receiverTypes.
|
|
631
|
+
// v179: Go unnamed interface parameters are retained in paramsStructured;
|
|
632
|
+
// old caches report zero-arity interface methods and break rename closure.
|
|
633
|
+
// v183: Go calls persist exact spans, builtin-new receiver types, indexed
|
|
634
|
+
// multi-return assignment targets, and lexical-closure result hints for exact
|
|
635
|
+
// chain/tuple flow; Rust macro-token calls retain compiler-declared closure
|
|
636
|
+
// parameter receiver types, and Rust functions retain generic trait bounds.
|
|
637
|
+
// v184: Python constructor-result records no longer retain a receiverType
|
|
638
|
+
// inferred from an uppercase name that is value-bound in the lexical scope
|
|
639
|
+
// (fix #304). Reject v183 calls shards so stale false receiver evidence
|
|
640
|
+
// cannot survive an engine upgrade.
|
|
641
|
+
// v185: C# deep member receivers retain their root+field path instead of a
|
|
642
|
+
// false terminal receiverType copied from the root variable (fix #305).
|
|
643
|
+
// v188: C++ call records retain enclosing macro-argument positions and macro
|
|
644
|
+
// symbols retain AST-derived parameter qualification/forwarding effects, so
|
|
645
|
+
// replacement-list requalification cannot masquerade as lexical calls
|
|
646
|
+
// (fix #306).
|
|
647
|
+
const CACHE_FORMAT_VERSION = 188;
|
|
603
648
|
|
|
604
649
|
/**
|
|
605
650
|
* Save index to cache file
|
|
@@ -640,7 +685,9 @@ function saveCache(index, cachePath) {
|
|
|
640
685
|
// Strip redundant fields from symbols and file entries to reduce cache size.
|
|
641
686
|
// v6: All paths stored as relative paths (saves ~60% on large codebases).
|
|
642
687
|
// symbol.file = path.join(root, symbol.relativePath) — reconstructable
|
|
643
|
-
// symbol.bindingId = relativePath:type:startLine — reconstructable
|
|
688
|
+
// Default symbol.bindingId = relativePath:type:startLine — reconstructable.
|
|
689
|
+
// Preserve non-default IDs: synthetic declarations can share a source line
|
|
690
|
+
// and need their explicit identity to survive a cache round-trip.
|
|
644
691
|
// fileEntry.path = Map key — redundant
|
|
645
692
|
// fileEntry.relativePath = now the Map key — redundant
|
|
646
693
|
const root = index.root;
|
|
@@ -648,6 +695,12 @@ function saveCache(index, cachePath) {
|
|
|
648
695
|
for (const [name, defs] of index.symbols) {
|
|
649
696
|
const stripped = defs.map(s => {
|
|
650
697
|
const { file, bindingId, ...rest } = s;
|
|
698
|
+
const defaultBindingId = s.relativePath && s.type && s.startLine
|
|
699
|
+
? `${s.relativePath}:${s.type}:${s.startLine}`
|
|
700
|
+
: null;
|
|
701
|
+
if (bindingId && bindingId !== defaultBindingId) {
|
|
702
|
+
rest.bindingId = bindingId;
|
|
703
|
+
}
|
|
651
704
|
return rest;
|
|
652
705
|
});
|
|
653
706
|
strippedSymbols.push([name, stripped]);
|
|
@@ -897,12 +950,15 @@ function loadCache(index, cachePath) {
|
|
|
897
950
|
const fileEntry = index.files.get(s.file);
|
|
898
951
|
if (fileEntry) {
|
|
899
952
|
fileEntry.symbols.push(s);
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
953
|
+
if (!s.memberAssigned && !s.bodyScopedName && !s.exportedAlias &&
|
|
954
|
+
s.type !== 'impl') {
|
|
955
|
+
fileEntry.bindings.push({
|
|
956
|
+
id: s.bindingId,
|
|
957
|
+
name: s.name,
|
|
958
|
+
type: s.type,
|
|
959
|
+
startLine: s.startLine
|
|
960
|
+
});
|
|
961
|
+
}
|
|
906
962
|
}
|
|
907
963
|
}
|
|
908
964
|
}
|