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.
- {memgit-0.6.0 → memgit-0.6.2}/PKG-INFO +1 -1
- {memgit-0.6.0 → memgit-0.6.2}/memgit/__init__.py +1 -1
- {memgit-0.6.0 → memgit-0.6.2}/memgit/hooks.py +17 -6
- {memgit-0.6.0 → memgit-0.6.2}/memgit/links.py +13 -8
- {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/PKG-INFO +1 -1
- {memgit-0.6.0 → memgit-0.6.2}/pyproject.toml +1 -1
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_v060.py +33 -0
- {memgit-0.6.0 → memgit-0.6.2}/LICENSE +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/README.md +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cli.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/__init__.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/client.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/commands.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/crypto.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/state.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/cloud/sync.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/delivery.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/gitdigest.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/graph.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/http_server.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/importer.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/mcp_server.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/models.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/project.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/repo.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/scorer.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/store.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/tokens.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/toon.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit/usage.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/SOURCES.txt +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/dependency_links.txt +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/entry_points.txt +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/requires.txt +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/memgit.egg-info/top_level.txt +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/setup.cfg +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_accrue.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_advanced.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_aliases.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_core.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_delivery.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_setup.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_store_repo.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_toon.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_v020.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_v030.py +0 -0
- {memgit-0.6.0 → memgit-0.6.2}/tests/test_v040.py +0 -0
|
@@ -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]
|
|
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).
|
|
179
|
-
|
|
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
|
|
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
|
-
|
|
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]
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memgit"
|
|
7
|
-
version = "0.6.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|