codecortex 0.2.0__tar.gz → 0.2.1__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 (54) hide show
  1. {codecortex-0.2.0/src/codecortex.egg-info → codecortex-0.2.1}/PKG-INFO +59 -5
  2. {codecortex-0.2.0 → codecortex-0.2.1}/README.md +58 -4
  3. {codecortex-0.2.0 → codecortex-0.2.1/src/codecortex.egg-info}/PKG-INFO +59 -5
  4. codecortex-0.2.1/src/codeintel/__init__.py +1 -0
  5. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/injector.py +5 -1
  6. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/installer.py +11 -1
  7. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/semantic_db.py +12 -0
  8. codecortex-0.2.0/src/codeintel/__init__.py +0 -1
  9. {codecortex-0.2.0 → codecortex-0.2.1}/LICENSE +0 -0
  10. {codecortex-0.2.0 → codecortex-0.2.1}/pyproject.toml +0 -0
  11. {codecortex-0.2.0 → codecortex-0.2.1}/setup.cfg +0 -0
  12. {codecortex-0.2.0 → codecortex-0.2.1}/src/codecortex.egg-info/SOURCES.txt +0 -0
  13. {codecortex-0.2.0 → codecortex-0.2.1}/src/codecortex.egg-info/dependency_links.txt +0 -0
  14. {codecortex-0.2.0 → codecortex-0.2.1}/src/codecortex.egg-info/entry_points.txt +0 -0
  15. {codecortex-0.2.0 → codecortex-0.2.1}/src/codecortex.egg-info/requires.txt +0 -0
  16. {codecortex-0.2.0 → codecortex-0.2.1}/src/codecortex.egg-info/top_level.txt +0 -0
  17. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/__main__.py +0 -0
  18. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/cache.py +0 -0
  19. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/config.py +0 -0
  20. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/doctor.py +0 -0
  21. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/gateway.py +0 -0
  22. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/http_server.py +0 -0
  23. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/indexer.py +0 -0
  24. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/mapper.py +0 -0
  25. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/onboarding.py +0 -0
  26. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/policy.py +0 -0
  27. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/provider.py +0 -0
  28. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/providers/__init__.py +0 -0
  29. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/providers/graph.py +0 -0
  30. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/providers/lsp.py +0 -0
  31. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/providers/none.py +0 -0
  32. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/providers/semantic.py +0 -0
  33. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/reindexer.py +0 -0
  34. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/reset.py +0 -0
  35. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/searcher.py +0 -0
  36. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/server.py +0 -0
  37. {codecortex-0.2.0 → codecortex-0.2.1}/src/codeintel/term.py +0 -0
  38. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_doctor.py +0 -0
  39. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_e2e.py +0 -0
  40. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_gateway.py +0 -0
  41. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_graph_provider.py +0 -0
  42. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_graph_real.py +0 -0
  43. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_graph_stdin.py +0 -0
  44. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_http_server.py +0 -0
  45. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_integration.py +0 -0
  46. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_lsp_provider.py +0 -0
  47. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_lsp_real.py +0 -0
  48. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_mapper.py +0 -0
  49. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_never_raise.py +0 -0
  50. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_onboarding.py +0 -0
  51. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_reindexer.py +0 -0
  52. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_reset.py +0 -0
  53. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_semantic_provider.py +0 -0
  54. {codecortex-0.2.0 → codecortex-0.2.1}/tests/test_term.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
5
5
  Author: Shammai Hamilton
6
6
  License-Expression: MIT
@@ -33,10 +33,55 @@ Dynamic: license-file
33
33
 
34
34
  # codeintel
35
35
 
36
- A unified code-intelligence gateway — graph + LSP + semantic that gives any coding agent a single safe API to search, trace, and understand any codebase.
36
+ **One MCP tool that lets a coding agent search, trace, and *understand* a codebase structurally, not by grepping.** codeintel unifies three engines — a call/import **graph**, an **LSP** for exact symbols, and **semantic** embedding search behind a single `code.query` call that routes to the right engine, caches the answer, and **never throws**. The agent always gets back a clean, well-formed result to reason over.
37
37
 
