5aace9e430
Memory behavior unchanged; the major signals a distribution/install break. - Legacy commands/ directory DELETED (skills-only; the 1.6.0 skills were verified on desktop + CoWork and already took precedence). Pre-skills clients install 1.6.0 from the release history. - Artifact policy: the repo tracks only the echo-memory.plugin pointer; the 15 historical versioned zips leave the tree (git history retains them); versioned builds ship as Gitea release assets, one release per v<version> tag, starting v2.0.0. .gitignore: *.plugin except the pointer. - README: post-2.0 repository layout; "packaging for other agent runtimes" note (ports are build targets from the canonical tree, never a second source tree). - ROADMAP-2.0 items ticked; manifest -> 2.0.0. All suites green (25/25 unit + 4 e2e suites); artifact rebuilt skills-only (79 entries, no commands/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
240 lines
12 KiB
Markdown
240 lines
12 KiB
Markdown
# Changelog
|
||
|
||
## 2.0.0
|
||
|
||
**Packaging & structure major — nothing about memory behavior changes.** The major
|
||
version signals "reinstall, re-clone expectations": how the plugin is distributed
|
||
and installed is what breaks.
|
||
|
||
### Removed — the legacy `commands/` directory (BREAKING for pre-skills clients)
|
||
|
||
The plugin is **skills-only**. The eight `/echo-*` entry points live exclusively at
|
||
`skills/<name>/SKILL.md` (introduced 1.6.0, verified on desktop + CoWork
|
||
2026-07-28, where they already took precedence). A Claude Code old enough to lack
|
||
skills support loses the slash commands — install 1.6.0 from the release history
|
||
instead.
|
||
|
||
### Changed — artifact policy (BREAKING for anyone pulling zips from the repo)
|
||
|
||
- The repo tracks **only** `echo-memory.plugin` (the current installable). The 15
|
||
historical `echo-memory-<version>.plugin` zips are deleted from the tree (git
|
||
history retains them).
|
||
- Versioned builds are published as **Gitea releases** on
|
||
`git.alwisp.com/jason/echo` — one release per `v<version>` tag, artifact
|
||
attached, starting with `v2.0.0`.
|
||
- `.gitignore` now ignores `*.plugin` except the pointer.
|
||
|
||
### Added — "packaging for other agent runtimes" README note
|
||
|
||
The retired Codex tree stays retired; the README now documents what a port needs
|
||
(manifest shape, skill entry point, script invocation + hooks) and the rule that a
|
||
port must be a **build target** from the canonical source tree, never a second
|
||
hand-maintained copy.
|
||
|
||
## 1.6.0
|
||
|
||
### Changed — the eight slash commands migrated to the skills format
|
||
|
||
Each `commands/<name>.md` now has a `skills/<name>/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
|
||
|
||
Live 1.5.0 use surfaced a false positive: creating the decision "echo-memory 1.5.0
|
||
improvement set" was blocked by the archived project `echo-v-05` (score 1.0), because
|
||
`score = overlap / min(|tokens|)` lets any entity whose single distinctive token
|
||
appears in the new title score 1.0 — and "echo" appears across many entities in an
|
||
echo-centric vault.
|
||
|
||
New `echo_index.gate_candidates()` (backed by `token_df()`) applies two precision
|
||
rules before blocking; `capture` gates through it. `fuzzy_candidates()` — the advisory
|
||
warning list — is unchanged:
|
||
|
||
- **Same-kind only.** A cross-kind name collision (a decision titled after its
|
||
project, a meeting named for a company) is intentional naming, not a duplicate.
|
||
Cross-kind lookalikes still warn, never block.
|
||
- **No single-common-token blocks.** A lone shared token gates only when it is unique
|
||
to that entity (vault df == 1). Multi-token overlaps still gate even when the
|
||
individual tokens are common.
|
||
|
||
Exit-76 / `--merge-into` / `--force` semantics unchanged. Tests: 3 new offline unit
|
||
tests (`gate_candidates`), end-to-end gate cases restructured + 2 new (cross-kind
|
||
no-gate, common-token no-gate); verified against the live vault (the original false
|
||
positive now creates cleanly; a same-kind multi-token lookalike still gates).
|
||
|
||
## 1.5.0
|
||
|
||
Six improvements from the July 2026 review + the frontmatter-drift field report.
|
||
|
||
### Fixed — capture-on-update kept only the first body line (silent data loss)
|
||
|
||
`capture` on an existing entity appended `- <date>: <first line>` and **discarded the
|
||
rest of the body**. It now appends the whole body as a dated block — first line on the
|
||
bullet, continuation lines indented under it. Idempotency (bullet-per-day) unchanged.
|
||
|
||
### Added — frontmatter completeness (prevent → tool → detect → remediate)
|
||
|
||
A field audit found 18/71 entity notes missing `status`/`tags`; the drift was created
|
||
at write time and invisible to the tooling. Four coordinated changes, driven by one
|
||
data source (`KIND_STATUS` / `KIND_REQUIRED_FM` in `echo_index.py`):
|
||
|
||
- **`capture`** stamps a kind-appropriate default `status` (all kinds, not just
|
||
projects) and seeds `tags` with the note's kind; new `--tags a,b` enriches it.
|
||
Reflect/triage proposals accept a `"tags"` field.
|
||
- **`echo.py fm`** is now **create-or-replace**: on `400 invalid-target` (missing key)
|
||
it surgically inserts the one line into the frontmatter block and re-PUTs, verified —
|
||
it previously failed on exactly the notes that needed repair.
|
||
- **`vault_lint.py`** gains a kind-scoped `incomplete-frontmatter` check
|
||
(missing/empty `status`/`tags` on entity notes; block-style lists handled).
|
||
- **`sweep.py --apply`** backfills flagged notes (kind-default status + baseline tag).
|
||
|
||
### Added — pre-write duplicate gate in capture
|
||
|
||
A strong fuzzy candidate (score ≥ 0.6, env `ECHO_DUP_GATE`) now **stops** `capture`
|
||
before it creates a near-duplicate (exit `76` + candidate list) instead of warning
|
||
after the file exists. `--merge-into <slug>` routes the capture as an update to the
|
||
existing entity (mention learned as an alias); `--force` creates anyway. Weak
|
||
resemblances still create + warn as before.
|
||
|
||
### Changed — recall corpus + ranking (recall-index schema 2)
|
||
|
||
- The BM25 corpus now includes **session logs and journal notes** (down-weighted 0.5 /
|
||
0.4 so entity notes win ties) — past discussions are findable without having been
|
||
promoted into an entity note. `echo.py put` keeps the index current for corpus paths.
|
||
- Ranking fuses BM25 with **freshness** (half-life decay on `updated:`, floored at 0.6,
|
||
env `ECHO_FRESH_HALF_LIFE`) and **status** (`active` ×1.1, `on-hold` ×0.9,
|
||
`archived` ×0.75) — a stale archived note no longer outranks the live one.
|
||
- Hits print `updated:`/`status:`; `recall --json` emits structured results.
|
||
- The recall index carries per-doc meta (schema 2); an old index is discarded and
|
||
rebuilt automatically on the next recall/sweep.
|
||
|
||
### Added — session hooks (self-firing load & reflect)
|
||
|
||
New `hooks/hooks.json` + two hook scripts. **SessionStart** runs `echo.py load` and
|
||
injects the output as context (skips resume/compact; NOT-CONFIGURED degrades to an
|
||
instruction, never an error). **Stop** nudges once per substantive session (≥5 user
|
||
turns, env `ECHO_REFLECT_MIN_TURNS`) when nothing was reflected or session-logged —
|
||
reflection itself still previews and asks before writing. Both use the 1.4.2 CoWork
|
||
path fallback and always exit 0.
|
||
|
||
### Added — one-tap inbox triage + `--json` on read verbs
|
||
|
||
New `echo.py triage`: `--list --json` parses the inbox into `{line, date, text,
|
||
age_days}`; a proposals file (reflect schema + `"line"`) is classified → previewed →
|
||
applied via `capture`, with the `inbox/processing-log/` audit line written per move
|
||
(originals never deleted). `/echo-triage` rewritten around it. Also: `--json` for
|
||
`recall`, `link`, and `scope show` (`resolve`/`capture` already had it).
|
||
|
||
Tests: +22 feature cases (mock end-to-end incl. hooks), patch-semantics updated for
|
||
the new `fm` contract; all suites green. Docs: SKILL.md, README, command docs updated.
|
||
|
||
## 1.4.2
|
||
|
||
### Fixed — CoWork sandbox plugin-path resolution
|
||
|
||
In a remote CoWork sandbox, `${CLAUDE_PLUGIN_ROOT}` can resolve to a host path the
|
||
sandbox can't reach (e.g. `/var/folders/…`), while the plugin is actually mounted under
|
||
`…/mnt/.remote-plugins/…`. Every `python3 "${CLAUDE_PLUGIN_ROOT}/…"` invocation then
|
||
failed until the agent manually located the real path. (Root cause is the harness env
|
||
var, not the plugin — the scripts always used the variable correctly, never a hardcoded
|
||
path — but the plugin now self-heals.)
|
||
|
||
- SKILL.md and all eight slash commands resolve the scripts path with a fallback:
|
||
prefer `${CLAUDE_PLUGIN_ROOT}`, else locate the copy under
|
||
`/sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/`. Reused via
|
||
`$ECHO`/`$LINT`/`$SWEEP`/`$SDIR`; on a normal host the primary path always wins, so
|
||
behaviour is unchanged.
|
||
- `echo-health` / `echo-sweep` `allowed-tools` broadened to match the resolved
|
||
(variable-dir) invocation and the `ls`/`dirname` resolver steps.
|
||
|
||
## 1.4.1
|
||
|
||
### Fixed — baked credentials now win unconditionally
|
||
|
||
A per-user baked artifact could still be prompted for credentials on install. The
|
||
baked `DEFAULT_*` tier was lowest priority, so a stale `ECHO_*` env var or a
|
||
leftover/placeholder `~/.claude/echo-memory/config.json` shadowed the baked key —
|
||
even an unedited `"<placeholder>"` value won over it, flipping `is_configured()` to
|
||
false and triggering the first-run "ask the operator" flow despite valid baked creds.
|
||
|
||
- `echo_config.load()` now treats a **complete** baked set (endpoint + key both
|
||
present) as authoritative: it wins over env vars and the config file and cannot be
|
||
shadowed. The generic (unbaked) plugin keeps its env → file → first-run flow.
|
||
- Added `baked_complete()`; `source()` reports `baked-in (plugin)` for that tier.
|
||
|
||
### Security — untracked leaked baked artifacts
|
||
|
||
- Two labeled baked artifacts (`echo-memory-1.4.0-jason.plugin`,
|
||
`echo-memory-1.4.0-gretchen.plugin`), each carrying a live vault bearer token, had
|
||
been committed at the repo root. They are now `git rm --cached`-removed and
|
||
`.gitignore` gains `echo-memory-*-*.plugin` so labeled builds can't be re-committed
|
||
(the version-only pointer `echo-memory.plugin` and `echo-memory-<ver>.plugin` stay
|
||
tracked). Tokens remain in prior history — rotate if the repo was ever shared.
|
||
|
||
## 1.4.0
|
||
|
||
### Added — per-user baked-key builds (CoWork zero-touch)
|
||
|
||
The plugin is the only thing reliably mounted into a CoWork session (the sandbox's
|
||
`~/.claude` is synthetic, not your real one), so the config now *can* travel inside
|
||
the artifact. `echo_config` gains a lowest-priority fallback tier — the
|
||
`DEFAULT_OWNER` / `DEFAULT_BASE` / `DEFAULT_KEY` constants — **empty in source**, so
|
||
the committed tree still ships zero credentials.
|
||
|
||
- `build.py --bake-key --from <config.json> [--label <name>]` substitutes a single
|
||
user's owner/endpoint/key into those constants, producing a per-user artifact that
|
||
needs **no config file and no per-session paste**. Values may also come from
|
||
`--owner/--endpoint/--key` or `ECHO_OWNER/ECHO_BASE/ECHO_KEY`.
|
||
- Baked builds default to `dist/` (gitignored) and never touch the shared
|
||
`echo-memory.plugin` pointer.
|
||
- `build.py --strip-key` now force-blanks the same constants (guaranteed token-free).
|
||
- `config show` / `doctor` report `[baked-in default]` when a field resolves from the
|
||
baked tier.
|
||
|
||
### Changed — artifact hygiene
|
||
|
||
- `.gitignore` excludes `dist/` and the filled-in `echo-memory.config.json`, where
|
||
baked builds and the key file are meant to live. A baked artifact carries a vault
|
||
key and must be **delivered directly to that one user, never committed, pushed, or
|
||
published.** (Note: labeled baked artifacts built at the repo *root* were not yet
|
||
guarded — see 1.4.1.)
|
||
|
||
### Resolution order (unchanged for hosts)
|
||
|
||
`env` → `$ECHO_CONFIG` file → canonical `~/.claude/echo-memory/config.json` →
|
||
**baked `DEFAULT_*`**. On a normal desktop the empty defaults mean behaviour is
|
||
identical to 1.3.x; the baked tier only matters in per-user artifacts.
|