ucn 4.1.2 → 4.2.1

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.
@@ -0,0 +1,63 @@
1
+ # UCN trust contract
2
+
3
+ Use this reference when an agent, CI job, or script will make decisions from UCN output.
4
+
5
+ ## Evidence classes
6
+
7
+ `confirmed` means UCN found positive identity evidence such as an exact binding, a same-class target, a validated receiver type, or an owned import/export chain. It does not mean the edge has a calibrated probability.
8
+
9
+ `unverified` means a syntactic candidate could reach the target but identity could not be established. Preserve and expose these edges. Do not silently treat them as negatives.
10
+
11
+ `excluded` means the engine found evidence for a different definition, an incompatible receiver, an external package, an arity mismatch, or another stated reason. Inspect excluded reasons when investigating an accuracy issue.
12
+
13
+ `non-call` means the literal name occurred as a definition, import, type reference, property, comment/string, or other text. Use `usages` for the underlying sites.
14
+
15
+ `beyond-text` means semantic binding or alias evidence produced an edge that a literal-name ground set could not observe.
16
+
17
+ ## Conservation scope
18
+
19
+ The caller account partitions a literal-name text-occurrence ground set. A conserved account answers: “Did UCN explain every observed literal-name line?” It does not answer: “Did UCN discover every semantically possible runtime caller?”
20
+
21
+ Required automation checks:
22
+
23
+ 1. `account.conserved` is true.
24
+ 2. `account.contract.textComplete` is true.
25
+ 3. No unreadable or unparsed files are reported.
26
+ 4. No contract metadata was lost to truncation.
27
+ 5. Unverified and excluded reasons are retained for review.
28
+ 6. Compiler/LSP evaluation reports keep configuration-unscored evidence below the release ceiling; a high-precision result from an undersized scored subset is not accepted.
29
+
30
+ Even when all five hold, `account.contract.semanticComplete` remains false. Use compiler/type-checker, test, runtime, and framework evidence for semantic decisions.
31
+
32
+ `account.contract.observedTextZero` is safe only for the claim “the complete observed literal-name ground set contained no caller candidates or beyond-text edges.” It is explicitly not safe-delete proof.
33
+
34
+ ## Numeric fields
35
+
36
+ `evidenceScore` (and the legacy `confidence` alias where present) is an ordinal weight used for ordering and thresholding evidence classes. `scoreKind` is `ordinal-evidence-not-probability`. Never display it as measured accuracy or use it as a probability in risk calculations.
37
+
38
+ ## Truncation
39
+
40
+ MCP results may include:
41
+
42
+ ```json
43
+ {
44
+ "truncated": true,
45
+ "contractMetadata": ["ACCOUNT: ...", "CONTRACT: ..."],
46
+ "contractMetadataComplete": true
47
+ }
48
+ ```
49
+
50
+ When `contractMetadataComplete` is false, narrow the query or increase the output budget. A truncated result without complete contract metadata cannot support an automated breaking-change decision.
51
+
52
+ ## Suggested machine policy
53
+
54
+ Allow an automated change to proceed to compiler/tests only when:
55
+
56
+ - the exact target is pinned by handle;
57
+ - the account is conserved and text-complete;
58
+ - warnings and filtered counts are zero;
59
+ - unverified sites are either zero or explicitly reviewed;
60
+ - UCN doctor reports no parse failure/recovery and its task-specific readiness has been reviewed;
61
+ - the change is still validated by the language toolchain and relevant tests.
62
+
63
+ Never auto-delete from a UCN-only signal. Require usages, entry-point review, public API review, and external validation.
package/README.md CHANGED
@@ -34,7 +34,20 @@ 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 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)**.
37
+ And it's built for **auditable trust**. grep hands you raw text matches to verify yourself; UCN separates target-backed edges from possible edges, explains exclusions, and reconciles its observed text set. It does not turn a zero into a deletion claim. CI re-derives answers from real compilers and language servers (ts-morph, pyright, gopls, rust-analyzer, jdtls). Publishing is gated on a representative five-repository board; the scheduled board covers nineteen plus rotating fresh repositories. See [Answers you can trust](#answers-you-can-trust).
38
+
39
+ ### Same engine, different transport
40
+
41
+ The CLI and MCP tool use the same command registry, handlers, project index, persisted cache, and output formatters. The skill is guidance for choosing and interpreting those commands; it is not a third analysis engine.
42
+
43
+ The transports intentionally have different defaults:
44
+
45
+ - CLI prints full text unless `--compact` is passed. `--json` emits raw machine-readable JSON.
46
+ - MCP defaults `about`, `context`, and `impact` to compact text. Targeted commands have a 10K character default, broad commands have a 3K default, and the hard ceiling is 100K. Truncated answers retain contract metadata.
47
+ - MCP commands and parameters use snake_case. CLI commands and flags use hyphenated names.
48
+ - A persistent MCP server keeps the process and index warm across calls. Repeated calls are normally faster than launching the CLI for every query, while semantic execution and cache behavior remain shared.
49
+
50
+ When comparing text output, use equivalent flags and compact settings. Transport-specific retry hints use the spelling appropriate to that surface, so the final hint line may differ.
38
51
 
39
52
  ---
40
53
 
