ucn 4.1.0 → 4.1.2
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/README.md +10 -8
- package/core/build-worker.js +21 -7
- package/core/cache.js +16 -1
- package/core/callers.js +1210 -98
- package/core/deadcode.js +59 -8
- package/core/entrypoints.js +13 -2
- package/core/imports.js +98 -6
- package/core/project.js +45 -13
- package/languages/go.js +56 -4
- package/languages/java.js +5 -1
- package/languages/javascript.js +95 -9
- package/languages/python.js +23 -1
- package/languages/rust.js +42 -8
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ UCN is deliberately lightweight:
|
|
|
34
34
|
- **No language servers** - tree-sitter does the parsing, no compilation needed
|
|
35
35
|
- **MCP is optional** - only needed if you connect UCN to an AI agent, the CLI and Skill work on their own
|
|
36
36
|
|
|
37
|
-
And it's built to be **trusted
|
|
37
|
+
And it's built to be **trusted**. grep hands you raw text matches to verify yourself; UCN splits every "who calls this?" into what it can prove and what it can't — each flagged with a reason, nothing silently dropped. That claim is measured, not promised: CI re-derives UCN's answers from the real compilers and language servers (ts-morph, pyright, gopls, rust-analyzer, jdtls) across nineteen production repos — **96.6–100% confirmed-tier precision on eighteen of them, zero unexplained call edges, [or the build fails](#answers-you-can-trust)**.
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -152,7 +152,7 @@ UCN sorts every one of the 55 places the name appears:
|
|
|
152
152
|
- **11 non-call** — imports, the definition, plain text.
|
|
153
153
|
- **`0 unaccounted`** — the partition is complete. Nothing was dropped on the floor.
|
|
154
154
|
|
|
155
|
-
The payoff: a **confirmed** answer is safe to refactor against, and a clean zero — no confirmed, no unverified, `0 unaccounted` — is a trustworthy zero (measured: 68 of 69 clean-zero samples across the
|
|
155
|
+
The payoff: a **confirmed** answer is safe to refactor against, and a clean zero — no confirmed, no unverified, `0 unaccounted` — is a trustworthy zero (measured: 68 of 69 clean-zero samples across the pinned-repo board agree with the compiler oracles; the one disagreement is a `new X()` on an old-style constructor function, which lands in the non-call counts, still visible). One caveat before deleting anything: callers aren't the only usages — if the NON-CALL counts are nonzero, run `ucn usages` to see what they are.
|
|
156
156
|
|
|
157
157
|
### Measured against ground truth
|
|
158
158
|
|
|
@@ -160,13 +160,15 @@ This isn't a promise — it's a gate. CI re-derives UCN's caller answers from re
|
|
|
160
160
|
|
|
161
161
|
| Language | Oracle | Confirmed-tier precision |
|
|
162
162
|
|---|---|---|
|
|
163
|
-
| TypeScript / JavaScript | ts-morph | 99.
|
|
163
|
+
| TypeScript / JavaScript | ts-morph | 99.5–100% |
|
|
164
164
|
| Python | pyright (LSP) | 97.7–99.9% |
|
|
165
|
-
| Go | gopls |
|
|
166
|
-
| Rust | rust-analyzer | 99.
|
|
167
|
-
| Java | jdtls | 96.6% |
|
|
165
|
+
| Go | gopls | 98.8–100% |
|
|
166
|
+
| Rust | rust-analyzer | 99.3–100%* |
|
|
167
|
+
| Java | jdtls | 96.6–100% |
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
\* clap reads 92.4% — its callers live behind cargo feature gates the oracle compiles out; UCN sees the text, rust-analyzer doesn't.
|
|
170
|
+
|
|
171
|
+
Nineteen pinned real-world repos (zod, preact-signals, express, hono, zustand, fastify, httpx, rich, click, cobra, grpc-go, viper, chi, ripgrep, cursive, clap, gson, javapoet, jsoup), three sampling seeds, every run gated at `missing-unexplained = 0` — plus a weekly fresh-repo arm: two unpinned repos the engine was never tuned on, same gate. The tree commands — `trace`, `blast`, `reverse-trace`, `affected-tests` — follow the same rule: confirmed trunk, uncertain branches flagged (`--expand-unverified` to follow). Run `ucn doctor` for the trust report on *your* repo.
|
|
170
172
|
|
|
171
173
|
## Change code without breaking things
|
|
172
174
|
|
|
@@ -401,7 +403,7 @@ function compareNames(a, b) {
|
|
|
401
403
|
- **Coverage** - every command, every supported language, every surface (CLI, MCP, interactive)
|
|
402
404
|
- **Systematic** - a harness exercises all command and flag combinations against real multi-language fixtures
|
|
403
405
|
- **Test types** - unit, integration, per-language regression, formatter, cache, MCP edge cases, architecture parity guards
|
|
404
|
-
- **Ground truth** - caller accuracy is measured against ts-morph, pyright, gopls, rust-analyzer, and jdtls on
|
|
406
|
+
- **Ground truth** - caller accuracy is measured against ts-morph, pyright, gopls, rust-analyzer, and jdtls on 19 pinned real repos, gated on zero unexplained edges (see [Answers you can trust](#answers-you-can-trust))
|
|
405
407
|
|
|
406
408
|
---
|
|
407
409
|
|
package/core/build-worker.js
CHANGED
|
@@ -61,14 +61,19 @@ function addSymbol(fileEntry, item, type) {
|
|
|
61
61
|
if (item.traitImpl) symbol.traitImpl = true;
|
|
62
62
|
if (item.traitName) symbol.traitName = item.traitName;
|
|
63
63
|
if (item.isSignature) symbol.isSignature = true;
|
|
64
|
+
if (item.memberAssigned) symbol.memberAssigned = true;
|
|
64
65
|
|
|
65
66
|
fileEntry.symbols.push(symbol);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
// Property-assignment defs declare no lexical name (fix #269) — kept in
|
|
68
|
+
// lockstep with project.js addSymbol (the parallel≡sequential guard).
|
|
69
|
+
if (!item.memberAssigned) {
|
|
70
|
+
fileEntry.bindings.push({
|
|
71
|
+
id: symbol.bindingId,
|
|
72
|
+
name: symbol.name,
|
|
73
|
+
type: symbol.type,
|
|
74
|
+
startLine: symbol.startLine,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
function processFile(filePath) {
|
|
@@ -119,6 +124,11 @@ function processFile(filePath) {
|
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
if (content.length - lineStart > 1000) longLineCount++;
|
|
127
|
+
// A trailing newline TERMINATES the last line rather than opening a new
|
|
128
|
+
// one; an empty file has 0 lines (fix #251 — adjusted in indexFile only,
|
|
129
|
+
// so parallel-built stats reported +1 line per newline-terminated file).
|
|
130
|
+
if (content.length === 0) lineCount = 0;
|
|
131
|
+
else if (content.charCodeAt(content.length - 1) === 10) lineCount--;
|
|
122
132
|
|
|
123
133
|
const isBundled = (
|
|
124
134
|
content.includes('__webpack_require__') || content.includes('__webpack_modules__') ||
|
|
@@ -148,7 +158,11 @@ function processFile(filePath) {
|
|
|
148
158
|
// the name-level scope/ownership discipline (found during fix #217).
|
|
149
159
|
importBindings: imports.flatMap(i => (i.names || [])
|
|
150
160
|
.filter(n => n && n !== '*' && n !== '_' && n !== '.')
|
|
151
|
-
.map(n =>
|
|
161
|
+
.map(n => {
|
|
162
|
+
// Rename pairing (fix #269) — lockstep with project.js.
|
|
163
|
+
const rn = (i.renames || []).find(r => r.original === n);
|
|
164
|
+
return { name: n, module: i.module, ...(rn && { alias: rn.local }) };
|
|
165
|
+
})),
|
|
152
166
|
exports: exports.map(e => e.name),
|
|
153
167
|
exportDetails: exports,
|
|
154
168
|
symbols: [],
|
package/core/cache.js
CHANGED
|
@@ -109,7 +109,22 @@ const UCN_VERSION = require('../package.json').version;
|
|
|
109
109
|
// function properties (module.exports = { doThing(x){} }) and list them in
|
|
110
110
|
// exports; member-expression assignments record RHS identifier function
|
|
111
111
|
// references (window.onload = handler).
|
|
112
|
-
|
|
112
|
+
// v44 (fix #262): JS/TS literal ASSIGNMENTS type the variable (const lines
|
|
113
|
+
// = [] → Array), and untyped reassignment deletes inferred types — call
|
|
114
|
+
// records carry receiverType on literal-assigned receivers.
|
|
115
|
+
// v45 (fix #265): Python typing @overload-decorated defs carry isSignature
|
|
116
|
+
// (TS overload parity — pin identity closes over the overload group, and
|
|
117
|
+
// pickBestDefinition prefers the implementation).
|
|
118
|
+
// v46 (fix #266): Go call records mark New*-prefix-derived receiver types
|
|
119
|
+
// as guesses (receiverTypeGuessed / receiverRootTypeGuessed) — convention,
|
|
120
|
+
// not compiler truth; the return-type flow map overrides them and
|
|
121
|
+
// exclusions never trust them. (fix #267) String-named TS module
|
|
122
|
+
// declarations (`declare module '../x'` augmentations) no longer index as
|
|
123
|
+
// namespace symbols — they declare no nameable identifier.
|
|
124
|
+
// v47 (fix #269): Python absolute imports resolve through the PEP-517 src
|
|
125
|
+
// layout (`import click` → src/click/__init__.py) — persisted
|
|
126
|
+
// importGraph/exportGraph/moduleResolved content changed.
|
|
127
|
+
const CACHE_FORMAT_VERSION = 47;
|
|
113
128
|
|
|
114
129
|
/**
|
|
115
130
|
* Save index to cache file
|