agent-memory-git 0.1.14__tar.gz → 0.1.16__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 (67) hide show
  1. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/PKG-INFO +1 -1
  2. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/pyproject.toml +1 -1
  3. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/pyproject.toml.orig +1 -1
  4. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/PROTOCOL.md +1 -1
  5. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/capture.py +72 -1
  6. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/cli.py +4 -8
  7. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/sessions.py +20 -0
  8. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/list.js +2 -2
  9. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/LICENSE +0 -0
  10. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/README.md +0 -0
  11. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/__init__.py +0 -0
  12. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/__main__.py +0 -0
  13. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/config.py +0 -0
  14. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/context.py +0 -0
  15. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/personal/2026/08/20260816-blog-publishing-k3qm.md +0 -0
  16. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/personal/2026/09/20260906-push-personal-repos-w8re.md +0 -0
  17. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/personal/2026/09/20260921-home-server-n2vb.md +0 -0
  18. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/personal/2026/10/20261004-options-first-7hcx.md +0 -0
  19. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/personal/2026/10/20261009-thesis-setup-fr6e.md +0 -0
  20. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/08/20260804-alex-profile-ujzd.md +0 -0
  21. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/08/20260804-reply-language-xdnc.md +0 -0
  22. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/08/20260812-ask-before-push-pf3d.md +0 -0
  23. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/08/20260820-tests-before-done-odzd.md +0 -0
  24. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/08/20260827-etl-orders-export-deadline-pft7.md +0 -0
  25. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/08/20260828-etl-dashboard-72ce.md +0 -0
  26. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/09/20260902-report-eta-is2j.md +0 -0
  27. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/09/20260908-shop-web-stripe-checkout-lgmx.md +0 -0
  28. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/09/20260910-shop-web-deploys-dn73.md +0 -0
  29. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/09/20260915-etl-parquet-partitioning-seh2.md +0 -0
  30. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/09/20260922-design-tokens-source-uvw7.md +0 -0
  31. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/10/20261002-shop-web-v3-freeze-5xtp.md +0 -0
  32. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo/work/2026/10/20261012-payments-runbook-4syw.md +0 -0
  33. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/demo.py +0 -0
  34. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/embed_worker.py +0 -0
  35. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/frontmatter.py +0 -0
  36. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/gitsync.py +0 -0
  37. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/hooks.py +0 -0
  38. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/importers.py +0 -0
  39. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/install.py +0 -0
  40. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/lint.py +0 -0
  41. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/profiles.py +0 -0
  42. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/select.py +0 -0
  43. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/store.py +0 -0
  44. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/ui.py +0 -0
  45. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/ui_server.py +0 -0
  46. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/update.py +0 -0
  47. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/util.py +0 -0
  48. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/app.css +0 -0
  49. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/index.html +0 -0
  50. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/api.js +0 -0
  51. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/avatar.js +0 -0
  52. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/filters.js +0 -0
  53. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/format.js +0 -0
  54. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/h.js +0 -0
  55. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/icons.js +0 -0
  56. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/main.js +0 -0
  57. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/markdown.js +0 -0
  58. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/search.js +0 -0
  59. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/store.js +0 -0
  60. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/insights.js +0 -0
  61. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/memory.js +0 -0
  62. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/overlays.js +0 -0
  63. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/profiles.js +0 -0
  64. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/settings.js +0 -0
  65. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/shell.js +0 -0
  66. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/webui/js/views/ui.js +0 -0
  67. {agent_memory_git-0.1.14 → agent_memory_git-0.1.16}/src/agent_memory/workdir.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-memory-git
3
- Version: 0.1.14
3
+ Version: 0.1.16
4
4
  Summary: Shared, git-backed memory for Claude Code and Codex.
5
5
  Keywords: claude-code,codex,memory,agents,git
6
6
  Author: Shichao Song
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-memory-git"
3
- version = "0.1.14"
3
+ version = "0.1.16"
4
4
  description = "Shared, git-backed memory for Claude Code and Codex."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-memory-git"
3
- version = "0.1.14"
3
+ version = "0.1.16"
4
4
  description = "Shared, git-backed memory for Claude Code and Codex."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  # Shared memory
2
2
 
3
- You have a persistent memory that Claude Code and Codex share. Every memory is one Markdown file in a git repository. At the start of each session a hook injects a block titled "Shared memory index": it lists the memories relevant to the current folder and repository, and gives the memories directory, this session's staging directory and the path of the `mem` command. If the active profile changes while the session runs, a new index arrives with your next message and replaces the earlier one: from then on use only its memories and paths. If that block is missing, the memory system is not active in this session; ignore this section.
3
+ You have a persistent memory that Claude Code and Codex share. Every memory is one Markdown file in a git repository. At the start of each session a hook injects a block titled "Shared memory index": it lists the memories relevant to the current folder and repository, and gives the memories directory, this session's staging directory and the path of the `mem` command. When the conversation holds more than one index, only the latest counts: use its memories and paths, its staging directory above all. A new one arrives with your next message when the active profile changes, and a session resumed, forked or continued from another starts with that session's index still in view. If that block is missing, the memory system is not active in this session; ignore this section.
4
4
 
