diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ef0ae1..0ffeb65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## 1.6.0 + +### Changed — the eight slash commands migrated to the skills format + +Each `commands/.md` now has a `skills//SKILL.md` twin with the same +invocation name and a byte-identical body — confirmed against the current docs +that a same-name skill **takes precedence** over the legacy command, so both +formats coexist safely in this release (deleting `commands/` is the 2.0 +packaging break). Done for the features, not the deprecation notice: + +- **`allowed-tools` per skill** — the resolved `python3/python/py -3` script + invocations (plus the CoWork `ls /sessions/*` fallback probe) are + pre-authorized, so `/echo-load`, `/echo-health`, `/echo-recall` etc. stop + prompting. +- **`disable-model-invocation: true`** on the write-heavy entry points + (`/echo-sweep`, `/echo-triage`) — only the operator triggers them; the + read-side skills stay model-invocable. +- `argument-hint` / `$ARGUMENTS` carry over unchanged (same semantics in + SKILL.md bodies). +- The `$ECHO` path-resolution block stays per-skill: the skills format has no + cross-skill snippet sharing (each skill directory is self-contained per the + official docs), and the block is already the 2-line minimum. + +### Fixed — lint blind spot: retired trees masked by the leaf-README route + +`vault_lint.py` checked retired patterns only for paths matching **no** route, +so the permissive `leaf-readme` route (`^(.+/)?README\.md$`) absolved any seed +README inside a retired tree — `archive/` and `_agent/outputs/` survived the +1.5.0 live cleanup unflagged. Retired patterns are now checked **first**: a +path in a retired tree flags `retired-path` regardless of what else matches. +New end-to-end test seeds `archive/notes/README.md` and asserts the flag +(fails against the pre-fix linter, passes now). + +### Added — plugin.json completeness + +`homepage` + `repository` → `https://git.alwisp.com/jason/echo`; manifest → +1.6.0. + ## 1.5.1 ### Fixed — duplicate gate over-firing on vault-common tokens and cross-kind names diff --git a/README.md b/README.md index d1438fa..0b3c5ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# echo-memory — v1.5.1 +# echo-memory — v1.6.0 Persistent memory for Claude / CoWork sessions via the **ECHO** Obsidian vault, driven over the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api). The skill makes direct REST calls through a bundled validated client (`scripts/echo.py`). The whole toolchain is **pure Python** (stdlib only), so it runs identically on Windows, macOS, and Linux — no bash, no platform-specific `date`. @@ -80,8 +80,11 @@ echo-v.05/ ├── .claude-plugin/plugin.json ← manifest (name, version, description) ├── README.md ← plugin-level README ├── hooks/hooks.json ← session hooks: SessionStart auto-load · Stop reflection nudge - ├── commands/ ← slash commands (legacy format; skills-format migration = TODO-1.6): - │ echo-load|save|recall|triage|health|sweep|reflect|doctor + ├── commands/ ← LEGACY slash commands (kept through 1.6 for compatibility; + │ shadowed by skills/, deleted in 2.0) + ├── skills/echo-{load,save,recall,triage,health,sweep,reflect,doctor}/ + │ ← the eight commands in the skills format (1.6.0): per-skill + │ allowed-tools; sweep/triage are operator-only └── skills/echo-memory/ ├── SKILL.md ← operating procedure (authoritative) ├── references/ @@ -417,6 +420,7 @@ From the credential-free harness (`eval/run_eval.py` against the deterministic m | Version | Highlights | |---------|-----------| +| **1.6.0** | **Skills-format migration + lint blind-spot fix.** The eight slash commands gain `skills//SKILL.md` twins (same names, byte-identical bodies; a same-name skill takes precedence, so `commands/` coexists until its 2.0 deletion): per-skill `allowed-tools` end the permission prompts, `disable-model-invocation: true` makes `/echo-sweep` + `/echo-triage` operator-only. `vault_lint` checks retired patterns **before** routes, so a seed README inside a retired tree (`archive/…`) is flagged instead of being absolved by the leaf-README route (+ regression test). `plugin.json` gains `homepage`/`repository`. | | **1.5.1** | **Duplicate-gate precision.** Live use caught the gate blocking a decision note because it shared the single vault-common token "echo" with an archived project (min-normalized scoring gives any single-token entity a 1.0 match). New `gate_candidates()` blocks only on **same-kind** candidates (cross-kind name collisions — a decision titled after its project — warn instead), and a **lone shared token blocks only when unique to that entity** (vault df == 1); multi-token overlaps still block. Advisory warnings (`fuzzy_candidates`) unchanged; exit-76/`--merge-into`/`--force` unchanged. +5 tests; verified against the live vault both ways (false positive creates cleanly, true lookalike still gates). | | **1.5.0** | **Six-improvement pass: retention, routing, and self-firing memory.** (1) **Capture-update keeps the whole body** — the update path previously appended only the first line of a multi-line body (silent data loss); now the full body rides under the dated bullet. (2) **Frontmatter completeness** — capture stamps a kind-default `status` and kind-seeded `tags` (`--tags` enriches); `fm` is create-or-replace (missing keys are surgically inserted instead of 400-failing); `vault_lint` gains a kind-scoped `incomplete-frontmatter` check; `sweep --apply` backfills (fixes the 18/71-notes field-audit drift, one data source: `KIND_STATUS`/`KIND_REQUIRED_FM`). (3) **Pre-write duplicate gate** — a strong fuzzy candidate stops `capture` (exit 76 + candidates) before the duplicate exists; `--merge-into ` updates the canonical entity, `--force` overrides. (4) **Recall corpus + ranking** — sessions and journal notes join the BM25 corpus (down-weighted); scores fuse freshness (half-life on `updated:`) and status (`active` boosted, `archived` demoted); hits show `updated:`/`status:`; `recall --json`; recall-index schema 2 (auto-rebuilds). (5) **Session hooks** — SessionStart auto-loads memory into context, Stop nudges reflection once per substantive session; fail-safe, CoWork-fallback-aware. (6) **One-tap triage** — `echo.py triage` lists the inbox structured, then classifies → previews → routes accepted items via `capture` with automatic processing-log audit lines; `--json` also lands on `link`/`scope show`. +22 mock end-to-end tests; all suites green. | | **1.4.2** | **CoWork sandbox path resilience.** In a remote CoWork sandbox `${CLAUDE_PLUGIN_ROOT}` can point at a host path the sandbox can't reach (`/var/folders/…`) while the plugin is mounted under `…/mnt/.remote-plugins/…`, so script invocations failed until the agent found the real path by hand. SKILL.md and all eight slash commands now resolve the scripts dir with a fallback — prefer `${CLAUDE_PLUGIN_ROOT}`, else locate the mounted copy under `/sessions/*/mnt/.remote-plugins/*/…` — reused via `$ECHO`/`$LINT`/`$SWEEP`/`$SDIR`. On a normal host the primary path always wins (no behaviour change). `echo-health`/`echo-sweep` `allowed-tools` broadened to match the resolved invocation. (The scripts never hardcoded a path — root cause is the harness env var — but the plugin now self-heals.) | diff --git a/TODO-1.6.md b/TODO-1.6.md index a745d0e..30771e8 100644 --- a/TODO-1.6.md +++ b/TODO-1.6.md @@ -14,22 +14,27 @@ the official docs (code.claude.com/docs/en/skills.md, plugins.md): 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. + - [x] `allowed-tools` per skill — **done 1.6.0** (all eight skills pre-authorize + the resolved `python3`/`python`/`py -3` invocations + the CoWork `ls` probe). + - [x] `disable-model-invocation: true` on the write-heavy entry points + (`/echo-sweep`, `/echo-triage`) — **done 1.6.0**; read-side stays + model-invocable. + - [x] De-duplicate the CoWork `$ECHO` path-resolution block — **resolved 1.6.0 as + not-supported**: per the official skills docs, skill directories are + self-contained (no cross-skill snippet sharing), so the 2-line block stays + per-skill. Docs confirmed same-name skill takes precedence over the legacy + command, so coexistence is safe. + - [x] `argument-hint` carries over as-is; `$ARGUMENTS` substitution unchanged — + **done 1.6.0** (save/recall/reflect). - [ ] 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). + see `ROADMAP-2.0.md`; in 1.6 both may coexist). *(1.6.0 artifact built — + operator install test pending.)* ## 2. plugin.json completeness (from the old MAINTENANCE checklist) -- [ ] Add `homepage` / repository URL — now decided: `https://git.alwisp.com/jason/echo`. +- [x] Add `homepage` / repository URL — **done 1.6.0**: both `homepage` and + `repository` set to `https://git.alwisp.com/jason/echo`. ## 3. Vault follow-ups (noticed during the 1.5.0 dead-link cleanup) @@ -46,7 +51,9 @@ they were unwrapped per instruction but repointing them would restore real graph *(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). +- [x] **Lint blind spot: retired dirs masked by the leaf-README route** (found + 2026-07-03; **fixed 1.6.0** — retired patterns now checked before routes, with + the `archive/notes/README.md` regression test). `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. diff --git a/echo-memory-1.6.0.plugin b/echo-memory-1.6.0.plugin new file mode 100644 index 0000000..5f9c60e Binary files /dev/null and b/echo-memory-1.6.0.plugin differ diff --git a/echo-memory.plugin b/echo-memory.plugin index c12f48d..5f9c60e 100644 Binary files a/echo-memory.plugin and b/echo-memory.plugin differ diff --git a/echo-memory.plugin.src/.claude-plugin/plugin.json b/echo-memory.plugin.src/.claude-plugin/plugin.json index 4899639..8d91ea7 100644 --- a/echo-memory.plugin.src/.claude-plugin/plugin.json +++ b/echo-memory.plugin.src/.claude-plugin/plugin.json @@ -1,6 +1,8 @@ { "name": "echo-memory", - "version": "1.5.1", + "version": "1.6.0", + "homepage": "https://git.alwisp.com/jason/echo", + "repository": "https://git.alwisp.com/jason/echo", "description": "Persistent memory via the ECHO Obsidian vault over the Obsidian Local REST API. Cross-platform Python client: one-call capture/resolve/recall/link/triage over an entity index, hybrid BM25 + graph recall spanning entities + sessions/journal (recency/status-aware), a pre-write duplicate gate, complete-frontmatter capture, session hooks that self-fire load/reflect, offline write-ahead queue, lock-guarded concurrency, linter-enforced routing, and /echo-* commands.", "author": { "name": "Jason Stedwell" diff --git a/echo-memory.plugin.src/skills/echo-doctor/SKILL.md b/echo-memory.plugin.src/skills/echo-doctor/SKILL.md new file mode 100644 index 0000000..91c4001 --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-doctor/SKILL.md @@ -0,0 +1,22 @@ +--- +name: echo-doctor +description: Check ECHO readiness — Python, vault reachability, auth, bootstrap/schema, and key source +allowed-tools: Bash(python3 *echo.py*), Bash(python *echo.py*), Bash(py -3 *echo.py*), Bash(ls /sessions/*) +--- + +Use the **echo-memory** skill to run a one-call readiness check before relying on memory. + +```bash +ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" +[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback +python3 "$ECHO" doctor +# Windows: use `python` or `py -3` if `python3` is not on PATH. +``` + +It prints green/red for: Python version, vault reachability, auth accepted, vault +bootstrapped (+ `schema_version`), and the **config source** for owner/endpoint/key +(per-field: env override `ECHO_OWNER`/`ECHO_BASE`/`ECHO_KEY`, then the machine-local +`~/.claude/echo-memory/config.json` — or `baked-in (plugin)` on a per-user baked build, +which is authoritative and reported as such). Exits non-zero if anything is red — if the config is +missing (or still the placeholder template), ask the operator for their echo-memory config file and install it with `echo.py config import ` (or `config set --owner … --endpoint … --key …`). For the full vault-invariant lint, run +`/echo-health`. diff --git a/echo-memory.plugin.src/skills/echo-health/SKILL.md b/echo-memory.plugin.src/skills/echo-health/SKILL.md new file mode 100644 index 0000000..5e4d2e1 --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-health/SKILL.md @@ -0,0 +1,18 @@ +--- +name: echo-health +description: Run the ECHO vault-health linter and summarize any invariant violations +allowed-tools: Bash(python3 *vault_lint.py*), Bash(python *vault_lint.py*), Bash(py -3 *vault_lint.py*), Bash(ls /sessions/*), Bash(dirname *) +--- + +Run the bundled, read-only vault linter and report findings. Pass the conversation's current date (`ECHO_TODAY`) so stale/aging math uses the same clock the agent writes with: + +```bash +SDIR="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts" +[ -d "$SDIR" ] || SDIR=$(dirname "$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/vault_lint.py 2>/dev/null | head -1)") # CoWork sandbox fallback +ECHO_TODAY= python3 "$SDIR/vault_lint.py" +# Windows: use `python` or `py -3` if `python3` is not on PATH. +``` + +Exit codes: `0` clean · `1` violations (printed, grouped by check) · `2` vault unreachable · `3` vault not bootstrapped (run `bootstrap.py`). + +Summarize the violations grouped by category and propose fixes, but **do not auto-fix** without the operator's go-ahead. If this is the first substantive session of a calendar month, offer to write the findings to `_agent/health/YYYY-MM-vault-health.md` (per the skill's **Vault Health** section). diff --git a/echo-memory.plugin.src/skills/echo-load/SKILL.md b/echo-memory.plugin.src/skills/echo-load/SKILL.md new file mode 100644 index 0000000..27c8c7e --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-load/SKILL.md @@ -0,0 +1,20 @@ +--- +name: echo-load +description: Load ECHO memory — cold-start context read (profile, scope, latest session, today, inbox) +allowed-tools: Bash(python3 *echo.py*), Bash(python *echo.py*), Bash(py -3 *echo.py*), Bash(ls /sessions/*) +--- + +Use the **echo-memory** skill to load memory now. Run the cold-start **Loading procedure** from `SKILL.md`: the six orientation reads (marker, operator-preferences, current-context, heartbeat, today's daily note, inbox) in **one call**, then do the load-time **reconcile** (inbox-depth + scope-drift) and surface it in a single line. + +```bash +ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" +[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback +python3 "$ECHO" load +# Windows: use `python` or `py -3` if `python3` is not on PATH. +``` + +`load` prints all six sections (404s on today's note / inbox are shown as absent, not errors) and flags an un-bootstrapped vault. Follow up with `echo.py scope show` if you need the sessions-since-switch count, and a project search if a specific project is in play. + +**If `load` prints `NOT CONFIGURED` (exit 78)**, this machine has no usable key file yet. Don't proceed with memory — follow **First run** in `SKILL.md`: tell the operator ECHO isn't configured, ask for their echo-memory config file (owner/endpoint/key), install it with `echo.py config import ` (or `config set`), then re-run `load`. + +Do not narrate the reads. End with a one-line orientation: who/what/where the active scope is, and any reconcile prompt. diff --git a/echo-memory.plugin.src/skills/echo-memory/scripts/vault_lint.py b/echo-memory.plugin.src/skills/echo-memory/scripts/vault_lint.py index 67c67c0..8083f5e 100644 --- a/echo-memory.plugin.src/skills/echo-memory/scripts/vault_lint.py +++ b/echo-memory.plugin.src/skills/echo-memory/scripts/vault_lint.py @@ -165,14 +165,16 @@ def main() -> int: # this shared cache instead of issuing a fresh GET per file per section. texts = echo.read_many(md_files) - # Path membership + retired-path detection + # Path membership + retired-path detection. Retired patterns are checked FIRST: + # a file inside a retired tree is flagged even when a permissive route (the + # leaf-README rule) would otherwise match it — previously `archive/x/README.md` + # was silently absolved by `leaf-readme` and retired dirs survived unflagged. for path in all_files: - if routes and not any(rx.match(path) for _, rx in routes): - replacement = next((repl for rx, repl in retired if rx.match(path)), None) - if replacement is not None: - flag("retired-path", f"{path}: retired location — should be {replacement}") - else: - flag("unknown-path", f"{path}: matches no route in routing.json") + replacement = next((repl for rx, repl in retired if rx.match(path)), None) + if replacement is not None: + flag("retired-path", f"{path}: retired location — should be {replacement}") + elif routes and not any(rx.match(path) for _, rx in routes): + flag("unknown-path", f"{path}: matches no route in routing.json") # Per-note frontmatter checks template_re = re.compile(r"(^|/)(templates/|.*-template\.md$)") diff --git a/echo-memory.plugin.src/skills/echo-recall/SKILL.md b/echo-memory.plugin.src/skills/echo-recall/SKILL.md new file mode 100644 index 0000000..f32a597 --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-recall/SKILL.md @@ -0,0 +1,23 @@ +--- +name: echo-recall +description: Recall a topic from ECHO memory — matching notes plus their linked neighbourhood +argument-hint: "[topic, person, or project]" +allowed-tools: Bash(python3 *echo.py*), Bash(python *echo.py*), Bash(py -3 *echo.py*), Bash(ls /sessions/*) +--- + +Use the **echo-memory** skill to recall context for: + +> $ARGUMENTS + +Run the one-call recall — it resolves the term against the entity index, searches, and expands one hop along `## Related` links and `source_notes`, so you get the connected web (linked decisions, people, prior sessions), not an isolated note: + +```bash +ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" +[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback +python3 "$ECHO" recall "$ARGUMENTS" +# Windows: use `python` or `py -3` if `python3` is not on PATH. +``` + +The corpus spans the entity graph **plus session logs and journal notes**, ranked by relevance × freshness × status — each hit shows its `updated:`/`status:`, so prefer fresh/active hits and treat stale ones as history. Add `--json` for structured hits (`path/score/type/updated/status/excerpt`) when you need to act on the results programmatically. + +Then answer from the assembled context. If you only need the canonical path for one entity, use `echo.py resolve ""` instead. Don't narrate the retrieval. diff --git a/echo-memory.plugin.src/skills/echo-reflect/SKILL.md b/echo-memory.plugin.src/skills/echo-reflect/SKILL.md new file mode 100644 index 0000000..628d649 --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-reflect/SKILL.md @@ -0,0 +1,33 @@ +--- +name: echo-reflect +description: Reflect on this session — extract durable memories and propose them for one-tap capture +argument-hint: "[optional focus, e.g. 'just decisions']" +allowed-tools: Bash(python3 *echo.py*), Bash(python *echo.py*), Bash(py -3 *echo.py*), Bash(ls /sessions/*) +--- + +Use the **echo-memory** skill to run **session reflection** (H5). Scan this conversation for +things worth remembering across sessions, then propose them — don't write blindly. + +1. **Extract** durable items from the conversation: new facts, preferences, decisions, + commitments, people/companies/projects introduced, and anything the operator said to remember. + Skip the ephemeral. Anchor relative dates on the conversation's `currentDate`. +2. **Emit a JSON array** of proposals (one per item), each: + `{"title","kind","body","aliases","tags","sources","confidence"}` — `kind` ∈ + `person, company, concept, reference, meeting, project, area, semantic, episodic, + working, skill, decision`; set `"inbox": true` when the home is genuinely unknown; + `confidence` 0–1 (items below 0.6 are dropped — send those to the inbox instead). + Write the array to a file with the Write tool (cross-platform; no heredoc). +3. **Preview, then apply.** Dry-run first (writes nothing) and show the operator the plan; only + apply after they confirm. + +```bash +ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" # python3 (Windows: python / py -3) +[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback +python3 "$ECHO" reflect proposals.json # dry-run: validate + classify + preview +python3 "$ECHO" reflect proposals.json --apply # write — routes each via capture (indexed, linked, logged) +``` + +`reflect` dedups every proposal against the entity index (so it shows create-vs-update), +skips below-confidence items, and routes `inbox` proposals to the capture inbox. Each +applied item goes through the normal `capture` path — canonical frontmatter, auto-linking, +and the recall index — and the writes are lock-guarded. $ARGUMENTS diff --git a/echo-memory.plugin.src/skills/echo-save/SKILL.md b/echo-memory.plugin.src/skills/echo-save/SKILL.md new file mode 100644 index 0000000..f3fc179 --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-save/SKILL.md @@ -0,0 +1,30 @@ +--- +name: echo-save +description: Save to ECHO memory — route content to its canonical home (search-first, idempotent) +argument-hint: "[what to remember]" +allowed-tools: Bash(python3 *echo.py*), Bash(python *echo.py*), Bash(py -3 *echo.py*), Bash(ls /sessions/*) +--- + +Use the **echo-memory** skill to persist this to the ECHO vault: + +> $ARGUMENTS + +Prefer the one-call **`capture`** — it routes (via the entity index), derives the canonical path, stamps complete frontmatter (kind-default `status`, the kind seeded into `tags`), indexes, auto-links mentioned entities, and writes the Agent-Log line. Pick the `--kind` from the content (`person, company, concept, reference, meeting, project, area, semantic, episodic, working, skill, decision`); add `--tags` when obvious; use `--inbox` only when the home is genuinely unknown. Write multi-line bodies to a file with the Write tool (cross-platform, no heredoc) — updates to an existing entity preserve the whole body, so don't pre-trim it. + +```bash +ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" # run with: python3 "$ECHO" ... (Windows: python / py -3) +[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback +python3 "$ECHO" capture "<title>" <bodyfile> --kind <kind> [--aliases a,b] [--tags t1,t2] [--source p1,p2] +python3 "$ECHO" capture "<title>" --inbox # unknown home -> idempotent inbox line +``` + +**If capture exits `76` (duplicate gate):** the title strongly resembles an existing entity — do NOT retry blindly. Show the operator the printed candidates and either update the existing note (`capture ... --merge-into <slug>`) or, only after they confirm it's genuinely distinct, re-run with `--force`. + +Drop to the low-level verbs only for shapes `capture` doesn't model (a project `## Status` replace, an ADR mirror, a daily-note edit): + +```bash +python3 "$ECHO" put <routed/path>.md <bodyfile> # create/overwrite (verifies) +python3 "$ECHO" patch <path>.md append heading "<H1::Sub>" <bodyfile> # targeted append +``` + +Write in third person about the vault owner; never put `[[wikilinks]]` in frontmatter. `capture` handles `agent_written`, `source_notes`, indexing, and linking for you; if you write by hand, `resolve "<title>"` first to avoid duplicates and `bump` `updated:` only on substantive changes. diff --git a/echo-memory.plugin.src/skills/echo-sweep/SKILL.md b/echo-memory.plugin.src/skills/echo-sweep/SKILL.md new file mode 100644 index 0000000..b6a80db --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-sweep/SKILL.md @@ -0,0 +1,18 @@ +--- +name: echo-sweep +description: Bring the ECHO vault up to the current feature spec (entity index + cross-links) +allowed-tools: Bash(python3 *sweep.py*), Bash(python *sweep.py*), Bash(py -3 *sweep.py*), Bash(ls /sessions/*), Bash(dirname *) +disable-model-invocation: true +--- + +Use the **echo-memory** skill to bring the vault up to the current spec. Run the sweep **dry-run first**, show the operator the plan, and only `--apply` on their go-ahead: + +```bash +SDIR="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts" +[ -d "$SDIR" ] || SDIR=$(dirname "$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/sweep.py 2>/dev/null | head -1)") # CoWork sandbox fallback +python3 "$SDIR/sweep.py" # plan (read-only) +python3 "$SDIR/sweep.py" --apply # write +# Windows: use `python` or `py -3` if `python3` is not on PATH. +``` + +The sweep (1) creates `_agent/index/`, (2) rebuilds the entity index from existing notes, (3) symmetrizes `## Related` cross-links (adds only the missing reciprocal direction — never invents new links), and (4) stamps the marker `schema_version`. It is idempotent and read-only without `--apply`. If `schema_version` is behind, run `migrate.py` first. After applying, run `/echo-health` to confirm invariants. diff --git a/echo-memory.plugin.src/skills/echo-triage/SKILL.md b/echo-memory.plugin.src/skills/echo-triage/SKILL.md new file mode 100644 index 0000000..15ef9c9 --- /dev/null +++ b/echo-memory.plugin.src/skills/echo-triage/SKILL.md @@ -0,0 +1,26 @@ +--- +name: echo-triage +description: Triage the ECHO inbox — route aging captures to their canonical homes and log the moves +allowed-tools: Bash(python3 *echo.py*), Bash(python *echo.py*), Bash(py -3 *echo.py*), Bash(ls /sessions/*) +disable-model-invocation: true +--- + +Use the **echo-memory** skill to run **one-tap Inbox Triage** via the `triage` verb (it reuses the reflect pipeline: classify against the entity index → preview → apply via `capture`, and writes the audit log for you). + +```bash +ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" +[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback +python3 "$ECHO" triage --list --json +# Windows: use `python` or `py -3` if `python3` is not on PATH. +``` + +1. **List** — the JSON gives each capture as `{line, date, text, age_days}`. Surface the aging ones (≥ ~7 days) to the operator and ask which to route. +2. **Propose** — for accepted items, write a proposals JSON with the Write tool (reflect schema: `{"title","kind","body",...}` **plus `"line"`: the original inbox line**, echoed into the audit log). Route per the map: preference/pattern → `semantic` (or a direct PATCH to `operator-preferences.md::Observations`); project idea → `project` with `--status incubating`; durable fact → `semantic`; person fact → `person`. +3. **Preview, then apply** with the operator's go-ahead: + +```bash +python3 "$ECHO" triage proposals.json # dry-run: classify + preview, writes nothing +python3 "$ECHO" triage proposals.json --apply # route via capture + log each move +``` + +`--apply` records every move in `inbox/processing-log/YYYY-MM-DD.md` (`- <original> → <destination>`) automatically. Do **not** delete the original captures unless the operator explicitly asks — the processing log is the audit trail. A proposal that stops at the duplicate gate (exit note in the summary) should be re-proposed with the existing entity's title.