memgit 0.8.0__tar.gz → 0.8.1__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.8.0 → memgit-0.8.1}/PKG-INFO +1 -1
- {memgit-0.8.0 → memgit-0.8.1}/memgit/__init__.py +1 -1
- {memgit-0.8.0 → memgit-0.8.1}/memgit/delivery.py +32 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit.egg-info/PKG-INFO +1 -1
- {memgit-0.8.0 → memgit-0.8.1}/pyproject.toml +1 -1
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v081.py +26 -0
- {memgit-0.8.0 → memgit-0.8.1}/LICENSE +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/README.md +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cli.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cloud/__init__.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cloud/client.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cloud/commands.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cloud/crypto.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cloud/state.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/cloud/sync.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/evaluate.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/gitdigest.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/graph.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/hooks.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/http_server.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/importer.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/links.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/mcp_server.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/metrics.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/models.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/project.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/repo.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/sanitize.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/scorer.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/store.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/tokens.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/toon.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit/usage.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit.egg-info/SOURCES.txt +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit.egg-info/dependency_links.txt +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit.egg-info/entry_points.txt +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit.egg-info/requires.txt +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/memgit.egg-info/top_level.txt +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/setup.cfg +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_accrue.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_advanced.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_aliases.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_core.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_delivery.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_setup.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_store_repo.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_toon.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v020.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v030.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v040.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v060.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v070.py +0 -0
- {memgit-0.8.0 → memgit-0.8.1}/tests/test_v080.py +0 -0
|
@@ -229,6 +229,37 @@ def collect_skills(root: Path, home: Optional[Path] = None) -> list[tuple[str, s
|
|
|
229
229
|
return sorted(found.items())
|
|
230
230
|
|
|
231
231
|
|
|
232
|
+
#: Per-skill budget in the guide's routing list. The guide is injected every
|
|
233
|
+
#: session on every host, so it is the single most expensive surface memgit
|
|
234
|
+
#: owns — and a 2026-08-05 audit measured it as the LOWEST-yield memory type
|
|
235
|
+
#: (2.10 recalls/memory vs 17.5 for feedback). Skill `description` frontmatter
|
|
236
|
+
#: is written to persuade a model to invoke the skill and routinely runs 600+
|
|
237
|
+
#: chars; on a store with 18 skills that was 6,968 of the guide's 9,425 chars.
|
|
238
|
+
#: The guide only needs to answer "which skill covers this?" — the full text
|
|
239
|
+
#: is in the skill itself, one Read away.
|
|
240
|
+
_SKILL_DESC_MAX = 150
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _routing_summary(desc: str) -> str:
|
|
244
|
+
"""Trim a skill description to the part that answers 'is this the one?'.
|
|
245
|
+
|
|
246
|
+
Prefers a clean sentence break inside the budget; falls back to a word
|
|
247
|
+
boundary. Never mid-word, and never a bare ellipsis with no content.
|
|
248
|
+
"""
|
|
249
|
+
desc = ' '.join((desc or '').split())
|
|
250
|
+
if len(desc) <= _SKILL_DESC_MAX:
|
|
251
|
+
return desc
|
|
252
|
+
window = desc[:_SKILL_DESC_MAX + 1]
|
|
253
|
+
for stop in ('. ', '; ', ' — ', ' - '):
|
|
254
|
+
cut = window.rfind(stop)
|
|
255
|
+
if cut >= _SKILL_DESC_MAX // 2:
|
|
256
|
+
return desc[:cut].rstrip(' .;—-') + '.'
|
|
257
|
+
cut = window.rfind(' ')
|
|
258
|
+
if cut < _SKILL_DESC_MAX // 2:
|
|
259
|
+
cut = _SKILL_DESC_MAX
|
|
260
|
+
return desc[:cut].rstrip(' .;,—-') + '…'
|
|
261
|
+
|
|
262
|
+
|
|
232
263
|
def _store_scale(repo, project: Optional[str]) -> Optional[tuple[int, list[str]]]:
|
|
233
264
|
"""(memory count, top topic tags) for this project — the concrete evidence
|
|
234
265
|
the guide leads with. Returns None when there is nothing worth stating."""
|
|
@@ -298,6 +329,7 @@ def build_seed(root: Path, home: Optional[Path] = None,
|
|
|
298
329
|
if skills:
|
|
299
330
|
lines += ["", "## Available skills (invoke by name when the task matches)"]
|
|
300
331
|
for name, desc in skills:
|
|
332
|
+
desc = _routing_summary(desc)
|
|
301
333
|
lines.append(f"- **{name}** — {desc}" if desc else f"- **{name}**")
|
|
302
334
|
|
|
303
335
|
rule_files = []
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memgit"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.1"
|
|
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"
|
|
@@ -368,6 +368,32 @@ class TestSeedEvidence:
|
|
|
368
368
|
assert "**9 saved memories**" in body
|
|
369
369
|
assert "crypto" in body
|
|
370
370
|
|
|
371
|
+
def test_long_skill_descriptions_are_trimmed(self, tmp_path):
|
|
372
|
+
"""The guide is injected every session on every host; skill frontmatter
|
|
373
|
+
is written to persuade and runs long. Routing needs a sentence."""
|
|
374
|
+
from memgit.delivery import build_seed, _SKILL_DESC_MAX
|
|
375
|
+
skills = tmp_path / ".claude" / "skills" / "verbose"
|
|
376
|
+
skills.mkdir(parents=True)
|
|
377
|
+
long_desc = ("Mandatory entry point: read this first for any request. "
|
|
378
|
+
+ "Additional qualifying detail. " * 30)
|
|
379
|
+
(skills / "SKILL.md").write_text(
|
|
380
|
+
f"---\nname: verbose\ndescription: {long_desc}\n---\nbody\n")
|
|
381
|
+
body = build_seed(tmp_path, home=tmp_path)
|
|
382
|
+
line = next(l for l in body.splitlines() if l.startswith("- **verbose**"))
|
|
383
|
+
assert len(line) < _SKILL_DESC_MAX + 40
|
|
384
|
+
assert "Mandatory entry point" in line, "the routing signal must survive"
|
|
385
|
+
|
|
386
|
+
def test_short_descriptions_untouched(self, tmp_path):
|
|
387
|
+
from memgit.delivery import _routing_summary
|
|
388
|
+
d = "Deploy the portfolio to Vercel."
|
|
389
|
+
assert _routing_summary(d) == d
|
|
390
|
+
|
|
391
|
+
def test_trim_never_cuts_mid_word(self, tmp_path):
|
|
392
|
+
from memgit.delivery import _routing_summary
|
|
393
|
+
out = _routing_summary("alpha " * 100)
|
|
394
|
+
assert not out.rstrip("…").endswith("alph")
|
|
395
|
+
assert out.endswith(("…", ".")) and len(out.strip("…. ")) > 20
|
|
396
|
+
|
|
371
397
|
def test_seed_stays_quiet_below_the_bar(self, tmp_path):
|
|
372
398
|
from memgit.delivery import build_seed
|
|
373
399
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|