Files
Claude-Code-Monitor/docs/superpowers/plans/2026-03-16-remove-native-sqlite-dependency.md
T
nntrivi2001 57dc91585d feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 14:39:03 +07:00

241 lines
7.2 KiB
Markdown

# Remove Native SQLite Dependency — Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace `better-sqlite3` (native C++ module requiring Python/build tools) with a compatibility layer over Node.js built-in `node:sqlite`, so `npm install` succeeds on any machine without native compilation tools.
**Architecture:** Create `server/compat-sqlite.js` — a thin wrapper that gives `DatabaseSync` (from `node:sqlite`) the same API as `better-sqlite3`. Move `better-sqlite3` to `optionalDependencies` so it's preferred when prebuilds are available but doesn't block install. The `server/db.js` loader tries `better-sqlite3` first, falls back to the compat wrapper. Update minimum Node version to 22.
**Tech Stack:** Node.js `node:sqlite` (DatabaseSync), existing Express/WS server
---
## File Structure
| File | Action | Responsibility |
|------|--------|---------------|
| `server/compat-sqlite.js` | **Create** | Wrapper class: DatabaseSync → better-sqlite3 API |
| `server/db.js` | **Modify** (line 1) | Try better-sqlite3, fallback to compat wrapper |
| `scripts/clear-data.js` | **Modify** (line 8) | Same fallback import |
| `package.json` | **Modify** | Move better-sqlite3 to optionalDependencies, bump engines to >=22 |
| `server/__tests__/api.test.js` | **Modify** (lines 952-959) | Fix `db.pragma()` calls to work with both backends |
---
## Chunk 1: Core Implementation
### Task 1: Create `server/compat-sqlite.js`
**Files:**
- Create: `server/compat-sqlite.js`
- [ ] **Step 1: Write the compat wrapper**
The wrapper must bridge these API differences:
| better-sqlite3 | node:sqlite (DatabaseSync) |
|----------------|---------------------------|
| `new Database(path)` | `new DatabaseSync(path)` |
| `db.pragma("key = value")` | `db.exec("PRAGMA key = value")` |
| `db.pragma("key")` → value | `db.prepare("PRAGMA key").get()``{key: value}` |
| `db.pragma("key", { simple: true })` → value | same as above, extract single value |
| `db.transaction(fn)` → wrapper fn | manual `BEGIN`/`COMMIT`/`ROLLBACK` |
| `db.prepare(sql)` → stmt with `.run()`, `.get()`, `.all()` | identical API |
| `db.exec(sql)` | identical |
| `db.close()` | identical |
```js
// server/compat-sqlite.js
const { DatabaseSync } = require("node:sqlite");
class Database {
constructor(filePath) {
this._db = new DatabaseSync(filePath);
}
exec(sql) {
this._db.exec(sql);
return this;
}
pragma(str, options) {
if (str.includes("=")) {
this._db.exec(`PRAGMA ${str}`);
return undefined;
}
const row = this._db.prepare(`PRAGMA ${str}`).get();
if (!row) return undefined;
const keys = Object.keys(row);
if (options?.simple || keys.length === 1) return row[keys[0]];
return row;
}
prepare(sql) {
return this._db.prepare(sql);
}
transaction(fn) {
const db = this._db;
const wrapper = (...args) => {
db.exec("BEGIN");
try {
const result = fn(...args);
db.exec("COMMIT");
return result;
} catch (err) {
db.exec("ROLLBACK");
throw err;
}
};
return wrapper;
}
close() {
this._db.close();
}
}
module.exports = Database;
```
- [ ] **Step 2: Verify the wrapper works standalone**
Run: `node -e "const DB = require('./server/compat-sqlite'); const db = new DB(':memory:'); db.pragma('journal_mode = WAL'); db.exec('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)'); const s = db.prepare('INSERT INTO t (v) VALUES (?)'); console.log(s.run('hi')); console.log(db.prepare('SELECT * FROM t').all()); const tx = db.transaction((items) => { for (const i of items) s.run(i); }); tx(['a','b','c']); console.log(db.prepare('SELECT COUNT(*) as c FROM t').get()); db.close(); console.log('OK')"`
Expected: `OK` printed at the end with correct query results.
- [ ] **Step 3: Commit**
```bash
git add server/compat-sqlite.js
git commit -m "feat: add node:sqlite compat wrapper for better-sqlite3 API"
```
---
### Task 2: Update `server/db.js` to use fallback import
**Files:**
- Modify: `server/db.js:1`
- [ ] **Step 1: Replace the import**
Change line 1 from:
```js
const Database = require("better-sqlite3");
```
To:
```js
let Database;
try {
Database = require("better-sqlite3");
} catch {
Database = require("./compat-sqlite");
}
```
- [ ] **Step 2: Verify server starts**
Run: `node -e "process.env.DASHBOARD_DB_PATH = require('path').join(require('os').tmpdir(), 'test-fallback-' + Date.now() + '.db'); const { db, stmts } = require('./server/db'); console.log('stmts keys:', Object.keys(stmts).length); stmts.insertSession.run('test-1', 'Test', 'active', null, null, null); console.log(stmts.getSession.get('test-1')); db.close(); console.log('OK')"`
Expected: Prints statement count (39), session row, and `OK`.
- [ ] **Step 3: Commit**
```bash
git add server/db.js
git commit -m "feat: fallback to node:sqlite when better-sqlite3 unavailable"
```
---
### Task 3: Update `scripts/clear-data.js` to use fallback import
**Files:**
- Modify: `scripts/clear-data.js:8`
- [ ] **Step 1: Replace the import**
Change line 8 from:
```js
const Database = require("better-sqlite3");
```
To:
```js
let Database;
try {
Database = require("better-sqlite3");
} catch {
Database = require("../server/compat-sqlite");
}
```
- [ ] **Step 2: Commit**
```bash
git add scripts/clear-data.js
git commit -m "fix: use fallback sqlite import in clear-data script"
```
---
### Task 4: Update `package.json`
**Files:**
- Modify: `package.json`
- [ ] **Step 1: Move better-sqlite3 to optionalDependencies, bump engines**
Move `"better-sqlite3": "^11.7.0"` from `dependencies` to `optionalDependencies`.
Change engines from `"node": ">=18.0.0"` to `"node": ">=22.0.0"`.
- [ ] **Step 2: Commit**
```bash
git add package.json
git commit -m "chore: make better-sqlite3 optional, require Node >= 22"
```
---
### Task 5: Fix test pragma calls
**Files:**
- Modify: `server/__tests__/api.test.js:952-959`
- [ ] **Step 1: Fix pragma calls in Database Integrity tests**
The tests call `db.pragma("journal_mode", { simple: true })` and `db.pragma("foreign_keys", { simple: true })`. The compat wrapper supports `{ simple: true }`, so these should work as-is. However, WAL mode isn't available for in-memory databases (returns "memory"). The test creates a file-based DB via `TEST_DB`, so WAL should work.
No change needed — verify by running tests.
- [ ] **Step 2: Run full test suite**
Run: `node --test server/__tests__/api.test.js`
Expected: All tests pass.
- [ ] **Step 3: Run setup to verify npm install succeeds without Python**
Run: `npm run setup`
Expected: Install succeeds (better-sqlite3 may warn but won't fail since it's optional).
---
### Task 6: Update documentation
**Files:**
- Modify: `SETUP.md` (if it mentions better-sqlite3 or Python requirements)
- [ ] **Step 1: Check and update SETUP.md**
Remove any mentions of Python or build tools as requirements. Note that Node >= 22 is required.
- [ ] **Step 2: Commit all remaining changes**
```bash
git add -A
git commit -m "docs: update setup requirements for native-free SQLite"
```