codecortex 0.23.0__tar.gz → 0.23.2__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 (148) hide show
  1. {codecortex-0.23.0/src/codecortex.egg-info → codecortex-0.23.2}/PKG-INFO +163 -170
  2. {codecortex-0.23.0 → codecortex-0.23.2}/README.md +161 -169
  3. {codecortex-0.23.0 → codecortex-0.23.2}/pyproject.toml +1 -0
  4. {codecortex-0.23.0 → codecortex-0.23.2/src/codecortex.egg-info}/PKG-INFO +163 -170
  5. {codecortex-0.23.0 → codecortex-0.23.2}/src/codecortex.egg-info/SOURCES.txt +3 -0
  6. codecortex-0.23.2/src/codeintel/__init__.py +1 -0
  7. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/__main__.py +25 -1
  8. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/index.py +26 -11
  9. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/status.py +38 -14
  10. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/doctor.py +16 -3
  11. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/gateway.py +16 -7
  12. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/indexer.py +51 -6
  13. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/mapper.py +26 -4
  14. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/provider.py +35 -1
  15. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/providers/graph.py +114 -7
  16. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/providers/lsp.py +117 -7
  17. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/providers/semantic.py +372 -25
  18. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/reindexer.py +16 -2
  19. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/searcher.py +56 -8
  20. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/semantic_db.py +238 -0
  21. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/server.py +12 -2
  22. codecortex-0.23.2/tests/test_background_index_failure.py +546 -0
  23. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_bench_oracle.py +8 -0
  24. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_cli_commands.py +51 -0
  25. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_cli_help.py +11 -1
  26. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_cold_process.py +4 -0
  27. codecortex-0.23.2/tests/test_could_not_ask_semantic.py +289 -0
  28. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_doctor.py +60 -0
  29. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_gateway.py +1 -0
  30. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_graph_failure_population.py +18 -0
  31. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_graph_provider.py +84 -0
  32. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_incompleteness.py +51 -0
  33. codecortex-0.23.2/tests/test_index_failure_reason.py +518 -0
  34. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_lsp_provider.py +144 -1
  35. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_lsp_real.py +3 -2
  36. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_mapper.py +54 -1
  37. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_mcp_server.py +10 -0
  38. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_model_dimension.py +19 -1
  39. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_never_raise.py +83 -0
  40. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_semantic_provider.py +30 -1
  41. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_staleness.py +16 -0
  42. codecortex-0.23.0/src/codeintel/__init__.py +0 -1
  43. {codecortex-0.23.0 → codecortex-0.23.2}/LICENSE +0 -0
  44. {codecortex-0.23.0 → codecortex-0.23.2}/setup.cfg +0 -0
  45. {codecortex-0.23.0 → codecortex-0.23.2}/src/codecortex.egg-info/dependency_links.txt +0 -0
  46. {codecortex-0.23.0 → codecortex-0.23.2}/src/codecortex.egg-info/entry_points.txt +0 -0
  47. {codecortex-0.23.0 → codecortex-0.23.2}/src/codecortex.egg-info/requires.txt +0 -0
  48. {codecortex-0.23.0 → codecortex-0.23.2}/src/codecortex.egg-info/top_level.txt +0 -0
  49. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/agent_prompt.py +0 -0
  50. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/auth.py +0 -0
  51. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/c4.py +0 -0
  52. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/c4_check.py +0 -0
  53. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/c4_layers.py +0 -0
  54. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/cache.py +0 -0
  55. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/__init__.py +0 -0
  56. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/_common.py +0 -0
  57. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/c4.py +0 -0
  58. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/doctor.py +0 -0
  59. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/gen_token.py +0 -0
  60. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/graph.py +0 -0
  61. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/install.py +0 -0
  62. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/map.py +0 -0
  63. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/prompt.py +0 -0
  64. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/query.py +0 -0
  65. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/reset.py +0 -0
  66. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/serve.py +0 -0
  67. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/serve_http.py +0 -0
  68. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/commands/setup.py +0 -0
  69. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/config.py +0 -0
  70. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/containment.py +0 -0
  71. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/graph_backend.py +0 -0
  72. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/graph_render.py +0 -0
  73. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/graph_resolution.py +0 -0
  74. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/grapher.py +0 -0
  75. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/http_server.py +0 -0
  76. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/injector.py +0 -0
  77. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/installer.py +0 -0
  78. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/lang_config.py +0 -0
  79. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/loc.py +0 -0
  80. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/logconfig.py +0 -0
  81. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/metrics.py +0 -0
  82. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/onboarding.py +0 -0
  83. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/outcome.py +0 -0
  84. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/paths.py +0 -0
  85. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/policy.py +0 -0
  86. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/progress.py +0 -0
  87. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/providers/__init__.py +0 -0
  88. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/providers/none.py +0 -0
  89. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/py.typed +0 -0
  90. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/query_ops.py +0 -0
  91. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/redact.py +0 -0
  92. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/reset.py +0 -0
  93. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/source_kind.py +0 -0
  94. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/term.py +0 -0
  95. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/verify.py +0 -0
  96. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/viewer/__init__.py +0 -0
  97. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/viewer/graph_template.html +0 -0
  98. {codecortex-0.23.0 → codecortex-0.23.2}/src/codeintel/wire_text.py +0 -0
  99. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_agent_prompt.py +0 -0
  100. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_backend_reaper.py +0 -0
  101. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_bench_oracle_ts.py +0 -0
  102. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_c4.py +0 -0
  103. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_c4_check.py +0 -0
  104. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_c4_layers.py +0 -0
  105. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_c4_model.py +0 -0
  106. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_c4_output.py +0 -0
  107. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_cache.py +0 -0
  108. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_chunking.py +0 -0
  109. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_cli_install_dry_run.py +0 -0
  110. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_cli_install_offer_injection.py +0 -0
  111. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_config.py +0 -0
  112. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_corpus.py +0 -0
  113. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_docs_ci_claims.py +0 -0
  114. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_docs_deadcode_withdrawal.py +0 -0
  115. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_e2e.py +0 -0
  116. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_edge_confidence.py +0 -0
  117. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_engine_adoption.py +0 -0
  118. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_enterprise.py +0 -0
  119. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_graph_real.py +0 -0
  120. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_graph_stdin.py +0 -0
  121. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_grapher.py +0 -0
  122. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_hard_exit.py +0 -0
  123. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_hardening.py +0 -0
  124. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_http_auth.py +0 -0
  125. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_http_server.py +0 -0
  126. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_index_progress.py +0 -0
  127. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_indexer_binary.py +0 -0
  128. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_installer.py +0 -0
  129. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_integration.py +0 -0
  130. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_lang_config.py +0 -0
  131. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_loc_census.py +0 -0
  132. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_lsp_language_coverage.py +0 -0
  133. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_mcp_handshake.py +0 -0
  134. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_null_reason_and_ripple.py +0 -0
  135. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_onboarding.py +0 -0
  136. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_rbac.py +0 -0
  137. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_redaction_boundary.py +0 -0
  138. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_reindexer.py +0 -0
  139. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_rerank.py +0 -0
  140. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_reset.py +0 -0
  141. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_source_kind.py +0 -0
  142. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_symbol_context.py +0 -0
  143. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_term.py +0 -0
  144. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_treesitter.py +0 -0
  145. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_verify.py +0 -0
  146. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_verify_call.py +0 -0
  147. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_version_skew.py +0 -0
  148. {codecortex-0.23.0 → codecortex-0.23.2}/tests/test_wire_text.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.23.0
