codevira 1.6.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.
- codevira-1.6.0.dist-info/LICENSE +21 -0
- codevira-1.6.0.dist-info/METADATA +477 -0
- codevira-1.6.0.dist-info/RECORD +58 -0
- codevira-1.6.0.dist-info/WHEEL +5 -0
- codevira-1.6.0.dist-info/entry_points.txt +2 -0
- codevira-1.6.0.dist-info/top_level.txt +2 -0
- indexer/__init__.py +1 -0
- indexer/chunker.py +428 -0
- indexer/global_db.py +197 -0
- indexer/graph_generator.py +380 -0
- indexer/index_codebase.py +588 -0
- indexer/outcome_tracker.py +172 -0
- indexer/rule_learner.py +186 -0
- indexer/sqlite_graph.py +640 -0
- indexer/treesitter_parser.py +423 -0
- mcp_server/__init__.py +1 -0
- mcp_server/__main__.py +20 -0
- mcp_server/auto_init.py +257 -0
- mcp_server/cli.py +622 -0
- mcp_server/crash_logger.py +236 -0
- mcp_server/data/__init__.py +1 -0
- mcp_server/data/agents/builder.md +84 -0
- mcp_server/data/agents/developer.md +111 -0
- mcp_server/data/agents/documenter.md +138 -0
- mcp_server/data/agents/orchestrator.md +96 -0
- mcp_server/data/agents/planner.md +106 -0
- mcp_server/data/agents/reviewer.md +82 -0
- mcp_server/data/agents/tester.md +83 -0
- mcp_server/data/config.example.yaml +33 -0
- mcp_server/data/rules/coding-standards.md +48 -0
- mcp_server/data/rules/engineering-excellence.md +28 -0
- mcp_server/data/rules/git-cicd-governance.md +32 -0
- mcp_server/data/rules/git_commits.md +130 -0
- mcp_server/data/rules/incremental-updates.md +5 -0
- mcp_server/data/rules/master_rule.md +187 -0
- mcp_server/data/rules/multi-language.md +19 -0
- mcp_server/data/rules/persistence.md +21 -0
- mcp_server/data/rules/resilience-observability.md +17 -0
- mcp_server/data/rules/smoke-testing.md +48 -0
- mcp_server/data/rules/testing-standards.md +23 -0
- mcp_server/detect.py +284 -0
- mcp_server/gitignore.py +284 -0
- mcp_server/global_sync.py +187 -0
- mcp_server/http_server.py +341 -0
- mcp_server/ide_inject.py +444 -0
- mcp_server/launchd.py +156 -0
- mcp_server/migrate.py +215 -0
- mcp_server/paths.py +256 -0
- mcp_server/prompts.py +136 -0
- mcp_server/server.py +1049 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/changesets.py +223 -0
- mcp_server/tools/code_reader.py +335 -0
- mcp_server/tools/graph.py +637 -0
- mcp_server/tools/learning.py +238 -0
- mcp_server/tools/playbook.py +89 -0
- mcp_server/tools/roadmap.py +599 -0
- mcp_server/tools/search.py +145 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""
|
|
2
|
+
treesitter_parser.py — Unified tree-sitter parser using tree-sitter-language-pack.
|
|
3
|
+
|
|
4
|
+
Supports parsing AST nodes (classes, functions, methods, imports) across many languages.
|
|
5
|
+
Python files are NOT handled here — they use the stdlib `ast` module.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import tree_sitter_language_pack as tslp
|
|
15
|
+
from tree_sitter import Node
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Data classes — unified output across all languages
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ParsedSymbol:
|
|
25
|
+
name: str
|
|
26
|
+
kind: str # "function" | "class" | "method" | "interface" | "struct" | "enum" | "trait" | "impl"
|
|
27
|
+
signature_line: str
|
|
28
|
+
start_line: int
|
|
29
|
+
end_line: int
|
|
30
|
+
docstring: str | None = None
|
|
31
|
+
is_public: bool = True
|
|
32
|
+
methods: list[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ParsedImport:
|
|
37
|
+
module: str
|
|
38
|
+
raw_line: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ParsedFile:
|
|
43
|
+
file_path: str
|
|
44
|
+
language: str
|
|
45
|
+
symbols: list[ParsedSymbol]
|
|
46
|
+
imports: list[ParsedImport]
|
|
47
|
+
module_docstring: str | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Language registry
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
EXTENSION_MAP: dict[str, str] = {
|
|
55
|
+
".ts": "typescript",
|
|
56
|
+
".tsx": "tsx",
|
|
57
|
+
".go": "go",
|
|
58
|
+
".rs": "rust",
|
|
59
|
+
".java": "java",
|
|
60
|
+
".cs": "csharp",
|
|
61
|
+
".rb": "ruby",
|
|
62
|
+
".cpp": "cpp",
|
|
63
|
+
".cc": "cpp",
|
|
64
|
+
".cxx": "cpp",
|
|
65
|
+
".c": "c",
|
|
66
|
+
".h": "c",
|
|
67
|
+
".hpp": "cpp",
|
|
68
|
+
".kt": "kotlin",
|
|
69
|
+
".swift": "swift",
|
|
70
|
+
".php": "php",
|
|
71
|
+
".sol": "solidity",
|
|
72
|
+
".vue": "vue",
|
|
73
|
+
".js": "javascript",
|
|
74
|
+
".jsx": "javascript",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def get_language(extension: str) -> str | None:
|
|
78
|
+
return EXTENSION_MAP.get(extension.lower())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Node mapping configurations
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
_CLASS_TYPES = {
|
|
86
|
+
"javascript": ["class_declaration", "class"],
|
|
87
|
+
"typescript": ["class_declaration", "class", "interface_declaration"],
|
|
88
|
+
"tsx": ["class_declaration", "class", "interface_declaration"],
|
|
89
|
+
"go": ["type_declaration"],
|
|
90
|
+
"rust": ["struct_item", "enum_item", "impl_item", "trait_item"],
|
|
91
|
+
"java": ["class_declaration", "interface_declaration", "enum_declaration"],
|
|
92
|
+
"c": ["struct_specifier", "type_definition"],
|
|
93
|
+
"cpp": ["class_specifier", "struct_specifier"],
|
|
94
|
+
"csharp": ["class_declaration", "interface_declaration", "enum_declaration", "struct_declaration"],
|
|
95
|
+
"ruby": ["class", "module"],
|
|
96
|
+
"kotlin": ["class_declaration", "object_declaration"],
|
|
97
|
+
"swift": ["class_declaration", "struct_declaration", "protocol_declaration"],
|
|
98
|
+
"php": ["class_declaration", "interface_declaration"],
|
|
99
|
+
"solidity": ["contract_declaration", "interface_declaration", "library_declaration"],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_FUNCTION_TYPES = {
|
|
103
|
+
"javascript": ["function_declaration", "method_definition", "arrow_function"],
|
|
104
|
+
"typescript": ["function_declaration", "method_definition", "arrow_function"],
|
|
105
|
+
"tsx": ["function_declaration", "method_definition", "arrow_function"],
|
|
106
|
+
"go": ["function_declaration", "method_declaration"],
|
|
107
|
+
"rust": ["function_item"],
|
|
108
|
+
"java": ["method_declaration", "constructor_declaration"],
|
|
109
|
+
"c": ["function_definition"],
|
|
110
|
+
"cpp": ["function_definition"],
|
|
111
|
+
"csharp": ["method_declaration", "constructor_declaration"],
|
|
112
|
+
"ruby": ["method", "singleton_method"],
|
|
113
|
+
"kotlin": ["function_declaration"],
|
|
114
|
+
"swift": ["function_declaration"],
|
|
115
|
+
"php": ["function_definition", "method_declaration"],
|
|
116
|
+
"solidity": ["function_definition", "constructor_definition"],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_IMPORT_TYPES = {
|
|
120
|
+
"javascript": ["import_statement"],
|
|
121
|
+
"typescript": ["import_statement"],
|
|
122
|
+
"tsx": ["import_statement"],
|
|
123
|
+
"go": ["import_declaration", "import_spec"],
|
|
124
|
+
"rust": ["use_declaration"],
|
|
125
|
+
"java": ["import_declaration"],
|
|
126
|
+
"c": ["preproc_include"],
|
|
127
|
+
"cpp": ["preproc_include"],
|
|
128
|
+
"csharp": ["using_directive"],
|
|
129
|
+
"ruby": ["call"],
|
|
130
|
+
"kotlin": ["import_header"],
|
|
131
|
+
"swift": ["import_declaration"],
|
|
132
|
+
"php": ["namespace_use_declaration"],
|
|
133
|
+
"solidity": ["import_directive"],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Parsing core
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
def parse_file(file_path: str, language: str | None = None) -> ParsedFile:
|
|
141
|
+
path = Path(file_path)
|
|
142
|
+
if not path.exists():
|
|
143
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
144
|
+
|
|
145
|
+
if language is None:
|
|
146
|
+
language = get_language(path.suffix)
|
|
147
|
+
if language is None:
|
|
148
|
+
raise ValueError(f"Cannot infer language for extension: {path.suffix}")
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
parser = tslp.get_parser(language)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
raise ValueError(f"Failed to load parser for {language}: {e}")
|
|
154
|
+
|
|
155
|
+
source_bytes = path.read_bytes()
|
|
156
|
+
source_text = source_bytes.decode("utf-8", errors="replace")
|
|
157
|
+
source_lines = source_text.splitlines()
|
|
158
|
+
|
|
159
|
+
tree = parser.parse(source_bytes)
|
|
160
|
+
root = tree.root_node
|
|
161
|
+
|
|
162
|
+
symbols: list[ParsedSymbol] = []
|
|
163
|
+
imports: list[ParsedImport] = []
|
|
164
|
+
seen: set[str] = set()
|
|
165
|
+
|
|
166
|
+
class_types = set(_CLASS_TYPES.get(language, []))
|
|
167
|
+
func_types = set(_FUNCTION_TYPES.get(language, []))
|
|
168
|
+
import_types = set(_IMPORT_TYPES.get(language, []))
|
|
169
|
+
|
|
170
|
+
def walk(node: Node, parent_kind: str | None = None, is_exported: bool = False):
|
|
171
|
+
if not node:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
node_type = node.type
|
|
175
|
+
current_exported = is_exported or "export" in node_type
|
|
176
|
+
|
|
177
|
+
# 1. Imports
|
|
178
|
+
if node_type in import_types:
|
|
179
|
+
raw = _node_text(node, source_bytes)
|
|
180
|
+
# JS/TS/Java: quoted module strings
|
|
181
|
+
matches = re.findall(r'["\']([^"\']+)["\']', raw)
|
|
182
|
+
if matches:
|
|
183
|
+
for m in matches:
|
|
184
|
+
imports.append(ParsedImport(module=m, raw_line=raw.strip()))
|
|
185
|
+
else:
|
|
186
|
+
# Rust/Go/C: extract scoped path or identifier directly
|
|
187
|
+
# For Rust use_declaration: "use std::collections::HashMap;" → "std::collections::HashMap"
|
|
188
|
+
# For Go import_spec: "fmt" or quoted
|
|
189
|
+
path_parts = re.findall(r'(?:use|import)\s+(.+?)(?:\s*;|\s*$)', raw.strip())
|
|
190
|
+
if path_parts:
|
|
191
|
+
module = path_parts[0].strip().rstrip(';')
|
|
192
|
+
imports.append(ParsedImport(module=module, raw_line=raw.strip()))
|
|
193
|
+
elif raw.strip():
|
|
194
|
+
imports.append(ParsedImport(module=raw.strip(), raw_line=raw.strip()))
|
|
195
|
+
|
|
196
|
+
# 2. Classes / Types
|
|
197
|
+
elif node_type in class_types:
|
|
198
|
+
name_node = node.child_by_field_name("name")
|
|
199
|
+
name = _node_text(name_node, source_bytes) if name_node else None
|
|
200
|
+
|
|
201
|
+
if not name:
|
|
202
|
+
for child in node.children:
|
|
203
|
+
if "identifier" in child.type or child.type == "type_identifier":
|
|
204
|
+
name = _node_text(child, source_bytes)
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
# Go: type_declaration wraps type_spec which contains the actual struct/interface
|
|
208
|
+
if language == "go" and node_type == "type_declaration":
|
|
209
|
+
for spec in node.children:
|
|
210
|
+
if spec.type == "type_spec":
|
|
211
|
+
# Extract the name from type_spec
|
|
212
|
+
for c in spec.children:
|
|
213
|
+
if c.type == "type_identifier":
|
|
214
|
+
name = _node_text(c, source_bytes)
|
|
215
|
+
elif c.type == "struct_type":
|
|
216
|
+
kind_override = "struct"
|
|
217
|
+
elif c.type == "interface_type":
|
|
218
|
+
kind_override = "interface"
|
|
219
|
+
if name and name not in seen:
|
|
220
|
+
seen.add(name)
|
|
221
|
+
k = kind_override if 'kind_override' in dir() else "struct"
|
|
222
|
+
public = _is_public(name, node, language)
|
|
223
|
+
symbols.append(ParsedSymbol(
|
|
224
|
+
name=name,
|
|
225
|
+
kind=k,
|
|
226
|
+
signature_line=_first_line_text(node, source_lines),
|
|
227
|
+
start_line=node.start_point[0] + 1,
|
|
228
|
+
end_line=node.end_point[0] + 1,
|
|
229
|
+
docstring=_get_preceding_comment(node, source_lines),
|
|
230
|
+
is_public=public,
|
|
231
|
+
methods=[]
|
|
232
|
+
))
|
|
233
|
+
# Walk children for nested declarations
|
|
234
|
+
for child in node.children:
|
|
235
|
+
walk(child, parent_kind, current_exported)
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
if name and name not in seen:
|
|
239
|
+
seen.add(name)
|
|
240
|
+
methods = []
|
|
241
|
+
body = node.child_by_field_name("body") or node.child_by_field_name("declaration_list") or node
|
|
242
|
+
for child in body.children:
|
|
243
|
+
if child.type in func_types:
|
|
244
|
+
m_name_node = child.child_by_field_name("name")
|
|
245
|
+
if m_name_node:
|
|
246
|
+
methods.append(_node_text(m_name_node, source_bytes))
|
|
247
|
+
|
|
248
|
+
kind = "class"
|
|
249
|
+
if "interface" in node_type: kind = "interface"
|
|
250
|
+
elif "struct" in node_type or "type" in node_type: kind = "struct"
|
|
251
|
+
elif "enum" in node_type: kind = "enum"
|
|
252
|
+
elif "trait" in node_type: kind = "trait"
|
|
253
|
+
elif "impl" in node_type: kind = "impl"
|
|
254
|
+
|
|
255
|
+
if language == "rust" and "impl" in node_type:
|
|
256
|
+
for c in node.children:
|
|
257
|
+
if c.type == "type_identifier":
|
|
258
|
+
name = _node_text(c, source_bytes)
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
public = current_exported or _is_public(name, node, language)
|
|
262
|
+
|
|
263
|
+
symbols.append(ParsedSymbol(
|
|
264
|
+
name=name,
|
|
265
|
+
kind=kind,
|
|
266
|
+
signature_line=_first_line_text(node, source_lines),
|
|
267
|
+
start_line=node.start_point[0] + 1,
|
|
268
|
+
end_line=node.end_point[0] + 1,
|
|
269
|
+
docstring=_get_preceding_comment(node, source_lines),
|
|
270
|
+
is_public=public,
|
|
271
|
+
methods=methods
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
for child in node.children:
|
|
275
|
+
walk(child, "class", current_exported)
|
|
276
|
+
|
|
277
|
+
# 3. Functions / Methods
|
|
278
|
+
elif node_type in func_types:
|
|
279
|
+
name_node = node.child_by_field_name("name")
|
|
280
|
+
name = _node_text(name_node, source_bytes) if name_node else None
|
|
281
|
+
if name and name not in seen:
|
|
282
|
+
seen.add(name)
|
|
283
|
+
kind = "method" if parent_kind == "class" or "method" in node_type else "function"
|
|
284
|
+
|
|
285
|
+
if language == "rust" and node.parent and node.parent.type == "declaration_list":
|
|
286
|
+
pass
|
|
287
|
+
else:
|
|
288
|
+
public = current_exported or _is_public(name, node, language)
|
|
289
|
+
symbols.append(ParsedSymbol(
|
|
290
|
+
name=name,
|
|
291
|
+
kind=kind,
|
|
292
|
+
signature_line=_first_line_text(node, source_lines),
|
|
293
|
+
start_line=node.start_point[0] + 1,
|
|
294
|
+
end_line=node.end_point[0] + 1,
|
|
295
|
+
docstring=_get_preceding_comment(node, source_lines),
|
|
296
|
+
is_public=public
|
|
297
|
+
))
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
else:
|
|
301
|
+
for child in node.children:
|
|
302
|
+
walk(child, parent_kind, current_exported)
|
|
303
|
+
|
|
304
|
+
walk(root)
|
|
305
|
+
mod_doc = _extract_module_docstring(root, source_lines)
|
|
306
|
+
|
|
307
|
+
return ParsedFile(
|
|
308
|
+
file_path=file_path,
|
|
309
|
+
language=language,
|
|
310
|
+
symbols=symbols,
|
|
311
|
+
imports=imports,
|
|
312
|
+
module_docstring=mod_doc,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def get_symbol_source(file_path: str, symbol_name: str, language: str | None = None) -> dict:
|
|
316
|
+
path = Path(file_path)
|
|
317
|
+
if not path.exists():
|
|
318
|
+
return {"found": False, "error": f"File not found: {file_path}"}
|
|
319
|
+
|
|
320
|
+
if language is None:
|
|
321
|
+
language = get_language(path.suffix)
|
|
322
|
+
if language is None:
|
|
323
|
+
return {"found": False, "error": f"Unsupported extension: {path.suffix}"}
|
|
324
|
+
|
|
325
|
+
parsed = parse_file(file_path, language)
|
|
326
|
+
source_text = path.read_text(encoding="utf-8", errors="replace")
|
|
327
|
+
source_lines = source_text.splitlines()
|
|
328
|
+
|
|
329
|
+
for sym in parsed.symbols:
|
|
330
|
+
if sym.name == symbol_name:
|
|
331
|
+
lines = source_lines[sym.start_line - 1 : sym.end_line]
|
|
332
|
+
return {
|
|
333
|
+
"found": True,
|
|
334
|
+
"file_path": file_path,
|
|
335
|
+
"symbol": symbol_name,
|
|
336
|
+
"kind": sym.kind,
|
|
337
|
+
"start_line": sym.start_line,
|
|
338
|
+
"end_line": sym.end_line,
|
|
339
|
+
"docstring": sym.docstring,
|
|
340
|
+
"source": "\n".join(lines),
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
"found": False,
|
|
345
|
+
"file_path": file_path,
|
|
346
|
+
"symbol": symbol_name,
|
|
347
|
+
"error": f"Symbol '{symbol_name}' not found in {file_path}",
|
|
348
|
+
"available_symbols": [s.name for s in parsed.symbols],
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
# Helpers
|
|
354
|
+
# ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
def _node_text(node: Node, source_bytes: bytes) -> str:
|
|
357
|
+
return source_bytes[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
|
|
358
|
+
|
|
359
|
+
def _first_line_text(node: Node, source_lines: list[str]) -> str:
|
|
360
|
+
start_row = node.start_point[0]
|
|
361
|
+
if start_row < len(source_lines):
|
|
362
|
+
line = source_lines[start_row].strip()
|
|
363
|
+
if len(line) > 120:
|
|
364
|
+
return line[:117] + "..."
|
|
365
|
+
return line
|
|
366
|
+
return ""
|
|
367
|
+
|
|
368
|
+
def _get_preceding_comment(node: Node, source_lines: list[str]) -> str | None:
|
|
369
|
+
comments = []
|
|
370
|
+
current_line = node.start_point[0] - 1
|
|
371
|
+
|
|
372
|
+
while current_line >= 0:
|
|
373
|
+
line = source_lines[current_line].strip()
|
|
374
|
+
if line.startswith(("//", "#", "*", "/*", "*/", "///")):
|
|
375
|
+
comments.append(line)
|
|
376
|
+
current_line -= 1
|
|
377
|
+
elif not line:
|
|
378
|
+
current_line -= 1
|
|
379
|
+
else:
|
|
380
|
+
break
|
|
381
|
+
|
|
382
|
+
if comments:
|
|
383
|
+
comments.reverse()
|
|
384
|
+
return "\n".join(comments)
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
def _extract_module_docstring(root: Node, source_lines: list[str]) -> str | None:
|
|
388
|
+
if not root.children: return None
|
|
389
|
+
first = root.children[0]
|
|
390
|
+
for i in range(min(5, len(root.children))):
|
|
391
|
+
n = root.children[i]
|
|
392
|
+
if n.type == "comment" or n.type == "line_comment" or n.type == "block_comment":
|
|
393
|
+
doc = _node_text(n, "\n".join(source_lines).encode('utf-8'))
|
|
394
|
+
return doc.strip()
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
def _is_public(name: str, node: Node, language: str) -> bool:
|
|
398
|
+
if not name:
|
|
399
|
+
return False
|
|
400
|
+
if language == "go":
|
|
401
|
+
return name[0].isupper()
|
|
402
|
+
if language in ("typescript", "tsx", "javascript", "jsx"):
|
|
403
|
+
if name.startswith("_"): return False
|
|
404
|
+
if node.parent and "export" in node.parent.type:
|
|
405
|
+
return True
|
|
406
|
+
return False
|
|
407
|
+
if language == "rust":
|
|
408
|
+
# Check if any child token is 'pub'
|
|
409
|
+
for child in node.children:
|
|
410
|
+
if child.type == "visibility_modifier" or child.type == "pub":
|
|
411
|
+
return True
|
|
412
|
+
# Also check the source text of the first line
|
|
413
|
+
try:
|
|
414
|
+
first_line = node.start_point[0]
|
|
415
|
+
# Walk up through source bytes to find 'pub' keyword
|
|
416
|
+
node_src = node.text.decode("utf-8", errors="replace") if node.text else ""
|
|
417
|
+
return node_src.lstrip().startswith("pub ")
|
|
418
|
+
except Exception:
|
|
419
|
+
pass
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
return not name.startswith("_")
|
|
423
|
+
|
mcp_server/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Codevira MCP Server package
|
mcp_server/__main__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Allows running the MCP server as:
|
|
3
|
+
python -m mcp_server [--project-dir <path>] [init|index|status]
|
|
4
|
+
|
|
5
|
+
This is the recommended approach when `codevira` is not in the MCP
|
|
6
|
+
host's PATH (common on macOS with Homebrew or user-level pip installs).
|
|
7
|
+
|
|
8
|
+
MCP config example:
|
|
9
|
+
{
|
|
10
|
+
"mcpServers": {
|
|
11
|
+
"codevira": {
|
|
12
|
+
"command": "python",
|
|
13
|
+
"args": ["-m", "mcp_server", "--project-dir", "/path/to/project"]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
"""
|
|
18
|
+
from mcp_server.cli import main
|
|
19
|
+
|
|
20
|
+
main()
|