ucn 5.0.6 → 5.1.1
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 +1 -1
- package/.claude/skills/ucn/references/trust-contract.md +3 -2
- package/README.md +3 -1
- package/core/analysis.js +66 -1
- package/core/bridge.js +2 -1
- package/core/cache.js +37 -8
- package/core/callers.js +484 -37
- package/core/index-ir.js +14 -4
- package/core/ir.js +48 -2
- package/core/output/analysis.js +25 -0
- package/core/output/shared.js +2 -2
- package/languages/javascript.js +203 -18
- package/languages/python.js +79 -2
- package/mcp/server.js +1 -1
- package/package.json +1 -1
package/languages/python.js
CHANGED
|
@@ -686,6 +686,67 @@ function typeNameFromAnnotation(typeNode) {
|
|
|
686
686
|
return typeNameFromExpr(inner);
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Companion to typeNameFromAnnotation: the dotted module qualifier that owns
|
|
691
|
+
* the annotated name (fix #286e, flask-measured: `app: flask.Flask` typed the
|
|
692
|
+
* receiver bare 'Flask', and with a second test-local Flask class the origin
|
|
693
|
+
* fell back to directory proximity and excluded a compiler-true caller).
|
|
694
|
+
* Returns undefined when the annotation carries no qualifier.
|
|
695
|
+
*/
|
|
696
|
+
function typeQualifierFromAnnotation(typeNode) {
|
|
697
|
+
if (!typeNode) return undefined;
|
|
698
|
+
const inner = typeNode.namedChildCount > 0 ? typeNode.namedChild(0) : null;
|
|
699
|
+
return typeQualifierFromExpr(inner);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function typeQualifierFromExpr(node) {
|
|
703
|
+
if (!node) return undefined;
|
|
704
|
+
switch (node.type) {
|
|
705
|
+
case 'attribute':
|
|
706
|
+
return node.childForFieldName('object')?.text;
|
|
707
|
+
case 'parenthesized_expression':
|
|
708
|
+
return node.namedChildCount === 1
|
|
709
|
+
? typeQualifierFromExpr(node.namedChild(0)) : undefined;
|
|
710
|
+
case 'binary_operator': {
|
|
711
|
+
const left = node.namedChild(0);
|
|
712
|
+
const right = node.namedChild(1);
|
|
713
|
+
if (left?.type === 'none' && right?.type !== 'none') return typeQualifierFromExpr(right);
|
|
714
|
+
if (right?.type === 'none' && left?.type !== 'none') return typeQualifierFromExpr(left);
|
|
715
|
+
return undefined;
|
|
716
|
+
}
|
|
717
|
+
case 'subscript': {
|
|
718
|
+
const base = typeNameFromExpr(node.childForFieldName('value'));
|
|
719
|
+
if (PY_TYPE_WRAPPERS.has(base)) {
|
|
720
|
+
return typeQualifierFromExpr(node.childForFieldName('subscript'));
|
|
721
|
+
}
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
case 'generic_type': {
|
|
725
|
+
const base = typeNameFromExpr(node.namedChild(0));
|
|
726
|
+
if (PY_TYPE_WRAPPERS.has(base)) {
|
|
727
|
+
const params = node.namedChild(1);
|
|
728
|
+
const firstType = params && params.namedChildCount > 0 ? params.namedChild(0) : null;
|
|
729
|
+
return typeQualifierFromAnnotation(firstType);
|
|
730
|
+
}
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
case 'string': {
|
|
734
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
735
|
+
const c = node.child(i);
|
|
736
|
+
if (c.type === 'string_content') {
|
|
737
|
+
const txt = c.text.trim();
|
|
738
|
+
if (/^[A-Za-z_][\w.]*$/.test(txt) && txt.includes('.')) {
|
|
739
|
+
return txt.split('.').slice(0, -1).join('.');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
default:
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
689
750
|
function typeNamesFromAnnotation(typeNode) {
|
|
690
751
|
if (!typeNode) return [];
|
|
691
752
|
const inner = typeNode.namedChildCount > 0 ? typeNode.namedChild(0) : null;
|
|
@@ -1470,6 +1531,15 @@ function findCallsInCode(code, parser) {
|
|
|
1470
1531
|
if (receiverType && !['self', 'cls'].includes(nameNode.text)) {
|
|
1471
1532
|
localVarTypes.set(nameNode.text, receiverType);
|
|
1472
1533
|
declaredVarTypes.set(nameNode.text, receiverType);
|
|
1534
|
+
// The annotation's module qualifier is identity (fix
|
|
1535
|
+
// #286e) — splat params got a builtin type, no qualifier.
|
|
1536
|
+
const annotationQualifier = receiverType === typeName
|
|
1537
|
+
? typeQualifierFromAnnotation(typeNode) : undefined;
|
|
1538
|
+
if (annotationQualifier) {
|
|
1539
|
+
localVarTypeQualifiers.set(nameNode.text, annotationQualifier);
|
|
1540
|
+
} else {
|
|
1541
|
+
localVarTypeQualifiers.delete(nameNode.text);
|
|
1542
|
+
}
|
|
1473
1543
|
}
|
|
1474
1544
|
if (unionTypes.length > 1) localVarUnionTypes.set(nameNode.text, unionTypes);
|
|
1475
1545
|
else localVarUnionTypes.delete(nameNode.text);
|
|
@@ -1599,8 +1669,15 @@ function findCallsInCode(code, parser) {
|
|
|
1599
1669
|
if (declaredType) localVarTypes.set(left.text, declaredType);
|
|
1600
1670
|
} else {
|
|
1601
1671
|
// An annotation is the authoritative type source; a
|
|
1602
|
-
// previous constructor qualifier must not survive it
|
|
1603
|
-
|
|
1672
|
+
// previous constructor qualifier must not survive it —
|
|
1673
|
+
// the annotation's OWN qualifier does (fix #286e).
|
|
1674
|
+
const annotationQualifier = typeNameFromAnnotation(typeNode)
|
|
1675
|
+
? typeQualifierFromAnnotation(typeNode) : undefined;
|
|
1676
|
+
if (annotationQualifier) {
|
|
1677
|
+
localVarTypeQualifiers.set(left.text, annotationQualifier);
|
|
1678
|
+
} else {
|
|
1679
|
+
localVarTypeQualifiers.delete(left.text);
|
|
1680
|
+
}
|
|
1604
1681
|
}
|
|
1605
1682
|
// Preserve a declared collection contract through the common
|
|
1606
1683
|
// normalization idiom `x = {} if x is None else x`. The
|
package/mcp/server.js
CHANGED
|
@@ -242,7 +242,7 @@ Use UCN for semantic questions: exact definitions, symbol-aware callers/callees,
|
|
|
242
242
|
- check: symbol signature check when name is set; precommit check when omitted. plan previews the selected declaration plus indexed call/import/export edits.
|
|
243
243
|
- deadcode/audit_async/stacktrace: focused audits and runtime evidence. Computed dispatch is reported as a health/deletion blind spot; unknown decorators and member-assigned handlers are withheld from default dead-code claims.
|
|
244
244
|
|
|
245
|
-
CONFIRMED carries target-identity evidence. UNVERIFIED is possible and requires review. ACCOUNT conserves observed literal-name lines; an observed-text zero never claims semantic completeness or safe deletion. Evidence weights are ordinal, not probabilities. Warnings, excluded reasons, and contract metadata remain visible when caller evidence is returned. Release evaluation cross-checks overlapping stable-handle claims across find, show, source, impact, tests, check, and caller trace, and compares pinned real-repository samples with ts-morph, Pyright, gopls, rust-analyzer, JDT LS, clangd, and Roslyn.` + generateMcpParamSection();
|
|
245
|
+
CONFIRMED carries target-identity evidence. UNVERIFIED is possible and requires review; when same-name definitions compete, show returns their stable handles once above the sites. ACCOUNT conserves observed literal-name lines; an observed-text zero never claims semantic completeness or safe deletion. Evidence weights are ordinal, not probabilities. Warnings, excluded reasons, and contract metadata remain visible when caller evidence is returned. Release evaluation cross-checks overlapping stable-handle claims across find, show, source, impact, tests, check, and caller trace, and compares pinned real-repository samples with ts-morph, Pyright, gopls, rust-analyzer, JDT LS, clangd, and Roslyn.` + generateMcpParamSection();
|
|
246
246
|
|
|
247
247
|
// The default description is the only public contract. The former verbose
|
|
248
248
|
// description is intentionally not selectable: it documented retired commands
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Auditable AST code intelligence for AI agents: 18 task-oriented commands through one MCP tool, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java, C, C++, C#, and HTML.",
|
|
6
6
|
"main": "index.js",
|