memory-seed 2.2.1__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {memory_seed-2.2.1 → memory_seed-2.3.0}/PKG-INFO +5 -5
  2. {memory_seed-2.2.1 → memory_seed-2.3.0}/README.md +4 -4
  3. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/core.py +28 -7
  4. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/agent-rules.md +3 -2
  5. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/hooks/session-log-check.py +36 -23
  6. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/project-bootstrap.md +1 -1
  7. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/code_search.md +1 -1
  8. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/data_architecture.md +1 -1
  9. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/index.md +1 -1
  10. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/local_compilation.md +1 -1
  11. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/memory_consolidation.md +1 -1
  12. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/memory_doctor.md +1 -1
  13. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/release_publishing.md +1 -1
  14. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/skills/security_triage.md +1 -1
  15. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/AGENTS.md +3 -2
  16. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/CLAUDE.md +1 -1
  17. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/GEMINI.md +1 -1
  18. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed.egg-info/PKG-INFO +5 -5
  19. {memory_seed-2.2.1 → memory_seed-2.3.0}/pyproject.toml +1 -1
  20. {memory_seed-2.2.1 → memory_seed-2.3.0}/tests/test_memory_seed.py +93 -16
  21. {memory_seed-2.2.1 → memory_seed-2.3.0}/LICENSE +0 -0
  22. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/__init__.py +0 -0
  23. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/cli.py +0 -0
  24. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/mcp_server.py +0 -0
  25. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/mcp_validate.py +0 -0
  26. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/archive/.gitkeep +0 -0
  27. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/hooks/memory-retrieval-check.py +0 -0
  28. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/seed/.memory-seed/sessions/.gitkeep +0 -0
  29. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed/semantic_cache.py +0 -0
  30. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed.egg-info/SOURCES.txt +0 -0
  31. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed.egg-info/dependency_links.txt +0 -0
  32. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed.egg-info/entry_points.txt +0 -0
  33. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed.egg-info/requires.txt +0 -0
  34. {memory_seed-2.2.1 → memory_seed-2.3.0}/memory_seed.egg-info/top_level.txt +0 -0
  35. {memory_seed-2.2.1 → memory_seed-2.3.0}/setup.cfg +0 -0
  36. {memory_seed-2.2.1 → memory_seed-2.3.0}/tests/test_mcp_server.py +0 -0
  37. {memory_seed-2.2.1 → memory_seed-2.3.0}/tests/test_mcp_validation.py +0 -0
  38. {memory_seed-2.2.1 → memory_seed-2.3.0}/tests/test_semantic_cache.py +0 -0
  39. {memory_seed-2.2.1 → memory_seed-2.3.0}/tests/test_session_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memory-seed
3
- Version: 2.2.1
3
+ Version: 2.3.0
4
4
  Summary: Portable local memory seed for file-reading AI coding agents
5
5
  Author: Jean Nathan Tshibuyi
6
6
  License: MIT
@@ -136,7 +136,7 @@ The result is a lightweight memory workflow you can understand, commit, review,
136
136
  | Agent or client | Support path |
137
137
  | --- | --- |
138
138
  | Codex | Starts from `AGENTS.md`; can use MCP when the client supports stdio MCP servers. |
139
- | Claude Code | Starts from `CLAUDE.md`; can register `memory-seed-mcp` through `uvx`. |
139
+ | Claude Code | Starts from `CLAUDE.md`; MCP server auto-registered via `uvx --from memory-seed`. |
140
140
  | Gemini CLI | Starts from `GEMINI.md`. |
141
141
  | Other file-reading agents | Start from `AGENTS.md` and follow nearest `.memory-seed/` runtime discovery. |
142
142
  | MCP-capable clients | Use `memory_search` and `memory_get_chunk` through `memory-seed-mcp --stdio`. |
@@ -200,7 +200,7 @@ GEMINI.md
200
200
 
201
201
  ## Current Version
202
202
 
203
- The current reusable control-plane version is `2.0`.
203
+ The current reusable control-plane version is `2.3`.
204
204
 
205
205
  Legacy `.AGENTS/` projects remain supported as a fallback during migration.
206
206
 
@@ -304,7 +304,7 @@ When `--force` creates backups, Memory Seed adds `.memory-seed/backups/` to the
304
304
 
