descry-codegraph 0.3.0__tar.gz → 0.3.2__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.2}/CHANGELOG.md +126 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/CLAUDE.md +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/PKG-INFO +2 -2
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/README.md +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/pyproject.toml +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/__init__.py +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/_graph.py +19 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/cli.py +7 -2
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/embeddings.py +10 -5
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/generate.py +7 -20
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/git_history.py +40 -34
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/handlers.py +201 -179
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/mcp_server.py +4 -4
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/query.py +578 -740
- descry_codegraph-0.3.2/src/descry/scip/adapters/_empty_environment.json +1 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/python.py +12 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/cache.py +7 -3
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/web/server.py +26 -9
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/web/web/index.html +45 -17
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_cli.py +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_embeddings.py +17 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_generate.py +19 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_git_history.py +61 -2
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_handlers.py +160 -8
- descry_codegraph-0.3.2/tests/test_interfaces.py +296 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_mcp.py +1 -1
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_query.py +704 -64
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_cache.py +38 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_web_server.py +101 -25
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/.github/assets/01-landing.png +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/.github/workflows/ci.yml +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/.github/workflows/release.yml +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/.gitignore +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/LICENSE +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/SECURITY.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/THIRD-PARTY-NOTICES.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/claude-skill/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/claude-skill/descry/SKILL.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/claude-skill/descry/references/cli-reference.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/claude-skill/descry/references/mcp-tools.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/claude-skill/descry/references/recipes.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/justfile +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/pi-extension/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/pi-extension/descry-cli.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/pi-extension/descry-tools.ts +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/_comments.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/_env.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/_files.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/_stdlib.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/clang_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/cross_lang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/dart_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/dotnet_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/go_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/java_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/php_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/ruby_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapter.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/_java_compat.init.gradle +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/clang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/dart.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/dotnet.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/go.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/java.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/php.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/ruby.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/rust.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/adapters/typescript.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/pb2.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/scip/support.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/c.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/cpp.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/dart.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/dotnet.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/go.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/java.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/node.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/php.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/python.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/ruby.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/rust.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/stdlib/typescript.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/web/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/src/descry/web/web/style.css +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/conftest.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/__init__.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/compare.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/corpus.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/metrics.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/registry.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/results/results-descry.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/eval/results/results-ripgrep.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/fixtures/small_graph.json +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_backtracking.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_clang_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_config.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_cross_lang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_dart_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_dotnet_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_go_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_hardening.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_java_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_non_regular_files.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_php_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_ruby_parser.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_clang.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_dart.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_dotnet.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_go.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_java.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_php.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_scip_ruby.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_stdlib.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tests/test_written_through.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/README.md +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/common.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_c.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_cpp.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_dart.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_dotnet.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_go.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_java.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_node.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_php.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_python.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_ruby.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_rust.py +0 -0
- {descry_codegraph-0.3.0 → descry_codegraph-0.3.2}/tools/stdlib/gen_typescript.py +0 -0
|
@@ -6,6 +6,132 @@ 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.2] — 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A caller that reaches a symbol only as one of an unresolved call's
|
|
14
|
+
candidates is counted apart from its callers. `in_degree` ("N callers")
|
|
15
|
+
counts the callers whose calls are resolved to the symbol, and is
|
|
16
|
+
counted again as a graph loads, so a graph from 0.3.1 needs no reindex.
|
|
17
|
+
Every other count gives the others as "(+N possible)": brief `context`
|
|
18
|
+
(callers and callees), `callers`, `callees`, and the web page's headers
|
|
19
|
+
(`possible` in the web API). Lists mark each one; `changes` does so in
|
|
20
|
+
text, in its structured form (`candidates`, `callers_possible`) and on
|
|
21
|
+
the page.
|
|
22
|
+
- A function no longer counts as a certain caller of a symbol it defines
|
|
23
|
+
when its call to it is unresolved.
|
|
24
|
+
- A node ID names that node alone: `callers`, `callees`, `quick`, `flow`
|
|
25
|
+
and `path` given one no longer also match other definitions filed under
|
|
26
|
+
the same owner and name (a Ruby `self.x` beside `x`).
|
|
27
|
+
- `context`'s usage examples are calls of that symbol, those resolved to
|
|
28
|
+
it first, then those it is one of the candidates of, marked; they were
|
|
29
|
+
any unresolved call ending in its name.
|
|
30
|
+
- `callers` of a name lists the calls the resolver left unresolved with no
|
|
31
|
+
candidates apart from its callers, marked "(unresolved; same name)" and
|
|
32
|
+
counted as "+N unresolved" (`unresolved` in the web API). Some are calls
|
|
33
|
+
of the project's definition the resolver cannot follow (a Rust re-export,
|
|
34
|
+
a TypeScript static member), some are calls of something else with that
|
|
35
|
+
name (`loader.list_templates()` on a library's loader). Neither is
|
|
36
|
+
counted as a caller. An unresolved call with candidates reaches only
|
|
37
|
+
those. Callers asked for by node ID are those resolved to it or narrowed
|
|
38
|
+
to it.
|
|
39
|
+
- A list cut short (`callers`, `callees`, `context`'s callers, `changes`,
|
|
40
|
+
backward `flow` in text and as a tree) keeps the callers resolved to the
|
|
41
|
+
symbol first, and says how many it leaves out and how many of those only
|
|
42
|
+
may be; a `flow` tree node gives them as `more` and `more_possible`, and
|
|
43
|
+
the web API's `callers` and `callees` as `more`, shown on the page.
|
|
44
|
+
- `flow`, in text and as a tree, places each symbol at the first level
|
|
45
|
+
that reaches it and marks it "(at level N)" where it is met again
|
|
46
|
+
(`seen`, that level, in the tree); a direct callee or caller reached
|
|
47
|
+
first through another one was dropped from its level, with everything
|
|
48
|
+
below it. `context`, at every depth, lists all of the symbol's own
|
|
49
|
+
callees and cuts each level below to the per-level limit with a note; it
|
|
50
|
+
opens a callee (inlines it and lists its own callees) at the first level
|
|
51
|
+
that reaches it while the token budget covers it, lists every other
|
|
52
|
+
callee by its signature or "(at level N)", and says when the budget, the
|
|
53
|
+
node limit or the timeout cut it short. Depth 1 ignored `max_tokens`. A
|
|
54
|
+
`flow` that stops early says so: "(timeout)" or "(node limit)" in text,
|
|
55
|
+
`cut` in the tree, a note on the page.
|
|
56
|
+
- A `flow` of `depth` N shows N levels of calls below the start; `depth=1`
|
|
57
|
+
showed the start alone. `[query] max_depth` is that many levels too, for
|
|
58
|
+
`flow` and for `context`'s callee expansion. A depth past it is traced to
|
|
59
|
+
it and says so; the web API returns the depth traced, and `/api/health`
|
|
60
|
+
gives the ceiling the page's depth field is held to.
|
|
61
|
+
- A callee the resolver left unresolved is no longer shown or counted as
|
|
62
|
+
certain or as external. `context`, at every depth, names it as one of
|
|
63
|
+
its candidates, or "(unresolved)" where it found none. `callees` marks
|
|
64
|
+
it the same way and counts it apart ("+N unresolved"). A callee is what
|
|
65
|
+
a call is written as: calls written apart (`a.add`, `b.add`) are two
|
|
66
|
+
callees, and calls written alike may reach any definition one of them
|
|
67
|
+
is narrowed to. Forward `flow` follows a possible callee to each
|
|
68
|
+
definition it may reach, marked, and `context` at a depth past 1 says
|
|
69
|
+
how many callees each level leaves out.
|
|
70
|
+
- `callees` says how many callees its `limit` leaves out, and of a name
|
|
71
|
+
several definitions share, which one it answers for (`others` in the web
|
|
72
|
+
API).
|
|
73
|
+
- A `limit`, a `flow` or `context` `depth`, a `context` `max_tokens` or
|
|
74
|
+
`callee_budget`, or a `path` `max_depth` below 1 is refused by every
|
|
75
|
+
tool; `limit=0` answered as if there were no
|
|
76
|
+
callers, matches, changes or history.
|
|
77
|
+
- The web page's changes view no longer throws for a changed symbol with
|
|
78
|
+
no callers.
|
|
79
|
+
- `context --max-output-tokens` cuts the parts the answer is made of, not
|
|
80
|
+
its text: expanded callees, then usage examples past 2, related tests
|
|
81
|
+
and configuration, callers past 5 (still counting possible callers
|
|
82
|
+
apart), the symbol's callees past 5 (planned again for those five, so
|
|
83
|
+
the note counts by kind only callees drawn nowhere in the answer and no
|
|
84
|
+
"(at level N)" mark points at a callee cut away), and the source past 30
|
|
85
|
+
of the symbol's lines, saying what each step left out; a section dropped
|
|
86
|
+
whole keeps its heading with "(omitted, max_output_tokens limit)".
|
|
87
|
+
It read the rendered text back, so code with a heading-like or a fence
|
|
88
|
+
line in it was cut wrongly or not at all, opened callees kept their
|
|
89
|
+
headings without their code, and counts were wrong.
|
|
90
|
+
- `flow` text indents the code it inlines as deep as its fence. The code
|
|
91
|
+
was printed from the first column inside an indented fence, so it broke
|
|
92
|
+
the tree, and below the first level the fence read as an indented code
|
|
93
|
+
block.
|
|
94
|
+
- A command that loads no embedding model no longer imports
|
|
95
|
+
sentence-transformers, and with it torch, as the service starts: where
|
|
96
|
+
they are installed every CLI command spent seconds on it.
|
|
97
|
+
- `churn` ranks symbols, and co-change pairs of symbols, with the same
|
|
98
|
+
number of commits by node ID, so the same history gives the same list.
|
|
99
|
+
Their order among ties changed from run to run, and so, at the limit,
|
|
100
|
+
did which of them were listed.
|
|
101
|
+
|
|
102
|
+
## [0.3.1] — 2026-10-22
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- The callers a change lists (`changes`), and those `context` lists and
|
|
107
|
+
counts and an upstream `flow` follows, are the callers of that symbol,
|
|
108
|
+
not of every symbol of its name. `context` marks a caller that reaches
|
|
109
|
+
the symbol only as one of an unresolved call's candidates, as `callers`
|
|
110
|
+
does.
|
|
111
|
+
- A symbol's caller count (`in_degree`, shown as "N callers") counts the
|
|
112
|
+
symbols that call or construct it, not every edge into it: the file
|
|
113
|
+
defining it was counted as a caller.
|
|
114
|
+
- A Python signature is written as the source writes it, with defaults,
|
|
115
|
+
`*`, `**`, keyword-only parameters and `async`, and with no `-> None`
|
|
116
|
+
where no return type is annotated. An unannotated function was recorded
|
|
117
|
+
as returning `None`.
|
|
118
|
+
- The web UI's Language filter lists the graph's languages, which the
|
|
119
|
+
graph status now gives (`/api/status`, `/api/health`), instead of four
|
|
120
|
+
fixed ones.
|
|
121
|
+
- A search during the startup pre-warm, or during the embeddings load
|
|
122
|
+
after an index, waits for that load (up to `embedding_timeout`) instead
|
|
123
|
+
of reporting that embeddings are loading. A streamed index reports the
|
|
124
|
+
embeddings line however long the load takes.
|
|
125
|
+
- scip-python runs without `pip` on PATH and outside a git repository: it
|
|
126
|
+
is given an empty environment and a project version.
|
|
127
|
+
- A SCIP indexer's failure is reported by its error, without the stack
|
|
128
|
+
frames that pushed it out of the message.
|
|
129
|
+
- `flatten` takes a class name as `context` does, not only a node ID.
|
|
130
|
+
- `impls` says trait implementations are recorded for Rust only, instead
|
|
131
|
+
of advising a reindex.
|
|
132
|
+
- An unresolved call with one candidate reads "unresolved; the only
|
|
133
|
+
candidate", not "one of 1 candidates".
|
|
134
|
+
|
|
9
135
|
## [0.3.0] — 2026-10-22
|
|
10
136
|
|
|
11
137
|
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 (2810 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.2
|
|
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.
|
|
@@ -107,4 +112,18 @@ def load_graph_with_schema(path: Path | str) -> dict:
|
|
|
107
112
|
f"run 'descry index' to rebuild."
|
|
108
113
|
)
|
|
109
114
|
_check_shape(data)
|
|
115
|
+
count_callers(data["nodes"], data["edges"])
|
|
110
116
|
return data
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def count_callers(nodes: list, edges: list) -> None:
|
|
120
|
+
"""Set each node's `in_degree` to the number of its callers: the
|
|
121
|
+
symbols whose calls are resolved to it, not those an unresolved call
|
|
122
|
+
only may be. A graph is counted again as it loads, so one written by an
|
|
123
|
+
older descry counts them the same way."""
|
|
124
|
+
callers: dict[str, set] = {}
|
|
125
|
+
for edge in edges:
|
|
126
|
+
if edge["relation"] in CALL_RELATIONS:
|
|
127
|
+
callers.setdefault(edge["target"], set()).add(edge["source"])
|
|
128
|
+
for node in nodes:
|
|
129
|
+
node["metadata"]["in_degree"] = len(callers.get(node["id"], ()))
|
|
@@ -302,7 +302,12 @@ def main():
|
|
|
302
302
|
choices=["forward", "backward"],
|
|
303
303
|
help="Trace direction (default: forward)",
|
|
304
304
|
)
|
|
305
|
-
sub.add_argument(
|
|
305
|
+
sub.add_argument(
|
|
306
|
+
"--depth",
|
|
307
|
+
type=int,
|
|
308
|
+
default=3,
|
|
309
|
+
help="Levels of calls below the start (default: 3)",
|
|
310
|
+
)
|
|
306
311
|
sub.add_argument("--target", default=None, help="Target symbol to reach")
|
|
307
312
|
sub.add_argument(
|
|
308
313
|
"--inline-threshold",
|
|
@@ -368,7 +373,7 @@ def main():
|
|
|
368
373
|
|
|
369
374
|
# impls
|
|
370
375
|
sub = subparsers.add_parser(
|
|
371
|
-
"impls", help="Find all implementations of a trait method"
|
|
376
|
+
"impls", help="Find all implementations of a Rust trait method"
|
|
372
377
|
)
|
|
373
378
|
sub.add_argument("method", help="Method name")
|
|
374
379
|
sub.add_argument("--trait-name", default=None, help="Filter by trait name")
|
|
@@ -12,6 +12,7 @@ Usage:
|
|
|
12
12
|
|
|
13
13
|
import contextlib
|
|
14
14
|
import hashlib
|
|
15
|
+
import importlib.util
|
|
15
16
|
import json
|
|
16
17
|
import logging
|
|
17
18
|
import os
|
|
@@ -347,13 +348,14 @@ def _resolve_cache_dir(graph_path: Path, cache_dir: str | None = None) -> Path:
|
|
|
347
348
|
# Try to import embedding dependencies
|
|
348
349
|
try:
|
|
349
350
|
import numpy as np
|
|
350
|
-
from sentence_transformers import SentenceTransformer
|
|
351
|
-
|
|
352
|
-
EMBEDDINGS_AVAILABLE = True
|
|
353
351
|
except ImportError:
|
|
354
|
-
EMBEDDINGS_AVAILABLE = False
|
|
355
352
|
np = None
|
|
356
|
-
|
|
353
|
+
# Imported by _load_sentence_transformer: it brings in torch, which takes
|
|
354
|
+
# seconds a command that loads no model should not spend.
|
|
355
|
+
SentenceTransformer = None
|
|
356
|
+
EMBEDDINGS_AVAILABLE = (
|
|
357
|
+
np is not None and importlib.util.find_spec("sentence_transformers") is not None
|
|
358
|
+
)
|
|
357
359
|
|
|
358
360
|
|
|
359
361
|
def embeddings_available() -> bool:
|
|
@@ -376,6 +378,9 @@ def _load_sentence_transformer(model_name: str, revision: str | None = None):
|
|
|
376
378
|
model_name: Registry alias, HuggingFace repo id, or local path.
|
|
377
379
|
revision: Overrides the registry's pinned revision.
|
|
378
380
|
"""
|
|
381
|
+
global SentenceTransformer
|
|
382
|
+
if SentenceTransformer is None:
|
|
383
|
+
from sentence_transformers import SentenceTransformer
|
|
379
384
|
spec = resolve_model_spec(model_name)
|
|
380
385
|
target = spec.repo_id if spec else model_name
|
|
381
386
|
if revision is None and spec:
|
|
@@ -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,10 @@ 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 CURRENT_SCHEMA, count_callers
|
|
25444
25437
|
|
|
25445
25438
|
self.resolve_references(scip_index=scip_index)
|
|
25446
|
-
|
|
25447
|
-
for edge in self.edges:
|
|
25448
|
-
t = edge["target"]
|
|
25449
|
-
if t:
|
|
25450
|
-
in_degree[t] = in_degree.get(t, 0) + 1
|
|
25451
|
-
for node in self.nodes:
|
|
25452
|
-
node["metadata"]["in_degree"] = in_degree.get(node["id"], 0)
|
|
25439
|
+
count_callers(self.nodes, self.edges)
|
|
25453
25440
|
# Atomic write: stream to a sibling tmp file then os.replace. A
|
|
25454
25441
|
# concurrent reader (MCP prewarm, web `/api/health`, another CLI
|
|
25455
25442
|
# invocation) that stats the destination mid-write would otherwise
|
|
@@ -758,7 +758,7 @@ class GitHistoryAnalyzer:
|
|
|
758
758
|
|
|
759
759
|
# Sort by commit count
|
|
760
760
|
sorted_symbols = sorted(
|
|
761
|
-
symbol_commits.items(), key=lambda x: len(x[1]),
|
|
761
|
+
symbol_commits.items(), key=lambda x: (-len(x[1]), x[0])
|
|
762
762
|
)[:limit]
|
|
763
763
|
|
|
764
764
|
lines = []
|
|
@@ -923,7 +923,7 @@ class GitHistoryAnalyzer:
|
|
|
923
923
|
|
|
924
924
|
# SYMBOLS MODE
|
|
925
925
|
sorted_symbols = sorted(
|
|
926
|
-
symbol_commits.items(), key=lambda x: len(x[1]),
|
|
926
|
+
symbol_commits.items(), key=lambda x: (-len(x[1]), x[0])
|
|
927
927
|
)[:limit]
|
|
928
928
|
|
|
929
929
|
symbols = []
|
|
@@ -983,7 +983,7 @@ class GitHistoryAnalyzer:
|
|
|
983
983
|
frequent_pairs = [
|
|
984
984
|
(pair, count) for pair, count in pair_counts.items() if count >= 2
|
|
985
985
|
]
|
|
986
|
-
frequent_pairs.sort(key=lambda x: x[1],
|
|
986
|
+
frequent_pairs.sort(key=lambda x: (-x[1], x[0]))
|
|
987
987
|
|
|
988
988
|
for (sym_a, sym_b), count in frequent_pairs[:limit]:
|
|
989
989
|
_, name_a, loc_a = self._get_node_display(sym_a)
|
|
@@ -1081,7 +1081,7 @@ class GitHistoryAnalyzer:
|
|
|
1081
1081
|
frequent_pairs = [
|
|
1082
1082
|
(pair, count) for pair, count in pair_counts.items() if count >= 2
|
|
1083
1083
|
]
|
|
1084
|
-
frequent_pairs.sort(key=lambda x: x[1],
|
|
1084
|
+
frequent_pairs.sort(key=lambda x: (-x[1], x[0]))
|
|
1085
1085
|
frequent_pairs = frequent_pairs[:limit]
|
|
1086
1086
|
|
|
1087
1087
|
if frequent_pairs:
|
|
@@ -1660,26 +1660,43 @@ class GitHistoryAnalyzer:
|
|
|
1660
1660
|
f"(+{stats['added']}/-{stats['removed']})"
|
|
1661
1661
|
)
|
|
1662
1662
|
|
|
1663
|
-
# Show callers if requested
|
|
1664
1663
|
if show_callers and self.querier:
|
|
1665
|
-
|
|
1666
|
-
callers = self.querier.get_callers(sym_name)
|
|
1664
|
+
callers, total, possible = self._symbol_callers(node_id)
|
|
1667
1665
|
if callers:
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
lines.append(f" Callers: {
|
|
1666
|
+
names = ", ".join(
|
|
1667
|
+
c["name"] + self.querier.one_of(c["candidates"])
|
|
1668
|
+
for c in callers
|
|
1669
|
+
)
|
|
1670
|
+
hidden = possible - sum(bool(c["candidates"]) for c in callers)
|
|
1671
|
+
hidden_certain = total - len(callers) - hidden
|
|
1672
|
+
more = (
|
|
1673
|
+
f"; {self.querier.more_callers(hidden_certain, hidden)}"
|
|
1674
|
+
if total > len(callers)
|
|
1675
|
+
else ""
|
|
1676
|
+
)
|
|
1677
|
+
lines.append(f" Callers: {names}{more}")
|
|
1680
1678
|
|
|
1681
1679
|
return "\n".join(lines)
|
|
1682
1680
|
|
|
1681
|
+
def _symbol_callers(self, node_id):
|
|
1682
|
+
"""The first five callers of the symbol ``node_id``, certain ones
|
|
1683
|
+
first, each ``{"id", "name", "candidates"}`` where ``candidates`` is
|
|
1684
|
+
the number an unresolved call reaching it only may be one of; the
|
|
1685
|
+
number of its callers; and how many of those only may be."""
|
|
1686
|
+
caller_ids, ambiguous = self.querier.ordered_callers(node_id)
|
|
1687
|
+
callers = []
|
|
1688
|
+
for caller_id in caller_ids[:5]:
|
|
1689
|
+
c_node = self.querier.get_node_info(caller_id)
|
|
1690
|
+
name = (
|
|
1691
|
+
c_node["metadata"].get("name", "?")
|
|
1692
|
+
if c_node
|
|
1693
|
+
else caller_id.split("::")[-1]
|
|
1694
|
+
)
|
|
1695
|
+
callers.append(
|
|
1696
|
+
{"id": caller_id, "name": name, "candidates": ambiguous.get(caller_id)}
|
|
1697
|
+
)
|
|
1698
|
+
return callers, len(caller_ids), len(ambiguous)
|
|
1699
|
+
|
|
1683
1700
|
def get_changes_structured(
|
|
1684
1701
|
self,
|
|
1685
1702
|
commit_range: str | None = None,
|
|
@@ -1742,22 +1759,11 @@ class GitHistoryAnalyzer:
|
|
|
1742
1759
|
}
|
|
1743
1760
|
|
|
1744
1761
|
if show_callers and self.querier:
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
if caller_ids:
|
|
1748
|
-
callers = []
|
|
1749
|
-
for caller_id in caller_ids[:5]:
|
|
1750
|
-
c_node = self.querier.get_node_info(caller_id)
|
|
1751
|
-
c_name = (
|
|
1752
|
-
c_node["metadata"].get("name", "?")
|
|
1753
|
-
if c_node
|
|
1754
|
-
else caller_id.split("::")[-1]
|
|
1755
|
-
if "::" in caller_id
|
|
1756
|
-
else "?"
|
|
1757
|
-
)
|
|
1758
|
-
callers.append({"id": caller_id, "name": c_name})
|
|
1762
|
+
callers, total, possible = self._symbol_callers(node_id)
|
|
1763
|
+
if callers:
|
|
1759
1764
|
entry["callers"] = callers
|
|
1760
|
-
entry["callers_total"] =
|
|
1765
|
+
entry["callers_total"] = total
|
|
1766
|
+
entry["callers_possible"] = possible
|
|
1761
1767
|
|
|
1762
1768
|
symbols.append(entry)
|
|
1763
1769
|
|