graph-mind-memory 0.6.2__tar.gz → 0.6.4__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 (69) hide show
  1. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/PKG-INFO +5 -1
  2. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/README.md +4 -0
  3. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/pyproject.toml +1 -1
  4. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/brain_log.py +41 -9
  5. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/brain_view.py +7 -0
  6. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_memory.egg-info/PKG-INFO +5 -1
  7. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_memory.egg-info/SOURCES.txt +1 -0
  8. graph_mind_memory-0.6.4/tests/test_hub_retry.py +33 -0
  9. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/LICENSE +0 -0
  10. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/setup.cfg +0 -0
  11. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/associative_memory.py +0 -0
  12. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/automatic_capture.py +0 -0
  13. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/automatic_capture_cli.py +0 -0
  14. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/clock.py +0 -0
  15. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/coding_memory.py +0 -0
  16. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/conversation_memory.py +0 -0
  17. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/development_memory.py +0 -0
  18. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/development_paths.py +0 -0
  19. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/embedding_warmup.py +0 -0
  20. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/entity_timeline.py +0 -0
  21. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/forget.py +0 -0
  22. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_mcp_server.py +0 -0
  23. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_memory.egg-info/dependency_links.txt +0 -0
  24. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_memory.egg-info/entry_points.txt +0 -0
  25. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_memory.egg-info/requires.txt +0 -0
  26. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/graph_mind_memory.egg-info/top_level.txt +0 -0
  27. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/install.py +0 -0
  28. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/local_brain.py +0 -0
  29. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/local_embedder.py +0 -0
  30. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/semantic_llm_extractor.py +0 -0
  31. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/semantic_memory.py +0 -0
  32. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/semantic_recall.py +0 -0
  33. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/temporal_resolver.py +0 -0
  34. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/universal_personal_memory.py +0 -0
  35. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/src/vector_cache.py +0 -0
  36. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_associative_memory.py +0 -0
  37. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_automatic_capture.py +0 -0
  38. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_brain_view.py +0 -0
  39. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_capture_health.py +0 -0
  40. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_coding_memory.py +0 -0
  41. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_cold_start.py +0 -0
  42. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_concurrent_use.py +0 -0
  43. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_conversation_memory.py +0 -0
  44. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_executable_memory.py +0 -0
  45. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_executable_plan_v2_diagnostic.py +0 -0
  46. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_forget.py +0 -0
  47. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_forget_conversation.py +0 -0
  48. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_graph_mind_mcp_server.py +0 -0
  49. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_ingestion_gate.py +0 -0
  50. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_ingestion_synonyms.py +0 -0
  51. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_install.py +0 -0
  52. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_llm_concept_planner.py +0 -0
  53. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_local_brain.py +0 -0
  54. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_longmemeval_adapter.py +0 -0
  55. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_longmemeval_ingestion_calibration.py +0 -0
  56. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_longmemeval_ingestion_preflight.py +0 -0
  57. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_longmemeval_ingestion_v7.py +0 -0
  58. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_memory_algebra.py +0 -0
  59. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_piece_index.py +0 -0
  60. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_precondition_verifier.py +0 -0
  61. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_recent_recall.py +0 -0
  62. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_resume_retries.py +0 -0
  63. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_secret_masking.py +0 -0
  64. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_semantic_llm_extractor.py +0 -0
  65. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_semantic_memory.py +0 -0
  66. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_semantic_recall.py +0 -0
  67. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_shared_brain.py +0 -0
  68. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_temporal_state.py +0 -0
  69. {graph_mind_memory-0.6.2 → graph_mind_memory-0.6.4}/tests/test_universal_personal_memory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-mind-memory
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Local-first memory for AI coding assistants: verbatim, on your own PC, no LLM calls at write time.
5
5
  Author-email: Yohan Ko <goyohan0611@gmail.com>
6
6
  License-Expression: AGPL-3.0-only
@@ -246,6 +246,10 @@ tools get confused with each other. Version 0.1 had thirteen.
246
246
 
247
247
  ## See your memory
248
248
 
