Skip to content

FE-535: SQLite foundation and project persistence#11

Merged
lunelson merged 7 commits into
mainfrom
ln/fe-535-sqlite-persistence
Apr 2, 2026
Merged

FE-535: SQLite foundation and project persistence#11
lunelson merged 7 commits into
mainfrom
ln/fe-535-sqlite-persistence

Conversation

@lunelson
Copy link
Copy Markdown
Contributor

feat: SQLite foundation — project persistence and conversation resume

Replace in-memory chat with better-sqlite3 persistence. Messages survive
browser close/reopen. Server replays conversation history into the prompt
for multi-turn context. App factory pattern enables testable DB injection.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

refactor: introduce Role union type for message role field

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

refactor: replace SSE adapter module state with per-request translator factory

Eliminates shared mutable state (thinkingBlocks/textBlocks Sets) that would
corrupt output under concurrent requests. Each request now creates its own
translator instance via createTranslator().

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

refactor: extract pure helpers from chat handler

extractPrompt, formatHistory, and collectText are now named functions.
The handler reads as a clear pipeline: extract → persist → format →
stream → persist. Exports AIEvent type from sse-adapter for reuse.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

chore: add manual testing workflow to CLAUDE.md

Documents cmux + cdp-cli pattern for outer-loop UI verification.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

docs: evolve spec with turn-tree domain model, re-plan slices

Grill session produced a fundamentally different domain model: turn tree
as version history, decision/assumption dependency graphs, observer agent
extraction, phase-as-skill architecture, soft invalidation. SPEC.md
evolved with new domain decisions D1-D6, lexicon, and requirements.
PLAN.md re-sliced against the new model (14 items, 2 done).
Schema captured in docs/design/schema.dbml.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@linear
Copy link
Copy Markdown

linear Bot commented Mar 31, 2026

FE-535 SQLite foundation + project persistence

Replace Dolt with better-sqlite3. Schema: project, interview_exchange, spec_output. Auto-create DB on startup. Session CRUD. Resume via Claude Agent SDK resume option.

Copy link
Copy Markdown
Contributor Author

lunelson commented Mar 31, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link
Copy Markdown
Contributor Author

@CiaranMn this is still draft but FWIW the .agents/skills and /memory dirs in this branch will give you a closer idea of the harness I'm applying and how it works

@lunelson lunelson changed the title feat: SQLite foundation — project persistence and conversation resume FE-535: SQLite foundation and project persistence Apr 1, 2026
@lunelson lunelson marked this pull request as ready for review April 1, 2026 11:29
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 1, 2026

🤖 Augment PR Summary

Summary: Adds SQLite-backed persistence for projects and chat messages so conversations survive refresh/close and can be replayed for multi-turn context.

Changes:

  • Introduce better-sqlite3 and a new src/server/db.ts module (project + message tables, CRUD helpers, WAL mode)
  • Refactor server into an createApp() factory to enable DB injection/cleanup in tests
  • Add GET /api/projects/current and persist user/assistant messages during POST /api/chat
  • Hydrate the client chat state on mount by fetching persisted history and calling setMessages
  • Replace SSE adapter global mutable state with a per-request createTranslator() instance
  • Expand integration/unit tests to cover DB lifecycle, persistence, and history endpoints

Technical Notes: Multi-turn context is implemented by formatting stored history into the next prompt (stateless SDK calls), and the SSE translator is now request-scoped to avoid cross-request corruption.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/server/db.ts

export function createDb(path: string = ':memory:'): DB {
const db = new Database(path);
db.pragma('journal_mode = WAL');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createDb() enables WAL but doesn’t enable SQLite foreign-key enforcement; without PRAGMA foreign_keys = ON, the message.project_id REFERENCES project(id) constraint won’t actually be enforced, which can allow orphaned rows if future code paths insert messages before projects.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/app.ts

const project = getOrCreateProject(db);
const history = getMessages(db, project.id);
saveMessage(db, project.id, 'user', prompt);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saveMessage(..., prompt) runs even when extractPrompt() returns an empty string (e.g., malformed client request or empty messages array), which will persist blank messages and replay them into formatHistory() on subsequent turns.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/app.ts
res.end();
});

if (assistantText) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Claude SDK stream throws mid-response, assistantText may contain a partial answer (accumulated before the exception) and will still be persisted; consider whether you want to persist incomplete assistant output (or otherwise record that it errored) to avoid confusing “truncated” history on reload.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/db.ts
}

export function getOrCreateProject(db: DB, name = 'default'): Project {
const existing = db.prepare('SELECT * FROM project ORDER BY created_at DESC LIMIT 1').get() as Project | undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getOrCreateProject(db, name) ignores the name parameter once any project exists (it always returns the most recently created project), which could produce surprising behavior if/when multiple projects are introduced.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Contributor Author

lunelson commented Apr 2, 2026

Merge activity

  • Apr 2, 6:20 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 2, 6:21 AM UTC: Graphite rebased this pull request as part of a merge.
  • Apr 2, 6:22 AM UTC: @lunelson merged this pull request with Graphite.

@lunelson lunelson changed the base branch from ln/fe-534-walking-skeleton to graphite-base/11 April 2, 2026 06:20
@lunelson lunelson changed the base branch from graphite-base/11 to main April 2, 2026 06:20
lunelson and others added 7 commits April 2, 2026 06:21
Replace in-memory chat with better-sqlite3 persistence. Messages survive
browser close/reopen. Server replays conversation history into the prompt
for multi-turn context. App factory pattern enables testable DB injection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r factory

Eliminates shared mutable state (thinkingBlocks/textBlocks Sets) that would
corrupt output under concurrent requests. Each request now creates its own
translator instance via createTranslator().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
extractPrompt, formatHistory, and collectText are now named functions.
The handler reads as a clear pipeline: extract → persist → format →
stream → persist. Exports AIEvent type from sse-adapter for reuse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents cmux + cdp-cli pattern for outer-loop UI verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Grill session produced a fundamentally different domain model: turn tree
as version history, decision/assumption dependency graphs, observer agent
extraction, phase-as-skill architecture, soft invalidation. SPEC.md
evolved with new domain decisions D1-D6, lexicon, and requirements.
PLAN.md re-sliced against the new model (14 items, 2 done).
Schema captured in docs/design/schema.dbml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ments

SPEC.md: D14 (AI Elements), A16/A17, I7/I8, assistant-ui constraint,
tool call feedback loops

PLAN.md: slice 3b (rich chat UI) with dependency graph update

Skills: patch mode for ln-spec/ln-plan, cross-reference integrity
checklist for ln-spec, post-edit checklist for ln-plan, parallelism
awareness for ln-scope/ln-build

AGENTS.md: PR title and branch naming conventions

Amp-Thread-ID: https://ampcode.com/threads/T-019d4866-d870-730e-bd14-3bebb55a2b63
Co-authored-by: Amp <amp@ampcode.com>
@lunelson lunelson force-pushed the ln/fe-535-sqlite-persistence branch from e090dff to 9dffd2b Compare April 2, 2026 06:21
@lunelson lunelson merged commit 97df4dc into main Apr 2, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant