memgraph-context-graph 0.7.1__tar.gz → 0.7.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.
Files changed (76) hide show
  1. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/PKG-INFO +1 -1
  2. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/command-hooks.md +1 -1
  3. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/pyproject.toml +1 -1
  4. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/codex.py +4 -3
  5. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/memory_backend.py +1 -0
  6. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/engine.py +5 -1
  7. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/memgraph.py +15 -0
  8. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/tool.py +4 -3
  9. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_codex_adapter.py +19 -0
  10. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_setup.py +31 -0
  11. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_setup_docker.py +17 -1
  12. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/.gitignore +0 -0
  13. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/CONTEXT.md +0 -0
  14. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/LICENSE +0 -0
  15. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/README.md +0 -0
  16. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/adr/0001-runtime-adapter-terminology.md +0 -0
  17. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/adr/0002-config-file-only-hook-resolution.md +0 -0
  18. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/adr/0003-config-path-override.md +0 -0
  19. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/adr/0004-user-id-in-session-start-event.md +0 -0
  20. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/adr/0005-config-file-covers-llm-key-and-subprocess-env.md +0 -0
  21. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/adr/0006-memory-tool-writes-explicit-memory.md +0 -0
  22. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/docs/research/2026-10-codex-memory.md +0 -0
  23. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/__init__.py +0 -0
  24. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/__init__.py +0 -0
  25. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/_identity.py +0 -0
  26. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/_opencode_plugin.js +0 -0
  27. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/_spec.py +0 -0
  28. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/antigravity_cli.py +0 -0
  29. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/claude.py +0 -0
  30. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/claude_code.py +0 -0
  31. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/claude_code_memory.py +0 -0
  32. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/codex_memory.py +0 -0
  33. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/copilot_cli.py +0 -0
  34. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/cursor.py +0 -0
  35. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/grok.py +0 -0
  36. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/openai.py +0 -0
  37. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/adapters/opencode.py +0 -0
  38. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/capture_log.py +0 -0
  39. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/cli.py +0 -0
  40. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/events.py +0 -0
  41. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/hooks/__init__.py +0 -0
  42. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/hooks/cli.py +0 -0
  43. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/hooks/runner.py +0 -0
  44. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/hooks/runtime_plugin.py +0 -0
  45. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/link.py +0 -0
  46. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/mcp_server.py +0 -0
  47. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/protocols.py +0 -0
  48. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/__init__.py +0 -0
  49. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/agents.py +0 -0
  50. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/cli.py +0 -0
  51. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/prompts.py +0 -0
  52. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/setup/uninstall.py +0 -0
  53. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/spaces.py +0 -0
  54. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/src/memgraph_context_graph/tools.py +0 -0
  55. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/__init__.py +0 -0
  56. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/conftest.py +0 -0
  57. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/fake_agent_cli.py +0 -0
  58. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_antigravity_cli_adapter.py +0 -0
  59. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_capture_log.py +0 -0
  60. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_claude_code_adapter.py +0 -0
  61. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_config_cli.py +0 -0
  62. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_context_before_tool.py +0 -0
  63. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_copilot_cli_adapter.py +0 -0
  64. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_cursor_adapter.py +0 -0
  65. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_dev_scripts.py +0 -0
  66. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_events.py +0 -0
  67. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_grok_adapter.py +0 -0
  68. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_hook_cli.py +0 -0
  69. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_identity.py +0 -0
  70. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_link.py +0 -0
  71. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_opencode_adapter.py +0 -0
  72. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_plugin_hooks.py +0 -0
  73. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_runner_sessions_graph_connector.py +0 -0
  74. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_runtime_plugin.py +0 -0
  75. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/tests/test_spaces.py +0 -0
  76. {memgraph_context_graph-0.7.1 → memgraph_context_graph-0.7.2}/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.7.1
3
+ Version: 0.7.2
4
4
  Summary: Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -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`) | none |
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` |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "memgraph-context-graph"
3
- version = "0.7.1"
3
+ version = "0.7.2"
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" }
@@ -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
- # Codex has no session-end hook, and Stop fires after every turn, so it is
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",
@@ -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
 
@@ -269,7 +269,11 @@ 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
+ summary = agents_mod.wire_plugin(agent, log)
273
+ if agent.runtime == "codex":
274
+ # Codex skips an untrusted plugin's hooks without a word, and `codex exec` never asks.
275
+ summary += "; Codex records nothing until you trust its hooks: open codex and accept them (/hooks)"
276
+ return Outcome(agent.label, "ok", summary)
273
277
  except agents_mod.WiringError as exc:
274
278
  return Outcome(agent.label, "fail", str(exc), fix=fix)
275
279
  if not choices.project_dirs:
@@ -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,
@@ -51,7 +51,8 @@ def uv_tool_environment() -> bool:
51
51
  uv = shutil.which("uv")
52
52
  if uv is None:
53
53
  return False
54
- result = subprocess.run([uv, "tool", "dir"], capture_output=True, text=True, check=False)
54
+ # --color never: FORCE_COLOR makes uv colour even captured output, and the path would no longer compare equal.
55
+ result = subprocess.run([uv, "--color", "never", "tool", "dir"], capture_output=True, text=True, check=False)
55
56
  if result.returncode != 0:
56
57
  return False
57
58
  tool_dir = Path(result.stdout.strip()) / "memgraph-context-graph"
@@ -70,12 +71,12 @@ def install(*, viewer: bool, log: Callable[[str], None]) -> None:
70
71
  uv = shutil.which("uv")
71
72
  if uv is None:
72
73
  raise ToolError("uv isn't on PATH")
73
- argv = [uv, "tool", "install", BASE]
74
+ argv = [uv, "--color", "never", "tool", "install", BASE]
74
75
  for requirement in CONNECTORS.values():
75
76
  argv += ["--with", requirement]
76
77
  if viewer or installed(VIEWER[0]):
77
78
  argv += ["--with", VIEWER[1]]
78
- log(" ".join(["uv", *argv[1:]]))
79
+ log(" ".join(["uv", *argv[3:]]))
79
80
  process = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
80
81
  last = ""
81
82
  assert process.stdout is not None
@@ -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()
@@ -419,3 +419,34 @@ 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_codex_is_reported_with_the_trust_it_still_needs_and_claude_code_is_not(fake_agents, setup_home, project):
444
+ env = engine.look_around(cwd=project)
445
+ choices = engine.default_choices(env, cwd=project)
446
+
447
+ codex = engine._agent(BY_RUNTIME["codex"], choices, lambda _: None)
448
+ claude = engine._agent(BY_RUNTIME["claude-code"], choices, lambda _: None)
449
+
450
+ assert codex.status == "ok"
451
+ assert "records nothing until you trust its hooks" in codex.summary
452
+ assert "trust" not in claude.summary
@@ -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