249
+ <div align="center">
250
+ <img src="demo/brain.png" alt="Three weeks of real coding chats as a 3D graph: coloured regions of related memories, joined by links" width="900">
251
+ </div>
252
+
249
253
  ```bash
250
254
  graph-mind-view # opens your memory as a 3D graph in the browser
251
255
  graph-mind-view --no-text # the same picture with no conversation text in the file
@@ -218,6 +218,10 @@ tools get confused with each other. Version 0.1 had thirteen.
218
218
 
219
219
  ## See your memory
220
220
 
221
+ <div align="center">
222
+ <img src="demo/brain.png" alt="Three weeks of real coding chats as a 3D graph: coloured regions of related memories, joined by links" width="900">
223
+ </div>
224
+
221
225
  ```bash
222
226
  graph-mind-view # opens your memory as a 3D graph in the browser
223
227
  graph-mind-view --no-text # the same picture with no conversation text in the file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "graph-mind-memory"
7
- version = "0.6.2"
7
+ version = "0.6.4"
8
8
  description = "Local-first memory for AI coding assistants: verbatim, on your own PC, no LLM calls at write time."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -35,6 +35,7 @@ import os
35
35
  import platform
36
36
  import re
37
37
  import sqlite3
38
+ import subprocess
38
39
 
39
40
  KINDS = ("memory", "turn", "forget") # forget: ids another PC erased (forget.py)
40
41
 
@@ -105,12 +106,14 @@ def local_postgres() -> str:
105
106
  with fasteners.InterProcessLock(str(data.parent / "postgres.lock")):
106
107
  fresh = not (data / "PG_VERSION").exists()
107
108
  if fresh:
108
- initdb(["--auth=trust", "--encoding=utf8", "-U", "postgres"], pgdata=data,
109
- **_DETACHED)
110
- running = PostmasterInfo.read_from_pgdata(data)
111
- if running is None or not running.is_running():
112
- pg_ctl(["-w", "-o", f"-p {PORT}", "-l", str(data / "log"), "start"],
113
- pgdata=data, timeout=60, **_DETACHED)
109
+ _initdb_with_retries(initdb, data)
110
+
111
+ def start():
112
+ running = PostmasterInfo.read_from_pgdata(data)
113
+ if running is None or not running.is_running(): # a failed try may have
114
+ pg_ctl(["-w", "-o", f"-p {PORT}", "-l", str(data / "log"), "start"],
115
+ pgdata=data, timeout=60, **_DETACHED) # started it anyway
116
+ _retry(start, attempts=10)
114
117
  if fresh:
115
118
  import psycopg
116
119
  with psycopg.connect(_admin_uri(), autocommit=True) as admin:
@@ -120,11 +123,40 @@ def local_postgres() -> str:
120
123
  return _LOCAL_URI["uri"]
121
124
 
122
125
 
126
+ def _retry(step, attempts, undo=None):
127
+ """Postgres's own tools fail at random on some Windows PCs: on one with AhnLab V3 running,
128
+ initdb succeeded in 3-6 of 10 identical runs ("popen failure: Bad file descriptor" when
129
+ its pipes to postgres broke), whichever way it was launched, and pg_ctl start failed too.
130
+ Every attempt is independent, so trying again is the fix: ten attempts at even odds fail
131
+ together 0.1% of the time."""
132
+ import time
133
+ for attempt in range(attempts):
134
+ try:
135
+ return step()
136
+ except subprocess.CalledProcessError:
137
+ if undo:
138
+ undo()
139
+ if attempt == attempts - 1:
140
+ raise
141
+ time.sleep(0.5)
142
+
143
+
144
+ def _initdb_with_retries(initdb, data):
145
+ """Create the cluster, once per PC. A failed run leaves the folder half made; clear it."""
146
+ import shutil
147
+ _retry(lambda: initdb(["--auth=trust", "--encoding=utf8", "-U", "postgres"], pgdata=data,
148
+ **_DETACHED),
149
+ attempts=10, undo=lambda: shutil.rmtree(data, ignore_errors=True))
150
+
151
+
123
152
  # Started from Claude's or Codex's process, the server shared that program's console, and closing
124
153
  # it sent Ctrl+C to Postgres too (0xC000013A in its log): the hub went down with an app window.
125
- # Its own process group, with no console, it outlives whoever happened to start it.
126
- _DETACHED = ({"creationflags": 0x00000008 | 0x00000200} # DETACHED_PROCESS | NEW_PROCESS_GROUP
127
- if os.name == "nt" else {"start_new_session": True})
154
+ # Its own process group, with no console, it outlives whoever happened to start it. Its stdin is
155
+ # the null device: Postgres must not hold on to the caller's (an MCP server's is the pipe its app
156
+ # talks through), and a caller may have none at all (pythonw).
157
+ _DETACHED = ({"creationflags": 0x00000008 | 0x00000200, # DETACHED_PROCESS | NEW_PROCESS_GROUP
158
+ "stdin": subprocess.DEVNULL}
159
+ if os.name == "nt" else {"start_new_session": True, "stdin": subprocess.DEVNULL})
128
160
 
