memgit 0.6.0__tar.gz → 0.6.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 (47) hide show
  1. {memgit-0.6.0 → memgit-0.6.2}/PKG-INFO +1 -1
  2. {memgit-0.6.0 → memgit-0.6.2}/memgit/__init__.py +1 -1
  3. {memgit-0.6.0 → memgit-0.6.2}/memgit/hooks.py +17 -6
  4. {memgit-0.6.0 → memgit-0.6.2}/memgit/links.py +13 -8
  5. {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/PKG-INFO +1 -1
  6. {memgit-0.6.0 → memgit-0.6.2}/pyproject.toml +1 -1
  7. {memgit-0.6.0 → memgit-0.6.2}/tests/test_v060.py +33 -0
  8. {memgit-0.6.0 → memgit-0.6.2}/LICENSE +0 -0
  9. {memgit-0.6.0 → memgit-0.6.2}/README.md +0 -0
  10. {memgit-0.6.0 → memgit-0.6.2}/memgit/cli.py +0 -0
  11. {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/__init__.py +0 -0
  12. {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/client.py +0 -0
  13. {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/commands.py +0 -0
  14. {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/crypto.py +0 -0
  15. {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/state.py +0 -0
  16. {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/sync.py +0 -0
  17. {memgit-0.6.0 → memgit-0.6.2}/memgit/delivery.py +0 -0
  18. {memgit-0.6.0 → memgit-0.6.2}/memgit/gitdigest.py +0 -0
  19. {memgit-0.6.0 → memgit-0.6.2}/memgit/graph.py +0 -0
  20. {memgit-0.6.0 → memgit-0.6.2}/memgit/http_server.py +0 -0
  21. {memgit-0.6.0 → memgit-0.6.2}/memgit/importer.py +0 -0
  22. {memgit-0.6.0 → memgit-0.6.2}/memgit/mcp_server.py +0 -0
  23. {memgit-0.6.0 → memgit-0.6.2}/memgit/models.py +0 -0
  24. {memgit-0.6.0 → memgit-0.6.2}/memgit/project.py +0 -0
  25. {memgit-0.6.0 → memgit-0.6.2}/memgit/repo.py +0 -0
  26. {memgit-0.6.0 → memgit-0.6.2}/memgit/scorer.py +0 -0
  27. {memgit-0.6.0 → memgit-0.6.2}/memgit/store.py +0 -0
  28. {memgit-0.6.0 → memgit-0.6.2}/memgit/tokens.py +0 -0
  29. {memgit-0.6.0 → memgit-0.6.2}/memgit/toon.py +0 -0
  30. {memgit-0.6.0 → memgit-0.6.2}/memgit/usage.py +0 -0
  31. {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/SOURCES.txt +0 -0
  32. {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/dependency_links.txt +0 -0
  33. {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/entry_points.txt +0 -0
  34. {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/requires.txt +0 -0
  35. {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/top_level.txt +0 -0
  36. {memgit-0.6.0 → memgit-0.6.2}/setup.cfg +0 -0
  37. {memgit-0.6.0 → memgit-0.6.2}/tests/test_accrue.py +0 -0
  38. {memgit-0.6.0 → memgit-0.6.2}/tests/test_advanced.py +0 -0
  39. {memgit-0.6.0 → memgit-0.6.2}/tests/test_aliases.py +0 -0
  40. {memgit-0.6.0 → memgit-0.6.2}/tests/test_core.py +0 -0
  41. {memgit-0.6.0 → memgit-0.6.2}/tests/test_delivery.py +0 -0
  42. {memgit-0.6.0 → memgit-0.6.2}/tests/test_setup.py +0 -0
  43. {memgit-0.6.0 → memgit-0.6.2}/tests/test_store_repo.py +0 -0
  44. {memgit-0.6.0 → memgit-0.6.2}/tests/test_toon.py +0 -0
  45. {memgit-0.6.0 → memgit-0.6.2}/tests/test_v020.py +0 -0
  46. {memgit-0.6.0 → memgit-0.6.2}/tests/test_v030.py +0 -0
  47. {memgit-0.6.0 → memgit-0.6.2}/tests/test_v040.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memgit
3
- Version: 0.6.0
3
+ Version: 0.6.2
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
@@ -1,3 +1,3 @@
1
1
  """memgit — git for AI memory."""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.6.2"
@@ -137,7 +137,7 @@ def prompt_recall() -> int:
137
137
  # (Measured before this line existed: 6.8% of recall-injected sessions
138
138
  # ever ran a search.) Hinted-but-not-shown memories are deliberately NOT
139
139
  # added to the dedup file or the usage ledger — they weren't surfaced.
140
- more_line, hinted_tag = _depth_hint(results, mnemonics, seen)
140
+ more_line, hinted_tag = _depth_hint(results, mnemonics, seen, project)
141
141
  if more_line:
142
142
  lines.append(more_line)
143
143
  lines.append('</memgit-recall>')
@@ -169,19 +169,25 @@ def prompt_recall() -> int:
169
169
  return 0
170
170
 
171
171
 
172
- def _depth_hint(results, mnemonics, seen: set[str]) -> tuple[Optional[str], Optional[str]]:
172
+ def _depth_hint(results, mnemonics, seen: set[str],
173
+ project: Optional[str] = None) -> tuple[Optional[str], Optional[str]]:
173
174
  """One '+N more on <tag>' line advertising depth behind the injected hits.
174
175
 
175
176
  Picks the single best tag: among tags carried by the injected results,
176
177
  the one with the most ACTIVE memories that were neither injected nor
177
178
  already seen this session. Requires >= 2 (a count of 1 is not depth and
178
- teaches the model to ignore counts). At most one line, ever.
179
- Returns (line, tag) both None when nothing clears the bar.
179
+ teaches the model to ignore counts). Project-label tags are excluded
180
+ the same noise rule as the memory index and context recall. At most one
181
+ line, ever. Returns (line, tag) — both None when nothing clears the bar.
180
182
  """
183
+ from .links import label_noise
184
+ noise = label_noise(project)
181
185
  injected = {r.mnemonic.slug for r in results}
182
186
  tags: set[str] = set()
183
187
  for r in results:
184
- tags.update(t.strip() for t in r.mnemonic.tags if t.strip() and len(t.strip()) <= 40)
188
+ tags.update(t.strip() for t in r.mnemonic.tags
189
+ if t.strip() and len(t.strip()) <= 40
190
+ and t.strip().lower() not in noise)
185
191
  if not tags:
186
192
  return None, None
187
193
  best_tag, best_n = None, 0
@@ -261,10 +267,15 @@ def context_recall() -> int:
261
267
  if not tokens:
262
268
  return 0
263
269
  # Tag matches a path token exactly, case-insensitive. (Substring matching
264
- # would fire 'api' against half the filesystem.)
270
+ # would fire 'api' against half the filesystem.) Project-label tags are
271
+ # excluded via the shared noise rule.
272
+ from .links import label_noise
273
+ noise = label_noise(project)
265
274
  lower_tags = {t.lower(): t for t in tagmap}
266
275
  candidates = []
267
276
  for tok in tokens:
277
+ if tok in noise:
278
+ continue
268
279
  tag = lower_tags.get(tok)
269
280
  if not tag:
270
281
  continue
@@ -149,6 +149,17 @@ def normalize_slug_list(value) -> list[str]:
149
149
 
150
150
  # ── entity index ──────────────────────────────────────────────────────────────
151
151
 
152
+ def label_noise(project: Optional[str]) -> set[str]:
153
+ """Lowercased tags that are noise for the current workspace: the project
154
+ label and its '-'-components. Importer-derived label tags ('personal',
155
+ 'business') are not topics, and every path inside a workspace contains
156
+ the label's words — so no depth surface may advertise them."""
157
+ proj_lower = (project or '').lower()
158
+ parts = {p for p in proj_lower.split('-') if p}
159
+ if proj_lower:
160
+ parts.add(proj_lower)
161
+ return parts
162
+
152
163
  def entity_index(mnemonics: list[Mnemonic], project: Optional[str],
153
164
  min_count: int = 2, cap: int = 8) -> list[tuple[str, int]]:
154
165
  """Tag → memory-count pairs advertising the store's depth on each topic.
@@ -175,16 +186,10 @@ def entity_index(mnemonics: list[Mnemonic], project: Optional[str],
175
186
  tag = tag.strip()
176
187
  if tag:
177
188
  counts[tag] += 1
178
- # The project's own label AND its '-'-separated components are noise, not
179
- # topics ("Personal-business" seeds importer tags 'personal'/'business'
180
- # that would dominate the index and lead to junk searches).
181
- proj_lower = (project or '').lower()
182
- label_parts = {p for p in proj_lower.split('-') if p}
189
+ noise = label_noise(project)
183
190
  items = [
184
191
  (tag, n) for tag, n in counts.items()
185
- if n >= min_count
186
- and tag.lower() != proj_lower
187
- and tag.lower() not in label_parts
192
+ if n >= min_count and tag.lower() not in noise
188
193
  ]
189
194
  items.sort(key=lambda tn: (-tn[1], tn[0]))
190
195
  return items[:cap]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memgit
3
- Version: 0.6.0
3
+ Version: 0.6.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memgit"
7
- version = "0.6.0"
7
+ version = "0.6.2"
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"
@@ -373,6 +373,28 @@ class TestEntityIndex:
373
373
  injected = {l.strip() for l in seen.splitlines()}
374
374
  assert len(injected) <= 3
375
375
 
376
+ def test_depth_hint_skips_project_label_tags(self, repo, monkeypatch, capsys):
377
+ """Third surface of the label-noise bug (found live: '+75 more on
378
+ 'business''): the recall depth hint must apply the same exclusion as
379
+ the memory index and context recall."""
380
+ from memgit.hooks import prompt_recall
381
+ proj = "Personal-business"
382
+ repo.add(_mk("zebpay-caps", rule="ZebPay caps INR withdrawals at 10L/day",
383
+ tags=["business"], project=proj))
384
+ for i in range(5):
385
+ repo.add(_mk(f"biz-{i}", rule=f"unrelated business fact {i}",
386
+ tags=["business"], project=proj))
387
+ repo.commit(message="seed")
388
+ monkeypatch.setattr("memgit.hooks.project_label_from_path",
389
+ lambda p: proj, raising=False)
390
+ import memgit.project as project_mod
391
+ monkeypatch.setattr(project_mod, "project_label_from_path", lambda p: proj)
392
+ rc, out = _run_hook(monkeypatch, capsys, prompt_recall, {
393
+ "prompt": "what are the zebpay withdrawal caps and limits?",
394
+ "cwd": "/tmp", "session_id": "noise1"}, repo)
395
+ assert "zebpay-caps" in out
396
+ assert "more saved on 'business'" not in out
397
+
376
398
  def test_no_hint_when_depth_below_two(self, repo, monkeypatch, capsys):
377
399
  from memgit.hooks import prompt_recall
378
400
  repo.add(_mk("zebpay-caps", rule="ZebPay caps INR withdrawals at 10L/day",
@@ -451,6 +473,17 @@ class TestContextRecall:
451
473
  payload("delta", "cap1"), repo)
452
474
  assert out4 == "" # hard cap 3/session
453
475
 
476
+ def test_project_label_component_tags_never_hinted(self, repo, monkeypatch, capsys):
477
+ """Every path under the workspace contains the label's words —
478
+ importer-derived label tags ('personal', 'business') are not topics.
479
+ Found live: 77 'business'-tagged memories hinted on MEMORY.md."""
480
+ from memgit.hooks import context_recall
481
+ self._seed(repo, n=5, tag="business")
482
+ rc, out = _run_hook(monkeypatch, capsys, context_recall, {
483
+ "tool_input": {"file_path": "/Users/x/Personal business/notes.md"},
484
+ "cwd": "/Users/x/Personal business", "session_id": "ctx6"}, repo)
485
+ assert out == ""
486
+
454
487
  def test_skips_tag_already_hinted_by_prompt_recall(self, repo, monkeypatch, capsys):
455
488
  from memgit.hooks import context_recall
456
489
  self._seed(repo, n=3, tag="zebpay")
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
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
File without changes
File without changes
File without changes