From 63f5c62dfd34234b5e543a56df938397202cfcd5 Mon Sep 17 00:00:00 2001 From: phernandez Date: Sun, 31 May 2026 11:09:56 -0500 Subject: [PATCH] fix(plugins): address review findings (UUID routing, outputStyle location, line length) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the in-scope findings from the github-actions review (its prepared commit 1283a46 couldn't push — 403) and the Codex P2 (review 4397168884): - pre-compact.sh: route a UUID primaryProject via --project-id, not --project. This mirrors session-start.sh; without it a UUID-configured project gets correct session briefs but SILENT checkpoint failures at every compaction. The substantive correctness fix. Verified end-to-end (checkpoint now lands in a UUID-keyed project). - skills/share/SKILL.md: same UUID routing for /basic-memory:share team targets — pass external_id UUIDs as project_id, qualified names as project. (Codex P2.) - skills/status/SKILL.md: read outputStyle from the ROOT settings object, not the basicMemory block — otherwise /basic-memory:status reports capture reflexes as off for a correctly-configured user. - docs/getting-started.md: move outputStyle out of the basicMemory block to root in the team example (matches settings.example.json; it's a top-level Claude Code key). - session-start.sh: split the >100-char project-routing line. Out of scope here: the hermes/__init__.py getattr-rationale comments the bot prepared live in the #859 consolidation code (not in this PR's diff) — flagged for that PR. Plugin validates; hooks smoke-tested. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: phernandez --- plugins/claude-code/docs/getting-started.md | 6 +++--- plugins/claude-code/hooks/pre-compact.sh | 13 ++++++++++++- plugins/claude-code/hooks/session-start.sh | 3 ++- plugins/claude-code/skills/share/SKILL.md | 4 +++- plugins/claude-code/skills/status/SKILL.md | 15 ++++++++------- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/plugins/claude-code/docs/getting-started.md b/plugins/claude-code/docs/getting-started.md index 5815eeaa..4e1d9ef6 100644 --- a/plugins/claude-code/docs/getting-started.md +++ b/plugins/claude-code/docs/getting-started.md @@ -93,9 +93,9 @@ repeat across workspaces, team projects use **workspace-qualified names** "basicMemory": { "primaryProject": "my-org/main", "secondaryProjects": ["my-team/main", "my-team/notes"], - "teamProjects": { "my-team/notes": { "promoteFolder": "shared" } }, - "outputStyle": "basic-memory" - } + "teamProjects": { "my-team/notes": { "promoteFolder": "shared" } } + }, + "outputStyle": "basic-memory" } ``` diff --git a/plugins/claude-code/hooks/pre-compact.sh b/plugins/claude-code/hooks/pre-compact.sh index e768e5a4..fe8430b1 100755 --- a/plugins/claude-code/hooks/pre-compact.sh +++ b/plugins/claude-code/hooks/pre-compact.sh @@ -25,12 +25,20 @@ BM="$(command -v basic-memory || command -v bm || true)" BM_HOOK_INPUT="$input" BM_BIN="$BM" python3 <<'PY' 2>/dev/null || exit 0 import json import os +import re import subprocess import sys from datetime import datetime bm = os.environ.get("BM_BIN", "basic-memory") +# A project ref can be a workspace-qualified name (route via --project) or an +# external_id UUID (route via --project-id) — names collide across workspaces, so +# bare names won't route. Mirror session-start.sh's detection. +UUID_RE = re.compile( + r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE +) + try: payload = json.loads(os.environ.get("BM_HOOK_INPUT") or "{}") except Exception: @@ -186,13 +194,16 @@ body += [ content = "\n".join(frontmatter + body) # --- Write the checkpoint (best-effort) --- +# A UUID primaryProject must route via --project-id, not --project, or the write +# silently fails to land in a UUID-configured project. +project_flag = "--project-id" if UUID_RE.match(primary_project) else "--project" try: subprocess.run( [ bm, "tool", "write-note", "--title", title, "--folder", capture_folder, - "--project", primary_project, + project_flag, primary_project, "--tags", "session", "--tags", "auto-capture", ], diff --git a/plugins/claude-code/hooks/session-start.sh b/plugins/claude-code/hooks/session-start.sh index e6a414ae..5ac99018 100755 --- a/plugins/claude-code/hooks/session-start.sh +++ b/plugins/claude-code/hooks/session-start.sh @@ -121,7 +121,8 @@ shared_refs = shared_refs[:MAX_SHARED] def search(filters, project_ref=None, timeout=10): cmd = [bm, "tool", "search-notes", *filters, "--page-size", "5"] if project_ref: - cmd += (["--project-id", project_ref] if UUID_RE.match(project_ref) else ["--project", project_ref]) + flag = "--project-id" if UUID_RE.match(project_ref) else "--project" + cmd += [flag, project_ref] try: out = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) if out.returncode != 0: diff --git a/plugins/claude-code/skills/share/SKILL.md b/plugins/claude-code/skills/share/SKILL.md index 6c489881..8fee1773 100644 --- a/plugins/claude-code/skills/share/SKILL.md +++ b/plugins/claude-code/skills/share/SKILL.md @@ -37,7 +37,9 @@ project — session checkpoints and `/basic-memory:remember` always stay persona wait for a yes. Never share silently. 5. **Write the shared copy** with `write_note`: - - `project` = the chosen team project ref + - Route to the target: if the team ref is an `external_id` UUID, pass it as + `project_id`; otherwise pass the workspace-qualified name as `project`. (A bare + UUID in `project` won't route — Basic Memory takes UUIDs only via `project_id`.) - `directory` = the `promoteFolder` - `title` = the source title - `content` = the source's content, with attribution added: keep its frontmatter diff --git a/plugins/claude-code/skills/status/SKILL.md b/plugins/claude-code/skills/status/SKILL.md index a403d3ee..1f56a264 100644 --- a/plugins/claude-code/skills/status/SKILL.md +++ b/plugins/claude-code/skills/status/SKILL.md @@ -16,13 +16,14 @@ This is a quick diagnostic — gather the facts and lay them out; don't over-inv nothing else will work without it. 2. **Configuration.** Read `.claude/settings.json` (and `.claude/settings.local.json` - if present) and report from the `basicMemory` block: - - `primaryProject` — or note that none is pinned (the default project is used) - - `secondaryProjects` — team/shared projects read for recall (read-only), if any - - `teamProjects` — share targets for `/basic-memory:share`, if any - - `captureFolder` (default `sessions`) and `rememberFolder` (default `bm-remember`) - - whether `outputStyle` is `basic-memory` (capture reflexes on/off) - - `preCompactCapture` mode (default `extractive`) + if present) and report: + - From the `basicMemory` block: `primaryProject` (or note none is pinned — the + default project is used), `secondaryProjects` (team/shared read sources), + `teamProjects` (share targets for `/basic-memory:share`), `captureFolder` + (default `sessions`), `rememberFolder` (default `bm-remember`), and + `preCompactCapture` mode (default `extractive`). + - From the **root** settings object (not `basicMemory`): whether `outputStyle` is + `basic-memory` — i.e. whether the capture reflexes are on. 3. **Recent checkpoints.** `search_notes` with `metadata_filters={"type": "session"}`, `page_size` 5, scoped to `primaryProject`