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.
- package/.claude/skills/ucn/SKILL.md +2 -2
- package/cli/index.js +2 -2
- package/core/output/graph.js +5 -5
- package/core/registry.js +1 -1
- package/core/search.js +40 -2
- package/package.json +1 -1
|
@@ -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 `
|
|
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
|
|
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;
|
package/core/output/graph.js
CHANGED
|
@@ -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', '
|
|
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
|
|
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.
|
|
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",
|