Sprint 3 closed: Memory Engine v0

Sprint 3 is closed.

The goal was to persist CEM entities, relations, and evidence locally — so project understanding survives between sessions instead of being rebuilt from scratch every handshake. The new @meronq/memory package, CLI commands, and an upgraded handshake now read and write engineering memory in SQLite.


What Sprint 3 delivered

Every item on the roadmap is checked off:

  • SQLite schema — entities, relations, evidence, access events, reinforcement events
  • Entity storage — full CEM snapshot sync from scanner output
  • Relation and evidence storage — foreign keys with cascade on full resync
  • Access events — recorded when handshake or tools touch entities
  • Reinforcement events — lifecycle signals aligned with ADR-0005
  • Handshake from memory — scan refreshes SQLite; CEM returned from the store

Local MCP is now v1.12.0 with memory_sync and a memory block in handshake.


New package: @meronq/memory

Memory is the persistence layer between scanner and AI clients:

Module Role
MemoryStore.open() Open .meronq/local/memory.db
syncFromSnapshot() Full CEM replace from live scan
loadSnapshot() Read entities, relations, evidence back as CEM
recordAccess() Log which entities were touched and by whom
recordReinforcement() Log lifecycle reinforcement (sync, handshake, …)
getStats() Count rows for diagnostics

Storage uses Node's built-in node:sqlite (DatabaseSync) — no native addon build step. Node ≥ 22.5 is required.

Example stats after a handshake:

entities: 21 · relations: 20 · evidence: 13 · access events: 21

Handshake pipeline: scan → sync → read

Sprint 2: handshake returned a live CEM snapshot from disk — read-only, ephemeral across process restarts in practice.

Sprint 3: handshake still scans the repo for freshness, but returns CEM from SQLite:

scanProject(root)
  └── indexToCemSnapshot()
        ├── syncFromSnapshot()  →  .meronq/local/memory.db
        ├── loadSnapshot()      →  handshake.cem
        ├── recordAccess()      →  access_events
        └── recordReinforcement()

The memory block in handshake confirms the source:

{
  "source": "sqlite",
  "path": ".meronq/local/memory.db",
  "stats": { "entities": 21, "relations": 20, "evidence": 13 }
}

The scanner remains the writer (via sync). Handshake is a reader with automatic refresh.


Stable entity IDs

Sprint 3 also hardens CEM identity across environments:

  • Project: project:c:/users/.../meronq — canonical path (Windows and WSL map to the same id)
  • Components: component:apps/cli, component:packages/memory
  • ADRs / commits: unchanged stable ids

This keeps SQLite rows consistent when the same repo is opened from different path spellings.


CLI: memory sync and stats

pnpm --filter meronq-cli build
node apps/cli/dist/index.js memory sync
node apps/cli/dist/index.js memory stats

memory sync scans the repo, saves project-index.json, and syncs CEM into SQLite. memory stats prints row counts without rescanning.


MCP tools

Tool Behavior
handshake Scan → sync → load CEM from memory + record access
project_scan Scan, save index, sync memory
memory_sync Explicit CEM sync into SQLite

Sprint 4 — GitHub Integration

Sprint 4 goal: connect Meronq to GitHub as the first external engineering system.

Planned work:

  • Read repository metadata, issues, and pull requests
  • Translate GitHub data into CEM entities and evidence
  • Generate issue and PR summaries
  • Draft documentation from Git history

Memory becomes the merge target for external translators — not just local scans.


Follow along

Sprint 0 gave us a durable repository. Sprint 1 gave us a shared language. Sprint 2 connected that language to real projects. Sprint 3 makes it stick.

← Back to blog