ucn 4.1.1 → 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 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**. 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 ten production repos — **96.6–100% confirmed-tier precision, zero unexplained call edges, [or the build fails](#answers-you-can-trust)**.
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 10-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.
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.4–100% |
163
+ | TypeScript / JavaScript | ts-morph | 99.5–100% |
164
164
  | Python | pyright (LSP) | 97.7–99.9% |
165
- | Go | gopls | 99.9–100% |
166
- | Rust | rust-analyzer | 99.0–100% |
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
- Ten pinned real-world repos (zod, express, httpx, rich, cobra, grpc-go, ripgrep, cursive, gson, preact-signals), 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.
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 10 pinned real repos, gated on zero unexplained edges (see [Answers you can trust](#answers-you-can-trust))
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
 
@@ -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
- fileEntry.bindings.push({
67
- id: symbol.bindingId,
68
- name: symbol.name,
69
- type: symbol.type,
70
- startLine: symbol.startLine,
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 => ({ name: n, module: i.module }))),
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
- const CACHE_FORMAT_VERSION = 41;
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