descry-codegraph 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/CHANGELOG.md +33 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/CLAUDE.md +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/PKG-INFO +2 -2
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/README.md +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/pyproject.toml +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/__init__.py +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/_graph.py +5 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/cli.py +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/generate.py +14 -18
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/git_history.py +3 -4
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/handlers.py +120 -145
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/mcp_server.py +3 -3
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/query.py +25 -31
- descry_codegraph-0.3.1/src/descry/scip/adapters/_empty_environment.json +1 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/python.py +12 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/cache.py +7 -3
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/web/web/index.html +16 -12
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_generate.py +19 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_git_history.py +45 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_handlers.py +44 -3
- descry_codegraph-0.3.1/tests/test_interfaces.py +178 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_query.py +66 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_cache.py +38 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_web_server.py +42 -19
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/.github/assets/01-landing.png +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/.github/workflows/ci.yml +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/.github/workflows/release.yml +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/.gitignore +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/LICENSE +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/SECURITY.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/THIRD-PARTY-NOTICES.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/claude-skill/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/claude-skill/descry/SKILL.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/claude-skill/descry/references/cli-reference.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/claude-skill/descry/references/mcp-tools.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/claude-skill/descry/references/recipes.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/justfile +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/pi-extension/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/pi-extension/descry-cli.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/pi-extension/descry-tools.ts +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/_comments.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/_env.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/_files.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/_stdlib.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/clang_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/cross_lang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/dart_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/dotnet_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/embeddings.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/go_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/java_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/php_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/ruby_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapter.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/_java_compat.init.gradle +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/clang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/dart.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/dotnet.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/go.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/java.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/php.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/ruby.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/rust.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/adapters/typescript.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/pb2.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/scip/support.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/c.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/cpp.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/dart.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/dotnet.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/go.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/java.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/node.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/php.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/python.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/ruby.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/rust.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/stdlib/typescript.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/web/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/web/server.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/src/descry/web/web/style.css +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/conftest.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/compare.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/corpus.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/metrics.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/registry.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/results/results-descry.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/eval/results/results-ripgrep.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/fixtures/small_graph.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_backtracking.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_clang_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_cli.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_config.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_cross_lang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_dart_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_dotnet_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_embeddings.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_go_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_hardening.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_java_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_mcp.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_non_regular_files.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_php_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_ruby_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_clang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_dart.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_dotnet.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_go.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_java.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_php.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_scip_ruby.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_stdlib.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tests/test_written_through.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/common.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_c.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_cpp.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_dart.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_dotnet.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_go.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_java.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_node.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_php.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_python.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_ruby.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_rust.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.1}/tools/stdlib/gen_typescript.py +0 -0
|
@@ -6,6 +6,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.1] — 2026-10-22
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The callers a change lists (`changes`), and those `context` lists and
|
|
14
|
+
counts and an upstream `flow` follows, are the callers of that symbol,
|
|
15
|
+
not of every symbol of its name. `context` marks a caller that reaches
|
|
16
|
+
the symbol only as one of an unresolved call's candidates, as `callers`
|
|
17
|
+
does.
|
|
18
|
+
- A symbol's caller count (`in_degree`, shown as "N callers") counts the
|
|
19
|
+
symbols that call or construct it, not every edge into it: the file
|
|
20
|
+
defining it was counted as a caller.
|
|
21
|
+
- A Python signature is written as the source writes it, with defaults,
|
|
22
|
+
`*`, `**`, keyword-only parameters and `async`, and with no `-> None`
|
|
23
|
+
where no return type is annotated. An unannotated function was recorded
|
|
24
|
+
as returning `None`.
|
|
25
|
+
- The web UI's Language filter lists the graph's languages, which the
|
|
26
|
+
graph status now gives (`/api/status`, `/api/health`), instead of four
|
|
27
|
+
fixed ones.
|
|
28
|
+
- A search during the startup pre-warm, or during the embeddings load
|
|
29
|
+
after an index, waits for that load (up to `embedding_timeout`) instead
|
|
30
|
+
of reporting that embeddings are loading. A streamed index reports the
|
|
31
|
+
embeddings line however long the load takes.
|
|
32
|
+
- scip-python runs without `pip` on PATH and outside a git repository: it
|
|
33
|
+
is given an empty environment and a project version.
|
|
34
|
+
- A SCIP indexer's failure is reported by its error, without the stack
|
|
35
|
+
frames that pushed it out of the message.
|
|
36
|
+
- `flatten` takes a class name as `context` does, not only a node ID.
|
|
37
|
+
- `impls` says trait implementations are recorded for Rust only, instead
|
|
38
|
+
of advising a reindex.
|
|
39
|
+
- An unresolved call with one candidate reads "unresolved; the only
|
|
40
|
+
candidate", not "one of 1 candidates".
|
|
41
|
+
|
|
9
42
|
## [0.3.0] — 2026-10-22
|
|
10
43
|
|
|
11
44
|
Call resolution is rebuilt for every supported language: a call is
|
|
@@ -23,7 +23,7 @@ just test tests/test_handlers.py::test_index # one test
|
|
|
23
23
|
just test -k "cross_lang and not slow" # by expression
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Python 3.11+ required (enforced by `pyproject.toml`). Pre-commit gate: `just lint && just fmt && just test` all pass (
|
|
26
|
+
Python 3.11+ required (enforced by `pyproject.toml`). Pre-commit gate: `just lint && just fmt && just test` all pass (2755 tests currently — a drop is a regression signal).
|
|
27
27
|
|
|
28
28
|
Invoke the tool while developing:
|
|
29
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: descry-codegraph
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Codebase knowledge graph: polyglot indexer, semantic search, call-graph analysis
|
|
5
5
|
Project-URL: Homepage, https://github.com/06chaynes/descry
|
|
6
6
|
Project-URL: Repository, https://github.com/06chaynes/descry
|
|
@@ -251,7 +251,7 @@ Descry provides 19 tools, available through all interfaces:
|
|
|
251
251
|
| `path` | Find shortest call path between two symbols |
|
|
252
252
|
| `structure` | Show file structure — imports, classes, functions |
|
|
253
253
|
| `flatten` | Show effective API of a class including inherited methods |
|
|
254
|
-
| `impls` | Find all implementations of a trait
|
|
254
|
+
| `impls` | Find all implementations of a Rust trait method |
|
|
255
255
|
| `cross-lang` | Trace frontend API calls to backend handlers via OpenAPI |
|
|
256
256
|
| `churn` | Find code churn hotspots (symbols, files, or co-change pairs) |
|
|
257
257
|
| `evolution` | Track how a symbol has changed over time |
|
|
@@ -186,7 +186,7 @@ Descry provides 19 tools, available through all interfaces:
|
|
|
186
186
|
| `path` | Find shortest call path between two symbols |
|
|
187
187
|
| `structure` | Show file structure — imports, classes, functions |
|
|
188
188
|
| `flatten` | Show effective API of a class including inherited methods |
|
|
189
|
-
| `impls` | Find all implementations of a trait
|
|
189
|
+
| `impls` | Find all implementations of a Rust trait method |
|
|
190
190
|
| `cross-lang` | Trace frontend API calls to backend handlers via OpenAPI |
|
|
191
191
|
| `churn` | Find code churn hotspots (symbols, files, or co-change pairs) |
|
|
192
192
|
| `evolution` | Track how a symbol has changed over time |
|
|
@@ -11,6 +11,11 @@ from pathlib import Path
|
|
|
11
11
|
|
|
12
12
|
CURRENT_SCHEMA = 2
|
|
13
13
|
|
|
14
|
+
# The edges by which one symbol calls or constructs another: its callers
|
|
15
|
+
CALL_RELATIONS = frozenset(
|
|
16
|
+
{"CALLS", "CALLS_RESOLVED", "INSTANTIATES", "INSTANTIATES_RESOLVED"}
|
|
17
|
+
)
|
|
18
|
+
|
|
14
19
|
|
|
15
20
|
class GraphSchemaError(Exception):
|
|
16
21
|
"""Raised when a graph file is not a graph this version can read.
|
|
@@ -368,7 +368,7 @@ def main():
|
|
|
368
368
|
|
|
369
369
|
# impls
|
|
370
370
|
sub = subparsers.add_parser(
|
|
371
|
-
"impls", help="Find all implementations of a trait method"
|
|
371
|
+
"impls", help="Find all implementations of a Rust trait method"
|
|
372
372
|
)
|
|
373
373
|
sub.add_argument("method", help="Method name")
|
|
374
374
|
sub.add_argument("--trait-name", default=None, help="Filter by trait name")
|
|
@@ -8388,15 +8388,6 @@ class PythonParser(BaseParser):
|
|
|
8388
8388
|
return f"[{elts}]"
|
|
8389
8389
|
return "Complex"
|
|
8390
8390
|
|
|
8391
|
-
def _get_args_info(self, args_node):
|
|
8392
|
-
args_list = []
|
|
8393
|
-
for arg in args_node.args:
|
|
8394
|
-
arg_str = arg.arg
|
|
8395
|
-
if arg.annotation:
|
|
8396
|
-
arg_str += f": {self._get_type_annotation(arg.annotation)}"
|
|
8397
|
-
args_list.append(arg_str)
|
|
8398
|
-
return args_list
|
|
8399
|
-
|
|
8400
8391
|
def _get_param_types(self, args_node):
|
|
8401
8392
|
"""Extract parameter types as a list for metadata storage."""
|
|
8402
8393
|
param_types = []
|
|
@@ -8492,12 +8483,14 @@ class PythonParser(BaseParser):
|
|
|
8492
8483
|
is_method = parent_id in self.class_ids
|
|
8493
8484
|
node_type = "Method" if is_method else "Function"
|
|
8494
8485
|
|
|
8495
|
-
args = self._get_args_info(node.args)
|
|
8496
8486
|
param_types = self._get_param_types(node.args)
|
|
8497
8487
|
return_type = (
|
|
8498
|
-
self._get_type_annotation(node.returns) if node.returns else "
|
|
8488
|
+
self._get_type_annotation(node.returns) if node.returns else ""
|
|
8499
8489
|
)
|
|
8500
|
-
|
|
8490
|
+
keyword = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def"
|
|
8491
|
+
signature = f"{keyword} {node.name}({ast.unparse(node.args)})"
|
|
8492
|
+
if node.returns:
|
|
8493
|
+
signature += f" -> {ast.unparse(node.returns)}"
|
|
8501
8494
|
|
|
8502
8495
|
self.builder.add_node(
|
|
8503
8496
|
func_id,
|
|
@@ -25440,16 +25433,19 @@ class CodeGraphBuilder:
|
|
|
25440
25433
|
output_path: Path to write the graph JSON
|
|
25441
25434
|
scip_index: Optional ScipIndex for type-aware resolution
|
|
25442
25435
|
"""
|
|
25443
|
-
from descry._graph import CURRENT_SCHEMA
|
|
25436
|
+
from descry._graph import CALL_RELATIONS, CURRENT_SCHEMA
|
|
25444
25437
|
|
|
25445
25438
|
self.resolve_references(scip_index=scip_index)
|
|
25446
|
-
|
|
25439
|
+
# The number of its callers: those certain and those it is one of
|
|
25440
|
+
# the candidates of
|
|
25441
|
+
callers = {}
|
|
25447
25442
|
for edge in self.edges:
|
|
25448
|
-
|
|
25449
|
-
|
|
25450
|
-
|
|
25443
|
+
if edge["relation"] in CALL_RELATIONS:
|
|
25444
|
+
candidates = edge.get("metadata", {}).get("candidates", ())
|
|
25445
|
+
for target in (edge["target"], *candidates):
|
|
25446
|
+
callers.setdefault(target, set()).add(edge["source"])
|
|
25451
25447
|
for node in self.nodes:
|
|
25452
|
-
node["metadata"]["in_degree"] =
|
|
25448
|
+
node["metadata"]["in_degree"] = len(callers.get(node["id"], ()))
|
|
25453
25449
|
# Atomic write: stream to a sibling tmp file then os.replace. A
|
|
25454
25450
|
# concurrent reader (MCP prewarm, web `/api/health`, another CLI
|
|
25455
25451
|
# invocation) that stats the destination mid-write would otherwise
|
|
@@ -1662,8 +1662,8 @@ class GitHistoryAnalyzer:
|
|
|
1662
1662
|
|
|
1663
1663
|
# Show callers if requested
|
|
1664
1664
|
if show_callers and self.querier:
|
|
1665
|
-
|
|
1666
|
-
callers = self.querier.get_callers(
|
|
1665
|
+
# The callers of this symbol, not of every one of its name
|
|
1666
|
+
callers = self.querier.get_callers(node_id)
|
|
1667
1667
|
if callers:
|
|
1668
1668
|
caller_names = []
|
|
1669
1669
|
for caller_id in callers[:5]:
|
|
@@ -1742,8 +1742,7 @@ class GitHistoryAnalyzer:
|
|
|
1742
1742
|
}
|
|
1743
1743
|
|
|
1744
1744
|
if show_callers and self.querier:
|
|
1745
|
-
|
|
1746
|
-
caller_ids = self.querier.get_callers(sym_name)
|
|
1745
|
+
caller_ids = self.querier.get_callers(node_id)
|
|
1747
1746
|
if caller_ids:
|
|
1748
1747
|
callers = []
|
|
1749
1748
|
for caller_id in caller_ids[:5]:
|
|
@@ -749,7 +749,9 @@ def format_search_result(
|
|
|
749
749
|
|
|
750
750
|
score_str = f"[{score:.2f}] " if show_score else ""
|
|
751
751
|
metrics_str = (
|
|
752
|
-
f"({token_count} toks, {in_degree}
|
|
752
|
+
f"({token_count} toks, {in_degree} caller{'' if in_degree == 1 else 's'})"
|
|
753
|
+
if token_count or in_degree
|
|
754
|
+
else ""
|
|
753
755
|
)
|
|
754
756
|
|
|
755
757
|
lines = [f"{score_str}[{node_type}] {name} | {location} {metrics_str}".rstrip()]
|
|
@@ -1050,17 +1052,7 @@ class DescryService:
|
|
|
1050
1052
|
self._git_cache_lock = asyncio.Lock()
|
|
1051
1053
|
self._dedup_cache_lock = asyncio.Lock()
|
|
1052
1054
|
|
|
1053
|
-
self.
|
|
1054
|
-
self._querier_cache = {"mtime": 0, "instance": None, "error": None}
|
|
1055
|
-
self._semantic_cache = {
|
|
1056
|
-
"mtime": 0,
|
|
1057
|
-
"instance": None,
|
|
1058
|
-
"loading": False,
|
|
1059
|
-
"error": None,
|
|
1060
|
-
}
|
|
1061
|
-
self._semantic_load = None
|
|
1062
|
-
self._git_cache = {"analyzer": None, "graph_mtime": None}
|
|
1063
|
-
self._dedup_cache: dict[str, tuple[float, str]] = {}
|
|
1055
|
+
self.reset_caches()
|
|
1064
1056
|
self._max_dedup_entries = 100
|
|
1065
1057
|
|
|
1066
1058
|
# Index runs write the same cache directory, so one runs at a time
|
|
@@ -1095,6 +1087,7 @@ class DescryService:
|
|
|
1095
1087
|
"mtime": mtime,
|
|
1096
1088
|
"nodes": len(data.get("nodes", [])),
|
|
1097
1089
|
"edges": len(data.get("edges", [])),
|
|
1090
|
+
"languages": self._graph_languages(data),
|
|
1098
1091
|
"error": None,
|
|
1099
1092
|
}
|
|
1100
1093
|
# ValueError: invalid JSON or invalid UTF-8.
|
|
@@ -1105,9 +1098,24 @@ class DescryService:
|
|
|
1105
1098
|
"mtime": mtime,
|
|
1106
1099
|
"nodes": 0,
|
|
1107
1100
|
"edges": 0,
|
|
1101
|
+
"languages": [],
|
|
1108
1102
|
"error": str(e),
|
|
1109
1103
|
}
|
|
1110
1104
|
|
|
1105
|
+
def _graph_languages(self, data) -> list[str]:
|
|
1106
|
+
"""The languages of the graph's nodes, as the `lang` search filter
|
|
1107
|
+
names them and finds them: by the extension of the file a node is
|
|
1108
|
+
in."""
|
|
1109
|
+
if self._get_syntax_lang is None:
|
|
1110
|
+
return []
|
|
1111
|
+
paths = {
|
|
1112
|
+
node["id"][5:].split("::")[0]
|
|
1113
|
+
for node in data.get("nodes", [])
|
|
1114
|
+
if node.get("id", "").startswith("FILE:")
|
|
1115
|
+
}
|
|
1116
|
+
langs = {self._get_syntax_lang(p, self.config.syntax_lang_map) for p in paths}
|
|
1117
|
+
return sorted(langs - {""})
|
|
1118
|
+
|
|
1111
1119
|
async def _get_querier(self):
|
|
1112
1120
|
"""The cached GraphQuerier; None if the graph is missing or unloadable."""
|
|
1113
1121
|
if self._GraphQuerier is None:
|
|
@@ -1174,17 +1182,24 @@ class DescryService:
|
|
|
1174
1182
|
self._dedup_cache[content_hash] = (graph_mtime, node_id)
|
|
1175
1183
|
|
|
1176
1184
|
def reset_caches(self):
|
|
1177
|
-
"""Reset all cached instances.
|
|
1178
|
-
|
|
1185
|
+
"""Reset all cached instances. Called on construction and after a
|
|
1186
|
+
reindex."""
|
|
1187
|
+
self._graph_cache = {
|
|
1188
|
+
"mtime": 0,
|
|
1189
|
+
"nodes": 0,
|
|
1190
|
+
"edges": 0,
|
|
1191
|
+
"languages": [],
|
|
1192
|
+
"error": None,
|
|
1193
|
+
}
|
|
1179
1194
|
self._querier_cache = {"mtime": 0, "instance": None, "error": None}
|
|
1180
1195
|
self._semantic_cache = {
|
|
1181
1196
|
"mtime": 0,
|
|
1182
1197
|
"instance": None,
|
|
1183
|
-
"
|
|
1198
|
+
"load": None,
|
|
1184
1199
|
"error": None,
|
|
1185
1200
|
}
|
|
1186
1201
|
self._git_cache = {"analyzer": None, "graph_mtime": None}
|
|
1187
|
-
self._dedup_cache = {}
|
|
1202
|
+
self._dedup_cache: dict[str, tuple[float, str]] = {}
|
|
1188
1203
|
# The module-level file-content cache in query.py is keyed on mtime,
|
|
1189
1204
|
# so it self-invalidates after an edit; but a reindex can shift file
|
|
1190
1205
|
# contents even without an mtime bump (copies, git checkouts). Clear
|
|
@@ -1229,55 +1244,93 @@ class DescryService:
|
|
|
1229
1244
|
source_root=str(self.config.project_root),
|
|
1230
1245
|
)
|
|
1231
1246
|
|
|
1247
|
+
def _start_semantic_load(self, cache, build):
|
|
1248
|
+
"""Run ``build`` in a thread as the load of ``cache``, which holds
|
|
1249
|
+
what it builds, or the error it raises, as of the graph it began
|
|
1250
|
+
on. Called holding the lock; returns the task."""
|
|
1251
|
+
mtime = self._graph_mtime() or 0
|
|
1252
|
+
|
|
1253
|
+
async def load():
|
|
1254
|
+
try:
|
|
1255
|
+
# No timeout: the thread cannot be stopped, so a load given
|
|
1256
|
+
# up on would go on while another started.
|
|
1257
|
+
searcher = await asyncio.to_thread(build)
|
|
1258
|
+
async with self._semantic_cache_lock:
|
|
1259
|
+
cache.update(mtime=mtime, instance=searcher, error=None)
|
|
1260
|
+
return searcher
|
|
1261
|
+
except Exception as e: # noqa: BLE001 — boundary over sentence-transformers/torch model load; error is cached and logged
|
|
1262
|
+
async with self._semantic_cache_lock:
|
|
1263
|
+
cache["error"] = str(e)
|
|
1264
|
+
logger.warning(f"Embeddings load failed: {e}")
|
|
1265
|
+
return None
|
|
1266
|
+
finally:
|
|
1267
|
+
async with self._semantic_cache_lock:
|
|
1268
|
+
cache["load"] = None
|
|
1269
|
+
|
|
1270
|
+
cache["load"] = asyncio.create_task(load())
|
|
1271
|
+
return cache["load"]
|
|
1272
|
+
|
|
1232
1273
|
async def _load_embeddings_background(self):
|
|
1274
|
+
"""Load the searcher and its model, so the first search pays for
|
|
1275
|
+
neither."""
|
|
1276
|
+
|
|
1277
|
+
def build():
|
|
1278
|
+
searcher = self._new_searcher()
|
|
1279
|
+
# On a warm cache the constructor never touches the model.
|
|
1280
|
+
searcher.ensure_model()
|
|
1281
|
+
return searcher
|
|
1282
|
+
|
|
1233
1283
|
async with self._semantic_cache_lock:
|
|
1234
|
-
# A reindex replaces the cache, so a load writes only to its own.
|
|
1235
1284
|
cache = self._semantic_cache
|
|
1236
|
-
if cache["instance"] is not None
|
|
1285
|
+
if cache["instance"] is not None:
|
|
1237
1286
|
return
|
|
1238
|
-
cache["
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1287
|
+
load = cache["load"] or self._start_semantic_load(cache, build)
|
|
1288
|
+
if await asyncio.shield(load) is not None:
|
|
1289
|
+
logger.info("Pre-warm: embeddings ready")
|
|
1290
|
+
|
|
1291
|
+
async def _load_indexed_embeddings(self, model_was_loaded):
|
|
1292
|
+
"""Load the embeddings of a graph just indexed, and the model where
|
|
1293
|
+
it was loaded before; returns the line on them for the index's
|
|
1294
|
+
summary, empty without embeddings."""
|
|
1295
|
+
if not (
|
|
1296
|
+
self._semantic_available
|
|
1297
|
+
and self._SemanticSearcher
|
|
1298
|
+
and self.config.graph_path.exists()
|
|
1299
|
+
):
|
|
1300
|
+
return ""
|
|
1301
|
+
logger.info("Loading embeddings for semantic search...")
|
|
1302
|
+
|
|
1303
|
+
def build():
|
|
1304
|
+
# The child process has just embedded this graph and cached the
|
|
1305
|
+
# vectors under a key derived from the graph's content, so this
|
|
1306
|
+
# loads them. It encodes only when the child could not, since no
|
|
1307
|
+
# cache then matches.
|
|
1308
|
+
searcher = self._new_searcher()
|
|
1309
|
+
# Loading from cache never touches the model. A server that had
|
|
1310
|
+
# it loaded keeps it loaded, so the first search after a reindex
|
|
1311
|
+
# does not pay for the load; a one-shot `descry index` skips it.
|
|
1312
|
+
if model_was_loaded:
|
|
1248
1313
|
searcher.ensure_model()
|
|
1249
|
-
|
|
1314
|
+
return searcher
|
|
1250
1315
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
cache["error"] = None
|
|
1259
|
-
logger.info("Pre-warm: embeddings ready")
|
|
1260
|
-
except Exception as e: # noqa: BLE001 — boundary over sentence-transformers/torch model load; error is cached and logged
|
|
1261
|
-
async with self._semantic_cache_lock:
|
|
1262
|
-
cache["error"] = str(e)
|
|
1263
|
-
logger.warning(f"Pre-warm: embeddings failed: {e}")
|
|
1264
|
-
finally:
|
|
1265
|
-
async with self._semantic_cache_lock:
|
|
1266
|
-
cache["loading"] = False
|
|
1316
|
+
async with self._semantic_cache_lock:
|
|
1317
|
+
cache = self._semantic_cache
|
|
1318
|
+
load = cache["load"] or self._start_semantic_load(cache, build)
|
|
1319
|
+
searcher = await asyncio.shield(load)
|
|
1320
|
+
if searcher is None:
|
|
1321
|
+
return f"Embeddings: Failed ({cache['error']})"
|
|
1322
|
+
return f"Embeddings: {len(searcher.nodes):,} nodes indexed"
|
|
1267
1323
|
|
|
1268
1324
|
async def _get_semantic_searcher(self):
|
|
1269
1325
|
"""Single-flight accessor for the semantic searcher.
|
|
1270
1326
|
|
|
1271
|
-
Returns the cached SemanticSearcher,
|
|
1272
|
-
codebase_graph.json mtime changed
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
surface an "embeddings loading" UX string should check
|
|
1279
|
-
_semantic_cache["error"] when this returns None: it is the last
|
|
1280
|
-
failed load's until a load succeeds.
|
|
1327
|
+
Returns the cached SemanticSearcher, loading it again if
|
|
1328
|
+
codebase_graph.json mtime changed, and waits up to
|
|
1329
|
+
`embedding_timeout` for a load under way. Returns None if
|
|
1330
|
+
embeddings are unavailable (module missing or disabled), if the
|
|
1331
|
+
graph file does not exist, or while a load is not done; then
|
|
1332
|
+
_semantic_cache["error"] is the last failed load's until a load
|
|
1333
|
+
succeeds.
|
|
1281
1334
|
"""
|
|
1282
1335
|
if not self._semantic_available or self._SemanticSearcher is None:
|
|
1283
1336
|
return None
|
|
@@ -1289,34 +1342,10 @@ class DescryService:
|
|
|
1289
1342
|
cache = self._semantic_cache
|
|
1290
1343
|
if cache["instance"] is not None and cache["mtime"] == mtime:
|
|
1291
1344
|
return cache["instance"]
|
|
1292
|
-
|
|
1293
|
-
return None
|
|
1294
|
-
cache["loading"] = True
|
|
1295
|
-
|
|
1296
|
-
async def load():
|
|
1297
|
-
try:
|
|
1298
|
-
searcher = await asyncio.to_thread(self._new_searcher)
|
|
1299
|
-
async with self._semantic_cache_lock:
|
|
1300
|
-
cache["mtime"] = mtime
|
|
1301
|
-
cache["instance"] = searcher
|
|
1302
|
-
cache["error"] = None
|
|
1303
|
-
return searcher
|
|
1304
|
-
except Exception as e: # noqa: BLE001 — boundary over sentence-transformers/torch model load; error is cached and returned
|
|
1305
|
-
async with self._semantic_cache_lock:
|
|
1306
|
-
cache["error"] = str(e)
|
|
1307
|
-
logger.warning(f"Embeddings load failed: {e}")
|
|
1308
|
-
return None
|
|
1309
|
-
finally:
|
|
1310
|
-
async with self._semantic_cache_lock:
|
|
1311
|
-
cache["loading"] = False
|
|
1312
|
-
|
|
1313
|
-
# The thread cannot be stopped: a load not done in time goes on and
|
|
1314
|
-
# caches what it builds, and a search meanwhile is told it is loading.
|
|
1315
|
-
self._semantic_load = asyncio.create_task(load())
|
|
1345
|
+
load = cache["load"] or self._start_semantic_load(cache, self._new_searcher)
|
|
1316
1346
|
try:
|
|
1317
1347
|
return await asyncio.wait_for(
|
|
1318
|
-
asyncio.shield(self.
|
|
1319
|
-
timeout=self.config.embedding_timeout,
|
|
1348
|
+
asyncio.shield(load), timeout=self.config.embedding_timeout
|
|
1320
1349
|
)
|
|
1321
1350
|
except TimeoutError:
|
|
1322
1351
|
logger.warning(
|
|
@@ -1394,7 +1423,9 @@ class DescryService:
|
|
|
1394
1423
|
health["features"]["embeddings"] = (
|
|
1395
1424
|
self._semantic_cache["instance"] is not None
|
|
1396
1425
|
)
|
|
1397
|
-
health["features"]["embeddings_loading"] =
|
|
1426
|
+
health["features"]["embeddings_loading"] = (
|
|
1427
|
+
self._semantic_cache["load"] is not None
|
|
1428
|
+
)
|
|
1398
1429
|
health["features"]["embeddings_error"] = self._semantic_cache.get("error")
|
|
1399
1430
|
|
|
1400
1431
|
if not self._semantic_available:
|
|
@@ -1439,6 +1470,7 @@ class DescryService:
|
|
|
1439
1470
|
"age_hours": round(age_hours, 2),
|
|
1440
1471
|
"nodes": self._graph_cache["nodes"],
|
|
1441
1472
|
"edges": self._graph_cache["edges"],
|
|
1473
|
+
"languages": self._graph_cache["languages"],
|
|
1442
1474
|
"stale": age_hours > self.config.max_stale_hours,
|
|
1443
1475
|
"error": self._graph_cache["error"],
|
|
1444
1476
|
}
|
|
@@ -1665,56 +1697,8 @@ class DescryService:
|
|
|
1665
1697
|
f"\nSCIP: {len(scip_files)} project(s) indexed"
|
|
1666
1698
|
)
|
|
1667
1699
|
|
|
1668
|
-
|
|
1669
|
-
if
|
|
1670
|
-
self._semantic_available
|
|
1671
|
-
and self._SemanticSearcher
|
|
1672
|
-
and self.config.graph_path.exists()
|
|
1673
|
-
):
|
|
1674
|
-
try:
|
|
1675
|
-
# Reset the cache, then construct in a thread so the
|
|
1676
|
-
# event loop is not blocked.
|
|
1677
|
-
async with self._semantic_cache_lock:
|
|
1678
|
-
cache = self._semantic_cache = {
|
|
1679
|
-
"mtime": 0,
|
|
1680
|
-
"instance": None,
|
|
1681
|
-
"loading": True,
|
|
1682
|
-
"error": None,
|
|
1683
|
-
}
|
|
1684
|
-
logger.info("Generating embeddings for semantic search...")
|
|
1685
|
-
mtime = self._graph_mtime() or 0
|
|
1686
|
-
|
|
1687
|
-
def _build_searcher():
|
|
1688
|
-
# The child process has just embedded this graph
|
|
1689
|
-
# and cached the vectors under a key derived from
|
|
1690
|
-
# the graph's content, so this loads them. It
|
|
1691
|
-
# encodes only when the child could not, since no
|
|
1692
|
-
# cache then matches.
|
|
1693
|
-
searcher = self._new_searcher()
|
|
1694
|
-
# Loading from cache never touches the model. A
|
|
1695
|
-
# server that had it loaded keeps it loaded, so
|
|
1696
|
-
# the first search after a reindex does not pay
|
|
1697
|
-
# for the load; a one-shot `descry index` skips it.
|
|
1698
|
-
if model_was_loaded:
|
|
1699
|
-
searcher.ensure_model()
|
|
1700
|
-
return searcher
|
|
1701
|
-
|
|
1702
|
-
searcher = await asyncio.to_thread(_build_searcher)
|
|
1703
|
-
# Seed the cache with the freshly-built instance so
|
|
1704
|
-
# the next search() call reuses it.
|
|
1705
|
-
async with self._semantic_cache_lock:
|
|
1706
|
-
cache["mtime"] = mtime
|
|
1707
|
-
cache["instance"] = searcher
|
|
1708
|
-
embeddings_status = (
|
|
1709
|
-
f"\nEmbeddings: {len(searcher.nodes):,} nodes indexed"
|
|
1710
|
-
)
|
|
1711
|
-
except Exception as e: # noqa: BLE001 — boundary over sentence-transformers/torch; reported in the index summary
|
|
1712
|
-
embeddings_status = f"\nEmbeddings: Failed ({e})"
|
|
1713
|
-
logger.warning(f"Embeddings generation failed: {e}")
|
|
1714
|
-
finally:
|
|
1715
|
-
async with self._semantic_cache_lock:
|
|
1716
|
-
cache["loading"] = False
|
|
1717
|
-
|
|
1700
|
+
embeddings = await self._load_indexed_embeddings(model_was_loaded)
|
|
1701
|
+
embeddings_status = f"\n{embeddings}" if embeddings else ""
|
|
1718
1702
|
return f"Index complete.{scip_status}{embeddings_status}\n{output}"
|
|
1719
1703
|
else:
|
|
1720
1704
|
return ServiceError(f"Index failed:\n{stderr}")
|
|
@@ -2584,16 +2568,8 @@ class DescryService:
|
|
|
2584
2568
|
return
|
|
2585
2569
|
|
|
2586
2570
|
self.reset_caches()
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
if searcher is not None:
|
|
2590
|
-
line = f"Embeddings: {len(searcher.nodes):,} nodes indexed"
|
|
2591
|
-
if model_was_loaded:
|
|
2592
|
-
try:
|
|
2593
|
-
await asyncio.to_thread(searcher.ensure_model)
|
|
2594
|
-
except Exception as e: # noqa: BLE001 — boundary over sentence-transformers/torch; reported in the stream
|
|
2595
|
-
line = f"Embeddings: Failed ({e})"
|
|
2596
|
-
yield {"type": "output", "line": line}
|
|
2571
|
+
if embeddings := await self._load_indexed_embeddings(model_was_loaded):
|
|
2572
|
+
yield {"type": "output", "line": embeddings}
|
|
2597
2573
|
yield {"type": "done", "success": True, "message": "Index complete."}
|
|
2598
2574
|
finally:
|
|
2599
2575
|
self._index_lock.release()
|
|
@@ -2619,9 +2595,8 @@ class DescryService:
|
|
|
2619
2595
|
if not results:
|
|
2620
2596
|
trait_filter = f" for trait '{trait_name}'" if trait_name else ""
|
|
2621
2597
|
return (
|
|
2622
|
-
f"No trait implementations found for method '{method}'{trait_filter}
|
|
2623
|
-
|
|
2624
|
-
f"Run descry index to regenerate the graph with this metadata."
|
|
2598
|
+
f"No trait implementations found for method '{method}'{trait_filter}. "
|
|
2599
|
+
"Trait implementations are recorded for Rust code only."
|
|
2625
2600
|
)
|
|
2626
2601
|
|
|
2627
2602
|
by_trait = defaultdict(list)
|
|
@@ -218,7 +218,7 @@ async def descry_context(
|
|
|
218
218
|
head_lines: int | None = None,
|
|
219
219
|
max_output_tokens: int | None = None,
|
|
220
220
|
) -> str:
|
|
221
|
-
"""Get full context for a symbol: source code, callers, callees, tests (~500+ tokens). Use brief=true (~50 tokens) to quickly verify a symbol before fetching full context. Options: full=true (no truncation), expand_callees=true (inline dependencies), head_lines=N (preview first N lines)."""
|
|
221
|
+
"""Get full context for a symbol: source code, callers, callees, tests (~500+ tokens). Use brief=true (~50 tokens) to quickly verify a symbol before fetching full context. Options: full=true (no truncation), expand_callees=true (inline dependencies), head_lines=N (preview first N lines). node_id takes a node ID or a symbol name."""
|
|
222
222
|
return _fenced(
|
|
223
223
|
await _svc().context(
|
|
224
224
|
node_id,
|
|
@@ -281,7 +281,7 @@ async def descry_structure(filename: str) -> str:
|
|
|
281
281
|
|
|
282
282
|
@mcp.tool()
|
|
283
283
|
async def descry_flatten(class_node_id: str) -> str:
|
|
284
|
-
"""Show the effective API of a class including inherited methods. Use for understanding class hierarchies in OOP codebases."""
|
|
284
|
+
"""Show the effective API of a class including inherited methods. Use for understanding class hierarchies in OOP codebases. class_node_id takes a node ID or a class name."""
|
|
285
285
|
return _fenced(await _svc().flatten(class_node_id))
|
|
286
286
|
|
|
287
287
|
|
|
@@ -308,7 +308,7 @@ async def descry_quick(name: str, full: bool = False, brief: bool = False) -> st
|
|
|
308
308
|
|
|
309
309
|
@mcp.tool()
|
|
310
310
|
async def descry_impls(method: str, trait_name: str | None = None) -> str:
|
|
311
|
-
"""Find all implementations of a trait method across the codebase. Use when you know a trait method name (e.g., 'from_request_parts') but need to find which types implement it. Optionally filter by trait name."""
|
|
311
|
+
"""Find all implementations of a Rust trait method across the codebase. Use when you know a trait method name (e.g., 'from_request_parts') but need to find which types implement it. Optionally filter by trait name."""
|
|
312
312
|
return _fenced(await _svc().impls(method, trait_name))
|
|
313
313
|
|
|
314
314
|
|