codegraph-engine 2.2.0__py3-none-any.whl → 2.2.1__py3-none-any.whl

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.
codegraph/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """CodeGraph MCP: local, evidence-backed codebase intelligence."""
2
2
 
3
- __version__ = "2.2.0"
3
+ __version__ = "2.2.1"
4
4
  from codegraph.errors import (
5
5
  CodeGraphError,
6
6
  ErrorCode,
@@ -597,29 +597,63 @@ class _PythonScopeVisitor(ast.NodeVisitor):
597
597
  doc = ast.get_docstring(node)
598
598
  c_hash = _normalized_body_hash(self.lines, node.lineno, end_line)
599
599
 
600
- self.symbols.append(
601
- Symbol(
602
- id=canon_id,
603
- canonical_id=canon_id,
604
- name=node.name,
605
- qualified_name=qname,
606
- kind=kind,
607
- language="python",
608
- module=self.module,
609
- path=self.file_path,
610
- file_path=self.file_path,
611
- scope=parent_scope,
612
- signature=sig,
613
- start_line=node.lineno,
614
- end_line=end_line,
600
+ # Avoid duplicate canonical symbol IDs for @name.setter within the same class
601
+ is_setter = any(d.endswith(".setter") for d in decorators)
602
+ existing_prop = None
603
+ if is_setter and parent_kind == "class":
604
+ for s in self.symbols:
605
+ if s.canonical_id == canon_id and s.kind == "property":
606
+ existing_prop = s
607
+ break
608
+
609
+ if existing_prop is not None:
610
+ # Merge setter signature/span into the existing property symbol
611
+ idx = self.symbols.index(existing_prop)
612
+ self.symbols[idx] = Symbol(
613
+ id=existing_prop.id,
614
+ canonical_id=existing_prop.canonical_id,
615
+ name=existing_prop.name,
616
+ qualified_name=existing_prop.qualified_name,
617
+ kind="property",
618
+ language=existing_prop.language,
619
+ module=existing_prop.module,
620
+ path=existing_prop.path,
621
+ file_path=existing_prop.file_path,
622
+ scope=existing_prop.scope,
623
+ signature=f"{existing_prop.signature} | setter: {sig}" if existing_prop.signature else sig,
624
+ start_line=existing_prop.start_line,
625
+ end_line=max(existing_prop.end_line, end_line),
615
626
  content_hash=c_hash,
616
- parent_symbol_id=parent_canon,
617
- decorators=decorators,
618
- return_type=ret_type,
619
- parameter_count=param_count,
620
- documentation=doc,
627
+ parent_symbol_id=existing_prop.parent_symbol_id,
628
+ decorators=list(dict.fromkeys(existing_prop.decorators + decorators)),
629
+ return_type=existing_prop.return_type,
630
+ parameter_count=existing_prop.parameter_count,
631
+ documentation=existing_prop.documentation or doc,
632
+ )
633
+ else:
634
+ self.symbols.append(
635
+ Symbol(
636
+ id=canon_id,
637
+ canonical_id=canon_id,
638
+ name=node.name,
639
+ qualified_name=qname,
640
+ kind=kind,
641
+ language="python",
642
+ module=self.module,
643
+ path=self.file_path,
644
+ file_path=self.file_path,
645
+ scope=parent_scope,
646
+ signature=sig,
647
+ start_line=node.lineno,
648
+ end_line=end_line,
649
+ content_hash=c_hash,
650
+ parent_symbol_id=parent_canon,
651
+ decorators=decorators,
652
+ return_type=ret_type,
653
+ parameter_count=param_count,
654
+ documentation=doc,
655
+ )
621
656
  )
622
- )
623
657
 
624
658
  # Framework analyzer hook: check function/class for route decorators during this traversal
625
659
  if node.decorator_list or isinstance(node, ast.ClassDef):