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,276 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Read-only compile_commands.json support for C/C++ language and include-path
5
+ * resolution. This module interprets build metadata only; source analysis
6
+ * remains tree-sitter AST based.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const CPP_EXTENSIONS = new Set(['.cc', '.cpp', '.cxx', '.c++', '.cp', '.mm']);
13
+ const C_EXTENSIONS = new Set(['.c', '.m']);
14
+ const cache = new Map();
15
+ const conventionCache = new Map();
16
+ const CONVENTION_IGNORES = new Set([
17
+ '.git', '.svn', 'node_modules', 'vendor', 'third_party',
18
+ 'build', 'dist', 'out', 'target',
19
+ ]);
20
+
21
+ function splitCommandLine(command) {
22
+ const args = [];
23
+ let current = '';
24
+ let quote = null;
25
+ let escaped = false;
26
+ for (const char of String(command || '')) {
27
+ if (escaped) {
28
+ current += char;
29
+ escaped = false;
30
+ } else if (char === '\\' && quote !== "'") {
31
+ escaped = true;
32
+ } else if (quote) {
33
+ if (char === quote) quote = null;
34
+ else current += char;
35
+ } else if (char === '"' || char === "'") {
36
+ quote = char;
37
+ } else if (/\s/.test(char)) {
38
+ if (current) {
39
+ args.push(current);
40
+ current = '';
41
+ }
42
+ } else {
43
+ current += char;
44
+ }
45
+ }
46
+ if (escaped) current += '\\';
47
+ if (current) args.push(current);
48
+ return args;
49
+ }
50
+
51
+ function findCompilationDatabase(startDir, stopDir = null) {
52
+ let dir = path.resolve(startDir);
53
+ const boundary = stopDir ? path.resolve(stopDir) : null;
54
+ while (true) {
55
+ const candidate = path.join(dir, 'compile_commands.json');
56
+ try {
57
+ if (fs.statSync(candidate).isFile()) return candidate;
58
+ } catch { /* keep walking */ }
59
+ if (boundary && dir === boundary) break;
60
+ const parent = path.dirname(dir);
61
+ if (parent === dir || (boundary && !parent.startsWith(boundary))) break;
62
+ dir = parent;
63
+ }
64
+ return null;
65
+ }
66
+
67
+ function languageFromArgs(file, args) {
68
+ for (let i = 0; i < args.length; i++) {
69
+ if (args[i] === '-x' && args[i + 1]) {
70
+ const value = args[i + 1].toLowerCase();
71
+ if (value.includes('c++') || value.includes('objective-c++')) return 'cpp';
72
+ if (value === 'c' || value.includes('objective-c')) return 'c';
73
+ }
74
+ if (args[i].startsWith('-x')) {
75
+ const value = args[i].slice(2).toLowerCase();
76
+ if (value.includes('c++') || value.includes('objective-c++')) return 'cpp';
77
+ if (value === 'c' || value.includes('objective-c')) return 'c';
78
+ }
79
+ }
80
+ const ext = path.extname(file).toLowerCase();
81
+ if (CPP_EXTENSIONS.has(ext)) return 'cpp';
82
+ if (C_EXTENSIONS.has(ext)) return 'c';
83
+ const compiler = path.basename(args[0] || '').toLowerCase();
84
+ if (/(^|-)c\+\+/.test(compiler) || /g\+\+|clang\+\+/.test(compiler)) return 'cpp';
85
+ return 'c';
86
+ }
87
+
88
+ function includeDirectories(directory, args) {
89
+ const result = [];
90
+ const add = value => {
91
+ if (!value) return;
92
+ const absolute = path.isAbsolute(value) ? value : path.resolve(directory, value);
93
+ if (!result.includes(absolute)) result.push(absolute);
94
+ };
95
+ for (let i = 0; i < args.length; i++) {
96
+ const arg = args[i];
97
+ if (arg === '-I' || arg === '-isystem' || arg === '-iquote') {
98
+ add(args[++i]);
99
+ } else if (arg.startsWith('-I') && arg.length > 2) {
100
+ add(arg.slice(2));
101
+ } else if (arg.startsWith('-isystem') && arg.length > 8) {
102
+ add(arg.slice(8));
103
+ } else if (arg.startsWith('-iquote') && arg.length > 7) {
104
+ add(arg.slice(7));
105
+ }
106
+ }
107
+ return result;
108
+ }
109
+
110
+ function loadCompilationDatabase(databasePath) {
111
+ if (!databasePath) return null;
112
+ let stat;
113
+ try { stat = fs.statSync(databasePath); }
114
+ catch { return null; }
115
+ const previous = cache.get(databasePath);
116
+ if (previous && previous.mtime === stat.mtimeMs && previous.size === stat.size) {
117
+ return previous.value;
118
+ }
119
+ try {
120
+ const rows = JSON.parse(fs.readFileSync(databasePath, 'utf8'));
121
+ if (!Array.isArray(rows)) return null;
122
+ const entries = [];
123
+ for (const row of rows) {
124
+ if (!row || typeof row.file !== 'string') continue;
125
+ const directory = path.resolve(row.directory || path.dirname(databasePath));
126
+ const file = path.isAbsolute(row.file) ? row.file : path.resolve(directory, row.file);
127
+ const args = Array.isArray(row.arguments)
128
+ ? row.arguments.map(String)
129
+ : splitCommandLine(row.command);
130
+ entries.push({
131
+ file,
132
+ directory,
133
+ language: languageFromArgs(file, args),
134
+ includeDirs: includeDirectories(directory, args),
135
+ });
136
+ }
137
+ const value = { path: databasePath, root: path.dirname(databasePath), entries };
138
+ cache.set(databasePath, { mtime: stat.mtimeMs, size: stat.size, value });
139
+ return value;
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ function directoryDistance(fromDir, candidateDir) {
146
+ const relative = path.relative(candidateDir, fromDir);
147
+ if (relative === '') return 0;
148
+ return relative.split(path.sep).filter(part => part && part !== '.').length;
149
+ }
150
+
151
+ function nearestEntries(database, filePath) {
152
+ if (!database) return [];
153
+ const directory = path.dirname(path.resolve(filePath));
154
+ return [...database.entries].sort((a, b) => {
155
+ const distance = directoryDistance(directory, path.dirname(a.file)) -
156
+ directoryDistance(directory, path.dirname(b.file));
157
+ // Code-unit tiebreak (rule 11): localeCompare is ICU-locale-dependent.
158
+ return distance || (a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
159
+ });
160
+ }
161
+
162
+ function projectBoundary(startDir) {
163
+ let directory = path.resolve(startDir);
164
+ let fallback = directory;
165
+ for (let depth = 0; depth < 8; depth++) {
166
+ fallback = directory;
167
+ if (fs.existsSync(path.join(directory, '.git')) ||
168
+ fs.existsSync(path.join(directory, 'CMakeLists.txt')) ||
169
+ fs.existsSync(path.join(directory, 'meson.build'))) {
170
+ return directory;
171
+ }
172
+ const parent = path.dirname(directory);
173
+ if (parent === directory) break;
174
+ directory = parent;
175
+ }
176
+ return fallback;
177
+ }
178
+
179
+ function projectTranslationUnitConvention(filePath, projectRoot = null) {
180
+ const root = projectRoot
181
+ ? path.resolve(projectRoot)
182
+ : projectBoundary(path.dirname(filePath));
183
+ if (conventionCache.has(root)) return conventionCache.get(root);
184
+ let cpp = 0;
185
+ let c = 0;
186
+ let visited = 0;
187
+ const queue = [root];
188
+ while (queue.length > 0 && visited < 5000) {
189
+ const directory = queue.shift();
190
+ let entries;
191
+ try {
192
+ entries = fs.readdirSync(directory, { withFileTypes: true });
193
+ } catch {
194
+ continue;
195
+ }
196
+ entries.sort((left, right) =>
197
+ left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
198
+ for (const entry of entries) {
199
+ if (visited++ >= 5000) break;
200
+ if (entry.isDirectory()) {
201
+ if (!CONVENTION_IGNORES.has(entry.name) &&
202
+ !entry.name.startsWith('.')) {
203
+ queue.push(path.join(directory, entry.name));
204
+ }
205
+ continue;
206
+ }
207
+ const extension = path.extname(entry.name).toLowerCase();
208
+ if (CPP_EXTENSIONS.has(extension)) cpp++;
209
+ else if (C_EXTENSIONS.has(extension)) c++;
210
+ }
211
+ }
212
+ const language = cpp === c ? null : cpp > c ? 'cpp' : 'c';
213
+ conventionCache.set(root, language);
214
+ return language;
215
+ }
216
+
217
+ function detectHeaderLanguage(filePath, projectRoot = null) {
218
+ const originalExt = path.extname(filePath);
219
+ if (originalExt === '.H' || originalExt === '.HPP') return 'cpp';
220
+ const stem = filePath.slice(0, -originalExt.length);
221
+ for (const ext of CPP_EXTENSIONS) {
222
+ try { if (fs.statSync(stem + ext).isFile()) return 'cpp'; } catch { /* next */ }
223
+ }
224
+ for (const ext of C_EXTENSIONS) {
225
+ try { if (fs.statSync(stem + ext).isFile()) return 'c'; } catch { /* next */ }
226
+ }
227
+ const dbPath = findCompilationDatabase(path.dirname(filePath), projectRoot);
228
+ const database = loadCompilationDatabase(dbPath);
229
+ const nearest = nearestEntries(database, filePath);
230
+ if (nearest.length > 0) {
231
+ const closestDistance = directoryDistance(
232
+ path.dirname(filePath), path.dirname(nearest[0].file));
233
+ const closest = nearest.filter(entry =>
234
+ directoryDistance(path.dirname(filePath), path.dirname(entry.file)) === closestDistance);
235
+ const cpp = closest.filter(entry => entry.language === 'cpp').length;
236
+ const c = closest.filter(entry => entry.language === 'c').length;
237
+ if (cpp !== c) return cpp > c ? 'cpp' : 'c';
238
+ }
239
+ // No database: use neighboring translation-unit extensions as a
240
+ // deterministic project convention, without reading source text.
241
+ try {
242
+ const names = fs.readdirSync(path.dirname(filePath));
243
+ const cpp = names.filter(name => CPP_EXTENSIONS.has(path.extname(name).toLowerCase())).length;
244
+ const c = names.filter(name => C_EXTENSIONS.has(path.extname(name).toLowerCase())).length;
245
+ if (cpp !== c) return cpp > c ? 'cpp' : 'c';
246
+ } catch { /* default below */ }
247
+ const projectConvention = projectTranslationUnitConvention(filePath, projectRoot);
248
+ if (projectConvention) return projectConvention;
249
+ return 'c';
250
+ }
251
+
252
+ function includeDirectoriesForFile(filePath, projectRoot = null) {
253
+ const dbPath = findCompilationDatabase(path.dirname(filePath), projectRoot);
254
+ const database = loadCompilationDatabase(dbPath);
255
+ if (!database) return [];
256
+ const exact = database.entries.find(entry => path.resolve(entry.file) === path.resolve(filePath));
257
+ const candidates = exact ? [exact] : nearestEntries(database, filePath).slice(0, 8);
258
+ const result = [];
259
+ for (const entry of candidates) {
260
+ for (const directory of entry.includeDirs) {
261
+ if (!result.includes(directory)) result.push(directory);
262
+ }
263
+ }
264
+ return result;
265
+ }
266
+
267
+ module.exports = {
268
+ CPP_EXTENSIONS,
269
+ C_EXTENSIONS,
270
+ splitCommandLine,
271
+ findCompilationDatabase,
272
+ loadCompilationDatabase,
273
+ detectHeaderLanguage,
274
+ projectTranslationUnitConvention,
275
+ includeDirectoriesForFile,
276
+ };
@@ -81,6 +81,7 @@ function scored(resolution, reasons) {
81
81
  * @param {boolean} [evidence.resolvedByReceiverHint] - Receiver type narrowed via local hints
82
82
  * @param {boolean} [evidence.hasImportEvidence] - File imports the target definition
83
83
  * @param {boolean} [evidence.hasReceiverEvidence] - Receiver variable has binding in file scope
84
+ * @param {boolean} [evidence.hasSingleOwnerEvidence] - One eligible project type owns the method
84
85
  * @param {boolean} [evidence.isUncertain] - Marked uncertain by resolution logic
85
86
  * @param {boolean} [evidence.isFunctionReference] - Passed as callback argument
86
87
  * @param {boolean} [evidence.hasReceiverType] - Go/Java/Rust parser-inferred receiverType
@@ -143,10 +144,12 @@ function scoreEdge(evidence) {
143
144
  }
144
145
 
145
146
  // Scope/import-supported match
146
- if (evidence.hasImportEvidence || evidence.hasReceiverEvidence || evidence.hasSamePackageEvidence) {
147
+ if (evidence.hasImportEvidence || evidence.hasReceiverEvidence ||
148
+ evidence.hasSamePackageEvidence || evidence.hasSingleOwnerEvidence) {
147
149
  if (evidence.hasImportEvidence) reasons.push('import-supported');
148
150
  if (evidence.hasReceiverEvidence) reasons.push('receiver binding in scope');
149
151
  if (evidence.hasSamePackageEvidence) reasons.push('same package/module');
152
+ if (evidence.hasSingleOwnerEvidence) reasons.push('single project method owner');
150
153
  return scored(RESOLUTION.SCOPE_MATCH, reasons);
151
154
  }
152
155