diff --git a/.github/workflows/consolidated-packages.yml b/.github/workflows/consolidated-packages.yml index a92de6231..bbe7f4ee5 100644 --- a/.github/workflows/consolidated-packages.yml +++ b/.github/workflows/consolidated-packages.yml @@ -44,6 +44,19 @@ jobs: - name: Verify shared version dry run run: just release-dry-run v0.99.0 + codex: + name: Codex plugin + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: extractions/setup-just@v4 + + - name: Validate manifest, hooks, skills, MCP config, and schemas + run: just --justfile plugins/codex/justfile --working-directory plugins/codex check + skills: name: Shared skills permissions: diff --git a/plugins/claude-code/README.md b/plugins/claude-code/README.md index 67e13058d..6932e3895 100644 --- a/plugins/claude-code/README.md +++ b/plugins/claude-code/README.md @@ -41,7 +41,7 @@ Plugin skills are namespaced under the plugin name: | `/basic-memory:bm-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:bm-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:bm-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:bm-status` | Diagnostic — shows the active project, team read-sources and share targets, capture folders, output-style state, recent session checkpoints, and active-task count. | +| `/basic-memory:bm-status` | Diagnostic — shows the active project, team read-sources and share targets, capture folders, shared local hook inbox/flush health, recent session checkpoints, and active-task count. | ## Requirements @@ -126,6 +126,14 @@ settings (or select it via `/config`). | `recallPrompt` | _(built-in)_ | The instruction appended to the brief | | `preCompactCapture` | `extractive` | How checkpoints are produced | | `captureEvents` | `false` | Opt-in: record redacted lifecycle-event envelopes to the local inbox (see `basic-memory hook status` / `flush`). Only the JSON boolean `true` enables it. | +| `redactKeys` | `[]` | Additional payload keys to redact before an event enters the local inbox | +| `redactPaths` | `[]` | Additional paths to redact from working-directory and path-bearing capture content | + +The plugin seeds schemas for notes the Claude integration writes directly: +`session`, `decision`, and `task`. Optional flush projection also writes +normalized `session` and `tool_ledger` artifacts. Those projection contracts are +owned and tested by Basic Memory core rather than copied into separate +host-plugin schemas. See [DESIGN.md](./DESIGN.md) for the complete configuration schema, the Claude-Code-project ↔ Basic-Memory-project mapping, and team-workspace behavior. diff --git a/plugins/claude-code/settings.example.json b/plugins/claude-code/settings.example.json index d5f1cba55..e954d0b9d 100644 --- a/plugins/claude-code/settings.example.json +++ b/plugins/claude-code/settings.example.json @@ -9,6 +9,8 @@ "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", "captureEvents": false, + "redactKeys": [], + "redactPaths": [], "placementConventions": null, "teamProjects": { "my-team/notes": { "promoteFolder": "shared" } diff --git a/plugins/claude-code/skills/bm-setup/SKILL.md b/plugins/claude-code/skills/bm-setup/SKILL.md index 74154572b..f029150fc 100644 --- a/plugins/claude-code/skills/bm-setup/SKILL.md +++ b/plugins/claude-code/skills/bm-setup/SKILL.md @@ -99,7 +99,17 @@ Ask only what you can't infer. Cover: 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 — +6. **Lifecycle-event capture.** "Should I also keep a local, redacted trail of + SessionStart and PreCompact events for later projection?" Default to **off**. + Explain that the normal session brief and PreCompact checkpoint work either + way; enabling this adds envelopes to a local inbox until `bm hook flush` + projects them. Only the JSON boolean `true` enables capture. + - If enabled, optionally ask for repo-specific `redactKeys` (additional payload + keys) and `redactPaths` (working directories or path-bearing content). The + built-in redaction floor still applies when these lists are empty. + - Capture stays local and personal. It never writes directly to team projects. + +7. **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:bm-remember` on demand)?" Enabling it sets @@ -107,7 +117,7 @@ Ask only what you can't infer. Cover: 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 — +8. **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). @@ -121,6 +131,11 @@ The plugin ships seed schemas at `/schemas/` — that's **two directorie from this skill's directory, then `schemas/`** (this skill is at `/skills/bm-setup/`). Read `session.md`, `decision.md`, and `task.md` there. +These schemas cover notes the Claude integration writes directly. The normalized +`session` and `tool_ledger` artifacts written by `bm hook flush` are core-owned +projections; their shape belongs to the core projector and its tests, not a +duplicated per-plugin `tool_ledger` schema. + For each one: - Check whether the chosen project already has a schema for that type (`search_notes` with `metadata_filters={"type": "schema"}`, or try @@ -176,6 +191,9 @@ Build the `basicMemory` block from the interview: "rememberFolder": "bm-remember", "recallTimeframe": "3d", "preCompactCapture": "extractive", + "captureEvents": false, + "redactKeys": [], + "redactPaths": [], "placementConventions": "", "teamProjects": {} }, @@ -186,7 +204,9 @@ 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. +valid JSON. Always persist `captureEvents` as a JSON boolean. Empty `redactKeys` +and `redactPaths` lists may be omitted; when present, they must be JSON arrays of +strings. 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. @@ -203,6 +223,12 @@ uses (`basic-memory` / `bm` / `uvx basic-memory`): - **One shared project** (only if `secondaryProjects` is non-empty): a `--type decision --status open` query against the first ref. It just needs to return *cleanly* — `0 results` is fine; an **error** means the ref doesn't route. +- **Hook health:** run + `basic-memory hook status --harness claude --project-dir ` with + the first available `basic-memory`, `bm`, or `uvx basic-memory` launcher. It + must find the intended settings and report the selected project and capture + state. Record the shared inbox depth and last-flush state in the setup summary; + the counts include envelopes from every supported harness. If a query errors, or the primary returns nothing, surface it and fix the project ref before closing — don't let the next session's brief come up empty. @@ -211,7 +237,8 @@ ref before closing — don't let the next session's brief come up empty. Confirm what you did in a few lines: the project mapping, which schemas were seeded vs. already present, whether placement was learned or suggested, the smoke-test -result, and whether the output style is on. +result, whether lifecycle-event capture is enabled, any extra redaction controls, +the shared hook inbox/flush state, and whether the output style is on. Then handle activation based on the output style: - **Output style enabled** → it's fixed at session start, so the full capture diff --git a/plugins/claude-code/skills/bm-status/SKILL.md b/plugins/claude-code/skills/bm-status/SKILL.md index dc5a668e5..74063d4cf 100644 --- a/plugins/claude-code/skills/bm-status/SKILL.md +++ b/plugins/claude-code/skills/bm-status/SKILL.md @@ -11,9 +11,10 @@ This is a quick diagnostic — gather the facts and lay them out; don't over-inv ## 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. +1. **CLI reachable?** Run `basic-memory --version`, then `bm --version`, then + `uvx basic-memory --version`. If no launcher resolves, report the CLI status as + unavailable and continue with MCP/config checks. The plugin hooks can still use + their uv-managed environment. 2. **Configuration.** Read the `basicMemory` block with the hooks' precedence — user-level `~/.claude/settings.json` as the base, then the project's @@ -23,15 +24,26 @@ This is a quick diagnostic — gather the facts and lay them out; don't over-inv default project is used), `secondaryProjects` (team/shared read sources), `teamProjects` (share targets for `/basic-memory:bm-share`), `captureFolder` (default `sessions`), `rememberFolder` (default `bm-remember`), and - `preCompactCapture` mode (default `extractive`). + `preCompactCapture` mode (default `extractive`), `captureEvents` (default + `false`), `redactKeys`, and `redactPaths`. - 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 +3. **Core hook health.** With the first available launcher, run + `basic-memory hook status --harness claude --project-dir `. + Report its shared inbox path, pending envelopes, processed envelopes, last + flush, settings state, resolved primary project, capture state, capture folder, + Basic Memory version, and uv version. Inbox counts are global across supported + harnesses; do not attribute a backlog solely to Claude. Treat this command's + settings resolution as canonical for hook behavior; if it disagrees with the + manually merged config, show the mismatch. If no launcher resolves, mark these + fields unavailable and continue. + +4. **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 +5. **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 @@ -53,6 +65,14 @@ you couldn't determine, rather than failing the whole report): - Remember folder: - Output style: - PreCompact: +- Event capture: +- Redact keys: +- Redact paths: +- Shared hook inbox: +- Shared pending envelopes: +- Shared processed envelopes: +- Last flush: +- Hook runtime: basic-memory ; uv - Recent checkpoints: - — <permalink> ... @@ -62,3 +82,7 @@ you couldn't determine, rather than failing the whole report): 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). + +Warn when event capture is enabled and pending envelopes are accumulating or the +last flush is `never`, while noting that another harness may contribute to the +shared counts. Do not warn about an empty inbox when capture is disabled. diff --git a/plugins/codex/README.md b/plugins/codex/README.md index 11178d3c9..4575dc97b 100644 --- a/plugins/codex/README.md +++ b/plugins/codex/README.md @@ -19,8 +19,8 @@ verification, decision capture, and resumable checkpoints. them into a full decision or session note. - **Share deliberately.** The `bm-share` skill copies personal notes to configured team projects only after confirmation. -- **Report status.** The `bm-status` skill shows configuration, reachability, and - recent memory state. +- **Report status.** The `bm-status` skill shows configuration, reachability, + shared local hook inbox/flush health, and recent memory state. ## Package Contents @@ -88,6 +88,8 @@ Run the setup skill, or create `.codex/basic-memory.json` in a repo: "rememberFolder": "codex-remember", "recallTimeframe": "7d", "captureEvents": false, + "redactKeys": [], + "redactPaths": [], "placementConventions": "Put decisions in decisions/ and work checkpoints in codex-sessions/." } } @@ -96,6 +98,15 @@ Run the setup skill, or create `.codex/basic-memory.json` in a repo: `captureEvents` is opt-in and off by default: only the JSON boolean `true` enables recording of redacted lifecycle-event envelopes to a local inbox under your Basic Memory home (`basic-memory hook status` / `basic-memory hook flush`). +Add `redactKeys` and `redactPaths` arrays to extend the built-in redaction floor +for repository-specific payload fields and paths. + +The plugin's seed schemas cover notes Codex writes directly: `codex_session`, +`decision`, and `task`. Optional flush projection also writes normalized +`session` and `tool_ledger` artifacts. Those are core-owned contracts implemented +and tested with the projector, not duplicate schema files maintained by each host +plugin. `bm-orient` and `bm-status` still recall normalized `session` notes +alongside Codex checkpoints. Codex plugin hooks must be reviewed and trusted before they run. Open `/hooks` in Codex after enabling the plugin and trust the Basic Memory hook definitions. diff --git a/plugins/codex/skills/bm-orient/SKILL.md b/plugins/codex/skills/bm-orient/SKILL.md index e59ddc823..0dc1280ca 100644 --- a/plugins/codex/skills/bm-orient/SKILL.md +++ b/plugins/codex/skills/bm-orient/SKILL.md @@ -18,7 +18,12 @@ the user asks where things stand. - active tasks: `type=task`, `status=active` - open decisions: `type=decision`, `status=open` - recent Codex sessions: `type=codex_session`, after `recallTimeframe` - - recent generic sessions only if no Codex sessions are found + - recent core-projected sessions: `type=session`, after `recallTimeframe` + + Always query both session types. Merge and deduplicate the results, sort them + newest first, and prefer the highest-signal checkpoint regardless of which + producer wrote it. `codex_session` preserves deliberate and legacy Codex + checkpoints; `session` carries normalized artifacts from `bm hook flush`. 3. Query configured `secondaryProjects` read-only for open decisions. Do not write to shared projects during orientation. diff --git a/plugins/codex/skills/bm-setup/SKILL.md b/plugins/codex/skills/bm-setup/SKILL.md index 011a92059..55ce568d2 100644 --- a/plugins/codex/skills/bm-setup/SKILL.md +++ b/plugins/codex/skills/bm-setup/SKILL.md @@ -35,6 +35,16 @@ repo, default project, current directory, or previous local state. - `rememberFolder`: default `codex-remember`. - `placementConventions`: a short note about where decisions, tasks, and research notes should land. +- `captureEvents`: whether to record redacted lifecycle-event envelopes in the + local hook inbox. Default to `false`; SessionStart briefs and PreCompact + checkpoints work without it. +- `redactKeys` and `redactPaths`: optional additions to the built-in redaction + floor. Ask for these only when event capture is enabled or the user has + repo-specific privacy requirements. + +Explain the capture tradeoff before asking: enabled capture adds a local, +redacted event trail that stays queued until `bm hook flush` projects it. It does +not write to team projects, and only the JSON boolean `true` enables it. If there are duplicate names, show qualified names and ask the user which one to use. Prefer qualified project names or project ids for cloud projects. Never pick @@ -59,14 +69,21 @@ After confirming the plan, write `.codex/basic-memory.json` in the repo: "captureFolder": "codex-sessions", "rememberFolder": "codex-remember", "recallTimeframe": "7d", + "captureEvents": false, + "redactKeys": [], + "redactPaths": [], "placementConventions": "<short convention>" } } ``` Preserve unrelated keys if the file already exists. Include `projectMode` when -the user chose cloud, local, or mixed routing. This file is intentionally -Codex-specific; do not write `.claude/settings.json`. +the user chose cloud, local, or mixed routing. Always persist `captureEvents` as +a JSON boolean. Empty `redactKeys` and `redactPaths` lists may be omitted; when +present, they must be JSON arrays of strings. `redactKeys` extends payload-key +redaction, while `redactPaths` also protects working-directory and path-bearing +checkpoint content. This file is intentionally Codex-specific; do not write +`.claude/settings.json`. ## Seed Schemas @@ -80,6 +97,11 @@ exist: - `decision.md` - `task.md` +These schemas cover notes Codex writes directly. The normalized `session` and +`tool_ledger` artifacts written by `bm hook flush` are core-owned projections; +their shape belongs to the core projector and its tests, not duplicated plugin +schema files. They remain queryable by their `type` frontmatter. + Use `write_note` with `directory="schemas"`, `note_type="schema"`, schema frontmatter as metadata, and the markdown body as content. Do not paste the YAML frontmatter into content. @@ -94,8 +116,12 @@ Before closing, prove the mapping works: - Search the primary project for `type=schema` with page size 5. - Search one shared project for open decisions if shared projects were configured. -- If either query errors, fix the project ref before finishing. - -Finish with the project mapping, schemas seeded or skipped, and the verification -result. Tell the user that plugin hooks need to be reviewed and trusted in Codex -before they run. +- Run `basic-memory hook status --harness codex --project-dir <repo-root>` (using + `bm` or `uvx basic-memory` if needed). Confirm that it finds this repo's + settings, reports the selected project, and shows the intended capture state. + Its inbox counts are shared across harnesses. +- If any check errors, fix the project ref or hook launcher before finishing. + +Finish with the project mapping, schemas seeded or skipped, capture/redaction +choices, shared inbox status, and the verification result. Tell the user that +plugin hooks need to be reviewed and trusted in Codex before they run. diff --git a/plugins/codex/skills/bm-status/SKILL.md b/plugins/codex/skills/bm-status/SKILL.md index 471025d51..cca2d4674 100644 --- a/plugins/codex/skills/bm-status/SKILL.md +++ b/plugins/codex/skills/bm-status/SKILL.md @@ -12,19 +12,38 @@ Gather a concise diagnostic. Do not over-investigate. 1. CLI reachability: - `basic-memory --version` - fallback `bm --version` + - fallback `uvx basic-memory --version` + + Keep going if no launcher resolves. The plugin hook scripts can still use + their uv-managed environment; report hook health as unavailable instead of + claiming the hooks cannot work. 2. Plugin config: - read `.codex/basic-memory.json` - report `primaryProject`, `secondaryProjects`, `teamProjects`, - `captureFolder`, `rememberFolder`, `recallTimeframe`, and `focus` + `captureFolder`, `rememberFolder`, `recallTimeframe`, `focus`, + `captureEvents`, `redactKeys`, and `redactPaths` + +3. Core hook health: + - with the first available launcher, run + `basic-memory hook status --harness codex --project-dir <repo-root>` + - report the shared inbox path, pending envelopes, processed envelopes, last + flush, settings state, resolved primary project, capture state, capture + folder, Basic Memory version, and uv version from that command + - inbox counts are global across supported harnesses; do not attribute a + backlog solely to Codex + - treat the command's settings resolution as canonical for hook behavior; if + it disagrees with the manually read config, show the mismatch -3. Hook files: +4. Hook files: - confirm `plugins/codex/hooks/hooks.json` exists if running from this repo - remind the user that Codex plugin hooks must be reviewed and trusted before they run -4. Basic Memory queries: - - recent `type=codex_session`, page size 5 +5. Basic Memory queries: + - query recent `type=codex_session` and `type=session`, then merge, deduplicate, + sort newest first, and keep the newest five; the first type covers deliberate + and PreCompact Codex checkpoints, while the second covers core projections - active `type=task`, `status=active` - open `type=decision`, `status=open` @@ -41,10 +60,21 @@ Basic Memory for Codex - Capture folder: <captureFolder> - Remember folder: <rememberFolder> - Recall timeframe: <recallTimeframe> -- Recent Codex checkpoints: <count> +- Event capture: <enabled | disabled> +- Redact keys: <configured count or none> +- Redact paths: <configured count or none> +- Shared hook inbox: <path or unavailable> +- Shared pending envelopes: <count or unavailable> +- Shared processed envelopes: <count or unavailable> +- Last flush: <timestamp, never, or unavailable> +- Hook runtime: basic-memory <version>; uv <version or missing> +- Recent checkpoints: <count across codex_session and session> - Active tasks: <count> - Open decisions: <count> - Hooks: installed; trust review required in Codex ``` -List recent checkpoints by title and permalink when available. +List recent checkpoints by type, title, and permalink when available. Warn when +event capture is enabled and pending envelopes are accumulating or the last flush +is `never`, while noting that another harness may contribute to the shared counts. +Do not warn about an empty inbox when capture is disabled. diff --git a/scripts/validate_claude_plugin.py b/scripts/validate_claude_plugin.py index a8ddfec5b..840d7f6b4 100644 --- a/scripts/validate_claude_plugin.py +++ b/scripts/validate_claude_plugin.py @@ -29,6 +29,20 @@ REQUIRED_SCHEMAS = ("session.md", "decision.md", "task.md") # Skills the plugin ships as namespaced slash commands (/basic-memory:<name>). REQUIRED_SKILLS = ("bm-setup", "bm-remember", "bm-status", "bm-share") +REQUIRED_SKILL_TEXT: dict[str, tuple[str, ...]] = { + "bm-setup": ( + "captureEvents", + "redactKeys", + "redactPaths", + "hook status --harness claude", + ), + "bm-status": ( + "hook status --harness claude", + "pending envelopes", + "processed envelopes", + "last flush", + ), +} def read_json(path: Path) -> dict: @@ -128,6 +142,19 @@ def validate_claude_plugin(plugin_dir: Path) -> None: raise SystemExit(f"{skill_dir}: skill name must match directory") if not frontmatter.get("description"): raise SystemExit(f"{skill_md}: missing description frontmatter") + skill_text = skill_md.read_text(encoding="utf-8") + for required_text in REQUIRED_SKILL_TEXT.get(skill_dir.name, ()): + if required_text not in skill_text: + raise SystemExit(f"{skill_md}: missing plugin contract text {required_text!r}") + + readme = (plugin_dir / "README.md").read_text(encoding="utf-8") + for required_text in ( + "normalized `session`", + "`tool_ledger`", + "owned and tested by Basic Memory core", + ): + if required_text not in readme: + raise SystemExit(f"README.md: missing schema ownership text {required_text!r}") print(f"validated Claude Code plugin in {plugin_dir}") diff --git a/scripts/validate_codex_plugin.py b/scripts/validate_codex_plugin.py index db05b90c9..14eb6e952 100755 --- a/scripts/validate_codex_plugin.py +++ b/scripts/validate_codex_plugin.py @@ -22,6 +22,27 @@ "bm-share", "bm-status", ) +REQUIRED_SKILL_TEXT: dict[str, tuple[str, ...]] = { + "bm-setup": ( + "captureEvents", + "redactKeys", + "redactPaths", + "hook status --harness codex", + ), + "bm-status": ( + "hook status --harness codex", + "pending envelopes", + "processed envelopes", + "last flush", + "type=codex_session", + "type=session", + ), + "bm-orient": ( + "Always query both session types", + "type=codex_session", + "type=session", + ), +} REQUIRED_SCHEMAS = ("codex-session.md", "decision.md", "task.md") REQUIRED_HOOK_EVENTS = ("SessionStart", "PreCompact") # Zero-logic shims: the only hook code the plugin ships. The Python bodies @@ -121,6 +142,10 @@ def validate_plugin(plugin_dir: Path) -> None: raise SystemExit(f"{skill_file}: name must match directory") if not frontmatter.get("description"): raise SystemExit(f"{skill_file}: missing description") + skill_text = skill_file.read_text(encoding="utf-8") + for required_text in REQUIRED_SKILL_TEXT.get(skill_dir.name, ()): + if required_text not in skill_text: + raise SystemExit(f"{skill_file}: missing plugin contract text {required_text!r}") for rel in REQUIRED_SKILL_AGENT_FILES: require_path(skill_dir / rel, f"skill {rel}") @@ -136,6 +161,11 @@ def validate_plugin(plugin_dir: Path) -> None: if not frontmatter.get("entity"): raise SystemExit(f"{schema_file}: missing entity") + readme = (plugin_dir / "README.md").read_text(encoding="utf-8") + for required_text in ("normalized `session`", "`tool_ledger`", "core-owned contracts"): + if required_text not in readme: + raise SystemExit(f"README.md: missing schema ownership text {required_text!r}") + print(f"validated Codex plugin in {plugin_dir}")