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.
@@ -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.text;
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.text;
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.text : patternNode.text.replace(/^\.\.\./, '');
172
+ info.name = innerName ? nodeTextWithoutComments(innerName) : nodeTextWithoutComments(patternNode).replace(/^\.\.\./, '');
152
173
  info.rest = true;
153
174
  } else {
154
- info.name = patternNode.text;
175
+ info.name = nodeTextWithoutComments(patternNode);
155
176
  }
156
177
  }
157
- if (typeNode) info.type = typeNode.text.replace(/^:\s*/, '');
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.text;
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.text;
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.text;
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.text;
195
- if (rightNode) info.default = rightNode.text;
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.text;
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.text;
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.text;
209
- if (typeNode) info.type = typeNode.text;
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.text;
222
- if (valueNode) info.default = valueNode.text;
223
- if (typeNode) info.type = typeNode.text;
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.text;
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.text);
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.text;
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.text;
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.text;
285
+ if (nameNode) info.name = nodeTextWithoutComments(nameNode);
265
286
  else info.name = '...';
266
- if (typeNode) info.type = '...' + typeNode.text;
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.text;
276
- if (typeNode) info.type = typeNode.text;
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.text;
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.text;
298
- if (typeNode) info.type = typeNode.text;
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.7",
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",