codingest 0.1.2__tar.gz → 0.1.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.
- {codingest-0.1.2 → codingest-0.1.3}/Cargo.lock +5 -4
- {codingest-0.1.2 → codingest-0.1.3}/Cargo.toml +2 -2
- {codingest-0.1.2 → codingest-0.1.3}/PKG-INFO +14 -7
- {codingest-0.1.2 → codingest-0.1.3}/README.md +13 -6
- codingest-0.1.3/codingest/mcp_server.py +38 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/call_edges.rs +22 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/load/edge_frames.rs +176 -2
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/load/entity_frames.rs +58 -4
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/load.rs +71 -2
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/mod.rs +46 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/other_edges.rs +16 -0
- codingest-0.1.3/crates/codingest/src/builder/semantic_edges.rs +530 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/models.rs +91 -0
- codingest-0.1.3/crates/codingest/src/parsers/agc.rs +984 -0
- codingest-0.1.3/crates/codingest/tests/agc_apollo.rs +116 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/README.md +3 -2
- codingest-0.1.3/crates/codingest/tests/goldens/agc_basic.sha256 +1 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/skills/codingest-code-review/SKILL.md +2 -2
- codingest-0.1.3/crates/codingest-mcp/Cargo.toml +19 -0
- codingest-0.1.3/crates/codingest-mcp/src/lib.rs +99 -0
- codingest-0.1.3/crates/codingest-mcp/src/main.rs +5 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-py/Cargo.toml +5 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-py/src/lib.rs +15 -0
- {codingest-0.1.2 → codingest-0.1.3}/pyproject.toml +6 -5
- codingest-0.1.2/crates/codingest/src/parsers/agc.rs +0 -522
- codingest-0.1.2/crates/codingest/tests/goldens/agc_basic.sha256 +0 -1
- {codingest-0.1.2 → codingest-0.1.3}/LICENSE +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/codingest/__init__.py +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/codingest/__init__.pyi +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/codingest/cli.py +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/Cargo.toml +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/bin/codingest_bench.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/bin/codingest_stats.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/routes/django.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/routes/fastapi.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/routes/flask.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/routes/mod.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/builder/type_edges.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/cross_lang.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/docs/mod.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/docs/rst.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/lib.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/manifest/mod.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/cpp.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/csharp.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/css.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/dart.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/go.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/html.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/java.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/mod.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/php.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/python.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/registry.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/rust_lang.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/shared.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/swift.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/parsers/typescript.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/repo.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/src/rev.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/cross_ts_py.sha256 +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/dup_minified_assets.sha256 +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/py_basic.sha256 +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/py_inheritance.sha256 +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/rust_xfile.sha256 +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/goldens/ts_callback.sha256 +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest/tests/parity.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/Cargo.toml +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/skills/codingest-code-review/references/mcp-upgrade.md +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/skills/codingest-code-review/references/public-repositories.md +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/skills/codingest-code-review/references/queries.md +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/src/code_tree_cli.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/src/lib.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/src/main.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/src/skill.rs +0 -0
- {codingest-0.1.2 → codingest-0.1.3}/crates/codingest-cli/src/skill_assets.rs +0 -0
|
@@ -313,7 +313,7 @@ dependencies = [
|
|
|
313
313
|
|
|
314
314
|
[[package]]
|
|
315
315
|
name = "codingest"
|
|
316
|
-
version = "0.1.
|
|
316
|
+
version = "0.1.3"
|
|
317
317
|
dependencies = [
|
|
318
318
|
"aho-corasick",
|
|
319
319
|
"base64",
|
|
@@ -345,7 +345,7 @@ dependencies = [
|
|
|
345
345
|
|
|
346
346
|
[[package]]
|
|
347
347
|
name = "codingest-cli"
|
|
348
|
-
version = "0.1.
|
|
348
|
+
version = "0.1.3"
|
|
349
349
|
dependencies = [
|
|
350
350
|
"anyhow",
|
|
351
351
|
"clap",
|
|
@@ -357,7 +357,7 @@ dependencies = [
|
|
|
357
357
|
|
|
358
358
|
[[package]]
|
|
359
359
|
name = "codingest-mcp"
|
|
360
|
-
version = "0.1.
|
|
360
|
+
version = "0.1.3"
|
|
361
361
|
dependencies = [
|
|
362
362
|
"anyhow",
|
|
363
363
|
"codingest",
|
|
@@ -366,10 +366,11 @@ dependencies = [
|
|
|
366
366
|
|
|
367
367
|
[[package]]
|
|
368
368
|
name = "codingest-py"
|
|
369
|
-
version = "0.1.
|
|
369
|
+
version = "0.1.3"
|
|
370
370
|
dependencies = [
|
|
371
371
|
"codingest",
|
|
372
372
|
"codingest-cli",
|
|
373
|
+
"codingest-mcp",
|
|
373
374
|
"kglite",
|
|
374
375
|
"pyo3",
|
|
375
376
|
"tempfile",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[workspace]
|
|
2
|
-
members = ["crates/codingest", "crates/codingest-cli", "crates/codingest-py"]
|
|
2
|
+
members = ["crates/codingest", "crates/codingest-cli", "crates/codingest-mcp", "crates/codingest-py"]
|
|
3
3
|
resolver = "2"
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.3"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
rust-version = "1.88"
|
|
9
9
|
license = "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codingest
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -70,10 +70,13 @@ a real `kglite.KnowledgeGraph`, ready for Cypher queries.
|
|
|
70
70
|
- **Revision-aware:** analyse a tag, branch, commit, or multiple revisions in
|
|
71
71
|
one graph and query what changed.
|
|
72
72
|
|
|
73
|
+
AGC assembly receives architecture-aware control and data relationships rather
|
|
74
|
+
than a generic text-level call graph; see the [AGC graph model](https://codingest.readthedocs.io/en/latest/agc-assembly.html).
|
|
75
|
+
|
|
73
76
|
## Give your agent a code-review MCP server
|
|
74
77
|
|
|
75
78
|
```bash
|
|
76
|
-
|
|
79
|
+
pip install codingest
|
|
77
80
|
codingest-mcp --watch /absolute/path/to/repo
|
|
78
81
|
```
|
|
79
82
|
|
|
@@ -172,14 +175,18 @@ for direct Cypher analysis.
|
|
|
172
175
|
## Install options
|
|
173
176
|
|
|
174
177
|
```bash
|
|
175
|
-
pip install codingest # Python API +
|
|
176
|
-
cargo install codingest-cli # pure-Rust builder CLI
|
|
177
|
-
cargo install codingest-mcp # MCP server for agents
|
|
178
|
+
pip install codingest # Python API + CLI + MCP server + KGLite runtime
|
|
178
179
|
codingest skill install # code-review Agent Skill
|
|
179
180
|
```
|
|
180
181
|
|
|
181
|
-
The wheel bundles every grammar
|
|
182
|
-
installed automatically as the query/storage
|
|
182
|
+
The wheel bundles every grammar plus the `codingest` and `codingest-mcp`
|
|
183
|
+
commands. KGLite ≥0.14.5 is installed automatically as the query/storage
|
|
184
|
+
engine; its MCP server and the transitive `mcp-methods` framework power the
|
|
185
|
+
builder-aware Codingest server. Nothing else needs to be installed.
|
|
186
|
+
|
|
187
|
+
Rust-only environments can instead use `cargo install codingest-cli
|
|
188
|
+
codingest-mcp`; these are alternative distributions of the same commands, not
|
|
189
|
+
Python prerequisites.
|
|
183
190
|
|
|
184
191
|
### Rust crate
|
|
185
192
|
|
|
@@ -43,10 +43,13 @@ a real `kglite.KnowledgeGraph`, ready for Cypher queries.
|
|
|
43
43
|
- **Revision-aware:** analyse a tag, branch, commit, or multiple revisions in
|
|
44
44
|
one graph and query what changed.
|
|
45
45
|
|
|
46
|
+
AGC assembly receives architecture-aware control and data relationships rather
|
|
47
|
+
than a generic text-level call graph; see the [AGC graph model](https://codingest.readthedocs.io/en/latest/agc-assembly.html).
|
|
48
|
+
|
|
46
49
|
## Give your agent a code-review MCP server
|
|
47
50
|
|
|
48
51
|
```bash
|
|
49
|
-
|
|
52
|
+
pip install codingest
|
|
50
53
|
codingest-mcp --watch /absolute/path/to/repo
|
|
51
54
|
```
|
|
52
55
|
|
|
@@ -145,14 +148,18 @@ for direct Cypher analysis.
|
|
|
145
148
|
## Install options
|
|
146
149
|
|
|
147
150
|
```bash
|
|
148
|
-
pip install codingest # Python API +
|
|
149
|
-
cargo install codingest-cli # pure-Rust builder CLI
|
|
150
|
-
cargo install codingest-mcp # MCP server for agents
|
|
151
|
+
pip install codingest # Python API + CLI + MCP server + KGLite runtime
|
|
151
152
|
codingest skill install # code-review Agent Skill
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
The wheel bundles every grammar
|
|
155
|
-
installed automatically as the query/storage
|
|
155
|
+
The wheel bundles every grammar plus the `codingest` and `codingest-mcp`
|
|
156
|
+
commands. KGLite ≥0.14.5 is installed automatically as the query/storage
|
|
157
|
+
engine; its MCP server and the transitive `mcp-methods` framework power the
|
|
158
|
+
builder-aware Codingest server. Nothing else needs to be installed.
|
|
159
|
+
|
|
160
|
+
Rust-only environments can instead use `cargo install codingest-cli
|
|
161
|
+
codingest-mcp`; these are alternative distributions of the same commands, not
|
|
162
|
+
Python prerequisites.
|
|
156
163
|
|
|
157
164
|
### Rust crate
|
|
158
165
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Console-script shim for the Rust ``codingest-mcp`` server in the wheel.
|
|
2
|
+
|
|
3
|
+
Codingest supplies the source-builder hooks; KGlite supplies graph/Cypher
|
|
4
|
+
serving and pulls the generic MCP lifecycle from ``mcp-methods``. All three are
|
|
5
|
+
linked into the wheel's existing native extension, so users need only
|
|
6
|
+
``pip install codingest``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv: list[str] | None = None) -> int:
|
|
17
|
+
"""Run the bundled MCP server with ``argv`` or ``sys.argv[1:]``."""
|
|
18
|
+
from codingest.codingest import _run_mcp_server
|
|
19
|
+
|
|
20
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
21
|
+
# In a wheel console script, current_exe() is Python rather than the Rust
|
|
22
|
+
# server. Give KGlite's selftest the exact command needed to re-enter this
|
|
23
|
+
# module with the same interpreter and environment.
|
|
24
|
+
os.environ["KGLITE_MCP_RESPAWN"] = json.dumps(
|
|
25
|
+
[sys.executable, "-m", "codingest.mcp_server"]
|
|
26
|
+
)
|
|
27
|
+
try:
|
|
28
|
+
_run_mcp_server(args)
|
|
29
|
+
except KeyboardInterrupt:
|
|
30
|
+
return 130
|
|
31
|
+
except RuntimeError as exc:
|
|
32
|
+
print(f"codingest-mcp: {exc}", file=sys.stderr)
|
|
33
|
+
return 1
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
raise SystemExit(main())
|
|
@@ -19,6 +19,12 @@ pub struct CallEdge {
|
|
|
19
19
|
/// Comma-separated sorted unique line numbers.
|
|
20
20
|
pub call_lines: String,
|
|
21
21
|
pub call_count: i64,
|
|
22
|
+
/// Optional parser-preserved transfer spelling/mechanism. Empty for the
|
|
23
|
+
/// ordinary language call resolver, populated by typed semantic sites.
|
|
24
|
+
pub raw_targets: Option<String>,
|
|
25
|
+
pub offsets: Option<String>,
|
|
26
|
+
pub via: Option<String>,
|
|
27
|
+
pub address_lines: Option<String>,
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/// Aggregate counters describing how the resolver classified every call
|
|
@@ -48,6 +54,18 @@ pub struct CallResolutionStats {
|
|
|
48
54
|
pub resolved_via_inheritance: u64,
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
impl CallResolutionStats {
|
|
58
|
+
pub(crate) fn merge(&mut self, other: Self) {
|
|
59
|
+
self.total_calls += other.total_calls;
|
|
60
|
+
self.excluded_noise += other.excluded_noise;
|
|
61
|
+
self.no_candidate += other.no_candidate;
|
|
62
|
+
self.ambiguous_dropped += other.ambiguous_dropped;
|
|
63
|
+
self.resolved_call_sites += other.resolved_call_sites;
|
|
64
|
+
self.resolved_edges += other.resolved_edges;
|
|
65
|
+
self.resolved_via_inheritance += other.resolved_via_inheritance;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
/// Per-function scratch counters, summed into [`CallResolutionStats`] after
|
|
52
70
|
/// the parallel match loop. Kept `Copy` so the rayon reduce stays alloc-free.
|
|
53
71
|
#[derive(Debug, Clone, Copy, Default)]
|
|
@@ -495,6 +513,10 @@ pub fn build_call_edges(
|
|
|
495
513
|
callee: callee.to_string(),
|
|
496
514
|
call_lines,
|
|
497
515
|
call_count: count,
|
|
516
|
+
raw_targets: None,
|
|
517
|
+
offsets: None,
|
|
518
|
+
via: None,
|
|
519
|
+
address_lines: None,
|
|
498
520
|
}
|
|
499
521
|
})
|
|
500
522
|
.collect();
|
|
@@ -88,11 +88,100 @@ pub(super) fn call_edges_df(edges: &[super::super::call_edges::CallEdge]) -> Dat
|
|
|
88
88
|
let callee: Vec<Option<String>> = edges.iter().map(|e| Some(e.callee.clone())).collect();
|
|
89
89
|
let lines: Vec<Option<String>> = edges.iter().map(|e| Some(e.call_lines.clone())).collect();
|
|
90
90
|
let count: Vec<Option<i64>> = edges.iter().map(|e| Some(e.call_count)).collect();
|
|
91
|
-
|
|
91
|
+
let mut columns = vec![
|
|
92
92
|
("caller", ColumnType::String, str_col(caller)),
|
|
93
93
|
("callee", ColumnType::String, str_col(callee)),
|
|
94
94
|
("call_lines", ColumnType::String, str_col(lines)),
|
|
95
95
|
("call_count", ColumnType::Int64, int_col(count)),
|
|
96
|
+
];
|
|
97
|
+
if edges.iter().any(|edge| edge.raw_targets.is_some()) {
|
|
98
|
+
columns.push((
|
|
99
|
+
"raw_targets",
|
|
100
|
+
ColumnType::String,
|
|
101
|
+
str_col(edges.iter().map(|edge| edge.raw_targets.clone()).collect()),
|
|
102
|
+
));
|
|
103
|
+
}
|
|
104
|
+
if edges.iter().any(|edge| edge.offsets.is_some()) {
|
|
105
|
+
columns.push((
|
|
106
|
+
"offsets",
|
|
107
|
+
ColumnType::String,
|
|
108
|
+
str_col(edges.iter().map(|edge| edge.offsets.clone()).collect()),
|
|
109
|
+
));
|
|
110
|
+
}
|
|
111
|
+
if edges.iter().any(|edge| edge.via.is_some()) {
|
|
112
|
+
columns.push((
|
|
113
|
+
"via",
|
|
114
|
+
ColumnType::String,
|
|
115
|
+
str_col(edges.iter().map(|edge| edge.via.clone()).collect()),
|
|
116
|
+
));
|
|
117
|
+
}
|
|
118
|
+
if edges.iter().any(|edge| edge.address_lines.is_some()) {
|
|
119
|
+
columns.push((
|
|
120
|
+
"address_lines",
|
|
121
|
+
ColumnType::String,
|
|
122
|
+
str_col(
|
|
123
|
+
edges
|
|
124
|
+
.iter()
|
|
125
|
+
.map(|edge| edge.address_lines.clone())
|
|
126
|
+
.collect(),
|
|
127
|
+
),
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
build_df(columns)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pub(super) fn control_edges_df(edges: &[super::super::semantic_edges::ControlEdge]) -> DataFrame {
|
|
134
|
+
build_df(vec![
|
|
135
|
+
(
|
|
136
|
+
"caller",
|
|
137
|
+
ColumnType::String,
|
|
138
|
+
str_col(edges.iter().map(|edge| Some(edge.caller.clone())).collect()),
|
|
139
|
+
),
|
|
140
|
+
(
|
|
141
|
+
"target",
|
|
142
|
+
ColumnType::String,
|
|
143
|
+
str_col(edges.iter().map(|edge| Some(edge.target.clone())).collect()),
|
|
144
|
+
),
|
|
145
|
+
(
|
|
146
|
+
"transfer_lines",
|
|
147
|
+
ColumnType::String,
|
|
148
|
+
str_col(
|
|
149
|
+
edges
|
|
150
|
+
.iter()
|
|
151
|
+
.map(|edge| Some(edge.transfer_lines.clone()))
|
|
152
|
+
.collect(),
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
(
|
|
156
|
+
"transfer_count",
|
|
157
|
+
ColumnType::Int64,
|
|
158
|
+
int_col(edges.iter().map(|edge| Some(edge.transfer_count)).collect()),
|
|
159
|
+
),
|
|
160
|
+
(
|
|
161
|
+
"raw_targets",
|
|
162
|
+
ColumnType::String,
|
|
163
|
+
str_col(edges.iter().map(|edge| edge.raw_targets.clone()).collect()),
|
|
164
|
+
),
|
|
165
|
+
(
|
|
166
|
+
"offsets",
|
|
167
|
+
ColumnType::String,
|
|
168
|
+
str_col(edges.iter().map(|edge| edge.offsets.clone()).collect()),
|
|
169
|
+
),
|
|
170
|
+
(
|
|
171
|
+
"via",
|
|
172
|
+
ColumnType::String,
|
|
173
|
+
str_col(edges.iter().map(|edge| edge.via.clone()).collect()),
|
|
174
|
+
),
|
|
175
|
+
(
|
|
176
|
+
"address_lines",
|
|
177
|
+
ColumnType::String,
|
|
178
|
+
str_col(
|
|
179
|
+
edges
|
|
180
|
+
.iter()
|
|
181
|
+
.map(|edge| edge.address_lines.clone())
|
|
182
|
+
.collect(),
|
|
183
|
+
),
|
|
184
|
+
),
|
|
96
185
|
])
|
|
97
186
|
}
|
|
98
187
|
|
|
@@ -144,10 +233,95 @@ pub(super) fn references_edges_df(
|
|
|
144
233
|
let fns: Vec<Option<String>> = edges.iter().map(|e| Some(e.function.clone())).collect();
|
|
145
234
|
let consts: Vec<Option<String>> = edges.iter().map(|e| Some(e.constant.clone())).collect();
|
|
146
235
|
let lines: Vec<Option<i64>> = edges.iter().map(|e| Some(e.line as i64)).collect();
|
|
147
|
-
|
|
236
|
+
let mut columns = vec![
|
|
148
237
|
("function", ColumnType::String, str_col(fns)),
|
|
149
238
|
("constant", ColumnType::String, str_col(consts)),
|
|
150
239
|
("line", ColumnType::Int64, int_col(lines)),
|
|
240
|
+
];
|
|
241
|
+
if edges.iter().any(|edge| edge.reference_lines.is_some()) {
|
|
242
|
+
columns.push((
|
|
243
|
+
"reference_lines",
|
|
244
|
+
ColumnType::String,
|
|
245
|
+
str_col(
|
|
246
|
+
edges
|
|
247
|
+
.iter()
|
|
248
|
+
.map(|edge| edge.reference_lines.clone())
|
|
249
|
+
.collect(),
|
|
250
|
+
),
|
|
251
|
+
));
|
|
252
|
+
}
|
|
253
|
+
if edges.iter().any(|edge| edge.reference_count.is_some()) {
|
|
254
|
+
columns.push((
|
|
255
|
+
"reference_count",
|
|
256
|
+
ColumnType::Int64,
|
|
257
|
+
int_col(edges.iter().map(|edge| edge.reference_count).collect()),
|
|
258
|
+
));
|
|
259
|
+
}
|
|
260
|
+
if edges.iter().any(|edge| edge.opcodes.is_some()) {
|
|
261
|
+
columns.push((
|
|
262
|
+
"opcodes",
|
|
263
|
+
ColumnType::String,
|
|
264
|
+
str_col(edges.iter().map(|edge| edge.opcodes.clone()).collect()),
|
|
265
|
+
));
|
|
266
|
+
}
|
|
267
|
+
if edges.iter().any(|edge| edge.accesses.is_some()) {
|
|
268
|
+
columns.push((
|
|
269
|
+
"accesses",
|
|
270
|
+
ColumnType::String,
|
|
271
|
+
str_col(edges.iter().map(|edge| edge.accesses.clone()).collect()),
|
|
272
|
+
));
|
|
273
|
+
}
|
|
274
|
+
if edges.iter().any(|edge| edge.has_read.is_some()) {
|
|
275
|
+
columns.push((
|
|
276
|
+
"has_read",
|
|
277
|
+
ColumnType::Boolean,
|
|
278
|
+
bool_col(edges.iter().map(|edge| edge.has_read).collect()),
|
|
279
|
+
));
|
|
280
|
+
}
|
|
281
|
+
if edges.iter().any(|edge| edge.has_write.is_some()) {
|
|
282
|
+
columns.push((
|
|
283
|
+
"has_write",
|
|
284
|
+
ColumnType::Boolean,
|
|
285
|
+
bool_col(edges.iter().map(|edge| edge.has_write).collect()),
|
|
286
|
+
));
|
|
287
|
+
}
|
|
288
|
+
if edges.iter().any(|edge| edge.has_address.is_some()) {
|
|
289
|
+
columns.push((
|
|
290
|
+
"has_address",
|
|
291
|
+
ColumnType::Boolean,
|
|
292
|
+
bool_col(edges.iter().map(|edge| edge.has_address).collect()),
|
|
293
|
+
));
|
|
294
|
+
}
|
|
295
|
+
build_df(columns)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
pub(super) fn symbol_edges_df(edges: &[super::super::semantic_edges::SymbolEdge]) -> DataFrame {
|
|
299
|
+
build_df(vec![
|
|
300
|
+
(
|
|
301
|
+
"source",
|
|
302
|
+
ColumnType::String,
|
|
303
|
+
str_col(edges.iter().map(|edge| Some(edge.source.clone())).collect()),
|
|
304
|
+
),
|
|
305
|
+
(
|
|
306
|
+
"target",
|
|
307
|
+
ColumnType::String,
|
|
308
|
+
str_col(edges.iter().map(|edge| Some(edge.target.clone())).collect()),
|
|
309
|
+
),
|
|
310
|
+
(
|
|
311
|
+
"line",
|
|
312
|
+
ColumnType::Int64,
|
|
313
|
+
int_col(edges.iter().map(|edge| Some(edge.line as i64)).collect()),
|
|
314
|
+
),
|
|
315
|
+
(
|
|
316
|
+
"raw_target",
|
|
317
|
+
ColumnType::String,
|
|
318
|
+
str_col(
|
|
319
|
+
edges
|
|
320
|
+
.iter()
|
|
321
|
+
.map(|edge| Some(edge.raw_target.clone()))
|
|
322
|
+
.collect(),
|
|
323
|
+
),
|
|
324
|
+
),
|
|
151
325
|
])
|
|
152
326
|
}
|
|
153
327
|
|
|
@@ -169,7 +169,7 @@ pub(super) fn functions_df(
|
|
|
169
169
|
file_is_test: &HashMap<&str, bool>,
|
|
170
170
|
file_to_module: &HashMap<&str, &str>,
|
|
171
171
|
) -> DataFrame {
|
|
172
|
-
|
|
172
|
+
let mut columns = vec![
|
|
173
173
|
(
|
|
174
174
|
"qualified_name",
|
|
175
175
|
ColumnType::String,
|
|
@@ -462,7 +462,33 @@ pub(super) fn functions_df(
|
|
|
462
462
|
.collect(),
|
|
463
463
|
),
|
|
464
464
|
),
|
|
465
|
-
]
|
|
465
|
+
];
|
|
466
|
+
for key in ["symbol_kind", "role_hint"] {
|
|
467
|
+
if fns.iter().any(|function| {
|
|
468
|
+
function
|
|
469
|
+
.metadata
|
|
470
|
+
.get(key)
|
|
471
|
+
.and_then(|value| value.as_str())
|
|
472
|
+
.is_some()
|
|
473
|
+
}) {
|
|
474
|
+
columns.push((
|
|
475
|
+
key,
|
|
476
|
+
ColumnType::String,
|
|
477
|
+
str_col(
|
|
478
|
+
fns.iter()
|
|
479
|
+
.map(|function| {
|
|
480
|
+
function
|
|
481
|
+
.metadata
|
|
482
|
+
.get(key)
|
|
483
|
+
.and_then(|value| value.as_str())
|
|
484
|
+
.map(str::to_string)
|
|
485
|
+
})
|
|
486
|
+
.collect(),
|
|
487
|
+
),
|
|
488
|
+
));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
build_df(columns)
|
|
466
492
|
}
|
|
467
493
|
|
|
468
494
|
pub(super) fn classes_df(
|
|
@@ -814,7 +840,7 @@ pub(super) fn constants_df(
|
|
|
814
840
|
consts: &[ConstantInfo],
|
|
815
841
|
file_to_module: &HashMap<&str, &str>,
|
|
816
842
|
) -> DataFrame {
|
|
817
|
-
|
|
843
|
+
let mut columns = vec![
|
|
818
844
|
(
|
|
819
845
|
"qualified_name",
|
|
820
846
|
ColumnType::String,
|
|
@@ -875,7 +901,35 @@ pub(super) fn constants_df(
|
|
|
875
901
|
ColumnType::Int64,
|
|
876
902
|
int_col(consts.iter().map(|c| Some(c.line_number as i64)).collect()),
|
|
877
903
|
),
|
|
878
|
-
]
|
|
904
|
+
];
|
|
905
|
+
if consts.iter().any(|constant| constant.kind == "agc_erase") {
|
|
906
|
+
columns.push((
|
|
907
|
+
"is_mutable",
|
|
908
|
+
ColumnType::Boolean,
|
|
909
|
+
bool_col(
|
|
910
|
+
consts
|
|
911
|
+
.iter()
|
|
912
|
+
.map(|constant| {
|
|
913
|
+
constant
|
|
914
|
+
.kind
|
|
915
|
+
.starts_with("agc_")
|
|
916
|
+
.then_some(constant.kind == "agc_erase")
|
|
917
|
+
})
|
|
918
|
+
.collect(),
|
|
919
|
+
),
|
|
920
|
+
));
|
|
921
|
+
columns.push((
|
|
922
|
+
"storage",
|
|
923
|
+
ColumnType::String,
|
|
924
|
+
str_col(
|
|
925
|
+
consts
|
|
926
|
+
.iter()
|
|
927
|
+
.map(|constant| (constant.kind == "agc_erase").then(|| "erasable".to_string()))
|
|
928
|
+
.collect(),
|
|
929
|
+
),
|
|
930
|
+
));
|
|
931
|
+
}
|
|
932
|
+
build_df(columns)
|
|
879
933
|
}
|
|
880
934
|
|
|
881
935
|
pub(super) fn elements_df(elements: &[crate::models::ElementInfo]) -> DataFrame {
|
|
@@ -120,6 +120,7 @@ pub fn load_into_graph(
|
|
|
120
120
|
run_stage!(type_relationship_edges, "implements+extends+has_method");
|
|
121
121
|
run_stage!(uses_type_edges, "uses_type");
|
|
122
122
|
run_stage!(reference_edges, "references");
|
|
123
|
+
run_stage!(symbol_relationship_edges, "alias_of+points_to");
|
|
123
124
|
run_stage!(function_reference_edges, "references_fn");
|
|
124
125
|
run_stage!(decorator_edges, "decorates");
|
|
125
126
|
run_stage!(ffi_edges, "ffi_exposes");
|
|
@@ -826,13 +827,19 @@ impl<'a> LoadPipeline<'a> {
|
|
|
826
827
|
let graph = &mut self.graph;
|
|
827
828
|
// Function CALLS Function (5-tier resolution).
|
|
828
829
|
let noise = super::call_edges::noise_names_for_files(&result.files);
|
|
829
|
-
let (call_edges, call_stats) = super::call_edges::build_call_edges(
|
|
830
|
+
let (mut call_edges, mut call_stats) = super::call_edges::build_call_edges(
|
|
830
831
|
&result.functions,
|
|
831
832
|
&result.files,
|
|
832
833
|
&noise,
|
|
833
834
|
5,
|
|
834
835
|
&result.type_relationships,
|
|
835
836
|
);
|
|
837
|
+
let semantic = super::semantic_edges::build_control_edges(
|
|
838
|
+
&result.control_transfers,
|
|
839
|
+
&result.functions,
|
|
840
|
+
);
|
|
841
|
+
call_edges.extend(semantic.calls);
|
|
842
|
+
call_stats.merge(semantic.call_stats);
|
|
836
843
|
if !call_edges.is_empty() {
|
|
837
844
|
maintain::add_connections(
|
|
838
845
|
graph,
|
|
@@ -848,6 +855,36 @@ impl<'a> LoadPipeline<'a> {
|
|
|
848
855
|
)
|
|
849
856
|
.map_err(py_err)?;
|
|
850
857
|
}
|
|
858
|
+
if !semantic.jumps.is_empty() {
|
|
859
|
+
maintain::add_connections(
|
|
860
|
+
graph,
|
|
861
|
+
control_edges_df(&semantic.jumps),
|
|
862
|
+
"JUMPS_TO".into(),
|
|
863
|
+
"Function".into(),
|
|
864
|
+
"caller".into(),
|
|
865
|
+
"Function".into(),
|
|
866
|
+
"target".into(),
|
|
867
|
+
None,
|
|
868
|
+
None,
|
|
869
|
+
None,
|
|
870
|
+
)
|
|
871
|
+
.map_err(py_err)?;
|
|
872
|
+
}
|
|
873
|
+
if !semantic.branches.is_empty() {
|
|
874
|
+
maintain::add_connections(
|
|
875
|
+
graph,
|
|
876
|
+
control_edges_df(&semantic.branches),
|
|
877
|
+
"BRANCHES_TO".into(),
|
|
878
|
+
"Function".into(),
|
|
879
|
+
"caller".into(),
|
|
880
|
+
"Function".into(),
|
|
881
|
+
"target".into(),
|
|
882
|
+
None,
|
|
883
|
+
None,
|
|
884
|
+
None,
|
|
885
|
+
)
|
|
886
|
+
.map_err(py_err)?;
|
|
887
|
+
}
|
|
851
888
|
|
|
852
889
|
self.call_stats = call_stats;
|
|
853
890
|
Ok(())
|
|
@@ -1099,7 +1136,13 @@ impl<'a> LoadPipeline<'a> {
|
|
|
1099
1136
|
let result = self.result;
|
|
1100
1137
|
let graph = &mut self.graph;
|
|
1101
1138
|
// REFERENCES (Function → Constant) — name-keyed identifier resolution.
|
|
1102
|
-
let refs =
|
|
1139
|
+
let mut refs =
|
|
1140
|
+
super::other_edges::build_references_edges(&result.functions, &result.constants);
|
|
1141
|
+
refs.extend(super::semantic_edges::build_reference_site_edges(
|
|
1142
|
+
&result.reference_sites,
|
|
1143
|
+
&result.functions,
|
|
1144
|
+
&result.constants,
|
|
1145
|
+
));
|
|
1103
1146
|
if !refs.is_empty() {
|
|
1104
1147
|
maintain::add_connections(
|
|
1105
1148
|
graph,
|
|
@@ -1119,6 +1162,32 @@ impl<'a> LoadPipeline<'a> {
|
|
|
1119
1162
|
Ok(())
|
|
1120
1163
|
}
|
|
1121
1164
|
|
|
1165
|
+
fn symbol_relationship_edges(&mut self) -> Result<(), String> {
|
|
1166
|
+
let result = self.result;
|
|
1167
|
+
let graph = &mut self.graph;
|
|
1168
|
+
let batches = super::semantic_edges::build_symbol_edges(
|
|
1169
|
+
&result.symbol_relationships,
|
|
1170
|
+
&result.functions,
|
|
1171
|
+
&result.constants,
|
|
1172
|
+
);
|
|
1173
|
+
for batch in batches {
|
|
1174
|
+
maintain::add_connections(
|
|
1175
|
+
graph,
|
|
1176
|
+
symbol_edges_df(&batch.edges),
|
|
1177
|
+
batch.relationship.into(),
|
|
1178
|
+
"Constant".into(),
|
|
1179
|
+
"source".into(),
|
|
1180
|
+
batch.target_node_type.into(),
|
|
1181
|
+
"target".into(),
|
|
1182
|
+
None,
|
|
1183
|
+
None,
|
|
1184
|
+
None,
|
|
1185
|
+
)
|
|
1186
|
+
.map_err(py_err)?;
|
|
1187
|
+
}
|
|
1188
|
+
Ok(())
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1122
1191
|
fn function_reference_edges(&mut self) -> Result<(), String> {
|
|
1123
1192
|
let result = self.result;
|
|
1124
1193
|
let graph = &mut self.graph;
|
|
@@ -4,6 +4,7 @@ pub mod call_edges;
|
|
|
4
4
|
pub mod load;
|
|
5
5
|
pub mod other_edges;
|
|
6
6
|
pub mod routes;
|
|
7
|
+
pub mod semantic_edges;
|
|
7
8
|
pub mod type_edges;
|
|
8
9
|
|
|
9
10
|
use crate::models::{ParseResult, ProjectInfo, SourceRoot};
|
|
@@ -257,6 +258,39 @@ fn retain_non_test_entities(result: &mut ParseResult) {
|
|
|
257
258
|
.selectors
|
|
258
259
|
.retain(|item| !test_paths.contains(&item.file_path));
|
|
259
260
|
|
|
261
|
+
let retained_functions: HashSet<&str> = result
|
|
262
|
+
.functions
|
|
263
|
+
.iter()
|
|
264
|
+
.map(|item| item.qualified_name.as_str())
|
|
265
|
+
.collect();
|
|
266
|
+
let retained_constants: HashSet<&str> = result
|
|
267
|
+
.constants
|
|
268
|
+
.iter()
|
|
269
|
+
.map(|item| item.qualified_name.as_str())
|
|
270
|
+
.collect();
|
|
271
|
+
result.control_transfers.retain(|site| {
|
|
272
|
+
retained_functions.contains(site.caller.as_str())
|
|
273
|
+
&& site
|
|
274
|
+
.target
|
|
275
|
+
.as_deref()
|
|
276
|
+
.is_none_or(|target| retained_functions.contains(target))
|
|
277
|
+
});
|
|
278
|
+
result.reference_sites.retain(|site| {
|
|
279
|
+
retained_functions.contains(site.caller.as_str())
|
|
280
|
+
&& retained_constants.contains(site.target.as_str())
|
|
281
|
+
});
|
|
282
|
+
result.symbol_relationships.retain(|relationship| {
|
|
283
|
+
retained_constants.contains(relationship.source.as_str())
|
|
284
|
+
&& match relationship.target_kind {
|
|
285
|
+
crate::models::SymbolTargetKind::Constant => {
|
|
286
|
+
retained_constants.contains(relationship.target.as_str())
|
|
287
|
+
}
|
|
288
|
+
crate::models::SymbolTargetKind::Function => {
|
|
289
|
+
retained_functions.contains(relationship.target.as_str())
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
260
294
|
let retained_types: HashSet<&str> = result
|
|
261
295
|
.classes
|
|
262
296
|
.iter()
|
|
@@ -524,6 +558,12 @@ fn finalize_and_load(
|
|
|
524
558
|
dedup_by_key(&mut combined.enums, |e| e.qualified_name.clone());
|
|
525
559
|
dedup_by_key(&mut combined.interfaces, |i| i.qualified_name.clone());
|
|
526
560
|
dedup_by_key(&mut combined.constants, |c| c.qualified_name.clone());
|
|
561
|
+
combined.control_transfers.sort();
|
|
562
|
+
combined.control_transfers.dedup();
|
|
563
|
+
combined.reference_sites.sort();
|
|
564
|
+
combined.reference_sites.dedup();
|
|
565
|
+
combined.symbol_relationships.sort();
|
|
566
|
+
combined.symbol_relationships.dedup();
|
|
527
567
|
if verbose {
|
|
528
568
|
eprintln!("[timing] dedup: {:.3}s", t_dedup.elapsed().as_secs_f64());
|
|
529
569
|
}
|
|
@@ -615,6 +655,12 @@ pub fn run(src_dir: &Path, verbose: bool) -> Result<Arc<DirGraph>, String> {
|
|
|
615
655
|
dedup_by_key(&mut combined.enums, |e| e.qualified_name.clone());
|
|
616
656
|
dedup_by_key(&mut combined.interfaces, |i| i.qualified_name.clone());
|
|
617
657
|
dedup_by_key(&mut combined.constants, |c| c.qualified_name.clone());
|
|
658
|
+
combined.control_transfers.sort();
|
|
659
|
+
combined.control_transfers.dedup();
|
|
660
|
+
combined.reference_sites.sort();
|
|
661
|
+
combined.reference_sites.dedup();
|
|
662
|
+
combined.symbol_relationships.sort();
|
|
663
|
+
combined.symbol_relationships.dedup();
|
|
618
664
|
|
|
619
665
|
if verbose {
|
|
620
666
|
eprintln!(
|