1
0
forked from jason/echo

Phase 5: group-voice docs, onboarding runbook, CHORUS icon — v2.0.0-rc.1

The final phase of CHORUS-PLAN.md. Code- and doc-complete for group use;
rc pends live deployment at chorusapi.mpm.to.

- Repo README rewritten for CHORUS: group model (schema 5), concurrency
  posture, configuration, baked-build onboarding, layout, testing, 2.0
  version history (ECHO history referenced at the fork tag).
- Operator→member voice sweep across SKILL.md (incl. the trigger
  description, now group-aware), all references, 8 command docs, plugin
  README; member preferences / semantic layer / triage guidance describe
  the group model.
- docs/ONBOARDING.md: member runbook — vault stand-up, per-member config,
  bake, deliver, self-onboarding first load, norms, offboarding.
- docs/USAGE.md rewritten as the member usage guide.
- New icon: chorus-icon.svg + 1024/512/64 PNGs (three voices, one center).
- Removed ECHO-era assets (icons, spec PDF, perf briefs); ECHO planning
  docs retired to docs/history/. All preserved at echo-fork-point.
- eval/README documents test_multimember.py. Manifest -> 2.0.0-rc.1;
  rebuilt chorus-memory.plugin.

Verified: routing-sync, 25/25 unit, scaffold, 5 mock e2e suites,
run_eval metrics green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 15:41:58 -05:00
parent 1a7690426f
commit f2bfd70dd4
36 changed files with 324 additions and 807 deletions
+66
View File
@@ -0,0 +1,66 @@
# CHORUS Member Onboarding
How to add a member to the group vault — from nothing to their first attributed write. The vault admin does steps 13; the member does step 4; the plugin does the rest.
## 0. One-time: stand up the vault (admin, once per group)
1. Run Obsidian with the **Local REST API** plugin on the backend host, pointed at an empty vault dedicated to the group.
2. Expose it at the deployment endpoint (**`https://chorusapi.mpm.to`**) behind the reverse proxy with a valid cert. Note the REST API's bearer key — this is the **vault key** every member's build will carry.
3. Configure your own machine (`chorus.py config set --group "…" --member "<your-id>" --endpoint "https://chorusapi.mpm.to" --key "…"`), then run `python3 scripts/bootstrap.py` — it stands up the folder tree, templates, group profile, group inbox, your member namespace, and the schema-5 marker. Follow with `sweep.py --apply` and `/chorus-doctor` to confirm green.
## 1. Create the member's config (admin)
Each member needs four values. Copy `chorus-memory.config.template.json` and fill it in:
```json
{
"group": "MPM",
"member": "alice",
"endpoint": "https://chorusapi.mpm.to",
"key": "<the vault key>"
}
```
`member` is a **kebab-case slug** (`^[a-z0-9][a-z0-9-]{0,31}$`) — it becomes the `author:` stamp on every one of their writes and their `_agent/members/alice/` namespace, so pick it once and keep it. `group` is the display name shared by everyone.
## 2. Bake their artifact (admin)
```bash
python build.py --bake-key --from alice-config.json --label alice
# -> dist/chorus-memory-<version>-alice.plugin
```
The baked artifact carries their member id, the endpoint, and the vault key inside the plugin, so it works on desktop **and** in every CoWork session with zero setup — no config file, no per-session paste. A complete baked set is authoritative and can't be shadowed by stale configs.
> **The artifact contains the live vault bearer token.** `dist/` is gitignored — deliver the file directly to that one member and never commit, push, or publish it. Offboarding/rotation = rotate the vault key, rebuild every member's artifact, redeliver.
## 3. Deliver (admin)
Send the member their `.plugin` file over a private channel. That file is their whole setup.
## 4. Install and start (member)
Install the plugin (Claude Code: `/plugin install <file>`; CoWork: add it to the session). Then just start a substantive session — the SessionStart hook runs `load`, and on that first load CHORUS **self-onboards** the member:
- creates `_agent/members/alice/` (preferences, current-context, inbox) and `_agent/sessions/alice/`,
- adds `- alice (joined YYYY-MM-DD)` to the group profile's `## Members` roster.
Nothing else to do. Sanity checks if wanted: `/chorus-doctor` (all green, member id shown with its source) and `load --all-members` (they appear in the roster).
## 5. First-session norms (member)
- Tell the agent a little about yourself when convenient — it lands in **your** `preferences.md` (`## Member` / `## Observations`). Preferences are group-visible by design; truly private material belongs in a personal vault, not CHORUS.
- Your quick captures go to **your** inbox; drop "someone should look at this" items in the **group inbox** (`inbox/captures/group.md`) as `- YYYY-MM-DD [you]: …`.
- Shared knowledge (projects, people, companies, decisions) is everyone's — the duplicate gate will stop you from creating a parallel note for something a teammate already tracks, and will tell you whose note it is.
- Never edit another member's `_agent/members/<id>/` namespace. Facts about a person as a person go in `resources/people/`.
## Troubleshooting
| Symptom | Meaning / fix |
|---|---|
| `NOT CONFIGURED` (exit 78) on load | The artifact isn't baked / config missing. Install the baked plugin, or `chorus.py config import <file>`. |
| `config set: --member must be a kebab-case slug` | Fix the member id — lowercase letters/digits/hyphens only. |
| Doctor: `member id configured — invalid` | Same as above; the config's `member` value isn't a valid slug. |
| Doctor: `vault reachable — FAIL` | Obsidian / Local REST API / the proxy at `chorusapi.mpm.to` isn't up. Not a config problem. |
| Load says `marker absent — vault not bootstrapped` | The vault itself was never stood up — run step 0.3 (admin). |
| `/chorus-health` reports `missing-author` | A note with `agent_written: true` lacks `author:` — someone hand-wrote it; add their member id. |
+40 -61
View File
@@ -1,89 +1,68 @@
# ECHO Memory — Usage Guide
# CHORUS Memory — Usage Guide
*Written for Bryan (and anyone else coming back to ECHO after a break). Current as of v1.5.1, July 2026.*
*Written for any group member coming to CHORUS (or back to it after a break). Current as of v2.0.0-rc.1, July 2026. Setup itself is in [ONBOARDING.md](ONBOARDING.md).*
ECHO gives Claude persistent memory across sessions. Everything durable — people, companies, projects, decisions, preferences, session history — lives as markdown notes in an Obsidian vault, read and written over the Obsidian Local REST API. The plugin ships the whole toolchain (pure Python, stdlib only — works the same on Windows/macOS/Linux) plus the operating procedure Claude follows.
CHORUS gives Claude persistent **group memory** across sessions. Everything durable — people, companies, projects, decisions, session history, each member's preferences — lives as markdown notes in one shared Obsidian vault, read and written over the Obsidian Local REST API. The whole team's sessions work against the same knowledge graph, and every agent write is attributed (`author: <member>`), so you always know who knows what.
The short version of how to use it: **you mostly don't have to do anything.** Since v1.5 the system loads itself at session start and nudges itself to save at session end. The slash commands below are for when you want to drive it explicitly.
The short version of how to use it: **you mostly don't have to do anything.** The system loads itself at session start (and onboards you automatically on your very first session), and nudges itself to save at session end. The slash commands below are for when you want to drive it explicitly.
---
## One-time setup
If Jason handed you a **per-user baked plugin** (`echo-memory-1.x.x-bryan.plugin`), just install it. Your vault credentials are baked into the artifact — no config file, no key pasting, works on desktop and in every CoWork session. This is the normal path.
You were handed a **per-member baked plugin** (`chorus-memory-2.x-<you>.plugin`). Just install it — your member id, the endpoint, and the vault key are baked in. No config file, no key pasting, works on desktop and in every CoWork session. Treat the file like a password: it contains the live vault key. Don't share or commit it.
If you have the **generic plugin** instead, it will report `NOT CONFIGURED` on first use and ask for your config file. Install it once with:
```bash
python3 echo.py config import <your-config.json>
```
That writes `~/.claude/echo-memory/config.json` (owner, endpoint, API key). It survives plugin updates — one time per machine.
**Verify either way:** run `/echo-doctor`. Green across the board (Python, vault reachable, auth, bootstrapped, key source) means you're live.
**Verify:** run `/chorus-doctor`. Green across the board (Python, endpoint, key, **member id**, group, vault reachable, bootstrapped schema 5) means you're live. Your first `load` creates your `_agent/members/<you>/` namespace and adds you to the group roster — nothing to do on your side.
---
## Day-to-day usage
### What happens automatically (v1.5+)
### What happens automatically
- **Session start** — a hook runs the cold-start load and injects your memory into context: your profile/preferences, current scope, the last session log, today's journal note, and inbox depth. You don't have to ask Claude to "load memory" anymore.
- **Session end** — if the session was substantive and nothing was saved, a hook nudges (once) to run `/echo-reflect` and write the session log. Reflection always previews before writing — nothing lands without your OK.
- **If the vault is unreachable**, writes queue to a local outbox and replay automatically next time it's up; reads fall back to a cached last-known-good. An outage degrades gracefully instead of erroring.
- **Session start** — a hook runs the cold-start load and injects memory into context: the **group profile**, *your* preferences, *your* current scope, *your* last session, today's **shared** journal note, and both inbox depths (yours + the group's).
- **Session end** — if the session was substantive and nothing was saved, a hook nudges (once) to run `/chorus-reflect` and write the session log. Reflection always previews before writing — nothing lands without your OK.
- **If the vault is unreachable**, writes queue to a local outbox and replay automatically next time it's up; reads fall back to a cached last-known-good.
### The slash commands
| Command | When to use it |
|---|---|
| `/echo-load` | Manually load memory context. Rarely needed now — the session-start hook does this — but useful mid-session after a lot of vault writes, or if the hook didn't fire. |
| `/echo-save <thing>` | Save something right now: "remember the Henagar tower password is in the vault", "log that we decided X". Routes it to the right note automatically. |
| `/echo-recall <topic>` | Ask what memory knows about a topic. Returns ranked matches **plus their linked neighbourhood** — a project comes back with its people, decisions, and recent session mentions attached. |
| `/echo-reflect` | End-of-session sweep: extracts durable facts, decisions, and new entities from the conversation, shows you the proposed writes, applies on confirm. The main way memory grows. |
| `/echo-triage` | Empty the inbox: classifies each quick capture, previews destinations, routes accepted items with an audit trail. Run when load reports the inbox is getting deep. |
| `/echo-doctor` | Readiness check — Python, vault reachability, auth, bootstrap/schema version, where the key came from. First stop when anything seems off. |
| `/echo-health` | Full vault lint: routing violations, broken wikilinks, orphan notes, stale scope, incomplete frontmatter, index drift. Run occasionally or when things feel inconsistent. |
| `/echo-sweep` | Bring the vault up to the current plugin spec after an upgrade — rebuilds the entity index, backfills frontmatter, fixes one-way links. Dry-run by default; **run this once right after updating from an old version.** |
| `/chorus-load` | Manually load memory context (the session-start hook usually does this). `load --all-members` adds a group-wide view: every member's scope and last session. |
| `/chorus-save <thing>` | Save something right now: "remember the Henagar tower password is in the vault", "log that we decided X". Routes it to the right note automatically, attributed to you. |
| `/chorus-recall <topic>` | Ask what the GROUP's memory knows about a topic — including things teammates saved. Returns ranked matches plus their linked neighbourhood; add `--author <member>` to see one person's notes only. |
| `/chorus-reflect` | End-of-session sweep: extracts durable facts, decisions, and new entities from the conversation, previews, applies on confirm. The main way memory grows. |
| `/chorus-triage` | Empty the inboxes — **yours and the group's**: classifies each capture, previews destinations, routes accepted items with a who-routed-what audit trail. |
| `/chorus-doctor` | Readiness check — Python, vault reachability, auth, member id, bootstrap/schema, key source. First stop when anything seems off. |
| `/chorus-health` | Full vault lint: routing violations, broken wikilinks, orphans, per-member stale scopes and aging inboxes, unattributed agent notes, index drift. |
| `/chorus-sweep` | Bring the vault up to the current plugin spec after an upgrade — rebuilds indexes, backfills frontmatter, fixes one-way links. Dry-run by default. |
### A typical session
1. Start a conversation — memory loads itself. Claude knows who you are, what's active, and what happened last session.
2. Work normally. Say "save that" / "remember this" whenever something durable comes up (or `/echo-save` explicitly). Not sure where something belongs? Save it anyway — it lands in the inbox and `/echo-triage` sorts it later.
3. At the end, accept the reflection nudge (or run `/echo-reflect` yourself). Confirm the preview. Done — next session picks up from here.
1. Start a conversation — memory loads itself. Claude knows who *you* are, what the group is, what you were doing, and what happened in your last session.
2. Work normally. Say "save that" / "remember this" whenever something durable comes up. Not sure where it belongs? It lands in **your** inbox; something the *team* should see lands in the **group inbox**.
3. At the end, accept the reflection nudge (or run `/chorus-reflect`). Confirm the preview. Done — your next session picks up from here, and your teammates' sessions can already see what you saved.
### Group etiquette (the short list)
- **Shared knowledge is everyone's.** Projects, people, companies, decisions — read them, update them (your updates are member-tagged), link to them.
- **Namespaces are personal.** Never edit another member's `_agent/members/<id>/` files. A fact about a person goes in `resources/people/`; a teammate's *preference* is theirs to record.
- **The duplicate gate is a teammate detector.** If a save is blocked with "likely already exists — created by jason", that's the system telling you the group already tracks it. Merge into the existing note.
- **Everything is group-visible.** One vault, one key. Truly private material belongs in a personal vault, not CHORUS.
---
## What the memory system can do (current capabilities)
## What the memory system can do
**Smart, deduplicating capture.** One `capture` call routes content to its canonical home via an entity index, stamps complete frontmatter (status, tags, timestamps), cross-links every entity it mentions, and logs the write. Names resolve through aliases and fuzzy matching, so "echo memory" finds the `echo` project instead of spawning a duplicate note. If a new title strongly resembles an existing **same-kind** entity, a pre-write duplicate gate *stops* the write and shows you the candidates — you choose merge or create. Duplicates get blocked before they exist, not cleaned up after.
**Smart, attributed, deduplicating capture.** One `capture` call routes content to its canonical home via a shared entity index, stamps complete frontmatter (status, tags, timestamps, **author**), cross-links every entity it mentions, and writes a member-tagged line on the team's daily log. Names resolve through aliases and fuzzy matching; a pre-write duplicate gate stops near-duplicates *before* they exist and names whose entity you collided with.
**Real retrieval, not keyword grep.** Recall fuses BM25 full-text search with graph expansion along note links, over entities *and* session logs *and* journal entries. Ranking is freshness- and status-aware: recently updated and `active` notes float up, `archived` sinks. You get a topic's connected neighbourhood, not one isolated file.
**Real retrieval over the whole group's knowledge.** Recall fuses BM25 full-text search with graph expansion along note links, over entities *and* every member's session logs *and* the journal. Ranking is freshness- and status-aware. Filter to one member with `--author`.
**Structured memory model.** Working (transient) / episodic (what happened, when) / semantic (durable facts and preferences) memory layers, plus a current-context scope, per-session logs, an append-only journal with rollups, and PARA-style projects/areas/resources/decisions. A machine-readable routing manifest defines what may be written where, and the linter enforces it.
**Structured memory model.** Working / episodic / semantic layers plus the group profile, per-member scope + preferences + inbox + sessions, an append-only shared journal with rollups, and PARA-style projects/areas/resources/decisions. A machine-readable routing manifest defines what may be written where, and the linter enforces it.
**Reflection.** `/echo-reflect` turns a conversation into memory: extract → classify → dedup against the index → confidence-filter → preview → apply. Low-confidence items go to the inbox instead of polluting the graph.
**Durability and safety.** Every write is HTTP-status-checked and read-back-verified. Appends are idempotent. Offline writes queue and replay. Shared indexes are lock-guarded with retry, and lock owners are member-attributed. The per-member namespacing means teammates' sessions can't clobber your scope or heartbeat even in principle.
**Durability and safety.** Every write is HTTP-status-checked and read-back-verified (a failed write fails loudly, never silently). Appends are idempotent — retries can't double-write. Offline writes queue and replay. A cooperative advisory lock keeps Claude Code and CoWork from trampling each other on the shared vault.
**Self-maintaining.** The vault bootstraps itself from an empty Obsidian vault, migrates its own schema on version bumps, and `/echo-sweep` + `/echo-health` keep the graph honest (index rebuilds, link symmetry, frontmatter backfill, orphan/broken-link detection).
**Fast.** Connection pooling + concurrent reads mean full-vault operations that used to time out in the sandbox now finish in under a second.
---
## What's new since 0.7 (your last consistent version)
You left off right after the toolchain got its executable spine (`echo.py`, routing manifest, linter). Since then, in rough order of what you'll actually notice:
- **You don't route anything by hand anymore.** 0.7 was "pick the path, write with the right verb." Now `capture`/`recall`/`resolve`/`link` do routing, frontmatter, indexing, and cross-linking in one call — and in practice you just talk to Claude and it uses them.
- **Memory loads and saves itself** (v1.5 hooks). No more remembering to run the loading procedure or write the session log.
- **Recall is actually good now** (v0.91.5): entity index + hybrid BM25/graph search + freshness/status ranking, spanning sessions and journal too.
- **Duplicates get blocked at write time** (v1.5 gate) instead of accumulating.
- **Offline resilience** (v1.0): vault down ≠ data lost; writes queue and replay.
- **Everything is pure Python** (v0.8): no bash, works identically on Windows.
- **Credentials moved out of the plugin** (v1.31.4): generic builds prompt once per machine; your baked build carries them invisibly. Treat a baked `.plugin` file like a password — it contains your vault key. Don't share or commit it.
**After installing the new version, run `/echo-doctor`, then `/echo-sweep` once** to bring your vault up to the current schema (it dry-runs first and shows the plan).
**Self-maintaining, self-onboarding.** The vault bootstraps itself from empty, onboards new members on their first load, migrates its own schema on version bumps, and `/chorus-sweep` + `/chorus-health` keep the graph honest.
---
@@ -91,9 +70,9 @@ You left off right after the toolchain got its executable spine (`echo.py`, rout
| Symptom | Fix |
|---|---|
| `NOT CONFIGURED` banner / doctor shows red config | Generic build with no config on this machine — `echo.py config import <file>`, or ask Jason for your baked build. |
| Vault unreachable | Check that Obsidian + the Local REST API plugin are running on the backend and the endpoint is reachable. Meanwhile writes queue safely and replay on reconnect. |
| Save blocked with "duplicate gate" (exit 76) | Not an error — it found a likely-existing entity. Review the candidates: merge into the existing note, or force-create if it's genuinely distinct. |
| Memory feels stale or inconsistent | `/echo-health` to see what's off, `/echo-sweep` to repair index/links/frontmatter. |
| Inbox keeps getting mentioned at load | `/echo-triage` — one pass empties it. |
| Anything else weird | `/echo-doctor` first, then ask Jason. |
| `NOT CONFIGURED` banner / doctor shows red config | Your build isn't baked or the config is missing this machine — see [ONBOARDING.md](ONBOARDING.md), or ask the vault admin for your baked build. |
| Vault unreachable | Obsidian + the Local REST API + the proxy at `chorusapi.mpm.to` must be up. Meanwhile writes queue safely and replay on reconnect. |
| Save blocked with "duplicate gate … created by \<member\>" (exit 76) | Not an error — the group already tracks that entity. Merge into the existing note (`--merge-into <slug>`), or force-create if genuinely distinct. |
| Memory feels stale or inconsistent | `/chorus-health` to see what's off, `/chorus-sweep` to repair. |
| Inboxes keep getting mentioned at load | `/chorus-triage` — one pass empties both. |
| Anything else weird | `/chorus-doctor` first, then ask the vault admin. |
+95
View File
@@ -0,0 +1,95 @@
# echo-memory — Roadmap to v2.0.0
> Status: **draft.** Current shipping version: **1.5.1**. Interim work tracked in
> `TODO-1.6.md`.
>
> **2.0 thesis: a packaging & structure major.** Nothing here changes what the plugin
> *does* for memory — 2.0 is the release where the repo layout, artifact policy, and
> plugin format are cleaned up in ways that are breaking for how the plugin is
> *distributed and installed*, so the major version signals "reinstall everything,
> re-clone expectations." Feature work keeps landing in 1.6.x until this is ready.
>
> This roadmap absorbs every open item from the retired `MAINTENANCE.md` (M5)
> checklist; the mapping is noted per item.
---
## 1. Repo-root artifact policy (from MAINTENANCE Build artifacts)
The repo root tracks every historical build (`echo-memory-0.6.0.plugin`
`echo-memory-1.5.1.plugin`) plus the `echo-memory.plugin` pointer. 2.0 changes the
policy — **breaking for anyone who pulls artifacts straight from the repo**:
- [ ] Track only `echo-memory.plugin` (the current installable); delete the versioned
zips from the tree.
- [ ] Publish versioned builds as **Gitea releases** on `git.alwisp.com/jason/echo`
instead (one release per tag, artifact attached).
- [ ] `.gitignore`: `*.plugin` except the pointer.
- [ ] Tag releases going forward (`v2.0.0`, …) so the release page is the version
history for artifacts, and the README table stays the narrative history.
## 2. Complete the skills-format migration (finishes the 1.6 work)
1.6 migrates the eight slash commands to `skills/*/SKILL.md` with both formats
coexisting (see `TODO-1.6.md`). 2.0 finishes it:
- [ ] **Delete the legacy `commands/` directory** — the packaging break that partly
motivates the major bump.
- [ ] Re-verify desktop + CoWork installs of the skills-only artifact.
- [ ] Update SKILL.md / README / command docs that reference `commands/`.
## 3. Codex packaging (from MAINTENANCE Canonical source tree)
The drifted `codex plugin/` tree was **deleted on 2026-07-03** (operator decision:
Codex usage doesn't currently justify a maintained second build; it will be
regenerated after 2.0 if needed). What remains for 2.0:
- [ ] If Codex support returns: implement it as a **build target** generated from the
canonical `echo-memory.plugin.src/` tree (`build.py --target codex` or similar) —
never as a second hand-maintained source tree (that's how the drift happened).
- [ ] Otherwise: a short "packaging for other agent runtimes" note in the README
documenting what a Codex/other-runtime port needs (manifest shape, skill entry
point, script invocation).
## 4. Eval refresh — publish current-version metrics (from MAINTENANCE Docs freshness; ROADMAP-1.0 H4 leftover) — ✅ DONE
`run_eval.py` was rewritten from the stale 0.6-vs-0.7 A/B into the current-version
metrics harness (retrieval / dedup / write-safety / durability); numbers published in
the README. Re-run it per release and refresh the README table.
- [x] Refresh `run_eval.py` to measure the current version — **done 2026-07-03**:
retrieval P@5/R@5/MRR + session/journal answerability + freshness top-1 (vs a
keyword/entities-only baseline), duplicate rate gate-on/off, silent-write-failure
rate under fault injection, offline-queue durability. `results/latest.json`.
- [x] Publish the numbers in the README and mark the old A/B harness historical
(git history) — **done 2026-07-03**.
## 5. Repo hygiene odds & ends
- [x] Retire `ROADMAP-1.0.md` (fully shipped; in git history) — **done 2026-07-03**.
- [ ] Root README "Repository layout" section updated for the post-2.0 tree
(no versioned zips, no codex tree, skills-only plugin).
- [x] `echo-improvements-prompt.md` (the 1.5.0 field report) moved to `docs/history/`
**done 2026-07-03**.
---
## MAINTENANCE.md disposition (why it was removed)
Every item from the retired checklist is either **done** or **absorbed above**:
| MAINTENANCE item | Status |
|---|---|
| Canonical tree = `echo-memory.plugin.src/` | Done (0.x) |
| `codex plugin/` decide regenerate-or-delete | **Deleted 2026-07-03**; regenerate-as-build-target tracked in §3 |
| Prune historical `.plugin` zips; gitignore policy | §1 |
| `.gitignore` basics | Done (1.0) |
| plugin.json `license` + version | Done (1.0) |
| plugin.json `homepage` | `TODO-1.6.md` §2 (URL now decided) |
| Register commands in manifest | Superseded by the skills migration (`TODO-1.6.md` §1, finished in §2 here) |
| `run_eval.py` refresh to current metrics | §4 |
| Secret handling (M1) | Done — superseded by `echo_config` in 1.3/1.4; committed tree ships zero credentials |
| Pre-1.0 checklist | Done/obsolete (1.0 shipped; the `DEFAULT_KEY` line it referenced was removed in 1.3) |
Build flags (`--bake-key`, `--strip-key`, `--no-pointer`, `--outdir`) are documented
in `build.py`'s docstring and the README Configuration section.
+63
View File
@@ -0,0 +1,63 @@
# echo-memory — v1.6 TODO
> Working list for the next minor release. Started 2026-07-03, right after 1.5.1
> shipped. **Add findings from 1.5.x live testing to the "Surfaced in testing"
> section as they come up** — that's what this doc is for.
## 1. Migrate the eight slash commands to the skills format
The installer flags the plugin's `commands/` directory as the legacy format
("Both formats work — consider migrating to skills/*/SKILL.md"). Confirmed against
the official docs (code.claude.com/docs/en/skills.md, plugins.md):
- **Non-breaking.** `commands/echo-save.md``skills/echo-save/SKILL.md` keeps the
invocation name: `/echo-save` works identically. No deprecation date on `commands/`;
this is future-proofing, not a fire.
- **Do it for the features, not the notice:**
- [ ] `allowed-tools` per skill — pre-authorize the resolved `python3 "$ECHO" …`
invocations so `/echo-load`, `/echo-health`, `/echo-recall` stop prompting
(1.4.2 hand-broadened two commands; the skill format does this properly).
- [ ] `disable-model-invocation: true` on the write-heavy entry points
(`/echo-sweep`, `/echo-triage`) so only the operator triggers them; leave the
read-side ones model-invocable.
- [ ] De-duplicate the CoWork `$ECHO` path-resolution block currently copy-pasted
into all eight command bodies — each skill folder can carry a shared snippet.
- [ ] `argument-hint` carries over as-is; `$ARGUMENTS` substitution unchanged.
- [ ] Verify a migrated build installs cleanly on desktop **and** in a CoWork session
before deleting `commands/` (deleting the legacy dir is a 2.0 item —
see `ROADMAP-2.0.md`; in 1.6 both may coexist).
## 2. plugin.json completeness (from the old MAINTENANCE checklist)
- [ ] Add `homepage` / repository URL — now decided: `https://git.alwisp.com/jason/echo`.
## 3. Vault follow-ups (noticed during the 1.5.0 dead-link cleanup)
Three of the 27 unwrapped "dead" links were actually **typos pointing at real notes**;
they were unwrapped per instruction but repointing them would restore real graph edges:
- [ ] `projects/active/forgerunner.md` + `_agent/sessions/2026-06-29-2200-forgerunner-v010-ship.md`:
"Gitea CI Docker jobs run on the host label" → the decision
`decisions/by-date/2026-06-29-gitea-ci-docker-jobs-run-on-the-host-lab.md` ("lab", not "label").
- [ ] `projects/active/mpm-brand-voice.md`: "Message Point Media (MPM)" ×3 → the company
note `resources/companies/mpm.md` (or whatever is canonical for MPM).
## 4. Surfaced in 1.5.x testing
*(add items here as the new plugin gets real use)*
- [ ] **Lint blind spot: retired dirs masked by the leaf-README route** (found 2026-07-03).
`archive/` and `_agent/outputs/` are retired/unrouted pre-0.6 leftovers that survived
in the live vault holding only their seed READMEs — and the permissive `leaf-readme`
route (`^(.+/)?README\.md$`) matches first, so `vault_lint` never flagged them.
Fix: in the path-membership check, test the *directory* against retired/unknown
patterns even when the only file in it is a README (e.g. evaluate `dirname(path) + "/"`
against retired patterns before letting `leaf-readme` absolve the file), or drop the
seed-README exemption inside retired trees. Add a lint test seeding
`archive/notes/README.md` → must flag `retired-path`.
*Related learning (same cleanup, 2026-07-03):* the REST API **cannot delete
directories** — removing a folder's last file orphans the empty folder on disk
(the REST listing 404s, falsely reading as gone; Obsidian still shows it — manual
deletion required). Documented in `references/api-reference.md` Deleting Files
and in the vault's `resources/references/obsidian-local-rest-api.md`. Any future
"remove retired tree" guidance must include the manual folder-cleanup step.