@nahisaho/yata-local 1.6.6

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 (77) hide show
  1. package/dist/auto-updater.d.ts +101 -0
  2. package/dist/auto-updater.d.ts.map +1 -0
  3. package/dist/auto-updater.js +402 -0
  4. package/dist/auto-updater.js.map +1 -0
  5. package/dist/database.d.ts +229 -0
  6. package/dist/database.d.ts.map +1 -0
  7. package/dist/database.js +959 -0
  8. package/dist/database.js.map +1 -0
  9. package/dist/index.d.ts +298 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +436 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/io.d.ts +77 -0
  14. package/dist/io.d.ts.map +1 -0
  15. package/dist/io.js +403 -0
  16. package/dist/io.js.map +1 -0
  17. package/dist/kgpr/diff-engine.d.ts +110 -0
  18. package/dist/kgpr/diff-engine.d.ts.map +1 -0
  19. package/dist/kgpr/diff-engine.js +335 -0
  20. package/dist/kgpr/diff-engine.js.map +1 -0
  21. package/dist/kgpr/index.d.ts +16 -0
  22. package/dist/kgpr/index.d.ts.map +1 -0
  23. package/dist/kgpr/index.js +14 -0
  24. package/dist/kgpr/index.js.map +1 -0
  25. package/dist/kgpr/kgpr-manager.d.ts +122 -0
  26. package/dist/kgpr/kgpr-manager.d.ts.map +1 -0
  27. package/dist/kgpr/kgpr-manager.js +344 -0
  28. package/dist/kgpr/kgpr-manager.js.map +1 -0
  29. package/dist/kgpr/privacy-filter.d.ts +109 -0
  30. package/dist/kgpr/privacy-filter.d.ts.map +1 -0
  31. package/dist/kgpr/privacy-filter.js +295 -0
  32. package/dist/kgpr/privacy-filter.js.map +1 -0
  33. package/dist/kgpr/types.d.ts +234 -0
  34. package/dist/kgpr/types.d.ts.map +1 -0
  35. package/dist/kgpr/types.js +54 -0
  36. package/dist/kgpr/types.js.map +1 -0
  37. package/dist/query-engine.d.ts +78 -0
  38. package/dist/query-engine.d.ts.map +1 -0
  39. package/dist/query-engine.js +368 -0
  40. package/dist/query-engine.js.map +1 -0
  41. package/dist/reasoning.d.ts +112 -0
  42. package/dist/reasoning.d.ts.map +1 -0
  43. package/dist/reasoning.js +455 -0
  44. package/dist/reasoning.js.map +1 -0
  45. package/dist/types.d.ts +580 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +37 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/wake-sleep/cycle-manager.d.ts +76 -0
  50. package/dist/wake-sleep/cycle-manager.d.ts.map +1 -0
  51. package/dist/wake-sleep/cycle-manager.js +291 -0
  52. package/dist/wake-sleep/cycle-manager.js.map +1 -0
  53. package/dist/wake-sleep/index.d.ts +15 -0
  54. package/dist/wake-sleep/index.d.ts.map +1 -0
  55. package/dist/wake-sleep/index.js +19 -0
  56. package/dist/wake-sleep/index.js.map +1 -0
  57. package/dist/wake-sleep/pattern-compressor.d.ts +86 -0
  58. package/dist/wake-sleep/pattern-compressor.d.ts.map +1 -0
  59. package/dist/wake-sleep/pattern-compressor.js +333 -0
  60. package/dist/wake-sleep/pattern-compressor.js.map +1 -0
  61. package/dist/wake-sleep/sleep-phase.d.ts +79 -0
  62. package/dist/wake-sleep/sleep-phase.d.ts.map +1 -0
  63. package/dist/wake-sleep/sleep-phase.js +329 -0
  64. package/dist/wake-sleep/sleep-phase.js.map +1 -0
  65. package/dist/wake-sleep/types.d.ts +244 -0
  66. package/dist/wake-sleep/types.d.ts.map +1 -0
  67. package/dist/wake-sleep/types.js +35 -0
  68. package/dist/wake-sleep/types.js.map +1 -0
  69. package/dist/wake-sleep/wake-phase.d.ts +83 -0
  70. package/dist/wake-sleep/wake-phase.d.ts.map +1 -0
  71. package/dist/wake-sleep/wake-phase.js +457 -0
  72. package/dist/wake-sleep/wake-phase.js.map +1 -0
  73. package/dist/yata-bridge.d.ts +78 -0
  74. package/dist/yata-bridge.d.ts.map +1 -0
  75. package/dist/yata-bridge.js +193 -0
  76. package/dist/yata-bridge.js.map +1 -0
  77. package/package.json +60 -0
