ucn 5.5.3 → 5.6.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.
- package/.claude/skills/ucn/SKILL.md +37 -12
- package/.claude/skills/ucn/references/commands.md +18 -14
- package/.claude/skills/ucn/references/resolution-rules.md +4 -0
- package/.claude/skills/ucn/references/trust-contract.md +4 -4
- package/CHANGELOG.md +485 -0
- package/README.md +66 -29
- package/cli/flags.js +184 -0
- package/cli/index.js +137 -324
- package/core/account.js +44 -87
- package/core/analysis.js +274 -108
- package/core/ast-analysis.js +2 -0
- package/core/bridge.js +14 -0
- package/core/cache.js +103 -16
- package/core/call-fit.js +130 -0
- package/core/callers.js +353 -71
- package/core/capture.js +363 -0
- package/core/change-kinds.js +1107 -0
- package/core/check.js +1150 -139
- package/core/command-contracts.js +8 -7
- package/core/constructed-class.js +1195 -0
- package/core/contract-membership.js +142 -0
- package/core/deadcode.js +455 -106
- package/core/deletion.js +421 -0
- package/core/discovery.js +104 -9
- package/core/entrypoint-toml.js +110 -0
- package/core/entrypoints.js +12 -6
- package/core/evidence-bands.js +68 -0
- package/core/evidence-bands.json +44 -0
- package/core/execute.js +172 -161
- package/core/hot-summary.js +241 -0
- package/core/index-ir.js +5 -3
- package/core/ir.js +6 -2
- package/core/member-signatures.js +789 -0
- package/core/outgoing-check.js +116 -0
- package/core/output/analysis-ext.js +20 -10
- package/core/output/analysis.js +98 -60
- package/core/output/check.js +101 -37
- package/core/output/doctor.js +34 -1
- package/core/output/find.js +2 -1
- package/core/output/lines.js +63 -3
- package/core/output/public.js +29 -11
- package/core/output/refactoring.js +32 -0
- package/core/output/reporting.js +19 -2
- package/core/output/search.js +14 -2
- package/core/output/tracing.js +36 -8
- package/core/output-budget.js +146 -61
- package/core/parallel-build.js +3 -3
- package/core/project.js +106 -23
- package/core/provenance-type.js +12 -5
- package/core/public-command.js +35 -9
- package/core/python-binding-identity.js +707 -0
- package/core/python-modules.js +6 -0
- package/core/reflection.js +248 -31
- package/core/registry.js +623 -72
- package/core/reporting.js +69 -34
- package/core/review-items.js +75 -0
- package/core/runtime-entrypoints.js +159 -0
- package/core/search.js +148 -164
- package/core/shared.js +0 -45
- package/core/snapshot.js +350 -0
- package/core/source-universe.js +707 -0
- package/core/test-units.js +3698 -0
- package/core/tracing.js +270 -658
- package/core/verify.js +63 -22
- package/languages/adapter.js +8 -3
- package/languages/c-family.js +12 -0
- package/languages/csharp.js +634 -0
- package/languages/go.js +120 -0
- package/languages/index.js +466 -4
- package/languages/java.js +712 -0
- package/languages/javascript.js +1498 -6
- package/languages/lexical-scope.js +68 -0
- package/languages/python.js +1708 -3
- package/languages/rust.js +209 -104
- package/languages/static-js.js +88 -0
- package/mcp/server.js +44 -110
- package/package.json +11 -5
package/core/account.js
CHANGED
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
* strings, and scanner-skipped tokens such as JS builtins —
|
|
17
17
|
* deliberately named "unclassified", not "comment")
|
|
18
18
|
* unparsed - line in a file that failed to parse (still readable text)
|
|
19
|
-
* unsupported - line in
|
|
20
|
-
* (
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
19
|
+
* unsupported - line in TEXT-ONLY source: readable source UCN does not
|
|
20
|
+
* analyze (an unsupported language such as .rb in a mixed
|
|
21
|
+
* repo, a bundled/minified file, a file over the size or
|
|
22
|
+
* file limit, a path `.ucn.json` excludes from indexing —
|
|
23
|
+
* core/source-universe.js). Text-scanned only; sites are
|
|
24
|
+
* listed so the answer is a superset of grep, and the
|
|
25
|
+
* CONTRACT degrades for THIS name whenever the bucket is
|
|
26
|
+
* non-zero. Without it a mixed-language repo got "partition
|
|
27
|
+
* complete" over a ground set that silently excluded whole
|
|
28
|
+
* languages; with a repo-wide withdrawal instead, one
|
|
29
|
+
* vendored bundle degraded every answer in the repository.
|
|
26
30
|
* unaccounted - residual; 0 when the arithmetic is conserved
|
|
27
31
|
*
|
|
28
32
|
* Conservation invariant:
|
|
@@ -50,15 +54,10 @@
|
|
|
50
54
|
|
|
51
55
|
'use strict';
|
|
52
56
|
|
|
53
|
-
const fs = require('fs');
|
|
54
57
|
const path = require('path');
|
|
55
58
|
const { codeUnitCompare, literalNameRegex } = require('./shared');
|
|
56
59
|
const { compareNames } = require('./discovery');
|
|
57
|
-
|
|
58
|
-
// Unsupported-language site listings are capped so a Rails-sized repo cannot
|
|
59
|
-
// flood the account object; the counts always cover the full set.
|
|
60
|
-
const UNSUPPORTED_SITE_CAP = 50;
|
|
61
|
-
const UNSUPPORTED_SITE_TEXT_MAX = 160;
|
|
60
|
+
const { scanTextOnly, countNameLines, opaqueSummary } = require('./source-universe');
|
|
62
61
|
|
|
63
62
|
/**
|
|
64
63
|
* Compute the text-occurrence ground set for a symbol name.
|
|
@@ -75,7 +74,9 @@ const UNSUPPORTED_SITE_TEXT_MAX = 160;
|
|
|
75
74
|
* languages: Object<string, number>,
|
|
76
75
|
* sites: Array<{file: string, line: number, text: string}>,
|
|
77
76
|
* sitesTruncated: boolean },
|
|
78
|
-
* unreadableFiles: string[]
|
|
77
|
+
* unreadableFiles: string[], // relative paths; OUTSIDE the arithmetic
|
|
78
|
+
* opaque: { count: number, reasons: Object<string, number>,
|
|
79
|
+
* sample: Array<{path: string, reason: string}> } // paths no name can be checked against
|
|
79
80
|
* }}
|
|
80
81
|
*/
|
|
81
82
|
function computeGroundSet(index, name) {
|
|
@@ -115,9 +116,12 @@ function computeGroundSet(index, name) {
|
|
|
115
116
|
// grep would show. Their matching lines join the ground set as
|
|
116
117
|
// `unsupported`, with sites listed so the caller answer strictly
|
|
117
118
|
// dominates grep instead of silently under-reporting it.
|
|
118
|
-
const unsupported = scanUnsupportedFiles(index, name
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
const unsupported = scanUnsupportedFiles(index, name);
|
|
120
|
+
// Paths whose contents cannot be read name by name (an unreadable
|
|
121
|
+
// directory, a failed git listing, text beyond the scan budget). They
|
|
122
|
+
// say nothing about this name in particular, so they weaken the claim
|
|
123
|
+
// for every name; everything readable was scanned above.
|
|
124
|
+
const opaque = opaqueSummary(index);
|
|
121
125
|
unreadableFiles.sort();
|
|
122
126
|
|
|
123
127
|
// fix #367e: ground lines inside a parser-recovery region of an indexed
|
|
@@ -155,7 +159,7 @@ function computeGroundSet(index, name) {
|
|
|
155
159
|
recovered,
|
|
156
160
|
unsupported,
|
|
157
161
|
unreadableFiles,
|
|
158
|
-
|
|
162
|
+
opaque,
|
|
159
163
|
};
|
|
160
164
|
if (index._groundSetCache) {
|
|
161
165
|
const weight = result.total + result.fileCount;
|
|
@@ -209,21 +213,15 @@ function scanFailedFiles(index, name) {
|
|
|
209
213
|
if (!index.failedFiles || index.failedFiles.size === 0) {
|
|
210
214
|
return { unparsed, unreadableFiles };
|
|
211
215
|
}
|
|
212
|
-
const wordRe = literalNameRegex(name);
|
|
213
216
|
for (const failedPath of index.failedFiles) {
|
|
214
217
|
if (index.files.has(failedPath)) continue;
|
|
215
|
-
let
|
|
218
|
+
let matched;
|
|
216
219
|
try {
|
|
217
|
-
|
|
220
|
+
matched = countNameLines(failedPath, name);
|
|
218
221
|
} catch (_) {
|
|
219
222
|
unreadableFiles.push(path.relative(index.root, failedPath));
|
|
220
223
|
continue;
|
|
221
224
|
}
|
|
222
|
-
if (!content.includes(name)) continue;
|
|
223
|
-
let matched = 0;
|
|
224
|
-
for (const line of content.split('\n')) {
|
|
225
|
-
if (line.includes(name) && wordRe.test(line)) matched++;
|
|
226
|
-
}
|
|
227
225
|
if (matched > 0) {
|
|
228
226
|
unparsed.fileCount++;
|
|
229
227
|
unparsed.lines += matched;
|
|
@@ -236,66 +234,22 @@ function scanFailedFiles(index, name) {
|
|
|
236
234
|
}
|
|
237
235
|
|
|
238
236
|
/**
|
|
239
|
-
* Word-boundary-scan
|
|
240
|
-
* `unsupported` account bucket ({ fileCount, lines, files, languages,
|
|
241
|
-
* sitesTruncated }). Shared by computeGroundSet and by
|
|
242
|
-
* is not account-shaped (tests, usages) but still
|
|
243
|
-
* a
|
|
237
|
+
* Word-boundary-scan the project's text-only source for `name`. Returns the
|
|
238
|
+
* `unsupported` account bucket ({ fileCount, lines, files, languages,
|
|
239
|
+
* reasons, sites, sitesTruncated }). Shared by computeGroundSet and by
|
|
240
|
+
* commands whose result is not account-shaped (tests, usages) but still
|
|
241
|
+
* must not stay silent when a repo has occurrences UCN cannot analyze. The
|
|
242
|
+
* scan itself belongs to core/source-universe.js.
|
|
243
|
+
*
|
|
244
|
+
* A file that cannot be read while it is scanned becomes an unsearchable
|
|
245
|
+
* path of the universe (reported through `opaqueSummary` / `unsearchedNote`),
|
|
246
|
+
* never a silently shorter answer.
|
|
244
247
|
*
|
|
245
248
|
* @param {object} index - ProjectIndex
|
|
246
|
-
* @param {string} name - Symbol name (matched with
|
|
247
|
-
* @param {object} [opts]
|
|
248
|
-
* @param {string[]} [opts.unreadableFiles] - collector for read failures
|
|
249
|
+
* @param {string} name - Symbol name (matched with word boundaries)
|
|
249
250
|
*/
|
|
250
|
-
function scanUnsupportedFiles(index, name
|
|
251
|
-
|
|
252
|
-
fileCount: 0, lines: 0, files: [], languages: {}, sites: [],
|
|
253
|
-
sitesTruncated: false,
|
|
254
|
-
};
|
|
255
|
-
if (!Array.isArray(index.unsupportedFiles) || index.unsupportedFiles.length === 0) {
|
|
256
|
-
return unsupported;
|
|
257
|
-
}
|
|
258
|
-
const wordRe = literalNameRegex(name);
|
|
259
|
-
const languageCounts = new Map();
|
|
260
|
-
for (const skipped of index.unsupportedFiles) {
|
|
261
|
-
const absPath = path.join(index.root, skipped.relativePath);
|
|
262
|
-
let content;
|
|
263
|
-
try {
|
|
264
|
-
content = index._readFile(absPath);
|
|
265
|
-
} catch (e) {
|
|
266
|
-
if (opts.unreadableFiles) opts.unreadableFiles.push(skipped.relativePath);
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (!content.includes(name)) continue;
|
|
270
|
-
const lines = content.split('\n');
|
|
271
|
-
let matched = 0;
|
|
272
|
-
for (let i = 0; i < lines.length; i++) {
|
|
273
|
-
if (!lines[i].includes(name) || !wordRe.test(lines[i])) continue;
|
|
274
|
-
matched++;
|
|
275
|
-
if (unsupported.sites.length < UNSUPPORTED_SITE_CAP) {
|
|
276
|
-
unsupported.sites.push({
|
|
277
|
-
file: skipped.relativePath,
|
|
278
|
-
line: i + 1,
|
|
279
|
-
text: lines[i].trim().slice(0, UNSUPPORTED_SITE_TEXT_MAX),
|
|
280
|
-
});
|
|
281
|
-
} else {
|
|
282
|
-
unsupported.sitesTruncated = true;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (matched > 0) {
|
|
286
|
-
unsupported.fileCount++;
|
|
287
|
-
unsupported.lines += matched;
|
|
288
|
-
unsupported.files.push(skipped.relativePath);
|
|
289
|
-
const lang = skipped.language || skipped.extension || 'unknown';
|
|
290
|
-
languageCounts.set(lang, (languageCounts.get(lang) || 0) + matched);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
unsupported.files.sort(codeUnitCompare);
|
|
294
|
-
unsupported.sites.sort((a, b) => codeUnitCompare(a.file, b.file) || a.line - b.line);
|
|
295
|
-
for (const lang of [...languageCounts.keys()].sort(codeUnitCompare)) {
|
|
296
|
-
unsupported.languages[lang] = languageCounts.get(lang);
|
|
297
|
-
}
|
|
298
|
-
return unsupported;
|
|
251
|
+
function scanUnsupportedFiles(index, name) {
|
|
252
|
+
return scanTextOnly(index, name);
|
|
299
253
|
}
|
|
300
254
|
|
|
301
255
|
/**
|
|
@@ -544,13 +498,16 @@ function buildAccount(index, name, parts) {
|
|
|
544
498
|
groundSet.unparsed.lines + unsupported.lines;
|
|
545
499
|
const unaccounted = groundSet.total - accountedTotal;
|
|
546
500
|
|
|
547
|
-
//
|
|
548
|
-
// UCN cannot adjudicate
|
|
501
|
+
// The claim is about THIS name. An occurrence in text-only source
|
|
502
|
+
// breaks it (UCN cannot adjudicate the line); a path nothing can be
|
|
503
|
+
// read from breaks it for every name; unindexed source the name does
|
|
504
|
+
// not occur in leaves it standing.
|
|
505
|
+
const opaque = groundSet.opaque || { count: 0, reasons: {}, sample: [] };
|
|
549
506
|
const textComplete = unaccounted === 0 &&
|
|
550
507
|
groundSet.unparsed.fileCount === 0 &&
|
|
551
508
|
unsupported.lines === 0 &&
|
|
552
509
|
groundSet.unreadableFiles.length === 0 &&
|
|
553
|
-
|
|
510
|
+
opaque.count === 0;
|
|
554
511
|
const observedTextZero = textComplete && confirmed === 0 && unverified === 0 &&
|
|
555
512
|
beyondText.count === 0;
|
|
556
513
|
|
|
@@ -570,7 +527,7 @@ function buildAccount(index, name, parts) {
|
|
|
570
527
|
...(groundSet.recovered?.lines > 0 && { recovered: groundSet.recovered }),
|
|
571
528
|
unsupported,
|
|
572
529
|
unreadableFiles: groundSet.unreadableFiles,
|
|
573
|
-
|
|
530
|
+
opaque,
|
|
574
531
|
beyondText,
|
|
575
532
|
unaccounted,
|
|
576
533
|
conserved: unaccounted === 0,
|