diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 21cfc100f..b7e9753bb 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ { "name": "basic-memory", "source": "./plugins/claude-code", - "description": "Skills, agents, and hooks for Basic Memory MCP — placement-aware note writing and knowledge graph workflows", + "description": "The bridge between Claude's working memory and Basic Memory's durable knowledge graph — session briefings, pre-compaction checkpoints, and capture reflexes", "version": "0.3.13", "author": { "name": "Basic Machines" diff --git a/.claude/commands/release/release.md b/.claude/commands/release/release.md index 27390b33a..db0550748 100644 --- a/.claude/commands/release/release.md +++ b/.claude/commands/release/release.md @@ -42,7 +42,7 @@ The justfile target handles: - ✅ Version format validation - ✅ Git status and branch checks - ✅ Quality checks (`just check` - lint, format, type-check, tests) -- ✅ Version update in `src/basic_memory/__init__.py` +- ✅ Version update across all consolidated manifests via `just set-version` (Python package + Claude Code plugin/marketplaces + Hermes + OpenClaw) - ✅ Automatic commit with proper message - ✅ Tag creation and pushing to GitHub - ✅ Release workflow trigger (automatic on tag push) @@ -191,7 +191,13 @@ Users can now upgrade: - This creates production releases used by end users - Must pass all quality gates before proceeding - Uses the automated justfile target for consistency -- Version is automatically updated in `__init__.py` and `server.json` +- Version is automatically updated across **all** consolidated manifests via + `just set-version ` (which calls `scripts/update_versions.py`): the + Python package (`__init__.py`, `server.json`) **and** the plugin/agent artifacts + (Claude Code `plugin.json` + root/local marketplaces, Hermes `plugin.yaml` + + `__init__.py`, OpenClaw `package.json`). To bump only the plugin/agent artifacts + out of band, use `just set-packages-version ` (preview with + `just set-packages-version-dry-run `). - Triggers automated GitHub release with changelog - Package is published to PyPI for `pip` and `uv` users - Homebrew formula is automatically updated for stable releases diff --git a/AGENTS.md b/AGENTS.md index 53d9ad88b..3b37bc508 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,7 @@ If testmon is “cold,” the first run may be long. Subsequent runs get much fa The monorepo ships several host-native packages alongside the Python core. Use the root justfile as the canonical entry point: - `just package-check` — validates every copied package and generated bundle path. -- `just package-check-claude-code` — validates the root and plugin-local Claude marketplace manifests, bundled Claude Code skills, hooks, `basic-memory-manager` agent, and runs `claude plugin validate . --strict`. +- `just package-check-claude-code` — validates the root and plugin-local Claude marketplace manifests, the SessionStart/PreCompact hooks, the bundled output style, and the seed schemas, then runs `claude plugin validate . --strict`. - `just package-check-skills` — validates every top-level `skills/memory-*/SKILL.md` frontmatter block. - `just package-check-hermes` — validates `integrations/hermes/plugin.yaml`, the Hermes provider entrypoint, bundled skill, and runs the hermetic unit suite. - `just package-check-openclaw` — runs the OpenClaw package install, copies top-level skills into the generated bundle, typechecks, lints, builds `dist/`, runs Bun tests, and performs `npm pack --dry-run`. diff --git a/README.md b/README.md index 60de99c46..93dd6255f 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,13 @@ just package-check-openclaw ### Claude Code plugin -The Claude Code plugin bundles Basic Memory-aware skills, hooks, and an agent: +The Claude Code plugin is the bridge between Claude's working memory and Basic +Memory — session-start briefings, pre-compaction checkpoints, an opt-in capture +output style, and `/basic-memory:setup` · `:remember` · `:share` · `:status`. + +**Connect the Basic Memory MCP server first** — see [Connect your AI +client](#connect-your-ai-client). The plugin's hooks and skills call it, so it's a +hard prerequisite. Then add the marketplace and install: ```bash claude plugin marketplace add basicmachines-co/basic-memory \ @@ -275,6 +281,10 @@ Restart Claude Desktop. Notes live in `~/basic-memory` by default. claude mcp add basic-memory -- uvx basic-memory mcp ``` +For the full memory bridge — session briefings, pre-compaction checkpoints, and +the `/basic-memory:*` commands — also install the [Claude Code +plugin](#claude-code-plugin) on top of this. + ### Codex CLI Add to `~/.codex/config.toml`: diff --git a/integrations/hermes/__init__.py b/integrations/hermes/__init__.py index b82501a48..f3ecf485b 100644 --- a/integrations/hermes/__init__.py +++ b/integrations/hermes/__init__.py @@ -449,6 +449,9 @@ def _extract_mcp_text(result: Any) -> str: Returns a JSON string for the agent. If the result is itself JSON, returns it as-is. Otherwise wraps the text in `{"text": "..."}` for downstream parsing. """ + # The `mcp` SDK is an unpinned dependency (see plugin.yaml), and its result + # shapes (CallToolResult, content blocks) have shifted across versions — read + # these fields defensively with getattr rather than direct attribute access. is_error = bool(getattr(result, "isError", False)) parts: list[str] = [] for c in getattr(result, "content", None) or []: @@ -594,6 +597,9 @@ async def _main(self) -> None: self._stop_future = asyncio.get_running_loop().create_future() try: listing = await session.list_tools() + # Same unpinned-`mcp`-SDK reason as parse_result above: + # ListToolsResult / Tool field shapes vary across SDK versions, + # so read them defensively rather than by direct attribute access. self._tools_cache = [ { "name": getattr(t, "name", ""), diff --git a/justfile b/justfile index b30bc0445..12594c89e 100644 --- a/justfile +++ b/justfile @@ -295,9 +295,25 @@ package-check-openclaw: migration message: cd src/basic_memory/alembic && alembic revision --autogenerate -m "{{message}}" +# Set the Basic Memory version across release manifests (scope: all | core | packages) +set-version version scope="all": + python3 scripts/update_versions.py "{{version}}" --scope "{{scope}}" + +# Preview a version update without writing (scope: all | core | packages) +set-version-dry-run version scope="all": + python3 scripts/update_versions.py "{{version}}" --scope "{{scope}}" --dry-run + +# Set the version for just the plugin/agent artifacts (plugin, marketplaces, Hermes, OpenClaw) +set-packages-version version: + just set-version "{{version}}" packages + +# Preview a plugin/agent-artifact version update without writing +set-packages-version-dry-run version: + just set-version-dry-run "{{version}}" packages + # Preview the consolidated manifest version update without changing files release-dry-run version: - python3 scripts/update_versions.py "{{version}}" --dry-run + just set-version-dry-run "{{version}}" # Create a stable release (e.g., just release v0.13.2) release version: @@ -340,7 +356,7 @@ release version: # Update all package manifests to the one Basic Memory product version. echo "📝 Updating consolidated package versions..." - python3 scripts/update_versions.py "{{version}}" + just set-version "{{version}}" # Commit version update git add \ @@ -413,7 +429,7 @@ beta version: # Update all package manifests to the one Basic Memory product version. echo "📝 Updating consolidated package versions..." - python3 scripts/update_versions.py "{{version}}" + just set-version "{{version}}" # Commit version update git add \ diff --git a/plugins/claude-code/.claude-plugin/marketplace.json b/plugins/claude-code/.claude-plugin/marketplace.json index 7f34c9318..88b4a8c3a 100644 --- a/plugins/claude-code/.claude-plugin/marketplace.json +++ b/plugins/claude-code/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ { "name": "basic-memory", "source": "./", - "description": "Skills, agents, and hooks for Basic Memory MCP — placement-aware note writing and knowledge graph workflows", + "description": "The bridge between Claude's working memory and Basic Memory's durable knowledge graph — session briefings, pre-compaction checkpoints, and capture reflexes", "version": "0.3.13", "author": { "name": "Basic Machines" diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json index 84c7668d4..396eba115 100644 --- a/plugins/claude-code/.claude-plugin/plugin.json +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "basic-memory", - "description": "Claude Code skills, agents, and hooks for Basic Memory — placement-aware note writing and knowledge graph workflows", + "description": "The bridge between Claude's working memory and Basic Memory's durable knowledge graph — session briefings, pre-compaction checkpoints, and capture reflexes", "version": "0.3.13", "author": { "name": "Basic Machines" diff --git a/plugins/claude-code/CHANGELOG.md b/plugins/claude-code/CHANGELOG.md index b1997ba4e..792f1656c 100644 --- a/plugins/claude-code/CHANGELOG.md +++ b/plugins/claude-code/CHANGELOG.md @@ -1,5 +1,94 @@ # Changelog +## Unreleased — v0.4 bridge redesign (Phases 1–4) + +The plugin is reframed as the **bridge between Claude's working memory and Basic +Memory's durable graph**, rather than a memory layer of its own. See +[DESIGN.md](./DESIGN.md) for the full rationale and roadmap. + +### Added + +- **Team workspace support** (Phase 4). SessionStart now reads **across** the primary + project plus configured shared/team projects — `secondaryProjects` (read-only recall + sources) and `teamProjects` (share targets) — querying open decisions from each in + parallel and folding them into the brief. Team refs use workspace-qualified names + (`my-team/notes`) or `external_id` UUIDs, since project names collide across + workspaces. Reads route over the user's OAuth session; capture **never** writes to a + shared project. +- **`/basic-memory:share `** (`skills/share/`) — the deliberate personal→team + write: copies a note from the primary project into a configured `teamProjects` + target's `promoteFolder`, with `shared_from` attribution and a confirmation step. + Preserves the note's type so shared decisions stay findable in the team's structured + recall. (Phase 4) +- **`/basic-memory:setup`** (`skills/setup/`) — a short guided interview that + configures the project for the plugin: maps it to a Basic Memory project (picking + an existing one or creating a new one), seeds the `session`/`decision`/`task` + schemas into the project, installs the shared `memory-*` skills via + `npx skills add basicmachines-co/basic-memory --path skills` (the plugin doesn't + vendor its own copies — `skills/` is the single source of truth, shared with + OpenClaw), optionally learns the project's placement conventions, and enables the + capture reflexes. Writes the `basicMemory` block to + `.claude/settings.json` (or `settings.local.json`). The SessionStart hook nudges + toward this on first run; running it (writing the config) stops the nudge. (Phase 3) +- **`/basic-memory:remember `** (`skills/remember/`) — quick deliberate + capture. Writes the text verbatim to the `rememberFolder` (default `bm-remember`) + with a first-line title and a `manual-capture` tag, via the connected Basic Memory + MCP server. Also fires when the user says "remember that…". (Phase 2) +- **`/basic-memory:status`** (`skills/status/`) — diagnostic that reports the active + project, capture/remember folders, output-style state, recent session checkpoints, + and active-task count. User-invoked only (`disable-model-invocation`). (Phase 2) + + Both verified discoverable via `claude plugin details` — they surface as + plugin-namespaced commands (`/basic-memory:`). + + +- **SessionStart hook** (`hooks/session-start.sh`) — briefs Claude at session + start with active tasks from the graph (one structured `type: task` query) plus + an always-on recall prompt. Works against the default project with zero config; + pin a project via `basicMemory.primaryProject`. Plain-stdout output, capped well + under the 10k limit, and silent if Basic Memory isn't installed. +- **PreCompact hook** (`hooks/pre-compact.sh`) — writes a `type: session` + checkpoint to the graph before context compaction (extractive in this phase; + LLM-summarized capture is the next step). Only writes when a `primaryProject` is + configured, so it never touches a graph the user hasn't opted in. +- **Output style** (`output-styles/basic-memory.md`) — opt-in reflexes: search + before recalling, capture decisions as typed `decision` notes, cite permalinks. + Sets `keep-coding-instructions: true` so it composes with normal dev work. +- **Seed schemas** (`schemas/{session,decision,task}.md`) — picoschema for the + note types the plugin writes, so recall via `search_notes` metadata filters is + precise. `task` mirrors the framework-agnostic `memory-tasks` skill. Validation + mode `warn` — advisory, never blocking. +- **`settings.example.json`** — copyable configuration with sensible defaults. + +### Changed + +- **SessionStart hook now nudges toward `/basic-memory:setup` on first run** — when + no `basicMemory` config block is present in either settings file. The nudge + survives a failed/empty task query (so a brand-new user with no project yet still + sees it), and stops once setup writes the config. (Phase 3) + +### Removed (clean break) + +- The six bundled skills (`placement`, `knowledge-capture`, `knowledge-organize`, + `continue-conversation`, `research`, `edit-note`). Equivalent, framework-agnostic + workflows live in the top-level [`skills/`](../../skills) package + (`memory-notes`, `memory-research`, `memory-tasks`, `memory-schema`, …); install + those for the old capabilities. +- The `basic-memory-manager` agent. The plugin ships no agent in v0.4 — memory is + handled in the main context via hooks and the output style, not delegated. +- The `PreToolUse`/`PostToolUse` `write_note` hooks (placement advisory + save + confirmation). Placement guidance now lives in the `basicMemory` settings block + and the output style. +- The `basic-memory` config-note convention, superseded by `.claude/settings.json`. +- `PLUGIN.md`, replaced by a bridge-framed `README.md` and `DESIGN.md`. + +### Notes + +- Slash commands shipped by later phases (`/basic-memory:setup`, + `:remember`, `:status`) will be **plugin-namespaced** — Claude Code namespaces + all plugin skills as `/:`. +- Requires `basic-memory >= 0.19.0` (for `metadata_filters` / structured recall). + ## 0.3.13 ### Fixed diff --git a/plugins/claude-code/DESIGN.md b/plugins/claude-code/DESIGN.md new file mode 100644 index 000000000..15e76b751 --- /dev/null +++ b/plugins/claude-code/DESIGN.md @@ -0,0 +1,621 @@ +# Basic Memory for Claude Code — Design + +**Status:** Draft (v0.4 modernization) +**Supersedes:** the current `PLUGIN.md` feature-list framing +**Related:** +- [SPEC-58: Consolidate Agent Integrations](../../../basic-memory-llc/specs/active/spec-58-consolidate-agent-integrations-into-basic-memory.md) (parent) +- [Plan — Basic Memory on Rails](../../../basic-memory-llc/planning/plan-basic-memory-on-rails.md) (parent) +- `integrations/hermes/` and `integrations/openclaw/` (sibling integrations — same pattern, different agent host) + +--- + +## 1. Positioning — what this plugin actually is + +Claude Code now has a working-memory layer of its own: auto-memory at `~/.claude/projects//memory/MEMORY.md`, plus per-subagent memory at `.claude/agent-memory/`. Claude writes to it itself, loads the first 200 lines / 25 KB at session start, and splits topic files off when MEMORY.md grows too long. + +**This plugin is not a memory layer. It is the bridge between Claude's working memory and Basic Memory's durable, semantic, portable graph.** + +This is the product's documented stance, not our invention. The official [Basic Memory vs built-in memory](https://docs.basicmemory.com/raw/concepts/vs-built-in-memory.md) page is explicit: + +> "Basic Memory doesn't replace them — it works alongside them. **The best setup uses both.**" + +Their stated division of labor: +- **Built-in memory** (Claude auto-memory, CLAUDE.md): coding standards, preferences, working instructions — "the immediate operational layer." +- **Basic Memory**: architecture decisions, research, meeting summaries, project context — "persistent, interconnected organizational infrastructure" that's searchable, linkable, and portable across every AI tool. + +The plugin is the *mechanism* that makes that documented "use both" setup actually happen automatically, instead of requiring the user to manually shuttle context between the two. + +Two memories, two jobs: + +| | Claude auto-memory | Basic Memory | +| -------------- | --------------------------------------- | --------------------------------------------- | +| **Where** | `~/.claude/projects//memory/` | Your filesystem + BM graph | +| **Who writes** | Claude, automatically | Both human and Claude, deliberately | +| **Scope** | Per-project, local-only, hidden | Cross-project, syncable, portable, sharable | +| **Captures** | Build commands, debugging tips, patterns | Decisions, research, observations + relations | +| **Loaded** | Every session, automatically | Queried on demand | +| **Format** | Whatever Claude finds useful | Markdown + frontmatter + observations + relations | + +1 + 1 = 3 is the thesis: auto-memory tells the plugin "we were here, on this topic, recently"; BM brings the graph neighborhood, open decisions, and active tasks. Neither alone is targeted. Together, the brief is. + +## 2. Personas + +The plugin serves three personas with the same spine and divergent add-ons. + +### The thinker — non-developer power user +Writer, researcher, consultant, planner. Lives in Claude Code or Claude Desktop. No git, no PRs. Keeps a body of work that compounds over months. **Cares about:** picking up where they left off, not losing texture of long conversations, recalling what they actually decided. + +### The builder — developer +Uses git, GitHub, runs Claude Code as their primary IDE companion. Same needs as the thinker, plus a code dimension. **Cares about:** tying decisions to commits, surviving multi-hour debugging without amnesia, code archaeology weeks later. + +### The operator — PM, team lead, ops +Runs multiple concurrent projects through Claude. Maybe occasional git, mostly meetings, decisions, status. **Cares about:** not bleeding context between projects, recalling cross-project patterns, weekly digests delivered without their attention. + +The thinker's needs are the foundation. The builder adds git hooks. The operator adds routines. + +## 3. Architecture — the four core surfaces + +We keep the plugin to a small number of well-chosen artifacts. Everything else (workflow skills, agents, deep references) loads from the top-level `skills/` package on demand. + +``` +plugins/claude-code/ +├── .claude-plugin/ +│ ├── plugin.json +│ └── marketplace.json +├── hooks/ +│ ├── session-start.sh # ambient: brief Claude on what's relevant +│ └── pre-compact.sh # ambient: checkpoint before amnesia +├── output-styles/ +│ └── basic-memory.md # reflexes: search first, capture decisions +│ # NOTE: rules/ deferred — path-scoped rules don't load yet (Q5) +├── skills/ +│ ├── setup/SKILL.md # /basic-memory:setup — bootstrap interview (first-run) +│ ├── remember/SKILL.md # /basic-memory:remember — quick deliberate capture +│ └── status/SKILL.md # /basic-memory:status — show plugin state +├── schemas/ # picoschema seeds, copied into the user's BM project at bootstrap +│ ├── session.md # type: session — resume checkpoints +│ ├── decision.md # type: decision — durable choices + rationale +│ └── task.md # type: task — active work tracking +├── settings.example.json # opinionated defaults, easy to copy +├── DESIGN.md # this file +└── README.md # rewritten around the bridge story +``` + +Three layers, matching what Hermes and OpenClaw converged on: + +| Layer | Surface | When it fires | What it does | +| ----------- | ---------------------------------- | ------------------------------------------ | ------------------------------------- | +| Ambient | `hooks/`, `rules/` | Lifecycle events, file context | Brief Claude, checkpoint, guide placement | +| Background | `output-styles/basic-memory.md` | System prompt, every turn | Reflexes — search first, capture inline | +| Deliberate | `skills/{setup,remember,status}/` | User invokes (`/basic-memory:setup`, `/basic-memory:remember`) | One-shot user gestures | + +## 4. The core flows + +### 4.1 Cold-start resume — SessionStart hook + +Fires once per session before the LLM sees anything. Assembles a **targeted briefing** by querying multiple sources in parallel and emitting a structured context block. + +**Inputs:** `cwd`, configured BM project(s), and (optionally) auto-memory's `MEMORY.md` read **from disk** — see the timing caveat below. + +> **Verified (Q1):** the firing order of `SessionStart` relative to auto-memory load is *not documented*, so we don't assume auto-memory is already in context when the hook runs. If the brief wants to factor in auto-memory's last-topic, the hook reads `~/.claude/projects//memory/MEMORY.md` directly from disk (always available) rather than relying on it being in context. The 1+1=3 trick still works; it's just a file read, not a context read. + +**Parallel queries — structured metadata search, not fuzzy full-text.** This is the multiplier: because the plugin ships schemas (§4.5) that stamp `type` and `status` onto every note it writes, recall is *deterministic*. We ask for exactly the notes that matter, not "things that look textually similar to the cwd." + +1. Active tasks: `search_notes("", metadata_filters={"type": "task", "status": {"$in": ["active", "in-progress"]}}, project=)` +2. Open decisions: `search_notes("", metadata_filters={"type": "decision", "status": "open"}, project=)` +3. Recent sessions: `search_notes("", metadata_filters={"type": "session"}, after_date=, project=)` — the last checkpoint carries the resume cursor +4. Recent activity (catch-all, anything untyped): `recent_activity(timeframe=, project=)` +5. (If team workspaces enabled) Queries 1–3 against team project(s) in parallel + +All five run concurrently. Structured filters return precise sets; the catch-all `recent_activity` sweeps up anything not yet schema-typed. The brief leads with the precise sets and falls back to the sweep. + +**Output format** (cribbed from OpenClaw, refined): + +```markdown +## Basic Memory — context for this session + +**Project:** my-app (personal) · 7 days since last activity + +### Active tasks +- **Wire SessionStart hook** — outline drafted, hook script next [permalink] +- **Decide on routines for v0.4** — blocked on platform docs [permalink] + +### Recent activity (last 3 days) +- DESIGN.md draft for Claude Code plugin (decisions/) +- Notes on Hermes recall pattern (research/) + +### Possibly relevant to your current work +- BM project mapping decision (decisions/) — referenced 2x last week + +--- + +You have Basic Memory available. Before answering recall questions ("what did we +decide", "where did we leave off"), search BM first. When the user makes a +material decision, capture it as a DecisionNote inline. Cite permalinks when +referencing prior work. +``` + +The trailing instruction block is **the recall prompt** — without it, agents ignore injected context. Customizable via `settings.json` (`recallPrompt` key). + +**Output mechanism (verified, Q4):** SessionStart injects context two ways — plain stdout (anything the hook prints is added to context, no JSON needed) or JSON `{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "..."}}`. We use **plain stdout** — simpler, fewer parse failures. Two hard constraints: +- **10,000-character cap per output string.** Overflow is spilled to a file and replaced with a preview + path. The brief must stay well under 10k — so recent-activity inclusion is bounded (cap each section at N items; prefer permalinks over previews). +- **Shell-profile interference.** If the user's shell profile echoes anything on startup, it corrupts hook output. The hook must run with a clean environment / guard against profile noise. + +**Performance budget:** SessionStart should complete in under 1 second. Parallelize all queries. If BM is unreachable, emit a minimal "BM unavailable; auto-memory only this session" stub and continue — never block the session. + +### 4.2 Compaction without amnesia — PreCompact hook + +Fires immediately before context compaction. Writes a **SessionNote** (checkpoint) to BM containing what Claude is about to forget. + +**Content of the SessionNote:** +- Frontmatter: `type: session`, `started`, `ended_or_compacted`, `project`, `cwd`, `claude_session_id` +- Observations: + - `[decision]` items surfaced this session + - `[problem]` / `[attempted]` / `[rejected]` — what we tried that didn't work (critical for resume) + - `[next-step]` — explicit cursor for the next session +- Relations: `produced [[]]` for each BM write this session +- Open threads: free-form list of unresolved questions + +**Where it lands:** `/sessions/-.md`. + +**Latency budget (verified, Q2) — much more generous than we assumed.** PreCompact hooks block compaction *synchronously* with a **600-second (10-minute) default timeout** (configurable). A multi-second MCP `write_note` call — or even a full LLM summarization pass — fits comfortably inside that window. This flips our earlier "extractive-only" assumption: + +- **Default is now a real summary**, not extractive heuristics. The hook can call out to summarize the transcript into a proper SessionNote. `preCompactCapture: "summarized"` becomes the sensible default; `"extractive"` stays as a fast fallback option. +- The hook does *not* need `decision: block` (we're not preventing compaction, just recording before it). It writes the SessionNote and exits 0, letting compaction proceed. +- One nuance from verification: the timeout cancels the hook if exceeded, and compaction proceeds regardless — so the write should be resilient to being killed mid-flight (write the note early, enrich after, rather than building everything then writing once). + +**Optional conformance check (verified, Q8):** the hook may validate the note it just wrote via `schema_validate(identifier=)` — the **single-note** path is cheap and bounded (one entity load + one schema-file read). Do **not** call batch `schema_validate(note_type="session")` here — that scans every session note with per-note file I/O (O(N)); leave batch validation and `schema_diff` to the nightly hygiene routine. + +### 4.3 The reflex layer — output-style (rules deferred) + +**`output-styles/basic-memory.md`** appends to the system prompt: +- Before answering questions that look like recall, call `search_notes` (prefer `metadata_filters` for typed notes) or `build_context`. Don't answer from training. +- When the user makes a material decision, write a `DecisionNote` (`type: decision`, `status: open`) inline as part of your response — conforming to the `decision` schema so it's queryable later. +- When referencing prior work, cite the permalink. +- When auto-memory and BM disagree, flag the conflict explicitly. +- Stay within the active BM project unless the user explicitly asks for another. + +The reflexes deliberately target *typed, schema-conforming* writes. A decision captured without `type: decision` is invisible to the next session's structured recall; a decision captured *with* it shows up in the SessionStart brief automatically. The output-style is what makes capture land in the queryable shape. + +User opts in via `outputStyle: basic-memory` in their settings. Not auto-applied — this is the user's choice of how Claude behaves. + +**Path-scoped rules are deferred (verified, Q5 — refuted).** The original plan was a `rules/basic-memory.md` with `paths:` frontmatter that loads placement/format conventions only when a BM note is in context. Verification found that path-scoped rules **do not load automatically at all** in the current Claude Code (open bug [anthropics/claude-code#16853](https://github.com/anthropics/claude-code/issues/16853), "never worked"), and even when fixed they're repo-relative — they would *not* match BM notes living outside the git tree under `~/basic-memory/` (issue #25562). So we don't ship a path-scoped rule. + +**Where placement/format conventions live instead:** the bootstrap-inferred conventions (folder naming, favored observation categories, frontmatter shape) are stored in the `basicMemory` block of `.claude/settings.json` and surfaced two ways: +1. The **SessionStart hook** folds a one-line conventions summary into the brief (within the 10k budget). +2. The **output-style** references "follow the project's stored placement conventions when writing notes." + +If/when path-scoped rules start working *and* support out-of-tree globs, we can add a `rules/basic-memory.md` as a third surface. Until then it's dead weight that wouldn't load. + +### 4.4 Deliberate gestures — skills + +Three skills only, each Claude-Code-specific (everything else lives in top-level `skills/`). + +> **Verified (Q3) — slash commands are always plugin-namespaced.** A skill folder `skills/remember/` in a plugin whose `plugin.json` name is `basic-memory` is invoked as **`/basic-memory:remember`**, not `/remember` — namespacing is mandatory and can't be shortened. Consequence: we drop the redundant `bm-` prefix from skill folder names (the namespace already says `basic-memory`). So the folders are `setup/`, `remember/`, `status/`, surfacing as `/basic-memory:setup`, `/basic-memory:remember`, `/basic-memory:status`. Skills are auto-discovered on install — no extra registration. + +**`/basic-memory:setup`** — bootstrap interview (§7). Run after install and any time the user wants to reconfigure. + +**`/basic-memory:remember `** — quick capture. Writes to a `bm-remember/` folder, separated from auto-captures. First line becomes title (truncated to 80 chars), tagged `manual-capture`. Optional `--project` flag for cross-project. + +**`/basic-memory:status`** — show plugin state: active BM project, capture folders, recent SessionNotes, sync status, last successful BM call. Trust-building UI. + +### 4.5 The schema layer — why our note types are contracts, not conventions + +Basic Memory ships a [schema system](https://docs.basicmemory.com/raw/concepts/schema-system.md) (Picoschema) and [structured metadata search](https://docs.basicmemory.com/raw/concepts/metadata-search.md). The plugin leans on both. This is the single biggest "leverage what BM already does" decision in the design. + +**The plugin ships schemas for the note types it creates.** Three to start: + +| Note type | `type:` | Written by | Purpose | +| --------- | ------- | ---------- | ------- | +| Session | `session` | PreCompact hook, `/basic-memory:handoff` (future) | Resume cursor — what we were doing, what's next | +| Decision | `decision` | output-style reflex, `/basic-memory:decide` (future) | Durable record of choices + rationale | +| Task | `task` | user + Claude | Active work tracking (aligns with `skills/memory-tasks`) | + +These conform to the SessionNote / DecisionNote picoschema shapes defined in SPEC-55, so when the SPEC-55 Writer SDK and async pipeline land, validation Just Works and nothing has to change in user-facing behavior. + +**Why schemas, not just "write notes with a convention":** + +1. **Deterministic recall.** A schema stamps `type` and `status` into frontmatter. SessionStart (§4.1) then queries `metadata_filters={"type":"session"}` and gets *exactly* the sessions — no fuzzy matching, no false positives, no missed notes. Structured search is AND-composable: `{"type":"decision","status":"open","tags":["auth"]}` returns open auth decisions, full stop. This is the difference between a recall brief that's *precise* and one that's *vibes*. + +2. **The schema teaches the structure.** Picoschema fields map to observations *and* relations *and* frontmatter: + + ```yaml + # schemas/session.md + --- + title: Session + type: schema + entity: session + version: 1 + schema: + summary: string, one-paragraph what-happened + next_step?(array): string, cursor for resuming + decision?(array): string, decisions made this session + problem?(array): string, problems hit (incl. attempted-and-rejected) + produced?(array): Entity, notes created this session # → `produced [[note]]` relations + settings: + validation: warn + frontmatter: + project: string + cwd?: string + started: string + status?(enum): [open, resumed, closed] + claude_session_id?: string + --- + ``` + + The PreCompact hook and the output-style don't need to memorize the SessionNote shape — they read it from the schema. The schema is the single source of truth for "what a good checkpoint looks like." + +3. **Drift detection becomes a hygiene routine.** `schema_diff` flags when session/decision notes start diverging from their schema over time. A future nightly routine can run it and surface drift. `schema_validate` can run in the PreCompact hook to confirm the checkpoint we just wrote actually conforms before we rely on it next session. + +4. **Bootstrap can infer, not dictate.** `schema_infer` analyzes the user's *existing* notes and suggests schemas/conventions. The bootstrap interview (§7) runs it to seed the stored placement/format conventions (in the `basicMemory` settings block — see §4.3, since path-scoped rules don't load yet) with the user's real patterns instead of imposing ours. Opinionated defaults, flexible to what's already there. + +**Validation mode: `warn`, never `strict`.** We stamp structure to make recall work, but we never block a write because a field is missing. The user's flow is sacred; the schema is a helper, not a gate. Users who want strict validation flip it themselves. + +**Where schemas live:** the plugin's bootstrap writes them into the primary BM project's `schemas/` folder (`schemas/session.md`, `schemas/decision.md`, `schemas/task.md`) — they become normal, editable BM notes the user owns, not hidden plugin internals. If the user already has schemas for these types, bootstrap leaves them alone. + +## 5. Claude Code projects ↔ Basic Memory projects + +These are orthogonal concepts that the plugin must explicitly map. + +**Claude Code project** = a cwd-keyed working directory (typically a repo). Auto-memory uses this as its key. + +**Basic Memory project** = a logical knowledge base. Could be 1:1 with a repo, could be cross-cutting, could be unrelated. + +### 5.1 Mapping model + +Each Claude Code project has: +- **One primary BM project** — destination for SessionStart context + PreCompact checkpoints + `/basic-memory:remember`. Required. +- **Zero or more secondary BM projects** — read-only by default for recall (SessionStart can query them); writes require explicit user gesture. + +Mapping is configured in `.claude/settings.json`: + +```json +{ + "basicMemory": { + "primaryProject": "my-app", + "secondaryProjects": ["team-engineering", "personal/notes"], + "captureFolder": "sessions", + "rememberFolder": "bm-remember", + "recallPrompt": "...", + "recallTimeframe": "3d" + } +} +``` + +Resolution order for the primary project when the setting is absent: +1. `.claude/settings.local.json` (per-user override) +2. `.claude/settings.json` (team-committed default) +3. `basic-memory` note at the repo root (legacy, kept for back-compat) +4. User's global default project (from BM CLI: `bm project list --default`) +5. Trigger bootstrap interview + +### 5.2 The 1:1 default is fine but not enforced + +For most thinker-persona users, one repo ↔ one BM project is natural. For developers and operators, fan-out is common (one BM project spans multiple repos: a "company-knowledge" project that aggregates context from every code repo). The plugin should not force 1:1. + +## 6. Team / shared workspaces + +Basic Memory Cloud has **workspaces** (each an org or personal space) containing +**projects**. Some users contribute to *shared* projects visible to teammates. This +needs deliberate, safe-by-default design. + +**Routing reality (verified against a real two-workspace account):** project names +**collide across workspaces** — e.g. `main` and `getting-started` exist in more than +one. A bare name won't route. Every cross-workspace ref in config must therefore be a +**workspace-qualified name** (`/`, e.g. `my-team-2/notes`, +from the `qualified_name` field) or an **`external_id` UUID** (most stable — survives +renames). The CLI accepts these via `--project` / `--project-id`; the hook detects a +UUID and routes accordingly. Cross-workspace reads route over the user's OAuth session +(no API key needed) — confirmed working for both structured search and recent-activity. + +### 6.1 Defaults — safe by design + +- **Auto-capture defaults to personal.** SessionNotes, PreCompact checkpoints, and + `/basic-memory:remember` quick captures **only ever** land in `primaryProject`. The + capture hooks never write to a shared project — full stop, no opt-in flag in v0.4. +- **Recall reads across.** SessionStart queries the shared projects in parallel for + open decisions and folds them into the brief (read-only — discloses nothing). +- **Sharing is a deliberate gesture.** `/basic-memory:share` copies a personal note + into a configured team project with attribution, after explicit confirmation. The + personal→team boundary is always a visible, manual action. + +### 6.2 Configuration shape + +```json +{ + "basicMemory": { + "primaryProject": "basic-memory-7020…/main", + "secondaryProjects": ["my-team-2/main", "my-team-2/notes"], + "teamProjects": { + "my-team-2/notes": { "promoteFolder": "shared" } + } + } +} +``` + +- `secondaryProjects` — workspace-qualified refs (or UUIDs) read for recall. Read-only. +- `teamProjects` — share targets for `/basic-memory:share`; each carries a + `promoteFolder` (default `shared`). Also read for recall (SessionStart reads the + union of `secondaryProjects` and `teamProjects` keys, capped at 6 per session). + +**`autoWrite` is deferred.** An earlier draft proposed `teamProjects..autoWrite` +to let auto-capture write to a team project. v0.4 does **not** implement it — auto-capture +stays personal and sharing is always manual. Revisit only if a team explicitly wants +shared session memory; until then we don't ship a flag we don't enforce. + +### 6.3 What this unlocks + +- Team-engineering project becomes a *living memory* shared across the team. Recent decisions, ADRs, debugging notes from any teammate's session show up in everyone's SessionStart brief. +- New team members get instant context — first SessionStart pulls the team graph and they're already oriented. +- Cross-pollination — operator running a strategy session sees recent technical decisions from builders. + +## 7. Bootstrap — `/basic-memory:setup` interview + +Users get overwhelmed starting from zero. The plugin opens with a guided interview that establishes opinionated defaults from a short conversation. + +> **Verified (Q6) — there is no install hook.** Claude Code has no PostInstall/PreInstall lifecycle event (feature request [#11240](https://github.com/anthropics/claude-code/issues/11240) was closed as duplicate). We can't auto-run setup the moment the plugin installs. The workaround is the **SessionStart hook detecting first-run**: it checks for a sentinel (e.g. `${CLAUDE_PLUGIN_DATA}/.bootstrapped` or the absence of a `basicMemory` config) and, if missing, injects a one-line nudge — *"Basic Memory isn't configured yet. Run `/basic-memory:setup` (≈3 min) to wire it up."* A bonus verified capability: SessionStart can return `{"reloadSkills": true}` to re-scan skills mid-session, useful if setup writes new skills/config that should activate without a restart. + +**Trigger paths:** +1. SessionStart detects no `basicMemory` config and no `basic-memory` note → inject the one-line nudge suggesting `/basic-memory:setup` (we cannot run it automatically) +2. User runs `/basic-memory:setup` explicitly (anytime, including for reconfiguration) + +**Interview script** (Claude executes it; SKILL.md provides the structure): + +1. *"Quick setup — about 3 minutes. What's this Claude Code project mostly about? (code / writing / research / planning / mixed)"* +2. *"Do you have a Basic Memory project for this already, or should I create one?"* + - If existing: offer `list_memory_projects()` output to pick from + - If new: ask name and folder (default: `~/basic-memory//`) +3. *"Are you using Basic Memory Cloud or local-only?"* + - If cloud + team workspace exists: *"You're on the `` workspace. Want me to also read from team projects for recall? (read-only by default; we can opt-in to writes later)"* +4. *"How chatty should I be?"* + - **Light** (default): SessionStart brief on each session, PreCompact checkpoint, `/basic-memory:remember` on demand + - **Standard**: above + capture decisions inline via output-style + - **Heavy**: above + every-session SessionNote even without compaction +5. *"Should I look at your existing notes and suggest some placement conventions?"* (yes → runs `schema_infer` on the existing notes, summarizes the patterns it found, and stores them in the `basicMemory` settings block — see §4.3, since path-scoped rules don't load yet — from the user's *real* conventions rather than imposed ones) +6. *"I'll set up schemas for session checkpoints and decisions so I can find them precisely later — okay?"* (yes → writes `schemas/session.md`, `schemas/decision.md`, `schemas/task.md` into the primary project, skipping any that already exist; validation mode `warn`) +7. *"Want me to enable the `basic-memory` output style now?"* (yes → adds `outputStyle: basic-memory` to settings) + +**Output:** writes `.claude/settings.json` (with prompt to commit or keep local) including any inferred conventions, writes the three schema notes, optionally creates the BM project, and drops the bootstrap sentinel so SessionStart stops nudging. Closes with: *"Done. I'll start using this on the next message. Try `/basic-memory:status` anytime to see what I'm tracking."* + +**Why an interview, not a config form:** the interview is *adaptive* — it skips questions when context is obvious (e.g., it sees you're on cloud and on a team; doesn't ask about local), suggests reasonable defaults the user can accept with a single word, and produces a meaningful starting point in under 3 minutes. A config form makes the user own every decision. + +## 8. Configuration — opinionated defaults + +`settings.example.json` ships the defaults; user copies to `.claude/settings.json` and tweaks. Bootstrap writes this automatically. + +```json +{ + "basicMemory": { + "primaryProject": null, + "secondaryProjects": [], + "captureFolder": "sessions", + "rememberFolder": "bm-remember", + "recallTimeframe": "3d", + "recallPrompt": "You have Basic Memory available. Search before answering recall questions. Capture material decisions inline. Cite permalinks when referencing prior work.", + "preCompactCapture": "summarized", + "placementConventions": null, + "teamProjects": {} + }, + "outputStyle": "basic-memory" +} +``` + +`preCompactCapture` defaults to `"summarized"` — verified (Q2) that PreCompact's 600s budget is ample for an LLM summarization pass; `"extractive"` remains as a fast fallback. `placementConventions` holds the bootstrap-inferred placement/format guidance (§4.3) since path-scoped rules don't load yet. Everything is overridable. Nothing is mandatory. + +## 9. What's out of scope (for v0.4) + +These come later or never: + +- **Per-turn capture into BM.** Auto-memory already does the per-turn working summary. Doubling it into BM creates noise without value. +- **Commit-hook integration (entire.io-style).** Worthy idea, but defer to a builder-add-on after the spine is proven. +- **Routines.** Defer to a separate doc + phase. Once the spine works, routines become "weekly digests, nightly hygiene, GitHub webhooks." +- **Statusline integration.** Polish; deferred. +- **Subagent memory bundling.** The platform now offers `memory: project|user|local` on subagents. Worth exploring but doesn't fit the spine. +- **Replacement of `basic-memory-manager` agent.** Plugin ships no agent in v0.4. Users who want one install from a separate package. + +## 10. What this replaces / deletes + +From the current plugin (v0.3.x): +- All six plugin-local skills (`continue-conversation`, `edit-note`, `knowledge-capture`, `knowledge-organize`, `placement`, `research`) → migrate workflows to top-level `skills/`, deprecate local copies +- `basic-memory-manager.md` agent → deleted; mention in CHANGELOG that users wanting it should install from `skills/` package +- `PreToolUse: write_note` hook (the "echo additionalContext" hook) → deleted; placement guidance moves to the `basicMemory` settings block + output-style (§4.3 — path-scoped rules don't load yet) +- `PostToolUse: write_note` confirmation echo → deleted (noise) +- `basic-memory` config-note convention → deprecated in favor of `.claude/settings.json` (kept as fallback for one release) +- 314-line `PLUGIN.md` feature list → replaced by a short `README.md` framed around the bridge story + +## 11. Verified findings (resolved open questions) + +Verified 2026-05-28 against Claude Code v2.1.153 and basic-memory 0.21.5, via a fan-out investigate → adversarial-verify workflow (each finding independently confirmed/refuted from a second angle). Verdicts: **confirmed** = both passes agree; **refuted** = verify pass overturned the first answer; **uncertain** = could not be confirmed, treat with caution. + +| # | Question | Verdict | Answer | Design consequence | +|---|----------|---------|--------|--------------------| +| **Q1** | Does SessionStart fire before/after auto-memory loads? Can the hook use auto-memory as input? | **uncertain** | Firing order vs `MEMORY.md` load is **not documented**. The often-cited "SessionStart fires before CLAUDE.md loads" phrasing isn't in current docs. What *is* certain: the hook can read `MEMORY.md` from disk anytime. | Don't assume auto-memory is in context at SessionStart. If the brief wants it, **read the file from disk** (§4.1). Don't build anything that depends on context-load ordering. | +| **Q2** | Does PreCompact block synchronously? Timeout? Can it do multi-second/LLM work? | **confirmed** | Blocks synchronously; **600s default timeout** (configurable). MCP/LLM calls fit easily. On timeout the hook is killed and compaction proceeds. (To *block* compaction you'd return exit 2 / `decision:block` before the timeout — we don't.) | **Upgraded the design.** `preCompactCapture` default is now `"summarized"` (real LLM pass), not extractive (§4.2, §8). Write the note early, enrich after, in case of kill. | +| **Q3** | Do plugin skills/commands appear in the `/` menu, and as what? | **confirmed** | Auto-discovered on install, but **always namespaced** as `/:`. Can't be shortened. No sparse/subdir caveats. | Drop the `bm-` prefix; folders become `setup/`, `remember/`, `status/` → `/basic-memory:setup` etc. (§4.4). README must show the namespaced form. | +| **Q4** | How does SessionStart inject context? Size limit? | **confirmed** | Plain stdout (added to context, no JSON needed) **or** JSON `hookSpecificOutput.additionalContext`. **10,000-char cap** per string; overflow spills to a file. Shell-profile echoes corrupt output. | Use plain stdout. Keep the brief well under 10k — cap each section's item count, prefer permalinks over previews. Guard against shell-profile noise (§4.1). | +| **Q5** | Do path-scoped `rules/` with `paths:` load for BM files (incl. outside the git tree)? | **refuted** | Path-scoped rules **don't load automatically at all** — open bug [#16853](https://github.com/anthropics/claude-code/issues/16853) ("never worked"). Even when fixed they're repo-relative (won't match `~/basic-memory/`, [#25562](https://github.com/anthropics/claude-code/issues/25562)). | **Dropped `rules/` from the plugin.** Placement/format conventions move to the `basicMemory` settings block + SessionStart brief + output-style (§4.3). Revisit if the platform bug is fixed *and* out-of-tree globs are supported. | +| **Q6** | Is there a run-on-install hook for bootstrap? | **confirmed** | No PostInstall/PreInstall lifecycle event ([#11240](https://github.com/anthropics/claude-code/issues/11240) closed as dup). Only SessionStart + explicit Setup. Bonus: SessionStart can return `{"reloadSkills": true}`. | Bootstrap can't auto-run. SessionStart detects first-run via a **sentinel file** and nudges the user to run `/basic-memory:setup` (§7). | +| **Q7** | Does `search_notes` support `metadata_filters` + `after_date` in 0.21.5? Min version? | **confirmed** | Both present and working in 0.21.5. Full operator set: `$in`, `$gt/$gte/$lt/$lte`, `$between`, array-contains, equality, dot-notation (`schema.confidence`). On `search_notes` since **v0.18.1** (verify corrected the first pass's "v0.19.0"). | Structured recall is safe as a **baseline** — no fallback path needed for the 0.21.5 target. Pin a minimum `basic-memory >= 0.19.0` in prerequisites for margin. Use `tags`/`status` shorthands for common cases (§4.1). | +| **Q8** | Is `schema_validate` cheap enough for PreCompact? Are the schema tools in 0.21.5? | **confirmed** | All three (`validate`/`infer`/`diff`) present since v0.19.0. `schema_validate(identifier=…)` = **cheap single-note**. `schema_validate(note_type=…)` = **batch, O(N)** with per-note file I/O. | PreCompact validates only the note it just wrote via the **identifier path** (§4.2). Batch validation + `schema_diff` go to the nightly hygiene routine (§13). | + +**Net effect on the design:** two findings made it *better* (Q2 → real LLM summaries at compaction; Q7 → structured recall is a safe baseline), two forced corrections (Q5 → no path-scoped rules; Q3 → namespaced commands), and one stays a known unknown to design around (Q1 → read auto-memory from disk, never assume context order). + +## 12. Task list — v0.4 milestone + +### Phase 0: Verify open questions — ✅ DONE (2026-05-28) +- [x] SessionStart timing vs auto-memory (Q1 — uncertain; read MEMORY.md from disk) +- [x] PreCompact latency budget (Q2 — 600s, LLM summary feasible) +- [x] Slash-command discovery + namespacing (Q3 — `/basic-memory:`) +- [x] `additionalContext` size limit (Q4 — 10k chars, plain stdout) +- [x] Path-scoped rules behavior (Q5 — refuted; rules don't load, dropped) +- [x] Bootstrap-on-install (Q6 — no install hook; SessionStart sentinel) +- [x] `metadata_filters`/`after_date` in 0.21.5 (Q7 — confirmed, baseline-safe) +- [x] `schema_validate` cost (Q8 — single-note cheap; batch → routine) +- [x] Findings recorded in §11 + +### Phase 1: The spine — ✅ DONE (2026-05-28) + +**Build decisions (user calls):** *minimal-first vertical slice* (one fast query at +SessionStart, extractive PreCompact — prove the loop, enrich later) and *hard-delete* +the old surfaces (no coverage audit, clean break). Hooks implemented in **bash + +python3** (python is a guaranteed BM dependency; the multi-query Python single-session +helper is the enrich step). `preCompactCapture` ships as `"extractive"` even though the +eventual default is `"summarized"` — the LLM pass is the first enrich step. + +- [x] Delete deprecated artifacts — six skills, `basic-memory-manager` agent, both + `write_note` hooks, `PLUGIN.md`, config-note convention +- [x] Write `schemas/{session,decision,task}.md` (picoschema, `validation: warn`; + `task` mirrors `memory-tasks`) +- [x] Validate the three schemas — confirmed they parse through BM's exact path + (`frontmatter.loads` → `parse_schema_note` → `parse_picoschema`). **Found + fixed an + enum-syntax bug**: enum descriptions must go *inside* the parens + (`status?(enum, desc): [a,b]`), not after the `[...]` value — the latter is invalid + YAML. (Same latent bug exists in the canonical `memory-tasks` schema → flagged as a + separate task.) +- [x] Write `hooks/session-start.sh` — plain-stdout brief, single `type: task` / + `status: active` query, recall prompt; works against the default project (pin via + `primaryProject`); silent if BM absent. Tested end-to-end. +- [x] Write `hooks/pre-compact.sh` — **extractive** checkpoint conforming to the + `session` schema; only writes when `primaryProject` is set; silent/no-op otherwise. + Tested end-to-end (note written + queryable by `--type session`). +- [x] Write `output-styles/basic-memory.md` — reflexes; `keep-coding-instructions: true` + so it composes with dev work. +- [x] ~~Write `rules/basic-memory.md`~~ — **cut** (Q5). Conventions go in `basicMemory` + settings + the brief. +- [x] Write `settings.example.json` (ships `preCompactCapture: "extractive"` for parity + with the implemented hook; `placementConventions` reserved) +- [x] Update `.claude-plugin/plugin.json` + both `marketplace.json` descriptions (bridge + framing; `name: basic-memory` confirmed for namespacing). Version left to release tooling. +- [x] Rewrite `scripts/validate_claude_plugin.py` for the new layout (hooks + + output-style + schemas; agent dropped; skills optional). Passes `ci-check` and + `claude plugin validate . --strict`. +- [x] Update plugin `CHANGELOG.md` + the `AGENTS.md` package-check description +- [x] Add `basic-memory >= 0.19.0` to prerequisites (Q7 margin) +- [ ] `update_versions.py` — no change needed (no new *versioned* manifests; hooks / + schemas / output-style / settings carry no version) + +**Carried into later phases (not part of the minimal cut):** the first-run *sentinel +nudge* moves to Phase 3 (it should point at `/basic-memory:setup`, which doesn't exist +yet); the multi-query parallel brief and the LLM-summarized PreCompact are the enrich +steps. + +**Implementation finding for Phase 3 bootstrap (corrected in Phase 3):** an earlier +Phase 1 note claimed the CLI `write-note` couldn't seed a resolvable schema and that +bootstrap must use the MCP `write_note(note_type="schema", metadata=…)` path. That was +**confounded by the enum-syntax YAML bug** — at the time of that test the schema +frontmatter didn't parse, so it couldn't index. Re-verified in Phase 3 with the fixed +schemas: writing a schema file's content via `write_note` (CLI or MCP, embedded +frontmatter) indexes it as `type: schema` **and** `schema_validate` resolves it +(`schema_entity: "Session"`, `valid_count: 1`). So **schema seeding is just a content +copy** — bootstrap reads each `schemas/*.md` and writes it via `write_note`; no +`note_type`/`metadata` gymnastics needed. + +### Phase 2: Deliberate gestures — ✅ DONE (2026-05-28) + +Both skills implemented as **prose skills** (skills are prompts) rather than +bash-injection scripts — avoids shell-quoting fragility on arbitrary user text and +the `${CLAUDE_SKILL_DIR}` path uncertainty, and works regardless of the MCP server's +tool-name prefix. + +- [x] Write `skills/remember/SKILL.md` → `/basic-memory:remember` — model-invocable + ("remember that…"); writes verbatim to `rememberFolder` with a first-line title and + `manual-capture` tag via `write_note`. +- [x] Write `skills/status/SKILL.md` → `/basic-memory:status` — `disable-model-invocation` + (user-only diagnostic); reports project, folders, output-style, recent checkpoints, + active-task count. +- [x] Test slash-command discovery end-to-end — installed from a local marketplace and + confirmed via `claude plugin details`: **Skills (2): remember, status**, namespaced + as `/basic-memory:` (validates Q3 live). Cleaned up afterward. +- [x] Validator now requires the shipped skill set (`REQUIRED_SKILLS`); passes + `claude plugin validate . --strict`. + +### Phase 3: Bootstrap interview — ✅ DONE (2026-05-28) + +Implemented as a **prose skill** (the interview is conversational; Claude runs it +using its MCP tools). Verified the whole loop end-to-end against throwaway projects. + +- [x] Write `skills/setup/SKILL.md` → `/basic-memory:setup` — adaptive interview that + maps the project, seeds schemas, optionally learns conventions, writes settings, and + enables the output style. Model-invocable ("set up basic memory") + user-invocable. +- [x] Wire `schema_infer`/`list_directory` into the bootstrap — the skill inspects the + folder layout + samples notes, summarizes the user's real conventions, and stores the + summary string in `basicMemory.placementConventions` (infer, don't dictate). +- [x] Wire schema-seeding — the skill reads `/schemas/*.md` (two dirs up from + the skill) and writes each via `write_note` (content copy, skip-if-exists). **Verified + end-to-end:** all three seed, index as `type: schema`, and resolve via + `schema_validate` (`entity=Session/Decision/Task`). This corrects the earlier Phase 1 + finding — schema seeding is a plain content copy; the previous "must use + `note_type`/`metadata`" conclusion was confounded by the enum YAML bug. +- [x] First-run detection in SessionStart — nudges toward `/basic-memory:setup` when no + `basicMemory` config block exists (config presence is the sentinel; no separate file). + The nudge survives a failed/empty task query. Verified across all three config states + (no config → nudge; block without project → pin tip; project pinned → silent). +- [x] `{"reloadSkills": true}` — **not needed.** Setup adds no new skill files mid-session + (the skills already ship with the plugin); config changes take effect when the hooks + next run. Documented as N/A rather than implemented. +- [x] Tests for inferred-conventions — the inference is **model-driven** (prose skill), + so there's no deterministic code unit to unit-test. The testable contract is the + skill's presence + frontmatter, enforced by `validate_claude_plugin.py`'s + `REQUIRED_SKILLS` (now `setup`, `remember`, `status`). Real validation is dogfooding. + +### Phase 4: Team workspace support — ✅ DONE (2026-05-28) + +Grounded in a real two-workspace BM Cloud account (verified name-collision routing, +OAuth cross-workspace reads). Pulled `/basic-memory:share` forward from future-work +since team usage needs a safe write path. + +- [x] Extend SessionStart to read primary + shared projects **in parallel** — + `ThreadPoolExecutor` over structured queries (primary: active tasks + open + decisions; each shared project: open decisions). Routes by qualified name or UUID, + per-call timeout, capped at 6 shared projects, graceful on any failure. Verified + against the real `my-team-2` workspace and with local fixtures. +- [x] Add `/basic-memory:share` (`skills/share/`) — the deliberate personal→team + write: reads a note from `primaryProject`, confirms, and copies it to a configured + `teamProjects` target's `promoteFolder` with `shared_from` attribution. Preserves + the note's type so shared decisions stay findable in the team's structured recall. +- [x] Config: `secondaryProjects` (read sources) + `teamProjects` (share targets with + `promoteFolder`). Documented in `settings.example.json` with the qualified-name + requirement. `autoWrite` deliberately **not** shipped (see §6.2). `REQUIRED_SKILLS` + now includes `share`. +- [x] `status` reports team read-sources + share targets; `setup` interview step 3 + now configures them via `list_workspaces` + qualified names. +- [x] Document the share-vs-capture distinction (README "Teams" section + the + read-only note in the SessionStart brief itself). + +### Phase 5: Docs + dogfood + +Docs done 2026-05-28; dogfood is the remaining (human) step. + +- [x] Rewrite `README.md` around the bridge story (done in Phase 1; expanded with + Commands + Teams sections in Phases 2-4). +- [x] `docs/why-combine-memory.md` — user-facing value prop, the three personas, use + cases, and the "use both" framing. +- [x] `docs/getting-started.md` — guided install → setup → see-it-work → team walkthrough. +- [x] `docs/architecture.md` — flow-by-flow with mermaid diagrams (the bridge, + SessionStart, PreCompact, capture reflexes, team read/share, component map). +- [x] Linked all three from the README's Documentation section. +- [x] ~~Migration guide~~ — **not needed.** v0.4 is a clean break; upgrade = uninstall + the old plugin, install the new one. Noted in the CHANGELOG's "Removed" section. +- [ ] Internal dogfood — full team uses v0.4 for a week of normal work (incl. the team + workspace path); file bugs against rough edges. +- [ ] Refine the recall prompt and SessionStart brief format based on real sessions. +- [ ] (Likely follow-ups surfaced by dogfood) the multi-query brief enrichment and the + LLM-summarized PreCompact checkpoint. + +### Phase 6: Release +- [ ] Bump version in lockstep with basic-memory release +- [ ] Update CHANGELOG +- [ ] `just package-check-claude-code` passes +- [ ] Tag and ship + +## 13. Future work (post-v0.4) + +- **Routines integration** — three routine templates (nightly hygiene, weekly digest, daily reflection). Separate design doc. The nightly hygiene routine is the natural home for `schema_diff` drift detection and the deferred LLM-summary pass over the day's extractive SessionNotes. +- ~~**`/basic-memory:share`** — promote personal note → team project~~ — shipped in Phase 4. +- **Team `autoWrite`** — opt-in for auto-capture (PreCompact/remember) to write to a + team project, for teams that want shared session memory. Deferred from Phase 4 (§6.2). +- **`/basic-memory:blame `** — code archaeology, builder add-on. +- **Commit-hook integration** — PostToolUse on `Bash(git commit *)` writes CommitNote linking SHA to session's BM writes. +- **Subagent memory bundling** — explore `memory: project|user` on dedicated BM subagents. +- **Statusline** — small visible presence (active project, last write). +- **`/basic-memory:promote`** — review auto-memory MEMORY.md, graduate observations into BM with proper schema. diff --git a/plugins/claude-code/PLUGIN.md b/plugins/claude-code/PLUGIN.md deleted file mode 100644 index f43948393..000000000 --- a/plugins/claude-code/PLUGIN.md +++ /dev/null @@ -1,314 +0,0 @@ -# Basic Memory Plugin for Claude Code - -This plugin provides skills and hooks for working with [Basic Memory](https://basicmemory.com) — a local-first knowledge management system built on the Model Context Protocol (MCP). - -It uses [Claude Code's plugin format](https://docs.claude.com/en/docs/claude-code/plugins) (skills and hooks bundled into an installable marketplace) and **only works with Claude Code**. - -This package lives in the canonical [`basic-memory`](https://github.com/basicmachines-co/basic-memory) repository under `plugins/claude-code/`. - -> **Looking for framework-agnostic skills?** See the top-level [`skills/`](../../skills) directory — `SKILL.md` files that work in Claude Code, Claude Desktop, and other MCP-compatible agents. The hooks in this plugin are Claude Code-specific and aren't available there. - -## Prerequisites - -You need the Basic Memory MCP server running. Install it via: - -```bash -# Install basic-memory -pip install basic-memory - -# Or with pipx -pipx install basic-memory -``` - -Then add it to your Claude Code MCP configuration. - -## Installation - -### Add the Marketplace - -``` -claude plugin marketplace add basicmachines-co/basic-memory --sparse .claude-plugin plugins/claude-code -``` - -### Install the Plugin - -``` -claude plugin install basic-memory@basicmachines-co -``` - -### Or via Repository Settings - -Add to your `.claude/settings.json`: - -```json -{ - "plugins": { - "extraKnownMarketplaces": { - "basicmachines-co": { - "source": { - "source": "github", - "repo": "basicmachines-co/basic-memory", - "sparse": [".claude-plugin", "plugins/claude-code"] - } - } - }, - "installed": ["basic-memory@basicmachines-co"] - } -} -``` - ---- - -## Configuration - -The plugin reads project conventions from a unified config file. Without one, it falls back to sensible built-in defaults. You can adopt the config gradually as you discover what you want to standardize. - -### Where it lives - -| Scope | Location | Format | -|-------|----------|--------| -| Project | A note titled `basic-memory` at the project root | Basic Memory note (read via MCP) | -| Global | `~/.basic-memory/basic-memory.md` | Filesystem markdown file | - -### Schema - -The config file uses H2 sections for categories and H3 sub-sections for project-specific overrides. Bare content under an H2 is the default; H3 sub-sections override it for a specific project. - -```markdown -# Basic Memory config - -## Projects -- work: default project for daily work -- personal: personal notes and reflections -- research: long-form research notes - -## Placements -- Place into existing folders by topic match -- Never create new top-level folders without asking -- Match the project's existing naming convention - -### research -- Long-form notes go in `papers/` -- Quick references go in `refs/` - -## Formats -- Required frontmatter: title, type, date -- Observation categories: fact, decision, technique, problem, solution - -## Schemas -### work -person: - - name - - email - - role -``` - -### Reserved sections - -| Section | Scope | Purpose | -|---------|-------|---------| -| `## Projects` | Global only | Routing rules — when to use which project | -| `## Placements` | Project or global | Folder conventions for new notes | -| `## Formats` | Project or global | Frontmatter and observation conventions | -| `## Schemas` | Project or global | Note type definitions | - -Other H2 sections are treated as user notes and ignored. - -### Precedence - -For each section the plugin needs: - -1. Project's `basic-memory` note → if the section exists, use it -2. Global `~/.basic-memory/basic-memory.md` → look for `### ` first, then bare content under the H2 -3. Built-in defaults - -Section-level fallback means a project file can override one section while inheriting others from global. - -### Bootstrap - -For an existing project with established conventions, ask Claude to generate a starter `basic-memory.md` based on what's already in the project: - -> "Look at my `` project structure and generate a starter `basic-memory` note for it. Inspect the folder layout and existing notes to infer placement and format conventions." - -Claude will: -1. Inspect the project tree (`list_directory`) and sample notes from each folder -2. Infer naming conventions, depth patterns, and organizational structure -3. Draft a `basic-memory` note with `## Placements` populated and other sections as commented placeholders -4. Show you the draft for review before writing - -This is a one-time conversational pattern — no slash command required. - ---- - -## Skills - -Model-invoked capabilities that Claude uses automatically based on context. - -### placement - -Decides which folder a new note belongs in. Runs automatically before every Basic Memory `write_note` call via a `PreToolUse` hook with matcher `mcp__.*__write_note` (catches local, cloud, and claude.ai connector variants). - -**Triggers when:** -- About to call any MCP basic-memory `write_note` tool -- Manually invoked when planning a write - -**How it works:** -1. Reads project and global config (`basic-memory.md`) — extracts `## Placements` rules -2. Short-circuits at the first definitive answer: - - Config rule applies → use it - - Tree match obvious → use it - - Search for related notes → use as a placement signal -3. Asks the user if placement remains ambiguous - -**Best for:** Keeping notes organized according to project conventions without per-write instruction. - -### knowledge-capture - -Automatically captures insights, decisions, and learnings into structured notes. - -**Triggers when:** -- Important decisions are made -- Technical insights are discovered -- Problems are solved -- Design trade-offs are discussed - -### continue-conversation - -Resumes previous work by building context from the knowledge graph. - -**Triggers when:** -- Starting a new session -- User mentions previous work ("continue with...", "back to...") -- Need context about ongoing projects - -### edit-note - -Interactively edit notes using MCP tools in a conversational workflow. - -**Triggers when:** -- User wants to edit, update, or modify a note -- User asks to change specific content in a note -- User wants to add observations or relations - -**How it works:** -1. Fetches the note via MCP -2. Shows current content -3. Applies edits using `edit_note` operations (append, prepend, find_replace, replace_section) -4. Shows the updated result - -### knowledge-organize - -Help organize, link, and maintain the knowledge graph. - -**Triggers when:** -- User wants to organize their notes -- User asks about orphan or unlinked notes -- User wants to find connections between notes -- User mentions duplicates or similar notes -- User asks for help with folder organization - -**Capabilities:** -- **Find orphan notes** - Identify notes with no relations -- **Suggest relations** - Propose meaningful links between notes -- **Identify duplicates** - Find notes covering similar topics -- **Folder organization** - Review and suggest folder structure -- **Tag consistency** - Normalize and improve tagging -- **Create index notes** - Generate hub notes linking related topics -- **Enrich sparse notes** - Suggest observations and structure - -**Best for:** Periodic knowledge base maintenance and improving discoverability. - -### research - -Research topics thoroughly and produce structured reports saved to Basic Memory. - -**Triggers when:** -- User asks to research or investigate something -- User wants to understand a concept or technology -- User needs context before making a decision -- Phrases like "research", "look into", "explore", "investigate" - -**What it produces:** -- Structured report with summary, findings, and analysis -- Recommendations when applicable -- Links to sources and related notes -- Saved to `research/` folder (or wherever `placement` directs) - -**Best for:** Building knowledge base through investigation and documentation. - ---- - -## Hooks - -Automated behaviors that enhance the Basic Memory workflow. - -### PreToolUse: write_note - -Advisory reminder before saving a note. Injects context that prompts the model to run the `placement` skill (if it hasn't already for this write). The hook returns `permissionDecision: allow` unconditionally — it never blocks the write — so the placement decision is made by the skill + model rather than by hook approval. Matcher is `mcp__.*__write_note`, so it catches any MCP basic-memory variant (local install, cloud, claude.ai connector). - -### PostToolUse: write_note - -Confirms when notes are saved to Basic Memory. Same `mcp__.*__write_note` matcher. - ---- - -## Development and Validation - -Use the monorepo root target when changing this plugin: - -```bash -just package-check-claude-code -``` - -Or from `plugins/claude-code/`: - -```bash -just check -``` - -The local justfile runs a portable manifest/agent/skill layout check and `claude plugin validate . --strict`. CI uses `just ci-check`, which performs the portable layout validation without requiring the Claude Code CLI on the runner. - ---- - -## MCP Tools Used - -This plugin leverages Basic Memory's MCP tools: - -| Tool | Purpose | -|------|---------| -| `write_note` | Create/update markdown notes | -| `read_note` | Read notes by title or permalink | -| `search_notes` | Full-text search across content | -| `list_directory` | Inspect project folder structure | -| `build_context` | Navigate knowledge graph via memory:// URLs | -| `recent_activity` | Get recently updated information | -| `edit_note` | Incrementally update notes | - ---- - -## Plugin Structure - -``` -basic-memory/ -├── .claude-plugin/ -│ └── marketplace.json # Root marketplace manifest -└── plugins/ - └── claude-code/ - ├── .claude-plugin/ - │ ├── marketplace.json # Plugin-local marketplace manifest - │ └── plugin.json # Plugin manifest - ├── skills/ - ├── hooks/ - ├── agents/ - ├── README.md # Quick start guide - └── PLUGIN.md # Full documentation -``` - ---- - -## Related - -- [Basic Memory Documentation](https://docs.basicmemory.com) -- [Basic Memory GitHub](https://github.com/basicmachines-co/basic-memory) -- [Model Context Protocol](https://modelcontextprotocol.io) -- [Claude Code Plugins](https://code.claude.com/docs/en/plugins) diff --git a/plugins/claude-code/README.md b/plugins/claude-code/README.md index 4a6fbea15..acd679ddf 100644 --- a/plugins/claude-code/README.md +++ b/plugins/claude-code/README.md @@ -1,55 +1,124 @@ -# Basic Memory Claude Code Plugin +# Basic Memory for Claude Code + +The bridge between **Claude's working memory** and **[Basic Memory](https://basicmemory.com)'s durable knowledge graph**. + +Claude Code now keeps its own auto-memory — fast, in-context notes Claude writes +to itself. Basic Memory is the other half: a searchable, portable, semantic graph +of markdown files you and Claude both own. This plugin connects the two so you get +the [documented "use both" setup](https://docs.basicmemory.com/concepts/vs-built-in-memory) +automatically: Claude starts each session briefed from the graph, and checkpoints +the session back to it before the context window compacts. + +> This package lives in the canonical [`basic-memory`](https://github.com/basicmachines-co/basic-memory) +> repository under `plugins/claude-code/` and only works with Claude Code. For +> framework-agnostic skills that work in any MCP agent, see the top-level +> [`skills/`](../../skills) directory. + +## What it does + +- **Session briefing (SessionStart hook).** When a session begins, the plugin + queries Basic Memory for your active tasks and recent work and puts a short + brief in front of Claude — so you start where you left off instead of cold. +- **Compaction checkpoint (PreCompact hook).** Right before Claude Code compacts + the context window, the plugin writes a `type: session` checkpoint note to the + graph, so the texture of the session survives and the next one can resume from + it. +- **Capture reflexes (output style).** An opt-in output style teaches Claude to + search the graph before answering recall questions, capture real decisions as + typed `decision` notes, and cite permalinks. +- **Seed schemas.** Picoschema definitions for `session`, `decision`, and `task` + notes, so the stuff the plugin writes is structured and findable by + `search_notes` metadata filters — recall is precise, not fuzzy. + +The full design and rationale live in [DESIGN.md](./DESIGN.md). + +## Commands + +Plugin skills are namespaced under the plugin name: + +| Command | What it does | +|---------|--------------| +| `/basic-memory:setup` | One-time guided setup — maps the project to a Basic Memory project, seeds the note schemas, installs the shared `memory-*` skills, optionally learns your conventions, and turns on the capture reflexes. Run this first. | +| `/basic-memory:remember ` | Quick capture — saves the text to the `bm-remember` folder with a `manual-capture` tag. Also fires when you say "remember that…". | +| `/basic-memory:share ` | Promote a personal note to a configured team project, with attribution and confirmation. The deliberate way to write to a shared workspace. | +| `/basic-memory:status` | Diagnostic — shows the active project, team read-sources and share targets, capture folders, output-style state, recent session checkpoints, and active-task count. | -**Claude Code-specific plugins** for [Basic Memory](https://basicmemory.com) knowledge management. - -This package lives in the canonical [`basic-memory`](https://github.com/basicmachines-co/basic-memory) repository under `plugins/claude-code/`. It uses [Claude Code's plugin format](https://docs.claude.com/en/docs/claude-code/plugins) — skills and hooks bundled into an installable marketplace. It only works with Claude Code. +## Requirements -> **Looking for framework-agnostic skills?** See the top-level [`skills/`](../../skills) directory — `SKILL.md` files that work in Claude Code, Claude Desktop, and other MCP-compatible agents. Some functionality (commands, hooks) is unique to this Claude Code plugin and isn't available there. +- [Basic Memory](https://github.com/basicmachines-co/basic-memory) `>= 0.19.0` + connected as an MCP server. `uv tool install basic-memory` is recommended (it puts + a `basic-memory` binary on PATH, which the hooks call directly). A `uvx + basic-memory mcp`-only setup also works — the hooks fall back to `uvx`/`uv` when no + binary is on PATH. +- Claude Code. ## Installation -Add the marketplace and install the plugin: - ```bash claude plugin marketplace add basicmachines-co/basic-memory --sparse .claude-plugin plugins/claude-code claude plugin install basic-memory@basicmachines-co ``` -## Available Plugins - -### basic-memory +## Configuration -Skills and hooks for [Basic Memory](https://github.com/basicmachines-co/basic-memory) MCP server integration. +The fastest path is **`/basic-memory:setup`** — a ~2-minute interview that writes +the config, seeds the schemas, and turns on the capture reflexes. The SessionStart +hook nudges you toward it on first run. + +To configure by hand instead: the hooks work out of the box against your **default** +Basic Memory project — no config required. To pin a specific project (recommended, +and required for the PreCompact checkpoint to write), add a `basicMemory` block to +your project's `.claude/settings.json`. Copy +[`settings.example.json`](./settings.example.json) and set `primaryProject`: + +```json +{ + "basicMemory": { + "primaryProject": "my-project", + "captureFolder": "sessions" + } +} +``` -**Skills:** -- `placement` - Decide which folder a new note belongs in (runs automatically before `write_note`) -- `knowledge-capture` - Capture important information from conversations -- `continue-conversation` - Continue previous conversations with context -- `knowledge-organize` - Maintain and organize the knowledge graph -- `research` - Research topics using web search and save to memory -- `edit-note` - Edit existing notes in the knowledge base +To enable the capture reflexes, also set `"outputStyle": "basic-memory"` in your +settings (or select it via `/config`). -Skills auto-expose as slash commands in Claude Code (e.g., `/knowledge-organize`). The Basic Memory MCP server provides additional prompts (`continue_conversation`, `recent_activity`, `search`) that surface as their own slash commands. +| Key | Default | What it does | +|-----|---------|--------------| +| `primaryProject` | _(default project)_ | Where briefs read from and checkpoints write to | +| `captureFolder` | `sessions` | Folder for PreCompact checkpoint notes | +| `recallTimeframe` | `3d` | Recency window for the session brief | +| `recallPrompt` | _(built-in)_ | The instruction appended to the brief | +| `preCompactCapture` | `extractive` | How checkpoints are produced | -**Hooks:** -- Pre-write placement (selects the right folder based on project conventions) -- Post-write confirmation +See [DESIGN.md](./DESIGN.md) for the complete configuration schema, the +Claude-Code-project ↔ Basic-Memory-project mapping, and team-workspace behavior. -## Configuration +## Teams -The plugin reads conventions from a unified config file: +If you're on Basic Memory Cloud with a team workspace, the plugin reads team context +into your session brief and gives you a deliberate way to publish back — **without +ever auto-writing to the shared graph.** -- **Per-project:** a note titled `basic-memory` at the project root -- **Global:** `~/.basic-memory/basic-memory.md` +- **Read across** — add team projects to `secondaryProjects`. SessionStart pulls their + open decisions into your brief (in parallel, read-only), so you start oriented on + what the team has decided. +- **Capture stays personal** — session checkpoints and `/basic-memory:remember` only + ever write to your `primaryProject`. Nothing lands in a team project automatically. +- **Share deliberately** — `/basic-memory:share` copies a chosen note into a + `teamProjects` target (with attribution and a confirmation step). That's the only + path to a shared write. -Sections (`## Projects`, `## Placements`, `## Formats`, `## Schemas`) define rules. H3 sub-sections (e.g. `### research`) provide project-specific overrides. +Because project names repeat across workspaces, team refs must be **workspace-qualified** +(`my-team/notes`) or `external_id` UUIDs — `/basic-memory:setup` fills these in for you +from `list_workspaces`. -If no config exists, the plugin uses sensible built-in defaults. See [PLUGIN.md](./PLUGIN.md) for the full schema and a bootstrap walkthrough. - -## Requirements +## Documentation -- [Basic Memory](https://github.com/basicmachines-co/basic-memory) MCP server must be configured -- Claude Code CLI +- [Why combine Basic Memory with Claude's built-in memory](./docs/why-combine-memory.md) — the value, the personas, the use cases. +- [Getting started](./docs/getting-started.md) — a ~5-minute walkthrough from install to a working memory loop. +- [Architecture](./docs/architecture.md) — how it works, flow by flow, with diagrams. +- [DESIGN.md](./DESIGN.md) — design rationale, decisions, and roadmap. ## Development @@ -65,11 +134,8 @@ From this directory: just check ``` -`just check` validates the root marketplace, plugin-local manifests, bundled skills, hooks, and `basic-memory-manager` agent, then runs `claude plugin validate . --strict`. - -## Documentation - -See [PLUGIN.md](./PLUGIN.md) for full documentation. +`just check` validates the manifests, hooks, output style, and seed schemas, then +runs `claude plugin validate . --strict`. ## License diff --git a/plugins/claude-code/agents/basic-memory-manager.md b/plugins/claude-code/agents/basic-memory-manager.md deleted file mode 100644 index af5789b40..000000000 --- a/plugins/claude-code/agents/basic-memory-manager.md +++ /dev/null @@ -1,332 +0,0 @@ ---- -name: basic-memory-manager -description: Use this agent for managing knowledge in Basic Memory. This agent understands note organization, semantic knowledge graphs, and effective documentation patterns. Use when documenting decisions, capturing conversations, organizing knowledge, or retrieving context from past work. Examples: user: 'Document what we decided about the authentication flow' assistant: 'I'll use the basic-memory-manager agent to create a properly structured decision record.' user: 'What did we discuss about the API design last week?' assistant: 'I'll use the basic-memory-manager agent to search notes and build context.' -tools: mcp__basic-memory__write_note, mcp__basic-memory__read_note, mcp__basic-memory__edit_note, mcp__basic-memory__delete_note, mcp__basic-memory__search_notes, mcp__basic-memory__build_context, mcp__basic-memory__recent_activity, mcp__basic-memory__list_directory, mcp__basic-memory__move_note, mcp__basic-memory__view_note, mcp__basic-memory__list_memory_projects -model: sonnet -color: blue ---- - -You are an expert knowledge management specialist using Basic Memory. You understand how to capture, organize, and retrieve knowledge effectively in a semantic knowledge graph built from markdown files. - -# Core Understanding - -Basic Memory is a **local-first semantic knowledge graph** where: -- Knowledge is stored as markdown files on the user's computer -- SQLite provides indexing for fast search and retrieval -- All data remains under user control -- Files are the authoritative source of truth - -# Project Discovery - -At the start of any session, discover available projects: - -```python -list_memory_projects() -``` - -Ask the user which project to use if unclear. Always pass the `project` parameter explicitly to all tool calls. - -# Knowledge Structure - -## Three Core Elements - -### 1. Entities -Markdown files representing concepts with: -- Unique titles (become permalinks) -- Frontmatter metadata -- Observations (categorized facts) -- Relations (links to other entities) - -### 2. Observations -Categorized facts using the syntax: `- [category] content #tags` - -**Common categories**: -- `[decision]` - Documented choices and rationales -- `[fact]` - Objective information -- `[technique]` - Methods and approaches -- `[requirement]` - Constraints and needs -- `[insight]` - Key realizations -- `[problem]` - Identified issues -- `[solution]` - Resolutions and fixes -- `[action]` - Action items and TODOs -- `[context]` - Background information -- `[pattern]` - Reusable approaches -- `[learning]` - Lessons learned - -### 3. Relations -Directional links between entities using `[[WikiLink]]` syntax: -- `implements` - Implementation relationships -- `requires` - Dependencies -- `part_of` - Hierarchical structure -- `extends` - Enhancements -- `contrasts_with` - Alternatives -- `relates_to` - General connections -- `uses` - Tool/technology usage -- `learned_from` - Source of insight -- `enables` - Makes something possible - -## Quality Observations - -**Good observations are**: -- Specific rather than vague -- Properly categorized -- Tagged with relevant keywords -- Atomic (one fact per observation) -- Contextually detailed - -**Examples**: - -Poor: "We use a database" -Good: "- [fact] PostgreSQL 14 provides full-text search for entity content #infrastructure #database" - -Poor: "Fixed the bug" -Good: "- [solution] Fixed race condition by adding transaction isolation level #debugging #concurrency" - -# Search and Discovery Strategy - -## Progressive Search Pattern - -1. **Start broad**, then narrow: - ``` - Search "authentication" → all related content - Filter by types ["decision", "spec"] → planning artifacts - Add date filter after_date="2025-01-01" → recent work - ``` - -2. **Use text search** for specific terms -3. **Check recent activity** to understand what's current - -## Search Parameters - -- `query` - Search terms (required) -- `search_type` - "text" or "semantic" (default: "text") -- `types` - Filter by note types -- `entity_types` - Filter by entity types -- `after_date` - Only results after this date -- `project` - Project name (required) - -# Context Building - -Use `build_context()` to navigate the knowledge graph: - -```python -build_context( - url="memory://decisions/api-design", - depth=2, # 1=direct, 2=recommended, 3+=comprehensive - timeframe="30d", # "7d", "30d", "3 months ago" - project="your-project" -) -``` - -**Depth guidelines**: -- Depth 1: Direct connections only -- Depth 2: Two levels (recommended for most uses) -- Depth 3+: Comprehensive but potentially large - -# Recording Conversations - -## Always Ask Permission First - -Before saving conversations: -1. Ask if the user wants to document it -2. Explain what will be saved -3. Get explicit confirmation - -**Worth documenting**: -- Important decisions with rationales -- Discoveries and troubleshooting solutions -- Action items and plans -- Technical insights and patterns - -## Templates for Common Note Types - -### Decision Record -```markdown -# [Decision Title] - -## Context -Background and situation that led to this decision. - -## Decision -The choice we made and why. - -## Observations -- [decision] The specific choice made #tag -- [context] Why this decision was needed #tag -- [consequence] Expected outcomes and impacts #tag -- [alternative] Other options considered #tag - -## Relations -- implements [[Related Spec]] -- relates_to [[Related Entity]] -``` - -### Meeting Note -```markdown -# [Meeting Title] - YYYY-MM-DD - -## Summary -Brief overview of the meeting. - -## Observations -- [context] Meeting purpose and attendees #meeting -- [decision] Decisions made #meeting -- [action] Action items with owners #meeting -- [insight] Key takeaways #meeting - -## Relations -- part_of [[Project Name]] -``` - -### Troubleshooting Record -```markdown -# [Problem Description] - -## Problem -The issue encountered. - -## Solution -How it was resolved. - -## Observations -- [problem] The issue encountered #debugging -- [context] When and where it occurred #debugging -- [solution] How it was resolved #debugging -- [insight] Root cause and lessons learned #debugging - -## Relations -- relates_to [[Affected System]] -``` - -# Note Operations - -## Creating Notes - -```python -write_note( - title="API Design Decision", - content=""" -## Context -We needed to choose between REST and GraphQL. - -## Observations -- [decision] Use REST for simplicity #api #architecture -- [requirement] Must support versioning #api -- [technique] Use path-based versioning /v1/endpoint #api - -## Relations -- implements [[System Architecture]] -- relates_to [[Client Integration]] - """, - folder="decisions", - tags=["architecture", "api"], - note_type="decision", - project="your-project" -) -``` - -## Editing Notes - -Four operations: -- `append` - Add to end -- `prepend` - Add to beginning -- `find_replace` - Replace specific text (with expected_replacements count) -- `replace_section` - Update markdown section by heading - -```python -edit_note( - identifier="API Design Decision", - operation="append", - content=""" -- [insight] REST reduced client complexity by 40% #metrics - """, - project="your-project" -) -``` - -## Moving Notes - -Preserves all relations automatically: - -```python -move_note( - identifier="API Design Decision", - destination_path="implementations/api-design-decision", - project="your-project" -) -``` - -# Best Practices - -## 1. Search Before Creating -Always search to avoid duplicates: -```python -search_notes( - query="api design", - types=["decision"], - project="your-project" -) -``` - -## 2. Use Exact Titles in Relations -Query for exact titles before adding relations to ensure links resolve correctly. - -## 3. Maintain Consistent Naming -- Use descriptive titles -- Be concise but clear -- Consider searchability - -## 4. Tag Appropriately -Use tags for: -- Domains and topics -- Note types -- Projects or initiatives -- Technologies or systems - -## 5. Link Generously -Create rich knowledge graphs by linking related concepts. - -## 6. Update Incrementally -Use `edit_note()` to add to existing notes rather than rewriting. - -## 7. Use Recent Activity -Check what's been worked on recently: -```python -recent_activity( - timeframe="7d", - project="your-project" -) -``` - -# Error Handling - -**Common scenarios**: - -- **Project not found** → List projects, ask user which to use -- **Entity not found** → Search for similar, suggest alternatives -- **Ambiguous references** → Show matching options with paths -- **Empty results** → Broaden search or offer to create - -Always fail gracefully with helpful explanations. - -# Response Format - -When working with Basic Memory: - -1. **Explain your strategy** - What you're searching for or documenting -2. **Execute operations** - Use appropriate tools -3. **Summarize results** - Present findings in user-friendly format -4. **Suggest next steps** - Related searches, documentation needs, connections - -Keep responses concise - the main conversation doesn't need verbose details. Return summaries with permalinks for follow-up. - -# Remember - -- Always discover and specify the project explicitly -- Ask permission before recording conversations -- Search before creating to avoid duplicates -- Use specific, tagged observations -- Link related concepts generously -- Update incrementally rather than rewriting - -Your goal: Create enduring, structured knowledge that persists across conversations and provides increasingly valuable context as it accumulates. diff --git a/plugins/claude-code/docs/architecture.md b/plugins/claude-code/docs/architecture.md new file mode 100644 index 000000000..bac19c8a6 --- /dev/null +++ b/plugins/claude-code/docs/architecture.md @@ -0,0 +1,166 @@ +# Architecture + +How the Basic Memory plugin works, flow by flow. For the design rationale and +decision history, see [DESIGN.md](../DESIGN.md). + +## The bridge + +Claude Code has its own **working memory** (auto-memory: short, in-context notes +Claude keeps per project). Basic Memory is a **durable graph** (Markdown files, +semantic + structured search, portable across tools). They do different jobs. The +plugin is the connective tissue that keeps each informed by the other. + +```mermaid +flowchart LR + subgraph CC["Claude Code session"] + WM["Working memory
(auto-memory)
fast · in-context · per-project"] + end + subgraph BM["Basic Memory"] + G["Durable graph
(Markdown + SQLite)
searchable · typed · portable"] + end + + WM -- "PreCompact: checkpoint
before forgetting" --> G + G -- "SessionStart: brief
tasks + decisions" --> WM + + classDef mem fill:#1f2430,stroke:#6A9BCC,color:#e8eaed; + class WM,G mem; +``` + +The plugin ships **four surfaces**, in three layers: + +```mermaid +flowchart TB + subgraph Ambient["Ambient (lifecycle hooks)"] + SS["SessionStart hook
→ brief from the graph"] + PC["PreCompact hook
→ checkpoint to the graph"] + end + subgraph Background["Background (system prompt)"] + OS["output-style
→ search-first / capture / cite reflexes"] + end + subgraph Deliberate["Deliberate (slash commands)"] + SK["/basic-memory:setup · remember · share · status"] + end + + Ambient --> MCP["Basic Memory MCP server"] + Background --> MCP + Deliberate --> MCP + MCP --> Files["Markdown files
(local and/or cloud)"] +``` + +Everything routes through the Basic Memory MCP server (and the `bm` CLI for the +hooks). The plugin itself holds no state — configuration lives in +`.claude/settings.json`, content lives in your Basic Memory projects. + +## SessionStart — the brief + +When a session begins, the hook puts the most relevant slice of the graph in front +of Claude *before the first prompt*, so the session starts oriented instead of cold. + +```mermaid +sequenceDiagram + participant CC as Claude Code + participant H as session-start.sh + participant BM as Basic Memory (bm CLI) + participant C as Claude + + CC->>H: SessionStart (cwd, source) + H->>H: read .claude/settings.json
(primaryProject, secondaryProjects, teamProjects) + par primary + H->>BM: search type=task status=active + H->>BM: search type=decision status=open + and each shared project (parallel) + H->>BM: search type=decision status=open + end + BM-->>H: results (or timeout → skip) + H-->>CC: brief (plain stdout, <10k chars) + CC->>C: brief injected into context + Note over C: starts on active tasks,
open decisions, team context +``` + +Key properties: +- **Structured, not fuzzy.** Queries filter on `type`/`status` frontmatter, so recall + is deterministic — exactly the active tasks and open decisions, not "things that + look similar." +- **Parallel.** Primary and shared-project queries run concurrently; total wall-clock + is ~one query, not the sum. +- **Best-effort.** No Basic Memory, no config, or a slow cloud read never blocks or + errors the session — the worst case is a missing or partial brief. +- **First-run aware.** With no config it nudges toward `/basic-memory:setup`. + +## PreCompact — the checkpoint + +Right before Claude Code compacts the context window (and the texture of the session +would be lost), the hook writes a durable checkpoint so the next session can resume. + +```mermaid +sequenceDiagram + participant CC as Claude Code + participant H as pre-compact.sh + participant BM as Basic Memory + + CC->>H: PreCompact (transcript_path, cwd) + H->>H: read settings → primaryProject + alt no primaryProject configured + H-->>CC: exit (never write un-opted-in) + else configured + H->>H: extract opening request + recent thread + H->>BM: write_note type=session status=open
→ primaryProject/sessions/ + BM-->>H: ok + end + Note over CC: compaction proceeds;
checkpoint surfaces in the next
SessionStart brief +``` + +The checkpoint is a schema-conforming `type: session` note, so the *next* session's +SessionStart query (`type=session`) finds it. Capture is extractive today; an +LLM-summarized version is the planned enrichment (PreCompact has a ~600s budget). + +## Capture while you work + +The opt-in output style turns three behaviors into reflexes during normal work — no +command needed: + +```mermaid +flowchart LR + Q["User asks a recall question"] --> S["search the graph first
(structured filters)"] --> A["answer with permalinks"] + D["User makes a material decision"] --> W["write a type:decision note inline"] +``` + +Because decisions are captured **typed**, they show up in the next session's brief +automatically — the read and write sides reinforce each other. + +## Teams — read across, share deliberately + +On Basic Memory Cloud, the plugin reads team context into your brief but never +auto-writes to a shared project. Publishing back is always a manual gesture. + +```mermaid +flowchart TB + subgraph You["Your session"] + P["primaryProject
(personal capture)"] + end + subgraph Team["Team workspace"] + T1["team/main"] + T2["team/notes"] + end + + T1 -- "read-only
(SessionStart)" --> P + T2 -- "read-only
(SessionStart)" --> P + P -- "/basic-memory:share
(deliberate, confirmed)" --> T2 + + note["Auto-capture (checkpoints, /remember)
writes ONLY to primaryProject"] +``` + +Team refs are workspace-qualified (`team/notes`) or `external_id` UUIDs, because +project names collide across workspaces. Reads route over the user's OAuth session. + +## Where things live + +| Path | Role | +|------|------| +| `hooks/session-start.sh`, `hooks/pre-compact.sh` | the ambient bridge (read / write) | +| `hooks/hooks.json` | registers the hooks | +| `output-styles/basic-memory.md` | the capture reflexes | +| `skills/{setup,remember,share,status}/` | the deliberate slash commands | +| `schemas/{session,decision,task}.md` | picoschema seeds (copied into your project at setup) | +| `.claude/settings.json` → `basicMemory` | per-project configuration | +| your Basic Memory projects | all actual content | diff --git a/plugins/claude-code/docs/getting-started.md b/plugins/claude-code/docs/getting-started.md new file mode 100644 index 000000000..4e1d9ef63 --- /dev/null +++ b/plugins/claude-code/docs/getting-started.md @@ -0,0 +1,138 @@ +# Getting started + +A ~5-minute walkthrough from zero to a working memory bridge. New to the idea? Read +[why-combine-memory.md](./why-combine-memory.md) first. + +## 1. Prerequisites + +- **Claude Code.** +- **Basic Memory** (`>= 0.19.0`) installed and connected as an MCP server: + ```bash + uv tool install basic-memory + ``` + Then add it to Claude Code (`claude mcp add` or your MCP config). Confirm it's + reachable — Claude should be able to call `list_memory_projects`. + +You don't need a Basic Memory Cloud account. Everything works local-first; cloud and +teams are optional (see step 5). + +## 2. Install the plugin + +```bash +claude plugin marketplace add basicmachines-co/basic-memory --sparse .claude-plugin plugins/claude-code +claude plugin install basic-memory@basicmachines-co +``` + +Verify it loaded: + +```bash +claude plugin details basic-memory@basicmachines-co +``` + +You should see **Skills (4): remember, setup, share, status** and **Hooks (2): +SessionStart, PreCompact**. + +## 3. Run setup + +In a project (repo) where you want memory, run: + +``` +/basic-memory:setup +``` + +It's a short interview. It will: +- map this project to a Basic Memory project (pick an existing one or create a new one), +- seed the `session` / `decision` / `task` schemas so notes are findable by structured + search, +- install the shared `memory-*` skills (`npx skills add …`) — the plugin ships only the + Claude-Code-specific glue and pulls the canonical skills on demand, +- optionally learn your existing folder/naming conventions, +- enable the capture reflexes (output style), +- write a `basicMemory` block to `.claude/settings.json`. + +> No cloud, no interview yet? The plugin still works against your **default** Basic +> Memory project with zero config — but pinning a project (which setup does) is what +> lets the PreCompact checkpoint write, and stops the first-run nudge. + +When it finishes, run: + +``` +/basic-memory:status +``` + +to see exactly what the plugin is tracking. + +## 4. See it work + +1. **Capture a decision.** In normal conversation, make a decision — e.g. *"Let's use + Postgres, not SQLite, because we need concurrent writers."* With the output style on, + Claude writes a `type: decision` note and tells you the permalink. +2. **Quick-capture something.** `/basic-memory:remember switch the staging job to the + new image after the rebase lands` → saved to `bm-remember/`. +3. **Start a fresh session.** Open a new Claude Code session in the same project. The + **SessionStart brief** appears first thing, showing your active tasks and the open + decision you just captured — Claude is oriented before you type anything. +4. **(Optional) Trigger compaction** on a long session and resume: the next session's + brief includes the checkpoint the PreCompact hook wrote. + +That loop — capture → checkpoint → brief — is the whole point. It gets richer as the +graph accumulates. + +## 5. Add your team (optional) + +On Basic Memory Cloud with a team workspace, you can read team context into your brief +and publish back deliberately. + +Re-run `/basic-memory:setup` (or edit `.claude/settings.json`). Because project names +repeat across workspaces, team projects use **workspace-qualified names** +(`my-team/notes`) or `external_id` UUIDs — setup finds these for you via +`list_workspaces`. + +```json +{ + "basicMemory": { + "primaryProject": "my-org/main", + "secondaryProjects": ["my-team/main", "my-team/notes"], + "teamProjects": { "my-team/notes": { "promoteFolder": "shared" } } + }, + "outputStyle": "basic-memory" +} +``` + +Now: +- SessionStart folds the team's **open decisions** into your brief (read-only). +- Your captures still go **only** to `primaryProject` — never to the team. +- `/basic-memory:share ` publishes a chosen note to `my-team/notes/shared`, with + attribution and a confirmation step. + +Tip: a team brief is only as rich as the team's typed notes. Share an existing decision +into a team project and watch it appear in the next session's brief. + +## 6. Tune it (optional) + +Everything is in the `basicMemory` block of `.claude/settings.json`. Common knobs: + +| Key | Default | What it does | +|-----|---------|--------------| +| `primaryProject` | (default project) | where briefs read from and captures write to | +| `secondaryProjects` | `[]` | team/shared projects read for recall (read-only) | +| `teamProjects` | `{}` | share targets for `/basic-memory:share` | +| `captureFolder` | `sessions` | folder for PreCompact checkpoints | +| `rememberFolder` | `bm-remember` | folder for `/basic-memory:remember` | +| `recallTimeframe` | `3d` | recency window for the brief | +| `preCompactCapture` | `extractive` | how checkpoints are produced | + +See [settings.example.json](../settings.example.json) for the full shape. + +## Troubleshooting + +- **No brief at session start?** Confirm Basic Memory is connected (`/basic-memory:status`). + The hooks are silent if `basic-memory` isn't on PATH. +- **Checkpoints aren't being written?** A `primaryProject` must be set — the PreCompact + hook never writes to an un-pinned/default project on its own. +- **Commands not showing?** They're namespaced: type `/basic-memory:` to see them. +- **Team brief is empty?** Those projects may have no `type: decision` notes yet — the + brief surfaces typed decisions, which accumulate as the team captures them. + +For the design and internals, see [architecture.md](./architecture.md) and +[DESIGN.md](../DESIGN.md). diff --git a/plugins/claude-code/docs/why-combine-memory.md b/plugins/claude-code/docs/why-combine-memory.md new file mode 100644 index 000000000..b38274f37 --- /dev/null +++ b/plugins/claude-code/docs/why-combine-memory.md @@ -0,0 +1,102 @@ +# Why combine Basic Memory with Claude's built-in memory + +Claude Code already has memory — auto-memory, the short notes Claude keeps to itself +per project. So why add Basic Memory on top? + +Because they're built for different jobs, and **the best setup uses both**. That's not +our marketing line — it's the [documented stance](https://docs.basicmemory.com/concepts/vs-built-in-memory) +of Basic Memory itself: + +> "Basic Memory doesn't replace them — it works alongside them. The best setup uses both." + +This plugin is the piece that makes "use both" happen automatically, instead of making +you copy context back and forth by hand. + +## Two memories, two jobs + +| | Claude's working memory | Basic Memory | +|--|------------------------|--------------| +| **Like** | RAM — what just happened | A journal — what matters over time | +| **Holds** | build commands, debugging tips, recent patterns | decisions, research, meeting notes, project context | +| **Scope** | one project, local, invisible | cross-project, portable, syncable, **sharable** | +| **Search** | whatever's in the window | semantic + structured (by `type`, `status`, …) | +| **Lifecycle** | summarized, compacted, eventually lost | durable Markdown files you own | + +Working memory is fast and automatic but shallow and disposable. Basic Memory is +deliberate, structured, and permanent. Neither is "better" — they compound. + +## 1 + 1 = 3 + +The multiplier is what each makes possible *for the other*: + +- Working memory knows you were "in the auth refactor." Basic Memory turns that into a + **briefing**: the open decisions, the active tasks, the note where you rejected the + single-token approach last week — with permalinks. +- When the context window compacts, working memory compresses and loses detail. The + plugin has already written a **durable checkpoint** to Basic Memory, so the next + session resumes with the thread intact. +- Working memory can't be searched, linked, or shared. Basic Memory can — so a decision + you capture today is findable in three weeks, from another repo, or by a teammate. + +Each session starts richer than the last. The graph is the part that accumulates. + +## Who it's for + +### The thinker — non-developer power user +Writers, researchers, consultants, planners who live in Claude (Code or Desktop), keep +a body of work that compounds over months, and don't touch git. + +> *"What did we conclude about the pricing model?"* — Claude searches the graph and +> answers with the actual note, not a guess. A month of conversations stays one +> `search_notes` away. + +**Wins:** picking up where you left off; never losing the texture of a long session; +recalling what you actually decided. + +### The builder — developer +Uses git and GitHub, runs Claude Code as the primary pairing partner, works across +multiple repos. + +> A multi-hour debugging session hits compaction. Instead of Claude "forgetting" what +> you already ruled out, the checkpoint records the dead-ends — so it doesn't suggest +> the thing that already failed. + +**Wins:** decisions tied to the work; surviving long sessions without amnesia; code +archaeology weeks later (*why did we do it this way?*). + +### The operator — PM, team lead, ops +Runs several projects through Claude, lives in decisions and status more than code, +often on a **team**. + +> First session on a teammate's project: the SessionStart brief already shows the +> team's recent open decisions. You're oriented before you ask a single question. + +**Wins:** no context bleeding between projects; team memory that compounds across +people; sharing a decision to the team in one gesture (`/basic-memory:share`). + +## What you actually get + +Once installed and set up (`/basic-memory:setup`): + +- **Session briefings** — start each session knowing your active tasks, open decisions, + and (if on a team) recent team context. +- **Checkpoints that survive compaction** — long sessions don't lose their thread. +- **Capture reflexes** — Claude searches before answering recall questions and writes + down real decisions as it goes, citing permalinks. +- **Quick capture** — `/basic-memory:remember` for a fast note without breaking flow. +- **Team memory** — read across shared projects; publish back deliberately. + +All of it in plain Markdown files you own, in projects you control — local, cloud, or +both. + +## What it deliberately does *not* do + +- It doesn't replace Claude's auto-memory or fight it — they run side by side. +- It doesn't capture *everything*. Auto-memory already handles the running summary; + Basic Memory is for what's worth keeping. The plugin captures decisions and + checkpoints, not every turn. +- It never auto-writes to a shared team project. Capture stays personal; sharing is a + deliberate, confirmed gesture. + +See [getting-started.md](./getting-started.md) to set it up, or +[architecture.md](./architecture.md) for how it works under the hood. diff --git a/plugins/claude-code/hooks/hooks.json b/plugins/claude-code/hooks/hooks.json index 2dc7539d3..1b3b5c802 100644 --- a/plugins/claude-code/hooks/hooks.json +++ b/plugins/claude-code/hooks/hooks.json @@ -1,23 +1,26 @@ { + "description": "Basic Memory bridge hooks — brief Claude from the knowledge graph on session start, checkpoint the session before compaction.", "hooks": { - "PreToolUse": [ + "SessionStart": [ { - "matcher": "mcp__.*__write_note", "hooks": [ { "type": "command", - "command": "echo '{\"permissionDecision\":\"allow\",\"additionalContext\":\"Before saving this note: invoke the placement skill (basic-memory:placement) to determine the correct directory parameter — unless placement was already settled for this write via the skill, basic-memory.md config, or explicit user instruction. The hook is advisory; this tool call is allowed regardless.\"}'" + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh", + "timeout": 20, + "statusMessage": "Briefing from Basic Memory" } ] } ], - "PostToolUse": [ + "PreCompact": [ { - "matcher": "mcp__.*__write_note", "hooks": [ { "type": "command", - "command": "echo '✓ Note saved to Basic Memory'" + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.sh", + "timeout": 120, + "statusMessage": "Checkpointing session to Basic Memory" } ] } diff --git a/plugins/claude-code/hooks/pre-compact.sh b/plugins/claude-code/hooks/pre-compact.sh new file mode 100755 index 000000000..ee1340469 --- /dev/null +++ b/plugins/claude-code/hooks/pre-compact.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash +# +# PreCompact hook — checkpoint the session to Basic Memory before compaction. +# +# This is the write side of the memory bridge: right before Claude Code compacts +# the context window (and the texture of the session is about to be lost), we +# write a durable SessionNote to the graph so the next session can resume from it. +# +# Phase 1 is the *extractive* cut (see DESIGN.md): we lift the opening request and +# the most recent turns straight from the transcript — no LLM call. Verified (Q2) +# that PreCompact has a ~600s budget, so a real LLM-summarized checkpoint is the +# planned "enrich later" upgrade; extractive is the safe, fast first version. +# +# Contract: advisory, never blocks compaction. Every failure path exits 0. We only +# write when a primaryProject is configured — we never write to a user's default +# graph unless they've explicitly pointed the plugin at a project. + +set -u + +input="$(cat 2>/dev/null || true)" + +# Resolve how to invoke the Basic Memory CLI — prefer a binary on PATH, fall back to +# uvx / uv so checkpointing still works when BM was connected only as an ephemeral +# `uvx basic-memory mcp` server (no persistent CLI). Silent no-op if none available. +if command -v basic-memory >/dev/null 2>&1; then + BM="basic-memory" +elif command -v bm >/dev/null 2>&1; then + BM="bm" +elif command -v uvx >/dev/null 2>&1; then + BM="uvx basic-memory" +elif command -v uv >/dev/null 2>&1; then + BM="uv tool run basic-memory" +else + exit 0 +fi + +BM_HOOK_INPUT="$input" BM_BIN="$BM" python3 <<'PY' 2>/dev/null || exit 0 +import json +import os +import re +import shlex +import subprocess +import sys +from datetime import datetime + +# May be a single binary ("basic-memory") or a multi-token launcher +# ("uvx basic-memory"); split so it prepends cleanly onto the write command. +bm_cmd = shlex.split(os.environ.get("BM_BIN") or "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: + payload = {} + +cwd = payload.get("cwd") or os.getcwd() +transcript_path = payload.get("transcript_path") or "" +session_id = payload.get("session_id") or "" + + +def load_settings(directory): + merged = {} + for name in ("settings.json", "settings.local.json"): + path = os.path.join(directory, ".claude", name) + try: + with open(path) as fh: + merged.update(json.load(fh).get("basicMemory") or {}) + except FileNotFoundError: + continue + except Exception: + continue + return merged + + +cfg = load_settings(cwd) +primary_project = (cfg.get("primaryProject") or "").strip() +capture_folder = (cfg.get("captureFolder") or "sessions").strip() + +# Trigger: no project pinned for this Claude Code project. +# Why: a checkpoint must land somewhere intentional. Writing to the default graph +# on every compaction would pollute it without consent. +# Outcome: silent no-op until the user sets basicMemory.primaryProject. +if not primary_project: + sys.exit(0) + + +# --- Extract conversation text from the transcript (JSONL) --- +# The transcript is one JSON object per line. Schemas vary across Claude Code +# versions, so we probe a few shapes defensively rather than assume one. +def text_of(content): + if isinstance(content, str): + return content + if isinstance(content, list): + parts = [] + for block in content: + if isinstance(block, dict) and block.get("type") == "text": + t = block.get("text") + if isinstance(t, str): + parts.append(t) + return "\n".join(parts) + return "" + + +def turns(path): + collected = [] + try: + with open(path) as fh: + for line in fh: + line = line.strip() + if not line: + continue + try: + obj = json.loads(line) + except Exception: + continue + # Skip injected/meta frames and tool results — only real human + # input and assistant prose count. Claude Code marks tool results + # with a `toolUseResult` field and injected/meta turns (command + # wrappers, system reminders, auto-continuations) with `isMeta`. + # Filtering on those flags — not a "<" content prefix — avoids both + # dropping legitimate messages that start with "<" and capturing + # tool-result noise. + if obj.get("isMeta") or obj.get("toolUseResult") is not None: + continue + msg = obj.get("message") if isinstance(obj.get("message"), dict) else obj + role = msg.get("role") or obj.get("type") + if role not in ("user", "assistant"): + continue + text = text_of(msg.get("content")).strip() + if not text: + continue + collected.append((role, text)) + except Exception: + return [] + return collected + + +conversation = turns(transcript_path) + +# Trigger: nothing usable in the transcript, or no real human turn in it. +# Why: an empty or human-less checkpoint is worse than none — it would write a +# note with a dangling title and no opening request. Require a user turn. +# Outcome: silent no-op. +if not conversation or not any(role == "user" for role, _ in conversation): + sys.exit(0) + +user_msgs = [t for r, t in conversation if r == "user"] +opening = user_msgs[0] if user_msgs else "" +recent_user = user_msgs[-3:] + + +def clip(s, n): + s = " ".join(s.split()) + return s if len(s) <= n else s[: n - 1].rstrip() + "…" + + +# --- Build a schema-conforming SessionNote --- +# Frontmatter carries type/status/started so structured recall (SessionStart) can +# find it with metadata filters. BM merges a leading frontmatter block from the +# content into the note's frontmatter (verified empirically). +now = datetime.now() +iso = now.strftime("%Y-%m-%dT%H:%M") +# Second precision keeps the title — and therefore the note's permalink — unique +# across rapid compactions within the same minute (otherwise the second write +# would collide with the first and be dropped or overwrite it). +title = f"Session {now.strftime('%Y-%m-%d %H:%M:%S')} — {clip(opening, 40)}" + +frontmatter = [ + "---", + "type: session", + "status: open", + f"started: {iso}", + f"ended: {iso}", + f"project: {primary_project}", + f"cwd: {cwd}", +] +if session_id: + frontmatter.append(f"claude_session_id: {session_id}") +frontmatter += ["capture: extractive", "---"] + +body = [ + "", + f"# {title}", + "", + "_Automatic pre-compaction checkpoint (extractive). Full detail lives in the " + "session transcript; this note captures the thread so the next session can " + "resume._", + "", + "## Summary", + f"Working in `{cwd}`.", + f"- Opening request: {clip(opening, 300)}" if opening else "", + "", + "## Recent thread", +] +body += [f"- {clip(m, 200)}" for m in recent_user] or ["- (no recent user messages captured)"] +body += [ + "", + "## Observations", + f"- [context] Session opened with: {clip(opening, 200)}" if opening else "- [context] Session checkpointed before compaction", + "- [next_step] Review this checkpoint and continue where the thread left off", +] + +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_cmd, "tool", "write-note", + "--title", title, + "--folder", capture_folder, + project_flag, primary_project, + "--tags", "session", + "--tags", "auto-capture", + ], + input=content, + capture_output=True, + text=True, + timeout=60, + ) +except Exception: + sys.exit(0) +PY diff --git a/plugins/claude-code/hooks/session-start.sh b/plugins/claude-code/hooks/session-start.sh new file mode 100755 index 000000000..71ba482e4 --- /dev/null +++ b/plugins/claude-code/hooks/session-start.sh @@ -0,0 +1,261 @@ +#!/usr/bin/env bash +# +# SessionStart hook — brief Claude from Basic Memory at the start of a session. +# +# This is the read side of the memory bridge: it puts the most relevant slice of +# the durable knowledge graph in front of Claude before the first prompt, so the +# session starts oriented instead of cold. +# +# Reads (all structured, all best-effort): +# - the primary project's active tasks + open decisions +# - open decisions from each configured shared/team project (secondaryProjects + +# teamProjects), queried in parallel — this is the Phase 4 "recall reads across +# the team" capability. Reads only; capture never touches a shared project. +# +# Contract: advisory, must NEVER disrupt a session. Every failure path exits 0 with +# no output. SessionStart adds plain stdout to Claude's context (verified — Q4), +# capped at 10,000 chars, so the brief stays small and bounded. + +set -u + +# --- Read the hook payload (stdin is JSON: cwd, source, session_id, ...) --- +# stdin can only be consumed once; capture it before anything else touches it. +input="$(cat 2>/dev/null || true)" + +# --- Resolve how to invoke the Basic Memory CLI --- +# Prefer a binary on PATH (fast — no per-call env resolution). Fall back to uvx / uv +# so the hook still works when Basic Memory was connected only as an ephemeral +# `uvx basic-memory mcp` server (the MCP setup our README recommends) with no +# persistent CLI installed — the uv cache is already warm from running the server. +# Trigger: none of basic-memory / bm / uvx / uv on PATH → BM isn't usable here. +# Outcome: silent no-op (the plugin must be invisible to non-BM users). +if command -v basic-memory >/dev/null 2>&1; then + BM="basic-memory" +elif command -v bm >/dev/null 2>&1; then + BM="bm" +elif command -v uvx >/dev/null 2>&1; then + BM="uvx basic-memory" +elif command -v uv >/dev/null 2>&1; then + BM="uv tool run basic-memory" +else + exit 0 +fi + +# Everything else runs in one Python pass: parse config, run the queries, format +# the brief. Python is a guaranteed dependency (basic-memory requires it) and +# avoids brittle shell JSON wrangling. The payload and binary path cross over via +# the environment to sidestep argument-quoting issues. +BM_HOOK_INPUT="$input" BM_BIN="$BM" python3 <<'PY' 2>/dev/null || exit 0 +import json +import os +import re +import shlex +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor + +# May be a single binary ("basic-memory") or a multi-token launcher +# ("uvx basic-memory"); split so it prepends cleanly onto each command list. +bm_cmd = shlex.split(os.environ.get("BM_BIN") or "basic-memory") + +# Cloud project refs come in two unambiguous forms (names collide across +# workspaces, so a bare name won't route): a workspace-qualified name like +# "my-team-2/notes", or an external_id UUID. Detect the UUID to pick the flag. +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 +) +# Cap how many shared projects we read per session — bounds latency and output. +MAX_SHARED = 6 + +# --- Resolve the working directory from the payload --- +try: + payload = json.loads(os.environ.get("BM_HOOK_INPUT") or "{}") +except Exception: + payload = {} +cwd = payload.get("cwd") or os.getcwd() + + +# --- Load plugin config from .claude settings (local overrides committed) --- +# Precedence: settings.local.json (per-user) wins over settings.json (team). +# `found` is True if either file declared a basicMemory block at all — its +# presence is the first-run sentinel (setup writing it stops the nudge below). +def load_settings(directory): + merged = {} + found = False + for name in ("settings.json", "settings.local.json"): + path = os.path.join(directory, ".claude", name) + try: + with open(path) as fh: + data = json.load(fh) + except FileNotFoundError: + continue + except Exception: + continue + block = data.get("basicMemory") + if isinstance(block, dict): + found = True + merged.update(block) + return merged, found + + +cfg, configured = load_settings(cwd) +primary_project = (cfg.get("primaryProject") or "").strip() +recall_timeframe = cfg.get("recallTimeframe") or "3d" +default_prompt = ( + "You have Basic Memory available for this project. Before answering recall " + 'questions ("what did we decide", "where did we leave off"), search the graph ' + "first — prefer structured filters (search_notes with type/status). When the " + "user makes a material decision, capture it as a note with type: decision. " + "Cite permalinks when referencing prior work." +) +recall_prompt = cfg.get("recallPrompt") or default_prompt + +# --- Resolve the shared/team read set --- +# secondaryProjects (read-only recall sources) + teamProjects keys (share targets, +# also read for recall). Dedup, preserve order, cap. These are read only — the +# capture hooks never write to them. +shared_refs = [] +# Guard the JSON types: a misconfigured string would otherwise be iterated +# character-by-character, firing a bogus per-character query for each one. +secondary = cfg.get("secondaryProjects") +secondary = secondary if isinstance(secondary, list) else [] +team = cfg.get("teamProjects") +team = team if isinstance(team, dict) else {} +for ref in list(secondary) + list(team.keys()): + if isinstance(ref, str) and ref.strip() and ref.strip() != primary_project: + if ref.strip() not in shared_refs: + shared_refs.append(ref.strip()) +shared_capped = len(shared_refs) > MAX_SHARED +shared_refs = shared_refs[:MAX_SHARED] + + +# --- Structured query helper (best-effort, per-call timeout) --- +# project_ref=None routes to the user's default project (zero-config usefulness). +# A UUID ref routes via --project-id; a qualified name via --project. +def search(filters, project_ref=None, timeout=10): + cmd = [*bm_cmd, "tool", "search-notes", *filters, "--page-size", "5"] + if 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: + return None + return json.loads(out.stdout) + except Exception: + return None + + +ACTIVE_TASKS = ["--type", "task", "--status", "active"] +OPEN_DECISIONS = ["--type", "decision", "--status", "open"] +# Recent session checkpoints carry the resume cursor. This is the one query the +# `recallTimeframe` window applies to — tasks and decisions are status-scoped (an +# old open decision is still open), but "recent sessions" is inherently time-scoped. +RECENT_SESSIONS = ["--type", "session", "--after_date", recall_timeframe] + +# --- Run everything concurrently --- +# Cloud reads cost a network round-trip each; parallelism keeps total wall-clock at +# ~one query instead of the sum. Each call is independently best-effort. +# Size the pool to cover every submitted search (3 primary + up to MAX_SHARED), +# so none queues — a queued call could otherwise serialize behind a slow one and +# push the hook past Claude Code's SessionStart timeout before the brief prints. +with ThreadPoolExecutor(max_workers=3 + MAX_SHARED) as pool: + fut_tasks = pool.submit(search, ACTIVE_TASKS, primary_project or None) + fut_decisions = pool.submit(search, OPEN_DECISIONS, primary_project or None) + fut_sessions = pool.submit(search, RECENT_SESSIONS, primary_project or None) + fut_shared = {ref: pool.submit(search, OPEN_DECISIONS, ref) for ref in shared_refs} + primary_tasks = fut_tasks.result() + primary_decisions = fut_decisions.result() + primary_sessions = fut_sessions.result() + shared_results = {ref: fut.result() for ref, fut in fut_shared.items()} + +# The first-run nudge — shown until setup writes a basicMemory config block. +setup_nudge = ( + "_Basic Memory isn't set up for this project yet. Run " + "`/basic-memory:setup` (~2 min) to configure session briefings and checkpoints._" +) + +# Trigger: every primary query failed (no default project, misnamed project, +# unreachable cloud, transient error). Why: a broken query must never error the +# session, but it must not silently look like "nothing tracked" either. +# Outcome: first-run → setup nudge; configured-but-broken → a one-line signal so +# the user can tell a typo'd/unreachable project from an empty one. +if primary_tasks is None and primary_decisions is None and primary_sessions is None: + if not configured: + print("# Basic Memory\n\n" + setup_nudge) + else: + proj = primary_project or "the default project" + print( + "# Basic Memory\n\n" + f"_Couldn't read from `{proj}` — it may be misnamed or unreachable. " + "Run `/basic-memory:status` to check._" + ) + sys.exit(0) + + +def label(result): + name = result.get("title") or result.get("file_path") or "(untitled)" + ref = result.get("permalink") or result.get("file_path") or "" + return f"- {name}" + (f" — {ref}" if ref else "") + + +def readable(ref): + # Qualified names ("my-team-2/notes") read fine as-is; UUIDs get shortened. + return f"shared project {ref[:8]}…" if UUID_RE.match(ref) else ref + + +def rows(result): + return (result or {}).get("results") or [] + + +# --- Assemble the brief (plain stdout → Claude's context) --- +lines = ["# Basic Memory — session context", ""] +header = f"**Project:** {primary_project or 'default project'}" +if shared_refs: + header += f" · reading {len(shared_refs)} shared project(s)" +lines.append(header) + +task_rows = rows(primary_tasks) +decision_rows = rows(primary_decisions) +session_rows = rows(primary_sessions) +if task_rows: + lines += ["", f"## Active tasks ({len(task_rows)})", *[label(r) for r in task_rows]] +if decision_rows: + lines += ["", f"## Open decisions ({len(decision_rows)})", *[label(r) for r in decision_rows]] +if session_rows: + lines += [ + "", + f"## Recent sessions ({len(session_rows)}) — where you left off", + *[label(r) for r in session_rows], + ] +if not (task_rows or decision_rows or session_rows): + lines += ["", "_No active tasks, open decisions, or recent sessions in this project._"] + +# --- Shared/team context (read-only) --- +shared_sections = [(ref, rows(shared_results.get(ref))) for ref in shared_refs] +shared_sections = [(ref, items) for ref, items in shared_sections if items] +if shared_sections: + lines += ["", "## From shared projects (read-only)"] + for ref, items in shared_sections: + lines += [f"### {readable(ref)} — open decisions", *[label(r) for r in items]] + lines += [ + "", + "_Shared-project context is read-only. Your captures stay in this project; " + "use `/basic-memory:share` to deliberately promote a note to the team._", + ] +if shared_capped: + lines += ["", f"_(reading the first {MAX_SHARED} shared projects; more are configured.)_"] + +# --- First-run / config nudges --- +if not configured: + lines += ["", setup_nudge] +elif not primary_project: + lines += [ + "", + "_Tip: set `basicMemory.primaryProject` in `.claude/settings.json` to " + "pin this project (see the plugin's settings.example.json)._", + ] + +lines += ["", "---", recall_prompt] +print("\n".join(lines)) +PY diff --git a/plugins/claude-code/output-styles/basic-memory.md b/plugins/claude-code/output-styles/basic-memory.md new file mode 100644 index 000000000..436d84066 --- /dev/null +++ b/plugins/claude-code/output-styles/basic-memory.md @@ -0,0 +1,55 @@ +--- +description: Work with Basic Memory as long-term memory — search before recalling, capture decisions as typed notes, cite permalinks +keep-coding-instructions: true +--- + +# Basic Memory reflexes + +You have a Basic Memory knowledge graph available through MCP tools +(`search_notes`, `read_note`, `build_context`, `write_note`, `edit_note`, +`recent_activity`). It is your durable, long-term memory — distinct from, and +complementary to, your built-in working memory. Treat the two as a team: your +working memory is what *just* happened; Basic Memory is what matters *across +time*. Lean on each to make the other better. + +Follow these reflexes without being asked: + +## Search before you recall +Before answering a question that depends on prior work — "what did we decide +about X", "where did we leave off", "have we seen this before", "what's the +status of Y" — search Basic Memory first instead of answering from training or +from the current context alone. Prefer **structured filters** when the answer +has a shape: + +- Decisions: `search_notes` with `metadata_filters={"type":"decision"}` (add + `"status":"open"` for live ones). +- Tasks: `metadata_filters={"type":"task","status":"active"}`. +- Past sessions: `metadata_filters={"type":"session"}` with a recent + `after_date`. + +Fall back to a text/semantic `search_notes` query when the question is open-ended. +Then `read_note` or `build_context` the hits before you answer. + +## Capture material decisions as typed notes +When the user makes a real decision — a choice with alternatives and a rationale, +not a passing preference — capture it inline as part of your response: write a +note with `type: decision` and `status: open` in its frontmatter, a short +rationale, the alternatives considered, and relations to the work it affects. +Tell the user where it landed (the permalink). Stamping `type: decision` is what +makes the decision findable in a later session's structured recall — an untyped +note is invisible to it. + +Don't over-capture. One good note per real decision. Routine chatter, throwaway +preferences, and things the user clearly doesn't want kept stay out of the graph. + +## Cite permalinks +When you reference prior work, include the permalink so the user can follow it +and so the claim is verifiable. Don't paraphrase from memory when you can cite. + +## Respect the boundary between memories +- If your built-in/auto memory and Basic Memory disagree on a fact, flag the + conflict explicitly rather than silently picking one. +- Keep working in the project's active Basic Memory project unless the user + explicitly asks for another. +- Follow the project's stored placement and format conventions when writing + notes (folder layout, observation categories, frontmatter shape). diff --git a/plugins/claude-code/schemas/decision.md b/plugins/claude-code/schemas/decision.md new file mode 100644 index 000000000..2a63a47f8 --- /dev/null +++ b/plugins/claude-code/schemas/decision.md @@ -0,0 +1,44 @@ +--- +title: Decision +type: schema +entity: Decision +version: 1 +schema: + decision: string, the choice that was made + rationale?: string, why this choice over the alternatives + alternative?(array): string, options that were considered and not taken + consequence?(array): string, what this decision commits us to + context?: string, the situation that prompted the decision + affects?(array): Entity, work or notes this decision bears on + supersedes?: Entity, a prior decision this one replaces +settings: + validation: warn + frontmatter: + status?(enum, lifecycle of the decision): [open, accepted, superseded, rejected] + decided?: string, when the decision was made (ISO timestamp) + project?: string, the Basic Memory project this decision belongs to +--- + +# Decision + +A **DecisionNote** is a durable record of a real choice — one with alternatives +and a rationale, not a passing preference. The Basic Memory plugin's output-style +prompts Claude to capture these inline as decisions are made, and the future +`/basic-memory:decide` command captures them explicitly. + +Decisions are found by structured recall: +`search_notes(metadata_filters={"type": "decision", "status": "open"})`. + +## What makes a good DecisionNote + +- **decision** — state the choice plainly. +- **rationale** + **alternative** — why this, and what was rejected. This is the + part that saves a future session from relitigating the same ground. +- **consequence** — what the choice commits the work to. +- **affects** / **supersedes** — relations that wire the decision into the graph. + +## Frontmatter + +`type: decision` plus `status` make decisions queryable. Capture decisions +sparingly — one note per genuine decision, not per opinion. Validation is `warn`, +never blocking. diff --git a/plugins/claude-code/schemas/session.md b/plugins/claude-code/schemas/session.md new file mode 100644 index 000000000..9c3fafc43 --- /dev/null +++ b/plugins/claude-code/schemas/session.md @@ -0,0 +1,48 @@ +--- +title: Session +type: schema +entity: Session +version: 1 +schema: + summary?: string, one-paragraph what-happened this session + context?(array): string, key context needed to resume after memory loss + next_step?(array): string, explicit cursor for the next session + decision?(array): string, decisions surfaced during the session + problem?(array): string, problems hit — including attempted-and-rejected approaches + produced?(array): Entity, notes created or updated during the session +settings: + validation: warn + frontmatter: + project: string, the Basic Memory project this session belongs to + started: string, when the session began (ISO timestamp) + ended?: string, when the session was checkpointed + status?(enum, lifecycle of the checkpoint): [open, resumed, closed] + cwd?: string, the working directory the session ran in + claude_session_id?: string, Claude Code session identifier + capture?(enum, how this checkpoint was produced): [extractive, summarized] +--- + +# Session + +A **SessionNote** is a resume checkpoint written by the Basic Memory plugin's +PreCompact hook (and, later, the `/basic-memory:handoff` command) right before +Claude Code compacts the context window. It records what the session was doing +so the next session can pick up where this one left off. + +Sessions are found by the SessionStart hook via structured recall: +`search_notes(metadata_filters={"type": "session"}, after_date="3d")`. + +## What goes in a SessionNote + +- **summary** — a short paragraph of what happened (richer once summarized + checkpoints replace the extractive first cut). +- **context** / **next_step** — the cursor: what's in flight and what to do next. +- **decision** / **problem** — choices made and dead-ends hit, so the next + session doesn't repeat them. +- **produced** — relations to the notes this session created or changed. + +## Frontmatter + +`type: session` and `status` are the queryable fields that power recall. `warn` +validation means a missing field is surfaced, never blocking — the user's flow +is never gated on schema conformance. diff --git a/plugins/claude-code/schemas/task.md b/plugins/claude-code/schemas/task.md new file mode 100644 index 000000000..9fbaf5014 --- /dev/null +++ b/plugins/claude-code/schemas/task.md @@ -0,0 +1,39 @@ +--- +title: Task +type: schema +entity: Task +version: 1 +schema: + description: string, what needs to be done + status?(enum, current state): [active, blocked, done, abandoned] + assigned_to?: string, who is working on this + steps?(array): string, ordered steps to complete + current_step?: integer, which step number we're on (1-indexed) + context?: string, key context needed to resume after memory loss + started?: string, when work began + completed?: string, when work finished + blockers?(array): string, what's preventing progress + parent_task?: Task, parent task if this is a subtask +settings: + validation: warn +--- + +# Task + +A **Task** is work-in-progress tracked as a note, so it survives context +compaction and shows up in the next session's brief. This schema is the same one +the framework-agnostic [`memory-tasks`](../../../skills/memory-tasks/SKILL.md) +skill defines — kept identical here so the plugin and the skill agree on the +shape. For the full task workflow (creating, updating, completing), use that +skill. + +Tasks are found by the SessionStart hook via structured recall: +`search_notes(metadata_filters={"type": "task", "status": "active"})`. + +## Frontmatter vs observations + +Put queryable fields (`status`, `priority`, `current_step`) in frontmatter so +`metadata_filters` can find them, and mirror them as `- [status] active` +observations so `schema_validate` sees them. `note_type="Task"` is stored as +lowercase `task` in frontmatter, so search with `note_types=["task"]`. +Validation is `warn` — advisory, never blocking. diff --git a/plugins/claude-code/settings.example.json b/plugins/claude-code/settings.example.json new file mode 100644 index 000000000..5a31d007b --- /dev/null +++ b/plugins/claude-code/settings.example.json @@ -0,0 +1,17 @@ +{ + "$comment": "Example Basic Memory plugin settings. Copy the basicMemory block (and optionally outputStyle) into your project's .claude/settings.json, then set primaryProject. The easiest way to fill this in is /basic-memory:setup. Team projects (secondaryProjects, teamProjects) must use workspace-qualified names like 'my-team/notes' or external_id UUIDs — bare names are ambiguous across workspaces. See DESIGN.md for the full schema.", + "basicMemory": { + "primaryProject": "my-project", + "secondaryProjects": ["my-team/main", "my-team/notes"], + "captureFolder": "sessions", + "rememberFolder": "bm-remember", + "recallTimeframe": "3d", + "recallPrompt": "You have Basic Memory available for this project. Before answering recall questions (\"what did we decide\", \"where did we leave off\"), search the graph first — prefer structured filters (search_notes with type/status). When the user makes a material decision, capture it as a note with type: decision. Cite permalinks when referencing prior work.", + "preCompactCapture": "extractive", + "placementConventions": null, + "teamProjects": { + "my-team/notes": { "promoteFolder": "shared" } + } + }, + "outputStyle": "basic-memory" +} diff --git a/plugins/claude-code/skills/continue-conversation/SKILL.md b/plugins/claude-code/skills/continue-conversation/SKILL.md deleted file mode 100644 index 64994397c..000000000 --- a/plugins/claude-code/skills/continue-conversation/SKILL.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -name: continue-conversation -description: Resume previous work by building context from Basic Memory knowledge graph using memory URLs and recent activity ---- - -# Continue Conversation - -This skill helps you resume previous work by building context from the Basic Memory knowledge graph, enabling seamless continuation across sessions. - -## When to Use - -Use this skill when: -- Starting a new session and need to pick up where you left off -- User mentions previous work ("continue with...", "back to...", "where were we with...") -- Need context about ongoing projects or specs -- User asks about something discussed in a previous conversation -- Working on a multi-session task - -## Building Context - -### 1. Identify What to Continue - -Ask if unclear: -- What topic or project to resume? -- What timeframe to look at? -- Any specific aspect to focus on? - -### 2. Gather Context with MCP Tools - -**Option A: Known Topic - Use build_context** - -```python -# Navigate knowledge graph from a known starting point -mcp__basic-memory__build_context( - url="memory://topic-or-note-name", - depth=2, # How many relation hops to follow - timeframe="7d", # Recent changes - project="main" # or "specs" for specifications -) -``` - -Memory URL formats: -- `memory://note-title` - Single note -- `memory://folder/*` - All notes in folder -- `memory://specs/SPEC-24*` - Pattern matching - -**Option B: Recent Activity - What's been happening?** - -```python -# See what's changed recently -mcp__basic-memory__recent_activity( - timeframe="3d", # "1d", "1 week", "2 weeks" - depth=1, - project="main" -) -``` - -**Option C: Search for Context** - -```python -# Find relevant notes -mcp__basic-memory__search_notes( - query="search terms", - page_size=10, - project="main" -) -``` - -### 3. Read Key Notes - -Once you identify relevant notes: - -```python -mcp__basic-memory__read_note( - identifier="note-title-or-permalink", - project="main" -) -``` - -### 4. Present Context to User - -Summarize what you found: -- Current state of the work -- Recent changes or progress -- Open items or next steps -- Related context that might be helpful - -## Context Strategies by Scenario - -### Resuming a Spec Implementation - -```python -# 1. Read the spec -mcp__basic-memory__read_note( - identifier="SPEC-24: Postgres Database Migration", - project="specs" -) - -# 2. Check recent activity on related topics -mcp__basic-memory__build_context( - url="memory://SPEC-24*", - timeframe="7d", - project="specs" -) - -# 3. Look at what's been done in the codebase -# (Use regular file tools for this) -``` - -### Continuing General Work - -```python -# 1. Check recent activity across projects -mcp__basic-memory__recent_activity( - timeframe="3d", - project="main" -) - -# 2. Read any notes from recent sessions -mcp__basic-memory__read_note( - identifier="relevant-note", - project="main" -) -``` - -### Following Up on a Topic - -```python -# 1. Search for the topic -mcp__basic-memory__search_notes( - query="topic keywords", - project="main" -) - -# 2. Build context from best match -mcp__basic-memory__build_context( - url="memory://found-note-permalink", - depth=2, - project="main" -) -``` - -## Timeframe Reference - -Natural language timeframes: -- `"today"` - Current day -- `"yesterday"` - Previous day -- `"3d"` or `"3 days"` - Last 3 days -- `"1 week"` or `"7d"` - Last week -- `"2 weeks"` - Last 2 weeks -- `"1 month"` - Last month - -## Project Reference - -Project names are user-specific. To discover what's available: - -```python -mcp__basic-memory__list_memory_projects() -``` - -Routing rules (when to use which project) live in `## Projects` of `~/.basic-memory/basic-memory.md` if configured. - -## Example Conversations - -### User: "Let's continue with the Postgres migration" - -``` -1. Read SPEC-24 from specs project -2. Check for related notes about implementation progress -3. Summarize: - - Spec overview and goals - - What's been completed (checkmarks) - - What's pending (checkboxes) - - Any blockers or decisions needed -``` - -### User: "What was I working on yesterday?" - -``` -1. Get recent activity for last 2 days -2. List modified notes with brief descriptions -3. Ask which topic to dive into -``` - -### User: "Back to the async client pattern" - -``` -1. Search for "async client pattern" -2. Build context from matching note -3. Include related notes via relations -4. Present the full picture -``` - -## Best Practices - -1. **Start broad, then narrow** - Get overview first, then specific details -2. **Follow relations** - Knowledge graph connections are valuable -3. **Check multiple projects** - Specs might be separate from implementation notes -4. **Present incrementally** - Share what you find as you go -5. **Confirm understanding** - Verify the context is what user needs -6. **Update as you go** - Capture new progress in notes during the session - -## Combining with Other Skills - -After building context, you might: -- Use **knowledge-capture** to document new progress -- Create new notes linking to the context you gathered diff --git a/plugins/claude-code/skills/edit-note/SKILL.md b/plugins/claude-code/skills/edit-note/SKILL.md deleted file mode 100644 index 18501e303..000000000 --- a/plugins/claude-code/skills/edit-note/SKILL.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -name: edit-note -description: Interactively edit Basic Memory notes using MCP tools - view, modify, and update notes in a conversational workflow (works with cloud and local) ---- - -# Edit Note - -This skill enables interactive editing of Basic Memory notes using MCP tools. It works with both Basic Memory Cloud and local installations since it operates through the MCP interface rather than direct file access. - -## When to Use - -Use this skill when: -- User wants to edit an existing note -- User asks to update, change, or modify note content -- User wants to refine observations or relations in a note -- User says things like "edit my note about...", "update the...", "change X to Y in..." - -## Editing Workflow - -### 1. Fetch the Current Note - -First, retrieve the note to show the user what exists: - -```python -mcp__basic-memory__read_note( - identifier="Note Title or permalink", - project="main" # or specified project -) -``` - -Present the note content clearly, highlighting: -- Current title and metadata -- Main content sections -- Observations (with categories) -- Relations (with link targets) - -### 2. Understand the Edit Request - -Ask clarifying questions if needed: -- Which section to modify? -- What specifically to change? -- Add new content or replace existing? - -### 3. Apply the Edit - -Use the appropriate `edit_note` operation: - -**Append** - Add content to the end: -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="append", - content="\n\n## New Section\n\nNew content here...", - project="main" -) -``` - -**Prepend** - Add content to the beginning: -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="prepend", - content="# Updated Header\n\n", - project="main" -) -``` - -**Find and Replace** - Replace specific text: -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="find_replace", - find_text="old text to find", - content="new replacement text", - project="main" -) -``` - -**Replace Section** - Replace an entire section by heading: -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="replace_section", - section="## Section Heading", - content="## Section Heading\n\nCompletely new section content...", - project="main" -) -``` - -### 4. Show the Result - -After editing, fetch and display the updated note: - -```python -mcp__basic-memory__read_note( - identifier="note-title", - project="main" -) -``` - -Highlight what changed so the user can verify. - -## Edit Operations Reference - -| Operation | Use Case | Required Parameters | -|-----------|----------|---------------------| -| `append` | Add to end | `content` | -| `prepend` | Add to beginning | `content` | -| `find_replace` | Change specific text | `find_text`, `content` | -| `replace_section` | Rewrite a section | `section`, `content` | - -## Common Edit Patterns - -### Adding a New Observation - -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="find_replace", - find_text="## Observations", - content="## Observations\n\n- [new-category] New observation here #tag", - project="main" -) -``` - -Or append to observations section: -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="append", - content="\n- [insight] Additional insight discovered #tag", - project="main" -) -``` - -### Adding a New Relation - -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="find_replace", - find_text="## Relations", - content="## Relations\n\n- relates-to [[New Related Note]]", - project="main" -) -``` - -### Updating a Specific Observation - -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="find_replace", - find_text="- [decision] Old decision text", - content="- [decision] Updated decision with new context #updated", - project="main" -) -``` - -### Rewriting the Context Section - -```python -mcp__basic-memory__edit_note( - identifier="note-title", - operation="replace_section", - section="## Context", - content="## Context\n\nCompletely rewritten context explaining the new situation...", - project="main" -) -``` - -## Multi-Step Editing Session - -For complex edits, work iteratively: - -1. **Show current state** → Read and display the note -2. **First edit** → Apply one change -3. **Show result** → Display updated note -4. **Next edit** → Apply another change if needed -5. **Confirm complete** → Final display and confirmation - -This keeps the user informed and allows course correction. - -## Best Practices - -1. **Always show before and after** - User should see what changed -2. **One edit at a time** - For complex changes, do multiple operations -3. **Preserve structure** - Maintain the note's markdown format -4. **Be careful with find_replace** - Ensure the find_text is unique -5. **Confirm destructive changes** - Ask before replacing large sections -6. **Keep observations formatted** - Maintain `[category]` prefix format -7. **Keep relations formatted** - Maintain `- relation-type [[Target]]` format - -## Example Conversation - -**User:** "Edit my note about the async client pattern - add an observation about testing" - -**Claude:** -1. Fetches "Async Client Pattern" note -2. Displays current content -3. Asks: "What observation about testing would you like to add?" - -**User:** "That the context manager pattern makes mocking easier in tests" - -**Claude:** -1. Uses `edit_note` with `append` to add: - `- [testing] Context manager pattern simplifies mocking in unit tests #testability` -2. Fetches and displays updated note -3. Confirms: "Added the testing observation. Here's the updated note..." diff --git a/plugins/claude-code/skills/knowledge-organize/SKILL.md b/plugins/claude-code/skills/knowledge-organize/SKILL.md deleted file mode 100644 index b31941ec0..000000000 --- a/plugins/claude-code/skills/knowledge-organize/SKILL.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -name: knowledge-organize -description: Help organize, link, and maintain the Basic Memory knowledge graph - find orphan notes, suggest relations, identify duplicates, and improve overall knowledge structure ---- - -# Knowledge Organize - -This skill helps users maintain a healthy, well-connected knowledge graph. As notes accumulate, it becomes valuable to periodically organize, link, and curate the knowledge base. - -## When to Use - -Use this skill when: -- User asks to organize their notes -- User wants to find connections between notes -- User mentions orphan or unlinked notes -- User wants to clean up or improve their knowledge base -- User asks about duplicate or similar notes -- User wants help with folder organization -- User asks to review or audit their notes -- Phrases like "help me organize", "find related notes", "what's not linked", "clean up my notes" - -## Organization Capabilities - -### 1. Find Orphan Notes - -Identify notes that have no relations to other notes - they're isolated in the knowledge graph. - -```python -# Get all notes -mcp__basic-memory__search_notes( - query="*", - page_size=50, - project="main" -) - -# For each note, check if it has relations -# Orphans have empty Relations sections -``` - -**What to do with orphans:** -- Suggest potential relations based on content similarity -- Ask if they should be linked to existing topics -- Propose creating hub notes to connect related orphans - -### 2. Suggest Relations - -Analyze note content and suggest meaningful connections. - -```python -# Read a note -mcp__basic-memory__read_note( - identifier="note-to-analyze", - project="main" -) - -# Search for potentially related notes -mcp__basic-memory__search_notes( - query="key terms from the note", - project="main" -) - -# Suggest relations based on: -# - Shared topics or concepts -# - Complementary content (problem/solution, question/answer) -# - Sequential relationship (part 1, part 2) -# - Hierarchical (parent concept, child detail) -``` - -**Relation types to suggest:** -- `relates-to` - General topical connection -- `extends` - Builds upon or expands -- `implements` - Realizes a concept -- `depends-on` - Requires understanding of -- `contradicts` - Presents alternative view -- `learned-from` - Source of insight -- `enables` - Makes something possible - -### 3. Identify Similar/Duplicate Notes - -Find notes that may cover the same topic. - -```python -# Search for notes with similar titles or content -mcp__basic-memory__search_notes( - query="topic keywords", - project="main" -) - -# Compare results for overlap -# Look for: -# - Similar titles -# - Overlapping observations -# - Same tags -# - Related timestamps (created around same time) -``` - -**Actions for duplicates:** -- Merge into a single comprehensive note -- Link them with `supersedes` or `updates` relations -- Differentiate by adding context about their distinct focus - -### 4. Folder Organization Review - -Analyze folder structure and suggest improvements. - -```python -# List directory structure -mcp__basic-memory__list_directory( - dir_name="/", - depth=3, - project="main" -) - -# Identify: -# - Overcrowded folders -# - Single-note folders -# - Inconsistent naming -# - Notes that might belong elsewhere -``` - -**Organization suggestions:** -- Group related notes into topic folders -- Create subfolders for large categories -- Suggest consistent naming conventions -- Move misplaced notes - -### 5. Tag Consistency - -Review and normalize tags across notes. - -```python -# Search notes to analyze tag patterns -mcp__basic-memory__search_notes( - query="*", - page_size=100, - project="main" -) - -# Look for: -# - Similar tags (architecture vs arch) -# - Unused tags -# - Over-used generic tags -# - Missing tags on relevant notes -``` - -**Tag improvements:** -- Suggest tag standardization (pick one variant) -- Propose new tags for common themes -- Identify notes missing obvious tags - -### 6. Create Index/Hub Notes - -Generate notes that serve as navigation hubs for related topics. - -```python -# After identifying a cluster of related notes -mcp__basic-memory__write_note( - title="Architecture Decisions Index", - content="""--- -title: Architecture Decisions Index -type: index -tags: -- architecture -- index ---- - -# Architecture Decisions Index - -A hub linking all architecture-related decisions and patterns. - -## Decisions - -- [[Database Selection Decision]] -- [[API Design Patterns]] -- [[Authentication Architecture]] - -## Patterns - -- [[Repository Pattern]] -- [[Async Client Pattern]] - -## Observations - -- [index] Central hub for architecture knowledge #navigation - -## Relations - -- indexes [[Architecture]] -""", - folder="indexes", - project="main" -) -``` - -### 7. Enrich Sparse Notes - -Find notes lacking observations or structure and suggest improvements. - -```python -# Read a sparse note -mcp__basic-memory__read_note( - identifier="sparse-note", - project="main" -) - -# If missing: -# - Observations section → suggest categories -# - Relations section → suggest links -# - Tags → suggest relevant tags -# - Context → suggest adding background -``` - -## Organization Workflows - -### Quick Health Check - -A fast overview of knowledge base status: - -1. Count total notes -2. Identify orphan count -3. List recently modified -4. Check for obvious duplicates -5. Report folder distribution - -### Deep Organization Session - -Thorough review and improvement: - -1. **Audit phase** - Catalog all notes, identify issues -2. **Orphan phase** - Address unlinked notes -3. **Relation phase** - Suggest new connections -4. **Duplicate phase** - Merge or differentiate similar notes -5. **Structure phase** - Reorganize folders if needed -6. **Index phase** - Create hub notes for major topics - -### Topic-Focused Organization - -Organize around a specific subject: - -1. Find all notes related to topic -2. Map existing relations -3. Identify gaps in the topic graph -4. Suggest new notes to fill gaps -5. Create topic index note - -## Best Practices - -1. **Work incrementally** - Don't reorganize everything at once -2. **Confirm before changing** - Always ask before moving/editing notes -3. **Preserve permalinks** - Moving is okay, changing permalinks breaks links -4. **Show the graph** - Help user visualize connections -5. **Explain suggestions** - Say why a relation makes sense -6. **Respect user's system** - Enhance their organization, don't impose a new one - -## Example Conversations - -**User:** "Help me organize my notes" - -**Claude:** -1. Runs health check on the knowledge base -2. Reports: "You have 47 notes. I found 12 orphan notes and 3 potential duplicates." -3. Asks: "Would you like to start by connecting the orphan notes, or review the duplicates first?" - -**User:** "Find notes that should be linked to my API design note" - -**Claude:** -1. Reads the API design note -2. Searches for related content -3. Suggests: "I found 5 notes that could relate: - - 'REST Best Practices' → relates-to - - 'Authentication Flow' → implements - - 'Rate Limiting Decision' → extends - Would you like me to add any of these relations?" - -**User:** "Are there any notes about similar topics?" - -**Claude:** -1. Analyzes note titles and content -2. Identifies clusters of similar notes -3. Reports: "I found these potential overlaps: - - 'Auth Flow' and 'Authentication Design' cover similar ground - - 'DB Schema v1' and 'DB Schema v2' might need a 'supersedes' relation - Would you like to review any of these?" diff --git a/plugins/claude-code/skills/placement/SKILL.md b/plugins/claude-code/skills/placement/SKILL.md deleted file mode 100644 index 23bf075e8..000000000 --- a/plugins/claude-code/skills/placement/SKILL.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: placement -description: Decide where a new note belongs in a Basic Memory project — which folder, matching project conventions read from a unified config file (basic-memory.md). Triggered automatically by a PreToolUse hook matching any MCP basic-memory write_note tool. ---- - -# Placement - -Decides the `directory` parameter for a Basic Memory `write_note` call before it runs. Reads project and global config (`basic-memory.md`), then applies a short-circuit decision flow. - -## When to Use - -This skill is invoked automatically by a `PreToolUse` hook (matcher: `mcp__.*__write_note`) that catches any MCP basic-memory variant — local, cloud, or claude.ai connector. You can also invoke it directly when planning a write. - -Inputs: the note's title and content (already drafted), and the active project name. - -## Steps (stop at first match) - -1. **Read configs** (project then global; reuse if cached in this conversation). - - Project: `read_note(project, "basic-memory")` — extract `## Placements` section if present - - Global: `~/.basic-memory/basic-memory.md` — extract `## Placements`. Look for `### ` first, then bare content under the H2. -2. **If config gives a definitive answer** → use it. Stop. -3. **List the project tree** via `list_directory`. If a folder is a clear topic match → use it. Stop. -4. **Follow precedent.** If similar notes already live at a specific location (root or a folder) — even if no folder name is a perfect topic match — place the new note there. Use `search_notes` to find the precedent if needed. Stop. -5. **Only ask the user if there is no config rule, no clear topic-matching folder, AND no precedent.** Don't ask just because nothing is a perfect topic match — precedent is enough. - -## Defaults (apply when no rule speaks) - -- Match by topic against existing folders. -- Follow precedent: if similar notes already live at a location, place there without asking. -- Never create new folders silently. Ask before creating. -- Avoid catch-all folders (`misc/`, `notes/`, `tmp/`) unless they already exist. -- Match the project's existing depth and naming convention. -- Never use date-based or type-based folders unless the project already does. - -## Caching - -If you have already read the project's `basic-memory` note or `~/.basic-memory/basic-memory.md` earlier in this conversation, reuse what you have. Re-read only after a known config change in the conversation (e.g., the user just edited it). - -## Output - -Set the `directory` parameter on the pending `write_note` call. If the project's naming convention differs from the proposed slug, also adjust the `title` to match. - -If placement is ambiguous (multiple plausible folders, or no fit), ask the user before proceeding. Do not guess. - -## Scope - -This skill decides **where** a note goes. It does not: -- Decide whether to write the note (that's a separate concern) -- Redirect to `edit_note` for updates (placement only sets `directory` on `write_note`) -- Validate format or schema (those are separate concerns) -- Create folders silently - -## Config file reference - -The unified `basic-memory.md` config file uses this structure. H2 sections are categories. H3 sub-sections under an H2 are project-specific overrides; bare content under an H2 is the default for that category. - -```markdown -# Basic Memory config - -## Projects -- work: default project for daily work -- personal: personal notes and reflections -- research: long-form research notes - -## Placements -- Place into existing folders by topic match -- Never create new top-level folders without asking -- Match the project's existing naming convention -- Default depth: 1-2 levels; avoid deeper unless content demands it - -### research -- Long-form notes go in `papers/` -- Quick references go in `refs/` -- Reading lists go in `reading-lists/` - -## Formats -- Required frontmatter: title, type, date -- Observation categories: fact, decision, technique, problem, solution - -## Schemas -### work -person: - - name - - email - - role - -project: - - name - - status - - owner -``` - -For more context on the config schema, see `PLUGIN.md`. diff --git a/plugins/claude-code/skills/remember/SKILL.md b/plugins/claude-code/skills/remember/SKILL.md new file mode 100644 index 000000000..09543e1bb --- /dev/null +++ b/plugins/claude-code/skills/remember/SKILL.md @@ -0,0 +1,51 @@ +--- +name: remember +description: Quickly capture a thought, fact, or reminder into Basic Memory as a lightweight note. Use when the user says "remember that…", "note this", "save this to memory", or runs /basic-memory:remember. For quick deliberate capture — not full decision or session records. +argument-hint: +--- + +# Remember + +Capture `$ARGUMENTS` into Basic Memory as a quick note, keeping the user's words. + +## Steps + +1. **Resolve config.** Read `.claude/settings.json` (and `.claude/settings.local.json` + if it exists) and look for the `basicMemory` block: + - `rememberFolder` — folder for quick captures (default: `bm-remember`) + - `primaryProject` — project to write to (default: omit the `project` argument so + Basic Memory uses its default project) + + Both are optional. Use the defaults if the block or a key is missing. Don't fail + if there's no settings file. + +2. **Derive the note.** + - **Content** = the text in `$ARGUMENTS`, verbatim. Don't rewrite or pad it. + - **Title** = the first line of that text, trimmed to ≤ 80 characters (append `…` + if you truncate). If it's one long line, write a short descriptive title instead. + - If `$ARGUMENTS` is empty (e.g. you were invoked because the user said "remember + that…"), capture the specific thing they asked you to remember from the + conversation. If it's genuinely unclear what to save, ask one short question. + +3. **Write it** with `write_note`: + - `title` = the derived title + - `directory` = the resolved `rememberFolder` + - `content` = the text + - `tags` = `["manual-capture"]` + - Route to `primaryProject` if set: pass it as `project`, or as `project_id` if + it's an `external_id` UUID (a bare UUID in `project` won't route). Omit both if + `primaryProject` isn't set. + Don't overwrite an existing note unless the user explicitly asks. + +4. **Confirm** in one line: what was saved and its permalink — + e.g. `Saved → bm-remember/`. + +## Notes + +- This is a *quick* capture. Keep the user's wording; don't add observations, + relations, or structure unless they ask. +- For a decision with rationale and alternatives, write a `type: decision` note + instead (the basic-memory output style covers this). Wrapping up a work session is + the PreCompact checkpoint's job, not this skill's. +- Use whichever Basic Memory MCP server is connected — don't assume a specific tool + name prefix. diff --git a/plugins/claude-code/skills/research/SKILL.md b/plugins/claude-code/skills/research/SKILL.md deleted file mode 100644 index 3588441cf..000000000 --- a/plugins/claude-code/skills/research/SKILL.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -name: research -description: Research a topic thoroughly and produce a structured report saved to Basic Memory - investigate concepts, gather context, and document findings ---- - -# Research - -This skill helps conduct thorough research on a topic and produces a structured report that gets saved to Basic Memory for future reference. - -## When to Use - -Use this skill when: -- User asks to research or investigate something -- User wants to understand a concept, technology, or approach -- User needs context gathered before making a decision -- User asks "what is...", "how does... work", "explore...", "investigate..." -- User wants findings documented for later -- Phrases like "research this", "look into", "find out about", "explore options for" - -## Research Process - -### 1. Understand the Research Question - -Clarify what specifically to investigate: -- What is the core question or topic? -- What scope - broad overview or deep dive? -- Any specific aspects to focus on? -- What will the research inform (a decision, implementation, understanding)? - -### 2. Gather Information - -Use available tools to collect information: - -**For codebase research:** -- Search the codebase for relevant code -- Read documentation and comments -- Trace how things connect -- Look at tests for usage examples - -**For concept research:** -- Use web search for current information -- Fetch documentation from official sources -- Look for examples and best practices -- Compare alternatives if relevant - -**For Basic Memory context:** -```python -# Check what we already know -mcp__basic-memory__search_notes( - query="topic keywords", - project="main" -) - -# Build context from related notes -mcp__basic-memory__build_context( - url="memory://related-topic", - depth=2, - project="main" -) -``` - -### 3. Analyze and Synthesize - -Organize findings into coherent insights: -- Identify key concepts and how they relate -- Note patterns, trade-offs, and considerations -- Highlight what's most relevant to the user's needs -- Flag uncertainties or areas needing more investigation - -### 4. Produce the Report - -Create a structured research report: - -```markdown ---- -title: "Research: [Topic]" -type: research -tags: -- research -- [topic-tags] ---- - -# Research: [Topic] - -## Summary - -[2-3 sentence executive summary of findings] - -## Research Question - -[What we set out to understand] - -## Key Findings - -### [Finding 1] -[Details, evidence, implications] - -### [Finding 2] -[Details, evidence, implications] - -### [Finding 3] -[Details, evidence, implications] - -## Analysis - -[Synthesis of findings - patterns, trade-offs, recommendations] - -## Open Questions - -- [Things that need more investigation] -- [Uncertainties or assumptions] - -## Sources - -- [Where information came from] -- [[Related Note]] - relevant prior knowledge - -## Observations - -- [finding] Key insight discovered #research -- [pattern] Pattern identified during research -- [recommendation] Suggested approach based on findings - -## Relations - -- researches [[Topic]] -- informs [[Decision or Implementation]] -- relates-to [[Related Concepts]] -``` - -### 5. Save to Basic Memory - -```python -mcp__basic-memory__write_note( - title="Research: [Topic]", - content="[Full report content]", - folder="research", # placement skill may override based on project conventions - tags=["research", "topic-tags"], - project="main" -) -``` - -The `placement` skill runs automatically before the write (via PreToolUse hook) and may adjust the `folder` to match project conventions defined in `basic-memory.md`. - -## Report Styles - -Adjust based on the research type: - -### Quick Investigation -- Focused summary -- 2-3 key findings -- Direct recommendation -- Saved to `research/` folder - -### Deep Dive -- Comprehensive analysis -- Multiple sections -- Detailed evidence -- Comparison of options -- Saved to `research/` folder - -### Decision Support -- Options evaluated -- Pros/cons for each -- Clear recommendation with rationale -- Saved to `decisions/` or `research/` folder - -### Technical Exploration -- How it works -- Architecture/design -- Code examples -- Integration considerations -- Saved to `research/` folder - -## Best Practices - -1. **Start with what we know** - Check Basic Memory for existing context -2. **Be thorough but focused** - Cover the topic well without tangents -3. **Cite sources** - Link to where information came from -4. **Be honest about uncertainty** - Flag what's unclear or needs verification -5. **Make it actionable** - Include recommendations when appropriate -6. **Link to related knowledge** - Connect to existing notes -7. **Save for future reference** - Always save the report to Basic Memory - -## Example Conversations - -**User:** "Research how other projects handle database migrations" - -**Claude:** -1. Searches codebase for migration patterns -2. Checks Basic Memory for related decisions -3. Looks up best practices online -4. Produces report comparing approaches -5. Saves to `research/Database Migration Approaches.md` -6. Presents summary with recommendation - -**User:** "Investigate the MCP protocol" - -**Claude:** -1. Fetches MCP documentation -2. Searches for examples in codebase -3. Checks Basic Memory for prior context -4. Produces comprehensive report on MCP -5. Saves to `research/MCP Protocol Overview.md` -6. Presents key concepts and how to use them - -**User:** "Look into authentication options for the API" - -**Claude:** -1. Researches common auth patterns (JWT, OAuth, API keys) -2. Checks existing codebase auth implementation -3. Evaluates trade-offs for the use case -4. Produces decision-support report -5. Saves to `research/API Authentication Options.md` -6. Recommends approach with rationale diff --git a/plugins/claude-code/skills/setup/SKILL.md b/plugins/claude-code/skills/setup/SKILL.md new file mode 100644 index 000000000..b40f39cd2 --- /dev/null +++ b/plugins/claude-code/skills/setup/SKILL.md @@ -0,0 +1,154 @@ +--- +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. +argument-hint: (no arguments — runs an interactive interview) +--- + +# Basic Memory setup + +Run a short, adaptive interview (~2-3 minutes) and then write the configuration. +Be conversational and **skip questions whose answer is already obvious** from +context (e.g. if `list_memory_projects` shows a single local project and no cloud +workspaces, don't ask about cloud/teams — just confirm). Suggest a sensible default +for every question so the user can accept with one word. Don't do any writes until +the interview is done and you've confirmed the plan. + +## Prerequisite check + +First confirm the **Basic Memory MCP server is connected** — call +`list_memory_projects`. If that tool isn't available or errors, Basic Memory isn't +wired into Claude Code yet. **Stop and walk the user through it first** (everything +below depends on it): + +1. Install it: `uv tool install basic-memory` (or `pip install basic-memory`), + version `>= 0.19.0`. +2. Connect it: `claude mcp add basic-memory -- uvx basic-memory mcp`, then restart + the session so the MCP server loads. + +Re-check `list_memory_projects` before continuing — don't start the interview until +it succeeds. + +## Interview + +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.) + +2. **Project mapping.** "Do you already have a Basic Memory project for this, or + should I create one?" + - Existing → show `list_memory_projects()` and let them pick. That name becomes + `primaryProject`. + - New → propose a name (default: this repo's directory name) and a path + (default: `~/basic-memory//`), then create it with + `create_memory_project`. + +3. **Cloud / teams** (skip if there are no extra workspaces). Run + `list_workspaces`. If the user belongs to more than one workspace, they likely + have a **team** workspace alongside their personal/default one. Use + `list_memory_projects` to see the projects in each (note: project names collide + across workspaces, so always use the **workspace-qualified name**, e.g. + `my-team-2/notes`, or the `external_id` UUID — never a bare name). + - **Read from the team** (recommended): ask which team projects to pull into the + session brief for recall. Store their qualified names in `secondaryProjects`. + These are **read-only** — recall reads across them; nothing is written to them. + - **Share target** (optional): if the user wants a place to *publish* notes to the + team via `/basic-memory:share`, add it to `teamProjects` as + `"": { "promoteFolder": "shared" }`. Sharing is always a manual + gesture — auto-capture never writes to a team project. + + 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. + +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.) + +6. **How active should I be? (output style)** "Want me to proactively capture — + search the graph before recalling, write material decisions as typed notes, and + cite permalinks? Or keep it quiet (just the session brief, the PreCompact + checkpoint, and `/basic-memory:remember` on demand)?" Enabling it sets + `outputStyle: "basic-memory"`. Default to enabled; leave it off for a recall-only, + low-noise setup. (This is the single knob for how proactive the assistant is — + the hooks always run regardless.) + +7. **Shared skills** (optional, default yes). "Want the full Basic Memory toolkit — + the shared `memory-*` skills (`memory-notes`, `memory-tasks`, `memory-research`, + `memory-schema`, `memory-defrag`, …)? I can install them alongside this plugin." + These are the canonical, framework-agnostic skills (the same set OpenClaw bundles). + This plugin ships only the Claude-Code-specific glue and pulls the shared set on + demand — it doesn't vendor its own copies. (See "Install the shared skills" below.) + +## Apply (after confirming the plan) + +### 1. Seed the schemas +The plugin ships seed schemas at `/schemas/` — that's **two directories up +from this skill's directory, then `schemas/`** (this skill is at +`/skills/setup/`). Read `session.md`, `decision.md`, and `task.md` there. + +For each one: +- Check whether the chosen project already has a schema for that type + (`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). + +### 2. Install the shared skills (if the user opted in) +Run, from the project root: + +``` +npx skills add basicmachines-co/basic-memory --path skills +``` + +This installs the canonical `memory-*` skills into the user's skills directory — the +single source of truth, shared with OpenClaw. The plugin does **not** vendor copies; +it relies on this shared set. If `npx` / the `skills` CLI isn't available, point the +user at the manual install in the top-level [`skills/README.md`](../../../../skills/README.md). + +### 3. Write settings +Build the `basicMemory` block from the interview: + +```json +{ + "basicMemory": { + "primaryProject": "", + "secondaryProjects": [], + "captureFolder": "sessions", + "rememberFolder": "bm-remember", + "recallTimeframe": "3d", + "preCompactCapture": "extractive", + "placementConventions": "", + "teamProjects": {} + }, + "outputStyle": "basic-memory" +} +``` +Only include `outputStyle` if the user opted in. Ask whether this is a **team +default** (write/merge into `.claude/settings.json`, suggest committing it) or +**personal** (`.claude/settings.local.json`). **Merge** into any existing file — +read it, add/replace only the keys above, preserve everything else. Use compact, +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. + +## 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. diff --git a/plugins/claude-code/skills/share/SKILL.md b/plugins/claude-code/skills/share/SKILL.md new file mode 100644 index 000000000..8fee17731 --- /dev/null +++ b/plugins/claude-code/skills/share/SKILL.md @@ -0,0 +1,61 @@ +--- +name: share +description: Promote a note from your personal Basic Memory project to a shared team project, with attribution. Use when the user says "share this with the team", "publish this decision", or runs /basic-memory:share. This is the deliberate way to write to a team workspace — auto-capture never does. +argument-hint: +--- + +# Share to a team project + +Copy a note from the personal/primary project into a configured **team project** so +teammates can see it. This is the *only* path by which the plugin writes to a shared +project — session checkpoints and `/basic-memory:remember` always stay personal. + +## Steps + +1. **Resolve config.** Read `.claude/settings.json` (+ `.local`) `basicMemory`: + - `teamProjects` — a map of `` → `{ "promoteFolder": "shared" }`. + These are the allowed share targets. `` is a workspace-qualified + name (e.g. `my-team-2/notes`) or an `external_id` UUID. + - `primaryProject` — the source project notes are read from. + + If `teamProjects` is empty, tell the user there's no share target configured and + suggest adding one (or running `/basic-memory:setup`), then stop. Don't invent a + target. + +2. **Find the source note.** From `$ARGUMENTS` (a title, permalink, or `memory://` + URL), `read_note` it from `primaryProject`. If `$ARGUMENTS` is empty (you were + invoked from "share this"), use the note most clearly referenced in the + conversation — confirm which one if there's any ambiguity. Capture its title, + full content (including frontmatter), and source permalink. + +3. **Pick the target.** If `teamProjects` has one entry, use it. If several, ask + which team project to share to. Use that entry's `promoteFolder` (default + `shared`). + +4. **Confirm before writing.** This write is visible to teammates, so show the user + what you're about to do — *"Share '' to <target>/<promoteFolder>?"* — and + wait for a yes. Never share silently. + +5. **Write the shared copy** with `write_note`: + - 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 + (so a shared decision stays `type: decision` and is findable in the team's + structured recall), add a `shared_from: <source permalink>` frontmatter field, + and add an observation `- [context] Shared from <source permalink>`. + Don't overwrite an existing note at the target unless the user says so. + +6. **Confirm** in one line: what was shared and the new team permalink, e.g. + `Shared → my-team-2/shared/<slug>`. + +## Notes + +- Sharing **copies** the note; the original stays in your project. Edits to one + don't propagate to the other. +- Don't share notes containing secrets, credentials, or anything the user wouldn't + want teammates to see — if in doubt, ask. +- Use whichever Basic Memory MCP server is connected; route to the team project by + the `project` (qualified name) or `project_id` (UUID) argument. diff --git a/plugins/claude-code/skills/status/SKILL.md b/plugins/claude-code/skills/status/SKILL.md new file mode 100644 index 000000000..eaa909a1f --- /dev/null +++ b/plugins/claude-code/skills/status/SKILL.md @@ -0,0 +1,62 @@ +--- +name: status +description: Show the Basic Memory plugin's current state for this project — active project, capture folders, output style, recent session checkpoints, and whether Basic Memory is reachable. +disable-model-invocation: true +--- + +# Basic Memory status + +Report the plugin's current state for this project, then present a concise summary. +This is a quick diagnostic — gather the facts and lay them out; don't over-investigate. + +## Gather + +1. **CLI reachable?** Run `basic-memory --version` (fall back to `bm --version`). If + neither is found, report that Basic Memory isn't installed or on PATH, and stop — + 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 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` + if one is set. List the most recent session checkpoints by title + permalink. + +4. **Active tasks.** `search_notes` with + `metadata_filters={"type": "task", "status": "active"}` — report just the count. + +When scoping these queries to `primaryProject`, pass it as `project`, or as +`project_id` if it's an `external_id` UUID (a bare UUID in `project` won't route). + +## Present + +Lay it out like this (fill in real values; write "—" or a short note for anything +you couldn't determine, rather than failing the whole report): + +``` +## Basic Memory status + +- CLI: basic-memory <version> +- Project: <primaryProject, or "default project (not pinned)"> +- Reads from (team): <secondaryProjects joined, or "none"> +- Share targets: <teamProjects keys joined, or "none"> +- Capture folder: <captureFolder> +- Remember folder: <rememberFolder> +- Output style: <enabled | not enabled> +- PreCompact: <mode> +- Recent checkpoints: <n> + - <title> — <permalink> + ... +- Active tasks: <n> +``` + +If there are no checkpoints yet, say "none yet" and remind the user that checkpoints +are written automatically before context compaction (and that a `primaryProject` must +be set for them to be written). diff --git a/scripts/update_versions.py b/scripts/update_versions.py index ed7e3ec1c..1150723b3 100644 --- a/scripts/update_versions.py +++ b/scripts/update_versions.py @@ -96,10 +96,15 @@ def set_package_version(data: dict[str, Any], version: str) -> None: data["version"] = version -def update_versions(raw_version: str, *, dry_run: bool) -> None: - version = parse_version(raw_version) - print(f"{'preview' if dry_run else 'writing'} Basic Memory version {version}") +# Version scopes. The two groups map to the two distribution tracks: +# core — the Python package and its MCP registry manifest +# packages — the host-native agent artifacts (Claude Code plugin + marketplaces, +# Hermes, OpenClaw). These are the "plugin/agent artifacts." +# `all` writes both. Lockstep releases use `all`; targeted fixes can use one group. +SCOPES = ("all", "core", "packages") + +def _update_core(version: str, *, dry_run: bool) -> None: update_text( "src/basic_memory/__init__.py", r'^__version__ = ".*"$', @@ -111,6 +116,9 @@ def update_versions(raw_version: str, *, dry_run: bool) -> None: lambda data: set_server_version(data, version), dry_run=dry_run, ) + + +def _update_packages(version: str, *, dry_run: bool) -> None: update_json( ".claude-plugin/marketplace.json", lambda data: set_claude_marketplace_version(data, version), @@ -145,12 +153,32 @@ def update_versions(raw_version: str, *, dry_run: bool) -> None: ) +def update_versions(raw_version: str, *, scope: str = "all", dry_run: bool) -> None: + if scope not in SCOPES: + raise SystemExit(f"Invalid scope {scope!r}. Choose one of: {', '.join(SCOPES)}") + + version = parse_version(raw_version) + print(f"{'preview' if dry_run else 'writing'} Basic Memory version {version} (scope: {scope})") + + if scope in ("all", "core"): + _update_core(version, dry_run=dry_run) + if scope in ("all", "packages"): + _update_packages(version, dry_run=dry_run) + + def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("version", help="Release version, with or without the leading v") + parser.add_argument( + "--scope", + choices=SCOPES, + default="all", + help="Which artifacts to update: all (default), core (Python + server.json), " + "or packages (Claude Code plugin, marketplaces, Hermes, OpenClaw)", + ) parser.add_argument("--dry-run", action="store_true", help="Preview changes without writing") args = parser.parse_args() - update_versions(args.version, dry_run=args.dry_run) + update_versions(args.version, scope=args.scope, dry_run=args.dry_run) if __name__ == "__main__": diff --git a/scripts/validate_claude_plugin.py b/scripts/validate_claude_plugin.py index f8d0b69de..34ddc706a 100644 --- a/scripts/validate_claude_plugin.py +++ b/scripts/validate_claude_plugin.py @@ -1,10 +1,18 @@ #!/usr/bin/env python3 -"""Validate the Basic Memory Claude Code plugin layout.""" +"""Validate the Basic Memory Claude Code plugin layout (v0.4 bridge redesign). + +The plugin's surfaces are intentionally minimal: lifecycle hooks (SessionStart, +PreCompact), an opt-in output style, and seed schemas. There is no bundled agent, +and skills are optional in this layout (added by later phases). This validator +mirrors that contract so `package-check-claude-code` passes for what the plugin +actually ships. +""" from __future__ import annotations import argparse import json +import os from pathlib import Path from validate_skills import parse_frontmatter @@ -12,6 +20,15 @@ ROOT = Path(__file__).resolve().parents[1] +# Hook events the bridge plugin must register, and the scripts that back them. +REQUIRED_HOOK_EVENTS = ("SessionStart", "PreCompact") +REQUIRED_HOOK_SCRIPTS = ("hooks/session-start.sh", "hooks/pre-compact.sh") +# Seed schemas the plugin ships for its note types (copied into the user's +# project at bootstrap). Each must be a parseable schema note. +REQUIRED_SCHEMAS = ("session.md", "decision.md", "task.md") +# Skills the plugin ships as namespaced slash commands (/basic-memory:<name>). +REQUIRED_SKILLS = ("setup", "remember", "status", "share") + def read_json(path: Path) -> dict: return json.loads(path.read_text()) @@ -19,6 +36,8 @@ def read_json(path: Path) -> dict: def validate_claude_plugin(plugin_dir: Path) -> None: plugin_dir = plugin_dir.resolve() + + # --- Manifests --- plugin_json = plugin_dir / ".claude-plugin/plugin.json" local_marketplace_json = plugin_dir / ".claude-plugin/marketplace.json" root_marketplace_json = ROOT / ".claude-plugin/marketplace.json" @@ -46,23 +65,61 @@ def validate_claude_plugin(plugin_dir: Path) -> None: if local_plugin.get("name") != "basic-memory" or local_plugin.get("source") != "./": raise SystemExit(f"{local_marketplace_json}: expected plugin-local source ./") - hooks = plugin_dir / "hooks/hooks.json" - hooks_json = read_json(hooks) - if "PreToolUse" not in hooks_json.get("hooks", {}): - raise SystemExit(f"{hooks}: missing PreToolUse hook") - - agent = plugin_dir / "agents/basic-memory-manager.md" - agent_frontmatter = parse_frontmatter(agent) - if agent_frontmatter.get("name") != "basic-memory-manager": - raise SystemExit(f"{agent}: missing basic-memory-manager frontmatter") - - skill_dirs = sorted(path for path in (plugin_dir / "skills").iterdir() if path.is_dir()) - if not skill_dirs: - raise SystemExit(f"{plugin_dir / 'skills'}: no bundled Claude Code skills") - for skill_dir in skill_dirs: - frontmatter = parse_frontmatter(skill_dir / "SKILL.md") + # --- Hooks --- + # The bridge runs on lifecycle events, not on tool calls. Require the two + # event keys and confirm each backing script exists and is executable + # (Claude Code invokes them directly via ${CLAUDE_PLUGIN_ROOT}). + hooks_json = read_json(plugin_dir / "hooks/hooks.json") + hooks = hooks_json.get("hooks", {}) + for event in REQUIRED_HOOK_EVENTS: + if event not in hooks: + raise SystemExit(f"hooks/hooks.json: missing {event} hook") + for rel in REQUIRED_HOOK_SCRIPTS: + script = plugin_dir / rel + if not script.exists(): + raise SystemExit(f"Missing hook script: {script}") + if not os.access(script, os.X_OK): + raise SystemExit(f"Hook script is not executable: {script}") + + # --- Output style --- + output_style = plugin_dir / "output-styles/basic-memory.md" + if not output_style.exists(): + raise SystemExit(f"Missing output style: {output_style}") + if not parse_frontmatter(output_style).get("description"): + raise SystemExit(f"{output_style}: missing description frontmatter") + + # --- Seed schemas --- + # Each must declare type: schema and an entity so it resolves once indexed. + for name in REQUIRED_SCHEMAS: + schema_file = plugin_dir / "schemas" / name + if not schema_file.exists(): + raise SystemExit(f"Missing seed schema: {schema_file}") + fm = parse_frontmatter(schema_file) + if fm.get("type") != "schema": + raise SystemExit(f"{schema_file}: expected type: schema") + if not fm.get("entity"): + raise SystemExit(f"{schema_file}: missing entity field") + + # --- Skills --- + # Each skill is a directory with a SKILL.md whose `name` matches the directory; + # it surfaces as /basic-memory:<dir>. Require the shipped set, and validate the + # frontmatter of every skill present. + skills_root = plugin_dir / "skills" + if not skills_root.exists(): + raise SystemExit(f"Missing skills directory: {skills_root}") + present_skills = {p.name for p in skills_root.iterdir() if p.is_dir()} + for required in REQUIRED_SKILLS: + if required not in present_skills: + raise SystemExit(f"Missing required skill: skills/{required}/SKILL.md") + for skill_dir in sorted(p for p in skills_root.iterdir() if p.is_dir()): + skill_md = skill_dir / "SKILL.md" + if not skill_md.exists(): + raise SystemExit(f"{skill_dir}: missing SKILL.md") + frontmatter = parse_frontmatter(skill_md) if frontmatter.get("name") != skill_dir.name: raise SystemExit(f"{skill_dir}: skill name must match directory") + if not frontmatter.get("description"): + raise SystemExit(f"{skill_md}: missing description frontmatter") print(f"validated Claude Code plugin in {plugin_dir}") diff --git a/scripts/validate_skills.py b/scripts/validate_skills.py index e3d27b390..753e60881 100644 --- a/scripts/validate_skills.py +++ b/scripts/validate_skills.py @@ -8,6 +8,15 @@ def parse_frontmatter(path: Path) -> dict[str, str]: + """Extract top-level frontmatter keys from a Markdown file. + + A deliberately minimal parser (no PyYAML — this runs under bare `python3` in + CI). It only captures **top-level** `key: value` lines. Indented lines are + skipped, so nested blocks (a schema note's `schema:`/`settings:` children) can't + overwrite a top-level key like `type` or `entity` via last-write-wins. It does + not interpret block scalars or multi-line values; callers rely on single-line + top-level fields (name, description, type, entity). + """ lines = path.read_text().splitlines() if not lines or lines[0] != "---": raise SystemExit(f"{path}: missing YAML frontmatter") @@ -16,6 +25,8 @@ def parse_frontmatter(path: Path) -> dict[str, str]: for line in lines[1:]: if line == "---": break + if line[:1] in (" ", "\t"): # nested key — not a top-level field + continue if ":" not in line: continue key, value = line.split(":", 1) diff --git a/skills/CLAUDE.md b/skills/CLAUDE.md index f034b7a41..e57f54f1a 100644 --- a/skills/CLAUDE.md +++ b/skills/CLAUDE.md @@ -23,6 +23,9 @@ memory-lifecycle/SKILL.md # Entity status transitions and folder-based arc memory-ingest/SKILL.md # Process external input into structured entities memory-research/SKILL.md # Web research synthesized into Basic Memory entities memory-literary-analysis/SKILL.md # Literary analysis knowledge graph pipeline +memory-curate/SKILL.md # Knowledge-graph curation: orphans, relations, tags, hub notes +memory-continue/SKILL.md # Resume prior work by rebuilding context from the graph +memory-capture/SKILL.md # Capture a thread's current state into one coherent note ``` There is no code to compile — this is a pure markdown project. Run `just check` diff --git a/skills/README.md b/skills/README.md index d8d4e549c..a43ca858c 100644 --- a/skills/README.md +++ b/skills/README.md @@ -17,13 +17,16 @@ Basic Memory provides the MCP server — tools like `write_note`, `search_notes` | **memory-tasks** | Structured task tracking that survives context compaction. Creates typed `Task` notes with steps, status, and context. | Multi-step work (3+ steps), anything that might outlast a context window, or after compaction to resume. | | **memory-schema** | Schema lifecycle management — discover unschemaed notes, infer schemas, create/edit definitions, validate, and detect drift. | When structured note types emerge (Task, Person, Meeting, etc.) and you want consistency. | | **memory-reflect** | Sleep-time memory reflection. Reviews recent conversations and daily notes, extracts insights, consolidates into long-term memory. Inspired by [sleep-time compute](https://www.letta.com/blog/sleep-time-compute). | Schedule via cron (1-2x daily), trigger from heartbeat, or run on demand. | +| **memory-capture** | Capture the current state of a working thread into a single coherent note — synthesize where it landed, not an append-log. Re-captures rewrite the same note in place via a `thread_id` key. | Mid-thread or end-of-thread, when decisions, insights, or context are worth preserving. | | **memory-notes** | How to write well-structured notes — frontmatter, observations with semantic categories, relations with wiki-links, and best practices. | When creating or improving notes, or when you need a reference for the note format. | | **memory-metadata-search** | Structured metadata search — query notes by custom frontmatter fields using equality, range, array, and nested filters. | When finding notes by status, priority, confidence, or any custom YAML field. | | **memory-defrag** | Memory defragmentation — split bloated files, merge duplicates, remove stale information, restructure the hierarchy. | Run weekly/biweekly via cron, or on demand when memory feels messy. | +| **memory-curate** | Knowledge-graph curation — find orphan notes and suggest links, propose typed relations, merge duplicates, audit tags and folders, build hub notes. | When organizing, connecting, or improving a knowledge base as notes accumulate. | | **memory-lifecycle** | Entity lifecycle management — status transitions through folder-based organization, archiving completed work. Core principle: archive, never delete. | When marking items complete, archiving old entities, or managing folder-based status workflows. | | **memory-ingest** | Process unstructured external input into structured entities. Parses meeting transcripts, conversation logs, and pasted documents. | When pasting a transcript, conversation log, or external document that should become structured knowledge. | | **memory-research** | Web research synthesized into Basic Memory entities. Researches a subject, checks for existing knowledge, presents findings, and creates entity notes. | When asked to research a company, person, technology, or topic. | | **memory-literary-analysis** | Analyze a complete literary work into a structured knowledge graph — characters, themes, chapters, locations, symbols, and literary devices. | Full-text literary analysis, book club companions, teaching resources, or research projects. | +| **memory-continue** | Resume prior work by rebuilding context from the knowledge graph — `build_context` via `memory://` URLs, recent activity, and search, then read the key notes. | Starting a session, or when the user says "continue with...", "back to...", or "where were we?" | ## Basic Memory Cloud diff --git a/plugins/claude-code/skills/knowledge-capture/SKILL.md b/skills/memory-capture/SKILL.md similarity index 72% rename from plugins/claude-code/skills/knowledge-capture/SKILL.md rename to skills/memory-capture/SKILL.md index bcd7ebc9b..8912aea85 100644 --- a/plugins/claude-code/skills/knowledge-capture/SKILL.md +++ b/skills/memory-capture/SKILL.md @@ -1,11 +1,11 @@ --- -name: knowledge-capture -description: Capture the meaningful context of a Claude Code thread into a single coherent Basic Memory note. On subsequent invocations within the same thread (identified by the JSONL session UUID) the same note is rewritten, not appended. +name: memory-capture +description: "Capture the current state of a working thread or conversation into a single coherent Basic Memory note — synthesize where it landed, don't append a log. On re-capture, rewrite the same note in place instead of duplicating. Use mid-thread or end-of-thread when decisions, insights, or context are worth preserving." --- -# Knowledge Capture +# Memory Capture -Capture the gist of a Claude Code thread — the decisions made, insights surfaced, and context built — into a single coherent Basic Memory note that reflects where the thread has landed. +Capture the gist of a working thread — the decisions made, insights surfaced, and context built — into a single coherent Basic Memory note that reflects where the thread has landed. ## Purpose @@ -27,36 +27,37 @@ It is fine — and expected — to invoke this skill multiple times in the same ## Same-Thread Detection -Each Claude Code session has a stable UUID embedded in its transcript filename. Derive it at runtime via Bash — search across all project directories and take the most-recently-modified jsonl file: +To rewrite the same note on re-capture instead of duplicating, key the note to a stable `thread_id` in its frontmatter. -```bash -ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1 | xargs basename | sed 's/\.jsonl$//' -``` - -The active session is continuously appending to its jsonl file, so it's reliably the most-recent. The filename (minus extension) is the session UUID. Use this as the `thread_id` for the note's frontmatter. +**If your agent exposes a stable session or thread id**, store it as `thread_id` so subsequent captures within the same thread find and rewrite the same note. Any value that stays constant for the duration of the thread works — a session UUID, a conversation id, a ticket number the work is scoped to. -**Note:** an earlier version of this command used `pwd` to scope to a single project directory, but that breaks when the shell has `cd`'d into a subdirectory of the Claude Code session's project root. The cross-project glob is more robust. +> **Example (hosts with a JSONL transcript):** some agents write a per-session transcript whose filename is a stable session UUID. If yours does, you can derive the id from the most-recently-modified transcript file and use it as `thread_id`. This is optional — only do it if your host actually exposes such a transcript. -**Edge case:** if the user has multiple Claude Code sessions running simultaneously, "most recent" can flip between them. Rare in practice. +**If no stable id is available**, match the existing note by title/topic instead: search for a note covering the same thread (`search_notes(query="<topic>")`), and if you find the one this thread already produced, rewrite it. Omit `thread_id` and rely on a consistent title. ## Decision Flow -1. **Derive the session UUID** with the Bash command above. -2. **Search Basic Memory** for an existing note tagged with this UUID. Use `metadata_filters` (not `query`) — full-text query doesn't reliably match YAML frontmatter custom fields: - ```python - mcp__basic-memory__search_notes( - metadata_filters={"thread_id": "<session-uuid>"}, - project="<project>" - ) - ``` +1. **Determine the thread key.** Use a stable session/thread id if your agent exposes one; otherwise plan to match by title/topic. +2. **Search Basic Memory** for the existing thread note. + - With a thread id, use `metadata_filters` (not `query`) — full-text query doesn't reliably match YAML frontmatter custom fields: + ```python + search_notes( + metadata_filters={"thread_id": "<thread-id>"}, + project="<project>" + ) + ``` + - Without one, search by topic and identify the note this thread already produced: + ```python + search_notes(query="<thread topic>", project="<project>") + ``` 3. **If a match is found:** - - Read the existing note (use the full permalink returned by search, e.g., `bmem/development/basic-memory/...`) + - Read the existing note (use the full permalink returned by search) - Synthesize a new version that integrates the latest understanding from the conversation - - Overwrite via `write_note` with `overwrite=true` (same title, same `thread_id`, same directory) + - Overwrite via `write_note` with `overwrite=True` (same title, same `thread_id` if used, same directory) 4. **If no match is found:** - Synthesize the note from the conversation - - Pass `metadata={"thread_id": "<session-uuid>"}` to `write_note` (it surfaces as a custom frontmatter field) - - Save — the `placement` skill picks the folder + - If you have a thread id, pass `metadata={"thread_id": "<thread-id>"}` to `write_note` (it surfaces as a custom frontmatter field) + - Save it ## Synthesis Rules @@ -77,9 +78,9 @@ If the user explicitly asks for a separate note (e.g., "capture this as a new no ```markdown --- -title: <descriptive title — placement skill may adjust naming convention> +title: <descriptive title for the thread> type: note -thread_id: <session-uuid> +thread_id: <thread-id, if your agent exposes one> tags: - relevant - tags @@ -103,7 +104,7 @@ The actual content. Could be decisions, a design rationale, an investigation sum ## Relations -- relates-to [[Related Concept]] +- relates_to [[Related Concept]] - implements [[Parent Spec]] ``` @@ -124,50 +125,50 @@ The title should reflect the thread's topic. On update, the title can be refined ## MCP Tools Used ```python -# Find existing thread note (use metadata_filters, not query) -mcp__basic-memory__search_notes( - metadata_filters={"thread_id": "<session-uuid>"}, +# Find existing thread note by thread id (use metadata_filters, not query) +search_notes( + metadata_filters={"thread_id": "<thread-id>"}, project="<project>" ) +# Or, without a thread id, find it by topic +search_notes(query="<thread topic>", project="<project>") + # Read existing thread note (use the full permalink from search results) -mcp__basic-memory__read_note( +read_note( identifier="<full-permalink>", - project="<project>", - include_frontmatter=True + project="<project>" ) # Create -mcp__basic-memory__write_note( +write_note( title="<title>", content="<markdown body — frontmatter is generated from title/tags/metadata>", directory="<folder>", tags=["..."], - metadata={"thread_id": "<session-uuid>"}, + metadata={"thread_id": "<thread-id>"}, # omit if no stable id project="<project>" ) # Overwrite an existing note (same path) -mcp__basic-memory__write_note( +write_note( title="<same title>", content="<new content>", directory="<same folder>", tags=["..."], - metadata={"thread_id": "<same session-uuid>"}, + metadata={"thread_id": "<same thread-id>"}, # omit if no stable id overwrite=True, project="<project>" ) ``` -The `placement` skill runs automatically before the write (via PreToolUse hook) to pick the folder. - ## Examples ### Example 1 — First capture during a brand design conversation **Preceding conversation:** The user has been working through visual identity decisions for a new product. They settled on a deep navy primary (`#2B3651`), explored accent options and chose orange (`#F26B3A`) for warmth, and picked Inter as the body font with Helvetica Neue as the display font. -**User invokes:** `/knowledge-capture` +**User asks to capture.** **Result — note created:** @@ -206,14 +207,14 @@ Working through the visual identity for the new product. This thread covers the ## Relations -- relates-to [[Brand Strategy]] +- relates_to [[Brand Strategy]] ``` ### Example 2 — Update capture later in the same thread **Preceding conversation (continued):** After the initial decisions above, the conversation continued. The orange accent felt too aggressive in mock-ups, so we tested a coral (`#E89B7A`) which read warmer and more refined. The body font also shifted: Geist felt slightly tighter and more modern than Inter. Helvetica Neue for display stayed. -**User invokes:** `/knowledge-capture` again — same thread. +**User asks to capture again — same thread.** **Result — same note rewritten (note the same `thread_id`):** @@ -255,7 +256,7 @@ The accent went through a round of revision: an initial orange (`#F26B3A`) felt ## Relations -- relates-to [[Brand Strategy]] +- relates_to [[Brand Strategy]] ``` Notice that: diff --git a/skills/memory-continue/SKILL.md b/skills/memory-continue/SKILL.md new file mode 100644 index 000000000..0274d1924 --- /dev/null +++ b/skills/memory-continue/SKILL.md @@ -0,0 +1,148 @@ +--- +name: memory-continue +description: "Resume prior work by rebuilding context from the Basic Memory knowledge graph — pick up where you left off using memory:// URLs, recent activity, and search. Use when starting a session or when the user says 'continue with...', 'back to...', or 'where were we?'" +--- + +# Memory Continue + +Resume previous work by reconstructing context from the Basic Memory knowledge graph, so the assistant can pick up across sessions instead of starting cold. + +## When to Use + +- Starting a new session and you need to pick up where you left off +- The user references earlier work: "continue with...", "back to...", "where were we on...?" +- You need context about an ongoing project or spec +- The user asks about something discussed in a previous conversation +- You're working on a task that spans multiple sessions + +## Building Context + +### 1. Identify What to Continue + +If it's unclear, ask: +- What topic or project should you resume? +- What timeframe matters? +- Any specific aspect to focus on? + +### 2. Gather Context with MCP Tools + +**Known topic — use `build_context`.** Navigate the graph from a starting point, following relations outward: + +```python +build_context( + url="memory://topic-or-note-name", + depth=2, # how many relation hops to follow + timeframe="7d", # bias toward recent changes +) +``` + +**No clear starting point — use `recent_activity`.** See what's changed and let it surface the thread: + +```python +recent_activity(timeframe="3d", depth=1) +``` + +**Looking for something specific — use `search_notes`.** Find candidate notes by keyword: + +```python +search_notes(query="async client refactor", page_size=10) +``` + +### 3. Read the Key Notes + +Once you've identified the relevant notes, read them in full: + +```python +read_note(identifier="note-title-or-permalink") +``` + +### 4. Present Context to the User + +Summarize what you found, incrementally: +- Current state of the work +- Recent changes or progress +- Open items and next steps +- Related context that might help + +## Memory URL Reference + +`build_context` and `read_note` both accept `memory://` URLs, which address notes by permalink and support wildcards for gathering groups of notes. + +``` +memory://note-title # a single note by permalink +memory://folder/* # all notes in a folder +memory://specs/SPEC-24* # pattern / prefix match +memory://project/*/requirements # path wildcards +``` + +Use a specific note URL to anchor on one starting point; use a wildcard to pull in a whole folder or family of related notes at once. + +## Timeframe Reference + +`build_context` and `recent_activity` accept natural-language timeframes: + +| Timeframe | Meaning | +|-----------|---------| +| `"today"` | Current day | +| `"yesterday"` | Previous day | +| `"3d"` or `"3 days"` | Last 3 days | +| `"1 week"` or `"7d"` | Last week | +| `"2 weeks"` | Last 2 weeks | +| `"1 month"` | Last month | + +## Scenario Playbooks + +### Resuming a Spec or Project + +```python +# 1. Read the spec / project note +read_note(identifier="SPEC-24: Postgres Database Migration") + +# 2. Pull in related context and recent changes via the graph +build_context(url="memory://SPEC-24*", timeframe="7d") +``` + +Then summarize: the goals, what's completed, what's pending, and any blockers or open decisions. + +### Continuing General Work + +```python +# 1. Check recent activity +recent_activity(timeframe="3d") + +# 2. Read notes from the recent sessions it surfaces +read_note(identifier="relevant-note") +``` + +Then list the modified notes with brief descriptions and ask which thread to dive into. + +### Following Up on a Topic + +```python +# 1. Find the topic +search_notes(query="topic keywords") + +# 2. Build context from the best match, following its relations +build_context(url="memory://found-note-permalink", depth=2) +``` + +Then present the full picture — the note plus its connected context. + +## Project Discovery + +Project names are user-specific. To discover what's available before scoping a search or `memory://` URL: + +```python +list_memory_projects() +``` + +In multi-project setups, prefix a `memory://` URL with the project name (e.g. `memory://research/papers/crdt`) to scope it. + +## Guidelines + +1. **Start broad, then narrow.** Get an overview with `recent_activity` or a wildcard `build_context`, then drill into specific notes. +2. **Present incrementally.** Share what you find as you go rather than holding everything until the end. +3. **Follow relations.** The graph's connections are the point — `build_context` with `depth` surfaces context you wouldn't find by reading one note. +4. **Check multiple projects.** Specs may live separately from implementation notes; discover projects with `list_memory_projects`. +5. **Confirm understanding.** Verify the reconstructed context is what the user actually needs before acting on it. +6. **Capture new progress.** As the resumed work advances, write it back to the graph (see the **memory-notes** skill) so the next session can continue too. diff --git a/skills/memory-curate/SKILL.md b/skills/memory-curate/SKILL.md new file mode 100644 index 000000000..c046c0726 --- /dev/null +++ b/skills/memory-curate/SKILL.md @@ -0,0 +1,243 @@ +--- +name: memory-curate +description: "Curate the Basic Memory knowledge graph: find orphan notes and suggest links, propose typed relations, merge duplicates, audit tags and folders, and build hub notes. Use to organize, connect, and improve a knowledge base as notes accumulate." +--- + +# Memory Curate + +Maintain a healthy, well-connected knowledge graph. As notes accumulate, it pays to periodically organize, link, and curate the knowledge base so isolated notes become a connected graph. + +This skill curates the **knowledge graph** — the notes, relations, and tags that make up the knowledge base. (For hygiene on an agent's own memory *files* — splitting bloated files, pruning stale entries — see **memory-defrag**.) + +## When to Use + +- Asked to organize, clean up, or improve the knowledge base +- Asked to find connections between notes, or what isn't linked yet +- Orphan or unlinked notes are mentioned +- Asked about duplicate or similar notes +- Asked for help with folder organization or tag consistency +- Phrases like "help me organize", "find related notes", "what's not linked", "clean up my notes" + +## Curation Capabilities + +### 1. Find Orphan Notes + +Orphans have no relations to other notes — they're islands in the graph. + +```python +# List notes, then read each to inspect its Relations section +search_notes(query="*", page_size=50) +read_note(identifier="note-to-check") +# Orphans have an empty (or missing) Relations section +``` + +**What to do with orphans:** +- Suggest relations based on content similarity +- Ask whether they should connect to existing topics +- Propose hub notes to gather related orphans (see capability 6) + +### 2. Suggest Typed Relations + +Analyze a note's content and propose meaningful connections. + +```python +read_note(identifier="note-to-analyze") +# Pull out key terms, then search for related notes +search_notes(query="key terms from the note") +``` + +Suggest relations based on shared topics, complementary content (problem/solution, +question/answer), sequence (part 1 → part 2), or hierarchy (parent concept → detail). + +**Relation-type vocabulary:** +- `relates_to` — general topical connection +- `extends` — builds upon or elaborates +- `implements` — realizes a concept or spec +- `depends_on` — requires understanding of +- `part_of` — hierarchy or composition +- `contrasts_with` — presents an alternative view +- `inspired_by` — source of insight +- `enables` — makes something possible + +Custom relation types are fine — use whatever verb is descriptive. + +Add a confirmed relation with `edit_note`: + +```python +edit_note( + identifier="API Design Decisions", + operation="append", + section="Relations", + content="- depends_on [[Rate Limiter]]", +) +``` + +### 3. Identify Similar / Duplicate Notes + +Find notes that may cover the same ground. + +```python +search_notes(query="topic keywords") +# Compare results for: similar titles, overlapping observations, +# shared tags, close-together timestamps +``` + +**Actions for duplicates:** +- **Merge** into a single comprehensive note, then redirect the loser with a relation +- Link with `supersedes` / `updates` when one revises the other +- **Differentiate** by adding context that clarifies each note's distinct focus + +```python +# Point an older note at the one that replaces it +edit_note( + identifier="DB Schema v1", + operation="append", + section="Relations", + content="- updates [[DB Schema v2]]", +) +``` + +### 4. Folder Organization Review + +```python +list_directory(dir_name="/", depth=3) +``` + +Look for overcrowded folders, single-note folders, inconsistent naming, and notes +that belong elsewhere. Suggest grouping related notes into topic folders, adding +subfolders for large categories, and a consistent naming convention. Move misplaced +notes with `move_note` — the permalink stays stable, so wiki-links keep resolving. + +```python +move_note( + identifier="API Design Decisions", + destination_path="architecture/api-design-decisions.md", +) +``` + +### 5. Tag Consistency + +```python +search_notes(query="*", page_size=100) +# Inspect tag patterns across results +``` + +Look for: +- **Variant tags** — `architecture` vs `arch`; pick one and standardize +- **Unused tags** — present on a single note, no longer carrying weight +- **Over-used generic tags** — so broad they don't aid discovery +- **Missing tags** — relevant notes lacking an obvious tag + +### 6. Create Index / Hub Notes + +After finding a cluster of related notes, build a navigation hub. + +```python +write_note( + title="Architecture Decisions Index", + directory="indexes", + tags=["architecture", "index"], + note_type="index", + content="""# Architecture Decisions Index + +A hub linking architecture-related decisions and patterns. + +## Decisions +- [[Database Selection Decision]] +- [[API Design Patterns]] +- [[Authentication Architecture]] + +## Patterns +- [[Repository Pattern]] +- [[Async Client Pattern]] + +## Observations +- [index] Central hub for architecture knowledge #navigation + +## Relations +- indexes [[Architecture]]""", +) +``` + +### 7. Enrich Sparse Notes + +Find notes lacking structure and fill them in. + +```python +read_note(identifier="sparse-note") +``` + +If the note is missing an Observations section, suggest categories. If it has no +Relations, suggest links. If it has no tags, suggest relevant ones. If it lacks +context, suggest adding background. Apply with `edit_note`. + +## Curation Workflows + +### Quick Health Check + +A fast overview of knowledge base status: + +1. Count total notes +2. Identify orphan count +3. List recently modified (`recent_activity`) +4. Check for obvious duplicates +5. Report folder distribution + +### Deep Organization Session + +Thorough review and improvement: + +1. **Audit** — catalog all notes, identify issues +2. **Orphans** — address unlinked notes +3. **Relations** — suggest new connections +4. **Duplicates** — merge or differentiate similar notes +5. **Structure** — reorganize folders if needed +6. **Index** — create hub notes for major topics + +### Topic-Focused Organization + +Organize around a specific subject: + +1. Find all notes related to the topic (`search_notes`) +2. Map existing relations with `build_context(url="memory://...")` +3. Identify gaps in the topic graph +4. Suggest new notes to fill them +5. Create a topic index note + +## Best Practices + +1. **Work incrementally.** Don't reorganize everything at once. +2. **Confirm before changing.** Always ask before moving, merging, or editing notes. +3. **Preserve permalinks.** Moving a note is fine; changing its permalink breaks inbound links. +4. **Explain suggestions.** Say *why* a relation or merge makes sense. +5. **Respect the existing system.** Enhance the user's organization — don't impose a new taxonomy. +6. **Show the graph.** Use `build_context` to help the user see how notes connect. + +## Example Conversations + +**User:** "Help me organize my notes" + +The assistant: +1. Runs a health check on the knowledge base +2. Reports: "You have 47 notes. I found 12 orphans and 3 potential duplicates." +3. Asks: "Want to start by connecting the orphans, or review the duplicates first?" + +**User:** "Find notes that should link to my API design note" + +The assistant: +1. Reads the API design note +2. Searches for related content +3. Suggests: "5 notes could relate — + - 'REST Best Practices' → `relates_to` + - 'Authentication Flow' → `implements` + - 'Rate Limiting Decision' → `extends` + Should I add any of these relations?" + +**User:** "Are there notes on similar topics?" + +The assistant: +1. Analyzes titles and content for clusters +2. Reports: "Possible overlaps — + - 'Auth Flow' and 'Authentication Design' cover similar ground + - 'DB Schema v1' and 'DB Schema v2' likely want a `supersedes` relation + Want to review either?" diff --git a/skills/memory-notes/SKILL.md b/skills/memory-notes/SKILL.md index 8c57caba4..b0c3ce3b9 100644 --- a/skills/memory-notes/SKILL.md +++ b/skills/memory-notes/SKILL.md @@ -276,26 +276,42 @@ Basic Memory auto-generates frontmatter (including the permalink and memory URL) ### Editing an Existing Note -Use `edit_note` to append, prepend, or find-and-replace within a note: +Use `edit_note` to update a note in place — four operations: ```python -# Append new observations +# append / prepend — add to the end or start (use for time-ordered logs) edit_note( identifier="API Design Decisions", operation="append", section="Observations", content="- [decision] Use OpenAPI 3.1 for spec generation #api" ) +edit_note( + identifier="API Design Decisions", + operation="prepend", + content="> Updated 2026-05-28: auth approach finalized.\n" +) -# Add a new relation +# replace_section — rewrite a named section (use for living content that stays current) edit_note( identifier="API Design Decisions", - operation="append", - section="Relations", - content="- depends_on [[Rate Limiter]]" + operation="replace_section", + section="Summary", + content="Concise, current summary of the decision and its rationale." +) + +# find_replace — swap specific text +edit_note( + identifier="API Design Decisions", + operation="find_replace", + find_text="OpenAPI 3.0", + content="OpenAPI 3.1" ) ``` +When an edit is destructive (`replace_section`, `find_replace`), it's good practice to +read the note first and confirm the change before applying it. + ### Moving a Note Use `move_note` to reorganize notes into different directories: diff --git a/tests/test_update_versions.py b/tests/test_update_versions.py index 9c8a78494..ec8ebe956 100644 --- a/tests/test_update_versions.py +++ b/tests/test_update_versions.py @@ -76,3 +76,72 @@ def write(path: str, content: str) -> None: ) openclaw_package = json.loads((tmp_path / "integrations/openclaw/package.json").read_text()) assert openclaw_package["version"] == "0.21.3-beta.1" + + +def _seed_repo(tmp_path: Path) -> None: + """Write all version-bearing manifests at 0.0.0 under a fake repo root.""" + + def write(path: str, content: str) -> None: + target = tmp_path / path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content) + + package_manifest = {"version": "0.0.0"} + marketplace_manifest = { + "metadata": {"version": "0.0.0"}, + "plugins": [{"name": "basic-memory", "version": "0.0.0"}], + } + write("src/basic_memory/__init__.py", '__version__ = "0.0.0"\n') + write( + "server.json", + json.dumps( + {"version": "0.0.0", "packages": [{"identifier": "basic-memory", "version": "0.0.0"}]} + ) + + "\n", + ) + write(".claude-plugin/marketplace.json", json.dumps(marketplace_manifest) + "\n") + write("plugins/claude-code/.claude-plugin/plugin.json", json.dumps(package_manifest) + "\n") + write( + "plugins/claude-code/.claude-plugin/marketplace.json", + json.dumps(marketplace_manifest) + "\n", + ) + write("integrations/hermes/plugin.yaml", "version: 0.0.0\n") + write("integrations/hermes/__init__.py", '__version__ = "0.0.0"\n') + write("integrations/openclaw/package.json", json.dumps(package_manifest) + "\n") + + +def _plugin_version(tmp_path: Path) -> str: + path = tmp_path / "plugins/claude-code/.claude-plugin/plugin.json" + return json.loads(path.read_text())["version"] + + +def test_scope_packages_leaves_core_untouched( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(update_versions, "ROOT", tmp_path) + _seed_repo(tmp_path) + + update_versions.update_versions("v0.21.6", scope="packages", dry_run=False) + + # Core stays put; only the agent/plugin artifacts move. + assert (tmp_path / "src/basic_memory/__init__.py").read_text() == '__version__ = "0.0.0"\n' + assert json.loads((tmp_path / "server.json").read_text())["version"] == "0.0.0" + assert _plugin_version(tmp_path) == "0.21.6" + + +def test_scope_core_leaves_packages_untouched( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(update_versions, "ROOT", tmp_path) + _seed_repo(tmp_path) + + update_versions.update_versions("v0.21.6", scope="core", dry_run=False) + + assert (tmp_path / "src/basic_memory/__init__.py").read_text() == '__version__ = "0.21.6"\n' + assert _plugin_version(tmp_path) == "0.0.0" + + +def test_invalid_scope_raises(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + monkeypatch.setattr(update_versions, "ROOT", tmp_path) + with pytest.raises(SystemExit): + update_versions.update_versions("v0.21.6", scope="nonsense", dry_run=True)