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.
Files changed (72) hide show
  1. package/.claude/skills/ucn/SKILL.md +89 -77
  2. package/.claude/skills/ucn/references/commands.md +62 -68
  3. package/.claude/skills/ucn/references/trust-contract.md +31 -6
  4. package/README.md +438 -305
  5. package/assets/demo.svg +31 -0
  6. package/cli/index.js +430 -1385
  7. package/core/account.js +144 -34
  8. package/core/analysis.js +182 -72
  9. package/core/ast-analysis.js +279 -0
  10. package/core/bridge.js +205 -24
  11. package/core/brief.js +27 -58
  12. package/core/build-worker.js +21 -140
  13. package/core/cache.js +513 -11
  14. package/core/callers.js +4920 -456
  15. package/core/check.js +13 -4
  16. package/core/command-contracts.js +402 -0
  17. package/core/compilation-database.js +276 -0
  18. package/core/confidence.js +4 -1
  19. package/core/deadcode.js +397 -19
  20. package/core/discovery.js +359 -46
  21. package/core/entrypoints.js +195 -41
  22. package/core/execute.js +887 -81
  23. package/core/graph-build.js +162 -7
  24. package/core/graph.js +53 -77
  25. package/core/imports.js +65 -6
  26. package/core/index-ir.js +138 -0
  27. package/core/ir.js +195 -0
  28. package/core/output/analysis.js +212 -22
  29. package/core/output/brief.js +23 -0
  30. package/core/output/check.js +4 -0
  31. package/core/output/doctor.js +37 -6
  32. package/core/output/endpoints.js +5 -2
  33. package/core/output/extraction.js +24 -12
  34. package/core/output/find.js +141 -36
  35. package/core/output/graph.js +11 -5
  36. package/core/output/public.js +462 -0
  37. package/core/output/refactoring.js +42 -10
  38. package/core/output/reporting.js +97 -20
  39. package/core/output/search.js +24 -16
  40. package/core/output/shared.js +22 -1
  41. package/core/output/tracing.js +30 -15
  42. package/core/output-budget.js +295 -0
  43. package/core/output.js +1 -0
  44. package/core/parallel-build.js +44 -11
  45. package/core/parser.js +3 -3
  46. package/core/project.js +384 -187
  47. package/core/public-command.js +47 -0
  48. package/core/registry.js +247 -117
  49. package/core/reporting.js +312 -290
  50. package/core/search.js +317 -185
  51. package/core/semantic-provider.js +110 -0
  52. package/core/stacktrace.js +25 -0
  53. package/core/tracing.js +101 -51
  54. package/core/trust-matrix.js +19 -40
  55. package/core/verify.js +534 -37
  56. package/languages/adapter.js +218 -0
  57. package/languages/c-family.js +2791 -0
  58. package/languages/c.js +3 -0
  59. package/languages/cpp.js +3 -0
  60. package/languages/csharp.js +1402 -0
  61. package/languages/go.js +60 -21
  62. package/languages/html.js +2 -2
  63. package/languages/index.js +85 -7
  64. package/languages/java.js +396 -13
  65. package/languages/javascript.js +199 -19
  66. package/languages/python.js +964 -22
  67. package/languages/rust.js +1317 -152
  68. package/languages/utils.js +40 -3
  69. package/mcp/server.js +254 -636
  70. package/package.json +39 -22
  71. package/eslint.config.js +0 -43
  72. 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 undefined;
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 undefined;
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' && functionStack.length > 0) {
1150
- const scopeKey = functionStack[functionStack.length - 1].startLine;
1151
- const varTypeMap = scopeTypes.get(scopeKey);
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
- const qualifiers = scopeTypeQualifiers.get(scopeKey);
1163
- if (qualifier) qualifiers?.set(id.text, qualifier);
1164
- else qualifiers?.delete(id.text);
1165
- scopeGuesses.get(scopeKey)?.delete(id.text); // declared type clears any guess
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) ? getReceiverType(receiver) : undefined;
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 = getReceiverTypeQualifier(rootNode.text);
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 ? getReceiverType(receiver) : undefined;
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 ? getReceiverType(receiver) : undefined;
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 ? getReceiverType(receiver) : undefined;
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, getLanguageModule } = require('./index');
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 = getLanguageModule('javascript');
142
+ const jsModule = getLanguageAdapter('javascript');
143
143
 
144
144
  return { virtualJS, jsParser, jsModule };
145
145
  }
@@ -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 ext = path.extname(filePath).toLowerCase();
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 language module for a language
431
+ * Get the normalized v5 adapter for a language.
356
432
  * @param {string} language - Language name
357
- * @returns {object} Language module with parse functions
433
+ * @returns {object} Language adapter
358
434
  */
359
- function getLanguageModule(language) {
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
- return config.module();
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
- getLanguageModule,
567
+ getLanguageAdapter,
490
568
  isSupported,
491
569
  getSupportedExtensions,
492
570
  getSupportedLanguages,