# memory-node (Milestone 2)

`memory-node` is a local MCP server for operational memory with SQLite storage.

## M1a Implemented

- MCP stdio runtime with 8 tools:
  - `memory_status`
  - `memory_add_entry`
  - `memory_search`
  - `memory_read_entry`
  - `memory_list_scopes`
  - `memory_list_topics`
  - `memory_list_tags`
  - `memory_invalidate_entry`
- SQLite core schema:
  - `projects`
  - `memory_entries`
  - `memory_tags`
  - `memory_entry_tags`
  - `memory_entries_fts` (FTS5, external `entry_id`)
- Project resolution priority:
  - explicit `project_id`
  - target project `.env` (`MEMORY_PROJECT_ID`)
  - git remote canonicalization
  - hashed `project_path` fallback
- Dedupe behavior:
  - with `dedupe=true`, exact duplicate in the same project returns `existing_entry_id`.

## M1a Explicit Exclusions

- No import/export.
- No MCP resources/prompts.
- No vector backend.
- No smoke wiring in `tests/smoke` yet (planned in M1b).
- No full `allowedRoots` hardening yet (planned in M1b).

## M1b Implemented

- Path policy hardening:
  - `project_path` and `source_path` validated through `allowedRoots` checks.
  - traversal/outside-root paths rejected with `INVALID_PARAM`.
- Audit log:
  - `audit_log` table initialized at startup.
  - write events recorded for add/search/read flows.
  - sensitive fields are redacted before persistence.
- Cross-project isolation:
  - default project mode isolation confirmed.
  - cross-project guard on read in project mode confirmed.
- Status surface:
  - `memory_status.features.audit_log = true`.
- Smoke coverage:
  - stdio smoke implemented in `tests/smoke/memory-node.smoke.mjs`.

## M1c Implemented

- Search quality:
  - predictable FTS ranking (`bm25`, tie-break by `updated_at DESC`).
  - snippet returned in search results with `>>>...<<<` highlighting.
  - stable `verification_hint` in search/read outputs.
- Fallback strategy when FTS is unavailable:
  - graceful degraded mode (server stays operational).
  - token-based LIKE fallback aligned with `docs-node` approach (escaped terms, AND semantics).
  - explicit warning in `memory_status` and `memory_search` when fallback LIKE is active.
- Validation coverage:
  - unit/integration tests include FTS and fallback-LIKE paths.
  - smoke test asserts search snippet and verification hint.

## M2 Implemented

- Project taxonomy:
  - `memory_list_scopes` returns project scopes with counts.
  - `memory_list_topics` returns project topics with optional `scope` filtering.
  - `memory_list_tags` returns project tags with optional `scope` and `topic` filtering.
  - listing tools default to `status="active"` and support supported status filters.
  - listing tools and `memory_status` are read-only and do not register project rows.
- Entry lifecycle:
  - `memory_invalidate_entry` soft-invalidates an entry in the resolved project.
  - invalidation is idempotent and preserves project isolation.
  - invalidated entries are excluded from default search and can be found with `status="invalidated"`.
- Search ranking:
  - FTS keeps `bm25` primary ordering and adds `importance`, `confidence`, and `updated_at` tie-breakers.
  - LIKE fallback uses the same deterministic tie-breakers after `like_score`.
- Validation coverage:
  - unit/integration tests cover taxonomy filters, invalidation, cross-project guards, and ranking tie-breakers.
  - stdio smoke covers the M2 tool flow and annotations.

## Scripts

- `npm run build`
- `npm test`
- `node ../tests/smoke/memory-node.smoke.mjs` (from `memory-node/`)

## Runtime Notes

- Config env:
  - `MEMORY_DB_PATH`
  - `MEMORY_AUDIT_LOG`
  - `MEMORY_MAX_ENTRY_BYTES`
  - `MEMORY_MAX_SEARCH_RESULTS`
  - `MEMORY_MAX_TAGS`
  - `MEMORY_MAX_TITLE_CHARS`
  - `MEMORY_MAX_SUMMARY_CHARS`
  - `MEMORY_MAX_SOURCE_REF_CHARS`
  - `MEMORY_MAX_SOURCE_URI_CHARS`
  - `MEMORY_MAX_PROJECT_ID_CHARS`
  - `MEMORY_MAX_SCOPE_CHARS`
  - `MEMORY_MAX_TOPIC_CHARS`
  - `MEMORY_MAX_TAG_CHARS`
- Default DB path is the user-local `~/.mcp-servers/memory/memory.sqlite`; set `MEMORY_DB_PATH` to override it.
- Read-only tools do not materialize project metadata. Project rows are created by write flows such as `memory_add_entry`.
- Avoid sharing `node_modules` between Windows and WSL: `sqlite3` uses a native binary for the platform that installed it.
- When runtime code changes, restart the MCP server process.

## Target Project `.env`

Optional project-local settings:

```dotenv
MEMORY_PROJECT_ID=customer/app
MEMORY_ALLOWED_ROOTS=
```

Project `.env` values take precedence over launcher env for project-domain settings.

## Example Tool Calls

Add a reusable decision:

```json
{
  "project_path": "/path/to/project",
  "scope": "backend",
  "topic": "auth-token-expiry",
  "title": "Auth token expiry comparison",
  "content": "Use a strict less-than comparison for the legacy token expiry check; verified in ticket 12345.",
  "source_type": "decision",
  "source_ref": "ticket:12345",
  "tags": ["auth", "token"],
  "dedupe": true
}
```

Search existing operational context:

```json
{
  "project_path": "/path/to/project",
  "query": "auth token expiry",
  "mode": "project",
  "source_type": "decision",
  "status": "active",
  "limit": 5
}
```

List project taxonomy:

```json
{
  "project_path": "/path/to/project",
  "status": "active"
}
```

Invalidate an obsolete memory entry:

```json
{
  "project_path": "/path/to/project",
  "entry_id": "entry-uuid",
  "reason": "Superseded by ticket 12345 follow-up."
}
```

Memory is operational context, not an authoritative source. Verify results against docs, code, tickets, or commits before acting.
