descry-codegraph 0.3.3__tar.gz → 0.3.4__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.3 → descry_codegraph-0.3.4}/CHANGELOG.md +15 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/CLAUDE.md +1 -1
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/PKG-INFO +1 -1
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/descry/references/cli-reference.md +2 -2
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/pyproject.toml +1 -1
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/__init__.py +1 -1
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/cli.py +10 -2
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/embeddings.py +14 -2
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/handlers.py +4 -4
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/query.py +36 -36
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_cli.py +26 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_embeddings.py +20 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_handlers.py +38 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_query.py +14 -2
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/.github/assets/01-landing.png +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/.github/workflows/ci.yml +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/.github/workflows/release.yml +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/.gitignore +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/LICENSE +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/README.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/SECURITY.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/THIRD-PARTY-NOTICES.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/README.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/descry/SKILL.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/descry/references/mcp-tools.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/descry/references/recipes.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/justfile +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/pi-extension/README.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/pi-extension/descry-cli.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/pi-extension/descry-tools.ts +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/_comments.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/_env.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/_files.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/_graph.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/_stdlib.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/clang_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/cross_lang.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/dart_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/dotnet_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/generate.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/git_history.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/go_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/java_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/mcp_server.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/php_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/ruby_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/__init__.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapter.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/__init__.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/_empty_environment.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/_java_compat.init.gradle +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/clang.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/dart.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/dotnet.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/go.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/java.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/php.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/python.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/ruby.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/rust.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/typescript.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/cache.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/pb2.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/support.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/c.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/cpp.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/dart.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/dotnet.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/go.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/java.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/node.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/php.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/python.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/ruby.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/rust.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/stdlib/typescript.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/web/__init__.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/web/server.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/web/web/index.html +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/web/web/style.css +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/__init__.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/conftest.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/README.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/__init__.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/compare.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/corpus.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/metrics.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/registry.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/results/results-descry.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/eval/results/results-ripgrep.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/fixtures/small_graph.json +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_backtracking.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_clang_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_config.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_cross_lang.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_dart_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_dotnet_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_generate.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_git_history.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_go_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_hardening.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_interfaces.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_java_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_mcp.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_non_regular_files.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_php_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_ruby_parser.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_cache.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_clang.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_dart.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_dotnet.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_go.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_java.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_php.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_scip_ruby.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_stdlib.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_web_server.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tests/test_written_through.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/README.md +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/common.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_c.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_cpp.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_dart.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_dotnet.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_go.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_java.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_node.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_php.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_python.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_ruby.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_rust.py +0 -0
- {descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/tools/stdlib/gen_typescript.py +0 -0
|
@@ -6,6 +6,21 @@ 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.4] — 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `search` answers what semantic search finds when the keywords find
|
|
14
|
+
nothing: the semantic pass ran because they found little, and its
|
|
15
|
+
results were then dropped ("No matches"). It now searches under
|
|
16
|
+
`lang`, `crate`, `type` and `exclude_tests` as the keyword side does; its
|
|
17
|
+
results were merged unfiltered.
|
|
18
|
+
- `search --type` with a type the graph has none of, or one descry does
|
|
19
|
+
not know, matches nothing, as an unknown `lang` does; it was ignored and
|
|
20
|
+
every type answered. The CLI lists the types it takes.
|
|
21
|
+
- `flow --target` takes a node ID as well as a name; a node ID was never
|
|
22
|
+
found, and the trace said nothing of it.
|
|
23
|
+
|
|
9
24
|
## [0.3.3] — 2026-10-23
|
|
10
25
|
|
|
11
26
|
### Fixed
|
|
@@ -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 (2822 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.4
|
|
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
|
{descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/descry/references/cli-reference.md
RENAMED
|
@@ -24,7 +24,7 @@ descry search <terms...>
|
|
|
24
24
|
[--limit N] # default 10
|
|
25
25
|
[--lang rust|ts|py|...]
|
|
26
26
|
[--crate NAME]
|
|
27
|
-
[--type
|
|
27
|
+
[--type function|class|method|constant|file|all]
|
|
28
28
|
[--exclude-tests]
|
|
29
29
|
|
|
30
30
|
descry semantic <query>
|
|
@@ -49,7 +49,7 @@ descry callees <name> [--limit N] # default 20
|
|
|
49
49
|
descry flow <start>
|
|
50
50
|
[--direction forward|backward] # default forward
|
|
51
51
|
[--depth N] # default 3, capped by [query] max_depth
|
|
52
|
-
[--target
|
|
52
|
+
[--target SYMBOL] # stop early if reached (name or node ID)
|
|
53
53
|
[--inline-threshold N] # inline callee source below N tokens
|
|
54
54
|
|
|
55
55
|
descry path <start> <end>
|
|
@@ -308,7 +308,9 @@ def main():
|
|
|
308
308
|
default=3,
|
|
309
309
|
help="Levels of calls below the start (default: 3)",
|
|
310
310
|
)
|
|
311
|
-
sub.add_argument(
|
|
311
|
+
sub.add_argument(
|
|
312
|
+
"--target", default=None, help="Symbol to reach, by name or node ID"
|
|
313
|
+
)
|
|
312
314
|
sub.add_argument(
|
|
313
315
|
"--inline-threshold",
|
|
314
316
|
type=int,
|
|
@@ -329,7 +331,13 @@ def main():
|
|
|
329
331
|
sub.add_argument("--limit", type=int, default=10, help="Max results (default: 10)")
|
|
330
332
|
sub.add_argument("--lang", default=None, help="Filter by language")
|
|
331
333
|
sub.add_argument("--crate", default=None, help="Filter by crate")
|
|
332
|
-
sub.add_argument(
|
|
334
|
+
sub.add_argument(
|
|
335
|
+
"--type",
|
|
336
|
+
default=None,
|
|
337
|
+
type=str.lower,
|
|
338
|
+
choices=["function", "class", "method", "constant", "file", "all"],
|
|
339
|
+
help="Filter by symbol type",
|
|
340
|
+
)
|
|
333
341
|
sub.add_argument(
|
|
334
342
|
"--exclude-tests", action="store_true", help="Exclude test symbols"
|
|
335
343
|
)
|
|
@@ -774,7 +774,9 @@ class SemanticSearcher:
|
|
|
774
774
|
self.embeddings = embeddings / np.where(norms == 0, 1, norms)
|
|
775
775
|
logger.info("Embeddings generated successfully")
|
|
776
776
|
|
|
777
|
-
def search(
|
|
777
|
+
def search(
|
|
778
|
+
self, query: str, limit: int = 10, min_score: float = 0.3, keep=None
|
|
779
|
+
) -> list:
|
|
778
780
|
"""Search for nodes semantically similar to the query.
|
|
779
781
|
|
|
780
782
|
Args:
|
|
@@ -782,6 +784,7 @@ class SemanticSearcher:
|
|
|
782
784
|
limit: Maximum number of results
|
|
783
785
|
min_score: Minimum final score, i.e. cosine similarity after
|
|
784
786
|
the in-degree and type boosts are added (may exceed 1).
|
|
787
|
+
keep: The node IDs results may have, None for any.
|
|
785
788
|
|
|
786
789
|
Returns:
|
|
787
790
|
List of (node, score) tuples sorted by relevance
|
|
@@ -826,10 +829,19 @@ class SemanticSearcher:
|
|
|
826
829
|
np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_embedding)
|
|
827
830
|
)
|
|
828
831
|
|
|
832
|
+
if keep is not None:
|
|
833
|
+
# Nodes outside `keep` never enter the pool
|
|
834
|
+
kept = np.fromiter(
|
|
835
|
+
(n["id"] in keep for n in self.nodes), bool, len(self.nodes)
|
|
836
|
+
)
|
|
837
|
+
similarities = np.where(kept, similarities, -np.inf)
|
|
838
|
+
if not kept.any():
|
|
839
|
+
return []
|
|
840
|
+
|
|
829
841
|
# Take a candidate pool by base score, then apply min_score to the
|
|
830
842
|
# final score, so a node whose boosts (worth up to +0.17) lift it over
|
|
831
843
|
# the threshold is kept.
|
|
832
|
-
pool = min(len(similarities), limit * 3)
|
|
844
|
+
pool = min(len(similarities) if keep is None else int(kept.sum()), limit * 3)
|
|
833
845
|
top_indices = np.argpartition(-similarities, pool - 1)[:pool]
|
|
834
846
|
top_indices = top_indices[np.argsort(-similarities[top_indices])]
|
|
835
847
|
|
|
@@ -2086,23 +2086,23 @@ class DescryService:
|
|
|
2086
2086
|
# and respects the cache lock).
|
|
2087
2087
|
searcher = await self._get_semantic_searcher()
|
|
2088
2088
|
if searcher is not None:
|
|
2089
|
+
# Under the filters the keyword side has
|
|
2089
2090
|
semantic_results = await asyncio.to_thread(
|
|
2090
2091
|
searcher.search,
|
|
2091
2092
|
" ".join(terms),
|
|
2092
2093
|
limit=limit * 2,
|
|
2093
2094
|
min_score=0.25,
|
|
2095
|
+
keep=q.filtered_ids(lang, crate, symbol_type, exclude_tests),
|
|
2094
2096
|
)
|
|
2095
2097
|
except Exception as e: # noqa: BLE001 — boundary over the embedding backend; search degrades to keyword-only
|
|
2096
2098
|
logger.warning(f"Semantic search failed, using keyword only: {e}")
|
|
2097
2099
|
|
|
2098
|
-
if semantic_results
|
|
2100
|
+
if semantic_results:
|
|
2099
2101
|
combined = reciprocal_rank_fusion(tfidf_results, semantic_results)
|
|
2100
2102
|
results = [node for node, _ in combined[:limit]]
|
|
2101
2103
|
method = "hybrid"
|
|
2102
|
-
elif tfidf_results:
|
|
2103
|
-
results = tfidf_results[:limit]
|
|
2104
2104
|
else:
|
|
2105
|
-
results = []
|
|
2105
|
+
results = tfidf_results[:limit]
|
|
2106
2106
|
|
|
2107
2107
|
return {"ok": True, "error": None, "results": results, "method": method}
|
|
2108
2108
|
|
|
@@ -1318,7 +1318,8 @@ class GraphQuerier:
|
|
|
1318
1318
|
once, at the first level that reaches it, and met again as `seen`,
|
|
1319
1319
|
the level of that entry (the start is level 0).
|
|
1320
1320
|
Returns ``(root, path, cut)``: the tree, the node IDs from the start
|
|
1321
|
-
to the first node
|
|
1321
|
+
to the first node ``target`` names, by name or node ID, and why it
|
|
1322
|
+
stopped early
|
|
1322
1323
|
("timeout", "node limit") or None."""
|
|
1323
1324
|
start_time = time.time()
|
|
1324
1325
|
|
|
@@ -1353,7 +1354,7 @@ class GraphQuerier:
|
|
|
1353
1354
|
|
|
1354
1355
|
root = tree_node(start_id, None)
|
|
1355
1356
|
paths = {start_id: [start_id]}
|
|
1356
|
-
if target
|
|
1357
|
+
if target in (root["name"], root["id"]):
|
|
1357
1358
|
return root, paths[start_id], None
|
|
1358
1359
|
level = [root]
|
|
1359
1360
|
for _ in range(depth):
|
|
@@ -1378,7 +1379,7 @@ class GraphQuerier:
|
|
|
1378
1379
|
child = tree_node(child_id, marks.get(child_id))
|
|
1379
1380
|
paths[child_id] = [*paths[parent["id"]], child_id]
|
|
1380
1381
|
parent["children"].append(child)
|
|
1381
|
-
if target
|
|
1382
|
+
if target in (child["name"], child["id"]):
|
|
1382
1383
|
return root, paths[child_id], None
|
|
1383
1384
|
next_level.append(child)
|
|
1384
1385
|
level = next_level
|
|
@@ -1500,7 +1501,7 @@ class GraphQuerier:
|
|
|
1500
1501
|
cut (why the trace stopped early, when it did). Each tree node has: id,
|
|
1501
1502
|
name, type, tokens, file, line, code, lang, candidates, children,
|
|
1502
1503
|
more, more_possible, and `seen`, the level its entry is at, where it
|
|
1503
|
-
is placed there already. The trace stops at the first node
|
|
1504
|
+
is placed there already. The trace stops at the first node `target` names, by name or node ID.
|
|
1504
1505
|
"""
|
|
1505
1506
|
if timeout_ms is None:
|
|
1506
1507
|
timeout_ms = self._timeout_ms
|
|
@@ -1666,6 +1667,33 @@ class GraphQuerier:
|
|
|
1666
1667
|
idf[term] = math.log((total_docs + 1) / (df + 1)) + 1
|
|
1667
1668
|
return idf
|
|
1668
1669
|
|
|
1670
|
+
def filtered_ids(
|
|
1671
|
+
self, lang=None, crate=None, symbol_type=None, exclude_tests=False
|
|
1672
|
+
):
|
|
1673
|
+
"""The IDs of the nodes `search_docs`'s filters keep, None when none
|
|
1674
|
+
is given: the language, the crate or module, the type, and no tests."""
|
|
1675
|
+
if not (lang or crate or symbol_type or exclude_tests):
|
|
1676
|
+
return None
|
|
1677
|
+
self._ensure_filter_indices()
|
|
1678
|
+
kept = set(self.nodes.keys())
|
|
1679
|
+
if exclude_tests and self._index_is_test:
|
|
1680
|
+
kept -= self._index_is_test
|
|
1681
|
+
if crate:
|
|
1682
|
+
kept &= self._index_by_crate.get(crate, set())
|
|
1683
|
+
if lang and lang != "all":
|
|
1684
|
+
lang = _LANG_ALIASES.get(lang.lower(), lang.lower())
|
|
1685
|
+
kept &= self._index_by_lang.get(lang, set())
|
|
1686
|
+
if symbol_type and symbol_type != "all":
|
|
1687
|
+
type_map = {
|
|
1688
|
+
"function": "Function",
|
|
1689
|
+
"class": "Class",
|
|
1690
|
+
"method": "Method",
|
|
1691
|
+
"constant": "Constant",
|
|
1692
|
+
"file": "File",
|
|
1693
|
+
}
|
|
1694
|
+
kept &= self._index_by_type.get(type_map.get(symbol_type.lower()), set())
|
|
1695
|
+
return kept
|
|
1696
|
+
|
|
1669
1697
|
def search_docs(
|
|
1670
1698
|
self, query_terms, lang=None, crate=None, symbol_type=None, exclude_tests=False
|
|
1671
1699
|
):
|
|
@@ -1695,38 +1723,10 @@ class GraphQuerier:
|
|
|
1695
1723
|
if num_terms == 0:
|
|
1696
1724
|
return []
|
|
1697
1725
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
self.
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
# Filter out test files and functions if requested
|
|
1704
|
-
if exclude_tests and self._index_is_test:
|
|
1705
|
-
candidate_ids -= self._index_is_test
|
|
1706
|
-
|
|
1707
|
-
if crate:
|
|
1708
|
-
# If crate filter specified but doesn't exist, return empty results
|
|
1709
|
-
if crate not in self._index_by_crate:
|
|
1710
|
-
return []
|
|
1711
|
-
candidate_ids &= self._index_by_crate[crate]
|
|
1712
|
-
if lang and lang != "all":
|
|
1713
|
-
lang = _LANG_ALIASES.get(lang.lower(), lang.lower())
|
|
1714
|
-
candidate_ids &= self._index_by_lang.get(lang, set())
|
|
1715
|
-
if symbol_type and symbol_type != "all":
|
|
1716
|
-
type_map = {
|
|
1717
|
-
"function": "Function",
|
|
1718
|
-
"class": "Class",
|
|
1719
|
-
"method": "Method",
|
|
1720
|
-
"constant": "Constant",
|
|
1721
|
-
"file": "File",
|
|
1722
|
-
}
|
|
1723
|
-
type_key = type_map.get(symbol_type.lower())
|
|
1724
|
-
if type_key and type_key in self._index_by_type:
|
|
1725
|
-
candidate_ids &= self._index_by_type[type_key]
|
|
1726
|
-
|
|
1727
|
-
candidates = [self.nodes[nid] for nid in candidate_ids]
|
|
1728
|
-
else:
|
|
1729
|
-
candidates = self.data["nodes"]
|
|
1726
|
+
kept = self.filtered_ids(lang, crate, symbol_type, exclude_tests)
|
|
1727
|
+
candidates = (
|
|
1728
|
+
self.data["nodes"] if kept is None else [self.nodes[nid] for nid in kept]
|
|
1729
|
+
)
|
|
1730
1730
|
|
|
1731
1731
|
for node in candidates:
|
|
1732
1732
|
score = 0.0
|
|
@@ -225,3 +225,29 @@ class TestCliStopped:
|
|
|
225
225
|
index.send_signal(signal.SIGHUP)
|
|
226
226
|
assert not indexer_tree.grandchild_exited(timeout=1)
|
|
227
227
|
assert index.poll() is None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestSearchType:
|
|
231
|
+
"""`--type` takes the types descry has, in any case, and refuses others."""
|
|
232
|
+
|
|
233
|
+
def test_a_type_is_passed_lowercased(self, monkeypatch):
|
|
234
|
+
seen = {}
|
|
235
|
+
|
|
236
|
+
class Service:
|
|
237
|
+
async def search(self, **kwargs):
|
|
238
|
+
seen.update(kwargs)
|
|
239
|
+
return "ok"
|
|
240
|
+
|
|
241
|
+
monkeypatch.setattr(cli, "_make_service", Service)
|
|
242
|
+
monkeypatch.setattr(
|
|
243
|
+
sys, "argv", ["descry", "search", "x", "--type", "Function"]
|
|
244
|
+
)
|
|
245
|
+
cli.main()
|
|
246
|
+
assert seen["symbol_type"] == "function"
|
|
247
|
+
|
|
248
|
+
def test_an_unknown_type_is_refused(self, monkeypatch, capsys):
|
|
249
|
+
monkeypatch.setattr(sys, "argv", ["descry", "search", "x", "--type", "struct"])
|
|
250
|
+
with pytest.raises(SystemExit) as exit_:
|
|
251
|
+
cli.main()
|
|
252
|
+
assert exit_.value.code == 2
|
|
253
|
+
assert "invalid choice: 'struct'" in capsys.readouterr().err
|
|
@@ -434,6 +434,26 @@ class TestSearchScoring:
|
|
|
434
434
|
with pytest.raises(EmbeddingCacheMismatch):
|
|
435
435
|
s.search("anything", limit=3)
|
|
436
436
|
|
|
437
|
+
def test_kept_nodes_are_the_pool_others_never_enter(self, fake_model, graph_writer):
|
|
438
|
+
"""A filter applies before the pool is cut, so a kept node far down
|
|
439
|
+
the ranking is still found."""
|
|
440
|
+
s = SemanticSearcher(str(graph_writer()))
|
|
441
|
+
dim = s.embeddings.shape[1]
|
|
442
|
+
s.embeddings = np.zeros((len(s.nodes), dim), dtype="float32")
|
|
443
|
+
s.embeddings[:, 0] = 1.0
|
|
444
|
+
last = len(s.nodes) - 1
|
|
445
|
+
s.embeddings[last] = 0.0
|
|
446
|
+
s.embeddings[last, 1] = 1.0
|
|
447
|
+
q = np.zeros(dim, dtype="float32")
|
|
448
|
+
q[0], q[1] = 1.0, 0.1
|
|
449
|
+
s.model = FakeSentenceTransformer(dim=dim, prompts={})
|
|
450
|
+
s.model.encode = lambda texts, **kw: q.reshape(1, -1)
|
|
451
|
+
wanted = s.nodes[last]["id"]
|
|
452
|
+
|
|
453
|
+
hits = s.search("q", limit=1, min_score=-1.0, keep={wanted})
|
|
454
|
+
assert [n["id"] for n, _ in hits] == [wanted]
|
|
455
|
+
assert s.search("q", limit=1, min_score=-1.0, keep=set()) == []
|
|
456
|
+
|
|
437
457
|
def test_results_are_sorted_and_limited(self, fake_model, graph_writer):
|
|
438
458
|
s = SemanticSearcher(str(graph_writer()))
|
|
439
459
|
hits = s.search("alpha", limit=2, min_score=-1.0)
|
|
@@ -1709,3 +1709,41 @@ async def test_a_context_budget_below_one_is_refused(tmp_path, bound):
|
|
|
1709
1709
|
result = await DescryService(config).context("FILE:x.py::f", depth=2, **{bound: 0})
|
|
1710
1710
|
assert isinstance(result, ServiceError)
|
|
1711
1711
|
assert f"{bound} must be at least 1, got 0." in result
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
@pytest.mark.asyncio
|
|
1715
|
+
@pytest.mark.parametrize(
|
|
1716
|
+
("terms", "lang", "found"),
|
|
1717
|
+
[
|
|
1718
|
+
(["who", "talks", "to", "nothing"], None, ["FILE:x.py::f", "FILE:y.rs::g"]),
|
|
1719
|
+
(["who", "talks", "to", "nothing"], "python", ["FILE:x.py::f"]),
|
|
1720
|
+
# The keywords find `f` too; the semantic side is still filtered
|
|
1721
|
+
(["f"], "python", ["FILE:x.py::f"]),
|
|
1722
|
+
],
|
|
1723
|
+
)
|
|
1724
|
+
async def test_search_keeps_semantic_matches_no_keyword_finds(
|
|
1725
|
+
tmp_path, terms, lang, found
|
|
1726
|
+
):
|
|
1727
|
+
"""The semantic side runs because the keywords found little; what it
|
|
1728
|
+
finds is answered, under the same filters as the keywords."""
|
|
1729
|
+
config = _plain_config(tmp_path)
|
|
1730
|
+
py = {"id": "FILE:x.py::f", "type": "Function", "metadata": {"name": "f"}}
|
|
1731
|
+
rs = {"id": "FILE:y.rs::g", "type": "Function", "metadata": {"name": "g"}}
|
|
1732
|
+
_write_graph(config, nodes=[py, rs])
|
|
1733
|
+
svc = DescryService(config)
|
|
1734
|
+
|
|
1735
|
+
class Searcher:
|
|
1736
|
+
def __init__(self, graph_path, **kwargs):
|
|
1737
|
+
pass
|
|
1738
|
+
|
|
1739
|
+
def ensure_model(self):
|
|
1740
|
+
pass
|
|
1741
|
+
|
|
1742
|
+
def search(self, query, limit=10, min_score=0.0, keep=None):
|
|
1743
|
+
return [
|
|
1744
|
+
r for r in [(py, 0.9), (rs, 0.8)] if keep is None or r[0]["id"] in keep
|
|
1745
|
+
]
|
|
1746
|
+
|
|
1747
|
+
svc._semantic_available, svc._SemanticSearcher = True, Searcher
|
|
1748
|
+
data = await svc.search_data(terms, lang=lang)
|
|
1749
|
+
assert ([n["id"] for n in data["results"]], data["method"]) == (found, "hybrid")
|
|
@@ -301,12 +301,16 @@ class TestFindCallPath:
|
|
|
301
301
|
|
|
302
302
|
os.unlink(f.name)
|
|
303
303
|
|
|
304
|
+
@pytest.mark.parametrize(
|
|
305
|
+
"target", ["validate_token", "FILE:auth.rs::validate_token"]
|
|
306
|
+
)
|
|
304
307
|
def test_structured_flow_stops_at_the_target_and_reports_the_path(
|
|
305
|
-
self, call_path_graph
|
|
308
|
+
self, call_path_graph, target
|
|
306
309
|
):
|
|
310
|
+
"""The target is named by name or by node ID."""
|
|
307
311
|
q = GraphQuerier(call_path_graph)
|
|
308
312
|
|
|
309
|
-
tree = q.trace_flow_structured("handle_request", target=
|
|
313
|
+
tree = q.trace_flow_structured("handle_request", target=target)
|
|
310
314
|
|
|
311
315
|
assert tree["path_to_target"] == [
|
|
312
316
|
"handle_request",
|
|
@@ -1073,6 +1077,14 @@ class TestLanguageFilter:
|
|
|
1073
1077
|
def test_all_and_none_do_not_filter(self, querier, lang):
|
|
1074
1078
|
assert len(querier.search_docs(["handler"], lang=lang)) == len(self.FILES)
|
|
1075
1079
|
|
|
1080
|
+
@pytest.mark.parametrize(
|
|
1081
|
+
("kind", "found"), [("function", len(FILES)), ("class", 0), ("struct", 0)]
|
|
1082
|
+
)
|
|
1083
|
+
def test_type_keeps_only_that_type(self, querier, kind, found):
|
|
1084
|
+
"""A type the graph has none of, or no type descry has, matches
|
|
1085
|
+
nothing, as an unknown language does."""
|
|
1086
|
+
assert len(querier.search_docs(["handler"], symbol_type=kind)) == found
|
|
1087
|
+
|
|
1076
1088
|
|
|
1077
1089
|
class TestCallPathDepthAndOutcome:
|
|
1078
1090
|
def _chain(self, tmp_path, length, **config):
|
|
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
|
{descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/claude-skill/descry/references/mcp-tools.md
RENAMED
|
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
|
{descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/_empty_environment.json
RENAMED
|
File without changes
|
{descry_codegraph-0.3.3 → descry_codegraph-0.3.4}/src/descry/scip/adapters/_java_compat.init.gradle
RENAMED
|
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
|