devin-graph 0.1.0__tar.gz → 0.1.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.
- {devin_graph-0.1.0/src/devin_graph.egg-info → devin_graph-0.1.2}/PKG-INFO +66 -24
- {devin_graph-0.1.0 → devin_graph-0.1.2}/README.md +62 -23
- {devin_graph-0.1.0 → devin_graph-0.1.2}/pyproject.toml +4 -2
- devin_graph-0.1.2/src/devin_graph/__init__.py +1 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/cli.py +81 -6
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/extract.py +3 -2
- devin_graph-0.1.2/src/devin_graph/mcp_server.py +134 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/query.py +100 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/store.py +35 -11
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/view.py +0 -2
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/vscdb.py +1 -1
- {devin_graph-0.1.0 → devin_graph-0.1.2/src/devin_graph.egg-info}/PKG-INFO +66 -24
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph.egg-info/SOURCES.txt +4 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph.egg-info/entry_points.txt +1 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph.egg-info/requires.txt +4 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_cli.py +7 -6
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_extract.py +3 -6
- devin_graph-0.1.2/tests/test_mcp.py +198 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_offline_core.py +4 -2
- devin_graph-0.1.2/tests/test_orphans_blame.py +160 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_query.py +2 -4
- devin_graph-0.1.2/tests/test_skill.py +47 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_store.py +6 -5
- devin_graph-0.1.0/src/devin_graph/__init__.py +0 -1
- {devin_graph-0.1.0 → devin_graph-0.1.2}/LICENSE +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/setup.cfg +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/identity.py +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph/paths.py +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph.egg-info/dependency_links.txt +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/src/devin_graph.egg-info/top_level.txt +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_paths.py +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_smoke.py +0 -0
- {devin_graph-0.1.0 → devin_graph-0.1.2}/tests/test_vscdb.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devin-graph
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Knowledge graph over Devin sessions — projects, files, tools and decisions as queryable nodes.
|
|
5
5
|
Author: Icaro0310
|
|
6
6
|
License: MIT
|
|
@@ -10,32 +10,34 @@ License-File: LICENSE
|
|
|
10
10
|
Requires-Dist: devin-internals-spec<0.4.0,>=0.3.0
|
|
11
11
|
Provides-Extra: dev
|
|
12
12
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
13
|
+
Requires-Dist: mcp>=1.2; extra == "dev"
|
|
14
|
+
Provides-Extra: mcp
|
|
15
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
13
16
|
Dynamic: license-file
|
|
14
17
|
|
|
15
18
|
<div align="center">
|
|
16
19
|
|
|
17
20
|
<img src="assets/banner.svg" alt="devin-graph" width="100%"/>
|
|
18
21
|
|
|
19
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
22
|
+
<a href="https://github.com/Icaro0310/devin-explore/actions/workflows/test-graph.yml"><img src="https://github.com/Icaro0310/devin-explore/actions/workflows/test-graph.yml/badge.svg" alt="ci"/></a>
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
<a href="https://scorecard.dev/viewer/?uri=github.com/Icaro0310/devin-graph"><img src="https://api.scorecard.dev/projects/github.com/Icaro0310/devin-graph/badge" alt="OpenSSF Scorecard"/></a>
|
|
24
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/Icaro0310/devin-explore"><img src="https://api.scorecard.dev/projects/github.com/Icaro0310/devin-explore/badge" alt="OpenSSF Scorecard"/></a>
|
|
23
25
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT"/></a>
|
|
24
26
|
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python 3.10+"/></a>
|
|
25
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
26
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
27
|
+
<a href="https://github.com/Icaro0310/devin-explore"><img src="https://img.shields.io/github/stars/Icaro0310/devin-explore" alt="GitHub stars"/></a>
|
|
28
|
+
<a href="https://github.com/Icaro0310/devin-explore/commits/main"><img src="https://img.shields.io/github/last-commit/Icaro0310/devin-explore" alt="Last commit"/></a>
|
|
27
29
|
<a href="https://github.com/Icaro0310/awesome-devin"><img src="https://img.shields.io/badge/part%20of-devin--*-ecosystem-7c3aed" alt="devin-* ecosystem"/></a>
|
|
28
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
30
|
+
<a href="https://github.com/Icaro0310/devin-explore/issues"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen" alt="PRs welcome"/></a>
|
|
29
31
|
</div>
|
|
30
32
|
|
|
31
33
|
<!-- DEVIN-ECO:BEGIN -->
|
|
32
34
|
> **Part of the [DEVIN ecosystem](https://github.com/Icaro0310/awesome-devin)**
|
|
33
35
|
> Track: Understand · Nature: product
|
|
34
|
-
> For: Developers, AI engineers
|
|
35
|
-
> Interface: CLI / Python library
|
|
36
|
+
> For: Developers, AI engineers, Data Scientists
|
|
37
|
+
> Interface: CLI / Python library
|
|
38
|
+
> Path: Data Scientists · step 1/4 — before `devin-search`
|
|
36
39
|
<!-- DEVIN-ECO:END -->
|
|
37
40
|
|
|
38
|
-
|
|
39
41
|
# devin-graph
|
|
40
42
|
|
|
41
43
|
> **Unofficial community project.** Not affiliated with, endorsed by, or
|
|
@@ -79,17 +81,6 @@ build. Schema drift is gated by `devin-internals-spec`'s version detector.
|
|
|
79
81
|
|
|
80
82
|
Requires Python ≥ 3.10 and `pipx` or `uv`. Per-OS setup lives in the platform guides: [Linux](README.linux.md) · [Personal Windows](README.windows.md) · [Corporate Windows](README.corporate-windows.md).
|
|
81
83
|
|
|
82
|
-
<!-- DIST-STATUS:BEGIN — generated from devin-powerups/registry.json -->
|
|
83
|
-
> **Source-only distribution.** This tool is not yet published to PyPI.
|
|
84
|
-
> Install from source:
|
|
85
|
-
>
|
|
86
|
-
> ```bash
|
|
87
|
-
> pipx install git+https://github.com/Icaro0310/devin-graph.git
|
|
88
|
-
> # or
|
|
89
|
-
> uv tool install git+https://github.com/Icaro0310/devin-graph.git
|
|
90
|
-
> ```
|
|
91
|
-
<!-- DIST-STATUS:END -->
|
|
92
|
-
|
|
93
84
|
## Usage
|
|
94
85
|
|
|
95
86
|
```bash
|
|
@@ -106,6 +97,10 @@ devin-graph query projects-graph --graph graph.db --json
|
|
|
106
97
|
|
|
107
98
|
# D3-friendly dump: {"meta", "nodes", "edges"}
|
|
108
99
|
devin-graph export --format json --graph graph.db --out graph.json
|
|
100
|
+
|
|
101
|
+
# diagnostics: edge-less nodes (report-only) and per-session blame
|
|
102
|
+
devin-graph orphans --graph graph.db
|
|
103
|
+
devin-graph blame-session <id-or-prefix> --graph graph.db
|
|
109
104
|
```
|
|
110
105
|
|
|
111
106
|
Matching is forgiving (`src/app.py` finds `/repo/alpha/src/app.py`);
|
|
@@ -114,6 +109,15 @@ by two or more distinct projects, with the project and session lists —
|
|
|
114
109
|
exportable as JSON like the other queries. The source DB is opened
|
|
115
110
|
`mode=ro` and never written — tests assert its hash is unchanged.
|
|
116
111
|
|
|
112
|
+
`orphans` lists nodes with no incident edges (id, kind, owner, a
|
|
113
|
+
`prunable` flag marking the subset the next `build` would drop —
|
|
114
|
+
shared, ownerless nodes; owned orphans persist with their owner). It is
|
|
115
|
+
report-only and never deletes. `blame-session <id>` shows what a
|
|
116
|
+
session produced and touched: its owned nodes and edges plus the
|
|
117
|
+
resolved files, tools, projects and commits reachable through them,
|
|
118
|
+
with the extraction-ledger timestamp. `<id>` accepts a unique prefix;
|
|
119
|
+
a missing or ambiguous match exits 2 and lists candidates.
|
|
120
|
+
|
|
117
121
|
## Works with Devin alone (Devin-only mode)
|
|
118
122
|
|
|
119
123
|
devin-graph builds `graph.db` locally from Devin's session stores — the whole
|
|
@@ -129,13 +133,10 @@ Windows and `$XDG_DATA_HOME/devin/cli/sessions.db` on Linux (default
|
|
|
129
133
|
`~/.local/share/devin/cli/sessions.db`). A legacy `~/.config/devin` location
|
|
130
134
|
is also checked. Pass `--sessions-db` to override.
|
|
131
135
|
|
|
132
|
-
|
|
133
136
|
`commit` nodes + `produced`/`referenced` edges attribute sessions to git SHAs seen in tool calls (exact when the SHA appears in a `git commit`/`git push` call, `seen` otherwise). `devin-graph sql "SELECT ..."` runs read-only SQL over graph.db (SELECT/WITH only, `query_only` pragma).
|
|
134
137
|
|
|
135
|
-
|
|
136
138
|
`--vscdb` (auto-detected on build) adds GUI coverage: `gui_session` nodes keyed by the generated session slug, `gui_workspace` edges to their workspace project, enriched with `lastAccessed` when a `resourceToSpace` editor URI links a space to the slug. Best-effort: only the observed `windsurfSpace.*` keys are read; unknown/malformed keys are skipped. Pass `--vscdb none` to disable.
|
|
137
139
|
|
|
138
|
-
|
|
139
140
|
`devin-graph view --out file.html` renders the graph as a self-contained HTML page (embedded JSON + vanilla-JS force layout — zero CDN, works fully offline). `--limit` caps nodes (highest-degree kept, flagged TRUNCATED in the header).
|
|
140
141
|
|
|
141
142
|
## Limitations
|
|
@@ -165,6 +166,47 @@ DDL, synthetic rows) — no binary fixtures are committed. See
|
|
|
165
166
|
[docs/SPEC.md](docs/SPEC.md) for the graph model and
|
|
166
167
|
[STATUS.md](STATUS.md) for the roadmap.
|
|
167
168
|
|
|
169
|
+
## Adapters (MCP server, Devin skill, plugin)
|
|
170
|
+
|
|
171
|
+
`devin_graph.mcp_server` exposes one read-only tool via the
|
|
172
|
+
`devin-graph-mcp` entry point (`pip install 'devin-graph[mcp]'`):
|
|
173
|
+
`graph_query` (same JSON as `devin-graph query <what> --json`).
|
|
174
|
+
|
|
175
|
+
`adapters/` is a self-contained Devin plugin root
|
|
176
|
+
(`adapters/.devin-plugin/plugin.json` +
|
|
177
|
+
`adapters/skills/devin-graph/SKILL.md`), installed with
|
|
178
|
+
`devin plugins install
|
|
179
|
+
Icaro0310/devin-explore#packages/graph/adapters` — it ships in the
|
|
180
|
+
repo, not inside the wheel. The manifest launches the server through
|
|
181
|
+
`uvx --from 'devin-graph[mcp]' devin-graph-mcp`, which resolves the
|
|
182
|
+
published PyPI release — no local install needed. To run the server
|
|
183
|
+
from a source checkout instead, install it editable
|
|
184
|
+
(`pip install -e '.[mcp]'`) and call `devin-graph-mcp` directly, or
|
|
185
|
+
point a local manifest copy at the checkout
|
|
186
|
+
(`uvx --from './packages/graph[mcp]' devin-graph-mcp`).
|
|
187
|
+
|
|
188
|
+
### MCP host configs
|
|
189
|
+
|
|
190
|
+
Same stdio server in other hosts — server name `devin-graph`:
|
|
191
|
+
|
|
192
|
+
**Zed** — `context_servers` in `~/.config/zed/settings.json`:
|
|
193
|
+
|
|
194
|
+
```jsonc
|
|
195
|
+
{ "context_servers": { "devin-graph": { "command": { "path": "devin-graph-mcp", "args": [] } } } }
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**JetBrains AI Assistant** — Settings → Tools → AI Assistant → Model Context Protocol → Add:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{ "mcpServers": { "devin-graph": { "command": "devin-graph-mcp" } } }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Antigravity** — `~/.gemini/config/mcp_config.json`:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{ "mcpServers": { "devin-graph": { "command": "devin-graph-mcp" } } }
|
|
208
|
+
```
|
|
209
|
+
|
|
168
210
|
## When to use this
|
|
169
211
|
|
|
170
212
|
- You need cross-session questions: which sessions touched file X, which
|
|
@@ -2,26 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
<img src="assets/banner.svg" alt="devin-graph" width="100%"/>
|
|
4
4
|
|
|
5
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
5
|
+
<a href="https://github.com/Icaro0310/devin-explore/actions/workflows/test-graph.yml"><img src="https://github.com/Icaro0310/devin-explore/actions/workflows/test-graph.yml/badge.svg" alt="ci"/></a>
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
<a href="https://scorecard.dev/viewer/?uri=github.com/Icaro0310/devin-graph"><img src="https://api.scorecard.dev/projects/github.com/Icaro0310/devin-graph/badge" alt="OpenSSF Scorecard"/></a>
|
|
7
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/Icaro0310/devin-explore"><img src="https://api.scorecard.dev/projects/github.com/Icaro0310/devin-explore/badge" alt="OpenSSF Scorecard"/></a>
|
|
9
8
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT"/></a>
|
|
10
9
|
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python 3.10+"/></a>
|
|
11
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
12
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
10
|
+
<a href="https://github.com/Icaro0310/devin-explore"><img src="https://img.shields.io/github/stars/Icaro0310/devin-explore" alt="GitHub stars"/></a>
|
|
11
|
+
<a href="https://github.com/Icaro0310/devin-explore/commits/main"><img src="https://img.shields.io/github/last-commit/Icaro0310/devin-explore" alt="Last commit"/></a>
|
|
13
12
|
<a href="https://github.com/Icaro0310/awesome-devin"><img src="https://img.shields.io/badge/part%20of-devin--*-ecosystem-7c3aed" alt="devin-* ecosystem"/></a>
|
|
14
|
-
<a href="https://github.com/Icaro0310/devin-
|
|
13
|
+
<a href="https://github.com/Icaro0310/devin-explore/issues"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen" alt="PRs welcome"/></a>
|
|
15
14
|
</div>
|
|
16
15
|
|
|
17
16
|
<!-- DEVIN-ECO:BEGIN -->
|
|
18
17
|
> **Part of the [DEVIN ecosystem](https://github.com/Icaro0310/awesome-devin)**
|
|
19
18
|
> Track: Understand · Nature: product
|
|
20
|
-
> For: Developers, AI engineers
|
|
21
|
-
> Interface: CLI / Python library
|
|
19
|
+
> For: Developers, AI engineers, Data Scientists
|
|
20
|
+
> Interface: CLI / Python library
|
|
21
|
+
> Path: Data Scientists · step 1/4 — before `devin-search`
|
|
22
22
|
<!-- DEVIN-ECO:END -->
|
|
23
23
|
|
|
24
|
-
|
|
25
24
|
# devin-graph
|
|
26
25
|
|
|
27
26
|
> **Unofficial community project.** Not affiliated with, endorsed by, or
|
|
@@ -65,17 +64,6 @@ build. Schema drift is gated by `devin-internals-spec`'s version detector.
|
|
|
65
64
|
|
|
66
65
|
Requires Python ≥ 3.10 and `pipx` or `uv`. Per-OS setup lives in the platform guides: [Linux](README.linux.md) · [Personal Windows](README.windows.md) · [Corporate Windows](README.corporate-windows.md).
|
|
67
66
|
|
|
68
|
-
<!-- DIST-STATUS:BEGIN — generated from devin-powerups/registry.json -->
|
|
69
|
-
> **Source-only distribution.** This tool is not yet published to PyPI.
|
|
70
|
-
> Install from source:
|
|
71
|
-
>
|
|
72
|
-
> ```bash
|
|
73
|
-
> pipx install git+https://github.com/Icaro0310/devin-graph.git
|
|
74
|
-
> # or
|
|
75
|
-
> uv tool install git+https://github.com/Icaro0310/devin-graph.git
|
|
76
|
-
> ```
|
|
77
|
-
<!-- DIST-STATUS:END -->
|
|
78
|
-
|
|
79
67
|
## Usage
|
|
80
68
|
|
|
81
69
|
```bash
|
|
@@ -92,6 +80,10 @@ devin-graph query projects-graph --graph graph.db --json
|
|
|
92
80
|
|
|
93
81
|
# D3-friendly dump: {"meta", "nodes", "edges"}
|
|
94
82
|
devin-graph export --format json --graph graph.db --out graph.json
|
|
83
|
+
|
|
84
|
+
# diagnostics: edge-less nodes (report-only) and per-session blame
|
|
85
|
+
devin-graph orphans --graph graph.db
|
|
86
|
+
devin-graph blame-session <id-or-prefix> --graph graph.db
|
|
95
87
|
```
|
|
96
88
|
|
|
97
89
|
Matching is forgiving (`src/app.py` finds `/repo/alpha/src/app.py`);
|
|
@@ -100,6 +92,15 @@ by two or more distinct projects, with the project and session lists —
|
|
|
100
92
|
exportable as JSON like the other queries. The source DB is opened
|
|
101
93
|
`mode=ro` and never written — tests assert its hash is unchanged.
|
|
102
94
|
|
|
95
|
+
`orphans` lists nodes with no incident edges (id, kind, owner, a
|
|
96
|
+
`prunable` flag marking the subset the next `build` would drop —
|
|
97
|
+
shared, ownerless nodes; owned orphans persist with their owner). It is
|
|
98
|
+
report-only and never deletes. `blame-session <id>` shows what a
|
|
99
|
+
session produced and touched: its owned nodes and edges plus the
|
|
100
|
+
resolved files, tools, projects and commits reachable through them,
|
|
101
|
+
with the extraction-ledger timestamp. `<id>` accepts a unique prefix;
|
|
102
|
+
a missing or ambiguous match exits 2 and lists candidates.
|
|
103
|
+
|
|
103
104
|
## Works with Devin alone (Devin-only mode)
|
|
104
105
|
|
|
105
106
|
devin-graph builds `graph.db` locally from Devin's session stores — the whole
|
|
@@ -115,13 +116,10 @@ Windows and `$XDG_DATA_HOME/devin/cli/sessions.db` on Linux (default
|
|
|
115
116
|
`~/.local/share/devin/cli/sessions.db`). A legacy `~/.config/devin` location
|
|
116
117
|
is also checked. Pass `--sessions-db` to override.
|
|
117
118
|
|
|
118
|
-
|
|
119
119
|
`commit` nodes + `produced`/`referenced` edges attribute sessions to git SHAs seen in tool calls (exact when the SHA appears in a `git commit`/`git push` call, `seen` otherwise). `devin-graph sql "SELECT ..."` runs read-only SQL over graph.db (SELECT/WITH only, `query_only` pragma).
|
|
120
120
|
|
|
121
|
-
|
|
122
121
|
`--vscdb` (auto-detected on build) adds GUI coverage: `gui_session` nodes keyed by the generated session slug, `gui_workspace` edges to their workspace project, enriched with `lastAccessed` when a `resourceToSpace` editor URI links a space to the slug. Best-effort: only the observed `windsurfSpace.*` keys are read; unknown/malformed keys are skipped. Pass `--vscdb none` to disable.
|
|
123
122
|
|
|
124
|
-
|
|
125
123
|
`devin-graph view --out file.html` renders the graph as a self-contained HTML page (embedded JSON + vanilla-JS force layout — zero CDN, works fully offline). `--limit` caps nodes (highest-degree kept, flagged TRUNCATED in the header).
|
|
126
124
|
|
|
127
125
|
## Limitations
|
|
@@ -151,6 +149,47 @@ DDL, synthetic rows) — no binary fixtures are committed. See
|
|
|
151
149
|
[docs/SPEC.md](docs/SPEC.md) for the graph model and
|
|
152
150
|
[STATUS.md](STATUS.md) for the roadmap.
|
|
153
151
|
|
|
152
|
+
## Adapters (MCP server, Devin skill, plugin)
|
|
153
|
+
|
|
154
|
+
`devin_graph.mcp_server` exposes one read-only tool via the
|
|
155
|
+
`devin-graph-mcp` entry point (`pip install 'devin-graph[mcp]'`):
|
|
156
|
+
`graph_query` (same JSON as `devin-graph query <what> --json`).
|
|
157
|
+
|
|
158
|
+
`adapters/` is a self-contained Devin plugin root
|
|
159
|
+
(`adapters/.devin-plugin/plugin.json` +
|
|
160
|
+
`adapters/skills/devin-graph/SKILL.md`), installed with
|
|
161
|
+
`devin plugins install
|
|
162
|
+
Icaro0310/devin-explore#packages/graph/adapters` — it ships in the
|
|
163
|
+
repo, not inside the wheel. The manifest launches the server through
|
|
164
|
+
`uvx --from 'devin-graph[mcp]' devin-graph-mcp`, which resolves the
|
|
165
|
+
published PyPI release — no local install needed. To run the server
|
|
166
|
+
from a source checkout instead, install it editable
|
|
167
|
+
(`pip install -e '.[mcp]'`) and call `devin-graph-mcp` directly, or
|
|
168
|
+
point a local manifest copy at the checkout
|
|
169
|
+
(`uvx --from './packages/graph[mcp]' devin-graph-mcp`).
|
|
170
|
+
|
|
171
|
+
### MCP host configs
|
|
172
|
+
|
|
173
|
+
Same stdio server in other hosts — server name `devin-graph`:
|
|
174
|
+
|
|
175
|
+
**Zed** — `context_servers` in `~/.config/zed/settings.json`:
|
|
176
|
+
|
|
177
|
+
```jsonc
|
|
178
|
+
{ "context_servers": { "devin-graph": { "command": { "path": "devin-graph-mcp", "args": [] } } } }
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**JetBrains AI Assistant** — Settings → Tools → AI Assistant → Model Context Protocol → Add:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{ "mcpServers": { "devin-graph": { "command": "devin-graph-mcp" } } }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Antigravity** — `~/.gemini/config/mcp_config.json`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{ "mcpServers": { "devin-graph": { "command": "devin-graph-mcp" } } }
|
|
191
|
+
```
|
|
192
|
+
|
|
154
193
|
## When to use this
|
|
155
194
|
|
|
156
195
|
- You need cross-session questions: which sessions touched file X, which
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devin-graph"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Knowledge graph over Devin sessions — projects, files, tools and decisions as queryable nodes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -15,10 +15,12 @@ dependencies = [
|
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[project.optional-dependencies]
|
|
18
|
-
dev = ["pytest>=8"]
|
|
18
|
+
dev = ["pytest>=8", "mcp>=1.2"]
|
|
19
|
+
mcp = ["mcp>=1.2"]
|
|
19
20
|
|
|
20
21
|
[project.scripts]
|
|
21
22
|
devin-graph = "devin_graph.cli:main"
|
|
23
|
+
devin-graph-mcp = "devin_graph.mcp_server:main"
|
|
22
24
|
|
|
23
25
|
[tool.setuptools.packages.find]
|
|
24
26
|
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
|
@@ -10,20 +10,19 @@ from pathlib import Path
|
|
|
10
10
|
from devin_internals import SchemaError
|
|
11
11
|
from devin_internals.parsers import SessionsStore
|
|
12
12
|
|
|
13
|
-
from devin_graph.paths import default_state_vscdb
|
|
14
|
-
from devin_graph.vscdb import extract_vscdb
|
|
15
|
-
|
|
16
13
|
from devin_graph import __version__
|
|
17
|
-
from devin_graph.paths import default_sessions_db
|
|
14
|
+
from devin_graph.paths import default_sessions_db, default_state_vscdb
|
|
18
15
|
from devin_graph.query import (
|
|
16
|
+
blame_session,
|
|
17
|
+
orphaned_nodes,
|
|
19
18
|
project_detail,
|
|
20
19
|
projects_graph,
|
|
21
20
|
sessions_for_file,
|
|
22
21
|
sessions_for_tool,
|
|
23
22
|
shared_files,
|
|
24
|
-
tools_for_project,
|
|
25
23
|
)
|
|
26
24
|
from devin_graph.store import GraphStore
|
|
25
|
+
from devin_graph.vscdb import extract_vscdb
|
|
27
26
|
|
|
28
27
|
DEFAULT_GRAPH = "graph.db"
|
|
29
28
|
|
|
@@ -56,7 +55,7 @@ def _open_graph(graph_arg: str | None) -> GraphStore:
|
|
|
56
55
|
file=sys.stderr,
|
|
57
56
|
)
|
|
58
57
|
raise SystemExit(2)
|
|
59
|
-
return GraphStore(path)
|
|
58
|
+
return GraphStore(path, readonly=True)
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
def _add_graph(sub: argparse.ArgumentParser) -> None:
|
|
@@ -113,6 +112,24 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
113
112
|
sq.add_argument("--json", action="store_true")
|
|
114
113
|
sq.set_defaults(func=_cmd_query)
|
|
115
114
|
|
|
115
|
+
o = sub.add_parser(
|
|
116
|
+
"orphans", help="nodes with no incident edges — report-only, "
|
|
117
|
+
"never deletes (prunable flags what the next build would drop)")
|
|
118
|
+
_add_graph(o)
|
|
119
|
+
o.add_argument("--json", action="store_true")
|
|
120
|
+
o.set_defaults(func=_cmd_orphans)
|
|
121
|
+
|
|
122
|
+
bl = sub.add_parser(
|
|
123
|
+
"blame-session",
|
|
124
|
+
help="what a session produced/touched in the graph — owned "
|
|
125
|
+
"nodes and edges plus the files, tools, projects and commits "
|
|
126
|
+
"they reach")
|
|
127
|
+
bl.add_argument(
|
|
128
|
+
"session_id", help="session id or unique prefix")
|
|
129
|
+
_add_graph(bl)
|
|
130
|
+
bl.add_argument("--json", action="store_true")
|
|
131
|
+
bl.set_defaults(func=_cmd_blame)
|
|
132
|
+
|
|
116
133
|
e = sub.add_parser("export", help="dump nodes+edges (D3-friendly)")
|
|
117
134
|
e.add_argument("--format", choices=["json"], default="json")
|
|
118
135
|
_add_graph(e)
|
|
@@ -229,6 +246,64 @@ def _print_query(what: str, data: dict) -> None:
|
|
|
229
246
|
print(f"call: {t['id']} → {t['file']}")
|
|
230
247
|
|
|
231
248
|
|
|
249
|
+
def _cmd_orphans(args: argparse.Namespace) -> int:
|
|
250
|
+
with _open_graph(args.graph) as gs:
|
|
251
|
+
data = orphaned_nodes(gs)
|
|
252
|
+
if args.json:
|
|
253
|
+
print(json.dumps(data, ensure_ascii=False, indent=2))
|
|
254
|
+
return 0
|
|
255
|
+
if not data["nodes"]:
|
|
256
|
+
print("no orphaned nodes")
|
|
257
|
+
return 0
|
|
258
|
+
for n in data["nodes"]:
|
|
259
|
+
label = n.get("name") or ""
|
|
260
|
+
keep = "" if n["prunable"] else " (owned — kept by prune)"
|
|
261
|
+
print(f"{n['id']} {label}{keep}".rstrip())
|
|
262
|
+
print(f"{data['count']} orphan(s)")
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _cmd_blame(args: argparse.Namespace) -> int:
|
|
267
|
+
with _open_graph(args.graph) as gs:
|
|
268
|
+
data = blame_session(gs, args.session_id)
|
|
269
|
+
if data["session"] is None:
|
|
270
|
+
cand = data.get("candidates") or []
|
|
271
|
+
hint = f" — candidates: {', '.join(cand[:10])}" if cand else ""
|
|
272
|
+
print(
|
|
273
|
+
f"devin-graph: no session matches {args.session_id!r}{hint}",
|
|
274
|
+
file=sys.stderr,
|
|
275
|
+
)
|
|
276
|
+
return 2
|
|
277
|
+
if args.json:
|
|
278
|
+
print(json.dumps(data, ensure_ascii=False, indent=2))
|
|
279
|
+
return 0
|
|
280
|
+
s = data["session"]
|
|
281
|
+
print(f"session: {data['session_id']} {s.get('title') or 'Untitled'}")
|
|
282
|
+
if data["projects"]:
|
|
283
|
+
print("project: " + ", ".join(data["projects"]))
|
|
284
|
+
c = data["counts"]
|
|
285
|
+
print(
|
|
286
|
+
f"produced: {c['nodes']} node(s), {c['edges']} edge(s) · "
|
|
287
|
+
f"touched: {c['files']} file(s), {c['tools']} tool(s), "
|
|
288
|
+
f"{c['commits']} commit(s)"
|
|
289
|
+
)
|
|
290
|
+
if data["nodes_owned"]:
|
|
291
|
+
owned = [n for n in data["nodes_owned"] if n["kind"] == "tool_call"]
|
|
292
|
+
if owned:
|
|
293
|
+
print(f"tool calls ({len(owned)}):")
|
|
294
|
+
for n in owned:
|
|
295
|
+
print(f" {n['key']}")
|
|
296
|
+
if data["files"]:
|
|
297
|
+
print(f"files ({len(data['files'])}):")
|
|
298
|
+
for f in data["files"]:
|
|
299
|
+
print(f" {f}")
|
|
300
|
+
if data["tools"]:
|
|
301
|
+
print("tools: " + ", ".join(data["tools"]))
|
|
302
|
+
for cm in data["commits"]:
|
|
303
|
+
print(f"commit {cm['sha'][:7]} ({cm['relation']})")
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
|
|
232
307
|
def _cmd_export(args: argparse.Namespace) -> int:
|
|
233
308
|
with _open_graph(args.graph) as gs:
|
|
234
309
|
data = gs.export()
|
|
@@ -30,10 +30,11 @@ from __future__ import annotations
|
|
|
30
30
|
import json
|
|
31
31
|
import posixpath
|
|
32
32
|
import re
|
|
33
|
+
from collections.abc import Iterable, Iterator
|
|
33
34
|
from dataclasses import dataclass, field
|
|
35
|
+
from typing import Any
|
|
34
36
|
|
|
35
37
|
from devin_internals.commits import commit_references
|
|
36
|
-
from typing import Any, Iterable, Iterator
|
|
37
38
|
|
|
38
39
|
NODE_KINDS = ("session", "project", "file", "tool", "tool_call", "commit",
|
|
39
40
|
"gui_session")
|
|
@@ -87,7 +88,7 @@ class Extraction:
|
|
|
87
88
|
nodes: list[Node] = field(default_factory=list)
|
|
88
89
|
edges: list[Edge] = field(default_factory=list)
|
|
89
90
|
|
|
90
|
-
def merged(self) ->
|
|
91
|
+
def merged(self) -> Extraction:
|
|
91
92
|
seen_n: dict[tuple[str, str], Node] = {}
|
|
92
93
|
seen_e: set[tuple] = set()
|
|
93
94
|
out = Extraction()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""devin-graph as an MCP server: the ``query`` subcommands as one tool.
|
|
2
|
+
|
|
3
|
+
One tool, ``graph_query`` — the read half of ``devin-graph query`` —
|
|
4
|
+
returns the same JSON payloads as ``devin-graph query <what> --json``.
|
|
5
|
+
Read-only by construction: ``build``, ``export``, ``sql`` and ``view``
|
|
6
|
+
stay CLI-only; opening the graph never writes to it, and the source
|
|
7
|
+
stores are never touched.
|
|
8
|
+
|
|
9
|
+
The logic lives in :func:`do_query`, unit-testable without a running
|
|
10
|
+
server or the ``mcp`` package. ``build_server()`` wraps it — needs the
|
|
11
|
+
``mcp`` extra: ``pip install 'devin-graph[mcp]'``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from devin_graph.query import (
|
|
20
|
+
project_detail,
|
|
21
|
+
projects_graph,
|
|
22
|
+
sessions_for_file,
|
|
23
|
+
sessions_for_tool,
|
|
24
|
+
shared_files,
|
|
25
|
+
)
|
|
26
|
+
from devin_graph.store import GraphStore
|
|
27
|
+
|
|
28
|
+
# Same default as the CLI (`devin-graph query ... --graph graph.db`).
|
|
29
|
+
DEFAULT_GRAPH = "graph.db"
|
|
30
|
+
|
|
31
|
+
QUERY_TYPES = ("file", "tool", "project", "projects-graph", "shared-files")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def do_query(query_type: str, target: str = "", graph: str = "") -> dict:
|
|
35
|
+
"""Run one canned graph query; return its ``--json`` payload as a dict.
|
|
36
|
+
|
|
37
|
+
``graph.db`` must already exist — the read-write
|
|
38
|
+
:class:`GraphStore` creates the file on open, so the missing-file
|
|
39
|
+
case (the CLI's exit-2) is checked *before* opening and maps to
|
|
40
|
+
``{"error": "no_graph"}``. The store is then opened read-only
|
|
41
|
+
(``mode=ro`` + ``query_only``): no schema DDL, no repair of an
|
|
42
|
+
incomplete schema.
|
|
43
|
+
"""
|
|
44
|
+
path = Path(graph or DEFAULT_GRAPH).expanduser()
|
|
45
|
+
if not path.exists():
|
|
46
|
+
return {
|
|
47
|
+
"error": "no_graph",
|
|
48
|
+
"detail": f"{path}: no such graph file — "
|
|
49
|
+
"run `devin-graph build` first",
|
|
50
|
+
}
|
|
51
|
+
with GraphStore(path, readonly=True) as gs:
|
|
52
|
+
if query_type == "file":
|
|
53
|
+
return sessions_for_file(gs, target)
|
|
54
|
+
if query_type == "tool":
|
|
55
|
+
return sessions_for_tool(gs, target)
|
|
56
|
+
if query_type == "project":
|
|
57
|
+
return project_detail(gs, target)
|
|
58
|
+
if query_type == "projects-graph":
|
|
59
|
+
return projects_graph(gs)
|
|
60
|
+
if query_type == "shared-files":
|
|
61
|
+
return shared_files(gs)
|
|
62
|
+
return {
|
|
63
|
+
"error": "bad_query",
|
|
64
|
+
"detail": f"unknown query_type {query_type!r} — "
|
|
65
|
+
f"expected one of {', '.join(QUERY_TYPES)}",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _err(error: Exception) -> dict:
|
|
70
|
+
return {"error": type(error).__name__, "detail": str(error)[:500]}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _make_app(name: str):
|
|
74
|
+
"""Return an MCP server app across SDK versions.
|
|
75
|
+
|
|
76
|
+
mcp 2.x renamed FastMCP -> MCPServer; both expose the same .tool()
|
|
77
|
+
decorator and .run(transport='stdio'). Support whichever is installed.
|
|
78
|
+
"""
|
|
79
|
+
try: # mcp 2.x
|
|
80
|
+
from mcp.server.mcpserver import MCPServer
|
|
81
|
+
return MCPServer(name)
|
|
82
|
+
except ImportError:
|
|
83
|
+
pass
|
|
84
|
+
try: # mcp 1.x
|
|
85
|
+
from mcp.server.fastmcp import FastMCP
|
|
86
|
+
return FastMCP(name)
|
|
87
|
+
except ImportError as e: # pragma: no cover
|
|
88
|
+
raise ImportError(
|
|
89
|
+
"The MCP server needs the 'mcp' extra: "
|
|
90
|
+
"pip install 'devin-graph[mcp]'"
|
|
91
|
+
) from e
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def build_server():
|
|
95
|
+
"""Build the MCP server app with every devin-graph tool registered."""
|
|
96
|
+
server = _make_app("devin-graph")
|
|
97
|
+
|
|
98
|
+
@server.tool()
|
|
99
|
+
def graph_query(
|
|
100
|
+
query_type: str,
|
|
101
|
+
target: str = "",
|
|
102
|
+
graph: str = "",
|
|
103
|
+
) -> dict:
|
|
104
|
+
"""Query the session knowledge graph — the same JSON as
|
|
105
|
+
``devin-graph query <what> --json``. Read-only: never modifies
|
|
106
|
+
``graph.db`` or the source stores. ``query_type`` is one of
|
|
107
|
+
``file`` (sessions that touched a path), ``tool``
|
|
108
|
+
(sessions/projects that used a tool), ``project`` (a project's
|
|
109
|
+
sessions, tools and files), ``projects-graph`` (project adjacency)
|
|
110
|
+
or ``shared-files`` (files touched by ≥2 projects). ``target`` is
|
|
111
|
+
the path/name for the first three; ``graph`` overrides the
|
|
112
|
+
default ``./graph.db`` location."""
|
|
113
|
+
try:
|
|
114
|
+
return do_query(query_type, target=target, graph=graph)
|
|
115
|
+
except Exception as error: # noqa: BLE001 — tool boundary must not raise
|
|
116
|
+
return _err(error)
|
|
117
|
+
|
|
118
|
+
return server
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main() -> None:
|
|
122
|
+
try:
|
|
123
|
+
server = build_server()
|
|
124
|
+
except ImportError as exc:
|
|
125
|
+
# Missing 'mcp' extra — install hint lives in the exception text.
|
|
126
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
127
|
+
raise SystemExit(1)
|
|
128
|
+
# Startup failures at .run() are NOT the missing-extra case: let them
|
|
129
|
+
# keep their traceback so the real failure stays debuggable.
|
|
130
|
+
server.run(transport="stdio")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|