5
5
  ## Reading
6
6
 
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
 
8
8
  from . import frontmatter, gitsync, sessions, workdir
9
9
  from .context import detect
10
- from .store import memories_dir, normalize, read, staging_dir
10
+ from .store import load_all, memories_dir, normalize, read, staging_dir
11
11
  from .util import atomic_write, id_path, log, new_id, now, now_iso
12
12
 
13
13
  SESSION_FILE = ".session.json"
@@ -124,6 +124,49 @@ def promote(data_dir, sdir, session=None):
124
124
  return count
125
125
 
126
126
 
127
+ PLACE_KEYS = ("cwd", "original_cwd", "git_root", "git_remote", "git_branch", "git_commit")
128
+
129
+
130
+ def relocated(source, ctx):
131
+ """`source` moved to the project `ctx` points at (a context from relocate()), in the order _source() writes."""
132
+ items = [(k, v) for k, v in source.items() if k not in PLACE_KEYS]
133
+ place = [(k, ctx[k]) for k in PLACE_KEYS if k != "git_commit" and ctx.get(k)]
134
+ at = next((i for i, (k, _) in enumerate(items) if k == "transcript"), len(items))
135
+ return dict(items[:at] + place + items[at:])
136
+
137
+
138
+ def rehome(data_dir, session):
139
+ """Move this session's `here` memories still filed under its throwaway folder to the project it worked in.
140
+
141
+ promote() decides from the transcript so far, which finds nothing when a memory is written before the
142
+ session touches a project folder on this machine; each later turn tries again. Returns how many moved.
143
+ """
144
+ ctx = session.get("ctx") or {}
145
+ tool, sid = session.get("tool"), session.get("session_id")
146
+ if not sid or not workdir.is_throwaway(ctx.get("cwd")):
147
+ return 0
148
+ left = [
149
+ m
150
+ for m in load_all(data_dir)
151
+ if m["scope"] == "here"
152
+ and m["source"].get("session_id") == sid
153
+ and m["source"].get("tool") == tool
154
+ and workdir.is_throwaway(m["source"].get("cwd"))
155
+ and not m["source"].get("original_cwd")
156
+ ]
157
+ if not left:
158
+ return 0
159
+ moved = relocate(ctx, session.get("transcript_path") or sessions.transcript_path(tool, sid))
160
+ if not moved.get("original_cwd"):
161
+ return 0 # still no project to point at
162
+ for m in left:
163
+ path = memories_dir(data_dir) / m["rel"]
164
+ meta, body = read(path)
165
+ meta["source"] = relocated(meta["source"], moved)
166
+ atomic_write(path, frontmatter.join(meta, body))
167
+ return len(left)
168
+
169
+
127
170
  def stamp_updates(data_dir):
128
171
  """Set `updated` on tracked memory files that were edited in place."""
129
172
  r = gitsync.git(data_dir, "status", "--porcelain", "-z", "--", "memories")
@@ -175,6 +218,7 @@ def flush(data_dir, sdir, final=False, lock_timeout=5.0):
175
218
  return False
176
219
  session = read_session(sdir)
177
220
  n = promote(data_dir, sdir, session)
221
+ rehome(data_dir, session)
178
222
  committed = commit(data_dir, session)
179
223
  if final and Path(sdir).is_dir() and not any(Path(sdir).glob("*.md")):
180
224
  shutil.rmtree(sdir, ignore_errors=True)
@@ -183,6 +227,31 @@ def flush(data_dir, sdir, final=False, lock_timeout=5.0):
183
227
  return committed
184
228
 
185
229
 
230
+ TOOLS = ("claude-code", "codex") # the prefixes of staging folder names: <tool>-<session id>
231
+
232
+
233
+ def adopt(sdir):
234
+ """Session info for a staging folder that has none, from its name and that session's transcript.
235
+
236
+ An agent can write into a staging folder that is not its session's own, such as the one named in the older
237
+ index of a session it was continued from; the folder it makes has no .session.json, and without this the
238
+ memories in it are committed with no provenance at all. They are credited to the folder's session.
239
+ """
240
+ name = Path(sdir).name
241
+ tool = next((t for t in TOOLS if name.startswith(t + "-")), None)
242
+ if not tool:
243
+ return
244
+ sid = name[len(tool) + 1 :]
245
+ transcript = sessions.transcript_path(tool, sid)
246
+ info = {"tool": tool, "session_id": sid, "transcript_path": transcript}
247
+ folder = sessions.cwd(tool, sid, transcript)
248
+ if folder:
249
+ ctx = detect(folder)
250
+ ctx.pop("git_commit", None) # HEAD now, not what the session saw
251
+ info["ctx"] = ctx
252
+ write_session(sdir, info)
253
+
254
+
186
255
  def recover_orphans(data_dir, keep=None):
