codecortex 0.12.0__tar.gz → 0.13.0__tar.gz

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 (97) hide show
  1. {codecortex-0.12.0/src/codecortex.egg-info → codecortex-0.13.0}/PKG-INFO +23 -3
  2. {codecortex-0.12.0 → codecortex-0.13.0}/README.md +18 -1
  3. codecortex-0.13.0/pyproject.toml +139 -0
  4. {codecortex-0.12.0 → codecortex-0.13.0/src/codecortex.egg-info}/PKG-INFO +23 -3
  5. {codecortex-0.12.0 → codecortex-0.13.0}/src/codecortex.egg-info/SOURCES.txt +17 -0
  6. {codecortex-0.12.0 → codecortex-0.13.0}/src/codecortex.egg-info/requires.txt +3 -0
  7. codecortex-0.13.0/src/codeintel/__init__.py +1 -0
  8. codecortex-0.13.0/src/codeintel/__main__.py +273 -0
  9. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/auth.py +10 -9
  10. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/cache.py +7 -8
  11. codecortex-0.13.0/src/codeintel/commands/__init__.py +6 -0
  12. codecortex-0.13.0/src/codeintel/commands/_common.py +46 -0
  13. codecortex-0.13.0/src/codeintel/commands/doctor.py +15 -0
  14. codecortex-0.13.0/src/codeintel/commands/gen_token.py +9 -0
  15. codecortex-0.13.0/src/codeintel/commands/graph.py +31 -0
  16. codecortex-0.13.0/src/codeintel/commands/index.py +63 -0
  17. codecortex-0.13.0/src/codeintel/commands/install.py +77 -0
  18. codecortex-0.13.0/src/codeintel/commands/map.py +36 -0
  19. codecortex-0.13.0/src/codeintel/commands/query.py +55 -0
  20. codecortex-0.13.0/src/codeintel/commands/reset.py +27 -0
  21. codecortex-0.13.0/src/codeintel/commands/serve.py +10 -0
  22. codecortex-0.13.0/src/codeintel/commands/serve_http.py +20 -0
  23. codecortex-0.13.0/src/codeintel/commands/setup.py +21 -0
  24. codecortex-0.13.0/src/codeintel/commands/status.py +42 -0
  25. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/config.py +2 -12
  26. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/doctor.py +8 -7
  27. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/gateway.py +4 -5
  28. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/http_server.py +13 -2
  29. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/indexer.py +6 -7
  30. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/installer.py +4 -5
  31. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/mapper.py +10 -10
  32. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/metrics.py +3 -4
  33. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/onboarding.py +6 -4
  34. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/policy.py +2 -4
  35. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/provider.py +5 -4
  36. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/providers/graph.py +27 -25
  37. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/providers/lsp.py +27 -29
  38. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/providers/semantic.py +3 -4
  39. codecortex-0.13.0/src/codeintel/py.typed +0 -0
  40. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/searcher.py +1 -1
  41. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/server.py +10 -6
  42. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/term.py +3 -4
  43. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/verify.py +23 -21
  44. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_chunking.py +3 -2
  45. codecortex-0.13.0/tests/test_cli_commands.py +773 -0
  46. codecortex-0.13.0/tests/test_cli_help.py +136 -0
  47. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_doctor.py +1 -3
  48. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_gateway.py +0 -1
  49. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_hardening.py +0 -1
  50. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_http_auth.py +3 -1
  51. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_http_server.py +1 -1
  52. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_lsp_real.py +2 -2
  53. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_mapper.py +2 -4
  54. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_mcp_handshake.py +21 -24
  55. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_mcp_server.py +36 -0
  56. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_never_raise.py +4 -8
  57. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_rbac.py +10 -1
  58. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_reindexer.py +1 -1
  59. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_rerank.py +4 -4
  60. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_semantic_provider.py +2 -10
  61. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_treesitter.py +1 -1
  62. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_verify.py +0 -1
  63. codecortex-0.12.0/pyproject.toml +0 -62
  64. codecortex-0.12.0/src/codeintel/__init__.py +0 -1
  65. codecortex-0.12.0/src/codeintel/__main__.py +0 -472
  66. {codecortex-0.12.0 → codecortex-0.13.0}/LICENSE +0 -0
  67. {codecortex-0.12.0 → codecortex-0.13.0}/setup.cfg +0 -0
  68. {codecortex-0.12.0 → codecortex-0.13.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  69. {codecortex-0.12.0 → codecortex-0.13.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  70. {codecortex-0.12.0 → codecortex-0.13.0}/src/codecortex.egg-info/top_level.txt +0 -0
  71. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/grapher.py +0 -0
  72. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/injector.py +0 -0
  73. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/logconfig.py +0 -0
  74. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/providers/__init__.py +0 -0
  75. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/providers/none.py +0 -0
  76. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/reindexer.py +1 -1
  77. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/reset.py +0 -0
  78. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/semantic_db.py +0 -0
  79. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/viewer/__init__.py +0 -0
  80. {codecortex-0.12.0 → codecortex-0.13.0}/src/codeintel/viewer/graph_template.html +0 -0
  81. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_cache.py +0 -0
  82. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_config.py +0 -0
  83. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_e2e.py +0 -0
  84. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_engine_adoption.py +0 -0
  85. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_enterprise.py +0 -0
  86. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_graph_provider.py +0 -0
  87. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_graph_real.py +0 -0
  88. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_graph_stdin.py +0 -0
  89. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_grapher.py +0 -0
  90. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_installer.py +1 -1
  91. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_integration.py +0 -0
  92. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_lsp_provider.py +0 -0
  93. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_model_dimension.py +0 -0
  94. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_onboarding.py +0 -0
  95. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_reset.py +0 -0
  96. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_term.py +0 -0
  97. {codecortex-0.12.0 → codecortex-0.13.0}/tests/test_verify_call.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.12.0
3
+ Version: 0.13.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
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/hamilton-sky/codeintel
9
9
  Project-URL: Issues, https://github.com/hamilton-sky/codeintel/issues
10
10
  Project-URL: Changelog, https://github.com/hamilton-sky/codeintel/blob/main/CHANGELOG.md
11
11
  Keywords: mcp,model-context-protocol,code-intelligence,code-search,llm,agents,lsp,semantic-search,knowledge-graph,static-analysis,developer-tools
12
- Classifier: Development Status :: 4 - Beta
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Environment :: Console
15
15
  Classifier: Operating System :: OS Independent
@@ -29,7 +29,10 @@ Requires-Dist: fastembed>=0.3
29
29
  Requires-Dist: tree-sitter-language-pack>=1.0
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: pytest>=8; extra == "dev"
32
+ Requires-Dist: pytest-cov>=5; extra == "dev"
32
33
  Requires-Dist: numpy>=1.24; extra == "dev"
34
+ Requires-Dist: ruff>=0.16; extra == "dev"
35
+ Requires-Dist: mypy>=1.11; extra == "dev"
33
36
  Dynamic: license-file
34
37
 
35
38
  # codeintel
@@ -229,6 +232,9 @@ against the built wheel in a clean environment: it registers Codex and Claude Co
229
232
  `code.query` over a fixture repo. A release that writes a config no host reads, or that returns
230
233
  `ok: true` with nothing in it, fails there instead of on your machine.
231
234
 
235
+ > Full reference — what each host reads, the absolute-path rationale, and troubleshooting:
236
+ > **[docs/install.md](docs/install.md)**.
237
+
232
238
  ## How it works
233
239
 
234
240
  A `Gateway` receives every query and dispatches it to one of three providers — graph (structural relationships), LSP (precise symbol resolution), or semantic (embedding-based search) — based on the operation type. Each provider is fully isolated: if it is unavailable or raises an exception, the gateway catches it and returns a safe-null envelope. The caller always gets a well-formed response with no exception to catch.
@@ -275,6 +281,7 @@ Pass `--engine auto` (the default) and codeintel chooses the best engine per ope
275
281
  Full system docs live in [`docs/`](docs/) — start with the index:
276
282
 
277
283
  - **[Architecture](docs/architecture.md)** — layers, the `CodeProvider` protocol, the safe-null contract, caching, freshness (ASCII + Mermaid).
284
+ - **[Install & registration](docs/install.md)** — what each agent host actually reads, why the registered command is an absolute path, and the three levels of proof that registration worked.
278
285
  - **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
279
286
  - **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
280
287
  - **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 25 k chunks indexed in ~8 min, ~235 ms warm queries, 60 MB index.
@@ -284,6 +291,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
284
291
 
285
292
  | Command | Purpose |
286
293
  |---|---|
294
+ | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
287
295
  | `codeintel install [--agent auto\|claude\|codex\|gemini\|zed\|all] [--no-verify] [--relative-command]` | Register codeintel with the agents installed on this machine (`auto`, the default), then prove it by completing a real MCP handshake against the registered command |
288
296
  | `codeintel setup [project_root] [--all] [--index] [--warm] [--install-uv] [--install-deps] [--json]` | Prepare backends + index this repo (`--all` = one command: do everything automatable, idempotent); ends with a health report + **Next:** steps |
289
297
  | `codeintel index [project_root]` | Index a project for semantic search |
@@ -409,7 +417,19 @@ docker build -t codeintel . && docker run -p 127.0.0.1:8766:8766 \
409
417
  git clone https://github.com/hamilton-sky/codeintel.git
410
418
  cd codeintel
411
419
  pip install -e .[dev]
412
- pytest tests/ -q # ~390 tests, ~35s (live graph/LSP backend tests skip when absent)
420
+
421
+ pytest tests/ -q # ~494 tests, ~35s; fails under 83% coverage
422
+ ruff check src tests # lint
423
+ mypy # types (src/ only)
424
+ ```
425
+
426
+ **Your local run is not CI's run.** A dev machine usually has `codebase-memory-mcp` and `uvx`
427
+ installed; CI has neither, so the live graph/LSP tests skip there *and* the never-raise envelopes
428
+ take different `reason`/`hint` paths. A bug reachable only on the no-backend path passes at your
429
+ desk and fails in CI. To see CI's shape before you push:
430
+
431
+ ```bash
432
+ env PATH="$(dirname "$(which python)"):/usr/bin:/bin" pytest -q
413
433
  ```
414
434
 
415
435
  **Release gate.** The unit suite runs against the source tree, so it cannot see a packaging break, a
@@ -195,6 +195,9 @@ against the built wheel in a clean environment: it registers Codex and Claude Co
195
195
  `code.query` over a fixture repo. A release that writes a config no host reads, or that returns
196
196
  `ok: true` with nothing in it, fails there instead of on your machine.
197
197
 
198
+ > Full reference — what each host reads, the absolute-path rationale, and troubleshooting:
199
+ > **[docs/install.md](docs/install.md)**.
200
+
198
201
  ## How it works
199
202
 
200
203
  A `Gateway` receives every query and dispatches it to one of three providers — graph (structural relationships), LSP (precise symbol resolution), or semantic (embedding-based search) — based on the operation type. Each provider is fully isolated: if it is unavailable or raises an exception, the gateway catches it and returns a safe-null envelope. The caller always gets a well-formed response with no exception to catch.
@@ -241,6 +244,7 @@ Pass `--engine auto` (the default) and codeintel chooses the best engine per ope
241
244
  Full system docs live in [`docs/`](docs/) — start with the index:
242
245
 
243
246
  - **[Architecture](docs/architecture.md)** — layers, the `CodeProvider` protocol, the safe-null contract, caching, freshness (ASCII + Mermaid).
247
+ - **[Install & registration](docs/install.md)** — what each agent host actually reads, why the registered command is an absolute path, and the three levels of proof that registration worked.
244
248
  - **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
245
249
  - **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
246
250
  - **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 25 k chunks indexed in ~8 min, ~235 ms warm queries, 60 MB index.
@@ -250,6 +254,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
250
254
 
251
255
  | Command | Purpose |
252
256
  |---|---|
257
+ | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
253
258
  | `codeintel install [--agent auto\|claude\|codex\|gemini\|zed\|all] [--no-verify] [--relative-command]` | Register codeintel with the agents installed on this machine (`auto`, the default), then prove it by completing a real MCP handshake against the registered command |
254
259
  | `codeintel setup [project_root] [--all] [--index] [--warm] [--install-uv] [--install-deps] [--json]` | Prepare backends + index this repo (`--all` = one command: do everything automatable, idempotent); ends with a health report + **Next:** steps |
255
260
  | `codeintel index [project_root]` | Index a project for semantic search |
@@ -375,7 +380,19 @@ docker build -t codeintel . && docker run -p 127.0.0.1:8766:8766 \
375
380
  git clone https://github.com/hamilton-sky/codeintel.git
376
381
  cd codeintel
377
382
  pip install -e .[dev]
378
- pytest tests/ -q # ~390 tests, ~35s (live graph/LSP backend tests skip when absent)
383
+
384
+ pytest tests/ -q # ~494 tests, ~35s; fails under 83% coverage
385
+ ruff check src tests # lint
386
+ mypy # types (src/ only)
387
+ ```
388
+
389
+ **Your local run is not CI's run.** A dev machine usually has `codebase-memory-mcp` and `uvx`
390
+ installed; CI has neither, so the live graph/LSP tests skip there *and* the never-raise envelopes
391
+ take different `reason`/`hint` paths. A bug reachable only on the no-backend path passes at your
392
+ desk and fails in CI. To see CI's shape before you push:
393
+
394
+ ```bash
395
+ env PATH="$(dirname "$(which python)"):/usr/bin:/bin" pytest -q
379
396
  ```
380
397
 
381
398
  **Release gate.** The unit suite runs against the source tree, so it cannot see a packaging break, a
@@ -0,0 +1,139 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Distribution name is `codecortex` because `codeintel` is taken on PyPI and `codeintel-*` names
7
+ # are rejected as "too similar" to it. The import package and the CLI are still `codeintel`
8
+ # (i.e. `pip install codecortex` installs the `codeintel` command).
9
+ name = "codecortex"
10
+ dynamic = ["version"]
11
+ description = "Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents."
12
+ readme = "README.md"
13
+ requires-python = ">=3.11"
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ authors = [{ name = "Shammai Hamilton" }]
17
+ keywords = [
18
+ "mcp", "model-context-protocol", "code-intelligence", "code-search", "llm", "agents",
19
+ "lsp", "semantic-search", "knowledge-graph", "static-analysis", "developer-tools",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 5 - Production/Stable",
23
+ "Intended Audience :: Developers",
24
+ "Environment :: Console",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ "Topic :: Software Development :: Quality Assurance",
32
+ "Typing :: Typed",
33
+ ]
34
+ dependencies = [
35
+ "mcp>=1.0",
36
+ "sqlite-vec>=0.1",
37
+ "fastembed>=0.3",
38
+ "tree-sitter-language-pack>=1.0", # def-aligned chunking for non-Python languages (P3)
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/hamilton-sky/codeintel"
43
+ Repository = "https://github.com/hamilton-sky/codeintel"
44
+ Issues = "https://github.com/hamilton-sky/codeintel/issues"
45
+ Changelog = "https://github.com/hamilton-sky/codeintel/blob/main/CHANGELOG.md"
46
+
47
+ [project.scripts]
48
+ codeintel = "codeintel.__main__:main"
49
+
50
+ [project.optional-dependencies]
51
+ dev = ["pytest>=8", "pytest-cov>=5", "numpy>=1.24", "ruff>=0.16", "mypy>=1.11"]
52
+
53
+ # Single source of truth for the version: read from codeintel.__version__ (AST-parsed, no import).
54
+ [tool.setuptools.dynamic]
55
+ version = { attr = "codeintel.__version__" }
56
+
57
+ [tool.setuptools.packages.find]
58
+ where = ["src"]
59
+
60
+ # Ship the self-contained interactive graph viewer template with the wheel, and the PEP 561 marker
61
+ # without which the `Typing :: Typed` classifier is a promise the installed package cannot keep —
62
+ # type checkers ignore an installed package's annotations entirely unless py.typed is present.
63
+ [tool.setuptools.package-data]
64
+ "codeintel.viewer" = ["*.html"]
65
+ "codeintel" = ["py.typed"]
66
+
67
+ [tool.ruff]
68
+ line-length = 120
69
+ target-version = "py311"
70
+
71
+ [tool.ruff.lint]
72
+ select = [
73
+ "E", "W", # pycodestyle
74
+ "F", # pyflakes
75
+ "I", # import sorting
76
+ "UP", # pyupgrade — this is a >=3.11 package, so modern syntax is always available
77
+ "B", # bugbear
78
+ "C4", # comprehensions
79
+ "SIM", # simplify
80
+ "PIE", "PERF", "RUF",
81
+ "S", # bandit — the HTTP transport is a real network surface
82
+ ]
83
+ ignore = [
84
+ # The never-raise contract IS the architecture: providers and handlers degrade to a `reason`
85
+ # string rather than propagate, and the best-effort refresh paths (graph reindex, map rewrite)
86
+ # are deliberately allowed to fail silently. These three rules flag that design at ~190 sites,
87
+ # so enforcing them would mean 190 `noqa`s asserting the same thing the module docstrings say.
88
+ "S110", # try-except-pass
89
+ "S112", # try-except-continue
90
+ "SIM105", # "use contextlib.suppress" — see below
91
+ "S603", # subprocess call — every one is an explicit argv list, never a shell string
92
+ ]
93
+ # On SIM105 specifically: the suppressed blocks carry their reason on the `pass` line itself
94
+ # (`pass # code_embeddings is created lazily at first embed — may not exist yet`), and
95
+ # contextlib.suppress has nowhere to put that. Losing 13 such explanations to gain a context
96
+ # manager is a bad trade in code whose whole contract is degrading quietly.
97
+
98
+ [tool.ruff.lint.per-file-ignores]
99
+ # Tests stand up fake servers on loopback, assert on hardcoded fixture tokens, and shell out to
100
+ # the CLI under test. Flagging that as a security finding is noise.
101
+ "tests/*" = [
102
+ "S101", "S104", "S105", "S106", "S108", "S404", "S607",
103
+ "S310", # urlopen against the loopback test server this same test just started
104
+ "E402", # a few modules import after a skip guard, so a missing backend skips, not errors
105
+ "E501", # fixture payloads (graph JSON, serena responses) are copied verbatim; rewrapping
106
+ # them would make them stop matching what the real backend returns
107
+ "E702", # `a = 1; b = 2` setup one-liners
108
+ "SIM115", # ad-hoc open() in assertions, inside tmp_path — nothing to leak
109
+ "UP031", # %-format is the readable choice for templating source into a fake server
110
+ ]
111
+
112
+ [tool.mypy]
113
+ python_version = "3.11"
114
+ files = ["src/codeintel"]
115
+ # Deliberately not --strict: the provider seams cross into untyped third-party backends (mcp,
116
+ # fastembed, sqlite-vec, tree-sitter), where demanding annotations buys noise rather than safety.
117
+ # These flags catch the errors that actually bite — unreachable branches, bad returns, silent Any
118
+ # propagation out of our own typed code.
119
+ warn_redundant_casts = true
120
+ warn_unused_ignores = true
121
+ warn_unreachable = true
122
+ warn_no_return = true
123
+ no_implicit_optional = true
124
+ check_untyped_defs = true
125
+
126
+ [[tool.mypy.overrides]]
127
+ # Don't type-check the backends themselves, and don't follow into them: `python_version = "3.11"`
128
+ # makes mypy parse their stubs under 3.11 syntax rules, and numpy's (reached transitively through
129
+ # fastembed) use 3.12 `type` statements — an error in a dependency's own stubs, not in our code.
130
+ module = ["mcp.*", "fastembed.*", "sqlite_vec.*", "tree_sitter_language_pack.*", "numpy.*"]
131
+ ignore_missing_imports = true
132
+ follow_imports = "skip"
133
+
134
+ [tool.pytest.ini_options]
135
+ # The floor sits below the current number (85.3% with every backend present) on purpose: the live
136
+ # graph/LSP tests skip when codebase-memory-mcp and uvx/serena are absent, which is exactly the
137
+ # case in CI, and that costs ~0.4 points. A floor pinned to today's measurement would go red on a
138
+ # runner's toolchain rather than on a real coverage regression. Ratchet it up deliberately.
139
+ addopts = "--cov=codeintel --cov-report=term-missing:skip-covered --cov-fail-under=83"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.12.0
3
+ Version: 0.13.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
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/hamilton-sky/codeintel
9
9
  Project-URL: Issues, https://github.com/hamilton-sky/codeintel/issues
10
10
  Project-URL: Changelog, https://github.com/hamilton-sky/codeintel/blob/main/CHANGELOG.md
11
11
  Keywords: mcp,model-context-protocol,code-intelligence,code-search,llm,agents,lsp,semantic-search,knowledge-graph,static-analysis,developer-tools
12
- Classifier: Development Status :: 4 - Beta
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Environment :: Console
15
15
  Classifier: Operating System :: OS Independent
@@ -29,7 +29,10 @@ Requires-Dist: fastembed>=0.3
29
29
  Requires-Dist: tree-sitter-language-pack>=1.0
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: pytest>=8; extra == "dev"
32
+ Requires-Dist: pytest-cov>=5; extra == "dev"
32
33
  Requires-Dist: numpy>=1.24; extra == "dev"
34
+ Requires-Dist: ruff>=0.16; extra == "dev"
35
+ Requires-Dist: mypy>=1.11; extra == "dev"
33
36
  Dynamic: license-file
34
37
 
35
38
  # codeintel
@@ -229,6 +232,9 @@ against the built wheel in a clean environment: it registers Codex and Claude Co
229
232
  `code.query` over a fixture repo. A release that writes a config no host reads, or that returns
230
233
  `ok: true` with nothing in it, fails there instead of on your machine.
231
234
 
235
+ > Full reference — what each host reads, the absolute-path rationale, and troubleshooting:
236
+ > **[docs/install.md](docs/install.md)**.
237
+
232
238
  ## How it works
233
239
 
234
240
  A `Gateway` receives every query and dispatches it to one of three providers — graph (structural relationships), LSP (precise symbol resolution), or semantic (embedding-based search) — based on the operation type. Each provider is fully isolated: if it is unavailable or raises an exception, the gateway catches it and returns a safe-null envelope. The caller always gets a well-formed response with no exception to catch.
@@ -275,6 +281,7 @@ Pass `--engine auto` (the default) and codeintel chooses the best engine per ope
275
281
  Full system docs live in [`docs/`](docs/) — start with the index:
276
282
 
277
283
  - **[Architecture](docs/architecture.md)** — layers, the `CodeProvider` protocol, the safe-null contract, caching, freshness (ASCII + Mermaid).
284
+ - **[Install & registration](docs/install.md)** — what each agent host actually reads, why the registered command is an absolute path, and the three levels of proof that registration worked.
278
285
  - **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
279
286
  - **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
280
287
  - **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 25 k chunks indexed in ~8 min, ~235 ms warm queries, 60 MB index.
@@ -284,6 +291,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
284
291
 
285
292
  | Command | Purpose |
286
293
  |---|---|
294
+ | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
287
295
  | `codeintel install [--agent auto\|claude\|codex\|gemini\|zed\|all] [--no-verify] [--relative-command]` | Register codeintel with the agents installed on this machine (`auto`, the default), then prove it by completing a real MCP handshake against the registered command |
288
296
  | `codeintel setup [project_root] [--all] [--index] [--warm] [--install-uv] [--install-deps] [--json]` | Prepare backends + index this repo (`--all` = one command: do everything automatable, idempotent); ends with a health report + **Next:** steps |
289
297
  | `codeintel index [project_root]` | Index a project for semantic search |
@@ -409,7 +417,19 @@ docker build -t codeintel . && docker run -p 127.0.0.1:8766:8766 \
409
417
  git clone https://github.com/hamilton-sky/codeintel.git
410
418
  cd codeintel
411
419
  pip install -e .[dev]
412
- pytest tests/ -q # ~390 tests, ~35s (live graph/LSP backend tests skip when absent)
420
+
421
+ pytest tests/ -q # ~494 tests, ~35s; fails under 83% coverage
422
+ ruff check src tests # lint
423
+ mypy # types (src/ only)
424
+ ```
425
+
426
+ **Your local run is not CI's run.** A dev machine usually has `codebase-memory-mcp` and `uvx`
427
+ installed; CI has neither, so the live graph/LSP tests skip there *and* the never-raise envelopes
428
+ take different `reason`/`hint` paths. A bug reachable only on the no-backend path passes at your
429
+ desk and fails in CI. To see CI's shape before you push:
430
+
431
+ ```bash
432
+ env PATH="$(dirname "$(which python)"):/usr/bin:/bin" pytest -q
413
433
  ```
414
434
 
415
435
  **Release gate.** The unit suite runs against the source tree, so it cannot see a packaging break, a
@@ -25,6 +25,7 @@ src/codeintel/metrics.py
25
25
  src/codeintel/onboarding.py
26
26
  src/codeintel/policy.py
27
27
  src/codeintel/provider.py
28
+ src/codeintel/py.typed
28
29
  src/codeintel/reindexer.py
29
30
  src/codeintel/reset.py
30
31
  src/codeintel/searcher.py
@@ -32,6 +33,20 @@ src/codeintel/semantic_db.py
32
33
  src/codeintel/server.py
33
34
  src/codeintel/term.py
34
35
  src/codeintel/verify.py
36
+ src/codeintel/commands/__init__.py
37
+ src/codeintel/commands/_common.py
38
+ src/codeintel/commands/doctor.py
39
+ src/codeintel/commands/gen_token.py
40
+ src/codeintel/commands/graph.py
41
+ src/codeintel/commands/index.py
42
+ src/codeintel/commands/install.py
43
+ src/codeintel/commands/map.py
44
+ src/codeintel/commands/query.py
45
+ src/codeintel/commands/reset.py
46
+ src/codeintel/commands/serve.py
47
+ src/codeintel/commands/serve_http.py
48
+ src/codeintel/commands/setup.py
49
+ src/codeintel/commands/status.py
35
50
  src/codeintel/providers/__init__.py
36
51
  src/codeintel/providers/graph.py
37
52
  src/codeintel/providers/lsp.py
@@ -41,6 +56,8 @@ src/codeintel/viewer/__init__.py
41
56
  src/codeintel/viewer/graph_template.html
42
57
  tests/test_cache.py
43
58
  tests/test_chunking.py
59
+ tests/test_cli_commands.py
60
+ tests/test_cli_help.py
44
61
  tests/test_config.py
45
62
  tests/test_doctor.py
46
63
  tests/test_e2e.py
@@ -5,4 +5,7 @@ tree-sitter-language-pack>=1.0
5
5
 
6
6
  [dev]
7
7
  pytest>=8
8
+ pytest-cov>=5
8
9
  numpy>=1.24
10
+ ruff>=0.16
11
+ mypy>=1.11
@@ -0,0 +1 @@
1
+ __version__ = "0.13.0"
@@ -0,0 +1,273 @@
1
+ import argparse
2
+ import difflib
3
+ import sys
4
+ from importlib import import_module
5
+
6
+ from codeintel import __version__
7
+
8
+ # Commands grouped by what you are trying to DO. argparse lists them in declaration order with no
9
+ # grouping, which turns "what can this thing do?" into reading twelve lines to find the one verb you
10
+ # wanted. Each entry is (command, one-line description).
11
+ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
12
+ ("Understand your code", [
13
+ ("query", "Ask one question — search, callers, callees, impact, chain, symbol, hotspots"),
14
+ ("map", "Write CODE_INTEL.md — a committable architecture overview"),
15
+ ("graph", "Interactive call-graph viewer (--html), or the graph as JSON"),
16
+ ]),
17
+ ("Set up", [
18
+ ("setup", "Prepare backends + index this repo (--all does everything automatable)"),
19
+ ("index", "Index a project for semantic search"),
20
+ ("install", "Register codeintel with the AI agents installed on this machine"),
21
+ ]),
22
+ ("Check health", [
23
+ ("doctor", "Per-engine health + index status, with the fix for each gap"),
24
+ ("status", "Engine readiness and index age at a glance"),
25
+ ("reset", "Clear the semantic index (recover from a corrupt or stale DB)"),
26
+ ]),
27
+ ("Run as a server", [
28
+ ("serve", "Start the MCP server over stdio — what an agent host launches"),
29
+ ("serve-http", "Start the HTTP transport (loopback only unless --allow-remote)"),
30
+ ("gen-token", "Print a secure random bearer token for serve-http / RBAC"),
31
+ ]),
32
+ ]
33
+
34
+ _COMMANDS = [name for _group, items in _COMMAND_GROUPS for name, _desc in items]
35
+
36
+ # Each command's body lives in codeintel.commands.<module> as `run(args) -> int`. The mapping is
37
+ # spelled out rather than derived from the command name so the target of any command is greppable,
38
+ # and the import happens at dispatch time so `codeintel serve` never pays for the semantic
39
+ # engine's imports (nor serve-http for the graph's).
40
+ _MODULES = {
41
+ "query": "query",
42
+ "map": "map",
43
+ "graph": "graph",
44
+ "setup": "setup",
45
+ "index": "index",
46
+ "install": "install",
47
+ "doctor": "doctor",
48
+ "status": "status",
49
+ "reset": "reset",
50
+ "serve": "serve",
51
+ "serve-http": "serve_http",
52
+ "gen-token": "gen_token",
53
+ }
54
+
55
+ _EXAMPLES = [
56
+ ("codeintel setup --all .", "prepare backends and index this repo"),
57
+ ("codeintel install", "register with the agents you have"),
58
+ ("codeintel query --op callers --target my_function", "who calls it?"),
59
+ ("codeintel doctor", "why is a query coming back empty?"),
60
+ ]
61
+
62
+
63
+ def render_help() -> str:
64
+ """The `codeintel` / `codeintel help` screen: grouped, colored, with real examples.
65
+
66
+ Color comes from codeintel.term, so it auto-degrades on a pipe, under NO_COLOR, and on a dumb
67
+ terminal — same as every other human-facing command."""
68
+ from codeintel.term import c
69
+
70
+ width = max(len(name) for name in _COMMANDS)
71
+ out = [
72
+ c.bold("codeintel") + c.dim(f" {__version__}")
73
+ + c.dim(" — code intelligence for AI agents: graph + LSP + semantic search"),
74
+ "",
75
+ c.dim("usage: ") + "codeintel <command> [options]",
76
+ ]
77
+ for group, items in _COMMAND_GROUPS:
78
+ out.append("")
79
+ out.append(" " + c.bold(group))
80
+ for name, desc in items:
81
+ out.append(" " + c.cyan(name.ljust(width)) + " " + desc)
82
+
83
+ out.append("")
84
+ out.append(" " + c.bold("Examples"))
85
+ # Width from the content, not a guess — a hardcoded column silently loses its gutter the moment
86
+ # one example grows past it, and ljust() will not pad below the string's own length.
87
+ cmd_width = max(len(cmd) for cmd, _why in _EXAMPLES) + 2
88
+ for cmd, why in _EXAMPLES:
89
+ out.append(" " + cmd.ljust(cmd_width) + c.dim("# " + why))
90
+
91
+ out.append("")
92
+ out.append(" " + c.dim("codeintel <command> --help") + " full options for one command")
93
+ out.append(" " + c.dim("docs: https://github.com/hamilton-sky/codeintel"))
94
+ return "\n".join(out)
95
+
96
+
97
+ def _suggest(unknown: str) -> list[str]:
98
+ """Commands a typo probably meant. Close matches first, then prefix matches — `gragh` should
99
+ land on `graph`, and a bare `serv` on both `serve` and `serve-http`."""
100
+ close = difflib.get_close_matches(unknown, _COMMANDS, n=3, cutoff=0.5)
101
+ prefix = [cmd for cmd in _COMMANDS if cmd.startswith(unknown) and cmd not in close]
102
+ return (close + prefix)[:3]
103
+
104
+
105
+ def _unknown_command(name: str) -> int:
106
+ """Report an unrecognized command with a way forward. argparse's own error dumps the full list
107
+ of choices and stops there, which is a dead end for a one-character typo."""
108
+ from codeintel.term import c_err as e
109
+
110
+ print(e.red(f"unknown command: {name!r}"), file=sys.stderr)
111
+ matches = _suggest(name)
112
+ if matches:
113
+ joined = " or ".join(e.cyan(m) for m in matches)
114
+ print(f"\n did you mean {joined}?", file=sys.stderr)
115
+ print("\n " + e.dim("run `codeintel help` to see every command"), file=sys.stderr)
116
+ return 2
117
+
118
+
119
+ def main() -> None:
120
+ parser = argparse.ArgumentParser(prog="codeintel")
121
+ parser.add_argument("--version", action="version", version=f"codeintel {__version__}")
122
+ subparsers = parser.add_subparsers(dest="command")
123
+
124
+ # Shared flags for the human-facing (styled) commands.
125
+ color_parent = argparse.ArgumentParser(add_help=False)
126
+ color_parent.add_argument("--no-color", action="store_true", help="Disable ANSI color output")
127
+ color_parent.add_argument("--ascii", action="store_true", help="Use ASCII-only glyphs")
128
+
129
+ subparsers.add_parser("serve", help="Start the MCP server")
130
+
131
+ # index subcommand
132
+ index_parser = subparsers.add_parser("index", help="Index a project for semantic search")
133
+ index_parser.add_argument(
134
+ "project_root",
135
+ nargs="?",
136
+ default=None,
137
+ help="Project root directory (default: cwd)",
138
+ )
139
+
140
+ # query subcommand
141
+ query_parser = subparsers.add_parser("query", help="Query the code intelligence engine")
142
+ query_parser.add_argument("--op", required=True, help="Query operation (e.g. search, symbol)")
143
+ query_parser.add_argument("--target", required=True, help="Query target")
144
+ query_parser.add_argument("--engine", default="auto", help="Engine to use (default: auto)")
145
+ query_parser.add_argument(
146
+ "--project-root",
147
+ default=None,
148
+ help="Project root directory (default: cwd)",
149
+ )
150
+
151
+ # status subcommand
152
+ status_parser = subparsers.add_parser("status", help="Show code intelligence engine status")
153
+ status_parser.add_argument(
154
+ "project_root",
155
+ nargs="?",
156
+ default=None,
157
+ help="Project root directory (default: cwd)",
158
+ )
159
+
160
+ # serve-http subcommand
161
+ http_parser = subparsers.add_parser("serve-http", help="Start the HTTP transport server")
162
+ http_parser.add_argument("--port", type=int, default=8766, help="Port to listen on (default: 8766)")
163
+ http_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
164
+ http_parser.add_argument("--allow-remote", action="store_true",
165
+ help="Permit binding a non-loopback host (use with --token, or the "
166
+ "endpoint is UNAUTHENTICATED)")
167
+ http_parser.add_argument("--token", default=None,
168
+ help="Require this bearer token on every request (or set "
169
+ "CODEINTEL_HTTP_TOKEN). Strongly recommended with --allow-remote.")
170
+
171
+ # install subcommand
172
+ install_parser = subparsers.add_parser("install", help="Register codeintel with AI agents")
173
+ install_parser.add_argument(
174
+ "--agent",
175
+ choices=["auto", "claude", "codex", "gemini", "zed", "all"],
176
+ default="auto",
177
+ help="Agent to register with (default: auto — only agents installed on this machine; "
178
+ "`all` forces every supported agent)",
179
+ )
180
+ install_parser.add_argument(
181
+ "--no-verify",
182
+ action="store_true",
183
+ help="Skip the post-registration MCP handshake (verification is on by default)",
184
+ )
185
+ install_parser.add_argument(
186
+ "--relative-command",
187
+ action="store_true",
188
+ help="Register the bare `codeintel` name instead of its absolute path (the absolute path "
189
+ "is the default because a GUI-launched agent does not inherit your shell's PATH)",
190
+ )
191
+
192
+ # map subcommand
193
+ map_parser = subparsers.add_parser("map", help="Generate CODE_INTEL.md orientation file")
194
+ map_parser.add_argument("project_root", nargs="?", default=None)
195
+ map_parser.add_argument("--inject", action="store_true", help="Inject reference block into CLAUDE.md/AGENTS.md")
196
+ map_parser.add_argument("--budget", type=int, default=32768, help="Byte budget for CODE_INTEL.md (default: 32768)")
197
+
198
+ # graph subcommand — interactive call-graph view (HTML) or the raw {nodes,edges} JSON
199
+ graph_parser = subparsers.add_parser(
200
+ "graph", help="Build an interactive call-graph view (--html) or emit the graph as JSON — "
201
+ "works on any indexed repo")
202
+ graph_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
203
+ graph_parser.add_argument("--html", action="store_true",
204
+ help="Write a self-contained interactive HTML viewer (default: print JSON)")
205
+ graph_parser.add_argument("--out", default=None, help="Output path for --html (default: codeintel-graph.html)")
206
+ graph_parser.add_argument("--limit", type=int, default=220, help="Max call edges to include (default: 220)")
207
+
208
+ # doctor subcommand
209
+ doctor_parser = subparsers.add_parser("doctor", parents=[color_parent],
210
+ help="Diagnose engine health + index status for a repo")
211
+ doctor_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
212
+ doctor_parser.add_argument("--deep", action="store_true",
213
+ help="Also boot-check serena (slower; first boot pulls it via uvx)")
214
+ doctor_parser.add_argument("--json", action="store_true",
215
+ help="Emit the structured JSON report instead of the table")
216
+
217
+ # setup subcommand
218
+ setup_parser = subparsers.add_parser("setup", parents=[color_parent],
219
+ help="Prepare backends and optionally index this repo")
220
+ setup_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
221
+ setup_parser.add_argument("--all", action="store_true", dest="all_steps",
222
+ help="One-command setup: do everything automatable (uv + deps + index + "
223
+ "warm serena). Idempotent — skips what's already installed.")
224
+ setup_parser.add_argument("--install-uv", action="store_true",
225
+ help="Run `pip install uv` (provides uvx for the LSP engine)")
226
+ setup_parser.add_argument("--install-deps", action="store_true",
227
+ help="Run `pip install -e .` (semantic engine deps)")
228
+ setup_parser.add_argument("--index", action="store_true",
229
+ help="Index this repo now (first run downloads the ~50MB model)")
230
+ setup_parser.add_argument("--warm", action="store_true", help="Boot serena now (first run pulls it via uvx; slow)")
231
+ setup_parser.add_argument("--json", action="store_true", help="Emit the structured JSON report")
232
+
233
+ # reset subcommand
234
+ reset_parser = subparsers.add_parser("reset", parents=[color_parent],
235
+ help="Clear the semantic index (recover from a corrupt/stale DB)")
236
+ reset_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
237
+ reset_parser.add_argument("--all", action="store_true",
238
+ help="Clear the ENTIRE index (all projects), not just this repo")
239
+ reset_parser.add_argument("--yes", "-y", action="store_true", help="Skip the confirmation prompt")
240
+ reset_parser.add_argument("--json", action="store_true", help="Emit the structured JSON report")
241
+
242
+ subparsers.add_parser("gen-token", help="Print a secure random bearer token (for serve-http / RBAC auth.toml)")
243
+ subparsers.add_parser("help", help="Show every command, grouped, with examples")
244
+
245
+ # Intercept an unrecognized command BEFORE argparse, whose error prints the full choice list and
246
+ # stops — a dead end for a one-character typo. Only a bare word is claimed here; anything
247
+ # starting with `-` (--version, --help) still goes to argparse.
248
+ argv = sys.argv[1:]
249
+ if argv and not argv[0].startswith("-") and argv[0] not in [*_COMMANDS, "help"]:
250
+ sys.exit(_unknown_command(argv[0]))
251
+ if not argv or argv[0] == "help":
252
+ from codeintel import term
253
+ term.configure(no_color=False, ascii_mode=None)
254
+ print(render_help())
255
+ sys.exit(0)
256
+
257
+ args = parser.parse_args()
258
+
259
+ from codeintel import term
260
+ term.configure(
261
+ no_color=getattr(args, "no_color", False),
262
+ ascii_mode=(True if getattr(args, "ascii", False) else None),
263
+ )
264
+
265
+ module = _MODULES.get(args.command)
266
+ if module is None: # unreachable via argparse; a bare `codeintel` is handled above
267
+ print(render_help())
268
+ sys.exit(0)
269
+ sys.exit(import_module(f"codeintel.commands.{module}").run(args))
270
+
271
+
272
+ if __name__ == "__main__":
273
+ main()