ucn 3.7.7 → 3.7.9
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/README.md +2 -1
- package/core/project.js +20 -5
- package/package.json +1 -1
- package/test/parser.test.js +26 -0
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ UCN gives AI agents call-graph-level understanding of code. Instead of reading e
|
|
|
5
5
|
Designed for large codebases where agents waste context on reading large files. UCN's surgical output means agents spend tokens on reasoning, not on ingesting thousands of lines to find three callers, discourages agents from cutting corners, as without UCN, agents working with large codebases tend to skip parts of the code structure, assuming they have "enough data".
|
|
6
6
|
|
|
7
7
|
Everything runs locally on your machine and nothing leaves your project.
|
|
8
|
+
The ucn mcp is kept light, as all 28 commands ship as a single MCP tool, under 2k tokens total.
|
|
8
9
|
|
|
9
10
|
---
|
|
10
11
|
|
|
@@ -43,7 +44,7 @@ Precise answers without reading files.
|
|
|
43
44
|
│ $ ucn about myFunc Works standalone, no agent required. │
|
|
44
45
|
│ │
|
|
45
46
|
│ 2. MCP Server Any MCP-compatible AI agent connects │
|
|
46
|
-
│ $ ucn --mcp and gets 28 commands automatically.
|
|
47
|
+
│ $ ucn --mcp and gets 28 commands automatically. │
|
|
47
48
|
│ │
|
|
48
49
|
│ 3. Agent Skill Drop-in skill for Claude Code and │
|
|
49
50
|
│ /ucn about myFunc OpenAI Codex CLI. No server needed. │
|
package/core/project.js
CHANGED
|
@@ -3206,7 +3206,7 @@ class ProjectIndex {
|
|
|
3206
3206
|
sharedCallees: []
|
|
3207
3207
|
};
|
|
3208
3208
|
|
|
3209
|
-
// 1. Same file functions
|
|
3209
|
+
// 1. Same file functions (sorted by proximity to target)
|
|
3210
3210
|
const fileEntry = this.files.get(def.file);
|
|
3211
3211
|
if (fileEntry) {
|
|
3212
3212
|
for (const sym of fileEntry.symbols) {
|
|
@@ -3218,6 +3218,10 @@ class ProjectIndex {
|
|
|
3218
3218
|
});
|
|
3219
3219
|
}
|
|
3220
3220
|
}
|
|
3221
|
+
// Sort by distance from target function (nearest first)
|
|
3222
|
+
related.sameFile.sort((a, b) =>
|
|
3223
|
+
Math.abs(a.line - def.startLine) - Math.abs(b.line - def.startLine)
|
|
3224
|
+
);
|
|
3221
3225
|
}
|
|
3222
3226
|
|
|
3223
3227
|
// 2. Similar names (shared prefix/suffix, camelCase similarity)
|
|
@@ -4097,14 +4101,25 @@ class ProjectIndex {
|
|
|
4097
4101
|
const language = detectLanguage(call.file);
|
|
4098
4102
|
if (!language) return { args: null, argCount: 0 };
|
|
4099
4103
|
|
|
4100
|
-
const parser = getParser(language);
|
|
4101
|
-
if (!parser) return { args: null, argCount: 0 };
|
|
4102
|
-
|
|
4103
4104
|
// Use tree cache to avoid re-parsing the same file in batch operations
|
|
4104
4105
|
let tree = this._treeCache?.get(call.file);
|
|
4105
4106
|
if (!tree) {
|
|
4106
4107
|
const content = this._readFile(call.file);
|
|
4107
|
-
|
|
4108
|
+
// HTML files need special handling: parse script blocks as JS
|
|
4109
|
+
if (language === 'html') {
|
|
4110
|
+
const htmlModule = getLanguageModule('html');
|
|
4111
|
+
const htmlParser = getParser('html');
|
|
4112
|
+
const jsParser = getParser('javascript');
|
|
4113
|
+
if (!htmlParser || !jsParser) return { args: null, argCount: 0 };
|
|
4114
|
+
const blocks = htmlModule.extractScriptBlocks(content, htmlParser);
|
|
4115
|
+
if (blocks.length === 0) return { args: null, argCount: 0 };
|
|
4116
|
+
const virtualJS = htmlModule.buildVirtualJSContent(content, blocks);
|
|
4117
|
+
tree = safeParse(jsParser, virtualJS);
|
|
4118
|
+
} else {
|
|
4119
|
+
const parser = getParser(language);
|
|
4120
|
+
if (!parser) return { args: null, argCount: 0 };
|
|
4121
|
+
tree = safeParse(parser, content);
|
|
4122
|
+
}
|
|
4108
4123
|
if (!tree) return { args: null, argCount: 0 };
|
|
4109
4124
|
if (!this._treeCache) this._treeCache = new Map();
|
|
4110
4125
|
this._treeCache.set(call.file, tree);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.9",
|
|
4
4
|
"description": "Universal Code Navigator — AST-based call graph analysis for AI agents. Find callers, trace impact, detect dead code across JS/TS, Python, Go, Rust, Java, and HTML. CLI, MCP server, and agent skill.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/test/parser.test.js
CHANGED
|
@@ -12871,6 +12871,32 @@ function unusedFn() { return 0; }
|
|
|
12871
12871
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
12872
12872
|
});
|
|
12873
12873
|
|
|
12874
|
+
it('verify/impact analyzeCallSite works for HTML inline scripts', () => {
|
|
12875
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-html-verify-'));
|
|
12876
|
+
fs.writeFileSync(path.join(tmpDir, 'game.html'), `<html><body>
|
|
12877
|
+
<script>
|
|
12878
|
+
function checkCollision(objA, objB, threshX, threshZ) { return true; }
|
|
12879
|
+
function update() {
|
|
12880
|
+
var hitbox = { x: 1, z: 2 };
|
|
12881
|
+
if (checkCollision(p, player, hitbox.x, hitbox.z)) { return; }
|
|
12882
|
+
}
|
|
12883
|
+
</script>
|
|
12884
|
+
</body></html>`);
|
|
12885
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name": "test"}');
|
|
12886
|
+
|
|
12887
|
+
const { ProjectIndex } = require('../core/project');
|
|
12888
|
+
const index = new ProjectIndex(tmpDir);
|
|
12889
|
+
index.build();
|
|
12890
|
+
|
|
12891
|
+
const result = index.verify('checkCollision');
|
|
12892
|
+
assert.ok(result.found);
|
|
12893
|
+
assert.strictEqual(result.totalCalls, 1);
|
|
12894
|
+
assert.strictEqual(result.valid, 1, `Expected 1 valid call, got ${result.valid} valid, ${result.uncertain} uncertain`);
|
|
12895
|
+
assert.strictEqual(result.uncertain, 0, 'dot-access args should not be uncertain');
|
|
12896
|
+
|
|
12897
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
12898
|
+
});
|
|
12899
|
+
|
|
12874
12900
|
it('findCallers detects callers from HTML event handlers', () => {
|
|
12875
12901
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ucn-html-callers-'));
|
|
12876
12902
|
fs.writeFileSync(path.join(tmpDir, 'page.html'), `<html><body>
|