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 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
- tree = safeParse(parser, content);
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.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": {
@@ -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>