@@ -70,12 +83,12 @@ build
70
83
  ├── buildImportGraph (core/project.js:798) 1x
71
84
  └── buildInheritanceGraph (core/project.js:803) 1x
72
85
  … calls UCN can't prove a receiver for (arr.push(), obj.get()) show as
73
- [unverified] leaves abridged here
86
+ [unverified] leaves (abridged here)
74
87
 
75
88
  CALLEE ACCOUNT: 26 nodes expanded · 394 call sites = 61 confirmed + 162 unverified (162 uncertain-receiver) + 68 external/builtin + 103 excluded
76
89
  ```
77
90
 
78
- One command, no files opened and the `CALLEE ACCOUNT:` line proves all 394 calls were sorted, nothing dropped.
91
+ One command, no files opened. The `CALLEE ACCOUNT:` line reconciles all 394 indexed call sites into explicit buckets.
79
92
 
80
93
  ---
81
94
 
@@ -94,7 +107,7 @@ expandGlob (pattern: string, options: number = {}) : string[]
94
107
  USAGES: 8 total
95
108
  3 calls, 3 imports, 2 references
96
109
 
97
- CALLERS CONFIRMED (7, 3 prod + 4 test):
110
+ CALLERS: CONFIRMED (7, 3 prod + 4 test):
98
111
  evidence: scope-match (all)
99
112
  cli/index.js:1267 [runGlobCommand]
100
113
  const files = expandGlob(pattern);
@@ -115,15 +128,16 @@ CALLEES (3):
115
128
 
116
129
  ACCOUNT: "expandGlob" occurs on 14 lines in 6 files: 7 confirmed, 0 unverified,
117
130
  7 non-call (4 import, 1 definition, 2 reference, 0 other-text), 0 other-target, 0 unaccounted
131
+ CONTRACT: literal-name text partition complete; semantic completeness is not claimed
118
132
 
119
133
  TESTS: 5 matches in 1 file(s)
120
134
  ```
121
135
 
122
- Callers split into **CONFIRMED** (binding/receiver/import evidence prod first, then tests) and **UNVERIFIED** (found but unproven, each with a reason). The `ACCOUNT:` line reconciles every occurrence; `0 unaccounted` means nothing was hidden. Tune with `--min-confidence` / `--hide-confidence` / `--git`; walk callers *upward* with `ucn reverse-trace fn`.
136
+ Callers split into **CONFIRMED** (binding/receiver/import evidence, with production before tests) and **UNVERIFIED** (found but unproven, each with a reason). `ACCOUNT:` reconciles the literal-name text set; `CONTRACT:` states its scope and completeness. Neither claims that aliases, generated code, reflection, runtime registration, or external consumers do not exist. Tune the evidence display with `--min-confidence` / `--hide-confidence` / `--git`; walk callers *upward* with `ucn reverse-trace fn`.
123
137
 
124
138
  ## Answers you can trust
125
139
 
126
- UCN doesn't just find a name it tells you how sure it is. Every answer from `about`, `context`, and `impact` partitions *every* place the name appears into buckets you can act on:
140
+ UCN doesn't just find a name. It shows the identity evidence it has and the uncertainty it retains. Every answer from `about`, `context`, and `impact` partitions *every observed literal-name occurrence* into auditable buckets:
127
141
 
128
142
  ```
129
143
  $ ucn impact saveCache
@@ -134,7 +148,7 @@ test/regression-go.test.js (2 calls)
134
148
  :2007
135
149
  saveCache(index, cachePath);
136
150
 
137
- UNVERIFIED CALL SITES (11) call syntax, no binding/receiver evidence:
151
+ UNVERIFIED CALL SITES (11): call syntax, no binding/receiver evidence
138
152
  core/project.js:2126: saveCache(cachePath) { ... } (call-not-resolved)
139
153
  mcp/server.js:810: try { index.saveCache(); } catch (_) ... (method-ambiguous)
140
154
  test/cache.test.js:1804: index.saveCache(); (method-ambiguous)
@@ -142,33 +156,38 @@ UNVERIFIED CALL SITES (11) — call syntax, no binding/receiver evidence:
142
156
 
143
157
  ACCOUNT: "saveCache" occurs on 55 lines in 8 files: 2 confirmed, 11 unverified,
144
158
  11 non-call (2 import, 1 definition, 1 reference, 7 other-text), 31 other-target, 0 unaccounted
159
+ CONTRACT: literal-name text partition complete; semantic completeness is not claimed
145
160
  ```
146
161
 
147
162
  UCN sorts every one of the 55 places the name appears:
148
163
 
