memgit 0.1.4__tar.gz → 0.1.5__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 (27) hide show
  1. {memgit-0.1.4 → memgit-0.1.5}/PKG-INFO +3 -3
  2. {memgit-0.1.4 → memgit-0.1.5}/README.md +2 -2
  3. {memgit-0.1.4 → memgit-0.1.5}/memgit/cli.py +63 -12
  4. {memgit-0.1.4 → memgit-0.1.5}/memgit.egg-info/PKG-INFO +3 -3
  5. {memgit-0.1.4 → memgit-0.1.5}/memgit.egg-info/SOURCES.txt +1 -0
  6. {memgit-0.1.4 → memgit-0.1.5}/pyproject.toml +1 -1
  7. memgit-0.1.5/tests/test_setup.py +78 -0
  8. {memgit-0.1.4 → memgit-0.1.5}/LICENSE +0 -0
  9. {memgit-0.1.4 → memgit-0.1.5}/memgit/__init__.py +0 -0
  10. {memgit-0.1.4 → memgit-0.1.5}/memgit/graph.py +0 -0
  11. {memgit-0.1.4 → memgit-0.1.5}/memgit/http_server.py +0 -0
  12. {memgit-0.1.4 → memgit-0.1.5}/memgit/importer.py +0 -0
  13. {memgit-0.1.4 → memgit-0.1.5}/memgit/mcp_server.py +0 -0
  14. {memgit-0.1.4 → memgit-0.1.5}/memgit/models.py +0 -0
  15. {memgit-0.1.4 → memgit-0.1.5}/memgit/repo.py +0 -0
  16. {memgit-0.1.4 → memgit-0.1.5}/memgit/scorer.py +0 -0
  17. {memgit-0.1.4 → memgit-0.1.5}/memgit/store.py +0 -0
  18. {memgit-0.1.4 → memgit-0.1.5}/memgit/tokens.py +0 -0
  19. {memgit-0.1.4 → memgit-0.1.5}/memgit/toon.py +0 -0
  20. {memgit-0.1.4 → memgit-0.1.5}/memgit.egg-info/dependency_links.txt +0 -0
  21. {memgit-0.1.4 → memgit-0.1.5}/memgit.egg-info/entry_points.txt +0 -0
  22. {memgit-0.1.4 → memgit-0.1.5}/memgit.egg-info/requires.txt +0 -0
  23. {memgit-0.1.4 → memgit-0.1.5}/memgit.egg-info/top_level.txt +0 -0
  24. {memgit-0.1.4 → memgit-0.1.5}/setup.cfg +0 -0
  25. {memgit-0.1.4 → memgit-0.1.5}/tests/test_advanced.py +0 -0
  26. {memgit-0.1.4 → memgit-0.1.5}/tests/test_store_repo.py +0 -0
  27. {memgit-0.1.4 → memgit-0.1.5}/tests/test_toon.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memgit
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Git for AI memory — version-controlled context persistence across Claude, GPT, Gemini, Cursor, Windsurf, and more
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://memgit.dev
@@ -353,11 +353,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
353
353
  - [x] `memgit git push/pull` — team sync via standard git
354
354
  - [x] Flat `memories/` directory — grep/diff/blame your memories
355
355
  - [x] D3.js graph visualization of memory relationships
356
- - [x] PyPI + Homebrew (tap) + npm published (v0.1.4)
356
+ - [x] PyPI + Homebrew (tap) + npm published (v0.1.5)
357
357
  - [ ] Chocolatey (not yet live on community.chocolatey.org)
358
358
  - [x] Interactive setup wizard (`memgit setup`)
359
359
  - [x] Smart `memgit init` (auto-detects tool, no path needed)
360
- - [x] VS Code extension (v0.1.4, Marketplace: code416-memgit.memgit)
360
+ - [x] VS Code extension (v0.1.5, Marketplace: code416-memgit.memgit)
361
361
  - [ ] JetBrains plugin (Phase 3)
362
362
  - [ ] Semantic search via embeddings (Phase 4)
363
363
  - [x] memgit.dev website (live)
@@ -322,11 +322,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
322
322
  - [x] `memgit git push/pull` — team sync via standard git
323
323
  - [x] Flat `memories/` directory — grep/diff/blame your memories
324
324
  - [x] D3.js graph visualization of memory relationships
325
- - [x] PyPI + Homebrew (tap) + npm published (v0.1.4)
325
+ - [x] PyPI + Homebrew (tap) + npm published (v0.1.5)
326
326
  - [ ] Chocolatey (not yet live on community.chocolatey.org)
327
327
  - [x] Interactive setup wizard (`memgit setup`)
