* refactor(plugins): ship each harness only the shared modules it imports `sync.mjs` grouped its lists by how they had grown rather than by what each target imports, so three modules travelled to plugins that never load them: `setup-wizard.mjs` reached dsh and zcode, neither of which ships a setup entry point, and `async-writer.mjs` reached opencode, which its host imports in-process and so has no hook subprocess to detach a write from. Split the groups by capability — the hook set, the wizard, the stdio proxy pair, the batch sender, the async write path — and give pi its own list, so a target's entry says which capabilities it has. `sync.test.mjs` kept its own copy of those lists, and the copy had drifted: it was missing `plugin-config`, `retryable`, `recall-compress-core` and `mcp-proxy-config`, so a stale vendored copy of any of them would have passed CI. Import the lists from `sync.mjs` instead, guarding the sync behind an entrypoint check, and add the check the duplicate could never make: every module in `lib/` is claimed by some target, and no target holds a banner-carrying file the sync no longer ships. * refactor(zcode): capture and shape the proxy config through the shared modules zcode sent every turn it parsed straight to the server: no length cap, no acknowledgement filter, no slash-command or injected-status guard — the four things `shouldCaptureText` does for every other harness. It re-derived the proxy config object by hand too, which is how it came to watch a narrower set of credential files than `buildMcpProxyConfig` watches. Route both through the shared modules. The dedup key stays keyed on the raw turn, so raising the cap later never resends a turn the server already holds in truncated form. * refactor(pi): log through the shared JSON Lines logger pi carried two copies of a hand-written `debugLog` — one in `index.ts`, one in `sync.ts` — that appended `<ISO timestamp> <message>` lines, read `OV_DEBUG_LOG` directly, and could only be turned on through the environment. Both are the shared `debug-log.mjs` with the structure taken out: no stage field, no JSON payload, no config knob, and a spelling of the variable no other harness uses. Use `createLogger` in both places, add a `debugLogPath` config key so the log can be turned on the way every other pi setting is, and read `OPENVIKING_DEBUG_LOG` with `OV_DEBUG_LOG` kept as a deprecated alias so existing setups keep logging. * fix(dsh): honor syncTurns on every write path, not just capture `syncTurns: false` gated `capture()` alone, so a read-only session still committed on `turn/end`, still committed again on dispose, and still replayed whatever an earlier session had queued. The toggle promised no writes and made three. Gate the commit paths and the replay on it too, and document it — the README and the integration page never mentioned the key at all. A backlog queued while capture was on stays on the queue for a session that still writes. * chore(plugins): bump the zcode and dsh plugin versions Both changed behavior in this branch — zcode now filters and truncates what it captures and watches the full credential set, dsh now writes nothing when `syncTurns` is off — and installed copies are keyed by version. * fix(plugins): make the installer and the plugin test matrix work in a git worktree `resolve_self_checkout` looked for `.git` as a directory. A linked worktree keeps it as a file pointing at the real gitdir, so `CHECKOUT_DIR` stayed empty there: `--source dev` resolved the marketplace to `/examples` and failed outright, and every other path fell through to `remote`, which clones from GitHub. On this machine that turned six of the thirteen installer tests red and made `release-marketplace.test.mjs` hang for twenty minutes on the network — long enough that the CPU starvation failed an unrelated recall timing assertion too. Test for existence instead of for a directory. Three test files were never run by CI, so nothing noticed that one of them had gone stale: the pi wiring assertion still required `new RecallManager(...)` to end at the session-id getter, which stopped being true when the recall ledger was added a fourth argument. Assert only the getter, and register all three files in `pr.yml` — the matrix now covers every `*.test.mjs` under `examples/`.
OpenViking Memory Plugin for ZCode
This package provides a ZCode lifecycle adapter for OpenViking long-term memory. It reuses the shared memory-plugin-shared runtime — no memory logic is duplicated. Only a thin ZCode adapter is new.
Requires an OpenViking server with
viking://~home-alias support. Recall targets the caller's own context space throughviking://~/memoriesandviking://~/skills; the uid-lessviking://user/memoriesshorthand is rejected by newer servers.
What it does
- SessionStart — injects user profile and preferences/entities into context.
- UserPromptSubmit — searches OpenViking for relevant memories and injects them.
- PreToolUse (
Read|Glob|Grep) — denies direct access toviking://URIs, redirects to MCP tools. - Stop — returns immediately, then captures incremental user/assistant turns and commits the OpenViking session in a detached worker.
ZCode does not support PreCompact/SessionEnd/SubagentStart/SubagentStop, so the commit-on-Stop strategy compensates for the absence of compact/end-of-session signals. The rollout file is the authoritative incremental transcript: stable host turnId values drive deduplication and allow a later Stop to recover missed turns. Hook stdin is only a fallback when the rollout file is unavailable.
Invariant: hook groups must OMIT the matcher key rather than writing
"matcher": "". Strict parsers treat an empty string as invalid and may silently drop the entire configuration source.
Install
Use the shared installer:
bash examples/memory-plugin-shared/install.sh --harness zcode
The installer detects ZCode via ~/.zcode/ or a zcode binary, merges hooks and MCP config into ~/.zcode/cli/config.json, and writes OpenViking credentials to ~/.openviking/ovcli.conf.
Architecture
The plugin vendors the shared runtime into scripts/shared/ via sync.mjs. The dispatcher (zcode-hook.mjs) branches on event name; three thin shim scripts set an environment variable and import the dispatcher, while the URI guard has its own entry point. Shared runtime modules provide recall, batching, pending queue, credential resolution, and MCP proxying; zcode-capture.mjs owns the ZCode-specific acknowledgement and cursor state transition.
See DESIGN.md for verified ZCode extension-surface facts and decision provenance.
Tests
node --test scripts/*.test.mjs