codecortex 0.2.2__py3-none-any.whl → 0.3.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.
- {codecortex-0.2.2.dist-info → codecortex-0.3.0.dist-info}/METADATA +22 -9
- codecortex-0.3.0.dist-info/RECORD +31 -0
- codeintel/__init__.py +1 -1
- codeintel/__main__.py +6 -3
- codeintel/cache.py +1 -1
- codeintel/config.py +58 -3
- codeintel/gateway.py +9 -6
- codeintel/http_server.py +90 -12
- codeintel/indexer.py +12 -1
- codeintel/onboarding.py +1 -0
- codeintel/provider.py +19 -0
- codeintel/providers/graph.py +17 -7
- codeintel/providers/lsp.py +3 -2
- codeintel/providers/semantic.py +23 -10
- codeintel/reindexer.py +26 -3
- codeintel/server.py +11 -5
- codecortex-0.2.2.dist-info/RECORD +0 -31
- {codecortex-0.2.2.dist-info → codecortex-0.3.0.dist-info}/WHEEL +0 -0
- {codecortex-0.2.2.dist-info → codecortex-0.3.0.dist-info}/entry_points.txt +0 -0
- {codecortex-0.2.2.dist-info → codecortex-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {codecortex-0.2.2.dist-info → codecortex-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
|
|
5
5
|
Author: Shammai Hamilton
|
|
6
6
|
License-Expression: MIT
|
|
@@ -183,7 +183,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
183
183
|
| `codeintel setup [project_root] [--index] [--warm] [--install-uv]` | Check backends + optionally index this repo; ends with a health report |
|
|
184
184
|
| `codeintel index [project_root]` | Index a project for semantic search |
|
|
185
185
|
| `codeintel serve` | Start the MCP server (stdio transport) |
|
|
186
|
-
| `codeintel serve-http [--host HOST] [--port 8766] [--allow-remote]` | Start the HTTP transport (loopback-only unless `--allow-remote`) |
|
|
186
|
+
| `codeintel serve-http [--host HOST] [--port 8766] [--allow-remote] [--token TOKEN]` | Start the HTTP transport (loopback-only unless `--allow-remote`; `--token` requires a bearer token on every request) |
|
|
187
187
|
| `codeintel query --op OP --target TARGET [--engine auto]` | Run a single query and print the result |
|
|
188
188
|
| `codeintel status [project_root]` | Show engine availability and index age |
|
|
189
189
|
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
|
|
@@ -197,17 +197,28 @@ Human-facing commands (`doctor`, `status`, `query`, `setup`, `reset`) honor `--n
|
|
|
197
197
|
Create `.codeintel.toml` at your project root to override defaults:
|
|
198
198
|
|
|
199
199
|
```toml
|
|
200
|
-
backend
|
|
201
|
-
semantic
|
|
202
|
-
reindex
|
|
203
|
-
cosine_floor
|
|
204
|
-
max_chunks
|
|
205
|
-
|
|
200
|
+
backend = "auto" # auto | graph | lsp | semantic
|
|
201
|
+
semantic = "on" # on | off
|
|
202
|
+
reindex = "on-demand" # on-demand | never
|
|
203
|
+
cosine_floor = 0.25 # minimum similarity score for semantic hits (0–1)
|
|
204
|
+
max_chunks = 500 # max chunks to embed per file
|
|
205
|
+
max_total_chunks = 100000 # safety ceiling on chunks embedded in one index pass
|
|
206
|
+
model = "BAAI/bge-small-en-v1.5" # fastembed embedding model
|
|
206
207
|
```
|
|
207
208
|
|
|
209
|
+
Config is **validated on load** — an out-of-range number, a misspelled enum, or a wrong type falls back to that key's default (with a logged warning) instead of breaking every query.
|
|
210
|
+
|
|
211
|
+
**Environment variables:**
|
|
212
|
+
|
|
213
|
+
| Variable | Effect |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `CODEINTEL_HTTP_TOKEN` | Bearer token required by `serve-http` (equivalent to `--token`) |
|
|
216
|
+
| `CODEINTEL_DEBUG=1` | Log the full traceback of any error the never-throw contract swallows (silent by default) — the switch for diagnosing an unexpected `null` |
|
|
217
|
+
| `CODEINTEL_REINDEX=off` | Disable the background reindexer; queries then index inline to stay fresh |
|
|
218
|
+
|
|
208
219
|
## Privacy & dependencies
|
|
209
220
|
|
|
210
|
-
**codeintel is local-first** — one local process, no cloud service, no API keys, no telemetry, and no per-query network. Its own code makes zero outbound HTTP calls, and the HTTP transport binds to `127.0.0.1` only.
|
|
221
|
+
**codeintel is local-first** — one local process, no cloud service, no API keys, no telemetry, and no per-query network. Its own code makes zero outbound HTTP calls, and the HTTP transport binds to `127.0.0.1` only by default — binding a non-loopback host requires `--allow-remote`, and `--token` (or `CODEINTEL_HTTP_TOKEN`) then gates every request behind a bearer token. The server bounds concurrent connections, but for exposure to a hostile network you should still front it with a reverse proxy (TLS, rate-limiting) — the built-in `http.server` is not hardened for the open internet.
|
|
211
222
|
|
|
212
223
|
**Bundled (installed with the package, run locally):** `mcp` (the tool interface) · `sqlite-vec` (the semantic index, a local DB file) · `fastembed` (the local embedding model).
|
|
213
224
|
|
|
@@ -240,6 +251,8 @@ Over MCP the agent calls `code.query` directly. Over HTTP, start the server and
|
|
|
240
251
|
codeintel serve-http & # listens on 127.0.0.1:8766 by default
|
|
241
252
|
```
|
|
242
253
|
|
|
254
|
+
For a shared or remote deployment, start it with `--allow-remote --token "$CODEINTEL_HTTP_TOKEN"` and send `Authorization: Bearer <token>` on each request — a missing or wrong token gets a clean `401`. Requests are handled concurrently, so one slow query never blocks another.
|
|
255
|
+
|
|
243
256
|
```python
|
|
244
257
|
import urllib.request, json
|
|
245
258
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
codecortex-0.3.0.dist-info/licenses/LICENSE,sha256=DIRvhlH8EulEUOYQxKDFFiimJrlVT5-kCKxQIgi2m7c,1073
|
|
2
|
+
codeintel/__init__.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
|
|
3
|
+
codeintel/__main__.py,sha256=m0MQ0qKFD6VAfrkXP__d_8rbVb1RtUykuzMaa1Wzq6E,15573
|
|
4
|
+
codeintel/cache.py,sha256=UESsUtVqzuIqNQOnXG78ckQfbAz8pz_w1AE9gzeHOww,3158
|
|
5
|
+
codeintel/config.py,sha256=r8tsX4yeiqBgqpnFmUkuMczs6Hbwt8iasEuYSceW200,3761
|
|
6
|
+
codeintel/doctor.py,sha256=jiPuZzS801xfWwTyeVO9UHUxQqAJ-YqTlX-_epZfoyI,6659
|
|
7
|
+
codeintel/gateway.py,sha256=3MEuD5SC2bIB3MLeZWj4q9FJYGfkVfRjmB1n8ISTp_U,9209
|
|
8
|
+
codeintel/http_server.py,sha256=c1IpKMhfLmujS7yKFzOGHQjYebphFtm8p_5cQYjevi0,7862
|
|
9
|
+
codeintel/indexer.py,sha256=brvvoCSdjO5v6VZrRe-8_VrakzQpp1ByyANFhUGTjQQ,10243
|
|
10
|
+
codeintel/injector.py,sha256=IHZTLajQURE2LLxL8dFE9pvJV1RMOAbE7pVRQbFVvgs,2755
|
|
11
|
+
codeintel/installer.py,sha256=PSgK6KA14VEQs2pQKCmjf9JDxJ2mpoEsaWrpFGWSy68,3488
|
|
12
|
+
codeintel/mapper.py,sha256=JN-lMO1CXIyDKSwHfzp0JuK396FR41XhFgtAip77G0E,7493
|
|
13
|
+
codeintel/onboarding.py,sha256=UxNJnlENxEAzG3JnEXu_Yue-4GZiEX2HG2BZVVA6jMI,8925
|
|
14
|
+
codeintel/policy.py,sha256=mUAlk9oYoszQLEcvoroFLRd9DSTzho7YcbM7xb6RS-k,935
|
|
15
|
+
codeintel/provider.py,sha256=GYNhOpJteq3zTp4R_vTp1Wz2k6TPjx6PicGhsq_CT9s,2013
|
|
16
|
+
codeintel/reindexer.py,sha256=sgCvb65scbmTtRd5LKhqxQtWolPOAmwKXJbmXWxvmVc,5667
|
|
17
|
+
codeintel/reset.py,sha256=8Mpak4Tj5e-yTcM3_EnZJzbJPwPV2o8xpxGjv-wk9EE,3675
|
|
18
|
+
codeintel/searcher.py,sha256=R9PV7jSx-GO--qmh65FXlYxpbKbjCnxV-EF9hGe8hZU,4588
|
|
19
|
+
codeintel/semantic_db.py,sha256=9umQ8UUUtHHy7Ngi0B-e8eGCy62XFIOkcWnd3wc3MMQ,3503
|
|
20
|
+
codeintel/server.py,sha256=evo3aZcQYOvSTK7UcfqZg-pRER487oePpLQwCA5BvQc,7497
|
|
21
|
+
codeintel/term.py,sha256=csbSYD8euC13FnnpThy8kylDgZeejQ6j6Ol6Gu21Clo,6819
|
|
22
|
+
codeintel/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
codeintel/providers/graph.py,sha256=nsVsSQueFENSlyXlRf9r4-5aU-f18AnMM_9xOGb8rok,20180
|
|
24
|
+
codeintel/providers/lsp.py,sha256=w4xCPmZcuro4W9MIGU-4pEtuVA2YrvEapXHTjVVd0gA,16840
|
|
25
|
+
codeintel/providers/none.py,sha256=VI84XQkT7opQm6rBAEn0CdM8CNVlKGWYmjAhyeWbHeg,783
|
|
26
|
+
codeintel/providers/semantic.py,sha256=LGIbJLt5zkSv1frbloEeAfy7oplY1xl-FmhxujTEmk0,6311
|
|
27
|
+
codecortex-0.3.0.dist-info/METADATA,sha256=42huRhrKjKhhiUyxr7LKyG1FnI5wRl4dHWL2BO-6czU,16842
|
|
28
|
+
codecortex-0.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
29
|
+
codecortex-0.3.0.dist-info/entry_points.txt,sha256=zrYfo95-8JkK0G2e5f9d94sYqqim7ZrdCW8knIfSy2U,54
|
|
30
|
+
codecortex-0.3.0.dist-info/top_level.txt,sha256=DF3TH1hHLWrrnX-7HhfMX0OnS2EXgqPRhPbiL_oSekw,10
|
|
31
|
+
codecortex-0.3.0.dist-info/RECORD,,
|
codeintel/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.3.0"
|
codeintel/__main__.py
CHANGED
|
@@ -50,7 +50,8 @@ def main() -> None:
|
|
|
50
50
|
http_parser = subparsers.add_parser("serve-http", help="Start the HTTP transport server")
|
|
51
51
|
http_parser.add_argument("--port", type=int, default=8766, help="Port to listen on (default: 8766)")
|
|
52
52
|
http_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
|
|
53
|
-
http_parser.add_argument("--allow-remote", action="store_true", help="Permit binding a non-loopback host (
|
|
53
|
+
http_parser.add_argument("--allow-remote", action="store_true", help="Permit binding a non-loopback host (use with --token, or the endpoint is UNAUTHENTICATED)")
|
|
54
|
+
http_parser.add_argument("--token", default=None, help="Require this bearer token on every request (or set CODEINTEL_HTTP_TOKEN). Strongly recommended with --allow-remote.")
|
|
54
55
|
|
|
55
56
|
# install subcommand
|
|
56
57
|
install_parser = subparsers.add_parser("install", help="Register codeintel with AI agents")
|
|
@@ -120,6 +121,7 @@ def main() -> None:
|
|
|
120
121
|
window=int(cfg.get("window", 20)),
|
|
121
122
|
stride=int(cfg.get("stride", 10)),
|
|
122
123
|
max_chunks=int(cfg.get("max_chunks", 500)),
|
|
124
|
+
max_total_chunks=int(cfg.get("max_total_chunks", 100000)),
|
|
123
125
|
).index(project_root)
|
|
124
126
|
if count > 0:
|
|
125
127
|
print(f"Indexed {count} chunks")
|
|
@@ -225,7 +227,7 @@ def main() -> None:
|
|
|
225
227
|
from codeintel import server
|
|
226
228
|
|
|
227
229
|
project_root = args.project_root or os.getcwd()
|
|
228
|
-
status = server.code_status_handler({})
|
|
230
|
+
status = server.code_status_handler({"project_root": project_root})
|
|
229
231
|
|
|
230
232
|
print("Engine status:")
|
|
231
233
|
for engine in ["graph", "lsp", "semantic"]:
|
|
@@ -318,7 +320,8 @@ def main() -> None:
|
|
|
318
320
|
elif args.command == "serve-http":
|
|
319
321
|
try:
|
|
320
322
|
from codeintel.http_server import run
|
|
321
|
-
|
|
323
|
+
token = args.token or os.environ.get("CODEINTEL_HTTP_TOKEN")
|
|
324
|
+
run(host=args.host, port=args.port, allow_remote=args.allow_remote, token=token)
|
|
322
325
|
except KeyboardInterrupt:
|
|
323
326
|
pass
|
|
324
327
|
except Exception as exc:
|
codeintel/cache.py
CHANGED
|
@@ -13,7 +13,7 @@ def _compute_hash(target: str, project_root: str) -> str:
|
|
|
13
13
|
try:
|
|
14
14
|
root = os.path.realpath(project_root) if project_root else ""
|
|
15
15
|
path = os.path.realpath(target)
|
|
16
|
-
if root and path.startswith(root + os.sep) or path == root:
|
|
16
|
+
if (root and path.startswith(root + os.sep)) or path == root:
|
|
17
17
|
if os.path.isfile(path):
|
|
18
18
|
with open(path, "rb") as fh:
|
|
19
19
|
return hashlib.sha256(fh.read()).hexdigest()
|
codeintel/config.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import pathlib
|
|
4
5
|
import sys
|
|
5
6
|
|
|
@@ -11,17 +12,28 @@ else: # pragma: no cover
|
|
|
11
12
|
except ImportError:
|
|
12
13
|
import tomli as tomllib # type: ignore[no-redef]
|
|
13
14
|
|
|
15
|
+
logger = logging.getLogger("codeintel")
|
|
16
|
+
|
|
14
17
|
_DEFAULTS: dict = {
|
|
15
18
|
"backend": "auto",
|
|
16
19
|
"semantic": "on",
|
|
17
20
|
"reindex": "on-demand",
|
|
18
21
|
"window": 20,
|
|
19
22
|
"stride": 10,
|
|
20
|
-
"max_chunks": 500,
|
|
23
|
+
"max_chunks": 500, # per file
|
|
24
|
+
"max_total_chunks": 100000, # safety ceiling on chunks embedded in one index pass
|
|
21
25
|
"cosine_floor": 0.25,
|
|
22
26
|
"model": "BAAI/bge-small-en-v1.5",
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
# Values restricted to a fixed set — anything else falls back to the default.
|
|
30
|
+
_ENUMS: dict = {
|
|
31
|
+
"backend": {"auto", "graph", "lsp", "semantic"},
|
|
32
|
+
"semantic": {"on", "off"},
|
|
33
|
+
"reindex": {"on-demand", "never"},
|
|
34
|
+
}
|
|
35
|
+
_POSITIVE_INTS = ("window", "stride", "max_chunks", "max_total_chunks")
|
|
36
|
+
|
|
25
37
|
|
|
26
38
|
def _read_toml(path: pathlib.Path) -> dict:
|
|
27
39
|
try:
|
|
@@ -31,12 +43,55 @@ def _read_toml(path: pathlib.Path) -> dict:
|
|
|
31
43
|
return {}
|
|
32
44
|
|
|
33
45
|
|
|
46
|
+
def _coerce(cfg: dict) -> dict:
|
|
47
|
+
"""Coerce/clamp a merged config to safe values, warning on and dropping bad ones. Never raises:
|
|
48
|
+
a malformed ``.codeintel.toml`` (a string where a number belongs, an out-of-range floor, an
|
|
49
|
+
unknown enum) must degrade to the default for that key, not break every query that loads it."""
|
|
50
|
+
out = dict(_DEFAULTS)
|
|
51
|
+
for key, default in _DEFAULTS.items():
|
|
52
|
+
if key not in cfg:
|
|
53
|
+
continue
|
|
54
|
+
val = cfg[key]
|
|
55
|
+
try:
|
|
56
|
+
if key in _ENUMS:
|
|
57
|
+
s = str(val).strip().lower()
|
|
58
|
+
if s in _ENUMS[key]:
|
|
59
|
+
out[key] = s
|
|
60
|
+
else:
|
|
61
|
+
logger.warning("config: %s=%r invalid (expected %s) — using %r",
|
|
62
|
+
key, val, sorted(_ENUMS[key]), default)
|
|
63
|
+
elif key == "model":
|
|
64
|
+
out[key] = str(val).strip() or default
|
|
65
|
+
elif key == "cosine_floor":
|
|
66
|
+
f = float(val)
|
|
67
|
+
if f != f: # NaN (TOML allows `nan`) — reject so it can't silently disable the floor
|
|
68
|
+
raise ValueError("nan")
|
|
69
|
+
out[key] = min(1.0, max(0.0, f))
|
|
70
|
+
elif key in _POSITIVE_INTS:
|
|
71
|
+
n = int(val)
|
|
72
|
+
if n > 0:
|
|
73
|
+
out[key] = n
|
|
74
|
+
else:
|
|
75
|
+
logger.warning("config: %s=%r must be > 0 — using %r", key, val, default)
|
|
76
|
+
except (TypeError, ValueError, OverflowError):
|
|
77
|
+
# int(float('inf')) raises OverflowError (TOML allows `inf`); float("x") → ValueError;
|
|
78
|
+
# int([]) → TypeError. Any non-usable value logs and keeps the default, so the
|
|
79
|
+
# docstring's never-raise promise holds even for the CLI paths that don't wrap this.
|
|
80
|
+
logger.warning("config: %s=%r not usable — using default %r", key, val, default)
|
|
81
|
+
# Keep any extra keys the user set (forward-compat) without validating them.
|
|
82
|
+
for k, v in cfg.items():
|
|
83
|
+
if k not in _DEFAULTS:
|
|
84
|
+
out[k] = v
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
|
|
34
88
|
def load_config(project_root: str | None = None) -> dict:
|
|
35
|
-
"""Return merged config: defaults < global < project.
|
|
89
|
+
"""Return the merged, validated config: defaults < global < project. Values that fail
|
|
90
|
+
validation fall back to their default (logged), so a bad config file never breaks a query."""
|
|
36
91
|
root = pathlib.Path(project_root) if project_root is not None else pathlib.Path.cwd()
|
|
37
92
|
|
|
38
93
|
global_cfg = _read_toml(pathlib.Path.home() / ".codeintel" / "config.toml")
|
|
39
94
|
project_cfg = _read_toml(root / ".codeintel.toml")
|
|
40
95
|
|
|
41
96
|
merged = {**_DEFAULTS, **global_cfg, **project_cfg}
|
|
42
|
-
return merged
|
|
97
|
+
return _coerce(merged)
|
codeintel/gateway.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
from codeintel.cache import ContentHashCache
|
|
7
7
|
from codeintel.policy import TieringPolicy
|
|
8
|
-
from codeintel.provider import Result, safe_null_result
|
|
8
|
+
from codeintel.provider import Result, log_swallowed, safe_null_result
|
|
9
9
|
from codeintel.providers.none import NoneProvider
|
|
10
10
|
from codeintel.reindexer import Reindexer
|
|
11
11
|
|
|
@@ -128,7 +128,8 @@ class Gateway:
|
|
|
128
128
|
if r is not None:
|
|
129
129
|
return r
|
|
130
130
|
return safe_null_result(op_str, target_str, engine=engine_str, reason="no-result")
|
|
131
|
-
except Exception:
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
log_swallowed(f"Gateway._dispatch_single[{engine_str}.{op_str}]", exc)
|
|
132
133
|
return safe_null_result(op_str, target_str, engine=engine_str, reason="provider-error")
|
|
133
134
|
|
|
134
135
|
def query(
|
|
@@ -206,14 +207,15 @@ class Gateway:
|
|
|
206
207
|
provider = self._provider_for(engine_str)
|
|
207
208
|
result = self._dispatch_single(provider, op_str, target_str, budget, project_root, engine_str)
|
|
208
209
|
|
|
209
|
-
# overview auto-fallback (F4 Story 2): when auto-routed to graph but graph
|
|
210
|
-
# unavailable,
|
|
210
|
+
# overview auto-fallback (F4 Story 2): when auto-routed to graph but graph can't serve
|
|
211
|
+
# it — the backend is unavailable, OR this repo simply isn't in the graph — try lsp,
|
|
212
|
+
# which can produce a file/symbol overview without the graph index.
|
|
211
213
|
if (
|
|
212
214
|
was_auto
|
|
213
215
|
and op_str == "overview"
|
|
214
216
|
and engine_str == "graph"
|
|
215
217
|
and result.get("result") is None
|
|
216
|
-
and result.get("reason")
|
|
218
|
+
and result.get("reason") in ("engine-unavailable", "project-not-indexed")
|
|
217
219
|
):
|
|
218
220
|
lsp_result = self._dispatch_single(
|
|
219
221
|
self.lsp, op_str, target_str, budget, project_root, "lsp"
|
|
@@ -224,5 +226,6 @@ class Gateway:
|
|
|
224
226
|
self._cache.put(op_str, target_str, engine_str, root_str, result, freshness)
|
|
225
227
|
return result
|
|
226
228
|
|
|
227
|
-
except Exception:
|
|
229
|
+
except Exception as exc:
|
|
230
|
+
log_swallowed("Gateway.query", exc)
|
|
228
231
|
return safe_null_result(op or "", target or "", reason="gateway-error")
|
codeintel/http_server.py
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import hmac
|
|
3
4
|
import json
|
|
4
5
|
import sys
|
|
6
|
+
import threading
|
|
5
7
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
8
|
+
from urllib.parse import parse_qs, urlparse
|
|
6
9
|
|
|
10
|
+
from codeintel.provider import log_swallowed
|
|
7
11
|
from codeintel.server import code_doctor_handler, code_query_handler, code_status_handler
|
|
8
12
|
|
|
9
|
-
_MAX_BODY_BYTES = 1_048_576
|
|
13
|
+
_MAX_BODY_BYTES = 1_048_576 # 1 MiB
|
|
14
|
+
_REQUEST_TIMEOUT_S = 60 # per-request socket read timeout — drops an idle/half-open client
|
|
15
|
+
_MAX_CONCURRENT_REQUESTS = 64 # cap live worker threads so a burst can't exhaust threads/FDs
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
class _Handler(BaseHTTPRequestHandler):
|
|
19
|
+
# Applied to the request socket by the base handler's setup(); a slow/half-open client
|
|
20
|
+
# connection is dropped instead of pinning a worker thread forever (basic slowloris guard).
|
|
21
|
+
timeout = _REQUEST_TIMEOUT_S
|
|
22
|
+
|
|
13
23
|
def log_message(self, format: str, *args: object) -> None:
|
|
14
24
|
pass # suppress default stderr noise
|
|
15
25
|
|
|
@@ -21,7 +31,26 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
21
31
|
self.end_headers()
|
|
22
32
|
self.wfile.write(body)
|
|
23
33
|
|
|
34
|
+
def _authorized(self) -> bool:
|
|
35
|
+
"""When the server was started with a token, require ``Authorization: Bearer <token>``
|
|
36
|
+
(constant-time compare). No token configured → auth is disabled (the loopback default)."""
|
|
37
|
+
token = getattr(self.server, "auth_token", None)
|
|
38
|
+
if not token:
|
|
39
|
+
return True
|
|
40
|
+
header = self.headers.get("Authorization", "")
|
|
41
|
+
prefix = "Bearer "
|
|
42
|
+
if not header.startswith(prefix):
|
|
43
|
+
return False
|
|
44
|
+
presented = header[len(prefix):].strip()
|
|
45
|
+
# Compare as UTF-8 bytes: hmac.compare_digest raises TypeError on a non-ASCII str, so a
|
|
46
|
+
# malformed header (e.g. `Bearer résumé`) would otherwise crash the handler thread. Encoding
|
|
47
|
+
# is always safe and keeps the comparison constant-time.
|
|
48
|
+
return hmac.compare_digest(presented.encode("utf-8"), token.encode("utf-8"))
|
|
49
|
+
|
|
24
50
|
def do_POST(self) -> None:
|
|
51
|
+
if not self._authorized():
|
|
52
|
+
self._send_json(401, {"error": "unauthorized"})
|
|
53
|
+
return
|
|
25
54
|
if self.path not in ("/code/query", "/code/doctor"):
|
|
26
55
|
self._send_json(404, {"error": "not-found"})
|
|
27
56
|
return
|
|
@@ -50,19 +79,59 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
50
79
|
self._send_json(200, result)
|
|
51
80
|
|
|
52
81
|
def do_GET(self) -> None:
|
|
53
|
-
if self.
|
|
82
|
+
if not self._authorized():
|
|
83
|
+
self._send_json(401, {"error": "unauthorized"})
|
|
84
|
+
return
|
|
85
|
+
parsed = urlparse(self.path)
|
|
86
|
+
if parsed.path != "/code/status":
|
|
54
87
|
self._send_json(404, {"error": "not-found"})
|
|
55
88
|
return
|
|
56
|
-
|
|
89
|
+
# Optional ?project_root=... scopes the `indexed` flag to that repo.
|
|
90
|
+
project_root = (parse_qs(parsed.query).get("project_root") or [""])[0]
|
|
91
|
+
result = code_status_handler({"project_root": project_root})
|
|
57
92
|
self._send_json(200, result)
|
|
58
93
|
|
|
59
94
|
|
|
60
95
|
class CodeIntelHTTPServer(ThreadingHTTPServer):
|
|
61
|
-
# Threaded so one slow request (
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
96
|
+
# Threaded so one slow request (an LSP session warming, or a first-time index) can't block
|
|
97
|
+
# every other agent's query. The gateway is a shared singleton, but its mutable state is
|
|
98
|
+
# lock-guarded (query cache, reindexer, LSP sessions, graph project cache) and the semantic
|
|
99
|
+
# engine is thread-confined with WAL, so concurrent requests are safe.
|
|
65
100
|
daemon_threads = True
|
|
101
|
+
auth_token: str | None = None # set by run() when a token is provided
|
|
102
|
+
|
|
103
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
104
|
+
super().__init__(*args, **kwargs)
|
|
105
|
+
# Stdlib ThreadingHTTPServer spawns one thread per connection with no ceiling, so many
|
|
106
|
+
# slow/half-open clients could exhaust threads/FDs. Bound live workers; past the cap we
|
|
107
|
+
# refuse fast with 503 rather than spawn an unbounded thread. (For a genuinely hostile
|
|
108
|
+
# network, still front this with a reverse proxy — http.server is not hardened for that.)
|
|
109
|
+
self._slots = threading.BoundedSemaphore(_MAX_CONCURRENT_REQUESTS)
|
|
110
|
+
|
|
111
|
+
def process_request(self, request, client_address) -> None:
|
|
112
|
+
if not self._slots.acquire(blocking=False):
|
|
113
|
+
try:
|
|
114
|
+
request.sendall(
|
|
115
|
+
b"HTTP/1.1 503 Service Unavailable\r\n"
|
|
116
|
+
b"Content-Type: application/json\r\nContent-Length: 22\r\n"
|
|
117
|
+
b'Connection: close\r\n\r\n{"error":"overloaded"}'
|
|
118
|
+
)
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
self.shutdown_request(request)
|
|
122
|
+
return
|
|
123
|
+
super().process_request(request, client_address) # spawns the worker thread
|
|
124
|
+
|
|
125
|
+
def process_request_thread(self, request, client_address) -> None:
|
|
126
|
+
try:
|
|
127
|
+
super().process_request_thread(request, client_address)
|
|
128
|
+
finally:
|
|
129
|
+
self._slots.release()
|
|
130
|
+
|
|
131
|
+
def handle_error(self, request, client_address) -> None:
|
|
132
|
+
# A stalled/reset client raises here (socket timeout, broken pipe) — expected noise, not a
|
|
133
|
+
# server bug (handlers are never-raise). Keep it off stderr; surface with CODEINTEL_DEBUG=1.
|
|
134
|
+
log_swallowed("http.handle_error", sys.exc_info()[1] or Exception("unknown"))
|
|
66
135
|
|
|
67
136
|
|
|
68
137
|
_LOOPBACK_NAMES = {"localhost"}
|
|
@@ -83,15 +152,24 @@ def _is_loopback(host: str) -> bool:
|
|
|
83
152
|
return False # a non-IP hostname is never treated as loopback
|
|
84
153
|
|
|
85
154
|
|
|
86
|
-
def run(
|
|
155
|
+
def run(
|
|
156
|
+
host: str = "127.0.0.1",
|
|
157
|
+
port: int = 8766,
|
|
158
|
+
*,
|
|
159
|
+
allow_remote: bool = False,
|
|
160
|
+
token: str | None = None,
|
|
161
|
+
) -> None:
|
|
87
162
|
if not _is_loopback(host) and not allow_remote:
|
|
88
163
|
print(f"refusing to bind non-loopback host {host!r} without --allow-remote — this would "
|
|
89
164
|
f"expose an UNAUTHENTICATED code-intel endpoint (your indexed repo) to the network",
|
|
90
165
|
file=sys.stderr)
|
|
91
166
|
raise SystemExit(2)
|
|
92
|
-
if not _is_loopback(host):
|
|
93
|
-
print(f"WARNING: serving codeintel on {host}:{port} with NO authentication — anyone who can "
|
|
94
|
-
f"reach this port can read your indexed repo", file=sys.stderr)
|
|
95
167
|
server = CodeIntelHTTPServer((host, port), _Handler)
|
|
96
|
-
|
|
168
|
+
server.auth_token = (token or "").strip() or None
|
|
169
|
+
if not _is_loopback(host) and not server.auth_token:
|
|
170
|
+
print(f"WARNING: serving codeintel on {host}:{port} with NO authentication — anyone who can "
|
|
171
|
+
f"reach this port can read your indexed repo (set --token to require a bearer token)",
|
|
172
|
+
file=sys.stderr)
|
|
173
|
+
auth_note = " (bearer-token auth required)" if server.auth_token else ""
|
|
174
|
+
print(f"Listening on http://{host}:{port}{auth_note}")
|
|
97
175
|
server.serve_forever()
|
codeintel/indexer.py
CHANGED
|
@@ -37,12 +37,14 @@ class Indexer:
|
|
|
37
37
|
window: int = 20,
|
|
38
38
|
stride: int = 10,
|
|
39
39
|
max_chunks: int = 500,
|
|
40
|
+
max_total_chunks: int = 100000,
|
|
40
41
|
) -> None:
|
|
41
42
|
self.db = db
|
|
42
43
|
self.model_name = model_name
|
|
43
44
|
self.window = window
|
|
44
45
|
self.stride = stride
|
|
45
|
-
self.max_chunks = max_chunks
|
|
46
|
+
self.max_chunks = max_chunks # per file
|
|
47
|
+
self.max_total_chunks = max_total_chunks # safety ceiling per index pass (memory backstop)
|
|
46
48
|
self._embedder = None
|
|
47
49
|
|
|
48
50
|
def _get_embedder(self):
|
|
@@ -131,6 +133,13 @@ class Indexer:
|
|
|
131
133
|
new_chunks: list[tuple[str, str, str, int, str]] = []
|
|
132
134
|
|
|
133
135
|
for filepath in self._walk_files(root):
|
|
136
|
+
if len(new_chunks) >= self.max_total_chunks:
|
|
137
|
+
logger.warning(
|
|
138
|
+
"index: reached max_total_chunks=%d this pass — stopping "
|
|
139
|
+
"(raise it in .codeintel.toml to embed more of a very large repo)",
|
|
140
|
+
self.max_total_chunks,
|
|
141
|
+
)
|
|
142
|
+
break
|
|
134
143
|
try:
|
|
135
144
|
with open(filepath, encoding="utf-8", errors="replace") as f:
|
|
136
145
|
lines = f.readlines()
|
|
@@ -145,6 +154,8 @@ class Indexer:
|
|
|
145
154
|
chunk_count = 0
|
|
146
155
|
|
|
147
156
|
for chunk_start in range(0, len(lines), self.stride):
|
|
157
|
+
if len(new_chunks) >= self.max_total_chunks:
|
|
158
|
+
break # global ceiling reached mid-file; the file-loop guard logs and stops
|
|
148
159
|
if chunk_count >= self.max_chunks:
|
|
149
160
|
logger.debug(
|
|
150
161
|
"chunk cap hit for %s, truncating at %d",
|
codeintel/onboarding.py
CHANGED
|
@@ -72,6 +72,7 @@ def _bounded_index(project_root: str, *, timeout_s: float, out) -> dict:
|
|
|
72
72
|
db, model_name=str(cfg.get("model") or "BAAI/bge-small-en-v1.5"),
|
|
73
73
|
window=int(cfg.get("window", 20)), stride=int(cfg.get("stride", 10)),
|
|
74
74
|
max_chunks=int(cfg.get("max_chunks", 500)),
|
|
75
|
+
max_total_chunks=int(cfg.get("max_total_chunks", 100000)),
|
|
75
76
|
).index(project_root)
|
|
76
77
|
finally:
|
|
77
78
|
db.close()
|
codeintel/provider.py
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import traceback
|
|
3
6
|
from typing import Any, Optional, Protocol, runtime_checkable
|
|
4
7
|
from typing_extensions import NotRequired, TypedDict
|
|
5
8
|
|
|
9
|
+
_logger = logging.getLogger("codeintel")
|
|
10
|
+
_DEBUG = os.environ.get("CODEINTEL_DEBUG", "").strip().lower() in ("1", "true", "on", "yes")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def log_swallowed(where: str, exc: BaseException) -> None:
|
|
14
|
+
"""Record an exception the never-raise contract is about to swallow. Quiet by default so the
|
|
15
|
+
contract stays silent in normal use; set ``CODEINTEL_DEBUG=1`` to surface a full traceback when
|
|
16
|
+
diagnosing why a query came back as a safe-null. Never raises — logging failures are ignored."""
|
|
17
|
+
try:
|
|
18
|
+
if _DEBUG:
|
|
19
|
+
_logger.warning("codeintel swallowed error in %s: %s\n%s", where, exc, traceback.format_exc())
|
|
20
|
+
else:
|
|
21
|
+
_logger.debug("codeintel swallowed error in %s: %s", where, exc)
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
|
|
6
25
|
|
|
7
26
|
class Result(TypedDict):
|
|
8
27
|
ok: bool
|
codeintel/providers/graph.py
CHANGED
|
@@ -7,7 +7,7 @@ import threading
|
|
|
7
7
|
import time
|
|
8
8
|
from typing import Any, Optional
|
|
9
9
|
|
|
10
|
-
from codeintel.provider import Result, safe_null_result
|
|
10
|
+
from codeintel.provider import Result, log_swallowed, safe_null_result
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def _cypher_literal(s: Any) -> str:
|
|
@@ -35,7 +35,8 @@ class GraphProvider:
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
37
|
def __init__(self) -> None:
|
|
38
|
-
self._project_cache: dict[str, Optional[str]] = {}
|
|
38
|
+
self._project_cache: dict[str, Optional[str]] = {} # resolved names (stable, kept)
|
|
39
|
+
self._negative_until: dict[str, float] = {} # failed lookups, short TTL only
|
|
39
40
|
self._project_cache_lock = threading.Lock() # concurrent HTTP requests share one provider
|
|
40
41
|
self._detect_backend()
|
|
41
42
|
|
|
@@ -129,14 +130,22 @@ class GraphProvider:
|
|
|
129
130
|
def _resolve_project(self, project_root: str) -> Optional[str]:
|
|
130
131
|
with self._project_cache_lock:
|
|
131
132
|
if project_root in self._project_cache:
|
|
132
|
-
return self._project_cache[project_root]
|
|
133
|
+
return self._project_cache[project_root] # positive: a repo's name is stable
|
|
134
|
+
neg_until = self._negative_until.get(project_root)
|
|
135
|
+
if neg_until is not None and time.monotonic() < neg_until:
|
|
136
|
+
return None # a recently-failed lookup, still within its short TTL
|
|
133
137
|
# list_projects shells out — resolve it OUTSIDE the lock so a slow backend can't serialize
|
|
134
|
-
# every concurrent request. A rare duplicate lookup on first contact is harmless
|
|
135
|
-
# result is idempotent); we simply never hold the lock across a subprocess.
|
|
138
|
+
# every concurrent request. A rare duplicate lookup on first contact is harmless.
|
|
136
139
|
raw = self._run("list_projects", {}, 3000)
|
|
137
140
|
name = self._match_project(raw, project_root)
|
|
138
141
|
with self._project_cache_lock:
|
|
139
|
-
|
|
142
|
+
if name is not None:
|
|
143
|
+
self._project_cache[project_root] = name
|
|
144
|
+
self._negative_until.pop(project_root, None)
|
|
145
|
+
else:
|
|
146
|
+
# Cache the MISS only briefly, so a repo indexed into the graph AFTER this failed
|
|
147
|
+
# lookup is picked up within the TTL rather than staying stuck until a restart.
|
|
148
|
+
self._negative_until[project_root] = time.monotonic() + 30.0
|
|
140
149
|
return name
|
|
141
150
|
|
|
142
151
|
def probe(self, project_root: str, timeout_ms: int = 3000) -> dict:
|
|
@@ -400,7 +409,8 @@ class GraphProvider:
|
|
|
400
409
|
"engine": "graph",
|
|
401
410
|
"cached": False,
|
|
402
411
|
}
|
|
403
|
-
except Exception:
|
|
412
|
+
except Exception as exc:
|
|
413
|
+
log_swallowed("GraphProvider.build_result", exc)
|
|
404
414
|
return safe_null_result(op, target, engine="graph", reason="error")
|
|
405
415
|
|
|
406
416
|
def _dispatch(
|
codeintel/providers/lsp.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import Any, Optional
|
|
|
12
12
|
from mcp import ClientSession
|
|
13
13
|
from mcp.client.stdio import stdio_client
|
|
14
14
|
|
|
15
|
-
from codeintel.provider import Result, safe_null_result
|
|
15
|
+
from codeintel.provider import Result, log_swallowed, safe_null_result
|
|
16
16
|
|
|
17
17
|
_COOLDOWN_SECONDS = 60
|
|
18
18
|
_DEFAULT_TIMEOUT_S = 5.0
|
|
@@ -231,7 +231,8 @@ class LspProvider:
|
|
|
231
231
|
"engine": "lsp",
|
|
232
232
|
"cached": False,
|
|
233
233
|
}
|
|
234
|
-
except Exception:
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
log_swallowed("LspProvider.build_result", exc)
|
|
235
236
|
return safe_null_result(op, target, engine="lsp", reason="error")
|
|
236
237
|
|
|
237
238
|
def _dispatch(
|
codeintel/providers/semantic.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import pathlib
|
|
4
5
|
|
|
5
|
-
from codeintel.provider import Result, safe_null_result
|
|
6
|
+
from codeintel.provider import Result, log_swallowed, safe_null_result
|
|
6
7
|
|
|
7
8
|
try:
|
|
8
9
|
import fastembed # noqa: F401
|
|
@@ -101,15 +102,26 @@ class SemanticProvider:
|
|
|
101
102
|
db = SemanticDb(str(_DB_PATH))
|
|
102
103
|
db.init()
|
|
103
104
|
|
|
104
|
-
Indexer(
|
|
105
|
-
db,
|
|
106
|
-
model_name=model,
|
|
107
|
-
window=int(cfg.get("window", 20)),
|
|
108
|
-
stride=int(cfg.get("stride", 10)),
|
|
109
|
-
max_chunks=int(cfg.get("max_chunks", 500)),
|
|
110
|
-
).index(project_root)
|
|
111
|
-
|
|
112
105
|
searcher = Searcher(db, model_name=model)
|
|
106
|
+
|
|
107
|
+
# A full index pass walks and hashes every file — too expensive to run on every query.
|
|
108
|
+
# The background Reindexer (gated by the CODEINTEL_REINDEX env) already keeps a warm repo
|
|
109
|
+
# fresh, so we only pay the inline pass on a COLD repo (nothing indexed yet). The one
|
|
110
|
+
# exception: when that background reindexer is turned off, the inline pass is the only
|
|
111
|
+
# thing keeping the index current, so we run it every query to preserve freshness.
|
|
112
|
+
background_reindex_off = (
|
|
113
|
+
os.environ.get("CODEINTEL_REINDEX", "on").strip().lower() == "off"
|
|
114
|
+
)
|
|
115
|
+
if background_reindex_off or not searcher.has_index(project_root):
|
|
116
|
+
Indexer(
|
|
117
|
+
db,
|
|
118
|
+
model_name=model,
|
|
119
|
+
window=int(cfg.get("window", 20)),
|
|
120
|
+
stride=int(cfg.get("stride", 10)),
|
|
121
|
+
max_chunks=int(cfg.get("max_chunks", 500)),
|
|
122
|
+
max_total_chunks=int(cfg.get("max_total_chunks", 100000)),
|
|
123
|
+
).index(project_root)
|
|
124
|
+
|
|
113
125
|
if not searcher.has_index(project_root):
|
|
114
126
|
return safe_null_result(
|
|
115
127
|
op, target, engine="semantic", reason="no-index",
|
|
@@ -135,5 +147,6 @@ class SemanticProvider:
|
|
|
135
147
|
"cached": False,
|
|
136
148
|
}
|
|
137
149
|
return result
|
|
138
|
-
except Exception:
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
log_swallowed("SemanticProvider.build_result", exc)
|
|
139
152
|
return safe_null_result(op, target, engine="semantic", reason="provider-error")
|
codeintel/reindexer.py
CHANGED
|
@@ -71,8 +71,21 @@ class Reindexer:
|
|
|
71
71
|
return
|
|
72
72
|
self._last_fired[project_root] = now
|
|
73
73
|
|
|
74
|
+
# Passed the debounce gate — honor a per-project `reindex = "never"` opt-out before doing
|
|
75
|
+
# expensive work, so that config key actually disables background reindexing (not only the
|
|
76
|
+
# inline path). Checked post-debounce, so config is read at most once per window.
|
|
77
|
+
if self._reindex_disabled(project_root):
|
|
78
|
+
return
|
|
79
|
+
|
|
74
80
|
self._executor.submit(self._do_reindex, project_root)
|
|
75
81
|
|
|
82
|
+
def _reindex_disabled(self, project_root: str) -> bool:
|
|
83
|
+
try:
|
|
84
|
+
from codeintel.config import load_config
|
|
85
|
+
return str(load_config(project_root).get("reindex") or "").strip().lower() == "never"
|
|
86
|
+
except Exception:
|
|
87
|
+
return False
|
|
88
|
+
|
|
76
89
|
def _do_reindex(self, project_root: str) -> None:
|
|
77
90
|
try:
|
|
78
91
|
self._semantic_reindex(project_root)
|
|
@@ -85,17 +98,27 @@ class Reindexer:
|
|
|
85
98
|
def _semantic_reindex(self, project_root: str) -> None:
|
|
86
99
|
import pathlib
|
|
87
100
|
|
|
101
|
+
from codeintel.config import load_config
|
|
88
102
|
from codeintel.semantic_db import SemanticDb, default_db_path
|
|
89
103
|
from codeintel.indexer import Indexer
|
|
90
104
|
|
|
91
|
-
# Same per-machine cache the SemanticProvider reads — index and search must
|
|
92
|
-
#
|
|
105
|
+
# Same per-machine cache the SemanticProvider reads — index and search must never diverge
|
|
106
|
+
# onto different files. Honor the project's config so the background pass indexes exactly
|
|
107
|
+
# like the inline and CLI paths (same model, window/stride, and chunk ceilings).
|
|
108
|
+
cfg = load_config(project_root)
|
|
93
109
|
db_path = default_db_path()
|
|
94
110
|
pathlib.Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
|
95
111
|
db = SemanticDb(db_path)
|
|
96
112
|
try:
|
|
97
113
|
db.init()
|
|
98
|
-
Indexer(
|
|
114
|
+
Indexer(
|
|
115
|
+
db,
|
|
116
|
+
model_name=str(cfg.get("model") or "BAAI/bge-small-en-v1.5"),
|
|
117
|
+
window=int(cfg.get("window", 20)),
|
|
118
|
+
stride=int(cfg.get("stride", 10)),
|
|
119
|
+
max_chunks=int(cfg.get("max_chunks", 500)),
|
|
120
|
+
max_total_chunks=int(cfg.get("max_total_chunks", 100000)),
|
|
121
|
+
).index(project_root)
|
|
99
122
|
finally:
|
|
100
123
|
db.close()
|
|
101
124
|
|
codeintel/server.py
CHANGED
|
@@ -103,15 +103,21 @@ def code_status_handler(args: dict) -> dict:
|
|
|
103
103
|
if not engines:
|
|
104
104
|
engines.append("none")
|
|
105
105
|
|
|
106
|
-
# Report real freshness/model instead of hardcoded nulls (SPEC §7).
|
|
106
|
+
# Report real freshness/model instead of hardcoded nulls (SPEC §7). When a project_root is
|
|
107
|
+
# supplied, `indexed` is scoped to THAT repo (does it have indexed chunks?) rather than the
|
|
108
|
+
# misleading "any semantic db file exists on this machine".
|
|
109
|
+
project_root = str(args.get("project_root", "") or "")
|
|
107
110
|
indexed = False
|
|
108
111
|
model = None
|
|
109
112
|
try:
|
|
110
|
-
import os
|
|
111
113
|
from codeintel.semantic_db import DEFAULT_MODEL, default_db_path
|
|
112
114
|
if semantic_available:
|
|
113
115
|
model = DEFAULT_MODEL
|
|
114
|
-
|
|
116
|
+
if project_root:
|
|
117
|
+
indexed = bool(SemanticProvider().probe(project_root).get("repo_indexed"))
|
|
118
|
+
else:
|
|
119
|
+
import os
|
|
120
|
+
indexed = os.path.exists(default_db_path())
|
|
115
121
|
except Exception:
|
|
116
122
|
pass
|
|
117
123
|
|
|
@@ -199,8 +205,8 @@ def run() -> None:
|
|
|
199
205
|
{"op": op, "target": target, "project_root": project_root, "engine": engine, "role": role}
|
|
200
206
|
)
|
|
201
207
|
|
|
202
|
-
async def _code_status() -> dict:
|
|
203
|
-
return code_status_handler({})
|
|
208
|
+
async def _code_status(project_root: str = "") -> dict:
|
|
209
|
+
return code_status_handler({"project_root": project_root})
|
|
204
210
|
|
|
205
211
|
async def _code_doctor(project_root: str = "", deep: bool = False) -> dict:
|
|
206
212
|
return code_doctor_handler({"project_root": project_root, "deep": deep})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
codecortex-0.2.2.dist-info/licenses/LICENSE,sha256=DIRvhlH8EulEUOYQxKDFFiimJrlVT5-kCKxQIgi2m7c,1073
|
|
2
|
-
codeintel/__init__.py,sha256=m6kyaNpwBcP1XYcqrelX2oS3PJuOnElOcRdBa9pEb8c,22
|
|
3
|
-
codeintel/__main__.py,sha256=3MpEhdpAfHiO0rwIB7hxXM_mDN3RovPQBLXNH36aLUg,15189
|
|
4
|
-
codeintel/cache.py,sha256=VhAc_xEA3FtC_dSLXHPe646FKvL-cgPWm27S9i6VuYE,3156
|
|
5
|
-
codeintel/config.py,sha256=Q7Mgq3UleJO6cpBKr1S7I1SeRbyL_rDekmCi8iasy9s,1133
|
|
6
|
-
codeintel/doctor.py,sha256=jiPuZzS801xfWwTyeVO9UHUxQqAJ-YqTlX-_epZfoyI,6659
|
|
7
|
-
codeintel/gateway.py,sha256=1c0rtjGGcgaa63Klk79LtdeOTl2ld8cvcnFlRSpKNsg,8929
|
|
8
|
-
codeintel/http_server.py,sha256=J44uZqpXFJ06X-wDOZZvLEM1wftuokKCvS09Z62eUyU,3942
|
|
9
|
-
codeintel/indexer.py,sha256=QKQtVu79DsEf1qSj9pLSdrspy5ajgzuNmupaZFL128s,9578
|
|
10
|
-
codeintel/injector.py,sha256=IHZTLajQURE2LLxL8dFE9pvJV1RMOAbE7pVRQbFVvgs,2755
|
|
11
|
-
codeintel/installer.py,sha256=PSgK6KA14VEQs2pQKCmjf9JDxJ2mpoEsaWrpFGWSy68,3488
|
|
12
|
-
codeintel/mapper.py,sha256=JN-lMO1CXIyDKSwHfzp0JuK396FR41XhFgtAip77G0E,7493
|
|
13
|
-
codeintel/onboarding.py,sha256=TOpRKkFvN2UesA7lW233s5PUJTFj98i7sby953EGWIQ,8846
|
|
14
|
-
codeintel/policy.py,sha256=mUAlk9oYoszQLEcvoroFLRd9DSTzho7YcbM7xb6RS-k,935
|
|
15
|
-
codeintel/provider.py,sha256=SlIModATCANJA4VyJs_Rz24bpQfzHG8ljHfpS1LWa8c,1214
|
|
16
|
-
codeintel/reindexer.py,sha256=zFNeLgPRZPfeczc5Cx3aPYkJpyrjd6AHyvuqsrLGf8E,4443
|
|
17
|
-
codeintel/reset.py,sha256=8Mpak4Tj5e-yTcM3_EnZJzbJPwPV2o8xpxGjv-wk9EE,3675
|
|
18
|
-
codeintel/searcher.py,sha256=R9PV7jSx-GO--qmh65FXlYxpbKbjCnxV-EF9hGe8hZU,4588
|
|
19
|
-
codeintel/semantic_db.py,sha256=9umQ8UUUtHHy7Ngi0B-e8eGCy62XFIOkcWnd3wc3MMQ,3503
|
|
20
|
-
codeintel/server.py,sha256=9eo531Hj1XZYz3I6Zx2DQZ-fsYPYtmIRJH2TTe5yRjo,7031
|
|
21
|
-
codeintel/term.py,sha256=csbSYD8euC13FnnpThy8kylDgZeejQ6j6Ol6Gu21Clo,6819
|
|
22
|
-
codeintel/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
codeintel/providers/graph.py,sha256=JUAnAo7UioUMZJ3PUGrKT4svWQ3mmaTa14JMKAGVvNk,19401
|
|
24
|
-
codeintel/providers/lsp.py,sha256=3jn2bMfo5jiaL4_X3kIjzzxLsL475xIZsT-okWy-vgg,16759
|
|
25
|
-
codeintel/providers/none.py,sha256=VI84XQkT7opQm6rBAEn0CdM8CNVlKGWYmjAhyeWbHeg,783
|
|
26
|
-
codeintel/providers/semantic.py,sha256=Yka9Puyd8LqY2jfvjoJzAsSMZvb3S-cViQC6fuesdUk,5397
|
|
27
|
-
codecortex-0.2.2.dist-info/METADATA,sha256=kL47WPI6VYU46EBctBloS984tmQ8ZRGNyfKNxRwYzAs,15376
|
|
28
|
-
codecortex-0.2.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
29
|
-
codecortex-0.2.2.dist-info/entry_points.txt,sha256=zrYfo95-8JkK0G2e5f9d94sYqqim7ZrdCW8knIfSy2U,54
|
|
30
|
-
codecortex-0.2.2.dist-info/top_level.txt,sha256=DF3TH1hHLWrrnX-7HhfMX0OnS2EXgqPRhPbiL_oSekw,10
|
|
31
|
-
codecortex-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|