codecortex 0.9.0__tar.gz → 0.10.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.
- {codecortex-0.9.0/src/codecortex.egg-info → codecortex-0.10.0}/PKG-INFO +21 -14
- {codecortex-0.9.0 → codecortex-0.10.0}/README.md +20 -13
- {codecortex-0.9.0 → codecortex-0.10.0/src/codecortex.egg-info}/PKG-INFO +21 -14
- codecortex-0.10.0/src/codeintel/__init__.py +1 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/__main__.py +8 -4
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/doctor.py +17 -4
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/onboarding.py +76 -12
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_doctor.py +18 -0
- codecortex-0.10.0/tests/test_onboarding.py +160 -0
- codecortex-0.9.0/src/codeintel/__init__.py +0 -1
- codecortex-0.9.0/tests/test_onboarding.py +0 -78
- {codecortex-0.9.0 → codecortex-0.10.0}/LICENSE +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/pyproject.toml +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/setup.cfg +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/SOURCES.txt +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/entry_points.txt +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/requires.txt +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/top_level.txt +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/auth.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/cache.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/config.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/gateway.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/http_server.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/indexer.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/injector.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/installer.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/logconfig.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/mapper.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/metrics.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/policy.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/provider.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/__init__.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/graph.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/lsp.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/none.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/semantic.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/reindexer.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/reset.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/searcher.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/semantic_db.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/server.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/term.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_cache.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_chunking.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_config.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_e2e.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_enterprise.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_gateway.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_graph_provider.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_graph_real.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_graph_stdin.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_hardening.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_http_auth.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_http_server.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_installer.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_integration.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_lsp_provider.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_lsp_real.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_mapper.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_mcp_server.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_model_dimension.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_never_raise.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_rbac.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_reindexer.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_rerank.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_reset.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_semantic_provider.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_term.py +0 -0
- {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_treesitter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -60,10 +60,13 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
|
|
|
60
60
|
| Who calls this? | `callers` | graph | caller symbols + files |
|
|
61
61
|
| What does this call? | `callees` | graph | callee symbols + files |
|
|
62
62
|
| Blast radius of a change | `impact` | graph | callers **and** callees together |
|
|
63
|
-
| Trace a call chain up/downstream | `chain` | graph | ordered hops |
|
|
63
|
+
| Trace a call chain up/downstream | `chain` | graph | ordered, risk-labeled hops |
|
|
64
64
|
| Find symbols by pattern | `pattern` | graph | matching nodes + locations |
|
|
65
65
|
| Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
|
|
66
66
|
| Everything about one symbol | `context` | graph + lsp | both views merged |
|
|
67
|
+
| **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
|
|
68
|
+
| Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
|
|
69
|
+
| Unreferenced (dead) code | `deadcode` | graph | non-test symbols with no callers |
|
|
67
70
|
|
|
68
71
|
Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
|
|
69
72
|
|
|
@@ -99,11 +102,21 @@ The agent hands `result` straight to the model. If the graph backend isn't insta
|
|
|
99
102
|
pip install codecortex
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
This installs the `codeintel` CLI; the **semantic** engine works out of the box.
|
|
103
|
-
and **LSP** engines use external backends (`codebase-memory-mcp`, and serena via `uvx`) —
|
|
104
|
-
run `codeintel doctor` to see what's available and how to enable the rest. (On PyPI the
|
|
105
|
+
This installs the `codeintel` CLI; the **semantic** engine works out of the box. (On PyPI the
|
|
105
106
|
distribution is `codecortex` because `codeintel` was taken; the CLI and import stay `codeintel`.)
|
|
106
107
|
|
|
108
|
+
**One command prepares the rest and indexes your repo:**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
codeintel setup --all /path/to/your/project
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This installs `uv` (for the LSP engine), warms serena, downloads the embedding model, indexes the
|
|
115
|
+
repo, and prints a health report ending in a **Next:** list — exactly what's ready and the one
|
|
116
|
+
remaining step. It's idempotent, so re-running is safe. The **graph** engine (`codebase-memory-mcp`)
|
|
117
|
+
is an *optional* external binary that adds who-calls / impact / hotspots / `changed`; codeintel is
|
|
118
|
+
fully usable without it.
|
|
119
|
+
|
|
107
120
|
Or from source:
|
|
108
121
|
|
|
109
122
|
```bash
|
|
@@ -112,17 +125,10 @@ cd codeintel
|
|
|
112
125
|
pip install -e .
|
|
113
126
|
```
|
|
114
127
|
|
|
115
|
-
Register with your AI agent(s):
|
|
128
|
+
Register with your AI agent(s), then query:
|
|
116
129
|
|
|
117
130
|
```bash
|
|
118
131
|
codeintel install # registers with Claude, Codex, Gemini, Zed
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Index a project, check what's ready, and run your first query:
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
codeintel index /path/to/your/project
|
|
125
|
-
codeintel doctor /path/to/your/project # which engines are ready + how to fix the rest
|
|
126
132
|
codeintel query --op search --target "authentication middleware"
|
|
127
133
|
```
|
|
128
134
|
|
|
@@ -174,6 +180,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
174
180
|
- **[Architecture](docs/architecture.md)** — layers, the `CodeProvider` protocol, the safe-null contract, caching, freshness (ASCII + Mermaid).
|
|
175
181
|
- **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
|
|
176
182
|
- **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
|
|
183
|
+
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 25 k chunks indexed in ~8 min, ~235 ms warm queries, 60 MB index.
|
|
177
184
|
- Engine references: **[graph](docs/graph.md)** · **[lsp](docs/lsp.md)** · **[semantic](docs/semantic.md)**.
|
|
178
185
|
|
|
179
186
|
## CLI reference
|
|
@@ -181,7 +188,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
181
188
|
| Command | Purpose |
|
|
182
189
|
|---|---|
|
|
183
190
|
| `codeintel install [--agent claude\|codex\|gemini\|zed\|all]` | Register codeintel with AI agent(s) |
|
|
184
|
-
| `codeintel setup [project_root] [--index] [--warm] [--install-uv]` |
|
|
191
|
+
| `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 |
|
|
185
192
|
| `codeintel index [project_root]` | Index a project for semantic search |
|
|
186
193
|
| `codeintel serve` | Start the MCP server (stdio transport) |
|
|
187
194
|
| `codeintel serve-http [--host HOST] [--port 8766] [--allow-remote] [--token TOKEN]` | Start the HTTP transport (loopback-only unless `--allow-remote`; `--token` requires a bearer token on every request) |
|
|
@@ -26,10 +26,13 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
|
|
|
26
26
|
| Who calls this? | `callers` | graph | caller symbols + files |
|
|
27
27
|
| What does this call? | `callees` | graph | callee symbols + files |
|
|
28
28
|
| Blast radius of a change | `impact` | graph | callers **and** callees together |
|
|
29
|
-
| Trace a call chain up/downstream | `chain` | graph | ordered hops |
|
|
29
|
+
| Trace a call chain up/downstream | `chain` | graph | ordered, risk-labeled hops |
|
|
30
30
|
| Find symbols by pattern | `pattern` | graph | matching nodes + locations |
|
|
31
31
|
| Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
|
|
32
32
|
| Everything about one symbol | `context` | graph + lsp | both views merged |
|
|
33
|
+
| **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
|
|
34
|
+
| Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
|
|
35
|
+
| Unreferenced (dead) code | `deadcode` | graph | non-test symbols with no callers |
|
|
33
36
|
|
|
34
37
|
Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
|
|
35
38
|
|
|
@@ -65,11 +68,21 @@ The agent hands `result` straight to the model. If the graph backend isn't insta
|
|
|
65
68
|
pip install codecortex
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
This installs the `codeintel` CLI; the **semantic** engine works out of the box.
|
|
69
|
-
and **LSP** engines use external backends (`codebase-memory-mcp`, and serena via `uvx`) —
|
|
70
|
-
run `codeintel doctor` to see what's available and how to enable the rest. (On PyPI the
|
|
71
|
+
This installs the `codeintel` CLI; the **semantic** engine works out of the box. (On PyPI the
|
|
71
72
|
distribution is `codecortex` because `codeintel` was taken; the CLI and import stay `codeintel`.)
|
|
72
73
|
|
|
74
|
+
**One command prepares the rest and indexes your repo:**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
codeintel setup --all /path/to/your/project
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This installs `uv` (for the LSP engine), warms serena, downloads the embedding model, indexes the
|
|
81
|
+
repo, and prints a health report ending in a **Next:** list — exactly what's ready and the one
|
|
82
|
+
remaining step. It's idempotent, so re-running is safe. The **graph** engine (`codebase-memory-mcp`)
|
|
83
|
+
is an *optional* external binary that adds who-calls / impact / hotspots / `changed`; codeintel is
|
|
84
|
+
fully usable without it.
|
|
85
|
+
|
|
73
86
|
Or from source:
|
|
74
87
|
|
|
75
88
|
```bash
|
|
@@ -78,17 +91,10 @@ cd codeintel
|
|
|
78
91
|
pip install -e .
|
|
79
92
|
```
|
|
80
93
|
|
|
81
|
-
Register with your AI agent(s):
|
|
94
|
+
Register with your AI agent(s), then query:
|
|
82
95
|
|
|
83
96
|
```bash
|
|
84
97
|
codeintel install # registers with Claude, Codex, Gemini, Zed
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Index a project, check what's ready, and run your first query:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
codeintel index /path/to/your/project
|
|
91
|
-
codeintel doctor /path/to/your/project # which engines are ready + how to fix the rest
|
|
92
98
|
codeintel query --op search --target "authentication middleware"
|
|
93
99
|
```
|
|
94
100
|
|
|
@@ -140,6 +146,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
140
146
|
- **[Architecture](docs/architecture.md)** — layers, the `CodeProvider` protocol, the safe-null contract, caching, freshness (ASCII + Mermaid).
|
|
141
147
|
- **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
|
|
142
148
|
- **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
|
|
149
|
+
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 25 k chunks indexed in ~8 min, ~235 ms warm queries, 60 MB index.
|
|
143
150
|
- Engine references: **[graph](docs/graph.md)** · **[lsp](docs/lsp.md)** · **[semantic](docs/semantic.md)**.
|
|
144
151
|
|
|
145
152
|
## CLI reference
|
|
@@ -147,7 +154,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
147
154
|
| Command | Purpose |
|
|
148
155
|
|---|---|
|
|
149
156
|
| `codeintel install [--agent claude\|codex\|gemini\|zed\|all]` | Register codeintel with AI agent(s) |
|
|
150
|
-
| `codeintel setup [project_root] [--index] [--warm] [--install-uv]` |
|
|
157
|
+
| `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 |
|
|
151
158
|
| `codeintel index [project_root]` | Index a project for semantic search |
|
|
152
159
|
| `codeintel serve` | Start the MCP server (stdio transport) |
|
|
153
160
|
| `codeintel serve-http [--host HOST] [--port 8766] [--allow-remote] [--token TOKEN]` | Start the HTTP transport (loopback-only unless `--allow-remote`; `--token` requires a bearer token on every request) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -60,10 +60,13 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
|
|
|
60
60
|
| Who calls this? | `callers` | graph | caller symbols + files |
|
|
61
61
|
| What does this call? | `callees` | graph | callee symbols + files |
|
|
62
62
|
| Blast radius of a change | `impact` | graph | callers **and** callees together |
|
|
63
|
-
| Trace a call chain up/downstream | `chain` | graph | ordered hops |
|
|
63
|
+
| Trace a call chain up/downstream | `chain` | graph | ordered, risk-labeled hops |
|
|
64
64
|
| Find symbols by pattern | `pattern` | graph | matching nodes + locations |
|
|
65
65
|
| Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
|
|
66
66
|
| Everything about one symbol | `context` | graph + lsp | both views merged |
|
|
67
|
+
| **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
|
|
68
|
+
| Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
|
|
69
|
+
| Unreferenced (dead) code | `deadcode` | graph | non-test symbols with no callers |
|
|
67
70
|
|
|
68
71
|
Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
|
|
69
72
|
|
|
@@ -99,11 +102,21 @@ The agent hands `result` straight to the model. If the graph backend isn't insta
|
|
|
99
102
|
pip install codecortex
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
This installs the `codeintel` CLI; the **semantic** engine works out of the box.
|
|
103
|
-
and **LSP** engines use external backends (`codebase-memory-mcp`, and serena via `uvx`) —
|
|
104
|
-
run `codeintel doctor` to see what's available and how to enable the rest. (On PyPI the
|
|
105
|
+
This installs the `codeintel` CLI; the **semantic** engine works out of the box. (On PyPI the
|
|
105
106
|
distribution is `codecortex` because `codeintel` was taken; the CLI and import stay `codeintel`.)
|
|
106
107
|
|
|
108
|
+
**One command prepares the rest and indexes your repo:**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
codeintel setup --all /path/to/your/project
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This installs `uv` (for the LSP engine), warms serena, downloads the embedding model, indexes the
|
|
115
|
+
repo, and prints a health report ending in a **Next:** list — exactly what's ready and the one
|
|
116
|
+
remaining step. It's idempotent, so re-running is safe. The **graph** engine (`codebase-memory-mcp`)
|
|
117
|
+
is an *optional* external binary that adds who-calls / impact / hotspots / `changed`; codeintel is
|
|
118
|
+
fully usable without it.
|
|
119
|
+
|
|
107
120
|
Or from source:
|
|
108
121
|
|
|
109
122
|
```bash
|
|
@@ -112,17 +125,10 @@ cd codeintel
|
|
|
112
125
|
pip install -e .
|
|
113
126
|
```
|
|
114
127
|
|
|
115
|
-
Register with your AI agent(s):
|
|
128
|
+
Register with your AI agent(s), then query:
|
|
116
129
|
|
|
117
130
|
```bash
|
|
118
131
|
codeintel install # registers with Claude, Codex, Gemini, Zed
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Index a project, check what's ready, and run your first query:
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
codeintel index /path/to/your/project
|
|
125
|
-
codeintel doctor /path/to/your/project # which engines are ready + how to fix the rest
|
|
126
132
|
codeintel query --op search --target "authentication middleware"
|
|
127
133
|
```
|
|
128
134
|
|
|
@@ -174,6 +180,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
174
180
|
- **[Architecture](docs/architecture.md)** — layers, the `CodeProvider` protocol, the safe-null contract, caching, freshness (ASCII + Mermaid).
|
|
175
181
|
- **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
|
|
176
182
|
- **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
|
|
183
|
+
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 25 k chunks indexed in ~8 min, ~235 ms warm queries, 60 MB index.
|
|
177
184
|
- Engine references: **[graph](docs/graph.md)** · **[lsp](docs/lsp.md)** · **[semantic](docs/semantic.md)**.
|
|
178
185
|
|
|
179
186
|
## CLI reference
|
|
@@ -181,7 +188,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
181
188
|
| Command | Purpose |
|
|
182
189
|
|---|---|
|
|
183
190
|
| `codeintel install [--agent claude\|codex\|gemini\|zed\|all]` | Register codeintel with AI agent(s) |
|
|
184
|
-
| `codeintel setup [project_root] [--index] [--warm] [--install-uv]` |
|
|
191
|
+
| `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 |
|
|
185
192
|
| `codeintel index [project_root]` | Index a project for semantic search |
|
|
186
193
|
| `codeintel serve` | Start the MCP server (stdio transport) |
|
|
187
194
|
| `codeintel serve-http [--host HOST] [--port 8766] [--allow-remote] [--token TOKEN]` | Start the HTTP transport (loopback-only unless `--allow-remote`; `--token` requires a bearer token on every request) |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.0"
|
|
@@ -77,6 +77,9 @@ def main() -> None:
|
|
|
77
77
|
# setup subcommand
|
|
78
78
|
setup_parser = subparsers.add_parser("setup", parents=[color_parent], help="Prepare backends and optionally index this repo")
|
|
79
79
|
setup_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
|
|
80
|
+
setup_parser.add_argument("--all", action="store_true", dest="all_steps",
|
|
81
|
+
help="One-command setup: do everything automatable (uv + deps + index + "
|
|
82
|
+
"warm serena). Idempotent — skips what's already installed.")
|
|
80
83
|
setup_parser.add_argument("--install-uv", action="store_true", help="Run `pip install uv` (provides uvx for the LSP engine)")
|
|
81
84
|
setup_parser.add_argument("--install-deps", action="store_true", help="Run `pip install -e .` (semantic engine deps)")
|
|
82
85
|
setup_parser.add_argument("--index", action="store_true", help="Index this repo now (first run downloads the ~50MB model)")
|
|
@@ -284,12 +287,13 @@ def main() -> None:
|
|
|
284
287
|
from codeintel import onboarding
|
|
285
288
|
|
|
286
289
|
project_root = args.project_root or os.getcwd()
|
|
290
|
+
all_steps = getattr(args, "all_steps", False) # --all implies every automatable step
|
|
287
291
|
report = onboarding.run_setup(
|
|
288
292
|
project_root,
|
|
289
|
-
install_uv=args.install_uv,
|
|
290
|
-
install_deps=args.install_deps,
|
|
291
|
-
do_index=args.index,
|
|
292
|
-
warm_lsp=args.warm,
|
|
293
|
+
install_uv=args.install_uv or all_steps,
|
|
294
|
+
install_deps=args.install_deps or all_steps,
|
|
295
|
+
do_index=args.index or all_steps,
|
|
296
|
+
warm_lsp=args.warm or all_steps,
|
|
293
297
|
)
|
|
294
298
|
if args.json:
|
|
295
299
|
import json as _json
|
|
@@ -11,6 +11,9 @@ import os
|
|
|
11
11
|
from typing import Any, Callable, Optional
|
|
12
12
|
|
|
13
13
|
_ENGINES = ("graph", "lsp", "semantic")
|
|
14
|
+
# Graph needs an external native binary (codebase-memory-mcp) codeintel can't auto-install, so a
|
|
15
|
+
# missing graph engine does NOT make a repo "unhealthy" — the tool is fully usable on semantic + lsp.
|
|
16
|
+
_OPTIONAL_ENGINES = frozenset({"graph"})
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def _status_for(report: dict) -> str:
|
|
@@ -112,13 +115,17 @@ def run_doctor(
|
|
|
112
115
|
treesitter = False
|
|
113
116
|
|
|
114
117
|
ready = sum(1 for e in engines.values() if e.get("status") != "fail")
|
|
118
|
+
# "healthy" ignores OPTIONAL engines (graph): a repo with semantic + lsp ready is healthy even
|
|
119
|
+
# without the external graph binary. `ready`/`total` stay literal (all three) for transparency.
|
|
120
|
+
healthy = all(
|
|
121
|
+
e.get("status") != "fail" for n, e in engines.items() if n not in _OPTIONAL_ENGINES
|
|
122
|
+
)
|
|
115
123
|
return {
|
|
116
124
|
"ok": True,
|
|
117
125
|
"project_root": root,
|
|
118
126
|
"deep": bool(deep),
|
|
119
127
|
"treesitter": treesitter,
|
|
120
|
-
"summary": {"ready": ready, "total": len(engines),
|
|
121
|
-
"healthy": all(e.get("status") != "fail" for e in engines.values())},
|
|
128
|
+
"summary": {"ready": ready, "total": len(engines), "healthy": healthy},
|
|
122
129
|
"engines": engines,
|
|
123
130
|
}
|
|
124
131
|
|
|
@@ -168,10 +175,16 @@ def render_doctor_text(report: dict) -> str:
|
|
|
168
175
|
tail = "" if report.get("deep") else c.dim(" (run with --deep to boot-check serena)")
|
|
169
176
|
out.append("")
|
|
170
177
|
out.append(f" {count} engines ready for this repo.{tail}")
|
|
178
|
+
opt_down = [n for n in _ENGINES
|
|
179
|
+
if n in _OPTIONAL_ENGINES and (engines.get(n) or {}).get("status") == "fail"]
|
|
180
|
+
if opt_down:
|
|
181
|
+
out.append(" " + c.dim(
|
|
182
|
+
f"({', '.join(opt_down)} optional — an external backend; codeintel works without it)"
|
|
183
|
+
))
|
|
171
184
|
if healthy is False:
|
|
172
185
|
out.append(" " + c.dim(
|
|
173
|
-
"tip: `codeintel setup --
|
|
174
|
-
"
|
|
186
|
+
"tip: `codeintel setup --all` installs + indexes everything automatable in one command; "
|
|
187
|
+
"each fix: line above has the per-engine command."
|
|
175
188
|
))
|
|
176
189
|
if report.get("treesitter") is False:
|
|
177
190
|
out.append(" " + c.dim(
|
|
@@ -17,12 +17,25 @@ from codeintel import doctor
|
|
|
17
17
|
_ENGINES = ("graph", "lsp", "semantic")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _platform_tag() -> str:
|
|
21
|
+
"""Best-effort ``System/arch`` (e.g. ``Darwin/arm64``) for platform-specific guidance."""
|
|
22
|
+
try:
|
|
23
|
+
import platform
|
|
24
|
+
return f"{platform.system() or 'your-OS'}/{platform.machine() or 'your-arch'}"
|
|
25
|
+
except Exception:
|
|
26
|
+
return "your platform"
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
def _guidance_for(engine: str, probe: dict) -> str:
|
|
21
30
|
"""One-line install instructions for an engine that is not installed."""
|
|
22
31
|
try:
|
|
23
32
|
if engine == "graph":
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
# codebase-memory-mcp is a standalone native binary distributed by its own project —
|
|
34
|
+
# codeintel can't (and shouldn't) auto-download a third-party binary. Frame it as the
|
|
35
|
+
# OPTIONAL add-on it is: the tool is fully usable on semantic + LSP without it.
|
|
36
|
+
return (f"OPTIONAL (adds who-calls / impact / hotspots / changed): install the "
|
|
37
|
+
f"codebase-memory-mcp binary for {_platform_tag()} on PATH, then re-run setup — "
|
|
38
|
+
f"codeintel works without it")
|
|
26
39
|
if engine == "lsp":
|
|
27
40
|
return "install uv (provides uvx): pip install uv — serena is fetched on first use"
|
|
28
41
|
if engine == "semantic":
|
|
@@ -123,10 +136,10 @@ def run_setup(
|
|
|
123
136
|
root = str(project_root or "")
|
|
124
137
|
|
|
125
138
|
try:
|
|
126
|
-
if warm_lsp:
|
|
127
|
-
print(" first serena launch fetches it via uvx; this can be slow the first time…", file=out)
|
|
128
139
|
try:
|
|
129
|
-
|
|
140
|
+
# Shallow diagnose only — the warm/deep serena boot happens AFTER the install loop (below)
|
|
141
|
+
# so a freshly-installed uv is visible to it (see the warm_lsp block).
|
|
142
|
+
report0 = doctor.run_doctor(root, deep=False)
|
|
130
143
|
engines0 = report0.get("engines", {}) if isinstance(report0, dict) else {}
|
|
131
144
|
except Exception as exc:
|
|
132
145
|
engines0 = {}
|
|
@@ -136,11 +149,18 @@ def run_setup(
|
|
|
136
149
|
if probe.get("installed") is False:
|
|
137
150
|
_step(f"{name}: preflight", probe.get("status", "fail"), _guidance_for(name, probe))
|
|
138
151
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
# Idempotent installs: skip when the engine already reports installed, so `--all` and re-runs
|
|
153
|
+
# don't reinstall (lsp installed ⇒ uv/uvx present; semantic installed ⇒ deps present).
|
|
154
|
+
lsp_installed = (engines0.get("lsp") or {}).get("installed") is True
|
|
155
|
+
semantic_installed = (engines0.get("semantic") or {}).get("installed") is True
|
|
156
|
+
for flag, pkg_args, step_name, already in (
|
|
157
|
+
(install_uv, ["uv"], "install uv", lsp_installed),
|
|
158
|
+
(install_deps, ["-e", "."], "install deps (-e .)", semantic_installed),
|
|
142
159
|
):
|
|
143
160
|
if flag:
|
|
161
|
+
if already:
|
|
162
|
+
_step(step_name, "ok", "already satisfied — skipped")
|
|
163
|
+
continue
|
|
144
164
|
r = _pip_install(pkg_args, out=out)
|
|
145
165
|
_step(step_name, "ok" if r["ok"] else "fail", r["detail"])
|
|
146
166
|
|
|
@@ -157,8 +177,17 @@ def run_setup(
|
|
|
157
177
|
_step("index: graph", "warn", f"graph reindex skipped ({type(exc).__name__})")
|
|
158
178
|
|
|
159
179
|
if warm_lsp:
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
# Warm with a FRESH deep probe AFTER the install loop: on a fresh machine uv was just
|
|
181
|
+
# installed above, so the pre-install preflight would report lsp missing — emitting a
|
|
182
|
+
# stale, self-contradictory "warm lsp: fail" under "install uv: ok". Re-probe so the boot
|
|
183
|
+
# actually runs against the now-present uvx.
|
|
184
|
+
print(" first serena launch fetches it via uvx; this can be slow the first time…", file=out)
|
|
185
|
+
try:
|
|
186
|
+
from codeintel.providers.lsp import LspProvider
|
|
187
|
+
wl = LspProvider().probe(root, deep=True, timeout_s=lsp_warm_timeout_s)
|
|
188
|
+
_step("warm lsp", doctor._status_for({**wl, "engine": "lsp"}), wl.get("detail", ""))
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
_step("warm lsp", "warn", f"warm attempt failed ({type(exc).__name__})")
|
|
162
191
|
|
|
163
192
|
try:
|
|
164
193
|
final_doctor = doctor.run_doctor(root)
|
|
@@ -170,6 +199,31 @@ def run_setup(
|
|
|
170
199
|
"detail": f"setup failed ({type(exc).__name__})"}
|
|
171
200
|
|
|
172
201
|
|
|
202
|
+
def _next_steps(doctor_report: dict, root: str) -> list[str]:
|
|
203
|
+
"""The crisp 'what's left' list after a setup pass, computed from the final doctor state —
|
|
204
|
+
so a user knows the ONE remaining action instead of parsing the engine table."""
|
|
205
|
+
try:
|
|
206
|
+
out: list[str] = []
|
|
207
|
+
engines = doctor_report.get("engines") if isinstance(doctor_report, dict) else None
|
|
208
|
+
engines = engines if isinstance(engines, dict) else {}
|
|
209
|
+
_get = lambda name: engines.get(name) if isinstance(engines.get(name), dict) else {}
|
|
210
|
+
sem, lsp, graph = _get("semantic"), _get("lsp"), _get("graph")
|
|
211
|
+
if sem.get("installed") is not True:
|
|
212
|
+
out.append("Semantic engine deps missing — pip install fastembed sqlite-vec")
|
|
213
|
+
elif sem.get("repo_indexed") is False:
|
|
214
|
+
out.append(f"Index this repo — codeintel index {root}")
|
|
215
|
+
if lsp.get("installed") is not True:
|
|
216
|
+
out.append("LSP engine — pip install uv (serena auto-fetched on first use)")
|
|
217
|
+
if graph.get("installed") is not True:
|
|
218
|
+
out.append(f"Graph engine (OPTIONAL — who-calls/impact/hotspots/changed) — put the "
|
|
219
|
+
f"codebase-memory-mcp binary for {_platform_tag()} on PATH")
|
|
220
|
+
# Always the last mile: an installed+indexed tool does nothing until the agent knows about it.
|
|
221
|
+
out.append("Make your AI agent use it — codeintel install")
|
|
222
|
+
return out
|
|
223
|
+
except Exception:
|
|
224
|
+
return []
|
|
225
|
+
|
|
226
|
+
|
|
173
227
|
def render_setup_text(report: dict) -> str:
|
|
174
228
|
"""Human CLI view: a ``[n/N]`` step list, the doctor table, then the overall summary."""
|
|
175
229
|
from codeintel.term import c # imported at call time so the CLI's term.configure() is honored
|
|
@@ -184,14 +238,24 @@ def render_setup_text(report: dict) -> str:
|
|
|
184
238
|
tail = f" {c.dim(str(detail))}" if detail else ""
|
|
185
239
|
name = c.bold(str(step.get("name", "")))
|
|
186
240
|
lines.append(f" [{i}/{n}] {c.glyph(step.get('status', 'na'))} {name}{tail}")
|
|
187
|
-
|
|
188
|
-
|
|
241
|
+
_ACTION_STEPS = {"install uv", "install deps (-e .)", "index: semantic", "index: graph", "warm lsp"}
|
|
242
|
+
if not any(s.get("name") in _ACTION_STEPS for s in steps):
|
|
243
|
+
lines.append(c.dim(" (diagnose only — run `codeintel setup --all` to install + index "
|
|
244
|
+
"everything automatically)"))
|
|
189
245
|
lines.append("")
|
|
190
246
|
try:
|
|
191
247
|
from codeintel.doctor import render_doctor_text
|
|
192
248
|
lines.append(render_doctor_text(report.get("doctor") or {}))
|
|
193
249
|
except Exception:
|
|
194
250
|
lines.append(c.dim("(doctor report unavailable)"))
|
|
251
|
+
|
|
252
|
+
nexts = _next_steps(report.get("doctor") or {}, root)
|
|
253
|
+
if nexts:
|
|
254
|
+
lines.append("")
|
|
255
|
+
lines.append(c.bold("Next:"))
|
|
256
|
+
for step in nexts:
|
|
257
|
+
lines.append(" " + c.cyan("→") + " " + step)
|
|
258
|
+
|
|
195
259
|
lines.append("")
|
|
196
260
|
lines.append(c.green("setup finished") if report.get("ok") else c.red("setup did not complete cleanly"))
|
|
197
261
|
return "\n".join(lines)
|
|
@@ -83,6 +83,24 @@ def test_run_doctor_reports_treesitter_availability():
|
|
|
83
83
|
assert isinstance(r.get("treesitter"), bool) # always reported (bool), never raises
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def test_healthy_ignores_optional_graph_engine():
|
|
87
|
+
# graph is optional (external binary) — its absence must NOT mark a repo unhealthy or exit 1.
|
|
88
|
+
class _Stub:
|
|
89
|
+
available = True
|
|
90
|
+
def __init__(self, installed, runnable=True, indexed=True):
|
|
91
|
+
self._d = {"installed": installed, "runnable": runnable, "repo_indexed": indexed,
|
|
92
|
+
"detail": "", "remediation": None}
|
|
93
|
+
def probe(self, *a, **k):
|
|
94
|
+
return dict(self._d)
|
|
95
|
+
|
|
96
|
+
r = doctor.run_doctor("/repo", graph=_Stub(False, False, False),
|
|
97
|
+
lsp=_Stub(True), semantic=_Stub(True))
|
|
98
|
+
assert r["engines"]["graph"]["status"] == "fail"
|
|
99
|
+
assert r["summary"]["healthy"] is True # semantic + lsp ready ⇒ healthy despite graph
|
|
100
|
+
text = doctor.render_doctor_text(r)
|
|
101
|
+
assert "optional" in text # render frames graph as optional, not a failure
|
|
102
|
+
|
|
103
|
+
|
|
86
104
|
# --------------------------------------------------------------------------- #
|
|
87
105
|
# never-raise + no-hang orchestration
|
|
88
106
|
# --------------------------------------------------------------------------- #
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Onboarding / setup tests — real-boundary, never-raise, bounded.
|
|
2
|
+
|
|
3
|
+
Mirrors tests/test_doctor.py's philosophy: pip install and semantic indexing are exercised
|
|
4
|
+
against a real subprocess / a real SemanticDb rather than fabricated mocks, so a genuine
|
|
5
|
+
contract drift (e.g. pip's actual CLI shape) cannot hide behind a mock that agrees with itself.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from codeintel import onboarding
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_pip_install_nonexistent_never_raises():
|
|
19
|
+
result = onboarding._pip_install(["codeintel-nonexistent-pkg-xyz-999"], timeout_s=30)
|
|
20
|
+
assert isinstance(result, dict)
|
|
21
|
+
assert result["ok"] is False
|
|
22
|
+
assert "detail" in result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_run_setup_never_raises_and_bounded(tmp_path, monkeypatch):
|
|
26
|
+
monkeypatch.setattr("codeintel.providers.graph.shutil.which", lambda x: None)
|
|
27
|
+
start = time.monotonic()
|
|
28
|
+
report = onboarding.run_setup(tmp_path, do_index=False, warm_lsp=False)
|
|
29
|
+
elapsed = time.monotonic() - start
|
|
30
|
+
assert isinstance(report, dict)
|
|
31
|
+
assert "doctor" in report
|
|
32
|
+
assert elapsed < 12
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_setup_index_timeout_bounded(tmp_path, monkeypatch):
|
|
36
|
+
def _slow_index(self, project_root):
|
|
37
|
+
time.sleep(5)
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
monkeypatch.setattr("codeintel.indexer.Indexer.index", _slow_index)
|
|
41
|
+
start = time.monotonic()
|
|
42
|
+
result = onboarding._bounded_index(str(tmp_path), timeout_s=1, out=io.StringIO())
|
|
43
|
+
elapsed = time.monotonic() - start
|
|
44
|
+
assert result["status"] == "timeout"
|
|
45
|
+
assert elapsed < 2.5
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_guidance_for_missing_graph():
|
|
49
|
+
probe = {"installed": False}
|
|
50
|
+
guidance = onboarding._guidance_for("graph", probe)
|
|
51
|
+
assert "codebase-memory-mcp" in guidance
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_setup_index_real_db(tmp_path, monkeypatch):
|
|
55
|
+
pytest.importorskip("fastembed")
|
|
56
|
+
|
|
57
|
+
repo = tmp_path / "repo"
|
|
58
|
+
repo.mkdir()
|
|
59
|
+
(repo / "sample.py").write_text("def greet():\n return 'hello'\n")
|
|
60
|
+
|
|
61
|
+
db_path = tmp_path / "semantic.db"
|
|
62
|
+
monkeypatch.setattr("codeintel.semantic_db.default_db_path", lambda *a, **k: str(db_path))
|
|
63
|
+
|
|
64
|
+
report = onboarding.run_setup(str(repo), do_index=True, out=io.StringIO())
|
|
65
|
+
assert report["ok"] is True
|
|
66
|
+
|
|
67
|
+
from codeintel.semantic_db import SemanticDb
|
|
68
|
+
|
|
69
|
+
db = SemanticDb(str(db_path))
|
|
70
|
+
try:
|
|
71
|
+
real_repo = os.path.realpath(str(repo))
|
|
72
|
+
row = db.conn().execute(
|
|
73
|
+
"SELECT COUNT(*) FROM chunk_hashes WHERE project_root = ?", (real_repo,)
|
|
74
|
+
).fetchone()
|
|
75
|
+
count = row[0] if row else 0
|
|
76
|
+
finally:
|
|
77
|
+
db.close()
|
|
78
|
+
assert count > 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# --------------------------------------------------------------------------- #
|
|
82
|
+
# one-command setup (--all): idempotency, optional-graph framing, next steps
|
|
83
|
+
# --------------------------------------------------------------------------- #
|
|
84
|
+
|
|
85
|
+
def test_guidance_for_graph_is_optional_and_platform_aware():
|
|
86
|
+
g = onboarding._guidance_for("graph", {"installed": False})
|
|
87
|
+
assert "codebase-memory-mcp" in g # (kept from the original guidance test)
|
|
88
|
+
assert "OPTIONAL" in g # framed as the optional add-on it is
|
|
89
|
+
assert "/" in g # carries a platform tag like Darwin/arm64
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_run_setup_skips_install_when_engine_already_installed(tmp_path, monkeypatch):
|
|
93
|
+
# Idempotency: `--all`/re-runs must NOT reinstall uv when the LSP engine already reports installed.
|
|
94
|
+
monkeypatch.setattr("codeintel.providers.graph.shutil.which", lambda x: None)
|
|
95
|
+
monkeypatch.setattr("codeintel.providers.lsp.shutil.which", lambda x: "/fake/uvx") # lsp installed
|
|
96
|
+
|
|
97
|
+
def _boom(*a, **k):
|
|
98
|
+
raise AssertionError("_pip_install must not run when the engine is already satisfied")
|
|
99
|
+
monkeypatch.setattr(onboarding, "_pip_install", _boom)
|
|
100
|
+
|
|
101
|
+
report = onboarding.run_setup(tmp_path, install_uv=True, do_index=False, warm_lsp=False)
|
|
102
|
+
step = next(s for s in report["steps"] if s["name"] == "install uv")
|
|
103
|
+
assert step["status"] == "ok"
|
|
104
|
+
assert "already satisfied" in step["detail"]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_next_steps_lists_remaining_actions():
|
|
108
|
+
doctor_report = {"engines": {
|
|
109
|
+
"semantic": {"installed": True, "repo_indexed": False},
|
|
110
|
+
"lsp": {"installed": False},
|
|
111
|
+
"graph": {"installed": False},
|
|
112
|
+
}}
|
|
113
|
+
joined = " | ".join(onboarding._next_steps(doctor_report, "/repo"))
|
|
114
|
+
assert "codeintel index /repo" in joined # indexed=False → index step
|
|
115
|
+
assert "pip install uv" in joined # lsp missing
|
|
116
|
+
assert "codebase-memory-mcp" in joined and "OPTIONAL" in joined # graph = optional
|
|
117
|
+
assert "codeintel install" in joined # always the last mile
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_next_steps_all_ready_is_just_agent_registration():
|
|
121
|
+
doctor_report = {"engines": {
|
|
122
|
+
"semantic": {"installed": True, "repo_indexed": True},
|
|
123
|
+
"lsp": {"installed": True},
|
|
124
|
+
"graph": {"installed": True},
|
|
125
|
+
}}
|
|
126
|
+
assert onboarding._next_steps(doctor_report, "/repo") == ["Make your AI agent use it — codeintel install"]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_render_diagnose_only_points_to_all(monkeypatch):
|
|
130
|
+
# Bare setup (no action flags) must point the user at the one-command form + show Next steps.
|
|
131
|
+
monkeypatch.setattr("codeintel.providers.graph.shutil.which", lambda x: None)
|
|
132
|
+
report = onboarding.run_setup("/tmp/x", do_index=False, warm_lsp=False)
|
|
133
|
+
text = onboarding.render_setup_text(report)
|
|
134
|
+
assert "setup --all" in text
|
|
135
|
+
assert "Next:" in text
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_warm_lsp_reprobes_after_install_not_stale_preflight(tmp_path, monkeypatch):
|
|
139
|
+
# The warm step must re-probe (deep) AFTER installs, not reuse the pre-install shallow preflight —
|
|
140
|
+
# else a fresh machine prints "warm lsp: fail" directly under "install uv: ok".
|
|
141
|
+
monkeypatch.setattr("codeintel.providers.graph.shutil.which", lambda x: None)
|
|
142
|
+
|
|
143
|
+
def _probe(self, root, deep=False, timeout_s=0):
|
|
144
|
+
if deep: # the dedicated warm boot, run AFTER the install loop
|
|
145
|
+
return {"installed": True, "runnable": True, "repo_indexed": None,
|
|
146
|
+
"detail": "READY (warmed)", "remediation": None}
|
|
147
|
+
return {"installed": False, "runnable": False, "repo_indexed": None,
|
|
148
|
+
"detail": "no uvx (preflight)", "remediation": None}
|
|
149
|
+
monkeypatch.setattr("codeintel.providers.lsp.LspProvider.probe", _probe)
|
|
150
|
+
|
|
151
|
+
report = onboarding.run_setup(tmp_path, warm_lsp=True, do_index=False, lsp_warm_timeout_s=5)
|
|
152
|
+
warm = next(s for s in report["steps"] if s["name"] == "warm lsp")
|
|
153
|
+
assert "READY (warmed)" in warm["detail"] # reflects the fresh deep probe, not the stale preflight
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_next_steps_never_raises_on_malformed_doctor():
|
|
157
|
+
# Hardened like every sibling helper — malformed doctor input degrades to a list, never raises.
|
|
158
|
+
for bad in ({"engines": None}, {"engines": {"semantic": "x"}}, {}, None,
|
|
159
|
+
{"engines": 5}, {"engines": {"lsp": 7}}):
|
|
160
|
+
assert isinstance(onboarding._next_steps(bad, "/repo"), list)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.9.0"
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"""Onboarding / setup tests — real-boundary, never-raise, bounded.
|
|
2
|
-
|
|
3
|
-
Mirrors tests/test_doctor.py's philosophy: pip install and semantic indexing are exercised
|
|
4
|
-
against a real subprocess / a real SemanticDb rather than fabricated mocks, so a genuine
|
|
5
|
-
contract drift (e.g. pip's actual CLI shape) cannot hide behind a mock that agrees with itself.
|
|
6
|
-
"""
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import io
|
|
10
|
-
import os
|
|
11
|
-
import time
|
|
12
|
-
|
|
13
|
-
import pytest
|
|
14
|
-
|
|
15
|
-
from codeintel import onboarding
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_pip_install_nonexistent_never_raises():
|
|
19
|
-
result = onboarding._pip_install(["codeintel-nonexistent-pkg-xyz-999"], timeout_s=30)
|
|
20
|
-
assert isinstance(result, dict)
|
|
21
|
-
assert result["ok"] is False
|
|
22
|
-
assert "detail" in result
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_run_setup_never_raises_and_bounded(tmp_path, monkeypatch):
|
|
26
|
-
monkeypatch.setattr("codeintel.providers.graph.shutil.which", lambda x: None)
|
|
27
|
-
start = time.monotonic()
|
|
28
|
-
report = onboarding.run_setup(tmp_path, do_index=False, warm_lsp=False)
|
|
29
|
-
elapsed = time.monotonic() - start
|
|
30
|
-
assert isinstance(report, dict)
|
|
31
|
-
assert "doctor" in report
|
|
32
|
-
assert elapsed < 12
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def test_setup_index_timeout_bounded(tmp_path, monkeypatch):
|
|
36
|
-
def _slow_index(self, project_root):
|
|
37
|
-
time.sleep(5)
|
|
38
|
-
return 0
|
|
39
|
-
|
|
40
|
-
monkeypatch.setattr("codeintel.indexer.Indexer.index", _slow_index)
|
|
41
|
-
start = time.monotonic()
|
|
42
|
-
result = onboarding._bounded_index(str(tmp_path), timeout_s=1, out=io.StringIO())
|
|
43
|
-
elapsed = time.monotonic() - start
|
|
44
|
-
assert result["status"] == "timeout"
|
|
45
|
-
assert elapsed < 2.5
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def test_guidance_for_missing_graph():
|
|
49
|
-
probe = {"installed": False}
|
|
50
|
-
guidance = onboarding._guidance_for("graph", probe)
|
|
51
|
-
assert "codebase-memory-mcp" in guidance
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_setup_index_real_db(tmp_path, monkeypatch):
|
|
55
|
-
pytest.importorskip("fastembed")
|
|
56
|
-
|
|
57
|
-
repo = tmp_path / "repo"
|
|
58
|
-
repo.mkdir()
|
|
59
|
-
(repo / "sample.py").write_text("def greet():\n return 'hello'\n")
|
|
60
|
-
|
|
61
|
-
db_path = tmp_path / "semantic.db"
|
|
62
|
-
monkeypatch.setattr("codeintel.semantic_db.default_db_path", lambda *a, **k: str(db_path))
|
|
63
|
-
|
|
64
|
-
report = onboarding.run_setup(str(repo), do_index=True, out=io.StringIO())
|
|
65
|
-
assert report["ok"] is True
|
|
66
|
-
|
|
67
|
-
from codeintel.semantic_db import SemanticDb
|
|
68
|
-
|
|
69
|
-
db = SemanticDb(str(db_path))
|
|
70
|
-
try:
|
|
71
|
-
real_repo = os.path.realpath(str(repo))
|
|
72
|
-
row = db.conn().execute(
|
|
73
|
-
"SELECT COUNT(*) FROM chunk_hashes WHERE project_root = ?", (real_repo,)
|
|
74
|
-
).fetchone()
|
|
75
|
-
count = row[0] if row else 0
|
|
76
|
-
finally:
|
|
77
|
-
db.close()
|
|
78
|
-
assert count > 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|