diff --git a/build.py b/build.py index dc5f781..ff17494 100644 --- a/build.py +++ b/build.py @@ -41,6 +41,8 @@ EXCLUDE_DIRS = {"__pycache__", ".git"} EXCLUDE_NAMES = {".DS_Store"} EXCLUDE_SUFFIXES = {".pyc", ".pyo"} FIXED_DATE = (2026, 1, 1, 0, 0, 0) # stable timestamp for reproducible archives +MAX_DESCRIPTION = 500 # plugin-marketplace cap: plugin.json "description" must be UNDER this + # (it has silently regressed past the limit before — fail the build now) _KEY_RE = re.compile(r'(DEFAULT_KEY\s*=\s*")([0-9a-fA-F]{16,})(")') @@ -102,7 +104,14 @@ def main(argv: list[str] | None = None) -> int: return 1 manifest_path = SRC / ".claude-plugin" / "plugin.json" - version = json.loads(manifest_path.read_text(encoding="utf-8"))["version"] + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + version = manifest["version"] + desc = manifest.get("description", "") + if len(desc) >= MAX_DESCRIPTION: + print(f"build: plugin description is {len(desc)} chars — must be under " + f"{MAX_DESCRIPTION} (marketplace cap). Trim \"description\" in {MANIFEST}.", + file=sys.stderr) + return 1 files = included_files() arcnames = {p.relative_to(SRC).as_posix() for p in files} if MANIFEST not in arcnames: diff --git a/echo-memory-1.2.0.plugin b/echo-memory-1.2.0.plugin index 669467d..b8f9b63 100644 Binary files a/echo-memory-1.2.0.plugin and b/echo-memory-1.2.0.plugin differ diff --git a/echo-memory.plugin b/echo-memory.plugin index 669467d..b8f9b63 100644 Binary files a/echo-memory.plugin and b/echo-memory.plugin differ diff --git a/echo-memory.plugin.src/.claude-plugin/plugin.json b/echo-memory.plugin.src/.claude-plugin/plugin.json index 2d0b490..69772b1 100644 --- a/echo-memory.plugin.src/.claude-plugin/plugin.json +++ b/echo-memory.plugin.src/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "echo-memory", "version": "1.2.0", - "description": "Persistent memory via the ECHO Obsidian vault over the Obsidian Local REST API. Cross-platform Python client (connection-pooled, with concurrent full-vault reads): one-call capture/resolve/recall/link over an entity index, hybrid BM25 + graph recall, auto-linking, an offline write-ahead queue + read cache, lock-guarded concurrency, and session-reflection capture, plus a linter-enforced routing manifest and /echo-load|save|recall|triage|health|sweep|reflect|doctor commands. Crosslinks notes across Claude/CoWork sessions.", + "description": "Persistent memory via the ECHO Obsidian vault over the Obsidian Local REST API. A cross-platform, connection-pooled Python client: one-call capture/resolve/recall/link over an entity index, hybrid BM25 + graph recall, auto-linking, an offline write-ahead queue, and lock-guarded concurrency. Linter-enforced routing manifest plus /echo-load|save|recall|triage|health|sweep|reflect|doctor commands. Crosslinks notes across Claude and CoWork sessions.", "author": { "name": "Jason" }, diff --git a/echo-memory.plugin.src/skills/echo-memory/scripts/test_echo_client.py b/echo-memory.plugin.src/skills/echo-memory/scripts/test_echo_client.py index 5d74f1c..852146d 100644 --- a/echo-memory.plugin.src/skills/echo-memory/scripts/test_echo_client.py +++ b/echo-memory.plugin.src/skills/echo-memory/scripts/test_echo_client.py @@ -61,6 +61,15 @@ def test_read_many_tolerates_unreadable_file_as_none() -> None: echo.get_text = orig +def test_plugin_description_under_500_chars() -> None: + # Marketplace cap — the description has silently regressed past it before (a 525-char + # relapse + an earlier "fix description length" commit). build.py fails the build on this; + # this guards it in CI so it's caught before packaging. + manifest = Path(__file__).resolve().parents[3] / ".claude-plugin" / "plugin.json" + desc = json.loads(manifest.read_text(encoding="utf-8")).get("description", "") + assert 0 < len(desc) < 500, f"plugin description is {len(desc)} chars (must be under 500)" + + def test_stem_extracts_literal_directory_prefix() -> None: assert check_routing.stem(r"^projects/active/[^/]+\.md$") == "projects/active/" assert check_routing.stem(r"^areas/(business|personal)/[^/]+\.md$") == "areas/"