codebrain 0.1.0__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.
- codebrain/__init__.py +3 -0
- codebrain/__main__.py +6 -0
- codebrain/agent_bridge.py +162 -0
- codebrain/analyzer.py +943 -0
- codebrain/api.py +578 -0
- codebrain/api_models.py +102 -0
- codebrain/cli.py +1927 -0
- codebrain/comprehension.py +1939 -0
- codebrain/config.py +46 -0
- codebrain/context.py +276 -0
- codebrain/export.py +334 -0
- codebrain/graph/__init__.py +0 -0
- codebrain/graph/query.py +656 -0
- codebrain/graph/schema.py +113 -0
- codebrain/graph/store.py +295 -0
- codebrain/hook_runner.py +71 -0
- codebrain/hooks.py +107 -0
- codebrain/indexer.py +450 -0
- codebrain/llm.py +676 -0
- codebrain/logging.py +42 -0
- codebrain/mcp_server.py +1635 -0
- codebrain/memory/__init__.py +5 -0
- codebrain/memory/store.py +270 -0
- codebrain/parser/__init__.py +0 -0
- codebrain/parser/base.py +27 -0
- codebrain/parser/config_parser.py +228 -0
- codebrain/parser/models.py +44 -0
- codebrain/parser/python_parser.py +658 -0
- codebrain/parser/registry.py +144 -0
- codebrain/parser/typescript_parser.py +1189 -0
- codebrain/parser/typescript_treesitter.py +535 -0
- codebrain/py.typed +0 -0
- codebrain/resolver.py +171 -0
- codebrain/settings.py +88 -0
- codebrain/utils.py +59 -0
- codebrain/validator.py +563 -0
- codebrain/watcher/__init__.py +0 -0
- codebrain/watcher/file_watcher.py +173 -0
- codebrain-0.1.0.dist-info/METADATA +360 -0
- codebrain-0.1.0.dist-info/RECORD +44 -0
- codebrain-0.1.0.dist-info/WHEEL +5 -0
- codebrain-0.1.0.dist-info/entry_points.txt +6 -0
- codebrain-0.1.0.dist-info/licenses/LICENSE +21 -0
- codebrain-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Parser registry — dispatches file parsing to the correct language parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from codebrain.logging import get_logger
|
|
8
|
+
from codebrain.parser.base import BaseParser
|
|
9
|
+
from codebrain.parser.models import ParsedFile
|
|
10
|
+
|
|
11
|
+
_log = get_logger("parser.registry")
|
|
12
|
+
|
|
13
|
+
# Module-level singleton
|
|
14
|
+
_registry: "ParserRegistry | None" = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ParserRegistry:
|
|
18
|
+
"""Manages registered language parsers and dispatches parse requests."""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self._parsers: list[BaseParser] = []
|
|
22
|
+
self._ext_map: dict[str, BaseParser] = {}
|
|
23
|
+
self._name_map: dict[str, BaseParser] = {}
|
|
24
|
+
|
|
25
|
+
def register(self, parser: BaseParser) -> None:
|
|
26
|
+
"""Register a parser instance."""
|
|
27
|
+
self._parsers.append(parser)
|
|
28
|
+
for ext in parser.extensions():
|
|
29
|
+
if ext in self._ext_map:
|
|
30
|
+
_log.debug(
|
|
31
|
+
"Extension %s already registered by %s, overriding with %s",
|
|
32
|
+
ext,
|
|
33
|
+
type(self._ext_map[ext]).__name__,
|
|
34
|
+
type(parser).__name__,
|
|
35
|
+
)
|
|
36
|
+
self._ext_map[ext] = parser
|
|
37
|
+
|
|
38
|
+
def register_name(self, name: str, parser: BaseParser) -> None:
|
|
39
|
+
"""Register a parser for a specific filename (e.g. 'docker-compose.yml')."""
|
|
40
|
+
self._name_map[name] = parser
|
|
41
|
+
|
|
42
|
+
def can_parse(self, path: Path) -> bool:
|
|
43
|
+
"""Return True if any registered parser handles this file."""
|
|
44
|
+
return path.suffix in self._ext_map or path.name in self._name_map
|
|
45
|
+
|
|
46
|
+
def parse(self, path: Path, repo_root: Path) -> ParsedFile:
|
|
47
|
+
"""Parse a file using the appropriate registered parser.
|
|
48
|
+
|
|
49
|
+
Raises ValueError if no parser handles this file.
|
|
50
|
+
"""
|
|
51
|
+
# Name-based match takes priority
|
|
52
|
+
parser = self._name_map.get(path.name)
|
|
53
|
+
if parser is None:
|
|
54
|
+
parser = self._ext_map.get(path.suffix)
|
|
55
|
+
if parser is None:
|
|
56
|
+
raise ValueError(f"No parser registered for '{path.name}' (ext '{path.suffix}')")
|
|
57
|
+
return parser.parse(path, repo_root)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def supported_extensions(self) -> frozenset[str]:
|
|
61
|
+
"""Return all extensions supported by registered parsers."""
|
|
62
|
+
return frozenset(self._ext_map.keys())
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def supported_names(self) -> frozenset[str]:
|
|
66
|
+
"""Return all filenames with name-based parser matching."""
|
|
67
|
+
return frozenset(self._name_map.keys())
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def parsers(self) -> list[BaseParser]:
|
|
71
|
+
"""Return list of registered parsers."""
|
|
72
|
+
return list(self._parsers)
|
|
73
|
+
|
|
74
|
+
def load_entrypoints(self) -> None:
|
|
75
|
+
"""Discover and load parsers from the ``codebrain.parsers`` entry-points group."""
|
|
76
|
+
try:
|
|
77
|
+
from importlib.metadata import entry_points
|
|
78
|
+
except ImportError:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
eps = entry_points()
|
|
82
|
+
# Python 3.12+ returns a SelectableGroups, older returns dict
|
|
83
|
+
if hasattr(eps, "select"):
|
|
84
|
+
group = eps.select(group="codebrain.parsers")
|
|
85
|
+
elif isinstance(eps, dict):
|
|
86
|
+
group = eps.get("codebrain.parsers", [])
|
|
87
|
+
else:
|
|
88
|
+
group = [ep for ep in eps if ep.group == "codebrain.parsers"]
|
|
89
|
+
|
|
90
|
+
for ep in group:
|
|
91
|
+
try:
|
|
92
|
+
parser_cls = ep.load()
|
|
93
|
+
# Skip entry-point parsers whose extensions are all already
|
|
94
|
+
# covered by built-in parsers (avoids regex overriding tree-sitter).
|
|
95
|
+
parser = parser_cls()
|
|
96
|
+
new_exts = parser.extensions() - frozenset(self._ext_map.keys())
|
|
97
|
+
if not new_exts:
|
|
98
|
+
_log.debug(
|
|
99
|
+
"Skipping plugin %s (%s) — all extensions already registered",
|
|
100
|
+
ep.name, type(parser).__name__,
|
|
101
|
+
)
|
|
102
|
+
continue
|
|
103
|
+
self.register(parser)
|
|
104
|
+
_log.debug("Loaded plugin parser: %s (%s)", ep.name, type(parser).__name__)
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
_log.warning("Failed to load parser plugin %s: %s", ep.name, exc)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_registry() -> ParserRegistry:
|
|
110
|
+
"""Return the global parser registry, initializing it on first call."""
|
|
111
|
+
global _registry
|
|
112
|
+
if _registry is None:
|
|
113
|
+
_registry = ParserRegistry()
|
|
114
|
+
_init_builtin_parsers(_registry)
|
|
115
|
+
_registry.load_entrypoints()
|
|
116
|
+
return _registry
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _init_builtin_parsers(registry: ParserRegistry) -> None:
|
|
120
|
+
"""Register the built-in Python, TypeScript, and config parsers."""
|
|
121
|
+
from codebrain.parser.python_parser import PythonParser
|
|
122
|
+
from codebrain.parser.config_parser import ConfigParser
|
|
123
|
+
|
|
124
|
+
registry.register(PythonParser())
|
|
125
|
+
|
|
126
|
+
# Prefer tree-sitter AST parser for TypeScript/JavaScript when available
|
|
127
|
+
try:
|
|
128
|
+
from codebrain.parser.typescript_treesitter import TypeScriptTreeSitterParser, _TS_AVAILABLE
|
|
129
|
+
if _TS_AVAILABLE:
|
|
130
|
+
registry.register(TypeScriptTreeSitterParser())
|
|
131
|
+
_log.debug("Using tree-sitter AST parser for TypeScript/JavaScript")
|
|
132
|
+
else:
|
|
133
|
+
from codebrain.parser.typescript_parser import TypeScriptParser
|
|
134
|
+
registry.register(TypeScriptParser())
|
|
135
|
+
_log.debug("tree-sitter not available, using regex parser for TypeScript/JavaScript")
|
|
136
|
+
except ImportError:
|
|
137
|
+
from codebrain.parser.typescript_parser import TypeScriptParser
|
|
138
|
+
registry.register(TypeScriptParser())
|
|
139
|
+
|
|
140
|
+
# Config parser: .env by extension, docker-compose by name
|
|
141
|
+
config = ConfigParser()
|
|
142
|
+
registry.register(config)
|
|
143
|
+
for name in config._COMPOSE_NAMES:
|
|
144
|
+
registry.register_name(name, config)
|