memgraph-context-graph 0.7.1__tar.gz → 0.7.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.
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/PKG-INFO +1 -1
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/command-hooks.md +1 -1
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/pyproject.toml +1 -1
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/codex.py +4 -3
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/cli.py +19 -3
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/memory_backend.py +1 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/cli.py +8 -3
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/engine.py +60 -2
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/memgraph.py +15 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/prompts.py +16 -13
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/tool.py +30 -10
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/spaces.py +4 -2
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_codex_adapter.py +19 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_dev_scripts.py +1 -1
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_setup.py +143 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_setup_docker.py +17 -1
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/.gitignore +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/CONTEXT.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/LICENSE +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/README.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0001-runtime-adapter-terminology.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0002-config-file-only-hook-resolution.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0003-config-path-override.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0004-user-id-in-session-start-event.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0005-config-file-covers-llm-key-and-subprocess-env.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0006-memory-tool-writes-explicit-memory.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/research/2026-10-codex-memory.md +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/__init__.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/__init__.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/_identity.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/_opencode_plugin.js +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/_spec.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/antigravity_cli.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/claude.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/claude_code.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/claude_code_memory.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/codex_memory.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/copilot_cli.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/cursor.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/grok.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/openai.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/adapters/opencode.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/capture_log.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/events.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/hooks/__init__.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/hooks/cli.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/hooks/runner.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/hooks/runtime_plugin.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/link.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/mcp_server.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/protocols.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/__init__.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/agents.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/setup/uninstall.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/tools.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/__init__.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/conftest.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/fake_agent_cli.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_antigravity_cli_adapter.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_capture_log.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_claude_code_adapter.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_config_cli.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_context_before_tool.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_copilot_cli_adapter.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_cursor_adapter.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_events.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_grok_adapter.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_hook_cli.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_identity.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_link.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_opencode_adapter.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_plugin_hooks.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_runner_sessions_graph_connector.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_runtime_plugin.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_spaces.py +0 -0
- {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_tools.py +0 -0
|
@@ -205,7 +205,7 @@ session end, so adapters emit two different events:
|
|
|
205
205
|
| Runtime | Turn end | Session end |
|
|
206
206
|
|---|---|---|
|
|
207
207
|
| Claude Code | `Stop` (records `last_assistant_message`) | `SessionEnd` |
|
|
208
|
-
| Codex | `Stop` (records `last_assistant_message`) |
|
|
208
|
+
| Codex | `Stop` (records `last_assistant_message`) | `SessionEnd`, only when Codex ends the session; with its shared background server `/exit` just detaches |
|
|
209
209
|
| Copilot CLI | `agentStop` | `sessionEnd` |
|
|
210
210
|
| Cursor | `stop` | `sessionEnd` |
|
|
211
211
|
| OpenCode | `session.execution.succeeded` / `.failed` | `session.deleted` |
|
|
@@ -11,6 +11,7 @@ from memgraph_context_graph.adapters._spec import (
|
|
|
11
11
|
SpecAdapter,
|
|
12
12
|
SpecPlugin,
|
|
13
13
|
pre_tool_use_context,
|
|
14
|
+
session_end,
|
|
14
15
|
session_start,
|
|
15
16
|
tool_end,
|
|
16
17
|
tool_start,
|
|
@@ -40,8 +41,7 @@ def _permission(context: EventContext) -> MessageEvent:
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
def _turn_end(context: EventContext) -> list[Event]:
|
|
43
|
-
#
|
|
44
|
-
# only ever a turn end.
|
|
44
|
+
# Stop fires after every turn; SessionEnd, registered separately, ends the session.
|
|
45
45
|
return turn_end(context, reply=context.value("reply"))
|
|
46
46
|
|
|
47
47
|
|
|
@@ -50,7 +50,7 @@ SPEC = RuntimeSpec(
|
|
|
50
50
|
name="codex",
|
|
51
51
|
source_sdk="codex",
|
|
52
52
|
event_key="hook_event_name",
|
|
53
|
-
hooks=("SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PermissionRequest", "Stop"),
|
|
53
|
+
hooks=("SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PermissionRequest", "Stop", "SessionEnd"),
|
|
54
54
|
rules={
|
|
55
55
|
"SessionStart": session_start,
|
|
56
56
|
"UserPromptSubmit": _user_prompt,
|
|
@@ -58,6 +58,7 @@ SPEC = RuntimeSpec(
|
|
|
58
58
|
"PostToolUse": tool_end,
|
|
59
59
|
"PermissionRequest": _permission,
|
|
60
60
|
"Stop": _turn_end,
|
|
61
|
+
"SessionEnd": session_end,
|
|
61
62
|
},
|
|
62
63
|
metadata_keys=(
|
|
63
64
|
"cwd",
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/cli.py
RENAMED
|
@@ -257,9 +257,9 @@ def _ui(argv: list[str]) -> int:
|
|
|
257
257
|
from context_graph_viewer.cli import main as viewer_main
|
|
258
258
|
except ImportError:
|
|
259
259
|
print(
|
|
260
|
-
"memgraph-context-graph ui needs the context-graph-viewer package
|
|
261
|
-
"
|
|
262
|
-
"
|
|
260
|
+
"memgraph-context-graph ui needs the context-graph-viewer package, which isn't installed.\n"
|
|
261
|
+
"Run setup again and say yes to the viewer:\n"
|
|
262
|
+
" memgraph-context-graph setup",
|
|
263
263
|
file=sys.stderr,
|
|
264
264
|
)
|
|
265
265
|
return 1
|
|
@@ -305,6 +305,7 @@ def _join(argv: list[str]) -> int:
|
|
|
305
305
|
parser.add_argument("--password", default=None, help="Prompted for when omitted.")
|
|
306
306
|
args = parser.parse_args(argv)
|
|
307
307
|
password = args.password if args.password is not None else getpass.getpass(f"Password for {args.login}: ")
|
|
308
|
+
_ensure_enterprise_package()
|
|
308
309
|
|
|
309
310
|
write_config(
|
|
310
311
|
edition=EDITION_ENTERPRISE, memgraph_url=args.url, memgraph_user=args.login, memgraph_password=password
|
|
@@ -328,6 +329,21 @@ def _join(argv: list[str]) -> int:
|
|
|
328
329
|
return 0
|
|
329
330
|
|
|
330
331
|
|
|
332
|
+
def _ensure_enterprise_package() -> None:
|
|
333
|
+
"""Install ``context-graph-spaces`` when this is the uv tool and it is missing; else the error says how."""
|
|
334
|
+
from memgraph_context_graph.setup import tool
|
|
335
|
+
from memgraph_context_graph.spaces import EDITION_ENTERPRISE, _edition_factory
|
|
336
|
+
|
|
337
|
+
if _edition_factory(EDITION_ENTERPRISE) is not None or not tool.uv_tool_environment():
|
|
338
|
+
return
|
|
339
|
+
print("Enterprise spaces need one more package, with the local extraction models; installing it.", flush=True)
|
|
340
|
+
print("This can take a minute.", flush=True)
|
|
341
|
+
try:
|
|
342
|
+
tool.install(viewer=False, spaces=True, log=lambda line: None)
|
|
343
|
+
except tool.ToolError as exc:
|
|
344
|
+
print(str(exc), file=sys.stderr)
|
|
345
|
+
|
|
346
|
+
|
|
331
347
|
def _doctor(argv: list[str]) -> int:
|
|
332
348
|
from memgraph_context_graph.hooks.runtime_plugin import load_runtime_plugins
|
|
333
349
|
|
|
@@ -117,6 +117,7 @@ def use_native_memory(plugin: RuntimeCLIPlugin, *, home: Path | None = None) ->
|
|
|
117
117
|
lines.append(
|
|
118
118
|
f"Set memory.backend = {MEMORY_BACKEND_NATIVE}; {plugin.name}'s own memory is back from its next session."
|
|
119
119
|
)
|
|
120
|
+
lines.append("Memories saved while Context Graph was your memory stay in Memgraph; they aren't copied back.")
|
|
120
121
|
return lines
|
|
121
122
|
|
|
122
123
|
|
|
@@ -90,7 +90,7 @@ def setup_main(argv: Sequence[str]) -> int:
|
|
|
90
90
|
_print_plan(choices, env)
|
|
91
91
|
print()
|
|
92
92
|
outcomes = engine.run(choices, env, log=_log, report=_report)
|
|
93
|
-
return _finish(outcomes)
|
|
93
|
+
return _finish(outcomes, choices, env)
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def uninstall_main(argv: Sequence[str]) -> int:
|
|
@@ -141,11 +141,16 @@ def _report(outcome: engine.Outcome) -> None:
|
|
|
141
141
|
print(f" {line}")
|
|
142
142
|
|
|
143
143
|
|
|
144
|
-
def _finish(outcomes: Sequence[engine.Outcome]) -> int:
|
|
144
|
+
def _finish(outcomes: Sequence[engine.Outcome], choices: engine.Choices, env: engine.Environment) -> int:
|
|
145
145
|
failures = [outcome for outcome in outcomes if outcome.status == "fail"]
|
|
146
146
|
print()
|
|
147
147
|
if not failures:
|
|
148
|
-
print("Context Graph is set up.
|
|
148
|
+
print("Context Graph is set up.")
|
|
149
|
+
print()
|
|
150
|
+
steps = engine.next_steps(choices, env)
|
|
151
|
+
print(steps[0])
|
|
152
|
+
for line in steps[1:]:
|
|
153
|
+
print(f" {line}")
|
|
149
154
|
return 0
|
|
150
155
|
print(f"{len(failures)} step(s) failed. To fix:")
|
|
151
156
|
for outcome in failures:
|
|
@@ -269,7 +269,7 @@ def _agent(agent: agents_mod.Agent, choices: Choices, log: Callable[[str], None]
|
|
|
269
269
|
fix = f"memgraph-context-graph setup {agent.runtime}"
|
|
270
270
|
if agent.wiring == "plugin":
|
|
271
271
|
try:
|
|
272
|
-
return Outcome(agent.label, "ok", agents_mod.wire_plugin(agent, log))
|
|
272
|
+
return Outcome(agent.label, "ok", _with_trust_note(agent, agents_mod.wire_plugin(agent, log)))
|
|
273
273
|
except agents_mod.WiringError as exc:
|
|
274
274
|
return Outcome(agent.label, "fail", str(exc), fix=fix)
|
|
275
275
|
if not choices.project_dirs:
|
|
@@ -283,7 +283,65 @@ def _agent(agent: agents_mod.Agent, choices: Choices, log: Callable[[str], None]
|
|
|
283
283
|
if errors:
|
|
284
284
|
dirs = " ".join(f"--project-dir {path}" for path in choices.project_dirs)
|
|
285
285
|
return Outcome(agent.label, "fail", "; ".join(errors + summaries), fix=f"{fix} {dirs}")
|
|
286
|
-
return Outcome(agent.label, "ok", "; ".join(summaries))
|
|
286
|
+
return Outcome(agent.label, "ok", _with_trust_note(agent, "; ".join(summaries)))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
#: Agents that skip hooks they haven't been told to trust, without a word: nothing is recorded until then.
|
|
290
|
+
_TRUST_NOTES = {
|
|
291
|
+
"codex": "Codex records nothing until you trust its hooks: open codex and accept them (/hooks)",
|
|
292
|
+
"grok": "Grok records nothing until you trust this project's hooks: run /hooks-trust in grok",
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _with_trust_note(agent: agents_mod.Agent, summary: str) -> str:
|
|
297
|
+
note = _TRUST_NOTES.get(agent.runtime)
|
|
298
|
+
return f"{summary}; {note}" if note else summary
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
#: What setup says before it asks anything: what the user is about to get, in plain words.
|
|
302
|
+
WELCOME = (
|
|
303
|
+
"Your coding agents are about to get a memory.",
|
|
304
|
+
"Each session is kept as one graph in Memgraph, on your machine by default:",
|
|
305
|
+
"prompts, replies and tool activity. No account or cloud needed.",
|
|
306
|
+
"A few questions, then setup does the rest. Nothing changes until you confirm.",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
#: What a user does once, so an agent starts recording; the same facts as :data:`_TRUST_NOTES`, for the last screen.
|
|
310
|
+
_FIRST_RUN = {
|
|
311
|
+
"codex": "in Codex, open it and accept its hooks (/hooks)",
|
|
312
|
+
"grok": "in Grok, run /hooks-trust once per wired project",
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def next_steps(choices: Choices, env: Environment) -> list[str]:
|
|
317
|
+
"""What happens now and what to try, for the end of a successful setup: short, and only what applies.
|
|
318
|
+
|
|
319
|
+
The first line is the heading; each bullet after it fits one terminal line, or continues on an indented one.
|
|
320
|
+
"""
|
|
321
|
+
from memgraph_context_graph.adapters._identity import MEMORY_BACKEND_GRAPH
|
|
322
|
+
|
|
323
|
+
graph_memory = choices.memory_backend == MEMORY_BACKEND_GRAPH or (
|
|
324
|
+
choices.memory_backend is None and env.configured_memory_backend == MEMORY_BACKEND_GRAPH
|
|
325
|
+
)
|
|
326
|
+
lines = [
|
|
327
|
+
"Your agents now remember. What to expect:",
|
|
328
|
+
"· Every session is kept in your graph. Finish one, start another, and ask:",
|
|
329
|
+
' "what did we do last time?"',
|
|
330
|
+
]
|
|
331
|
+
remembering = [agents_mod.BY_RUNTIME[r].label for r in choices.runtimes if has_native_memory(r)]
|
|
332
|
+
if graph_memory and remembering:
|
|
333
|
+
lines += [
|
|
334
|
+
f"· {' and '.join(remembering)} remember through the graph now.",
|
|
335
|
+
' Say "remember that ..." and every agent can recall it.',
|
|
336
|
+
]
|
|
337
|
+
if choices.viewer:
|
|
338
|
+
lines.append("· Watch it grow: memgraph-context-graph ui")
|
|
339
|
+
lines += [f"· To start recording {_FIRST_RUN[r]}" for r in choices.runtimes if r in _FIRST_RUN]
|
|
340
|
+
lines += [
|
|
341
|
+
"· Change anything any time: memgraph-context-graph setup",
|
|
342
|
+
"· Undo the wiring: memgraph-context-graph uninstall (your graph stays)",
|
|
343
|
+
]
|
|
344
|
+
return lines
|
|
287
345
|
|
|
288
346
|
|
|
289
347
|
def _memory(runtime: str, backend: str) -> Outcome | None:
|
|
@@ -180,6 +180,11 @@ def _restart(
|
|
|
180
180
|
f"See `docker logs {name}`. Setup never removes it, because it holds your graph.",
|
|
181
181
|
)
|
|
182
182
|
port = _published_port(docker, name)
|
|
183
|
+
if port is None and (current := _inspect(docker, name)) is not None and _lost_its_network(current):
|
|
184
|
+
log(f"The container {name} lost its network, so its port isn't published; reattaching it")
|
|
185
|
+
_run([docker, "network", "connect", "bridge", name])
|
|
186
|
+
_run([docker, "restart", name])
|
|
187
|
+
port = _published_port(docker, name)
|
|
183
188
|
if port is None:
|
|
184
189
|
raise ProvisionError(
|
|
185
190
|
f"The container {name} doesn't publish Memgraph's Bolt port 7687",
|
|
@@ -191,6 +196,16 @@ def _restart(
|
|
|
191
196
|
return Memgraph(url, version, how, container=name)
|
|
192
197
|
|
|
193
198
|
|
|
199
|
+
def _lost_its_network(info: dict) -> bool:
|
|
200
|
+
"""Whether a container asked for Bolt's port but is attached to no network, so Docker can't publish it.
|
|
201
|
+
|
|
202
|
+
Docker leaves a container in this state after its network is removed underneath it; the binding is still
|
|
203
|
+
in its config, and reattaching it (then a restart) makes Docker apply it. Its data is never touched.
|
|
204
|
+
"""
|
|
205
|
+
bindings = info["HostConfig"].get("PortBindings") or {}
|
|
206
|
+
return "7687/tcp" in bindings and not info["NetworkSettings"].get("Networks")
|
|
207
|
+
|
|
208
|
+
|
|
194
209
|
def _start(
|
|
195
210
|
docker: str,
|
|
196
211
|
name: str,
|
|
@@ -223,13 +223,14 @@ class Prompts:
|
|
|
223
223
|
self.write("\n" + "".join(f"{self.style.wordmark(row)}\n" for row in WORDMARK))
|
|
224
224
|
self.write("".join(f"{self.style.graph(row, width)}\n" for row in GRAPH) + "\n")
|
|
225
225
|
|
|
226
|
-
def intro(self, title: str, subtitle: str = "") -> None:
|
|
227
|
-
"""The orange graph divider, some space, then the title: the questions start apart from what came before."""
|
|
226
|
+
def intro(self, title: str, subtitle: str | Sequence[str] = "") -> None:
|
|
227
|
+
"""The orange graph divider, some space, then the title and its lines: the questions start apart from what came before."""
|
|
228
228
|
rule = RULE[: min(len(WORDMARK[0]), self.term.columns - 1)]
|
|
229
229
|
rule = rule[: rule.rfind("●") + 1] # cut short, it still ends on a node
|
|
230
230
|
self.write(f"\n{self.style.accent(rule)}\n\n")
|
|
231
231
|
self.write(f"{self.style.accent('◆')} {self.style.bold(title)}\n")
|
|
232
|
-
|
|
232
|
+
for text in [subtitle] if isinstance(subtitle, str) else subtitle:
|
|
233
|
+
self.line(self.style.muted(self.fit(text)))
|
|
233
234
|
|
|
234
235
|
def answered(self, label: str, value: str) -> None:
|
|
235
236
|
value = self.fit(value, _ANSWER_COLUMN)
|
|
@@ -400,7 +401,7 @@ def ask(prompts: Prompts, choices: Choices, env: Environment, *, preselect: bool
|
|
|
400
401
|
|
|
401
402
|
*preselect* starts the agent list with *choices*' agents ticked; off, the user ticks every one.
|
|
402
403
|
"""
|
|
403
|
-
prompts.intro("Context Graph setup",
|
|
404
|
+
prompts.intro("Context Graph setup", engine.WELCOME)
|
|
404
405
|
enterprise = env.edition == "enterprise"
|
|
405
406
|
prompts.answered("Memgraph", "from your Enterprise login" if enterprise else _memgraph_summary(choices, env))
|
|
406
407
|
|
|
@@ -418,7 +419,7 @@ def ask(prompts: Prompts, choices: Choices, env: Environment, *, preselect: bool
|
|
|
418
419
|
def name(answers: _Answers) -> str | _Back:
|
|
419
420
|
if enterprise:
|
|
420
421
|
return SKIPPED
|
|
421
|
-
answer = prompts.text("Your name", answers.user_id, hint="
|
|
422
|
+
answer = prompts.text("Your name", answers.user_id, hint="your memories are filed under it")
|
|
422
423
|
if isinstance(answer, str):
|
|
423
424
|
answers.user_id = answer
|
|
424
425
|
return answer if isinstance(answer, _Back) else DONE
|
|
@@ -444,7 +445,7 @@ def ask(prompts: Prompts, choices: Choices, env: Environment, *, preselect: bool
|
|
|
444
445
|
answer = prompts.confirm(
|
|
445
446
|
"Install the terminal viewer?",
|
|
446
447
|
answers.viewer,
|
|
447
|
-
note="
|
|
448
|
+
note="Watch your agents' memory grow, right in this terminal: memgraph-context-graph ui.",
|
|
448
449
|
short="Viewer",
|
|
449
450
|
)
|
|
450
451
|
if isinstance(answer, bool):
|
|
@@ -459,7 +460,7 @@ def ask(prompts: Prompts, choices: Choices, env: Environment, *, preselect: bool
|
|
|
459
460
|
answer = prompts.confirm(
|
|
460
461
|
f"Use Context Graph as the memory of {' and '.join(switchable)}?",
|
|
461
462
|
answers.use_graph_memory,
|
|
462
|
-
note="Their
|
|
463
|
+
note="Their memories move into the graph, shared across agents. Switch back any time.",
|
|
463
464
|
short="Agent memory",
|
|
464
465
|
)
|
|
465
466
|
if isinstance(answer, bool):
|
|
@@ -524,7 +525,7 @@ def run(prompts: Prompts, choices: Choices, env: Environment) -> int:
|
|
|
524
525
|
style = prompts.style
|
|
525
526
|
marks = {"ok": style.success("✓"), "skip": style.muted("–"), "warn": style.warning("!"), "fail": style.error("✗")}
|
|
526
527
|
prompts.line()
|
|
527
|
-
prompts.line(style.muted("
|
|
528
|
+
prompts.line(style.muted("Setting up… (a first Memgraph start downloads about 1 GB)"))
|
|
528
529
|
|
|
529
530
|
def log(text: str) -> None:
|
|
530
531
|
prompts.line(style.muted(f" {text}"))
|
|
@@ -537,10 +538,12 @@ def run(prompts: Prompts, choices: Choices, env: Environment) -> int:
|
|
|
537
538
|
outcomes = engine.run(choices, env, log=log, report=report)
|
|
538
539
|
failures = [outcome for outcome in outcomes if outcome.status == "fail"]
|
|
539
540
|
if not failures:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
541
|
+
steps = engine.next_steps(choices, env)
|
|
542
|
+
prompts.line()
|
|
543
|
+
prompts.line(style.bold(steps[0]))
|
|
544
|
+
for text in steps[1:]:
|
|
545
|
+
prompts.line(prompts.fit(text))
|
|
546
|
+
prompts.outro(style.success("Context Graph is set up."))
|
|
544
547
|
return 0
|
|
545
548
|
prompts.line()
|
|
546
549
|
prompts.line(style.error(f"{len(failures)} step(s) failed. To fix:"))
|
|
@@ -577,7 +580,7 @@ def _memgraph_summary(choices: Choices, env: Environment) -> str:
|
|
|
577
580
|
return choices.memgraph_url
|
|
578
581
|
if env.configured_url:
|
|
579
582
|
return f"{env.configured_url} (configured)"
|
|
580
|
-
return "
|
|
583
|
+
return "uses Memgraph on localhost:7687, or starts one in Docker for you"
|
|
581
584
|
|
|
582
585
|
|
|
583
586
|
def _agent_options(selected: Sequence[str], wired: Sequence[str]) -> list[_Option]:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import importlib
|
|
6
|
+
import shlex
|
|
6
7
|
import shutil
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
@@ -14,13 +15,14 @@ if TYPE_CHECKING:
|
|
|
14
15
|
from collections.abc import Callable
|
|
15
16
|
|
|
16
17
|
#: Kept in step with the plugins' bootstrap scripts and install.sh, which install the same set first.
|
|
17
|
-
BASE = "memgraph-context-graph[mcp]>=0.7.
|
|
18
|
+
BASE = "memgraph-context-graph[mcp]>=0.7.2"
|
|
18
19
|
CONNECTORS = {
|
|
19
20
|
"skills-graph": "skills-graph[memgraph-context-graph]>=0.2.1",
|
|
20
21
|
"actions-graph": "actions-graph[memgraph-context-graph]>=0.5.0",
|
|
21
22
|
"sessions-graph": "sessions-graph[memgraph-context-graph]>=0.8.1",
|
|
22
23
|
}
|
|
23
24
|
VIEWER = ("context-graph-viewer", "context-graph-viewer>=0.1.0")
|
|
25
|
+
SPACES = ("context-graph-spaces", "context-graph-spaces>=0.1.0")
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class ToolError(Exception):
|
|
@@ -51,15 +53,37 @@ def uv_tool_environment() -> bool:
|
|
|
51
53
|
uv = shutil.which("uv")
|
|
52
54
|
if uv is None:
|
|
53
55
|
return False
|
|
54
|
-
|
|
56
|
+
# --color never: FORCE_COLOR makes uv colour even captured output, and the path would no longer compare equal.
|
|
57
|
+
result = subprocess.run([uv, "--color", "never", "tool", "dir"], capture_output=True, text=True, check=False)
|
|
55
58
|
if result.returncode != 0:
|
|
56
59
|
return False
|
|
57
60
|
tool_dir = Path(result.stdout.strip()) / "memgraph-context-graph"
|
|
58
61
|
return Path(sys.prefix).resolve() == tool_dir.resolve()
|
|
59
62
|
|
|
60
63
|
|
|
61
|
-
def
|
|
62
|
-
"""
|
|
64
|
+
def requirements(*, viewer: bool = False, spaces: bool = False) -> list[str]:
|
|
65
|
+
"""The ``uv tool install`` arguments for the tool, every connector, and the optional packages.
|
|
66
|
+
|
|
67
|
+
An optional package already installed is kept: ``uv tool install`` replaces the tool's requirements with
|
|
68
|
+
what it is given, so leaving one out would remove it.
|
|
69
|
+
"""
|
|
70
|
+
argv = [BASE]
|
|
71
|
+
for requirement in CONNECTORS.values():
|
|
72
|
+
argv += ["--with", requirement]
|
|
73
|
+
if viewer or installed(VIEWER[0]):
|
|
74
|
+
argv += ["--with", VIEWER[1]]
|
|
75
|
+
if spaces or installed(SPACES[0]):
|
|
76
|
+
argv += ["--with", SPACES[1]]
|
|
77
|
+
return argv
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def install_command(*, spaces: bool = False) -> str:
|
|
81
|
+
"""The whole ``uv tool install`` command as one line a person can paste, with *spaces* added."""
|
|
82
|
+
return " ".join(["uv tool install", *map(shlex.quote, requirements(spaces=spaces))])
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def install(*, viewer: bool, log: Callable[[str], None], spaces: bool = False) -> None:
|
|
86
|
+
"""Install the tool with every connector (and the viewer, and the spaces package), keeping what is already there.
|
|
63
87
|
|
|
64
88
|
The full set is always passed: ``uv tool install`` replaces the tool's requirements with what it is
|
|
65
89
|
given, so naming only the missing package would drop the others.
|
|
@@ -70,12 +94,8 @@ def install(*, viewer: bool, log: Callable[[str], None]) -> None:
|
|
|
70
94
|
uv = shutil.which("uv")
|
|
71
95
|
if uv is None:
|
|
72
96
|
raise ToolError("uv isn't on PATH")
|
|
73
|
-
argv = [uv, "tool", "install",
|
|
74
|
-
|
|
75
|
-
argv += ["--with", requirement]
|
|
76
|
-
if viewer or installed(VIEWER[0]):
|
|
77
|
-
argv += ["--with", VIEWER[1]]
|
|
78
|
-
log(" ".join(["uv", *argv[1:]]))
|
|
97
|
+
argv = [uv, "--color", "never", "tool", "install", *requirements(viewer=viewer, spaces=spaces)]
|
|
98
|
+
log(" ".join(["uv", *argv[3:]]))
|
|
79
99
|
process = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
|
80
100
|
last = ""
|
|
81
101
|
assert process.stdout is not None
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/spaces.py
RENAMED
|
@@ -192,9 +192,11 @@ def _build(config: HookConfig) -> SpaceResolver:
|
|
|
192
192
|
if edition == EDITION_ENTERPRISE:
|
|
193
193
|
factory = _edition_factory(edition)
|
|
194
194
|
if factory is None:
|
|
195
|
+
from memgraph_context_graph.setup import tool
|
|
196
|
+
|
|
195
197
|
raise SpaceError(
|
|
196
|
-
"edition = enterprise needs the context-graph-spaces package: "
|
|
197
|
-
"
|
|
198
|
+
f"edition = enterprise needs the context-graph-spaces package. Install it, with the rest: "
|
|
199
|
+
f"{tool.install_command(spaces=True)}"
|
|
198
200
|
)
|
|
199
201
|
return factory(config)
|
|
200
202
|
raise SpaceError(f"Unknown edition {edition!r} in the config file; use one of: {', '.join(sorted(EDITIONS))}.")
|
|
@@ -149,6 +149,25 @@ def test_stop_ends_the_turn_with_the_reply_and_keeps_the_session_open():
|
|
|
149
149
|
assert PLUGIN.response_for_payload(payload) == {"continue": True}
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def test_session_end_ends_the_session():
|
|
153
|
+
link = AgentLink()
|
|
154
|
+
rec = _RecordingConnector()
|
|
155
|
+
link.add_connector(rec)
|
|
156
|
+
|
|
157
|
+
# The payload Codex sends when a session really ends (no shared daemon holding it open).
|
|
158
|
+
payload = {
|
|
159
|
+
"session_id": "s1",
|
|
160
|
+
"transcript_path": "/home/u/.codex/sessions/rollout-s1.jsonl",
|
|
161
|
+
"cwd": "/work/billing",
|
|
162
|
+
"hook_event_name": "SessionEnd",
|
|
163
|
+
"reason": "other",
|
|
164
|
+
}
|
|
165
|
+
CodexHooksAdapter(link).handle_payload(payload)
|
|
166
|
+
|
|
167
|
+
(session_end,) = rec.events
|
|
168
|
+
assert session_end.event_type == EventType.SESSION_END
|
|
169
|
+
|
|
170
|
+
|
|
152
171
|
def test_stop_records_the_turns_reply_before_the_turn_end():
|
|
153
172
|
"""The reply is the answer recall needs; the turn end that follows spawns its embedding."""
|
|
154
173
|
link = AgentLink()
|
|
@@ -107,6 +107,6 @@ def test_installer_installs_the_tool_then_hands_its_arguments_to_setup(tmp_path)
|
|
|
107
107
|
) # fmt: skip
|
|
108
108
|
|
|
109
109
|
lines = calls.read_text().splitlines()
|
|
110
|
-
assert lines[0].startswith("uv tool install memgraph-context-graph[mcp]>=")
|
|
110
|
+
assert lines[0].startswith("uv tool install --quiet memgraph-context-graph[mcp]>=")
|
|
111
111
|
assert "--with sessions-graph[memgraph-context-graph]>=" in lines[0]
|
|
112
112
|
assert lines[-1] == "memgraph-context-graph setup --no-tui codex --memgraph-url bolt://my-host:7687"
|
|
@@ -419,3 +419,146 @@ def test_a_second_run_starts_from_what_is_set_up(fake_agents, setup_home, projec
|
|
|
419
419
|
choices = ask(Prompts(term), engine.default_choices(env, cwd=project), env, preselect=False)
|
|
420
420
|
assert choices is not None
|
|
421
421
|
assert choices.memory_backend == "native"
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def test_the_uv_tool_is_recognised_even_when_the_shell_forces_colour(monkeypatch):
|
|
425
|
+
"""FORCE_COLOR makes uv colour what it prints into a pipe, which broke comparing its tool directory."""
|
|
426
|
+
import shutil
|
|
427
|
+
import subprocess
|
|
428
|
+
import sys
|
|
429
|
+
from pathlib import Path
|
|
430
|
+
|
|
431
|
+
from memgraph_context_graph.setup import tool
|
|
432
|
+
|
|
433
|
+
uv = shutil.which("uv")
|
|
434
|
+
if uv is None:
|
|
435
|
+
pytest.skip("needs uv")
|
|
436
|
+
tool_dir = subprocess.run([uv, "--color", "never", "tool", "dir"], capture_output=True, text=True, check=True)
|
|
437
|
+
monkeypatch.setenv("FORCE_COLOR", "3")
|
|
438
|
+
monkeypatch.setattr(sys, "prefix", str(Path(tool_dir.stdout.strip()) / "memgraph-context-graph"))
|
|
439
|
+
|
|
440
|
+
assert tool.uv_tool_environment()
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_grok_is_reported_with_the_trust_it_still_needs(fake_agents, setup_home, project):
|
|
444
|
+
env = engine.look_around(cwd=project)
|
|
445
|
+
choices = engine.default_choices(env, cwd=project)
|
|
446
|
+
|
|
447
|
+
grok = engine._agent(BY_RUNTIME["grok"], choices, lambda _: None)
|
|
448
|
+
|
|
449
|
+
assert grok.status == "ok"
|
|
450
|
+
assert "/hooks-trust" in grok.summary
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def test_codex_is_reported_with_the_trust_it_still_needs_and_claude_code_is_not(fake_agents, setup_home, project):
|
|
454
|
+
env = engine.look_around(cwd=project)
|
|
455
|
+
choices = engine.default_choices(env, cwd=project)
|
|
456
|
+
|
|
457
|
+
codex = engine._agent(BY_RUNTIME["codex"], choices, lambda _: None)
|
|
458
|
+
claude = engine._agent(BY_RUNTIME["claude-code"], choices, lambda _: None)
|
|
459
|
+
|
|
460
|
+
assert codex.status == "ok"
|
|
461
|
+
assert "records nothing until you trust its hooks" in codex.summary
|
|
462
|
+
assert "trust" not in claude.summary
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def test_reinstalling_the_tool_keeps_the_spaces_package_once_it_is_there(monkeypatch):
|
|
466
|
+
from memgraph_context_graph.setup import tool
|
|
467
|
+
|
|
468
|
+
monkeypatch.setattr(tool, "installed", lambda package: package == "context-graph-spaces")
|
|
469
|
+
|
|
470
|
+
argv = tool.requirements()
|
|
471
|
+
|
|
472
|
+
assert tool.BASE in argv
|
|
473
|
+
assert all(requirement in argv for requirement in tool.CONNECTORS.values())
|
|
474
|
+
assert tool.SPACES[1] in argv
|
|
475
|
+
assert tool.VIEWER[1] not in argv
|
|
476
|
+
|
|
477
|
+
monkeypatch.setattr(tool, "installed", lambda package: False)
|
|
478
|
+
assert tool.SPACES[1] not in tool.requirements()
|
|
479
|
+
assert tool.SPACES[1] in tool.requirements(spaces=True)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def test_the_enterprise_hint_names_the_whole_install_not_just_the_spaces_package(monkeypatch):
|
|
483
|
+
import pytest
|
|
484
|
+
|
|
485
|
+
from memgraph_context_graph import spaces
|
|
486
|
+
from memgraph_context_graph.adapters._identity import HookConfig
|
|
487
|
+
|
|
488
|
+
monkeypatch.setattr(spaces, "_edition_factory", lambda edition: None)
|
|
489
|
+
spaces._reset_cache()
|
|
490
|
+
|
|
491
|
+
with pytest.raises(spaces.SpaceError) as excinfo:
|
|
492
|
+
spaces.spaces_for(HookConfig(edition="enterprise"))
|
|
493
|
+
|
|
494
|
+
hint = str(excinfo.value)
|
|
495
|
+
assert "context-graph-spaces" in hint
|
|
496
|
+
assert "skills-graph" in hint and "sessions-graph" in hint
|
|
497
|
+
spaces._reset_cache()
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def test_join_installs_the_spaces_package_inside_the_uv_tool_and_not_elsewhere(monkeypatch, setup_home, capsys):
|
|
501
|
+
from memgraph_context_graph import spaces
|
|
502
|
+
from memgraph_context_graph.setup import tool
|
|
503
|
+
|
|
504
|
+
installs = []
|
|
505
|
+
monkeypatch.setattr(spaces, "_edition_factory", lambda edition: None)
|
|
506
|
+
monkeypatch.setattr(tool, "install", lambda **kwargs: installs.append(kwargs))
|
|
507
|
+
|
|
508
|
+
monkeypatch.setattr(tool, "uv_tool_environment", lambda: False)
|
|
509
|
+
spaces._reset_cache()
|
|
510
|
+
assert main(["join", "bolt://localhost:1", "ante", "--password=x"]) == 1
|
|
511
|
+
assert installs == []
|
|
512
|
+
assert "uv tool install" in capsys.readouterr().err
|
|
513
|
+
|
|
514
|
+
monkeypatch.setattr(tool, "uv_tool_environment", lambda: True)
|
|
515
|
+
spaces._reset_cache()
|
|
516
|
+
main(["join", "bolt://localhost:1", "ante", "--password=x"])
|
|
517
|
+
assert [kwargs["spaces"] for kwargs in installs] == [True]
|
|
518
|
+
spaces._reset_cache()
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def test_the_welcome_says_what_the_user_is_getting_before_any_question(fake_agents, setup_home, project):
|
|
522
|
+
from memgraph_context_graph.setup.prompts import ENTER, Prompts, ask
|
|
523
|
+
|
|
524
|
+
env = engine.look_around(cwd=project)
|
|
525
|
+
term = ScriptedTerminal(keys=[ENTER] * 5, columns=100)
|
|
526
|
+
|
|
527
|
+
ask(Prompts(term), engine.default_choices(env, cwd=project), env, preselect=False)
|
|
528
|
+
|
|
529
|
+
first_question = term.text.index("Your name")
|
|
530
|
+
for line in ("about to get a memory", "No account or cloud needed", "Nothing changes until you confirm"):
|
|
531
|
+
assert term.text.index(line) < first_question
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def test_the_last_screen_says_what_to_expect_and_only_what_applies():
|
|
535
|
+
choices = engine.Choices(
|
|
536
|
+
runtimes=["claude-code", "codex", "grok"], project_dirs=[], user_id="carol", memory_backend="context-graph"
|
|
537
|
+
)
|
|
538
|
+
env = engine.Environment(edition="community", configured_url=None, configured_user=None, found_agents=())
|
|
539
|
+
|
|
540
|
+
text = "\n".join(engine.next_steps(choices, env))
|
|
541
|
+
|
|
542
|
+
assert "Claude Code and Codex remember through the graph now." in text
|
|
543
|
+
assert "memgraph-context-graph ui" in text
|
|
544
|
+
assert "in Codex, open it and accept its hooks" in text
|
|
545
|
+
assert "in Grok, run /hooks-trust" in text
|
|
546
|
+
assert "memgraph-context-graph uninstall" in text
|
|
547
|
+
|
|
548
|
+
plain = engine.Choices(runtimes=["opencode"], project_dirs=[], user_id="carol", viewer=False)
|
|
549
|
+
quiet = "\n".join(engine.next_steps(plain, env))
|
|
550
|
+
|
|
551
|
+
assert "remember through the graph" not in quiet
|
|
552
|
+
assert "memgraph-context-graph ui" not in quiet
|
|
553
|
+
assert "accept the hooks" not in quiet
|
|
554
|
+
assert "what did we do last time" in quiet
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_every_line_of_the_welcome_and_the_last_screen_fits_a_narrow_terminal():
|
|
558
|
+
choices = engine.Choices(
|
|
559
|
+
runtimes=["claude-code", "codex", "grok"], project_dirs=[], user_id="carol", memory_backend="context-graph"
|
|
560
|
+
)
|
|
561
|
+
env = engine.Environment(edition="community", configured_url=None, configured_user=None, found_agents=())
|
|
562
|
+
|
|
563
|
+
for line in (*engine.WELCOME, *engine.next_steps(choices, env)):
|
|
564
|
+
assert len(line) <= 78, line
|
|
@@ -54,7 +54,7 @@ def _query(url: str, cypher: LiteralString) -> list[dict]:
|
|
|
54
54
|
def names():
|
|
55
55
|
"""Container names for this run, and the cleanup of whatever setup made under them."""
|
|
56
56
|
prefix = f"cg-setup-test-{uuid.uuid4().hex[:8]}"
|
|
57
|
-
made = {"own": f"{prefix}-own", "demo": f"{prefix}-demo", "other": f"{prefix}-other"}
|
|
57
|
+
made = {"own": f"{prefix}-own", "demo": f"{prefix}-demo", "other": f"{prefix}-other", "lost": f"{prefix}-lost"}
|
|
58
58
|
yield made
|
|
59
59
|
for name in made.values():
|
|
60
60
|
_docker("rm", "-f", name)
|
|
@@ -123,6 +123,22 @@ def test_our_stopped_container_comes_back_on_its_port_with_its_data(names, demo)
|
|
|
123
123
|
assert _query(url, "MATCH (n:SetupProbe) RETURN count(n) AS n") == [{"n": 1}]
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def test_a_container_that_lost_its_network_is_reattached_and_published(names):
|
|
127
|
+
port = _free_port()
|
|
128
|
+
volume = f"{names['lost']}-data:/var/lib/memgraph"
|
|
129
|
+
run = ["run", "-d", "--name", names["lost"], "-v", volume, "-p", f"127.0.0.1:{port}:7687", mg.IMAGE]
|
|
130
|
+
assert _docker(*run).returncode == 0
|
|
131
|
+
# Docker keeps the binding in the config but publishes nothing once the container has no network.
|
|
132
|
+
assert _docker("network", "disconnect", "bridge", names["lost"]).returncode == 0
|
|
133
|
+
assert _docker("restart", names["lost"]).returncode == 0
|
|
134
|
+
assert _docker("port", names["lost"], "7687/tcp").stdout.strip() == ""
|
|
135
|
+
|
|
136
|
+
found = mg.find_or_start(configured_url=None, default_port=port, container=names["lost"], log=lambda _: None)
|
|
137
|
+
|
|
138
|
+
assert found.url == mg.local_url(port)
|
|
139
|
+
assert _query(found.url, "RETURN 1 AS ok") == [{"ok": 1}]
|
|
140
|
+
|
|
141
|
+
|
|
126
142
|
def test_setup_then_uninstall_end_to_end(names, demo, fake_agents, setup_home, tmp_path, capsys):
|
|
127
143
|
"""The plain front end, the way the shim runs it without a terminal: every step, then a re-run, then undo."""
|
|
128
144
|
from memgraph_context_graph.adapters._identity import load_config
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/adr/0003-config-path-override.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/docs/research/2026-10-codex-memory.md
RENAMED
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/events.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/link.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/src/memgraph_context_graph/tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_antigravity_cli_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_claude_code_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_context_before_tool.py
RENAMED
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_copilot_cli_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.3}/tests/test_opencode_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|