codecortex 0.12.1__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.1/src/codecortex.egg-info → codecortex-0.13.0}/PKG-INFO +18 -3
  2. {codecortex-0.12.1 → codecortex-0.13.0}/README.md +13 -1
  3. codecortex-0.13.0/pyproject.toml +139 -0
  4. {codecortex-0.12.1 → codecortex-0.13.0/src/codecortex.egg-info}/PKG-INFO +18 -3
  5. {codecortex-0.12.1 → codecortex-0.13.0}/src/codecortex.egg-info/SOURCES.txt +16 -0
  6. {codecortex-0.12.1 → 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.1 → codecortex-0.13.0}/src/codeintel/auth.py +10 -9
  10. {codecortex-0.12.1 → 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.1 → codecortex-0.13.0}/src/codeintel/config.py +2 -12
  26. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/doctor.py +8 -7
  27. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/gateway.py +4 -5
  28. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/http_server.py +6 -2
  29. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/indexer.py +6 -7
  30. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/installer.py +4 -5
  31. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/mapper.py +10 -10
  32. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/metrics.py +3 -4
  33. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/onboarding.py +6 -4
  34. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/policy.py +2 -4
  35. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/provider.py +5 -4
  36. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/providers/graph.py +27 -25
  37. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/providers/lsp.py +27 -29
  38. {codecortex-0.12.1 → 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.1 → codecortex-0.13.0}/src/codeintel/searcher.py +1 -1
  41. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/server.py +10 -6
  42. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/term.py +3 -4
  43. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/verify.py +23 -21
  44. {codecortex-0.12.1 → 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.12.1 → codecortex-0.13.0}/tests/test_doctor.py +1 -3
  47. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_gateway.py +0 -1
  48. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_hardening.py +0 -1
  49. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_http_auth.py +3 -1
  50. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_http_server.py +1 -1
  51. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_lsp_real.py +2 -2
  52. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_mapper.py +2 -4
  53. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_mcp_handshake.py +21 -24
  54. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_mcp_server.py +36 -0
  55. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_never_raise.py +4 -8
  56. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_rbac.py +0 -1
  57. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_reindexer.py +1 -1
  58. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_rerank.py +4 -4
  59. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_semantic_provider.py +2 -10
  60. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_treesitter.py +1 -1
  61. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_verify.py +0 -1
  62. codecortex-0.12.1/pyproject.toml +0 -62
  63. codecortex-0.12.1/src/codeintel/__init__.py +0 -1
  64. codecortex-0.12.1/src/codeintel/__main__.py +0 -577
  65. {codecortex-0.12.1 → codecortex-0.13.0}/LICENSE +0 -0
  66. {codecortex-0.12.1 → codecortex-0.13.0}/setup.cfg +0 -0
  67. {codecortex-0.12.1 → codecortex-0.13.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  68. {codecortex-0.12.1 → codecortex-0.13.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  69. {codecortex-0.12.1 → codecortex-0.13.0}/src/codecortex.egg-info/top_level.txt +0 -0
  70. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/grapher.py +0 -0
  71. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/injector.py +0 -0
  72. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/logconfig.py +0 -0
  73. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/providers/__init__.py +0 -0
  74. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/providers/none.py +0 -0
  75. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/reindexer.py +1 -1
  76. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/reset.py +0 -0
  77. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/semantic_db.py +0 -0
  78. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/viewer/__init__.py +0 -0
  79. {codecortex-0.12.1 → codecortex-0.13.0}/src/codeintel/viewer/graph_template.html +0 -0
  80. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_cache.py +0 -0
  81. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_cli_help.py +0 -0
  82. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_config.py +0 -0
  83. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_e2e.py +0 -0
  84. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_engine_adoption.py +0 -0
  85. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_enterprise.py +0 -0
  86. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_graph_provider.py +0 -0
  87. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_graph_real.py +0 -0
  88. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_graph_stdin.py +0 -0
  89. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_grapher.py +0 -0
  90. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_installer.py +1 -1
  91. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_integration.py +0 -0
  92. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_lsp_provider.py +0 -0
  93. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_model_dimension.py +0 -0
  94. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_onboarding.py +0 -0
  95. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_reset.py +0 -0
  96. {codecortex-0.12.1 → codecortex-0.13.0}/tests/test_term.py +0 -0
  97. {codecortex-0.12.1 → 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.1
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
@@ -414,7 +417,19 @@ docker build -t codeintel . && docker run -p 127.0.0.1:8766:8766 \
414
417
  git clone https://github.com/hamilton-sky/codeintel.git
415
418
  cd codeintel
416
419
  pip install -e .[dev]
417
- pytest tests/ -q # ~410 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
418
433
  ```
419
434
 
420
435
  **Release gate.** The unit suite runs against the source tree, so it cannot see a packaging break, a
@@ -380,7 +380,19 @@ docker build -t codeintel . && docker run -p 127.0.0.1:8766:8766 \
380
380
  git clone https://github.com/hamilton-sky/codeintel.git
381
381
  cd codeintel
382
382
  pip install -e .[dev]
383
- pytest tests/ -q # ~410 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
384
396
  ```
385
397
 
386
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.1
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
@@ -414,7 +417,19 @@ docker build -t codeintel . && docker run -p 127.0.0.1:8766:8766 \
414
417
  git clone https://github.com/hamilton-sky/codeintel.git
415
418
  cd codeintel
416
419
  pip install -e .[dev]
417
- pytest tests/ -q # ~410 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
418
433
  ```
419
434
 
420
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,7 @@ 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
44
60
  tests/test_cli_help.py
45
61
  tests/test_config.py
46
62
  tests/test_doctor.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()
@@ -25,7 +25,6 @@ import logging
25
25
  import os
26
26
  import pathlib
27
27
  import tomllib # stdlib on Python 3.11+ (the project's minimum)
28
- from typing import Dict, List, Optional
29
28
 
30
29
  from codeintel.policy import TieringPolicy
31
30
 
@@ -39,7 +38,7 @@ class TokenAuth:
39
38
  """Resolves a bearer token to a role and builds the matching op policy. Tokens are held as
40
39
  sha256 hashes; a presented token is hashed and looked up in O(1). Immutable; thread-safe."""
41
40
 
42
- def __init__(self, token_hash_to_role: Dict[str, str], role_ops: Dict[str, List[str]]) -> None:
41
+ def __init__(self, token_hash_to_role: dict[str, str], role_ops: dict[str, list[str]]) -> None:
43
42
  self._tokens = token_hash_to_role
44
43
  self._role_ops = role_ops
45
44
 
@@ -47,7 +46,7 @@ class TokenAuth:
47
46
  def enabled(self) -> bool:
48
47
  return bool(self._tokens)
49
48
 
50
- def role_for(self, presented_token: str) -> Optional[str]:
49
+ def role_for(self, presented_token: str) -> str | None:
51
50
  """The role for a presented token, or None if it isn't a known token."""
52
51
  if not presented_token:
53
52
  return None
@@ -58,7 +57,7 @@ class TokenAuth:
58
57
  """A role with ops ``["*"]`` (or none) is unrestricted → omit it from the rules, since
59
58
  TieringPolicy treats a role absent from its rules as full-access. Every other role maps to
60
59
  its explicit op allowlist (an empty list = deny all ops, a fail-safe default)."""
61
- rules: Dict[str, List[str]] = {}
60
+ rules: dict[str, list[str]] = {}
62
61
  for role, ops in self._role_ops.items():
63
62
  if _ALL in ops:
64
63
  continue # unrestricted
@@ -66,7 +65,7 @@ class TokenAuth:
66
65
  return TieringPolicy(enabled=bool(rules), rules=rules)
67
66
 
68
67
 
69
- def _auth_config_path() -> Optional[pathlib.Path]:
68
+ def _auth_config_path() -> pathlib.Path | None:
70
69
  env = os.environ.get("CODEINTEL_AUTH_CONFIG", "").strip()
71
70
  if env:
72
71
  p = pathlib.Path(env)
@@ -88,8 +87,9 @@ def load_auth() -> TokenAuth:
88
87
  logger.warning("auth: %s could not be parsed (%s) — RBAC is OFF", path, type(exc).__name__)
89
88
  return TokenAuth({}, {})
90
89
 
91
- raw_roles = data.get("roles") if isinstance(data.get("roles"), dict) else {}
92
- role_ops: Dict[str, List[str]] = {}
90
+ roles_section = data.get("roles")
91
+ raw_roles: dict = roles_section if isinstance(roles_section, dict) else {}
92
+ role_ops: dict[str, list[str]] = {}
93
93
  for role, ops in raw_roles.items():
94
94
  if isinstance(ops, list):
95
95
  role_ops[str(role)] = [str(o) for o in ops]
@@ -99,8 +99,9 @@ def load_auth() -> TokenAuth:
99
99
  logger.warning("auth: role %r ops must be a list (got %r) — denying all ops for it", role, ops)
100
100
  role_ops[str(role)] = []
101
101
 
102
- raw_tokens = data.get("tokens") if isinstance(data.get("tokens"), dict) else {}
103
- token_hash_to_role: Dict[str, str] = {}
102
+ tokens_section = data.get("tokens")
103
+ raw_tokens: dict = tokens_section if isinstance(tokens_section, dict) else {}
104
+ token_hash_to_role: dict[str, str] = {}
104
105
  for tok, role in raw_tokens.items():
105
106
  role = str(role)
106
107
  # A token mapped to an undefined role fails safe: define it as deny-all (empty op list).