codecortex 0.15.5__tar.gz → 0.16.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 (119) hide show
  1. {codecortex-0.15.5/src/codecortex.egg-info → codecortex-0.16.0}/PKG-INFO +64 -32
  2. {codecortex-0.15.5 → codecortex-0.16.0}/README.md +63 -31
  3. {codecortex-0.15.5 → codecortex-0.16.0/src/codecortex.egg-info}/PKG-INFO +64 -32
  4. {codecortex-0.15.5 → codecortex-0.16.0}/src/codecortex.egg-info/SOURCES.txt +7 -0
  5. codecortex-0.16.0/src/codeintel/__init__.py +1 -0
  6. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/__main__.py +14 -0
  7. codecortex-0.16.0/src/codeintel/agent_prompt.py +107 -0
  8. codecortex-0.16.0/src/codeintel/commands/prompt.py +33 -0
  9. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/doctor.py +4 -0
  10. codecortex-0.16.0/src/codeintel/graph_backend.py +281 -0
  11. codecortex-0.16.0/src/codeintel/graph_render.py +95 -0
  12. codecortex-0.16.0/src/codeintel/graph_resolution.py +274 -0
  13. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/onboarding.py +2 -0
  14. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/providers/graph.py +696 -683
  15. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/server.py +3 -0
  16. codecortex-0.16.0/tests/test_agent_prompt.py +86 -0
  17. codecortex-0.16.0/tests/test_backend_reaper.py +57 -0
  18. codecortex-0.16.0/tests/test_corpus.py +890 -0
  19. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_docs_deadcode_withdrawal.py +52 -3
  20. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_doctor.py +2 -0
  21. codecortex-0.16.0/tests/test_graph_failure_population.py +850 -0
  22. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_graph_provider.py +112 -156
  23. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_graph_real.py +14 -37
  24. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_hardening.py +5 -0
  25. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_incompleteness.py +21 -6
  26. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_loc_census.py +3 -0
  27. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_onboarding.py +1 -0
  28. codecortex-0.16.0/tests/test_reset.py +671 -0
  29. codecortex-0.15.5/src/codeintel/__init__.py +0 -1
  30. codecortex-0.15.5/tests/test_corpus.py +0 -335
  31. codecortex-0.15.5/tests/test_graph_failure_population.py +0 -256
  32. codecortex-0.15.5/tests/test_reset.py +0 -181
  33. {codecortex-0.15.5 → codecortex-0.16.0}/LICENSE +0 -0
  34. {codecortex-0.15.5 → codecortex-0.16.0}/pyproject.toml +0 -0
  35. {codecortex-0.15.5 → codecortex-0.16.0}/setup.cfg +0 -0
  36. {codecortex-0.15.5 → codecortex-0.16.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  37. {codecortex-0.15.5 → codecortex-0.16.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  38. {codecortex-0.15.5 → codecortex-0.16.0}/src/codecortex.egg-info/requires.txt +0 -0
  39. {codecortex-0.15.5 → codecortex-0.16.0}/src/codecortex.egg-info/top_level.txt +0 -0
  40. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/auth.py +0 -0
  41. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/cache.py +0 -0
  42. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/__init__.py +0 -0
  43. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/_common.py +0 -0
  44. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/doctor.py +0 -0
  45. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/gen_token.py +0 -0
  46. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/graph.py +0 -0
  47. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/index.py +0 -0
  48. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/install.py +0 -0
  49. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/map.py +0 -0
  50. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/query.py +0 -0
  51. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/reset.py +0 -0
  52. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/serve.py +0 -0
  53. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/serve_http.py +0 -0
  54. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/setup.py +0 -0
  55. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/commands/status.py +0 -0
  56. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/config.py +0 -0
  57. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/containment.py +0 -0
  58. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/gateway.py +0 -0
  59. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/grapher.py +0 -0
  60. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/http_server.py +0 -0
  61. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/indexer.py +0 -0
  62. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/injector.py +0 -0
  63. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/installer.py +0 -0
  64. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/loc.py +0 -0
  65. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/logconfig.py +0 -0
  66. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/mapper.py +0 -0
  67. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/metrics.py +0 -0
  68. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/outcome.py +0 -0
  69. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/policy.py +0 -0
  70. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/provider.py +0 -0
  71. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/providers/__init__.py +0 -0
  72. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/providers/lsp.py +0 -0
  73. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/providers/none.py +0 -0
  74. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/providers/semantic.py +0 -0
  75. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/py.typed +0 -0
  76. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/redact.py +0 -0
  77. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/reindexer.py +0 -0
  78. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/reset.py +0 -0
  79. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/searcher.py +0 -0
  80. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/semantic_db.py +0 -0
  81. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/source_kind.py +0 -0
  82. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/term.py +0 -0
  83. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/verify.py +0 -0
  84. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/viewer/__init__.py +0 -0
  85. {codecortex-0.15.5 → codecortex-0.16.0}/src/codeintel/viewer/graph_template.html +0 -0
  86. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_cache.py +0 -0
  87. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_chunking.py +0 -0
  88. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_cli_commands.py +0 -0
  89. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_cli_help.py +0 -0
  90. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_cold_process.py +0 -0
  91. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_config.py +0 -0
  92. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_docs_ci_claims.py +0 -0
  93. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_e2e.py +0 -0
  94. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_engine_adoption.py +0 -0
  95. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_enterprise.py +0 -0
  96. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_gateway.py +0 -0
  97. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_graph_stdin.py +0 -0
  98. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_grapher.py +0 -0
  99. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_http_auth.py +0 -0
  100. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_http_server.py +0 -0
  101. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_installer.py +0 -0
  102. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_integration.py +0 -0
  103. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_lsp_provider.py +0 -0
  104. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_lsp_real.py +0 -0
  105. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_mapper.py +0 -0
  106. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_mcp_handshake.py +0 -0
  107. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_mcp_server.py +0 -0
  108. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_model_dimension.py +0 -0
  109. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_never_raise.py +0 -0
  110. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_rbac.py +0 -0
  111. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_reindexer.py +0 -0
  112. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_rerank.py +0 -0
  113. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_semantic_provider.py +0 -0
  114. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_source_kind.py +0 -0
  115. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_term.py +0 -0
  116. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_treesitter.py +0 -0
  117. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_verify.py +0 -0
  118. {codecortex-0.15.5 → codecortex-0.16.0}/tests/test_verify_call.py +0 -0
  119. {codecortex-0.15.5 → codecortex-0.16.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.15.5
3
+ Version: 0.16.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
@@ -95,45 +95,61 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
95
95
  | Everything about one symbol | `context` | graph + lsp | both views merged |
96
96
  | **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
97
97
  | Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
98
- | Unreferenced (dead) code | `deadcode` | graph | **withdrawn** — measured wrong in both directions on real repos; safe-nulls with `reason: "op-withdrawn"` unless you opt in — [why, and what to use instead](#deadcode-is-withdrawn) |
98
+ | Unreferenced (dead) code | `deadcode` | graph | **withdrawn and now retired** — a labelled corpus measured its precision at 25%; safe-nulls with `reason: "op-withdrawn"`, and no flag brings it back — [the measurement, and what to use instead](#deadcode-is-retired) |
99
99
 
100
100
  Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
101
101
 
102
- #### `deadcode` is withdrawn
102
+ `callers`, `callees` and `impact` resolve the target by its **unqualified name**. When several
103
+ symbols share it, each matched symbol's rows are reported separately under its own heading and the
104
+ result says how many it found — narrow to one with a qualified target (`core.Group.invoke`) or a file
105
+ hint (`invoke@src/click/testing.py`); see
106
+ [when several symbols share a name](docs/graph.md#when-several-symbols-share-a-name).
103
107
 
104
- `deadcode` is withdrawn: it returns a safe-null (`reason: "op-withdrawn"`) instead of running. It
105
- was measured wrong in **both** directions on real repositories — on one it named five candidates of
106
- which four were live code (a rollup plugin hook; two entries of a `Record<string, fn>` reached by a
107
- runtime string; a `predicate` passed inline to the call that consumes it), and on another it
108
- reported "(none found)" for a 4,883-function codebase that had at least three genuinely unreferenced
109
- private helpers. It is the one op whose output is an instruction to delete code, so it needs the
110
- highest evidence bar of any op here, and currently has the least. It returns when a labelled corpus
111
- measures its precision and recall — not before.
108
+ #### `deadcode` is retired
112
109
 
113
- **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
114
- `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
110
+ `deadcode` no longer exists. Asking for it returns a safe-null (`reason: "op-withdrawn"`) with a hint
111
+ naming what to use instead, and **no flag brings it back** the implementation has been deleted.
115
112
 
116
- **Escape hatch, if you understand the risk.** Set `CODEINTEL_ENABLE_UNVERIFIED_OPS=1` to run it
117
- anyway. It still re-reads the source before reporting a hit, which removes the *common* false
118
- positives but that verification is exactly what was measured wrong on the repositories above, so
119
- **review every hit before deleting anything, and never wire it into an agent that deletes without a
120
- human in the loop.**
113
+ It was withdrawn pending one condition: *"it returns when a labelled corpus measures its precision
114
+ and recall not before."* That corpus now exists, in
115
+ [`tests/test_corpus.py`](tests/test_corpus.py), and the measurement is what retired it.
121
116
 
122
- **Example "who uses `safe_null_result`?"**
117
+ **How it was measured.** Two pinned real Python repositories (`pallets/click`, `psf/requests`), with
118
+ every function and method collected from the **AST** — 2,425 definitions, `async def` and class
119
+ methods included, because a verification whose population comes from a pattern like `^\s*def ` cannot
120
+ see half of them. Each is labelled live or dead with the reference behind the label recorded beside
121
+ it. The oracle errs toward *live*: a decorator, a dunder, an override of an external interface, a
122
+ string-dispatch mention, or public-API status is each enough to call a symbol live, so "dead" is only
123
+ what survives all of them. That biases the numbers against the op, which is the correct direction for
124
+ a check whose output is an instruction to delete code. Known-answer canaries are planted in both trees
125
+ so recall has a denominator at all.
123
126
 
124
- ```jsonc
125
- // request
126
- { "op": "callers", "target": "safe_null_result", "engine": "auto" }
127
+ **The numbers.**
127
128
 
128
- // response always this exact envelope; `result` is ready-to-read markdown
129
- {
130
- "ok": true, "op": "callers", "target": "safe_null_result",
131
- "engine": "graph", "cached": false,
132
- "result": "## Callers of safe_null_result (7)\n- …gateway [USAGE] (src/codeintel/gateway.py)\n- …providers.graph [USAGE] (src/codeintel/providers/graph.py)\n- …server [USAGE] (src/codeintel/server.py)\n- … (4 more)"
133
- }
134
- ```
129
+ | | precision | recall |
130
+ |---|---|---|
131
+ | as shipped | **25%** (6 of 24) | 60% (6 of 10) |
132
+ | with the two repairs this codebase already contains elsewhere | 89% (8 of 9) | 80% (8 of 10) |
133
+
134
+ And the measurement that decided it — **real code only, canaries removed**: the op as shipped named
135
+ **18 candidates across those two repositories, and every one of them was live.** All 18 were Makefile
136
+ targets, which the graph backend indexes as `Function` nodes. Repaired, it names exactly one, and
137
+ that one is `MockRequest.get_type` in requests — a method `http.cookiejar` calls by duck-typed
138
+ convention, whose name appears once in the source.
139
+
140
+ **Why it was not repaired further.** The verification was a name-frequency scan over the source, so it
141
+ fails on exactly one condition: a symbol whose name appears once and is called by a convention
142
+ outside the source. Two repositories produced three distinct instances of that condition — non-code
143
+ nodes labelled `Function`, interpreter-called dunders, and stdlib duck-typed protocol methods — and
144
+ the earlier TypeScript evidence adds a rollup plugin hook and object-literal properties. The set is
145
+ not enumerable: no specification lists `get_type`. Every repository added revealed a new member of it.
146
+
147
+ Weighed against that: in 2,425 real definitions across two maintained repositories there was **not
148
+ one** dead private symbol to find. An op whose measured yield on real code is zero true positives has
149
+ no benefit to set against that error rate.
135
150
 
136
- The agent hands `result` straight to the model. If the graph backend isn't installed, the identical call returns `"result": null, "reason": "engine-unavailable"` — no exception, and the agent just falls back to its own search.
151
+ **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
152
+ `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
137
153
 
138
154
  ## What makes it good
139
155
 
@@ -181,6 +197,21 @@ codeintel install # registers with the agents you actually have insta
181
197
  codeintel query --op search --target "authentication middleware"
182
198
  ```
183
199
 
200
+ ### Or: have your agent set it up
201
+
202
+ Prefer to let your coding agent run the steps? Generate a paste-ready prompt, tailored to this
203
+ machine and agent:
204
+
205
+ ```bash
206
+ codeintel prompt # this repo — probes health, emits only the steps still outstanding
207
+ codeintel prompt --fresh | pbcopy # the full sequence from `pip install`, to send a friend
208
+ ```
209
+
210
+ It runs a `doctor` probe and prints a block you copy into Claude Code / Codex / Gemini / Zed: the
211
+ exact remaining commands (or "just restart me" when everything is already healthy and registered),
212
+ a `doctor --deep` verification, and the reminder to restart the agent so the MCP tools load. The
213
+ prompt goes to stdout (so `| pbcopy` grabs exactly it); the "paste this" note goes to stderr.
214
+
184
215
  ### Enable native Codex integration
185
216
 
186
217
  `codeintel` is an MCP server, so Codex can call its tools directly rather than invoking the CLI.
@@ -364,6 +395,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
364
395
  | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
365
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 |
366
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
+ | `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 |
367
399
  | `codeintel index [project_root]` | Index a project for semantic search |
368
400
  | `codeintel serve` | Start the MCP server (stdio transport) |
369
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) |
@@ -517,7 +549,7 @@ back to grep rather than crashing.
517
549
 
518
550
  | Area | Why |
519
551
  |---|---|
520
- | `deadcode` | Withdrawn by default (`reason: "op-withdrawn"`) — measured wrong in both directions on real repos. Use `callers` on a specific symbol instead — [details](#deadcode-is-withdrawn). |
552
+ | `deadcode` | Withdrawn, then **retired** (`reason: "op-withdrawn"`) — a labelled corpus measured 25% precision. Use `callers` on a specific symbol instead — [the measurement](#deadcode-is-retired). |
521
553
  | Non-loopback serving | `serve-http` is stdlib `http.server`. It binds loopback by default for a reason; front it with a reverse proxy and see [docs/deploy.md](docs/deploy.md). |
522
554
  | RBAC between **untrusting** tenants | It separates privilege levels among callers you already trust. It is not a wall against an adversary with write access to their own root — see the warning in [docs/deploy.md](docs/deploy.md). |
523
555
  | Unattended automation | Anything that acts on a result without a human reading it deserves a pilot first. |
@@ -541,7 +573,7 @@ not write, and that is where its bugs have come from — every fix in `0.15.x` c
541
573
  at an unfamiliar codebase. Its characteristic failure mode is **answering confidently from the
542
574
  wrong index rather than failing loudly**, which the never-raise contract makes harder to notice: a
543
575
  wrong answer and a right one are the same shape. Run `codeintel doctor` before trusting a repo-wide
544
- answer — and `deadcode` in particular is withdrawn rather than merely caveated (see above) — and if
576
+ answer — and `deadcode` in particular is retired rather than merely caveated (see above) — and if
545
577
  something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
546
578
  author is the single most useful thing this project can receive right now.
547
579
 
@@ -58,45 +58,61 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
58
58
  | Everything about one symbol | `context` | graph + lsp | both views merged |
59
59
  | **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
60
60
  | Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
61
- | Unreferenced (dead) code | `deadcode` | graph | **withdrawn** — measured wrong in both directions on real repos; safe-nulls with `reason: "op-withdrawn"` unless you opt in — [why, and what to use instead](#deadcode-is-withdrawn) |
61
+ | Unreferenced (dead) code | `deadcode` | graph | **withdrawn and now retired** — a labelled corpus measured its precision at 25%; safe-nulls with `reason: "op-withdrawn"`, and no flag brings it back — [the measurement, and what to use instead](#deadcode-is-retired) |
62
62
 
63
63
  Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
64
64
 
65
- #### `deadcode` is withdrawn
65
+ `callers`, `callees` and `impact` resolve the target by its **unqualified name**. When several
66
+ symbols share it, each matched symbol's rows are reported separately under its own heading and the
67
+ result says how many it found — narrow to one with a qualified target (`core.Group.invoke`) or a file
68
+ hint (`invoke@src/click/testing.py`); see
69
+ [when several symbols share a name](docs/graph.md#when-several-symbols-share-a-name).
66
70
 
67
- `deadcode` is withdrawn: it returns a safe-null (`reason: "op-withdrawn"`) instead of running. It
68
- was measured wrong in **both** directions on real repositories — on one it named five candidates of
69
- which four were live code (a rollup plugin hook; two entries of a `Record<string, fn>` reached by a
70
- runtime string; a `predicate` passed inline to the call that consumes it), and on another it
71
- reported "(none found)" for a 4,883-function codebase that had at least three genuinely unreferenced
72
- private helpers. It is the one op whose output is an instruction to delete code, so it needs the
73
- highest evidence bar of any op here, and currently has the least. It returns when a labelled corpus
74
- measures its precision and recall — not before.
71
+ #### `deadcode` is retired
75
72
 
76
- **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
77
- `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
73
+ `deadcode` no longer exists. Asking for it returns a safe-null (`reason: "op-withdrawn"`) with a hint
74
+ naming what to use instead, and **no flag brings it back** the implementation has been deleted.
78
75
 
79
- **Escape hatch, if you understand the risk.** Set `CODEINTEL_ENABLE_UNVERIFIED_OPS=1` to run it
80
- anyway. It still re-reads the source before reporting a hit, which removes the *common* false
81
- positives but that verification is exactly what was measured wrong on the repositories above, so
82
- **review every hit before deleting anything, and never wire it into an agent that deletes without a
83
- human in the loop.**
76
+ It was withdrawn pending one condition: *"it returns when a labelled corpus measures its precision
77
+ and recall not before."* That corpus now exists, in
78
+ [`tests/test_corpus.py`](tests/test_corpus.py), and the measurement is what retired it.
84
79
 
85
- **Example "who uses `safe_null_result`?"**
80
+ **How it was measured.** Two pinned real Python repositories (`pallets/click`, `psf/requests`), with
81
+ every function and method collected from the **AST** — 2,425 definitions, `async def` and class
82
+ methods included, because a verification whose population comes from a pattern like `^\s*def ` cannot
83
+ see half of them. Each is labelled live or dead with the reference behind the label recorded beside
84
+ it. The oracle errs toward *live*: a decorator, a dunder, an override of an external interface, a
85
+ string-dispatch mention, or public-API status is each enough to call a symbol live, so "dead" is only
86
+ what survives all of them. That biases the numbers against the op, which is the correct direction for
87
+ a check whose output is an instruction to delete code. Known-answer canaries are planted in both trees
88
+ so recall has a denominator at all.
86
89
 
87
- ```jsonc
88
- // request
89
- { "op": "callers", "target": "safe_null_result", "engine": "auto" }
90
+ **The numbers.**
90
91
 
91
- // response always this exact envelope; `result` is ready-to-read markdown
92
- {
93
- "ok": true, "op": "callers", "target": "safe_null_result",
94
- "engine": "graph", "cached": false,
95
- "result": "## Callers of safe_null_result (7)\n- …gateway [USAGE] (src/codeintel/gateway.py)\n- …providers.graph [USAGE] (src/codeintel/providers/graph.py)\n- …server [USAGE] (src/codeintel/server.py)\n- … (4 more)"
96
- }
97
- ```
92
+ | | precision | recall |
93
+ |---|---|---|
94
+ | as shipped | **25%** (6 of 24) | 60% (6 of 10) |
95
+ | with the two repairs this codebase already contains elsewhere | 89% (8 of 9) | 80% (8 of 10) |
96
+
97
+ And the measurement that decided it — **real code only, canaries removed**: the op as shipped named
98
+ **18 candidates across those two repositories, and every one of them was live.** All 18 were Makefile
99
+ targets, which the graph backend indexes as `Function` nodes. Repaired, it names exactly one, and
100
+ that one is `MockRequest.get_type` in requests — a method `http.cookiejar` calls by duck-typed
101
+ convention, whose name appears once in the source.
102
+
103
+ **Why it was not repaired further.** The verification was a name-frequency scan over the source, so it
104
+ fails on exactly one condition: a symbol whose name appears once and is called by a convention
105
+ outside the source. Two repositories produced three distinct instances of that condition — non-code
106
+ nodes labelled `Function`, interpreter-called dunders, and stdlib duck-typed protocol methods — and
107
+ the earlier TypeScript evidence adds a rollup plugin hook and object-literal properties. The set is
108
+ not enumerable: no specification lists `get_type`. Every repository added revealed a new member of it.
109
+
110
+ Weighed against that: in 2,425 real definitions across two maintained repositories there was **not
111
+ one** dead private symbol to find. An op whose measured yield on real code is zero true positives has
112
+ no benefit to set against that error rate.
98
113
 
99
- The agent hands `result` straight to the model. If the graph backend isn't installed, the identical call returns `"result": null, "reason": "engine-unavailable"` — no exception, and the agent just falls back to its own search.
114
+ **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
115
+ `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
100
116
 
101
117
  ## What makes it good
102
118
 
@@ -144,6 +160,21 @@ codeintel install # registers with the agents you actually have insta
144
160
  codeintel query --op search --target "authentication middleware"
145
161
  ```
146
162
 
163
+ ### Or: have your agent set it up
164
+
165
+ Prefer to let your coding agent run the steps? Generate a paste-ready prompt, tailored to this
166
+ machine and agent:
167
+
168
+ ```bash
169
+ codeintel prompt # this repo — probes health, emits only the steps still outstanding
170
+ codeintel prompt --fresh | pbcopy # the full sequence from `pip install`, to send a friend
171
+ ```
172
+
173
+ It runs a `doctor` probe and prints a block you copy into Claude Code / Codex / Gemini / Zed: the
174
+ exact remaining commands (or "just restart me" when everything is already healthy and registered),
175
+ a `doctor --deep` verification, and the reminder to restart the agent so the MCP tools load. The
176
+ prompt goes to stdout (so `| pbcopy` grabs exactly it); the "paste this" note goes to stderr.
177
+
147
178
  ### Enable native Codex integration
148
179
 
149
180
  `codeintel` is an MCP server, so Codex can call its tools directly rather than invoking the CLI.
@@ -327,6 +358,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
327
358
  | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
328
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 |
329
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
+ | `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 |
330
362
  | `codeintel index [project_root]` | Index a project for semantic search |
331
363
  | `codeintel serve` | Start the MCP server (stdio transport) |
332
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) |
@@ -480,7 +512,7 @@ back to grep rather than crashing.
480
512
 
481
513
  | Area | Why |
482
514
  |---|---|
483
- | `deadcode` | Withdrawn by default (`reason: "op-withdrawn"`) — measured wrong in both directions on real repos. Use `callers` on a specific symbol instead — [details](#deadcode-is-withdrawn). |
515
+ | `deadcode` | Withdrawn, then **retired** (`reason: "op-withdrawn"`) — a labelled corpus measured 25% precision. Use `callers` on a specific symbol instead — [the measurement](#deadcode-is-retired). |
484
516
  | Non-loopback serving | `serve-http` is stdlib `http.server`. It binds loopback by default for a reason; front it with a reverse proxy and see [docs/deploy.md](docs/deploy.md). |
485
517
  | RBAC between **untrusting** tenants | It separates privilege levels among callers you already trust. It is not a wall against an adversary with write access to their own root — see the warning in [docs/deploy.md](docs/deploy.md). |
486
518
  | Unattended automation | Anything that acts on a result without a human reading it deserves a pilot first. |
@@ -504,7 +536,7 @@ not write, and that is where its bugs have come from — every fix in `0.15.x` c
504
536
  at an unfamiliar codebase. Its characteristic failure mode is **answering confidently from the
505
537
  wrong index rather than failing loudly**, which the never-raise contract makes harder to notice: a
506
538
  wrong answer and a right one are the same shape. Run `codeintel doctor` before trusting a repo-wide
507
- answer — and `deadcode` in particular is withdrawn rather than merely caveated (see above) — and if
539
+ answer — and `deadcode` in particular is retired rather than merely caveated (see above) — and if
508
540
  something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
509
541
  author is the single most useful thing this project can receive right now.
510
542
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.15.5
3
+ Version: 0.16.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
@@ -95,45 +95,61 @@ It's one call: `code.query(op, target, engine="auto")`. In `auto` mode (the defa
95
95
  | Everything about one symbol | `context` | graph + lsp | both views merged |
96
96
  | **Impact of your uncommitted edits** | `changed` | graph | changed files → impacted symbols |
97
97
  | Refactor-risk hotspots | `hotspots` | graph | highest complexity / fan-in symbols |
98
- | Unreferenced (dead) code | `deadcode` | graph | **withdrawn** — measured wrong in both directions on real repos; safe-nulls with `reason: "op-withdrawn"` unless you opt in — [why, and what to use instead](#deadcode-is-withdrawn) |
98
+ | Unreferenced (dead) code | `deadcode` | graph | **withdrawn and now retired** — a labelled corpus measured its precision at 25%; safe-nulls with `reason: "op-withdrawn"`, and no flag brings it back — [the measurement, and what to use instead](#deadcode-is-retired) |
99
99
 
100
100
  Pin one engine with `--engine graph│lsp│semantic`, or fan out with `--engine both` / `all` to merge results.
101
101
 
102
- #### `deadcode` is withdrawn
102
+ `callers`, `callees` and `impact` resolve the target by its **unqualified name**. When several
103
+ symbols share it, each matched symbol's rows are reported separately under its own heading and the
104
+ result says how many it found — narrow to one with a qualified target (`core.Group.invoke`) or a file
105
+ hint (`invoke@src/click/testing.py`); see
106
+ [when several symbols share a name](docs/graph.md#when-several-symbols-share-a-name).
103
107
 
104
- `deadcode` is withdrawn: it returns a safe-null (`reason: "op-withdrawn"`) instead of running. It
105
- was measured wrong in **both** directions on real repositories — on one it named five candidates of
106
- which four were live code (a rollup plugin hook; two entries of a `Record<string, fn>` reached by a
107
- runtime string; a `predicate` passed inline to the call that consumes it), and on another it
108
- reported "(none found)" for a 4,883-function codebase that had at least three genuinely unreferenced
109
- private helpers. It is the one op whose output is an instruction to delete code, so it needs the
110
- highest evidence bar of any op here, and currently has the least. It returns when a labelled corpus
111
- measures its precision and recall — not before.
108
+ #### `deadcode` is retired
112
109
 
113
- **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
114
- `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
110
+ `deadcode` no longer exists. Asking for it returns a safe-null (`reason: "op-withdrawn"`) with a hint
111
+ naming what to use instead, and **no flag brings it back** the implementation has been deleted.
115
112
 
116
- **Escape hatch, if you understand the risk.** Set `CODEINTEL_ENABLE_UNVERIFIED_OPS=1` to run it
117
- anyway. It still re-reads the source before reporting a hit, which removes the *common* false
118
- positives but that verification is exactly what was measured wrong on the repositories above, so
119
- **review every hit before deleting anything, and never wire it into an agent that deletes without a
120
- human in the loop.**
113
+ It was withdrawn pending one condition: *"it returns when a labelled corpus measures its precision
114
+ and recall not before."* That corpus now exists, in
115
+ [`tests/test_corpus.py`](tests/test_corpus.py), and the measurement is what retired it.
121
116
 
122
- **Example "who uses `safe_null_result`?"**
117
+ **How it was measured.** Two pinned real Python repositories (`pallets/click`, `psf/requests`), with
118
+ every function and method collected from the **AST** — 2,425 definitions, `async def` and class
119
+ methods included, because a verification whose population comes from a pattern like `^\s*def ` cannot
120
+ see half of them. Each is labelled live or dead with the reference behind the label recorded beside
121
+ it. The oracle errs toward *live*: a decorator, a dunder, an override of an external interface, a
122
+ string-dispatch mention, or public-API status is each enough to call a symbol live, so "dead" is only
123
+ what survives all of them. That biases the numbers against the op, which is the correct direction for
124
+ a check whose output is an instruction to delete code. Known-answer canaries are planted in both trees
125
+ so recall has a denominator at all.
123
126
 
124
- ```jsonc
125
- // request
126
- { "op": "callers", "target": "safe_null_result", "engine": "auto" }
127
+ **The numbers.**
127
128
 
128
- // response always this exact envelope; `result` is ready-to-read markdown
129
- {
130
- "ok": true, "op": "callers", "target": "safe_null_result",
131
- "engine": "graph", "cached": false,
132
- "result": "## Callers of safe_null_result (7)\n- …gateway [USAGE] (src/codeintel/gateway.py)\n- …providers.graph [USAGE] (src/codeintel/providers/graph.py)\n- …server [USAGE] (src/codeintel/server.py)\n- … (4 more)"
133
- }
134
- ```
129
+ | | precision | recall |
130
+ |---|---|---|
131
+ | as shipped | **25%** (6 of 24) | 60% (6 of 10) |
132
+ | with the two repairs this codebase already contains elsewhere | 89% (8 of 9) | 80% (8 of 10) |
133
+
134
+ And the measurement that decided it — **real code only, canaries removed**: the op as shipped named
135
+ **18 candidates across those two repositories, and every one of them was live.** All 18 were Makefile
136
+ targets, which the graph backend indexes as `Function` nodes. Repaired, it names exactly one, and
137
+ that one is `MockRequest.get_type` in requests — a method `http.cookiejar` calls by duck-typed
138
+ convention, whose name appears once in the source.
139
+
140
+ **Why it was not repaired further.** The verification was a name-frequency scan over the source, so it
141
+ fails on exactly one condition: a symbol whose name appears once and is called by a convention
142
+ outside the source. Two repositories produced three distinct instances of that condition — non-code
143
+ nodes labelled `Function`, interpreter-called dunders, and stdlib duck-typed protocol methods — and
144
+ the earlier TypeScript evidence adds a rollup plugin hook and object-literal properties. The set is
145
+ not enumerable: no specification lists `get_type`. Every repository added revealed a new member of it.
146
+
147
+ Weighed against that: in 2,425 real definitions across two maintained repositories there was **not
148
+ one** dead private symbol to find. An op whose measured yield on real code is zero true positives has
149
+ no benefit to set against that error rate.
135
150
 
136
- The agent hands `result` straight to the model. If the graph backend isn't installed, the identical call returns `"result": null, "reason": "engine-unavailable"` — no exception, and the agent just falls back to its own search.
151
+ **Use `callers` on a specific symbol instead.** "Does anything call this?" is exactly the question
152
+ `deadcode` was trying to answer in bulk, and `callers` answers it accurately, one symbol at a time.
137
153
 
138
154
  ## What makes it good
139
155
 
@@ -181,6 +197,21 @@ codeintel install # registers with the agents you actually have insta
181
197
  codeintel query --op search --target "authentication middleware"
182
198
  ```
183
199
 
200
+ ### Or: have your agent set it up
201
+
202
+ Prefer to let your coding agent run the steps? Generate a paste-ready prompt, tailored to this
203
+ machine and agent:
204
+
205
+ ```bash
206
+ codeintel prompt # this repo — probes health, emits only the steps still outstanding
207
+ codeintel prompt --fresh | pbcopy # the full sequence from `pip install`, to send a friend
208
+ ```
209
+
210
+ It runs a `doctor` probe and prints a block you copy into Claude Code / Codex / Gemini / Zed: the
211
+ exact remaining commands (or "just restart me" when everything is already healthy and registered),
212
+ a `doctor --deep` verification, and the reminder to restart the agent so the MCP tools load. The
213
+ prompt goes to stdout (so `| pbcopy` grabs exactly it); the "paste this" note goes to stderr.
214
+
184
215
  ### Enable native Codex integration
185
216
 
186
217
  `codeintel` is an MCP server, so Codex can call its tools directly rather than invoking the CLI.
@@ -364,6 +395,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
364
395
  | `codeintel help` | Every command grouped by task, with descriptions and examples (also the bare `codeintel`). A mistyped command suggests what you meant. |
365
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 |
366
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
+ | `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 |
367
399
  | `codeintel index [project_root]` | Index a project for semantic search |
368
400
  | `codeintel serve` | Start the MCP server (stdio transport) |
369
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) |
@@ -517,7 +549,7 @@ back to grep rather than crashing.
517
549
 
518
550
  | Area | Why |
519
551
  |---|---|
520
- | `deadcode` | Withdrawn by default (`reason: "op-withdrawn"`) — measured wrong in both directions on real repos. Use `callers` on a specific symbol instead — [details](#deadcode-is-withdrawn). |
552
+ | `deadcode` | Withdrawn, then **retired** (`reason: "op-withdrawn"`) — a labelled corpus measured 25% precision. Use `callers` on a specific symbol instead — [the measurement](#deadcode-is-retired). |
521
553
  | Non-loopback serving | `serve-http` is stdlib `http.server`. It binds loopback by default for a reason; front it with a reverse proxy and see [docs/deploy.md](docs/deploy.md). |
522
554
  | RBAC between **untrusting** tenants | It separates privilege levels among callers you already trust. It is not a wall against an adversary with write access to their own root — see the warning in [docs/deploy.md](docs/deploy.md). |
523
555
  | Unattended automation | Anything that acts on a result without a human reading it deserves a pilot first. |
@@ -541,7 +573,7 @@ not write, and that is where its bugs have come from — every fix in `0.15.x` c
541
573
  at an unfamiliar codebase. Its characteristic failure mode is **answering confidently from the
542
574
  wrong index rather than failing loudly**, which the never-raise contract makes harder to notice: a
543
575
  wrong answer and a right one are the same shape. Run `codeintel doctor` before trusting a repo-wide
544
- answer — and `deadcode` in particular is withdrawn rather than merely caveated (see above) — and if
576
+ answer — and `deadcode` in particular is retired rather than merely caveated (see above) — and if
545
577
  something looks off please [report it](#reporting-a-problem) — an issue from someone who is not the
546
578
  author is the single most useful thing this project can receive right now.
547
579
 
@@ -9,12 +9,16 @@ src/codecortex.egg-info/requires.txt
9
9
  src/codecortex.egg-info/top_level.txt
10
10
  src/codeintel/__init__.py
11
11
  src/codeintel/__main__.py
12
+ src/codeintel/agent_prompt.py
12
13
  src/codeintel/auth.py
13
14
  src/codeintel/cache.py
14
15
  src/codeintel/config.py
15
16
  src/codeintel/containment.py
16
17
  src/codeintel/doctor.py
17
18
  src/codeintel/gateway.py
19
+ src/codeintel/graph_backend.py
20
+ src/codeintel/graph_render.py
21
+ src/codeintel/graph_resolution.py
18
22
  src/codeintel/grapher.py
19
23
  src/codeintel/http_server.py
20
24
  src/codeintel/indexer.py
@@ -46,6 +50,7 @@ src/codeintel/commands/graph.py
46
50
  src/codeintel/commands/index.py
47
51
  src/codeintel/commands/install.py
48
52
  src/codeintel/commands/map.py
53
+ src/codeintel/commands/prompt.py
49
54
  src/codeintel/commands/query.py
50
55
  src/codeintel/commands/reset.py
51
56
  src/codeintel/commands/serve.py
@@ -59,6 +64,8 @@ src/codeintel/providers/none.py
59
64
  src/codeintel/providers/semantic.py
60
65
  src/codeintel/viewer/__init__.py
61
66
  src/codeintel/viewer/graph_template.html
67
+ tests/test_agent_prompt.py
68
+ tests/test_backend_reaper.py
62
69
  tests/test_cache.py
63
70
  tests/test_chunking.py
64
71
  tests/test_cli_commands.py
@@ -0,0 +1 @@
1
+ __version__ = "0.16.0"
@@ -18,6 +18,7 @@ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
18
18
  ("setup", "Prepare backends + index this repo (--all does everything automatable)"),
19
19
  ("index", "Index a project for semantic search"),
20
20
  ("install", "Register codeintel with the AI agents installed on this machine"),
21
+ ("prompt", "Print a paste-to-your-agent setup prompt, tailored to this machine"),
21
22
  ]),
22
23
  ("Check health", [
23
24
  ("doctor", "Per-engine health + index status, with the fix for each gap"),
@@ -44,6 +45,7 @@ _MODULES = {
44
45
  "setup": "setup",
45
46
  "index": "index",
46
47
  "install": "install",
48
+ "prompt": "prompt",
47
49
  "doctor": "doctor",
48
50
  "status": "status",
49
51
  "reset": "reset",
@@ -236,6 +238,18 @@ def main() -> None:
236
238
  setup_parser.add_argument("--warm", action="store_true", help="Boot serena now (first run pulls it via uvx; slow)")
237
239
  setup_parser.add_argument("--json", action="store_true", help="Emit the structured JSON report")
238
240
 
241
+ # prompt subcommand
242
+ prompt_parser = subparsers.add_parser("prompt", parents=[color_parent],
243
+ help="Print a paste-to-your-agent setup prompt for this repo")
244
+ prompt_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
245
+ prompt_parser.add_argument("--agent", default="auto",
246
+ help="Agent the prompt targets: claude|codex|gemini|zed|auto (default: auto)")
247
+ prompt_parser.add_argument("--fresh", action="store_true",
248
+ help="Emit the full sequence from `pip install`, ignoring local state "
249
+ "(a template to paste to a friend on a clean machine)")
250
+ prompt_parser.add_argument("--deep", action="store_true",
251
+ help="Boot-check serena while probing (slower; sharper 'already set up' result)")
252
+
239
253
  # reset subcommand
240
254
  reset_parser = subparsers.add_parser("reset", parents=[color_parent],
241
255
  help="Clear the semantic index (recover from a corrupt/stale DB)")
@@ -0,0 +1,107 @@
1
+ """`codeintel prompt` — generate a paste-to-your-agent setup prompt, tailored to this machine.
2
+
3
+ `setup` DOES the bring-up and `install` REGISTERS the MCP server; this hands the same job to your
4
+ coding agent instead. It runs a doctor probe, sees which engines are actually missing and whether
5
+ this agent is already registered, and prints a natural-language prompt you copy into Claude Code /
6
+ Codex / etc. — telling it the exact remaining commands for THIS repo, to verify with `doctor`, and to
7
+ have you restart it so the MCP tools load. A static prompt would just repeat the README; reflecting
8
+ the live health is what makes it worth a command.
9
+
10
+ Two modes: the default reflects this machine (it drops steps already satisfied, and when everything
11
+ is healthy and registered it reduces to "just restart me"); `--fresh` ignores local state and emits
12
+ the full sequence from `pip install`, for pasting to a friend on a clean machine.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from typing import Any
17
+
18
+ from codeintel.installer import _AGENTS, detect_agents
19
+
20
+ # On PyPI the distribution is `codecortex` (the name `codeintel` was taken); the CLI and import stay
21
+ # `codeintel`. A pasted prompt has to name the installable, so this is the one place the CLI spells it.
22
+ _PYPI_PACKAGE = "codecortex"
23
+
24
+
25
+ def resolve_agent(agent: str) -> str:
26
+ """Which agent the prompt targets. A named agent is honoured; `auto` picks the one installed on
27
+ this machine (the first, if several); `""` when none is detected → the prompt says 'your agent'
28
+ and omits the `--agent` flag rather than guessing a host you do not have."""
29
+ a = (agent or "auto").strip().lower()
30
+ if a in _AGENTS:
31
+ return a
32
+ detected = detect_agents()
33
+ return detected[0] if detected else ""
34
+
35
+
36
+ def build_prompt(project_root: str, agent: str, report: dict[str, Any], *, fresh: bool = False) -> str:
37
+ """The paste-ready prompt, tailored to what *report* (a doctor report) says is still missing.
38
+
39
+ Pure and side-effect-free so a test can assert the tailoring without a live backend. With
40
+ ``fresh=True`` the local state is ignored and every step is emitted."""
41
+ engines = {} if fresh else (report.get("engines") or {})
42
+
43
+ def _ok(name: str) -> bool:
44
+ return (engines.get(name) or {}).get("status") == "ok"
45
+
46
+ graph_ok, lsp_ok, semantic_ok = _ok("graph"), _ok("lsp"), _ok("semantic")
47
+ healthy = (not fresh) and bool((report.get("summary") or {}).get("healthy"))
48
+ registered = (not fresh) and any(
49
+ r.get("agent") == agent and r.get("runnable")
50
+ for r in (report.get("registrations") or [])
51
+ )
52
+ install_flag = f" --agent {agent}" if agent else ""
53
+ tools = "code.query, code.doctor, code.map"
54
+
55
+ # Already fully set up: the one thing an agent cannot do for itself is get restarted.
56
+ if healthy and registered:
57
+ return (
58
+ f"codeintel is already installed and healthy for this project ({project_root}) and "
59
+ f"registered as one of your MCP servers — nothing to install. Confirm with "
60
+ f"`codeintel doctor --deep {project_root}` (expect \"ready 3/3, healthy\"), then tell me "
61
+ f"to restart you so your codeintel MCP tools ({tools}) load."
62
+ )
63
+
64
+ # Emit only the steps that are actually outstanding.
65
+ steps: list[str] = []
66
+ pip_targets: list[str] = []
67
+ if not semantic_ok:
68
+ pip_targets.append(_PYPI_PACKAGE)
69
+ if not graph_ok:
70
+ pip_targets.append("'codebase-memory-mcp==0.9.*'")
71
+ if pip_targets:
72
+ adds = " and ".join(
73
+ part for part in (
74
+ "the codeintel CLI + semantic engine" if not semantic_ok else "",
75
+ "the graph engine" if not graph_ok else "",
76
+ ) if part
77
+ )
78
+ steps.append(f"`pip install {' '.join(pip_targets)}` — installs {adds}")
79
+ # setup --all installs uv, indexes, and warms serena — needed unless all three engines are
80
+ # already ok (in which case the repo is indexed too, and only registration can be outstanding).
81
+ if not (graph_ok and lsp_ok and semantic_ok):
82
+ steps.append(
83
+ f"`codeintel setup --all {project_root}` — installs uv (the LSP engine), indexes this "
84
+ f"repo, and warms serena; idempotent, safe to re-run"
85
+ )
86
+ if not registered:
87
+ steps.append(f"`codeintel install{install_flag}` — registers codeintel as my MCP server")
88
+
89
+ numbered = "\n".join(f"{i}. {s}" for i, s in enumerate(steps, 1))
90
+ return (
91
+ f"Set up the codeintel code-intelligence tool for this project ({project_root}) and wire it "
92
+ f"into me. Run these in order, showing me the output of each:\n\n{numbered}\n\n"
93
+ f"Then run `codeintel doctor --deep {project_root}` and show me the result — I expect "
94
+ f"\"ready 3/3, healthy\". If any engine is not \"ok\", run the exact remediation `doctor` "
95
+ f"prints for it and re-check. When it is healthy, tell me to restart you so my codeintel MCP "
96
+ f"tools ({tools}) load."
97
+ )
98
+
99
+
100
+ def run_prompt(project_root: str, agent: str = "auto", *, fresh: bool = False, deep: bool = False) -> str:
101
+ """Probe (unless ``fresh``) and render the prompt. Never raises here — the command wrapper also
102
+ guards, but keeping the probe optional means `--fresh` needs no backend at all."""
103
+ report: dict[str, Any] = {}
104
+ if not fresh:
105
+ from codeintel import doctor as _doctor
106
+ report = _doctor.run_doctor(project_root, deep=deep)
107
+ return build_prompt(project_root, resolve_agent(agent), report, fresh=fresh)