@hivehub/rulebook 4.3.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/rulebook-decision-create.md +55 -0
- package/.claude/commands/rulebook-decision-list.md +15 -0
- package/.claude/commands/rulebook-knowledge-add.md +41 -0
- package/.claude/commands/rulebook-knowledge-list.md +15 -0
- package/.claude/commands/rulebook-learn-capture.md +48 -0
- package/.claude/commands/rulebook-learn-list.md +13 -0
- package/.claude/commands/rulebook-task-archive.md +24 -0
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +53 -10
- package/dist/cli/commands.d.ts +32 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +257 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/agent-template-engine.d.ts +51 -0
- package/dist/core/agent-template-engine.d.ts.map +1 -0
- package/dist/core/agent-template-engine.js +273 -0
- package/dist/core/agent-template-engine.js.map +1 -0
- package/dist/core/complexity-detector.d.ts +36 -0
- package/dist/core/complexity-detector.d.ts.map +1 -0
- package/dist/core/complexity-detector.js +254 -0
- package/dist/core/complexity-detector.js.map +1 -0
- package/dist/core/decision-manager.d.ts +25 -0
- package/dist/core/decision-manager.d.ts.map +1 -0
- package/dist/core/decision-manager.js +188 -0
- package/dist/core/decision-manager.js.map +1 -0
- package/dist/core/generator.d.ts +1 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +47 -3
- package/dist/core/generator.js.map +1 -1
- package/dist/core/indexer/background-indexer.d.ts +9 -2
- package/dist/core/indexer/background-indexer.d.ts.map +1 -1
- package/dist/core/indexer/background-indexer.js +99 -83
- package/dist/core/indexer/background-indexer.js.map +1 -1
- package/dist/core/knowledge-manager.d.ts +24 -0
- package/dist/core/knowledge-manager.d.ts.map +1 -0
- package/dist/core/knowledge-manager.js +173 -0
- package/dist/core/knowledge-manager.js.map +1 -0
- package/dist/core/learn-manager.d.ts +29 -0
- package/dist/core/learn-manager.d.ts.map +1 -0
- package/dist/core/learn-manager.js +159 -0
- package/dist/core/learn-manager.js.map +1 -0
- package/dist/core/rule-engine.d.ts +64 -0
- package/dist/core/rule-engine.d.ts.map +1 -0
- package/dist/core/rule-engine.js +333 -0
- package/dist/core/rule-engine.js.map +1 -0
- package/dist/core/task-manager.d.ts +4 -0
- package/dist/core/task-manager.d.ts.map +1 -1
- package/dist/core/task-manager.js +39 -24
- package/dist/core/task-manager.js.map +1 -1
- package/dist/core/workspace/project-worker.d.ts +9 -0
- package/dist/core/workspace/project-worker.d.ts.map +1 -1
- package/dist/core/workspace/project-worker.js +27 -0
- package/dist/core/workspace/project-worker.js.map +1 -1
- package/dist/index.js +250 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +758 -22
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/dist/memory/hnsw-index.d.ts +3 -1
- package/dist/memory/hnsw-index.d.ts.map +1 -1
- package/dist/memory/hnsw-index.js +121 -18
- package/dist/memory/hnsw-index.js.map +1 -1
- package/dist/memory/memory-manager.d.ts +5 -0
- package/dist/memory/memory-manager.d.ts.map +1 -1
- package/dist/memory/memory-manager.js +34 -8
- package/dist/memory/memory-manager.js.map +1 -1
- package/dist/memory/memory-store.d.ts +15 -3
- package/dist/memory/memory-store.d.ts.map +1 -1
- package/dist/memory/memory-store.js +327 -272
- package/dist/memory/memory-store.js.map +1 -1
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -3
- package/templates/agents/compiler/codegen-debugger.md +34 -0
- package/templates/agents/compiler/stdlib-engineer.md +28 -0
- package/templates/agents/compiler/test-coverage-guardian.md +31 -0
- package/templates/agents/context-intelligence.md +52 -0
- package/templates/agents/game-engine/cpp-core-expert.md +35 -0
- package/templates/agents/game-engine/render-engineer.md +22 -0
- package/templates/agents/game-engine/shader-engineer.md +38 -0
- package/templates/agents/game-engine/systems-integration.md +43 -0
- package/templates/agents/generic/code-reviewer.md +39 -0
- package/templates/agents/generic/docs-writer.md +25 -0
- package/templates/agents/generic/project-manager.md +34 -0
- package/templates/agents/generic/researcher.md +34 -0
- package/templates/agents/generic/test-engineer.md +40 -0
- package/templates/agents/mobile/platform-specialist.md +22 -0
- package/templates/agents/mobile/ui-engineer.md +22 -0
- package/templates/agents/web-app/api-designer.md +22 -0
- package/templates/agents/web-app/backend-engineer.md +30 -0
- package/templates/agents/web-app/database-engineer.md +22 -0
- package/templates/agents/web-app/frontend-engineer.md +29 -0
- package/templates/agents/web-app/security-reviewer.md +32 -0
- package/templates/commands/rulebook-decision-create.md +55 -0
- package/templates/commands/rulebook-decision-list.md +15 -0
- package/templates/commands/rulebook-knowledge-add.md +41 -0
- package/templates/commands/rulebook-knowledge-list.md +15 -0
- package/templates/commands/rulebook-learn-capture.md +48 -0
- package/templates/commands/rulebook-learn-list.md +13 -0
- package/templates/core/AGENT_AUTOMATION.md +8 -0
- package/templates/core/DECISIONS.md +38 -0
- package/templates/core/KNOWLEDGE.md +49 -0
- package/templates/core/RULEBOOK.md +12 -0
- package/templates/core/TIER1_PROHIBITIONS.md +154 -0
- package/templates/core/TOKEN_OPTIMIZATION.md +49 -0
- package/templates/git/GIT_WORKFLOW.md +35 -0
- package/templates/rules/follow-task-sequence.md +36 -0
- package/templates/rules/git-safety.md +29 -0
- package/templates/rules/incremental-tests.md +29 -0
- package/templates/rules/no-deferred.md +31 -0
- package/templates/rules/no-shortcuts.md +30 -0
- package/templates/rules/research-first.md +30 -0
- package/templates/rules/sequential-editing.md +21 -0
- package/templates/rules/session-workflow.md +24 -0
- package/templates/rules/task-decomposition.md +32 -0
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite Storage Layer using
|
|
2
|
+
* SQLite Storage Layer using better-sqlite3 (native)
|
|
3
3
|
*
|
|
4
4
|
* Provides CRUD operations for memories and sessions,
|
|
5
5
|
* with FTS5 full-text search (BM25 ranking).
|
|
6
|
+
*
|
|
7
|
+
* Replaced sql.js (WASM) in v5.0 to eliminate:
|
|
8
|
+
* - Full DB export() copies on every save (100-500MB allocations)
|
|
9
|
+
* - WASM JIT warmup delay (~300ms on init)
|
|
10
|
+
* - Event loop blocking on synchronous writeFileSync of entire DB
|
|
11
|
+
*
|
|
12
|
+
* better-sqlite3 writes directly to disk via SQLite's WAL journal.
|
|
13
|
+
* No export(), no manual saveToDisk(), no memory copies.
|
|
6
14
|
*/
|
|
7
|
-
import { existsSync, mkdirSync,
|
|
15
|
+
import { existsSync, mkdirSync, statSync, writeFileSync } from 'fs';
|
|
8
16
|
import { join } from 'path';
|
|
9
|
-
const AUTO_SAVE_THRESHOLD = 50;
|
|
10
17
|
export class MemoryStore {
|
|
11
18
|
db = null;
|
|
12
19
|
dbPath;
|
|
13
|
-
writeCount = 0;
|
|
14
20
|
initialized = false;
|
|
15
21
|
constructor(dbPath) {
|
|
16
22
|
this.dbPath = dbPath;
|
|
@@ -23,223 +29,217 @@ export class MemoryStore {
|
|
|
23
29
|
if (!existsSync(dir)) {
|
|
24
30
|
mkdirSync(dir, { recursive: true });
|
|
25
31
|
}
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.db =
|
|
32
|
+
// Try better-sqlite3 (native, fast) first.
|
|
33
|
+
// Falls back to sql.js (WASM, portable) if native addon isn't available
|
|
34
|
+
// (e.g. no C++ build tools on Windows).
|
|
35
|
+
try {
|
|
36
|
+
const Database = (await import('better-sqlite3')).default;
|
|
37
|
+
this.db = new Database(this.dbPath);
|
|
38
|
+
this.db.pragma('journal_mode = WAL');
|
|
39
|
+
this.db.pragma('foreign_keys = ON');
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
catch {
|
|
42
|
+
// better-sqlite3 not available — fall back to sql.js (WASM)
|
|
43
|
+
console.error('[MemoryStore] better-sqlite3 unavailable, falling back to sql.js (slower but portable)');
|
|
44
|
+
const { default: initSqlJs } = await import('sql.js');
|
|
45
|
+
const SQL = await initSqlJs();
|
|
46
|
+
let rawDb;
|
|
47
|
+
if (existsSync(this.dbPath)) {
|
|
48
|
+
const { readFileSync } = await import('fs');
|
|
49
|
+
rawDb = new SQL.Database(readFileSync(this.dbPath));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
rawDb = new SQL.Database();
|
|
53
|
+
}
|
|
54
|
+
// Wrap sql.js to match better-sqlite3 API surface used in this file
|
|
55
|
+
this.db = this.wrapSqlJs(rawDb);
|
|
36
56
|
}
|
|
37
57
|
this.createSchema();
|
|
38
58
|
this.initialized = true;
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// which means sessions that end early never create the file on disk.
|
|
59
|
+
// For sql.js: force initial save so the .db file exists on disk
|
|
60
|
+
// (better-sqlite3 creates the file automatically on open)
|
|
42
61
|
this.saveToDisk();
|
|
43
62
|
}
|
|
44
63
|
createSchema() {
|
|
45
64
|
if (!this.db)
|
|
46
65
|
throw new Error('Database not initialized');
|
|
47
|
-
this.db.
|
|
48
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
49
|
-
id TEXT PRIMARY KEY,
|
|
50
|
-
type TEXT NOT NULL,
|
|
51
|
-
title TEXT NOT NULL,
|
|
52
|
-
content TEXT NOT NULL,
|
|
53
|
-
project TEXT NOT NULL DEFAULT '',
|
|
54
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
55
|
-
session_id TEXT,
|
|
56
|
-
created_at INTEGER NOT NULL,
|
|
57
|
-
updated_at INTEGER NOT NULL,
|
|
58
|
-
accessed_at INTEGER NOT NULL
|
|
59
|
-
)
|
|
66
|
+
this.db.exec(`
|
|
67
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
type TEXT NOT NULL,
|
|
70
|
+
title TEXT NOT NULL,
|
|
71
|
+
content TEXT NOT NULL,
|
|
72
|
+
project TEXT NOT NULL DEFAULT '',
|
|
73
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
74
|
+
session_id TEXT,
|
|
75
|
+
created_at INTEGER NOT NULL,
|
|
76
|
+
updated_at INTEGER NOT NULL,
|
|
77
|
+
accessed_at INTEGER NOT NULL
|
|
78
|
+
)
|
|
60
79
|
`);
|
|
61
|
-
this.db.
|
|
62
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
63
|
-
id TEXT PRIMARY KEY,
|
|
64
|
-
project TEXT NOT NULL DEFAULT '',
|
|
65
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
66
|
-
started_at INTEGER NOT NULL,
|
|
67
|
-
ended_at INTEGER,
|
|
68
|
-
summary TEXT,
|
|
69
|
-
tool_calls INTEGER NOT NULL DEFAULT 0
|
|
70
|
-
)
|
|
80
|
+
this.db.exec(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
project TEXT NOT NULL DEFAULT '',
|
|
84
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
85
|
+
started_at INTEGER NOT NULL,
|
|
86
|
+
ended_at INTEGER,
|
|
87
|
+
summary TEXT,
|
|
88
|
+
tool_calls INTEGER NOT NULL DEFAULT 0
|
|
89
|
+
)
|
|
71
90
|
`);
|
|
72
91
|
// --- Indexer Extensions ---
|
|
73
|
-
this.db.
|
|
74
|
-
CREATE TABLE IF NOT EXISTS code_nodes (
|
|
75
|
-
id TEXT PRIMARY KEY,
|
|
76
|
-
type TEXT NOT NULL,
|
|
77
|
-
name TEXT NOT NULL,
|
|
78
|
-
file_path TEXT NOT NULL,
|
|
79
|
-
start_line INTEGER NOT NULL,
|
|
80
|
-
end_line INTEGER NOT NULL,
|
|
81
|
-
content TEXT NOT NULL,
|
|
82
|
-
summary TEXT,
|
|
83
|
-
hash TEXT NOT NULL,
|
|
84
|
-
updated_at INTEGER NOT NULL
|
|
85
|
-
)
|
|
92
|
+
this.db.exec(`
|
|
93
|
+
CREATE TABLE IF NOT EXISTS code_nodes (
|
|
94
|
+
id TEXT PRIMARY KEY,
|
|
95
|
+
type TEXT NOT NULL,
|
|
96
|
+
name TEXT NOT NULL,
|
|
97
|
+
file_path TEXT NOT NULL,
|
|
98
|
+
start_line INTEGER NOT NULL,
|
|
99
|
+
end_line INTEGER NOT NULL,
|
|
100
|
+
content TEXT NOT NULL,
|
|
101
|
+
summary TEXT,
|
|
102
|
+
hash TEXT NOT NULL,
|
|
103
|
+
updated_at INTEGER NOT NULL
|
|
104
|
+
)
|
|
86
105
|
`);
|
|
87
|
-
this.db.
|
|
88
|
-
CREATE TABLE IF NOT EXISTS code_edges (
|
|
89
|
-
id TEXT PRIMARY KEY,
|
|
90
|
-
source_id TEXT NOT NULL,
|
|
91
|
-
target_id TEXT NOT NULL,
|
|
92
|
-
type TEXT NOT NULL,
|
|
93
|
-
weight REAL NOT NULL DEFAULT 1.0,
|
|
94
|
-
FOREIGN KEY(source_id) REFERENCES code_nodes(id) ON DELETE CASCADE
|
|
95
|
-
)
|
|
106
|
+
this.db.exec(`
|
|
107
|
+
CREATE TABLE IF NOT EXISTS code_edges (
|
|
108
|
+
id TEXT PRIMARY KEY,
|
|
109
|
+
source_id TEXT NOT NULL,
|
|
110
|
+
target_id TEXT NOT NULL,
|
|
111
|
+
type TEXT NOT NULL,
|
|
112
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
113
|
+
FOREIGN KEY(source_id) REFERENCES code_nodes(id) ON DELETE CASCADE
|
|
114
|
+
)
|
|
96
115
|
`);
|
|
97
116
|
// FTS5 virtual table for BM25 search
|
|
98
|
-
// Use external content mode synced with memories table
|
|
99
117
|
try {
|
|
100
|
-
this.db.
|
|
101
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
102
|
-
title, content, type,
|
|
103
|
-
content=memories,
|
|
104
|
-
content_rowid=rowid
|
|
105
|
-
)
|
|
118
|
+
this.db.exec(`
|
|
119
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
120
|
+
title, content, type,
|
|
121
|
+
content=memories,
|
|
122
|
+
content_rowid=rowid
|
|
123
|
+
)
|
|
106
124
|
`);
|
|
107
125
|
// Triggers to sync FTS with memories table
|
|
108
|
-
this.db.
|
|
109
|
-
CREATE TRIGGER IF NOT EXISTS memory_fts_ai AFTER INSERT ON memories BEGIN
|
|
110
|
-
INSERT INTO memory_fts(rowid, title, content, type)
|
|
111
|
-
VALUES (new.rowid, new.title, new.content, new.type);
|
|
112
|
-
END
|
|
126
|
+
this.db.exec(`
|
|
127
|
+
CREATE TRIGGER IF NOT EXISTS memory_fts_ai AFTER INSERT ON memories BEGIN
|
|
128
|
+
INSERT INTO memory_fts(rowid, title, content, type)
|
|
129
|
+
VALUES (new.rowid, new.title, new.content, new.type);
|
|
130
|
+
END
|
|
113
131
|
`);
|
|
114
|
-
this.db.
|
|
115
|
-
CREATE TRIGGER IF NOT EXISTS memory_fts_ad AFTER DELETE ON memories BEGIN
|
|
116
|
-
INSERT INTO memory_fts(memory_fts, rowid, title, content, type)
|
|
117
|
-
VALUES ('delete', old.rowid, old.title, old.content, old.type);
|
|
118
|
-
END
|
|
132
|
+
this.db.exec(`
|
|
133
|
+
CREATE TRIGGER IF NOT EXISTS memory_fts_ad AFTER DELETE ON memories BEGIN
|
|
134
|
+
INSERT INTO memory_fts(memory_fts, rowid, title, content, type)
|
|
135
|
+
VALUES ('delete', old.rowid, old.title, old.content, old.type);
|
|
136
|
+
END
|
|
119
137
|
`);
|
|
120
|
-
this.db.
|
|
121
|
-
CREATE TRIGGER IF NOT EXISTS memory_fts_au AFTER UPDATE ON memories BEGIN
|
|
122
|
-
INSERT INTO memory_fts(memory_fts, rowid, title, content, type)
|
|
123
|
-
VALUES ('delete', old.rowid, old.title, old.content, old.type);
|
|
124
|
-
INSERT INTO memory_fts(rowid, title, content, type)
|
|
125
|
-
VALUES (new.rowid, new.title, new.content, new.type);
|
|
126
|
-
END
|
|
138
|
+
this.db.exec(`
|
|
139
|
+
CREATE TRIGGER IF NOT EXISTS memory_fts_au AFTER UPDATE ON memories BEGIN
|
|
140
|
+
INSERT INTO memory_fts(memory_fts, rowid, title, content, type)
|
|
141
|
+
VALUES ('delete', old.rowid, old.title, old.content, old.type);
|
|
142
|
+
INSERT INTO memory_fts(rowid, title, content, type)
|
|
143
|
+
VALUES (new.rowid, new.title, new.content, new.type);
|
|
144
|
+
END
|
|
127
145
|
`);
|
|
128
146
|
}
|
|
129
147
|
catch {
|
|
130
148
|
// FTS5 may not be available in some builds; continue without it
|
|
131
149
|
}
|
|
132
150
|
// Index for common queries
|
|
133
|
-
this.db.
|
|
134
|
-
this.db.
|
|
135
|
-
this.db.
|
|
136
|
-
this.db.
|
|
137
|
-
this.db.
|
|
138
|
-
this.db.
|
|
139
|
-
this.db.
|
|
140
|
-
this.db.
|
|
141
|
-
}
|
|
142
|
-
trackWrite() {
|
|
143
|
-
this.writeCount++;
|
|
144
|
-
if (this.writeCount >= AUTO_SAVE_THRESHOLD) {
|
|
145
|
-
this.saveToDisk();
|
|
146
|
-
this.writeCount = 0;
|
|
147
|
-
}
|
|
151
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type)');
|
|
152
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at)');
|
|
153
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_memories_accessed ON memories(accessed_at)');
|
|
154
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(session_id)');
|
|
155
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_code_nodes_type ON code_nodes(type)');
|
|
156
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_code_nodes_path ON code_nodes(file_path)');
|
|
157
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_code_edges_source ON code_edges(source_id)');
|
|
158
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_code_edges_target ON code_edges(target_id)');
|
|
148
159
|
}
|
|
149
160
|
// --- Memory CRUD ---
|
|
150
161
|
saveMemory(memory) {
|
|
151
162
|
if (!this.db)
|
|
152
163
|
throw new Error('Database not initialized');
|
|
153
|
-
this.db.
|
|
154
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
155
|
-
memory.id,
|
|
156
|
-
memory.type,
|
|
157
|
-
memory.title,
|
|
158
|
-
memory.content,
|
|
159
|
-
memory.project,
|
|
160
|
-
JSON.stringify(memory.tags),
|
|
161
|
-
memory.sessionId ?? null,
|
|
162
|
-
memory.createdAt,
|
|
163
|
-
memory.updatedAt,
|
|
164
|
-
memory.accessedAt,
|
|
165
|
-
]);
|
|
166
|
-
this.trackWrite();
|
|
164
|
+
this.db.prepare(`INSERT OR REPLACE INTO memories (id, type, title, content, project, tags, session_id, created_at, updated_at, accessed_at)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(memory.id, memory.type, memory.title, memory.content, memory.project, JSON.stringify(memory.tags), memory.sessionId ?? null, memory.createdAt, memory.updatedAt, memory.accessedAt);
|
|
167
166
|
}
|
|
168
167
|
getMemory(id) {
|
|
169
168
|
if (!this.db)
|
|
170
169
|
throw new Error('Database not initialized');
|
|
171
|
-
const
|
|
172
|
-
FROM memories WHERE id =
|
|
173
|
-
if (
|
|
170
|
+
const row = this.db.prepare(`SELECT id, type, title, content, project, tags, session_id, created_at, updated_at, accessed_at
|
|
171
|
+
FROM memories WHERE id = ?`).get(id);
|
|
172
|
+
if (!row)
|
|
174
173
|
return null;
|
|
175
|
-
const row = result[0].values[0];
|
|
176
174
|
return this.rowToMemory(row);
|
|
177
175
|
}
|
|
178
176
|
deleteMemory(id) {
|
|
179
177
|
if (!this.db)
|
|
180
178
|
throw new Error('Database not initialized');
|
|
181
|
-
this.db.
|
|
182
|
-
this.trackWrite();
|
|
179
|
+
this.db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
|
|
183
180
|
}
|
|
184
181
|
listMemories(options) {
|
|
185
182
|
if (!this.db)
|
|
186
183
|
throw new Error('Database not initialized');
|
|
187
184
|
const conditions = [];
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
185
|
+
const params = [];
|
|
186
|
+
if (options?.type) {
|
|
187
|
+
conditions.push(`type = ?`);
|
|
188
|
+
params.push(options.type);
|
|
189
|
+
}
|
|
190
|
+
if (options?.project) {
|
|
191
|
+
conditions.push(`project = ?`);
|
|
192
|
+
params.push(options.project);
|
|
193
|
+
}
|
|
192
194
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
193
|
-
const limit = options?.limit
|
|
194
|
-
const offset = options?.offset
|
|
195
|
-
const
|
|
196
|
-
FROM memories ${where} ORDER BY created_at DESC
|
|
197
|
-
|
|
198
|
-
return [];
|
|
199
|
-
return result[0].values.map((row) => this.rowToMemory(row));
|
|
195
|
+
const limit = options?.limit ?? 100;
|
|
196
|
+
const offset = options?.offset ?? 0;
|
|
197
|
+
const rows = this.db.prepare(`SELECT id, type, title, content, project, tags, session_id, created_at, updated_at, accessed_at
|
|
198
|
+
FROM memories ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
199
|
+
return rows.map((row) => this.rowToMemory(row));
|
|
200
200
|
}
|
|
201
201
|
updateAccessedAt(id) {
|
|
202
202
|
if (!this.db)
|
|
203
203
|
throw new Error('Database not initialized');
|
|
204
|
-
this.db.
|
|
205
|
-
this.trackWrite();
|
|
204
|
+
this.db.prepare(`UPDATE memories SET accessed_at = ? WHERE id = ?`).run(Date.now(), id);
|
|
206
205
|
}
|
|
207
206
|
getMemoryCount() {
|
|
208
207
|
if (!this.db)
|
|
209
208
|
throw new Error('Database not initialized');
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
return 0;
|
|
213
|
-
return Number(result[0].values[0][0]);
|
|
209
|
+
const row = this.db.prepare('SELECT COUNT(*) as count FROM memories').get();
|
|
210
|
+
return row.count;
|
|
214
211
|
}
|
|
215
212
|
// --- BM25 Search ---
|
|
216
213
|
searchBM25(query, limit = 20, filters) {
|
|
217
214
|
if (!this.db)
|
|
218
215
|
throw new Error('Database not initialized');
|
|
219
216
|
try {
|
|
220
|
-
// Escape FTS5 special characters
|
|
221
|
-
const escaped = query.replace(/['"]/g, ' ').trim();
|
|
217
|
+
// Escape FTS5 special characters — strip quotes and special operators
|
|
218
|
+
const escaped = query.replace(/['"*(){}[\]:^~!]/g, ' ').trim();
|
|
222
219
|
if (!escaped)
|
|
223
220
|
return [];
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
// Build FTS5 match query with proper term quoting
|
|
222
|
+
const terms = escaped.split(/\s+/).filter(t => t.length > 1);
|
|
223
|
+
if (terms.length === 0)
|
|
224
|
+
return [];
|
|
225
|
+
const ftsQuery = terms.map(t => `"${t}"`).join(' OR ');
|
|
226
|
+
let sql = `
|
|
227
|
+
SELECT m.id, bm25(memory_fts) as score
|
|
228
|
+
FROM memory_fts f
|
|
229
|
+
JOIN memories m ON m.rowid = f.rowid
|
|
230
|
+
WHERE memory_fts MATCH '${ftsQuery}'
|
|
229
231
|
`;
|
|
230
232
|
if (filters?.type) {
|
|
231
|
-
sql += ` AND m.type = '${filters.type}'`;
|
|
233
|
+
sql += ` AND m.type = '${filters.type.replace(/'/g, "''")}'`;
|
|
232
234
|
}
|
|
233
235
|
if (filters?.project) {
|
|
234
236
|
sql += ` AND m.project = '${filters.project.replace(/'/g, "''")}'`;
|
|
235
237
|
}
|
|
236
238
|
sql += ` ORDER BY score LIMIT ${limit}`;
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
id: row[0],
|
|
242
|
-
score: Math.abs(row[1]), // BM25 returns negative scores
|
|
239
|
+
const rows = this.db.prepare(sql).all();
|
|
240
|
+
return rows.map((row) => ({
|
|
241
|
+
id: row.id,
|
|
242
|
+
score: Math.abs(row.score), // BM25 returns negative scores
|
|
243
243
|
}));
|
|
244
244
|
}
|
|
245
245
|
catch {
|
|
@@ -263,91 +263,72 @@ export class MemoryStore {
|
|
|
263
263
|
if (filters?.project)
|
|
264
264
|
sql += ` AND project = '${filters.project.replace(/'/g, "''")}'`;
|
|
265
265
|
sql += ` LIMIT ${limit}`;
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
id: row[0],
|
|
271
|
-
score: 1 / (i + 1), // Simple rank-based score
|
|
266
|
+
const rows = this.db.prepare(sql).all();
|
|
267
|
+
return rows.map((row, i) => ({
|
|
268
|
+
id: row.id,
|
|
269
|
+
score: 1 / (i + 1),
|
|
272
270
|
}));
|
|
273
271
|
}
|
|
274
272
|
// --- Sessions ---
|
|
275
273
|
createSession(session) {
|
|
276
274
|
if (!this.db)
|
|
277
275
|
throw new Error('Database not initialized');
|
|
278
|
-
this.db.
|
|
279
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
280
|
-
session.id,
|
|
281
|
-
session.project,
|
|
282
|
-
session.status,
|
|
283
|
-
session.startedAt,
|
|
284
|
-
session.endedAt ?? null,
|
|
285
|
-
session.summary ?? null,
|
|
286
|
-
session.toolCalls,
|
|
287
|
-
]);
|
|
288
|
-
this.trackWrite();
|
|
276
|
+
this.db.prepare(`INSERT INTO sessions (id, project, status, started_at, ended_at, summary, tool_calls)
|
|
277
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(session.id, session.project, session.status, session.startedAt, session.endedAt ?? null, session.summary ?? null, session.toolCalls);
|
|
289
278
|
}
|
|
290
279
|
endSession(id, summary) {
|
|
291
280
|
if (!this.db)
|
|
292
281
|
throw new Error('Database not initialized');
|
|
293
|
-
this.db.
|
|
294
|
-
this.trackWrite();
|
|
282
|
+
this.db.prepare(`UPDATE sessions SET status = 'completed', ended_at = ?, summary = ? WHERE id = ?`).run(Date.now(), summary ?? null, id);
|
|
295
283
|
}
|
|
296
284
|
getActiveSession() {
|
|
297
285
|
if (!this.db)
|
|
298
286
|
throw new Error('Database not initialized');
|
|
299
|
-
const
|
|
300
|
-
FROM sessions WHERE status = 'active' ORDER BY started_at DESC LIMIT 1`);
|
|
301
|
-
if (
|
|
287
|
+
const row = this.db.prepare(`SELECT id, project, status, started_at, ended_at, summary, tool_calls
|
|
288
|
+
FROM sessions WHERE status = 'active' ORDER BY started_at DESC LIMIT 1`).get();
|
|
289
|
+
if (!row)
|
|
302
290
|
return null;
|
|
303
|
-
const row = result[0].values[0];
|
|
304
291
|
return {
|
|
305
|
-
id: row
|
|
306
|
-
project: row
|
|
307
|
-
status: row
|
|
308
|
-
startedAt: row
|
|
309
|
-
endedAt: row
|
|
310
|
-
summary: row
|
|
311
|
-
toolCalls: row
|
|
292
|
+
id: row.id,
|
|
293
|
+
project: row.project,
|
|
294
|
+
status: row.status,
|
|
295
|
+
startedAt: row.started_at,
|
|
296
|
+
endedAt: row.ended_at,
|
|
297
|
+
summary: row.summary,
|
|
298
|
+
toolCalls: row.tool_calls,
|
|
312
299
|
};
|
|
313
300
|
}
|
|
314
301
|
getSessionCount() {
|
|
315
302
|
if (!this.db)
|
|
316
303
|
throw new Error('Database not initialized');
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
return 0;
|
|
320
|
-
return Number(result[0].values[0][0]);
|
|
304
|
+
const row = this.db.prepare('SELECT COUNT(*) as count FROM sessions').get();
|
|
305
|
+
return row.count;
|
|
321
306
|
}
|
|
322
307
|
// --- Queries for cache/stats ---
|
|
323
308
|
getOldestMemoryTimestamp() {
|
|
324
309
|
if (!this.db)
|
|
325
310
|
return undefined;
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
return undefined;
|
|
329
|
-
return Number(result[0].values[0][0]);
|
|
311
|
+
const row = this.db.prepare('SELECT MIN(created_at) as ts FROM memories').get();
|
|
312
|
+
return row.ts ?? undefined;
|
|
330
313
|
}
|
|
331
314
|
getNewestMemoryTimestamp() {
|
|
332
315
|
if (!this.db)
|
|
333
316
|
return undefined;
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
return undefined;
|
|
337
|
-
return Number(result[0].values[0][0]);
|
|
317
|
+
const row = this.db.prepare('SELECT MAX(created_at) as ts FROM memories').get();
|
|
318
|
+
return row.ts ?? undefined;
|
|
338
319
|
}
|
|
339
320
|
getEvictionCandidates(batchSize, activeSessionId) {
|
|
340
321
|
if (!this.db)
|
|
341
322
|
return [];
|
|
342
323
|
let sql = `SELECT id FROM memories WHERE type != 'decision'`;
|
|
324
|
+
const params = [];
|
|
343
325
|
if (activeSessionId) {
|
|
344
|
-
sql += ` AND (session_id IS NULL OR session_id !=
|
|
326
|
+
sql += ` AND (session_id IS NULL OR session_id != ?)`;
|
|
327
|
+
params.push(activeSessionId);
|
|
345
328
|
}
|
|
346
|
-
sql += ` ORDER BY accessed_at ASC, created_at ASC LIMIT
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return [];
|
|
350
|
-
return result[0].values.map((row) => ({ id: row[0] }));
|
|
329
|
+
sql += ` ORDER BY accessed_at ASC, created_at ASC LIMIT ?`;
|
|
330
|
+
params.push(batchSize);
|
|
331
|
+
return this.db.prepare(sql).all(...params);
|
|
351
332
|
}
|
|
352
333
|
/**
|
|
353
334
|
* Get memories in chronological order around a given memory
|
|
@@ -356,126 +337,200 @@ export class MemoryStore {
|
|
|
356
337
|
if (!this.db)
|
|
357
338
|
return [];
|
|
358
339
|
// Get anchor memory's timestamp
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
340
|
+
const anchor = this.db.prepare(`SELECT created_at FROM memories WHERE id = ?`).get(memoryId);
|
|
341
|
+
if (!anchor)
|
|
361
342
|
return [];
|
|
362
|
-
const anchorTs =
|
|
343
|
+
const anchorTs = anchor.created_at;
|
|
363
344
|
// Get before + anchor + after
|
|
364
|
-
const
|
|
365
|
-
SELECT id, title, type, created_at FROM memories
|
|
366
|
-
WHERE created_at <=
|
|
367
|
-
ORDER BY created_at DESC LIMIT
|
|
368
|
-
)
|
|
369
|
-
UNION ALL
|
|
370
|
-
SELECT id, title, type, created_at FROM (
|
|
371
|
-
SELECT id, title, type, created_at FROM memories
|
|
372
|
-
WHERE created_at >
|
|
373
|
-
ORDER BY created_at ASC LIMIT
|
|
374
|
-
)
|
|
375
|
-
ORDER BY created_at ASC`);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
type: row[2],
|
|
382
|
-
createdAt: row[3],
|
|
345
|
+
const rows = this.db.prepare(`SELECT id, title, type, created_at FROM (
|
|
346
|
+
SELECT id, title, type, created_at FROM memories
|
|
347
|
+
WHERE created_at <= ?
|
|
348
|
+
ORDER BY created_at DESC LIMIT ?
|
|
349
|
+
)
|
|
350
|
+
UNION ALL
|
|
351
|
+
SELECT id, title, type, created_at FROM (
|
|
352
|
+
SELECT id, title, type, created_at FROM memories
|
|
353
|
+
WHERE created_at > ?
|
|
354
|
+
ORDER BY created_at ASC LIMIT ?
|
|
355
|
+
)
|
|
356
|
+
ORDER BY created_at ASC`).all(anchorTs, window + 1, anchorTs, window);
|
|
357
|
+
return rows.map((row) => ({
|
|
358
|
+
id: row.id,
|
|
359
|
+
title: row.title,
|
|
360
|
+
type: row.type,
|
|
361
|
+
createdAt: row.created_at,
|
|
383
362
|
}));
|
|
384
363
|
}
|
|
385
364
|
// --- Persistence ---
|
|
386
365
|
getDbSizeBytes() {
|
|
387
366
|
if (!this.db)
|
|
388
367
|
return 0;
|
|
389
|
-
|
|
368
|
+
// O(1) — reads page count and page size from SQLite internal state
|
|
369
|
+
// No memory allocation, no data copy (unlike sql.js db.export())
|
|
370
|
+
try {
|
|
371
|
+
const stat = statSync(this.dbPath);
|
|
372
|
+
return stat.size;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
390
377
|
}
|
|
391
378
|
saveToDisk() {
|
|
392
379
|
if (!this.db)
|
|
393
380
|
return;
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
382
|
+
if (this.db._isSqlJs) {
|
|
383
|
+
// sql.js: export + writeFileSync
|
|
384
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
385
|
+
this.db._sqlJsSave();
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// better-sqlite3: WAL checkpoint
|
|
389
|
+
this.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
397
390
|
}
|
|
398
|
-
const data = this.db.export();
|
|
399
|
-
writeFileSync(this.dbPath, data);
|
|
400
391
|
}
|
|
401
392
|
close() {
|
|
402
393
|
if (this.db) {
|
|
403
|
-
this.saveToDisk();
|
|
404
394
|
this.db.close();
|
|
405
395
|
this.db = null;
|
|
406
396
|
this.initialized = false;
|
|
407
397
|
}
|
|
408
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Wraps a sql.js database to expose the same API surface as better-sqlite3.
|
|
401
|
+
* This enables the rest of the class to use db.prepare().run/get/all uniformly.
|
|
402
|
+
*/
|
|
403
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
404
|
+
wrapSqlJs(rawDb) {
|
|
405
|
+
const dbPath = this.dbPath;
|
|
406
|
+
let writeCount = 0;
|
|
407
|
+
const saveToDisk = () => {
|
|
408
|
+
const data = rawDb.export();
|
|
409
|
+
const dir = join(dbPath, '..');
|
|
410
|
+
if (!existsSync(dir))
|
|
411
|
+
mkdirSync(dir, { recursive: true });
|
|
412
|
+
writeFileSync(dbPath, data);
|
|
413
|
+
};
|
|
414
|
+
const trackWrite = () => {
|
|
415
|
+
writeCount++;
|
|
416
|
+
if (writeCount >= 200) {
|
|
417
|
+
saveToDisk();
|
|
418
|
+
writeCount = 0;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
return {
|
|
422
|
+
exec: (sql) => rawDb.run(sql),
|
|
423
|
+
pragma: () => { }, // No-op for sql.js
|
|
424
|
+
prepare: (sql) => ({
|
|
425
|
+
run: (...params) => {
|
|
426
|
+
rawDb.run(sql, params.length === 1 && Array.isArray(params[0]) ? params[0] : params);
|
|
427
|
+
trackWrite();
|
|
428
|
+
},
|
|
429
|
+
get: (...params) => {
|
|
430
|
+
const result = rawDb.exec(sql.replace(/\?/g, () => {
|
|
431
|
+
const p = params.shift();
|
|
432
|
+
if (p === null || p === undefined)
|
|
433
|
+
return 'NULL';
|
|
434
|
+
if (typeof p === 'string')
|
|
435
|
+
return `'${p.replace(/'/g, "''")}'`;
|
|
436
|
+
return String(p);
|
|
437
|
+
}));
|
|
438
|
+
if (!result.length || !result[0].values.length)
|
|
439
|
+
return undefined;
|
|
440
|
+
const cols = result[0].columns;
|
|
441
|
+
const row = result[0].values[0];
|
|
442
|
+
const obj = {};
|
|
443
|
+
cols.forEach((c, i) => { obj[c] = row[i]; });
|
|
444
|
+
return obj;
|
|
445
|
+
},
|
|
446
|
+
all: (...params) => {
|
|
447
|
+
let processed = sql;
|
|
448
|
+
for (const p of params) {
|
|
449
|
+
if (p === null || p === undefined) {
|
|
450
|
+
processed = processed.replace('?', 'NULL');
|
|
451
|
+
}
|
|
452
|
+
else if (typeof p === 'string') {
|
|
453
|
+
processed = processed.replace('?', `'${p.replace(/'/g, "''")}'`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
processed = processed.replace('?', String(p));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const result = rawDb.exec(processed);
|
|
460
|
+
if (!result.length)
|
|
461
|
+
return [];
|
|
462
|
+
const cols = result[0].columns;
|
|
463
|
+
return result[0].values.map((row) => {
|
|
464
|
+
const obj = {};
|
|
465
|
+
cols.forEach((c, i) => { obj[c] = row[i]; });
|
|
466
|
+
return obj;
|
|
467
|
+
});
|
|
468
|
+
},
|
|
469
|
+
}),
|
|
470
|
+
close: () => { saveToDisk(); rawDb.close(); },
|
|
471
|
+
// For saveToDisk() compatibility
|
|
472
|
+
_sqlJsSave: saveToDisk,
|
|
473
|
+
_isSqlJs: true,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
409
476
|
rowToMemory(row) {
|
|
410
477
|
return {
|
|
411
|
-
id: row
|
|
412
|
-
type: row
|
|
413
|
-
title: row
|
|
414
|
-
content: row
|
|
415
|
-
project: row
|
|
416
|
-
tags: JSON.parse(row
|
|
417
|
-
sessionId: row
|
|
418
|
-
createdAt: row
|
|
419
|
-
updatedAt: row
|
|
420
|
-
accessedAt: row
|
|
478
|
+
id: row.id,
|
|
479
|
+
type: row.type,
|
|
480
|
+
title: row.title,
|
|
481
|
+
content: row.content,
|
|
482
|
+
project: row.project,
|
|
483
|
+
tags: JSON.parse(row.tags || '[]'),
|
|
484
|
+
sessionId: row.session_id || undefined,
|
|
485
|
+
createdAt: row.created_at,
|
|
486
|
+
updatedAt: row.updated_at,
|
|
487
|
+
accessedAt: row.accessed_at,
|
|
421
488
|
};
|
|
422
489
|
}
|
|
423
490
|
// --- Background Indexer Graph Persistence ---
|
|
424
491
|
saveCodeNode(node) {
|
|
425
492
|
if (!this.db)
|
|
426
493
|
throw new Error('Database not initialized');
|
|
427
|
-
this.db.
|
|
428
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
429
|
-
node.id,
|
|
430
|
-
node.type,
|
|
431
|
-
node.name,
|
|
432
|
-
node.filePath,
|
|
433
|
-
node.startLine,
|
|
434
|
-
node.endLine,
|
|
435
|
-
node.content,
|
|
436
|
-
node.summary ?? null,
|
|
437
|
-
node.hash,
|
|
438
|
-
node.updatedAt,
|
|
439
|
-
]);
|
|
440
|
-
this.trackWrite();
|
|
494
|
+
this.db.prepare(`INSERT OR REPLACE INTO code_nodes (id, type, name, file_path, start_line, end_line, content, summary, hash, updated_at)
|
|
495
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(node.id, node.type, node.name, node.filePath, node.startLine, node.endLine, node.content, node.summary ?? null, node.hash, node.updatedAt);
|
|
441
496
|
}
|
|
442
497
|
saveCodeEdge(edge) {
|
|
443
498
|
if (!this.db)
|
|
444
499
|
throw new Error('Database not initialized');
|
|
445
|
-
this.db.
|
|
446
|
-
VALUES (?, ?, ?, ?, ?)
|
|
447
|
-
|
|
500
|
+
this.db.prepare(`INSERT OR IGNORE INTO code_edges (id, source_id, target_id, type, weight)
|
|
501
|
+
VALUES (?, ?, ?, ?, ?)`).run(edge.id, edge.sourceId, edge.targetId, edge.type, edge.weight);
|
|
502
|
+
}
|
|
503
|
+
getCodeNodeIdsByFile(filePath) {
|
|
504
|
+
if (!this.db)
|
|
505
|
+
return [];
|
|
506
|
+
const rows = this.db.prepare(`SELECT id FROM code_nodes WHERE file_path = ?`).all(filePath);
|
|
507
|
+
return rows.map((row) => row.id);
|
|
448
508
|
}
|
|
449
509
|
deleteCodeNodesByFile(filePath) {
|
|
450
510
|
if (!this.db)
|
|
451
511
|
throw new Error('Database not initialized');
|
|
452
|
-
|
|
453
|
-
this.db.run(`DELETE FROM code_nodes WHERE file_path = ?`, [filePath]);
|
|
454
|
-
this.trackWrite();
|
|
512
|
+
this.db.prepare(`DELETE FROM code_nodes WHERE file_path = ?`).run(filePath);
|
|
455
513
|
}
|
|
456
514
|
getCodeNodeByHash(id) {
|
|
457
515
|
if (!this.db)
|
|
458
516
|
throw new Error('Database not initialized');
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
return null;
|
|
462
|
-
return result[0].values[0][0];
|
|
517
|
+
const row = this.db.prepare(`SELECT hash FROM code_nodes WHERE id = ?`).get(id);
|
|
518
|
+
return row?.hash ?? null;
|
|
463
519
|
}
|
|
464
520
|
getCodeNode(id) {
|
|
465
521
|
if (!this.db)
|
|
466
522
|
throw new Error('Database not initialized');
|
|
467
|
-
const
|
|
468
|
-
if (
|
|
523
|
+
const row = this.db.prepare(`SELECT id, type, name, file_path, content, hash, updated_at FROM code_nodes WHERE id = ?`).get(id);
|
|
524
|
+
if (!row)
|
|
469
525
|
return null;
|
|
470
|
-
const row = result[0].values[0];
|
|
471
526
|
return {
|
|
472
|
-
id: row
|
|
473
|
-
type: row
|
|
474
|
-
name: row
|
|
475
|
-
filePath: row
|
|
476
|
-
content: row
|
|
477
|
-
hash: row
|
|
478
|
-
updatedAt: row
|
|
527
|
+
id: row.id,
|
|
528
|
+
type: row.type,
|
|
529
|
+
name: row.name,
|
|
530
|
+
filePath: row.file_path,
|
|
531
|
+
content: row.content,
|
|
532
|
+
hash: row.hash,
|
|
533
|
+
updatedAt: row.updated_at,
|
|
479
534
|
};
|
|
480
535
|
}
|
|
481
536
|
}
|