ucn 4.1.3 → 4.2.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/.claude/skills/ucn/SKILL.md +80 -353
- package/.claude/skills/ucn/references/commands.md +93 -0
- package/.claude/skills/ucn/references/trust-contract.md +63 -0
- package/README.md +67 -44
- package/cli/index.js +24 -19
- package/core/account.js +18 -0
- package/core/analysis.js +27 -7
- package/core/bridge.js +0 -1
- package/core/brief.js +2 -3
- package/core/build-worker.js +5 -0
- package/core/cache.js +51 -6
- package/core/callers.js +1709 -162
- package/core/check.js +107 -19
- package/core/confidence.js +29 -16
- package/core/deadcode.js +1 -2
- package/core/entrypoints.js +32 -27
- package/core/execute.js +19 -3
- package/core/graph-build.js +33 -0
- package/core/output/analysis.js +53 -14
- package/core/output/check.js +11 -0
- package/core/output/doctor.js +40 -19
- package/core/output/endpoints.js +0 -2
- package/core/output/search.js +26 -1
- package/core/parser.js +1 -1
- package/core/project.js +25 -4
- package/core/registry.js +2 -2
- package/core/reporting.js +166 -108
- package/core/search.js +271 -49
- package/core/tracing.js +2 -2
- package/core/trust-matrix.js +158 -0
- package/core/verify.js +0 -1
- package/eslint.config.js +2 -2
- package/languages/go.js +143 -13
- package/languages/html.js +5 -0
- package/languages/index.js +5 -0
- package/languages/java.js +61 -4
- package/languages/javascript.js +233 -42
- package/languages/python.js +54 -15
- package/languages/rust.js +180 -22
- package/languages/utils.js +0 -1
- package/mcp/server.js +118 -38
- package/package.json +11 -4
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
150
|
-
- **11 unverified
|
|
151
|
-
- **31 other-target
|
|
152
|
-
- **11 non-call
|
|
153
|
-
- **`0 unaccounted
|
|
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
|
|
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
|
|
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
|
-
|
|
|
162
|
-
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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?"
|
|
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
|
|
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
|
|
262
|
-
ProjectIndex.build
|
|
263
|
-
getParser
|
|
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
|
|
267
|
-
TRUST: MEDIUM
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
347
|
-
[ 796- 806] Matcher.try_captures_iter (method) [only self-references
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
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
|
|
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
|
|
1591
|
-
orient One-screen repo
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = [];
|
|
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
|
|
1079
|
-
//
|
|
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 {
|
|
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.
|