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,341 @@
1
+ """
2
+ http_server.py — HTTP/Streamable transport for Codevira MCP server.
3
+
4
+ Runs the same 36 MCP tools as stdio mode but over HTTP, enabling:
5
+ - URL-based MCP registration in Claude Code, Cursor, Windsurf
6
+ - HTTPS via mkcert for locally-trusted certificates (required by Claude.ai)
7
+ - Parallel multi-client connections without spawning a process per client
8
+
9
+ Endpoint layout:
10
+ POST /mcp — Streamable HTTP (MCP 2025-03-26 spec, preferred)
11
+ GET / — Health check → {"status": "ok", "transport": "streamable-http"}
12
+
13
+ Usage:
14
+ codevira serve # HTTP on 127.0.0.1:7007
15
+ codevira serve --port 7443 --https # HTTPS on 127.0.0.1:7443
16
+ codevira serve --host 0.0.0.0 # Expose on all interfaces (LAN)
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import contextlib
21
+ import logging
22
+ import secrets
23
+ import subprocess
24
+ from pathlib import Path
25
+ from typing import AsyncIterator
26
+
27
+ from starlette.applications import Starlette
28
+ from starlette.middleware import Middleware
29
+ from starlette.middleware.base import BaseHTTPMiddleware
30
+ from starlette.requests import Request
31
+ from starlette.responses import JSONResponse
32
+ from starlette.routing import Mount, Route
33
+
34
+ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
35
+
36
+ # Import the Server instance — all tool handlers are registered at module level
37
+ # via decorators, so this is safe to import from both stdio and HTTP modes.
38
+ from mcp_server.server import server
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ # Bearer token file — auto-generated on first non-loopback serve
43
+ _TOKEN_FILE_NAME = "http_bearer_token"
44
+
45
+
46
+ def _certs_dir() -> Path:
47
+ from mcp_server.paths import get_global_home
48
+ return get_global_home() / "certs"
49
+
50
+
51
+ def _cert_file() -> Path:
52
+ return _certs_dir() / "localhost.pem"
53
+
54
+
55
+ def _key_file() -> Path:
56
+ return _certs_dir() / "localhost-key.pem"
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Bearer token auth (required when binding to non-loopback addresses)
61
+ # ---------------------------------------------------------------------------
62
+
63
+ def _get_or_create_token() -> str:
64
+ """Return the bearer token, creating one if it doesn't exist.
65
+
66
+ Token is stored in ~/.codevira/http_bearer_token so it persists across
67
+ restarts but is NOT committed to any project repo.
68
+ """
69
+ from mcp_server.paths import get_global_home
70
+ token_path = get_global_home() / _TOKEN_FILE_NAME
71
+ if token_path.exists():
72
+ token = token_path.read_text(encoding="utf-8").strip()
73
+ if token:
74
+ return token
75
+ token = secrets.token_urlsafe(32)
76
+ token_path.write_text(token + "\n", encoding="utf-8")
77
+ token_path.chmod(0o600)
78
+ return token
79
+
80
+
81
+ class _BearerAuthMiddleware(BaseHTTPMiddleware):
82
+ """Reject requests without a valid Bearer token.
83
+
84
+ Applied only when the server binds to a non-loopback address (0.0.0.0, LAN IP, etc.)
85
+ to prevent unauthenticated access from other machines on the network.
86
+ The health endpoint (GET /) is exempt so uptime monitors still work.
87
+ """
88
+
89
+ def __init__(self, app, token: str):
90
+ super().__init__(app)
91
+ self._token = token
92
+
93
+ async def dispatch(self, request: Request, call_next):
94
+ # Allow health check without auth
95
+ if request.url.path == "/" and request.method == "GET":
96
+ return await call_next(request)
97
+
98
+ auth = request.headers.get("Authorization", "")
99
+ if auth != f"Bearer {self._token}":
100
+ return JSONResponse(
101
+ {"error": "Unauthorized", "hint": "Set Authorization: Bearer <token> header"},
102
+ status_code=401,
103
+ )
104
+ return await call_next(request)
105
+
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # Certificate helpers
109
+ # ---------------------------------------------------------------------------
110
+
111
+ def _certs_exist() -> bool:
112
+ return _cert_file().exists() and _key_file().exists()
113
+
114
+
115
+ def generate_mkcert_certs() -> tuple[Path, Path]:
116
+ """
117
+ Generate trusted localhost certs using mkcert.
118
+ Requires mkcert to be installed and its CA to be in the system trust store
119
+ (`mkcert -install` must have been run at least once).
120
+ Returns (cert_path, key_path).
121
+ Raises RuntimeError if mkcert is not found.
122
+ """
123
+ import shutil
124
+
125
+ if not shutil.which("mkcert"):
126
+ raise RuntimeError(
127
+ "mkcert not found.\n"
128
+ " Install: brew install mkcert\n"
129
+ " Trust CA: mkcert -install\n"
130
+ "Then re-run: codevira serve --https"
131
+ )
132
+
133
+ cert_f = _cert_file()
134
+ key_f = _key_file()
135
+ _certs_dir().mkdir(parents=True, exist_ok=True)
136
+
137
+ subprocess.run(
138
+ [
139
+ "mkcert",
140
+ "-cert-file", str(cert_f),
141
+ "-key-file", str(key_f),
142
+ "localhost",
143
+ "127.0.0.1",
144
+ "::1",
145
+ ],
146
+ check=True,
147
+ )
148
+ return cert_f, key_f
149
+
150
+
151
+ # ---------------------------------------------------------------------------
152
+ # ASGI app factory
153
+ # ---------------------------------------------------------------------------
154
+
155
+ def create_app(bearer_token: str | None = None) -> Starlette:
156
+ """
157
+ Build and return the Starlette ASGI application.
158
+
159
+ Args:
160
+ bearer_token: If set, all requests (except GET /) must include
161
+ an ``Authorization: Bearer <token>`` header.
162
+ Used when binding to non-loopback addresses.
163
+
164
+ Routes:
165
+ GET / → health check (useful for uptime monitoring)
166
+ POST /mcp → MCP Streamable HTTP transport (MCP 2025-03-26)
167
+ GET /mcp → MCP SSE stream (some clients open a GET first)
168
+ DELETE /mcp → session teardown
169
+ """
170
+ session_manager = StreamableHTTPSessionManager(
171
+ app=server,
172
+ stateless=True, # no resumability — simpler for local single-user use
173
+ json_response=False, # SSE streaming (not JSON batching)
174
+ )
175
+
176
+ @contextlib.asynccontextmanager
177
+ async def lifespan(_app: Starlette) -> AsyncIterator[None]:
178
+ async with session_manager.run():
179
+ yield
180
+
181
+ async def health(_req: Request) -> JSONResponse:
182
+ return JSONResponse({"status": "ok", "transport": "streamable-http", "server": "codevira"})
183
+
184
+ middleware = []
185
+ if bearer_token:
186
+ middleware.append(Middleware(_BearerAuthMiddleware, token=bearer_token))
187
+
188
+ return Starlette(
189
+ routes=[
190
+ Route("/", endpoint=health, methods=["GET"]),
191
+ Mount("/mcp", app=session_manager.handle_request),
192
+ ],
193
+ lifespan=lifespan,
194
+ middleware=middleware,
195
+ )
196
+
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # Server runner
200
+ # ---------------------------------------------------------------------------
201
+
202
+ def run_http_server(
203
+ host: str = "127.0.0.1",
204
+ port: int = 7007,
205
+ use_https: bool = False,
206
+ project_dir: Path | None = None,
207
+ ) -> None:
208
+ """
209
+ Start the HTTP MCP server. Blocks until Ctrl+C.
210
+
211
+ Args:
212
+ host: Bind address. Use "0.0.0.0" to expose on LAN.
213
+ port: TCP port to listen on.
214
+ use_https: If True, load (or auto-generate) mkcert certs and serve TLS.
215
+ project_dir: Optional project root override (sets paths context).
216
+ """
217
+ import uvicorn
218
+
219
+ # If a project directory was passed explicitly, set it so all path
220
+ # resolution in this process uses it. The CLI also calls set_project_dir()
221
+ # globally, but a direct caller of run_http_server() may not have done so.
222
+ if project_dir is not None:
223
+ from mcp_server.paths import set_project_dir
224
+ set_project_dir(project_dir)
225
+
226
+ # ---- Startup side effects (mirror server.py main()) ----
227
+ try:
228
+ from mcp_server.crash_logger import install_global_handler
229
+ install_global_handler()
230
+ except Exception as e:
231
+ logger.warning("Could not install crash handler: %s", e)
232
+
233
+ # v1.6: Auto-migrate legacy .codevira/ → ~/.codevira/projects/<key>/
234
+ try:
235
+ from mcp_server.migrate import detect_migration_needed, migrate_to_centralized
236
+ from mcp_server.paths import get_project_root
237
+ _proj_root = get_project_root()
238
+ if detect_migration_needed(_proj_root):
239
+ logger.info("Migrating legacy .codevira/ to centralized storage...")
240
+ result = migrate_to_centralized(_proj_root)
241
+ if result.get("migrated"):
242
+ logger.info("Migration complete: %d files moved to %s",
243
+ result.get("files_copied", 0), result.get("new_path", ""))
244
+ except Exception as e:
245
+ logger.warning("Could not run storage migration: %s", e)
246
+
247
+ try:
248
+ from indexer.index_codebase import start_background_watcher
249
+ start_background_watcher(quiet=True)
250
+ logger.info("Live file watcher active")
251
+ except Exception as e:
252
+ logger.warning("Could not start background watcher: %s", e)
253
+ try:
254
+ from mcp_server.crash_logger import log_crash
255
+ log_crash(e, context="http serve: background watcher")
256
+ except Exception:
257
+ pass
258
+
259
+ try:
260
+ from indexer.outcome_tracker import analyze_session_outcomes
261
+ from indexer.rule_learner import run_rule_inference
262
+ analyze_session_outcomes()
263
+ run_rule_inference()
264
+ except Exception as e:
265
+ logger.warning("Could not run startup learning: %s", e)
266
+
267
+ try:
268
+ from mcp_server.global_sync import import_global_to_project
269
+ import_global_to_project()
270
+ except Exception as e:
271
+ logger.warning("Could not sync global memory: %s", e)
272
+
273
+ # ---- TLS certificate setup ----
274
+ ssl_certfile: str | None = None
275
+ ssl_keyfile: str | None = None
276
+
277
+ if use_https:
278
+ if not _certs_exist():
279
+ print(" Generating localhost TLS certificate via mkcert ...")
280
+ try:
281
+ generate_mkcert_certs()
282
+ print(f" Certificate: {_cert_file()}")
283
+ print(f" Private key: {_key_file()}")
284
+ except RuntimeError as e:
285
+ print(f"\n ERROR: {e}\n")
286
+ return
287
+ ssl_certfile = str(_cert_file())
288
+ ssl_keyfile = str(_key_file())
289
+
290
+ # ---- Print registration instructions ----
291
+ scheme = "https" if use_https else "http"
292
+ url = f"{scheme}://{host}:{port}/mcp"
293
+ display_host = "localhost" if host in ("127.0.0.1", "::1") else host
294
+ display_url = f"{scheme}://{display_host}:{port}/mcp"
295
+
296
+ print()
297
+ print(" Codevira MCP — HTTP Server")
298
+ print(" " + "─" * 44)
299
+ print(f" Endpoint : {display_url}")
300
+ print(f" Transport: MCP Streamable HTTP (2025-03-26)")
301
+ print()
302
+ print(" ── Register in Claude Code ──────────────────")
303
+ print(" Add to ~/.claude/settings.json (global) or")
304
+ print(" .claude/settings.json (project):")
305
+ print()
306
+ print(' {')
307
+ print(' "mcpServers": {')
308
+ print(' "codevira": {')
309
+ print(f' "url": "{display_url}"')
310
+ print(' }')
311
+ print(' }')
312
+ print(' }')
313
+ print()
314
+ if not use_https:
315
+ print(" Tip: Use --https for a trusted HTTPS URL (required for Claude.ai)")
316
+ print()
317
+ print(" Press Ctrl+C to stop.")
318
+ print()
319
+
320
+ # ---- Bearer token auth (required for non-loopback binds) ----
321
+ bearer_token: str | None = None
322
+ is_loopback = host in ("127.0.0.1", "::1", "localhost")
323
+ if not is_loopback:
324
+ bearer_token = _get_or_create_token()
325
+ print(f" ── Auth (non-loopback) ──────────────────────")
326
+ print(f" Bearer token: {bearer_token}")
327
+ print(f" All /mcp requests require: Authorization: Bearer <token>")
328
+ print(f" Token stored in: ~/.codevira/{_TOKEN_FILE_NAME}")
329
+ print()
330
+
331
+ # ---- Run uvicorn ----
332
+ app = create_app(bearer_token=bearer_token)
333
+
334
+ uvicorn.run(
335
+ app,
336
+ host=host,
337
+ port=port,
338
+ ssl_certfile=ssl_certfile,
339
+ ssl_keyfile=ssl_keyfile,
340
+ log_level="warning",
341
+ )