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
mcp_server/auto_init.py
ADDED
|
@@ -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)
|