codecortex 0.22.0__tar.gz → 0.23.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 (146) hide show
  1. {codecortex-0.22.0/src/codecortex.egg-info → codecortex-0.23.0}/PKG-INFO +62 -9
  2. {codecortex-0.22.0 → codecortex-0.23.0}/README.md +59 -8
  3. {codecortex-0.22.0 → codecortex-0.23.0}/pyproject.toml +19 -0
  4. {codecortex-0.22.0 → codecortex-0.23.0/src/codecortex.egg-info}/PKG-INFO +62 -9
  5. {codecortex-0.22.0 → codecortex-0.23.0}/src/codecortex.egg-info/SOURCES.txt +9 -0
  6. {codecortex-0.22.0 → codecortex-0.23.0}/src/codecortex.egg-info/requires.txt +3 -0
  7. codecortex-0.23.0/src/codeintel/__init__.py +1 -0
  8. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/__main__.py +35 -0
  9. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/c4.py +174 -31
  10. codecortex-0.23.0/src/codeintel/c4_check.py +438 -0
  11. codecortex-0.23.0/src/codeintel/c4_layers.py +657 -0
  12. codecortex-0.23.0/src/codeintel/commands/c4.py +349 -0
  13. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/setup.py +1 -0
  14. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/gateway.py +5 -1
  15. codecortex-0.23.0/src/codeintel/lang_config.py +291 -0
  16. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/mapper.py +91 -26
  17. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/onboarding.py +41 -3
  18. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/providers/graph.py +19 -2
  19. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/providers/lsp.py +53 -1
  20. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/providers/semantic.py +20 -1
  21. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/server.py +6 -4
  22. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/verify.py +53 -7
  23. codecortex-0.23.0/tests/test_bench_oracle.py +203 -0
  24. codecortex-0.23.0/tests/test_bench_oracle_ts.py +218 -0
  25. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_c4.py +198 -0
  26. codecortex-0.23.0/tests/test_c4_check.py +458 -0
  27. codecortex-0.23.0/tests/test_c4_layers.py +331 -0
  28. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_c4_output.py +2 -1
  29. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_cli_commands.py +5 -3
  30. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_cold_process.py +6 -0
  31. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_graph_provider.py +37 -0
  32. codecortex-0.23.0/tests/test_hard_exit.py +132 -0
  33. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_incompleteness.py +16 -10
  34. codecortex-0.23.0/tests/test_lang_config.py +260 -0
  35. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_lsp_provider.py +114 -0
  36. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_mapper.py +185 -4
  37. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_onboarding.py +48 -0
  38. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_semantic_provider.py +108 -0
  39. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_verify.py +85 -0
  40. codecortex-0.22.0/src/codeintel/__init__.py +0 -1
  41. codecortex-0.22.0/src/codeintel/commands/c4.py +0 -182
  42. {codecortex-0.22.0 → codecortex-0.23.0}/LICENSE +0 -0
  43. {codecortex-0.22.0 → codecortex-0.23.0}/setup.cfg +0 -0
  44. {codecortex-0.22.0 → codecortex-0.23.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  45. {codecortex-0.22.0 → codecortex-0.23.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  46. {codecortex-0.22.0 → codecortex-0.23.0}/src/codecortex.egg-info/top_level.txt +0 -0
  47. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/agent_prompt.py +0 -0
  48. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/auth.py +0 -0
  49. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/cache.py +0 -0
  50. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/__init__.py +0 -0
  51. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/_common.py +0 -0
  52. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/doctor.py +0 -0
  53. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/gen_token.py +0 -0
  54. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/graph.py +0 -0
  55. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/index.py +0 -0
  56. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/install.py +0 -0
  57. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/map.py +0 -0
  58. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/prompt.py +0 -0
  59. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/query.py +0 -0
  60. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/reset.py +0 -0
  61. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/serve.py +0 -0
  62. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/serve_http.py +0 -0
  63. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/commands/status.py +0 -0
  64. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/config.py +0 -0
  65. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/containment.py +0 -0
  66. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/doctor.py +0 -0
  67. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/graph_backend.py +0 -0
  68. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/graph_render.py +0 -0
  69. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/graph_resolution.py +0 -0
  70. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/grapher.py +0 -0
  71. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/http_server.py +0 -0
  72. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/indexer.py +0 -0
  73. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/injector.py +0 -0
  74. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/installer.py +0 -0
  75. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/loc.py +0 -0
  76. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/logconfig.py +0 -0
  77. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/metrics.py +0 -0
  78. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/outcome.py +0 -0
  79. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/paths.py +0 -0
  80. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/policy.py +0 -0
  81. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/progress.py +0 -0
  82. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/provider.py +0 -0
  83. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/providers/__init__.py +0 -0
  84. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/providers/none.py +0 -0
  85. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/py.typed +0 -0
  86. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/query_ops.py +0 -0
  87. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/redact.py +0 -0
  88. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/reindexer.py +0 -0
  89. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/reset.py +0 -0
  90. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/searcher.py +0 -0
  91. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/semantic_db.py +0 -0
  92. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/source_kind.py +0 -0
  93. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/term.py +0 -0
  94. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/viewer/__init__.py +0 -0
  95. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/viewer/graph_template.html +0 -0
  96. {codecortex-0.22.0 → codecortex-0.23.0}/src/codeintel/wire_text.py +0 -0
  97. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_agent_prompt.py +0 -0
  98. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_backend_reaper.py +0 -0
  99. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_c4_model.py +0 -0
  100. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_cache.py +0 -0
  101. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_chunking.py +0 -0
  102. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_cli_help.py +0 -0
  103. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_cli_install_dry_run.py +0 -0
  104. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_cli_install_offer_injection.py +0 -0
  105. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_config.py +0 -0
  106. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_corpus.py +0 -0
  107. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_docs_ci_claims.py +0 -0
  108. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_docs_deadcode_withdrawal.py +0 -0
  109. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_doctor.py +0 -0
  110. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_e2e.py +0 -0
  111. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_edge_confidence.py +0 -0
  112. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_engine_adoption.py +0 -0
  113. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_enterprise.py +0 -0
  114. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_gateway.py +0 -0
  115. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_graph_failure_population.py +0 -0
  116. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_graph_real.py +0 -0
  117. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_graph_stdin.py +0 -0
  118. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_grapher.py +0 -0
  119. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_hardening.py +0 -0
  120. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_http_auth.py +0 -0
  121. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_http_server.py +0 -0
  122. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_index_progress.py +0 -0
  123. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_indexer_binary.py +0 -0
  124. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_installer.py +0 -0
  125. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_integration.py +0 -0
  126. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_loc_census.py +0 -0
  127. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_lsp_language_coverage.py +0 -0
  128. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_lsp_real.py +0 -0
  129. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_mcp_handshake.py +0 -0
  130. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_mcp_server.py +0 -0
  131. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_model_dimension.py +0 -0
  132. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_never_raise.py +0 -0
  133. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_null_reason_and_ripple.py +0 -0
  134. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_rbac.py +0 -0
  135. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_redaction_boundary.py +0 -0
  136. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_reindexer.py +0 -0
  137. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_rerank.py +0 -0
  138. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_reset.py +0 -0
  139. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_source_kind.py +0 -0
  140. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_staleness.py +0 -0
  141. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_symbol_context.py +0 -0
  142. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_term.py +0 -0
  143. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_treesitter.py +0 -0
  144. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_verify_call.py +0 -0
  145. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_version_skew.py +0 -0
  146. {codecortex-0.22.0 → codecortex-0.23.0}/tests/test_wire_text.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.22.0
3
+ Version: 0.23.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
@@ -33,6 +33,8 @@ Requires-Dist: pytest-cov>=5; extra == "dev"
33
33
  Requires-Dist: numpy>=1.24; extra == "dev"
34
34
  Requires-Dist: ruff>=0.16; extra == "dev"
35
35
  Requires-Dist: mypy>=1.11; extra == "dev"
36
+ Provides-Extra: bench
37
+ Requires-Dist: anthropic>=0.40; extra == "bench"
36
38
  Dynamic: license-file
37
39
 
38
40
  # codeintel
@@ -61,9 +63,9 @@ Prefer plain text? `codeintel map` writes a **readable architecture overview** t
61
63
 
62
64
  **What `CODE_INTEL.md` is for.** It's a *static, committable* snapshot of a codebase's shape — meant to be read (by a person or an agent) **first**, instead of reconstructing structure by grepping. It covers the cases the live `code.query` tool doesn't:
63
65
 
64
- - **Agents & hosts that don't speak MCP.** Not every agent supports MCP, and the server isn't always running. `codeintel map` writes a plain file any agent can read; `codeintel map --inject` also drops a short, tool-naming pointer block into `AGENTS.md` (the cross-tool surface read by Codex, Cursor, Zed, and others — created with your consent if it doesn't exist yet) plus a one-line `@AGENTS.md` import into `CLAUDE.md`, and writes the fuller [`USING_CODEINTEL.md`](USING_CODEINTEL.md) guide the block points to — so an agent knows to reach for `code.query` before it reaches for grep, not just that a `CODE_INTEL.md` exists.
66
+ - **Agents & hosts that don't speak MCP.** Not every agent supports MCP, and the server isn't always running. `codeintel map` writes a plain file any agent can read; `codeintel map --inject` also drops a short, tool-naming pointer block into `AGENTS.md` (the cross-tool surface read by Codex, Cursor, Zed, and others — created with your consent if it doesn't exist yet) plus a one-line `@AGENTS.md` import into `CLAUDE.md`, and writes the fuller `USING_CODEINTEL.md` guide the block points to — into **your** repo, which is why there is no such file to click here — so an agent knows to reach for `code.query` before it reaches for grep, not just that a `CODE_INTEL.md` exists.
65
67
  - **A committed, diffable overview.** It lives *in the repo* — reviewable in a PR, browsable on GitHub, available offline. Re-run `codeintel map` after `codeintel index` to refresh it.
66
- - **The load-bearing code at a glance.** Ranking symbols by caller count surfaces what most of the codebase depends on (the risky-to-change core) plus the entry points — the first things a newcomer, or an agent, should understand before touching anything.
68
+ - **The load-bearing code at a glance.** Ranking symbols by caller count surfaces what most of the codebase depends on (the risky-to-change core) plus the entry points — the first things a newcomer, or an agent, should understand before touching anything. The ranking counts `CALLS` edges into **callable** nodes only (functions, methods, classes, interfaces, routes), and skips test, generated and archived files. Both constraints are load-bearing rather than tidy-up: without them a 12,638-node TypeScript repo ranked `logger` (a *folder* node) at 716 and `onClick` at 107, and a Python repo ranked YAML and JSON keys — `flow` from a `.flow.yaml`, `feature` from a `.schema.json` — as its most depended-on symbols. This repo's own map is Python-heavy and barely showed it, which is exactly why dogfooding did not catch it.
67
69
 
68
70
  See **[docs/map-file.md](docs/map-file.md)** for the format and the `--inject` flow.
69
71
 
@@ -91,7 +93,7 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
91
93
  | Who calls this? | `callers` | graph | caller symbols + files |
92
94
  | What does this call? | `callees` | graph | callee symbols + files |
93
95
  | Blast radius of a change | `impact` | graph | callers **and** callees together |
94
- | Trace a call chain up/downstream | `chain` | graph | ordered, risk-labeled hops |
96
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops, each labelled with how it was resolved |
95
97
  | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
96
98
  | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
97
99
  | Everything about one symbol | `context` | graph + lsp | both views merged |
@@ -153,6 +155,57 @@ no benefit to set against that error rate.
153
155
  **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
154
156
  `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
155
157
 
158
+ ## An architecture model, as source
159
+
160
+ `codeintel c4` turns the graph index into a [LikeC4](https://likec4.dev) model — architecture-as-code
161
+ you can commit, diff and hand-edit, rather than a rendered picture you have to regenerate to read.
162
+ Abridged output, from `--scope src/codeintel/providers`:
163
+
164
+ ```
165
+ // Generated by codeintel from the graph index — do not edit by hand.
166
+ // edges: union of IMPORTS (static module-level imports) and CALLS|USAGE (call/usage references)
167
+ // coverage is NOT complete: dispatch through a name/lookup table can still be invisible to both
168
+ // hotspot ranking uses IMPORTS-only fan-in, never the CALLS|USAGE union
169
+
170
+ model {
171
+ src = area 'src' {
172
+ codeintel = area 'codeintel' {
173
+ providers = area 'providers' {
174
+ lsp = module 'lsp' {
175
+ technology 'Python'
176
+ metadata { path 'src/codeintel/providers/lsp.py' churn '12' fan_out '2' }
177
+ }
178
+ graph = module 'graph' { /* … */ }
179
+ }
180
+ }
181
+ }
182
+
183
+ src.codeintel.providers.lsp -[calls_usage]-> src.codeintel.providers.graph { metadata { n '2' } }
184
+ }
185
+ ```
186
+
187
+ Directories become `area`, files become `module`, and every element carries its real path plus churn
188
+ and fan-in as `metadata` — so the model is queryable, not only drawable. Each file opens with what it
189
+ cannot know: which edge sources it used, that coverage is incomplete, and how many references were
190
+ folded or dropped. `npx likec4 start codeintel-c4` renders it; nothing else here needs Node.
191
+
192
+ **Two edge sources, and the choice matters more than it sounds.** The default unions static `IMPORTS`
193
+ with `CALLS|USAGE`, which recovers lazy and function-body imports. On this repo at
194
+ `--scope src --depth 3` that is 179 relations — **134 of them `CALLS|USAGE`-only**, which renders as a
195
+ hairball. `--edges imports` leaves the same 45 elements with 45 edges: legible, and roughly layered,
196
+ because every remaining edge descends.
197
+
198
+ **It also answers two questions without drawing anything.** `--layers` infers architectural bands
199
+ from the import graph — 7 bands over 35 of those 45 elements, the other 10 having no module-level
200
+ import edge either way. `--check` gates CI on a declared
201
+ `[layers]` block in `.codeintel.toml`, exiting **2** on architectural drift — deliberately distinct
202
+ from exit 1 for a broken run, so a CI step can tell "your architecture drifted" from "codeintel is
203
+ broken". A config generated by `--suggest-config` is a *provably* green baseline on the commit that
204
+ produced it, so adoption is paste-confirm-tighten rather than a wall of false positives.
205
+
206
+ Full reference: **[docs/c4.md](docs/c4.md)**. Why LikeC4 and not something else, measured:
207
+ [docs/eval-2026-08-26-likec4.md](docs/eval-2026-08-26-likec4.md).
208
+
156
209
  ## What makes it good
157
210
 
158
211
  - **Local-first and private.** One process on your machine — no cloud service, no API keys, no telemetry, no per-query network. Safe to point at a private repo, even with `--engine all`. (The one-time exception: `fastembed` downloads its embedding model once, then runs fully offline.)
@@ -281,7 +334,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
281
334
  ```text
282
335
  v claude: registered at /Users/you/.claude.json
283
336
 
284
- v verified: codeintel 0.22.0 — 4 tools (code.query, code.status, code.doctor, code.map)
337
+ v verified: codeintel 0.23.0 — 4 tools (code.query, code.status, code.doctor, code.map)
285
338
  ```
286
339
 
287
340
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -343,7 +396,7 @@ The optional keys are the ones worth reading when an answer surprises you:
343
396
 
344
397
  | Engine | Key ops | Install prereq |
345
398
  |---|---|---|
346
- | `graph` | `impact`, `callers`, `callees`, `chain`, `pattern`, `overview`, `context` | `codebase-memory-mcp` **0.9.x** on PATH (`pip install 'codebase-memory-mcp==0.9.*'`) — 0.10.x changed its response format and returns nothing for every op but resolution; see [docs/graph.md](docs/graph.md) |
399
+ | `graph` | `impact`, `callers`, `callees`, `chain`, `pattern`, `overview`, `context` | `codebase-memory-mcp` **0.9.x or 0.10.x** on PATH — both wire dialects are read; `0.10.x` is the more accurate backend and is recommended (`pip install 'codebase-memory-mcp==0.10.*'`). See [docs/graph.md](docs/graph.md) |
347
400
  | `lsp` | `symbol`, `overview`, `context` | `uvx` on PATH — serena is fetched from GitHub on first use; see [docs/lsp.md](docs/lsp.md) |
348
401
  | `semantic` | `search`, `context` | `fastembed` + `sqlite-vec` (installed with the package) — see [docs/semantic.md](docs/semantic.md) |
349
402
 
@@ -396,7 +449,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
396
449
  |---|---|
397
450
  | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
398
451
  | `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 |
399
- | `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 |
452
+ | `codeintel setup [project_root] [--all] [--index] [--warm] [--languages] [--install-uv] [--install-deps] [--json]` | Prepare backends + index this repo (`--all` = one command: do everything automatable, idempotent); ends with a health report + **Next:** steps |
400
453
  | `codeintel prompt [project_root] [--agent auto\|claude\|codex\|gemini\|zed] [--fresh] [--deep]` | Print a paste-to-your-agent setup prompt; probes health and emits only the outstanding steps (or "just restart me" when already healthy). `--fresh` = the full sequence from `pip install`, to send a friend |
401
454
  | `codeintel index [project_root] [--quiet]` | Index a repo (semantic embeddings + best-effort graph & map refresh), with a live progress display; `--quiet` prints only the result line |
402
455
  | `codeintel serve` | Start the MCP server (stdio transport) |
@@ -406,7 +459,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
406
459
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
407
460
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
408
461
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
409
- | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--json]` | Write a LikeC4 architecture model (`.c4`) of the repo's file/directory structure and its import graph committable, diffable, hand-editable source rather than a rendered picture. Indexes the repo first if it has no graph index. See [docs/c4.md](docs/c4.md) |
462
+ | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--edges union\|imports] [--json] [--layers] [--suggest-config] [--check] [--layers-from auto\|inferred\|declared]` | Write a LikeC4 architecture model (`.c4`) of the repo's files and import graph. Indexes the repo first if it has no graph index. `--edges imports` drops the lower-confidence edges for a legible diagram; `--layers` reports inferred architectural bands; `--check` gates CI on a declared `[layers]` config, exit 2 on drift. See [An architecture model, as source](#an-architecture-model-as-source) and [docs/c4.md](docs/c4.md) |
410
463
  | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index — **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
411
464
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
412
465
 
@@ -646,7 +699,7 @@ cd codeintel
646
699
  pip install -e .[dev]
647
700
 
648
701
  pytest tests/ -q # ~740 tests, ~30s; fails under 83% coverage
649
- ruff check src tests # lint
702
+ ruff check src tests bench # lint
650
703
  mypy # types (src/ only)
651
704
  ```
652
705
 
@@ -24,9 +24,9 @@ Prefer plain text? `codeintel map` writes a **readable architecture overview** t
24
24
 
25
25
  **What `CODE_INTEL.md` is for.** It's a *static, committable* snapshot of a codebase's shape — meant to be read (by a person or an agent) **first**, instead of reconstructing structure by grepping. It covers the cases the live `code.query` tool doesn't:
26
26
 
27
- - **Agents & hosts that don't speak MCP.** Not every agent supports MCP, and the server isn't always running. `codeintel map` writes a plain file any agent can read; `codeintel map --inject` also drops a short, tool-naming pointer block into `AGENTS.md` (the cross-tool surface read by Codex, Cursor, Zed, and others — created with your consent if it doesn't exist yet) plus a one-line `@AGENTS.md` import into `CLAUDE.md`, and writes the fuller [`USING_CODEINTEL.md`](USING_CODEINTEL.md) guide the block points to — so an agent knows to reach for `code.query` before it reaches for grep, not just that a `CODE_INTEL.md` exists.
27
+ - **Agents & hosts that don't speak MCP.** Not every agent supports MCP, and the server isn't always running. `codeintel map` writes a plain file any agent can read; `codeintel map --inject` also drops a short, tool-naming pointer block into `AGENTS.md` (the cross-tool surface read by Codex, Cursor, Zed, and others — created with your consent if it doesn't exist yet) plus a one-line `@AGENTS.md` import into `CLAUDE.md`, and writes the fuller `USING_CODEINTEL.md` guide the block points to — into **your** repo, which is why there is no such file to click here — so an agent knows to reach for `code.query` before it reaches for grep, not just that a `CODE_INTEL.md` exists.
28
28
  - **A committed, diffable overview.** It lives *in the repo* — reviewable in a PR, browsable on GitHub, available offline. Re-run `codeintel map` after `codeintel index` to refresh it.
29
- - **The load-bearing code at a glance.** Ranking symbols by caller count surfaces what most of the codebase depends on (the risky-to-change core) plus the entry points — the first things a newcomer, or an agent, should understand before touching anything.
29
+ - **The load-bearing code at a glance.** Ranking symbols by caller count surfaces what most of the codebase depends on (the risky-to-change core) plus the entry points — the first things a newcomer, or an agent, should understand before touching anything. The ranking counts `CALLS` edges into **callable** nodes only (functions, methods, classes, interfaces, routes), and skips test, generated and archived files. Both constraints are load-bearing rather than tidy-up: without them a 12,638-node TypeScript repo ranked `logger` (a *folder* node) at 716 and `onClick` at 107, and a Python repo ranked YAML and JSON keys — `flow` from a `.flow.yaml`, `feature` from a `.schema.json` — as its most depended-on symbols. This repo's own map is Python-heavy and barely showed it, which is exactly why dogfooding did not catch it.
30
30
 
31
31
  See **[docs/map-file.md](docs/map-file.md)** for the format and the `--inject` flow.
32
32
 
@@ -54,7 +54,7 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
54
54
  | Who calls this? | `callers` | graph | caller symbols + files |
55
55
  | What does this call? | `callees` | graph | callee symbols + files |
56
56
  | Blast radius of a change | `impact` | graph | callers **and** callees together |
57
- | Trace a call chain up/downstream | `chain` | graph | ordered, risk-labeled hops |
57
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops, each labelled with how it was resolved |
58
58
  | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
59
59
  | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
60
60
  | Everything about one symbol | `context` | graph + lsp | both views merged |
@@ -116,6 +116,57 @@ no benefit to set against that error rate.
116
116
  **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
117
117
  `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
118
118
 
119
+ ## An architecture model, as source
120
+
121
+ `codeintel c4` turns the graph index into a [LikeC4](https://likec4.dev) model — architecture-as-code
122
+ you can commit, diff and hand-edit, rather than a rendered picture you have to regenerate to read.
123
+ Abridged output, from `--scope src/codeintel/providers`:
124
+
125
+ ```
126
+ // Generated by codeintel from the graph index — do not edit by hand.
127
+ // edges: union of IMPORTS (static module-level imports) and CALLS|USAGE (call/usage references)
128
+ // coverage is NOT complete: dispatch through a name/lookup table can still be invisible to both
129
+ // hotspot ranking uses IMPORTS-only fan-in, never the CALLS|USAGE union
130
+
131
+ model {
132
+ src = area 'src' {
133
+ codeintel = area 'codeintel' {
134
+ providers = area 'providers' {
135
+ lsp = module 'lsp' {
136
+ technology 'Python'
137
+ metadata { path 'src/codeintel/providers/lsp.py' churn '12' fan_out '2' }
138
+ }
139
+ graph = module 'graph' { /* … */ }
140
+ }
141
+ }
142
+ }
143
+
144
+ src.codeintel.providers.lsp -[calls_usage]-> src.codeintel.providers.graph { metadata { n '2' } }
145
+ }
146
+ ```
147
+
148
+ Directories become `area`, files become `module`, and every element carries its real path plus churn
149
+ and fan-in as `metadata` — so the model is queryable, not only drawable. Each file opens with what it
150
+ cannot know: which edge sources it used, that coverage is incomplete, and how many references were
151
+ folded or dropped. `npx likec4 start codeintel-c4` renders it; nothing else here needs Node.
152
+
153
+ **Two edge sources, and the choice matters more than it sounds.** The default unions static `IMPORTS`
154
+ with `CALLS|USAGE`, which recovers lazy and function-body imports. On this repo at
155
+ `--scope src --depth 3` that is 179 relations — **134 of them `CALLS|USAGE`-only**, which renders as a
156
+ hairball. `--edges imports` leaves the same 45 elements with 45 edges: legible, and roughly layered,
157
+ because every remaining edge descends.
158
+
159
+ **It also answers two questions without drawing anything.** `--layers` infers architectural bands
160
+ from the import graph — 7 bands over 35 of those 45 elements, the other 10 having no module-level
161
+ import edge either way. `--check` gates CI on a declared
162
+ `[layers]` block in `.codeintel.toml`, exiting **2** on architectural drift — deliberately distinct
163
+ from exit 1 for a broken run, so a CI step can tell "your architecture drifted" from "codeintel is
164
+ broken". A config generated by `--suggest-config` is a *provably* green baseline on the commit that
165
+ produced it, so adoption is paste-confirm-tighten rather than a wall of false positives.
166
+
167
+ Full reference: **[docs/c4.md](docs/c4.md)**. Why LikeC4 and not something else, measured:
168
+ [docs/eval-2026-08-26-likec4.md](docs/eval-2026-08-26-likec4.md).
169
+
119
170
  ## What makes it good
120
171
 
121
172
  - **Local-first and private.** One process on your machine — no cloud service, no API keys, no telemetry, no per-query network. Safe to point at a private repo, even with `--engine all`. (The one-time exception: `fastembed` downloads its embedding model once, then runs fully offline.)
@@ -244,7 +295,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
244
295
  ```text
245
296
  v claude: registered at /Users/you/.claude.json
246
297
 
247
- v verified: codeintel 0.22.0 — 4 tools (code.query, code.status, code.doctor, code.map)
298
+ v verified: codeintel 0.23.0 — 4 tools (code.query, code.status, code.doctor, code.map)
248
299
  ```
249
300
 
250
301
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -306,7 +357,7 @@ The optional keys are the ones worth reading when an answer surprises you:
306
357
 
307
358
  | Engine | Key ops | Install prereq |
308
359
  |---|---|---|
309
- | `graph` | `impact`, `callers`, `callees`, `chain`, `pattern`, `overview`, `context` | `codebase-memory-mcp` **0.9.x** on PATH (`pip install 'codebase-memory-mcp==0.9.*'`) — 0.10.x changed its response format and returns nothing for every op but resolution; see [docs/graph.md](docs/graph.md) |
360
+ | `graph` | `impact`, `callers`, `callees`, `chain`, `pattern`, `overview`, `context` | `codebase-memory-mcp` **0.9.x or 0.10.x** on PATH — both wire dialects are read; `0.10.x` is the more accurate backend and is recommended (`pip install 'codebase-memory-mcp==0.10.*'`). See [docs/graph.md](docs/graph.md) |
310
361
  | `lsp` | `symbol`, `overview`, `context` | `uvx` on PATH — serena is fetched from GitHub on first use; see [docs/lsp.md](docs/lsp.md) |
311
362
  | `semantic` | `search`, `context` | `fastembed` + `sqlite-vec` (installed with the package) — see [docs/semantic.md](docs/semantic.md) |
312
363
 
@@ -359,7 +410,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
359
410
  |---|---|
360
411
  | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
361
412
  | `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 |
362
- | `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 |
413
+ | `codeintel setup [project_root] [--all] [--index] [--warm] [--languages] [--install-uv] [--install-deps] [--json]` | Prepare backends + index this repo (`--all` = one command: do everything automatable, idempotent); ends with a health report + **Next:** steps |
363
414
  | `codeintel prompt [project_root] [--agent auto\|claude\|codex\|gemini\|zed] [--fresh] [--deep]` | Print a paste-to-your-agent setup prompt; probes health and emits only the outstanding steps (or "just restart me" when already healthy). `--fresh` = the full sequence from `pip install`, to send a friend |
364
415
  | `codeintel index [project_root] [--quiet]` | Index a repo (semantic embeddings + best-effort graph & map refresh), with a live progress display; `--quiet` prints only the result line |
365
416
  | `codeintel serve` | Start the MCP server (stdio transport) |
@@ -369,7 +420,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
369
420
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
370
421
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
371
422
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
372
- | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--json]` | Write a LikeC4 architecture model (`.c4`) of the repo's file/directory structure and its import graph committable, diffable, hand-editable source rather than a rendered picture. Indexes the repo first if it has no graph index. See [docs/c4.md](docs/c4.md) |
423
+ | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--edges union\|imports] [--json] [--layers] [--suggest-config] [--check] [--layers-from auto\|inferred\|declared]` | Write a LikeC4 architecture model (`.c4`) of the repo's files and import graph. Indexes the repo first if it has no graph index. `--edges imports` drops the lower-confidence edges for a legible diagram; `--layers` reports inferred architectural bands; `--check` gates CI on a declared `[layers]` config, exit 2 on drift. See [An architecture model, as source](#an-architecture-model-as-source) and [docs/c4.md](docs/c4.md) |
373
424
  | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index — **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
374
425
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
375
426
 
@@ -609,7 +660,7 @@ cd codeintel
609
660
  pip install -e .[dev]
610
661
 
611
662
  pytest tests/ -q # ~740 tests, ~30s; fails under 83% coverage
612
- ruff check src tests # lint
663
+ ruff check src tests bench # lint
613
664
  mypy # types (src/ only)
614
665
  ```
615
666
 
@@ -54,6 +54,14 @@ codeintel = "codeintel.__main__:main"
54
54
  [project.optional-dependencies]
55
55
  dev = ["pytest>=8", "pytest-cov>=5", "numpy>=1.24", "ruff>=0.16", "mypy>=1.11"]
56
56
 
57
+ # `bench/agent_bench.py` only. Deliberately NOT in `dev`, and emphatically not a runtime dependency:
58
+ # codeintel's own code makes zero outbound HTTP calls and that claim is load-bearing in the README.
59
+ # The agent-cost benchmark is the one artifact here that talks to a model API, it is a manual paid
60
+ # run, and nothing in `src/` or the test suite imports this. Its own extra keeps `dev` (and CI) from
61
+ # installing an API client for code they never execute.
62
+ # pip install -e '.[bench]'
63
+ bench = ["anthropic>=0.40"]
64
+
57
65
  # Single source of truth for the version: read from codeintel.__version__ (AST-parsed, no import).
58
66
  [tool.setuptools.dynamic]
59
67
  version = { attr = "codeintel.__version__" }
@@ -100,6 +108,17 @@ ignore = [
100
108
  # manager is a bad trade in code whose whole contract is degrading quietly.
101
109
 
102
110
  [tool.ruff.lint.per-file-ignores]
111
+ # The oracle's fixture corpus is a micro-repository whose SOURCE is the test input. Every finding
112
+ # ruff has here is the property under test: an import that is never used is the `IMPORT` case, a
113
+ # star import is the abstention case, an undefined name is the injected-global case. Fixing them
114
+ # would delete the fixtures. They are ignored as a group rather than with inline `noqa`s, so the
115
+ # corpus keeps looking like the ordinary code it is standing in for.
116
+ "bench/fixtures/corpus/**" = [
117
+ "F401", # imported and never used -> `imports_only.py`, and both re-export links
118
+ "F403", # `import *` -> the star-import abstention
119
+ "F405", # name may be undefined from a star import -> the same file
120
+ "F821", # undefined name -> `injected.py`, the `describe` shape
121
+ ]
103
122
  # Tests stand up fake servers on loopback, assert on hardcoded fixture tokens, and shell out to
104
123
  # the CLI under test. Flagging that as a security finding is noise.
105
124
  "tests/*" = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.22.0
3
+ Version: 0.23.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
@@ -33,6 +33,8 @@ Requires-Dist: pytest-cov>=5; extra == "dev"
33
33
  Requires-Dist: numpy>=1.24; extra == "dev"
34
34
  Requires-Dist: ruff>=0.16; extra == "dev"
35
35
  Requires-Dist: mypy>=1.11; extra == "dev"
36
+ Provides-Extra: bench
37
+ Requires-Dist: anthropic>=0.40; extra == "bench"
36
38
  Dynamic: license-file
37
39
 
38
40
  # codeintel
@@ -61,9 +63,9 @@ Prefer plain text? `codeintel map` writes a **readable architecture overview** t
61
63
 
62
64
  **What `CODE_INTEL.md` is for.** It's a *static, committable* snapshot of a codebase's shape — meant to be read (by a person or an agent) **first**, instead of reconstructing structure by grepping. It covers the cases the live `code.query` tool doesn't:
63
65
 
64
- - **Agents & hosts that don't speak MCP.** Not every agent supports MCP, and the server isn't always running. `codeintel map` writes a plain file any agent can read; `codeintel map --inject` also drops a short, tool-naming pointer block into `AGENTS.md` (the cross-tool surface read by Codex, Cursor, Zed, and others — created with your consent if it doesn't exist yet) plus a one-line `@AGENTS.md` import into `CLAUDE.md`, and writes the fuller [`USING_CODEINTEL.md`](USING_CODEINTEL.md) guide the block points to — so an agent knows to reach for `code.query` before it reaches for grep, not just that a `CODE_INTEL.md` exists.
66
+ - **Agents & hosts that don't speak MCP.** Not every agent supports MCP, and the server isn't always running. `codeintel map` writes a plain file any agent can read; `codeintel map --inject` also drops a short, tool-naming pointer block into `AGENTS.md` (the cross-tool surface read by Codex, Cursor, Zed, and others — created with your consent if it doesn't exist yet) plus a one-line `@AGENTS.md` import into `CLAUDE.md`, and writes the fuller `USING_CODEINTEL.md` guide the block points to — into **your** repo, which is why there is no such file to click here — so an agent knows to reach for `code.query` before it reaches for grep, not just that a `CODE_INTEL.md` exists.
65
67
  - **A committed, diffable overview.** It lives *in the repo* — reviewable in a PR, browsable on GitHub, available offline. Re-run `codeintel map` after `codeintel index` to refresh it.
66
- - **The load-bearing code at a glance.** Ranking symbols by caller count surfaces what most of the codebase depends on (the risky-to-change core) plus the entry points — the first things a newcomer, or an agent, should understand before touching anything.
68
+ - **The load-bearing code at a glance.** Ranking symbols by caller count surfaces what most of the codebase depends on (the risky-to-change core) plus the entry points — the first things a newcomer, or an agent, should understand before touching anything. The ranking counts `CALLS` edges into **callable** nodes only (functions, methods, classes, interfaces, routes), and skips test, generated and archived files. Both constraints are load-bearing rather than tidy-up: without them a 12,638-node TypeScript repo ranked `logger` (a *folder* node) at 716 and `onClick` at 107, and a Python repo ranked YAML and JSON keys — `flow` from a `.flow.yaml`, `feature` from a `.schema.json` — as its most depended-on symbols. This repo's own map is Python-heavy and barely showed it, which is exactly why dogfooding did not catch it.
67
69
 
68
70
  See **[docs/map-file.md](docs/map-file.md)** for the format and the `--inject` flow.
69
71
 
@@ -91,7 +93,7 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
91
93
  | Who calls this? | `callers` | graph | caller symbols + files |
92
94
  | What does this call? | `callees` | graph | callee symbols + files |
93
95
  | Blast radius of a change | `impact` | graph | callers **and** callees together |
94
- | Trace a call chain up/downstream | `chain` | graph | ordered, risk-labeled hops |
96
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops, each labelled with how it was resolved |
95
97
  | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
96
98
  | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
97
99
  | Everything about one symbol | `context` | graph + lsp | both views merged |
@@ -153,6 +155,57 @@ no benefit to set against that error rate.
153
155
  **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
154
156
  `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
155
157
 
158
+ ## An architecture model, as source
159
+
160
+ `codeintel c4` turns the graph index into a [LikeC4](https://likec4.dev) model — architecture-as-code
161
+ you can commit, diff and hand-edit, rather than a rendered picture you have to regenerate to read.
162
+ Abridged output, from `--scope src/codeintel/providers`:
163
+
164
+ ```
165
+ // Generated by codeintel from the graph index — do not edit by hand.
166
+ // edges: union of IMPORTS (static module-level imports) and CALLS|USAGE (call/usage references)
167
+ // coverage is NOT complete: dispatch through a name/lookup table can still be invisible to both
168
+ // hotspot ranking uses IMPORTS-only fan-in, never the CALLS|USAGE union
169
+
170
+ model {
171
+ src = area 'src' {
172
+ codeintel = area 'codeintel' {
173
+ providers = area 'providers' {
174
+ lsp = module 'lsp' {
175
+ technology 'Python'
176
+ metadata { path 'src/codeintel/providers/lsp.py' churn '12' fan_out '2' }
177
+ }
178
+ graph = module 'graph' { /* … */ }
179
+ }
180
+ }
181
+ }
182
+
183
+ src.codeintel.providers.lsp -[calls_usage]-> src.codeintel.providers.graph { metadata { n '2' } }
184
+ }
185
+ ```
186
+
187
+ Directories become `area`, files become `module`, and every element carries its real path plus churn
188
+ and fan-in as `metadata` — so the model is queryable, not only drawable. Each file opens with what it
189
+ cannot know: which edge sources it used, that coverage is incomplete, and how many references were
190
+ folded or dropped. `npx likec4 start codeintel-c4` renders it; nothing else here needs Node.
191
+
192
+ **Two edge sources, and the choice matters more than it sounds.** The default unions static `IMPORTS`
193
+ with `CALLS|USAGE`, which recovers lazy and function-body imports. On this repo at
194
+ `--scope src --depth 3` that is 179 relations — **134 of them `CALLS|USAGE`-only**, which renders as a
195
+ hairball. `--edges imports` leaves the same 45 elements with 45 edges: legible, and roughly layered,
196
+ because every remaining edge descends.
197
+
198
+ **It also answers two questions without drawing anything.** `--layers` infers architectural bands
199
+ from the import graph — 7 bands over 35 of those 45 elements, the other 10 having no module-level
200
+ import edge either way. `--check` gates CI on a declared
201
+ `[layers]` block in `.codeintel.toml`, exiting **2** on architectural drift — deliberately distinct
202
+ from exit 1 for a broken run, so a CI step can tell "your architecture drifted" from "codeintel is
203
+ broken". A config generated by `--suggest-config` is a *provably* green baseline on the commit that
204
+ produced it, so adoption is paste-confirm-tighten rather than a wall of false positives.
205
+
206
+ Full reference: **[docs/c4.md](docs/c4.md)**. Why LikeC4 and not something else, measured:
207
+ [docs/eval-2026-08-26-likec4.md](docs/eval-2026-08-26-likec4.md).
208
+
156
209
  ## What makes it good
157
210
 
158
211
  - **Local-first and private.** One process on your machine — no cloud service, no API keys, no telemetry, no per-query network. Safe to point at a private repo, even with `--engine all`. (The one-time exception: `fastembed` downloads its embedding model once, then runs fully offline.)
@@ -281,7 +334,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
281
334
  ```text
282
335
  v claude: registered at /Users/you/.claude.json
283
336
 
284
- v verified: codeintel 0.22.0 — 4 tools (code.query, code.status, code.doctor, code.map)
337
+ v verified: codeintel 0.23.0 — 4 tools (code.query, code.status, code.doctor, code.map)
285
338
  ```
286
339
 
287
340
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -343,7 +396,7 @@ The optional keys are the ones worth reading when an answer surprises you:
343
396
 
344
397
  | Engine | Key ops | Install prereq |
345
398
  |---|---|---|
346
- | `graph` | `impact`, `callers`, `callees`, `chain`, `pattern`, `overview`, `context` | `codebase-memory-mcp` **0.9.x** on PATH (`pip install 'codebase-memory-mcp==0.9.*'`) — 0.10.x changed its response format and returns nothing for every op but resolution; see [docs/graph.md](docs/graph.md) |
399
+ | `graph` | `impact`, `callers`, `callees`, `chain`, `pattern`, `overview`, `context` | `codebase-memory-mcp` **0.9.x or 0.10.x** on PATH — both wire dialects are read; `0.10.x` is the more accurate backend and is recommended (`pip install 'codebase-memory-mcp==0.10.*'`). See [docs/graph.md](docs/graph.md) |
347
400
  | `lsp` | `symbol`, `overview`, `context` | `uvx` on PATH — serena is fetched from GitHub on first use; see [docs/lsp.md](docs/lsp.md) |
348
401
  | `semantic` | `search`, `context` | `fastembed` + `sqlite-vec` (installed with the package) — see [docs/semantic.md](docs/semantic.md) |
349
402
 
@@ -396,7 +449,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
396
449
  |---|---|
397
450
  | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
398
451
  | `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 |
399
- | `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 |
452
+ | `codeintel setup [project_root] [--all] [--index] [--warm] [--languages] [--install-uv] [--install-deps] [--json]` | Prepare backends + index this repo (`--all` = one command: do everything automatable, idempotent); ends with a health report + **Next:** steps |
400
453
  | `codeintel prompt [project_root] [--agent auto\|claude\|codex\|gemini\|zed] [--fresh] [--deep]` | Print a paste-to-your-agent setup prompt; probes health and emits only the outstanding steps (or "just restart me" when already healthy). `--fresh` = the full sequence from `pip install`, to send a friend |
401
454
  | `codeintel index [project_root] [--quiet]` | Index a repo (semantic embeddings + best-effort graph & map refresh), with a live progress display; `--quiet` prints only the result line |
402
455
  | `codeintel serve` | Start the MCP server (stdio transport) |
@@ -406,7 +459,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
406
459
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
407
460
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
408
461
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
409
- | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--json]` | Write a LikeC4 architecture model (`.c4`) of the repo's file/directory structure and its import graph committable, diffable, hand-editable source rather than a rendered picture. Indexes the repo first if it has no graph index. See [docs/c4.md](docs/c4.md) |
462
+ | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--edges union\|imports] [--json] [--layers] [--suggest-config] [--check] [--layers-from auto\|inferred\|declared]` | Write a LikeC4 architecture model (`.c4`) of the repo's files and import graph. Indexes the repo first if it has no graph index. `--edges imports` drops the lower-confidence edges for a legible diagram; `--layers` reports inferred architectural bands; `--check` gates CI on a declared `[layers]` config, exit 2 on drift. See [An architecture model, as source](#an-architecture-model-as-source) and [docs/c4.md](docs/c4.md) |
410
463
  | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index — **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
411
464
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
412
465
 
@@ -646,7 +699,7 @@ cd codeintel
646
699
  pip install -e .[dev]
647
700
 
648
701
  pytest tests/ -q # ~740 tests, ~30s; fails under 83% coverage
649
- ruff check src tests # lint
702
+ ruff check src tests bench # lint
650
703
  mypy # types (src/ only)
651
704
  ```
652
705
 
@@ -12,6 +12,8 @@ src/codeintel/__main__.py
12
12
  src/codeintel/agent_prompt.py
13
13
  src/codeintel/auth.py
14
14
  src/codeintel/c4.py
15
+ src/codeintel/c4_check.py
16
+ src/codeintel/c4_layers.py
15
17
  src/codeintel/cache.py
16
18
  src/codeintel/config.py
17
19
  src/codeintel/containment.py
@@ -25,6 +27,7 @@ src/codeintel/http_server.py
25
27
  src/codeintel/indexer.py
26
28
  src/codeintel/injector.py
27
29
  src/codeintel/installer.py
30
+ src/codeintel/lang_config.py
28
31
  src/codeintel/loc.py
29
32
  src/codeintel/logconfig.py
30
33
  src/codeintel/mapper.py
@@ -72,7 +75,11 @@ src/codeintel/viewer/__init__.py
72
75
  src/codeintel/viewer/graph_template.html
73
76
  tests/test_agent_prompt.py
74
77
  tests/test_backend_reaper.py
78
+ tests/test_bench_oracle.py
79
+ tests/test_bench_oracle_ts.py
75
80
  tests/test_c4.py
81
+ tests/test_c4_check.py
82
+ tests/test_c4_layers.py
76
83
  tests/test_c4_model.py
77
84
  tests/test_c4_output.py
78
85
  tests/test_cache.py
@@ -97,6 +104,7 @@ tests/test_graph_provider.py
97
104
  tests/test_graph_real.py
98
105
  tests/test_graph_stdin.py
99
106
  tests/test_grapher.py
107
+ tests/test_hard_exit.py
100
108
  tests/test_hardening.py
101
109
  tests/test_http_auth.py
102
110
  tests/test_http_server.py
@@ -105,6 +113,7 @@ tests/test_index_progress.py
105
113
  tests/test_indexer_binary.py
106
114
  tests/test_installer.py
107
115
  tests/test_integration.py
116
+ tests/test_lang_config.py
108
117
  tests/test_loc_census.py
109
118
  tests/test_lsp_language_coverage.py
110
119
  tests/test_lsp_provider.py
@@ -3,6 +3,9 @@ sqlite-vec>=0.1
3
3
  fastembed>=0.3
4
4
  tree-sitter-language-pack>=1.0
5
5
 
6
+ [bench]
7
+ anthropic>=0.40
8
+
6
9
  [dev]
7
10
  pytest>=8
8
11
  pytest-cov>=5
@@ -0,0 +1 @@
1
+ __version__ = "0.23.0"
@@ -518,7 +518,36 @@ def build_parser() -> argparse.ArgumentParser:
518
518
  c4_parser.add_argument("--no-index", action="store_true",
519
519
  help="Fail instead of indexing an un-indexed repo (default: index it "
520
520
  "first, so one command always produces a model)")
521
+ # The union is the default because it has higher recall; a lower-recall default would silently
522
+ # hide real dependencies from anyone who never read this flag. But the union is already excluded
523
+ # from hotspot ranking and layer inference, and it is 75% of the edges on this repo — so the
524
+ # diagram, the one artifact a human reads, needs a way to opt out.
525
+ c4_parser.add_argument("--edges", choices=["union", "imports"], default="union",
526
+ help="Which edges enter the model: `union` (default — IMPORTS plus "
527
+ "CALLS|USAGE, higher recall) or `imports` (static module-level "
528
+ "imports only, far more legible on a large model)")
521
529
  c4_parser.add_argument("--json", action="store_true", help="Print the payload; write nothing")
530
+ # Phase 1 of the layer feature is inference only: `--layers` REPORTS the inferred bands, it does
531
+ # not add a view to the model. Named now rather than later so the flag does not change meaning
532
+ # between releases — a `--layers` that printed a summary in one version and emitted a diagram in
533
+ # the next would be the same flag doing two jobs.
534
+ c4_parser.add_argument("--layers", action="store_true",
535
+ help="Print the inferred architectural layers (from IMPORTS only) and "
536
+ "any import cycles. Reports; does not add a view to the model.")
537
+ c4_parser.add_argument("--suggest-config", action="store_true",
538
+ help="Print a pasteable [layers] TOML block from the inferred ranks and "
539
+ "write nothing — a green baseline on the tree that generated it")
540
+ # Exit 2, not 1. A CI step must be able to tell "codeintel is broken / the repo is not indexed"
541
+ # from "your architecture drifted": conflate them and the first person to hit a broken index
542
+ # allowlists the failure, and the gate is dead.
543
+ c4_parser.add_argument("--check", action="store_true",
544
+ help="Check the declared [layers] config in .codeintel.toml and exit 2 "
545
+ "on gating findings. Still writes the model. Exits 0 with a note if "
546
+ "no [layers] block exists — nobody gets a wall they did not ask for.")
547
+ c4_parser.add_argument("--layers-from", choices=["auto", "inferred", "declared"], default="auto",
548
+ help="Which ranking to report (default: auto — declared if a [layers] "
549
+ "block exists, else inferred). `declared` exits 1 if none exists, so "
550
+ "CI can assert that a config had better be present.")
522
551
 
523
552
  # doctor subcommand
524
553
  doctor_parser = subparsers.add_parser(
@@ -543,6 +572,12 @@ def build_parser() -> argparse.ArgumentParser:
543
572
  setup_parser.add_argument("--index", action="store_true",
544
573
  help="Index this repo now (first run downloads the ~50MB model)")
545
574
  setup_parser.add_argument("--warm", action="store_true", help="Boot serena now (first run pulls it via uvx; slow)")
575
+ # Editing the user's serena config is a real side effect, so it needs its own flag — `run_setup`'s
576
+ # rule is that each flag IS consent. Without it the step still runs and reports what it WOULD do.
577
+ setup_parser.add_argument("--languages", action="store_true",
578
+ help="Add every language this repo contains to `language_servers:` in "
579
+ ".serena/project.yml, so the LSP engine stops silently returning "
580
+ "nothing for the languages serena was not configured to serve")
546
581
  setup_parser.add_argument("--json", action="store_true", help="Emit the structured JSON report")
547
582
 
548
583
  # prompt subcommand