feat: replace sqlite with .confession storage and add cli mvp - #16
Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates the web backend’s persistence layer from Prisma/SQLite to a project-local FileStore (.confession/*.json), and introduces an MVP CLI plus extension-side config/ignore syncing to align all clients on the new storage contract.
Changes:
- Replaced Prisma/SQLite with a FileStore-backed “prisma-like” facade (file lock + atomic writes + one-time SQLite migration).
- Updated web routes/serializers and health/advice logic to tolerate FileStore record shapes (date normalization + typed casts).
- Updated extension to use root-aware ignore/config from
.confession/config.json, added watcher-based sync, and added aconfession-cliMVP.
Reviewed changes
Copilot reviewed 36 out of 39 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/server/db.ts | Implements FileStore snapshot persistence, locking, and legacy SQLite migration behind a prisma-like API. |
| web/src/server/db.test.ts | Updates persistence tests to exercise FileStore idempotency and transaction consistency. |
| web/src/server/routes/vulnerabilities.ts | Adjusts dedupe + serialization to handle FileStore records and date parsing. |
| web/src/server/routes/scan.ts | Hardens scan progress event normalization for FileStore/legacy shapes. |
| web/src/server/routes/export.ts | Adapts export serialization typing for new store shapes. |
| web/src/server/health-score.ts | Casts query results to expected health-score input shapes under FileStore. |
| web/src/server/advice-gate.ts | Aligns vulnerability dedupe typing under FileStore. |
| extension/src/ignore-file.ts | Adds .confession/config.json read/write + root-aware ignore resolution utilities. |
| extension/src/extension.ts | Merges project config over VS Code settings and watches .confession/config.json for sync. |
| extension/src/file-watcher.ts | Applies root-aware ignore rules for onSave incremental scans. |
| extension/src/webview.ts | Normalizes ignore config before persisting and syncs to .confession/config.json. |
| extension/src/ignore-file.test.ts | Adds unit tests for config normalization and root-aware ignore behavior. |
| extension/src/webview.test.ts | Extends message-dispatch tests to cover project-config sync calls. |
| confession-cli/bin/confession.js | Adds CLI commands for init/scan/list/status against the new FileStore. |
| confession-cli/package.json | Declares the CLI package and bin entry. |
| web/package.json | Removes Prisma scripts/deps in favor of FileStore. |
| turbo.json | Removes Prisma-related global env configuration. |
| pnpm-workspace.yaml | Adds confession-cli to the workspace. |
| .gitignore | Ignores .confession/ and keeps legacy SQLite artifacts ignored. |
| .github/workflows/ci.yml | Removes SQLite env requirement from CI. |
| README.md / AGENTS.md / .kiro/steering/* | Updates documentation/steering to reflect FileStore + CLI usage and contracts. |
| web/prisma/* | Removes Prisma schema and migrations from the repo. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const fallbackUsed = Boolean(task.fallbackUsed); | ||
| const createdAt = toDateOrNow(task.createdAt); | ||
| const updatedAt = toDateOrNow(task.updatedAt); | ||
| return { | ||
| id: task.id, | ||
| id: typeof task.id === 'string' ? task.id : '', |
There was a problem hiding this comment.
fallbackUsed is normalized via Boolean(...), which treats any non-empty string (including 'false') as true. If legacy/corrupted storage contains string values, this will incorrectly set fallbackUsed and expose fallback fields in the API. Prefer strict parsing: accept only boolean true (or the string 'true') as true, otherwise false.
| return { exists: false, config: cloneDefaultConfig() } | ||
| } | ||
|
|
||
| return { exists: true, config: cloneDefaultConfig() } |
There was a problem hiding this comment.
On JSON parse errors, readConfigFileByPath() returns exists: true with the default config. That makes callers treat the project config as present and override VS Code settings with defaults (and may later sync those defaults back to disk). Consider treating parse errors as exists: false (fallback to settings) or returning an explicit error flag so the UI can prompt the user to fix/remove the corrupted file.
| return { exists: true, config: cloneDefaultConfig() } | |
| // On JSON parse or other read errors, treat the config as non-existent so | |
| // callers can fall back to settings or prompt the user to fix the file. | |
| return { exists: false, config: cloneDefaultConfig() } |
| const parsed = new Date(value); | ||
| if (!Number.isNaN(parsed.getTime())) return parsed.toISOString(); | ||
| } | ||
| return new Date(0).toISOString(); | ||
| } |
There was a problem hiding this comment.
toIsoString() falls back to new Date(0).toISOString() when the value is missing/invalid. This can silently turn bad storage data into a misleading 1970-01-01... timestamp in API responses. Prefer returning null for invalid values (and making the field nullable) or throwing/logging at least for required fields like createdAt/updatedAt so corruption is visible.
| } catch { | ||
| return fallback | ||
| } |
There was a problem hiding this comment.
readJsonFile() returns the fallback on any error (including JSON parse errors and permission errors). If a storage file is corrupted/unreadable, a subsequent write can load empty defaults and then persist them, effectively wiping data. Consider only falling back on ENOENT/ENOTDIR and surfacing parse errors (throw or quarantine/backup + log) to avoid silent data loss.
| async function withReadClient<T>(callback: (client: ReturnType<typeof buildScopedClient>) => Promise<T>): Promise<T> { | ||
| const projectRoot = resolveProjectRoot() | ||
| await ensureBootstrapped(projectRoot) | ||
| const snapshot = await loadSnapshot(projectRoot) | ||
| return callback(buildScopedClient(snapshot)) |
There was a problem hiding this comment.
Reads (withReadClient) don’t take the file lock, while writes update multiple files in a snapshot. Even with atomic per-file renames, a reader can observe a mixed snapshot (some files old, some new). Consider locking reads too, or implementing a manifest/generation scheme so readers can detect and retry inconsistent versions.
| throw error | ||
| } | ||
| if (Date.now() >= deadline) { | ||
| throw new Error('Confession storage lock timeout') | ||
| } |
There was a problem hiding this comment.
If the process crashes while holding the lock, the .lock file will remain and all future writes will time out. Consider writing lock metadata (pid + timestamp) and treating old locks as stale (or using an OS-level advisory lock) so the store can recover after crashes.
摘要
.confession/*.json檔案儲存.confession/config.json(不再使用.confessionignore)confession-cli(init/scan/list/status)DATABASE_URL,並將.confession/納入.gitignore主要變更
web/src/server/db.ts:FileStore + 檔鎖 + 原子寫入 + SQLite 一次性遷移web/prisma/*與 Prisma 依賴/腳本extension/src/ignore-file.ts:改為讀寫.confession/config.jsonextension/src/extension.ts/extension/src/file-watcher.ts/extension/src/webview.ts:設定同步與 root-aware ignoreconfession-cli/bin/confession.js驗證
pnpm lintpnpm buildpnpm test