149
- - **2 confirmed** call sites it can prove resolve to *this* `saveCache`.
150
- - **11 unverified** real call sites it found but won't claim. `index.saveCache()` has an untyped receiver, so UCN can't prove which `saveCache` runs; it shows the site and the reason (`method-ambiguous`) instead of guessing.
151
- - **31 other-target** occurrences that belong to a *different* `saveCache`, kept separate so they never pollute the answer.
152
- - **11 non-call** imports, the definition, plain text.
153
- - **`0 unaccounted`** the partition is complete. Nothing was dropped on the floor.
164
+ - **2 confirmed**: call sites it can prove resolve to *this* `saveCache`.
165
+ - **11 unverified**: real call sites it found but won't claim. `index.saveCache()` has an untyped receiver, so UCN can't prove which `saveCache` runs; it shows the site and the reason (`method-ambiguous`) instead of guessing.
166
+ - **31 other-target**: occurrences that belong to a *different* `saveCache`, kept separate so they never pollute the answer.
167
+ - **11 non-call**: imports, the definition, plain text.
168
+ - **`0 unaccounted`**: every line in the observed literal-name set was assigned to a bucket.
154
169
 
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.
170
+ The payoff is an answer an agent can audit instead of a single opaque match count. A confirmed edge is evidence for the pinned target; an unverified edge requires review. A clean account with no callers is an **observed-text zero**, not semantic-zero or safe-delete proof. Before deleting anything, run `ucn usages`, inspect entry points and public API exposure, check `ucn doctor --deep` deletion readiness, and corroborate with the compiler/type checker and tests.
156
171
 
157
172
  ### Measured against ground truth
158
173
 
159
- This isn't a promise it's a gate. CI re-derives UCN's caller answers from real compilers and language servers and fails the build if a single true call edge is neither shown nor accounted for:
174
+ This is a release gate, not a promise of universal program understanding. CI re-derives UCN answers from real compilers and language servers. The pinned release board must pass at least 98% confirmed precision, zero semantically missing in-scope edges, a conserved caller account, command-surface checks, dead-code checks, and performance budgets.
175
+
176
+ Current pinned release-board results:
160
177
 
161
- | Language | Oracle | Confirmed-tier precision |
162
- |---|---|---|
163
- | TypeScript / JavaScript | ts-morph | 99.5–100% |
164
- | Python | pyright (LSP) | 97.7–99.9% |
165
- | Go | gopls | 98.8–100% |
166
- | Rust | rust-analyzer | 99.3–100%* |
167
- | Java | jdtls | 96.6–100% |
178
+ | Repository | Language oracle | Confirmed caller precision | Caller recall | Callee precision/recall | Command checks |
179
+ |---|---|---:|---:|---:|---:|
180
+ | preact-signals | ts-morph | 100% | 100% | 100% / 100% | 100% |
181
+ | httpx | pyright | 100% | 100% | 100% / 100% | 100% |
182
+ | cobra | gopls | 100% | 100% | 100% / 100% | 100% |
183
+ | clap | rust-analyzer | 100% | 100% | 100% / 100% | 100% |
184
+ | javapoet | jdtls | 100% | 100% | 100% / 100% | 100% |
168
185
 
169
- \* clap reads 92.4% its callers live behind cargo feature gates the oracle compiles out; UCN sees the text, rust-analyzer doesn't.
186
+ The command checks cover exact definition lookup, `find`, `fn`/`class`, `brief`, `typedef`, `usages`, `tests`, and `example` against the same external oracle population. The dead-code arm currently has zero false-dead claims on the release board. The semantic gate also caps configuration-unscored caller and callee evidence at 10%, so platform filtering cannot silently make a small scored subset look representative. The performance arm runs every repository in an isolated process, takes three fresh-cache startup samples, reports median and maximum first-query latency, and independently gates steady-state p50/p95 and peak memory.
170
187
 
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.
188
+ Unverified precision is reported separately and is intentionally much lower on dispatch-heavy code. Unverified entries are review candidates, not confirmed claims. Rust feature-gated sites that one compiler configuration cannot load are reported as unscored rather than counted as passes.
189
+
190
+ The scheduled board covers nineteen pinned repositories, multiple sampling seeds, and a rotating fresh-repository arm. It is broader than the five-repository publish gate and is used to expose regressions and overfitting. The tree commands `trace`, `blast`, `reverse-trace`, and `affected-tests` follow the same evidence discipline. Run `ucn doctor --deep` for task-specific readiness on your repository.
172
191
 
173
192
  ## Change code without breaking things
174
193
 
@@ -192,7 +211,7 @@ STATUS: ✓ All calls valid
192
211
  Patterns: 4 in try, 4 in callback
193
212
  ```
194
213
 
195
- The `Patterns:` line surfaces structural classification of each call site `inLoop`, `inTry`, `inCallback`, `inTestCase`, `awaited` so you can spot risky call sites (e.g., calls inside loops, missing `await`) at a glance. Same line appears on `impact` and inside `about`.
214
+ The `Patterns:` line surfaces structural classification of each call site (`inLoop`, `inTry`, `inCallback`, `inTestCase`, `awaited`) so you can spot risky call sites such as calls inside loops or missing `await`. The same line appears on `impact` and inside `about`.
196
215
 
197
216
  Then preview the refactoring. UCN shows exactly what needs to change and where:
198
217
 
@@ -237,16 +256,16 @@ Changed: 3 functions
237
256
  ...
238
257
  ```
239
258
 
240
- `ucn check` composes `diff-impact` + `verify` + `affected-tests` in one shot flags ADDED functions with no callers, signature drift across call sites, and recommends which tests to run.
259
+ `ucn check` composes `diff-impact` + `verify` + `affected-tests` in one shot. It flags added functions with no callers, signature drift across call sites, and recommends which tests to run.
241
260
 
242
261
  ## Get the lay of the land in a new repo
243
262
 
