From 2ba1ca87229c8ee545b1cd40272386198b77e8d3 Mon Sep 17 00:00:00 2001 From: phernandez Date: Sun, 31 May 2026 13:49:39 -0500 Subject: [PATCH] feat(plugins): make setup focus load-bearing, seed schemas via metadata, add smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Focus question is now load-bearing: it drives an adaptive light folder-structure suggestion and is stored. The placement step branches learn (existing project with notes) vs suggest (new/empty project) instead of only learning. - Seed schema notes via the write_note `metadata` param instead of content frontmatter. The cloud write path silently coerces nested YAML to the string '[object Object]', corrupting schema/settings (basic-memory-cloud#1000); the metadata param round-trips correctly on both local and cloud. - Surface placementConventions + captureFolder in the SessionStart brief so the output style's "follow stored placement conventions" reflex has something to follow — previously written by setup but never shown to Claude (dead config). - Add a post-settings smoke test (run the hook's recall query, confirm routing) and a restart prompt gated on outputStyle in the close. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: phernandez --- plugins/claude-code/hooks/session-start.sh | 18 +++++ plugins/claude-code/skills/setup/SKILL.md | 93 +++++++++++++++++----- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/plugins/claude-code/hooks/session-start.sh b/plugins/claude-code/hooks/session-start.sh index 71ba482e..28e94743 100755 --- a/plugins/claude-code/hooks/session-start.sh +++ b/plugins/claude-code/hooks/session-start.sh @@ -109,6 +109,11 @@ default_prompt = ( "Cite permalinks when referencing prior work." ) recall_prompt = cfg.get("recallPrompt") or default_prompt +# Placement guidance — surfaced in the brief below so the output style's "follow the +# project's stored placement conventions" reflex has something concrete to follow. +# Without this, setup writes them but they never reach Claude (dead config). +placement_conventions = (cfg.get("placementConventions") or "").strip() +capture_folder = (cfg.get("captureFolder") or "sessions").strip() # --- Resolve the shared/team read set --- # secondaryProjects (read-only recall sources) + teamProjects keys (share targets, @@ -246,6 +251,19 @@ if shared_sections: if shared_capped: lines += ["", f"_(reading the first {MAX_SHARED} shared projects; more are configured.)_"] +# --- Where to write (placement guidance) --- +# Trigger: a primaryProject is set (so capture is actually active — pre-compact and +# proactive writes land somewhere intentional). Why: the output style tells Claude to +# follow the project's placement conventions, but nothing else surfaces them. +# Outcome: Claude sees the capture folder + any stored conventions, so writes land +# where the user expects instead of being guesswork. Bounded — conventions are a +# short string by design. +if primary_project: + placement = ["", "## Where to write", f"- Auto-capture and checkpoints go to `{capture_folder}/`."] + if placement_conventions: + placement.append(f"- Placement conventions: {placement_conventions}") + lines += placement + # --- First-run / config nudges --- if not configured: lines += ["", setup_nudge] diff --git a/plugins/claude-code/skills/setup/SKILL.md b/plugins/claude-code/skills/setup/SKILL.md index b40f39cd..bdfb40e4 100644 --- a/plugins/claude-code/skills/setup/SKILL.md +++ b/plugins/claude-code/skills/setup/SKILL.md @@ -1,6 +1,6 @@ --- name: setup -description: Set up the Basic Memory plugin for this project — a short guided interview that configures the project mapping, seeds note schemas, optionally learns the project's conventions, and enables capture reflexes. Use when the user runs /basic-memory:setup, says "set up basic memory", or asks to configure/bootstrap the plugin. +description: Set up the Basic Memory plugin for this project — a short guided interview that configures the project mapping, seeds note schemas, learns or suggests placement conventions, and enables capture reflexes. Use when the user runs /basic-memory:setup, says "set up basic memory", or asks to configure/bootstrap the plugin. argument-hint: (no arguments — runs an interactive interview) --- @@ -32,8 +32,13 @@ it succeeds. Ask only what you can't infer. Cover: -1. **Focus.** "What's this project mostly about — code, writing, research, planning, - or a mix?" (Shapes folder suggestions later; one quick question.) +1. **Focus / how you'll use it.** "What will this project mostly be — code/dev, + research, writing, knowledge capture, planning, or a mix?" This answer is + **load-bearing**, not small talk: it drives the folder structure you suggest + (step 4) and is stored so the SessionStart brief can surface it, keeping capture + matched to the use-case. Don't let it evaporate — if you infer it from context + instead of asking, still say the use-case you assumed and the structure it + implies, and let the user correct it in one word. 2. **Project mapping.** "Do you already have a Basic Memory project for this, or should I create one?" @@ -60,14 +65,23 @@ Ask only what you can't infer. Cover: Keep `primaryProject` a project the user owns for their *own* capture; team projects are for reading and deliberate sharing only. -4. **Learn conventions** (optional). "Want me to look at your existing notes and - note your conventions so I place new notes consistently?" If yes, inspect the - project: `list_directory` for the folder layout, sample a few notes per folder - (and, where a folder holds recurring typed notes, you may run `schema_infer` to - see their shape). Summarize what you find — folder-by-topic conventions, naming - style, the observation categories they favor — into 3-6 short lines and store - that string as `placementConventions`. Infer from their *real* notes; don't - impose a structure. +4. **Placement — learn or suggest** (depends on the project's state). The goal is a + short `placementConventions` string (3-6 lines) telling you where new notes go. + How you get it depends on whether the project already has notes: + - **Existing project with notes** → *learn*. Inspect it: `list_directory` for the + folder layout, sample a few notes per folder (and, where a folder holds + recurring typed notes, you may run `schema_infer` to see their shape). + Summarize the *real* conventions — folder-by-topic layout, naming style, the + observation categories they favor. Infer from their actual notes; don't impose. + - **New or empty project** → *suggest* (there's nothing to learn yet). Propose a + **light** structure that fits the focus from step 1 — 3-5 optional top-level + folders, no deep taxonomy — and be explicit that it's a starting point, not a + scaffold: notes work fine without it and structure can stay emergent. Don't + create empty folders; folders appear as notes land in them. Let the user edit + or decline in one word. + Either way, keep it short and store the result as `placementConventions`. The + SessionStart brief surfaces it (alongside `captureFolder`), so this is what makes + your captures land where the user expects — without it, placement is guesswork. 5. **Schemas.** "I'll add schemas for session checkpoints, decisions, and tasks so I can find them precisely later — okay?" (See "Seed the schemas" below.) @@ -99,12 +113,21 @@ For each one: (`search_notes` with `metadata_filters={"type": "schema"}`, or try `read_note("schemas/")`). **If it exists, skip it** — never overwrite a schema the user may have customized. -- Otherwise write it with `write_note`: `directory="schemas"`, - `title` = the schema's title (Session / Decision / Task), `content` = the file's - full contents (including its `---` frontmatter — Basic Memory merges that into the - note's frontmatter, so the `type: schema` + `entity` + `schema` definition land - intact and become resolvable by `schema_validate`), routed to `primaryProject` - (pass it as `project`, or as `project_id` if it's an `external_id` UUID). +- Otherwise write it with `write_note`, routed to `primaryProject` (pass it as + `project`, or as `project_id` if it's an `external_id` UUID): + - `directory="schemas"`, `note_type="schema"`, `title` = the schema's title + (Session / Decision / Task). + - `content` = the markdown **body only** — everything *after* the `---` + frontmatter block (the `# Session` heading and the prose). + - `metadata` = the schema's structured frontmatter as a **nested dict**: `entity`, + `version`, the full `schema` map, and `settings` (keep its nested `frontmatter`, + and pass enum values as JSON arrays, e.g. `["open","resumed","closed"]`). + - **Do not** put the schema's `---` frontmatter inside `content`. On the cloud + write path that nested YAML is silently coerced to the string `'[object Object]'` + (basic-memory-cloud#1000), corrupting `schema`/`settings`. The `metadata` param + round-trips correctly on both local and cloud. After seeding, verify one note + with `read_note(..., output_format="json", include_frontmatter=true)` — + `schema`/`settings` must come back as nested objects, not strings. ### 2. Install the shared skills (if the user opted in) Run, from the project root: @@ -130,7 +153,7 @@ Build the `basicMemory` block from the interview: "rememberFolder": "bm-remember", "recallTimeframe": "3d", "preCompactCapture": "extractive", - "placementConventions": "", + "placementConventions": "", "teamProjects": {} }, "outputStyle": "basic-memory" @@ -145,10 +168,36 @@ valid JSON. Writing the `basicMemory` block is also what stops the SessionStart hook's first-run nudge — the config's presence is the signal that setup has run. +### 4. Smoke-test the wiring +Before you close, prove recall actually resolves — this catches a misnamed project, +missing cloud credentials, or a ref that doesn't route, while the user is still here +to fix it. Run the same structured query the SessionStart hook runs, via the CLI it +uses (`basic-memory` / `bm` / `uvx basic-memory`): + +- **Primary:** `… tool search-notes --type schema --page-size 5` against + `primaryProject` — use `--project-id ` for a UUID, `--project ` + otherwise. It should return the three schemas you just seeded. +- **One shared project** (only if `secondaryProjects` is non-empty): a + `--type decision --status open` query against the first ref. It just needs to + return *cleanly* — `0 results` is fine; an **error** means the ref doesn't route. + +If a query errors, or the primary returns nothing, surface it and fix the project +ref before closing — don't let the next session's brief come up empty. + ## Close Confirm what you did in a few lines: the project mapping, which schemas were seeded -vs. already present, whether conventions were learned, and whether the output style -is on. End with: *"Done — I'll use this from the next message. Run -`/basic-memory:status` anytime to see what I'm tracking."* Note that the output -style (if enabled) takes effect on the next session, since it's fixed at startup. +vs. already present, whether placement was learned or suggested, the smoke-test +result, and whether the output style is on. + +Then handle activation based on the output style: +- **Output style enabled** → it's fixed at session start, so the full capture + reflexes only take effect next session. Prompt the user to **restart the session** + (start a new Claude Code session) to activate them. Be precise so it doesn't read + as "nothing works yet": recall is already live this session (the SessionStart + hook's prompt ran), and the PreCompact checkpoint works now too — only the + proactive-capture reflexes wait for the restart. +- **Output style off** → no restart needed; the hooks already run. + +End with: *"Done — I'll use this from the next message. Run `/basic-memory:status` +anytime to see what I'm tracking."*