descry-codegraph 0.3.1__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/CHANGELOG.md +106 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/CLAUDE.md +1 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/PKG-INFO +3 -3
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/README.md +2 -2
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/pyproject.toml +1 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/__init__.py +1 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/_graph.py +14 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/cli.py +6 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/embeddings.py +10 -5
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/generate.py +21 -14
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/git_history.py +40 -33
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/handlers.py +83 -36
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/mcp_server.py +1 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/query.py +562 -718
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/web/server.py +26 -9
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/web/web/index.html +32 -8
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_cli.py +1 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_embeddings.py +17 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_generate.py +48 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_git_history.py +16 -2
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_handlers.py +116 -5
- descry_codegraph-0.3.3/tests/test_interfaces.py +296 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_mcp.py +1 -1
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_query.py +638 -63
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_web_server.py +59 -6
- descry_codegraph-0.3.1/tests/test_interfaces.py +0 -178
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/.github/assets/01-landing.png +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/.github/workflows/ci.yml +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/.github/workflows/release.yml +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/.gitignore +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/LICENSE +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/SECURITY.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/THIRD-PARTY-NOTICES.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/claude-skill/README.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/claude-skill/descry/SKILL.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/claude-skill/descry/references/cli-reference.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/claude-skill/descry/references/mcp-tools.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/claude-skill/descry/references/recipes.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/justfile +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/pi-extension/README.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/pi-extension/descry-cli.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/pi-extension/descry-tools.ts +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/_comments.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/_env.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/_files.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/_stdlib.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/clang_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/cross_lang.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/dart_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/dotnet_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/go_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/java_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/php_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/ruby_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/__init__.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapter.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/__init__.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/_empty_environment.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/_java_compat.init.gradle +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/clang.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/dart.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/dotnet.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/go.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/java.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/php.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/python.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/ruby.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/rust.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/adapters/typescript.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/cache.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/pb2.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/scip/support.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/c.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/cpp.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/dart.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/dotnet.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/go.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/java.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/node.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/php.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/python.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/ruby.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/rust.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/stdlib/typescript.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/web/__init__.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/src/descry/web/web/style.css +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/__init__.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/conftest.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/README.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/__init__.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/compare.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/corpus.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/metrics.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/registry.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/results/results-descry.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/eval/results/results-ripgrep.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/fixtures/small_graph.json +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_backtracking.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_clang_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_config.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_cross_lang.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_dart_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_dotnet_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_go_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_hardening.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_java_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_non_regular_files.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_php_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_ruby_parser.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_cache.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_clang.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_dart.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_dotnet.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_go.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_java.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_php.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_scip_ruby.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_stdlib.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tests/test_written_through.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/README.md +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/common.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_c.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_cpp.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_dart.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_dotnet.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_go.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_java.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_node.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_php.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_python.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_ruby.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_rust.py +0 -0
- {descry_codegraph-0.3.1 → descry_codegraph-0.3.3}/tools/stdlib/gen_typescript.py +0 -0
|
@@ -6,6 +6,112 @@ 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.3] — 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A `bin` directory is indexed: Dart keeps its entry points there and Rust
|
|
14
|
+
its extra binaries (`src/bin/`). Only the `bin` of a .NET project or of
|
|
15
|
+
an Eclipse project, beside its project file or `.classpath`, is left out
|
|
16
|
+
as build output; every `bin` was.
|
|
17
|
+
- A Dart call of a standard type's name without `new`, `StringBuffer()`,
|
|
18
|
+
makes an instance of that type, as `new StringBuffer()` does. Calls on it
|
|
19
|
+
were taken as calls on an unknown receiver and listed as possible calls
|
|
20
|
+
of every project method of their name (`toString`, `write`, `close`).
|
|
21
|
+
|
|
22
|
+
## [0.3.2] — 2026-10-23
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- A caller that reaches a symbol only as one of an unresolved call's
|
|
27
|
+
candidates is counted apart from its callers. `in_degree` ("N callers")
|
|
28
|
+
counts the callers whose calls are resolved to the symbol, and is
|
|
29
|
+
counted again as a graph loads, so a graph from 0.3.1 needs no reindex.
|
|
30
|
+
Every other count gives the others as "(+N possible)": brief `context`
|
|
31
|
+
(callers and callees), `callers`, `callees`, and the web page's headers
|
|
32
|
+
(`possible` in the web API). Lists mark each one; `changes` does so in
|
|
33
|
+
text, in its structured form (`candidates`, `callers_possible`) and on
|
|
34
|
+
the page.
|
|
35
|
+
- A function no longer counts as a certain caller of a symbol it defines
|
|
36
|
+
when its call to it is unresolved.
|
|
37
|
+
- A node ID names that node alone: `callers`, `callees`, `quick`, `flow`
|
|
38
|
+
and `path` given one no longer also match other definitions filed under
|
|
39
|
+
the same owner and name (a Ruby `self.x` beside `x`).
|
|
40
|
+
- `context`'s usage examples are calls of that symbol, those resolved to
|
|
41
|
+
it first, then those it is one of the candidates of, marked; they were
|
|
42
|
+
any unresolved call ending in its name.
|
|
43
|
+
- `callers` of a name lists the calls the resolver left unresolved with no
|
|
44
|
+
candidates apart from its callers, marked "(unresolved; same name)" and
|
|
45
|
+
counted as "+N unresolved" (`unresolved` in the web API). Some are calls
|
|
46
|
+
of the project's definition the resolver cannot follow (a Rust re-export,
|
|
47
|
+
a TypeScript static member), some are calls of something else with that
|
|
48
|
+
name (`loader.list_templates()` on a library's loader). Neither is
|
|
49
|
+
counted as a caller. An unresolved call with candidates reaches only
|
|
50
|
+
those. Callers asked for by node ID are those resolved to it or narrowed
|
|
51
|
+
to it.
|
|
52
|
+
- A list cut short (`callers`, `callees`, `context`'s callers, `changes`,
|
|
53
|
+
backward `flow` in text and as a tree) keeps the callers resolved to the
|
|
54
|
+
symbol first, and says how many it leaves out and how many of those only
|
|
55
|
+
may be; a `flow` tree node gives them as `more` and `more_possible`, and
|
|
56
|
+
the web API's `callers` and `callees` as `more`, shown on the page.
|
|
57
|
+
- `flow`, in text and as a tree, places each symbol at the first level
|
|
58
|
+
that reaches it and marks it "(at level N)" where it is met again
|
|
59
|
+
(`seen`, that level, in the tree); a direct callee or caller reached
|
|
60
|
+
first through another one was dropped from its level, with everything
|
|
61
|
+
below it. `context`, at every depth, lists all of the symbol's own
|
|
62
|
+
callees and cuts each level below to the per-level limit with a note; it
|
|
63
|
+
opens a callee (inlines it and lists its own callees) at the first level
|
|
64
|
+
that reaches it while the token budget covers it, lists every other
|
|
65
|
+
callee by its signature or "(at level N)", and says when the budget, the
|
|
66
|
+
node limit or the timeout cut it short. Depth 1 ignored `max_tokens`. A
|
|
67
|
+
`flow` that stops early says so: "(timeout)" or "(node limit)" in text,
|
|
68
|
+
`cut` in the tree, a note on the page.
|
|
69
|
+
- A `flow` of `depth` N shows N levels of calls below the start; `depth=1`
|
|
70
|
+
showed the start alone. `[query] max_depth` is that many levels too, for
|
|
71
|
+
`flow` and for `context`'s callee expansion. A depth past it is traced to
|
|
72
|
+
it and says so; the web API returns the depth traced, and `/api/health`
|
|
73
|
+
gives the ceiling the page's depth field is held to.
|
|
74
|
+
- A callee the resolver left unresolved is no longer shown or counted as
|
|
75
|
+
certain or as external. `context`, at every depth, names it as one of
|
|
76
|
+
its candidates, or "(unresolved)" where it found none. `callees` marks
|
|
77
|
+
it the same way and counts it apart ("+N unresolved"). A callee is what
|
|
78
|
+
a call is written as: calls written apart (`a.add`, `b.add`) are two
|
|
79
|
+
callees, and calls written alike may reach any definition one of them
|
|
80
|
+
is narrowed to. Forward `flow` follows a possible callee to each
|
|
81
|
+
definition it may reach, marked, and `context` at a depth past 1 says
|
|
82
|
+
how many callees each level leaves out.
|
|
83
|
+
- `callees` says how many callees its `limit` leaves out, and of a name
|
|
84
|
+
several definitions share, which one it answers for (`others` in the web
|
|
85
|
+
API).
|
|
86
|
+
- A `limit`, a `flow` or `context` `depth`, a `context` `max_tokens` or
|
|
87
|
+
`callee_budget`, or a `path` `max_depth` below 1 is refused by every
|
|
88
|
+
tool; `limit=0` answered as if there were no
|
|
89
|
+
callers, matches, changes or history.
|
|
90
|
+
- The web page's changes view no longer throws for a changed symbol with
|
|
91
|
+
no callers.
|
|
92
|
+
- `context --max-output-tokens` cuts the parts the answer is made of, not
|
|
93
|
+
its text: expanded callees, then usage examples past 2, related tests
|
|
94
|
+
and configuration, callers past 5 (still counting possible callers
|
|
95
|
+
apart), the symbol's callees past 5 (planned again for those five, so
|
|
96
|
+
the note counts by kind only callees drawn nowhere in the answer and no
|
|
97
|
+
"(at level N)" mark points at a callee cut away), and the source past 30
|
|
98
|
+
of the symbol's lines, saying what each step left out; a section dropped
|
|
99
|
+
whole keeps its heading with "(omitted, max_output_tokens limit)".
|
|
100
|
+
It read the rendered text back, so code with a heading-like or a fence
|
|
101
|
+
line in it was cut wrongly or not at all, opened callees kept their
|
|
102
|
+
headings without their code, and counts were wrong.
|
|
103
|
+
- `flow` text indents the code it inlines as deep as its fence. The code
|
|
104
|
+
was printed from the first column inside an indented fence, so it broke
|
|
105
|
+
the tree, and below the first level the fence read as an indented code
|
|
106
|
+
block.
|
|
107
|
+
- A command that loads no embedding model no longer imports
|
|
108
|
+
sentence-transformers, and with it torch, as the service starts: where
|
|
109
|
+
they are installed every CLI command spent seconds on it.
|
|
110
|
+
- `churn` ranks symbols, and co-change pairs of symbols, with the same
|
|
111
|
+
number of commits by node ID, so the same history gives the same list.
|
|
112
|
+
Their order among ties changed from run to run, and so, at the limit,
|
|
113
|
+
did which of them were listed.
|
|
114
|
+
|
|
9
115
|
## [0.3.1] — 2026-10-22
|
|
10
116
|
|
|
11
117
|
### 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 (2812 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.3
|
|
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
|
|
@@ -267,7 +267,7 @@ For customization, add a `.descry.toml` to your project root.
|
|
|
267
267
|
|
|
268
268
|
```toml
|
|
269
269
|
[project]
|
|
270
|
-
# OPTIONAL — REPLACES the
|
|
270
|
+
# OPTIONAL — REPLACES the 24-element default. Leave unset to keep defaults
|
|
271
271
|
# (.git, .gradle, .next, .svelte-kit, .venv, node_modules, target, build, …).
|
|
272
272
|
# excluded_dirs = ["target", "node_modules", "dist", "build", "vendor"]
|
|
273
273
|
# OPTIONAL — in a git repository, files git ignores are not indexed.
|
|
@@ -393,7 +393,7 @@ Share of project calls resolved without SCIP, measured on public repositories at
|
|
|
393
393
|
| C# | serilog `bebc771`, Newtonsoft.Json `52fa3ae` (`Src/Newtonsoft.Json`) | 94%, 91% |
|
|
394
394
|
| C | jq `a19efdd`, libuv `954c1d1` | 99%, 79% |
|
|
395
395
|
| C++ | leveldb `7ee830d`, fmt `c4927e6` | 97%, 86% |
|
|
396
|
-
| Dart | shelf `b2cb503`, riverpod `1efba1c` (`packages/riverpod`) |
|
|
396
|
+
| Dart | shelf `b2cb503`, riverpod `1efba1c` (`packages/riverpod`) | 95%, 91% |
|
|
397
397
|
| Ruby | rack `a9833c8`, sinatra `cb22afd` | 70%, 39% |
|
|
398
398
|
|
|
399
399
|
The remainder is unresolved, not wrong: of 450 resolved edges drawn at random from these repositories and read against the source, every one reaches the definition the call reaches. Ruby is low because the language declares no types and calls inside DSL blocks run with another `self`; libuv because most of its functions exist once for Unix and once for Windows, and the build files, not the source, choose between them.
|
|
@@ -202,7 +202,7 @@ For customization, add a `.descry.toml` to your project root.
|
|
|
202
202
|
|
|
203
203
|
```toml
|
|
204
204
|
[project]
|
|
205
|
-
# OPTIONAL — REPLACES the
|
|
205
|
+
# OPTIONAL — REPLACES the 24-element default. Leave unset to keep defaults
|
|
206
206
|
# (.git, .gradle, .next, .svelte-kit, .venv, node_modules, target, build, …).
|
|
207
207
|
# excluded_dirs = ["target", "node_modules", "dist", "build", "vendor"]
|
|
208
208
|
# OPTIONAL — in a git repository, files git ignores are not indexed.
|
|
@@ -328,7 +328,7 @@ Share of project calls resolved without SCIP, measured on public repositories at
|
|
|
328
328
|
| C# | serilog `bebc771`, Newtonsoft.Json `52fa3ae` (`Src/Newtonsoft.Json`) | 94%, 91% |
|
|
329
329
|
| C | jq `a19efdd`, libuv `954c1d1` | 99%, 79% |
|
|
330
330
|
| C++ | leveldb `7ee830d`, fmt `c4927e6` | 97%, 86% |
|
|
331
|
-
| Dart | shelf `b2cb503`, riverpod `1efba1c` (`packages/riverpod`) |
|
|
331
|
+
| Dart | shelf `b2cb503`, riverpod `1efba1c` (`packages/riverpod`) | 95%, 91% |
|
|
332
332
|
| Ruby | rack `a9833c8`, sinatra `cb22afd` | 70%, 39% |
|
|
333
333
|
|
|
334
334
|
The remainder is unresolved, not wrong: of 450 resolved edges drawn at random from these repositories and read against the source, every one reaches the definition the call reaches. Ruby is low because the language declares no types and calls inside DSL blocks run with another `self`; libuv because most of its functions exist once for Unix and once for Windows, and the build files, not the source, choose between them.
|
|
@@ -112,4 +112,18 @@ def load_graph_with_schema(path: Path | str) -> dict:
|
|
|
112
112
|
f"run 'descry index' to rebuild."
|
|
113
113
|
)
|
|
114
114
|
_check_shape(data)
|
|
115
|
+
count_callers(data["nodes"], data["edges"])
|
|
115
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",
|
|
@@ -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:
|
|
@@ -18650,6 +18650,15 @@ class CallResolver:
|
|
|
18650
18650
|
if self.standard[lang].key(callee, source_id) == callee:
|
|
18651
18651
|
held = self._returned_type(constructed, source_id)
|
|
18652
18652
|
self._result = held or self._result
|
|
18653
|
+
elif verdict[1] == _EXTERNAL and not origin and lang == "dart":
|
|
18654
|
+
# Dart constructs without `new`: a call of a standard type's
|
|
18655
|
+
# name gives an instance of it.
|
|
18656
|
+
library = self.standard[lang]
|
|
18657
|
+
if (
|
|
18658
|
+
callee in library.data["types"]
|
|
18659
|
+
and library.key(callee, source_id) == callee
|
|
18660
|
+
):
|
|
18661
|
+
self._result = self._returned_type(callee, source_id) or self._result
|
|
18653
18662
|
elif verdict[1] == _EXTERNAL and not origin and lang == "typescript":
|
|
18654
18663
|
known, value = self._js_global(callee, source_id)
|
|
18655
18664
|
if known:
|
|
@@ -24878,12 +24887,19 @@ class CodeGraphBuilder:
|
|
|
24878
24887
|
for root, dirs, files in os.walk(self.root_dir, followlinks=False):
|
|
24879
24888
|
dirs.sort()
|
|
24880
24889
|
files.sort()
|
|
24881
|
-
# Filter by name first (excluded dirs, dotfiles
|
|
24882
|
-
# ignores.
|
|
24890
|
+
# Filter by name first (excluded dirs, dotfiles, the build output
|
|
24891
|
+
# of a .NET or an Eclipse project), and by what git ignores.
|
|
24892
|
+
# `bin` holds sources in Dart and Rust.
|
|
24893
|
+
built = any(
|
|
24894
|
+
f.endswith((".csproj", ".vbproj", ".fsproj")) or f == ".classpath"
|
|
24895
|
+
for f in files
|
|
24896
|
+
)
|
|
24883
24897
|
dirs[:] = [
|
|
24884
24898
|
d
|
|
24885
24899
|
for d in kept(root, dirs)
|
|
24886
|
-
if not d.startswith(".")
|
|
24900
|
+
if not d.startswith(".")
|
|
24901
|
+
and d not in self.excluded_dirs
|
|
24902
|
+
and not (built and d == "bin")
|
|
24887
24903
|
]
|
|
24888
24904
|
# Then filter by realpath containment — drop directories that are
|
|
24889
24905
|
# symlinks pointing outside the project root.
|
|
@@ -25433,19 +25449,10 @@ class CodeGraphBuilder:
|
|
|
25433
25449
|
output_path: Path to write the graph JSON
|
|
25434
25450
|
scip_index: Optional ScipIndex for type-aware resolution
|
|
25435
25451
|
"""
|
|
25436
|
-
from descry._graph import
|
|
25452
|
+
from descry._graph import CURRENT_SCHEMA, count_callers
|
|
25437
25453
|
|
|
25438
25454
|
self.resolve_references(scip_index=scip_index)
|
|
25439
|
-
|
|
25440
|
-
# the candidates of
|
|
25441
|
-
callers = {}
|
|
25442
|
-
for edge in self.edges:
|
|
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"])
|
|
25447
|
-
for node in self.nodes:
|
|
25448
|
-
node["metadata"]["in_degree"] = len(callers.get(node["id"], ()))
|
|
25455
|
+
count_callers(self.nodes, self.edges)
|
|
25449
25456
|
# Atomic write: stream to a sibling tmp file then os.replace. A
|
|
25450
25457
|
# concurrent reader (MCP prewarm, web `/api/health`, another CLI
|
|
25451
25458
|
# 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(node_id)
|
|
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,21 +1759,11 @@ class GitHistoryAnalyzer:
|
|
|
1742
1759
|
}
|
|
1743
1760
|
|
|
1744
1761
|
if show_callers and self.querier:
|
|
1745
|
-
|
|
1746
|
-
if
|
|
1747
|
-
callers = []
|
|
1748
|
-
for caller_id in caller_ids[:5]:
|
|
1749
|
-
c_node = self.querier.get_node_info(caller_id)
|
|
1750
|
-
c_name = (
|
|
1751
|
-
c_node["metadata"].get("name", "?")
|
|
1752
|
-
if c_node
|
|
1753
|
-
else caller_id.split("::")[-1]
|
|
1754
|
-
if "::" in caller_id
|
|
1755
|
-
else "?"
|
|
1756
|
-
)
|
|
1757
|
-
callers.append({"id": caller_id, "name": c_name})
|
|
1762
|
+
callers, total, possible = self._symbol_callers(node_id)
|
|
1763
|
+
if callers:
|
|
1758
1764
|
entry["callers"] = callers
|
|
1759
|
-
entry["callers_total"] =
|
|
1765
|
+
entry["callers_total"] = total
|
|
1766
|
+
entry["callers_possible"] = possible
|
|
1760
1767
|
|
|
1761
1768
|
symbols.append(entry)
|
|
1762
1769
|
|