244
- One command answers "what is this codebase?" size and language mix, where the code lives, the most-called production functions, entry points, and how far to trust the index:
263
+ One command answers "what is this codebase?": size and language mix, where the code lives, the most-called production functions, entry points, and how far to trust the index.
245
264
 
246
265
  ```
247
266
  $ ucn orient
248
267
 
249
- PROJECT ORIENTATION /path/to/project
268
+ PROJECT ORIENTATION: /path/to/project
250
269
  ════════════════════════════════════════════════════════════
251
270
  169 files · 2111 symbols · javascript 67%, rust 8%, typescript 8%, java 7%, go 5%, python 5%
252
271
 
@@ -258,13 +277,13 @@ TOP DIRS (by symbols):
258
277
  ...
259
278
 
260
279
  HOT (most-called production functions, top 8 of 1028):
261
- execute 1124 call(s) · core/execute.js:1608
262
- ProjectIndex.build 340 call(s) · core/project.js:221
263
- getParser 150 call(s) · languages/index.js:312
280
+ execute: 1124 call(s) · core/execute.js:1608
281
+ ProjectIndex.build: 340 call(s) · core/project.js:221
282
+ getParser: 150 call(s) · languages/index.js:312
264
283
  ...
265
284
 
266
- ENTRY POINTS: 389 test 284, runtime 72, http 32, di 1
267
- TRUST: MEDIUM 41 dynamic import(s), 13 eval, 6 reflection (ucn doctor for detail)
285
+ ENTRY POINTS: 389; test 284, runtime 72, http 32, di 1
286
+ TRUST: MEDIUM; 41 dynamic import(s), 13 eval, 6 reflection (ucn doctor for detail)
268
287
 
269
288
  Next: ucn about execute · ucn toc --detailed · ucn stats --hot --top=20 · ucn doctor --deep
270
289
  ```
@@ -279,20 +298,24 @@ fetch_user(user_id: int): dict
279
298
  async: no | side_effects: [fs, network, process] | complexity: branches=2, depth=2
280
299
  ```
281
300
 
282
- `brief` is the lighter alternative to `about` typed signature, first sentence of the docstring, side-effect classification, and complexity, all in one screen. Pair with `--git` to see who last touched it and how often.
301
+ `brief` is the lighter alternative to `about`: typed signature, first sentence of the docstring, side-effect classification, and complexity, all in one screen. Pair with `--git` to see who last touched it and how often.
283
302
 
284
303
  ```
285
304
  $ ucn doctor
286
305
 
287
- UCN Trust Report /path/to/project
306
+ UCN Trust Report: /path/to/project
288
307
  Index: 169 files, 2104 symbols
289
308
  Languages: javascript (72%), typescript (14%), java (4%), python (4%), rust (4%), go (3%)
290
309
  Cache: fresh, 344ms build
291
- ...
292
- Trust level: HIGH
310
+ Command proofs: 39/39 classified, 22 external-oracle-backed, 0 unclassified
311
+
312
+ Readiness:
313
+ navigation: HIGH: fresh index; no parse failures
314
+ refactor: UNKNOWN: run --deep; review unverified and non-call occurrences
315
+ deletion: REVIEW: usages, public API, compiler, and tests are still required
293
316
  ```
294
317
 
295
- `doctor` reports how much UCN trusts the index file/symbol counts, blind spots (dynamic imports, eval, reflection), parse failures, and a verdict. Use `--deep` to also sample resolution coverage.
318
+ `doctor` reports task-specific readiness for the index: file/symbol counts, blind spots (dynamic imports, eval, reflection), parse failures, command-proof classification, and separate navigation/refactor/deletion levels. Use `--deep` to sample the resolution evidence profile. This profile is not measured accuracy; use the oracle reports for accuracy.
296
319
 
297
320
  `entrypoints` lists detected framework handlers (HTTP routes, DI beans, jobs, tests):
298
321
 
@@ -320,7 +343,7 @@ Test files to run (20):
320
343
  L167: const files = expandGlob('**/*.go', { root: tmpDir }); [call]
321
344
  ...
322
345
 
323
- POSSIBLY AFFECTED (1) reachable only through unverified call edges:
346
+ POSSIBLY AFFECTED (1): reachable only through unverified call edges
324
347
  doctor
325
348
 
326
349
  Uncovered (12): runGlobCommand, main, isCacheStale, runProjectCommand, runFileCommand, ...
@@ -329,7 +352,7 @@ Uncovered (12): runGlobCommand, main, isCacheStale, runProjectCommand, runFileCo
329
352
  Summary: 15 affected → 20 test files, 3/15 functions covered (20%) · 1 possibly affected (unverified chains)
330
353
  ```
331
354
 
332
- The confirmed closure is what you run; `POSSIBLY AFFECTED` lists functions reached only through unverified edges extra tests worth a look, kept separate.
355
+ The confirmed closure is what you run; `POSSIBLY AFFECTED` lists functions reached only through unverified edges. These extra tests are worth reviewing and remain separate.
333
356
 
334
357
  ## Find unused code
335
358
 
@@ -343,14 +366,14 @@ crates/globset/src/serde_impl.rs
343
366
  [ 70- 74] GlobSet.deserialize (method)
344
367
  crates/matcher/src/lib.rs
345
368
  [ 397- 399] Captures.as_match (method)
