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/core/index-ir.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert normalized language IR into the persisted ProjectIndex shape.
|
|
5
|
+
*
|
|
6
|
+
* This is the only translation boundary used by sequential and worker builds.
|
|
7
|
+
* Keeping it data-only prevents the two build paths from silently dropping
|
|
8
|
+
* parser fields or changing symbol/binding behavior.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function createImportBindings(imports) {
|
|
12
|
+
return imports.flatMap(item => (item.names || [])
|
|
13
|
+
.filter(name => name && name !== '*' && name !== '_' && name !== '.')
|
|
14
|
+
.map(name => {
|
|
15
|
+
const rename = (item.renames || []).find(candidate => candidate.original === name);
|
|
16
|
+
return {
|
|
17
|
+
name,
|
|
18
|
+
module: item.module,
|
|
19
|
+
...(item.line != null && { line: item.line }),
|
|
20
|
+
...(rename && { alias: rename.local }),
|
|
21
|
+
...(item.defaultLike && { defaultLike: true }),
|
|
22
|
+
};
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createFileEntryFromIR({
|
|
27
|
+
ir,
|
|
28
|
+
filePath,
|
|
29
|
+
relativePath,
|
|
30
|
+
hash,
|
|
31
|
+
mtime,
|
|
32
|
+
size,
|
|
33
|
+
lineCount,
|
|
34
|
+
isBundled = false,
|
|
35
|
+
isGenerated = false,
|
|
36
|
+
}) {
|
|
37
|
+
const imports = ir.imports || [];
|
|
38
|
+
const exports = ir.exports || [];
|
|
39
|
+
return {
|
|
40
|
+
path: filePath,
|
|
41
|
+
relativePath,
|
|
42
|
+
language: ir.language,
|
|
43
|
+
lines: lineCount,
|
|
44
|
+
hash,
|
|
45
|
+
mtime,
|
|
46
|
+
size,
|
|
47
|
+
imports: imports.map(item => item.module),
|
|
48
|
+
globalImports: imports.filter(item => item.global).map(item => item.module),
|
|
49
|
+
importNames: imports.flatMap(item => item.names || []),
|
|
50
|
+
importBindings: createImportBindings(imports),
|
|
51
|
+
exports: exports.map(item => item.name),
|
|
52
|
+
exportDetails: exports,
|
|
53
|
+
symbols: [],
|
|
54
|
+
bindings: [],
|
|
55
|
+
dynamicImports: ir.dynamicImports || 0,
|
|
56
|
+
...(ir.diagnostics?.parseRecovery && { parseRecovery: true }),
|
|
57
|
+
...(ir.importAliases && { importAliases: ir.importAliases }),
|
|
58
|
+
...(ir.moduleAssignedNames?.length > 0 && {
|
|
59
|
+
moduleAssignedNames: ir.moduleAssignedNames,
|
|
60
|
+
}),
|
|
61
|
+
...(isBundled && { isBundled: true }),
|
|
62
|
+
...(isGenerated && { isGenerated: true }),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const OPTIONAL_SYMBOL_FIELDS = Object.freeze([
|
|
67
|
+
'returnedFunctionResult', 'isFunctionVariable', 'paramTypes', 'isAsync',
|
|
68
|
+
'isGenerator', 'generics', 'extends', 'implements', 'indent', 'isNested',
|
|
69
|
+
'enclosingType', 'isMethod', 'receiver', 'memberType', 'fieldType',
|
|
70
|
+
'aliasOf', 'derefTarget', 'decorators', 'decoratorsWithArgs',
|
|
71
|
+
'annotationsWithArgs', 'attributesWithArgs', 'nameLine', 'traitImpl',
|
|
72
|
+
'traitName', 'isSignature', 'memberAssigned', 'bodyScopedName',
|
|
73
|
+
'registryMember', 'registryContainer', 'namespace',
|
|
74
|
+
'isExtensionMethod', 'extensionReceiver', 'explicitInterface',
|
|
75
|
+
'lexicalScopeStartLine', 'lexicalScopeEndLine',
|
|
76
|
+
'returnTypeQualifier', 'macroNeverReturns', 'callbackParamTypes', 'iteratorItemType',
|
|
77
|
+
'returnedConcreteType', 'returnedConstructors', 'templateDependent',
|
|
78
|
+
'linkage', 'functionLike',
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
function materializeSymbol(fileEntry, item) {
|
|
82
|
+
const symbol = {
|
|
83
|
+
name: item.name,
|
|
84
|
+
type: item.kind,
|
|
85
|
+
file: fileEntry.path,
|
|
86
|
+
relativePath: fileEntry.relativePath,
|
|
87
|
+
startLine: item.startLine,
|
|
88
|
+
endLine: item.endLine,
|
|
89
|
+
params: item.params,
|
|
90
|
+
paramsStructured: item.paramsStructured,
|
|
91
|
+
returnType: item.returnType,
|
|
92
|
+
modifiers: item.modifiers,
|
|
93
|
+
docstring: item.docstring,
|
|
94
|
+
bindingId: `${fileEntry.relativePath}:${item.kind}:${item.startLine}`,
|
|
95
|
+
...(item.owner && { className: item.owner }),
|
|
96
|
+
};
|
|
97
|
+
for (const field of OPTIONAL_SYMBOL_FIELDS) {
|
|
98
|
+
if (item[field] === undefined || item[field] === null) continue;
|
|
99
|
+
if (Array.isArray(item[field]) && item[field].length === 0) continue;
|
|
100
|
+
// Most false feature flags are omitted for compactness, but
|
|
101
|
+
// functionLike=false is the semantic distinction between an
|
|
102
|
+
// object-like macro and a callable macro (UCN5-170).
|
|
103
|
+
if (item[field] === false && field !== 'functionLike') continue;
|
|
104
|
+
symbol[field] = item[field];
|
|
105
|
+
}
|
|
106
|
+
return symbol;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function addIRSymbol(fileEntry, item, symbolTable = null) {
|
|
110
|
+
const symbol = materializeSymbol(fileEntry, item);
|
|
111
|
+
fileEntry.symbols.push(symbol);
|
|
112
|
+
if (!item.memberAssigned && !item.bodyScopedName) {
|
|
113
|
+
fileEntry.bindings.push({
|
|
114
|
+
id: symbol.bindingId,
|
|
115
|
+
name: symbol.name,
|
|
116
|
+
type: symbol.type,
|
|
117
|
+
startLine: symbol.startLine,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (symbolTable) {
|
|
121
|
+
if (!symbolTable.has(symbol.name)) symbolTable.set(symbol.name, []);
|
|
122
|
+
symbolTable.get(symbol.name).push(symbol);
|
|
123
|
+
}
|
|
124
|
+
return symbol;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function populateFileEntryFromIR(fileEntry, ir, symbolTable = null) {
|
|
128
|
+
for (const symbol of ir.symbols) addIRSymbol(fileEntry, symbol, symbolTable);
|
|
129
|
+
return fileEntry;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
createImportBindings,
|
|
134
|
+
createFileEntryFromIR,
|
|
135
|
+
materializeSymbol,
|
|
136
|
+
addIRSymbol,
|
|
137
|
+
populateFileEntryFromIR,
|
|
138
|
+
};
|
package/core/ir.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalized language-analysis boundary for v5.
|
|
5
|
+
*
|
|
6
|
+
* ProjectIndex and build workers consume this shape directly. Language
|
|
7
|
+
* adapters must not leak tree-sitter nodes or raw parser-module objects across
|
|
8
|
+
* this boundary. The shape is deliberately data-only and versioned.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const IR_SCHEMA_VERSION = 1;
|
|
12
|
+
const EVIDENCE_TIERS = Object.freeze(['confirmed', 'unverified', 'excluded']);
|
|
13
|
+
|
|
14
|
+
function normalizeSymbol(symbol, family, language, kind, owner = null) {
|
|
15
|
+
let normalizedOwner = owner || symbol.className || null;
|
|
16
|
+
if (!normalizedOwner && symbol.receiver && family === 'callable') {
|
|
17
|
+
// Go and some Rust parser records expose methods as top-level
|
|
18
|
+
// functions with a receiver. Normalize their owning type here so
|
|
19
|
+
// every index consumer sees the same method identity.
|
|
20
|
+
normalizedOwner = String(symbol.receiver)
|
|
21
|
+
.replace(/^[*&]\s*/, '')
|
|
22
|
+
.replace(/^mut\s+/, '')
|
|
23
|
+
.replace(/<.*$/, '')
|
|
24
|
+
.trim();
|
|
25
|
+
if (normalizedOwner === 'self' || normalizedOwner === 'Self') {
|
|
26
|
+
normalizedOwner = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const normalized = {
|
|
30
|
+
id: symbol.bindingId || null,
|
|
31
|
+
name: symbol.name,
|
|
32
|
+
kind,
|
|
33
|
+
family,
|
|
34
|
+
language,
|
|
35
|
+
startLine: symbol.startLine,
|
|
36
|
+
endLine: symbol.endLine,
|
|
37
|
+
...(normalizedOwner && { owner: normalizedOwner }),
|
|
38
|
+
...(symbol.receiver && { receiver: symbol.receiver }),
|
|
39
|
+
...(symbol.params !== undefined && { params: symbol.params }),
|
|
40
|
+
...(symbol.paramsStructured && { paramsStructured: symbol.paramsStructured }),
|
|
41
|
+
...(symbol.returnType && { returnType: symbol.returnType }),
|
|
42
|
+
modifiers: [...(symbol.modifiers || [])],
|
|
43
|
+
};
|
|
44
|
+
const passthrough = [
|
|
45
|
+
'docstring', 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes',
|
|
46
|
+
'isAsync', 'isGenerator', 'generics', 'extends', 'implements', 'indent',
|
|
47
|
+
'isNested', 'enclosingType', 'isMethod', 'memberType', 'fieldType',
|
|
48
|
+
'aliasOf', 'derefTarget', 'decorators', 'decoratorsWithArgs',
|
|
49
|
+
'annotationsWithArgs', 'attributesWithArgs', 'nameLine', 'traitImpl',
|
|
50
|
+
'traitName', 'isSignature', 'memberAssigned', 'bodyScopedName',
|
|
51
|
+
'registryMember', 'registryContainer', 'isConstructor',
|
|
52
|
+
'isExtensionMethod', 'extensionReceiver', 'explicitInterface',
|
|
53
|
+
'namespace', 'lexicalScopeStartLine', 'lexicalScopeEndLine',
|
|
54
|
+
'returnTypeQualifier', 'macroNeverReturns', 'callbackParamTypes', 'iteratorItemType',
|
|
55
|
+
'returnedConcreteType', 'returnedConstructors', 'templateDependent',
|
|
56
|
+
'linkage', 'functionLike',
|
|
57
|
+
];
|
|
58
|
+
for (const field of passthrough) {
|
|
59
|
+
if (symbol[field] !== undefined && symbol[field] !== null) {
|
|
60
|
+
normalized[field] = Array.isArray(symbol[field])
|
|
61
|
+
? [...symbol[field]]
|
|
62
|
+
: symbol[field];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createFileIR({
|
|
69
|
+
language,
|
|
70
|
+
file = null,
|
|
71
|
+
parsed = {},
|
|
72
|
+
calls = [],
|
|
73
|
+
capabilities = {},
|
|
74
|
+
} = {}) {
|
|
75
|
+
const functions = parsed.functions || [];
|
|
76
|
+
const classes = parsed.classes || [];
|
|
77
|
+
const stateObjects = parsed.stateObjects || [];
|
|
78
|
+
const macros = parsed.macros || [];
|
|
79
|
+
const normalizedSymbols = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const append = (symbol, family, kind, owner = null) => {
|
|
82
|
+
const key = `${owner || symbol.className || ''}\0${symbol.name}\0${kind}\0` +
|
|
83
|
+
`${symbol.startLine}\0${symbol.endLine}`;
|
|
84
|
+
if (seen.has(key)) return;
|
|
85
|
+
seen.add(key);
|
|
86
|
+
normalizedSymbols.push(normalizeSymbol(symbol, family, language, kind, owner));
|
|
87
|
+
};
|
|
88
|
+
for (const symbol of functions) {
|
|
89
|
+
append(symbol, 'callable',
|
|
90
|
+
symbol.type || (symbol.isConstructor ? 'constructor' : 'function'));
|
|
91
|
+
}
|
|
92
|
+
for (const type of classes) {
|
|
93
|
+
append(type, 'type', type.type || 'class');
|
|
94
|
+
for (const member of type.members || []) {
|
|
95
|
+
const inherited = {
|
|
96
|
+
...member,
|
|
97
|
+
...(type.namespace && member.namespace == null && {
|
|
98
|
+
namespace: type.namespace,
|
|
99
|
+
}),
|
|
100
|
+
...(type.enclosingType && member.enclosingType == null && {
|
|
101
|
+
enclosingType: type.enclosingType,
|
|
102
|
+
}),
|
|
103
|
+
...(type.traitName && {
|
|
104
|
+
traitImpl: true,
|
|
105
|
+
traitName: type.traitName,
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
append(inherited,
|
|
109
|
+
inherited.memberType === 'field' ? 'state' : 'callable',
|
|
110
|
+
inherited.memberType || (inherited.isConstructor ? 'constructor' : 'method'),
|
|
111
|
+
type.name);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const symbol of stateObjects) append(symbol, 'state', 'state');
|
|
115
|
+
for (const symbol of macros) append(symbol,
|
|
116
|
+
symbol.functionLike ? 'callable' : 'state', 'macro');
|
|
117
|
+
const imports = [...(parsed.imports || [])];
|
|
118
|
+
return {
|
|
119
|
+
schemaVersion: IR_SCHEMA_VERSION,
|
|
120
|
+
language,
|
|
121
|
+
file,
|
|
122
|
+
totalLines: parsed.totalLines || 0,
|
|
123
|
+
symbols: normalizedSymbols,
|
|
124
|
+
calls: [...calls],
|
|
125
|
+
imports,
|
|
126
|
+
exports: [...(parsed.exports || [])],
|
|
127
|
+
dynamicImports: imports.filter(item => item.dynamic).length,
|
|
128
|
+
importAliases: parsed.imports?.aliases || null,
|
|
129
|
+
moduleAssignedNames: [...(parsed.moduleAssignedNames || [])],
|
|
130
|
+
diagnostics: {
|
|
131
|
+
parseRecovery: !!parsed.parseRecovery,
|
|
132
|
+
},
|
|
133
|
+
capabilities: { ...capabilities },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function validateFileIR(ir) {
|
|
138
|
+
const failures = [];
|
|
139
|
+
if (!ir || typeof ir !== 'object') return ['IR must be an object'];
|
|
140
|
+
if (ir.schemaVersion !== IR_SCHEMA_VERSION) {
|
|
141
|
+
failures.push(`schemaVersion must be ${IR_SCHEMA_VERSION}`);
|
|
142
|
+
}
|
|
143
|
+
if (typeof ir.language !== 'string' || !ir.language) failures.push('language is required');
|
|
144
|
+
for (const field of ['symbols', 'calls', 'imports', 'exports']) {
|
|
145
|
+
if (!Array.isArray(ir[field])) failures.push(`${field} must be an array`);
|
|
146
|
+
}
|
|
147
|
+
if (!Number.isInteger(ir.totalLines) || ir.totalLines < 0) {
|
|
148
|
+
failures.push('totalLines must be a non-negative integer');
|
|
149
|
+
}
|
|
150
|
+
for (const [index, symbol] of (ir.symbols || []).entries()) {
|
|
151
|
+
if (!symbol?.name) failures.push(`symbols[${index}].name is required`);
|
|
152
|
+
if (!symbol?.kind) failures.push(`symbols[${index}].kind is required`);
|
|
153
|
+
if (!symbol?.family) failures.push(`symbols[${index}].family is required`);
|
|
154
|
+
if (!Number.isInteger(symbol?.startLine) || symbol.startLine < 1) {
|
|
155
|
+
failures.push(`symbols[${index}].startLine must be a positive integer`);
|
|
156
|
+
}
|
|
157
|
+
if (!Number.isInteger(symbol?.endLine) || symbol.endLine < symbol.startLine) {
|
|
158
|
+
failures.push(`symbols[${index}].endLine must be >= startLine`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return failures;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createEvidenceEdge({
|
|
165
|
+
from = null,
|
|
166
|
+
to = null,
|
|
167
|
+
site,
|
|
168
|
+
tier,
|
|
169
|
+
resolution,
|
|
170
|
+
reason = null,
|
|
171
|
+
evidence = {},
|
|
172
|
+
} = {}) {
|
|
173
|
+
if (!EVIDENCE_TIERS.includes(tier)) {
|
|
174
|
+
throw new Error(`Unknown evidence tier: ${tier}`);
|
|
175
|
+
}
|
|
176
|
+
if (!resolution) throw new Error('Evidence resolution is required');
|
|
177
|
+
return {
|
|
178
|
+
schemaVersion: IR_SCHEMA_VERSION,
|
|
179
|
+
from,
|
|
180
|
+
to,
|
|
181
|
+
site,
|
|
182
|
+
tier,
|
|
183
|
+
resolution,
|
|
184
|
+
...(reason && { reason }),
|
|
185
|
+
evidence: { ...evidence },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
IR_SCHEMA_VERSION,
|
|
191
|
+
EVIDENCE_TIERS,
|
|
192
|
+
createFileIR,
|
|
193
|
+
validateFileIR,
|
|
194
|
+
createEvidenceEdge,
|
|
195
|
+
};
|