@su-record/vibe 2.6.25 → 2.6.27

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.
Files changed (63) hide show
  1. package/CLAUDE.md +49 -0
  2. package/README.md +31 -0
  3. package/agents/docs/api-documenter.md +99 -0
  4. package/agents/docs/changelog-writer.md +93 -0
  5. package/agents/planning/requirements-analyst.md +84 -0
  6. package/agents/planning/ux-advisor.md +83 -0
  7. package/agents/qa/acceptance-tester.md +86 -0
  8. package/agents/qa/edge-case-finder.md +93 -0
  9. package/dist/lib/MemoryManager.d.ts +30 -0
  10. package/dist/lib/MemoryManager.d.ts.map +1 -1
  11. package/dist/lib/MemoryManager.js +74 -0
  12. package/dist/lib/MemoryManager.js.map +1 -1
  13. package/dist/lib/memory/ObservationStore.d.ts.map +1 -1
  14. package/dist/lib/memory/ObservationStore.js +0 -1
  15. package/dist/lib/memory/ObservationStore.js.map +1 -1
  16. package/dist/lib/memory/SessionRAGRetriever.d.ts +66 -0
  17. package/dist/lib/memory/SessionRAGRetriever.d.ts.map +1 -0
  18. package/dist/lib/memory/SessionRAGRetriever.js +196 -0
  19. package/dist/lib/memory/SessionRAGRetriever.js.map +1 -0
  20. package/dist/lib/memory/SessionRAGRetriever.test.d.ts +2 -0
  21. package/dist/lib/memory/SessionRAGRetriever.test.d.ts.map +1 -0
  22. package/dist/lib/memory/SessionRAGRetriever.test.js +180 -0
  23. package/dist/lib/memory/SessionRAGRetriever.test.js.map +1 -0
  24. package/dist/lib/memory/SessionRAGStore.d.ts +153 -0
  25. package/dist/lib/memory/SessionRAGStore.d.ts.map +1 -0
  26. package/dist/lib/memory/SessionRAGStore.js +673 -0
  27. package/dist/lib/memory/SessionRAGStore.js.map +1 -0
  28. package/dist/lib/memory/SessionRAGStore.test.d.ts +2 -0
  29. package/dist/lib/memory/SessionRAGStore.test.d.ts.map +1 -0
  30. package/dist/lib/memory/SessionRAGStore.test.js +326 -0
  31. package/dist/lib/memory/SessionRAGStore.test.js.map +1 -0
  32. package/dist/tools/index.d.ts +5 -0
  33. package/dist/tools/index.d.ts.map +1 -1
  34. package/dist/tools/index.js +4 -0
  35. package/dist/tools/index.js.map +1 -1
  36. package/dist/tools/memory/index.d.ts +3 -0
  37. package/dist/tools/memory/index.d.ts.map +1 -1
  38. package/dist/tools/memory/index.js +4 -0
  39. package/dist/tools/memory/index.js.map +1 -1
  40. package/dist/tools/memory/manageGoals.d.ts +11 -0
  41. package/dist/tools/memory/manageGoals.d.ts.map +1 -0
  42. package/dist/tools/memory/manageGoals.js +152 -0
  43. package/dist/tools/memory/manageGoals.js.map +1 -0
  44. package/dist/tools/memory/retrieveSessionContext.d.ts +9 -0
  45. package/dist/tools/memory/retrieveSessionContext.d.ts.map +1 -0
  46. package/dist/tools/memory/retrieveSessionContext.js +140 -0
  47. package/dist/tools/memory/retrieveSessionContext.js.map +1 -0
  48. package/dist/tools/memory/saveSessionItem.d.ts +26 -0
  49. package/dist/tools/memory/saveSessionItem.d.ts.map +1 -0
  50. package/dist/tools/memory/saveSessionItem.js +218 -0
  51. package/dist/tools/memory/saveSessionItem.js.map +1 -0
  52. package/dist/tools/memory/startSession.d.ts.map +1 -1
  53. package/dist/tools/memory/startSession.js +29 -0
  54. package/dist/tools/memory/startSession.js.map +1 -1
  55. package/dist/tools/spec/e2eTestGenerator.d.ts +62 -0
  56. package/dist/tools/spec/e2eTestGenerator.d.ts.map +1 -0
  57. package/dist/tools/spec/e2eTestGenerator.js +256 -0
  58. package/dist/tools/spec/e2eTestGenerator.js.map +1 -0
  59. package/dist/tools/spec/index.d.ts +2 -0
  60. package/dist/tools/spec/index.d.ts.map +1 -1
  61. package/dist/tools/spec/index.js +2 -0
  62. package/dist/tools/spec/index.js.map +1 -1
  63. package/package.json +1 -1