328
328
  - [x] Smart `memgit init` (auto-detects tool, no path needed)
329
- - [x] VS Code extension (v0.1.4, Marketplace: code416-memgit.memgit)
329
+ - [x] VS Code extension (v0.1.5, Marketplace: code416-memgit.memgit)
330
330
  - [ ] JetBrains plugin (Phase 3)
331
331
  - [ ] Semantic search via embeddings (Phase 4)
332
332
  - [x] memgit.dev website (live)
@@ -1143,7 +1143,11 @@ def _patch_mcp_servers(config_path: Path, dry_run: bool = False) -> str:
1143
1143
  try:
1144
1144
  data = _json.loads(config_path.read_text(encoding='utf-8'))
1145
1145
  except _json.JSONDecodeError:
1146
- data = {}
1146
+ # Never clobber an existing config we can't parse — for Claude Code
1147
+ # this file (~/.claude.json) holds all user state, not just MCP.
1148
+ raise RuntimeError(
1149
+ f'{config_path} exists but is not valid JSON — fix it manually, then re-run setup'
1150
+ )
1147
1151
  else:
1148
1152
  data = {}
1149
1153
 
@@ -1189,7 +1193,11 @@ def _patch_continue(config_path: Path, dry_run: bool = False) -> str:
1189
1193
  return 'registered'
1190
1194
 
1191
1195
 
1192
- # Targets: (label, config_path_fn, patch_fn)
1196
+ # Targets: (label, config_path, patch_fn, detect_path)
1197
+ # detect_path: existence marks the tool as installed; None falls back to
1198
+ # "config file or its parent dir exists". Claude Code needs an explicit one
1199
+ # because its real MCP config (~/.claude.json) sits directly in $HOME —
1200
+ # Claude Code ignores mcpServers in ~/.claude/settings.json.
1193
1201
  def _all_targets():
1194
1202
  home = Path.home()
1195
1203
  app_support = home / 'Library' / 'Application Support'
@@ -1197,43 +1205,51 @@ def _all_targets():
1197
1205
  return [
1198
1206
  (
1199
1207
  'Claude Code',
1200
- home / '.claude' / 'settings.json',
1208
+ home / '.claude.json',
1201
1209
  _patch_mcp_servers,
1210
+ home / '.claude',
1202
1211
  ),
1203
1212
  (
1204
1213
  'Claude Desktop (macOS)',
1205
1214
  app_support / 'Claude' / 'claude_desktop_config.json',
1206
1215
  _patch_mcp_servers,
1216
+ None,
1207
1217
  ),
1208
1218
  (
1209
1219
  'Claude Desktop (Linux)',
1210
1220
  linux_config / 'Claude' / 'claude_desktop_config.json',
1211
1221
  _patch_mcp_servers,
1222
+ None,
1212
1223
  ),
1213
1224
  (
1214
1225
  'Cursor',
1215
1226
  home / '.cursor' / 'mcp.json',
1216
1227
  _patch_mcp_servers,
1228
+ None,
1217
1229
  ),
1218
1230
  (
1219
1231
  'Windsurf',
1220
1232
  home / '.windsurf' / 'mcp.json',
1221
1233
  _patch_mcp_servers,
1234
+ None,
1222
1235
  ),
1223
1236
  (
1224
1237
  'Cline (VS Code)',
1225
1238
  app_support / 'Code' / 'User' / 'globalStorage' / 'saoudrizwan.claude-dev' / 'settings' / 'cline_mcp_settings.json',
1226
1239
  _patch_mcp_servers,
1240
+ None,
1227
1241
  ),
1228
1242
  (
1229
1243
  'Roo-Code (VS Code)',
1230
1244
  app_support / 'Code' / 'User' / 'globalStorage' / 'rooveterinaryinc.roo-cline' / 'settings' / 'cline_mcp_settings.json',
1231
1245
  _patch_mcp_servers,
1246
+ None,
1232
1247
  ),
1233
1248
  (
1234
1249
  'Continue.dev',
1235
1250
  home / '.continue' / 'config.json',
1236
1251
  _patch_continue,
1252
+ None,
1237
1253
  ),
1238
1254
  ]
1239
1255
 
@@ -1243,10 +1259,13 @@ def _setup_wizard() -> None:
1243
1259
  targets = _all_targets()
1244
1260
 
1245
1261
  detected, missing = [], []