@@ -0,0 +1,959 @@
1
+ /**
2
+ * YATA Local - SQLite Database Layer
3
+ *
4
+ * @packageDocumentation
5
+ * @module @nahisaho/yata-local/database
6
+ *
7
+ * @see REQ-YL-STORE-001
8
+ * @see REQ-YL-STORE-002
9
+ * @see REQ-WSL-003
10
+ * @see REQ-NFR-001
11
+ */
12
+ import { DEFAULT_DB_CONFIG } from './types.js';
13
+ /**
14
+ * SQL schema for knowledge graph
15
+ * @see REQ-YL-STORE-002
16
+ */
17
+ const SCHEMA = `
18
+ -- Entities table
19
+ CREATE TABLE IF NOT EXISTS entities (
20
+ id TEXT PRIMARY KEY,
21
+ type TEXT NOT NULL,
22
+ name TEXT NOT NULL,
23
+ namespace TEXT NOT NULL DEFAULT '',
24
+ file_path TEXT,
25
+ line INTEGER,
26
+ description TEXT,
27
+ metadata TEXT NOT NULL DEFAULT '{}',
28
+ created_at TEXT NOT NULL,
29
+ updated_at TEXT NOT NULL
30
+ );
31
+
32
+ -- Relationships table
33
+ CREATE TABLE IF NOT EXISTS relationships (
34
+ id TEXT PRIMARY KEY,
35
+ source_id TEXT NOT NULL,
36
+ target_id TEXT NOT NULL,
37
+ type TEXT NOT NULL,
38
+ weight REAL NOT NULL DEFAULT 1.0,
39
+ metadata TEXT NOT NULL DEFAULT '{}',
40
+ created_at TEXT NOT NULL,
41
+ FOREIGN KEY (source_id) REFERENCES entities(id) ON DELETE CASCADE,
42
+ FOREIGN KEY (target_id) REFERENCES entities(id) ON DELETE CASCADE
43
+ );
44
+
45
+ -- Indexes for fast queries
46
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
47
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
48
+ CREATE INDEX IF NOT EXISTS idx_entities_namespace ON entities(namespace);
49
+ CREATE INDEX IF NOT EXISTS idx_entities_file_path ON entities(file_path);
50
+ CREATE INDEX IF NOT EXISTS idx_entities_updated ON entities(updated_at);
51
+
52
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_id);
53
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_id);
54
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
55
+
56
+ -- Full-text search virtual table
57
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
58
+ id,
59
+ name,
60
+ namespace,
61
+ description,
62
+ content='entities',
63
+ content_rowid='rowid'
64
+ );
65
+
66
+ -- Triggers to keep FTS in sync
67
+ CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
68
+ INSERT INTO entities_fts(id, name, namespace, description)
69
+ VALUES (new.id, new.name, new.namespace, new.description);
70
+ END;
71
+
72
+ CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
73
+ INSERT INTO entities_fts(entities_fts, id, name, namespace, description)
74
+ VALUES ('delete', old.id, old.name, old.namespace, old.description);
75
+ END;
76
+
77
+ CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
78
+ INSERT INTO entities_fts(entities_fts, id, name, namespace, description)
79
+ VALUES ('delete', old.id, old.name, old.namespace, old.description);
80
+ INSERT INTO entities_fts(id, name, namespace, description)
81
+ VALUES (new.id, new.name, new.namespace, new.description);
82
+ END;
83
+
84
+ -- Change tracking table
85
+ CREATE TABLE IF NOT EXISTS change_log (
86
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
87
+ entity_id TEXT,
88
+ relationship_id TEXT,
89
+ operation TEXT NOT NULL,
90
+ timestamp TEXT NOT NULL,
91
+ data TEXT
92
+ );
93
+
94
+ CREATE INDEX IF NOT EXISTS idx_change_log_timestamp ON change_log(timestamp);
95
+
96
+ -- Patterns table for Wake-Sleep learning
97
+ -- @see REQ-WSL-003
98
+ -- @see TSK-NFR-003
99
+ CREATE TABLE IF NOT EXISTS patterns (
100
+ id TEXT PRIMARY KEY,
101
+ name TEXT NOT NULL,
102
+ category TEXT NOT NULL DEFAULT 'code',
103
+ content TEXT NOT NULL,
104
+ ast TEXT,
105
+ confidence REAL NOT NULL DEFAULT 0.5,
106
+ occurrences INTEGER NOT NULL DEFAULT 1,
107
+ last_used_at TEXT,
108
+ usage_count INTEGER NOT NULL DEFAULT 0,
109
+ source TEXT,
110
+ metadata TEXT NOT NULL DEFAULT '{}',
111
+ created_at TEXT NOT NULL,
112
+ updated_at TEXT NOT NULL
113
+ );
114
+
115
+ -- Pattern indexes for performance
116
+ -- @see REQ-NFR-001
117
+ CREATE INDEX IF NOT EXISTS idx_patterns_category ON patterns(category);
118
+ CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence);
119
+ CREATE INDEX IF NOT EXISTS idx_patterns_last_used ON patterns(last_used_at);
120
+ CREATE INDEX IF NOT EXISTS idx_patterns_name ON patterns(name);
121
+
122
+ -- Learning cycles table for tracking Wake-Sleep cycles
123
+ -- @see REQ-WSL-005
124
+ -- @see TSK-NFR-004
125
+ CREATE TABLE IF NOT EXISTS learning_cycles (
126
+ id TEXT PRIMARY KEY,
127
+ wake_extracted INTEGER NOT NULL DEFAULT 0,
128
+ wake_observed_files INTEGER NOT NULL DEFAULT 0,
129
+ sleep_clustered INTEGER NOT NULL DEFAULT 0,
130
+ sleep_decayed INTEGER NOT NULL DEFAULT 0,
131
+ duration_ms INTEGER NOT NULL DEFAULT 0,
132
+ metadata TEXT NOT NULL DEFAULT '{}',
133
+ completed_at TEXT NOT NULL
134
+ );
135
+
136
+ CREATE INDEX IF NOT EXISTS idx_learning_cycles_completed ON learning_cycles(completed_at);
137
+
138
+ -- KGPR tracking table
139
+ -- @see REQ-KGPR-001
140
+ -- @see TSK-KGPR-004
141
+ CREATE TABLE IF NOT EXISTS kgprs (
142
+ id TEXT PRIMARY KEY,
143
+ title TEXT NOT NULL,
144
+ description TEXT,
145
+ status TEXT NOT NULL DEFAULT 'draft',
146
+ author TEXT NOT NULL DEFAULT 'local',
147
+ namespace TEXT NOT NULL DEFAULT '*',
148
+ diff_json TEXT NOT NULL DEFAULT '{}',
149
+ privacy_level TEXT NOT NULL DEFAULT 'strict',
150
+ entity_types TEXT NOT NULL DEFAULT '[]',
151
+ created_at TEXT NOT NULL,
152
+ updated_at TEXT NOT NULL,
153
+ submitted_at TEXT,
154
+ reviewed_at TEXT,
155
+ merged_at TEXT,
156
+ closed_at TEXT
157
+ );
158
+
159
+ CREATE INDEX IF NOT EXISTS idx_kgprs_status ON kgprs(status);
160
+ CREATE INDEX IF NOT EXISTS idx_kgprs_author ON kgprs(author);
161
+ CREATE INDEX IF NOT EXISTS idx_kgprs_namespace ON kgprs(namespace);
162
+
163
+ -- KGPR reviews table
164
+ -- @see REQ-KGPR-003
165
+ CREATE TABLE IF NOT EXISTS kgpr_reviews (
166
+ id TEXT PRIMARY KEY,
167
+ kgpr_id TEXT NOT NULL,
168
+ reviewer TEXT NOT NULL,
169
+ action TEXT NOT NULL,
170
+ comment TEXT,
171
+ created_at TEXT NOT NULL,
172
+ FOREIGN KEY (kgpr_id) REFERENCES kgprs(id) ON DELETE CASCADE
173
+ );
174
+
175
+ CREATE INDEX IF NOT EXISTS idx_kgpr_reviews_kgpr ON kgpr_reviews(kgpr_id);
176
+ `;
177
+ /**
178
+ * Database layer for YATA Local
179
+ */
180
+ export class YataDatabase {
181
+ db = null;
182
+ config;
183
+ constructor(config = {}) {
184
+ this.config = { ...DEFAULT_DB_CONFIG, ...config };
185
+ }
186
+ /**
187
+ * Open database connection
188
+ * @see REQ-YL-STORE-001
189
+ */
190
+ async open() {
191
+ // Dynamic import for better-sqlite3
192
+ const BetterSqlite3 = (await import('better-sqlite3')).default;
193
+ this.db = new BetterSqlite3(this.config.path);
194
+ // Configure database
195
+ if (this.config.walMode) {
196
+ this.db.pragma('journal_mode = WAL');
197
+ }
198
+ if (this.config.mmapSize > 0) {
199
+ this.db.pragma(`mmap_size = ${this.config.mmapSize}`);
200
+ }
201
+ if (this.config.cacheSize > 0) {
202
+ this.db.pragma(`cache_size = -${this.config.cacheSize}`);
203
+ }
204
+ if (this.config.foreignKeys) {
205
+ this.db.pragma('foreign_keys = ON');
206
+ }
207
+ // Create schema
208
+ this.db.exec(SCHEMA);
209
+ }
210
+ /**
211
+ * Close database connection
212
+ */
213
+ async close() {
214
+ if (this.db) {
215
+ this.db.close();
216
+ this.db = null;
217
+ }
218
+ }
219
+ /**
220
+ * Check if database is open
221
+ */
222
+ isOpen() {
223
+ return this.db !== null;
224
+ }
225
+ /**
226
+ * Get database instance (throws if not open)
227
+ * Made public for advanced query use cases
228
+ */
229
+ getDb() {
230
+ if (!this.db) {
231
+ throw new Error('Database is not open. Call open() first.');
232
+ }
233
+ return this.db;
234
+ }
235
+ // Entity operations
236
+ /**
237
+ * Insert entity
238
+ * @see REQ-YL-STORE-003
239
+ */
240
+ insertEntity(entity) {
241
+ const db = this.getDb();
242
+ const stmt = db.prepare(`
243
+ INSERT INTO entities (id, type, name, namespace, file_path, line, description, metadata, created_at, updated_at)
244
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
245
+ `);
246
+ stmt.run(entity.id, entity.type, entity.name, entity.namespace, entity.filePath ?? null, entity.line ?? null, entity.description ?? null, JSON.stringify(entity.metadata), entity.createdAt.toISOString(), entity.updatedAt.toISOString());
247
+ // Log change
248
+ this.logChange(entity.id, null, 'insert', entity);
249
+ }
250
+ /**
251
+ * Insert multiple entities in transaction
252
+ * @see REQ-YL-STORE-005
253
+ */
254
+ insertEntities(entities) {
255
+ const db = this.getDb();
256
+ const stmt = db.prepare(`
257
+ INSERT INTO entities (id, type, name, namespace, file_path, line, description, metadata, created_at, updated_at)
258
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
259
+ `);
260
+ const insertMany = db.transaction((items) => {
261
+ for (const entity of items) {
262
+ stmt.run(entity.id, entity.type, entity.name, entity.namespace, entity.filePath ?? null, entity.line ?? null, entity.description ?? null, JSON.stringify(entity.metadata), entity.createdAt.toISOString(), entity.updatedAt.toISOString());
263
+ }
264
+ });
265
+ insertMany(entities);
266
+ }
267
+ /**
268
+ * Get entity by ID
269
+ */
270
+ getEntity(id) {
271
+ const db = this.getDb();
272
+ const stmt = db.prepare('SELECT * FROM entities WHERE id = ?');
273
+ const row = stmt.get(id);
274
+ return row ? this.rowToEntity(row) : null;
275
+ }
276
+ /**
277
+ * Update entity
278
+ * @see REQ-YL-UPDATE-002
279
+ */
280
+ updateEntity(id, updates) {
281
+ const db = this.getDb();
282
+ const existing = this.getEntity(id);
283
+ if (!existing) {
284
+ throw new Error(`Entity not found: ${id}`);
285
+ }
286
+ const updated = { ...existing, ...updates, updatedAt: new Date() };
287
+ const stmt = db.prepare(`
288
+ UPDATE entities SET
289
+ type = ?, name = ?, namespace = ?, file_path = ?, line = ?,
290
+ description = ?, metadata = ?, updated_at = ?
291
+ WHERE id = ?
292
+ `);
293
+ stmt.run(updated.type, updated.name, updated.namespace, updated.filePath ?? null, updated.line ?? null, updated.description ?? null, JSON.stringify(updated.metadata), updated.updatedAt.toISOString(), id);
294
+ this.logChange(id, null, 'update', updated);
295
+ }
296
+ /**
297
+ * Delete entity
298
+ * @see REQ-YL-UPDATE-003
299
+ */
300
+ deleteEntity(id) {
301
+ const db = this.getDb();
302
+ const stmt = db.prepare('DELETE FROM entities WHERE id = ?');
303
+ stmt.run(id);
304
+ this.logChange(id, null, 'delete', null);
305
+ }
306
+ /**
307
+ * Delete entities by file path
308
+ */
309
+ deleteEntitiesByFile(filePath) {
310
+ const db = this.getDb();
311
+ const stmt = db.prepare('DELETE FROM entities WHERE file_path = ?');
312
+ const result = stmt.run(filePath);
313
+ return result.changes;
314
+ }
315
+ // Relationship operations
316
+ /**
317
+ * Insert relationship
318
+ * @see REQ-YL-STORE-004
319
+ */
320
+ insertRelationship(rel) {
321
+ const db = this.getDb();
322
+ const stmt = db.prepare(`
323
+ INSERT INTO relationships (id, source_id, target_id, type, weight, metadata, created_at)
324
+ VALUES (?, ?, ?, ?, ?, ?, ?)
325
+ `);
326
+ stmt.run(rel.id, rel.sourceId, rel.targetId, rel.type, rel.weight, JSON.stringify(rel.metadata), rel.createdAt.toISOString());
327
+ this.logChange(null, rel.id, 'insert', rel);
328
+ }
329
+ /**
330
+ * Get relationships for entity
331
+ */
332
+ getRelationships(entityId, direction = 'both') {
333
+ const db = this.getDb();
334
+ let sql;
335
+ if (direction === 'out') {
336
+ sql = 'SELECT * FROM relationships WHERE source_id = ?';
337
+ }
338
+ else if (direction === 'in') {
339
+ sql = 'SELECT * FROM relationships WHERE target_id = ?';
340
+ }
341
+ else {
342
+ sql = 'SELECT * FROM relationships WHERE source_id = ? OR target_id = ?';
343
+ }
344
+ const stmt = db.prepare(sql);
345
+ const rows = direction === 'both'
346
+ ? stmt.all(entityId, entityId)
347
+ : stmt.all(entityId);
348
+ return rows.map(row => this.rowToRelationship(row));
349
+ }
350
+ /**
351
+ * Delete relationship
352
+ */
353
+ deleteRelationship(id) {
354
+ const db = this.getDb();
355
+ const stmt = db.prepare('DELETE FROM relationships WHERE id = ?');
356
+ stmt.run(id);
357
+ this.logChange(null, id, 'delete', null);
358
+ }
359
+ // Query operations
360
+ /**
361
+ * Query entities with filters
362
+ * @see REQ-YL-QUERY-001
363
+ */
364
+ queryEntities(filters, limit = 100, offset = 0) {
365
+ const db = this.getDb();
366
+ const conditions = [];
367
+ const params = [];
368
+ if (filters.type) {
369
+ if (Array.isArray(filters.type)) {
370
+ conditions.push(`type IN (${filters.type.map(() => '?').join(', ')})`);
371
+ params.push(...filters.type);
372
+ }
373
+ else {
374
+ conditions.push('type = ?');
375
+ params.push(filters.type);
376
+ }
377
+ }
378
+ if (filters.name) {
379
+ conditions.push('name = ?');
380
+ params.push(filters.name);
381
+ }
382
+ if (filters.namePattern) {
383
+ conditions.push('name LIKE ?');
384
+ params.push(filters.namePattern.replace(/\*/g, '%'));
385
+ }
386
+ if (filters.namespace) {
387
+ conditions.push('namespace = ?');
388
+ params.push(filters.namespace);
389
+ }
390
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
391
+ // Get total count
392
+ const countStmt = db.prepare(`SELECT COUNT(*) as count FROM entities ${whereClause}`);
393
+ const countResult = countStmt.get(...params);
394
+ // Get paginated results
395
+ const stmt = db.prepare(`SELECT * FROM entities ${whereClause} LIMIT ? OFFSET ?`);
396
+ const rows = stmt.all(...params, limit, offset);
397
+ return {
398
+ entities: rows.map(row => this.rowToEntity(row)),
399
+ totalCount: countResult.count,
400
+ };
401
+ }
402
+ /**
403
+ * Full-text search
404
+ * @see REQ-YL-QUERY-005
405
+ */
406
+ searchEntities(text, limit = 100) {
407
+ const db = this.getDb();
408
+ const stmt = db.prepare(`
409
+ SELECT e.* FROM entities e
410
+ JOIN entities_fts fts ON e.id = fts.id
411
+ WHERE entities_fts MATCH ?
412
+ LIMIT ?
413
+ `);
414
+ const rows = stmt.all(text, limit);
415
+ return rows.map(row => this.rowToEntity(row));
416
+ }
417
+ /**
418
+ * Get changes since timestamp
419
+ * @see REQ-YL-UPDATE-005
420
+ */
421
+ getChangesSince(since) {
422
+ const db = this.getDb();
423
+ const stmt = db.prepare(`
424
+ SELECT * FROM change_log WHERE timestamp > ? ORDER BY id
425
+ `);
426
+ const rows = stmt.all(since.toISOString());
427
+ const result = {
428
+ entities: { added: [], updated: [], deleted: [] },
429
+ relationships: { added: [], deleted: [] },
430
+ };
431
+ for (const row of rows) {
432
+ if (row.entity_id) {
433
+ if (row.operation === 'insert') {
434
+ const entity = this.getEntity(row.entity_id);
435
+ if (entity)
436
+ result.entities.added.push(entity);
437
+ }
438
+ else if (row.operation === 'update') {
439
+ const entity = this.getEntity(row.entity_id);
440
+ if (entity)
441
+ result.entities.updated.push(entity);
442
+ }
443
+ else if (row.operation === 'delete') {
444
+ result.entities.deleted.push(row.entity_id);
445
+ }
446
+ }
447
+ else if (row.relationship_id) {
448
+ if (row.operation === 'insert' && row.data) {
449
+ result.relationships.added.push(JSON.parse(row.data));
450
+ }
451
+ else if (row.operation === 'delete') {
452
+ result.relationships.deleted.push(row.relationship_id);
453
+ }
454
+ }
455
+ }
456
+ return result;
457
+ }
458
+ /**
459
+ * Get graph statistics
460
+ */
461
+ getStats() {
462
+ const db = this.getDb();
463
+ // Entity count
464
+ const entityCount = db.prepare('SELECT COUNT(*) as count FROM entities').get().count;
465
+ // Entities by type
466
+ const typeRows = db.prepare('SELECT type, COUNT(*) as count FROM entities GROUP BY type').all();
467
+ const entitiesByType = {};
468
+ for (const row of typeRows) {
469
+ entitiesByType[row.type] = row.count;
470
+ }
471
+ // Relationship count
472
+ const relationshipCount = db.prepare('SELECT COUNT(*) as count FROM relationships').get().count;
473
+ // Relationships by type
474
+ const relTypeRows = db.prepare('SELECT type, COUNT(*) as count FROM relationships GROUP BY type').all();
475
+ const relationshipsByType = {};
476
+ for (const row of relTypeRows) {
477
+ relationshipsByType[row.type] = row.count;
478
+ }
479
+ // Database size (approximate)
480
+ const pageCount = db.prepare('PRAGMA page_count').get().page_count;
481
+ const pageSize = db.prepare('PRAGMA page_size').get().page_size;
482
+ const databaseSize = pageCount * pageSize;
483
+ // Last modified
484
+ const lastModifiedRow = db.prepare('SELECT MAX(updated_at) as last FROM entities').get();
485
+ const lastModified = lastModifiedRow.last ? new Date(lastModifiedRow.last) : new Date();
486
+ return {
487
+ entityCount,
488
+ entitiesByType: entitiesByType,
489
+ relationshipCount,
490
+ relationshipsByType: relationshipsByType,
491
+ databaseSize,
492
+ lastModified,
493
+ };
494
+ }
495
+ // ============================================================
496
+ // Pattern Operations (TSK-NFR-003, TSK-WSL-003)
497
+ // @see REQ-WSL-003
498
+ // ============================================================
499
+ /**
500
+ * Insert a learned pattern
501
+ * @see REQ-WSL-001
502
+ */
503
+ insertPattern(pattern) {
504
+ const db = this.getDb();
505
+ const stmt = db.prepare(`
506
+ INSERT INTO patterns (id, name, category, content, ast, confidence, occurrences, last_used_at, usage_count, source, metadata, created_at, updated_at)
507
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
508
+ `);
509
+ stmt.run(pattern.id, pattern.name, pattern.category, pattern.content, pattern.ast ? JSON.stringify(pattern.ast) : null, pattern.confidence, pattern.occurrences, pattern.lastUsedAt?.toISOString() ?? null, pattern.usageCount, pattern.source ?? null, JSON.stringify(pattern.metadata), pattern.createdAt.toISOString(), pattern.updatedAt.toISOString());
510
+ }
511
+ /**
512
+ * Get a pattern by ID
513
+ */
514
+ getPattern(id) {
515
+ const db = this.getDb();
516
+ const stmt = db.prepare('SELECT * FROM patterns WHERE id = ?');
517
+ const row = stmt.get(id);
518
+ return row ? this.rowToPattern(row) : null;
519
+ }
520
+ /**
521
+ * Update a pattern
522
+ */
523
+ updatePattern(pattern) {
524
+ const db = this.getDb();
525
+ const stmt = db.prepare(`
526
+ UPDATE patterns SET
527
+ name = ?, category = ?, content = ?, ast = ?, confidence = ?,
528
+ occurrences = ?, last_used_at = ?, usage_count = ?, source = ?,
529
+ metadata = ?, updated_at = ?
530
+ WHERE id = ?
531
+ `);
532
+ stmt.run(pattern.name, pattern.category, pattern.content, pattern.ast ? JSON.stringify(pattern.ast) : null, pattern.confidence, pattern.occurrences, pattern.lastUsedAt?.toISOString() ?? null, pattern.usageCount, pattern.source ?? null, JSON.stringify(pattern.metadata), pattern.updatedAt.toISOString(), pattern.id);
533
+ }
534
+ /**
535
+ * Upsert a pattern (insert or update)
536
+ * @see REQ-WSL-002
537
+ */
538
+ upsertPattern(pattern) {
539
+ const existing = this.getPattern(pattern.id);
540
+ if (existing) {
541
+ this.updatePattern(pattern);
542
+ }
543
+ else {
544
+ this.insertPattern(pattern);
545
+ }
546
+ }
547
+ /**
548
+ * Delete a pattern
549
+ */
550
+ deletePattern(id) {
551
+ const db = this.getDb();
552
+ const stmt = db.prepare('DELETE FROM patterns WHERE id = ?');
553
+ const result = stmt.run(id);
554
+ return result.changes > 0;
555
+ }
556
+ /**
557
+ * Query patterns with options
558
+ * @see REQ-WSL-003
559
+ */
560
+ queryPatterns(options = {}) {
561
+ const db = this.getDb();
562
+ const conditions = [];
563
+ const params = [];
564
+ if (options.category) {
565
+ conditions.push('category = ?');
566
+ params.push(options.category);
567
+ }
568
+ if (options.minConfidence !== undefined) {
569
+ conditions.push('confidence >= ?');
570
+ params.push(options.minConfidence);
571
+ }
572
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
573
+ const sortBy = options.sortBy ?? 'created_at';
574
+ const sortOrder = options.sortOrder ?? 'desc';
575
+ const limit = options.limit ?? 100;
576
+ const offset = options.offset ?? 0;
577
+ const sql = `
578
+ SELECT * FROM patterns
579
+ ${whereClause}
580
+ ORDER BY ${sortBy} ${sortOrder}
581
+ LIMIT ? OFFSET ?
582
+ `;
583
+ params.push(limit, offset);
584
+ const stmt = db.prepare(sql);
585
+ const rows = stmt.all(...params);
586
+ return rows.map(row => this.rowToPattern(row));
587
+ }
588
+ /**
589
+ * Get patterns that haven't been used recently
590
+ * @see REQ-WSL-003
591
+ */
592
+ getUnusedPatterns(monthsThreshold = 6) {
593
+ const db = this.getDb();
594
+ const thresholdDate = new Date();
595
+ thresholdDate.setMonth(thresholdDate.getMonth() - monthsThreshold);
596
+ const stmt = db.prepare(`
597
+ SELECT * FROM patterns
598
+ WHERE last_used_at IS NULL OR last_used_at < ?
599
+ ORDER BY last_used_at ASC
600
+ `);
601
+ const rows = stmt.all(thresholdDate.toISOString());
602
+ return rows.map(row => this.rowToPattern(row));
603
+ }
604
+ /**
605
+ * Get low confidence patterns
606
+ * @see REQ-WSL-003
607
+ */
608
+ getLowConfidencePatterns(threshold = 0.3) {
609
+ const db = this.getDb();
610
+ const stmt = db.prepare(`
611
+ SELECT * FROM patterns
612
+ WHERE confidence < ?
613
+ ORDER BY confidence ASC
614
+ `);
615
+ const rows = stmt.all(threshold);
616
+ return rows.map(row => this.rowToPattern(row));
617
+ }
618
+ /**
619
+ * Mark pattern as used (update lastUsedAt)
620
+ * @see REQ-WSL-003
621
+ */
622
+ markPatternUsed(id) {
623
+ const db = this.getDb();
624
+ const stmt = db.prepare(`
625
+ UPDATE patterns SET
626
+ last_used_at = ?,
627
+ usage_count = usage_count + 1,
628
+ updated_at = ?
629
+ WHERE id = ?
630
+ `);
631
+ const now = new Date().toISOString();
632
+ stmt.run(now, now, id);
633
+ }
634
+ /**
635
+ * Decay pattern confidence
636
+ * @see REQ-WSL-003
637
+ */
638
+ decayPatternConfidence(id, factor) {
639
+ const db = this.getDb();
640
+ const stmt = db.prepare(`
641
+ UPDATE patterns SET
642
+ confidence = confidence * ?,
643
+ updated_at = ?
644
+ WHERE id = ?
645
+ `);
646
+ stmt.run(factor, new Date().toISOString(), id);
647
+ }
648
+ /**
649
+ * Get pattern count
650
+ */
651
+ getPatternCount() {
652
+ const db = this.getDb();
653
+ const result = db.prepare('SELECT COUNT(*) as count FROM patterns').get();
654
+ return result.count;
655
+ }
656
+ // ============================================================
657
+ // Learning Cycle Operations (TSK-NFR-004)
658
+ // @see REQ-WSL-005
659
+ // ============================================================
660
+ /**
661
+ * Log a learning cycle
662
+ * @see REQ-WSL-005
663
+ */
664
+ logLearningCycle(cycle) {
665
+ const db = this.getDb();
666
+ const id = `LC-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 6)}`;
667
+ const now = new Date();
668
+ const stmt = db.prepare(`
669
+ INSERT INTO learning_cycles (id, wake_extracted, wake_observed_files, sleep_clustered, sleep_decayed, duration_ms, metadata, completed_at)
670
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
671
+ `);
672
+ stmt.run(id, cycle.wakeExtracted, cycle.wakeObservedFiles, cycle.sleepClustered, cycle.sleepDecayed, cycle.durationMs, JSON.stringify(cycle.metadata ?? {}), now.toISOString());
673
+ return {
674
+ id,
675
+ wakeExtracted: cycle.wakeExtracted,
676
+ wakeObservedFiles: cycle.wakeObservedFiles,
677
+ sleepClustered: cycle.sleepClustered,
678
+ sleepDecayed: cycle.sleepDecayed,
679
+ durationMs: cycle.durationMs,
680
+ metadata: cycle.metadata ?? {},
681
+ completedAt: now,
682
+ };
683
+ }
684
+ /**
685
+ * Get recent learning cycles
686
+ * @see REQ-WSL-005
687
+ */
688
+ getRecentLearningCycles(limit = 10) {
689
+ const db = this.getDb();
690
+ const stmt = db.prepare(`
691
+ SELECT * FROM learning_cycles
692
+ ORDER BY completed_at DESC
693
+ LIMIT ?
694
+ `);
695
+ const rows = stmt.all(limit);
696
+ return rows.map(row => this.rowToLearningCycle(row));
697
+ }
698
+ /**
699
+ * Get learning statistics
700
+ * @see REQ-WSL-005
701
+ */
702
+ getLearningStats() {
703
+ const db = this.getDb();
704
+ // Pattern count
705
+ const totalPatterns = this.getPatternCount();
706
+ // Patterns by category
707
+ const categoryRows = db.prepare('SELECT category, COUNT(*) as count FROM patterns GROUP BY category').all();
708
+ const patternsByCategory = {};
709
+ for (const row of categoryRows) {
710
+ patternsByCategory[row.category] = row.count;
711
+ }
712
+ // Average confidence
713
+ const avgResult = db.prepare('SELECT AVG(confidence) as avg FROM patterns').get();
714
+ const averageConfidence = avgResult.avg ?? 0;
715
+ // Total cycles
716
+ const totalCycles = db.prepare('SELECT COUNT(*) as count FROM learning_cycles').get().count;
717
+ // Last cycle
718
+ const lastCycleRow = db.prepare('SELECT MAX(completed_at) as last FROM learning_cycles').get();
719
+ const lastCycleAt = lastCycleRow.last ? new Date(lastCycleRow.last) : undefined;
720
+ return {
721
+ totalPatterns,
722
+ patternsByCategory: patternsByCategory,
723
+ averageConfidence,
724
+ totalCycles,
725
+ lastCycleAt,
726
+ };
727
+ }
728
+ // ============================================================
729
+ // KGPR Local Operations (TSK-KGPR-004)
730
+ // @see REQ-KGPR-001
731
+ // ============================================================
732
+ /**
733
+ * Insert a local KGPR record
734
+ */
735
+ insertKGPR(kgpr) {
736
+ const db = this.getDb();
737
+ const stmt = db.prepare(`
738
+ INSERT INTO kgprs (id, title, description, status, author, namespace, diff_json, privacy_level, entity_types, created_at, updated_at, submitted_at)
739
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
740
+ `);
741
+ stmt.run(kgpr.id, kgpr.title, kgpr.description ?? null, kgpr.status, kgpr.author, kgpr.namespace ?? '*', kgpr.diffJson, kgpr.privacyLevel, kgpr.entityTypesJson ?? '[]', kgpr.createdAt, kgpr.updatedAt ?? kgpr.createdAt, kgpr.submittedAt ?? null);
742
+ }
743
+ /**
744
+ * Get a KGPR by ID
745
+ */
746
+ getKGPR(id) {
747
+ const db = this.getDb();
748
+ const stmt = db.prepare('SELECT * FROM kgprs WHERE id = ?');
749
+ const row = stmt.get(id);
750
+ return row ? this.rowToKGPR(row) : null;
751
+ }
752
+ /**
753
+ * Update KGPR status
754
+ */
755
+ updateKGPRStatus(id, status, timestamp) {
756
+ const db = this.getDb();
757
+ const now = timestamp ?? new Date();
758
+ let timestampField = '';
759
+ switch (status) {
760
+ case 'open':
761
+ timestampField = 'submitted_at';
762
+ break;
763
+ case 'approved':
764
+ case 'changes_requested':
765
+ timestampField = 'reviewed_at';
766
+ break;
767
+ case 'merged':
768
+ timestampField = 'merged_at';
769
+ break;
770
+ case 'closed':
771
+ timestampField = 'closed_at';
772
+ break;
773
+ }
774
+ if (timestampField) {
775
+ const stmt = db.prepare(`UPDATE kgprs SET status = ?, ${timestampField} = ? WHERE id = ?`);
776
+ stmt.run(status, now.toISOString(), id);
777
+ }
778
+ else {
779
+ const stmt = db.prepare('UPDATE kgprs SET status = ? WHERE id = ?');
780
+ stmt.run(status, id);
781
+ }
782
+ }
783
+ /**
784
+ * List KGPRs with optional filters
785
+ */
786
+ listKGPRs(options) {
787
+ const db = this.getDb();
788
+ let sql = 'SELECT * FROM kgprs WHERE 1=1';
789
+ const params = [];
790
+ if (options?.status) {
791
+ sql += ' AND status = ?';
792
+ params.push(options.status);
793
+ }
794
+ if (options?.namespace) {
795
+ sql += ' AND namespace LIKE ?';
796
+ params.push(`${options.namespace}%`);
797
+ }
798
+ sql += ' ORDER BY created_at DESC';
799
+ if (options?.limit) {
800
+ sql += ' LIMIT ?';
801
+ params.push(options.limit);
802
+ if (options?.offset) {
803
+ sql += ' OFFSET ?';
804
+ params.push(options.offset);
805
+ }
806
+ }
807
+ const stmt = db.prepare(sql);
808
+ const rows = stmt.all(...params);
809
+ return rows.map(row => this.rowToKGPR(row));
810
+ }
811
+ /**
812
+ * Insert a KGPR review
813
+ */
814
+ insertKGPRReview(review) {
815
+ const db = this.getDb();
816
+ const stmt = db.prepare(`
817
+ INSERT INTO kgpr_reviews (id, kgpr_id, reviewer, action, comment, created_at)
818
+ VALUES (?, ?, ?, ?, ?, ?)
819
+ `);
820
+ stmt.run(review.id, review.kgprId, review.reviewer, review.action, review.comment ?? null, review.createdAt);
821
+ }
822
+ /**
823
+ * Get reviews for a KGPR
824
+ */
825
+ getKGPRReviews(kgprId) {
826
+ const db = this.getDb();
827
+ const stmt = db.prepare('SELECT * FROM kgpr_reviews WHERE kgpr_id = ? ORDER BY created_at DESC');
828
+ const rows = stmt.all(kgprId);
829
+ return rows.map(row => ({
830
+ id: row.id,
831
+ kgprId: row.kgpr_id,
832
+ reviewer: row.reviewer,
833
+ action: row.action,
834
+ comment: row.comment ?? undefined,
835
+ createdAt: new Date(row.created_at),
836
+ }));
837
+ }
838
+ // ============================================================
839
+ // Transaction Support (TSK-NFR-002)
840
+ // @see REQ-NFR-002
841
+ // ============================================================
842
+ /**
843
+ * Execute operations within a transaction
844
+ * @see REQ-NFR-002
845
+ */
846
+ withTransaction(fn) {
847
+ const db = this.getDb();
848
+ db.exec('BEGIN TRANSACTION');
849
+ try {
850
+ const result = fn();
851
+ db.exec('COMMIT');
852
+ return result;
853
+ }
854
+ catch (error) {
855
+ db.exec('ROLLBACK');
856
+ throw error;
857
+ }
858
+ }
859
+ /**
860
+ * Execute async operations within a transaction
861
+ * @see REQ-NFR-002
862
+ */
863
+ async withTransactionAsync(fn) {
864
+ const db = this.getDb();
865
+ db.exec('BEGIN TRANSACTION');
866
+ try {
867
+ const result = await fn();
868
+ db.exec('COMMIT');
869
+ return result;
870
+ }
871
+ catch (error) {
872
+ db.exec('ROLLBACK');
873
+ throw error;
874
+ }
875
+ }
876
+ // Helper methods
877
+ rowToEntity(row) {
878
+ return {
879
+ id: row.id,
880
+ type: row.type,
881
+ name: row.name,
882
+ namespace: row.namespace,
883
+ filePath: row.file_path ?? undefined,
884
+ line: row.line ?? undefined,
885
+ description: row.description ?? undefined,
886
+ metadata: JSON.parse(row.metadata),
887
+ createdAt: new Date(row.created_at),
888
+ updatedAt: new Date(row.updated_at),
889
+ };
890
+ }
891
+ rowToRelationship(row) {
892
+ return {
893
+ id: row.id,
894
+ sourceId: row.source_id,
895
+ targetId: row.target_id,
896
+ type: row.type,
897
+ weight: row.weight,
898
+ metadata: JSON.parse(row.metadata),
899
+ createdAt: new Date(row.created_at),
900
+ };
901
+ }
902
+ logChange(entityId, relationshipId, operation, data) {
903
+ const db = this.getDb();
904
+ const stmt = db.prepare(`
905
+ INSERT INTO change_log (entity_id, relationship_id, operation, timestamp, data)
906
+ VALUES (?, ?, ?, ?, ?)
907
+ `);
908
+ stmt.run(entityId, relationshipId, operation, new Date().toISOString(), data ? JSON.stringify(data) : null);
909
+ }
910
+ rowToPattern(row) {
911
+ return {
912
+ id: row.id,
913
+ name: row.name,
914
+ category: row.category,
915
+ content: row.content,
916
+ ast: row.ast ? JSON.parse(row.ast) : undefined,
917
+ confidence: row.confidence,
918
+ occurrences: row.occurrences,
919
+ lastUsedAt: row.last_used_at ? new Date(row.last_used_at) : undefined,
920
+ usageCount: row.usage_count,
921
+ source: row.source ?? undefined,
922
+ metadata: JSON.parse(row.metadata),
923
+ createdAt: new Date(row.created_at),
924
+ updatedAt: new Date(row.updated_at),
925
+ };
926
+ }
927
+ rowToLearningCycle(row) {
928
+ return {
929
+ id: row.id,
930
+ wakeExtracted: row.wake_extracted,
931
+ wakeObservedFiles: row.wake_observed_files,
932
+ sleepClustered: row.sleep_clustered,
933
+ sleepDecayed: row.sleep_decayed,
934
+ durationMs: row.duration_ms,
935
+ metadata: JSON.parse(row.metadata),
936
+ completedAt: new Date(row.completed_at),
937
+ };
938
+ }
939
+ rowToKGPR(row) {
940
+ return {
941
+ id: row.id,
942
+ title: row.title,
943
+ description: row.description ?? undefined,
944
+ status: row.status,
945
+ author: row.author,
946
+ namespace: row.namespace,
947
+ diffJson: row.diff_json,
948
+ privacyLevel: row.privacy_level,
949
+ entityTypesJson: row.entity_types,
950
+ createdAt: new Date(row.created_at),
951
+ updatedAt: new Date(row.updated_at),
952
+ submittedAt: row.submitted_at ? new Date(row.submitted_at) : undefined,
953
+ reviewedAt: row.reviewed_at ? new Date(row.reviewed_at) : undefined,
954
+ mergedAt: row.merged_at ? new Date(row.merged_at) : undefined,
955
+ closedAt: row.closed_at ? new Date(row.closed_at) : undefined,
956
+ };
957
+ }
958
+ }
959
+ //# sourceMappingURL=database.js.map