ucn 5.3.7 → 5.4.0
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 +35 -3
- package/.claude/skills/ucn/references/commands.md +15 -1
- package/cli/index.js +4 -2
- package/core/account.js +4 -0
- package/core/analysis.js +80 -1
- package/core/cache.js +3 -1
- package/core/callers.js +21 -2
- package/core/execute.js +24 -4
- package/core/graph.js +6 -2
- package/core/output/analysis.js +5 -5
- package/core/output/doctor.js +1 -1
- package/core/output/lines.js +8 -2
- package/core/output/public.js +16 -4
- package/core/output/refactoring.js +4 -1
- package/core/output/reporting.js +1 -1
- package/core/output/search.js +6 -1
- package/core/project.js +5 -0
- package/core/registry.js +1 -1
- package/core/reporting.js +2 -0
- package/core/search.js +13 -2
- package/core/shared.js +13 -4
- package/core/verify.js +6 -1
- package/languages/c-family.js +9 -8
- package/languages/csharp.js +11 -10
- package/languages/go.js +8 -7
- package/languages/index.js +4 -0
- package/languages/java.js +3 -2
- package/languages/javascript.js +2 -1
- package/languages/python.js +11 -3
- package/languages/rust.js +3 -2
- package/languages/utils.js +51 -29
- package/package.json +2 -2
package/languages/utils.js
CHANGED
|
@@ -54,11 +54,32 @@ function extractParams(paramsNode) {
|
|
|
54
54
|
// functions that actually take zero arguments. Empty → '' so callers can
|
|
55
55
|
// render `main()` cleanly.
|
|
56
56
|
if (!paramsNode) return '...';
|
|
57
|
-
const text = paramsNode
|
|
57
|
+
const text = nodeTextWithoutComments(paramsNode);
|
|
58
58
|
const stripped = text.replace(/^\(|\)$/g, '').trim();
|
|
59
59
|
return stripped; // '' for empty params, '...' only when paramsNode missing
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Signature text without AST comment nodes. Keep source offsets/line breaks
|
|
64
|
+
* intact for declarator slicing, and preserve strings (including forward
|
|
65
|
+
* annotations and defaults containing comment-like text) verbatim.
|
|
66
|
+
*/
|
|
67
|
+
function nodeTextWithoutComments(node) {
|
|
68
|
+
if (!node) return '';
|
|
69
|
+
const text = node.text;
|
|
70
|
+
const parts = [];
|
|
71
|
+
let offset = 0;
|
|
72
|
+
traverseTree(node, child => {
|
|
73
|
+
if (child.type !== 'comment' && !child.type.endsWith('_comment')) return true;
|
|
74
|
+
const start = child.startIndex - node.startIndex;
|
|
75
|
+
const end = child.endIndex - node.startIndex;
|
|
76
|
+
parts.push(text.slice(offset, start), text.slice(start, end).replace(/[^\r\n]/g, ' '));
|
|
77
|
+
offset = end;
|
|
78
|
+
return false;
|
|
79
|
+
});
|
|
80
|
+
return parts.length ? parts.join('') + text.slice(offset) : text;
|
|
81
|
+
}
|
|
82
|
+
|
|
62
83
|
/**
|
|
63
84
|
* Parse parameters into structured format
|
|
64
85
|
* @param {object} paramsNode - Tree-sitter parameters node
|
|
@@ -140,7 +161,7 @@ function parseStructuredParams(paramsNode, language) {
|
|
|
140
161
|
|
|
141
162
|
function parseJSParam(param, info) {
|
|
142
163
|
if (param.type === 'identifier') {
|
|
143
|
-
info.name = param
|
|
164
|
+
info.name = nodeTextWithoutComments(param);
|
|
144
165
|
} else if (param.type === 'required_parameter' || param.type === 'optional_parameter') {
|
|
145
166
|
const patternNode = param.childForFieldName('pattern');
|
|
146
167
|
const typeNode = param.childForFieldName('type');
|
|
@@ -148,18 +169,18 @@ function parseJSParam(param, info) {
|
|
|
148
169
|
// Check if pattern is a rest_pattern (e.g., ...args inside required_parameter)
|
|
149
170
|
if (patternNode.type === 'rest_pattern') {
|
|
150
171
|
const innerName = patternNode.namedChild(0);
|
|
151
|
-
info.name = innerName ? innerName
|
|
172
|
+
info.name = innerName ? nodeTextWithoutComments(innerName) : nodeTextWithoutComments(patternNode).replace(/^\.\.\./, '');
|
|
152
173
|
info.rest = true;
|
|
153
174
|
} else {
|
|
154
|
-
info.name = patternNode
|
|
175
|
+
info.name = nodeTextWithoutComments(patternNode);
|
|
155
176
|
}
|
|
156
177
|
}
|
|
157
|
-
if (typeNode) info.type = typeNode.
|
|
178
|
+
if (typeNode) info.type = nodeTextWithoutComments(typeNode).replace(/^:\s*/, '');
|
|
158
179
|
if (param.type === 'optional_parameter') info.optional = true;
|
|
159
180
|
// Check for default value (e.g., priority: number = 1)
|
|
160
181
|
const valueNode = param.childForFieldName('value');
|
|
161
182
|
if (valueNode) {
|
|
162
|
-
info.default = valueNode
|
|
183
|
+
info.default = nodeTextWithoutComments(valueNode);
|
|
163
184
|
info.optional = true;
|
|
164
185
|
} else if (!info.rest) {
|
|
165
186
|
// Also check for bare number/string/etc. children as defaults.
|
|
@@ -170,14 +191,14 @@ function parseJSParam(param, info) {
|
|
|
170
191
|
// wrecking expectedArgs.min and the signature display).
|
|
171
192
|
const NON_DEFAULT_PARAM_CHILDREN = new Set([
|
|
172
193
|
'identifier', 'type_annotation', 'rest_pattern',
|
|
173
|
-
'accessibility_modifier', 'override_modifier', 'readonly', 'decorator',
|
|
194
|
+
'accessibility_modifier', 'override_modifier', 'readonly', 'decorator', 'comment',
|
|
174
195
|
]);
|
|
175
196
|
for (let i = 0; i < param.namedChildCount; i++) {
|
|
176
197
|
const child = param.namedChild(i);
|
|
177
198
|
if (child !== patternNode && child !== (typeNode && typeNode.parent === param ? typeNode : null) &&
|
|
178
199
|
!NON_DEFAULT_PARAM_CHILDREN.has(child.type)) {
|
|
179
200
|
// This is likely a default value node
|
|
180
|
-
info.default = child
|
|
201
|
+
info.default = nodeTextWithoutComments(child);
|
|
181
202
|
info.optional = true;
|
|
182
203
|
break;
|
|
183
204
|
}
|
|
@@ -186,27 +207,27 @@ function parseJSParam(param, info) {
|
|
|
186
207
|
} else if (param.type === 'rest_parameter' || param.type === 'rest_pattern') {
|
|
187
208
|
// rest_parameter = TypeScript, rest_pattern = JavaScript
|
|
188
209
|
const patternNode = param.childForFieldName('pattern') || param.namedChild(0);
|
|
189
|
-
if (patternNode) info.name = patternNode
|
|
210
|
+
if (patternNode) info.name = nodeTextWithoutComments(patternNode);
|
|
190
211
|
info.rest = true;
|
|
191
212
|
} else if (param.type === 'assignment_pattern') {
|
|
192
213
|
const leftNode = param.childForFieldName('left');
|
|
193
214
|
const rightNode = param.childForFieldName('right');
|
|
194
|
-
if (leftNode) info.name = leftNode
|
|
195
|
-
if (rightNode) info.default = rightNode
|
|
215
|
+
if (leftNode) info.name = nodeTextWithoutComments(leftNode);
|
|
216
|
+
if (rightNode) info.default = nodeTextWithoutComments(rightNode);
|
|
196
217
|
} else if (param.type === 'object_pattern' || param.type === 'array_pattern') {
|
|
197
218
|
// Destructured params: { name, value } or [a, b]
|
|
198
|
-
info.name = param
|
|
219
|
+
info.name = nodeTextWithoutComments(param);
|
|
199
220
|
}
|
|
200
221
|
}
|
|
201
222
|
|
|
202
223
|
function parsePythonParam(param, info) {
|
|
203
224
|
if (param.type === 'identifier') {
|
|
204
|
-
info.name = param
|
|
225
|
+
info.name = nodeTextWithoutComments(param);
|
|
205
226
|
} else if (param.type === 'typed_parameter') {
|
|
206
227
|
const nameNode = param.namedChild(0);
|
|
207
228
|
const typeNode = param.childForFieldName('type');
|
|
208
|
-
if (nameNode) info.name = nameNode
|
|
209
|
-
if (typeNode) info.type = typeNode
|
|
229
|
+
if (nameNode) info.name = nodeTextWithoutComments(nameNode);
|
|
230
|
+
if (typeNode) info.type = nodeTextWithoutComments(typeNode);
|
|
210
231
|
// Python wraps annotated splats in typed_parameter, with the actual
|
|
211
232
|
// `*args` / `**kwargs` node as its first named child. Treating those
|
|
212
233
|
// as ordinary required parameters makes every short call look broken.
|
|
@@ -218,12 +239,12 @@ function parsePythonParam(param, info) {
|
|
|
218
239
|
const nameNode = param.childForFieldName('name');
|
|
219
240
|
const valueNode = param.childForFieldName('value');
|
|
220
241
|
const typeNode = param.childForFieldName('type');
|
|
221
|
-
if (nameNode) info.name = nameNode
|
|
222
|
-
if (valueNode) info.default = valueNode
|
|
223
|
-
if (typeNode) info.type = typeNode
|
|
242
|
+
if (nameNode) info.name = nodeTextWithoutComments(nameNode);
|
|
243
|
+
if (valueNode) info.default = nodeTextWithoutComments(valueNode);
|
|
244
|
+
if (typeNode) info.type = nodeTextWithoutComments(typeNode);
|
|
224
245
|
info.optional = true;
|
|
225
246
|
} else if (param.type === 'list_splat_pattern' || param.type === 'dictionary_splat_pattern') {
|
|
226
|
-
info.name = param
|
|
247
|
+
info.name = nodeTextWithoutComments(param);
|
|
227
248
|
info.rest = true;
|
|
228
249
|
}
|
|
229
250
|
}
|
|
@@ -237,11 +258,11 @@ function parseGoParam(param, info) {
|
|
|
237
258
|
for (let i = 0; i < param.namedChildCount; i++) {
|
|
238
259
|
const child = param.namedChild(i);
|
|
239
260
|
if (child && child.type === 'identifier') {
|
|
240
|
-
names.push(child
|
|
261
|
+
names.push(nodeTextWithoutComments(child));
|
|
241
262
|
}
|
|
242
263
|
}
|
|
243
264
|
if (names.length > 0) info.name = names[0];
|
|
244
|
-
if (typeNode) info.type = typeNode
|
|
265
|
+
if (typeNode) info.type = nodeTextWithoutComments(typeNode);
|
|
245
266
|
// Interface method declarations commonly omit parameter names:
|
|
246
267
|
// `Match(*http.Request, *RouteMatch) bool`. These are still two real
|
|
247
268
|
// signature slots. Dropping them made verify/plan see zero arguments
|
|
@@ -249,7 +270,7 @@ function parseGoParam(param, info) {
|
|
|
249
270
|
// slot. Preserve the authored type as the display token while marking
|
|
250
271
|
// it unnamed so signature consumers can distinguish it from a name.
|
|
251
272
|
if (names.length === 0 && typeNode) {
|
|
252
|
-
info.name = typeNode
|
|
273
|
+
info.name = nodeTextWithoutComments(typeNode);
|
|
253
274
|
info.unnamed = true;
|
|
254
275
|
delete info.type;
|
|
255
276
|
}
|
|
@@ -261,9 +282,9 @@ function parseGoParam(param, info) {
|
|
|
261
282
|
// Go variadic: `args ...int`
|
|
262
283
|
const nameNode = param.childForFieldName('name');
|
|
263
284
|
const typeNode = param.childForFieldName('type');
|
|
264
|
-
if (nameNode) info.name = nameNode
|
|
285
|
+
if (nameNode) info.name = nodeTextWithoutComments(nameNode);
|
|
265
286
|
else info.name = '...';
|
|
266
|
-
if (typeNode) info.type = '...' + typeNode
|
|
287
|
+
if (typeNode) info.type = '...' + nodeTextWithoutComments(typeNode);
|
|
267
288
|
info.rest = true;
|
|
268
289
|
}
|
|
269
290
|
}
|
|
@@ -272,10 +293,10 @@ function parseRustParam(param, info) {
|
|
|
272
293
|
if (param.type === 'parameter') {
|
|
273
294
|
const patternNode = param.childForFieldName('pattern');
|
|
274
295
|
const typeNode = param.childForFieldName('type');
|
|
275
|
-
if (patternNode) info.name = patternNode
|
|
276
|
-
if (typeNode) info.type = typeNode
|
|
296
|
+
if (patternNode) info.name = nodeTextWithoutComments(patternNode);
|
|
297
|
+
if (typeNode) info.type = nodeTextWithoutComments(typeNode);
|
|
277
298
|
} else if (param.type === 'self_parameter') {
|
|
278
|
-
info.name = param
|
|
299
|
+
info.name = nodeTextWithoutComments(param);
|
|
279
300
|
}
|
|
280
301
|
}
|
|
281
302
|
|
|
@@ -294,8 +315,8 @@ function parseJavaParam(param, info) {
|
|
|
294
315
|
}
|
|
295
316
|
}
|
|
296
317
|
}
|
|
297
|
-
if (nameNode) info.name = nameNode
|
|
298
|
-
if (typeNode) info.type = typeNode
|
|
318
|
+
if (nameNode) info.name = nodeTextWithoutComments(nameNode);
|
|
319
|
+
if (typeNode) info.type = nodeTextWithoutComments(typeNode);
|
|
299
320
|
if (param.type === 'spread_parameter') info.rest = true;
|
|
300
321
|
}
|
|
301
322
|
}
|
|
@@ -1051,6 +1072,7 @@ function sameNode(a, b) {
|
|
|
1051
1072
|
}
|
|
1052
1073
|
|
|
1053
1074
|
module.exports = {
|
|
1075
|
+
nodeTextWithoutComments,
|
|
1054
1076
|
sameNode,
|
|
1055
1077
|
traverseTree,
|
|
1056
1078
|
traverseTreeCached,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Auditable AST code intelligence for AI agents: 18 task-oriented commands through one MCP tool, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java, C, C++, C#, and HTML.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"version": "node scripts/sync-server-version.js && git add server.json",
|
|
13
|
-
"test": "node --test test/audit-5.3.6.test.js test/evidence-provenance.test.js test/provenance-unit.test.js test/shell-output.test.js test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-c-family.test.js test/regression-cross.test.js test/regression-mcp.test.js test/mcp-protocol.test.js test/mcp-sdk-compat.test.js test/dependency-security.test.js test/regression-parser.test.js test/regression-commands.test.js test/regression-fixes.test.js test/regression-bugfixes.test.js test/release-surface-regressions.test.js test/prerelease-audit.test.js test/release-readiness-audit.test.js test/cross-language.test.js test/accuracy.test.js test/command-coverage.test.js test/perf-optimizations.test.js test/performance-gate-policy.test.js test/oracle-gate-policy.test.js test/outcome-policy.test.js test/consistency-eval.test.js test/agent-public-surface-benchmark.test.js test/command-contracts.test.js test/language-adapter.test.js test/systematic-test.js test/mcp-edge-cases.js test/conservation.test.js test/parity-test.js test/trust-matrix.test.js",
|
|
13
|
+
"test": "node --test test/audit-5.3.8.test.js test/audit-5.3.7.test.js test/audit-5.3.6.test.js test/evidence-provenance.test.js test/provenance-unit.test.js test/shell-output.test.js test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-c-family.test.js test/regression-cross.test.js test/regression-mcp.test.js test/mcp-protocol.test.js test/mcp-sdk-compat.test.js test/dependency-security.test.js test/regression-parser.test.js test/regression-commands.test.js test/regression-fixes.test.js test/regression-bugfixes.test.js test/release-surface-regressions.test.js test/prerelease-audit.test.js test/release-readiness-audit.test.js test/cross-language.test.js test/accuracy.test.js test/command-coverage.test.js test/perf-optimizations.test.js test/performance-gate-policy.test.js test/oracle-gate-policy.test.js test/outcome-policy.test.js test/consistency-eval.test.js test/agent-public-surface-benchmark.test.js test/command-contracts.test.js test/language-adapter.test.js test/systematic-test.js test/mcp-edge-cases.js test/conservation.test.js test/parity-test.js test/trust-matrix.test.js",
|
|
14
14
|
"benchmark:agent": "node test/agent-public-surface-benchmark.js",
|
|
15
15
|
"benchmark:agent:gate": "node test/agent-public-surface-benchmark.js --gate",
|
|
16
16
|
"benchmark:agent:legacy": "node test/agent-understanding-benchmark.js",
|