38
38
  [![CI](https://github.com/hamilton-sky/codeintel/actions/workflows/ci.yml/badge.svg)](https://github.com/hamilton-sky/codeintel/actions/workflows/ci.yml)
39
39
 
40
+ ## Why an agent needs it
41
+
42
+ Without structural tools, an agent dropped into unfamiliar code falls back on `grep` and reads whole files to reconstruct relationships by hand — burning tokens, missing call sites, and guessing at blast radius before it edits anything. codeintel answers those questions directly instead:
43
+
44
+ - **"What calls this? What breaks if I change it?"** → the real call graph, which catches cross-file and module-level callers a text search silently misses.
45
+ - **"Where is this symbol defined, and everywhere it's used?"** → the language server, with exact locations.
46
+ - **"Where's the code that does X?"** (when you don't know the name) → semantic search over the repo.
47
+ - **Always a clean answer.** Every call returns the same JSON envelope. A missing or broken backend degrades to a safe `null` *with a reason* — so the agent falls back to grep instead of crashing on an exception it can't reason its way out of.
48
+
49
+ Net effect: fewer, sharper tool calls, less re-reading, and an agent that can see *structure* — callers, impact, call chains — that plain search can't.
50
+
51
+ ## What your agent can ask
52
+
53
+ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the default) codeintel picks the engine per operation:
54
+
55
+ | Ask | `op` | Engine (auto) | Comes back as |
56
+ |---|---|---|---|
57
+ | Find code by meaning ("auth middleware") | `search` | semantic | ranked `path:line │ snippet` hits |
58
+ | A symbol's definition **and** all references | `symbol` | lsp | definition body + reference list |
59
+ | Who calls this? | `callers` | graph | caller symbols + files |
60
+ | What does this call? | `callees` | graph | callee symbols + files |
61
+ | Blast radius of a change | `impact` | graph | callers **and** callees together |
62
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops |
63
+ | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
64
+ | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
65
+ | Everything about one symbol | `context` | graph + lsp | both views merged |
66
+
67
+ Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
68
+
69
+ **Example — "who uses `safe_null_result`?"**
70
+
71
+ ```jsonc
72
+ // request
73
+ { "op": "callers", "target": "safe_null_result", "engine": "auto" }
74
+
75
+ // response — always this exact envelope; `result` is ready-to-read markdown
76
+ {
77
+ "ok": true, "op": "callers", "target": "safe_null_result",
78
+ "engine": "graph", "cached": false,
79
+ "result": "## Callers of safe_null_result (7)\n- …gateway [USAGE] (src/codeintel/gateway.py)\n- …providers.graph [USAGE] (src/codeintel/providers/graph.py)\n- …server [USAGE] (src/codeintel/server.py)\n- … (4 more)"
80
+ }
81
+ ```
82
+
83
+ The agent hands `result` straight to the model. If the graph backend isn't installed, the identical call returns `"result": null, "reason": "engine-unavailable"` — no exception, and the agent just falls back to its own search.
84
+
40
85
  ## Quickstart
41
86
 
42
87
  ```bash
@@ -146,7 +191,7 @@ backend = "auto" # auto | graph | lsp | semantic
146
191
  semantic = "on" # on | off
147
192
  reindex = "on-demand" # on-demand | never
148
193
  cosine_floor = 0.25 # minimum similarity score for semantic hits
149
- max_chunks = 500 # max chunks to embed per project
194
+ max_chunks = 500 # max chunks to embed per file
150
195
  model = "BAAI/bge-small-en-v1.5" # fastembed embedding model
151
196
  ```
152
197
 
@@ -170,7 +215,16 @@ Not sure what's installed? `codeintel doctor` reports exactly which backends are
170
215
 
171
216
  ## For agents
172
217
 
173
- Start the HTTP server, then POST queries to `/code/query`:
218
+ Register codeintel as an MCP server (`codeintel install`) and the agent gets four tools:
219
+
220
+ | MCP tool | HTTP equivalent | Purpose |
221
+ |---|---|---|
222
+ | `code.query` | `POST /code/query` | The main call — search, trace, understand (the `op` table above) |
223
+ | `code.status` | `GET /code/status` | Which engines are live + whether an index exists |
224
+ | `code.doctor` | `POST /code/doctor` | Per-engine health + repo index status, with a fix for each gap |
225
+ | `code.map` | — | Generate/refresh `CODE_INTEL.md`, a static orientation file for hosts without MCP |
226
+
227
+ Over MCP the agent calls `code.query` directly. Over HTTP, start the server and POST to `/code/query`:
174
228
 
175
229
  ```bash
176
230
  codeintel serve-http & # listens on 127.0.0.1:8766 by default
@@ -202,5 +256,5 @@ The response is always JSON-safe. Check `result["result"] is not None` before us
202
256
  git clone https://github.com/hamilton-sky/codeintel.git
203
257
  cd codeintel
204
258
  pip install -e .[dev]
205
- pytest tests/ -q # full suite, ~1s
259
+ pytest tests/ -q # full suite (~15s — includes live graph/LSP backend tests)
206
260
  ```
@@ -1,9 +1,54 @@
1
1
  # codeintel
2
2
 
3
- A unified code-intelligence gateway — graph + LSP + semantic that gives any coding agent a single safe API to search, trace, and understand any codebase.
3
+ **One MCP tool that lets a coding agent search, trace, and *understand* a codebase structurally, not by grepping.** codeintel unifies three engines — a call/import **graph**, an **LSP** for exact symbols, and **semantic** embedding search behind a single `code.query` call that routes to the right engine, caches the answer, and **never throws**. The agent always gets back a clean, well-formed result to reason over.
4
4
 
5
5
  [![CI](https://github.com/hamilton-sky/codeintel/actions/workflows/ci.yml/badge.svg)](https://github.com/hamilton-sky/codeintel/actions/workflows/ci.yml)
6
6
 
7
+ ## Why an agent needs it
8
+
9
+ Without structural tools, an agent dropped into unfamiliar code falls back on `grep` and reads whole files to reconstruct relationships by hand — burning tokens, missing call sites, and guessing at blast radius before it edits anything. codeintel answers those questions directly instead:
10
+
11
+ - **"What calls this? What breaks if I change it?"** → the real call graph, which catches cross-file and module-level callers a text search silently misses.
12
+ - **"Where is this symbol defined, and everywhere it's used?"** → the language server, with exact locations.
13
+ - **"Where's the code that does X?"** (when you don't know the name) → semantic search over the repo.
14
+ - **Always a clean answer.** Every call returns the same JSON envelope. A missing or broken backend degrades to a safe `null` *with a reason* — so the agent falls back to grep instead of crashing on an exception it can't reason its way out of.
15
+
16
+ Net effect: fewer, sharper tool calls, less re-reading, and an agent that can see *structure* — callers, impact, call chains — that plain search can't.
17
+
18
+ ## What your agent can ask
19
+
20
+ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the default) codeintel picks the engine per operation:
21
+
22
+ | Ask | `op` | Engine (auto) | Comes back as |
23
+ |---|---|---|---|
24
+ | Find code by meaning ("auth middleware") | `search` | semantic | ranked `path:line │ snippet` hits |
25
+ | A symbol's definition **and** all references | `symbol` | lsp | definition body + reference list |
26
+ | Who calls this? | `callers` | graph | caller symbols + files |
27
+ | What does this call? | `callees` | graph | callee symbols + files |
28
+ | Blast radius of a change | `impact` | graph | callers **and** callees together |
29
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops |
30
+ | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
31
+ | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
32
+ | Everything about one symbol | `context` | graph + lsp | both views merged |
33
+
34
+ Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
35
+
36
+ **Example — "who uses `safe_null_result`?"**
37
+
38
+ ```jsonc
39
+ // request
40
+ { "op": "callers", "target": "safe_null_result", "engine": "auto" }
41
+
42
+ // response — always this exact envelope; `result` is ready-to-read markdown
43
+ {
44
+ "ok": true, "op": "callers", "target": "safe_null_result",
45
+ "engine": "graph", "cached": false,
46
+ "result": "## Callers of safe_null_result (7)\n- …gateway [USAGE] (src/codeintel/gateway.py)\n- …providers.graph [USAGE] (src/codeintel/providers/graph.py)\n- …server [USAGE] (src/codeintel/server.py)\n- … (4 more)"
47
+ }
48
+ ```
49
+
50
+ The agent hands `result` straight to the model. If the graph backend isn't installed, the identical call returns `"result": null, "reason": "engine-unavailable"` — no exception, and the agent just falls back to its own search.
51
+
7
52
  ## Quickstart
8
53
 
9
54
  ```bash
@@ -113,7 +158,7 @@ backend = "auto" # auto | graph | lsp | semantic
113
158
  semantic = "on" # on | off
114
159
  reindex = "on-demand" # on-demand | never
115
160
  cosine_floor = 0.25 # minimum similarity score for semantic hits
116
- max_chunks = 500 # max chunks to embed per project
161
+ max_chunks = 500 # max chunks to embed per file
117
162
  model = "BAAI/bge-small-en-v1.5" # fastembed embedding model
118
163
  ```
119
164
 
@@ -137,7 +182,16 @@ Not sure what's installed? `codeintel doctor` reports exactly which backends are
137
182
 
138
183
  ## For agents
139
184
 
140
- Start the HTTP server, then POST queries to `/code/query`:
185
+ Register codeintel as an MCP server (`codeintel install`) and the agent gets four tools:
186
+
187
+ | MCP tool | HTTP equivalent | Purpose |
188
+ |---|---|---|
189
+ | `code.query` | `POST /code/query` | The main call — search, trace, understand (the `op` table above) |
190
+ | `code.status` | `GET /code/status` | Which engines are live + whether an index exists |
191
+ | `code.doctor` | `POST /code/doctor` | Per-engine health + repo index status, with a fix for each gap |
192
+ | `code.map` | — | Generate/refresh `CODE_INTEL.md`, a static orientation file for hosts without MCP |
193
+
194
+ Over MCP the agent calls `code.query` directly. Over HTTP, start the server and POST to `/code/query`:
141
195
 
142
196
  ```bash
143
197
  codeintel serve-http & # listens on 127.0.0.1:8766 by default
@@ -169,5 +223,5 @@ The response is always JSON-safe. Check `result["result"] is not None` before us
169
223
  git clone https://github.com/hamilton-sky/codeintel.git
170
224
  cd codeintel
171
225
  pip install -e .[dev]
172
- pytest tests/ -q # full suite, ~1s
226
+ pytest tests/ -q # full suite (~15s — includes live graph/LSP backend tests)
173
227
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
5
5
  Author: Shammai Hamilton
6
6
  License-Expression: MIT
@@ -33,10 +33,55 @@ Dynamic: license-file
33
33
 
34
34
  # codeintel
35
35
 
36
- A unified code-intelligence gateway — graph + LSP + semantic that gives any coding agent a single safe API to search, trace, and understand any codebase.
36
+ **One MCP tool that lets a coding agent search, trace, and *understand* a codebase structurally, not by grepping.** codeintel unifies three engines — a call/import **graph**, an **LSP** for exact symbols, and **semantic** embedding search behind a single `code.query` call that routes to the right engine, caches the answer, and **never throws**. The agent always gets back a clean, well-formed result to reason over.
37
37
 
38
38
  [![CI](https://github.com/hamilton-sky/codeintel/actions/workflows/ci.yml/badge.svg)](https://github.com/hamilton-sky/codeintel/actions/workflows/ci.yml)
39
39
 
40
+ ## Why an agent needs it
41
+
42
+ Without structural tools, an agent dropped into unfamiliar code falls back on `grep` and reads whole files to reconstruct relationships by hand — burning tokens, missing call sites, and guessing at blast radius before it edits anything. codeintel answers those questions directly instead:
43
+
44
+ - **"What calls this? What breaks if I change it?"** → the real call graph, which catches cross-file and module-level callers a text search silently misses.
45
+ - **"Where is this symbol defined, and everywhere it's used?"** → the language server, with exact locations.
46
+ - **"Where's the code that does X?"** (when you don't know the name) → semantic search over the repo.
47
+ - **Always a clean answer.** Every call returns the same JSON envelope. A missing or broken backend degrades to a safe `null` *with a reason* — so the agent falls back to grep instead of crashing on an exception it can't reason its way out of.
48
+
49
+ Net effect: fewer, sharper tool calls, less re-reading, and an agent that can see *structure* — callers, impact, call chains — that plain search can't.
50
+
51
+ ## What your agent can ask
52
+
53
+ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the default) codeintel picks the engine per operation:
54
+
55
+ | Ask | `op` | Engine (auto) | Comes back as |
56
+ |---|---|---|---|
57
+ | Find code by meaning ("auth middleware") | `search` | semantic | ranked `path:line │ snippet` hits |
58
+ | A symbol's definition **and** all references | `symbol` | lsp | definition body + reference list |
59
+ | Who calls this? | `callers` | graph | caller symbols + files |
60
+ | What does this call? | `callees` | graph | callee symbols + files |
61
+ | Blast radius of a change | `impact` | graph | callers **and** callees together |
62
+ | Trace a call chain up/downstream | `chain` | graph | ordered hops |
63
+ | Find symbols by pattern | `pattern` | graph | matching nodes + locations |
64
+ | Project shape at a glance | `overview` | graph → lsp | modules, node/edge counts, languages |
65
+ | Everything about one symbol | `context` | graph + lsp | both views merged |
66
+
67
+ Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
68
+
69
+ **Example — "who uses `safe_null_result`?"**
70
+
71
+ ```jsonc
72
+ // request
73
+ { "op": "callers", "target": "safe_null_result", "engine": "auto" }
74
+
75
+ // response — always this exact envelope; `result` is ready-to-read markdown
76
+ {
77
+ "ok": true, "op": "callers", "target": "safe_null_result",
78
+ "engine": "graph", "cached": false,
79
+ "result": "## Callers of safe_null_result (7)\n- …gateway [USAGE] (src/codeintel/gateway.py)\n- …providers.graph [USAGE] (src/codeintel/providers/graph.py)\n- …server [USAGE] (src/codeintel/server.py)\n- … (4 more)"
80
+ }
81
+ ```
82
+
83
+ The agent hands `result` straight to the model. If the graph backend isn't installed, the identical call returns `"result": null, "reason": "engine-unavailable"` — no exception, and the agent just falls back to its own search.
84
+
40
85
  ## Quickstart
41
86
 
42
87
  ```bash
@@ -146,7 +191,7 @@ backend = "auto" # auto | graph | lsp | semantic
146
191
  semantic = "on" # on | off
147
192
  reindex = "on-demand" # on-demand | never
148
193
  cosine_floor = 0.25 # minimum similarity score for semantic hits
149
- max_chunks = 500 # max chunks to embed per project
194
+ max_chunks = 500 # max chunks to embed per file
150
195
  model = "BAAI/bge-small-en-v1.5" # fastembed embedding model
151
196
  ```
152
197
 
@@ -170,7 +215,16 @@ Not sure what's installed? `codeintel doctor` reports exactly which backends are
170
215
 
171
216
  ## For agents
172
217
 
173
- Start the HTTP server, then POST queries to `/code/query`:
218
+ Register codeintel as an MCP server (`codeintel install`) and the agent gets four tools:
219
+
220
+ | MCP tool | HTTP equivalent | Purpose |
221
+ |---|---|---|
222
+ | `code.query` | `POST /code/query` | The main call — search, trace, understand (the `op` table above) |
223
+ | `code.status` | `GET /code/status` | Which engines are live + whether an index exists |
224
+ | `code.doctor` | `POST /code/doctor` | Per-engine health + repo index status, with a fix for each gap |
225
+ | `code.map` | — | Generate/refresh `CODE_INTEL.md`, a static orientation file for hosts without MCP |
226
+
227
+ Over MCP the agent calls `code.query` directly. Over HTTP, start the server and POST to `/code/query`:
174
228
 
175
229
  ```bash
176
230
  codeintel serve-http & # listens on 127.0.0.1:8766 by default
@@ -202,5 +256,5 @@ The response is always JSON-safe. Check `result["result"] is not None` before us
202
256
  git clone https://github.com/hamilton-sky/codeintel.git
203
257
  cd codeintel
204
258
  pip install -e .[dev]
205
- pytest tests/ -q # full suite, ~1s
259
+ pytest tests/ -q # full suite (~15s — includes live graph/LSP backend tests)
206
260
  ```
@@ -0,0 +1 @@
1
+ __version__ = "0.2.1"
@@ -57,8 +57,12 @@ def _read_file(path: str) -> str | None:
57
57
 
58
58
 
59
59
  def _write_file(path: str, content: str) -> None:
60
- with open(path, "w", encoding="utf-8") as f:
60
+ # Atomic: write a sibling temp file then os.replace it into place, so an interrupted
61
+ # write can never leave the user's CLAUDE.md/AGENTS.md truncated or half-written.
62
+ tmp = path + ".codeintel.tmp"
63
+ with open(tmp, "w", encoding="utf-8") as f:
61
64
  f.write(content)
65
+ os.replace(tmp, path)
62
66
 
63
67
 
64
68
  def _update_block(content: str) -> tuple[str, str]:
@@ -1,10 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import os
4
5
  import pathlib
5
6
 
6
7
  _AGENTS = ["claude", "codex", "gemini", "zed"]
7
8
 
9
+
10
+ def _atomic_write_text(path: pathlib.Path, text: str) -> None:
11
+ """Write via a sibling temp file + os.replace so an interrupted write can never truncate
12
+ the user's existing agent config (which holds unrelated settings) to a partial/empty file.
13
+ The temp lives in the same directory as ``path`` so the replace stays on one filesystem."""
14
+ tmp = path.with_name(path.name + ".codeintel.tmp")
15
+ tmp.write_text(text, encoding="utf-8")
16
+ os.replace(tmp, path)
17
+
8
18
  _CONFIG: dict[str, dict] = {
9
19
  "claude": {
10
20
  "path": "~/.claude/settings.json",
@@ -80,7 +90,7 @@ class Installer:
80
90
 
81
91
  config_path.parent.mkdir(parents=True, exist_ok=True)
82
92
  _set_nested(data, spec["key"], spec["value"])
83
- config_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
93
+ _atomic_write_text(config_path, json.dumps(data, indent=2))
84
94
 
85
95
  return {
86
96
  "agent": agent,
@@ -31,6 +31,18 @@ class SemanticDb:
31
31
  self._conn = sqlite3.connect(self.db_path)
32
32
  self._conn.enable_load_extension(True)
33
33
  self._conn.row_factory = sqlite3.Row
34
+ # Concurrency: the background Reindexer writes on a daemon thread while a foreground
35
+ # query indexes inline — two separate connections to this one file. With the SQLite
36
+ # default (busy_timeout=0) the loser of that write race gets an immediate
37
+ # "database is locked" and silently drops its work; a busy timeout makes it wait
38
+ # instead, and WAL lets a search read while a reindex writes. (reset.py already
39
+ # cleans up the -wal/-shm siblings WAL creates.) Never-raise: if the pragmas can't
40
+ # be applied, fall back to default locking rather than fail to open the db.
41
+ try:
42
+ self._conn.execute("PRAGMA busy_timeout=5000")
43
+ self._conn.execute("PRAGMA journal_mode=WAL")
44
+ except Exception:
45
+ pass
34
46
  return self._conn
35
47
 
36
48
  def init(self) -> None:
@@ -1 +0,0 @@
1
- __version__ = "0.2.0"
File without changes
File without changes
File without changes
File without changes