ucn 3.7.45 → 3.7.47
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 +6 -4
- package/README.md +23 -24
- package/cli/index.js +14 -6
- package/core/cache.js +176 -51
- package/core/callers.js +315 -51
- package/core/deadcode.js +42 -16
- package/core/discovery.js +1 -1
- package/core/execute.js +148 -11
- package/core/output.js +26 -4
- package/core/project.js +290 -52
- package/core/registry.js +1 -0
- package/core/shared.js +1 -1
- package/core/stacktrace.js +31 -2
- package/core/verify.js +11 -0
- package/languages/go.js +331 -23
- package/languages/index.js +20 -1
- package/languages/java.js +109 -4
- package/languages/rust.js +93 -4
- package/mcp/server.js +33 -16
- package/package.json +11 -10
package/languages/java.js
CHANGED
|
@@ -623,12 +623,57 @@ function findCallsInCode(code, parser) {
|
|
|
623
623
|
const tree = parseTree(parser, code);
|
|
624
624
|
const calls = [];
|
|
625
625
|
const functionStack = []; // Stack of { name, startLine, endLine }
|
|
626
|
+
// Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
|
|
627
|
+
const scopeTypes = new Map();
|
|
626
628
|
|
|
627
629
|
// Helper to check if a node creates a function scope
|
|
628
630
|
const isFunctionNode = (node) => {
|
|
629
631
|
return ['method_declaration', 'constructor_declaration', 'lambda_expression'].includes(node.type);
|
|
630
632
|
};
|
|
631
633
|
|
|
634
|
+
// Extract type name from a Java type node (strips generics, qualified names)
|
|
635
|
+
const extractTypeName = (typeNode) => {
|
|
636
|
+
if (!typeNode) return null;
|
|
637
|
+
if (typeNode.type === 'type_identifier') return typeNode.text;
|
|
638
|
+
if (typeNode.type === 'generic_type') {
|
|
639
|
+
// List<String> -> List (first named child is the base type)
|
|
640
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
641
|
+
const r = extractTypeName(typeNode.namedChild(i));
|
|
642
|
+
if (r) return r;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (typeNode.type === 'scoped_type_identifier') {
|
|
646
|
+
// pkg.Type -> Type (last identifier)
|
|
647
|
+
const nameNode = typeNode.childForFieldName('name') ||
|
|
648
|
+
typeNode.namedChild(typeNode.namedChildCount - 1);
|
|
649
|
+
return nameNode?.text || null;
|
|
650
|
+
}
|
|
651
|
+
if (typeNode.type === 'array_type') {
|
|
652
|
+
return extractTypeName(typeNode.namedChild(0));
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// Build type map from method/constructor parameters
|
|
658
|
+
const buildScopeTypeMap = (node) => {
|
|
659
|
+
const typeMap = new Map();
|
|
660
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
661
|
+
if (paramsNode) {
|
|
662
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
663
|
+
const param = paramsNode.namedChild(i);
|
|
664
|
+
if (param.type === 'formal_parameter' || param.type === 'spread_parameter') {
|
|
665
|
+
const nameNode = param.childForFieldName('name');
|
|
666
|
+
const typeNode = param.childForFieldName('type');
|
|
667
|
+
const typeName = extractTypeName(typeNode);
|
|
668
|
+
if (nameNode && typeName) {
|
|
669
|
+
typeMap.set(nameNode.text, typeName);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return typeMap;
|
|
675
|
+
};
|
|
676
|
+
|
|
632
677
|
// Helper to extract function name from a function node
|
|
633
678
|
const extractFunctionName = (node) => {
|
|
634
679
|
if (node.type === 'method_declaration') {
|
|
@@ -652,14 +697,25 @@ function findCallsInCode(code, parser) {
|
|
|
652
697
|
: null;
|
|
653
698
|
};
|
|
654
699
|
|
|
700
|
+
// Look up variable type from scope chain
|
|
701
|
+
const getReceiverType = (varName) => {
|
|
702
|
+
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
703
|
+
const typeMap = scopeTypes.get(functionStack[i].startLine);
|
|
704
|
+
if (typeMap?.has(varName)) return typeMap.get(varName);
|
|
705
|
+
}
|
|
706
|
+
return undefined;
|
|
707
|
+
};
|
|
708
|
+
|
|
655
709
|
traverseTree(tree.rootNode, (node) => {
|
|
656
710
|
// Track function entry
|
|
657
711
|
if (isFunctionNode(node)) {
|
|
658
|
-
|
|
712
|
+
const entry = {
|
|
659
713
|
name: extractFunctionName(node),
|
|
660
714
|
startLine: node.startPosition.row + 1,
|
|
661
715
|
endLine: node.endPosition.row + 1
|
|
662
|
-
}
|
|
716
|
+
};
|
|
717
|
+
functionStack.push(entry);
|
|
718
|
+
scopeTypes.set(entry.startLine, buildScopeTypeMap(node));
|
|
663
719
|
}
|
|
664
720
|
|
|
665
721
|
// Handle method invocations: foo(), obj.foo(), this.foo()
|
|
@@ -669,11 +725,14 @@ function findCallsInCode(code, parser) {
|
|
|
669
725
|
|
|
670
726
|
if (nameNode) {
|
|
671
727
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
728
|
+
const receiver = (objNode?.type === 'identifier' || objNode?.type === 'this') ? objNode.text : undefined;
|
|
729
|
+
const receiverType = (receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined;
|
|
672
730
|
calls.push({
|
|
673
731
|
name: nameNode.text,
|
|
674
732
|
line: node.startPosition.row + 1,
|
|
675
733
|
isMethod: !!objNode,
|
|
676
|
-
receiver
|
|
734
|
+
receiver,
|
|
735
|
+
...(receiverType && { receiverType }),
|
|
677
736
|
enclosingFunction
|
|
678
737
|
});
|
|
679
738
|
}
|
|
@@ -708,11 +767,57 @@ function findCallsInCode(code, parser) {
|
|
|
708
767
|
return true;
|
|
709
768
|
}
|
|
710
769
|
|
|
770
|
+
// Detect method references passed as arguments: this::worker, obj::method
|
|
771
|
+
if (node.type === 'method_reference') {
|
|
772
|
+
const nameNode = node.namedChild(node.namedChildCount - 1);
|
|
773
|
+
const objNode = node.namedChild(0);
|
|
774
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
775
|
+
const receiver = objNode ? (objNode.type === 'identifier' || objNode.type === 'this' ? objNode.text : undefined) : undefined;
|
|
776
|
+
const receiverType = (receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined;
|
|
777
|
+
const enclosingFunction = getCurrentEnclosingFunction();
|
|
778
|
+
calls.push({
|
|
779
|
+
name: nameNode.text,
|
|
780
|
+
line: node.startPosition.row + 1,
|
|
781
|
+
isMethod: !!receiver,
|
|
782
|
+
receiver,
|
|
783
|
+
...(receiverType && { receiverType }),
|
|
784
|
+
isFunctionReference: true,
|
|
785
|
+
isPotentialCallback: true,
|
|
786
|
+
enclosingFunction
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Track local variable types from new Type() assignments
|
|
793
|
+
// e.g., Foo f = new Foo(); or var f = new Foo();
|
|
794
|
+
if (node.type === 'local_variable_declaration' && functionStack.length > 0) {
|
|
795
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
796
|
+
const child = node.namedChild(i);
|
|
797
|
+
if (child.type === 'variable_declarator') {
|
|
798
|
+
const nameNode = child.childForFieldName('name');
|
|
799
|
+
const valueNode = child.childForFieldName('value');
|
|
800
|
+
if (nameNode && valueNode && valueNode.type === 'object_creation_expression') {
|
|
801
|
+
const typeNode = valueNode.childForFieldName('type');
|
|
802
|
+
const typeName = extractTypeName(typeNode);
|
|
803
|
+
if (typeName) {
|
|
804
|
+
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
805
|
+
const typeMap = scopeTypes.get(scopeKey);
|
|
806
|
+
if (typeMap) typeMap.set(nameNode.text, typeName);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
711
813
|
return true;
|
|
712
814
|
}, {
|
|
713
815
|
onLeave: (node) => {
|
|
714
816
|
if (isFunctionNode(node)) {
|
|
715
|
-
functionStack.pop();
|
|
817
|
+
const leaving = functionStack.pop();
|
|
818
|
+
if (leaving) {
|
|
819
|
+
scopeTypes.delete(leaving.startLine);
|
|
820
|
+
}
|
|
716
821
|
}
|
|
717
822
|
}
|
|
718
823
|
});
|
package/languages/rust.js
CHANGED
|
@@ -631,12 +631,59 @@ function findCallsInCode(code, parser) {
|
|
|
631
631
|
const tree = parseTree(parser, code);
|
|
632
632
|
const calls = [];
|
|
633
633
|
const functionStack = []; // Stack of { name, startLine, endLine }
|
|
634
|
+
// Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
|
|
635
|
+
const scopeTypes = new Map();
|
|
634
636
|
|
|
635
637
|
// Helper to check if a node creates a function scope
|
|
636
638
|
const isFunctionNode = (node) => {
|
|
637
639
|
return ['function_item', 'closure_expression'].includes(node.type);
|
|
638
640
|
};
|
|
639
641
|
|
|
642
|
+
// Extract the base type name from a Rust type node (strips &, &mut, Box<>, etc.)
|
|
643
|
+
const extractTypeName = (typeNode) => {
|
|
644
|
+
if (!typeNode) return null;
|
|
645
|
+
if (typeNode.type === 'type_identifier') return typeNode.text;
|
|
646
|
+
if (typeNode.type === 'reference_type') {
|
|
647
|
+
// &Filter or &mut Filter -> Filter
|
|
648
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
649
|
+
const r = extractTypeName(typeNode.namedChild(i));
|
|
650
|
+
if (r) return r;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (typeNode.type === 'generic_type') {
|
|
654
|
+
// Box<Filter> -> Filter (or get the outer type)
|
|
655
|
+
return extractTypeName(typeNode.namedChild(0));
|
|
656
|
+
}
|
|
657
|
+
if (typeNode.type === 'scoped_type_identifier') {
|
|
658
|
+
// module::Type -> Type
|
|
659
|
+
const nameNode = typeNode.childForFieldName('name');
|
|
660
|
+
return nameNode?.text || null;
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// Build type map from function parameters (including self receiver for impl methods)
|
|
666
|
+
const buildScopeTypeMap = (node) => {
|
|
667
|
+
const typeMap = new Map();
|
|
668
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
669
|
+
if (paramsNode) {
|
|
670
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
671
|
+
const param = paramsNode.namedChild(i);
|
|
672
|
+
if (param.type === 'parameter') {
|
|
673
|
+
const patternNode = param.childForFieldName('pattern');
|
|
674
|
+
const typeNode = param.childForFieldName('type');
|
|
675
|
+
const typeName = extractTypeName(typeNode);
|
|
676
|
+
if (patternNode && typeName) {
|
|
677
|
+
// Pattern can be identifier or _
|
|
678
|
+
const name = patternNode.type === 'identifier' ? patternNode.text : null;
|
|
679
|
+
if (name) typeMap.set(name, typeName);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return typeMap;
|
|
685
|
+
};
|
|
686
|
+
|
|
640
687
|
// Helper to extract function name from a function node
|
|
641
688
|
const extractFunctionName = (node) => {
|
|
642
689
|
if (node.type === 'function_item') {
|
|
@@ -656,14 +703,25 @@ function findCallsInCode(code, parser) {
|
|
|
656
703
|
: null;
|
|
657
704
|
};
|
|
658
705
|
|
|
706
|
+
// Look up variable type from scope chain
|
|
707
|
+
const getReceiverType = (varName) => {
|
|
708
|
+
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
709
|
+
const typeMap = scopeTypes.get(functionStack[i].startLine);
|
|
710
|
+
if (typeMap?.has(varName)) return typeMap.get(varName);
|
|
711
|
+
}
|
|
712
|
+
return undefined;
|
|
713
|
+
};
|
|
714
|
+
|
|
659
715
|
traverseTree(tree.rootNode, (node) => {
|
|
660
716
|
// Track function entry
|
|
661
717
|
if (isFunctionNode(node)) {
|
|
662
|
-
|
|
718
|
+
const entry = {
|
|
663
719
|
name: extractFunctionName(node),
|
|
664
720
|
startLine: node.startPosition.row + 1,
|
|
665
721
|
endLine: node.endPosition.row + 1
|
|
666
|
-
}
|
|
722
|
+
};
|
|
723
|
+
functionStack.push(entry);
|
|
724
|
+
scopeTypes.set(entry.startLine, buildScopeTypeMap(node));
|
|
667
725
|
}
|
|
668
726
|
|
|
669
727
|
// Handle function calls: foo(), obj.method(), Type::func(), foo::<T>()
|
|
@@ -692,11 +750,14 @@ function findCallsInCode(code, parser) {
|
|
|
692
750
|
const valueNode = funcNode.childForFieldName('value');
|
|
693
751
|
|
|
694
752
|
if (fieldNode) {
|
|
753
|
+
const receiver = (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined;
|
|
754
|
+
const receiverType = (receiver && receiver !== 'self') ? getReceiverType(receiver) : undefined;
|
|
695
755
|
calls.push({
|
|
696
756
|
name: fieldNode.text,
|
|
697
757
|
line: node.startPosition.row + 1,
|
|
698
758
|
isMethod: true,
|
|
699
|
-
receiver
|
|
759
|
+
receiver,
|
|
760
|
+
...(receiverType && { receiverType }),
|
|
700
761
|
enclosingFunction
|
|
701
762
|
});
|
|
702
763
|
}
|
|
@@ -738,11 +799,39 @@ function findCallsInCode(code, parser) {
|
|
|
738
799
|
return true;
|
|
739
800
|
}
|
|
740
801
|
|
|
802
|
+
// Detect function/method references passed as arguments:
|
|
803
|
+
// field_expression inside arguments (obj.method as callback)
|
|
804
|
+
if (node.type === 'field_expression' && node.parent?.type === 'arguments') {
|
|
805
|
+
const grandparent = node.parent?.parent;
|
|
806
|
+
if (!grandparent || grandparent.type !== 'call_expression' || grandparent.childForFieldName('function') !== node) {
|
|
807
|
+
const fieldNode = node.childForFieldName('field');
|
|
808
|
+
const valueNode = node.childForFieldName('value');
|
|
809
|
+
if (fieldNode) {
|
|
810
|
+
const receiver = (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined;
|
|
811
|
+
const receiverType = (receiver && receiver !== 'self') ? getReceiverType(receiver) : undefined;
|
|
812
|
+
const enclosingFunction = getCurrentEnclosingFunction();
|
|
813
|
+
calls.push({
|
|
814
|
+
name: fieldNode.text,
|
|
815
|
+
line: node.startPosition.row + 1,
|
|
816
|
+
isMethod: true,
|
|
817
|
+
receiver,
|
|
818
|
+
...(receiverType && { receiverType }),
|
|
819
|
+
isFunctionReference: true,
|
|
820
|
+
isPotentialCallback: true,
|
|
821
|
+
enclosingFunction
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
741
827
|
return true;
|
|
742
828
|
}, {
|
|
743
829
|
onLeave: (node) => {
|
|
744
830
|
if (isFunctionNode(node)) {
|
|
745
|
-
functionStack.pop();
|
|
831
|
+
const leaving = functionStack.pop();
|
|
832
|
+
if (leaving) {
|
|
833
|
+
scopeTypes.delete(leaving.startLine);
|
|
834
|
+
}
|
|
746
835
|
}
|
|
747
836
|
}
|
|
748
837
|
});
|
package/mcp/server.js
CHANGED
|
@@ -52,11 +52,17 @@ function getIndex(projectDir) {
|
|
|
52
52
|
}
|
|
53
53
|
const root = findProjectRoot(absDir);
|
|
54
54
|
const cached = indexCache.get(root);
|
|
55
|
+
const STALE_CHECK_INTERVAL_MS = 2000;
|
|
55
56
|
|
|
56
|
-
//
|
|
57
|
-
if (cached
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// Throttle staleness checks — isCacheStale() re-globs and stats all files
|
|
58
|
+
if (cached) {
|
|
59
|
+
if (Date.now() - cached.checkedAt < STALE_CHECK_INTERVAL_MS) {
|
|
60
|
+
return cached.index; // Recently verified fresh
|
|
61
|
+
}
|
|
62
|
+
if (!cached.index.isCacheStale()) {
|
|
63
|
+
cached.checkedAt = Date.now();
|
|
64
|
+
return cached.index;
|
|
65
|
+
}
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
// Build new index (or rebuild stale one)
|
|
@@ -158,7 +164,7 @@ function requireName(name) {
|
|
|
158
164
|
// CONSOLIDATED TOOL REGISTRATION
|
|
159
165
|
// ============================================================================
|
|
160
166
|
|
|
161
|
-
const TOOL_DESCRIPTION = `
|
|
167
|
+
const TOOL_DESCRIPTION = `Code intelligence toolkit for AI agents. Extract specific functions, trace call chains, find all callers, and detect dead code — without reading entire files or scanning full projects. Use instead of grep/read for code relationships. Supports JavaScript/TypeScript, Python, Go, Rust, Java, and HTML.
|
|
162
168
|
|
|
163
169
|
TOP 5 (covers 90% of tasks): about, impact, trace, find, deadcode
|
|
164
170
|
|
|
@@ -252,7 +258,9 @@ server.registerTool(
|
|
|
252
258
|
case_sensitive: z.boolean().optional().describe('Case-sensitive search (default: false, case-insensitive)'),
|
|
253
259
|
all: z.boolean().optional().describe('Show all results (expand truncated sections). Applies to about, toc, related, trace, and others.'),
|
|
254
260
|
top_level: z.boolean().optional().describe('Show only top-level functions in toc (exclude nested/indented)'),
|
|
255
|
-
class_name: z.string().optional().describe('Class name to scope method analysis (e.g. "MarketDataFetcher" for close)')
|
|
261
|
+
class_name: z.string().optional().describe('Class name to scope method analysis (e.g. "MarketDataFetcher" for close)'),
|
|
262
|
+
limit: z.number().optional().describe('Max results to return (default: 500). Caps find, usages, search, deadcode, api, toc --detailed.'),
|
|
263
|
+
max_files: z.number().optional().describe('Max files to index (default: 10000). Use for very large codebases.')
|
|
256
264
|
|
|
257
265
|
})
|
|
258
266
|
},
|
|
@@ -350,18 +358,22 @@ server.registerTool(
|
|
|
350
358
|
|
|
351
359
|
case 'usages': {
|
|
352
360
|
const index = getIndex(project_dir);
|
|
353
|
-
const { ok, result, error } = execute(index, 'usages', ep);
|
|
361
|
+
const { ok, result, error, note } = execute(index, 'usages', ep);
|
|
354
362
|
if (!ok) return toolResult(error); // soft error
|
|
355
|
-
|
|
363
|
+
let text = output.formatUsages(result, ep.name);
|
|
364
|
+
if (note) text += '\n\n' + note;
|
|
365
|
+
return toolResult(text);
|
|
356
366
|
}
|
|
357
367
|
|
|
358
368
|
case 'toc': {
|
|
359
369
|
const index = getIndex(project_dir);
|
|
360
|
-
const { ok, result, error } = execute(index, 'toc', ep);
|
|
370
|
+
const { ok, result, error, note } = execute(index, 'toc', ep);
|
|
361
371
|
if (!ok) return toolResult(error); // soft error
|
|
362
|
-
|
|
372
|
+
let text = output.formatToc(result, {
|
|
363
373
|
topHint: 'Set top=N or use detailed=false for compact view.'
|
|
364
|
-
})
|
|
374
|
+
});
|
|
375
|
+
if (note) text += '\n\n' + note;
|
|
376
|
+
return toolResult(text);
|
|
365
377
|
}
|
|
366
378
|
|
|
367
379
|
case 'search': {
|
|
@@ -380,13 +392,16 @@ server.registerTool(
|
|
|
380
392
|
|
|
381
393
|
case 'deadcode': {
|
|
382
394
|
const index = getIndex(project_dir);
|
|
383
|
-
const { ok, result, error } = execute(index, 'deadcode', ep);
|
|
395
|
+
const { ok, result, error, note } = execute(index, 'deadcode', ep);
|
|
384
396
|
if (!ok) return toolResult(error); // soft error
|
|
385
|
-
|
|
397
|
+
const dcNote = note;
|
|
398
|
+
let dcText = output.formatDeadcode(result, {
|
|
386
399
|
top: ep.top || 0,
|
|
387
400
|
decoratedHint: !ep.includeDecorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use include_decorated=true to include them.` : undefined,
|
|
388
401
|
exportedHint: !ep.includeExported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use include_exported=true to audit them.` : undefined
|
|
389
|
-
})
|
|
402
|
+
});
|
|
403
|
+
if (dcNote) dcText += '\n\n' + dcNote;
|
|
404
|
+
return toolResult(dcText);
|
|
390
405
|
}
|
|
391
406
|
|
|
392
407
|
// ── File Dependencies ───────────────────────────────────────
|
|
@@ -465,9 +480,11 @@ server.registerTool(
|
|
|
465
480
|
|
|
466
481
|
case 'api': {
|
|
467
482
|
const index = getIndex(project_dir);
|
|
468
|
-
const { ok, result, error } = execute(index, 'api', ep);
|
|
483
|
+
const { ok, result, error, note } = execute(index, 'api', ep);
|
|
469
484
|
if (!ok) return toolResult(error); // soft error
|
|
470
|
-
|
|
485
|
+
let apiText = output.formatApi(result, ep.file || '.');
|
|
486
|
+
if (note) apiText += '\n\n' + note;
|
|
487
|
+
return toolResult(apiText);
|
|
471
488
|
}
|
|
472
489
|
|
|
473
490
|
case 'stats': {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.47",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
|
-
"description": "
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ucn": "cli/index.js",
|
|
@@ -16,28 +16,29 @@
|
|
|
16
16
|
"mcp",
|
|
17
17
|
"mcp-server",
|
|
18
18
|
"model-context-protocol",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"ai-coding",
|
|
21
|
+
"code-intelligence",
|
|
19
22
|
"code-navigation",
|
|
20
23
|
"code-analysis",
|
|
21
|
-
"
|
|
24
|
+
"code-extraction",
|
|
22
25
|
"call-graph",
|
|
23
26
|
"callers",
|
|
24
27
|
"impact-analysis",
|
|
25
28
|
"dead-code",
|
|
26
29
|
"deadcode",
|
|
27
|
-
"ast",
|
|
28
|
-
"tree-sitter",
|
|
29
|
-
"parser",
|
|
30
|
-
"skill",
|
|
31
30
|
"agent-skill",
|
|
31
|
+
"skill",
|
|
32
32
|
"cli",
|
|
33
|
-
"
|
|
33
|
+
"tree-sitter",
|
|
34
|
+
"ast",
|
|
35
|
+
"static-analysis",
|
|
34
36
|
"javascript",
|
|
35
37
|
"typescript",
|
|
36
38
|
"python",
|
|
37
39
|
"go",
|
|
38
40
|
"rust",
|
|
39
|
-
"java"
|
|
40
|
-
"html"
|
|
41
|
+
"java"
|
|
41
42
|
],
|
|
42
43
|
"author": "Constantin-Mihail Leoca (https://github.com/mleoca)",
|
|
43
44
|
"repository": {
|