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,257 @@
1
+ """
2
+ auto_init.py — Auto-initialization on first tool call for Codevira v1.6.
3
+
4
+ When an AI tool calls any Codevira MCP tool and the project has not been
5
+ initialized yet, this module:
6
+ 1. Detects the project from cwd
7
+ 2. Creates the centralized data directory (~/.codevira/projects/<key>/)
8
+ 3. Writes a minimal config.yaml
9
+ 4. Starts background indexing (graph generation + optional semantic index)
10
+ 5. Returns partial/minimal results while indexing progresses
11
+
12
+ Tools check ensure_project_initialized() before dispatching. If already
13
+ initialized, the call is a no-op (< 1ms overhead via a flag).
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import threading
19
+ import time
20
+ from dataclasses import dataclass, field
21
+ from pathlib import Path
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Module-level state — initialized once per process
26
+ _init_lock = threading.Lock()
27
+ # True once the init thread has been *started* (not necessarily finished).
28
+ # Named _init_started to be precise — use _progress["status"] to know if
29
+ # initialization has *completed*.
30
+ _init_started: bool = False
31
+ _indexing_thread: threading.Thread | None = None
32
+ _progress: dict = {
33
+ "status": "not_started", # not_started | initializing | indexing | ready | error
34
+ "files_indexed": 0,
35
+ "total_files": 0,
36
+ "elapsed_seconds": 0.0,
37
+ "error": None,
38
+ }
39
+ _progress_lock = threading.Lock()
40
+ _start_time: float | None = None
41
+
42
+
43
+ @dataclass
44
+ class InitStatus:
45
+ """Result of ensure_project_initialized()."""
46
+ ready: bool # True if the project was already initialized
47
+ indexing: bool # True if background indexing is running now
48
+ files_indexed: int = 0
49
+ total_files: int = 0
50
+
51
+
52
+ def get_init_progress() -> dict:
53
+ """Return current indexing progress. Thread-safe."""
54
+ with _progress_lock:
55
+ prog = dict(_progress)
56
+ if _start_time is not None:
57
+ prog["elapsed_seconds"] = round(time.monotonic() - _start_time, 1)
58
+ return prog
59
+
60
+
61
+ def ensure_project_initialized(project_root: Path | None = None) -> InitStatus:
62
+ """Check if the project is initialized; auto-init in background if not.
63
+
64
+ This is a fast path: if already done, returns immediately (<1ms).
65
+ Only the first call that finds an uninitialized project triggers init.
66
+
67
+ Args:
68
+ project_root: Override for testing. Uses get_project_root() by default.
69
+
70
+ Returns:
71
+ InitStatus with ready/indexing flags and progress counts.
72
+ """
73
+ global _init_started, _indexing_thread, _start_time
74
+
75
+ # Fast path — init thread already started this process
76
+ if _init_started:
77
+ with _progress_lock:
78
+ status = _progress["status"]
79
+ files_indexed = _progress["files_indexed"]
80
+ total_files = _progress["total_files"]
81
+ return InitStatus(
82
+ ready=(status == "ready"),
83
+ indexing=(status == "indexing"),
84
+ files_indexed=files_indexed,
85
+ total_files=total_files,
86
+ )
87
+
88
+ with _init_lock:
89
+ # Double-checked locking
90
+ if _init_started:
91
+ with _progress_lock:
92
+ return InitStatus(
93
+ ready=(_progress["status"] == "ready"),
94
+ indexing=(_progress["status"] == "indexing"),
95
+ files_indexed=_progress["files_indexed"],
96
+ total_files=_progress["total_files"],
97
+ )
98
+
99
+ from mcp_server.paths import get_project_root, get_data_dir
100
+ root = project_root or get_project_root()
101
+ data_dir = get_data_dir()
102
+
103
+ # Check if already initialized (config.yaml exists)
104
+ if (data_dir / "config.yaml").is_file():
105
+ _init_started = True
106
+ with _progress_lock:
107
+ _progress["status"] = "ready"
108
+ return InitStatus(ready=True, indexing=False)
109
+
110
+ # Not initialized — start background init
111
+ logger.info("Project not initialized. Starting auto-init for %s", root)
112
+ _start_time = time.monotonic()
113
+ with _progress_lock:
114
+ _progress["status"] = "initializing"
115
+
116
+ _indexing_thread = threading.Thread(
117
+ target=_run_background_init,
118
+ args=(root, data_dir),
119
+ daemon=True,
120
+ name="codevira-auto-init",
121
+ )
122
+ _indexing_thread.start()
123
+ _init_started = True # Prevent duplicate init threads — thread completion
124
+ # is tracked via _progress["status"], not this flag.
125
+
126
+ return InitStatus(ready=False, indexing=True)
127
+
128
+
129
+ def _run_background_init(project_root: Path, data_dir: Path) -> None:
130
+ """Background thread: detect project, write config, build graph, index files."""
131
+ global _start_time
132
+
133
+ try:
134
+ _update_progress(status="initializing")
135
+
136
+ # Step 1: Auto-detect project settings
137
+ from mcp_server.detect import auto_detect_project
138
+ detected = auto_detect_project(project_root)
139
+
140
+ # Step 2: Create data directory structure
141
+ (data_dir / "graph" / "changesets").mkdir(parents=True, exist_ok=True)
142
+ (data_dir / "codeindex").mkdir(parents=True, exist_ok=True)
143
+ (data_dir / "logs").mkdir(parents=True, exist_ok=True)
144
+
145
+ # Step 3: Write config.yaml
146
+ _write_config(data_dir, detected, project_root)
147
+
148
+ # Step 4: Write metadata.json (centralized storage marker)
149
+ _write_metadata(data_dir, project_root)
150
+
151
+ # Invalidate the data-dir cache so get_data_dir() now returns the newly
152
+ # created centralized directory instead of the pre-init default path.
153
+ from mcp_server.paths import invalidate_data_dir_cache
154
+ invalidate_data_dir_cache(project_root)
155
+
156
+ # Step 5: Register in global.db
157
+ _register_global(data_dir, project_root, detected)
158
+
159
+ # Step 6: Generate graph (fast — no ML deps required)
160
+ _update_progress(status="indexing")
161
+ try:
162
+ from indexer.graph_generator import generate_graph_sqlite
163
+ generate_graph_sqlite(str(project_root), str(data_dir / "graph" / "graph.db"))
164
+ logger.info("Auto-init: graph generated for %s", project_root)
165
+ except Exception as e:
166
+ logger.warning("Auto-init: graph generation failed: %s", e)
167
+
168
+ # Step 7: Count source files for progress tracking
169
+ try:
170
+ from mcp_server.gitignore import discover_source_files
171
+ files = discover_source_files(project_root)
172
+ _update_progress(total_files=len(files))
173
+ except Exception:
174
+ files = []
175
+
176
+ # Step 8: Build semantic search index (optional — requires [search] extras)
177
+ # Uses start_background_full_index() which holds _chroma_write_lock to prevent
178
+ # race conditions with the file watcher.
179
+ try:
180
+ from indexer.index_codebase import start_background_full_index
181
+ _update_progress(status="indexing")
182
+ idx_thread = start_background_full_index()
183
+ # Wait up to 5 minutes; if ChromaDB or embedding model hangs we still
184
+ # surface "ready" so tool calls aren't blocked indefinitely.
185
+ idx_thread.join(timeout=300)
186
+ if idx_thread.is_alive():
187
+ logger.warning("Auto-init: semantic indexing timed out after 5 min; "
188
+ "continuing in graph-only mode")
189
+ _update_progress(files_indexed=len(files), status="ready")
190
+ except ImportError:
191
+ # ChromaDB not installed — graph-only mode is fine
192
+ _update_progress(files_indexed=0, status="ready")
193
+ except Exception as e:
194
+ logger.warning("Auto-init: semantic indexing failed (non-fatal): %s", e)
195
+ _update_progress(status="ready")
196
+
197
+ logger.info("Auto-init complete for %s (%.1fs)", project_root,
198
+ time.monotonic() - (_start_time or 0))
199
+
200
+ except Exception as e:
201
+ logger.error("Auto-init failed: %s", e)
202
+ _update_progress(status="error", error=str(e))
203
+
204
+
205
+ def _update_progress(**kwargs) -> None:
206
+ with _progress_lock:
207
+ _progress.update(kwargs)
208
+
209
+
210
+ def _write_config(data_dir: Path, detected: dict, project_root: Path) -> None:
211
+ """Write .codevira/config.yaml (or centralized equivalent)."""
212
+ import yaml
213
+ config = {
214
+ "project": {
215
+ "name": detected["name"],
216
+ "language": detected["language"],
217
+ "watched_dirs": detected["watched_dirs"],
218
+ "file_extensions": detected["file_extensions"],
219
+ "collection_name": detected["collection_name"],
220
+ }
221
+ }
222
+ with open(data_dir / "config.yaml", "w") as f:
223
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
224
+
225
+
226
+ def _write_metadata(data_dir: Path, project_root: Path) -> None:
227
+ """Write metadata.json so the centralized dir is recognized."""
228
+ import json
229
+ from datetime import datetime, timezone
230
+ from mcp_server.paths import _sanitize_path_key, _get_git_remote_url
231
+
232
+ metadata = {
233
+ "path_key": _sanitize_path_key(project_root),
234
+ "git_remote": _get_git_remote_url(project_root),
235
+ "original_path": str(project_root),
236
+ "created_at": datetime.now(timezone.utc).isoformat(),
237
+ "version": "1.6.0",
238
+ "auto_initialized": True,
239
+ }
240
+ (data_dir / "metadata.json").write_text(json.dumps(metadata, indent=2))
241
+
242
+
243
+ def _register_global(data_dir: Path, project_root: Path, detected: dict) -> None:
244
+ """Register the project in global.db for cross-project intelligence."""
245
+ try:
246
+ from indexer.global_db import GlobalDB
247
+ from mcp_server.paths import get_global_db_path, _get_git_remote_url
248
+ gdb = GlobalDB(get_global_db_path())
249
+ gdb.register_project(
250
+ path=str(data_dir),
251
+ name=detected["name"],
252
+ language=detected["language"],
253
+ git_remote=_get_git_remote_url(project_root),
254
+ )
255
+ gdb.close()
256
+ except Exception as e:
257
+ logger.warning("Auto-init: could not register in global.db: %s", e)