3
+ Version: 0.23.2
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Classifier: Topic :: Software Development :: Quality Assurance
22
23
  Classifier: Typing :: Typed
@@ -57,173 +58,19 @@ Dynamic: license-file
57
58
 
58
59
  > *codeintel visualizing its own codebase.* One command — `codeintel graph <repo> --html` — turns any indexed repo into a **self-contained, interactive call graph** you can open offline or share as a file. Layouts, complexity-sized nodes, click-to-inspect metrics, and JSON/Markdown/SVG/PNG export. See **[docs/graph-viewer.md](docs/graph-viewer.md)**.
59
60
 
60
- Prefer plain text? `codeintel map` writes a **readable architecture overview** to `CODE_INTEL.md` — node/edge counts, ranked symbols by caller count, and entry points — for skimming or for MCP hosts that can't render a graph:
61
-
62
- <img src="docs/images/code-intel-map.png" width="520" alt="CODE_INTEL.md — the codeintel map: an architecture overview with node/edge counts and symbols ranked by caller count.">
63
-
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:
65
-
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.
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.
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.
69
-
70
- See **[docs/map-file.md](docs/map-file.md)** for the format and the `--inject` flow.
71
-
72
- ## Why an agent needs it
73
-
74
- Without structural tools, an agent dropped into unfamiliar code falls back on `grep` and reads whole files to reconstruct relationships by hand — burning tokens, missing call sites, and guessing at blast radius before it edits anything. codeintel answers those questions directly instead:
75
-
76
- - **"What calls this? What breaks if I change it?"** → the real call graph, which catches cross-file and module-level callers a text search silently misses.
77
- - **"Where is this symbol defined, and everywhere it's used?"** → the language server, with exact locations.
78
- - **"Where's the code that does X?"** (when you don't know the name) → semantic search over the repo.
79
- - **Always a clean answer.** Every call returns the same JSON envelope. A missing or broken backend degrades to a safe `null` *with a reason* — so the agent falls back to grep instead of crashing on an exception it can't reason its way out of.
80
-
81
- Net effect: fewer, sharper tool calls, less re-reading, and an agent that can see *structure* — callers, impact, call chains — that plain search can't.
82
-
83
- **The honest framing.** Agentic grep is still the backbone, and codeintel doesn't claim otherwise — Claude Code itself ships grep-only and that is a reasonable default for most of what an agent reads. The defensible claim is narrower: a structural index *where it pays*, degrading to grep the moment an engine is missing or not indexed, which is exactly what the safe-null contract above already does under the hood. Worth saying explicitly rather than leaving it implicit in a failure mode.
84
-
85
- ## What your agent can ask
86
-
87
- It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the default) codeintel picks the engine per operation:
88
-
89
- | Ask | `op` | Engine (auto) | Comes back as |
90
- |---|---|---|---|
91
- | Find code by meaning ("auth middleware") | `search` | semantic | ranked `path:line │ snippet` hits |
92
- | A symbol's definition **and** all references | `symbol` | lsp | definition body + reference list |
93
- | Who calls this? | `callers` | graph | caller symbols + files |
94
- | What does this call? | `callees` | graph | callee symbols + files |
95
- | Blast radius of a change | `impact` | graph | callers **and** callees together |
96
- | Trace a call chain up/downstream | `chain` | graph | ordered hops, each labelled with how it was resolved |
97
- | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
98
- | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
99
- | Everything about one symbol | `context` | graph + lsp | both views merged |
100
- | **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
101
- | Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
102
- | Unreferenced (dead) code | `deadcode` | graph | **withdrawn and now retired** — a labelled corpus measured its precision at 25%; safe-nulls with `reason: "op-withdrawn"`, and no flag brings it back — [the measurement, and what to use instead](#deadcode-is-retired) |
103
-
104
- Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
105
-
106
- `callers`, `callees` and `impact` resolve the target by its **unqualified name**. When several
107
- symbols share it, each matched symbol's rows are reported separately under its own heading and the
108
- result says how many it found — narrow to one with a qualified target (`core.Group.invoke`) or a file
109
- hint (`invoke@src/click/testing.py`); see
110
- [when several symbols share a name](docs/graph.md#when-several-symbols-share-a-name).
111
-
112
- #### `deadcode` is retired
113
-
114
- `deadcode` no longer exists. Asking for it returns a safe-null (`reason: "op-withdrawn"`) with a hint
115
- naming what to use instead, and **no flag brings it back** — the implementation has been deleted.
116
-
117
- It was withdrawn pending one condition: *"it returns when a labelled corpus measures its precision
118
- and recall — not before."* That corpus now exists, in
119
- [`tests/test_corpus.py`](tests/test_corpus.py), and the measurement is what retired it.
120
-
121
- **How it was measured.** Two pinned real Python repositories (`pallets/click`, `psf/requests`), with
122
- every function and method collected from the **AST** — 2,425 definitions, `async def` and class
123
- methods included, because a verification whose population comes from a pattern like `^\s*def ` cannot
124
- see half of them. Each is labelled live or dead with the reference behind the label recorded beside
125
- it. The oracle errs toward *live*: a decorator, a dunder, an override of an external interface, a
126
- string-dispatch mention, or public-API status is each enough to call a symbol live, so "dead" is only
127
- what survives all of them. That biases the numbers against the op, which is the correct direction for
128
- a check whose output is an instruction to delete code. Known-answer canaries are planted in both trees
129
- so recall has a denominator at all.
130
-
131
- **The numbers.**
132
-
133
- | | precision | recall |
134
- |---|---|---|
135
- | as shipped | **25%** (6 of 24) | 60% (6 of 10) |
136
- | with the two repairs this codebase already contains elsewhere | 89% (8 of 9) | 80% (8 of 10) |
137
-
138
- And the measurement that decided it — **real code only, canaries removed**: the op as shipped named
139
- **18 candidates across those two repositories, and every one of them was live.** All 18 were Makefile
140
- targets, which the graph backend indexes as `Function` nodes. Repaired, it names exactly one, and
141
- that one is `MockRequest.get_type` in requests — a method `http.cookiejar` calls by duck-typed
142
- convention, whose name appears once in the source.
143
-
144
- **Why it was not repaired further.** The verification was a name-frequency scan over the source, so it
145
- fails on exactly one condition: a symbol whose name appears once and is called by a convention
146
- outside the source. Two repositories produced three distinct instances of that condition — non-code
147
- nodes labelled `Function`, interpreter-called dunders, and stdlib duck-typed protocol methods — and
148
- the earlier TypeScript evidence adds a rollup plugin hook and object-literal properties. The set is
149
- not enumerable: no specification lists `get_type`. Every repository added revealed a new member of it.
150
-
151
- Weighed against that: in 2,425 real definitions across two maintained repositories there was **not
152
- one** dead private symbol to find. An op whose measured yield on real code is zero true positives has
153
- no benefit to set against that error rate.
154
-
155
- **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
156
- `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
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
-
209
- ## What makes it good
210
-
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.)
212
- - **It never throws.** Every call returns the same JSON envelope; a missing or broken backend degrades to `null` *with a reason*. No exceptions, no 500s, no malformed output for the agent to trip over — so you never wrap `code.query` in a `try`.
213
- - **One tool, not three.** Register a single MCP server and it auto-routes each question to graph, LSP, or semantic — instead of wiring up three backends with three response shapes and three failure modes.
214
- - **Degrades instead of breaking.** No graph backend installed? That engine returns `null` and the agent falls back to grep. The semantic engine needs nothing external, so codeintel is useful the moment it's installed and only gets sharper as you add backends.
215
- - **Fast on repeat, and the cache never lies.** A content-hash cache returns instantly for unchanged code and self-invalidates when a background reindex advances the index, so you never read a cached answer for code that moved on. The cache is bounded (LRU), so a long-running server holds steady memory. (The *cache* is always consistent with the index; how current the index itself is depends on the engine — see [Keeping answers fresh](#keeping-answers-fresh).)
216
- - **Concurrency-safe.** The HTTP transport handles requests on threads, so one slow query (an LSP session warming, a first-time index) can't block every other agent.
217
- - **Honest about its own health.** `codeintel doctor` answers three separate questions per engine — *installed?* *runnable?* *is this repo indexed?* — with the single command to fix each gap, so "installed" is never mistaken for "working". And a readiness claim is one a query can actually honor: install a missing backend mid-session and the running server picks it up on the next call, rather than reporting the engine healthy while quietly routing around it until you restart the host.
218
-
219
61
  ## Quickstart
220
62
 
221
63
  ```bash
222
64
  pip install codecortex
223
65
  ```
224
66
 
225
- This installs the `codeintel` CLI; the **semantic** engine works out of the box. (On PyPI the
226
- distribution is `codecortex` because `codeintel` was taken; the CLI and import stay `codeintel`.)
67
+ This installs the `codeintel` CLI; the **semantic** engine works out of the box with one
68
+ network step: `fastembed` downloads its ~50 MB embedding model from `huggingface.co` the first
69
+ time you index, then runs fully offline. Behind a corporate proxy, in restricted CI, or
70
+ air-gapped, that download is the one thing here that can fail — see
71
+ [Offline / air-gapped install](docs/install.md#offline--air-gapped-install) for the
72
+ `FASTEMBED_CACHE_PATH` workaround. (On PyPI the distribution is `codecortex` because `codeintel`
73
+ was taken; the CLI and import stay `codeintel`.)
227
74
 
228
75
  **One command prepares the rest and indexes your repo:**
229
76
 
@@ -237,19 +84,34 @@ remaining step. It's idempotent, so re-running is safe. The **graph** engine (`c
237
84
  is an *optional* external binary that adds who-calls / impact / hotspots / `changed`; codeintel is
238
85
  fully usable without it.
239
86
 
240
- Or from source:
87
+ **Then verify, before trusting an answer.** `doctor` answers three separate questions per engine —
88
+ *installed?* *runnable?* *is this repo indexed?* — so "installed" is never mistaken for "working",
89
+ and names the one command that fixes each gap:
241
90
 
242
91
  ```bash
243
- git clone https://github.com/hamilton-sky/codeintel.git
244
- cd codeintel
245
- pip install -e .
92
+ codeintel doctor
246
93
  ```
247
94
 
248
- Register with your AI agent(s), then query:
95
+ **Register with your agent, and ask it something grep cannot answer:**
249
96
 
250
97
  ```bash
251
98
  codeintel install # registers with the agents you actually have installed
252
- codeintel query --op search --target "authentication middleware"
99
+ codeintel query --op callers --target aFunctionYouKnow
100
+ ```
101
+
102
+ `callers` is the first query worth running because you can check it by eye: it should name the call
103
+ sites you already know about, including cross-file and module-level ones a text search misses. If
104
+ the graph engine isn't installed it safe-nulls with a reason and a hint rather than failing — that
105
+ is the contract, visible on your first call. `--op search --target "authentication middleware"`
106
+ works with no backend at all, but a semantic hit on an unfamiliar repo is harder to judge, which
107
+ makes it the weaker thing to try first.
108
+
109
+ Or from source:
110
+
111
+ ```bash
112
+ git clone https://github.com/hamilton-sky/codeintel.git
113
+ cd codeintel
114
+ pip install -e .
253
115
  ```
254
116
 
255
117
  ### Or: have your agent set it up
@@ -351,6 +213,136 @@ against the built wheel in a clean environment: it registers Codex and Claude Co
351
213
  > Full reference — what each host reads, the absolute-path rationale, and troubleshooting:
352
214
  > **[docs/install.md](docs/install.md)**.
353
215
 
216
+ ## Why an agent needs it
217
+
218
+ Without structural tools, an agent dropped into unfamiliar code falls back on `grep` and reads whole files to reconstruct relationships by hand — burning tokens, missing call sites, and guessing at blast radius before it edits anything. codeintel answers those questions directly instead:
219
+
220
+ - **"What calls this? What breaks if I change it?"** → the real call graph, which catches cross-file and module-level callers a text search silently misses.
221
+ - **"Where is this symbol defined, and everywhere it's used?"** → the language server, with exact locations.
222
+ - **"Where's the code that does X?"** (when you don't know the name) → semantic search over the repo.
223
+ - **Always a clean answer.** Every call returns the same JSON envelope. A missing or broken backend degrades to a safe `null` *with a reason* — so the agent falls back to grep instead of crashing on an exception it can't reason its way out of.
224
+
225
+ Net effect: fewer, sharper tool calls, less re-reading, and an agent that can see *structure* — callers, impact, call chains — that plain search can't.
226
+
227
+ **The honest framing.** Agentic grep is still the backbone, and codeintel doesn't claim otherwise — Claude Code itself ships grep-only and that is a reasonable default for most of what an agent reads. The defensible claim is narrower: a structural index *where it pays*, degrading to grep the moment an engine is missing or not indexed, which is exactly what the safe-null contract above already does under the hood. Worth saying explicitly rather than leaving it implicit in a failure mode.
228
+
229
+ ## What your agent can ask
230
+
231
+ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the default) codeintel picks the engine per operation:
232
+
233
+ | Ask | `op` | Engine (auto) | Comes back as |
234
+ |---|---|---|---|
235
+ | Find code by meaning ("auth middleware") | `search` | semantic | ranked `path:line │ snippet` hits |
236
+ | A symbol's definition **and** all references | `symbol` | lsp | definition body + reference list |
237
+ | Who calls this? | `callers` | graph | caller symbols + files |
238
+ | What does this call? | `callees` | graph | callee symbols + files |
239
+ | Blast radius of a change | `impact` | graph | callers **and** callees together |
240
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops, each labelled with how it was resolved |
241
+ | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
242
+ | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
243
+ | Everything about one symbol | `context` | graph + lsp | both views merged |
244
+ | **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
245
+ | Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
246
+ | Unreferenced (dead) code | `deadcode` | graph | **withdrawn and now retired** — a labelled corpus measured its precision at 25%; safe-nulls with `reason: "op-withdrawn"`, and no flag brings it back — [the measurement, and what to use instead](#deadcode-is-retired) |
247
+
248
+ Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
249
+
250
+ `callers`, `callees` and `impact` resolve the target by its **unqualified name**. When several
251
+ symbols share it, each matched symbol's rows are reported separately under its own heading and the
252
+ result says how many it found — narrow to one with a qualified target (`core.Group.invoke`) or a file
253
+ hint (`invoke@src/click/testing.py`); see
254
+ [when several symbols share a name](docs/graph.md#when-several-symbols-share-a-name).
255
+
256
+ #### `deadcode` is retired
257
+
258
+ `deadcode` no longer exists. Asking for it returns a safe-null (`reason: "op-withdrawn"`) with a
259
+ hint naming what to use instead, and **no flag brings it back** — the implementation has been
260
+ deleted. A labelled corpus of 2,425 AST-collected definitions across `pallets/click` and
261
+ `psf/requests` measured it at **25% precision**, and on real code with the canaries removed it
262
+ named 18 candidates of which **every one was live**.
263
+
264
+ **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
265
+ `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
266
+
267
+ The full measurement — the oracle's design, why it errs toward *live*, the two repairs that would
268
+ take it to 89%, and why it was not repaired further — is
269
+ [ADR 0002](docs/adr/0002-retire-deadcode.md).
270
+
271
+ ## A committed architecture overview
272
+
273
+ Prefer plain text? `codeintel map` writes a **readable architecture overview** to `CODE_INTEL.md` — node/edge counts, ranked symbols by caller count, and entry points — for skimming or for MCP hosts that can't render a graph:
274
+
275
+ <img src="docs/images/code-intel-map.png" width="520" alt="CODE_INTEL.md — the codeintel map: an architecture overview with node/edge counts and symbols ranked by caller count.">
276
+
277
+ **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:
278
+
279
+ - **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.
280
+ - **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.
281
+ - **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.
282
+
283
+ See **[docs/map-file.md](docs/map-file.md)** for the format and the `--inject` flow.
284
+
285
+ ## An architecture model, as source
286
+
287
+ `codeintel c4` turns the graph index into a [LikeC4](https://likec4.dev) model — architecture-as-code
288
+ you can commit, diff and hand-edit, rather than a rendered picture you have to regenerate to read.
289
+ Abridged output, from `--scope src/codeintel/providers`:
290
+
291
+ ```
292
+ // Generated by codeintel from the graph index — do not edit by hand.
293
+ // edges: union of IMPORTS (static module-level imports) and CALLS|USAGE (call/usage references)
294
+ // coverage is NOT complete: dispatch through a name/lookup table can still be invisible to both
295
+ // hotspot ranking uses IMPORTS-only fan-in, never the CALLS|USAGE union
296
+
297
+ model {
298
+ src = area 'src' {
299
+ codeintel = area 'codeintel' {
300
+ providers = area 'providers' {
301
+ lsp = module 'lsp' {
302
+ technology 'Python'
303
+ metadata { path 'src/codeintel/providers/lsp.py' churn '12' fan_out '2' }
304
+ }
305
+ graph = module 'graph' { /* … */ }
306
+ }
307
+ }
308
+ }
309
+
310
+ src.codeintel.providers.lsp -[calls_usage]-> src.codeintel.providers.graph { metadata { n '2' } }
311
+ }
312
+ ```
313
+
314
+ Directories become `area`, files become `module`, and every element carries its real path plus churn
315
+ and fan-in as `metadata` — so the model is queryable, not only drawable. Each file opens with what it
316
+ cannot know: which edge sources it used, that coverage is incomplete, and how many references were
317
+ folded or dropped. `npx likec4 start codeintel-c4` renders it; nothing else here needs Node.
318
+
319
+ **Two edge sources, and the choice matters more than it sounds.** The default unions static `IMPORTS`
320
+ with `CALLS|USAGE`, which recovers lazy and function-body imports. On this repo at
321
+ `--scope src --depth 3` that is 179 relations — **134 of them `CALLS|USAGE`-only**, which renders as a
322
+ hairball. `--edges imports` leaves the same 45 elements with 45 edges: legible, and roughly layered,
323
+ because every remaining edge descends.
324
+
325
+ **It also answers two questions without drawing anything.** `--layers` infers architectural bands
326
+ from the import graph — 7 bands over 35 of those 45 elements, the other 10 having no module-level
327
+ import edge either way. `--check` gates CI on a declared
328
+ `[layers]` block in `.codeintel.toml`, exiting **2** on architectural drift — deliberately distinct
329
+ from exit 1 for a broken run, so a CI step can tell "your architecture drifted" from "codeintel is
330
+ broken". A config generated by `--suggest-config` is a *provably* green baseline on the commit that
331
+ produced it, so adoption is paste-confirm-tighten rather than a wall of false positives.
332
+
333
+ Full reference: **[docs/c4.md](docs/c4.md)**. Why LikeC4 and not something else, measured:
334
+ [docs/eval-2026-08-26-likec4.md](docs/eval-2026-08-26-likec4.md).
335
+
336
+ ## What makes it good
337
+
338
+ - **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.)
339
+ - **It never throws.** Every call returns the same JSON envelope; a missing or broken backend degrades to `null` *with a reason*. No exceptions, no 500s, no malformed output for the agent to trip over — so you never wrap `code.query` in a `try`.
340
+ - **One tool, not three.** Register a single MCP server and it auto-routes each question to graph, LSP, or semantic — instead of wiring up three backends with three response shapes and three failure modes.
341
+ - **Degrades instead of breaking.** No graph backend installed? That engine returns `null` and the agent falls back to grep. The semantic engine needs nothing external, so codeintel is useful the moment it's installed and only gets sharper as you add backends.
342
+ - **Fast on repeat, and the cache never lies.** A content-hash cache returns instantly for unchanged code and self-invalidates when a background reindex advances the index, so you never read a cached answer for code that moved on. The cache is bounded (LRU), so a long-running server holds steady memory. (The *cache* is always consistent with the index; how current the index itself is depends on the engine — see [Keeping answers fresh](#keeping-answers-fresh).)
343
+ - **Concurrency-safe.** The HTTP transport handles requests on threads, so one slow query (an LSP session warming, a first-time index) can't block every other agent.
344
+ - **Honest about its own health.** `codeintel doctor` answers three separate questions per engine — *installed?* *runnable?* *is this repo indexed?* — with the single command to fix each gap, so "installed" is never mistaken for "working". And a readiness claim is one a query can actually honor: install a missing backend mid-session and the running server picks it up on the next call, rather than reporting the engine healthy while quietly routing around it until you restart the host.
345
+
354
346
  ## How it works
355
347
 
356
348
  A `Gateway` receives every query and dispatches it to one of three providers — graph (structural relationships), LSP (precise symbol resolution), or semantic (embedding-based search) — based on the operation type. Each provider is fully isolated: if it is unavailable or raises an exception, the gateway catches it and returns a safe-null envelope. The caller always gets a well-formed response with no exception to catch.
@@ -542,7 +534,7 @@ Register codeintel as an MCP server (`codeintel install`) and the agent gets fou
542
534
  | MCP tool | HTTP equivalent | Purpose |
543
535
  |---|---|---|
544
536
  | `code.query` | `POST /code/query` | The main call — search, trace, understand (the `op` table above) |
545
- | `code.status` | `GET /code/status` | Per-engine `installed` / `runnable` / `repo_indexed`, probed against the live engines a query actually hits |
537
+ | `code.status` | `GET /code/status` | Per-engine `installed` / `runnable` / `repo_indexed` (plus `model_cached` for semantic), probed against the live engines a query actually hits |
546
538
  | `code.doctor` | `POST /code/doctor` | Per-engine health + repo index status, with a fix for each gap |
547
539
  | `code.map` | — | Generate/refresh `CODE_INTEL.md`, a static orientation file for hosts without MCP |
548
540
 
@@ -648,7 +640,8 @@ answer — and `deadcode` in particular is retired rather than merely caveated (
648
640
  something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
649
641
  author is the single most useful thing this project can receive right now.
650
642
 
651
- **Engine coverage depends on external binaries.** Semantic search works out of the box. The graph
643
+ **Engine coverage depends on external binaries.** Semantic search works out of the box once its
644
+ embedding model is cached ([offline install](docs/install.md#offline--air-gapped-install)). The graph
652
645
  engine needs `codebase-memory-mcp` and the LSP engine needs `uvx` on `PATH` — without them those
653
646
  engines safe-null and you get a fraction of the capability table above. `codeintel setup --all`
654
647
  installs what it can and `codeintel doctor` tells you exactly what is missing and how to fix it.