346
- [ 669- 678] Matcher.try_find_iter (method) [only self-references recursive]
347
- [ 796- 806] Matcher.try_captures_iter (method) [only self-references recursive]
369
+ [ 669- 678] Matcher.try_find_iter (method) [only self-references, recursive]
370
+ [ 796- 806] Matcher.try_captures_iter (method) [only self-references, recursive]
348
371
  ...
349
372
 
350
373
  921 exported symbol(s) excluded from the audit (public API may have external callers). Use --include-exported to audit them.
351
374
  ```
352
375
 
353
- Classes, structs, traits, and enums are audited alongside functions. Symbols whose only call sites live inside their own definitions are claimed too, marked `[only self-references recursive]`. Deadcode claims are re-derived against compiler/LSP ground truth in CI a default-audit claim with an oracle-visible reference fails the build.
376
+ Classes, structs, traits, and enums are audited alongside functions. Symbols whose only call sites live inside their own definitions are claimed too, marked `[only self-references, recursive]`. Deadcode claims are re-derived against compiler/LSP ground truth in CI. A default-audit claim with an oracle-visible reference fails the build.
354
377
 
355
378
  Find missing-await bugs:
356
379
 
@@ -362,7 +385,7 @@ Lists async calls inside async functions that lack `await` (JS/TS/Python).
362
385
 
363
386
  ## Map your API surface across languages
364
387
 
365
- UCN can match server routes to client requests across the supported languages Express/Fastify/Koa/NestJS/Next.js, Flask/FastAPI, Spring/JAX-RS, Go net/http (Gin/Echo/Chi/Fiber), axum/actix-web on the server side; fetch/axios, requests/httpx, RestTemplate/WebClient, reqwest on the client side.
388
+ UCN can match server routes to client requests across the supported languages: Express/Fastify/Koa/NestJS/Next.js, Flask/FastAPI, Spring/JAX-RS, Go net/http (Gin/Echo/Chi/Fiber), and axum/actix-web on the server side; fetch/axios, requests/httpx, RestTemplate/WebClient, and reqwest on the client side.
366
389
 
367
390
  ```bash
368
391
  ucn endpoints --bridge
