ucn 5.0.5 → 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.
@@ -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
- localVarTypeQualifiers.delete(left.text);
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.0.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",