ucn 5.4.0 → 5.4.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 +14 -4
- package/.claude/skills/ucn/references/commands.md +3 -3
- package/cli/index.js +3 -2
- package/core/accessors.js +48 -1
- package/core/analysis.js +144 -9
- package/core/ast-analysis.js +96 -0
- package/core/bridge.js +11 -6
- package/core/check.js +35 -6
- package/core/command-contracts.js +1 -1
- package/core/execute.js +4 -1
- package/core/output/analysis-ext.js +30 -6
- package/core/output/analysis.js +6 -4
- package/core/output/check.js +6 -2
- package/core/output/endpoints.js +10 -7
- package/core/output/find.js +4 -0
- package/core/output/lines.js +33 -5
- package/core/output/reporting.js +1 -1
- package/core/output-budget.js +30 -17
- package/core/registry.js +1 -1
- package/core/verify.js +23 -5
- package/mcp/server.js +3 -3
- package/package.json +2 -2
package/core/verify.js
CHANGED
|
@@ -247,14 +247,26 @@ function classifyCallContext(callNode, language) {
|
|
|
247
247
|
/**
|
|
248
248
|
* Find a call expression node at the target line matching funcName
|
|
249
249
|
*/
|
|
250
|
-
function findCallNode(node, callTypes, targetRow, funcName, occurrence = 0) {
|
|
250
|
+
function findCallNode(node, callTypes, targetRow, funcName, occurrence = 0, site = null) {
|
|
251
251
|
// Several same-name calls can share one line (`greet("a") + greet("b")`,
|
|
252
252
|
// f-strings) — fix #231: callers pass the site's per-line ordinal so each
|
|
253
253
|
// record is arg-checked against ITS OWN node, not the line's first.
|
|
254
254
|
// Records and this walk are both pre-order, so ordinals align; an
|
|
255
255
|
// out-of-range ordinal falls back to the first match (never worse than
|
|
256
256
|
// the pre-fix behavior when a parse shape hides a node).
|
|
257
|
-
const matches = _collectCallNodes(node, callTypes, targetRow, funcName, occurrence + 1);
|
|
257
|
+
const matches = _collectCallNodes(node, callTypes, targetRow, funcName, site ? Infinity : occurrence + 1);
|
|
258
|
+
if (Number.isInteger(site?.start)) {
|
|
259
|
+
// The resolver already identified the callee token. Preserve that
|
|
260
|
+
// identity through argument extraction, including aliases and several
|
|
261
|
+
// calls on one line (some of which may target another declaration).
|
|
262
|
+
const exact = matches.filter(candidate => {
|
|
263
|
+
const callee = candidate.childForFieldName('function') ||
|
|
264
|
+
candidate.childForFieldName('name') ||
|
|
265
|
+
candidate.childForFieldName('constructor') || candidate.childForFieldName('type');
|
|
266
|
+
return callee && callee.startIndex <= site.start && callee.endIndex >= site.end;
|
|
267
|
+
});
|
|
268
|
+
if (exact.length === 1) return exact[0];
|
|
269
|
+
}
|
|
258
270
|
return matches[occurrence] || matches[0] || null;
|
|
259
271
|
}
|
|
260
272
|
|
|
@@ -795,8 +807,10 @@ function computePlanCallSites(index, name, def) {
|
|
|
795
807
|
content: c.content,
|
|
796
808
|
usageType: 'call',
|
|
797
809
|
receiver: c.receiver,
|
|
810
|
+
calledAs: c.calledAs,
|
|
811
|
+
callSite: c.provenance?.facts?.site,
|
|
798
812
|
};
|
|
799
|
-
const siteKey = `${c.file}:${c.line}`;
|
|
813
|
+
const siteKey = `${c.file}:${c.line}:${c.calledAs || name}`;
|
|
800
814
|
const occurrence = planLineSeen.get(siteKey) || 0;
|
|
801
815
|
planLineSeen.set(siteKey, occurrence + 1);
|
|
802
816
|
const analysis = analyzeCallSite(index, call, name, occurrence);
|
|
@@ -898,7 +912,9 @@ function analyzeCallSite(index, call, funcName, occurrence = 0) {
|
|
|
898
912
|
const targetRow = call.line - 1; // tree-sitter is 0-indexed
|
|
899
913
|
|
|
900
914
|
// Find the call expression at the target line matching funcName
|
|
901
|
-
const
|
|
915
|
+
const spelling = call.calledAs && call.calledAs !== 'bound' ? call.calledAs : funcName;
|
|
916
|
+
const callNode = findCallNode(tree.rootNode, callTypes, targetRow, spelling, occurrence,
|
|
917
|
+
call.callSite || call.provenance?.facts?.site);
|
|
902
918
|
if (!callNode) return { args: null, argCount: 0 };
|
|
903
919
|
|
|
904
920
|
// Check if this is a method call (obj.func()) vs a direct call (func())
|
|
@@ -1353,6 +1369,8 @@ function verify(index, name, options = {}) {
|
|
|
1353
1369
|
content: c.content,
|
|
1354
1370
|
usageType: 'call',
|
|
1355
1371
|
receiver: c.receiver,
|
|
1372
|
+
calledAs: c.calledAs,
|
|
1373
|
+
callSite: c.provenance?.facts?.site,
|
|
1356
1374
|
// Preserve receiver identity through the usage-shaped adapter. Go
|
|
1357
1375
|
// permits a local value to have the same spelling as its type; only
|
|
1358
1376
|
// a type-qualified call is a method expression with an explicit
|
|
@@ -1394,7 +1412,7 @@ function verify(index, name, options = {}) {
|
|
|
1394
1412
|
|
|
1395
1413
|
const verifyLineSeen = new Map(); // 'file:line' -> per-line ordinal (fix #231)
|
|
1396
1414
|
for (const call of calls) {
|
|
1397
|
-
const siteKey = `${call.file}:${call.line}`;
|
|
1415
|
+
const siteKey = `${call.file}:${call.line}:${call.calledAs || name}`;
|
|
1398
1416
|
const occurrence = verifyLineSeen.get(siteKey) || 0;
|
|
1399
1417
|
verifyLineSeen.set(siteKey, occurrence + 1);
|
|
1400
1418
|
const analysis = analyzeCallSite(index, call, name, occurrence);
|
package/mcp/server.js
CHANGED
|
@@ -119,7 +119,7 @@ const server = new StdioMcpServer({
|
|
|
119
119
|
|
|
120
120
|
function toolResult(text, command, maxChars, suffixNote, params = {}) {
|
|
121
121
|
const suffix = suffixNote || '';
|
|
122
|
-
const budget = applyOutputBudget(text, {
|
|
122
|
+
const budget = applyOutputBudget(text + suffix, {
|
|
123
123
|
command,
|
|
124
124
|
maxChars,
|
|
125
125
|
surface: 'mcp',
|
|
@@ -130,7 +130,7 @@ function toolResult(text, command, maxChars, suffixNote, params = {}) {
|
|
|
130
130
|
// narrowing hints; preserved contract lines follow). A structuredContent
|
|
131
131
|
// side-channel is rendered INSTEAD of content by MCP clients that prefer
|
|
132
132
|
// structured results, which discards the entire answer (fix #284).
|
|
133
|
-
return { content: [{ type: 'text', text: budget.text
|
|
133
|
+
return { content: [{ type: 'text', text: budget.text }] };
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
function toolError(message) {
|
|
@@ -307,7 +307,7 @@ const INPUT_SHAPE = {
|
|
|
307
307
|
line: integerParam('Definition line pin. Resolves the symbol defined at this exact line (the middle component of a file:line:name handle). Disambiguates same-file same-name definitions.', { exclusiveMinimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
|
|
308
308
|
limit: integerParam('Max results to return (default: 500; structural search: 50; usages and lines: uncapped). Caps find, usages, search, deadcode, api, and repo files. Must be a positive integer.', { exclusiveMinimum: 0, maximum: 1000000 }),
|
|
309
309
|
max_files: integerParam('Max files to index (default: 10000). Use for very large codebases. Must be a positive integer.', { exclusiveMinimum: 0, maximum: 10000000 }),
|
|
310
|
-
max_chars: integerParam('Max output
|
|
310
|
+
max_chars: integerParam('Max output characters, not UTF-8 bytes, including notes and preserved metadata. Broad sweep commands (repo, entrypoints, endpoints, deadcode, deps, check, audit_async) default to 3K; all other commands default to 10K. Maximum: 100K. all=true lifts formatter caps but keeps the 100K transport ceiling.', { exclusiveMinimum: 0, maximum: 100000 }),
|
|
311
311
|
type: stringParam('Symbol type filter for structural search: function, class, call, method, type, state, field, constant, macro. Triggers index-based search.'),
|
|
312
312
|
param: stringParam('Filter by parameter name or type (structural search). E.g. "Request", "ctx".'),
|
|
313
313
|
receiver: stringParam('Filter calls by receiver (structural search, type=call). E.g. "db", "http".'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.2",
|
|
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.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",
|
|
13
|
+
"test": "node --test test/audit-5.4.0.test.js 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",
|