ucn 5.5.1 → 5.5.3
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 +35 -9
- package/.claude/skills/ucn/references/commands.md +40 -18
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +32 -21
- package/cli/index.js +2 -1
- package/core/accessors.js +341 -54
- package/core/account.js +74 -20
- package/core/analysis.js +257 -125
- package/core/ast-analysis.js +12 -3
- package/core/bridge.js +15 -65
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +137 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +4726 -940
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/client-receivers.js +189 -0
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +61 -16
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/inprocess-clients.js +41 -15
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +2 -0
- package/core/output/reporting.js +12 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +217 -112
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +270 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/request-helpers.js +63 -7
- package/core/rust-macro-expansion.js +151 -19
- package/core/rust-modules.js +1 -1
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +124 -37
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +510 -82
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +3 -3
package/core/account.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* Ground-set semantics are grep `-n -w`: unit is the (file, line) pair, each
|
|
37
37
|
* line with >= 1 word-boundary match counts once, case-sensitive.
|
|
38
38
|
*
|
|
39
|
-
* Performance: the first ground scan is one
|
|
39
|
+
* Performance: the first ground scan is one literal-name-gated read per project
|
|
40
40
|
* file — the same I/O profile as the existing `search`/`usages` commands.
|
|
41
41
|
* Exact results are retained in a small LRU for the lifetime of one built index
|
|
42
42
|
* so context/about/impact projections of the same symbol do not rescan the
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
const fs = require('fs');
|
|
54
54
|
const path = require('path');
|
|
55
55
|
const { codeUnitCompare, literalNameRegex } = require('./shared');
|
|
56
|
+
const { compareNames } = require('./discovery');
|
|
56
57
|
|
|
57
58
|
// Unsupported-language site listings are capped so a Rails-sized repo cannot
|
|
58
59
|
// flood the account object; the counts always cover the full set.
|
|
@@ -84,7 +85,7 @@ function computeGroundSet(index, name) {
|
|
|
84
85
|
index._groundSetCache.set(name, cached);
|
|
85
86
|
return cached.result;
|
|
86
87
|
}
|
|
87
|
-
const wordRe = literalNameRegex(name);
|
|
88
|
+
const wordRe = literalNameRegex(name, name ? 'y' : '');
|
|
88
89
|
const perFile = new Map();
|
|
89
90
|
let total = 0;
|
|
90
91
|
let fileCount = 0;
|
|
@@ -96,14 +97,7 @@ function computeGroundSet(index, name) {
|
|
|
96
97
|
} catch (e) {
|
|
97
98
|
continue; // deleted since indexing; not part of the universe anymore
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
-
// Shared read-only lines array — split once per file per operation,
|
|
101
|
-
// reused across the symbols of a multi-symbol command (diff-impact).
|
|
102
|
-
const lines = index._getFileLines(filePath);
|
|
103
|
-
const matched = [];
|
|
104
|
-
for (let i = 0; i < lines.length; i++) {
|
|
105
|
-
if (wordRe.test(lines[i])) matched.push(i + 1);
|
|
106
|
-
}
|
|
100
|
+
const matched = matchingGroundLines(content, name, wordRe);
|
|
107
101
|
if (matched.length > 0) {
|
|
108
102
|
perFile.set(filePath, matched);
|
|
109
103
|
total += matched.length;
|
|
@@ -180,6 +174,34 @@ function computeGroundSet(index, name) {
|
|
|
180
174
|
return result;
|
|
181
175
|
}
|
|
182
176
|
|
|
177
|
+
// Find literal candidates natively, checking the same Unicode boundaries
|
|
178
|
+
// at their exact offsets. Count line breaks only up to matching positions;
|
|
179
|
+
// once a line matches, its remaining occurrences cannot change the account.
|
|
180
|
+
function matchingGroundLines(content, name, wordRe) {
|
|
181
|
+
if (name.includes('\n')) return [];
|
|
182
|
+
if (!name) return content.split('\n').flatMap((line, i) => wordRe.test(line) ? [i + 1] : []);
|
|
183
|
+
const matched = [];
|
|
184
|
+
let at = content.indexOf(name);
|
|
185
|
+
if (at === -1) return matched;
|
|
186
|
+
let line = 1, end = content.indexOf('\n');
|
|
187
|
+
while (at !== -1) {
|
|
188
|
+
wordRe.lastIndex = at;
|
|
189
|
+
if (wordRe.test(content)) {
|
|
190
|
+
while (end !== -1 && end < at) {
|
|
191
|
+
line++;
|
|
192
|
+
end = content.indexOf('\n', end + 1);
|
|
193
|
+
}
|
|
194
|
+
matched.push(line);
|
|
195
|
+
if (end === -1) break;
|
|
196
|
+
at = content.indexOf(name, end + 1);
|
|
197
|
+
} else {
|
|
198
|
+
// A rejected candidate can overlap a later valid occurrence.
|
|
199
|
+
at = content.indexOf(name, at + 1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return matched;
|
|
203
|
+
}
|
|
204
|
+
|
|
183
205
|
/** Scan only files the parser/index could not ingest. */
|
|
184
206
|
function scanFailedFiles(index, name) {
|
|
185
207
|
const unparsed = { fileCount: 0, lines: 0, files: [] };
|
|
@@ -200,7 +222,7 @@ function scanFailedFiles(index, name) {
|
|
|
200
222
|
if (!content.includes(name)) continue;
|
|
201
223
|
let matched = 0;
|
|
202
224
|
for (const line of content.split('\n')) {
|
|
203
|
-
if (wordRe.test(line)) matched++;
|
|
225
|
+
if (line.includes(name) && wordRe.test(line)) matched++;
|
|
204
226
|
}
|
|
205
227
|
if (matched > 0) {
|
|
206
228
|
unparsed.fileCount++;
|
|
@@ -248,7 +270,7 @@ function scanUnsupportedFiles(index, name, opts = {}) {
|
|
|
248
270
|
const lines = content.split('\n');
|
|
249
271
|
let matched = 0;
|
|
250
272
|
for (let i = 0; i < lines.length; i++) {
|
|
251
|
-
if (!wordRe.test(lines[i])) continue;
|
|
273
|
+
if (!lines[i].includes(name) || !wordRe.test(lines[i])) continue;
|
|
252
274
|
matched++;
|
|
253
275
|
if (unsupported.sites.length < UNSUPPORTED_SITE_CAP) {
|
|
254
276
|
unsupported.sites.push({
|
|
@@ -344,18 +366,31 @@ function classifyGroundLines(index, name, groundSet, claimedKeys) {
|
|
|
344
366
|
// Cheap pass: calls cache + symbol table (only records bearing the
|
|
345
367
|
// name on unclaimed lines are read below).
|
|
346
368
|
if (!getCachedCallsBearing) getCachedCallsBearing = require('./callers').getCachedCallsBearing;
|
|
347
|
-
const
|
|
369
|
+
const unclaimedLines = new Set(unclaimed);
|
|
370
|
+
const cachedCalls = getCachedCallsBearing(index, filePath, name, unclaimedLines);
|
|
371
|
+
const recordIsNoCall = require('./callers')._recordIsNoCall;
|
|
348
372
|
const callLines = new Set();
|
|
373
|
+
// `(T)(x)` casts: uses of the type, not calls (fix #427).
|
|
374
|
+
let castLines = null;
|
|
349
375
|
if (Array.isArray(cachedCalls)) {
|
|
350
376
|
for (const c of cachedCalls) {
|
|
377
|
+
// The ordinary cache can return the whole file. Only these
|
|
378
|
+
// name-bearing, unclaimed lines can affect this account.
|
|
379
|
+
if (!(c.name === name || c.resolvedName === name ||
|
|
380
|
+
(c.resolvedNames && c.resolvedNames.includes(name))) || !unclaimedLines.has(c.line)) continue;
|
|
351
381
|
// Unclaimed callback/type references have no invocation
|
|
352
382
|
// syntax. Let the usage AST classify them as references;
|
|
353
383
|
// merely entering the candidate cache is not a call fact.
|
|
354
|
-
|
|
355
|
-
if (c.
|
|
356
|
-
|
|
357
|
-
|
|
384
|
+
// An implicit initializer has no written invocation either.
|
|
385
|
+
if (c.isFunctionReference || c.isTypeReference || c.constructorSyntax === 'implicit') continue;
|
|
386
|
+
// Nor a data member's mem-initializer or a default
|
|
387
|
+
// initialization of no class (fix #421).
|
|
388
|
+
if ((c.memberInitializer || c.constructorSyntax === 'default') && recordIsNoCall(index, filePath, c)) continue;
|
|
389
|
+
if (c.parenthesizedCallee && c.name === name && recordIsNoCall(index, filePath, c)) {
|
|
390
|
+
(castLines ||= new Set()).add(c.line);
|
|
391
|
+
continue;
|
|
358
392
|
}
|
|
393
|
+
callLines.add(c.line);
|
|
359
394
|
}
|
|
360
395
|
}
|
|
361
396
|
const defLines = defLinesByFile.get(filePath);
|
|
@@ -364,6 +399,8 @@ function classifyGroundLines(index, name, groundSet, claimedKeys) {
|
|
|
364
399
|
for (const line of unclaimed) {
|
|
365
400
|
if (callLines.has(line)) {
|
|
366
401
|
callNotResolved.push({ file: filePath, relativePath, line });
|
|
402
|
+
} else if (castLines?.has(line)) {
|
|
403
|
+
nonCall.references++;
|
|
367
404
|
} else if (defLines && defLines.has(line)) {
|
|
368
405
|
nonCall.definitions++;
|
|
369
406
|
} else {
|
|
@@ -377,8 +414,11 @@ function classifyGroundLines(index, name, groundSet, claimedKeys) {
|
|
|
377
414
|
// Call lines were classified from the complete calls cache above.
|
|
378
415
|
// Language adapters may skip usage-only call recovery here; notably,
|
|
379
416
|
// C/C++ avoids reparsing every matching macro replacement list.
|
|
417
|
+
// Only occurrence kinds are consumed below, so receiver ownership
|
|
418
|
+
// is optional on this path too.
|
|
380
419
|
const usages = index._getCachedUsages(filePath, name, {
|
|
381
420
|
skipCallRecovery: true,
|
|
421
|
+
usageKindsOnly: true,
|
|
382
422
|
});
|
|
383
423
|
const byLine = new Map();
|
|
384
424
|
if (Array.isArray(usages)) {
|
|
@@ -433,9 +473,13 @@ const RANK = { call: 0, import: 1, definition: 2, reference: 3 };
|
|
|
433
473
|
*/
|
|
434
474
|
function buildAccount(index, name, parts) {
|
|
435
475
|
const { groundSet } = parts;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
476
|
+
// Entries in (file, line) order, stable (fix #407): samples, the evidence
|
|
477
|
+
// list and the reason of a line excluded twice must not follow the order
|
|
478
|
+
// the engine visited files in, which a warm session's derivation history
|
|
479
|
+
// changes.
|
|
480
|
+
const confirmedEntries = sortedSites(parts.confirmedEntries || []);
|
|
481
|
+
const unverifiedEntries = sortedSites(parts.unverifiedEntries || []);
|
|
482
|
+
const excludedEntries = sortedSites(parts.excludedEntries || []);
|
|
439
483
|
|
|
440
484
|
// (file, line) keys, kept per file (fix #405b: one composed string per
|
|
441
485
|
// ground line and per entry); a line is keyed by its text, as the
|
|
@@ -562,6 +606,16 @@ function relPath(index, filePath) {
|
|
|
562
606
|
return fileEntry ? fileEntry.relativePath : path.relative(index.root, filePath);
|
|
563
607
|
}
|
|
564
608
|
|
|
609
|
+
/** Sites by (file in the index's canonical order, line), stable; the input
|
|
610
|
+
* itself when already in that order, else a sorted copy (fix #407). */
|
|
611
|
+
function sortedSites(entries) {
|
|
612
|
+
const before = (a, b) => compareNames(String(a.file), String(b.file)) || ((a.line || 0) - (b.line || 0));
|
|
613
|
+
for (let i = 1; i < entries.length; i++) {
|
|
614
|
+
if (before(entries[i - 1], entries[i]) > 0) return [...entries].sort(before);
|
|
615
|
+
}
|
|
616
|
+
return entries;
|
|
617
|
+
}
|
|
618
|
+
|
|
565
619
|
module.exports = {
|
|
566
620
|
computeGroundSet,
|
|
567
621
|
classifyGroundLines,
|