Files
Claude-Code-Workflow/codex-lens/examples/association_tree_demo.py
catlog22 261c98549d feat: Implement association tree for LSP-based code relationship discovery
- Add `association_tree` module with components for building and processing call association trees using LSP call hierarchy capabilities.
- Introduce `AssociationTreeBuilder` for constructing call trees from seed locations with depth-first expansion.
- Create data structures: `TreeNode`, `CallTree`, and `UniqueNode` for representing nodes and relationships in the call tree.
- Implement `ResultDeduplicator` to extract unique nodes from call trees and assign relevance scores based on depth, frequency, and kind.
- Add unit tests for `AssociationTreeBuilder` and `ResultDeduplicator` to ensure functionality and correctness.
2026-01-20 22:09:04 +08:00

157 lines
4.5 KiB
Python

"""Demo script for association tree building.
This script demonstrates how to use the AssociationTreeBuilder and
ResultDeduplicator to explore code relationships via LSP call hierarchy.
"""
import asyncio
import sys
from pathlib import Path
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from codexlens.lsp.standalone_manager import StandaloneLspManager
from codexlens.search.association_tree import (
AssociationTreeBuilder,
ResultDeduplicator,
)
async def demo_simple_tree():
"""Build a simple call tree from a Python file."""
print("=" * 70)
print("Association Tree Demo")
print("=" * 70)
print()
# Use this file as the test subject
test_file = Path(__file__).resolve()
workspace_root = test_file.parent.parent
print(f"Workspace: {workspace_root}")
print(f"Test file: {test_file.name}")
print()
# Initialize LSP manager
async with StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=10.0,
) as lsp:
print("LSP manager initialized")
print()
# Create tree builder
builder = AssociationTreeBuilder(lsp, timeout=5.0)
# Build tree from a function in this file
# Using line 50 as an example (adjust based on actual file)
print(f"Building call tree from {test_file.name}:50...")
tree = await builder.build_tree(
seed_file_path=str(test_file),
seed_line=50,
seed_character=1,
max_depth=3,
expand_callers=True,
expand_callees=True,
)
print(f"Tree built: {tree}")
print(f" Roots: {len(tree.roots)}")
print(f" Total unique nodes: {len(tree.all_nodes)}")
print(f" Total node instances: {len(tree.node_list)}")
print(f" Edges: {len(tree.edges)}")
print()
if tree.roots:
print("Root nodes:")
for root in tree.roots:
print(f" - {root.item.name} ({root.item.kind})")
print(f" {root.item.file_path}:{root.item.range.start_line}")
print()
# Deduplicate and score
print("Deduplicating and scoring nodes...")
deduplicator = ResultDeduplicator(
depth_weight=0.4,
frequency_weight=0.3,
kind_weight=0.3,
)
unique_nodes = deduplicator.deduplicate(tree, max_results=20)
print(f"Found {len(unique_nodes)} unique nodes")
print()
if unique_nodes:
print("Top 10 nodes by score:")
print("-" * 70)
for i, node in enumerate(unique_nodes[:10], 1):
print(f"{i:2}. {node.name} ({node.kind})")
print(f" Location: {Path(node.file_path).name}:{node.range.start_line}")
print(
f" Depth: {node.min_depth}, "
f"Occurrences: {node.occurrences}, "
f"Score: {node.score:.3f}"
)
if node.paths:
print(f" Paths: {len(node.paths)}")
print()
# Show filtering capabilities
functions = deduplicator.filter_by_kind(
unique_nodes, ["function", "method"]
)
print(f"Functions/methods only: {len(functions)} nodes")
if functions:
print("Top 5 functions:")
for i, node in enumerate(functions[:5], 1):
print(f" {i}. {node.name} (score: {node.score:.3f})")
else:
print("No nodes found. Try a different seed location.")
print()
print("Demo complete!")
async def demo_cycle_detection():
"""Demonstrate cycle detection in call trees."""
print("\n" + "=" * 70)
print("Cycle Detection Demo")
print("=" * 70)
print()
# Create a simple Python file with circular calls for testing
test_code = '''
def func_a():
"""Function A calls B."""
func_b()
def func_b():
"""Function B calls A (creates a cycle)."""
func_a()
'''
print("This demo would detect cycles in:")
print(test_code)
print("The tree builder automatically marks cycle nodes to prevent infinite expansion.")
def main():
"""Run the demo."""
try:
asyncio.run(demo_simple_tree())
demo_cycle_detection()
except KeyboardInterrupt:
print("\nDemo interrupted by user")
except Exception as e:
print(f"\nError running demo: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()