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.
Files changed (58) hide show
  1. codevira-1.6.0.dist-info/LICENSE +21 -0
  2. codevira-1.6.0.dist-info/METADATA +477 -0
  3. codevira-1.6.0.dist-info/RECORD +58 -0
  4. codevira-1.6.0.dist-info/WHEEL +5 -0
  5. codevira-1.6.0.dist-info/entry_points.txt +2 -0
  6. codevira-1.6.0.dist-info/top_level.txt +2 -0
  7. indexer/__init__.py +1 -0
  8. indexer/chunker.py +428 -0
  9. indexer/global_db.py +197 -0
  10. indexer/graph_generator.py +380 -0
  11. indexer/index_codebase.py +588 -0
  12. indexer/outcome_tracker.py +172 -0
  13. indexer/rule_learner.py +186 -0
  14. indexer/sqlite_graph.py +640 -0
  15. indexer/treesitter_parser.py +423 -0
  16. mcp_server/__init__.py +1 -0
  17. mcp_server/__main__.py +20 -0
  18. mcp_server/auto_init.py +257 -0
  19. mcp_server/cli.py +622 -0
  20. mcp_server/crash_logger.py +236 -0
  21. mcp_server/data/__init__.py +1 -0
  22. mcp_server/data/agents/builder.md +84 -0
  23. mcp_server/data/agents/developer.md +111 -0
  24. mcp_server/data/agents/documenter.md +138 -0
  25. mcp_server/data/agents/orchestrator.md +96 -0
  26. mcp_server/data/agents/planner.md +106 -0
  27. mcp_server/data/agents/reviewer.md +82 -0
  28. mcp_server/data/agents/tester.md +83 -0
  29. mcp_server/data/config.example.yaml +33 -0
  30. mcp_server/data/rules/coding-standards.md +48 -0
  31. mcp_server/data/rules/engineering-excellence.md +28 -0
  32. mcp_server/data/rules/git-cicd-governance.md +32 -0
  33. mcp_server/data/rules/git_commits.md +130 -0
  34. mcp_server/data/rules/incremental-updates.md +5 -0
  35. mcp_server/data/rules/master_rule.md +187 -0
  36. mcp_server/data/rules/multi-language.md +19 -0
  37. mcp_server/data/rules/persistence.md +21 -0
  38. mcp_server/data/rules/resilience-observability.md +17 -0
  39. mcp_server/data/rules/smoke-testing.md +48 -0
  40. mcp_server/data/rules/testing-standards.md +23 -0
  41. mcp_server/detect.py +284 -0
  42. mcp_server/gitignore.py +284 -0
  43. mcp_server/global_sync.py +187 -0
  44. mcp_server/http_server.py +341 -0
  45. mcp_server/ide_inject.py +444 -0
  46. mcp_server/launchd.py +156 -0
  47. mcp_server/migrate.py +215 -0
  48. mcp_server/paths.py +256 -0
  49. mcp_server/prompts.py +136 -0
  50. mcp_server/server.py +1049 -0
  51. mcp_server/tools/__init__.py +0 -0
  52. mcp_server/tools/changesets.py +223 -0
  53. mcp_server/tools/code_reader.py +335 -0
  54. mcp_server/tools/graph.py +637 -0
  55. mcp_server/tools/learning.py +238 -0
  56. mcp_server/tools/playbook.py +89 -0
  57. mcp_server/tools/roadmap.py +599 -0
  58. 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()