1246
- for label, config_path, patch_fn in targets:
1247
- (detected if config_path.exists() or config_path.parent.exists() else missing).append(
1248
- (label, config_path, patch_fn)
1262
+ for label, config_path, patch_fn, detect_path in targets:
1263
+ installed = (
1264
+ detect_path.exists()
1265
+ if detect_path is not None
1266
+ else config_path.exists() or config_path.parent.exists()
1249
1267
  )
1268
+ (detected if installed else missing).append((label, config_path, patch_fn))
1250
1269
 
1251
1270
  console.print('[bold]memgit setup[/bold] — interactive tool registration\n')
1252
1271
 
@@ -1327,6 +1346,33 @@ def _run_target(label: str, config_path: Path, patch_fn, dry_run: bool) -> None:
1327
1346
  console.print(f'[red]✗[/red] {label}: {e}')
1328
1347
 
1329
1348
 
1349
+ def _cleanup_legacy_claude_code(dry_run: bool = False) -> None:
1350
+ """Drop the memgit entry from ~/.claude/settings.json if present.
1351
+
1352
+ Releases before 0.1.5 registered there, but Claude Code only loads MCP
1353
+ servers from ~/.claude.json — the old entry is dead weight.
1354
+ """
1355
+ legacy = Path.home() / '.claude' / 'settings.json'
1356
+ if not legacy.exists():
1357
+ return
1358
+ try:
1359
+ data = _json.loads(legacy.read_text(encoding='utf-8'))
1360
+ except _json.JSONDecodeError:
1361
+ return
1362
+ servers = data.get('mcpServers')
1363
+ if not isinstance(servers, dict) or 'memgit' not in servers:
1364
+ return
1365
+ servers.pop('memgit')
1366
+ if not servers:
1367
+ data.pop('mcpServers', None)
1368
+ if not dry_run:
1369
+ _write_json_safe(legacy, data)
1370
+ console.print(
1371
+ f'[yellow]↻[/yellow] removed stale memgit entry from {legacy} '
1372
+ f'[dim](Claude Code ignores mcpServers there)[/dim]'
1373
+ )
1374
+
1375
+
1330
1376
  @setup.command('all')
1331
1377
  @click.option('--dry-run', is_flag=True, help='Show what would be changed without writing files.')
1332
1378
  def setup_all(dry_run):
@@ -1340,14 +1386,18 @@ def setup_all(dry_run):
1340
1386
 
1341
1387
  registered = 0
1342
1388
  skipped = 0
1343
- for label, config_path, patch_fn in _all_targets():
1344
- # Skip if neither the file nor its parent directory exists
1345
- # (tool not installed on this machine)
1346
- if not config_path.exists() and not config_path.parent.exists():
1389
+ for label, config_path, patch_fn, detect_path in _all_targets():
1390
+ installed = (
1391
+ detect_path.exists()
1392
+ if detect_path is not None
1393
+ else config_path.exists() or config_path.parent.exists()
1394
+ )
1395
+ if not installed:
1347
1396
  skipped += 1
1348
1397
  continue
1349
1398
  _run_target(label, config_path, patch_fn, dry_run)
1350
1399
  registered += 1
1400
+ _cleanup_legacy_claude_code(dry_run)
1351
1401
 
1352
1402
  console.print(f'\n[dim]{registered} tool(s) processed, {skipped} not installed (skipped)[/dim]')
1353
1403
  if not dry_run and registered:
@@ -1357,9 +1407,10 @@ def setup_all(dry_run):
1357
1407
  @setup.command('claude-code')
1358
1408
  @click.option('--dry-run', is_flag=True)
1359
1409
  def setup_claude_code(dry_run):
1360
- """Register with Claude Code (~/.claude/settings.json)."""
1361
- path = Path.home() / '.claude' / 'settings.json'
1410
+ """Register with Claude Code (~/.claude.json, user scope)."""
1411
+ path = Path.home() / '.claude.json'
1362
1412
  _run_target('Claude Code', path, _patch_mcp_servers, dry_run)
1413
+ _cleanup_legacy_claude_code(dry_run)
1363
1414
 
1364
1415
 
1365
1416
  @setup.command('claude-desktop')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memgit
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Git for AI memory — version-controlled context persistence across Claude, GPT, Gemini, Cursor, Windsurf, and more
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://memgit.dev
@@ -353,11 +353,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
353
353
  - [x] `memgit git push/pull` — team sync via standard git
354
354
  - [x] Flat `memories/` directory — grep/diff/blame your memories
355
355
  - [x] D3.js graph visualization of memory relationships
356
- - [x] PyPI + Homebrew (tap) + npm published (v0.1.4)
356
+ - [x] PyPI + Homebrew (tap) + npm published (v0.1.5)
357
357
  - [ ] Chocolatey (not yet live on community.chocolatey.org)
358
358
  - [x] Interactive setup wizard (`memgit setup`)
359
359
  - [x] Smart `memgit init` (auto-detects tool, no path needed)
360
- - [x] VS Code extension (v0.1.4, Marketplace: code416-memgit.memgit)
360
+ - [x] VS Code extension (v0.1.5, Marketplace: code416-memgit.memgit)
361
361
  - [ ] JetBrains plugin (Phase 3)
362
362
  - [ ] Semantic search via embeddings (Phase 4)
363
363
  - [x] memgit.dev website (live)
@@ -20,5 +20,6 @@ memgit.egg-info/entry_points.txt
20
20
  memgit.egg-info/requires.txt
21
21
  memgit.egg-info/top_level.txt
22
22
  tests/test_advanced.py
23
+ tests/test_setup.py
23
24
  tests/test_store_repo.py
24
25
  tests/test_toon.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memgit"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "Git for AI memory — version-controlled context persistence across Claude, GPT, Gemini, Cursor, Windsurf, and more"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,78 @@
1
+ """Tests for `memgit setup` MCP registration paths."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from memgit import cli as cli_mod
9
+ from memgit.cli import (
10
+ _all_targets,
11
+ _cleanup_legacy_claude_code,
12
+ _patch_mcp_servers,
13
+ )
14
+
15
+
16
+ @pytest.fixture
17
+ def fake_home(tmp_path, monkeypatch):
18
+ monkeypatch.setattr(Path, 'home', classmethod(lambda cls: tmp_path))
19
+ return tmp_path
20
+
21
+
22
+ def test_claude_code_target_is_claude_json(fake_home):
23
+ """Claude Code loads MCP servers from ~/.claude.json, NOT ~/.claude/settings.json."""
24
+ targets = {label: (config, detect) for label, config, _, detect in _all_targets()}
25
+ config, detect = targets['Claude Code']
26
+ assert config == fake_home / '.claude.json'
27
+ assert detect == fake_home / '.claude'
28
+
29
+
30
+ def test_patch_registers_and_is_idempotent(fake_home):
31
+ config = fake_home / '.claude.json'
32
+ assert _patch_mcp_servers(config) == 'registered'
33
+ data = json.loads(config.read_text())
34
+ assert 'memgit' in data['mcpServers']
35
+ assert data['mcpServers']['memgit']['args'][-1] == 'serve'
36
+ assert _patch_mcp_servers(config) == 'already registered'
37
+
38
+
39
+ def test_patch_preserves_existing_state(fake_home):
40
+ """~/.claude.json holds all Claude Code user state — never drop other keys."""
41
+ config = fake_home / '.claude.json'
42
+ config.write_text(json.dumps({'projects': {'/x': {}}, 'mcpServers': {'other': {'command': 'x'}}}))
43
+ _patch_mcp_servers(config)
44
+ data = json.loads(config.read_text())
45
+ assert data['projects'] == {'/x': {}}
46
+ assert set(data['mcpServers']) == {'other', 'memgit'}
47
+
48
+
49
+ def test_patch_refuses_to_clobber_invalid_json(fake_home):
50
+ config = fake_home / '.claude.json'
51
+ config.write_text('{not json')
52
+ with pytest.raises(RuntimeError, match='not valid JSON'):
53
+ _patch_mcp_servers(config)
54
+ assert config.read_text() == '{not json'
55
+
56
+
57
+ def test_cleanup_removes_legacy_entry(fake_home):
58
+ legacy = fake_home / '.claude' / 'settings.json'
59
+ legacy.parent.mkdir()
60
+ legacy.write_text(json.dumps({'theme': 'dark', 'mcpServers': {'memgit': {'command': 'memgit'}}}))
61
+ _cleanup_legacy_claude_code()
62
+ data = json.loads(legacy.read_text())
63
+ assert data['theme'] == 'dark'
64
+ assert 'mcpServers' not in data
65
+
66
+
67
+ def test_cleanup_keeps_other_servers(fake_home):
68
+ legacy = fake_home / '.claude' / 'settings.json'
69
+ legacy.parent.mkdir()
70
+ legacy.write_text(json.dumps({'mcpServers': {'memgit': {}, 'other': {'command': 'x'}}}))
71
+ _cleanup_legacy_claude_code()
72
+ data = json.loads(legacy.read_text())
73
+ assert set(data['mcpServers']) == {'other'}
74
+
75
+
76
+ def test_cleanup_noop_without_legacy_file(fake_home):
77
+ _cleanup_legacy_claude_code() # must not raise or create files
78
+ assert not (fake_home / '.claude' / 'settings.json').exists()
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