@@ -403,7 +426,7 @@ function compareNames(a, b) {
403
426
  - **Coverage** - every command, every supported language, every surface (CLI, MCP, interactive)
404
427
  - **Systematic** - a harness exercises all command and flag combinations against real multi-language fixtures
405
428
  - **Test types** - unit, integration, per-language regression, formatter, cache, MCP edge cases, architecture parity guards
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))
429
+ - **Ground truth** - caller, callee, and oracle-judgable command behavior is measured against ts-morph, pyright, gopls, rust-analyzer, and jdtls. The publish gate uses five representative repositories; the scheduled board uses nineteen plus a rotating fresh-repository arm. Gates track confirmed precision, semantic recall, conservation, observed-zero agreement, oracle configuration coverage, dead-code false positives, isolated startup latency, steady-state latency, and peak memory (see [Answers you can trust](#answers-you-can-trust))
407
430
 
408
431
  ---
409
432
 
package/cli/index.js CHANGED
@@ -126,17 +126,12 @@ function validateNumericFlags(flags) {
126
126
  * and write the same plain message to stderr (for humans piping to a TTY).
127
127
  */
128
128
  function fail(msg) {
129
- // Honor --json by writing a structured envelope to stdout for pipelines.
130
- // We use try/catch around symbol lookups because `flags` may not be initialized
131
- // yet when fail() is called from the early arg-parsing path (TDZ).
132
- let wantsJson = false;
133
- try { if (typeof flags !== 'undefined' && flags && flags.json) wantsJson = true; } catch (_) {}
134
- if (!wantsJson) {
135
- try { if (Array.isArray(process.argv) && process.argv.includes('--json')) wantsJson = true; } catch (_) {}
136
- }
129
+ // This helper can run before parsed flags exist, so raw argv is the single
130
+ // reliable source for the output mode.
131
+ const wantsJson = process.argv.includes('--json');
137
132
  if (wantsJson) {
138
133
  const env = { meta: { ok: false }, error: typeof msg === 'string' ? msg : String(msg) };
139
- try { process.stdout.write(JSON.stringify(env) + '\n'); } catch (_) {}
134
+ try { process.stdout.write(JSON.stringify(env) + '\n'); } catch (_) { /* stdout may be closed */ }
140
135
  }
141
136
  console.error(msg);
142
137
  throw new CommandError();
@@ -336,7 +331,7 @@ try {
336
331
  if (e instanceof FlagValidationError) {
337
332
  if (flags.json) {
338
333
  const env = { meta: { ok: false }, error: e.message };
339
- try { process.stdout.write(JSON.stringify(env) + '\n'); } catch (_) {}
334
+ try { process.stdout.write(JSON.stringify(env) + '\n'); } catch (_) { /* stdout may be closed */ }
340
335
  }
341
336
  console.error(e.message);
342
337
  process.exit(1);
@@ -1575,7 +1570,7 @@ REFACTORING HELPERS
1575
1570
  verify <name> Check all call sites match signature
1576
1571
  diff-impact What changed in git diff and who calls it (--base, --staged)
1577
1572
  check Pre-commit summary: diff-impact + verify + affected-tests in one shot
1578
- deadcode Find unused functions/classes
1573
+ deadcode Unreferenced-symbol candidates (review before deletion)
1579
1574
  entrypoints Detect framework entry points (routes, DI, tasks)
1580
1575
  endpoints HTTP API: list server routes + client requests; --bridge to match
1581
1576
  --bridge --server-only --client-only --unmatched
@@ -1587,8 +1582,8 @@ OTHER
1587
1582
  api Show exported/public symbols
1588
1583
  typedef <name> Find type definitions
1589
1584
  stats Project statistics (--functions for per-function line counts, --hot for top callers)
1590
- doctor Project trust report (counts, blind spots, parse failures, verdict; --deep for resolution coverage)
1591
- orient One-screen repo orientation: size, top dirs, hot functions, entry points, trust verdict (--top=N)
1585
+ doctor Parse health, blind spots, command proofs, and task readiness (--deep adds evidence profile)
1586
+ orient One-screen repo map: size, top dirs, hot functions, entry points, readiness (--top=N)
1592
1587
  stacktrace <text> Parse stack trace, show code at each frame (alias: stack)
1593
1588
  audit-async Find calls in async functions that are likely missing await (JS/TS/Python)
1594
1589
 
@@ -1605,19 +1600,20 @@ Common Flags:
1605
1600
  --max-files=N Max files to index (large projects)
1606
1601
  --context=N Lines of context around matches (search, usages)
1607
1602
  --json Machine-readable output
1603
+ --compact Token-efficient about/context/impact output
1608
1604
  --code-only Filter out comments/strings (search, usages)
1609
1605
  --with-types Include type definitions (about, smart)
1610
1606
  --detailed Show all symbols in toc (not just counts)
1611
1607
  --include-tests Include test files in usage counts (about) and results (find, usages, deadcode)
1612
- --exclude-tests Exclude test files (entrypoints tests are included by default)
1608
+ --exclude-tests Exclude test files (entrypoints includes tests by default)
1613
1609
  --class-name=X Scope to specific class (e.g., --class-name=Repository)
1614
1610
  --include-methods Include method-call (obj.fn) callee expansion in trace/smart
1615
- (no effect on caller-direction commands about/context/impact/verify/
1611
+ (no effect on caller-direction commands: about/context/impact/verify/
1616
1612
  blast/reverse-trace/affected-tests always tier method calls by evidence)
1617
- --include-uncertain No effect on tiered commands unverified candidates are always
1613
+ --include-uncertain No effect on tiered commands; unverified candidates are always
1618
1614
  shown in their own section with reasons
1619
1615
  --expand-unverified Follow unverified caller edges in blast/reverse-trace trees
1620
- (downstream nodes marked as unverified chains — possible, not confirmed, impact)
1616
+ (downstream nodes marked as possible, not confirmed, impact chains)
1621
1617
  --hide-confidence Hide confidence scores (shown by default in about, context)
1622
1618
  --min-confidence=N Filter low-confidence edges (about, context, blast, trace,
1623
1619
  reverse-trace, smart, affected-tests)
@@ -1629,6 +1625,7 @@ Common Flags:
1629
1625
  --diverse Cluster call sites by argument shape (example command, pair with --top=N)
1630
1626
  --git Attach git enrichment (last modified, author, recent commits) to about/brief
1631
1627
  --include-decorated Include decorated/annotated symbols in deadcode
1628
+ --deep Add a stratified evidence profile to doctor (not measured accuracy)
1632
1629
  --framework=X Filter entrypoints by framework (e.g., --framework=express,spring)
1633
1630
  --bridge Match server routes to client requests (endpoints command).
1634
1631
  Confidence tiers: EXACT, PARTIAL, UNCERTAIN
@@ -1649,6 +1646,7 @@ Common Flags:
1649
1646
  --base=<ref> Git ref for diff-impact (default: HEAD)
1650
1647
  --staged Analyze staged changes (diff-impact)
1651
1648
  --no-follow-symlinks Don't follow symbolic links
1649
+ --mcp Start the MCP stdio server
1652
1650
  -i, --interactive Keep index in memory for multiple queries
1653
1651
  -v, --version Print the UCN version and exit
1654
1652
 
@@ -1674,7 +1672,7 @@ function runInteractive(rootDir) {
1674
1672
  // Same cache discipline as one-shot mode (fix #250: the REPL fully
1675
1673
  // re-parsed every session and never consumed cache-persisted state —
1676
1674
  // the divergence mechanism behind the relocation P1).
1677
- let iCacheFresh = false;
1675
+ let iCacheFresh;
1678
1676
  if (flags.cache && !flags.clearCache) {
1679
1677
  const loaded = index.loadCache();
1680
1678
  iCacheFresh = loaded && !index.isCacheStale();
@@ -1720,6 +1718,7 @@ function runInteractive(rootDir) {
1720
1718
  Commands:
1721
1719
  toc Project overview (--detailed)
1722
1720
  find <name> Find symbol (--exact, glob: "handle*")
1721
+ brief <name> Signature, docs, side effects, and complexity
1723
1722
  about <name> Everything about a symbol
1724
1723
  usages <name> All usages grouped by type
1725
1724
  context <name> Callers + callees
@@ -1744,13 +1743,19 @@ Commands:
1744
1743
  search <term> Text search (--context=N, --exclude=, --in=)
1745
1744
  Structural: --type= --param= --returns= --decorator= --exported --unused
1746
1745
  typedef <name> Find type definitions
1747
- deadcode Find unused functions/classes
1746
+ deadcode Unreferenced-symbol candidates (review before deletion)
1747
+ entrypoints Detect runtime, framework, task, and test entry points
1748
+ endpoints List server/client HTTP endpoints (--bridge to match)
1748
1749
  verify <name> Check call sites match signature
1749
1750
  plan <name> Preview refactoring (--add-param=, --remove-param=, --rename-to=, --default-value=)
1751
+ check Pre-commit diff, signature, and affected-test checks
1750
1752
  stacktrace <text> Parse a stack trace
1751
1753
  api Show public symbols
1752
1754
  diff-impact What changed and who's affected
1753
1755
  stats Index statistics
1756
+ doctor Parse health, blind spots, command proofs, and task readiness
1757
+ orient Repository map and readiness summary
1758
+ audit-async Find likely missing-await calls (JS/TS/Python)
1754
1759
  rebuild Rebuild index
1755
1760
  quit Exit
1756
1761
 
package/core/account.js CHANGED
@@ -316,6 +316,12 @@ function buildAccount(index, name, parts) {
316
316
  const accountedTotal = confirmed + unverified + nonCall.total + excludedTotal + groundSet.unparsed.lines;
317
317
  const unaccounted = groundSet.total - accountedTotal;
318
318
 
319
+ const textComplete = unaccounted === 0 &&
320
+ groundSet.unparsed.fileCount === 0 &&
321
+ groundSet.unreadableFiles.length === 0;
322
+ const observedTextZero = textComplete && confirmed === 0 && unverified === 0 &&
323
+ beyondText.count === 0;
324
+
319
325
  const account = {
320
326
  symbol: name,
321
327
  groundTotal: groundSet.total,
@@ -329,6 +335,18 @@ function buildAccount(index, name, parts) {
329
335
  beyondText,
330
336
  unaccounted,
331
337
  conserved: unaccounted === 0,
338
+ // This deliberately describes a narrower guarantee than "all callers".
339
+ // The conservation model proves that every literal-name text line was
340
+ // classified. It cannot prove that aliases, indirect calls, generated
341
+ // code, or runtime dispatch contain no additional semantic references.
342
+ contract: {
343
+ kind: 'literal-name-text-partition',
344
+ textComplete,
345
+ observedTextZero,
346
+ semanticComplete: false,
347
+ safeToDelete: false,
348
+ requiresUsageReview: nonCall.references > 0 || nonCall.unclassifiedText > 0,
349
+ },
332
350
  };
333
351
  if (parts.filtered && parts.filtered.total > 0) {
334
352
  account.filtered = parts.filtered;
package/core/analysis.js CHANGED
@@ -108,7 +108,7 @@ function tagInTestCase(index, sites) {
108
108
  // ============================================================================
109
109
 
110
110
  /**
111
- * Bucket a confidence score into 'high' / 'medium' / 'low'.
111
+ * Bucket an ordinal evidence score into 'high' / 'medium' / 'low'.
112
112
  * Boundaries are inclusive at the lower end:
113
113
  * confidence > 0.8 → high
114
114
  * 0.5 <= c <= 0.8 → medium
@@ -125,7 +125,7 @@ function bucketConfidence(c) {
125
125
  }
126
126
 
127
127
  /**
128
- * Build a confidence histogram from an array of edges (callers/callees).
128
+ * Build an ordinal evidence histogram from caller/callee edges.
129
129
  * Returns null when there are no edges (caller drops the section entirely).
130
130
  *
131
131
  * @param {Array} edges - Array of objects with `confidence` field
@@ -434,7 +434,7 @@ function context(index, name, options = {}) {
434
434
  return (a.startLine || 0) - (b.startLine || 0);
435
435
  });
436
436
 
437
- // Trust signals: tag each caller/callee with reachability and build confidence histograms.
437
+ // Trust signals: tag each caller/callee with reachability and build ordinal evidence histograms.
438
438
  // Reachability is computed once per index and cached (see entrypoints.computeReachability).
439
439
  const reachableSet = computeReachability(index);
440
440
  tagCallersReachable(callers, reachableSet);
@@ -922,7 +922,7 @@ function impact(index, name, options = {}) {
922
922
  // about kept — impact answered differently from its siblings on the
923
923
  // same pin.
924
924
 
925
- callSites = []; callSites = [];
925
+ callSites = [];
926
926
  for (const c of callerResults) {
927
927
  const analysis = index.analyzeCallSite(
928
928
  { file: c.file, relativePath: c.relativePath, line: c.line, content: c.content },
@@ -937,6 +937,8 @@ function impact(index, name, options = {}) {
937
937
  callerFile: c.callerFile,
938
938
  callerStartLine: c.callerStartLine,
939
939
  confidence: c.confidence,
940
+ evidenceScore: c.evidenceScore,
941
+ scoreKind: c.scoreKind,
940
942
  resolution: c.resolution,
941
943
  ...(c.tier && { tier: c.tier }),
942
944
  ...analysis
@@ -968,6 +970,8 @@ function impact(index, name, options = {}) {
968
970
  callerFile: c.callerFile,
969
971
  callerStartLine: c.callerStartLine,
970
972
  confidence: c.confidence,
973
+ evidenceScore: c.evidenceScore,
974
+ scoreKind: c.scoreKind,
971
975
  resolution: c.resolution,
972
976
  tier: c.tier,
973
977
  }));
@@ -1033,6 +1037,8 @@ function impact(index, name, options = {}) {
1033
1037
  callerFile: call.callerFile,
1034
1038
  callerStartLine: call.callerStartLine,
1035
1039
  confidence: call.confidence,
1040
+ evidenceScore: call.evidenceScore,
1041
+ scoreKind: call.scoreKind,
1036
1042
  resolution: call.resolution,
1037
1043
  ...(call.tier && { tier: call.tier }),
1038
1044
  ...analysis
@@ -1052,6 +1058,8 @@ function impact(index, name, options = {}) {
1052
1058
  expression: (u.content || '').trim(),
1053
1059
  callerName: u.callerName ?? null,
1054
1060
  confidence: u.confidence,
1061
+ evidenceScore: u.evidenceScore,
1062
+ scoreKind: u.scoreKind,
1055
1063
  resolution: u.resolution,
1056
1064
  tier: 'unverified',
1057
1065
  ...(u.reason && { reason: u.reason }),
@@ -1075,8 +1083,8 @@ function impact(index, name, options = {}) {
1075
1083
  impactFilteredByFlag.exclude += beforeUnverified - unverifiedSites.length;
1076
1084
  }
1077
1085
 
1078
- // Trust signals: tag each call site with reachability, build a confidence histogram.
1079
- // Histogram is computed BEFORE top-N truncation so the trust signal reflects the full scope.
1086
+ // Trust signals: tag each call site with reachability and build an ordinal evidence histogram.
1087
+ // It is computed BEFORE top-N truncation so the evidence profile reflects the full scope.
1080
1088
  const impactReachable = computeReachability(index);
1081
1089
  for (const site of filteredSites) {
1082
1090
  if (site.callerFile && site.callerStartLine != null) {
@@ -1426,6 +1434,8 @@ function about(index, name, options = {}) {
1426
1434
  expression: c.content.trim(),
1427
1435
  callerName: c.callerName,
1428
1436
  confidence: c.confidence,
1437
+ evidenceScore: c.evidenceScore,
1438
+ scoreKind: c.scoreKind,
1429
1439
  resolution: c.resolution,
1430
1440
  reachable: c.reachable,
1431
1441
  }));
@@ -1448,6 +1458,8 @@ function about(index, name, options = {}) {
1448
1458
  expression: (c.content || '').trim(),
1449
1459
  callerName: c.callerName ?? null,
1450
1460
  confidence: c.confidence,
1461
+ evidenceScore: c.evidenceScore,
1462
+ scoreKind: c.scoreKind,
1451
1463
  resolution: c.resolution,
1452
1464
  ...(c.reason && { reason: c.reason }),
1453
1465
  ...(c.dispatchVia && { dispatchVia: c.dispatchVia }),
@@ -1495,6 +1507,8 @@ function about(index, name, options = {}) {
1495
1507
  weight: c.weight,
1496
1508
  callCount: c.callCount,
1497
1509
  confidence: c.confidence,
1510
+ evidenceScore: c.evidenceScore,
1511
+ scoreKind: c.scoreKind,
1498
1512
  resolution: c.resolution,
1499
1513
  reachable: c.reachable,
1500
1514
  ...(c.returnType && { returnType: c.returnType }),
@@ -2049,6 +2063,8 @@ function diffImpact(index, options = {}) {
2049
2063
  callerName: c.callerName,
2050
2064
  content: (c.content || '').trim(),
2051
2065
  confidence: c.confidence,
2066
+ evidenceScore: c.evidenceScore,
2067
+ scoreKind: c.scoreKind,
2052
2068
  resolution: c.resolution,
2053
2069
  ...(c.tier && { tier: c.tier }),
2054
2070
  })),
@@ -2058,6 +2074,10 @@ function diffImpact(index, options = {}) {
2058
2074
  line: u.line,
2059
2075
  callerName: u.callerName ?? null,
2060
2076
  content: (u.content || '').trim(),
2077
+ confidence: u.confidence,
2078
+ evidenceScore: u.evidenceScore,
2079
+ scoreKind: u.scoreKind,
2080
+ resolution: u.resolution,
2061
2081
  tier: 'unverified',
2062
2082
  ...(u.reason && { reason: u.reason }),
2063
2083
  ...(u.dispatchVia && { dispatchVia: u.dispatchVia }),
@@ -2298,7 +2318,7 @@ const _FIRE_AND_FORGET_PROMISE_FNS = new Set(['all', 'allSettled', 'race', 'any'
2298
2318
  function auditAsync(index, options = {}) {
2299
2319
  index._beginOp();
2300
2320
  try {
2301
- const { detectLanguage, getParser, getLanguageModule, safeParse } = require('../languages');
2321
+ const { getParser, getLanguageModule, safeParse } = require('../languages');
2302
2322
  const issues = [];
2303
2323
 
2304
2324
  // Build a "is this name provably async" lookup from the symbol table.