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.
Files changed (70) hide show
  1. {codecortex-0.9.0/src/codecortex.egg-info → codecortex-0.10.0}/PKG-INFO +21 -14
  2. {codecortex-0.9.0 → codecortex-0.10.0}/README.md +20 -13
  3. {codecortex-0.9.0 → codecortex-0.10.0/src/codecortex.egg-info}/PKG-INFO +21 -14
  4. codecortex-0.10.0/src/codeintel/__init__.py +1 -0
  5. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/__main__.py +8 -4
  6. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/doctor.py +17 -4
  7. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/onboarding.py +76 -12
  8. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_doctor.py +18 -0
  9. codecortex-0.10.0/tests/test_onboarding.py +160 -0
  10. codecortex-0.9.0/src/codeintel/__init__.py +0 -1
  11. codecortex-0.9.0/tests/test_onboarding.py +0 -78
  12. {codecortex-0.9.0 → codecortex-0.10.0}/LICENSE +0 -0
  13. {codecortex-0.9.0 → codecortex-0.10.0}/pyproject.toml +0 -0
  14. {codecortex-0.9.0 → codecortex-0.10.0}/setup.cfg +0 -0
  15. {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/SOURCES.txt +0 -0
  16. {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  17. {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  18. {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/requires.txt +0 -0
  19. {codecortex-0.9.0 → codecortex-0.10.0}/src/codecortex.egg-info/top_level.txt +0 -0
  20. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/auth.py +0 -0
  21. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/cache.py +0 -0
  22. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/config.py +0 -0
  23. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/gateway.py +0 -0
  24. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/http_server.py +0 -0
  25. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/indexer.py +0 -0
  26. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/injector.py +0 -0
  27. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/installer.py +0 -0
  28. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/logconfig.py +0 -0
  29. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/mapper.py +0 -0
  30. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/metrics.py +0 -0
  31. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/policy.py +0 -0
  32. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/provider.py +0 -0
  33. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/__init__.py +0 -0
  34. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/graph.py +0 -0
  35. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/lsp.py +0 -0
  36. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/none.py +0 -0
  37. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/providers/semantic.py +0 -0
  38. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/reindexer.py +0 -0
  39. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/reset.py +0 -0
  40. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/searcher.py +0 -0
  41. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/semantic_db.py +0 -0
  42. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/server.py +0 -0
  43. {codecortex-0.9.0 → codecortex-0.10.0}/src/codeintel/term.py +0 -0
  44. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_cache.py +0 -0
  45. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_chunking.py +0 -0
  46. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_config.py +0 -0
  47. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_e2e.py +0 -0
  48. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_enterprise.py +0 -0
  49. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_gateway.py +0 -0
  50. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_graph_provider.py +0 -0
  51. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_graph_real.py +0 -0
  52. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_graph_stdin.py +0 -0
  53. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_hardening.py +0 -0
  54. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_http_auth.py +0 -0
  55. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_http_server.py +0 -0
  56. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_installer.py +0 -0
  57. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_integration.py +0 -0
  58. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_lsp_provider.py +0 -0
  59. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_lsp_real.py +0 -0
  60. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_mapper.py +0 -0
  61. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_mcp_server.py +0 -0
  62. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_model_dimension.py +0 -0
  63. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_never_raise.py +0 -0
  64. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_rbac.py +0 -0
  65. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_reindexer.py +0 -0
  66. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_rerank.py +0 -0
  67. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_reset.py +0 -0
  68. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_semantic_provider.py +0 -0
  69. {codecortex-0.9.0 → codecortex-0.10.0}/tests/test_term.py +0 -0
  70. {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.9.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. The **graph**
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]` | Check backends + optionally index this repo; ends with a health report |
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. The **graph**
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]` | Check backends + optionally index this repo; ends with a health report |
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.9.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. The **graph**
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]` | Check backends + optionally index this repo; ends with a health report |
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 --install-uv --install-deps --index` bootstraps the "
174
- "pip-installable backends; each fix: line above has the per-engine command."
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
- return ("install codebase-memory-mcp (a platform-specific binary) and ensure "
25
- "it's on PATH see its project; then `codeintel index`")
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
- report0 = doctor.run_doctor(root, deep=warm_lsp, lsp_deep_timeout_s=lsp_warm_timeout_s)
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
- for flag, pkg_args, step_name in (
140
- (install_uv, ["uv"], "install uv"),
141
- (install_deps, ["-e", "."], "install deps (-e .)"),
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
- lsp = engines0.get("lsp") or {}
161
- _step("warm lsp", lsp.get("status", "warn"), lsp.get("detail", ""))
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
- if not steps:
188
- lines.append(c.dim(" (nothing to do no opt-in flags set)"))
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