187
256
  """Promote staging dirs left by sessions that ended without a final flush."""
188
257
  root = staging_dir(data_dir)
@@ -196,6 +265,8 @@ def recover_orphans(data_dir, keep=None):
196
265
  age = time.time() - (marker.stat().st_mtime if marker.exists() else sdir.stat().st_mtime)
197
266
  has_files = any(sdir.glob("*.md"))
198
267
  if has_files and age > ORPHAN_AGE:
268
+ if not marker.exists():
269
+ adopt(sdir)
199
270
  committed |= flush(data_dir, sdir, final=True, lock_timeout=1.0)
200
271
  elif not has_files and age > STALE_AGE:
201
272
  shutil.rmtree(sdir, ignore_errors=True)
@@ -129,12 +129,7 @@ def cmd_relocate(args):
129
129
  if args.dry_run:
130
130
  continue
131
131
  meta, body = store.read(mdir / m["rel"])
132
- source = dict(meta.get("source") or {})
133
- for key in ("cwd", "git_root", "git_remote", "git_branch"):
134
- source.pop(key, None)
135
- if ctx.get(key):
136
- source[key] = ctx[key]
137
- source["original_cwd"] = ctx["original_cwd"]
132
+ source = capture.relocated(dict(meta.get("source") or {}), ctx)
138
133
  if transcript:
139
134
  source.setdefault("transcript", transcript)
140
135
  meta["source"] = source
@@ -274,11 +269,12 @@ def cmd_profile(args):
274
269
  _switched(found[pid]["name"])
275
270
  _access_note()
276
271
  elif args.action == "remove":
277
- found, _ = profiles.listing()
272
+ found, before = profiles.listing()
278
273
  pid = profiles.find(args.profile, found)
279
274
  active = profiles.remove(pid)
280
275
  print(f"removed {found[pid]['name']}; {profiles.short(found[pid]['data_dir'])} is left as it is")
281
- _switched(profiles.listing()[0][active]["name"])
276
+ if active != before: # only removing the active profile switches sessions to another
277
+ _switched(profiles.listing()[0][active]["name"])
282
278
  _access_note()
283
279
  else:
284
280
  for p in profiles.summary()["profiles"]:
@@ -53,6 +53,26 @@ def transcript_path(tool, session_id):
53
53
  return str(hits[0]) if hits else ""
54
54
 
55
55
 
56
+ def cwd(tool, session_id, transcript=None):
57
+ """The folder a session ran in: Codex's thread record, or the first Claude transcript line that names one."""
58
+ if tool == "codex":
59
+ return _codex_thread(session_id, "cwd")
60
+ try:
61
+ with open(transcript, encoding="utf-8", errors="replace") as f:
62
+ for raw in f:
63
+ if '"cwd"' not in raw:
64
+ continue
65
+ try:
66
+ found = json.loads(raw).get("cwd")
67
+ except ValueError:
68
+ continue
69
+ if found:
70
+ return found
71
+ except (OSError, TypeError):
72
+ pass
73
+ return ""
74
+
75
+
56
76
  def title(tool, session_id, transcript=None):
57
77
  if tool == "codex":
58
78
  name = _codex_title(session_id)
@@ -52,7 +52,7 @@ function facet(def) {
52
52
  ${options.map((o) => {
53
53
  const on = values.includes(o.value);
54
54
  return html`<button type="button" role="menuitemcheckbox" class="${cx("menu-item", !o.count && !on && "faint")}"${attrs({ "aria-checked": String(on), "data-act": "toggle", "data-arg": `${def.key}:${o.value}`, "data-key": `${def.key}:${o.value}` })}>
55
- <span class="tick">${on ? icon("check", "", 3) : ""}</span><span class="grow">${o.label}</span><span class="count">${o.count}</span></button>`;
55
+ <span class="tick">${on ? icon("check", "", 3) : ""}</span><span class="grow truncate"${attrs({ title: o.label })}>${o.label}</span><span class="count">${o.count}</span></button>`;
56
56
  })}
57
57
  </div>
58
58
  ${values.length ? html`<div class="menu-foot"><button type="button" class="menu-wide" data-act="setList" data-arg="${def.key}">Clear ${def.label.toLowerCase()}</button></div>` : ""}`;
@@ -73,7 +73,7 @@ function added() {
73
73
  ${opts.map((o) => {
74
74
  const on = v === o.value;
75
75
  return html`<button type="button" role="radio" class="${cx("menu-item", !o.count && !on && "faint")}"${attrs({ "aria-checked": String(on), "data-act": "setAdded", "data-arg": on ? "" : o.value })}>
76
- <span class="radio">${on ? html`<i></i>` : ""}</span><span class="grow">${o.label}</span><span class="count">${o.count}</span></button>`;
76
+ <span class="radio">${on ? html`<i></i>` : ""}</span><span class="grow truncate"${attrs({ title: o.label })}>${o.label}</span><span class="count">${o.count}</span></button>`;
77
77
  })}
78
78
  </div>
79
79
  <div class="menu-range">