codecortex 0.23.3__tar.gz → 0.24.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codecortex-0.23.3/src/codecortex.egg-info → codecortex-0.24.0}/PKG-INFO +8 -3
- {codecortex-0.23.3 → codecortex-0.24.0}/README.md +6 -2
- {codecortex-0.23.3 → codecortex-0.24.0}/pyproject.toml +33 -1
- {codecortex-0.23.3 → codecortex-0.24.0/src/codecortex.egg-info}/PKG-INFO +8 -3
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codecortex.egg-info/SOURCES.txt +19 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codecortex.egg-info/requires.txt +1 -0
- codecortex-0.24.0/src/codeintel/__init__.py +1 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/__main__.py +30 -2
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/c4.py +2 -1
- codecortex-0.24.0/src/codeintel/commands/uninstall.py +106 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/doctor.py +5 -2
- codecortex-0.24.0/src/codeintel/filelock.py +120 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/gateway.py +164 -20
- codecortex-0.24.0/src/codeintel/graph_answer.py +970 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/graph_backend.py +5 -2
- codecortex-0.24.0/src/codeintel/graph_confidence.py +155 -0
- codecortex-0.24.0/src/codeintel/graph_edges.py +89 -0
- codecortex-0.24.0/src/codeintel/graph_ops.py +712 -0
- codecortex-0.24.0/src/codeintel/graph_render.py +295 -0
- codecortex-0.24.0/src/codeintel/graph_targets.py +138 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/grapher.py +2 -5
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/installer.py +156 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/outcome.py +22 -0
- codecortex-0.24.0/src/codeintel/per_thread.py +64 -0
- codecortex-0.24.0/src/codeintel/provider.py +242 -0
- codecortex-0.24.0/src/codeintel/providers/graph.py +916 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/providers/lsp.py +460 -47
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/providers/semantic.py +159 -3
- codecortex-0.24.0/src/codeintel/query_ops.py +32 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/reindexer.py +36 -11
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/server.py +33 -11
- codecortex-0.24.0/tests/test_bench_corpus_typed.py +146 -0
- codecortex-0.24.0/tests/test_bench_gate.py +103 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_bench_oracle.py +24 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_bench_oracle_ts.py +125 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_corpus.py +2 -3
- codecortex-0.24.0/tests/test_docs_trust_claims.py +194 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_doctor.py +242 -1
- codecortex-0.24.0/tests/test_edge_confidence.py +687 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_enterprise.py +5 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_gateway.py +154 -1
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_graph_failure_population.py +36 -13
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_graph_provider.py +3 -3
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_grapher.py +2 -1
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_hardening.py +92 -3
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_http_auth.py +8 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_http_server.py +1 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_incompleteness.py +1 -1
- codecortex-0.24.0/tests/test_index_incompleteness.py +237 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_installer.py +8 -1
- codecortex-0.24.0/tests/test_lsp_language_coverage.py +231 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_lsp_provider.py +515 -2
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_mapper.py +15 -14
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_mcp_handshake.py +6 -5
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_mcp_server.py +36 -0
- codecortex-0.24.0/tests/test_missing_target.py +270 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_null_reason_and_ripple.py +201 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_onboarding.py +3 -5
- codecortex-0.24.0/tests/test_per_thread.py +101 -0
- codecortex-0.24.0/tests/test_query_budget.py +220 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_rbac.py +22 -6
- codecortex-0.24.0/tests/test_reindex_lock.py +209 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_reindexer.py +7 -0
- codecortex-0.24.0/tests/test_summary_integrity.py +2078 -0
- codecortex-0.24.0/tests/test_uninstall.py +335 -0
- codecortex-0.24.0/tests/test_upgrade_path.py +220 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_verify.py +3 -3
- codecortex-0.23.3/src/codeintel/__init__.py +0 -1
- codecortex-0.23.3/src/codeintel/graph_render.py +0 -95
- codecortex-0.23.3/src/codeintel/provider.py +0 -150
- codecortex-0.23.3/src/codeintel/providers/graph.py +0 -2445
- codecortex-0.23.3/src/codeintel/query_ops.py +0 -17
- codecortex-0.23.3/tests/test_edge_confidence.py +0 -151
- codecortex-0.23.3/tests/test_lsp_language_coverage.py +0 -100
- {codecortex-0.23.3 → codecortex-0.24.0}/LICENSE +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/setup.cfg +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codecortex.egg-info/entry_points.txt +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codecortex.egg-info/top_level.txt +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/agent_prompt.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/auth.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/c4_check.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/c4_layers.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/cache.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/__init__.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/_common.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/c4.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/doctor.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/gen_token.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/graph.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/index.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/install.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/map.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/prompt.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/query.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/reset.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/serve.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/serve_http.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/setup.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/commands/status.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/config.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/containment.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/graph_resolution.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/http_server.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/indexer.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/injector.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/lang_config.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/loc.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/logconfig.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/mapper.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/metrics.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/onboarding.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/paths.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/policy.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/progress.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/providers/__init__.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/providers/none.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/py.typed +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/redact.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/reset.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/searcher.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/semantic_db.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/source_kind.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/term.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/verify.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/viewer/__init__.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/viewer/graph_template.html +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/src/codeintel/wire_text.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_agent_prompt.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_backend_reaper.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_background_index_failure.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_c4.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_c4_check.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_c4_layers.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_c4_model.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_c4_output.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_cache.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_chunking.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_cli_commands.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_cli_help.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_cli_install_dry_run.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_cli_install_offer_injection.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_cold_process.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_config.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_could_not_ask_semantic.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_docs_ci_claims.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_docs_deadcode_withdrawal.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_e2e.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_engine_adoption.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_graph_real.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_graph_stdin.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_hard_exit.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_index_failure_reason.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_index_progress.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_indexer_binary.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_integration.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_lang_config.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_loc_census.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_lsp_real.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_model_dimension.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_never_raise.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_redaction_boundary.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_rerank.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_reset.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_semantic_provider.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_source_kind.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_staleness.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_symbol_context.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_term.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_treesitter.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_verify_call.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_version_skew.py +0 -0
- {codecortex-0.23.3 → codecortex-0.24.0}/tests/test_wire_text.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.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
|
|
@@ -31,6 +31,7 @@ Requires-Dist: tree-sitter-language-pack>=1.0
|
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
33
33
|
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
|
|
34
35
|
Requires-Dist: numpy>=1.24; extra == "dev"
|
|
35
36
|
Requires-Dist: ruff>=0.16; extra == "dev"
|
|
36
37
|
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
@@ -53,6 +54,10 @@ Dynamic: license-file
|
|
|
53
54
|
> been pointed at an unfamiliar codebase it has found real bugs. **Use it locally, on a developer
|
|
54
55
|
> machine, for a single user** — that is the case it is built and tested for. Before relying on it
|
|
55
56
|
> for anything beyond that, read **[Project status](#project-status)**.
|
|
57
|
+
>
|
|
58
|
+
> **New here? Read [docs/trust.md](docs/trust.md) first** — what each engine's answer is worth, a
|
|
59
|
+
> five-minute check on a symbol whose callers you already know, and the eight states your
|
|
60
|
+
> repository can be in with one command for each.
|
|
56
61
|
|
|
57
62
|

|
|
58
63
|
|
|
@@ -432,7 +437,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
432
437
|
- **[Install & registration](docs/install.md)** — what each agent host actually reads, why the registered command is an absolute path, and the three levels of proof that registration worked.
|
|
433
438
|
- **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
|
|
434
439
|
- **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
|
|
435
|
-
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale:
|
|
440
|
+
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 30 k chunks indexed in ~10 min, ~233 ms warm queries, 68 MB index.
|
|
436
441
|
- Engine references: **[graph](docs/graph.md)** · **[lsp](docs/lsp.md)** · **[semantic](docs/semantic.md)**.
|
|
437
442
|
|
|
438
443
|
## CLI reference
|
|
@@ -448,7 +453,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
448
453
|
| `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) |
|
|
449
454
|
| `codeintel query --op OP --target TARGET [--engine auto] [--project-root DIR] [--json]` | Run a single query and print the result |
|
|
450
455
|
| `codeintel status [project_root]` | Show engine availability and index age |
|
|
451
|
-
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
|
|
456
|
+
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap. `--deep` puts one **real query** to each engine and requires an answer with content — a process that starts is not a process that answers, and `runnable` means the second |
|
|
452
457
|
| `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
|
|
453
458
|
| `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) |
|
|
454
459
|
| `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--edges union\|imports] [--json] [--layers] [--suggest-config] [--check] [--layers-from auto\|inferred\|declared]` | Write a LikeC4 architecture model (`.c4`) of the repo's files and import graph. Indexes the repo first if it has no graph index. `--edges imports` drops the lower-confidence edges for a legible diagram; `--layers` reports inferred architectural bands; `--check` gates CI on a declared `[layers]` config, exit 2 on drift. See [An architecture model, as source](#an-architecture-model-as-source) and [docs/c4.md](docs/c4.md) |
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
> been pointed at an unfamiliar codebase it has found real bugs. **Use it locally, on a developer
|
|
14
14
|
> machine, for a single user** — that is the case it is built and tested for. Before relying on it
|
|
15
15
|
> for anything beyond that, read **[Project status](#project-status)**.
|
|
16
|
+
>
|
|
17
|
+
> **New here? Read [docs/trust.md](docs/trust.md) first** — what each engine's answer is worth, a
|
|
18
|
+
> five-minute check on a symbol whose callers you already know, and the eight states your
|
|
19
|
+
> repository can be in with one command for each.
|
|
16
20
|
|
|
17
21
|

|
|
18
22
|
|
|
@@ -392,7 +396,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
392
396
|
- **[Install & registration](docs/install.md)** — what each agent host actually reads, why the registered command is an absolute path, and the three levels of proof that registration worked.
|
|
393
397
|
- **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
|
|
394
398
|
- **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
|
|
395
|
-
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale:
|
|
399
|
+
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 30 k chunks indexed in ~10 min, ~233 ms warm queries, 68 MB index.
|
|
396
400
|
- Engine references: **[graph](docs/graph.md)** · **[lsp](docs/lsp.md)** · **[semantic](docs/semantic.md)**.
|
|
397
401
|
|
|
398
402
|
## CLI reference
|
|
@@ -408,7 +412,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
408
412
|
| `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) |
|
|
409
413
|
| `codeintel query --op OP --target TARGET [--engine auto] [--project-root DIR] [--json]` | Run a single query and print the result |
|
|
410
414
|
| `codeintel status [project_root]` | Show engine availability and index age |
|
|
411
|
-
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
|
|
415
|
+
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap. `--deep` puts one **real query** to each engine and requires an answer with content — a process that starts is not a process that answers, and `runnable` means the second |
|
|
412
416
|
| `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
|
|
413
417
|
| `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) |
|
|
414
418
|
| `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--edges union\|imports] [--json] [--layers] [--suggest-config] [--check] [--layers-from auto\|inferred\|declared]` | Write a LikeC4 architecture model (`.c4`) of the repo's files and import graph. Indexes the repo first if it has no graph index. `--edges imports` drops the lower-confidence edges for a legible diagram; `--layers` reports inferred architectural bands; `--check` gates CI on a declared `[layers]` config, exit 2 on drift. See [An architecture model, as source](#an-architecture-model-as-source) and [docs/c4.md](docs/c4.md) |
|
|
@@ -53,7 +53,8 @@ Changelog = "https://github.com/hamilton-sky/codeintel/blob/main/CHANGELOG.md"
|
|
|
53
53
|
codeintel = "codeintel.__main__:main"
|
|
54
54
|
|
|
55
55
|
[project.optional-dependencies]
|
|
56
|
-
dev = ["pytest>=8", "pytest-cov>=5", "numpy>=1.24", "ruff>=0.16",
|
|
56
|
+
dev = ["pytest>=8", "pytest-cov>=5", "pytest-timeout>=2.3", "numpy>=1.24", "ruff>=0.16",
|
|
57
|
+
"mypy>=1.11"]
|
|
57
58
|
|
|
58
59
|
# `bench/agent_bench.py` only. Deliberately NOT in `dev`, and emphatically not a runtime dependency:
|
|
59
60
|
# codeintel's own code makes zero outbound HTTP calls and that claim is load-bearing in the README.
|
|
@@ -161,3 +162,34 @@ follow_imports = "skip"
|
|
|
161
162
|
# case in CI, and that costs ~0.4 points. A floor pinned to today's measurement would go red on a
|
|
162
163
|
# runner's toolchain rather than on a real coverage regression. Ratchet it up deliberately.
|
|
163
164
|
addopts = "--cov=codeintel --cov-report=term-missing:skip-covered --cov-fail-under=83"
|
|
165
|
+
|
|
166
|
+
# No test may run indefinitely. The ceiling is deliberately far above any legitimate test rather
|
|
167
|
+
# than tuned to the current slowest: its job is to convert a hang into a named failure, not to
|
|
168
|
+
# police performance. Measured worst cases after the background-reindex guard landed —
|
|
169
|
+
# `test_cold_process` spawns real cold interpreters at ~60s, `test_hard_exit` runs nested pytest
|
|
170
|
+
# sessions at ~12s — so 180s leaves room for a slow CI runner and still fails in three minutes
|
|
171
|
+
# instead of never.
|
|
172
|
+
#
|
|
173
|
+
# `signal` rather than `thread`: SIGALRM fails the ONE test that overran and lets the session carry
|
|
174
|
+
# on to a normal report. pytest-timeout's `thread` method kills the whole process, which would
|
|
175
|
+
# reproduce the thing being fixed — an unexplained death partway through — with better provenance
|
|
176
|
+
# but no more information. A test that needs longer says so with
|
|
177
|
+
# `@pytest.mark.timeout(<seconds>)` and, ideally, why.
|
|
178
|
+
timeout = 180
|
|
179
|
+
timeout_method = "signal"
|
|
180
|
+
|
|
181
|
+
# A leaked socket, pipe or file handle is a defect, not a note. Promoted to an error once the
|
|
182
|
+
# suite's own leaks were closed — sixteen HTTP listeners kept open by `shutdown()` without
|
|
183
|
+
# `server_close()`, a template file, eight bare `open(...).read()` calls in `test_mapper`, and two
|
|
184
|
+
# MCP children killed without being waited on or having their pipes closed. Leaving it at
|
|
185
|
+
# `warning` is what let all of those accumulate: the count was in every run's output and had been
|
|
186
|
+
# for months.
|
|
187
|
+
#
|
|
188
|
+
# `default::ResourceWarning:_pytest.unraisableexception` is NOT an exemption for our own leaks —
|
|
189
|
+
# pytest surfaces a garbage-collected leak through that module rather than from the test that
|
|
190
|
+
# caused it, and as an error it fails whichever unrelated test happened to trigger the collection.
|
|
191
|
+
# Our leaks are caught at their own site by the first rule; this keeps the blame accurate.
|
|
192
|
+
filterwarnings = [
|
|
193
|
+
"error::ResourceWarning",
|
|
194
|
+
"default::ResourceWarning:_pytest.unraisableexception",
|
|
195
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.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
|
|
@@ -31,6 +31,7 @@ Requires-Dist: tree-sitter-language-pack>=1.0
|
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
33
33
|
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
|
|
34
35
|
Requires-Dist: numpy>=1.24; extra == "dev"
|
|
35
36
|
Requires-Dist: ruff>=0.16; extra == "dev"
|
|
36
37
|
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
@@ -53,6 +54,10 @@ Dynamic: license-file
|
|
|
53
54
|
> been pointed at an unfamiliar codebase it has found real bugs. **Use it locally, on a developer
|
|
54
55
|
> machine, for a single user** — that is the case it is built and tested for. Before relying on it
|
|
55
56
|
> for anything beyond that, read **[Project status](#project-status)**.
|
|
57
|
+
>
|
|
58
|
+
> **New here? Read [docs/trust.md](docs/trust.md) first** — what each engine's answer is worth, a
|
|
59
|
+
> five-minute check on a symbol whose callers you already know, and the eight states your
|
|
60
|
+
> repository can be in with one command for each.
|
|
56
61
|
|
|
57
62
|

|
|
58
63
|
|
|
@@ -432,7 +437,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
432
437
|
- **[Install & registration](docs/install.md)** — what each agent host actually reads, why the registered command is an absolute path, and the three levels of proof that registration worked.
|
|
433
438
|
- **[Query flow](docs/query-flow.md)** — request lifecycle, engine selection, fan-out & merge, and why it never throws.
|
|
434
439
|
- **[Map file](docs/map-file.md)** — the static `CODE_INTEL.md` orientation layer for hosts with no MCP support.
|
|
435
|
-
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale:
|
|
440
|
+
- **[Benchmarks](docs/benchmarks.md)** — real numbers at scale: 30 k chunks indexed in ~10 min, ~233 ms warm queries, 68 MB index.
|
|
436
441
|
- Engine references: **[graph](docs/graph.md)** · **[lsp](docs/lsp.md)** · **[semantic](docs/semantic.md)**.
|
|
437
442
|
|
|
438
443
|
## CLI reference
|
|
@@ -448,7 +453,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
|
|
|
448
453
|
| `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) |
|
|
449
454
|
| `codeintel query --op OP --target TARGET [--engine auto] [--project-root DIR] [--json]` | Run a single query and print the result |
|
|
450
455
|
| `codeintel status [project_root]` | Show engine availability and index age |
|
|
451
|
-
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
|
|
456
|
+
| `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap. `--deep` puts one **real query** to each engine and requires an answer with content — a process that starts is not a process that answers, and `runnable` means the second |
|
|
452
457
|
| `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
|
|
453
458
|
| `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) |
|
|
454
459
|
| `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--edges union\|imports] [--json] [--layers] [--suggest-config] [--check] [--layers-from auto\|inferred\|declared]` | Write a LikeC4 architecture model (`.c4`) of the repo's files and import graph. Indexes the repo first if it has no graph index. `--edges imports` drops the lower-confidence edges for a legible diagram; `--layers` reports inferred architectural bands; `--check` gates CI on a declared `[layers]` config, exit 2 on drift. See [An architecture model, as source](#an-architecture-model-as-source) and [docs/c4.md](docs/c4.md) |
|
|
@@ -18,10 +18,16 @@ src/codeintel/cache.py
|
|
|
18
18
|
src/codeintel/config.py
|
|
19
19
|
src/codeintel/containment.py
|
|
20
20
|
src/codeintel/doctor.py
|
|
21
|
+
src/codeintel/filelock.py
|
|
21
22
|
src/codeintel/gateway.py
|
|
23
|
+
src/codeintel/graph_answer.py
|
|
22
24
|
src/codeintel/graph_backend.py
|
|
25
|
+
src/codeintel/graph_confidence.py
|
|
26
|
+
src/codeintel/graph_edges.py
|
|
27
|
+
src/codeintel/graph_ops.py
|
|
23
28
|
src/codeintel/graph_render.py
|
|
24
29
|
src/codeintel/graph_resolution.py
|
|
30
|
+
src/codeintel/graph_targets.py
|
|
25
31
|
src/codeintel/grapher.py
|
|
26
32
|
src/codeintel/http_server.py
|
|
27
33
|
src/codeintel/indexer.py
|
|
@@ -35,6 +41,7 @@ src/codeintel/metrics.py
|
|
|
35
41
|
src/codeintel/onboarding.py
|
|
36
42
|
src/codeintel/outcome.py
|
|
37
43
|
src/codeintel/paths.py
|
|
44
|
+
src/codeintel/per_thread.py
|
|
38
45
|
src/codeintel/policy.py
|
|
39
46
|
src/codeintel/progress.py
|
|
40
47
|
src/codeintel/provider.py
|
|
@@ -66,6 +73,7 @@ src/codeintel/commands/serve.py
|
|
|
66
73
|
src/codeintel/commands/serve_http.py
|
|
67
74
|
src/codeintel/commands/setup.py
|
|
68
75
|
src/codeintel/commands/status.py
|
|
76
|
+
src/codeintel/commands/uninstall.py
|
|
69
77
|
src/codeintel/providers/__init__.py
|
|
70
78
|
src/codeintel/providers/graph.py
|
|
71
79
|
src/codeintel/providers/lsp.py
|
|
@@ -76,6 +84,8 @@ src/codeintel/viewer/graph_template.html
|
|
|
76
84
|
tests/test_agent_prompt.py
|
|
77
85
|
tests/test_backend_reaper.py
|
|
78
86
|
tests/test_background_index_failure.py
|
|
87
|
+
tests/test_bench_corpus_typed.py
|
|
88
|
+
tests/test_bench_gate.py
|
|
79
89
|
tests/test_bench_oracle.py
|
|
80
90
|
tests/test_bench_oracle_ts.py
|
|
81
91
|
tests/test_c4.py
|
|
@@ -95,6 +105,7 @@ tests/test_corpus.py
|
|
|
95
105
|
tests/test_could_not_ask_semantic.py
|
|
96
106
|
tests/test_docs_ci_claims.py
|
|
97
107
|
tests/test_docs_deadcode_withdrawal.py
|
|
108
|
+
tests/test_docs_trust_claims.py
|
|
98
109
|
tests/test_doctor.py
|
|
99
110
|
tests/test_e2e.py
|
|
100
111
|
tests/test_edge_confidence.py
|
|
@@ -112,6 +123,7 @@ tests/test_http_auth.py
|
|
|
112
123
|
tests/test_http_server.py
|
|
113
124
|
tests/test_incompleteness.py
|
|
114
125
|
tests/test_index_failure_reason.py
|
|
126
|
+
tests/test_index_incompleteness.py
|
|
115
127
|
tests/test_index_progress.py
|
|
116
128
|
tests/test_indexer_binary.py
|
|
117
129
|
tests/test_installer.py
|
|
@@ -124,21 +136,28 @@ tests/test_lsp_real.py
|
|
|
124
136
|
tests/test_mapper.py
|
|
125
137
|
tests/test_mcp_handshake.py
|
|
126
138
|
tests/test_mcp_server.py
|
|
139
|
+
tests/test_missing_target.py
|
|
127
140
|
tests/test_model_dimension.py
|
|
128
141
|
tests/test_never_raise.py
|
|
129
142
|
tests/test_null_reason_and_ripple.py
|
|
130
143
|
tests/test_onboarding.py
|
|
144
|
+
tests/test_per_thread.py
|
|
145
|
+
tests/test_query_budget.py
|
|
131
146
|
tests/test_rbac.py
|
|
132
147
|
tests/test_redaction_boundary.py
|
|
148
|
+
tests/test_reindex_lock.py
|
|
133
149
|
tests/test_reindexer.py
|
|
134
150
|
tests/test_rerank.py
|
|
135
151
|
tests/test_reset.py
|
|
136
152
|
tests/test_semantic_provider.py
|
|
137
153
|
tests/test_source_kind.py
|
|
138
154
|
tests/test_staleness.py
|
|
155
|
+
tests/test_summary_integrity.py
|
|
139
156
|
tests/test_symbol_context.py
|
|
140
157
|
tests/test_term.py
|
|
141
158
|
tests/test_treesitter.py
|
|
159
|
+
tests/test_uninstall.py
|
|
160
|
+
tests/test_upgrade_path.py
|
|
142
161
|
tests/test_verify.py
|
|
143
162
|
tests/test_verify_call.py
|
|
144
163
|
tests/test_version_skew.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.24.0"
|
|
@@ -44,6 +44,7 @@ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
44
44
|
("setup", "Prepare backends + index this repo (--all does it all)"),
|
|
45
45
|
("index", "Build the index every other command reads (semantic + graph)"),
|
|
46
46
|
("install", "Register codeintel with the AI agents on this machine"),
|
|
47
|
+
("uninstall", "Remove those registrations — the index is left alone"),
|
|
47
48
|
("prompt", "Print a paste-to-your-agent setup prompt for this machine"),
|
|
48
49
|
]),
|
|
49
50
|
("Check health", [
|
|
@@ -79,6 +80,7 @@ _MODULES = {
|
|
|
79
80
|
"setup": "setup",
|
|
80
81
|
"index": "index",
|
|
81
82
|
"install": "install",
|
|
83
|
+
"uninstall": "uninstall",
|
|
82
84
|
"prompt": "prompt",
|
|
83
85
|
"doctor": "doctor",
|
|
84
86
|
"status": "status",
|
|
@@ -333,6 +335,13 @@ The one-shot path on a new machine.""",
|
|
|
333
335
|
Writes the MCP server config so an agent can call codeintel. Then restart the
|
|
334
336
|
agent (or start a new session) — a running host does not reload its MCP
|
|
335
337
|
config. `codeintel doctor` lists what got registered where.""",
|
|
338
|
+
"uninstall": """examples:
|
|
339
|
+
codeintel uninstall
|
|
340
|
+
codeintel uninstall --dry-run show what would go, change nothing
|
|
341
|
+
|
|
342
|
+
Removes only codeintel's own entry from each agent config — neighbouring
|
|
343
|
+
servers and your settings are left alone. The index is NOT deleted; use
|
|
344
|
+
`codeintel reset --all` for that.""",
|
|
336
345
|
"prompt": """examples:
|
|
337
346
|
codeintel prompt
|
|
338
347
|
|
|
@@ -462,7 +471,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
462
471
|
"--json", action="store_true", help="Emit the structured status and index-age report")
|
|
463
472
|
status_parser.add_argument(
|
|
464
473
|
"--deep", action="store_true",
|
|
465
|
-
help="
|
|
474
|
+
help="Put a real query to each engine and require an answer, boot the LSP, and sample "
|
|
475
|
+
"indexed source readability (slower, read-only)")
|
|
466
476
|
|
|
467
477
|
# serve-http subcommand
|
|
468
478
|
http_parser = subparsers.add_parser(
|
|
@@ -503,6 +513,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
503
513
|
help="Show what would be registered, and where — write nothing",
|
|
504
514
|
)
|
|
505
515
|
|
|
516
|
+
# uninstall subcommand
|
|
517
|
+
uninstall_parser = subparsers.add_parser(
|
|
518
|
+
"uninstall", help=_DESCRIPTIONS["uninstall"], description=_DESCRIPTIONS["uninstall"])
|
|
519
|
+
uninstall_parser.add_argument(
|
|
520
|
+
"--agent",
|
|
521
|
+
choices=["auto", "claude", "codex", "gemini", "zed", "all"],
|
|
522
|
+
default="auto",
|
|
523
|
+
help="Agent to remove from (default: auto — every agent codeintel is REGISTERED with, "
|
|
524
|
+
"which is a different set from the agents installed here)",
|
|
525
|
+
)
|
|
526
|
+
uninstall_parser.add_argument(
|
|
527
|
+
"--dry-run",
|
|
528
|
+
action="store_true",
|
|
529
|
+
help="Show what would be removed, and from where — change nothing",
|
|
530
|
+
)
|
|
531
|
+
|
|
506
532
|
# map subcommand
|
|
507
533
|
map_parser = subparsers.add_parser(
|
|
508
534
|
"map", help=_DESCRIPTIONS["map"], description=_DESCRIPTIONS["map"])
|
|
@@ -575,7 +601,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
575
601
|
"doctor", help=_DESCRIPTIONS["doctor"], description=_DESCRIPTIONS["doctor"])
|
|
576
602
|
doctor_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
|
|
577
603
|
doctor_parser.add_argument("--deep", action="store_true",
|
|
578
|
-
help="
|
|
604
|
+
help="Ask each engine one REAL query and require content — a "
|
|
605
|
+
"process that starts is not one that answers. Also boots "
|
|
606
|
+
"serena (slower; first boot pulls it via uvx)")
|
|
579
607
|
doctor_parser.add_argument("--json", action="store_true",
|
|
580
608
|
help="Emit the structured JSON report instead of the table")
|
|
581
609
|
|
|
@@ -495,7 +495,8 @@ def build_c4_payload(project_root: Any, *, depth: int | None = None, scope: tupl
|
|
|
495
495
|
payload at an auto-fit (or requested) directory depth. Never raises -- returns a flat `reason`
|
|
496
496
|
string and empty lists on any failure."""
|
|
497
497
|
try:
|
|
498
|
-
from codeintel.
|
|
498
|
+
from codeintel.graph_render import _repo_display_name
|
|
499
|
+
from codeintel.providers.graph import GraphProvider
|
|
499
500
|
|
|
500
501
|
p = GraphProvider()
|
|
501
502
|
if not getattr(p, "available", False):
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""`codeintel uninstall` — remove codeintel's entry from the agent configs `install` wrote.
|
|
2
|
+
|
|
3
|
+
The inverse of `install`, and deliberately only that. It does NOT delete the semantic index: that
|
|
4
|
+
is `reset`'s job, it is expensive to rebuild (ten minutes on a large repo), and a command whose
|
|
5
|
+
name says "uninstall" quietly destroying ten minutes of work is the kind of surprise this project
|
|
6
|
+
spends most of its comments avoiding. What the index costs and how to remove it is printed instead,
|
|
7
|
+
which is the same rule every other disclosure here follows — name what was NOT done.
|
|
8
|
+
|
|
9
|
+
There is no confirmation prompt, and that is a considered difference from `reset --all`. This edits
|
|
10
|
+
one entry in a config file and `codeintel install` puts it straight back; `reset` destroys an index
|
|
11
|
+
that has to be re-embedded. `--dry-run` covers the reader who wants to look first.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from codeintel.commands._common import never_raise
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _targets(agent: str, installer_mod: Any) -> tuple[list[str], list[str]]:
|
|
20
|
+
"""`(agents, untouched)` for the requested `--agent` selection.
|
|
21
|
+
|
|
22
|
+
`auto` means "wherever codeintel is actually registered", not "agents installed on this
|
|
23
|
+
machine" — the question `install` asks. They differ in both directions that matter: an agent
|
|
24
|
+
uninstalled from this machine can still hold our entry, and an agent that is present but never
|
|
25
|
+
registered has nothing to remove and should not be reported as though it did.
|
|
26
|
+
"""
|
|
27
|
+
if agent == "auto":
|
|
28
|
+
registered = installer_mod.registered_agents()
|
|
29
|
+
return registered, [a for a in installer_mod._AGENTS if a not in registered]
|
|
30
|
+
if agent == "all":
|
|
31
|
+
return list(installer_mod._AGENTS), []
|
|
32
|
+
return [agent], []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _dry_run_line(installer_mod: Any, agent: str) -> str:
|
|
36
|
+
"""What `--dry-run` would do to one agent's config — read-only, through the same lookup
|
|
37
|
+
`codeintel doctor` uses to notice a registration at all."""
|
|
38
|
+
spec = installer_mod._CONFIG[agent]
|
|
39
|
+
path, current = installer_mod.registered_command(spec)
|
|
40
|
+
if current is None:
|
|
41
|
+
return f"~ {agent}: no codeintel entry in {path} (no change)"
|
|
42
|
+
return f"- {agent}: would remove the codeintel entry from {path} (command: {current})"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _report(results: list) -> tuple[bool, bool, list[str]]:
|
|
46
|
+
"""Print one line per agent; return (removed_any, failed_any, legacy_paths)."""
|
|
47
|
+
removed_any = failed_any = False
|
|
48
|
+
legacy_paths: list[str] = []
|
|
49
|
+
for r in results:
|
|
50
|
+
agent, path, action = r["agent"], r["path"], r["action"]
|
|
51
|
+
if action == "removed":
|
|
52
|
+
print(f"v {agent}: removed from {path}")
|
|
53
|
+
removed_any = True
|
|
54
|
+
elif action == "absent":
|
|
55
|
+
print(f"~ {agent}: nothing registered at {path}")
|
|
56
|
+
else:
|
|
57
|
+
print(f"x {agent}: failed — {r['reason']}")
|
|
58
|
+
failed_any = True
|
|
59
|
+
if r.get("legacy"):
|
|
60
|
+
legacy_paths.append(r["legacy"])
|
|
61
|
+
return removed_any, failed_any, legacy_paths
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@never_raise("uninstall failed: {exc}", code=1)
|
|
65
|
+
def run(args: Any) -> int:
|
|
66
|
+
from codeintel import installer as installer_mod
|
|
67
|
+
|
|
68
|
+
agents, untouched = _targets(args.agent, installer_mod)
|
|
69
|
+
|
|
70
|
+
if args.agent == "auto" and not agents:
|
|
71
|
+
# Exit 0, not 1. The requested end state — codeintel is not registered with anything —
|
|
72
|
+
# already holds, and reporting "failure" for a goal that is met would make this command
|
|
73
|
+
# unusable in a teardown script that runs whether or not install ever ran.
|
|
74
|
+
print("codeintel is not registered with any agent on this machine — nothing to remove.")
|
|
75
|
+
print(f" (looked at: {', '.join(untouched)})")
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
if getattr(args, "dry_run", False):
|
|
79
|
+
for agent in agents:
|
|
80
|
+
print(_dry_run_line(installer_mod, agent))
|
|
81
|
+
print("\nDry run — nothing was changed.")
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
results = installer_mod.Installer().unregister_many(agents)
|
|
85
|
+
removed_any, failed_any, legacy_paths = _report(results)
|
|
86
|
+
|
|
87
|
+
for legacy in dict.fromkeys(legacy_paths):
|
|
88
|
+
print(f"\n! stale entry: {legacy} has an `mcpServers.codeintel` block that this host does "
|
|
89
|
+
f"NOT read (an older codeintel wrote it). It is inert, and safe to delete by hand.")
|
|
90
|
+
|
|
91
|
+
if args.agent == "auto" and untouched:
|
|
92
|
+
print(f"\n- untouched (nothing registered): {', '.join(untouched)}")
|
|
93
|
+
|
|
94
|
+
if removed_any:
|
|
95
|
+
# A host that is already running keeps the server it launched at session start, so the
|
|
96
|
+
# tools stay on screen until it is restarted — the mirror of the note `install` prints,
|
|
97
|
+
# and the same surprise in the other direction.
|
|
98
|
+
print("\nRestart your agent (or start a new session) — a running host does not reload "
|
|
99
|
+
"its MCP config, so codeintel's tools stay visible until it does.")
|
|
100
|
+
|
|
101
|
+
# Named, not done. The index is the expensive artifact and this command does not own it.
|
|
102
|
+
from codeintel.paths import codeintel_home
|
|
103
|
+
print(f"\nThe index is untouched: {codeintel_home()}")
|
|
104
|
+
print(" Remove it with `codeintel reset --all` if you are done with codeintel entirely.")
|
|
105
|
+
|
|
106
|
+
return 1 if failed_any else 0
|
|
@@ -280,7 +280,7 @@ def run_doctor(
|
|
|
280
280
|
try:
|
|
281
281
|
from codeintel.providers.graph import GraphProvider
|
|
282
282
|
engines["graph"] = _probe_engine(
|
|
283
|
-
"graph", graph, GraphProvider, lambda p: p.probe(root), on_provider
|
|
283
|
+
"graph", graph, GraphProvider, lambda p: p.probe(root, deep=deep), on_provider
|
|
284
284
|
)
|
|
285
285
|
except Exception:
|
|
286
286
|
engines["graph"] = {"engine": "graph", "status": "fail", "installed": False,
|
|
@@ -414,7 +414,10 @@ def render_doctor_text(report: dict) -> str:
|
|
|
414
414
|
ready, total, healthy = summ.get("ready", "?"), summ.get("total", "?"), summ.get("healthy")
|
|
415
415
|
count = c.bold(f"{ready} / {total}")
|
|
416
416
|
count = c.red(count) if healthy is False else (c.green(count) if healthy else count)
|
|
417
|
-
|
|
417
|
+
# Names what --deep actually does. "boot-check serena" described one third of it and the
|
|
418
|
+
# least useful third: booting is the question this tool keeps learning not to stop at.
|
|
419
|
+
tail = "" if report.get("deep") else c.dim(
|
|
420
|
+
" (run with --deep to ask each engine a real query)")
|
|
418
421
|
out.append("")
|
|
419
422
|
out.append(f" {count} engines ready for this repo.{tail}")
|
|
420
423
|
opt_down = [n for n in _ENGINES
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""A cross-process advisory lock, used to stop two codeintel processes reindexing one repo at once.
|
|
2
|
+
|
|
3
|
+
MEASURED 2026-09-18: two `codeintel index` passes started together on a 600-file repository each
|
|
4
|
+
embedded all 600 chunks and each reported "Indexed 600 chunks". There is no natural serialisation —
|
|
5
|
+
both read the same "what is new" answer from the database before either had written anything, so
|
|
6
|
+
the whole embedding cost is paid twice. On a large repository that is ten minutes of CPU doubled,
|
|
7
|
+
and the two passes contend for the same cores while doing it.
|
|
8
|
+
|
|
9
|
+
WHY `flock` AND NOT A FLAG FILE. The obvious way to record "a reindex is running" is to write a
|
|
10
|
+
file and delete it at the end. That is strictly WORSE than keeping the state in memory: a process
|
|
11
|
+
that is killed never runs its cleanup, so the flag survives forever and the tool reports a reindex
|
|
12
|
+
in progress that is not. A signal true of a FILE and false of the WORLD is the defect this
|
|
13
|
+
repository catalogues in `tests/test_summary_integrity.py`, and buying one to save some CPU would
|
|
14
|
+
be a poor trade.
|
|
15
|
+
|
|
16
|
+
`flock` has no such failure mode, and that is the entire reason it is the primitive here: the lock
|
|
17
|
+
belongs to an open file descriptor, and **the kernel releases it when the process dies**, however it
|
|
18
|
+
dies. There is no cleanup to skip and no staleness to age out. The lock file itself is left behind,
|
|
19
|
+
which is fine — it is an empty file whose existence means nothing; only the lock on it does.
|
|
20
|
+
|
|
21
|
+
DEGRADES, NEVER BLOCKS THE WORK. `fcntl` is POSIX and this package claims `OS Independent`, so
|
|
22
|
+
every entry point here has a fallback, and the two fallbacks are deliberately asymmetric:
|
|
23
|
+
|
|
24
|
+
* failing to ACQUIRE degrades to "you hold it" — indexing still happens, exactly as it did before
|
|
25
|
+
this module existed. A locking bug must never be able to stop a repository being indexed.
|
|
26
|
+
* failing to PROBE degrades to "nobody holds it" — we do not claim a reindex is running on evidence
|
|
27
|
+
we could not gather. That is the same rule the envelope's `gaps` follow one level up.
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import contextlib
|
|
32
|
+
import logging
|
|
33
|
+
import os
|
|
34
|
+
import pathlib
|
|
35
|
+
import re
|
|
36
|
+
from collections.abc import Iterator
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
try: # POSIX only; Windows has no fcntl
|
|
41
|
+
import fcntl
|
|
42
|
+
_HAVE_FLOCK = True
|
|
43
|
+
except Exception: # pragma: no cover - platform dependent
|
|
44
|
+
fcntl = None # type: ignore[assignment]
|
|
45
|
+
_HAVE_FLOCK = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def lock_path(canonical_root: str) -> pathlib.Path:
|
|
49
|
+
"""The lock file for one repository root.
|
|
50
|
+
|
|
51
|
+
Slugged the same way the graph cache names its files — every run of non-alphanumeric characters
|
|
52
|
+
collapses to a single `-` — which also flattens away every path separator, so a crafted root can
|
|
53
|
+
never place the lock outside the lock directory.
|
|
54
|
+
"""
|
|
55
|
+
from codeintel.paths import codeintel_home
|
|
56
|
+
|
|
57
|
+
slug = re.sub(r"[^A-Za-z0-9]+", "-", canonical_root).strip("-") or "root"
|
|
58
|
+
return codeintel_home() / "locks" / f"reindex-{slug}.lock"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@contextlib.contextmanager
|
|
62
|
+
def exclusive(path: pathlib.Path) -> Iterator[bool]:
|
|
63
|
+
"""Hold an exclusive, non-blocking lock on *path*. Yields whether we got it.
|
|
64
|
+
|
|
65
|
+
`False` means another LIVE process holds it — not that one once did. Callers use that to skip
|
|
66
|
+
work somebody else is already doing, so the answer has to be about the world rather than about
|
|
67
|
+
a file that happens to exist.
|
|
68
|
+
"""
|
|
69
|
+
if not _HAVE_FLOCK:
|
|
70
|
+
# No advisory locking available: behave exactly as the code did before this module.
|
|
71
|
+
yield True
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
fd = None
|
|
75
|
+
try:
|
|
76
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
fd = os.open(str(path), os.O_CREAT | os.O_RDWR, 0o644)
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
# An unwritable home is not a reason to stop indexing.
|
|
80
|
+
logger.debug("reindex lock unavailable at %s: %s", path, exc)
|
|
81
|
+
if fd is not None:
|
|
82
|
+
with contextlib.suppress(Exception):
|
|
83
|
+
os.close(fd)
|
|
84
|
+
yield True
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
acquired = False
|
|
88
|
+
try:
|
|
89
|
+
try:
|
|
90
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
91
|
+
acquired = True
|
|
92
|
+
except OSError:
|
|
93
|
+
acquired = False # somebody else is mid-pass
|
|
94
|
+
yield acquired
|
|
95
|
+
finally:
|
|
96
|
+
with contextlib.suppress(Exception):
|
|
97
|
+
if acquired:
|
|
98
|
+
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
99
|
+
os.close(fd) # closing would release it anyway; explicit is clearer
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def held_by_another_process(path: pathlib.Path) -> bool:
|
|
103
|
+
"""True when some other live process holds this lock.
|
|
104
|
+
|
|
105
|
+
Implemented by trying to take it and letting go immediately, because that is the only question
|
|
106
|
+
the kernel will answer — "is this lock free right now". The momentary acquisition is harmless:
|
|
107
|
+
a real pass that loses this race retries on the next debounce window, and the alternative
|
|
108
|
+
(recording holders in a file) is the stale-state design this module exists to avoid.
|
|
109
|
+
|
|
110
|
+
Degrades to False. Reporting "a reindex is running" on evidence we could not gather would be a
|
|
111
|
+
claim about the world made from a failed syscall.
|
|
112
|
+
"""
|
|
113
|
+
if not _HAVE_FLOCK or not path.exists():
|
|
114
|
+
return False
|
|
115
|
+
try:
|
|
116
|
+
with exclusive(path) as got:
|
|
117
|
+
return not got
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
logger.debug("reindex lock probe failed for %s: %s", path, exc)
|
|
120
|
+
return False
|