codecortex 0.23.2__tar.gz → 0.23.3__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.2/src/codecortex.egg-info → codecortex-0.23.3}/PKG-INFO +1 -1
- {codecortex-0.23.2 → codecortex-0.23.3/src/codecortex.egg-info}/PKG-INFO +1 -1
- codecortex-0.23.3/src/codeintel/__init__.py +1 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/gateway.py +46 -23
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/providers/lsp.py +48 -1
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_lsp_provider.py +58 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_null_reason_and_ripple.py +64 -0
- codecortex-0.23.2/src/codeintel/__init__.py +0 -1
- {codecortex-0.23.2 → codecortex-0.23.3}/LICENSE +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/README.md +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/pyproject.toml +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/setup.cfg +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codecortex.egg-info/SOURCES.txt +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codecortex.egg-info/dependency_links.txt +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codecortex.egg-info/entry_points.txt +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codecortex.egg-info/requires.txt +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codecortex.egg-info/top_level.txt +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/__main__.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/agent_prompt.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/auth.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/c4.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/c4_check.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/c4_layers.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/cache.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/__init__.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/_common.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/c4.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/doctor.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/gen_token.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/graph.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/index.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/install.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/map.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/prompt.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/query.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/reset.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/serve.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/serve_http.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/setup.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/commands/status.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/config.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/containment.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/doctor.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/graph_backend.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/graph_render.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/graph_resolution.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/grapher.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/http_server.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/indexer.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/injector.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/installer.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/lang_config.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/loc.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/logconfig.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/mapper.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/metrics.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/onboarding.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/outcome.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/paths.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/policy.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/progress.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/provider.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/providers/__init__.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/providers/graph.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/providers/none.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/providers/semantic.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/py.typed +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/query_ops.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/redact.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/reindexer.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/reset.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/searcher.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/semantic_db.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/server.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/source_kind.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/term.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/verify.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/viewer/__init__.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/viewer/graph_template.html +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/src/codeintel/wire_text.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_agent_prompt.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_backend_reaper.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_background_index_failure.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_bench_oracle.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_bench_oracle_ts.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_c4.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_c4_check.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_c4_layers.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_c4_model.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_c4_output.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_cache.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_chunking.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_cli_commands.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_cli_help.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_cli_install_dry_run.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_cli_install_offer_injection.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_cold_process.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_config.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_corpus.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_could_not_ask_semantic.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_docs_ci_claims.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_docs_deadcode_withdrawal.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_doctor.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_e2e.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_edge_confidence.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_engine_adoption.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_enterprise.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_gateway.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_graph_failure_population.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_graph_provider.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_graph_real.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_graph_stdin.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_grapher.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_hard_exit.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_hardening.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_http_auth.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_http_server.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_incompleteness.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_index_failure_reason.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_index_progress.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_indexer_binary.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_installer.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_integration.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_lang_config.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_loc_census.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_lsp_language_coverage.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_lsp_real.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_mapper.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_mcp_handshake.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_mcp_server.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_model_dimension.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_never_raise.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_onboarding.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_rbac.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_redaction_boundary.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_reindexer.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_rerank.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_reset.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_semantic_provider.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_source_kind.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_staleness.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_symbol_context.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_term.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_treesitter.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_verify.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_verify_call.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_version_skew.py +0 -0
- {codecortex-0.23.2 → codecortex-0.23.3}/tests/test_wire_text.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.3
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.3
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.23.3"
|
|
@@ -174,7 +174,7 @@ class Gateway:
|
|
|
174
174
|
self, result: Result, op: str, target: str, budget: Any,
|
|
175
175
|
project_root: Any, was_auto: bool,
|
|
176
176
|
) -> Result:
|
|
177
|
-
"""Ask the LSP when the graph's
|
|
177
|
+
"""Ask the LSP when the graph's caller answer carries a known binding failure.
|
|
178
178
|
|
|
179
179
|
This closes the routing gap that let the worst failure through. `_AUTO_ENGINE` is a static
|
|
180
180
|
op→engine map, not a cascade: `callers` goes to the graph and, uniquely among the ops, has
|
|
@@ -185,48 +185,71 @@ class Gateway:
|
|
|
185
185
|
being confidently wrong is a bug; no second engine ever being consulted is the design that
|
|
186
186
|
let it reach the caller.
|
|
187
187
|
|
|
188
|
-
Deliberately narrow. It fires
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
not call edges), so presenting its
|
|
193
|
-
another. And it never fires for an
|
|
194
|
-
said which engine they want."""
|
|
188
|
+
Deliberately narrow. It fires when every graph row was name-resolved, or when a caller used
|
|
189
|
+
a file hint and the graph found same-named symbols but no edge for that exact definition.
|
|
190
|
+
The latter catches property calls that the TypeScript graph resolver failed to bind, while
|
|
191
|
+
avoiding an LSP call for ordinary healthy answers. It APPENDS rather than replaces: the LSP
|
|
192
|
+
answers a related but different question (references, not call edges), so presenting its
|
|
193
|
+
list as the graph's would substitute one over-claim for another. And it never fires for an
|
|
194
|
+
explicitly pinned `--engine graph`, where the caller has said which engine they want."""
|
|
195
195
|
try:
|
|
196
196
|
if not was_auto or op not in self._CROSS_CHECKED_OPS:
|
|
197
197
|
return result
|
|
198
198
|
if result.get("result") is None or self.lsp is None:
|
|
199
199
|
return result
|
|
200
200
|
gaps = result.get("gaps") or []
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
kinds = {
|
|
202
|
+
str(g.get("kind") or "") for g in gaps if isinstance(g, dict)
|
|
203
|
+
}
|
|
204
|
+
all_name_resolved = "all-rows-name-resolved" in kinds
|
|
205
|
+
exact_target_unbound = "target-hint-unmatched" in kinds
|
|
206
|
+
if not (all_name_resolved or exact_target_unbound):
|
|
203
207
|
return result
|
|
208
|
+
graph_problem = (
|
|
209
|
+
"every graph row was resolved by name"
|
|
210
|
+
if all_name_resolved
|
|
211
|
+
else "the graph found no caller edge for the file-qualified symbol"
|
|
212
|
+
)
|
|
204
213
|
probe = self._dispatch_single(
|
|
205
214
|
self.lsp, "symbol", target, budget, project_root, "lsp")
|
|
206
215
|
body = probe.get("result")
|
|
207
|
-
|
|
216
|
+
probe_gaps = probe.get("gaps") or []
|
|
217
|
+
reference_gap = next((
|
|
218
|
+
gap for gap in probe_gaps
|
|
219
|
+
if isinstance(gap, dict) and gap.get("section") == "references"
|
|
220
|
+
), None)
|
|
221
|
+
if not body or reference_gap is not None:
|
|
208
222
|
# Silence from the LSP is not agreement. Say which check did not happen — and
|
|
209
223
|
# separate "not yet booted" from "had nothing", because only the first is fixed by
|
|
210
224
|
# asking again. A one-shot CLI process meets a cold serena on every invocation; the
|
|
211
225
|
# long-lived MCP server keeps the session warm and takes this branch once at most.
|
|
212
226
|
# Waiting here is deliberately NOT done: it would hold back a graph answer that is
|
|
213
227
|
# already complete, to append a section that is only advisory.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
228
|
+
unavailable_reason = (
|
|
229
|
+
str(reference_gap.get("kind") or "not-asked")
|
|
230
|
+
if reference_gap is not None else str(probe.get("reason") or "no-result")
|
|
231
|
+
)
|
|
232
|
+
warming = unavailable_reason in {"warming", "timeout"}
|
|
233
|
+
why = (
|
|
234
|
+
"the language server had not finished booting"
|
|
235
|
+
if unavailable_reason == "warming"
|
|
236
|
+
else str(reference_gap.get("detail") or "the reference lookup did not answer")
|
|
237
|
+
if reference_gap is not None
|
|
238
|
+
else "the LSP engine reported nothing for this symbol"
|
|
239
|
+
)
|
|
217
240
|
nxt = (" Ask again once it is warm and this section will be filled in."
|
|
218
241
|
if warming else
|
|
219
242
|
" Check it yourself with `--engine lsp --op symbol`.")
|
|
220
243
|
return {**result, "gaps": [*gaps, {
|
|
221
244
|
"section": op, "kind": "cross-check-unavailable",
|
|
222
245
|
"engine": "lsp",
|
|
223
|
-
"reason":
|
|
224
|
-
"detail": f"
|
|
225
|
-
f"
|
|
246
|
+
"reason": unavailable_reason,
|
|
247
|
+
"detail": f"{graph_problem} and the LSP could not provide an independent "
|
|
248
|
+
f"reference check ({why}), so the graph answer remains unverified"
|
|
226
249
|
+ (" — retry" if warming else ""),
|
|
227
250
|
**({"retry_after_s": 2} if warming else {}),
|
|
228
251
|
}], "result": str(result["result"]) + (
|
|
229
|
-
f"\n\n> Cross-check unavailable:
|
|
252
|
+
f"\n\n> Cross-check unavailable: {graph_problem}, and "
|
|
230
253
|
f"{why}, so nothing here has been confirmed against a second engine.{nxt}"
|
|
231
254
|
)}
|
|
232
255
|
refs = self._reference_lines(str(body))
|
|
@@ -236,15 +259,15 @@ class Gateway:
|
|
|
236
259
|
merged = (
|
|
237
260
|
f"{result['result']}\n\n## Cross-check — LSP references to `{target}` "
|
|
238
261
|
f"({len(refs)})\n"
|
|
239
|
-
f"_The
|
|
240
|
-
f"language server, which resolves
|
|
241
|
-
f"but absent here is
|
|
242
|
-
f"is a
|
|
262
|
+
f"_The graph answer has a known binding gap: {graph_problem}. These locations come "
|
|
263
|
+
f"from the language server, which resolves the exact definition. A caller listed "
|
|
264
|
+
f"above but absent here is likely a name collision; a location here but missing "
|
|
265
|
+
f"above is a reference the graph could not bind._\n" + listing + more
|
|
243
266
|
)
|
|
244
267
|
return {**result, "result": merged, "gaps": [*gaps, {
|
|
245
268
|
"section": op, "kind": "cross-checked-with-lsp",
|
|
246
269
|
"engine": "lsp",
|
|
247
|
-
"detail": f"
|
|
270
|
+
"detail": f"{graph_problem}, so the LSP was consulted "
|
|
248
271
|
f"independently and reported {len(refs)} reference location(s); the two "
|
|
249
272
|
f"lists answer related but different questions and are shown separately",
|
|
250
273
|
}]}
|
|
@@ -67,6 +67,30 @@ _BACKEND_ERROR_MARKERS = (
|
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def _split_target_file_hint(target: str) -> tuple[str, str]:
|
|
71
|
+
"""Return Serena's symbol pattern and an optional repo-relative file hint.
|
|
72
|
+
|
|
73
|
+
Graph operations accept ``name@path`` to disambiguate duplicate symbols. The gateway may pass
|
|
74
|
+
that exact target to the LSP for an independent reference cross-check, but Serena understands
|
|
75
|
+
only the name-path portion. Keep the syntax aligned here without coupling the LSP provider to
|
|
76
|
+
graph.py's private target parser.
|
|
77
|
+
"""
|
|
78
|
+
raw = str(target or "").strip()
|
|
79
|
+
if "@" not in raw:
|
|
80
|
+
return raw, ""
|
|
81
|
+
head, _, tail = raw.rpartition("@")
|
|
82
|
+
if not head.strip() or not tail.strip():
|
|
83
|
+
return raw, ""
|
|
84
|
+
return head.strip(), tail.strip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _file_hint_matches(file_path: Any, hint: str) -> bool:
|
|
88
|
+
"""Match a file hint on path-segment boundaries, consistently with the graph provider."""
|
|
89
|
+
have = str(file_path or "").replace("\\", "/").strip().strip("/").lower()
|
|
90
|
+
want = str(hint or "").replace("\\", "/").strip().strip("/").lower()
|
|
91
|
+
return bool(have and want and (have == want or have.endswith("/" + want)))
|
|
92
|
+
|
|
93
|
+
|
|
70
94
|
def _looks_like_backend_error(text: str) -> bool:
|
|
71
95
|
"""Whether *text* is a backend failure message rather than a result.
|
|
72
96
|
|
|
@@ -773,10 +797,18 @@ class LspProvider:
|
|
|
773
797
|
self, session: _LspSession, target: str, root: str, timeout_s: float
|
|
774
798
|
) -> str | None:
|
|
775
799
|
try:
|
|
800
|
+
symbol_target, file_hint = _split_target_file_hint(target)
|
|
776
801
|
def_out = self._call_tool(
|
|
777
802
|
session,
|
|
778
803
|
"find_symbol",
|
|
779
|
-
{
|
|
804
|
+
{
|
|
805
|
+
"name_path_pattern": symbol_target,
|
|
806
|
+
"include_body": True,
|
|
807
|
+
# A file hint exists specifically because the name is ambiguous. Retrieve
|
|
808
|
+
# enough definitions to find the requested file instead of silently selecting
|
|
809
|
+
# whichever five Serena happens to return first.
|
|
810
|
+
"max_matches": 50 if file_hint else 5,
|
|
811
|
+
},
|
|
780
812
|
timeout_s,
|
|
781
813
|
)
|
|
782
814
|
if isinstance(def_out, Missing):
|
|
@@ -789,6 +821,21 @@ class LspProvider:
|
|
|
789
821
|
def_raw = def_out.value
|
|
790
822
|
def_text = self._extract_text(def_raw)
|
|
791
823
|
matches = self._loads(def_text)
|
|
824
|
+
if isinstance(matches, list) and file_hint:
|
|
825
|
+
matches = [
|
|
826
|
+
match for match in matches
|
|
827
|
+
if isinstance(match, dict)
|
|
828
|
+
and _file_hint_matches(match.get("relative_path"), file_hint)
|
|
829
|
+
]
|
|
830
|
+
if not matches:
|
|
831
|
+
# Do not fall through and render the original JSON containing definitions
|
|
832
|
+
# from other files. More importantly, the reference lookup below must remain
|
|
833
|
+
# "not asked", rather than turning an unresolved exact definition into a
|
|
834
|
+
# confident zero-reference answer.
|
|
835
|
+
def_text = (
|
|
836
|
+
f"> The language server found no definition for `{symbol_target}` in "
|
|
837
|
+
f"`{file_hint}`."
|
|
838
|
+
)
|
|
792
839
|
|
|
793
840
|
first: dict | None = None
|
|
794
841
|
if isinstance(matches, list) and matches:
|
|
@@ -163,6 +163,64 @@ def test_lsp_provider_ready_symbol(monkeypatch):
|
|
|
163
163
|
assert "parse_result" in r["result"]
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def test_file_qualified_symbol_selects_the_matching_definition(monkeypatch):
|
|
167
|
+
"""A graph file hint must survive the LSP cross-check instead of selecting a same-name method."""
|
|
168
|
+
monkeypatch.setattr("codeintel.providers.lsp.shutil.which", lambda x: "/fake/uvx")
|
|
169
|
+
p = LspProvider()
|
|
170
|
+
p._sessions["/my/repo"] = _make_fake_session(_State.READY)
|
|
171
|
+
calls: list[tuple[str, dict]] = []
|
|
172
|
+
|
|
173
|
+
def _fake_call_tool(session, tool, args, timeout_s):
|
|
174
|
+
calls.append((tool, args))
|
|
175
|
+
if tool == "find_symbol":
|
|
176
|
+
return Ok('[{"name_path":"Other/createSession","kind":"Method",'
|
|
177
|
+
'"relative_path":"src/other.ts","body_location":{"start_line":1,'
|
|
178
|
+
'"end_line":2},"body":"wrong"},{"name_path":"WsSessionHandler/createSession",'
|
|
179
|
+
'"kind":"Method","relative_path":"backend/src/session.handler.ts",'
|
|
180
|
+
'"body_location":{"start_line":138,"end_line":177},"body":"right"}]')
|
|
181
|
+
if tool == "find_referencing_symbols":
|
|
182
|
+
assert args["name_path"] == "WsSessionHandler/createSession"
|
|
183
|
+
assert args["relative_path"] == "backend/src/session.handler.ts"
|
|
184
|
+
return Ok('{"backend/src/gateway.ts":{"Method":[{"name_path":"Gateway/routeMessage",'
|
|
185
|
+
'"content_around_reference":"> 612: await handler.createSession()"}]}}')
|
|
186
|
+
return Missing("backend-error", "unstubbed tool")
|
|
187
|
+
|
|
188
|
+
monkeypatch.setattr(p, "_call_tool", _fake_call_tool)
|
|
189
|
+
r = p.build_result(
|
|
190
|
+
"symbol", "createSession@backend/src/session.handler.ts", [], 30000, "/my/repo"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
assert r["confidence"] == "complete"
|
|
194
|
+
assert "right" in r["result"] and "wrong" not in r["result"]
|
|
195
|
+
assert "backend/src/gateway.ts:613" in r["result"]
|
|
196
|
+
assert calls[0][1]["name_path_pattern"] == "createSession"
|
|
197
|
+
assert calls[0][1]["max_matches"] == 50
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_file_qualified_symbol_does_not_use_a_definition_from_another_file(monkeypatch):
|
|
201
|
+
monkeypatch.setattr("codeintel.providers.lsp.shutil.which", lambda x: "/fake/uvx")
|
|
202
|
+
p = LspProvider()
|
|
203
|
+
p._sessions["/my/repo"] = _make_fake_session(_State.READY)
|
|
204
|
+
calls: list[str] = []
|
|
205
|
+
|
|
206
|
+
def _fake_call_tool(session, tool, args, timeout_s):
|
|
207
|
+
calls.append(tool)
|
|
208
|
+
return Ok('[{"name_path":"Other/createSession","kind":"Method",'
|
|
209
|
+
'"relative_path":"src/other.ts","body_location":{"start_line":1,'
|
|
210
|
+
'"end_line":2},"body":"wrong"}]')
|
|
211
|
+
|
|
212
|
+
monkeypatch.setattr(p, "_call_tool", _fake_call_tool)
|
|
213
|
+
r = p.build_result(
|
|
214
|
+
"symbol", "createSession@backend/src/session.handler.ts", [], 30000, "/my/repo"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
assert r["confidence"] == "partial"
|
|
218
|
+
assert "found no definition" in r["result"] and "wrong" not in r["result"]
|
|
219
|
+
assert "References — not retrieved" in r["result"]
|
|
220
|
+
assert calls == ["find_symbol"]
|
|
221
|
+
assert any(g["section"] == "references" and g["kind"] == "not-asked" for g in r["gaps"])
|
|
222
|
+
|
|
223
|
+
|
|
166
224
|
# ---------------------------------------------------------------------------
|
|
167
225
|
# Group 8 — Unsupported op when READY
|
|
168
226
|
# ---------------------------------------------------------------------------
|
|
@@ -150,6 +150,70 @@ def test_an_all_guesses_answer_is_cross_checked_against_the_lsp():
|
|
|
150
150
|
assert any(g["kind"] == "cross-checked-with-lsp" for g in env["gaps"]), env["gaps"]
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
def test_a_file_qualified_graph_miss_is_cross_checked_against_the_lsp():
|
|
154
|
+
target = "createSession@backend/src/session.handler.ts"
|
|
155
|
+
graph = {
|
|
156
|
+
"ok": True,
|
|
157
|
+
"op": "callers",
|
|
158
|
+
"target": target,
|
|
159
|
+
"engine": "graph",
|
|
160
|
+
"result": "## Callers\nNo matching file-qualified symbol has callers in this index.",
|
|
161
|
+
"confidence": "partial",
|
|
162
|
+
"gaps": [{
|
|
163
|
+
"section": "callers",
|
|
164
|
+
"kind": "target-hint-unmatched",
|
|
165
|
+
"detail": "the exact symbol has no bound caller edge",
|
|
166
|
+
}],
|
|
167
|
+
}
|
|
168
|
+
lsp = {
|
|
169
|
+
"ok": True,
|
|
170
|
+
"result": f"## Symbol: {target}\n**Method** — backend/src/session.handler.ts:139\n"
|
|
171
|
+
"```\nbody\n```\n\n## References (1)\n"
|
|
172
|
+
"- backend/src/gateway.ts:613 (Gateway/routeMessage)\n",
|
|
173
|
+
"engine": "lsp",
|
|
174
|
+
"confidence": "complete",
|
|
175
|
+
}
|
|
176
|
+
calls: list[tuple[str, str]] = []
|
|
177
|
+
|
|
178
|
+
class _RecordingLsp(_StubLsp):
|
|
179
|
+
def build_result(self, op, asked_target, files, budget, project_root, **kw):
|
|
180
|
+
calls.append((op, asked_target))
|
|
181
|
+
return super().build_result(op, asked_target, files, budget, project_root, **kw)
|
|
182
|
+
|
|
183
|
+
gw = Gateway(graph=_StubGraph(graph), lsp=_RecordingLsp(lsp))
|
|
184
|
+
env = gw.query("callers", target, engine="auto", project_root=ROOT, budget=30000)
|
|
185
|
+
|
|
186
|
+
assert calls == [("symbol", target)]
|
|
187
|
+
assert "backend/src/gateway.ts:613" in env["result"]
|
|
188
|
+
assert "file-qualified symbol" in env["result"]
|
|
189
|
+
assert any(g["kind"] == "cross-checked-with-lsp" for g in env["gaps"])
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_an_unresolved_lsp_definition_does_not_claim_zero_references():
|
|
193
|
+
target = "createSession@backend/src/session.handler.ts"
|
|
194
|
+
graph = {
|
|
195
|
+
"ok": True, "op": "callers", "target": target, "engine": "graph",
|
|
196
|
+
"result": "No bound graph caller.", "confidence": "partial",
|
|
197
|
+
"gaps": [{"section": "callers", "kind": "target-hint-unmatched"}],
|
|
198
|
+
}
|
|
199
|
+
lsp = {
|
|
200
|
+
"ok": True, "engine": "lsp", "confidence": "partial",
|
|
201
|
+
"result": "## Symbol\nNo matching definition.\n\n## References — not retrieved",
|
|
202
|
+
"gaps": [{
|
|
203
|
+
"section": "references", "kind": "not-asked",
|
|
204
|
+
"detail": "the exact file-qualified definition was not resolved",
|
|
205
|
+
}],
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
env = Gateway(graph=_StubGraph(graph), lsp=_StubLsp(lsp)).query(
|
|
209
|
+
"callers", target, engine="auto", project_root=ROOT, budget=30000
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
assert "Cross-check unavailable" in env["result"]
|
|
213
|
+
assert "(no references reported)" not in env["result"]
|
|
214
|
+
assert any(g["kind"] == "cross-check-unavailable" for g in env["gaps"])
|
|
215
|
+
|
|
216
|
+
|
|
153
217
|
def test_a_healthy_graph_answer_is_never_sent_to_the_lsp():
|
|
154
218
|
"""The escalation is for the collision signature only. Firing it on ordinary answers would
|
|
155
219
|
double every callers query's cost for nothing."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.23.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|