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
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ const { createFileIR, validateFileIR } = require('../core/ir');
4
+
5
+ const REQUIRED_ADAPTER_OPERATIONS = Object.freeze([
6
+ 'parse',
7
+ 'findCallsInCode',
8
+ 'findImportsInCode',
9
+ 'findExportsInCode',
10
+ 'findUsagesInCode',
11
+ 'isEntryPoint',
12
+ 'getEntryPointKind',
13
+ ]);
14
+
15
+ const OPTIONAL_HELPERS = Object.freeze([
16
+ 'findFunctions',
17
+ 'findClasses',
18
+ 'findStateObjects',
19
+ 'findMacros',
20
+ 'findCallbackUsages',
21
+ 'findInstanceAttributeTypes',
22
+ 'findReExports',
23
+ 'extractScriptBlocks',
24
+ 'buildVirtualJSContent',
25
+ 'extractEventHandlerCalls',
26
+ 'findTestCallRanges',
27
+ 'getBuiltinCallReturnType',
28
+ 'getBuiltinFieldType',
29
+ 'isPlatformConcreteCall',
30
+ 'isPlatformConcreteType',
31
+ ]);
32
+
33
+ function adapterCapabilities(languageModule) {
34
+ return Object.freeze({
35
+ symbols: typeof languageModule.parse === 'function',
36
+ calls: typeof languageModule.findCallsInCode === 'function',
37
+ imports: typeof languageModule.findImportsInCode === 'function',
38
+ exports: typeof languageModule.findExportsInCode === 'function',
39
+ usages: typeof languageModule.findUsagesInCode === 'function',
40
+ callbacks: typeof languageModule.findCallbackUsages === 'function',
41
+ reExports: typeof languageModule.findReExports === 'function',
42
+ entrypoints: typeof languageModule.getEntryPointKind === 'function',
43
+ semanticProvider: false,
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Wrap one parser implementation in the v5 language-adapter contract.
49
+ * Generic code receives normalized methods and capabilities. Optional
50
+ * grammar-specific helpers are explicitly named on the adapter; the raw
51
+ * parser module is never exposed.
52
+ */
53
+ function createLanguageAdapter(config) {
54
+ if (!config || !config.name) throw new Error('Language adapter name is required');
55
+ if (typeof config.module !== 'function') {
56
+ throw new Error(`${config.name}: language module factory is required`);
57
+ }
58
+ const languageModule = config.module();
59
+ const capabilities = adapterCapabilities(languageModule);
60
+ const adapter = {
61
+ id: config.name,
62
+ extensions: Object.freeze([...(config.extensions || [])]),
63
+ grammar: config.treeSitterLang || config.name,
64
+ traits: Object.freeze({ ...(config.traits || {}) }),
65
+ capabilities,
66
+ managesOwnParseTree: !!languageModule.managesOwnParseTree,
67
+ loadGrammar: config.treeSitterModule,
68
+ parse(code, parser) {
69
+ return languageModule.parse(code, parser);
70
+ },
71
+ findCalls(code, parser) {
72
+ return languageModule.findCallsInCode(code, parser);
73
+ },
74
+ findImports(code, parser) {
75
+ return languageModule.findImportsInCode(code, parser);
76
+ },
77
+ findExports(code, parser) {
78
+ return languageModule.findExportsInCode(code, parser);
79
+ },
80
+ findUsages(code, name, parser, tree) {
81
+ return languageModule.findUsagesInCode(code, name, parser, tree);
82
+ },
83
+ getEntryPointKind(symbol) {
84
+ return languageModule.getEntryPointKind(symbol);
85
+ },
86
+ isEntryPoint(symbol) {
87
+ return languageModule.isEntryPoint(symbol);
88
+ },
89
+ analyze(code, parser, file = null) {
90
+ // Full-file indexing consumes immutable records, not native ASTs.
91
+ // Parsers with a heavier internal recovery cache (notably C/C++)
92
+ // may release analysis-only trees as soon as those records have
93
+ // been extracted. Direct parser/query calls omit this option and
94
+ // retain their bounded cross-operation cache.
95
+ const parsed = languageModule.parse(code, parser, {
96
+ releaseAnalysisTree: true,
97
+ });
98
+ const parseProvidesFacts = !!languageModule.parseProvidesAnalysisFacts;
99
+ const imports = parseProvidesFacts
100
+ ? (parsed.imports || [])
101
+ : languageModule.findImportsInCode(code, parser);
102
+ const exports = parseProvidesFacts
103
+ ? (parsed.exports || [])
104
+ : languageModule.findExportsInCode(code, parser);
105
+ if (!parseProvidesFacts) {
106
+ parsed.imports = imports;
107
+ parsed.exports = exports;
108
+ }
109
+ const callOptions = {};
110
+ if (config.traits?.hasReceiverPackageCalls) {
111
+ callOptions.imports = imports.flatMap(item => item.names || []);
112
+ }
113
+ const calls = parseProvidesFacts && Array.isArray(parsed.calls)
114
+ ? parsed.calls
115
+ : languageModule.findCallsInCode(code, parser, callOptions);
116
+ return createFileIR({
117
+ language: config.name,
118
+ file,
119
+ parsed,
120
+ calls,
121
+ capabilities,
122
+ });
123
+ },
124
+ };
125
+
126
+ // AST implementation aliases retained inside the adapter contract so
127
+ // focused parser tests and HTML delegation exercise the same object as
128
+ // production. These are not a second module surface.
129
+ adapter.findCallsInCode = adapter.findCalls;
130
+ adapter.findImportsInCode = adapter.findImports;
131
+ adapter.findExportsInCode = adapter.findExports;
132
+ adapter.findUsagesInCode = adapter.findUsages;
133
+ for (const helper of OPTIONAL_HELPERS) {
134
+ if (typeof languageModule[helper] === 'function') {
135
+ adapter[helper] = languageModule[helper].bind(languageModule);
136
+ }
137
+ }
138
+ return Object.freeze(adapter);
139
+ }
140
+
141
+ /**
142
+ * Skeleton adapter used by conformance tests and new-language scaffolding.
143
+ * It proves a language can join the registry with navigation-safe empty
144
+ * results before grammar-specific extraction is implemented. It is never
145
+ * advertised as a supported language.
146
+ */
147
+ function createNoopLanguageAdapter({
148
+ id = 'noop',
149
+ extensions = ['.noop'],
150
+ traits = {},
151
+ } = {}) {
152
+ const emptyParse = code => ({
153
+ language: id,
154
+ totalLines: code ? code.split('\n').length : 0,
155
+ functions: [],
156
+ classes: [],
157
+ stateObjects: [],
158
+ imports: [],
159
+ exports: [],
160
+ });
161
+ const languageModule = {
162
+ parse: emptyParse,
163
+ findCallsInCode: () => [],
164
+ findImportsInCode: () => [],
165
+ findExportsInCode: () => [],
166
+ findUsagesInCode: () => [],
167
+ isEntryPoint: () => false,
168
+ getEntryPointKind: () => null,
169
+ };
170
+ return createLanguageAdapter({
171
+ name: id,
172
+ extensions,
173
+ treeSitterLang: id,
174
+ module: () => languageModule,
175
+ treeSitterModule: () => null,
176
+ traits,
177
+ });
178
+ }
179
+
180
+ function validateLanguageAdapter(adapter, { analyzeSample = false } = {}) {
181
+ const failures = [];
182
+ if (!adapter || typeof adapter !== 'object') return ['adapter must be an object'];
183
+ if (!adapter.id) failures.push('id is required');
184
+ if (!Array.isArray(adapter.extensions) || adapter.extensions.length === 0) {
185
+ failures.push(`${adapter.id || 'adapter'}: at least one extension is required`);
186
+ }
187
+ for (const method of ['parse', 'findCalls', 'findImports', 'findExports',
188
+ 'findUsages', 'getEntryPointKind', 'isEntryPoint', 'analyze']) {
189
+ if (typeof adapter[method] !== 'function') {
190
+ failures.push(`${adapter.id || 'adapter'}: missing ${method}()`);
191
+ }
192
+ }
193
+ for (const method of REQUIRED_ADAPTER_OPERATIONS) {
194
+ if (typeof adapter[method] !== 'function') {
195
+ failures.push(`${adapter.id || 'adapter'}: missing parser operation ${method}()`);
196
+ }
197
+ }
198
+ if (!adapter.capabilities || typeof adapter.capabilities !== 'object') {
199
+ failures.push(`${adapter.id || 'adapter'}: capabilities are required`);
200
+ }
201
+ if (analyzeSample && failures.length === 0) {
202
+ try {
203
+ const ir = adapter.analyze('', null, 'empty' + adapter.extensions[0]);
204
+ failures.push(...validateFileIR(ir).map(failure => `${adapter.id}: ${failure}`));
205
+ } catch (error) {
206
+ failures.push(`${adapter.id}: sample analysis failed: ${error.message}`);
207
+ }
208
+ }
209
+ return failures;
210
+ }
211
+
212
+ module.exports = {
213
+ REQUIRED_ADAPTER_OPERATIONS,
214
+ OPTIONAL_HELPERS,
215
+ createLanguageAdapter,
216
+ createNoopLanguageAdapter,
217
+ validateLanguageAdapter,
218
+ };