# @psraghuveer/memento-dashboard

## 0.5.0

### Minor Changes

- f09b009: feat: surface stale embeddings (new `embeddingStatus: 'stale'` value + dashboard rebuild surface)

  The wire-level `embeddingStatus` enum on every memory output (`memory.read`, `memory.list`, `memory.search`, `memory.context`, `memory.write`, etc.) gains a fourth value, **`'stale'`**, to mean: "the row has an embedding, but its `model` / `dimension` mismatch the configured embedder, so the vector arm of search will skip it until `embedding.rebuild` re-embeds it."

  Pre-change, `embeddingStatus` was binary on the "is there a vector?" axis: `'present'` (any non-null row) / `'pending'` (no row yet) / `'disabled'` (vector arm off). After a user changed `embedder.local.model` (or installed a fresh build with a different default), every previously-embedded row continued to report `'present'` even though the vector arm would silently skip it. The "stale" state was real (the bulk re-embed driver's `isFresh` check has always treated mismatched rows as rebuild candidates) but invisible to dashboards, scripts, and assistants.

  **What changes for consumers**

  - **Wire schema (`@psraghuveer/memento-schema`):** `embeddingStatus: z.enum(['present', 'stale', 'pending', 'disabled']).optional()` — additive. Existing consumers that branch on `'present'` keep working; consumers that exhaustively match all values now need a `'stale'` case (TypeScript will surface the gap at the call site).

  - **`computeEmbeddingStatus` (`@psraghuveer/memento-core`):** new optional third argument `configuredEmbedder?: {model, dimension}`. When supplied, embedded rows whose model / dim mismatch return `'stale'`; when omitted, embedded rows fall back to `'present'` (legacy host shape — hosts without an active embedder have no comparison point, so the safer answer is to leave behavior unchanged for those hosts).

  - **Tool descriptions:** `memory.search` and `memory.context` MCP tool descriptions now document all four values and explain the `'stale'` remediation (`run embedding.rebuild`). The auto-generated `docs/reference/mcp-tools.md` + `docs/reference/cli.md` are updated accordingly.

  - **Shared helper:** new `isEmbeddingFresh(embedding, configuredEmbedder)` in `@psraghuveer/memento-core/embedding`. Single source of truth for "is this embedding row current?" — replaces the duplicated local check inside `reembedAll` and matches the read-time `computeEmbeddingStatus` projection.

  - **`embedding.rebuild` gains the `'dashboard'` surface.** Previously `mcp+cli` only. The dashboard's UI now calls this command directly via the existing registry-over-HTTP proxy.

  **Dashboard (`@psraghuveer/memento-dashboard`)**

  - **Per-row embedding badge** on `/memory`. Renders next to the existing pinned / sensitive badges when status is anything other than `'present'` (the silent default). `stale` is warn-toned with a hover tooltip explaining the model/dim mismatch; `pending` / `disabled` are muted-toned with their own explanations.

  - **"Stale only" filter chip.** Toggles client-side narrowing of the result list to rows with `embeddingStatus === 'stale'`. There is no engine-side `embeddingStatus` filter on `memory.list` yet, so the narrowing applies to whatever the engine already paged in — the count is "stale in this view," not a global tally.

  - **Rebuild banner.** When any row in the current view is `stale`, a warn-toned banner above the result list explains the situation and offers a "rebuild stale embeddings" button. The button calls `embedding.rebuild` via the new `useEmbeddingRebuild` mutation hook; on success, the hook invalidates `memory.list` / `memory.search` / `memory.read` / `memory.events` / `system.info` so the badge / banner re-evaluate on next render. Per-call success and error summaries render inline below the banner.

  - **Memory-detail page additions.** The header pill row gains an embedding pill matching the list-page badge; the tooltip on the `stale` pill carries the full `stored ${model}@${dim}d vs configured ${model}@${dim}d` comparison so the user can see the exact mismatch without leaving the page. A `rebuild` action button appears in the action row alongside `pin` / `confirm` / `forget` when the current memory is `stale`.

  **Tests**

  - `packages/core/test/commands/memory.test.ts` gains two cases inside the existing `embeddingStatus + lean responses` describe block: one that pins `'stale'` on a configured-embedder mismatch, and one that pins the legacy `'present'` fallback when no `configuredEmbedder` is wired.
  - `packages/core/test/commands/conflict-embedding-compact.test.ts` updated to assert the new `['mcp', 'cli', 'dashboard']` surface set on `embedding.rebuild`.
  - All 1614 existing tests pass plus the 2 new ones (1616 total).

### Patch Changes

- Updated dependencies [f09b009]
  - @psraghuveer/memento-schema@0.14.0
  - @psraghuveer/memento-core@0.17.0

## 0.4.7

### Patch Changes

- Updated dependencies [c0433c2]
  - @psraghuveer/memento-core@0.16.0
  - @psraghuveer/memento-schema@0.13.0

## 0.4.6

### Patch Changes

- Updated dependencies [96a62f0]
  - @psraghuveer/memento-core@0.15.0

## 0.4.5

### Patch Changes

- Updated dependencies [c430d82]
  - @psraghuveer/memento-core@0.14.0
  - @psraghuveer/memento-schema@0.12.1

## 0.4.4

### Patch Changes

- Updated dependencies [ab0eca1]
  - @psraghuveer/memento-core@0.13.0
  - @psraghuveer/memento-schema@0.12.0

## 0.4.3

### Patch Changes

- Updated dependencies [0dc4716]
  - @psraghuveer/memento-schema@0.11.0
  - @psraghuveer/memento-core@0.12.0

## 0.4.2

### Patch Changes

- Updated dependencies [af104e5]
  - @psraghuveer/memento-core@0.11.0
  - @psraghuveer/memento-schema@0.10.0

## 0.4.1

### Patch Changes

- Updated dependencies [7ebe1c6]
  - @psraghuveer/memento-core@0.10.0
  - @psraghuveer/memento-schema@0.9.0

## 0.4.0

### Minor Changes

- 65e49d3: Add Memento Packs — installable YAML bundles of memories that close the cold-start gap (an empty memory store has no value).

  - `memento pack install <id>` seeds your store from a curated bundle. One pack ships in this release: `engineering-simplicity` — 11 memories adapted from John Maeda's _The Laws of Simplicity_ with original engineering applications, CC0-1.0. Use `memento pack list` to see what's installed and `memento pack preview <id>` to inspect a pack before committing.
  - Three install sources: bundled (`memento pack install engineering-simplicity`), local file (`--from-file ./pack.yaml`), or HTTPS URL (`--from-url https://...`). URL fetches are HTTPS-only, capped at `packs.maxPackSizeBytes` (default 1 MiB), and time out at `packs.urlFetchTimeoutMs` (default 10s). Disable URL installs with `packs.allowRemoteUrls=false`.
  - Bundled lookups support omitting `--version`: the resolver scans the pack directory and picks the highest semver, with stable beating prerelease per semver §11.
  - Drift detection is built in. Re-installing the same version with edited content fails fast with `PACK_VERSION_REUSED`; bump the version (e.g. `v0.1.0` → `v0.1.1`) to ship changes. Cosmetic edits to pack-level metadata (title, description, license, tags) don't trigger drift.
  - Provenance is the canonical tag `pack:<id>:<version>`. `memento memory list --tag pack:engineering-simplicity:0.1.0` shows exactly what a pack contributed; `memento pack uninstall <id>` removes it via `memory.forget_many`. The `pack:` prefix is reserved — user writes can't forge it.
  - Author your own with `memento pack create` — interactive prompts walk through filtering existing memories into a YAML pack, or pass flags for non-interactive use. The format is `memento-pack/v1`; full authoring guide at [docs/guides/packs.md](https://github.com/veerps57/memento/blob/main/docs/guides/packs.md), JSON Schema at [docs/reference/pack-schema.json](https://github.com/veerps57/memento/blob/main/docs/reference/pack-schema.json).
  - The dashboard gains a `/packs` tab: browse installed packs, preview a pack's memories, install or uninstall via the UI — same engine as the CLI behind it.

  Design rationale: [ADR-0020](https://github.com/veerps57/memento/blob/main/docs/adr/0020-memento-packs.md).

### Patch Changes

- Updated dependencies [65e49d3]
  - @psraghuveer/memento-core@0.9.0
  - @psraghuveer/memento-schema@0.8.0

## 0.3.0

### Minor Changes

- 5479c6a: Dashboard stress-test fix pass + engine corrections it surfaced.

  A multi-round audit of the v0 dashboard surfaced eight visible bugs and a stack of UX/copy issues. Closing them required matching engine work: a broken conflict-scan surface, an audit-log gap that made every first-time config edit read as `null → x`, a missing dedup invariant in conflict detection, and three new `system.info` fields. The dashboard was largely rewritten on top of the corrected engine surface — every read view, the inline config editor, the ⌘K palette, the system & health page, and the auth-error UX shipped together so the user-visible polish lands as one coherent change rather than a flurry of single-line PRs.

  Functional behaviour on the happy path is unchanged outside the dashboard. Existing CLI / MCP callers see the same output shapes — the new `system.info` fields are additive, the new `ConfigSetInput.priorEffectiveValue` parameter is optional and back-compat (legacy callers that don't pass it keep their existing oldValue=null semantics), and the new `ConflictRepository.openPartners` method extends the interface without changing the existing five.

  **Conflict detection** (`@psraghuveer/memento-core`)

  - `conflict.scan` is now exposed on the dashboard surface so the conflicts page's "re-scan (24h)" button actually works. Previously the click returned `INVALID_INPUT — Command 'conflict.scan' is not exposed on the dashboard surface`.
  - The detector now skips candidates that already share an open conflict with the memory being scanned (either direction). Without this, every press of `re-scan (24h)` and every redundant post-write hook fire inserted a duplicate row for the same logical pair. Observable in the dashboard's overview tile as a count that grew monotonically with the number of clicks. New `ConflictRepository.openPartners(memoryId)` returns the partner-id set in one query; the detector seeds an in-memory mutable copy at the top of the run so newly-opened pairs aren't re-opened later in the same scan.
  - `conflict.scan` in `since` mode now reports `scanned` as the number of memories processed, not the candidate-pairing count summed across them. Re-runs over a 5,000-memory corpus used to print `scanned 68413 memories` (the work the detector did, not the size of the haystack); now they print the actual memories processed.

  **Config audit log** (`@psraghuveer/memento-core`, `@psraghuveer/memento-schema`)

  `config.set` and `config.unset` accept an optional `priorEffectiveValue` field that the engine plumbs from `configStore.entry(key).value` at the command-handler layer. When no prior event exists for the key, OR when the latest event was an `unset` (i.e. the runtime override layer is empty), the audit `oldValue` records the engine's effective value — typically the schema default — instead of literal `null`. The dashboard's per-key history view now reads `100 → 42 by cli` for the first-time edit instead of the previous `null → 42`. Legacy callers that don't pass `priorEffectiveValue` keep the original oldValue=null behaviour.

  `@psraghuveer/memento-schema` exports a new `IMMUTABLE_CONFIG_KEY_NAMES` constant — the snapshot of every key flagged `mutable: false` in `CONFIG_KEYS`. The dashboard's config editor consumes this directly so its IMMUTABLE_KEYS gate cannot drift from the engine; a structural test in `packages/schema/test/config-keys.test.ts` pins the relationship.

  **`system.info` additions** (`@psraghuveer/memento-core`, `@psraghuveer/memento-schema`)

  Three new top-level fields on the output schema:

  - `openConflicts: number` — exact aggregate from `SELECT COUNT(*) FROM conflicts WHERE resolved_at IS NULL`. The dashboard's overview tile now reads this directly instead of counting a paged `conflict.list` response capped at `conflict.list.maxLimit`. The previous `1,000+` capped display is gone; resolving a conflict decrements the value monotonically through the existing query-cache invalidation path.
  - `runtime: { node, modulesAbi, nativeBinding: 'ok' }` — process-level health subset of `memento doctor`. `nativeBinding` is always `'ok'` because reaching the handler implies the better-sqlite3 .node addon loaded successfully. Powers the dashboard's `~/system` `node` and `native binding` probes.
  - `scrubber: { enabled }` — resolved `scrubber.enabled` config. Surfaces the write-path redaction master switch on `~/system` so the operator can confirm the safety net is active. The key remains pinned at server start (`mutable: false`); the boolean here just mirrors the resolved value the engine is actually applying to writes.

  The handler description metadata is updated to mention the new fields. Reference docs (`docs/reference/mcp-tools.md`, `docs/reference/cli.md`) regenerated.

  **Dashboard surface** (`@psraghuveer/memento-dashboard`)

  The package gets a behaviour + visual sweep on the v0 routes:

  - **Re-scan button works.** Driven by the engine's `conflict.scan` surface fix.
  - **Inline config editor.**
    - The Reset (config.unset) button now renders for runtime overrides — i.e. when `entry.source` is `cli` or `mcp`. The previous predicate compared against the literal `'runtime'`, which is not a member of `ConfigSourceSchema`; the button never rendered.
    - `inferEditorType` treats `null` on the known string-or-null keys (`user.preferredName`, `export.defaultPath`, `embedder.local.cacheDir`) as `'string'`. The previous fallback to JSON forced users to type `"Raghu"` (with quotes) to set their preferred name; bare names failed `JSON.parse`. Empty drafts on these keys are saved as `null` so the field can be cleared without an explicit `config.unset`.
    - `IMMUTABLE_KEYS` now derives from `IMMUTABLE_CONFIG_KEY_NAMES`. Four keys (`embedder.local.timeoutMs`, `embedder.local.cacheDir`, `scrubber.enabled`, `scrubber.rules`) used to render an editor and surface `IMMUTABLE` only on save.
    - Local `ConfigSource` TypeScript type widened to match the schema's actual enum (`default | user-file | workspace-file | env | cli | mcp`); the previous `'default' | 'startup' | 'runtime'` was wrong.
  - **Auth-error UX.** A new `TokenMissingPanel` component renders uniformly across every route when the launch token is missing or rejected. Detection lives in the API client (`callCommand`) which short-circuits to a synthetic `AUTH_REQUIRED` code; the app shell subscribes to the React-Query cache and replaces the active route with the panel. Replaces the seven different `failed: …` prefixes that used to surface on each route.
  - **Pagination.** `memory.list`, `memory.events`, `conflict.list`, and the memory-detail audit timeline get a "load next 100" affordance up to the engine's `*.maxLimit` ceiling (1,000 by default). The hooks use `keepPreviousData` so the page doesn't snap to the top during the next fetch. State resets when filters change.
  - **Filter chips.** Status and kind on `~/memory` are now multi-select sets. The engine takes a single optional `kind`/`status`, so the dashboard sends a wire-level filter only when one chip is active and narrows the rest client-side. Status chips refuse to deselect the last-remaining chip (a zero-status filter is ambiguous without an `all` chip; the four statuses always cover the universe).
  - **Mutation invalidation.** `useSetConfig` / `useUnsetConfig` invalidate `system.info` so the wordmark refreshes after a `user.preferredName` edit. `useResolveConflict` and `useScanConflicts` invalidate `system.info` so the overview tile decrements after triage. Memory mutations also invalidate `system.list_scopes`.
  - **Visual polish.** Overview rows share a `ACROSS ALL SCOPES` / `BY STATUS` header shape with no per-tile subtexts. `BY STATUS` shows three lifecycle-exit tiles (active is in the headline row). Top-N scope distribution gets a `+ N more scopes (M)` reconciliation row when truncated. The capped `1,000+` open-conflict tile is gone in favour of the exact engine count. The footer's `vec: on/off` indicator is gone (the `~/system` page owns the vector-retrieval probe). Memory + conflict + audit row pills are lowercase in neutral foreground; the `forget` button matches `pin` / `confirm` (no warn tone). Audit row memory-IDs default to white, accent on hover.
  - **System & health.** Six probes ordered along the dependency chain: `node` → `database` → `native binding` → `vector retrieval` → `scrubber` → `version`. Indicator dots use a traffic-light mapping (`synapse` for ok, `warn` for warn, `destructive` for off) that's no longer ambiguous between ok and warn. `schema version` and `last write` removed (former invariant, latter content-state). The standalone `embedder` probe rolled into `vector retrieval` (which absorbs the embedder model + dimension on the note line and flips to `warn` when vector is on but embedder is missing).

  Plus a regression-test pass: 17 new unit tests cover the new `priorEffectiveValue` paths in the config repository, the schema-default-as-oldValue contract in the config command handler, the `IMMUTABLE_CONFIG_KEY_NAMES` drift-prevention invariant, the conflict detector's two dedup branches (re-run on the same memory; reverse-direction pair), and the three `system.info` additions. The total moves from 1,211 → 1,225 passing tests.

### Patch Changes

- Updated dependencies [5479c6a]
  - @psraghuveer/memento-core@0.8.0
  - @psraghuveer/memento-schema@0.7.0

## 0.2.1

### Patch Changes

- Updated dependencies [1dc5f71]
  - @psraghuveer/memento-core@0.7.0
  - @psraghuveer/memento-schema@0.6.0

## 0.2.0

### Minor Changes

- a83c2c0: End-to-end security hardening pass before public launch. Findings from a full-codebase audit (DoS surface, scrubber correctness, import/export trust boundaries, dashboard auth, storage hygiene, install supply-chain) addressed in code, with regression tests and updated docs. Defaults are conservative — every behaviour change is either rejecting input that was already a DoS or bypass risk, or a new opt-in. Functional behaviour on the happy path is unchanged.

  **Scrubber correctness** (`@psraghuveer/memento-schema`, `@psraghuveer/memento-core`)

  - Now scrubs `summary` and (for `decision`-kind memories) `kind.rationale` in addition to `content`. Earlier the scrubber operated on `content` only — an LLM auto-generating a summary from raw content trivially round-tripped secrets into the persisted summary, defeating the whole defence.
  - Two new default rules: `private-key-block` (PEM private-key blocks) and `bearer-token` (HTTP `Authorization: Bearer …`). Previously claimed in `SECURITY.md` but missing from the code.
  - Email regex rewritten to be ReDoS-safe (split the domain into non-overlapping label classes); JWT regex tightened to admit real-world short payloads.
  - New `scrubber.engineBudgetMs` ConfigKey (default 50 ms) caps each rule's wallclock runtime; aborts a runaway operator-installed regex without blocking the writer thread.
  - `scrubber.enabled` and `scrubber.rules` flipped to immutable at runtime (`mutable: false`). A prompt-injected MCP `config.set` can no longer disable redaction before writing a secret. `IMMUTABLE` error fires regardless of which surface invoked the command.

  **Import re-stamp policy** (`@psraghuveer/memento-core`, ADR-0019)

  `memento import` no longer trusts caller-supplied audit claims. Three transformations always happen on every imported artefact, regardless of flags:

  1. `OwnerRef` rewritten to local-self (closes the future-multi-user owner-spoofing vector at the wire boundary; AGENTS.md rule 4).
  2. Memory `content` / `summary` / `decision.rationale` re-scrubbed using the **importer's** current rule set. An artefact authored on a host with a weaker scrubber has its secrets re-redacted on arrival.
  3. `MemoryEvent.payload`, `Conflict.evidence`, and `ConflictEvent.payload` JSON capped per record at 64 KiB. A forged artefact cannot stuff multi-megabyte audit-log blobs.

  On top of those, the new `--trust-source` flag controls the audit chain. Default (flag absent): the source artefact's per-memory event chain is collapsed into one synthetic `memory.imported` event per memory; `actor` and `at` reflect the importer, not the source. With `--trust-source`: original events are inserted verbatim — for the "I am restoring my own backup, preserve the history" case. The `imported` variant is added to `MEMORY_EVENT_TYPES` and migration `0006_memory_events_imported_type.ts` widens the SQLite CHECK constraint to admit it.

  **Resource caps** (`@psraghuveer/memento-schema`, `@psraghuveer/memento-core`, `@psraghuveer/memento-server`, `@psraghuveer/memento-embedder-local`)

  A wire input that previously could OOM the process is now rejected with `INVALID_INPUT`. Every cap has a structural ceiling at the schema boundary plus an operator-tunable floor below it.

  - `memory.write`/`write_many`/`supersede`/`extract` content > 1 MiB rejected at the schema; `safety.memoryContentMaxBytes` (default 64 KiB) tightens at the handler. Companion caps: `safety.summaryMaxBytes` (2 KiB), `safety.tagMaxCount` (64).
  - New stdio transport wrapper enforces `server.maxMessageBytes` (default 4 MiB, immutable). A peer that withholds the trailing newline can no longer grow the JSON-RPC read buffer until Node OOMs.
  - Local embedder accepts `embedder.local.maxInputBytes` (default 32 KiB, immutable; UTF-8-safe truncation before tokenisation) and `embedder.local.timeoutMs` (default 10 s, immutable; `Promise.race` against the embed call).
  - `memento import` rejects artefacts larger than `import.maxBytes` (default 256 MiB) up-front via `fs.stat`, then streams the file via `readline.createInterface`. Multi-GB artefacts no longer OOM the CLI before parsing begins.

  **Dashboard hardening** (`@psraghuveer/memento-dashboard`, `@psraghuveer/memento-core`)

  The dashboard is the project's only network-bound surface. Three independent defence layers added:

  1. **Per-launch random token.** Every `memento dashboard` invocation mints a 256-bit token and embeds it in the URL fragment passed to the browser. The SPA reads it from `window.location.hash`, persists to `sessionStorage`, sends `Authorization: Bearer …` on every API call. Closes the "any local process can hit `127.0.0.1:<port>`" gap. Note: bookmarks of the dashboard URL no longer work — re-launch via `memento dashboard` to get a fresh token.
  2. **Origin guard now exact-port-matched** (was: prefix-match against any localhost). A sibling localhost web server (Vite dev server, another local app) can no longer forge requests against the dashboard.
  3. **Host-header allowlist** for DNS-rebinding defence: only `127.0.0.1:<port>`, `localhost:<port>`, `[::1]:<port>` accepted.

  Plus: a new `'dashboard'` value on `CommandSurface`. Only commands the UI uses today are opted in (read paths + `memory.confirm`/`update`/`forget`, `config.*`, `conflict.list`/`resolve`); everything else (writes, supersede, set*embedding, archive variants, restore, *\_many, conflict.scan, embedding.rebuild, compact.run, memory.context, memory.extract, system.list*tags) returns `INVALID_INPUT` from the dashboard pointing at the CLI. Hono `secureHeaders` (CSP, X-Frame-Options DENY, nosniff, no-referrer); 4 MiB body limit on `/api/commands/*`; static handler now does realpath-based containment; sourcemaps no longer ship in production builds.

  **Storage and file hygiene** (`@psraghuveer/memento-core`, `@psraghuveer/memento`)

  - `pragma trusted_schema = OFF` in the canonical PRAGMA set. A user opening an attacker-supplied `.db` via `--db /tmp/evil.db` can no longer be hit by trigger-borne side effects.
  - DB files written with mode `0600`, data directory and embedder cache with mode `0700`. Memory content is operator-private even after scrubbing; permissive umasks no longer expose it on multi-user hosts.
  - `memento backup` now writes via tempfile + atomic rename (closes the existsSync→unlink→VACUUM TOCTOU), uses `VACUUM INTO ?` with a bound parameter (was: single-quote-escape interpolation), and produces a 0600 file.
  - `INTERNAL` and `STORAGE_ERROR` messages returned to MCP clients have absolute filesystem paths replaced with `<path>` (was: SQLite errors leaked `/Users/<name>/...` to the wire). Well-known messages (NOT_FOUND, CONFLICT) are preserved for actionable error UX.
  - `memento init`'s WAL/SHM/journal-sidecar cleanup now `lstat`s before unlink and refuses anything that isn't a regular file (closes the symlink-replacement footgun on shared `/tmp` paths).
  - `memento export` defaults to `flags: 'wx'` (refuse to clobber existing files), `mode: 0o600`. New `--overwrite` flag opts in.

  **Install / supply-chain hardening** (`@psraghuveer/memento`)

  - Both postinstall scripts now pass a closed env allowlist when invoking `npm`/`npx`. Drops `npm_config_script_shell`, `NODE_OPTIONS`, `PREBUILD_INSTALL_HOST`, every other `npm_config_*` env, etc. — closes the documented "malicious sibling dep stages env vars to subvert npm" supply-chain vector.
  - `npm`/`npx` resolved via `process.env.npm_execpath` (set by npm during install) instead of `PATH`. Avoids `node_modules/.bin/npm` hijack by a colluding dep with a `bin: "npm"` entry.
  - Embedder model cache moved from `node_modules/.../@huggingface/transformers/.cache/` to `$XDG_CACHE_HOME/memento/models` (or platform equivalent). Persistent across reinstalls, owner-private, and not plantable from `node_modules`. **First run after upgrade re-downloads the model (`bge-base-en-v1.5`, ~110 MB) into the new location.**
  - `memento doctor --mcp` JSON-parse failures report only the error class name, dropping byte-positional context (Node 22's SyntaxError can include surrounding-line bytes; some MCP client configs hold API tokens; doctor reports get pasted into bug reports).

  **Documentation**

  - New ADR: [ADR-0019 — Import re-stamp policy](docs/adr/0019-import-re-stamp-policy.md).
  - `SECURITY.md` rewritten — "Defenses Memento Provides" / "Does Not Provide" sections now match what the code delivers.
  - `KNOWN_LIMITATIONS.md` extended (dashboard token is per-launch; embedder cache moved to XDG).
  - `docs/architecture/{config,scrubber,data-model}.md` updated for new ConfigKeys, scrubber rules + summary/rationale coverage, the `imported` event variant.
  - `AGENTS.md` rules 4 and 11 cross-reference ADR-0019.
  - ADR-0012 marked as extended; the `safety.*` namespace now also carries the resource caps added in this pass.

  **Behaviour changes worth knowing**

  - Writes that were previously accepted but DoS-shaped (e.g. > 64 KiB content, > 4 MiB JSON-RPC message, multi-GB import) are now `INVALID_INPUT`. No legitimate workflow is affected; raise `safety.*` / `import.maxBytes` if your data exceeds the defaults.
  - `config.set scrubber.enabled` / `scrubber.rules` from MCP now returns `IMMUTABLE`. Set them at startup via configuration overrides instead.
  - `memento export` refuses to overwrite by default; pass `--overwrite` to keep the prior behaviour.
  - `memento dashboard` URLs include a token fragment; bookmarks of the URL won't authenticate on a future visit (re-launch to get a fresh URL).
  - First `memento dashboard` / vector-search after upgrade re-downloads the embedder model into the new XDG cache location.

### Patch Changes

- Updated dependencies [a83c2c0]
  - @psraghuveer/memento-core@0.6.0
  - @psraghuveer/memento-schema@0.5.0

## 0.1.3

### Patch Changes

- d70b151: Lean responses + clear signals so AI assistants — and human operators — can use Memento intuitively.

  Themed pass driven by a persona-3 production test (an AI assistant calling Memento over MCP). Every response was audited for "did the assistant know what just happened, what to expect next, and what to do?" — the gaps below were the failures.

  **Embedding vectors stripped from single-memory responses** (`@psraghuveer/memento-core`)

  - `memory.write`, `memory.update`, `memory.confirm`, `memory.forget`, `memory.archive`, `memory.restore`, `memory.supersede`, `memory.set_embedding` no longer echo the 768-float embedding vector back. Previously a single `memory.read` could push ~12KB of pure noise into the assistant's context window for free.
  - `memory.read` now accepts an opt-in `includeEmbedding: true` for the rare debugging case (the only single-memory command where echoing the vector has a real use case).
  - `memory.list` and `memory.search` already stripped by default; behavior unchanged.

  **New `embeddingStatus` field on every memory output** (`@psraghuveer/memento-schema`, `@psraghuveer/memento-core`)

  The previous `embedding: null` was ambiguous — an assistant couldn't tell "vector retrieval is off," "embedder hasn't caught up yet," and "stripped from this response" apart. Three states, one response, no infrastructure work for the assistant. Surfaced on every memory output:

  - `'present'` — the vector exists (whether echoed back or stripped)
  - `'pending'` — `retrieval.vector.enabled` is true but the embedder hasn't run yet (common right after a write)
  - `'disabled'` — vector retrieval is off

  Additive (optional field on `MemoryBaseSchema`); existing storage layers and consumers stay unchanged.

  **`get_memory_context` returns a `hint` when results are empty** (`@psraghuveer/memento-core`)

  A fresh assistant calling `get_memory_context` on an empty store would otherwise see `{ results: [], resolvedKinds: [...] }` and have no nudge to start writing. The new `hint` field distinguishes "store is genuinely empty — capture preferences as they come up" from "no matches in the requested scope — try a different filter." Set only when `results.length === 0`; absent otherwise.

  **`extract_memory` response now carries `mode` and `hint`** (`@psraghuveer/memento-core`)

  In the default async mode (per `extraction.processing` config), `memory.extract` returned `{ written: [], skipped: [], superseded: [], batchId, status: 'accepted' }` — empty arrays gave assistants no way to tell users what happened. The response now also includes:

  - `mode: 'sync' | 'async'` — `'sync'` means the arrays are authoritative; `'async'` means processing in background, expect ~1–5 sec, do not retry
  - `hint` (async only) — explicit next-step guidance: "Processing N candidate(s) in background. Results land as memories within ~1–5 seconds; verify with list_memories or search_memory if needed."

  Both fields are additive; sync responses gain `mode: 'sync'`, async responses gain `mode: 'async'` + `hint`.

  **`update_memory` redirect short-circuits redundant errors** (`@psraghuveer/memento-core`)

  Previously, `update_memory({ patch: { content: '...' } })` returned both the helpful `cannot update content — use memory.supersede` redirect (good) AND a redundant `patch must change at least one field` (noise — `content` was the field, the redirect already explained why it was rejected). The `superRefine` now short-circuits the second check when forbidden-key issues already fired, so the response stays a single actionable line.

  **New `user.preferredName` config + `system.info` surface** (`@psraghuveer/memento-schema`, `@psraghuveer/memento-core`)

  The bundled assistant skill teaches AIs to attribute writes ("Raghu prefers pnpm" rather than "User prefers pnpm"), but there was no canonical way to discover the user's preferred handle. New `user.preferredName` config key (string, nullable, default `null`); the value is surfaced in `system.info.user.preferredName` so the assistant learns it once at session start. When `null`, the skill instructs the assistant to write "The user" instead. Set with `memento config set user.preferredName "<name>"`.

  **Dashboard personalization + brand-title alignment** (`@psraghuveer/memento-dashboard`)

  - The dashboard wordmark in the top bar now reads `<name>@memento_` when `user.preferredName` is set — a shell-prompt cue that matches the dashboard's terminal aesthetic (`~/overview`, `~/memory/$id` route style). Falls back to `memento_` when null. Reuses the existing blinking-cursor caret; no new visual concept.
  - Browser tab title aligned to `Memento — Dashboard` (was `memento — dashboard`) for visual parity with the landing page (which now reads `Memento — A local-first, LLM-agnostic memory layer for AI assistants` everywhere — `<title>`, OG title, Twitter title).

  **Skill + tool description alignment** (`skills/memento/SKILL.md`, tool descriptions for `write_memory`, `update_memory`, `read_memory`, `extract_memory`, `info_system`, `write_many_memories`)

  The conflict-detection policies for `preference` and `decision` parse the first line of `content` as `key: value` — but the skill's worked example used freeform prose, so AIs following the skill silently bypassed conflict detection. Skill and the `write_memory` tool description now teach a two-line pattern: `topic: value` on line 1 (the structural anchor for the detector) followed by free prose for retrieval. Without that line, two contradictory preferences ("I use bun" vs "I use npm") will silently coexist instead of being surfaced for triage.

  The skill also now contains a **Quick decision tree** appendix covering the four most-touched judgement calls in any session — which write tool (`write_memory` vs `extract_memory` vs N-sequential-writes vs `write_many_memories`), which kind (with the rule-of-thumb "does the user expect to defend the choice if asked 'why'?" → `decision`; otherwise `preference`), which scope (user-facts → `global`, project-facts → `repo`), and when to deviate from the `storedConfidence` / `pinned` / `sensitive` defaults.

  `write_many_memories` tool description rewritten to make clear it is a programmatic / operator surface — for batched explicit user statements, AI assistants should prefer N sequential `write_memory` calls (no all-or-nothing rollback) or `extract_memory` (server dedups + scrubs). `clientToken` description marked as a programmatic-idempotency surface (scripts, migrations, retry-safe pipelines) that AI assistants typically omit. `update_memory` field list corrected from `(tags / kind / pinned)` to `(tags / kind / pinned / sensitive)` everywhere it appeared. `forget` vs `archive` distinction tightened to a single rule: **forget retracts (was wrong); archive retires (was right, no longer current)**.

  The skill additionally documents:

  - The async-extract response contract (`mode` + `hint` — empty arrays are not failure)
  - The `info_system.user.preferredName` flow for user attribution

  `docs/architecture/conflict-detection.md` adds a paragraph noting that the per-policy first-line shape is the contract the skill teaches — so future contributors don't try to "fix" the silent-conflict case by widening the detector and end up with false positives instead. `docs/architecture/data-model.md` documents the new `embeddingStatus` field as a wire-only projection. `docs/architecture/decay-and-supersession.md` and `docs/guides/teach-your-assistant.md` updated to match the corrected `update_memory` field list and to teach the same `key: value` shape + `info_system.user.preferredName` attribution pattern as the skill (so non-skill clients — Cursor, Cline, OpenCode — get the same guidance via the persona snippet).

  **Skill install discoverability** (no package bumps; `skills/README.md`, `CONTRIBUTING.md`, `AGENTS.md`)

  The contributor `memento-dev` skill was essentially undiscoverable — no mention in `CONTRIBUTING.md` or `AGENTS.md`, and `skills/README.md`'s install section was written entirely for the end-user `memento` skill. Now:

  - `skills/README.md`: install section split into "End users: the `memento` skill" (existing path via `memento init`) and "Contributors: the `memento-dev` skill" (one-line `cp -R` install + a "verifying the dev skill auto-triggers" walkthrough)
  - `CONTRIBUTING.md`: new "Install the contributor skill (Claude Code only)" subsection in Local setup
  - `AGENTS.md`: dev-skill install promoted to the top of the "For AI agents specifically" section, framing it as the load-on-intent companion to AGENTS.md itself

  The `memento-dev` skill also picked up two new pitfalls — workspace postinstall coordination (`scripts/ensure-better-sqlite3.mjs` ↔ `packages/cli/scripts/postinstall.mjs`) and the `topic: value` first-line requirement for new `preference` / `decision` features — and corrected the `memory.update` field list to include `sensitive`.

- Updated dependencies [d70b151]
  - @psraghuveer/memento-core@0.5.2
  - @psraghuveer/memento-schema@0.4.1

## 0.1.2

### Patch Changes

- fe72460: Persona-driven UX, install, and brand polish across the CLI, core, dashboard, and embedder.

  **CLI** (`@psraghuveer/memento`)

  - **Node 22.11+ baseline.** Node 20 exited LTS on 2026-04-30; the runtime check in `init` and `doctor` now correctly enforces this. Previously a user on Node 20 would pass the version check but crash later when `better-sqlite3` tried to load against the wrong ABI. CI matrix now tests Node 22 and 24.
  - **`doctor` no longer reports false positives** for `native-binding` and `embedder` checks when run from a bundled CLI inside a workspace (e.g. `node packages/cli/dist/cli.js doctor` against the local build). The probes fall back to a filesystem walk when `require` resolution fails for resolution reasons (not actual binding/load failures), so the doctor's verdict reflects reality instead of a require-graph quirk.
  - **Global flags accepted in any position.** `memento --format text init` and `memento init --format text` now both parse — previously the second form errored "unknown argument '--format' for 'init'". Same for `--db`, `--debug`. `--` separator semantics preserved (POSIX behavior pre-subcommand, pass-through after).
  - **`doctor` text renderer.** A flat ✓/✗ checklist with hint lines on failure and a one-line summary, instead of pretty-printed JSON. JSON path unchanged for pipes / `--format json`.
  - **`init` next-steps footer.** After printing the MCP snippets, `init` now points at `memento status` (one-screen summary) and `memento dashboard` (browser UI). Closes the journey loop instead of dropping the user mid-air.
  - **Help regrouped by purpose** — Setup / Verify & inspect / Operate / Help & teardown — instead of an alphabetical-ish wall.
  - **Banner color now matches the brand accent** (truecolor amber `rgb(232 184 108)`) instead of cyan. Aligns with the dashboard's `--accent` token and the new landing page.
  - **Dashboard launcher always prints the readiness URL** on stderr. Previously gated on `isStderrTTY`, which silently hid the URL from anyone running `memento dashboard --no-open` or with stderr redirected. The browser auto-open is best-effort; the printed URL is now the deterministic surface.
  - **Workspace install self-heal.** A new root `postinstall` (`scripts/ensure-better-sqlite3.mjs`) plus a workspace-detector in the CLI's end-user `postinstall` together prevent the "Could not locate the bindings file" trap that bit every fresh contributor. Source tracked in [`packages/cli/scripts/postinstall.mjs`](https://github.com/veerps57/memento/tree/main/packages/cli/scripts/postinstall.mjs).

  **Core** (`@psraghuveer/memento-core`)

  - **`memory.update` patch validation now hands out actionable redirects** instead of `Unrecognized key(s) in object`. Trying to `update({ patch: { content: ... } })` returns "cannot update `content` via memory.update — use memory.supersede"; same for `scope` (→ supersede) and `storedConfidence` (→ confirm or supersede). Delivers on the promise in AGENTS.md rule 13. Visible to AI assistants over MCP and to humans via `memento memory update`.

  **Embedder** (`@psraghuveer/memento-embedder-local`)

  - **Silenced the noisy `dtype not specified for "model"` warning** that transformers.js emitted on the first `embed()` call by pinning `dtype: 'fp32'` (the bge-\* family's training precision and the lib's own default). Behavior is identical; output is just quieter.

  **Dashboard** (`@psraghuveer/memento-dashboard`)

  - **Command palette feels smoother.** Debounce bumped 120ms → 250ms (the search-as-you-type sweet spot) and TanStack Query's `placeholderData: keepPreviousData` keeps prior results visible during refetch — no more "no matches" flash between keystrokes.
  - **Browser tab title** is now `memento — dashboard` (so users with both the marketing landing and the dashboard open can tell tabs apart) and the dashboard inherits the marketing landing's amber-`m` favicon for visual continuity.

- Updated dependencies [fe72460]
  - @psraghuveer/memento-core@0.5.1

## 0.1.1

### Patch Changes

- abc4216: Republish `@psraghuveer/memento-dashboard` to fix unresolved `workspace:^` specifiers in the published `package.json`.

  The initial `0.1.0` tarball was pushed manually, bypassing the changesets/CI publish path that rewrites pnpm's `workspace:` protocol to concrete semver ranges. As a result, `npm install -g @psraghuveer/memento` fails during dependency resolution because npm cannot resolve `workspace:^` for `@psraghuveer/memento-core` and `@psraghuveer/memento-schema`.

  This release is a no-op republish through the standard `changeset publish` flow, which produces a tarball with the workspace specifiers correctly rewritten to `^0.5.0` and `^0.4.0`.

## 0.1.0

### Minor Changes

- 0186ea0: Add `@psraghuveer/memento-dashboard` and the `memento dashboard` lifecycle command, and fix `memento init` failing when the user deleted only the main `memento.db` and left the SQLite WAL/SHM sidecars behind.

  ## The dashboard

  The new sibling package ships a local-first web UI for browsing, auditing, and curating your Memento store. Every named route lands as a real, functional view rather than a placeholder:

  - **`~/overview`** — landing page with active count, last write, vector retrieval status, open conflicts; kind breakdown; scope distribution.
  - **`~/memory`** — browse with filter chips (status, kind, pinned), search box wired to `memory.search` (FTS + vector when enabled), sort by `lastConfirmedAt`, decay-aware effective-confidence meter on every row, click-through to detail.
  - **`~/memory/$id`** — full content with sensitive-reveal toggle, supersession chain (up / down links), audit timeline (`memory.events` for that id with type-pill colour coding and per-event payload summary), provenance (created, last confirmed, stored vs. effective confidence), pin / confirm / forget actions.
  - **`~/conflicts`** — pending conflicts triaged as side-by-side memory cards with the four `conflict.resolve` actions (accept-new, accept-existing, supersede, ignore), evidence detail toggle, "re-scan last 24h" button.
  - **`~/audit`** — global activity feed via the id-less mode of `memory.events`, with type filters and deep links to each memory.
  - **`~/config`** — every registered config key grouped by dotted prefix, with current value, source layer (default / startup / runtime), mutability flag, per-key history (`config.history`), and a "copy as `memento config set` command" snippet for friction-free editing via the CLI.
  - **`~/system`** — doctor-style probes (database, vector retrieval, embedder, schema version, last write, version) plus a status-count tile row.

  Plus two cross-cutting pieces:

  - **Cmd-K command palette** — global ⌘K / Ctrl-K overlay with three modes: live `memory.search` as you type (debounced), `>` prefix for page navigation, `:` prefix for direct memory open by ULID. Read-only; destructive verbs stay on the detail page where the full context lives.
  - **Inline config editor** on `~/config` — typed editors per row (boolean → checkbox, number → number input, string → text input, otherwise → JSON textarea), inferred from the current value's runtime type. Save calls `config.set`; a "reset to default" button (visible when the source is `runtime`) calls `config.unset`. Engine-side `INVALID_INPUT` and `IMMUTABLE` errors render inline. Known immutable keys are read-only client-side as a UX shortcut, with the server's `IMMUTABLE` response as the canonical fallback.

  Architecturally, the dashboard is a third adapter on the existing command registry — every read and mutation goes through `executeCommand(...)` over the same surface MCP and CLI use. **No new MCP commands, no new registered CLI commands, no new config keys, no schema migrations.** Every view is a thin projection of one or more existing registry commands. See [ADR-0018](docs/adr/0018-dashboard-package.md) for the full rationale.

  Mobile-responsive from day one — every view stacks cleanly at narrow widths and the chrome collapses into a header-toggled drawer.

  The CLI gains one new lifecycle command, `memento dashboard`, that opens a `MementoApp`, mounts the Hono server bound to `127.0.0.1` on a random port, opens the user's browser, and blocks until SIGINT. The dashboard package is loaded via dynamic import so non-dashboard invocations (`memento serve`, `memento doctor`, etc.) do not pay the load cost. The lifecycle command accepts `--port`, `--host 127.0.0.1|localhost`, and `--no-open`; it is print-free on stdout during operation so machine-readable consumers can capture the post-shutdown snapshot via `--format json`.

  `KNOWN_LIMITATIONS.md` and `AGENTS.md` were updated to reflect the reversal of the prior "Web UI out of scope" stance. A TUI remains out of scope; the dashboard covers the same need.

  Stack:

  - Server: Hono on Node, in-process with the engine, with a generic `/api/commands/:name` surface that wraps every registered command.
  - UI: React + Tailwind + TanStack Query / Router, built by Vite into a static SPA. Theme is "warm dark default with one amber accent and one cyan-teal accent," monospace-leaning typography (Inter + JetBrains Mono via `@fontsource/*` for offline-first fonts).
  - Security: same-origin guard on mutating requests; the server binds to `127.0.0.1` only.

  ## Fix: `memento init` cleans orphan WAL/SHM sidecars

  Memento opens its database in WAL mode (`PRAGMA journal_mode = WAL`), which produces three files alongside the main `.db`: `memento.db-wal`, `memento.db-shm`, and (rarely) `memento.db-journal`. SQLite owns the sidecars and recovers from them on next open. If the user removed only `memento.db` (`rm memento.db`), the sidecars survived; the next open created an empty `.db`, set WAL mode, and SQLite tripped on a WAL whose contents did not match the new file. The recovery surfaced as a generic, misleading `STORAGE_ERROR: failed to open database … disk I/O error`.

  `memento init` now detects the half-deleted-store state — main `.db` absent, sidecars present — and removes the orphan sidecars before opening the database. The cleanup is observable in the snapshot as a new `stale-wal-sidecars` `InitCheck` so the operator sees what happened on their behalf rather than a silent surprise.

  The cleanup is sound only when the main `.db` is absent; when the file exists the sidecars belong to SQLite and the check is a no-op (regression test pins this so a future change cannot accidentally over-reach).

  Workaround for prior versions: `rm ~/.local/share/memento/memento.db-wal ~/.local/share/memento/memento.db-shm`, then re-run `memento init`.
