codecortex 0.16.0__tar.gz → 0.18.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 (126) hide show
  1. {codecortex-0.16.0/src/codecortex.egg-info → codecortex-0.18.0}/PKG-INFO +7 -7
  2. {codecortex-0.16.0 → codecortex-0.18.0}/README.md +6 -6
  3. {codecortex-0.16.0 → codecortex-0.18.0/src/codecortex.egg-info}/PKG-INFO +7 -7
  4. {codecortex-0.16.0 → codecortex-0.18.0}/src/codecortex.egg-info/SOURCES.txt +7 -0
  5. codecortex-0.18.0/src/codeintel/__init__.py +1 -0
  6. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/__main__.py +7 -1
  7. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/auth.py +8 -1
  8. codecortex-0.18.0/src/codeintel/commands/index.py +144 -0
  9. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/reset.py +2 -2
  10. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/config.py +24 -2
  11. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/containment.py +15 -0
  12. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/gateway.py +3 -3
  13. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/indexer.py +205 -33
  14. codecortex-0.18.0/src/codeintel/paths.py +31 -0
  15. codecortex-0.18.0/src/codeintel/progress.py +78 -0
  16. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/providers/semantic.py +108 -7
  17. codecortex-0.18.0/src/codeintel/redact.py +187 -0
  18. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/reset.py +47 -1
  19. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/searcher.py +125 -32
  20. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/semantic_db.py +59 -16
  21. codecortex-0.18.0/src/codeintel/term.py +394 -0
  22. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_chunking.py +6 -6
  23. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_cli_commands.py +20 -2
  24. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_config.py +63 -3
  25. codecortex-0.18.0/tests/test_index_progress.py +263 -0
  26. codecortex-0.18.0/tests/test_indexer_binary.py +42 -0
  27. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_rbac.py +73 -0
  28. codecortex-0.18.0/tests/test_redaction_boundary.py +217 -0
  29. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_reset.py +62 -0
  30. codecortex-0.18.0/tests/test_staleness.py +265 -0
  31. codecortex-0.18.0/tests/test_symbol_context.py +172 -0
  32. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_treesitter.py +5 -5
  33. codecortex-0.16.0/src/codeintel/__init__.py +0 -1
  34. codecortex-0.16.0/src/codeintel/commands/index.py +0 -76
  35. codecortex-0.16.0/src/codeintel/redact.py +0 -113
  36. codecortex-0.16.0/src/codeintel/term.py +0 -161
  37. {codecortex-0.16.0 → codecortex-0.18.0}/LICENSE +0 -0
  38. {codecortex-0.16.0 → codecortex-0.18.0}/pyproject.toml +0 -0
  39. {codecortex-0.16.0 → codecortex-0.18.0}/setup.cfg +0 -0
  40. {codecortex-0.16.0 → codecortex-0.18.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  41. {codecortex-0.16.0 → codecortex-0.18.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  42. {codecortex-0.16.0 → codecortex-0.18.0}/src/codecortex.egg-info/requires.txt +0 -0
  43. {codecortex-0.16.0 → codecortex-0.18.0}/src/codecortex.egg-info/top_level.txt +0 -0
  44. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/agent_prompt.py +0 -0
  45. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/cache.py +0 -0
  46. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/__init__.py +0 -0
  47. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/_common.py +0 -0
  48. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/doctor.py +0 -0
  49. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/gen_token.py +0 -0
  50. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/graph.py +0 -0
  51. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/install.py +0 -0
  52. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/map.py +0 -0
  53. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/prompt.py +0 -0
  54. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/query.py +0 -0
  55. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/serve.py +0 -0
  56. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/serve_http.py +0 -0
  57. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/setup.py +0 -0
  58. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/commands/status.py +0 -0
  59. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/doctor.py +0 -0
  60. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/graph_backend.py +0 -0
  61. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/graph_render.py +0 -0
  62. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/graph_resolution.py +0 -0
  63. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/grapher.py +0 -0
  64. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/http_server.py +0 -0
  65. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/injector.py +0 -0
  66. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/installer.py +0 -0
  67. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/loc.py +0 -0
  68. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/logconfig.py +0 -0
  69. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/mapper.py +0 -0
  70. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/metrics.py +0 -0
  71. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/onboarding.py +0 -0
  72. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/outcome.py +0 -0
  73. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/policy.py +0 -0
  74. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/provider.py +0 -0
  75. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/providers/__init__.py +0 -0
  76. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/providers/graph.py +0 -0
  77. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/providers/lsp.py +0 -0
  78. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/providers/none.py +0 -0
  79. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/py.typed +0 -0
  80. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/reindexer.py +0 -0
  81. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/server.py +0 -0
  82. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/source_kind.py +0 -0
  83. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/verify.py +0 -0
  84. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/viewer/__init__.py +0 -0
  85. {codecortex-0.16.0 → codecortex-0.18.0}/src/codeintel/viewer/graph_template.html +0 -0
  86. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_agent_prompt.py +0 -0
  87. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_backend_reaper.py +0 -0
  88. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_cache.py +0 -0
  89. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_cli_help.py +0 -0
  90. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_cold_process.py +0 -0
  91. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_corpus.py +0 -0
  92. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_docs_ci_claims.py +0 -0
  93. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_docs_deadcode_withdrawal.py +0 -0
  94. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_doctor.py +0 -0
  95. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_e2e.py +0 -0
  96. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_engine_adoption.py +0 -0
  97. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_enterprise.py +0 -0
  98. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_gateway.py +0 -0
  99. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_graph_failure_population.py +0 -0
  100. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_graph_provider.py +0 -0
  101. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_graph_real.py +0 -0
  102. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_graph_stdin.py +0 -0
  103. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_grapher.py +0 -0
  104. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_hardening.py +0 -0
  105. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_http_auth.py +0 -0
  106. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_http_server.py +0 -0
  107. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_incompleteness.py +0 -0
  108. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_installer.py +0 -0
  109. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_integration.py +0 -0
  110. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_loc_census.py +0 -0
  111. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_lsp_provider.py +0 -0
  112. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_lsp_real.py +0 -0
  113. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_mapper.py +0 -0
  114. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_mcp_handshake.py +0 -0
  115. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_mcp_server.py +0 -0
  116. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_model_dimension.py +0 -0
  117. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_never_raise.py +0 -0
  118. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_onboarding.py +0 -0
  119. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_reindexer.py +0 -0
  120. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_rerank.py +0 -0
  121. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_semantic_provider.py +0 -0
  122. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_source_kind.py +0 -0
  123. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_term.py +0 -0
  124. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_verify.py +0 -0
  125. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_verify_call.py +0 -0
  126. {codecortex-0.16.0 → codecortex-0.18.0}/tests/test_version_skew.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.16.0
3
+ Version: 0.18.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
@@ -279,7 +279,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
279
279
  ```text
280
280
  v claude: registered at /Users/you/.claude.json
281
281
 
282
- v verified: codeintel 0.11.2 — 4 tools (code.query, code.status, code.doctor, code.map)
282
+ v verified: codeintel 0.18.0 — 4 tools (code.query, code.status, code.doctor, code.map)
283
283
  ```
284
284
 
285
285
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -396,7 +396,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
396
396
  | `codeintel install [--agent auto\|claude\|codex\|gemini\|zed\|all] [--no-verify] [--relative-command]` | Register codeintel with the agents installed on this machine (`auto`, the default), then prove it by completing a real MCP handshake against the registered command |
397
397
  | `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 |
398
398
  | `codeintel prompt [project_root] [--agent auto\|claude\|codex\|gemini\|zed] [--fresh] [--deep]` | Print a paste-to-your-agent setup prompt; probes health and emits only the outstanding steps (or "just restart me" when already healthy). `--fresh` = the full sequence from `pip install`, to send a friend |
399
- | `codeintel index [project_root]` | Index a project for semantic search |
399
+ | `codeintel index [project_root] [--quiet]` | Index a repo (semantic embeddings + best-effort graph & map refresh), with a live progress display; `--quiet` prints only the result line |
400
400
  | `codeintel serve` | Start the MCP server (stdio transport) |
401
401
  | `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) |
402
402
  | `codeintel query --op OP --target TARGET [--engine auto] [--project-root DIR] [--json]` | Run a single query and print the result |
@@ -404,10 +404,10 @@ Full system docs live in [`docs/`](docs/) — start with the index:
404
404
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
405
405
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
406
406
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
407
- | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear the semantic index (this repo, or `--all`) to recover from a corrupt/stale DB |
407
+ | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
408
408
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
409
409
 
410
- Human-facing commands (`doctor`, `status`, `query`, `setup`, `reset`) honor `--no-color` / `NO_COLOR` and `--ascii`, and auto-degrade to plain text when piped.
410
+ Human-facing commands (`doctor`, `setup`, `prompt`, `reset`) honor `--no-color` / `NO_COLOR` and `--ascii`, and auto-degrade to plain text when piped.
411
411
 
412
412
  **Exit codes**, so a `make` target or CI step can gate on `$?`:
413
413
 
@@ -444,7 +444,7 @@ Config is **validated on load** — an out-of-range number, a misspelled enum, o
444
444
  | `CODEINTEL_HTTP_ACCESS_LOG=1` | One log line per HTTP request (method, path, status, latency) |
445
445
  | `CODEINTEL_DEBUG=1` | Log the full traceback of any error the never-throw contract swallows (silent by default) — the switch for diagnosing an unexpected `null` |
446
446
  | `CODEINTEL_REINDEX=off` | Disable the background reindexer; queries then index inline to stay fresh |
447
- | `CODEINTEL_HOME` | Where the index cache lives (default `~/.codeintel`). Set this when the process has **no resolvable home directory** — a container running as a UID with no passwd entry and no `$HOME`, which is common when an agent runs in one. Without it, `Path.home()` raises and every command fails somewhere far from the cause. |
447
+ | `CODEINTEL_HOME` | Where per-machine state lives (default `~/.codeintel`) — the index cache, the global `config.toml`, and `auth.toml`. Set this when the process has **no resolvable home directory** — a container running as a UID with no passwd entry and no `$HOME`, which is common when an agent runs in one. Without it, `Path.home()` raises and every command fails somewhere far from the cause. |
448
448
 
449
449
  ## Privacy & dependencies
450
450
 
@@ -617,7 +617,7 @@ git clone https://github.com/hamilton-sky/codeintel.git
617
617
  cd codeintel
618
618
  pip install -e .[dev]
619
619
 
620
- pytest tests/ -q # ~620 tests, ~30s; fails under 83% coverage
620
+ pytest tests/ -q # ~740 tests, ~30s; fails under 83% coverage
621
621
  ruff check src tests # lint
622
622
  mypy # types (src/ only)
623
623
  ```
@@ -242,7 +242,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
242
242
  ```text
243
243
  v claude: registered at /Users/you/.claude.json
244
244
 
245
- v verified: codeintel 0.11.2 — 4 tools (code.query, code.status, code.doctor, code.map)
245
+ v verified: codeintel 0.18.0 — 4 tools (code.query, code.status, code.doctor, code.map)
246
246
  ```
247
247
 
248
248
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -359,7 +359,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
359
359
  | `codeintel install [--agent auto\|claude\|codex\|gemini\|zed\|all] [--no-verify] [--relative-command]` | Register codeintel with the agents installed on this machine (`auto`, the default), then prove it by completing a real MCP handshake against the registered command |
360
360
  | `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 |
361
361
  | `codeintel prompt [project_root] [--agent auto\|claude\|codex\|gemini\|zed] [--fresh] [--deep]` | Print a paste-to-your-agent setup prompt; probes health and emits only the outstanding steps (or "just restart me" when already healthy). `--fresh` = the full sequence from `pip install`, to send a friend |
362
- | `codeintel index [project_root]` | Index a project for semantic search |
362
+ | `codeintel index [project_root] [--quiet]` | Index a repo (semantic embeddings + best-effort graph & map refresh), with a live progress display; `--quiet` prints only the result line |
363
363
  | `codeintel serve` | Start the MCP server (stdio transport) |
364
364
  | `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) |
365
365
  | `codeintel query --op OP --target TARGET [--engine auto] [--project-root DIR] [--json]` | Run a single query and print the result |
@@ -367,10 +367,10 @@ Full system docs live in [`docs/`](docs/) — start with the index:
367
367
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
368
368
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
369
369
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
370
- | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear the semantic index (this repo, or `--all`) to recover from a corrupt/stale DB |
370
+ | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
371
371
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
372
372
 
373
- Human-facing commands (`doctor`, `status`, `query`, `setup`, `reset`) honor `--no-color` / `NO_COLOR` and `--ascii`, and auto-degrade to plain text when piped.
373
+ Human-facing commands (`doctor`, `setup`, `prompt`, `reset`) honor `--no-color` / `NO_COLOR` and `--ascii`, and auto-degrade to plain text when piped.
374
374
 
375
375
  **Exit codes**, so a `make` target or CI step can gate on `$?`:
376
376
 
@@ -407,7 +407,7 @@ Config is **validated on load** — an out-of-range number, a misspelled enum, o
407
407
  | `CODEINTEL_HTTP_ACCESS_LOG=1` | One log line per HTTP request (method, path, status, latency) |
408
408
  | `CODEINTEL_DEBUG=1` | Log the full traceback of any error the never-throw contract swallows (silent by default) — the switch for diagnosing an unexpected `null` |
409
409
  | `CODEINTEL_REINDEX=off` | Disable the background reindexer; queries then index inline to stay fresh |
410
- | `CODEINTEL_HOME` | Where the index cache lives (default `~/.codeintel`). Set this when the process has **no resolvable home directory** — a container running as a UID with no passwd entry and no `$HOME`, which is common when an agent runs in one. Without it, `Path.home()` raises and every command fails somewhere far from the cause. |
410
+ | `CODEINTEL_HOME` | Where per-machine state lives (default `~/.codeintel`) — the index cache, the global `config.toml`, and `auth.toml`. Set this when the process has **no resolvable home directory** — a container running as a UID with no passwd entry and no `$HOME`, which is common when an agent runs in one. Without it, `Path.home()` raises and every command fails somewhere far from the cause. |
411
411
 
412
412
  ## Privacy & dependencies
413
413
 
@@ -580,7 +580,7 @@ git clone https://github.com/hamilton-sky/codeintel.git
580
580
  cd codeintel
581
581
  pip install -e .[dev]
582
582
 
583
- pytest tests/ -q # ~620 tests, ~30s; fails under 83% coverage
583
+ pytest tests/ -q # ~740 tests, ~30s; fails under 83% coverage
584
584
  ruff check src tests # lint
585
585
  mypy # types (src/ only)
586
586
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.16.0
3
+ Version: 0.18.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
@@ -279,7 +279,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
279
279
  ```text
280
280
  v claude: registered at /Users/you/.claude.json
281
281
 
282
- v verified: codeintel 0.11.2 — 4 tools (code.query, code.status, code.doctor, code.map)
282
+ v verified: codeintel 0.18.0 — 4 tools (code.query, code.status, code.doctor, code.map)
283
283
  ```
284
284
 
285
285
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -396,7 +396,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
396
396
  | `codeintel install [--agent auto\|claude\|codex\|gemini\|zed\|all] [--no-verify] [--relative-command]` | Register codeintel with the agents installed on this machine (`auto`, the default), then prove it by completing a real MCP handshake against the registered command |
397
397
  | `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 |
398
398
  | `codeintel prompt [project_root] [--agent auto\|claude\|codex\|gemini\|zed] [--fresh] [--deep]` | Print a paste-to-your-agent setup prompt; probes health and emits only the outstanding steps (or "just restart me" when already healthy). `--fresh` = the full sequence from `pip install`, to send a friend |
399
- | `codeintel index [project_root]` | Index a project for semantic search |
399
+ | `codeintel index [project_root] [--quiet]` | Index a repo (semantic embeddings + best-effort graph & map refresh), with a live progress display; `--quiet` prints only the result line |
400
400
  | `codeintel serve` | Start the MCP server (stdio transport) |
401
401
  | `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) |
402
402
  | `codeintel query --op OP --target TARGET [--engine auto] [--project-root DIR] [--json]` | Run a single query and print the result |
@@ -404,10 +404,10 @@ Full system docs live in [`docs/`](docs/) — start with the index:
404
404
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
405
405
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
406
406
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
407
- | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear the semantic index (this repo, or `--all`) to recover from a corrupt/stale DB |
407
+ | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
408
408
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
409
409
 
410
- Human-facing commands (`doctor`, `status`, `query`, `setup`, `reset`) honor `--no-color` / `NO_COLOR` and `--ascii`, and auto-degrade to plain text when piped.
410
+ Human-facing commands (`doctor`, `setup`, `prompt`, `reset`) honor `--no-color` / `NO_COLOR` and `--ascii`, and auto-degrade to plain text when piped.
411
411
 
412
412
  **Exit codes**, so a `make` target or CI step can gate on `$?`:
413
413
 
@@ -444,7 +444,7 @@ Config is **validated on load** — an out-of-range number, a misspelled enum, o
444
444
  | `CODEINTEL_HTTP_ACCESS_LOG=1` | One log line per HTTP request (method, path, status, latency) |
445
445
  | `CODEINTEL_DEBUG=1` | Log the full traceback of any error the never-throw contract swallows (silent by default) — the switch for diagnosing an unexpected `null` |
446
446
  | `CODEINTEL_REINDEX=off` | Disable the background reindexer; queries then index inline to stay fresh |
447
- | `CODEINTEL_HOME` | Where the index cache lives (default `~/.codeintel`). Set this when the process has **no resolvable home directory** — a container running as a UID with no passwd entry and no `$HOME`, which is common when an agent runs in one. Without it, `Path.home()` raises and every command fails somewhere far from the cause. |
447
+ | `CODEINTEL_HOME` | Where per-machine state lives (default `~/.codeintel`) — the index cache, the global `config.toml`, and `auth.toml`. Set this when the process has **no resolvable home directory** — a container running as a UID with no passwd entry and no `$HOME`, which is common when an agent runs in one. Without it, `Path.home()` raises and every command fails somewhere far from the cause. |
448
448
 
449
449
  ## Privacy & dependencies
450
450
 
@@ -617,7 +617,7 @@ git clone https://github.com/hamilton-sky/codeintel.git
617
617
  cd codeintel
618
618
  pip install -e .[dev]
619
619
 
620
- pytest tests/ -q # ~620 tests, ~30s; fails under 83% coverage
620
+ pytest tests/ -q # ~740 tests, ~30s; fails under 83% coverage
621
621
  ruff check src tests # lint
622
622
  mypy # types (src/ only)
623
623
  ```
@@ -30,7 +30,9 @@ src/codeintel/mapper.py
30
30
  src/codeintel/metrics.py
31
31
  src/codeintel/onboarding.py
32
32
  src/codeintel/outcome.py
33
+ src/codeintel/paths.py
33
34
  src/codeintel/policy.py
35
+ src/codeintel/progress.py
34
36
  src/codeintel/provider.py
35
37
  src/codeintel/py.typed
36
38
  src/codeintel/redact.py
@@ -89,6 +91,8 @@ tests/test_hardening.py
89
91
  tests/test_http_auth.py
90
92
  tests/test_http_server.py
91
93
  tests/test_incompleteness.py
94
+ tests/test_index_progress.py
95
+ tests/test_indexer_binary.py
92
96
  tests/test_installer.py
93
97
  tests/test_integration.py
94
98
  tests/test_loc_census.py
@@ -101,11 +105,14 @@ tests/test_model_dimension.py
101
105
  tests/test_never_raise.py
102
106
  tests/test_onboarding.py
103
107
  tests/test_rbac.py
108
+ tests/test_redaction_boundary.py
104
109
  tests/test_reindexer.py
105
110
  tests/test_rerank.py
106
111
  tests/test_reset.py
107
112
  tests/test_semantic_provider.py
108
113
  tests/test_source_kind.py
114
+ tests/test_staleness.py
115
+ tests/test_symbol_context.py
109
116
  tests/test_term.py
110
117
  tests/test_treesitter.py
111
118
  tests/test_verify.py
@@ -0,0 +1 @@
1
+ __version__ = "0.18.0"
@@ -131,13 +131,19 @@ def main() -> None:
131
131
  subparsers.add_parser("serve", help="Start the MCP server")
132
132
 
133
133
  # index subcommand
134
- index_parser = subparsers.add_parser("index", help="Index a project for semantic search")
134
+ index_parser = subparsers.add_parser(
135
+ "index", parents=[color_parent],
136
+ help="Index a project for semantic search")
135
137
  index_parser.add_argument(
136
138
  "project_root",
137
139
  nargs="?",
138
140
  default=None,
139
141
  help="Project root directory (default: cwd)",
140
142
  )
143
+ index_parser.add_argument(
144
+ "--quiet", "-q", action="store_true",
145
+ help="Suppress the live progress display and header; keep only the result line",
146
+ )
141
147
 
142
148
  # query subcommand
143
149
  query_parser = subparsers.add_parser("query", help="Query the code intelligence engine")
@@ -26,6 +26,7 @@ import os
26
26
  import pathlib
27
27
  import tomllib # stdlib on Python 3.11+ (the project's minimum)
28
28
 
29
+ from codeintel.paths import codeintel_home
29
30
  from codeintel.policy import TieringPolicy
30
31
 
31
32
  logger = logging.getLogger("codeintel")
@@ -78,7 +79,13 @@ def _auth_config_path() -> pathlib.Path | None:
78
79
  if env:
79
80
  p = pathlib.Path(env)
80
81
  return p if p.is_file() else None
81
- default = pathlib.Path.home() / ".codeintel" / "auth.toml"
82
+ try:
83
+ default = codeintel_home() / "auth.toml"
84
+ except Exception:
85
+ # No CODEINTEL_HOME and no resolvable home. `load_auth` promises never to raise, and its
86
+ # whole contract is "absent config → auth disabled"; a host with nowhere to look is the
87
+ # absent case, not a crash. (Previously this read `Path.home()` inline and raised.)
88
+ return None
82
89
  return default if default.is_file() else None
83
90
 
84
91
 
@@ -0,0 +1,144 @@
1
+ """`codeintel index` — build the semantic index, then best-effort refresh the graph and the map."""
2
+
3
+ import logging
4
+ import os
5
+ from typing import Any
6
+
7
+ from codeintel.commands._common import resolve_root
8
+
9
+
10
+ class _ProgressLogBridge(logging.Handler):
11
+ """Routes WARNING+ log records from the indexer to a live progress counter's ``notice()``, so a
12
+ per-file skip warning prints as a clean permanent line ABOVE the redrawing status line instead
13
+ of colliding with it (``0 chunksskipping …``). Installed only for a live (TTY) run and removed
14
+ the moment indexing returns — a non-TTY has no live line to protect, and the warnings keep their
15
+ normal stderr path there."""
16
+
17
+ def __init__(self, counter: Any) -> None:
18
+ super().__init__(level=logging.WARNING)
19
+ self._counter = counter
20
+
21
+ def emit(self, record: logging.LogRecord) -> None:
22
+ try:
23
+ self._counter.notice(record.getMessage())
24
+ except Exception:
25
+ pass
26
+
27
+
28
+ def run(args: Any) -> int:
29
+ import sys
30
+
31
+ import codeintel.term as term
32
+ from codeintel.config import load_config
33
+ from codeintel.indexer import Indexer
34
+ from codeintel.semantic_db import SemanticDb, default_db_path
35
+
36
+ project_root = resolve_root(args)
37
+ # Validate before doing anything. `codeintel index /typo/path` walked nothing, found nothing,
38
+ # and printed "Nothing new to index" at exit 0 — indistinguishable from a correct incremental
39
+ # run, which is the worst possible response to a mistyped path in a script.
40
+ if not os.path.isdir(project_root):
41
+ print(f"index failed: not a directory: {project_root}")
42
+ return 1
43
+
44
+ # A console bound to the CURRENT stdout (honouring --no-color/--ascii/NO_COLOR), so progress is
45
+ # captured under test and routed correctly whether or not main() already ran configure(). The
46
+ # LiveCounter auto-goes silent on a non-TTY; --quiet suppresses it (and the header) outright.
47
+ console = term.Console(
48
+ stream=sys.stdout,
49
+ no_color=getattr(args, "no_color", False),
50
+ ascii_mode=(True if getattr(args, "ascii", False) else None),
51
+ )
52
+ quiet = getattr(args, "quiet", False)
53
+ counter = None if quiet else term.LiveCounter(console)
54
+
55
+ failed = False
56
+ # Wrap the whole semantic pass so a setup failure (e.g. an unresolvable home dir →
57
+ # Path.home() raising) degrades with a message, like every other subcommand, not a traceback.
58
+ try:
59
+ cfg = load_config(project_root)
60
+ if not quiet:
61
+ # One dim header naming the model + chunk strategy: it says WHAT is running (and that it
62
+ # is all local), and on a cold cache it pre-explains the model-download stall.
63
+ strat = str(cfg.get("chunk_strategy", "syntax"))
64
+ strat_label = "def-aligned" if strat == "syntax" else "line-window"
65
+ model = str(cfg.get("model") or "BAAI/bge-small-en-v1.5")
66
+ sep = "." if console.ascii else "·"
67
+ print(console.header("index", project_root))
68
+ print(f" {console.dim(f'{model} {sep} {strat_label} chunks')}")
69
+ print()
70
+ db_path = default_db_path(str(cfg.get("model") or ""))
71
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
72
+ db = SemanticDb(db_path)
73
+ # While a live status line is on screen, the indexer's skip WARNINGs must not print raw to
74
+ # the terminal (they land mid-line). Route them through the counter's notice() for the span
75
+ # of the index pass only; a non-TTY needs no bridge (its status lines end in \n).
76
+ bridge = _ProgressLogBridge(counter) if (counter is not None and counter.live) else None
77
+ idx_logger = logging.getLogger("codeintel")
78
+ try:
79
+ db.init()
80
+ if bridge is not None:
81
+ idx_logger.addHandler(bridge)
82
+ try:
83
+ count = Indexer(
84
+ db,
85
+ model_name=str(cfg.get("model") or "BAAI/bge-small-en-v1.5"),
86
+ window=int(cfg.get("window", 20)),
87
+ stride=int(cfg.get("stride", 10)),
88
+ max_chunks=int(cfg.get("max_chunks", 500)),
89
+ max_total_chunks=int(cfg.get("max_total_chunks", 100000)),
90
+ chunk_strategy=str(cfg.get("chunk_strategy", "syntax")),
91
+ progress=counter,
92
+ ).index(project_root)
93
+ finally:
94
+ if bridge is not None:
95
+ idx_logger.removeHandler(bridge)
96
+ # Seal the live line before the plain summary: commit the ✓ row on real work, else erase
97
+ # it so "Nothing new" / the failure message stands on its own clean line.
98
+ if counter:
99
+ counter.finish(commit=count > 0)
100
+ if count > 0:
101
+ print(f"Indexed {count} chunks")
102
+ elif count < 0:
103
+ # Indexer.index() returns -1 for an unrecoverable failure. `> 0` sent that into
104
+ # the "Nothing new to index" branch, so a total failure read as a clean no-op.
105
+ print("index failed — the indexer could not complete (see the warnings above)")
106
+ failed = True
107
+ else:
108
+ print("Nothing new to index")
109
+ finally:
110
+ db.close()
111
+ except Exception as exc:
112
+ if counter:
113
+ counter.finish(commit=False)
114
+ print(f"index failed: {exc}")
115
+ failed = True
116
+
117
+ # best-effort graph reindex — an opaque subprocess, so a ticking elapsed heartbeat is the honest
118
+ # fidelity (no counts cross the boundary): the number keeps moving so a minute-long reindex never
119
+ # reads as a hang, but we never fabricate a percentage we can't see.
120
+ import shutil
121
+
122
+ if shutil.which("codebase-memory-mcp"):
123
+ beat = None if quiet else term.LiveHeartbeat(console, "graph reindex").start()
124
+ try:
125
+ from codeintel.reindexer import Reindexer
126
+
127
+ Reindexer()._graph_reindex(project_root)
128
+ if beat:
129
+ beat.stop("ok")
130
+ except Exception:
131
+ if beat:
132
+ beat.stop("warn", "skipped")
133
+
134
+ # best-effort map refresh after index (fast, kept silent — not worth a checklist row)
135
+ try:
136
+ from codeintel.mapper import MapGenerator
137
+ from codeintel.providers.graph import GraphProvider
138
+
139
+ gen = MapGenerator(GraphProvider())
140
+ gen.write(project_root, gen.generate(project_root))
141
+ except Exception:
142
+ pass
143
+
144
+ return 1 if failed else 0
@@ -1,4 +1,4 @@
1
- """`codeintel reset` — clear the semantic index (recover from a corrupt/stale DB)."""
1
+ """`codeintel reset` — clear a repo's index (semantic + graph), or the whole cache with --all."""
2
2
 
3
3
  import sys
4
4
  from typing import Any
@@ -18,7 +18,7 @@ def run(args: Any) -> int:
18
18
  return 1
19
19
  target = "ALL projects" if args.all else project_root
20
20
  count = preview.get("count", 0)
21
- ans = input(f"Reset semantic index for {target} ({count} entries)? [y/N] ").strip().lower()
21
+ ans = input(f"Reset index for {target} ({count} entries)? [y/N] ").strip().lower()
22
22
  if ans not in ("y", "yes"):
23
23
  print("aborted")
24
24
  return 0
@@ -4,6 +4,8 @@ import logging
4
4
  import pathlib
5
5
  import tomllib # stdlib since 3.11, which is this package's floor
6
6
 
7
+ from codeintel.paths import codeintel_home
8
+
7
9
  logger = logging.getLogger("codeintel")
8
10
 
9
11
  _DEFAULTS: dict = {
@@ -18,7 +20,12 @@ _DEFAULTS: dict = {
18
20
  "model": "BAAI/bge-small-en-v1.5",
19
21
  "chunk_strategy": "syntax", # syntax-aware (def/class boundaries) vs fixed line windows
20
22
  "rerank": "on", # hybrid lexical+semantic rerank of search results
21
- "rerank_candidates": 30, # cosine candidates fused/re-ranked before returning top-k
23
+ # Cosine candidates retrieved, fused and re-ranked before the top-k is returned. Was 30, but
24
+ # the semantic provider hardcoded its own `_display_k * 6` widening that overrode it, so the
25
+ # effective breadth has always been 60; 30 only ever described behaviour the code didn't have.
26
+ # The provider now honours this key, so the default is set to the value that was really in use
27
+ # — otherwise "fixing" the wiring would have silently narrowed everyone's retrieval by half.
28
+ "rerank_candidates": 60,
22
29
  }
23
30
 
24
31
  # Values restricted to a fixed set — anything else falls back to the default.
@@ -80,12 +87,27 @@ def _coerce(cfg: dict) -> dict:
80
87
  return out
81
88
 
82
89
 
90
+ def _global_config() -> dict:
91
+ """The machine-wide ``config.toml``, or ``{}`` when there isn't one.
92
+
93
+ Resolving the directory can RAISE (no ``CODEINTEL_HOME`` and no resolvable home — a container
94
+ UID with no passwd entry). That used to propagate straight out of ``load_config``: the call
95
+ read ``pathlib.Path.home()`` inline, so it blew up before ``_read_toml``'s handler could see
96
+ it, and every query on such a host died as an opaque ``provider-error``. "There is nowhere to
97
+ look for a global config" is not an error — it means there is no global config."""
98
+ try:
99
+ return _read_toml(codeintel_home() / "config.toml")
100
+ except Exception as exc:
101
+ logger.debug("no resolvable config directory (%s) — using defaults + project config", exc)
102
+ return {}
103
+
104
+
83
105
  def load_config(project_root: str | None = None) -> dict:
84
106
  """Return the merged, validated config: defaults < global < project. Values that fail
85
107
  validation fall back to their default (logged), so a bad config file never breaks a query."""
86
108
  root = pathlib.Path(project_root) if project_root is not None else pathlib.Path.cwd()
87
109
 
88
- global_cfg = _read_toml(pathlib.Path.home() / ".codeintel" / "config.toml")
110
+ global_cfg = _global_config()
89
111
  project_cfg = _read_toml(root / ".codeintel.toml")
90
112
 
91
113
  merged = {**_DEFAULTS, **global_cfg, **project_cfg}
@@ -69,9 +69,24 @@ def open_contained(root_real: str, candidate: str | Path, **kwargs):
69
69
  Raises ``ContainmentError`` rather than returning a sentinel, so a caller that wraps reads in a
70
70
  broad ``except Exception`` cannot quietly downgrade an escape to "file not found" — the escape
71
71
  is logged at WARNING here regardless of what the caller does with the exception.
72
+
73
+ A path that simply DOES NOT EXIST is reported as ``FileNotFoundError``, not as an escape.
74
+ ``contained_path`` answers the single question "may this be read", and a deleted file is a
75
+ legitimate no — but it is also the most ordinary thing that can happen to an indexed file, and
76
+ reporting it as a refusal meant every deletion between indexing and a query logged a WARNING
77
+ asking whether a symlink had been "planted after indexing" and rendered the hit as
78
+ ``[refused: resolves outside the indexed root]``. That is a false security alarm on a routine
79
+ event, and it drowns the real ones. The distinction is made HERE rather than by relaxing
80
+ ``contained_path``, whose ``None`` also drives ``_cleanup_deleted``'s reconciliation of deleted
81
+ files — loosening it there would leave their rows in the index forever.
72
82
  """
73
83
  safe = contained_path(root_real, candidate)
74
84
  if safe is None:
85
+ # lexists(), not exists(): a dangling symlink still EXISTS as a link, and a link whose
86
+ # target vanished is a construct worth refusing loudly rather than calling "not found".
87
+ if not os.path.lexists(candidate):
88
+ logger.debug("indexed file no longer exists: %s", candidate)
89
+ raise FileNotFoundError(str(candidate))
75
90
  logger.warning(
76
91
  "refusing to read %s — it does not resolve to a file inside %s (symlink or hard link "
77
92
  "planted after indexing?)", candidate, root_real,
@@ -24,7 +24,6 @@ _AUTO_ENGINE: dict[str, str] = {
24
24
  "overview": "graph",
25
25
  "changed": "graph",
26
26
  "changes": "graph",
27
- "deadcode": "graph",
28
27
  "hotspots": "graph",
29
28
  "symbol": "lsp",
30
29
  "search": "semantic",
@@ -33,8 +32,9 @@ _AUTO_ENGINE: dict[str, str] = {
33
32
 
34
33
  # Ops whose answer depends on live, unhashable state (the git worktree) rather than the indexed
35
34
  # content the cache key is built from — caching them would serve a stale answer within a freshness
36
- # generation. `changed` reads uncommitted edits; `deadcode`/`hotspots` are pure functions of the
37
- # index and stay cached (correctly keyed by the freshness generation).
35
+ # generation. `changed` reads uncommitted edits; `hotspots` is a pure function of the index and
36
+ # stays cached (correctly keyed by the freshness generation). A retired/unknown op falls through
37
+ # to the graph engine (the `_AUTO_ENGINE.get(op, "graph")` default), which safe-nulls it.
38
38
  _UNCACHED_OPS: frozenset[str] = frozenset({"changed", "changes"})
39
39
 
40
40
  # Engines whose slot can be filled after construction (see `Gateway.adopt_provider`).