129
161
 
130
162
  def _admin_uri() -> str:
@@ -197,6 +197,13 @@ const centers = regions.map((_, i) => {
197
197
  const y = 1 - 2 * (i + 0.5) / regions.length, r = Math.sqrt(1 - y * y), t = i * 2.39996;
198
198
  return {x: 380 * r * Math.cos(t), y: 380 * y, z: 380 * r * Math.sin(t)};
199
199
  });
200
+ // start every node near its region's point, so the lobes show from the first frame instead of
201
+ // depending on how long the browser lets the layout run (a background tab barely runs it)
202
+ DATA.nodes.forEach(n => {
203
+ const c = n.region >= 0 ? centers[n.region] : {x: 0, y: 0, z: 0}, s = 70;
204
+ n.x = c.x + (Math.random() - 0.5) * s; n.y = c.y + (Math.random() - 0.5) * s;
205
+ n.z = c.z + (Math.random() - 0.5) * s;
206
+ });
200
207
  const lobes = alpha => graph.graphData().nodes.forEach(n => {
201
208
  const c = n.region >= 0 ? centers[n.region] : {x: 0, y: 0, z: 0}, k = n.region >= 0 ? 0.35 : 0.05;
202
209
  n.vx += (c.x - n.x) * k * alpha; n.vy += (c.y - n.y) * k * alpha; n.vz += (c.z - n.z) * k * alpha;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-mind-memory
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Local-first memory for AI coding assistants: verbatim, on your own PC, no LLM calls at write time.
5
5
  Author-email: Yohan Ko <goyohan0611@gmail.com>
6
6
  License-Expression: AGPL-3.0-only
@@ -246,6 +246,10 @@ tools get confused with each other. Version 0.1 had thirteen.
246
246
 
247
247
  ## See your memory
248
248
 
249
+ <div align="center">
250
+ <img src="demo/brain.png" alt="Three weeks of real coding chats as a 3D graph: coloured regions of related memories, joined by links" width="900">
251
+ </div>
252
+
249
253
  ```bash
250
254
  graph-mind-view # opens your memory as a 3D graph in the browser
251
255
  graph-mind-view --no-text # the same picture with no conversation text in the file
@@ -43,6 +43,7 @@ tests/test_executable_plan_v2_diagnostic.py
43
43
  tests/test_forget.py
44
44
  tests/test_forget_conversation.py
45
45
  tests/test_graph_mind_mcp_server.py
46
+ tests/test_hub_retry.py
46
47
  tests/test_ingestion_gate.py
47
48
  tests/test_ingestion_synonyms.py
48
49
  tests/test_install.py
@@ -0,0 +1,33 @@
1
+ """Postgres's tools fail at random on some Windows PCs (AhnLab V3): hub setup tries again."""
2
+ import _isolate # noqa: F401 (first: never touch the real ~/.graph-mind)
3
+ import subprocess
4
+ import unittest
5
+ from unittest import mock
6
+
7
+ import brain_log
8
+
9
+
10
+ class HubRetryTests(unittest.TestCase):
11
+ def test_a_flaky_step_is_tried_again_and_cleaned_up_between(self):
12
+ calls, undone = [], []
13
+
14
+ def flaky():
15
+ calls.append(1)
16
+ if len(calls) < 3:
17
+ raise subprocess.CalledProcessError(1, "initdb")
18
+ return "ok"
19
+
20
+ with mock.patch("time.sleep"):
21
+ self.assertEqual(brain_log._retry(flaky, attempts=10, undo=lambda: undone.append(1)),
22
+ "ok")
23
+ self.assertEqual((len(calls), len(undone)), (3, 2))
24
+
25
+ def test_gives_up_after_the_last_attempt(self):
26
+ def broken():
27
+ raise subprocess.CalledProcessError(1, "pg_ctl")
28
+ with mock.patch("time.sleep"), self.assertRaises(subprocess.CalledProcessError):
29
+ brain_log._retry(broken, attempts=3)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ unittest.main()