memgraph-context-graph 0.5.0__tar.gz → 0.6.0__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.
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/PKG-INFO +31 -43
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/README.md +29 -42
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/command-hooks.md +3 -6
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/pyproject.toml +9 -1
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/_identity.py +43 -31
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/_spec.py +73 -2
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/opencode.py +2 -2
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/capture_log.py +417 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/cli.py +150 -211
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/hooks/cli.py +19 -30
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/hooks/runner.py +215 -45
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/link.py +79 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/__init__.py +1 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/agents.py +249 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/cli.py +143 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/engine.py +306 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/memgraph.py +294 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/tool.py +88 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/tui.py +227 -0
- memgraph_context_graph-0.6.0/src/memgraph_context_graph/setup/uninstall.py +110 -0
- memgraph_context_graph-0.6.0/tests/conftest.py +71 -0
- memgraph_context_graph-0.6.0/tests/fake_agent_cli.py +61 -0
- memgraph_context_graph-0.6.0/tests/test_capture_log.py +289 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_dev_scripts.py +23 -19
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_hook_cli.py +0 -202
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_plugin_hooks.py +2 -2
- memgraph_context_graph-0.6.0/tests/test_setup.py +267 -0
- memgraph_context_graph-0.6.0/tests/test_setup_docker.py +162 -0
- memgraph_context_graph-0.5.0/src/memgraph_context_graph/link.py +0 -50
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/.gitignore +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/CONTEXT.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/LICENSE +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/adr/0001-runtime-adapter-terminology.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/adr/0002-config-file-only-hook-resolution.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/adr/0003-config-path-override.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/adr/0004-user-id-in-session-start-event.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/adr/0005-config-file-covers-llm-key-and-subprocess-env.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/adr/0006-memory-tool-writes-explicit-memory.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/docs/research/2026-10-codex-memory.md +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/__init__.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/__init__.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/_opencode_plugin.js +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/antigravity_cli.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/claude.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/claude_code.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/claude_code_memory.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/codex.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/codex_memory.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/copilot_cli.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/cursor.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/grok.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/adapters/openai.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/events.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/hooks/__init__.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/hooks/runtime_plugin.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/mcp_server.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/memory_backend.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/protocols.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/spaces.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/src/memgraph_context_graph/tools.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/__init__.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_antigravity_cli_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_claude_code_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_codex_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_config_cli.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_context_before_tool.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_copilot_cli_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_cursor_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_events.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_grok_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_identity.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_link.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_opencode_adapter.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_runner_sessions_graph_connector.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_runtime_plugin.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_spaces.py +0 -0
- {memgraph_context_graph-0.5.0 → memgraph_context_graph-0.6.0}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: memgraph-context-graph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Requires-Dist: memgraph-toolbox>=0.1.11
|
|
13
|
+
Requires-Dist: textual<9,>=8.2
|
|
13
14
|
Requires-Dist: tomlkit<1,>=0.13
|
|
14
15
|
Provides-Extra: claude
|
|
15
16
|
Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'claude'
|
|
@@ -49,7 +50,7 @@ uv tool install "memgraph-context-graph[mcp]" --with "skills-graph[memgraph-cont
|
|
|
49
50
|
|
|
50
51
|
The `mcp` extra is what `memgraph-context-graph mcp` serves [recall](#recall-memory-for-the-harnesss-model) with.
|
|
51
52
|
|
|
52
|
-
Or
|
|
53
|
+
Or run `memgraph-context-graph setup`, which installs whatever connectors are missing (see [Setup](#setup)).
|
|
53
54
|
|
|
54
55
|
For SDK usage inside an application:
|
|
55
56
|
|
|
@@ -178,7 +179,8 @@ Implemented:
|
|
|
178
179
|
| Grok Build | `GrokHooksAdapter` | Command receives one JSON object on `stdin` |
|
|
179
180
|
|
|
180
181
|
Codex and Claude Code have distributable Runtime Plugins. Copilot CLI, Cursor,
|
|
181
|
-
OpenCode, Antigravity CLI, and Grok Build use project-local wiring
|
|
182
|
+
OpenCode, Antigravity CLI, and Grok Build use project-local wiring, which `setup`
|
|
183
|
+
writes (or, one runtime at a time, `hook init`):
|
|
182
184
|
|
|
183
185
|
```bash
|
|
184
186
|
memgraph-context-graph hook init copilot-cli
|
|
@@ -191,44 +193,32 @@ memgraph-context-graph hook init grok
|
|
|
191
193
|
See the [command-hook reference](docs/command-hooks.md) for generated paths,
|
|
192
194
|
payload differences, and current upstream hook documentation.
|
|
193
195
|
|
|
194
|
-
###
|
|
196
|
+
### Setup
|
|
195
197
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
Prerequisites:
|
|
199
|
-
|
|
200
|
-
- `uv` on `PATH`. (`uv` manages Python for the tool; if uv-managed Python downloads are blocked, install Python 3.10+ and rerun bootstrap.)
|
|
201
|
-
- Memgraph running and reachable over Bolt. Defaults are `bolt://localhost:7687`, empty user/password, database `memgraph`. If it isn't running locally:
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
docker run --rm -p 7687:7687 memgraph/memgraph-mage
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
**1. Bootstrap all three connectors** (this is what the installed plugin wires into its hooks):
|
|
198
|
+
`memgraph-context-graph setup` installs Context Graph for every agent on the machine; the
|
|
199
|
+
[family README](../README.md#getting-started) walks through it. In short:
|
|
208
200
|
|
|
209
201
|
```bash
|
|
210
|
-
#
|
|
211
|
-
memgraph-context-graph
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
#
|
|
215
|
-
memgraph-context-graph bootstrap --runtime claude-code \
|
|
216
|
-
--connector skills-graph --connector actions-graph --connector sessions-graph
|
|
202
|
+
memgraph-context-graph setup # interactive on a terminal; plain otherwise
|
|
203
|
+
memgraph-context-graph setup claude-code codex # just these agents
|
|
204
|
+
memgraph-context-graph setup cursor --project-dir ~/work/billing
|
|
205
|
+
memgraph-context-graph setup --plan # print the plan, change nothing
|
|
206
|
+
memgraph-context-graph uninstall [runtime ...] # remove the wiring; the graph and config stay
|
|
217
207
|
```
|
|
218
208
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
209
|
+
It finds or starts Memgraph (a `memgraph-mage` container with a volume, on a free port when 7687 is taken),
|
|
210
|
+
writes `memgraph.url` and `identity.user_id`, installs missing connectors and the viewer into the uv tool,
|
|
211
|
+
wires each agent, and runs `doctor`'s checks. Every step checks before it acts, so rerunning only does what's
|
|
212
|
+
missing. The TUI starts only when stdin and stdout are both terminals; the plugins' `scripts/bootstrap.sh`
|
|
213
|
+
runs `setup <runtime> --no-tui`.
|
|
224
214
|
|
|
225
|
-
**
|
|
215
|
+
**Identity and connection.** setup writes `~/.config/context-graph/config.toml`; hooks read their configuration from that file at runtime (see [Configuration](#configuration) — env vars are **not** read at hook time). To change your identity later:
|
|
226
216
|
|
|
227
217
|
```bash
|
|
228
218
|
memgraph-context-graph config set identity.user_id "your-name"
|
|
229
219
|
```
|
|
230
220
|
|
|
231
|
-
|
|
221
|
+
For a remote or HA instance, pass `setup --memgraph-url`, or set it with credentials afterwards:
|
|
232
222
|
|
|
233
223
|
```bash
|
|
234
224
|
memgraph-context-graph config set memgraph.url "neo4j://<coordinator-host>:7687"
|
|
@@ -237,7 +227,7 @@ memgraph-context-graph config set memgraph.password # prompts; stored 060
|
|
|
237
227
|
memgraph-context-graph config set memgraph.database "memgraph"
|
|
238
228
|
```
|
|
239
229
|
|
|
240
|
-
**
|
|
230
|
+
**Verify** (setup runs the same checks):
|
|
241
231
|
|
|
242
232
|
```bash
|
|
243
233
|
memgraph-context-graph config show
|
|
@@ -292,7 +282,7 @@ turns_k = "8"
|
|
|
292
282
|
[sessions-graph § reconciliation](../sessions-graph/README.md#session-reconciliation)),
|
|
293
283
|
which needs no LLM. `[llm]` is optional: a key only lets reconciliation learn
|
|
294
284
|
the user's own ontology (`sessions-graph derive`).
|
|
295
|
-
`[reconcile]` is omitted entirely from a
|
|
285
|
+
`[reconcile]` is omitted entirely from a file setup wrote — absent
|
|
296
286
|
means "never configured," distinct from an explicit `auto_reconcile = false`.
|
|
297
287
|
`[recall]` is optional too: without it, recall runs with the widths the
|
|
298
288
|
benchmark measured; see [Recall](#recall-memory-for-the-harnesss-model).
|
|
@@ -311,14 +301,9 @@ memgraph-context-graph config set <key> <value>
|
|
|
311
301
|
# recall.embedding_model, ontology.{path,derive}, memory.backend
|
|
312
302
|
```
|
|
313
303
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
`
|
|
317
|
-
`SESSIONS_GRAPH_AUTO_RECONCILE` from the environment, and re-running
|
|
318
|
-
`bootstrap` preserves whatever it's currently set to rather than resetting it.
|
|
319
|
-
Unlike the keys above, nobody has that env var exported for an unrelated
|
|
320
|
-
reason — it's only ever set via `memgraph-context-graph config set
|
|
321
|
-
reconcile.auto_reconcile true`.
|
|
304
|
+
Hooks never read environment variables for these values, and neither does setup: it writes only
|
|
305
|
+
`memgraph.url` and `identity.user_id`, from its flags or what it found. Everything else is set with
|
|
306
|
+
`config set`, and setup leaves it alone when it rewrites the file.
|
|
322
307
|
|
|
323
308
|
### Recall: memory for the harness's model
|
|
324
309
|
|
|
@@ -383,6 +368,9 @@ memgraph-context-graph setup claude-code --memory-backend context-graph
|
|
|
383
368
|
memgraph-context-graph setup claude-code --memory-backend native # hand it back
|
|
384
369
|
```
|
|
385
370
|
|
|
371
|
+
(The interactive `setup` offers the same switch as a checkbox. `uninstall` turns the harness's own memory
|
|
372
|
+
back on, since without the plugin it would have none.)
|
|
373
|
+
|
|
386
374
|
Opting in does three things, in this order:
|
|
387
375
|
1. Imports the existing auto-memory files from every project once:
|
|
388
376
|
- `user`/`feedback` memories go to `/memories/<type>/`;
|
|
@@ -477,7 +465,7 @@ Check the installed hook environment with:
|
|
|
477
465
|
memgraph-context-graph doctor --runtime codex --connector skills-graph --connector actions-graph --connector sessions-graph
|
|
478
466
|
```
|
|
479
467
|
|
|
480
|
-
Graph credentials live in `~/.config/context-graph/config.toml` (written by `
|
|
468
|
+
Graph credentials live in `~/.config/context-graph/config.toml` (written by `setup`/`config set`), not in plugin hook files or the process environment — hooks read that file at runtime. See [Configuration](#configuration).
|
|
481
469
|
|
|
482
470
|
### Claude Code Plugin
|
|
483
471
|
|
|
@@ -512,7 +500,7 @@ memgraph-context-graph doctor --runtime claude-code --connector skills-graph --c
|
|
|
512
500
|
For source development and per-project experiments, you can generate local Codex hook files:
|
|
513
501
|
|
|
514
502
|
```bash
|
|
515
|
-
memgraph-context-graph
|
|
503
|
+
memgraph-context-graph hook init codex --project-dir "$PWD" --setup-schema
|
|
516
504
|
```
|
|
517
505
|
|
|
518
506
|
This writes local, ignored files:
|
|
@@ -693,7 +681,7 @@ Then register it in your own package's `pyproject.toml` — this is the entire i
|
|
|
693
681
|
my-runtime = "my_package.adapter:PLUGIN"
|
|
694
682
|
```
|
|
695
683
|
|
|
696
|
-
Once installed, `
|
|
684
|
+
Once installed, `doctor --runtime my-runtime`, `hook run my-runtime`, and `hook init my-runtime` (if `init` is implemented) work like the built-in registrations. See `runtime_plugin.py` for the full protocol and `pyproject.toml`'s own `[project.entry-points."memgraph_context_graph.runtimes"]` for registration examples.
|
|
697
685
|
|
|
698
686
|
### Adding a New Graph Component
|
|
699
687
|
|
|
@@ -24,7 +24,7 @@ uv tool install "memgraph-context-graph[mcp]" --with "skills-graph[memgraph-cont
|
|
|
24
24
|
|
|
25
25
|
The `mcp` extra is what `memgraph-context-graph mcp` serves [recall](#recall-memory-for-the-harnesss-model) with.
|
|
26
26
|
|
|
27
|
-
Or
|
|
27
|
+
Or run `memgraph-context-graph setup`, which installs whatever connectors are missing (see [Setup](#setup)).
|
|
28
28
|
|
|
29
29
|
For SDK usage inside an application:
|
|
30
30
|
|
|
@@ -153,7 +153,8 @@ Implemented:
|
|
|
153
153
|
| Grok Build | `GrokHooksAdapter` | Command receives one JSON object on `stdin` |
|
|
154
154
|
|
|
155
155
|
Codex and Claude Code have distributable Runtime Plugins. Copilot CLI, Cursor,
|
|
156
|
-
OpenCode, Antigravity CLI, and Grok Build use project-local wiring
|
|
156
|
+
OpenCode, Antigravity CLI, and Grok Build use project-local wiring, which `setup`
|
|
157
|
+
writes (or, one runtime at a time, `hook init`):
|
|
157
158
|
|
|
158
159
|
```bash
|
|
159
160
|
memgraph-context-graph hook init copilot-cli
|
|
@@ -166,44 +167,32 @@ memgraph-context-graph hook init grok
|
|
|
166
167
|
See the [command-hook reference](docs/command-hooks.md) for generated paths,
|
|
167
168
|
payload differences, and current upstream hook documentation.
|
|
168
169
|
|
|
169
|
-
###
|
|
170
|
+
### Setup
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Prerequisites:
|
|
174
|
-
|
|
175
|
-
- `uv` on `PATH`. (`uv` manages Python for the tool; if uv-managed Python downloads are blocked, install Python 3.10+ and rerun bootstrap.)
|
|
176
|
-
- Memgraph running and reachable over Bolt. Defaults are `bolt://localhost:7687`, empty user/password, database `memgraph`. If it isn't running locally:
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
docker run --rm -p 7687:7687 memgraph/memgraph-mage
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
**1. Bootstrap all three connectors** (this is what the installed plugin wires into its hooks):
|
|
172
|
+
`memgraph-context-graph setup` installs Context Graph for every agent on the machine; the
|
|
173
|
+
[family README](../README.md#getting-started) walks through it. In short:
|
|
183
174
|
|
|
184
175
|
```bash
|
|
185
|
-
#
|
|
186
|
-
memgraph-context-graph
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
#
|
|
190
|
-
memgraph-context-graph bootstrap --runtime claude-code \
|
|
191
|
-
--connector skills-graph --connector actions-graph --connector sessions-graph
|
|
176
|
+
memgraph-context-graph setup # interactive on a terminal; plain otherwise
|
|
177
|
+
memgraph-context-graph setup claude-code codex # just these agents
|
|
178
|
+
memgraph-context-graph setup cursor --project-dir ~/work/billing
|
|
179
|
+
memgraph-context-graph setup --plan # print the plan, change nothing
|
|
180
|
+
memgraph-context-graph uninstall [runtime ...] # remove the wiring; the graph and config stay
|
|
192
181
|
```
|
|
193
182
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
183
|
+
It finds or starts Memgraph (a `memgraph-mage` container with a volume, on a free port when 7687 is taken),
|
|
184
|
+
writes `memgraph.url` and `identity.user_id`, installs missing connectors and the viewer into the uv tool,
|
|
185
|
+
wires each agent, and runs `doctor`'s checks. Every step checks before it acts, so rerunning only does what's
|
|
186
|
+
missing. The TUI starts only when stdin and stdout are both terminals; the plugins' `scripts/bootstrap.sh`
|
|
187
|
+
runs `setup <runtime> --no-tui`.
|
|
199
188
|
|
|
200
|
-
**
|
|
189
|
+
**Identity and connection.** setup writes `~/.config/context-graph/config.toml`; hooks read their configuration from that file at runtime (see [Configuration](#configuration) — env vars are **not** read at hook time). To change your identity later:
|
|
201
190
|
|
|
202
191
|
```bash
|
|
203
192
|
memgraph-context-graph config set identity.user_id "your-name"
|
|
204
193
|
```
|
|
205
194
|
|
|
206
|
-
|
|
195
|
+
For a remote or HA instance, pass `setup --memgraph-url`, or set it with credentials afterwards:
|
|
207
196
|
|
|
208
197
|
```bash
|
|
209
198
|
memgraph-context-graph config set memgraph.url "neo4j://<coordinator-host>:7687"
|
|
@@ -212,7 +201,7 @@ memgraph-context-graph config set memgraph.password # prompts; stored 060
|
|
|
212
201
|
memgraph-context-graph config set memgraph.database "memgraph"
|
|
213
202
|
```
|
|
214
203
|
|
|
215
|
-
**
|
|
204
|
+
**Verify** (setup runs the same checks):
|
|
216
205
|
|
|
217
206
|
```bash
|
|
218
207
|
memgraph-context-graph config show
|
|
@@ -267,7 +256,7 @@ turns_k = "8"
|
|
|
267
256
|
[sessions-graph § reconciliation](../sessions-graph/README.md#session-reconciliation)),
|
|
268
257
|
which needs no LLM. `[llm]` is optional: a key only lets reconciliation learn
|
|
269
258
|
the user's own ontology (`sessions-graph derive`).
|
|
270
|
-
`[reconcile]` is omitted entirely from a
|
|
259
|
+
`[reconcile]` is omitted entirely from a file setup wrote — absent
|
|
271
260
|
means "never configured," distinct from an explicit `auto_reconcile = false`.
|
|
272
261
|
`[recall]` is optional too: without it, recall runs with the widths the
|
|
273
262
|
benchmark measured; see [Recall](#recall-memory-for-the-harnesss-model).
|
|
@@ -286,14 +275,9 @@ memgraph-context-graph config set <key> <value>
|
|
|
286
275
|
# recall.embedding_model, ontology.{path,derive}, memory.backend
|
|
287
276
|
```
|
|
288
277
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
`
|
|
292
|
-
`SESSIONS_GRAPH_AUTO_RECONCILE` from the environment, and re-running
|
|
293
|
-
`bootstrap` preserves whatever it's currently set to rather than resetting it.
|
|
294
|
-
Unlike the keys above, nobody has that env var exported for an unrelated
|
|
295
|
-
reason — it's only ever set via `memgraph-context-graph config set
|
|
296
|
-
reconcile.auto_reconcile true`.
|
|
278
|
+
Hooks never read environment variables for these values, and neither does setup: it writes only
|
|
279
|
+
`memgraph.url` and `identity.user_id`, from its flags or what it found. Everything else is set with
|
|
280
|
+
`config set`, and setup leaves it alone when it rewrites the file.
|
|
297
281
|
|
|
298
282
|
### Recall: memory for the harness's model
|
|
299
283
|
|
|
@@ -358,6 +342,9 @@ memgraph-context-graph setup claude-code --memory-backend context-graph
|
|
|
358
342
|
memgraph-context-graph setup claude-code --memory-backend native # hand it back
|
|
359
343
|
```
|
|
360
344
|
|
|
345
|
+
(The interactive `setup` offers the same switch as a checkbox. `uninstall` turns the harness's own memory
|
|
346
|
+
back on, since without the plugin it would have none.)
|
|
347
|
+
|
|
361
348
|
Opting in does three things, in this order:
|
|
362
349
|
1. Imports the existing auto-memory files from every project once:
|
|
363
350
|
- `user`/`feedback` memories go to `/memories/<type>/`;
|
|
@@ -452,7 +439,7 @@ Check the installed hook environment with:
|
|
|
452
439
|
memgraph-context-graph doctor --runtime codex --connector skills-graph --connector actions-graph --connector sessions-graph
|
|
453
440
|
```
|
|
454
441
|
|
|
455
|
-
Graph credentials live in `~/.config/context-graph/config.toml` (written by `
|
|
442
|
+
Graph credentials live in `~/.config/context-graph/config.toml` (written by `setup`/`config set`), not in plugin hook files or the process environment — hooks read that file at runtime. See [Configuration](#configuration).
|
|
456
443
|
|
|
457
444
|
### Claude Code Plugin
|
|
458
445
|
|
|
@@ -487,7 +474,7 @@ memgraph-context-graph doctor --runtime claude-code --connector skills-graph --c
|
|
|
487
474
|
For source development and per-project experiments, you can generate local Codex hook files:
|
|
488
475
|
|
|
489
476
|
```bash
|
|
490
|
-
memgraph-context-graph
|
|
477
|
+
memgraph-context-graph hook init codex --project-dir "$PWD" --setup-schema
|
|
491
478
|
```
|
|
492
479
|
|
|
493
480
|
This writes local, ignored files:
|
|
@@ -668,7 +655,7 @@ Then register it in your own package's `pyproject.toml` — this is the entire i
|
|
|
668
655
|
my-runtime = "my_package.adapter:PLUGIN"
|
|
669
656
|
```
|
|
670
657
|
|
|
671
|
-
Once installed, `
|
|
658
|
+
Once installed, `doctor --runtime my-runtime`, `hook run my-runtime`, and `hook init my-runtime` (if `init` is implemented) work like the built-in registrations. See `runtime_plugin.py` for the full protocol and `pyproject.toml`'s own `[project.entry-points."memgraph_context_graph.runtimes"]` for registration examples.
|
|
672
659
|
|
|
673
660
|
### Adding a New Graph Component
|
|
674
661
|
|
|
@@ -234,14 +234,11 @@ memgraph-context-graph config set memgraph.url "bolt://localhost:7687"
|
|
|
234
234
|
memgraph-context-graph config show
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
`memgraph-context-graph setup` writes `memgraph.url` and `identity.user_id` there,
|
|
238
|
+
and wires the project in one go:
|
|
239
239
|
|
|
240
240
|
```bash
|
|
241
|
-
memgraph-context-graph
|
|
242
|
-
--connector skills-graph \
|
|
243
|
-
--connector actions-graph \
|
|
244
|
-
--connector sessions-graph
|
|
241
|
+
memgraph-context-graph setup antigravity-cli --project-dir "$PWD"
|
|
245
242
|
```
|
|
246
243
|
|
|
247
244
|
## Verification
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "memgraph-context-graph"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -15,6 +15,9 @@ dependencies = [
|
|
|
15
15
|
"memgraph-toolbox>=0.1.11",
|
|
16
16
|
# Edits ~/.codex/config.toml when switching Codex's memory, keeping the user's comments and layout.
|
|
17
17
|
"tomlkit>=0.13,<1",
|
|
18
|
+
# setup's interactive installer. Imported only when it runs, so hooks never load it; pinned to the
|
|
19
|
+
# major the viewer uses, since Textual's API moves between majors.
|
|
20
|
+
"textual>=8.2,<9",
|
|
18
21
|
]
|
|
19
22
|
|
|
20
23
|
[project.scripts]
|
|
@@ -45,6 +48,11 @@ test = [
|
|
|
45
48
|
"pytest-asyncio>=0.24.0",
|
|
46
49
|
]
|
|
47
50
|
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
markers = [
|
|
53
|
+
"docker: starts real Memgraph containers, so it needs Docker; CI runs these in their own job",
|
|
54
|
+
]
|
|
55
|
+
|
|
48
56
|
[build-system]
|
|
49
57
|
requires = ["hatchling"]
|
|
50
58
|
build-backend = "hatchling.build"
|
|
@@ -49,7 +49,12 @@ _DEFAULT_CONFIG_DIR = Path.home() / ".config" / "context-graph"
|
|
|
49
49
|
def config_file() -> Path:
|
|
50
50
|
"""The config file hooks read, honouring :data:`CONFIG_PATH_ENV`."""
|
|
51
51
|
override = os.environ.get(CONFIG_PATH_ENV)
|
|
52
|
-
return Path(override) if override else
|
|
52
|
+
return Path(override) if override else default_config_file()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def default_config_file() -> Path:
|
|
56
|
+
"""The config file used when :data:`CONFIG_PATH_ENV` is unset."""
|
|
57
|
+
return _DEFAULT_CONFIG_DIR / "config.toml"
|
|
53
58
|
|
|
54
59
|
|
|
55
60
|
# Defaults matching memgraph-toolbox's MEMGRAPH_ENV_DEFAULTS.
|
|
@@ -116,6 +121,10 @@ class HookConfig:
|
|
|
116
121
|
#: "left at the default" from "written": Enterprise refuses an explicit ``identity.user_id`` or
|
|
117
122
|
#: ``memgraph.database``, which would quietly override what the login decides.
|
|
118
123
|
present: frozenset[str] = frozenset()
|
|
124
|
+
#: Why the file could not be read as written: unreadable, or lines that are not ``key = value``.
|
|
125
|
+
#: Values those lines meant to set fell back to defaults, which can point hooks at the wrong
|
|
126
|
+
#: Memgraph, so hooks log these to the capture-failure log and ``doctor`` fails on them.
|
|
127
|
+
problems: tuple[str, ...] = ()
|
|
119
128
|
|
|
120
129
|
@property
|
|
121
130
|
def graph_memory(self) -> bool:
|
|
@@ -321,18 +330,11 @@ def write_full_config(
|
|
|
321
330
|
anthropic_api_key: str = _LLM_DEFAULTS["anthropic_api_key"],
|
|
322
331
|
auto_reconcile: bool | None = _RECONCILE_DEFAULTS["auto_reconcile"],
|
|
323
332
|
) -> Path:
|
|
324
|
-
"""Write a complete config file
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
``
|
|
329
|
-
exported for an unrelated reason the way they might already have
|
|
330
|
-
``OPENAI_API_KEY``/`MEMGRAPH_PASSWORD` set) — it is only ever set via
|
|
331
|
-
``config set reconcile.auto_reconcile``. Re-running bootstrap must not
|
|
332
|
-
silently revert it to off, so ``auto_reconcile`` is preserved from the
|
|
333
|
-
existing file unless explicitly given here. ``[recall]``, ``[ontology]`` and
|
|
334
|
-
``[github]`` are preserved the same way: they are only ever set via ``config set`` or by
|
|
335
|
-
editing the file. ``[memory]`` is set by ``setup --memory-backend`` and kept likewise.
|
|
333
|
+
"""Write a complete config file, resetting identity, Memgraph and LLM settings to what is given.
|
|
334
|
+
|
|
335
|
+
Every other section (``[reconcile]``, ``[recall]``, ``[ontology]``, ``[github]``, ``[memory]``) and the
|
|
336
|
+
edition are kept from the existing file, since only ``config set`` or a hand edit ever sets them.
|
|
337
|
+
``auto_reconcile`` is kept likewise unless given here. :func:`write_config` is the partial update.
|
|
336
338
|
"""
|
|
337
339
|
global _cached_config
|
|
338
340
|
|
|
@@ -380,14 +382,19 @@ def config_dir_path() -> Path:
|
|
|
380
382
|
|
|
381
383
|
|
|
382
384
|
def _read_config_file() -> HookConfig:
|
|
383
|
-
"""Parse the config file.
|
|
384
|
-
if not config_file().is_file():
|
|
385
|
-
return HookConfig()
|
|
385
|
+
"""Parse the config file.
|
|
386
386
|
|
|
387
|
+
A missing file gives defaults. An unreadable or partly unparsable one also
|
|
388
|
+
gives defaults for whatever it failed to set, never an exception (a hook
|
|
389
|
+
must not crash on its config), and says so in :attr:`HookConfig.problems`.
|
|
390
|
+
"""
|
|
391
|
+
problems: list[str] = []
|
|
387
392
|
try:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
393
|
+
if not config_file().is_file():
|
|
394
|
+
return HookConfig()
|
|
395
|
+
sections = _parse_toml(config_file(), problems)
|
|
396
|
+
except Exception as exc:
|
|
397
|
+
return HookConfig(problems=(f"unreadable: {type(exc).__name__}: {exc}",))
|
|
391
398
|
|
|
392
399
|
identity = sections.get("identity", {})
|
|
393
400
|
memgraph = sections.get("memgraph", {})
|
|
@@ -421,19 +428,21 @@ def _read_config_file() -> HookConfig:
|
|
|
421
428
|
for key, value in keys.items()
|
|
422
429
|
if value
|
|
423
430
|
),
|
|
431
|
+
problems=tuple(problems),
|
|
424
432
|
)
|
|
425
433
|
|
|
426
434
|
|
|
427
|
-
def _parse_toml(path: Path) -> dict[str, dict[str, str]]:
|
|
435
|
+
def _parse_toml(path: Path, problems: list[str] | None = None) -> dict[str, dict[str, str]]:
|
|
428
436
|
"""Minimal TOML parser — handles [section] and key = "value" pairs.
|
|
429
437
|
|
|
430
438
|
Only supports string values (quoted or bare). Sufficient for our config shape.
|
|
431
|
-
Keys before the first section header land in the ``""`` section.
|
|
439
|
+
Keys before the first section header land in the ``""`` section. A line that
|
|
440
|
+
is none of these is skipped and, when *problems* is given, reported there.
|
|
432
441
|
"""
|
|
433
442
|
sections: dict[str, dict[str, str]] = {"": {}}
|
|
434
443
|
current_section = ""
|
|
435
444
|
|
|
436
|
-
for line in path.read_text(encoding="utf-8").splitlines():
|
|
445
|
+
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
|
|
437
446
|
stripped = line.strip()
|
|
438
447
|
if not stripped or stripped.startswith("#"):
|
|
439
448
|
continue
|
|
@@ -441,15 +450,18 @@ def _parse_toml(path: Path) -> dict[str, dict[str, str]]:
|
|
|
441
450
|
current_section = stripped[1:-1].strip()
|
|
442
451
|
sections.setdefault(current_section, {})
|
|
443
452
|
continue
|
|
444
|
-
if "=" in stripped:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
+
if "=" not in stripped or not stripped.partition("=")[0].strip():
|
|
454
|
+
if problems is not None:
|
|
455
|
+
problems.append(f"line {number} is not `key = value` and was ignored")
|
|
456
|
+
continue
|
|
457
|
+
key, _, value = stripped.partition("=")
|
|
458
|
+
key = key.strip()
|
|
459
|
+
value = value.strip()
|
|
460
|
+
if value[:1] not in {'"', "'"} and "#" in value:
|
|
461
|
+
# Bare (unquoted) value with a trailing comment, e.g. `auto_reconcile = true # off for now`.
|
|
462
|
+
value = value.partition("#")[0].strip()
|
|
463
|
+
value = value.strip('"').strip("'")
|
|
464
|
+
sections[current_section][key] = value
|
|
453
465
|
|
|
454
466
|
return sections
|
|
455
467
|
|
|
@@ -298,7 +298,9 @@ def write_hook_config(
|
|
|
298
298
|
"""Write a runtime's project-local JSON hook configuration.
|
|
299
299
|
|
|
300
300
|
Existing settings files are merged only when the runtime declares
|
|
301
|
-
``merge=True
|
|
301
|
+
``merge=True``: every hook entry this runtime's command didn't write is
|
|
302
|
+
kept, and this runtime's own entries are replaced, so writing twice leaves
|
|
303
|
+
one copy. Otherwise callers must pass ``force=True`` to replace the file.
|
|
302
304
|
|
|
303
305
|
Raises:
|
|
304
306
|
ValueError: if *spec* declares no config path, or an existing file to
|
|
@@ -326,13 +328,82 @@ def write_hook_config(
|
|
|
326
328
|
existing_hooks = document.get(spec.config.root_key, {})
|
|
327
329
|
if not isinstance(existing_hooks, dict):
|
|
328
330
|
raise ValueError(f"Expected {spec.config.root_key!r} to be an object in {config_path}")
|
|
329
|
-
|
|
331
|
+
merged = _without_own_entries(spec, existing_hooks)
|
|
332
|
+
for event, entries in hooks.items():
|
|
333
|
+
merged[event] = [*merged.get(event, []), *entries]
|
|
334
|
+
document[spec.config.root_key] = merged
|
|
330
335
|
|
|
331
336
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
332
337
|
config_path.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8")
|
|
333
338
|
return config_path
|
|
334
339
|
|
|
335
340
|
|
|
341
|
+
def has_hook_config(spec: RuntimeSpec, project_dir: Path) -> bool:
|
|
342
|
+
"""Whether *project_dir*'s hook file for *spec* holds an entry this runtime's command wrote."""
|
|
343
|
+
hooks = _read_hooks(spec, project_dir)
|
|
344
|
+
return hooks is not None and any(_is_own_entry(spec, entry) for entries in hooks.values() for entry in entries)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def remove_hook_config(spec: RuntimeSpec, project_dir: Path) -> bool:
|
|
348
|
+
"""Remove this runtime's entries from *project_dir*'s hook file, keeping everything else.
|
|
349
|
+
|
|
350
|
+
A file left with nothing but an empty hook map (and ``version``) is deleted. Returns whether anything
|
|
351
|
+
was removed.
|
|
352
|
+
|
|
353
|
+
Raises:
|
|
354
|
+
ValueError: if the file exists but isn't a JSON object with an object-valued hook map.
|
|
355
|
+
"""
|
|
356
|
+
if not has_hook_config(spec, project_dir):
|
|
357
|
+
return False
|
|
358
|
+
config_path = project_dir / str(spec.config.path)
|
|
359
|
+
document = json.loads(config_path.read_text(encoding="utf-8"))
|
|
360
|
+
remaining = _without_own_entries(spec, document[spec.config.root_key])
|
|
361
|
+
if remaining:
|
|
362
|
+
document[spec.config.root_key] = remaining
|
|
363
|
+
else:
|
|
364
|
+
del document[spec.config.root_key]
|
|
365
|
+
if set(document) <= {"version"}:
|
|
366
|
+
config_path.unlink()
|
|
367
|
+
else:
|
|
368
|
+
config_path.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8")
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _read_hooks(spec: RuntimeSpec, project_dir: Path) -> dict[str, list[Any]] | None:
|
|
373
|
+
if spec.config.path is None:
|
|
374
|
+
return None
|
|
375
|
+
config_path = project_dir / spec.config.path
|
|
376
|
+
if not config_path.is_file():
|
|
377
|
+
return None
|
|
378
|
+
document = json.loads(config_path.read_text(encoding="utf-8"))
|
|
379
|
+
if not isinstance(document, dict) or not isinstance(document.get(spec.config.root_key, {}), dict):
|
|
380
|
+
raise ValueError(f"Expected a JSON object with an object {spec.config.root_key!r} in {config_path}")
|
|
381
|
+
return {
|
|
382
|
+
event: entries for event, entries in document.get(spec.config.root_key, {}).items() if isinstance(entries, list)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _without_own_entries(spec: RuntimeSpec, hooks: dict[str, Any]) -> dict[str, Any]:
|
|
387
|
+
"""*hooks* minus this runtime's own entries; events left empty are dropped."""
|
|
388
|
+
kept: dict[str, Any] = {}
|
|
389
|
+
for event, entries in hooks.items():
|
|
390
|
+
if not isinstance(entries, list):
|
|
391
|
+
kept[event] = entries
|
|
392
|
+
continue
|
|
393
|
+
others = [entry for entry in entries if not _is_own_entry(spec, entry)]
|
|
394
|
+
if others:
|
|
395
|
+
kept[event] = others
|
|
396
|
+
return kept
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _is_own_entry(spec: RuntimeSpec, entry: Any) -> bool:
|
|
400
|
+
"""Whether a hook entry (flat, or a matcher group holding ``hooks``) runs this runtime's capture command."""
|
|
401
|
+
marker = f"hook run {spec.name}"
|
|
402
|
+
if isinstance(entry, dict) and isinstance(entry.get("hooks"), list):
|
|
403
|
+
return any(_is_own_entry(spec, inner) for inner in entry["hooks"])
|
|
404
|
+
return isinstance(entry, dict) and marker in str(entry.get(spec.config.command_key, ""))
|
|
405
|
+
|
|
406
|
+
|
|
336
407
|
def hook_command_argv(runtime: str, connectors: list[str]) -> list[str]:
|
|
337
408
|
"""Build the installed hook command for *runtime* and *connectors* as argv."""
|
|
338
409
|
executable = shutil.which("memgraph-context-graph")
|
|
@@ -131,7 +131,7 @@ class OpenCodeHooksAdapter(SpecAdapter):
|
|
|
131
131
|
SPEC = SPEC
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
PLUGIN_PATH = ".opencode/plugins/memgraph-context-graph/index.js"
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
def init(
|
|
@@ -151,7 +151,7 @@ def init(
|
|
|
151
151
|
Raises:
|
|
152
152
|
FileExistsError: if the plugin file exists and *force* is false.
|
|
153
153
|
"""
|
|
154
|
-
target = project_dir /
|
|
154
|
+
target = project_dir / PLUGIN_PATH
|
|
155
155
|
if target.exists() and not force:
|
|
156
156
|
raise FileExistsError(f"Refusing to overwrite existing OpenCode plugin: {target} (pass force=True)")
|
|
157
157
|
|