@esparkman/pensieve 0.1.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/README.md +238 -0
- package/dist/__tests__/database.test.d.ts +1 -0
- package/dist/__tests__/database.test.js +210 -0
- package/dist/__tests__/security.test.d.ts +1 -0
- package/dist/__tests__/security.test.js +216 -0
- package/dist/database.d.ts +106 -0
- package/dist/database.js +344 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +524 -0
- package/dist/security.d.ts +20 -0
- package/dist/security.js +79 -0
- package/package.json +45 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export declare const LIMITS: {
|
|
2
|
+
readonly MAX_DECISIONS: 1000;
|
|
3
|
+
readonly MAX_DISCOVERIES: 500;
|
|
4
|
+
readonly MAX_ENTITIES: 200;
|
|
5
|
+
readonly MAX_QUESTIONS: 100;
|
|
6
|
+
readonly MAX_SESSIONS: 100;
|
|
7
|
+
readonly SESSION_RETENTION_DAYS: 90;
|
|
8
|
+
readonly MAX_FIELD_LENGTH: 10000;
|
|
9
|
+
};
|
|
10
|
+
export interface Decision {
|
|
11
|
+
id?: number;
|
|
12
|
+
topic: string;
|
|
13
|
+
decision: string;
|
|
14
|
+
rationale?: string;
|
|
15
|
+
alternatives?: string;
|
|
16
|
+
decided_at?: string;
|
|
17
|
+
source?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Preference {
|
|
20
|
+
id?: number;
|
|
21
|
+
category: string;
|
|
22
|
+
key: string;
|
|
23
|
+
value: string;
|
|
24
|
+
notes?: string;
|
|
25
|
+
updated_at?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface Discovery {
|
|
28
|
+
id?: number;
|
|
29
|
+
category: string;
|
|
30
|
+
name: string;
|
|
31
|
+
location?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
metadata?: string;
|
|
34
|
+
discovered_at?: string;
|
|
35
|
+
confidence?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface Entity {
|
|
38
|
+
id?: number;
|
|
39
|
+
name: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
relationships?: string;
|
|
42
|
+
attributes?: string;
|
|
43
|
+
location?: string;
|
|
44
|
+
updated_at?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface Session {
|
|
47
|
+
id?: number;
|
|
48
|
+
started_at?: string;
|
|
49
|
+
ended_at?: string;
|
|
50
|
+
summary?: string;
|
|
51
|
+
work_in_progress?: string;
|
|
52
|
+
next_steps?: string;
|
|
53
|
+
key_files?: string;
|
|
54
|
+
tags?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface OpenQuestion {
|
|
57
|
+
id?: number;
|
|
58
|
+
question: string;
|
|
59
|
+
context?: string;
|
|
60
|
+
status?: string;
|
|
61
|
+
resolution?: string;
|
|
62
|
+
created_at?: string;
|
|
63
|
+
resolved_at?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare class MemoryDatabase {
|
|
66
|
+
private db;
|
|
67
|
+
private projectPath;
|
|
68
|
+
constructor(projectPath?: string);
|
|
69
|
+
private getDbPath;
|
|
70
|
+
/**
|
|
71
|
+
* Truncate a string to the maximum field length
|
|
72
|
+
*/
|
|
73
|
+
private truncateField;
|
|
74
|
+
/**
|
|
75
|
+
* Prune old entries when limits are exceeded
|
|
76
|
+
*/
|
|
77
|
+
private pruneIfNeeded;
|
|
78
|
+
private initSchema;
|
|
79
|
+
addDecision(decision: Decision): number;
|
|
80
|
+
searchDecisions(query: string): Decision[];
|
|
81
|
+
getRecentDecisions(limit?: number): Decision[];
|
|
82
|
+
setPreference(pref: Preference): void;
|
|
83
|
+
getPreference(category: string, key: string): Preference | undefined;
|
|
84
|
+
getPreferencesByCategory(category: string): Preference[];
|
|
85
|
+
getAllPreferences(): Preference[];
|
|
86
|
+
addDiscovery(discovery: Discovery): number;
|
|
87
|
+
searchDiscoveries(query: string): Discovery[];
|
|
88
|
+
getDiscoveriesByCategory(category: string): Discovery[];
|
|
89
|
+
upsertEntity(entity: Entity): void;
|
|
90
|
+
getEntity(name: string): Entity | undefined;
|
|
91
|
+
getAllEntities(): Entity[];
|
|
92
|
+
startSession(): number;
|
|
93
|
+
endSession(sessionId: number, summary: string, workInProgress?: string, nextSteps?: string, keyFiles?: string[], tags?: string[]): void;
|
|
94
|
+
getLastSession(): Session | undefined;
|
|
95
|
+
getCurrentSession(): Session | undefined;
|
|
96
|
+
addQuestion(question: string, context?: string): number;
|
|
97
|
+
resolveQuestion(id: number, resolution: string): void;
|
|
98
|
+
getOpenQuestions(): OpenQuestion[];
|
|
99
|
+
search(query: string): {
|
|
100
|
+
decisions: Decision[];
|
|
101
|
+
discoveries: Discovery[];
|
|
102
|
+
entities: Entity[];
|
|
103
|
+
};
|
|
104
|
+
getPath(): string;
|
|
105
|
+
close(): void;
|
|
106
|
+
}
|
package/dist/database.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
// Configuration limits
|
|
6
|
+
export const LIMITS = {
|
|
7
|
+
MAX_DECISIONS: 1000,
|
|
8
|
+
MAX_DISCOVERIES: 500,
|
|
9
|
+
MAX_ENTITIES: 200,
|
|
10
|
+
MAX_QUESTIONS: 100,
|
|
11
|
+
MAX_SESSIONS: 100,
|
|
12
|
+
SESSION_RETENTION_DAYS: 90,
|
|
13
|
+
MAX_FIELD_LENGTH: 10000, // 10KB per field
|
|
14
|
+
};
|
|
15
|
+
export class MemoryDatabase {
|
|
16
|
+
db;
|
|
17
|
+
projectPath;
|
|
18
|
+
constructor(projectPath) {
|
|
19
|
+
// Use provided path, or detect from current directory, or use home
|
|
20
|
+
this.projectPath = projectPath || process.cwd();
|
|
21
|
+
const dbPath = this.getDbPath();
|
|
22
|
+
// Ensure directory exists
|
|
23
|
+
const dbDir = dirname(dbPath);
|
|
24
|
+
if (!existsSync(dbDir)) {
|
|
25
|
+
mkdirSync(dbDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
this.db = new Database(dbPath);
|
|
28
|
+
this.initSchema();
|
|
29
|
+
}
|
|
30
|
+
getDbPath() {
|
|
31
|
+
// Check for explicit environment variable override first
|
|
32
|
+
const envPath = process.env.PENSIEVE_DB_PATH;
|
|
33
|
+
if (envPath) {
|
|
34
|
+
console.error(`[Pensieve] Using database from PENSIEVE_DB_PATH: ${envPath}`);
|
|
35
|
+
return envPath;
|
|
36
|
+
}
|
|
37
|
+
// Try project-local first, then fall back to home directory
|
|
38
|
+
const localPath = join(this.projectPath, '.pensieve', 'memory.sqlite');
|
|
39
|
+
const globalPath = join(homedir(), '.claude-pensieve', 'memory.sqlite');
|
|
40
|
+
// If local .pensieve directory exists or we're in a git repo, use local
|
|
41
|
+
if (existsSync(join(this.projectPath, '.pensieve')) ||
|
|
42
|
+
existsSync(join(this.projectPath, '.git'))) {
|
|
43
|
+
console.error(`[Pensieve] Using project-local database: ${localPath}`);
|
|
44
|
+
return localPath;
|
|
45
|
+
}
|
|
46
|
+
console.error(`[Pensieve] Using global database: ${globalPath}`);
|
|
47
|
+
return globalPath;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Truncate a string to the maximum field length
|
|
51
|
+
*/
|
|
52
|
+
truncateField(value, fieldName) {
|
|
53
|
+
if (!value)
|
|
54
|
+
return null;
|
|
55
|
+
if (value.length <= LIMITS.MAX_FIELD_LENGTH)
|
|
56
|
+
return value;
|
|
57
|
+
console.error(`[Pensieve] Warning: Truncating ${fieldName || 'field'} from ${value.length} to ${LIMITS.MAX_FIELD_LENGTH} chars`);
|
|
58
|
+
return value.substring(0, LIMITS.MAX_FIELD_LENGTH) + '... [truncated]';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Prune old entries when limits are exceeded
|
|
62
|
+
*/
|
|
63
|
+
pruneIfNeeded() {
|
|
64
|
+
// Prune old sessions beyond retention period
|
|
65
|
+
this.db.prepare(`
|
|
66
|
+
DELETE FROM sessions
|
|
67
|
+
WHERE ended_at IS NOT NULL
|
|
68
|
+
AND datetime(ended_at) < datetime('now', '-${LIMITS.SESSION_RETENTION_DAYS} days')
|
|
69
|
+
`).run();
|
|
70
|
+
// Prune excess decisions (keep most recent)
|
|
71
|
+
const decisionCount = this.db.prepare('SELECT COUNT(*) as count FROM decisions').get().count;
|
|
72
|
+
if (decisionCount > LIMITS.MAX_DECISIONS) {
|
|
73
|
+
const excess = decisionCount - LIMITS.MAX_DECISIONS;
|
|
74
|
+
this.db.prepare(`
|
|
75
|
+
DELETE FROM decisions WHERE id IN (
|
|
76
|
+
SELECT id FROM decisions ORDER BY decided_at ASC LIMIT ?
|
|
77
|
+
)
|
|
78
|
+
`).run(excess);
|
|
79
|
+
console.error(`[Pensieve] Pruned ${excess} old decisions`);
|
|
80
|
+
}
|
|
81
|
+
// Prune excess discoveries
|
|
82
|
+
const discoveryCount = this.db.prepare('SELECT COUNT(*) as count FROM discoveries').get().count;
|
|
83
|
+
if (discoveryCount > LIMITS.MAX_DISCOVERIES) {
|
|
84
|
+
const excess = discoveryCount - LIMITS.MAX_DISCOVERIES;
|
|
85
|
+
this.db.prepare(`
|
|
86
|
+
DELETE FROM discoveries WHERE id IN (
|
|
87
|
+
SELECT id FROM discoveries ORDER BY discovered_at ASC LIMIT ?
|
|
88
|
+
)
|
|
89
|
+
`).run(excess);
|
|
90
|
+
console.error(`[Pensieve] Pruned ${excess} old discoveries`);
|
|
91
|
+
}
|
|
92
|
+
// Prune resolved questions older than 30 days
|
|
93
|
+
this.db.prepare(`
|
|
94
|
+
DELETE FROM open_questions
|
|
95
|
+
WHERE status = 'resolved'
|
|
96
|
+
AND datetime(resolved_at) < datetime('now', '-30 days')
|
|
97
|
+
`).run();
|
|
98
|
+
}
|
|
99
|
+
initSchema() {
|
|
100
|
+
this.db.exec(`
|
|
101
|
+
-- Core discoveries about the codebase
|
|
102
|
+
CREATE TABLE IF NOT EXISTS discoveries (
|
|
103
|
+
id INTEGER PRIMARY KEY,
|
|
104
|
+
category TEXT NOT NULL,
|
|
105
|
+
name TEXT NOT NULL,
|
|
106
|
+
location TEXT,
|
|
107
|
+
description TEXT,
|
|
108
|
+
metadata TEXT,
|
|
109
|
+
discovered_at TEXT DEFAULT (datetime('now')),
|
|
110
|
+
confidence REAL DEFAULT 1.0
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
-- Architectural and design decisions
|
|
114
|
+
CREATE TABLE IF NOT EXISTS decisions (
|
|
115
|
+
id INTEGER PRIMARY KEY,
|
|
116
|
+
topic TEXT NOT NULL,
|
|
117
|
+
decision TEXT NOT NULL,
|
|
118
|
+
rationale TEXT,
|
|
119
|
+
alternatives TEXT,
|
|
120
|
+
decided_at TEXT DEFAULT (datetime('now')),
|
|
121
|
+
source TEXT
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
-- User preferences and conventions
|
|
125
|
+
CREATE TABLE IF NOT EXISTS preferences (
|
|
126
|
+
id INTEGER PRIMARY KEY,
|
|
127
|
+
category TEXT NOT NULL,
|
|
128
|
+
key TEXT NOT NULL,
|
|
129
|
+
value TEXT NOT NULL,
|
|
130
|
+
notes TEXT,
|
|
131
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
132
|
+
UNIQUE(category, key)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
-- Session summaries for continuity
|
|
136
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
137
|
+
id INTEGER PRIMARY KEY,
|
|
138
|
+
started_at TEXT DEFAULT (datetime('now')),
|
|
139
|
+
ended_at TEXT,
|
|
140
|
+
summary TEXT,
|
|
141
|
+
work_in_progress TEXT,
|
|
142
|
+
next_steps TEXT,
|
|
143
|
+
key_files TEXT,
|
|
144
|
+
tags TEXT
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
-- Entities/domain model understanding
|
|
148
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
149
|
+
id INTEGER PRIMARY KEY,
|
|
150
|
+
name TEXT NOT NULL UNIQUE,
|
|
151
|
+
description TEXT,
|
|
152
|
+
relationships TEXT,
|
|
153
|
+
attributes TEXT,
|
|
154
|
+
location TEXT,
|
|
155
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
-- Open questions and blockers
|
|
159
|
+
CREATE TABLE IF NOT EXISTS open_questions (
|
|
160
|
+
id INTEGER PRIMARY KEY,
|
|
161
|
+
question TEXT NOT NULL,
|
|
162
|
+
context TEXT,
|
|
163
|
+
status TEXT DEFAULT 'open',
|
|
164
|
+
resolution TEXT,
|
|
165
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
166
|
+
resolved_at TEXT
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
-- Indexes for common queries
|
|
170
|
+
CREATE INDEX IF NOT EXISTS idx_discoveries_category ON discoveries(category);
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_discoveries_name ON discoveries(name);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_topic ON decisions(topic);
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_preferences_category ON preferences(category);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_open_questions_status ON open_questions(status);
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
// Decision methods
|
|
179
|
+
addDecision(decision) {
|
|
180
|
+
this.pruneIfNeeded();
|
|
181
|
+
const stmt = this.db.prepare(`
|
|
182
|
+
INSERT INTO decisions (topic, decision, rationale, alternatives, source)
|
|
183
|
+
VALUES (?, ?, ?, ?, ?)
|
|
184
|
+
`);
|
|
185
|
+
const result = stmt.run(this.truncateField(decision.topic, 'topic'), this.truncateField(decision.decision, 'decision'), this.truncateField(decision.rationale, 'rationale'), this.truncateField(decision.alternatives, 'alternatives'), decision.source || 'user');
|
|
186
|
+
return result.lastInsertRowid;
|
|
187
|
+
}
|
|
188
|
+
searchDecisions(query) {
|
|
189
|
+
const stmt = this.db.prepare(`
|
|
190
|
+
SELECT * FROM decisions
|
|
191
|
+
WHERE topic LIKE ? OR decision LIKE ? OR rationale LIKE ?
|
|
192
|
+
ORDER BY decided_at DESC
|
|
193
|
+
LIMIT 50
|
|
194
|
+
`);
|
|
195
|
+
const pattern = `%${query}%`;
|
|
196
|
+
return stmt.all(pattern, pattern, pattern);
|
|
197
|
+
}
|
|
198
|
+
getRecentDecisions(limit = 10) {
|
|
199
|
+
const stmt = this.db.prepare(`
|
|
200
|
+
SELECT * FROM decisions
|
|
201
|
+
ORDER BY decided_at DESC
|
|
202
|
+
LIMIT ?
|
|
203
|
+
`);
|
|
204
|
+
return stmt.all(limit);
|
|
205
|
+
}
|
|
206
|
+
// Preference methods
|
|
207
|
+
setPreference(pref) {
|
|
208
|
+
const stmt = this.db.prepare(`
|
|
209
|
+
INSERT OR REPLACE INTO preferences (category, key, value, notes, updated_at)
|
|
210
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
211
|
+
`);
|
|
212
|
+
stmt.run(this.truncateField(pref.category, 'category'), this.truncateField(pref.key, 'key'), this.truncateField(pref.value, 'value'), this.truncateField(pref.notes, 'notes'));
|
|
213
|
+
}
|
|
214
|
+
getPreference(category, key) {
|
|
215
|
+
const stmt = this.db.prepare(`
|
|
216
|
+
SELECT * FROM preferences WHERE category = ? AND key = ?
|
|
217
|
+
`);
|
|
218
|
+
return stmt.get(category, key);
|
|
219
|
+
}
|
|
220
|
+
getPreferencesByCategory(category) {
|
|
221
|
+
const stmt = this.db.prepare(`
|
|
222
|
+
SELECT * FROM preferences WHERE category = ? ORDER BY key
|
|
223
|
+
`);
|
|
224
|
+
return stmt.all(category);
|
|
225
|
+
}
|
|
226
|
+
getAllPreferences() {
|
|
227
|
+
const stmt = this.db.prepare(`
|
|
228
|
+
SELECT * FROM preferences ORDER BY category, key
|
|
229
|
+
`);
|
|
230
|
+
return stmt.all();
|
|
231
|
+
}
|
|
232
|
+
// Discovery methods
|
|
233
|
+
addDiscovery(discovery) {
|
|
234
|
+
this.pruneIfNeeded();
|
|
235
|
+
const stmt = this.db.prepare(`
|
|
236
|
+
INSERT INTO discoveries (category, name, location, description, metadata, confidence)
|
|
237
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
238
|
+
`);
|
|
239
|
+
const result = stmt.run(this.truncateField(discovery.category, 'category'), this.truncateField(discovery.name, 'name'), this.truncateField(discovery.location, 'location'), this.truncateField(discovery.description, 'description'), this.truncateField(discovery.metadata, 'metadata'), discovery.confidence || 1.0);
|
|
240
|
+
return result.lastInsertRowid;
|
|
241
|
+
}
|
|
242
|
+
searchDiscoveries(query) {
|
|
243
|
+
const stmt = this.db.prepare(`
|
|
244
|
+
SELECT * FROM discoveries
|
|
245
|
+
WHERE name LIKE ? OR description LIKE ? OR location LIKE ?
|
|
246
|
+
ORDER BY discovered_at DESC
|
|
247
|
+
LIMIT 50
|
|
248
|
+
`);
|
|
249
|
+
const pattern = `%${query}%`;
|
|
250
|
+
return stmt.all(pattern, pattern, pattern);
|
|
251
|
+
}
|
|
252
|
+
getDiscoveriesByCategory(category) {
|
|
253
|
+
const stmt = this.db.prepare(`
|
|
254
|
+
SELECT * FROM discoveries WHERE category = ? ORDER BY name
|
|
255
|
+
`);
|
|
256
|
+
return stmt.all(category);
|
|
257
|
+
}
|
|
258
|
+
// Entity methods
|
|
259
|
+
upsertEntity(entity) {
|
|
260
|
+
const stmt = this.db.prepare(`
|
|
261
|
+
INSERT OR REPLACE INTO entities (name, description, relationships, attributes, location, updated_at)
|
|
262
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))
|
|
263
|
+
`);
|
|
264
|
+
stmt.run(this.truncateField(entity.name, 'name'), this.truncateField(entity.description, 'description'), this.truncateField(entity.relationships, 'relationships'), this.truncateField(entity.attributes, 'attributes'), this.truncateField(entity.location, 'location'));
|
|
265
|
+
}
|
|
266
|
+
getEntity(name) {
|
|
267
|
+
const stmt = this.db.prepare(`SELECT * FROM entities WHERE name = ?`);
|
|
268
|
+
return stmt.get(name);
|
|
269
|
+
}
|
|
270
|
+
getAllEntities() {
|
|
271
|
+
const stmt = this.db.prepare(`SELECT * FROM entities ORDER BY name`);
|
|
272
|
+
return stmt.all();
|
|
273
|
+
}
|
|
274
|
+
// Session methods
|
|
275
|
+
startSession() {
|
|
276
|
+
const stmt = this.db.prepare(`INSERT INTO sessions (started_at) VALUES (datetime('now'))`);
|
|
277
|
+
const result = stmt.run();
|
|
278
|
+
return result.lastInsertRowid;
|
|
279
|
+
}
|
|
280
|
+
endSession(sessionId, summary, workInProgress, nextSteps, keyFiles, tags) {
|
|
281
|
+
this.pruneIfNeeded();
|
|
282
|
+
const stmt = this.db.prepare(`
|
|
283
|
+
UPDATE sessions
|
|
284
|
+
SET ended_at = datetime('now'),
|
|
285
|
+
summary = ?,
|
|
286
|
+
work_in_progress = ?,
|
|
287
|
+
next_steps = ?,
|
|
288
|
+
key_files = ?,
|
|
289
|
+
tags = ?
|
|
290
|
+
WHERE id = ?
|
|
291
|
+
`);
|
|
292
|
+
stmt.run(this.truncateField(summary, 'summary'), this.truncateField(workInProgress, 'work_in_progress'), this.truncateField(nextSteps, 'next_steps'), keyFiles ? this.truncateField(JSON.stringify(keyFiles), 'key_files') : null, tags ? tags.join(',') : null, sessionId);
|
|
293
|
+
}
|
|
294
|
+
getLastSession() {
|
|
295
|
+
const stmt = this.db.prepare(`
|
|
296
|
+
SELECT * FROM sessions ORDER BY started_at DESC LIMIT 1
|
|
297
|
+
`);
|
|
298
|
+
return stmt.get();
|
|
299
|
+
}
|
|
300
|
+
getCurrentSession() {
|
|
301
|
+
const stmt = this.db.prepare(`
|
|
302
|
+
SELECT * FROM sessions WHERE ended_at IS NULL ORDER BY started_at DESC LIMIT 1
|
|
303
|
+
`);
|
|
304
|
+
return stmt.get();
|
|
305
|
+
}
|
|
306
|
+
// Open questions methods
|
|
307
|
+
addQuestion(question, context) {
|
|
308
|
+
const stmt = this.db.prepare(`
|
|
309
|
+
INSERT INTO open_questions (question, context) VALUES (?, ?)
|
|
310
|
+
`);
|
|
311
|
+
const result = stmt.run(this.truncateField(question, 'question'), this.truncateField(context, 'context'));
|
|
312
|
+
return result.lastInsertRowid;
|
|
313
|
+
}
|
|
314
|
+
resolveQuestion(id, resolution) {
|
|
315
|
+
const stmt = this.db.prepare(`
|
|
316
|
+
UPDATE open_questions
|
|
317
|
+
SET status = 'resolved', resolution = ?, resolved_at = datetime('now')
|
|
318
|
+
WHERE id = ?
|
|
319
|
+
`);
|
|
320
|
+
stmt.run(resolution, id);
|
|
321
|
+
}
|
|
322
|
+
getOpenQuestions() {
|
|
323
|
+
const stmt = this.db.prepare(`
|
|
324
|
+
SELECT * FROM open_questions WHERE status = 'open' ORDER BY created_at DESC
|
|
325
|
+
`);
|
|
326
|
+
return stmt.all();
|
|
327
|
+
}
|
|
328
|
+
// General search
|
|
329
|
+
search(query) {
|
|
330
|
+
return {
|
|
331
|
+
decisions: this.searchDecisions(query),
|
|
332
|
+
discoveries: this.searchDiscoveries(query),
|
|
333
|
+
entities: this.getAllEntities().filter(e => e.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
334
|
+
e.description?.toLowerCase().includes(query.toLowerCase()))
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// Get database path for debugging
|
|
338
|
+
getPath() {
|
|
339
|
+
return this.getDbPath();
|
|
340
|
+
}
|
|
341
|
+
close() {
|
|
342
|
+
this.db.close();
|
|
343
|
+
}
|
|
344
|
+
}
|
package/dist/index.d.ts
ADDED