Make SQLite the default local store - #25
Merged
Merged
Conversation
Create and migrate a gitignored local SQLite database during setup, persist normalized reports and operational state, route hot reads/search through SQLite, add recovery/retention tooling, cross-platform tests, benchmarks, and Setup status UI. Closes the implementation requirements tracked in #20 pending CI.
Use FTS5 where available and preserve identical literal search behavior on Node SQLite bundles that omit the module, including Linux and macOS CI.
Capture Express child stdout and stderr in the cross-platform integration test so Linux startup failures include the underlying exception.
Treat stdin EPIPE during optional model discovery as a soft failure so headless Linux startup remains healthy when the Copilot CLI exits early.
34 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes SQLite the default local operational store for Content Scout, wiring the web UI and CLI tooling to create, migrate, import, and serve report/search/dashboard data from a local .local/state/content-scout.db using Node’s built-in node:sqlite.
Changes:
- Added a SQLite-backed artifact store with migrations (schema v8), reconciliation/import, search (FTS5 + fallback), run history, normalized index persistence, and retention/backup/export tooling.
- Updated the web UI server to initialize and query via the SQLite store for listings/search/dashboard hydration, plus added a storage status endpoint and Setup UI status panel.
- Raised the Node.js requirement to 22.13+ and expanded CI coverage with a cross-platform SQLite test matrix and benchmarks/docs.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/web-ui/test/sqlite-store.test.js | New unit/integration coverage for SQLite store behaviors (import/search/backup/retention/migrations/etc.). |
| tools/web-ui/test/sqlite-server.test.js | New end-to-end Express test verifying SQLite initialization, offline reads, and restart hydration. |
| tools/web-ui/test/copilot-models.test.js | Test ensuring ACP model discovery treats EPIPE as a soft failure. |
| tools/web-ui/server.js | Swaps listings/search/runs/index hydration to SQLite-backed store and adds /api/storage/status. |
| tools/web-ui/README.md | Documents Node 22.13+ requirement and storage commands/behavior. |
| tools/web-ui/public/styles.css | Adds styles for Setup storage status panel. |
| tools/web-ui/public/index.html | Adds Setup “Local storage” status section + refresh control. |
| tools/web-ui/public/app.js | Fetches and renders SQLite storage status in Setup. |
| tools/web-ui/package.json | Bumps Node engine to >=22.13.0 and adds npm run storage. |
| tools/web-ui/lib/copilot-models.mjs | Exposes injectable ACP spawn + timeout and handles stdin EPIPE as soft failure. |
| tools/storage.mjs | New CLI for storage init/import/status/verify/backup/restore/export/retention. |
| tools/lib/sqlite-store.mjs | New SQLite store implementation: migrations, reconciliation, search, retention, backups, normalized snapshots. |
| tools/lib/report-index.mjs | Allows injecting stored report JSON + sentiment overrides to avoid filesystem reads. |
| tools/lib/paths.mjs | Adds canonical CONTENT_SCOUT_DB_FILE and allows SCOUT_REPO_ROOT override. |
| tools/benchmark-storage.mjs | New benchmark harness comparing legacy file search vs SQLite + server cold path timings. |
| README.md | Documents Node requirement + first-start SQLite import behavior. |
| docs/SQLITE-BENCHMARKS.md | New benchmark documentation and interpretation notes. |
| docs/ARCHITECTURE.md | Adds local storage architecture section describing SQLite contract and commands. |
| CHANGELOG.md | Adds 0.32.0 entry detailing SQLite default store, commands, validation notes. |
| .github/workflows/ci.yml | Updates Node version and adds cross-platform SQLite storage job. |
Comment on lines
+437
to
+446
| const fullPath = path.join(absoluteDirectory, name); | ||
| const stat = statSync(fullPath); | ||
| const relativePath = posixPath(path.relative(this.repoRoot, fullPath)); | ||
| seen.add(relativePath); | ||
| const prior = existing.get(relativePath); | ||
| if (prior && Number(prior.mtime_ms) === Math.trunc(stat.mtimeMs) && Number(prior.size_bytes) === stat.size) { | ||
| unchanged++; | ||
| continue; | ||
| } | ||
| const content = readFileSync(fullPath, 'utf8'); |
Comment on lines
+555
to
+563
| const fullPath = path.join(absoluteDirectory, name); | ||
| const stat = statSync(fullPath); | ||
| seen.add(name); | ||
| const prior = existing.get(name); | ||
| if (prior && Number(prior.mtime_ms) === Math.trunc(stat.mtimeMs) && Number(prior.size_bytes) === stat.size) { | ||
| unchanged++; | ||
| continue; | ||
| } | ||
| const content = readFileSync(fullPath, 'utf8'); |
Comment on lines
+1325
to
+1342
| const placeholders = kinds.map(() => '?').join(', '); | ||
| const useFts = !options.regex && this.fts5Enabled; | ||
| const parameters = options.regex || !useFts | ||
| ? kinds | ||
| : [quotedFtsQuery(value), value, ...kinds]; | ||
| const sql = options.regex || !useFts | ||
| ? `SELECT kind, name, path, mtime_ms, content FROM artifacts WHERE kind IN (${placeholders}) ORDER BY mtime_ms DESC` | ||
| : ` | ||
| WITH candidates(id) AS ( | ||
| SELECT rowid FROM artifacts_fts WHERE artifacts_fts MATCH ? | ||
| UNION | ||
| SELECT id FROM artifacts WHERE instr(lower(content), lower(?)) > 0 | ||
| ) | ||
| SELECT kind, name, path, mtime_ms, content | ||
| FROM artifacts | ||
| WHERE id IN (SELECT id FROM candidates) AND kind IN (${placeholders}) | ||
| ORDER BY mtime_ms DESC | ||
| `; |
Comment on lines
+836
to
+839
| const kind = path.resolve(dir) === path.resolve(SOCIAL_DIR) ? 'social-posts' : 'reports'; | ||
| const stored = artifactStore.readArtifact(kind, name); | ||
| if (!stored) throw new Error('not found'); | ||
| const raw = stored.content; |
Co-authored-by: jaydestro <2974195+jaydestro@users.noreply.github.com>
Co-authored-by: jaydestro <2974195+jaydestro@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.local/state/content-scout.dbautomatically during web UI setup using built-innode:sqliteStorage contract
node:sqliteMigration and rollback
node tools/storage.mjs verify|backup|restore|export|retentionprovides recovery and portability controlsValidation
docs/SQLITE-BENCHMARKS.mdCloses #20