Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "memware",
"source": "./integrations/claude-code",
"description": "Session transcripts indexed for recall; a belief ledger that only remembers the latest truth. Hooks: SessionEnd/PreCompact sync, prompt-time belief context.",
"version": "0.2.6",
"version": "0.3.0",
"author": {
"name": "ericwalisko"
}
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ All notable changes to this project are documented here. The format follows

## [Unreleased]

## [0.3.0] - 2026-09-04

### Added
- **`--plain` output** for `recall`, `beliefs`, and `read`: tab-separated, id-first, one
record per line (tabs/newlines in values collapse to spaces), for piping to fzf/awk/cut.
- **Shell completions**: `memware completions {bash,zsh,fish}` prints a completion script
(generated from the parser via shtab). New `memware[shell]` extra.
- **`memware assert -`**: batch-assert tab-separated `subject/relation/value[/source]` lines
from stdin (blank lines and `#` comments skipped). Pairs with `beliefs --plain | cut` for
an `$EDITOR` round-trip.
- **Richer `--help`**: per-command examples and an ENVIRONMENT/FILES/ACCESSIBILITY epilog,
option defaults shown; duplicate global flags hidden from subcommand help.
- **XDG Base Directory support** for fresh installs: the memware home resolves to
`$MEMWARE_HOME`, else an existing `~/.memware`, else `$XDG_DATA_HOME/memware`, else
`~/.memware` — so an existing install is never migrated. The store path follows it.
- Man page (`docs/memware.1`), `docs/editor-integration.md` (emacs/vim/neovim/fzf recipes,
no plugins), and `docs/accessibility.md`.

### Changed
- **Accessibility**: memware emits no colour at all (so `NO_COLOR` is honoured by
construction and nothing depends on colour). The default human output is now labeled
(`field : value`, one per line, empty fields skipped) — linear for screen readers. New
`--ascii` flag and `MEMWARE_ASCII`, plus automatic fallback on a non-UTF-8 locale, replace
the `…` elision glyph with `...`.

## [0.2.6] - 2026-09-03

### Added
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,28 @@ surface, and does the stale value stay hidden? It needs no model, so results are
reproducible. The protocol for end-to-end comparisons — agent alone vs agent + memware —
is in [docs/eval.md](docs/eval.md).

## Editor and shell integration

memware ships no editor plugins — `--plain` (tab-separated, id-first) and `--json` are the
integration surface, and everything is a copy-paste recipe on top of them. Shell completions
come from `memware completions zsh|bash|fish` (needs the `[shell]` extra:
`uv tool install "memware[mcp,shell]"`). `--plain` pipes cleanly to `fzf`/`awk`/`cut`:

```bash
memware recall "which port does the api use" --plain | fzf --delimiter='\t' --with-nth=10
```

Emacs, Vim, Neovim, an `$EDITOR` bulk-edit round-trip, and completion install steps are in
[docs/editor-integration.md](docs/editor-integration.md).

## Accessibility

memware emits no colour at all (so `NO_COLOR` is honoured by construction), and no information
is ever carried by colour. Default output is screen-reader-friendly — labeled, one field per
line, blank line between records; `--plain` and `--json` are the stable machine formats; and
`--ascii` (auto-on in a non-UTF-8 locale) avoids glyphs a screen reader or terminal might
mangle. Full statement: [docs/accessibility.md](docs/accessibility.md).

## Status

Alpha. The schema may change before 1.0; the ledger semantics will not.
Expand Down
57 changes: 57 additions & 0 deletions docs/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Accessibility

memware is a command-line tool, so it is accessible by nature: keyboard-only, no mouse, no
GUI, and fully scriptable. Beyond that baseline, these are commitments about how it behaves.
They are CLI accessibility practices — not WCAG conformance, which is a standard for web UIs.

## No colour, ever

memware emits **no colour escape sequences at all**, under any flag, terminal, or theme. No
information is ever carried by colour, so output is legible under any palette, to colour-blind
users, and in monochrome terminals. `NO_COLOR` is honoured **by construction**: there is
nothing to disable, and nothing that could regress it.

## Screen-reader-friendly default output

The default human output for the record listers (`recall`, `beliefs`, `read`) is **labeled**:
one `field : value` per line, a blank line between records, and empty fields skipped. Nothing
is aligned into columns by eye. A screen reader reads it linearly and unambiguously — there is
no spatial layout to get lost in.

Two machine formats are available when you want structure instead of prose:

- `--plain` — tab-separated, one record per line, id-first, in a fixed column order; tabs and
newlines inside values collapse to spaces. Stable for piping to `cut`, `awk`, `fzf`, or a
braille display's filter.
- `--json` — machine-readable JSON for programmatic consumers.

## ASCII fallback for glyphs

The one non-ASCII glyph in normal output is the elision mark `…`. `--ascii` (or
`MEMWARE_ASCII=1`) replaces it with `...`, so it is not mispronounced by a screen reader or
mojibaked by a terminal that cannot render it. This also switches on **automatically** when the
locale is not UTF-8, so a stripped-down or remote environment gets ASCII without any flag.

## stdout / stderr separation

Data goes to **stdout**; diagnostics and progress go to **stderr**. Assistive tooling and
scripts can consume results without narration or warnings mixed in, and a screen reader driving
a pipeline hears only the data.

## Documented, meaningful exit codes

Exit status is `0` on success and `2` on a usage error (for example, `assert` missing its VALUE
without `-`, or `completions` when `shtab` is not installed). Automation and assistive wrappers
can branch on the status without parsing text.

## Plain-text integration surfaces

The MCP server's tool outputs and the prompt-time hook's injected context are plain text — the
same accessible, colourless, linear content, not a rendered widget.

## Feedback

If any output is hard to read with a screen reader, a braille display, or a non-UTF-8 terminal,
that is a bug. Please open an issue at
<https://github.com/ericwalisko/memware>. Accessibility regressions are treated as bugs, not
enhancements.
158 changes: 158 additions & 0 deletions docs/editor-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Editor and terminal integration

memware ships **no editor plugins**. It ships two machine-readable output modes —
`--plain` (tab-separated, one record per line, id-first) and `--json` — and those are the
integration surface. Everything below is a copy-paste recipe built on the CLI, so nothing
here goes stale against an editor's plugin API. See [Why no plugin](#why-no-plugin) at the end.

The `--plain` column order for `recall` is fixed:

```text
id kind score session when role subject relation source text
```

so `cut -f1` is the id, `cut -f4` is the session, and `cut -f10` is the text. The `read`
command takes a **session** id, so `cut -f4 | xargs memware read` reads a hit back in context.

## Shell completions

`memware completions {bash,zsh,fish}` prints a completion script (generated with `shtab`).
It needs the `[shell]` extra:

```bash
uv tool install "memware[mcp,shell]" # or: pipx install "memware[shell]" / pip install "memware[shell]"
```

Without `shtab` the command exits 2. Install recipes:

```bash
# zsh
memware completions zsh > ~/.zfunc/_memware
# then in ~/.zshrc: fpath+=~/.zfunc and autoload -Uz compinit && compinit

# bash
memware completions bash | sudo tee /etc/bash_completion.d/memware
# or a user dir: memware completions bash > ~/.local/share/bash-completion/completions/memware

# fish
memware completions fish > ~/.config/fish/completions/memware.fish
```

## fzf

Pick a hit interactively and read its session back:

```bash
memware recall "which port does the api use" --plain \
| fzf --delimiter='\t' --with-nth=10 \
| cut -f4 | xargs -r memware read
```

`--with-nth=10` shows only the text column in the picker; the full tab record is still on the
selected line, so `cut -f4` recovers the session for `read`.

A reusable function that echoes the chosen id and text (drop it in `~/.zshrc` or
`~/.bashrc`), passing any recall args through:

```bash
mw-recall() {
local hit
hit=$(memware recall "$@" --plain | fzf --delimiter='\t' --with-nth=10) || return
printf 'id\t%s\n' "$(printf '%s' "$hit" | cut -f1)"
printf 'text\t%s\n' "$(printf '%s' "$hit" | cut -f10)"
}
# mw-recall "api port" "gateway listen port"
```

## Emacs

A small command that reads a query, offers the hits with `completing-read`, and inserts the
chosen record's text at point:

```elisp
(defun memware-recall (queries)
"Recall from memware and insert the chosen record's text at point."
(interactive "sQueries: ")
(let* ((lines (split-string
(shell-command-to-string
(format "memware recall %s --plain"
(shell-quote-argument queries)))
"\n" t))
(choice (completing-read "hit: " lines)))
;; text is the 10th tab-separated field (0-based index 9)
(insert (nth 9 (split-string choice "\t")))))
```

For a grep-style buffer, point `grep-command` at recall's plain output and use `M-x grep`;
you get the hits in a `compilation-mode` buffer you can navigate as text (the records are
id-first tab lines, not `file:line`, so there is nothing to jump to):

```elisp
(setq grep-command "memware recall --plain ")
```

Consult users can wrap the same `memware recall … --plain` lines as a `consult--read` source
for live minibuffer recall.

## Vim

A user command that opens plain recall output in a scratch buffer:

```vim
command! -nargs=+ MemwareRecall call s:MemwareRecall(<q-args>)
function! s:MemwareRecall(args) abort
let l:out = system('memware recall ' . shellescape(a:args) . ' --plain')
new
setlocal buftype=nofile bufhidden=wipe noswapfile
call setline(1, split(l:out, "\n"))
endfunction
" :MemwareRecall api port
```

With [fzf.vim](https://github.com/junegunn/fzf.vim), pick a hit in a fuzzy window:

```vim
command! -nargs=+ MemwareFzf call fzf#run(fzf#wrap({
\ 'source': 'memware recall ' . shellescape(<q-args>) . ' --plain',
\ 'options': ['--delimiter', "\t", '--with-nth', '10']}))
```

## Neovim

The Lua equivalent, writing plain recall output into a scratch buffer:

```lua
vim.api.nvim_create_user_command('MemwareRecall', function(opts)
local out = vim.fn.system({ 'memware', 'recall', opts.args, '--plain' })
vim.cmd('new')
vim.bo.buftype = 'nofile'
vim.bo.bufhidden = 'wipe'
vim.bo.swapfile = false
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(out, '\n'))
end, { nargs = '+' })
-- :MemwareRecall api port
```

Telescope users can wrap `memware recall … --plain` as a custom finder over the same lines.

## Bulk-edit facts through `$EDITOR`

Dump the beliefs, edit them in your editor, and feed them back. `beliefs --plain` is id-first,
while `assert -` reads `subject<TAB>relation<TAB>value[<TAB>source]`, so drop the id and the
audit columns with `cut` first — fields 2,3,4,9 are exactly subject, relation, value, source:

```bash
memware beliefs --plain | cut -f2,3,4,9 > /tmp/f # -> subject relation value source
$EDITOR /tmp/f
memware assert - < /tmp/f
```

`assert -` skips blank lines and lines starting with `#`. This **asserts** every edited or
added row (each goes through the normal supersession rule); it does **not** delete — removing a
line from the file does not retract a belief.

## Why no plugin

The CLI is the stable contract. `--plain` and `--json` are the whole integration surface, so a
twenty-line recipe against them keeps working across editor releases in a way a plugin tracking
an editor's evolving API would not. There is nothing to update when your editor updates.
Loading
Loading