codecortex 0.15.3__tar.gz → 0.15.5__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.
- {codecortex-0.15.3/src/codecortex.egg-info → codecortex-0.15.5}/PKG-INFO +35 -22
- {codecortex-0.15.3 → codecortex-0.15.5}/README.md +34 -21
- {codecortex-0.15.3 → codecortex-0.15.5/src/codecortex.egg-info}/PKG-INFO +35 -22
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codecortex.egg-info/SOURCES.txt +11 -1
- codecortex-0.15.5/src/codeintel/__init__.py +1 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/query.py +35 -1
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/status.py +10 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/doctor.py +71 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/gateway.py +70 -11
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/indexer.py +5 -6
- codecortex-0.15.5/src/codeintel/loc.py +73 -0
- codecortex-0.15.5/src/codeintel/outcome.py +77 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/provider.py +40 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/providers/graph.py +382 -21
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/providers/lsp.py +132 -35
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/providers/semantic.py +50 -5
- codecortex-0.15.5/src/codeintel/redact.py +113 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/reset.py +63 -2
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/server.py +40 -10
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/source_kind.py +69 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_cli_commands.py +9 -9
- codecortex-0.15.5/tests/test_cold_process.py +532 -0
- codecortex-0.15.5/tests/test_docs_ci_claims.py +99 -0
- codecortex-0.15.5/tests/test_docs_deadcode_withdrawal.py +100 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_gateway.py +3 -2
- codecortex-0.15.5/tests/test_graph_failure_population.py +256 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_graph_provider.py +1 -1
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_graph_real.py +67 -1
- codecortex-0.15.5/tests/test_incompleteness.py +580 -0
- codecortex-0.15.5/tests/test_loc_census.py +212 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_lsp_provider.py +5 -4
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_lsp_real.py +20 -15
- codecortex-0.15.5/tests/test_version_skew.py +124 -0
- codecortex-0.15.3/src/codeintel/__init__.py +0 -1
- {codecortex-0.15.3 → codecortex-0.15.5}/LICENSE +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/pyproject.toml +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/setup.cfg +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codecortex.egg-info/dependency_links.txt +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codecortex.egg-info/entry_points.txt +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codecortex.egg-info/requires.txt +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codecortex.egg-info/top_level.txt +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/__main__.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/auth.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/cache.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/__init__.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/_common.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/doctor.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/gen_token.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/graph.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/index.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/install.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/map.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/reset.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/serve.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/serve_http.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/commands/setup.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/config.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/containment.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/grapher.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/http_server.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/injector.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/installer.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/logconfig.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/mapper.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/metrics.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/onboarding.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/policy.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/providers/__init__.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/providers/none.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/py.typed +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/reindexer.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/searcher.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/semantic_db.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/term.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/verify.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/viewer/__init__.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/src/codeintel/viewer/graph_template.html +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_cache.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_chunking.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_cli_help.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_config.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_corpus.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_doctor.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_e2e.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_engine_adoption.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_enterprise.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_graph_stdin.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_grapher.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_hardening.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_http_auth.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_http_server.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_installer.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_integration.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_mapper.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_mcp_handshake.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_mcp_server.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_model_dimension.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_never_raise.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_onboarding.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_rbac.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_reindexer.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_rerank.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_reset.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_semantic_provider.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_source_kind.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_term.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_treesitter.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_verify.py +0 -0
- {codecortex-0.15.3 → codecortex-0.15.5}/tests/test_verify_call.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.5
|
|
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
|
|
@@ -95,23 +95,29 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
|
|
|
95
95
|
| Everything about one symbol | `context` | graph + lsp | both views merged |
|
|
96
96
|
| **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
|
|
97
97
|
| Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
|
|
98
|
-
| Unreferenced (dead) code | `deadcode` | graph |
|
|
98
|
+
| Unreferenced (dead) code | `deadcode` | graph | **withdrawn** — measured wrong in both directions on real repos; safe-nulls with `reason: "op-withdrawn"` unless you opt in — [why, and what to use instead](#deadcode-is-withdrawn) |
|
|
99
99
|
|
|
100
100
|
Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
|
|
101
101
|
|
|
102
|
-
#### `deadcode` is
|
|
102
|
+
#### `deadcode` is withdrawn
|
|
103
103
|
|
|
104
|
-
`deadcode` is
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
`deadcode` is withdrawn: it returns a safe-null (`reason: "op-withdrawn"`) instead of running. It
|
|
105
|
+
was measured wrong in **both** directions on real repositories — on one it named five candidates of
|
|
106
|
+
which four were live code (a rollup plugin hook; two entries of a `Record<string, fn>` reached by a
|
|
107
|
+
runtime string; a `predicate` passed inline to the call that consumes it), and on another it
|
|
108
|
+
reported "(none found)" for a 4,883-function codebase that had at least three genuinely unreferenced
|
|
109
|
+
private helpers. It is the one op whose output is an instruction to delete code, so it needs the
|
|
110
|
+
highest evidence bar of any op here, and currently has the least. It returns when a labelled corpus
|
|
111
|
+
measures its precision and recall — not before.
|
|
111
112
|
|
|
112
|
-
**
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
**Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
|
|
114
|
+
`deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
|
|
115
|
+
|
|
116
|
+
**Escape hatch, if you understand the risk.** Set `CODEINTEL_ENABLE_UNVERIFIED_OPS=1` to run it
|
|
117
|
+
anyway. It still re-reads the source before reporting a hit, which removes the *common* false
|
|
118
|
+
positives — but that verification is exactly what was measured wrong on the repositories above, so
|
|
119
|
+
**review every hit before deleting anything, and never wire it into an agent that deletes without a
|
|
120
|
+
human in the loop.**
|
|
115
121
|
|
|
116
122
|
**Example — "who uses `safe_null_result`?"**
|
|
117
123
|
|
|
@@ -511,26 +517,33 @@ back to grep rather than crashing.
|
|
|
511
517
|
|
|
512
518
|
| Area | Why |
|
|
513
519
|
|---|---|
|
|
514
|
-
| `deadcode` |
|
|
520
|
+
| `deadcode` | Withdrawn by default (`reason: "op-withdrawn"`) — measured wrong in both directions on real repos. Use `callers` on a specific symbol instead — [details](#deadcode-is-withdrawn). |
|
|
515
521
|
| Non-loopback serving | `serve-http` is stdlib `http.server`. It binds loopback by default for a reason; front it with a reverse proxy and see [docs/deploy.md](docs/deploy.md). |
|
|
516
522
|
| RBAC between **untrusting** tenants | It separates privilege levels among callers you already trust. It is not a wall against an adversary with write access to their own root — see the warning in [docs/deploy.md](docs/deploy.md). |
|
|
517
523
|
| Unattended automation | Anything that acts on a result without a human reading it deserves a pilot first. |
|
|
518
524
|
|
|
519
525
|
**On the test numbers.** The suite is large and the coverage floor is enforced, but read the figure
|
|
520
|
-
with its caveat
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
how
|
|
526
|
+
with its caveat. In the **main test job** the graph and LSP backends are absent, so their live tests
|
|
527
|
+
skip and those engines run against hand-authored mocks rather than the real wire contract. Separate
|
|
528
|
+
jobs cover the contract itself: `graph-contract` installs the pinned `codebase-memory-mcp` and runs
|
|
529
|
+
the live graph tests — and **fails if they skipped**, because a silently-skipped contract test is
|
|
530
|
+
how a total backend outage stayed green here once — while the nightly corpus job runs that same
|
|
531
|
+
real backend against pinned third-party repositories. `lsp-contract` runs the live serena tests but
|
|
532
|
+
is **`continue-on-error`**: serena is fetched from an upstream git HEAD this project does not
|
|
533
|
+
control, so a breakage there must be visible without blocking an unrelated release. Read that as
|
|
534
|
+
the LSP wire contract being *watched* rather than *gated*. The release canary — the only check that
|
|
535
|
+
asserts on real answer text from a built wheel — still covers **the semantic engine only**.
|
|
536
|
+
|
|
537
|
+
Line coverage measures how much of the intended behavior runs, not how much of reality it has met.
|
|
525
538
|
|
|
526
539
|
**The honest one-paragraph version.** codeintel has been run on very few repositories its author did
|
|
527
540
|
not write, and that is where its bugs have come from — every fix in `0.15.x` came from pointing it
|
|
528
541
|
at an unfamiliar codebase. Its characteristic failure mode is **answering confidently from the
|
|
529
542
|
wrong index rather than failing loudly**, which the never-raise contract makes harder to notice: a
|
|
530
543
|
wrong answer and a right one are the same shape. Run `codeintel doctor` before trusting a repo-wide
|
|
531
|
-
answer
|
|
532
|
-
[report it](#reporting-a-problem) — an issue from someone who is not the
|
|
533
|
-
useful thing this project can receive right now.
|
|
544
|
+
answer — and `deadcode` in particular is withdrawn rather than merely caveated (see above) — and if
|
|
545
|
+
something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
|
|
546
|
+
author is the single most useful thing this project can receive right now.
|
|
534
547
|
|
|
535
548
|
**Engine coverage depends on external binaries.** Semantic search works out of the box. The graph
|
|
536
549
|
engine needs `codebase-memory-mcp` and the LSP engine needs `uvx` on `PATH` — without them those
|
|
@@ -58,23 +58,29 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
|
|
|
58
58
|
| Everything about one symbol | `context` | graph + lsp | both views merged |
|
|
59
59
|
| **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
|
|
60
60
|
| Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
|
|
61
|
-
| Unreferenced (dead) code | `deadcode` | graph |
|
|
61
|
+
| Unreferenced (dead) code | `deadcode` | graph | **withdrawn** — measured wrong in both directions on real repos; safe-nulls with `reason: "op-withdrawn"` unless you opt in — [why, and what to use instead](#deadcode-is-withdrawn) |
|
|
62
62
|
|
|
63
63
|
Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
|
|
64
64
|
|
|
65
|
-
#### `deadcode` is
|
|
65
|
+
#### `deadcode` is withdrawn
|
|
66
66
|
|
|
67
|
-
`deadcode` is
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
`deadcode` is withdrawn: it returns a safe-null (`reason: "op-withdrawn"`) instead of running. It
|
|
68
|
+
was measured wrong in **both** directions on real repositories — on one it named five candidates of
|
|
69
|
+
which four were live code (a rollup plugin hook; two entries of a `Record<string, fn>` reached by a
|
|
70
|
+
runtime string; a `predicate` passed inline to the call that consumes it), and on another it
|
|
71
|
+
reported "(none found)" for a 4,883-function codebase that had at least three genuinely unreferenced
|
|
72
|
+
private helpers. It is the one op whose output is an instruction to delete code, so it needs the
|
|
73
|
+
highest evidence bar of any op here, and currently has the least. It returns when a labelled corpus
|
|
74
|
+
measures its precision and recall — not before.
|
|
74
75
|
|
|
75
|
-
**
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
**Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
|
|
77
|
+
`deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
|
|
78
|
+
|
|
79
|
+
**Escape hatch, if you understand the risk.** Set `CODEINTEL_ENABLE_UNVERIFIED_OPS=1` to run it
|
|
80
|
+
anyway. It still re-reads the source before reporting a hit, which removes the *common* false
|
|
81
|
+
positives — but that verification is exactly what was measured wrong on the repositories above, so
|
|
82
|
+
**review every hit before deleting anything, and never wire it into an agent that deletes without a
|
|
83
|
+
human in the loop.**
|
|
78
84
|
|
|
79
85
|
**Example — "who uses `safe_null_result`?"**
|
|
80
86
|
|
|
@@ -474,26 +480,33 @@ back to grep rather than crashing.
|
|
|
474
480
|
|
|
475
481
|
| Area | Why |
|
|
476
482
|
|---|---|
|
|
477
|
-
| `deadcode` |
|
|
483
|
+
| `deadcode` | Withdrawn by default (`reason: "op-withdrawn"`) — measured wrong in both directions on real repos. Use `callers` on a specific symbol instead — [details](#deadcode-is-withdrawn). |
|
|
478
484
|
| Non-loopback serving | `serve-http` is stdlib `http.server`. It binds loopback by default for a reason; front it with a reverse proxy and see [docs/deploy.md](docs/deploy.md). |
|
|
479
485
|
| RBAC between **untrusting** tenants | It separates privilege levels among callers you already trust. It is not a wall against an adversary with write access to their own root — see the warning in [docs/deploy.md](docs/deploy.md). |
|
|
480
486
|
| Unattended automation | Anything that acts on a result without a human reading it deserves a pilot first. |
|
|
481
487
|
|
|
482
488
|
**On the test numbers.** The suite is large and the coverage floor is enforced, but read the figure
|
|
483
|
-
with its caveat
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
how
|
|
489
|
+
with its caveat. In the **main test job** the graph and LSP backends are absent, so their live tests
|
|
490
|
+
skip and those engines run against hand-authored mocks rather than the real wire contract. Separate
|
|
491
|
+
jobs cover the contract itself: `graph-contract` installs the pinned `codebase-memory-mcp` and runs
|
|
492
|
+
the live graph tests — and **fails if they skipped**, because a silently-skipped contract test is
|
|
493
|
+
how a total backend outage stayed green here once — while the nightly corpus job runs that same
|
|
494
|
+
real backend against pinned third-party repositories. `lsp-contract` runs the live serena tests but
|
|
495
|
+
is **`continue-on-error`**: serena is fetched from an upstream git HEAD this project does not
|
|
496
|
+
control, so a breakage there must be visible without blocking an unrelated release. Read that as
|
|
497
|
+
the LSP wire contract being *watched* rather than *gated*. The release canary — the only check that
|
|
498
|
+
asserts on real answer text from a built wheel — still covers **the semantic engine only**.
|
|
499
|
+
|
|
500
|
+
Line coverage measures how much of the intended behavior runs, not how much of reality it has met.
|
|
488
501
|
|
|
489
502
|
**The honest one-paragraph version.** codeintel has been run on very few repositories its author did
|
|
490
503
|
not write, and that is where its bugs have come from — every fix in `0.15.x` came from pointing it
|
|
491
504
|
at an unfamiliar codebase. Its characteristic failure mode is **answering confidently from the
|
|
492
505
|
wrong index rather than failing loudly**, which the never-raise contract makes harder to notice: a
|
|
493
506
|
wrong answer and a right one are the same shape. Run `codeintel doctor` before trusting a repo-wide
|
|
494
|
-
answer
|
|
495
|
-
[report it](#reporting-a-problem) — an issue from someone who is not the
|
|
496
|
-
useful thing this project can receive right now.
|
|
507
|
+
answer — and `deadcode` in particular is withdrawn rather than merely caveated (see above) — and if
|
|
508
|
+
something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
|
|
509
|
+
author is the single most useful thing this project can receive right now.
|
|
497
510
|
|
|
498
511
|
**Engine coverage depends on external binaries.** Semantic search works out of the box. The graph
|
|
499
512
|
engine needs `codebase-memory-mcp` and the LSP engine needs `uvx` on `PATH` — without them those
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.5
|
|
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
|
|
@@ -95,23 +95,29 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
|
|
|
95
95
|
| Everything about one symbol | `context` | graph + lsp | both views merged |
|
|
96
96
|
| **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
|
|
97
97
|
| Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
|
|
98
|
-
| Unreferenced (dead) code | `deadcode` | graph |
|
|
98
|
+
| Unreferenced (dead) code | `deadcode` | graph | **withdrawn** — measured wrong in both directions on real repos; safe-nulls with `reason: "op-withdrawn"` unless you opt in — [why, and what to use instead](#deadcode-is-withdrawn) |
|
|
99
99
|
|
|
100
100
|
Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
|
|
101
101
|
|
|
102
|
-
#### `deadcode` is
|
|
102
|
+
#### `deadcode` is withdrawn
|
|
103
103
|
|
|
104
|
-
`deadcode` is
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
`deadcode` is withdrawn: it returns a safe-null (`reason: "op-withdrawn"`) instead of running. It
|
|
105
|
+
was measured wrong in **both** directions on real repositories — on one it named five candidates of
|
|
106
|
+
which four were live code (a rollup plugin hook; two entries of a `Record<string, fn>` reached by a
|
|
107
|
+
runtime string; a `predicate` passed inline to the call that consumes it), and on another it
|
|
108
|
+
reported "(none found)" for a 4,883-function codebase that had at least three genuinely unreferenced
|
|
109
|
+
private helpers. It is the one op whose output is an instruction to delete code, so it needs the
|
|
110
|
+
highest evidence bar of any op here, and currently has the least. It returns when a labelled corpus
|
|
111
|
+
measures its precision and recall — not before.
|
|
111
112
|
|
|
112
|
-
**
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
**Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
|
|
114
|
+
`deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
|
|
115
|
+
|
|
116
|
+
**Escape hatch, if you understand the risk.** Set `CODEINTEL_ENABLE_UNVERIFIED_OPS=1` to run it
|
|
117
|
+
anyway. It still re-reads the source before reporting a hit, which removes the *common* false
|
|
118
|
+
positives — but that verification is exactly what was measured wrong on the repositories above, so
|
|
119
|
+
**review every hit before deleting anything, and never wire it into an agent that deletes without a
|
|
120
|
+
human in the loop.**
|
|
115
121
|
|
|
116
122
|
**Example — "who uses `safe_null_result`?"**
|
|
117
123
|
|
|
@@ -511,26 +517,33 @@ back to grep rather than crashing.
|
|
|
511
517
|
|
|
512
518
|
| Area | Why |
|
|
513
519
|
|---|---|
|
|
514
|
-
| `deadcode` |
|
|
520
|
+
| `deadcode` | Withdrawn by default (`reason: "op-withdrawn"`) — measured wrong in both directions on real repos. Use `callers` on a specific symbol instead — [details](#deadcode-is-withdrawn). |
|
|
515
521
|
| Non-loopback serving | `serve-http` is stdlib `http.server`. It binds loopback by default for a reason; front it with a reverse proxy and see [docs/deploy.md](docs/deploy.md). |
|
|
516
522
|
| RBAC between **untrusting** tenants | It separates privilege levels among callers you already trust. It is not a wall against an adversary with write access to their own root — see the warning in [docs/deploy.md](docs/deploy.md). |
|
|
517
523
|
| Unattended automation | Anything that acts on a result without a human reading it deserves a pilot first. |
|
|
518
524
|
|
|
519
525
|
**On the test numbers.** The suite is large and the coverage floor is enforced, but read the figure
|
|
520
|
-
with its caveat
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
how
|
|
526
|
+
with its caveat. In the **main test job** the graph and LSP backends are absent, so their live tests
|
|
527
|
+
skip and those engines run against hand-authored mocks rather than the real wire contract. Separate
|
|
528
|
+
jobs cover the contract itself: `graph-contract` installs the pinned `codebase-memory-mcp` and runs
|
|
529
|
+
the live graph tests — and **fails if they skipped**, because a silently-skipped contract test is
|
|
530
|
+
how a total backend outage stayed green here once — while the nightly corpus job runs that same
|
|
531
|
+
real backend against pinned third-party repositories. `lsp-contract` runs the live serena tests but
|
|
532
|
+
is **`continue-on-error`**: serena is fetched from an upstream git HEAD this project does not
|
|
533
|
+
control, so a breakage there must be visible without blocking an unrelated release. Read that as
|
|
534
|
+
the LSP wire contract being *watched* rather than *gated*. The release canary — the only check that
|
|
535
|
+
asserts on real answer text from a built wheel — still covers **the semantic engine only**.
|
|
536
|
+
|
|
537
|
+
Line coverage measures how much of the intended behavior runs, not how much of reality it has met.
|
|
525
538
|
|
|
526
539
|
**The honest one-paragraph version.** codeintel has been run on very few repositories its author did
|
|
527
540
|
not write, and that is where its bugs have come from — every fix in `0.15.x` came from pointing it
|
|
528
541
|
at an unfamiliar codebase. Its characteristic failure mode is **answering confidently from the
|
|
529
542
|
wrong index rather than failing loudly**, which the never-raise contract makes harder to notice: a
|
|
530
543
|
wrong answer and a right one are the same shape. Run `codeintel doctor` before trusting a repo-wide
|
|
531
|
-
answer
|
|
532
|
-
[report it](#reporting-a-problem) — an issue from someone who is not the
|
|
533
|
-
useful thing this project can receive right now.
|
|
544
|
+
answer — and `deadcode` in particular is withdrawn rather than merely caveated (see above) — and if
|
|
545
|
+
something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
|
|
546
|
+
author is the single most useful thing this project can receive right now.
|
|
534
547
|
|
|
535
548
|
**Engine coverage depends on external binaries.** Semantic search works out of the box. The graph
|
|
536
549
|
engine needs `codebase-memory-mcp` and the LSP engine needs `uvx` on `PATH` — without them those
|
|
@@ -20,13 +20,16 @@ src/codeintel/http_server.py
|
|
|
20
20
|
src/codeintel/indexer.py
|
|
21
21
|
src/codeintel/injector.py
|
|
22
22
|
src/codeintel/installer.py
|
|
23
|
+
src/codeintel/loc.py
|
|
23
24
|
src/codeintel/logconfig.py
|
|
24
25
|
src/codeintel/mapper.py
|
|
25
26
|
src/codeintel/metrics.py
|
|
26
27
|
src/codeintel/onboarding.py
|
|
28
|
+
src/codeintel/outcome.py
|
|
27
29
|
src/codeintel/policy.py
|
|
28
30
|
src/codeintel/provider.py
|
|
29
31
|
src/codeintel/py.typed
|
|
32
|
+
src/codeintel/redact.py
|
|
30
33
|
src/codeintel/reindexer.py
|
|
31
34
|
src/codeintel/reset.py
|
|
32
35
|
src/codeintel/searcher.py
|
|
@@ -60,13 +63,17 @@ tests/test_cache.py
|
|
|
60
63
|
tests/test_chunking.py
|
|
61
64
|
tests/test_cli_commands.py
|
|
62
65
|
tests/test_cli_help.py
|
|
66
|
+
tests/test_cold_process.py
|
|
63
67
|
tests/test_config.py
|
|
64
68
|
tests/test_corpus.py
|
|
69
|
+
tests/test_docs_ci_claims.py
|
|
70
|
+
tests/test_docs_deadcode_withdrawal.py
|
|
65
71
|
tests/test_doctor.py
|
|
66
72
|
tests/test_e2e.py
|
|
67
73
|
tests/test_engine_adoption.py
|
|
68
74
|
tests/test_enterprise.py
|
|
69
75
|
tests/test_gateway.py
|
|
76
|
+
tests/test_graph_failure_population.py
|
|
70
77
|
tests/test_graph_provider.py
|
|
71
78
|
tests/test_graph_real.py
|
|
72
79
|
tests/test_graph_stdin.py
|
|
@@ -74,8 +81,10 @@ tests/test_grapher.py
|
|
|
74
81
|
tests/test_hardening.py
|
|
75
82
|
tests/test_http_auth.py
|
|
76
83
|
tests/test_http_server.py
|
|
84
|
+
tests/test_incompleteness.py
|
|
77
85
|
tests/test_installer.py
|
|
78
86
|
tests/test_integration.py
|
|
87
|
+
tests/test_loc_census.py
|
|
79
88
|
tests/test_lsp_provider.py
|
|
80
89
|
tests/test_lsp_real.py
|
|
81
90
|
tests/test_mapper.py
|
|
@@ -93,4 +102,5 @@ tests/test_source_kind.py
|
|
|
93
102
|
tests/test_term.py
|
|
94
103
|
tests/test_treesitter.py
|
|
95
104
|
tests/test_verify.py
|
|
96
|
-
tests/test_verify_call.py
|
|
105
|
+
tests/test_verify_call.py
|
|
106
|
+
tests/test_version_skew.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.15.5"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""`codeintel query` — one question against the gateway: search, callers, callees, impact, chain."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
import time
|
|
6
7
|
from typing import Any
|
|
@@ -12,6 +13,36 @@ from codeintel.provider import Result, safe_null_result
|
|
|
12
13
|
# and reporting whatever the gateway last said.
|
|
13
14
|
_WARMING_TIMEOUT_S = 45.0
|
|
14
15
|
|
|
16
|
+
# Per-query time budget, in milliseconds, handed to whichever engine answers.
|
|
17
|
+
#
|
|
18
|
+
# This used to be omitted entirely, so every engine fell back to its own default — 5s for the LSP
|
|
19
|
+
# provider — against a cold first `symbol` query measured at 11.65s on a real 841-file TypeScript
|
|
20
|
+
# repo. The call timed out, the reference lookup came back empty, and the empty list was rendered
|
|
21
|
+
# as "(none)": a confident false answer produced by a missing argument. A CLI invocation is a
|
|
22
|
+
# human or an agent waiting on one question; it can afford to wait properly.
|
|
23
|
+
_CLI_BUDGET_MS = 30_000
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _budget_ms() -> int:
|
|
27
|
+
"""The per-query budget, overridable via ``CODEINTEL_BUDGET_MS``.
|
|
28
|
+
|
|
29
|
+
Two reasons this is an env var rather than a constant. Operators on slow machines or huge
|
|
30
|
+
repositories need to raise it. And tests need to LOWER it: the cold-process tier exists to catch
|
|
31
|
+
the defect where a timed-out backend call is rendered as a confident "(none)", but on a fast
|
|
32
|
+
machine the cold call simply succeeds, so the tier passed with that exact regression planted
|
|
33
|
+
back in. A budget it can drive to near-zero lets it reproduce the starvation condition
|
|
34
|
+
deterministically instead of waiting for a slow day.
|
|
35
|
+
"""
|
|
36
|
+
raw = os.environ.get("CODEINTEL_BUDGET_MS", "").strip()
|
|
37
|
+
if raw:
|
|
38
|
+
try:
|
|
39
|
+
n = int(raw)
|
|
40
|
+
if n > 0:
|
|
41
|
+
return n
|
|
42
|
+
except ValueError:
|
|
43
|
+
pass
|
|
44
|
+
return _CLI_BUDGET_MS
|
|
45
|
+
|
|
15
46
|
|
|
16
47
|
def run(args: Any) -> int:
|
|
17
48
|
"""`--json` promises parseable stdout, so its failures must be JSON too.
|
|
@@ -55,7 +86,9 @@ def _query(args: Any) -> Result:
|
|
|
55
86
|
|
|
56
87
|
project_root = resolve_root(args)
|
|
57
88
|
engine = args.engine if args.engine != "auto" else None
|
|
58
|
-
|
|
89
|
+
# oneshot: this process exits when the query returns, so it must not start a background
|
|
90
|
+
# reindex it cannot finish (and must not then report that reindex as staleness).
|
|
91
|
+
gw = server._build_gateway(oneshot=True)
|
|
59
92
|
|
|
60
93
|
def _run_query() -> Result:
|
|
61
94
|
return gw.query(
|
|
@@ -63,6 +96,7 @@ def _query(args: Any) -> Result:
|
|
|
63
96
|
target=args.target,
|
|
64
97
|
engine=engine,
|
|
65
98
|
role="",
|
|
99
|
+
budget=_budget_ms(),
|
|
66
100
|
project_root=project_root,
|
|
67
101
|
)
|
|
68
102
|
|
|
@@ -32,6 +32,16 @@ def run(args: Any) -> int:
|
|
|
32
32
|
if status.get("healthy") is False:
|
|
33
33
|
print("\n run `codeintel doctor` for the fix for each gap")
|
|
34
34
|
|
|
35
|
+
# Printed before anything else the user might act on. A skew means every line above describes
|
|
36
|
+
# the code this process loaded, not the code installed — so a fix the user can read in the
|
|
37
|
+
# CHANGELOG can be absent from every answer while the engines all report green.
|
|
38
|
+
skew = status.get("version_skew")
|
|
39
|
+
if isinstance(skew, dict) and skew.get("running") and skew.get("installed"):
|
|
40
|
+
print(
|
|
41
|
+
f"\n ! serving {skew['running']}, but {skew['installed']} is installed"
|
|
42
|
+
"\n restart the codeintel server (or the agent hosting it) to pick it up"
|
|
43
|
+
)
|
|
44
|
+
|
|
35
45
|
from codeintel.config import load_config
|
|
36
46
|
from codeintel.semantic_db import default_db_path
|
|
37
47
|
|
|
@@ -8,6 +8,7 @@ effects). The same report drives the CLI `doctor` command, the `code.doctor` MCP
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import os
|
|
11
|
+
import pathlib
|
|
11
12
|
import shutil
|
|
12
13
|
from collections.abc import Callable
|
|
13
14
|
from typing import Any
|
|
@@ -106,6 +107,51 @@ def _dist_version(name: str) -> str | None:
|
|
|
106
107
|
return None
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
def running_version_skew() -> tuple[str, str] | None:
|
|
111
|
+
"""``(running, on_disk)`` when this process is serving code older than what is installed.
|
|
112
|
+
|
|
113
|
+
A long-lived server holds the module it imported at startup. Upgrading the package underneath
|
|
114
|
+
it — `uv tool install`, `pip install -U` — replaces the files on disk and changes nothing about
|
|
115
|
+
the running process, which keeps answering with the old code until something restarts it. That
|
|
116
|
+
gap is silent and it is not hypothetical: a fix can be committed, released, installed, read in
|
|
117
|
+
the CHANGELOG, and still absent from every answer the user is getting, with `status` reporting
|
|
118
|
+
the stale version as if it were the truth.
|
|
119
|
+
|
|
120
|
+
Detected by re-reading `__version__` out of the very file this module was loaded FROM, at call
|
|
121
|
+
time. `importlib.metadata` would be the obvious route and is the wrong one here: it answers
|
|
122
|
+
"what does the installed distribution claim", which is the same number for a fresh process and
|
|
123
|
+
a stale one, and its path caches make the negative case unreliable. Parsed with `ast` rather
|
|
124
|
+
than imported or regexed — re-importing would either return the cached stale module or execute
|
|
125
|
+
freshly-installed code inside a process running the old version, and neither is something a
|
|
126
|
+
health check should do.
|
|
127
|
+
|
|
128
|
+
Never raises, and stays silent whenever it cannot be sure: a missing file, an unparseable
|
|
129
|
+
source, or an absent `__version__` all mean "no claim", because a false upgrade prompt costs
|
|
130
|
+
more trust than a missed one.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
import ast as _ast
|
|
134
|
+
|
|
135
|
+
import codeintel
|
|
136
|
+
running = getattr(codeintel, "__version__", None)
|
|
137
|
+
path = getattr(codeintel, "__file__", None)
|
|
138
|
+
if not isinstance(running, str) or not path:
|
|
139
|
+
return None
|
|
140
|
+
src = pathlib.Path(path).read_text(encoding="utf-8", errors="replace")
|
|
141
|
+
for node in _ast.parse(src).body:
|
|
142
|
+
if not isinstance(node, _ast.Assign):
|
|
143
|
+
continue
|
|
144
|
+
for target in node.targets:
|
|
145
|
+
if isinstance(target, _ast.Name) and target.id == "__version__":
|
|
146
|
+
on_disk = _ast.literal_eval(node.value)
|
|
147
|
+
if isinstance(on_disk, str) and on_disk != running:
|
|
148
|
+
return (running, on_disk)
|
|
149
|
+
return None
|
|
150
|
+
except Exception:
|
|
151
|
+
return None
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
109
155
|
def collect_versions(engines: dict) -> dict:
|
|
110
156
|
"""Versions of the external backends each engine depends on.
|
|
111
157
|
|
|
@@ -270,12 +316,27 @@ def run_doctor(
|
|
|
270
316
|
healthy = all(
|
|
271
317
|
e.get("status") != "fail" for n, e in engines.items() if n not in _OPTIONAL_ENGINES
|
|
272
318
|
)
|
|
319
|
+
# A skew is reported even when every engine is green, because it makes all the other numbers
|
|
320
|
+
# untrustworthy: they describe the code this process loaded, not the code that is installed.
|
|
321
|
+
skew = running_version_skew()
|
|
273
322
|
return {
|
|
274
323
|
"ok": True,
|
|
275
324
|
"project_root": root,
|
|
276
325
|
"deep": bool(deep),
|
|
277
326
|
"treesitter": treesitter,
|
|
278
327
|
"versions": versions,
|
|
328
|
+
"version_skew": (
|
|
329
|
+
{
|
|
330
|
+
"running": skew[0],
|
|
331
|
+
"installed": skew[1],
|
|
332
|
+
"remediation": (
|
|
333
|
+
f"this process is serving {skew[0]} while {skew[1]} is installed — "
|
|
334
|
+
"restart the codeintel server (or the agent hosting it) to pick it up"
|
|
335
|
+
),
|
|
336
|
+
}
|
|
337
|
+
if skew
|
|
338
|
+
else None
|
|
339
|
+
),
|
|
279
340
|
"summary": {"ready": ready, "total": len(engines), "healthy": healthy},
|
|
280
341
|
"engines": engines,
|
|
281
342
|
"registrations": collect_registrations(),
|
|
@@ -326,6 +387,16 @@ def render_doctor_text(report: dict) -> str:
|
|
|
326
387
|
if rem:
|
|
327
388
|
out.append(" " + c.bold(c.cyan("fix:")) + " " + rem)
|
|
328
389
|
|
|
390
|
+
# Rendered with the engine notes rather than in the version block: it is a gap with a fix,
|
|
391
|
+
# which is what this section is for, and it outranks the engine rows — those describe the
|
|
392
|
+
# loaded code, and a skew means the loaded code is not the installed code.
|
|
393
|
+
skew = report.get("version_skew")
|
|
394
|
+
if isinstance(skew, dict) and skew.get("running") and skew.get("installed"):
|
|
395
|
+
out.append(" " + c.dim("└─") + " " + c.cyan("codeintel")
|
|
396
|
+
+ f": serving {skew['running']}, installed is {skew['installed']}")
|
|
397
|
+
out.append(" " + c.bold(c.cyan("fix:")) + " restart the codeintel server "
|
|
398
|
+
"(or the agent hosting it) to pick up the installed version")
|
|
399
|
+
|
|
329
400
|
summ = report.get("summary", {})
|
|
330
401
|
ready, total, healthy = summ.get("ready", "?"), summ.get("total", "?"), summ.get("healthy")
|
|
331
402
|
count = c.bold(f"{ready} / {total}")
|