ucn 4.2.3 → 5.0.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 +89 -77
- package/.claude/skills/ucn/references/commands.md +62 -68
- package/.claude/skills/ucn/references/trust-contract.md +31 -6
- package/README.md +438 -305
- package/assets/demo.svg +31 -0
- package/cli/index.js +430 -1385
- package/core/account.js +144 -34
- package/core/analysis.js +182 -72
- package/core/ast-analysis.js +279 -0
- package/core/bridge.js +205 -24
- package/core/brief.js +27 -58
- package/core/build-worker.js +21 -140
- package/core/cache.js +513 -11
- package/core/callers.js +4920 -456
- package/core/check.js +13 -4
- package/core/command-contracts.js +402 -0
- package/core/compilation-database.js +276 -0
- package/core/confidence.js +4 -1
- package/core/deadcode.js +397 -19
- package/core/discovery.js +359 -46
- package/core/entrypoints.js +195 -41
- package/core/execute.js +887 -81
- package/core/graph-build.js +162 -7
- package/core/graph.js +53 -77
- package/core/imports.js +65 -6
- package/core/index-ir.js +138 -0
- package/core/ir.js +195 -0
- package/core/output/analysis.js +212 -22
- package/core/output/brief.js +23 -0
- package/core/output/check.js +4 -0
- package/core/output/doctor.js +37 -6
- package/core/output/endpoints.js +5 -2
- package/core/output/extraction.js +24 -12
- package/core/output/find.js +141 -36
- package/core/output/graph.js +11 -5
- package/core/output/public.js +462 -0
- package/core/output/refactoring.js +42 -10
- package/core/output/reporting.js +97 -20
- package/core/output/search.js +24 -16
- package/core/output/shared.js +22 -1
- package/core/output/tracing.js +30 -15
- package/core/output-budget.js +295 -0
- package/core/output.js +1 -0
- package/core/parallel-build.js +44 -11
- package/core/parser.js +3 -3
- package/core/project.js +384 -187
- package/core/public-command.js +47 -0
- package/core/registry.js +247 -117
- package/core/reporting.js +312 -290
- package/core/search.js +317 -185
- package/core/semantic-provider.js +110 -0
- package/core/stacktrace.js +25 -0
- package/core/tracing.js +101 -51
- package/core/trust-matrix.js +19 -40
- package/core/verify.js +534 -37
- package/languages/adapter.js +218 -0
- package/languages/c-family.js +2791 -0
- package/languages/c.js +3 -0
- package/languages/cpp.js +3 -0
- package/languages/csharp.js +1402 -0
- package/languages/go.js +60 -21
- package/languages/html.js +2 -2
- package/languages/index.js +85 -7
- package/languages/java.js +396 -13
- package/languages/javascript.js +199 -19
- package/languages/python.js +964 -22
- package/languages/rust.js +1317 -152
- package/languages/utils.js +40 -3
- package/mcp/server.js +254 -636
- package/package.json +39 -22
- package/eslint.config.js +0 -43
- package/jsconfig.json +0 -10
package/languages/go.js
CHANGED
|
@@ -733,6 +733,12 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
733
733
|
const scopeTypes = new Map();
|
|
734
734
|
// Package qualifier paired with an inferred/declared receiver type.
|
|
735
735
|
const scopeTypeQualifiers = new Map();
|
|
736
|
+
// Compiler-declared package variables are visible inside every function
|
|
737
|
+
// unless a lexical declaration shadows the name (`var v *Viper`;
|
|
738
|
+
// `func Set(...) { v.Set(...) }`). They are stored separately so local
|
|
739
|
+
// scope restoration cannot erase them.
|
|
740
|
+
const packageTypes = new Map();
|
|
741
|
+
const packageTypeQualifiers = new Map();
|
|
736
742
|
// Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
|
|
737
743
|
const scopeGuesses = new Map();
|
|
738
744
|
// Track function-typed parameter names per scope (scopeStartLine -> Set<name>)
|
|
@@ -898,19 +904,21 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
898
904
|
};
|
|
899
905
|
|
|
900
906
|
// Look up variable type from scope chain
|
|
901
|
-
const getReceiverType = (varName) => {
|
|
907
|
+
const getReceiverType = (varName, refNode) => {
|
|
902
908
|
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
903
909
|
const typeMap = scopeTypes.get(functionStack[i].startLine);
|
|
904
910
|
if (typeMap?.has(varName)) return typeMap.get(varName);
|
|
905
911
|
}
|
|
906
|
-
return
|
|
912
|
+
return refNode && isShadowedByLocal(refNode, varName)
|
|
913
|
+
? undefined : packageTypes.get(varName);
|
|
907
914
|
};
|
|
908
|
-
const getReceiverTypeQualifier = (varName) => {
|
|
915
|
+
const getReceiverTypeQualifier = (varName, refNode) => {
|
|
909
916
|
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
910
917
|
const qualifiers = scopeTypeQualifiers.get(functionStack[i].startLine);
|
|
911
918
|
if (qualifiers?.has(varName)) return qualifiers.get(varName);
|
|
912
919
|
}
|
|
913
|
-
return
|
|
920
|
+
return refNode && isShadowedByLocal(refNode, varName)
|
|
921
|
+
? undefined : packageTypeQualifiers.get(varName);
|
|
914
922
|
};
|
|
915
923
|
// Is the FIRST scope-chain hit for this variable a New*-prefix GUESS
|
|
916
924
|
// (fix #266, viper-measured)? `registry := NewCodecRegistry()` types
|
|
@@ -1146,9 +1154,12 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1146
1154
|
// on an untyped receiver fell to single-owner confirmation). Same
|
|
1147
1155
|
// semantics as parameter annotations: the declared type is the
|
|
1148
1156
|
// receiver's compile-time type.
|
|
1149
|
-
if (node.type === 'var_declaration'
|
|
1150
|
-
const scopeKey = functionStack
|
|
1151
|
-
|
|
1157
|
+
if (node.type === 'var_declaration') {
|
|
1158
|
+
const scopeKey = functionStack.length > 0
|
|
1159
|
+
? functionStack[functionStack.length - 1].startLine : null;
|
|
1160
|
+
const varTypeMap = scopeKey == null ? packageTypes : scopeTypes.get(scopeKey);
|
|
1161
|
+
const varQualifierMap = scopeKey == null
|
|
1162
|
+
? packageTypeQualifiers : scopeTypeQualifiers.get(scopeKey);
|
|
1152
1163
|
if (varTypeMap) {
|
|
1153
1164
|
const recordSpec = (spec) => {
|
|
1154
1165
|
if (spec.type !== 'var_spec') return;
|
|
@@ -1159,10 +1170,11 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1159
1170
|
const id = spec.namedChild(j);
|
|
1160
1171
|
if (id.type === 'identifier') {
|
|
1161
1172
|
varTypeMap.set(id.text, typeName);
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1173
|
+
if (qualifier) varQualifierMap?.set(id.text, qualifier);
|
|
1174
|
+
else varQualifierMap?.delete(id.text);
|
|
1175
|
+
if (scopeKey != null) {
|
|
1176
|
+
scopeGuesses.get(scopeKey)?.delete(id.text);
|
|
1177
|
+
}
|
|
1166
1178
|
}
|
|
1167
1179
|
}
|
|
1168
1180
|
};
|
|
@@ -1288,9 +1300,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1288
1300
|
// Distinguish pkg.Func() (package-qualified) from obj.Method()
|
|
1289
1301
|
// If receiver is a known import alias, this is a package call, not a method call
|
|
1290
1302
|
const isPkgCall = receiver && importAliases.has(receiver);
|
|
1291
|
-
const receiverType = (!isPkgCall && receiver)
|
|
1303
|
+
const receiverType = (!isPkgCall && receiver)
|
|
1304
|
+
? getReceiverType(receiver, operandNode) : undefined;
|
|
1292
1305
|
const receiverTypeQualifier = receiverType
|
|
1293
|
-
? getReceiverTypeQualifier(receiver) : undefined;
|
|
1306
|
+
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1294
1307
|
// fix #202: one-hop declared-field receivers — h.inner.Run().
|
|
1295
1308
|
// receiverRoot/Field/RootType let findCallers hop to the
|
|
1296
1309
|
// field's declared struct-field type cross-file.
|
|
@@ -1310,8 +1323,9 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1310
1323
|
// concrete type lives outside the project.
|
|
1311
1324
|
receiverRootIsModule = true;
|
|
1312
1325
|
} else {
|
|
1313
|
-
receiverRootType = getReceiverType(rootNode.text);
|
|
1314
|
-
receiverRootTypeQualifier =
|
|
1326
|
+
receiverRootType = getReceiverType(rootNode.text, rootNode);
|
|
1327
|
+
receiverRootTypeQualifier =
|
|
1328
|
+
getReceiverTypeQualifier(rootNode.text, rootNode);
|
|
1315
1329
|
if (receiverRootType && isGuessedType(rootNode.text)) {
|
|
1316
1330
|
receiverRootTypeGuessed = true;
|
|
1317
1331
|
}
|
|
@@ -1443,9 +1457,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1443
1457
|
const operandNode = node.childForFieldName('operand');
|
|
1444
1458
|
if (fieldNode && operandNode) {
|
|
1445
1459
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1446
|
-
const receiverType = receiver
|
|
1460
|
+
const receiverType = receiver
|
|
1461
|
+
? getReceiverType(receiver, operandNode) : undefined;
|
|
1447
1462
|
const receiverTypeQualifier = receiverType
|
|
1448
|
-
? getReceiverTypeQualifier(receiver) : undefined;
|
|
1463
|
+
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1449
1464
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1450
1465
|
calls.push({
|
|
1451
1466
|
name: fieldNode.text,
|
|
@@ -1502,9 +1517,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1502
1517
|
const operandNode = rhs.childForFieldName('operand');
|
|
1503
1518
|
if (fieldNode && operandNode) {
|
|
1504
1519
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1505
|
-
const receiverType = receiver
|
|
1520
|
+
const receiverType = receiver
|
|
1521
|
+
? getReceiverType(receiver, operandNode) : undefined;
|
|
1506
1522
|
const receiverTypeQualifier = receiverType
|
|
1507
|
-
? getReceiverTypeQualifier(receiver) : undefined;
|
|
1523
|
+
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1508
1524
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1509
1525
|
calls.push({
|
|
1510
1526
|
name: fieldNode.text,
|
|
@@ -1600,9 +1616,10 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1600
1616
|
const operandNode = valueNode.childForFieldName('operand');
|
|
1601
1617
|
if (fieldNode && operandNode) {
|
|
1602
1618
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1603
|
-
const receiverType = receiver
|
|
1619
|
+
const receiverType = receiver
|
|
1620
|
+
? getReceiverType(receiver, operandNode) : undefined;
|
|
1604
1621
|
const receiverTypeQualifier = receiverType
|
|
1605
|
-
? getReceiverTypeQualifier(receiver) : undefined;
|
|
1622
|
+
? getReceiverTypeQualifier(receiver, operandNode) : undefined;
|
|
1606
1623
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1607
1624
|
calls.push({
|
|
1608
1625
|
name: fieldNode.text,
|
|
@@ -1952,6 +1969,26 @@ function isEntryPoint(symbol) {
|
|
|
1952
1969
|
return getEntryPointKind(symbol) !== null;
|
|
1953
1970
|
}
|
|
1954
1971
|
|
|
1972
|
+
// Stable Go standard-library runtime identities. Core consults these only
|
|
1973
|
+
// after proving the qualifier resolves outside the indexed project.
|
|
1974
|
+
const GO_PLATFORM_CONCRETE_CALLS = new Set([
|
|
1975
|
+
'errors.New',
|
|
1976
|
+
'fmt.Errorf',
|
|
1977
|
+
'slog.New',
|
|
1978
|
+
]);
|
|
1979
|
+
|
|
1980
|
+
const GO_PLATFORM_CONCRETE_TYPES = new Set([
|
|
1981
|
+
'slog.Logger',
|
|
1982
|
+
]);
|
|
1983
|
+
|
|
1984
|
+
function isPlatformConcreteCall(moduleName, functionName) {
|
|
1985
|
+
return GO_PLATFORM_CONCRETE_CALLS.has(`${moduleName}.${functionName}`);
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
function isPlatformConcreteType(moduleName, typeName) {
|
|
1989
|
+
return GO_PLATFORM_CONCRETE_TYPES.has(`${moduleName}.${typeName}`);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1955
1992
|
module.exports = {
|
|
1956
1993
|
findFunctions,
|
|
1957
1994
|
findClasses,
|
|
@@ -1960,6 +1997,8 @@ module.exports = {
|
|
|
1960
1997
|
findImportsInCode,
|
|
1961
1998
|
findExportsInCode,
|
|
1962
1999
|
findUsagesInCode,
|
|
2000
|
+
isPlatformConcreteCall,
|
|
2001
|
+
isPlatformConcreteType,
|
|
1963
2002
|
isEntryPoint,
|
|
1964
2003
|
getEntryPointKind,
|
|
1965
2004
|
parse
|
package/languages/html.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* as the original HTML file, with empty lines for non-script content.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const { getParser,
|
|
10
|
+
const { getParser, getLanguageAdapter } = require('./index');
|
|
11
11
|
|
|
12
12
|
// Script type values that indicate JavaScript content
|
|
13
13
|
const JS_TYPES = new Set([
|
|
@@ -139,7 +139,7 @@ function extractJS(htmlContent, htmlParser) {
|
|
|
139
139
|
|
|
140
140
|
const virtualJS = buildVirtualJSContent(htmlContent, blocks);
|
|
141
141
|
const jsParser = getParser('javascript');
|
|
142
|
-
const jsModule =
|
|
142
|
+
const jsModule = getLanguageAdapter('javascript');
|
|
143
143
|
|
|
144
144
|
return { virtualJS, jsParser, jsModule };
|
|
145
145
|
}
|
package/languages/index.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { createLanguageAdapter } = require('./adapter');
|
|
8
9
|
|
|
9
10
|
// Lazy-loaded tree-sitter
|
|
10
11
|
let TreeSitter = null;
|
|
11
12
|
|
|
12
13
|
// Cached parser instances
|
|
13
14
|
const parsers = {};
|
|
15
|
+
const adapters = {};
|
|
14
16
|
|
|
15
17
|
// Shared trait presets for languages with the same type-system characteristics
|
|
16
18
|
const STRUCTURAL_TRAITS = {
|
|
@@ -229,6 +231,75 @@ const LANGUAGES = {
|
|
|
229
231
|
testFileCandidates: (base, ext) => [`${base}Test.java`, `${base}Tests.java`, `${base}TestCase.java`],
|
|
230
232
|
},
|
|
231
233
|
},
|
|
234
|
+
c: {
|
|
235
|
+
name: 'c',
|
|
236
|
+
extensions: ['.c', '.h'],
|
|
237
|
+
treeSitterLang: 'c',
|
|
238
|
+
module: () => require('./c'),
|
|
239
|
+
treeSitterModule: () => require('tree-sitter-c'),
|
|
240
|
+
traits: {
|
|
241
|
+
...NOMINAL_TRAITS,
|
|
242
|
+
selfParam: null,
|
|
243
|
+
packageScope: 'file',
|
|
244
|
+
hasDynamicImports: false,
|
|
245
|
+
exportVisibility: 'linkage',
|
|
246
|
+
typeQualifiedCallStyle: 'static',
|
|
247
|
+
bareCallReachesMethods: false,
|
|
248
|
+
methodCallReachesFunctions: true,
|
|
249
|
+
testFileCandidates: (base, ext) => [
|
|
250
|
+
`${base}_test${ext}`, `test_${base}${ext}`, `${base}.test${ext}`,
|
|
251
|
+
],
|
|
252
|
+
testDirs: ['test', 'tests'],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
cpp: {
|
|
256
|
+
name: 'cpp',
|
|
257
|
+
extensions: ['.cc', '.cpp', '.cxx', '.c++', '.hpp', '.hh', '.hxx', '.h++'],
|
|
258
|
+
treeSitterLang: 'cpp',
|
|
259
|
+
module: () => require('./cpp'),
|
|
260
|
+
treeSitterModule: () => require('tree-sitter-cpp'),
|
|
261
|
+
traits: {
|
|
262
|
+
...NOMINAL_TRAITS,
|
|
263
|
+
selfParam: ['this'],
|
|
264
|
+
packageScope: 'file',
|
|
265
|
+
hasDynamicImports: false,
|
|
266
|
+
exportVisibility: 'linkage',
|
|
267
|
+
typeQualifiedCallStyle: 'path',
|
|
268
|
+
hasArityOverloads: true,
|
|
269
|
+
// C++ instance dispatch is virtual only when the declaration
|
|
270
|
+
// explicitly says virtual/override (unlike Java's implicit rule).
|
|
271
|
+
explicitVirtualDispatch: true,
|
|
272
|
+
bareCallReachesMethods: true,
|
|
273
|
+
// `obj.f()` performs member lookup and cannot bind a namespace
|
|
274
|
+
// free function. `ns::f()` remains supported through isPathCall.
|
|
275
|
+
methodCallReachesFunctions: false,
|
|
276
|
+
testFileCandidates: (base, ext) => [
|
|
277
|
+
`${base}_test${ext}`, `test_${base}${ext}`, `${base}.test${ext}`,
|
|
278
|
+
],
|
|
279
|
+
testDirs: ['test', 'tests'],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
csharp: {
|
|
283
|
+
name: 'csharp',
|
|
284
|
+
extensions: ['.cs', '.csx'],
|
|
285
|
+
treeSitterLang: 'c_sharp',
|
|
286
|
+
module: () => require('./csharp'),
|
|
287
|
+
treeSitterModule: () => require('tree-sitter-c-sharp'),
|
|
288
|
+
traits: {
|
|
289
|
+
...NOMINAL_TRAITS,
|
|
290
|
+
selfParam: ['this', 'base'],
|
|
291
|
+
packageScope: 'namespace',
|
|
292
|
+
hasDynamicImports: false,
|
|
293
|
+
hasArityOverloads: true,
|
|
294
|
+
bareCallReachesMethods: true,
|
|
295
|
+
typeQualifiedCallStyle: 'static',
|
|
296
|
+
universalSupertype: 'Object',
|
|
297
|
+
testFileCandidates: (base, ext) => [
|
|
298
|
+
`${base}Tests${ext}`, `${base}Test${ext}`, `${base}.Tests${ext}`,
|
|
299
|
+
],
|
|
300
|
+
testDirs: ['test', 'tests'],
|
|
301
|
+
},
|
|
302
|
+
},
|
|
232
303
|
html: {
|
|
233
304
|
name: 'html',
|
|
234
305
|
extensions: ['.html', '.htm'],
|
|
@@ -346,22 +417,29 @@ function getParser(language) {
|
|
|
346
417
|
* @param {string} filePath - File path
|
|
347
418
|
* @returns {string|null} Language name or null if unsupported
|
|
348
419
|
*/
|
|
349
|
-
function detectLanguage(filePath) {
|
|
350
|
-
const
|
|
420
|
+
function detectLanguage(filePath, projectRoot = null) {
|
|
421
|
+
const rawExt = path.extname(filePath);
|
|
422
|
+
const ext = rawExt.toLowerCase();
|
|
423
|
+
if (ext === '.h') {
|
|
424
|
+
const { detectHeaderLanguage } = require('../core/compilation-database');
|
|
425
|
+
return detectHeaderLanguage(filePath, projectRoot);
|
|
426
|
+
}
|
|
351
427
|
return EXT_MAP[ext] || null;
|
|
352
428
|
}
|
|
353
429
|
|
|
354
430
|
/**
|
|
355
|
-
* Get
|
|
431
|
+
* Get the normalized v5 adapter for a language.
|
|
356
432
|
* @param {string} language - Language name
|
|
357
|
-
* @returns {object} Language
|
|
433
|
+
* @returns {object} Language adapter
|
|
358
434
|
*/
|
|
359
|
-
function
|
|
435
|
+
function getLanguageAdapter(language) {
|
|
436
|
+
if (adapters[language]) return adapters[language];
|
|
360
437
|
const config = LANGUAGES[language];
|
|
361
438
|
if (!config) {
|
|
362
439
|
throw new Error(`Unsupported language: ${language}`);
|
|
363
440
|
}
|
|
364
|
-
|
|
441
|
+
adapters[language] = createLanguageAdapter(config);
|
|
442
|
+
return adapters[language];
|
|
365
443
|
}
|
|
366
444
|
|
|
367
445
|
/**
|
|
@@ -486,7 +564,7 @@ function langTraits(language) {
|
|
|
486
564
|
module.exports = {
|
|
487
565
|
detectLanguage,
|
|
488
566
|
getParser,
|
|
489
|
-
|
|
567
|
+
getLanguageAdapter,
|
|
490
568
|
isSupported,
|
|
491
569
|
getSupportedExtensions,
|
|
492
570
|
getSupportedLanguages,
|