305
305
  The `update` command refreshes routing files, reusable runtime procedure files, and generic skill templates by version, sourcing them **from the installed package** rather than from PyPI — upgrade the package first to get newer templates (see [Updating](#updating)). Before replacing stale reusable control-plane files, it backs them up under `.memory-seed/backups/<timestamp>/` and archives their old version under `.memory-seed/archive/<old-version>/` or `.memory-seed/archive/unknown-<timestamp>/` when the old version is missing. Generated local memory files such as `index.md`, `policy.md`, and sessions are preserved.
306
306
 
307
- Use `update --dry-run` to list the reusable control-plane targets without writing files. Current behavior is conservative but broad: dry-run lists bundled seed paths rather than calculating which files are missing or version-mismatched. The real `update` command skips files that already have the current `memory-system-version` and preserves existing `.memory-seed/` runtime files.
307
+ Use `update --dry-run` to list the reusable control-plane targets without writing files. Current behavior is conservative but broad: dry-run lists bundled seed paths rather than calculating which files are missing or version-mismatched. The real `update` command skips files already at the current `memory-system-version` or newer — so a stale installed tool never downgrades a project — and preserves existing `.memory-seed/` runtime files.
308
308
 
309
309
  The `compact` command summarises recent session activity from the nearest runtime so an agent can identify durable facts to promote into `index.md`, `policy.md`, or skills:
310
310
 
@@ -365,7 +365,7 @@ For the version distinction (`pip show memory-seed` reports the package version;
365
365
 
366
366
  Memory Seed also includes a lightweight MCP server that lets agents search local session memory through structured tool calls instead of shelling out to broad compact summaries.
367
367
 
368
- **Auto-registration:** `memory-seed init` and `memory-seed update` automatically register `memory-seed-mcp --stdio` in each supported vendor's config — `.claude/settings.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.gemini/settings.json` (Gemini CLI). No manual config is needed for projects initialised with Memory Seed.
368
+ **Auto-registration:** `memory-seed init` and `memory-seed update` automatically register `uvx --from memory-seed memory-seed-mcp --stdio` in each supported vendor's config — `.claude/settings.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.gemini/settings.json` (Gemini CLI). No manual config is needed for projects initialised with Memory Seed. The `uvx --from` form is used so the command works regardless of whether `~/.local/bin` is on the agent's PATH.
369
369
 
370
370
  If you are configuring the server manually, run it over stdio:
371
371
 
@@ -115,7 +115,7 @@ The result is a lightweight memory workflow you can understand, commit, review,
115
115
  | Agent or client | Support path |
116
116
  | --- | --- |
117
117
  | Codex | Starts from `AGENTS.md`; can use MCP when the client supports stdio MCP servers. |
118
- | Claude Code | Starts from `CLAUDE.md`; can register `memory-seed-mcp` through `uvx`. |
118
+ | Claude Code | Starts from `CLAUDE.md`; MCP server auto-registered via `uvx --from memory-seed`. |
119
119
  | Gemini CLI | Starts from `GEMINI.md`. |
120
120
  | Other file-reading agents | Start from `AGENTS.md` and follow nearest `.memory-seed/` runtime discovery. |
121
121
  | MCP-capable clients | Use `memory_search` and `memory_get_chunk` through `memory-seed-mcp --stdio`. |
@@ -179,7 +179,7 @@ GEMINI.md
179
179
 
180
180
  ## Current Version
181
181
 
182
- The current reusable control-plane version is `2.0`.
182
+ The current reusable control-plane version is `2.3`.
183
183
 
184
184
  Legacy `.AGENTS/` projects remain supported as a fallback during migration.
185
185
 
@@ -283,7 +283,7 @@ When `--force` creates backups, Memory Seed adds `.memory-seed/backups/` to the
283
283
 
284
284
  The `update` command refreshes routing files, reusable runtime procedure files, and generic skill templates by version, sourcing them **from the installed package** rather than from PyPI — upgrade the package first to get newer templates (see [Updating](#updating)). Before replacing stale reusable control-plane files, it backs them up under `.memory-seed/backups/<timestamp>/` and archives their old version under `.memory-seed/archive/<old-version>/` or `.memory-seed/archive/unknown-<timestamp>/` when the old version is missing. Generated local memory files such as `index.md`, `policy.md`, and sessions are preserved.
285
285
 
286
- Use `update --dry-run` to list the reusable control-plane targets without writing files. Current behavior is conservative but broad: dry-run lists bundled seed paths rather than calculating which files are missing or version-mismatched. The real `update` command skips files that already have the current `memory-system-version` and preserves existing `.memory-seed/` runtime files.
286
+ Use `update --dry-run` to list the reusable control-plane targets without writing files. Current behavior is conservative but broad: dry-run lists bundled seed paths rather than calculating which files are missing or version-mismatched. The real `update` command skips files already at the current `memory-system-version` or newer — so a stale installed tool never downgrades a project — and preserves existing `.memory-seed/` runtime files.
287
287
 
288
288
  The `compact` command summarises recent session activity from the nearest runtime so an agent can identify durable facts to promote into `index.md`, `policy.md`, or skills:
289
289
 
@@ -344,7 +344,7 @@ For the version distinction (`pip show memory-seed` reports the package version;
344
344
 
345
345
  Memory Seed also includes a lightweight MCP server that lets agents search local session memory through structured tool calls instead of shelling out to broad compact summaries.
346
346
 
347
- **Auto-registration:** `memory-seed init` and `memory-seed update` automatically register `memory-seed-mcp --stdio` in each supported vendor's config — `.claude/settings.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.gemini/settings.json` (Gemini CLI). No manual config is needed for projects initialised with Memory Seed.
347
+ **Auto-registration:** `memory-seed init` and `memory-seed update` automatically register `uvx --from memory-seed memory-seed-mcp --stdio` in each supported vendor's config — `.claude/settings.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.gemini/settings.json` (Gemini CLI). No manual config is needed for projects initialised with Memory Seed. The `uvx --from` form is used so the command works regardless of whether `~/.local/bin` is on the agent's PATH.
348
348
 
349
349
  If you are configuring the server manually, run it over stdio:
350
350
 
@@ -10,7 +10,7 @@ from pathlib import Path
10
10
 
11
11
  PACKAGE_ROOT = Path(__file__).resolve().parent
12
12
  SEED_ROOT = PACKAGE_ROOT / "seed"
13
- VERSION = "2.1"
13
+ VERSION = "2.3"
14
14
  MEMORY_DIR_NAME = ".memory-seed"
15
15
  LEGACY_MEMORY_DIR_NAME = ".AGENTS"
16
16
  BACKUP_IGNORE_ENTRY = ".memory-seed/backups/"
@@ -128,9 +128,10 @@ _CODEX_RETRIEVAL_COMMAND = "python3 .memory-seed/hooks/memory-retrieval-check.py
128
128
  _CURSOR_RETRIEVAL_COMMAND = "python3 .memory-seed/hooks/memory-retrieval-check.py --cursor"
129
129
  _GEMINI_RETRIEVAL_COMMAND = "python3 .memory-seed/hooks/memory-retrieval-check.py --gemini"
130
130
 
131
- _MCP_SERVER_COMMAND = "memory-seed-mcp"
132
- _MCP_SERVER_ARGS = ["--stdio"]
131
+ _MCP_SERVER_COMMAND = "uvx"
132
+ _MCP_SERVER_ARGS = ["--from", "memory-seed", "memory-seed-mcp", "--stdio"]
133
133
  _MCP_SERVER_KEY = "memory-seed"
134
+ _OWN_MCP_COMMANDS = {"uvx", "memory-seed-mcp"}
134
135
 
135
136
  BOOTSTRAP_GENERATED_FILES = [
136
137
  ".memory-seed/index.md",
@@ -364,7 +365,8 @@ def _merge_claude_mcp(target_root: Path) -> bool:
364
365
  existing = data.get("mcpServers", {}).get(_MCP_SERVER_KEY, {})
365
366
  if existing == expected:
366
367
  return False
367
- if existing and existing.get("command") != _MCP_SERVER_COMMAND:
368
+ is_ours = existing.get("command") in _OWN_MCP_COMMANDS or "memory-seed-mcp" in existing.get("args", [])
369
+ if existing and not is_ours:
368
370
  return False # a different server is using this key; don't overwrite
369
371
 
370
372
  data.setdefault("mcpServers", {})[_MCP_SERVER_KEY] = expected
@@ -393,7 +395,8 @@ def _merge_cursor_mcp(target_root: Path) -> bool:
393
395
  existing = data.get("mcpServers", {}).get(_MCP_SERVER_KEY, {})
394
396
  if existing == expected:
395
397
  return False
396
- if existing and existing.get("command") != _MCP_SERVER_COMMAND:
398
+ is_ours = existing.get("command") in _OWN_MCP_COMMANDS or "memory-seed-mcp" in existing.get("args", [])
399
+ if existing and not is_ours:
397
400
  return False # a different server is using this key; don't overwrite
398
401
 
399
402
  data.setdefault("mcpServers", {})[_MCP_SERVER_KEY] = expected
@@ -422,7 +425,8 @@ def _merge_gemini_mcp(target_root: Path) -> bool:
422
425
  existing = data.get("mcpServers", {}).get(_MCP_SERVER_KEY, {})
423
426
  if existing == expected:
424
427
  return False
425
- if existing and existing.get("command") != _MCP_SERVER_COMMAND:
428
+ is_ours = existing.get("command") in _OWN_MCP_COMMANDS or "memory-seed-mcp" in existing.get("args", [])
429
+ if existing and not is_ours:
426
430
  return False # a different server is using this key; don't overwrite
427
431
 
428
432
  data.setdefault("mcpServers", {})[_MCP_SERVER_KEY] = expected
@@ -513,7 +517,9 @@ def update_project(cwd: str | Path = ".", dry_run: bool = False) -> InitResult:
513
517
  if _is_runtime_local_file(seed_file.destination) and destination.exists():
514
518
  continue
515
519
 
516
- if destination.exists() and _read_memory_system_version(destination) == VERSION:
520
+ if destination.exists() and _version_at_least(
521
+ _read_memory_system_version(destination), VERSION
522
+ ):
517
523
  continue
518
524
 
519
525
  if destination.exists():
@@ -673,6 +679,21 @@ def _read_memory_system_version(path: Path) -> str | None:
673
679
  return match.group(1)
674
680
 
675
681
 
682
+ def _version_tuple(version: str | None) -> tuple[int, ...]:
683
+ """Dotted version -> int tuple. Missing/unparseable -> (-1,) so it always
684
+ compares older than any real version and is upgraded forward."""
685
+ if not version:
686
+ return (-1,)
687
+ try:
688
+ return tuple(int(part) for part in version.strip().split("."))
689
+ except ValueError:
690
+ return (-1,)
691
+
692
+
693
+ def _version_at_least(actual: str | None, minimum: str) -> bool:
694
+ return _version_tuple(actual) >= _version_tuple(minimum)
695
+
696
+
676
697
  def _is_runtime_local_file(destination: str) -> bool:
677
698
  reusable_runtime_files = {
678
699
  f"{MEMORY_DIR_NAME}/agent-rules.md",
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - agent-rules
@@ -292,11 +292,12 @@ Deferring or batching session log writes is a discipline failure, not an accepta
292
292
  The session file is strictly append-only and must stay in ascending time order. To guarantee this without ever reordering:
293
293
 
294
294
  - Append every new entry to the **end** of the day's file. Never insert an entry above an existing one.
295
+ - **Append each entry at the physical end of the file; never insert above an existing entry.** That is the rule; the mechanism is up to your tools. The common failure is anchor-based edit tools — if you target a line you wrote earlier, a later edit may already sit below it, so the new entry lands mid-file. Avoid it by confirming the *actual* last line immediately before appending (for example, read the file's tail); never reuse an anchor from memory. With a POSIX shell or Python, append mode (`>> file`, `open(f, 'a')`) sidesteps anchors entirely — write UTF-8 (some shells, for example PowerShell `>>`, default to other encodings).
295
296
  - The entry heading timestamp is the **actual current clock time** at the moment you write it. Read it from the system clock; never reuse a time from your context, memory, or an earlier message, and never backdate it to when the work happened.
296
297
  - Because entries are always appended with the current time, file order, write order, and timestamp order are identical. No manual reordering is ever needed or allowed.
297
298
  - If you are recording work that completed earlier in the session (you forgot, or you are catching up), still stamp the heading with the current time. If the original work time matters, state it in the entry body — do not move the entry above newer ones and do not rewrite earlier headings.
298
299
 
299
- This is the invariant the "do not rewrite old session entries" rule protects: out-of-order or backdated entries force a human to manually re-sort the log, which is exactly what append-only is meant to prevent.
300
+ This is the invariant the "do not rewrite old session entries" rule protects: out-of-order or backdated entries force a human to manually re-sort the log, which is exactly what append-only is meant to prevent. Confirming the real last line before each append — or using append mode where your environment supports it — makes this invariant mechanical rather than reliant on a remembered anchor.
300
301
 
301
302
  Detailed work logs belong in the nearest active runtime. Add a parent/root summary only when sub-project work changes parent-visible topology, shared design, release behavior, policy inheritance, cross-project dependencies, risks, or active priorities. Do not mirror sub-project logs into root memory.
302
303
 
@@ -16,16 +16,34 @@ if not d.exists():
16
16
  today = datetime.now().strftime("%Y-%m-%d")
17
17
  messages = []
18
18
 
19
- # Staleness check: nudge a session write if nothing was logged recently.
19
+ # Read today's entry timestamps once used by both checks below.
20
+ # File mtime is intentionally not used: a git commit touching the session
21
+ # file would defeat a mtime-based staleness check.
22
+ heading_re = re.compile(r"^## (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})\b")
23
+ today_file = d / f"{today}.md"
24
+ stamps = []
25
+ if today_file.exists():
26
+ for line in today_file.read_text(encoding="utf-8").splitlines():
27
+ m = heading_re.match(line)
28
+ if m:
29
+ stamps.append(f"{m.group(1)} {m.group(2)}")
30
+
31
+ # Staleness check: the most recent timestamped entry heading must be within
32
+ # the last 15 minutes.
20
33
  cutoff = datetime.now() - timedelta(minutes=15)
21
- recent = [
22
- f for f in d.glob("*.md")
23
- if datetime.fromtimestamp(f.stat().st_mtime) > cutoff
24
- ]
25
- if not recent:
34
+ latest = None
35
+ for stamp in stamps:
36
+ try:
37
+ t = datetime.strptime(stamp, "%Y-%m-%d %H:%M")
38
+ if latest is None or t > latest:
39
+ latest = t
40
+ except ValueError:
41
+ pass
42
+
43
+ if latest is None or latest < cutoff:
26
44
  messages.append(
27
45
  f"SESSION LOG REMINDER: No .memory-seed/sessions/ entry has been "
28
- f"updated in the last 15 minutes. If you completed meaningful work "
46
+ f"written in the last 15 minutes. If you completed meaningful work "
29
47
  f"this turn, append an entry to .memory-seed/sessions/{today}.md "
30
48
  f"now — before this turn ends. "
31
49
  f"For decisions, use DRAFT labels: "
@@ -34,22 +52,17 @@ if not recent:
34
52
  )
35
53
 
36
54
  # Chronology check: today's entry headings must be in non-decreasing time order.
37
- heading_re = re.compile(r"^## (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})\b")
38
- today_file = d / f"{today}.md"
39
- if today_file.exists():
40
- stamps = []
41
- for line in today_file.read_text(encoding="utf-8").splitlines():
42
- m = heading_re.match(line)
43
- if m:
44
- stamps.append(f"{m.group(1)} {m.group(2)}")
45
- if any(stamps[i] < stamps[i - 1] for i in range(1, len(stamps))):
46
- messages.append(
47
- f"SESSION LOG ORDER WARNING: Entries in "
48
- f".memory-seed/sessions/{today}.md are not in ascending time "
49
- f"order. The log is append-only: append new entries at the end "
50
- f"with the current clock time, never backdated. Do not reorder "
51
- f"existing entries unless the user asks for a repair."
52
- )
55
+ if any(stamps[i] < stamps[i - 1] for i in range(1, len(stamps))):
56
+ messages.append(
57
+ f"SESSION LOG ORDER WARNING: Entries in "
58
+ f".memory-seed/sessions/{today}.md are not in ascending time "
59
+ f"order. The log is append-only. To repair: move the out-of-order "
60
+ f"entry to the physical end of the file with the current clock "
61
+ f"time; confirm the actual last line before appending rather than "
62
+ f"reusing a remembered anchor (append mode like >> or open(f, 'a') "
63
+ f"avoids the problem where supported). Do not reorder existing "
64
+ f"entries unless the user asks for a repair."
65
+ )
53
66
 
54
67
  if not messages:
55
68
  sys.exit(0)
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - project-bootstrap
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill-registry
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - memory-seed
5
5
  - skill
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - agent-entry
5
5
  - ai-memory
@@ -46,7 +46,8 @@ When initialized memory files exist, start here:
46
46
  2. Read the active `.memory-seed/index.md` for topology, active state, and inheritance rules.
47
47
  3. Read parent `.memory-seed/policy.md` only when the active index says policy is inherited.
48
48
  4. Read the active `.memory-seed/policy.md` for behavioral constraints and local overrides.
49
- 5. Load files from `.memory-seed/skills/` only when the task matches a listed skill.
49
+ 5. Read `.memory-seed/skills/index.md` as the deterministic skill trigger registry.
50
+ 6. Load full files from `.memory-seed/skills/` only when the trigger registry matches the task.
50
51
 
51
52
  Do not read skills preemptively. Skills are lazy-loaded execution runbooks.
52
53
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - agent-entry
5
5
  - ai-memory
@@ -1,5 +1,5 @@
1
1
  ---
2
- memory-system-version: 2.1
2
+ memory-system-version: 2.3
3
3
  tags:
4
4
  - agent-entry
5
5
  - ai-memory
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memory-seed
3
- Version: 2.2.1
3
+ Version: 2.3.0
4
4
  Summary: Portable local memory seed for file-reading AI coding agents
5
5
  Author: Jean Nathan Tshibuyi
6
6
  License: MIT
@@ -136,7 +136,7 @@ The result is a lightweight memory workflow you can understand, commit, review,
136
136
  | Agent or client | Support path |
137
137
  | --- | --- |
138
138
  | Codex | Starts from `AGENTS.md`; can use MCP when the client supports stdio MCP servers. |
139
- | Claude Code | Starts from `CLAUDE.md`; can register `memory-seed-mcp` through `uvx`. |
139
+ | Claude Code | Starts from `CLAUDE.md`; MCP server auto-registered via `uvx --from memory-seed`. |
140
140
  | Gemini CLI | Starts from `GEMINI.md`. |
141
141
  | Other file-reading agents | Start from `AGENTS.md` and follow nearest `.memory-seed/` runtime discovery. |
142
142
  | MCP-capable clients | Use `memory_search` and `memory_get_chunk` through `memory-seed-mcp --stdio`. |
@@ -200,7 +200,7 @@ GEMINI.md
200
200
 
201
201
  ## Current Version
202
202
 
203
- The current reusable control-plane version is `2.0`.
203
+ The current reusable control-plane version is `2.3`.
204
204
 
205
205
  Legacy `.AGENTS/` projects remain supported as a fallback during migration.
206
206
 
@@ -304,7 +304,7 @@ When `--force` creates backups, Memory Seed adds `.memory-seed/backups/` to the
304
304
 
305
305
  The `update` command refreshes routing files, reusable runtime procedure files, and generic skill templates by version, sourcing them **from the installed package** rather than from PyPI — upgrade the package first to get newer templates (see [Updating](#updating)). Before replacing stale reusable control-plane files, it backs them up under `.memory-seed/backups/<timestamp>/` and archives their old version under `.memory-seed/archive/<old-version>/` or `.memory-seed/archive/unknown-<timestamp>/` when the old version is missing. Generated local memory files such as `index.md`, `policy.md`, and sessions are preserved.
306
306
 
307
- Use `update --dry-run` to list the reusable control-plane targets without writing files. Current behavior is conservative but broad: dry-run lists bundled seed paths rather than calculating which files are missing or version-mismatched. The real `update` command skips files that already have the current `memory-system-version` and preserves existing `.memory-seed/` runtime files.
307
+ Use `update --dry-run` to list the reusable control-plane targets without writing files. Current behavior is conservative but broad: dry-run lists bundled seed paths rather than calculating which files are missing or version-mismatched. The real `update` command skips files already at the current `memory-system-version` or newer — so a stale installed tool never downgrades a project — and preserves existing `.memory-seed/` runtime files.
308
308
 
309
309
  The `compact` command summarises recent session activity from the nearest runtime so an agent can identify durable facts to promote into `index.md`, `policy.md`, or skills:
310
310
 
@@ -365,7 +365,7 @@ For the version distinction (`pip show memory-seed` reports the package version;
365
365
 
366
366
  Memory Seed also includes a lightweight MCP server that lets agents search local session memory through structured tool calls instead of shelling out to broad compact summaries.
367
367
 
368
- **Auto-registration:** `memory-seed init` and `memory-seed update` automatically register `memory-seed-mcp --stdio` in each supported vendor's config — `.claude/settings.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.gemini/settings.json` (Gemini CLI). No manual config is needed for projects initialised with Memory Seed.
368
+ **Auto-registration:** `memory-seed init` and `memory-seed update` automatically register `uvx --from memory-seed memory-seed-mcp --stdio` in each supported vendor's config — `.claude/settings.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.gemini/settings.json` (Gemini CLI). No manual config is needed for projects initialised with Memory Seed. The `uvx --from` form is used so the command works regardless of whether `~/.local/bin` is on the agent's PATH.
369
369
 
370
370
  If you are configuring the server manually, run it over stdio:
371
371
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memory-seed"
7
- version = "2.2.1"
7
+ version = "2.3.0"
8
8
  description = "Portable local memory seed for file-reading AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -23,7 +23,17 @@ class MemorySeedTests(unittest.TestCase):
23
23
  return path
24
24
 
25
25
  def test_version_reads_reusable_control_plane_version(self):
26
- self.assertEqual(get_version(), "2.1")
26
+ self.assertEqual(get_version(), "2.3")
27
+
28
+ def test_version_at_least_orders_versions_numerically(self):
29
+ from memory_seed.core import _version_at_least
30
+
31
+ self.assertTrue(_version_at_least("2.2", "2.2")) # equal
32
+ self.assertTrue(_version_at_least("2.3", "2.2")) # newer
33
+ self.assertTrue(_version_at_least("2.10", "2.9")) # multi-digit, not string compare
34
+ self.assertFalse(_version_at_least("2.1", "2.2")) # older
35
+ self.assertFalse(_version_at_least(None, "2.2")) # missing -> treat as older
36
+ self.assertFalse(_version_at_least("garbage", "2.2")) # unparseable -> older
27
37
 
28
38
  def test_init_dry_run_reports_seed_files_without_writing(self):
29
39
  cwd = self.make_project()
@@ -90,7 +100,7 @@ class MemorySeedTests(unittest.TestCase):
90
100
  self.assertTrue(result.backed_up[0].startswith(".memory-seed/backups/"))
91
101
  self.assertEqual((cwd / result.backed_up[0]).read_text(encoding="utf-8"), "existing")
92
102
  self.assertIn(
93
- "memory-system-version: 2.1",
103
+ "memory-system-version: 2.3",
94
104
  (cwd / "AGENTS.md").read_text(encoding="utf-8"),
95
105
  )
96
106
  self.assertIn(".memory-seed/backups/", (cwd / ".gitignore").read_text(encoding="utf-8"))
@@ -111,7 +121,7 @@ class MemorySeedTests(unittest.TestCase):
111
121
  init_project(cwd=cwd)
112
122
  gemini = cwd / "GEMINI.md"
113
123
  gemini.write_text(
114
- gemini.read_text(encoding="utf-8").replace("2.1", "1.1"),
124
+ gemini.read_text(encoding="utf-8").replace("2.3", "1.1"),
115
125
  encoding="utf-8",
116
126
  )
117
127
  (cwd / "CLAUDE.md").unlink()
@@ -128,7 +138,7 @@ class MemorySeedTests(unittest.TestCase):
128
138
  )
129
139
  self.assertEqual(
130
140
  result.version_mismatches,
131
- [{"file": "GEMINI.md", "expected": "2.1", "actual": "1.1"}],
141
+ [{"file": "GEMINI.md", "expected": "2.3", "actual": "1.1"}],
132
142
  )
133
143
 
134
144
  def test_doctor_distinguishes_bootstrap_completeness_from_control_plane_health(self):
@@ -211,11 +221,11 @@ class MemorySeedTests(unittest.TestCase):
211
221
  self.assertIn("CLAUDE.md", result.created)
212
222
  self.assertTrue(any(path.endswith("/AGENTS.md") for path in result.backed_up))
213
223
  self.assertIn(
214
- "memory-system-version: 2.1",
224
+ "memory-system-version: 2.3",
215
225
  (cwd / "AGENTS.md").read_text(encoding="utf-8"),
216
226
  )
217
227
  self.assertIn(
218
- "memory-system-version: 2.1",
228
+ "memory-system-version: 2.3",
219
229
  (cwd / "CLAUDE.md").read_text(encoding="utf-8"),
220
230
  )
221
231
  self.assertEqual(
@@ -229,7 +239,7 @@ class MemorySeedTests(unittest.TestCase):
229
239
  init_project(cwd=cwd)
230
240
  agents = cwd / "AGENTS.md"
231
241
  agents.write_text(
232
- agents.read_text(encoding="utf-8").replace("2.1", "1.4"),
242
+ agents.read_text(encoding="utf-8").replace("2.3", "1.4"),
233
243
  encoding="utf-8",
234
244
  )
235
245
 
@@ -241,6 +251,26 @@ class MemorySeedTests(unittest.TestCase):
241
251
  self.assertTrue(archived.exists())
242
252
  self.assertIn("memory-system-version: 1.4", archived.read_text(encoding="utf-8"))
243
253
 
254
+ def test_update_does_not_downgrade_newer_control_plane_files(self):
255
+ cwd = self.make_project()
256
+ init_project(cwd=cwd)
257
+ agents = cwd / "AGENTS.md"
258
+ # Simulate a project on a newer control plane than this tool ships.
259
+ agents.write_text(
260
+ agents.read_text(encoding="utf-8").replace(
261
+ f"memory-system-version: {get_version()}",
262
+ "memory-system-version: 9.9",
263
+ ),
264
+ encoding="utf-8",
265
+ )
266
+
267
+ result = update_project(cwd=cwd)
268
+
269
+ # The newer file must be left untouched: no overwrite, no archive.
270
+ self.assertIn("memory-system-version: 9.9", agents.read_text(encoding="utf-8"))
271
+ self.assertNotIn("AGENTS.md", result.created)
272
+ self.assertFalse(any("AGENTS.md" in archived for archived in result.archived))
273
+
244
274
  def test_update_archives_unknown_version_control_plane_files_under_timestamped_folder(self):
245
275
  cwd = self.make_project()
246
276
  init_project(cwd=cwd)
@@ -313,14 +343,14 @@ class MemorySeedTests(unittest.TestCase):
313
343
  self.assertTrue(
314
344
  any(path.endswith("/.memory-seed/agent-rules.md") for path in result.backed_up)
315
345
  )
316
- self.assertIn("memory-system-version: 2.1", rules.read_text(encoding="utf-8"))
346
+ self.assertIn("memory-system-version: 2.3", rules.read_text(encoding="utf-8"))
317
347
 
318
348
  def test_control_plane_files_report_current_version(self):
319
349
  for seed_file in SEED_FILES:
320
350
  if not seed_file.source.suffix == ".md":
321
351
  continue
322
352
  content = seed_file.source.read_text(encoding="utf-8")
323
- self.assertIn("memory-system-version: 2.1", content, seed_file.destination)
353
+ self.assertIn("memory-system-version: 2.3", content, seed_file.destination)
324
354
 
325
355
  def test_seed_files_use_memory_seed_runtime(self):
326
356
  destinations = sorted(seed_file.destination for seed_file in SEED_FILES)
@@ -593,6 +623,51 @@ class SessionLogOrderingHookTests(unittest.TestCase):
593
623
  )
594
624
  self.assertNotIn("ORDER WARNING", self._run(cwd))
595
625
 
626
+ def test_staleness_fires_when_no_session_file(self):
627
+ cwd = self.make_project()
628
+ out = self._run(cwd)
629
+ self.assertIn("SESSION LOG REMINDER", out)
630
+
631
+ def test_staleness_fires_when_last_entry_is_old(self):
632
+ import datetime
633
+
634
+ cwd = self.make_project()
635
+ today = datetime.date.today().isoformat()
636
+ (cwd / ".memory-seed" / "sessions" / f"{today}.md").write_text(
637
+ f"## {today} 01:00 - old entry\n\ntext\n",
638
+ encoding="utf-8",
639
+ )
640
+ self.assertIn("SESSION LOG REMINDER", self._run(cwd))
641
+
642
+ def test_staleness_silent_when_recent_entry(self):
643
+ import datetime
644
+
645
+ cwd = self.make_project()
646
+ now = datetime.datetime.now()
647
+ today = now.strftime("%Y-%m-%d")
648
+ recent_time = now.strftime("%H:%M")
649
+ (cwd / ".memory-seed" / "sessions" / f"{today}.md").write_text(
650
+ f"## {today} {recent_time} - recent entry\n\ntext\n",
651
+ encoding="utf-8",
652
+ )
653
+ self.assertNotIn("SESSION LOG REMINDER", self._run(cwd))
654
+
655
+ def test_staleness_not_defeated_by_file_mtime(self):
656
+ import datetime
657
+ import os
658
+
659
+ cwd = self.make_project()
660
+ today = datetime.date.today().isoformat()
661
+ session_file = cwd / ".memory-seed" / "sessions" / f"{today}.md"
662
+ session_file.write_text(
663
+ f"## {today} 01:00 - old entry\n\ntext\n",
664
+ encoding="utf-8",
665
+ )
666
+ # Touch the file to update mtime to now — simulating what git commit does.
667
+ os.utime(session_file, None)
668
+ # Staleness check should still fire because the entry heading is old.
669
+ self.assertIn("SESSION LOG REMINDER", self._run(cwd))
670
+
596
671
 
597
672
  class McpMergeTests(unittest.TestCase):
598
673
  def make_project(self):
@@ -609,8 +684,8 @@ class McpMergeTests(unittest.TestCase):
609
684
  data = json.loads((cwd / ".claude" / "settings.json").read_text())
610
685
  self.assertIn("memory-seed", data["mcpServers"])
611
686
  entry = data["mcpServers"]["memory-seed"]
612
- self.assertEqual(entry["command"], "memory-seed-mcp")
613
- self.assertEqual(entry["args"], ["--stdio"])
687
+ self.assertEqual(entry["command"], "uvx")
688
+ self.assertEqual(entry["args"], ["--from", "memory-seed", "memory-seed-mcp", "--stdio"])
614
689
  self.assertEqual(entry["type"], "stdio")
615
690
 
616
691
  def test_init_installs_mcp_for_cursor(self):
@@ -622,8 +697,8 @@ class McpMergeTests(unittest.TestCase):
622
697
  data = json.loads((cwd / ".cursor" / "mcp.json").read_text())
623
698
  self.assertIn("memory-seed", data["mcpServers"])
624
699
  entry = data["mcpServers"]["memory-seed"]
625
- self.assertEqual(entry["command"], "memory-seed-mcp")
626
- self.assertEqual(entry["args"], ["--stdio"])
700
+ self.assertEqual(entry["command"], "uvx")
701
+ self.assertEqual(entry["args"], ["--from", "memory-seed", "memory-seed-mcp", "--stdio"])
627
702
  self.assertNotIn("type", entry)
628
703
 
629
704
  def test_init_installs_mcp_for_gemini(self):
@@ -635,8 +710,8 @@ class McpMergeTests(unittest.TestCase):
635
710
  data = json.loads((cwd / ".gemini" / "settings.json").read_text())
636
711
  self.assertIn("memory-seed", data["mcpServers"])
637
712
  entry = data["mcpServers"]["memory-seed"]
638
- self.assertEqual(entry["command"], "memory-seed-mcp")
639
- self.assertEqual(entry["args"], ["--stdio"])
713
+ self.assertEqual(entry["command"], "uvx")
714
+ self.assertEqual(entry["args"], ["--from", "memory-seed", "memory-seed-mcp", "--stdio"])
640
715
 
641
716
  def test_mcp_merges_are_idempotent(self):
642
717
  from memory_seed.core import (
@@ -669,7 +744,9 @@ class McpMergeTests(unittest.TestCase):
669
744
  self.assertTrue(result)
670
745
 
671
746
  data = json.loads(settings.read_text())
672
- self.assertEqual(data["mcpServers"]["memory-seed"]["args"], ["--stdio"])
747
+ entry = data["mcpServers"]["memory-seed"]
748
+ self.assertEqual(entry["command"], "uvx")
749
+ self.assertEqual(entry["args"], ["--from", "memory-seed", "memory-seed-mcp", "--stdio"])
673
750
 
674
751
  def test_mcp_merge_preserves_unrelated_mcp_server(self):
675
752
  import json
File without changes
File without changes