@@ -0,0 +1,673 @@
1
+ // Session RAG Store - Structured session context storage
2
+ // Manages Decisions, Constraints, Goals, Evidence with FTS5 search
3
+ // ============================================================================
4
+ // SessionRAGStore
5
+ // ============================================================================
6
+ export class SessionRAGStore {
7
+ db;
8
+ fts5Available = false;
9
+ constructor(storage) {
10
+ this.db = storage.getDatabase();
11
+ this.fts5Available = storage.isFTS5Available();
12
+ this.initializeTables();
13
+ }
14
+ // ==========================================================================
15
+ // Schema Initialization
16
+ // ==========================================================================
17
+ initializeTables() {
18
+ this.db.exec(`
19
+ CREATE TABLE IF NOT EXISTS session_decisions (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ sessionId TEXT,
22
+ title TEXT NOT NULL,
23
+ description TEXT,
24
+ rationale TEXT,
25
+ alternatives TEXT,
26
+ impact TEXT,
27
+ status TEXT DEFAULT 'active' CHECK(status IN ('active','superseded','cancelled')),
28
+ priority INTEGER DEFAULT 1,
29
+ relatedFiles TEXT,
30
+ tags TEXT,
31
+ timestamp TEXT NOT NULL
32
+ );
33
+
34
+ CREATE INDEX IF NOT EXISTS idx_sdec_session ON session_decisions(sessionId);
35
+ CREATE INDEX IF NOT EXISTS idx_sdec_status ON session_decisions(status);
36
+ CREATE INDEX IF NOT EXISTS idx_sdec_priority ON session_decisions(priority);
37
+ CREATE INDEX IF NOT EXISTS idx_sdec_timestamp ON session_decisions(timestamp);
38
+ `);
39
+ this.db.exec(`
40
+ CREATE TABLE IF NOT EXISTS session_constraints (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ sessionId TEXT,
43
+ title TEXT NOT NULL,
44
+ description TEXT,
45
+ type TEXT NOT NULL CHECK(type IN ('technical','business','resource','quality')),
46
+ severity TEXT DEFAULT 'medium' CHECK(severity IN ('low','medium','high','critical')),
47
+ scope TEXT,
48
+ timestamp TEXT NOT NULL
49
+ );
50
+
51
+ CREATE INDEX IF NOT EXISTS idx_scon_session ON session_constraints(sessionId);
52
+ CREATE INDEX IF NOT EXISTS idx_scon_type ON session_constraints(type);
53
+ CREATE INDEX IF NOT EXISTS idx_scon_severity ON session_constraints(severity);
54
+ `);
55
+ this.db.exec(`
56
+ CREATE TABLE IF NOT EXISTS session_goals (
57
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
58
+ sessionId TEXT,
59
+ parentId INTEGER,
60
+ title TEXT NOT NULL,
61
+ description TEXT,
62
+ status TEXT DEFAULT 'active' CHECK(status IN ('active','completed','blocked','cancelled')),
63
+ priority INTEGER DEFAULT 1,
64
+ progressPercent INTEGER DEFAULT 0 CHECK(progressPercent >= 0 AND progressPercent <= 100),
65
+ successCriteria TEXT,
66
+ timestamp TEXT NOT NULL,
67
+ completedAt TEXT,
68
+ FOREIGN KEY(parentId) REFERENCES session_goals(id)
69
+ );
70
+
71
+ CREATE INDEX IF NOT EXISTS idx_sgoal_session ON session_goals(sessionId);
72
+ CREATE INDEX IF NOT EXISTS idx_sgoal_status ON session_goals(status);
73
+ CREATE INDEX IF NOT EXISTS idx_sgoal_priority ON session_goals(priority);
74
+ CREATE INDEX IF NOT EXISTS idx_sgoal_parent ON session_goals(parentId);
75
+ `);
76
+ this.db.exec(`
77
+ CREATE TABLE IF NOT EXISTS session_evidence (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ sessionId TEXT,
80
+ type TEXT NOT NULL CHECK(type IN ('test','build','lint','coverage','hud','review')),
81
+ title TEXT NOT NULL,
82
+ status TEXT NOT NULL CHECK(status IN ('pass','fail','warning','info')),
83
+ details TEXT,
84
+ metrics TEXT,
85
+ relatedGoals TEXT,
86
+ timestamp TEXT NOT NULL
87
+ );
88
+
89
+ CREATE INDEX IF NOT EXISTS idx_sevi_session ON session_evidence(sessionId);
90
+ CREATE INDEX IF NOT EXISTS idx_sevi_type ON session_evidence(type);
91
+ CREATE INDEX IF NOT EXISTS idx_sevi_status ON session_evidence(status);
92
+ CREATE INDEX IF NOT EXISTS idx_sevi_timestamp ON session_evidence(timestamp);
93
+ `);
94
+ this.initializeFTS5();
95
+ }
96
+ initializeFTS5() {
97
+ if (!this.fts5Available)
98
+ return;
99
+ try {
100
+ // Decisions FTS
101
+ this.db.exec(`
102
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_decisions_fts
103
+ USING fts5(title, description, rationale, content=session_decisions, content_rowid=id);
104
+
105
+ CREATE TRIGGER IF NOT EXISTS sdec_fts_ai AFTER INSERT ON session_decisions BEGIN
106
+ INSERT INTO session_decisions_fts(rowid, title, description, rationale)
107
+ VALUES (new.id, new.title, new.description, new.rationale);
108
+ END;
109
+ CREATE TRIGGER IF NOT EXISTS sdec_fts_ad AFTER DELETE ON session_decisions BEGIN
110
+ INSERT INTO session_decisions_fts(session_decisions_fts, rowid, title, description, rationale)
111
+ VALUES('delete', old.id, old.title, old.description, old.rationale);
112
+ END;
113
+ CREATE TRIGGER IF NOT EXISTS sdec_fts_au AFTER UPDATE ON session_decisions BEGIN
114
+ INSERT INTO session_decisions_fts(session_decisions_fts, rowid, title, description, rationale)
115
+ VALUES('delete', old.id, old.title, old.description, old.rationale);
116
+ INSERT INTO session_decisions_fts(rowid, title, description, rationale)
117
+ VALUES (new.id, new.title, new.description, new.rationale);
118
+ END;
119
+ `);
120
+ // Constraints FTS
121
+ this.db.exec(`
122
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_constraints_fts
123
+ USING fts5(title, description, content=session_constraints, content_rowid=id);
124
+
125
+ CREATE TRIGGER IF NOT EXISTS scon_fts_ai AFTER INSERT ON session_constraints BEGIN
126
+ INSERT INTO session_constraints_fts(rowid, title, description)
127
+ VALUES (new.id, new.title, new.description);
128
+ END;
129
+ CREATE TRIGGER IF NOT EXISTS scon_fts_ad AFTER DELETE ON session_constraints BEGIN
130
+ INSERT INTO session_constraints_fts(session_constraints_fts, rowid, title, description)
131
+ VALUES('delete', old.id, old.title, old.description);
132
+ END;
133
+ CREATE TRIGGER IF NOT EXISTS scon_fts_au AFTER UPDATE ON session_constraints BEGIN
134
+ INSERT INTO session_constraints_fts(session_constraints_fts, rowid, title, description)
135
+ VALUES('delete', old.id, old.title, old.description);
136
+ INSERT INTO session_constraints_fts(rowid, title, description)
137
+ VALUES (new.id, new.title, new.description);
138
+ END;
139
+ `);
140
+ // Goals FTS
141
+ this.db.exec(`
142
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_goals_fts
143
+ USING fts5(title, description, content=session_goals, content_rowid=id);
144
+
145
+ CREATE TRIGGER IF NOT EXISTS sgoal_fts_ai AFTER INSERT ON session_goals BEGIN
146
+ INSERT INTO session_goals_fts(rowid, title, description)
147
+ VALUES (new.id, new.title, new.description);
148
+ END;
149
+ CREATE TRIGGER IF NOT EXISTS sgoal_fts_ad AFTER DELETE ON session_goals BEGIN
150
+ INSERT INTO session_goals_fts(session_goals_fts, rowid, title, description)
151
+ VALUES('delete', old.id, old.title, old.description);
152
+ END;
153
+ CREATE TRIGGER IF NOT EXISTS sgoal_fts_au AFTER UPDATE ON session_goals BEGIN
154
+ INSERT INTO session_goals_fts(session_goals_fts, rowid, title, description)
155
+ VALUES('delete', old.id, old.title, old.description);
156
+ INSERT INTO session_goals_fts(rowid, title, description)
157
+ VALUES (new.id, new.title, new.description);
158
+ END;
159
+ `);
160
+ // Evidence FTS
161
+ this.db.exec(`
162
+ CREATE VIRTUAL TABLE IF NOT EXISTS session_evidence_fts
163
+ USING fts5(title, details, content=session_evidence, content_rowid=id);
164
+
165
+ CREATE TRIGGER IF NOT EXISTS sevi_fts_ai AFTER INSERT ON session_evidence BEGIN
166
+ INSERT INTO session_evidence_fts(rowid, title, details)
167
+ VALUES (new.id, new.title, new.details);
168
+ END;
169
+ CREATE TRIGGER IF NOT EXISTS sevi_fts_ad AFTER DELETE ON session_evidence BEGIN
170
+ INSERT INTO session_evidence_fts(session_evidence_fts, rowid, title, details)
171
+ VALUES('delete', old.id, old.title, old.details);
172
+ END;
173
+ CREATE TRIGGER IF NOT EXISTS sevi_fts_au AFTER UPDATE ON session_evidence BEGIN
174
+ INSERT INTO session_evidence_fts(session_evidence_fts, rowid, title, details)
175
+ VALUES('delete', old.id, old.title, old.details);
176
+ INSERT INTO session_evidence_fts(rowid, title, details)
177
+ VALUES (new.id, new.title, new.details);
178
+ END;
179
+ `);
180
+ }
181
+ catch {
182
+ // FTS5 initialization failed for session RAG tables - non-critical
183
+ }
184
+ }
185
+ // ==========================================================================
186
+ // Decisions
187
+ // ==========================================================================
188
+ addDecision(input) {
189
+ const timestamp = new Date().toISOString();
190
+ const stmt = this.db.prepare(`
191
+ INSERT INTO session_decisions (sessionId, title, description, rationale, alternatives, impact, status, priority, relatedFiles, tags, timestamp)
192
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
193
+ `);
194
+ const result = stmt.run(input.sessionId || null, input.title, input.description || null, input.rationale || null, input.alternatives ? JSON.stringify(input.alternatives) : null, input.impact || null, input.status || 'active', input.priority ?? 1, input.relatedFiles ? JSON.stringify(input.relatedFiles) : null, input.tags ? JSON.stringify(input.tags) : null, timestamp);
195
+ return result.lastInsertRowid;
196
+ }
197
+ getDecision(id) {
198
+ const row = this.db.prepare(`SELECT * FROM session_decisions WHERE id = ?`).get(id);
199
+ return row ? this.rowToDecision(row) : null;
200
+ }
201
+ updateDecision(id, updates) {
202
+ const fields = [];
203
+ const values = [];
204
+ if (updates.title !== undefined) {
205
+ fields.push('title = ?');
206
+ values.push(updates.title);
207
+ }
208
+ if (updates.description !== undefined) {
209
+ fields.push('description = ?');
210
+ values.push(updates.description);
211
+ }
212
+ if (updates.rationale !== undefined) {
213
+ fields.push('rationale = ?');
214
+ values.push(updates.rationale);
215
+ }
216
+ if (updates.alternatives !== undefined) {
217
+ fields.push('alternatives = ?');
218
+ values.push(JSON.stringify(updates.alternatives));
219
+ }
220
+ if (updates.impact !== undefined) {
221
+ fields.push('impact = ?');
222
+ values.push(updates.impact);
223
+ }
224
+ if (updates.status !== undefined) {
225
+ fields.push('status = ?');
226
+ values.push(updates.status);
227
+ }
228
+ if (updates.priority !== undefined) {
229
+ fields.push('priority = ?');
230
+ values.push(updates.priority);
231
+ }
232
+ if (updates.relatedFiles !== undefined) {
233
+ fields.push('relatedFiles = ?');
234
+ values.push(JSON.stringify(updates.relatedFiles));
235
+ }
236
+ if (updates.tags !== undefined) {
237
+ fields.push('tags = ?');
238
+ values.push(JSON.stringify(updates.tags));
239
+ }
240
+ if (fields.length === 0)
241
+ return false;
242
+ values.push(id);
243
+ const result = this.db.prepare(`UPDATE session_decisions SET ${fields.join(', ')} WHERE id = ?`).run(...values);
244
+ return result.changes > 0;
245
+ }
246
+ listDecisions(sessionId, status, limit = 50) {
247
+ let sql = 'SELECT * FROM session_decisions WHERE 1=1';
248
+ const params = [];
249
+ if (sessionId) {
250
+ sql += ' AND sessionId = ?';
251
+ params.push(sessionId);
252
+ }
253
+ if (status) {
254
+ sql += ' AND status = ?';
255
+ params.push(status);
256
+ }
257
+ sql += ' ORDER BY priority DESC, timestamp DESC LIMIT ?';
258
+ params.push(limit);
259
+ const rows = this.db.prepare(sql).all(...params);
260
+ return rows.map(this.rowToDecision);
261
+ }
262
+ searchDecisions(query, limit = 20) {
263
+ if (this.fts5Available) {
264
+ try {
265
+ const rows = this.db.prepare(`
266
+ SELECT d.*, bm25(session_decisions_fts) as rank
267
+ FROM session_decisions_fts fts
268
+ JOIN session_decisions d ON d.id = fts.rowid
269
+ WHERE session_decisions_fts MATCH ?
270
+ ORDER BY rank
271
+ LIMIT ?
272
+ `).all(query, limit);
273
+ return rows.map(this.rowToDecision);
274
+ }
275
+ catch {
276
+ // FTS5 query failed, fall through to LIKE
277
+ }
278
+ }
279
+ const pattern = `%${query}%`;
280
+ const rows = this.db.prepare(`
281
+ SELECT * FROM session_decisions
282
+ WHERE title LIKE ? OR description LIKE ? OR rationale LIKE ?
283
+ ORDER BY priority DESC, timestamp DESC LIMIT ?
284
+ `).all(pattern, pattern, pattern, limit);
285
+ return rows.map(this.rowToDecision);
286
+ }
287
+ deleteDecision(id) {
288
+ const result = this.db.prepare('DELETE FROM session_decisions WHERE id = ?').run(id);
289
+ return result.changes > 0;
290
+ }
291
+ rowToDecision(row) {
292
+ return {
293
+ id: row.id,
294
+ sessionId: row.sessionId,
295
+ title: row.title,
296
+ description: row.description,
297
+ rationale: row.rationale,
298
+ alternatives: row.alternatives ? JSON.parse(row.alternatives) : [],
299
+ impact: row.impact,
300
+ status: row.status,
301
+ priority: row.priority,
302
+ relatedFiles: row.relatedFiles ? JSON.parse(row.relatedFiles) : [],
303
+ tags: row.tags ? JSON.parse(row.tags) : [],
304
+ timestamp: row.timestamp,
305
+ };
306
+ }
307
+ // ==========================================================================
308
+ // Constraints
309
+ // ==========================================================================
310
+ addConstraint(input) {
311
+ const timestamp = new Date().toISOString();
312
+ const stmt = this.db.prepare(`
313
+ INSERT INTO session_constraints (sessionId, title, description, type, severity, scope, timestamp)
314
+ VALUES (?, ?, ?, ?, ?, ?, ?)
315
+ `);
316
+ const result = stmt.run(input.sessionId || null, input.title, input.description || null, input.type, input.severity || 'medium', input.scope || null, timestamp);
317
+ return result.lastInsertRowid;
318
+ }
319
+ getConstraint(id) {
320
+ const row = this.db.prepare('SELECT * FROM session_constraints WHERE id = ?').get(id);
321
+ return row ? this.rowToConstraint(row) : null;
322
+ }
323
+ updateConstraint(id, updates) {
324
+ const fields = [];
325
+ const values = [];
326
+ if (updates.title !== undefined) {
327
+ fields.push('title = ?');
328
+ values.push(updates.title);
329
+ }
330
+ if (updates.description !== undefined) {
331
+ fields.push('description = ?');
332
+ values.push(updates.description);
333
+ }
334
+ if (updates.type !== undefined) {
335
+ fields.push('type = ?');
336
+ values.push(updates.type);
337
+ }
338
+ if (updates.severity !== undefined) {
339
+ fields.push('severity = ?');
340
+ values.push(updates.severity);
341
+ }
342
+ if (updates.scope !== undefined) {
343
+ fields.push('scope = ?');
344
+ values.push(updates.scope);
345
+ }
346
+ if (fields.length === 0)
347
+ return false;
348
+ values.push(id);
349
+ const result = this.db.prepare(`UPDATE session_constraints SET ${fields.join(', ')} WHERE id = ?`).run(...values);
350
+ return result.changes > 0;
351
+ }
352
+ listConstraints(sessionId, type, severity, limit = 50) {
353
+ let sql = 'SELECT * FROM session_constraints WHERE 1=1';
354
+ const params = [];
355
+ if (sessionId) {
356
+ sql += ' AND sessionId = ?';
357
+ params.push(sessionId);
358
+ }
359
+ if (type) {
360
+ sql += ' AND type = ?';
361
+ params.push(type);
362
+ }
363
+ if (severity) {
364
+ sql += ' AND severity = ?';
365
+ params.push(severity);
366
+ }
367
+ sql += ' ORDER BY CASE severity WHEN \'critical\' THEN 0 WHEN \'high\' THEN 1 WHEN \'medium\' THEN 2 WHEN \'low\' THEN 3 END, timestamp DESC LIMIT ?';
368
+ params.push(limit);
369
+ const rows = this.db.prepare(sql).all(...params);
370
+ return rows.map(this.rowToConstraint);
371
+ }
372
+ searchConstraints(query, limit = 20) {
373
+ if (this.fts5Available) {
374
+ try {
375
+ const rows = this.db.prepare(`
376
+ SELECT c.*, bm25(session_constraints_fts) as rank
377
+ FROM session_constraints_fts fts
378
+ JOIN session_constraints c ON c.id = fts.rowid
379
+ WHERE session_constraints_fts MATCH ?
380
+ ORDER BY rank
381
+ LIMIT ?
382
+ `).all(query, limit);
383
+ return rows.map(this.rowToConstraint);
384
+ }
385
+ catch {
386
+ // Fall through to LIKE
387
+ }
388
+ }
389
+ const pattern = `%${query}%`;
390
+ const rows = this.db.prepare(`
391
+ SELECT * FROM session_constraints
392
+ WHERE title LIKE ? OR description LIKE ?
393
+ ORDER BY timestamp DESC LIMIT ?
394
+ `).all(pattern, pattern, limit);
395
+ return rows.map(this.rowToConstraint);
396
+ }
397
+ deleteConstraint(id) {
398
+ const result = this.db.prepare('DELETE FROM session_constraints WHERE id = ?').run(id);
399
+ return result.changes > 0;
400
+ }
401
+ rowToConstraint(row) {
402
+ return {
403
+ id: row.id,
404
+ sessionId: row.sessionId,
405
+ title: row.title,
406
+ description: row.description,
407
+ type: row.type,
408
+ severity: row.severity,
409
+ scope: row.scope,
410
+ timestamp: row.timestamp,
411
+ };
412
+ }
413
+ // ==========================================================================
414
+ // Goals
415
+ // ==========================================================================
416
+ addGoal(input) {
417
+ const timestamp = new Date().toISOString();
418
+ const stmt = this.db.prepare(`
419
+ INSERT INTO session_goals (sessionId, parentId, title, description, status, priority, progressPercent, successCriteria, timestamp, completedAt)
420
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
421
+ `);
422
+ const result = stmt.run(input.sessionId || null, input.parentId || null, input.title, input.description || null, input.status || 'active', input.priority ?? 1, input.progressPercent ?? 0, input.successCriteria ? JSON.stringify(input.successCriteria) : null, timestamp, null);
423
+ return result.lastInsertRowid;
424
+ }
425
+ getGoal(id) {
426
+ const row = this.db.prepare('SELECT * FROM session_goals WHERE id = ?').get(id);
427
+ return row ? this.rowToGoal(row) : null;
428
+ }
429
+ updateGoal(id, updates) {
430
+ const fields = [];
431
+ const values = [];
432
+ if (updates.title !== undefined) {
433
+ fields.push('title = ?');
434
+ values.push(updates.title);
435
+ }
436
+ if (updates.description !== undefined) {
437
+ fields.push('description = ?');
438
+ values.push(updates.description);
439
+ }
440
+ if (updates.status !== undefined) {
441
+ fields.push('status = ?');
442
+ values.push(updates.status);
443
+ if (updates.status === 'completed') {
444
+ fields.push('completedAt = ?');
445
+ values.push(new Date().toISOString());
446
+ }
447
+ }
448
+ if (updates.priority !== undefined) {
449
+ fields.push('priority = ?');
450
+ values.push(updates.priority);
451
+ }
452
+ if (updates.progressPercent !== undefined) {
453
+ fields.push('progressPercent = ?');
454
+ values.push(updates.progressPercent);
455
+ }
456
+ if (updates.successCriteria !== undefined) {
457
+ fields.push('successCriteria = ?');
458
+ values.push(JSON.stringify(updates.successCriteria));
459
+ }
460
+ if (updates.parentId !== undefined) {
461
+ fields.push('parentId = ?');
462
+ values.push(updates.parentId);
463
+ }
464
+ if (fields.length === 0)
465
+ return false;
466
+ values.push(id);
467
+ const result = this.db.prepare(`UPDATE session_goals SET ${fields.join(', ')} WHERE id = ?`).run(...values);
468
+ return result.changes > 0;
469
+ }
470
+ listGoals(sessionId, status, limit = 50) {
471
+ let sql = 'SELECT * FROM session_goals WHERE 1=1';
472
+ const params = [];
473
+ if (sessionId) {
474
+ sql += ' AND sessionId = ?';
475
+ params.push(sessionId);
476
+ }
477
+ if (status) {
478
+ sql += ' AND status = ?';
479
+ params.push(status);
480
+ }
481
+ sql += ' ORDER BY priority DESC, timestamp DESC LIMIT ?';
482
+ params.push(limit);
483
+ const rows = this.db.prepare(sql).all(...params);
484
+ return rows.map(this.rowToGoal);
485
+ }
486
+ getActiveGoals(limit = 10) {
487
+ const rows = this.db.prepare(`
488
+ SELECT * FROM session_goals
489
+ WHERE status = 'active'
490
+ ORDER BY priority DESC, timestamp DESC
491
+ LIMIT ?
492
+ `).all(limit);
493
+ return rows.map(this.rowToGoal);
494
+ }
495
+ getGoalHierarchy(rootId) {
496
+ if (rootId) {
497
+ // Get root + all descendants (max 3 levels)
498
+ const rows = this.db.prepare(`
499
+ SELECT * FROM session_goals
500
+ WHERE id = ? OR parentId = ?
501
+ ORDER BY parentId NULLS FIRST, priority DESC
502
+ `).all(rootId, rootId);
503
+ return rows.map(this.rowToGoal);
504
+ }
505
+ // Get all top-level goals with children
506
+ const rows = this.db.prepare(`
507
+ SELECT * FROM session_goals
508
+ ORDER BY parentId NULLS FIRST, priority DESC, timestamp DESC
509
+ `).all();
510
+ return rows.map(this.rowToGoal);
511
+ }
512
+ searchGoals(query, limit = 20) {
513
+ if (this.fts5Available) {
514
+ try {
515
+ const rows = this.db.prepare(`
516
+ SELECT g.*, bm25(session_goals_fts) as rank
517
+ FROM session_goals_fts fts
518
+ JOIN session_goals g ON g.id = fts.rowid
519
+ WHERE session_goals_fts MATCH ?
520
+ ORDER BY rank
521
+ LIMIT ?
522
+ `).all(query, limit);
523
+ return rows.map(this.rowToGoal);
524
+ }
525
+ catch {
526
+ // Fall through to LIKE
527
+ }
528
+ }
529
+ const pattern = `%${query}%`;
530
+ const rows = this.db.prepare(`
531
+ SELECT * FROM session_goals
532
+ WHERE title LIKE ? OR description LIKE ?
533
+ ORDER BY priority DESC, timestamp DESC LIMIT ?
534
+ `).all(pattern, pattern, limit);
535
+ return rows.map(this.rowToGoal);
536
+ }
537
+ deleteGoal(id) {
538
+ const result = this.db.prepare('DELETE FROM session_goals WHERE id = ?').run(id);
539
+ return result.changes > 0;
540
+ }
541
+ rowToGoal(row) {
542
+ return {
543
+ id: row.id,
544
+ sessionId: row.sessionId,
545
+ parentId: row.parentId,
546
+ title: row.title,
547
+ description: row.description,
548
+ status: row.status,
549
+ priority: row.priority,
550
+ progressPercent: row.progressPercent,
551
+ successCriteria: row.successCriteria ? JSON.parse(row.successCriteria) : [],
552
+ timestamp: row.timestamp,
553
+ completedAt: row.completedAt,
554
+ };
555
+ }
556
+ // ==========================================================================
557
+ // Evidence
558
+ // ==========================================================================
559
+ addEvidence(input) {
560
+ const timestamp = new Date().toISOString();
561
+ const stmt = this.db.prepare(`
562
+ INSERT INTO session_evidence (sessionId, type, title, status, details, metrics, relatedGoals, timestamp)
563
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
564
+ `);
565
+ const result = stmt.run(input.sessionId || null, input.type, input.title, input.status, input.details ? JSON.stringify(input.details) : null, input.metrics ? JSON.stringify(input.metrics) : null, input.relatedGoals ? JSON.stringify(input.relatedGoals) : null, timestamp);
566
+ return result.lastInsertRowid;
567
+ }
568
+ getEvidence(id) {
569
+ const row = this.db.prepare('SELECT * FROM session_evidence WHERE id = ?').get(id);
570
+ return row ? this.rowToEvidence(row) : null;
571
+ }
572
+ listEvidence(sessionId, type, status, limit = 50) {
573
+ let sql = 'SELECT * FROM session_evidence WHERE 1=1';
574
+ const params = [];
575
+ if (sessionId) {
576
+ sql += ' AND sessionId = ?';
577
+ params.push(sessionId);
578
+ }
579
+ if (type) {
580
+ sql += ' AND type = ?';
581
+ params.push(type);
582
+ }
583
+ if (status) {
584
+ sql += ' AND status = ?';
585
+ params.push(status);
586
+ }
587
+ sql += ' ORDER BY timestamp DESC LIMIT ?';
588
+ params.push(limit);
589
+ const rows = this.db.prepare(sql).all(...params);
590
+ return rows.map(this.rowToEvidence);
591
+ }
592
+ getRecentEvidence(limit = 10) {
593
+ const rows = this.db.prepare(`
594
+ SELECT * FROM session_evidence
595
+ ORDER BY timestamp DESC
596
+ LIMIT ?
597
+ `).all(limit);
598
+ return rows.map(this.rowToEvidence);
599
+ }
600
+ searchEvidence(query, limit = 20) {
601
+ if (this.fts5Available) {
602
+ try {
603
+ const rows = this.db.prepare(`
604
+ SELECT e.*, bm25(session_evidence_fts) as rank
605
+ FROM session_evidence_fts fts
606
+ JOIN session_evidence e ON e.id = fts.rowid
607
+ WHERE session_evidence_fts MATCH ?
608
+ ORDER BY rank
609
+ LIMIT ?
610
+ `).all(query, limit);
611
+ return rows.map(this.rowToEvidence);
612
+ }
613
+ catch {
614
+ // Fall through to LIKE
615
+ }
616
+ }
617
+ const pattern = `%${query}%`;
618
+ const rows = this.db.prepare(`
619
+ SELECT * FROM session_evidence
620
+ WHERE title LIKE ? OR details LIKE ?
621
+ ORDER BY timestamp DESC LIMIT ?
622
+ `).all(pattern, pattern, limit);
623
+ return rows.map(this.rowToEvidence);
624
+ }
625
+ deleteEvidence(id) {
626
+ const result = this.db.prepare('DELETE FROM session_evidence WHERE id = ?').run(id);
627
+ return result.changes > 0;
628
+ }
629
+ rowToEvidence(row) {
630
+ return {
631
+ id: row.id,
632
+ sessionId: row.sessionId,
633
+ type: row.type,
634
+ title: row.title,
635
+ status: row.status,
636
+ details: row.details ? JSON.parse(row.details) : null,
637
+ metrics: row.metrics ? JSON.parse(row.metrics) : null,
638
+ relatedGoals: row.relatedGoals ? JSON.parse(row.relatedGoals) : [],
639
+ timestamp: row.timestamp,
640
+ };
641
+ }
642
+ // ==========================================================================
643
+ // Stats
644
+ // ==========================================================================
645
+ getStats() {
646
+ const decStatuses = this.db.prepare('SELECT status, COUNT(*) as count FROM session_decisions GROUP BY status').all();
647
+ const conTypes = this.db.prepare('SELECT type, COUNT(*) as count FROM session_constraints GROUP BY type').all();
648
+ const goalStatuses = this.db.prepare('SELECT status, COUNT(*) as count FROM session_goals GROUP BY status').all();
649
+ const eviTypes = this.db.prepare('SELECT type, COUNT(*) as count FROM session_evidence GROUP BY type').all();
650
+ const toRecord = (rows, keyField) => {
651
+ const record = {};
652
+ let total = 0;
653
+ for (const row of rows) {
654
+ const key = row[keyField];
655
+ const count = row.count;
656
+ record[key] = count;
657
+ total += count;
658
+ }
659
+ return { total, record };
660
+ };
661
+ const dec = toRecord(decStatuses, 'status');
662
+ const con = toRecord(conTypes, 'type');
663
+ const goal = toRecord(goalStatuses, 'status');
664
+ const evi = toRecord(eviTypes, 'type');
665
+ return {
666
+ decisions: { total: dec.total, byStatus: dec.record },
667
+ constraints: { total: con.total, byType: con.record },
668
+ goals: { total: goal.total, byStatus: goal.record },
669
+ evidence: { total: evi.total, byType: evi.record },
670
+ };
671
+ }
672
+ }
673
+ //# sourceMappingURL=SessionRAGStore.js.map