Testing Strategy¶
Testing should protect the project contracts before implementation details.
Local gates that build, clean, package, or execute packages/*/dist/ must run
sequentially. They intentionally share generated output, so parallel execution
can create false failures even when each command passes in isolation.
Test organization follows ADR 0007: package behavior tests stay near their packages, public contracts live with their owning package, and test-only shared helpers are extracted only after there are at least two real consumers.
Repeated validation work should become a script, shared fixture, or contract helper once it appears in more than one place. Keep exact public contracts such as the MCP tool set in one source of truth and import that source from tests, packaging validation, and runtime diagnostics instead of copying lists by hand.
Test Pyramid¶
- Core unit tests.
- Storage integration tests with temporary SQLite databases.
- CLI contract tests.
- MCP tool contract tests.
- End-to-end smoke tests.
Required MVP Tests¶
- init creates expected directories and database;
- init is idempotent;
- remember stores valid memories;
- remember rejects obvious secrets;
- secret scanning blocks common credential formats while allowing redacted examples;
- recall returns relevant FTS results;
- list filters by scope and tag;
- update records audit event;
- history returns only the requested memory's audit events;
- forget archives by default;
- hard delete requires explicit confirmation;
- bulk forget defaults to dry-run and isolates scope/tag filters;
- export produces valid versioned format;
- import dry-run reports planned changes;
- import preflight rejects the full request before writes and handles duplicate items consistently;
- SQLite mutations roll back memory, FTS, and audit writes after injected failures;
- FTS diagnostics detect missing, orphaned, duplicate, and mismatched derived rows without exposing content;
- explicit FTS repair preserves canonical memory, event, and relation rows, creates a validated owner-only restoreable backup, rejects overlapping or unsafe paths and altered schemas, removes temporary sidecars, and restores search consistency;
- multi-item imports are atomic when a later item fails;
- normal recall does not persist query text or usage metadata unless the core caller explicitly opts in, and opt-in recall events persist only a query hash plus bounded recall metadata rather than the full query;
- doctor reports tracked memory files;
- the privacy doctor profile exposes bounded counts and stable finding codes without local paths, raw config values, memory content, or secret fragments;
- MCP doctor reports aggregate store health without exposing memory content.
- staged npm artifacts complete real installed CLI and stdio MCP lifecycle
flows for capture suggestion, confirmed writes, recall hooks, the exact
public tool set, and healthy
memory.doctorresponses. - release-tool tests reject invalid SemVer and direct workflow input interpolation, and verify local npm credentials remain ignored.
Test Data¶
Use fake memory content only.
Never place real user facts, credentials, or local project secrets in fixtures.
Contract Tests¶
MCP and CLI should have snapshot-style contract tests for:
- input validation;
- output shape;
- error codes;
- warnings.
Snapshots should avoid timestamps and generated IDs unless normalized.
MCP protocol coverage connects an SDK Client and the Nuzo server through
InMemoryTransport. It asserts exact tool discovery, registered schema
defaults, representative JSON responses, and invalid-input rejection without
network access or a stdio subprocess.
Artifact validation reuses the MCP server's exported tool contract so new tools do not require repeated manual updates across test files and release scripts.
CLI process coverage runs the built dist/index.js entrypoint in subprocesses
and asserts stable success, operational, usage, and internal exit codes plus
stack-trace-free stderr.
Migration Tests¶
Each migration should be tested from:
- an empty store;
- the immediately previous schema;
- a small realistic fixture.
Migration tests verify an empty database, reopen a populated store to confirm
idempotency, and reject a fixture with a newer unsupported user_version.
Schema version 7 also opens an explicit version 6 fixture, backfills normalized
capture keys, creates the active exact-key index, and proves indexed
duplicate lookup after migration. Future schema bumps should add a fixture for
the immediately previous schema before changing schemaVersion.
Manual Smoke Test¶
Before a release:
nuzo memory init
nuzo memory remember "The user prefers local-first tools." --kind preference --tag example
nuzo memory recall "local-first"
nuzo memory list
nuzo memory export --path ./memories.memory.export.json
nuzo memory doctor
Recall-quality changes should also run:
The benchmark uses synthetic public data and validates relevance, noise, scope isolation, bounded results, ranking reasons, and local latency without telemetry, network calls, or embeddings. English recall is the primary quality group and has its own strict top-1, recall, and zero-noise envelope; other language cases protect compatibility and tokenization regressions.
Capture-intelligence changes should run:
The default profile preserves the v0.5.0 exact-duplicate baseline for
reproducibility. The current bounded relationship contract has a separate
explicit gate:
The capture benchmark uses public synthetic data and independently reports
English relationship quality, policy blocks, scope and archived isolation,
candidate/evidence bounds, latency, and zero memory or audit writes. The
bounded profile shipped in 0.6.0 and remains the regression gate for that
contract.
Relation read and governance changes should also run:
The first gate proves ordered response equivalence while bounding relation query growth at recall/list limits 50 and 200. The second repeats a content-free read-only governance report over sparse and relation-dense stores, checking stable ordering, bounded latency, expected candidate counts, and unchanged memory, relation, and audit-event counts.
Optional-semantics work should also run:
This benchmark compares FTS, a deterministic offline semantic candidate, and hybrid reciprocal-rank fusion. It reports the English quality bar separately from compatibility cases and treats scope isolation, archived exclusion, bounded output, zero writes, and no network as independent safety gates. The medium-store profile also gates scoped status, fallback, cold and warm hybrid recall, peak RSS, and authorized vector row counts. The benchmark encoder is test evidence, not a bundled runtime provider.
Smoke tests may set NUZO_DOCTOR_SKIP_GIT=1 so restricted environments do not
warn only because Git tracking inspection is unavailable. Tests should still
cover normal warning behavior for missing stores and tracked memory files.
MCP doctor tests should cover both ok and warning states. They must assert that diagnostics include aggregate counts, store readability/writability, and schema status, but not memory content.
Continuous Integration¶
GitHub Actions runs validation from:
The workflow runs on pull requests, pushes to main, and manual dispatch.
Node validation uses the lockfile on Node.js 22 LTS and 24 LTS and runs:
npm ci
npm run check
npm run release:check
npm test
npm run build
npm run benchmark:recall
npm run benchmark:capture
npm run benchmark:capture -- --expect bounded
npm run benchmark:relations
npm run benchmark:governance
npm run benchmark:semantics
npm run benchmark:semantics -- --store-size medium
npm run package:plugins
npm run validate:npm
npm run smoke:cli
npm run smoke:host-hooks
npm run smoke:claude-code-plugin
npm run smoke:codex-plugin
The one-line installer has an optional Docker smoke gate:
It runs docs/install.sh in ephemeral Linux containers with representative
POSIX shells and Node/npm states. Deterministic success scenarios use fake
npm and nuzo commands so the gate proves the installer invokes
npm install --global @nuzo/memory@... without configuring Codex or Claude
Code. Release scenarios also perform a real global npm install inside Node 22
and Node 24 containers for the current public package. Failure scenarios cover
missing Node.js, unsupported Node.js, unsupported npm, and invalid installer
versions. Containers use --rm and a Nuzo-specific label; the harness removes
only containers with that label if cleanup is needed.
On Node.js 24, CI additionally installs the tracked repository marketplaces
with pinned Codex and Claude Code CLIs and runs npm run smoke:host-canary.
Published-package smokes remain post-release checks because they require the
new version to exist in the npm registry.
Published host canaries create their SQLite fixture with the published CLI and
inspect post-hook history with that same runtime. This keeps the source schema
out of a public-version continuity test and prevents false failures after a
new migration lands on main.
Installed artifact validation also runs a cost-conscious OS/architecture matrix on Node.js 22 LTS and 24 LTS:
Each matrix lane runs:
smoke:os-artifacts builds staged npm tarballs, installs the staged
active @nuzo/memory-core, @nuzo/memory, and, from 1.1.0,
@nuzo/memory-mcp packages into a temporary project,
exercises CLI session continuity, MCP stdio session continuity, installed host
hook doctor diagnostics, and generated Codex and Claude Code plugin command
forms. This is the release-blocking OS matrix documented in
Runtime Support.
Manual dispatch can also run a release rehearsal job. It uses Node.js 24 and executes:
Manual dispatch requires an explicit rehearsal version.
Release-state validation confirms that root/workspace versions, Nuzo internal dependency pins, lockfile workspace entries, CLI version output, MCP server version metadata, and changelog structure stay aligned.
The npm artifact validation installs generated core and Nuzo package tarballs into a temporary project. It exercises the installed CLI workflow, read-only capture suggestions, confirmed writes, recall, and exit codes, then confirms the installed MCP binary starts and supports the same suggestion-to-recall lifecycle over stdio. It does not publish packages or require npm credentials.
Documentation Drift Gates¶
User-facing documentation is part of the release contract. CI and Pages run:
That command combines three bounded gates:
tools/docs-contracts.test.mjsderives release versions, MCP tool names and count, supported Node.js lines, current-versus-upcoming setup language, npm package guidance, and primary navigation boundaries from canonical sources.tools/docs-link-check.mjsvalidates Markdown local links, anchors, and deduplicated external links with a short timeout and an explicit ignore policy for dynamic historical evidence such as old GitHub Actions run URLs and generated badge images.tools/docs-snippet-check.mjsvalidates shell snippets withbash -nwithout executing them, keeps first-use setup/update snippets aligned across README, docs, and npm README entry points, and protects a small set of public product terms such asNuzo,Codex, andClaude Code.
Use NUZO_DOCS_LINK_CHECK_EXTERNAL=0 npm run docs:links only for local
diagnosis when the network is unavailable. Do not use that override in CI or as
release evidence. If an external site is rate-limited, historical, or dynamic,
add the narrowest ignore rule to tools/docs-link-check.mjs with a comment or
surrounding code that explains the category. Do not ignore ordinary broken
documentation links.
The recall benchmark is a required CI gate, not only a local release check. It
protects the canonical FTS relevance envelope before host lifecycle tests reuse
recall results. The capture benchmark runs twice in CI: the default baseline
profile preserves the historical exact-duplicate behavior, while
--expect bounded is the current bounded relationship contract gate.
The relation hydration and governance benchmarks independently gate query
growth, output equivalence, deterministic content-free candidate ordering,
bounded dense-store latency, and proof that the governance report writes no
store state.
The host hook continuity smoke uses an isolated SQLite store populated only
with synthetic data. Its 0.2.1 matrix covers 75 memories and 53 scenarios,
including global and project scopes, autoload bootstrap, exact topical tags,
archived and unrelated records, bounded output, fail-open input handling,
common preference/fact/instruction/decision workflows, and prompts in 14
languages (English plus 14 non-English languages). Representative ephemeral
Codex sessions complement this deterministic test before release; Claude Code
remains covered by artifact and schema validation when its authenticated host
CLI is unavailable locally.
The NUZO-37 host canary is the smallest cross-host lifecycle proof. It creates
one fake user:default instruction memory tagged autoload, runs separate
fresh SessionStart hook invocations through the generated Codex and Claude
Code plugin artifacts against the same SQLite store, and asserts both hosts
receive the same attributed record inside the untrusted-memory boundary. It
also checks that unrelated UserPromptSubmit recall does not repeat the
autoload canary and that hook execution creates no audit writes. This proves
Nuzo delivery, not model obedience: hosts may use or ignore recalled memory
according to their own instruction hierarchy.
For host-native packaging confidence, run:
This additionally installs the tracked Nuzo marketplace and plugin under an
isolated CODEX_HOME and isolated Claude Code home. Both hosts must discover
and enable nuzo@nuzo-memory; Claude Code also validates the repository
marketplace schema. It still separates plugin and hook delivery from LLM
response compliance.
The published CLI and MCP smokes install @nuzo/memory into a temporary npm
prefix and validate session continuity through separate nuzo and stdio server
processes. The published optional-semantics smoke validates that a default
install contains no Transformers.js runtime or model files, that hybrid recall
falls back visibly without a sidecar, and, when NUZO_SEMANTIC_MODEL_PATH is
set, that the published CLI can install the exact optional peer, rebuild a
derived sidecar, and recall a paraphrased memory through semantic and hybrid
mode. These are post-release confidence checks for package users and host
plugin installs, not replacements for staged artifact validation before
publication.
Starting with 1.1.0, the staged npm matrix also installs
@nuzo/memory-mcp, verifies its canonical mcpName and single executable,
and opens an MCP SDK session through memory-mcp. npm run registry:check
guards the repository, package, and version boundary. npm run
registry:validate adds exhaustive validation with the checksum-pinned
official Registry publisher; it never authenticates or publishes.
The Codex plugin artifact smoke regenerates the release-layout plugin, verifies
the host-facing metadata loads as Nuzo, reads its bundled MCP config, and
validates continuity through staged npm tarballs before publication. It still
asserts the version-pinned command in the generated config. After publication,
NUZO_PLUGIN_SMOKE_PUBLISHED=1 npm run smoke:codex-plugin validates the exact
public command that the plugin exposes. Published hook canaries launch from the
repository root, while npm resolution is anchored at the plugin root, so a
user's npm workspace cannot shadow the pinned runtime. Codex MCP uses . as a
plugin-relative cwd; hook commands use ${CLAUDE_PLUGIN_ROOT}.
The continuity assertion covers the 0.2.0 Codex task-start recall contract:
a first MCP session writes a fake confirmed memory, a later session calls
memory.recall_hook, the remembered content is returned, and the hook reports
memory_writes: false plus capture_suggestions: false without adding recall
audit events.
The same smoke covers the inferred capture confirmation contract: a ready
memory.suggest_capture draft is not visible to recall before confirmation, a
rejected draft is never persisted, a confirmed draft is written through
memory.confirm_capture, and a later equivalent suggestion is reported as a
duplicate.
For update safety, the smoke also updates a confirmed capture with
memory.confirm_capture, target_memory_id, and expected_revision, then
verifies a stale update returns MEMORY_REVISION_CONFLICT instead of
overwriting the newer memory.
The Claude Code plugin artifact smoke performs the same release-layout
continuity validation while also resolving the ${CLAUDE_PLUGIN_ROOT} cwd
placeholder against an isolated temporary plugin install path. Set
NUZO_PLUGIN_SMOKE_PUBLISHED=1 after release to exercise its exact public
version-pinned command.
Documentation validation installs requirements-docs.txt and runs:
CI does not deploy GitHub Pages and only receives read access to repository contents.
The matrix is the supported Node.js policy for the MVP. The package.json
engine declarations provide a minimum version guard, while CI defines the
major versions Nuzo actively claims and tests.
npm audit --audit-level=moderate remains a dependency-change and release gate. Advisory changes outside the repository should not make ordinary pull requests nondeterministically fail.