ucn 3.8.16 → 3.8.17

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.
@@ -157,11 +157,11 @@ ucn [target] <command> [name] [--flags]
157
157
 
158
158
  | Flag | When to use it |
159
159
  |------|---------------|
160
- | `--class-name=X` | Scope to specific class (e.g., `--class-name=Repository` for method `save`) |
160
+ | `--class-name=X` | Scope to specific class (e.g., `--class-name=Repository` for method `save`). Works with `fn`, `about`, `context`, `impact`, `tests`, `example`, `typedef`, `verify`, `plan` |
161
161
  | `--file=<pattern>` | Disambiguate when a name exists in multiple files (e.g., `--file=api`) |
162
162
  | `--exclude=test,mock` | Focus on production code only |
163
163
  | `--in=src/core` | Limit search to a subdirectory |
164
- | `--depth=N` | Control tree depth for `trace` and `graph` (default 3). Also expands all children — no breadth limit |
164
+ | `--depth=N` | Control tree depth for `trace`, `graph`, and detail level for `find` (default 3). Also expands all children — no breadth limit |
165
165
  | `--all` | Expand truncated sections in `about`, `trace`, `graph`, `related` |
166
166
  | `--include-tests` | Include test files in results (excluded by default) |
167
167
  | `--include-methods` | Include `obj.method()` calls in `context`/`smart` (only direct calls shown by default) |
package/cli/index.js CHANGED
@@ -658,7 +658,7 @@ function runProjectCommand(rootDir, command, arg) {
658
658
 
659
659
  case 'class': {
660
660
  requireArg(arg, 'Usage: ucn . class <name>');
661
- const { ok, result, error, note } = execute(index, 'class', { name: arg, file: flags.file, all: flags.all, maxLines: flags.maxLines, className: flags.className });
661
+ const { ok, result, error, note } = execute(index, 'class', { name: arg, file: flags.file, all: flags.all, maxLines: flags.maxLines });
662
662
  if (!ok) fail(error);
663
663
  if (note) console.error(note);
664
664
  printOutput(result, output.formatClassResultJson, output.formatClassResult);
@@ -703,7 +703,7 @@ function runProjectCommand(rootDir, command, arg) {
703
703
  const { ok, result, error } = execute(index, 'fileExports', { file: filePath });
704
704
  if (!ok) fail(error);
705
705
  printOutput(result,
706
- output.formatFileExportsJson,
706
+ r => output.formatFileExportsJson(r, filePath),
707
707
  r => output.formatFileExports(r, filePath)
708
708
  );
709
709
  break;
@@ -120,14 +120,14 @@ function formatFileExports(exports, filePath) {
120
120
  return lines.join('\n');
121
121
  }
122
122
 
123
- function formatFileExportsJson(result) {
123
+ function formatFileExportsJson(result, filePath) {
124
124
  if (!result) return JSON.stringify({ found: false });
125
+ // result is an array of exported symbols from index.fileExports()
125
126
  return JSON.stringify({
126
- meta: { command: 'fileExports', file: result.file },
127
+ meta: { command: 'fileExports', file: filePath || (Array.isArray(result) ? result[0]?.file : result.file) },
127
128
  data: {
128
- file: result.file,
129
- exports: result.exports || [],
130
- reExports: result.reExports || [],
129
+ file: filePath || (Array.isArray(result) ? result[0]?.file : result.file),
130
+ exports: Array.isArray(result) ? result : (result.exports || []),
131
131
  },
132
132
  }, null, 2);
133
133
  }
package/core/registry.js CHANGED
@@ -116,7 +116,7 @@ const FLAG_APPLICABILITY = {
116
116
  entrypoints: ['file', 'exclude', 'includeTests', 'limit', 'type', 'framework'],
117
117
  // Extracting code
118
118
  fn: ['file', 'className', 'all'],
119
- class: ['file', 'className', 'all', 'maxLines'],
119
+ class: ['file', 'all', 'maxLines'],
120
120
  lines: ['file', 'range'],
121
121
  expand: [],
122
122
  // File dependencies
package/core/search.js CHANGED
@@ -845,11 +845,15 @@ function tests(index, nameOrFile, options = {}) {
845
845
  const callPattern = new RegExp(escapeRegExp(searchTerm) + '\\s*\\(');
846
846
  const strPattern = new RegExp("['\"`]" + escapeRegExp(searchTerm) + "['\"`]");
847
847
 
848
- // When className is provided, build a pattern to scope matches.
849
- // We require the test file to also reference the class name (import, instantiation, or receiver).
848
+ // When className is provided, build patterns for match-level scoping.
850
849
  const classNameFilter = options.className
851
850
  ? new RegExp('\\b' + escapeRegExp(options.className) + '\\b')
852
851
  : null;
852
+ // Match-level class scoping: for calls, check receiver or nearby context;
853
+ // e.g., "new ClassName().method()" or "instance.method()" where instance is typed.
854
+ const classReceiverPattern = options.className
855
+ ? new RegExp('\\b' + escapeRegExp(options.className) + '\\s*[\\.\\(]')
856
+ : null;
853
857
 
854
858
  for (const { path: testPath, entry } of testFiles) {
855
859
  try {
@@ -880,6 +884,40 @@ function tests(index, nameOrFile, options = {}) {
880
884
  }
881
885
  }
882
886
 
887
+ // Match-level className scoping: for call matches,
888
+ // the class name must appear on the SAME line as receiver
889
+ // (e.g., "new ClassName().method()" or "className.method()").
890
+ if (classReceiverPattern && matchType === 'call') {
891
+ if (!classNameFilter.test(line)) return; // skip — different receiver
892
+ }
893
+
894
+ // For reference matches, check same line or ±1 line.
895
+ if (classReceiverPattern && matchType === 'reference') {
896
+ let classNearby = classNameFilter.test(line);
897
+ if (!classNearby && idx > 0) classNearby = classNameFilter.test(lines[idx - 1]);
898
+ if (!classNearby && idx + 1 < lines.length) classNearby = classNameFilter.test(lines[idx + 1]);
899
+ if (!classNearby) return; // skip this match
900
+ }
901
+
902
+ // For test-case matches with className, keep if the test
903
+ // description mentions the class or the test body (next few
904
+ // lines) references the class as a receiver.
905
+ if (classNameFilter && matchType === 'test-case') {
906
+ let classInContext = classNameFilter.test(line);
907
+ if (!classInContext) {
908
+ // Look forward into the test body for class usage
909
+ for (let d = 1; d <= 5 && !classInContext; d++) {
910
+ if (idx + d < lines.length) {
911
+ const bodyLine = lines[idx + d];
912
+ // Stop at next test-case boundary
913
+ if (/\b(describe|it|test|spec)\s*\(/.test(bodyLine)) break;
914
+ if (classNameFilter.test(bodyLine)) classInContext = true;
915
+ }
916
+ }
917
+ }
918
+ if (!classInContext) return; // skip test-case not related to this class
919
+ }
920
+
883
921
  matches.push({
884
922
  line: idx + 1,
885
923
  content: line.trim(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.8.16",
3
+ "version": "3.8.17",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Code intelligence toolkit for AI agents — extract functions, trace call chains, find callers, detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
6
6
  "main": "index.js",