Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ __pycache__/
.mypy_cache/
.ruff_cache/

**/node_modules/**
# Claude Differ sentinel
.claude-writing
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ the index is fast, free to build, and requires zero maintenance.

```bash
# 1. Install
pip install "git+https://github.com/darshil3011/codedrift[mcp]"
pip install "codedrift[mcp] @ git+https://github.com/darshil3011/codedrift.git"

# 2. Index your project
cd /path/to/your/project
Expand Down
87 changes: 86 additions & 1 deletion codedrift/languages/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,88 @@ class ImportRef:
import_line: str # "from auth.jwt import validate_token"


# ── tree-sitter API compatibility shim (0.24 ↔ 0.25+) ────────────────────────
#
# tree-sitter 0.25 changed every Node/Tree attribute from a property to a
# method call (root_node(), kind(), start_position(), …) and dropped node.text.
# _TSNode/_TSTree wrap the new API to expose the old property-based interface
# so all language adapters continue to work without modification.

class _TSNode:
"""Wraps a tree-sitter ≥0.25 Node to expose the ≤0.24 property API."""

def __init__(self, node, source: bytes):
self._n = node
self._src = source

@property
def type(self) -> str:
return self._n.kind()

@property
def start_point(self):
p = self._n.start_position()
return (p.row, p.column)

@property
def end_point(self):
p = self._n.end_position()
return (p.row, p.column)

@property
def text(self) -> bytes:
br = self._n.byte_range()
return self._src[br.start:br.end]

@property
def children(self) -> list:
count = self._n.child_count()
return [_TSNode(self._n.child(i), self._src) for i in range(count)]

@property
def parent(self):
p = self._n.parent()
return _TSNode(p, self._src) if p is not None else None

def child_by_field_name(self, name: str):
n = self._n.child_by_field_name(name)
return _TSNode(n, self._src) if n is not None else None

def child(self, i: int):
n = self._n.child(i)
return _TSNode(n, self._src) if n is not None else None

def __eq__(self, other):
if isinstance(other, _TSNode):
return (self._n.start_byte() == other._n.start_byte() and
self._n.end_byte() == other._n.end_byte())
return NotImplemented

def __hash__(self):
return hash((self._n.start_byte(), self._n.end_byte()))


class _TSTree:
"""Wraps a tree-sitter ≥0.25 Tree to expose the ≤0.24 property API."""

def __init__(self, tree, source: bytes):
self._t = tree
self._src = source

@property
def root_node(self) -> _TSNode:
return _TSNode(self._t.root_node(), self._src)


def _wrap_tree(raw_tree, source: bytes):
"""Wrap raw_tree in _TSTree when the ≥0.25 API is detected."""
if callable(getattr(raw_tree, "root_node", None)):
return _TSTree(raw_tree, source)
return raw_tree # old API — pass through unchanged


# ── Language adapter base class ───────────────────────────────────────────────

class LanguageAdapter:
"""Base class for language-specific AST extraction."""

Expand All @@ -44,7 +126,10 @@ def _get_parser(self):
return get_parser(self.language_name)

def parse(self, source: bytes):
return self._get_parser().parse(source)
parser = self._get_parser()
# tree-sitter >= 0.25 renamed parse(bytes) → parse_bytes(bytes)
parse_fn = getattr(parser, "parse_bytes", parser.parse)
return _wrap_tree(parse_fn(source), source)

def _node_text(self, node, source_lines: List[str]) -> str:
return source_lines[node.start_point[0]] if source_lines else ""
Expand Down
5 changes: 4 additions & 1 deletion codedrift/languages/javascript_lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ def _parser_for(self, filepath: str):
return get_parser(lang)

def parse_file(self, source: bytes, filepath: str):
return self._parser_for(filepath).parse(source)
parser = self._parser_for(filepath)
parse_fn = getattr(parser, "parse_bytes", parser.parse)
from .base import _wrap_tree
return _wrap_tree(parse_fn(source), source)

def extract_functions(self, tree, source_lines: List[str], filepath: str) -> List[Symbol]:
symbols = []
Expand Down