@plumpslabs/kuma 2.3.20 → 2.3.23

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 (39) hide show
  1. package/README.md +18 -0
  2. package/dist/index.js +10316 -380
  3. package/package.json +2 -2
  4. package/packages/ide/studio/dist/index.js +90 -9
  5. package/packages/ide/studio/public/index.html +528 -233
  6. package/dist/agentDetector-YOWQVJFR.js +0 -186
  7. package/dist/chunk-3BRBJZ7P.js +0 -1055
  8. package/dist/chunk-3OHYYXYN.js +0 -71
  9. package/dist/chunk-ABKE45T4.js +0 -1264
  10. package/dist/chunk-E2KFPEBT.js +0 -183
  11. package/dist/chunk-FKRSI5U5.js +0 -282
  12. package/dist/chunk-GFLSAXAH.js +0 -155
  13. package/dist/chunk-L7F67KUP.js +0 -172
  14. package/dist/chunk-LVKOGXLC.js +0 -658
  15. package/dist/chunk-PRUTTZBS.js +0 -1113
  16. package/dist/contextDigest-QB5XHPXE.js +0 -177
  17. package/dist/domainRules-QLPAQASB.js +0 -20
  18. package/dist/init-PL4XL662.js +0 -15
  19. package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
  20. package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
  21. package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
  22. package/dist/kumaContractEngine-KX27T4N7.js +0 -305
  23. package/dist/kumaDb-4XZ5S2LH.js +0 -65
  24. package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
  25. package/dist/kumaGotchas-XRGFFBTA.js +0 -151
  26. package/dist/kumaGraph-UMXZNGYF.js +0 -44
  27. package/dist/kumaMemory-FBJMV77G.js +0 -16
  28. package/dist/kumaMiner-XJETL7TL.js +0 -176
  29. package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
  30. package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
  31. package/dist/kumaSearch-PV4QTKE7.js +0 -321
  32. package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
  33. package/dist/kumaVerifier-6YEGC77M.js +0 -265
  34. package/dist/kumaVisualize-264OEBGJ.js +0 -264
  35. package/dist/pathValidator-V4DC6U6Z.js +0 -22
  36. package/dist/safetyAudit-O45SPNTS.js +0 -12
  37. package/dist/safetyScore-TMMRD2MV.js +0 -333
  38. package/dist/sessionMemory-YPKVIOMV.js +0 -6
  39. package/dist/skillGenerator-PWEJKZNX.js +0 -304
@@ -1,1055 +0,0 @@
1
- import {
2
- getKumaDir
3
- } from "./chunk-E2KFPEBT.js";
4
-
5
- // src/engine/kumaDb.ts
6
- import initSqlJs from "sql.js";
7
- import fs from "fs";
8
- import path from "path";
9
- var DB_FILENAME = "kuma.db";
10
- var dbInstance = null;
11
- var initPromise = null;
12
- function resetDbInstance() {
13
- dbInstance = null;
14
- initPromise = null;
15
- }
16
- async function getDb() {
17
- if (dbInstance) return dbInstance;
18
- if (initPromise) return initPromise;
19
- initPromise = initDb();
20
- return initPromise;
21
- }
22
- async function initDb() {
23
- const SQL = await initSqlJs();
24
- const kumaDir = getKumaDir();
25
- const dbPath = path.join(kumaDir, DB_FILENAME);
26
- if (!fs.existsSync(kumaDir)) {
27
- fs.mkdirSync(kumaDir, { recursive: true });
28
- }
29
- let db;
30
- if (fs.existsSync(dbPath)) {
31
- const buffer = fs.readFileSync(dbPath);
32
- db = new SQL.Database(buffer);
33
- } else {
34
- db = new SQL.Database();
35
- }
36
- db.run("PRAGMA journal_mode=WAL");
37
- db.run("PRAGMA synchronous=NORMAL");
38
- createSchema(db);
39
- saveDb(db);
40
- dbInstance = db;
41
- return db;
42
- }
43
- var _saveTimeout = null;
44
- var _pendingDb = null;
45
- var SAVE_DEBOUNCE_MS = 100;
46
- function saveDb(db) {
47
- const d = db ?? dbInstance;
48
- if (!d) return;
49
- _pendingDb = d;
50
- if (_saveTimeout) return;
51
- _saveTimeout = setTimeout(() => {
52
- _saveTimeout = null;
53
- const targetDb = _pendingDb;
54
- _pendingDb = null;
55
- if (!targetDb) return;
56
- try {
57
- const kumaDir = getKumaDir();
58
- const dbPath = path.join(kumaDir, DB_FILENAME);
59
- const data = targetDb.export();
60
- const buffer = Buffer.from(data);
61
- fs.writeFileSync(dbPath, buffer);
62
- } catch (err) {
63
- console.error(`[KumaDB] Failed to save database: ${err}`);
64
- }
65
- }, SAVE_DEBOUNCE_MS);
66
- }
67
- function createSchema(db) {
68
- db.run(`CREATE TABLE IF NOT EXISTS nodes (
69
- id TEXT PRIMARY KEY,
70
- type TEXT NOT NULL,
71
- name TEXT NOT NULL,
72
- file_path TEXT,
73
- metadata TEXT DEFAULT '{}',
74
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
75
- updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
76
- )`);
77
- db.run(`CREATE TABLE IF NOT EXISTS edges (
78
- id INTEGER PRIMARY KEY AUTOINCREMENT,
79
- source_id TEXT NOT NULL REFERENCES nodes(id),
80
- target_id TEXT NOT NULL REFERENCES nodes(id),
81
- type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with')),
82
- weight REAL DEFAULT 1.0,
83
- metadata TEXT DEFAULT '{}',
84
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
85
- UNIQUE(source_id, target_id, type)
86
- )`);
87
- try {
88
- const schema = db.exec(`SELECT sql FROM sqlite_master WHERE type='table' AND name='edges'`);
89
- const edgeSql = schema[0]?.values?.[0]?.[0] || "";
90
- if (edgeSql.includes("flows_through") === false) {
91
- db.run(`ALTER TABLE edges RENAME TO edges_old`);
92
- db.run(`CREATE TABLE IF NOT EXISTS edges (
93
- id INTEGER PRIMARY KEY AUTOINCREMENT,
94
- source_id TEXT NOT NULL REFERENCES nodes(id),
95
- target_id TEXT NOT NULL REFERENCES nodes(id),
96
- type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with')),
97
- weight REAL DEFAULT 1.0,
98
- metadata TEXT DEFAULT '{}',
99
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
100
- UNIQUE(source_id, target_id, type)
101
- )`);
102
- db.run(`INSERT OR IGNORE INTO edges SELECT * FROM edges_old`);
103
- db.run(`DROP TABLE edges_old`);
104
- }
105
- } catch {
106
- }
107
- db.run(`CREATE TABLE IF NOT EXISTS sessions (
108
- id INTEGER PRIMARY KEY AUTOINCREMENT,
109
- started_at INTEGER NOT NULL,
110
- ended_at INTEGER,
111
- goal TEXT,
112
- tool_calls INTEGER DEFAULT 0,
113
- edits INTEGER DEFAULT 0,
114
- rollbacks INTEGER DEFAULT 0,
115
- failures INTEGER DEFAULT 0,
116
- safety_score INTEGER
117
- )`);
118
- db.run(`CREATE TABLE IF NOT EXISTS tool_calls (
119
- id INTEGER PRIMARY KEY AUTOINCREMENT,
120
- session_id INTEGER REFERENCES sessions(id),
121
- tool_name TEXT NOT NULL,
122
- params TEXT,
123
- success INTEGER DEFAULT 1,
124
- duration_ms INTEGER,
125
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
126
- )`);
127
- db.run(`CREATE TABLE IF NOT EXISTS experiences (
128
- id INTEGER PRIMARY KEY AUTOINCREMENT,
129
- tool_name TEXT NOT NULL,
130
- params_hash TEXT NOT NULL,
131
- success INTEGER NOT NULL DEFAULT 1,
132
- duration_ms INTEGER,
133
- error_pattern TEXT,
134
- context_file TEXT,
135
- context_action TEXT,
136
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
137
- )`);
138
- db.run(`CREATE TABLE IF NOT EXISTS experience_patterns (
139
- id INTEGER PRIMARY KEY AUTOINCREMENT,
140
- antecedent_tool TEXT NOT NULL,
141
- antecedent_hash TEXT NOT NULL,
142
- consequent_tool TEXT NOT NULL,
143
- confidence REAL DEFAULT 0.0,
144
- count INTEGER DEFAULT 1,
145
- avg_duration_ms INTEGER DEFAULT 0,
146
- success_rate REAL DEFAULT 1.0,
147
- last_seen_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
148
- UNIQUE(antecedent_tool, antecedent_hash, consequent_tool)
149
- )`);
150
- db.run(`CREATE TABLE IF NOT EXISTS research_cache (
151
- id INTEGER PRIMARY KEY AUTOINCREMENT,
152
- scope TEXT NOT NULL UNIQUE,
153
- version INTEGER DEFAULT 1,
154
- confidence REAL DEFAULT 0.0,
155
- content_hash TEXT,
156
- record TEXT NOT NULL,
157
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
158
- updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
159
- )`);
160
- db.run(`CREATE TABLE IF NOT EXISTS change_log (
161
- id INTEGER PRIMARY KEY AUTOINCREMENT,
162
- session_id INTEGER REFERENCES sessions(id),
163
- file_path TEXT NOT NULL,
164
- change_type TEXT NOT NULL CHECK(change_type IN ('modified','created','deleted','renamed')),
165
- symbol TEXT,
166
- diff_summary TEXT,
167
- git_commit_hash TEXT,
168
- previous_content TEXT DEFAULT NULL,
169
- current_content TEXT DEFAULT NULL,
170
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
171
- )`);
172
- db.run(`CREATE TABLE IF NOT EXISTS health_snapshots (
173
- id INTEGER PRIMARY KEY AUTOINCREMENT,
174
- score INTEGER NOT NULL,
175
- risk_level TEXT NOT NULL DEFAULT 'low',
176
- checks TEXT DEFAULT '[]',
177
- summary TEXT,
178
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
179
- )`);
180
- try {
181
- db.run(`CREATE TABLE IF NOT EXISTS safety_audit (
182
- id INTEGER PRIMARY KEY AUTOINCREMENT,
183
- timestamp INTEGER NOT NULL,
184
- tool_name TEXT NOT NULL,
185
- action TEXT NOT NULL,
186
- file_path TEXT,
187
- risk_level TEXT NOT NULL DEFAULT 'low',
188
- policy_violations INTEGER DEFAULT 0,
189
- allowed INTEGER NOT NULL DEFAULT 1,
190
- duration_ms INTEGER DEFAULT 0,
191
- metadata TEXT DEFAULT '{}'
192
- )`);
193
- } catch {
194
- }
195
- db.run(`CREATE TABLE IF NOT EXISTS todos (
196
- id INTEGER PRIMARY KEY AUTOINCREMENT,
197
- title TEXT NOT NULL,
198
- description TEXT DEFAULT '',
199
- scope TEXT DEFAULT '',
200
- deps TEXT DEFAULT '[]',
201
- success_criteria TEXT DEFAULT '',
202
- status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','active','done','cancelled')),
203
- session_id INTEGER,
204
- file_paths TEXT DEFAULT '[]',
205
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
206
- updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
207
- )`);
208
- db.run(`CREATE TABLE IF NOT EXISTS security_findings (
209
- id INTEGER PRIMARY KEY AUTOINCREMENT,
210
- file_path TEXT NOT NULL,
211
- line_number INTEGER,
212
- pattern TEXT NOT NULL,
213
- severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low','medium','high','critical')),
214
- message TEXT NOT NULL,
215
- suggestion TEXT,
216
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
217
- )`);
218
- db.run(`CREATE TABLE IF NOT EXISTS api_endpoints (
219
- id INTEGER PRIMARY KEY AUTOINCREMENT,
220
- method TEXT NOT NULL,
221
- path TEXT NOT NULL,
222
- params TEXT DEFAULT '{}',
223
- returns TEXT DEFAULT '{}',
224
- auth TEXT,
225
- handler_file TEXT,
226
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
227
- UNIQUE(method, path)
228
- )`);
229
- db.run(`CREATE TABLE IF NOT EXISTS patterns (
230
- id INTEGER PRIMARY KEY AUTOINCREMENT,
231
- pattern_type TEXT NOT NULL,
232
- before_code TEXT,
233
- after_code TEXT,
234
- description TEXT,
235
- file_path TEXT,
236
- matched_files TEXT DEFAULT '[]',
237
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
238
- )`);
239
- db.run(`CREATE TABLE IF NOT EXISTS context_notes (
240
- id INTEGER PRIMARY KEY AUTOINCREMENT,
241
- source TEXT NOT NULL,
242
- content TEXT NOT NULL,
243
- scope TEXT DEFAULT '',
244
- file_paths TEXT DEFAULT '[]',
245
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
246
- )`);
247
- db.run(`CREATE TABLE IF NOT EXISTS benchmarks (
248
- id INTEGER PRIMARY KEY AUTOINCREMENT,
249
- label TEXT NOT NULL,
250
- timestamp INTEGER NOT NULL,
251
- metrics TEXT NOT NULL DEFAULT '{}',
252
- comparison_with TEXT,
253
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
254
- )`);
255
- db.run(`CREATE TABLE IF NOT EXISTS decision_log (
256
- id INTEGER PRIMARY KEY AUTOINCREMENT,
257
- title TEXT NOT NULL,
258
- context TEXT,
259
- rationale TEXT,
260
- outcome TEXT,
261
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','superseded','deprecated','proposed')),
262
- superseded_by INTEGER,
263
- file_paths TEXT DEFAULT '[]',
264
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
265
- )`);
266
- db.run(`CREATE TABLE IF NOT EXISTS file_summaries (
267
- id INTEGER PRIMARY KEY AUTOINCREMENT,
268
- file_path TEXT NOT NULL UNIQUE,
269
- summary TEXT,
270
- exports TEXT DEFAULT '[]',
271
- imports TEXT DEFAULT '[]',
272
- content_hash TEXT,
273
- file_size INTEGER DEFAULT 0,
274
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
275
- updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
276
- )`);
277
- db.run(`CREATE TABLE IF NOT EXISTS otel_config (
278
- id INTEGER PRIMARY KEY AUTOINCREMENT,
279
- endpoint TEXT,
280
- service_name TEXT DEFAULT 'kuma',
281
- enabled INTEGER DEFAULT 0,
282
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
283
- )`);
284
- db.run(`CREATE TABLE IF NOT EXISTS cost_tracking (
285
- id INTEGER PRIMARY KEY AUTOINCREMENT,
286
- session_id INTEGER REFERENCES sessions(id),
287
- tool_name TEXT NOT NULL,
288
- token_estimate INTEGER DEFAULT 0,
289
- cost_estimate REAL DEFAULT 0.0,
290
- budget_limit REAL DEFAULT 0.0,
291
- escalated INTEGER DEFAULT 0,
292
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
293
- )`);
294
- db.run(`CREATE TABLE IF NOT EXISTS scratch_entries (
295
- id INTEGER PRIMARY KEY AUTOINCREMENT,
296
- file_path TEXT NOT NULL,
297
- reason TEXT,
298
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
299
- )`);
300
- db.run(`CREATE TABLE IF NOT EXISTS verifications (
301
- id INTEGER PRIMARY KEY AUTOINCREMENT,
302
- scope TEXT NOT NULL,
303
- runner TEXT NOT NULL,
304
- test_command TEXT NOT NULL,
305
- passed INTEGER NOT NULL,
306
- output TEXT,
307
- duration_ms INTEGER,
308
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
309
- )`);
310
- db.exec(`
311
- CREATE TABLE IF NOT EXISTS blackboard_events (
312
- id INTEGER PRIMARY KEY AUTOINCREMENT,
313
- type TEXT NOT NULL,
314
- source TEXT NOT NULL,
315
- topic TEXT NOT NULL,
316
- payload TEXT NOT NULL DEFAULT '{}',
317
- severity TEXT NOT NULL DEFAULT 'info'
318
- CHECK(severity IN ('info','warning','critical')),
319
- tags TEXT DEFAULT '[]',
320
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
321
- );
322
- `);
323
- db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_type ON blackboard_events(type)`);
324
- db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_topic ON blackboard_events(topic)`);
325
- db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_severity ON blackboard_events(severity)`);
326
- db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_created ON blackboard_events(created_at)`);
327
- db.exec(`
328
- CREATE TABLE IF NOT EXISTS known_gotchas (
329
- id INTEGER PRIMARY KEY AUTOINCREMENT,
330
- file_path TEXT NOT NULL,
331
- description TEXT NOT NULL,
332
- severity TEXT NOT NULL DEFAULT 'medium'
333
- CHECK(severity IN ('low','medium','high','critical')),
334
- workaround TEXT,
335
- added_by TEXT DEFAULT 'agent',
336
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
337
- updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
338
- );
339
- `);
340
- db.exec(`CREATE INDEX IF NOT EXISTS idx_gotchas_file ON known_gotchas(file_path)`);
341
- db.exec(`CREATE INDEX IF NOT EXISTS idx_gotchas_severity ON known_gotchas(severity)`);
342
- db.exec(`
343
- CREATE TABLE IF NOT EXISTS trajectories (
344
- id INTEGER PRIMARY KEY AUTOINCREMENT,
345
- goal TEXT NOT NULL,
346
- steps TEXT NOT NULL DEFAULT '[]',
347
- total_duration_ms INTEGER DEFAULT 0,
348
- success_rate REAL DEFAULT 0.0,
349
- complexity INTEGER DEFAULT 0,
350
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
351
- );
352
- `);
353
- db.exec(`CREATE INDEX IF NOT EXISTS idx_traj_goal ON trajectories(goal)`);
354
- db.exec(`CREATE INDEX IF NOT EXISTS idx_traj_complexity ON trajectories(complexity)`);
355
- db.exec(`CREATE INDEX IF NOT EXISTS idx_traj_created ON trajectories(created_at)`);
356
- db.exec(`
357
- CREATE TABLE IF NOT EXISTS distilled_skills (
358
- id INTEGER PRIMARY KEY AUTOINCREMENT,
359
- name TEXT NOT NULL UNIQUE,
360
- description TEXT NOT NULL,
361
- pattern TEXT NOT NULL,
362
- parameters TEXT DEFAULT '[]',
363
- success_count INTEGER DEFAULT 1,
364
- avg_duration_ms INTEGER DEFAULT 0,
365
- source_trajectory_id INTEGER,
366
- last_used_at INTEGER,
367
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
368
- );
369
- `);
370
- db.exec(`CREATE INDEX IF NOT EXISTS idx_skills_name ON distilled_skills(name)`);
371
- db.exec(`CREATE INDEX IF NOT EXISTS idx_skills_used ON distilled_skills(last_used_at)`);
372
- db.run(`CREATE TABLE IF NOT EXISTS portability_entries (
373
- id INTEGER PRIMARY KEY AUTOINCREMENT,
374
- entry_type TEXT NOT NULL,
375
- relative_path TEXT NOT NULL,
376
- absolute_path TEXT,
377
- status TEXT DEFAULT 'ok',
378
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
379
- )`);
380
- try {
381
- db.run(`CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
382
- name, metadata, content='nodes', content_rowid='rowid'
383
- )`);
384
- } catch {
385
- console.warn("[KumaDB] FTS5 not available, full-text search disabled");
386
- }
387
- db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type)`);
388
- db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name)`);
389
- db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_updated ON nodes(updated_at)`);
390
- db.run(`CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id)`);
391
- db.run(`CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id)`);
392
- db.run(`CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type)`);
393
- db.run(`CREATE INDEX IF NOT EXISTS idx_edges_updated ON edges(created_at)`);
394
- db.run(`CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id)`);
395
- db.run(`CREATE INDEX IF NOT EXISTS idx_tool_calls_created ON tool_calls(created_at)`);
396
- db.run(`CREATE INDEX IF NOT EXISTS idx_experiences_tool ON experiences(tool_name)`);
397
- db.run(`CREATE INDEX IF NOT EXISTS idx_experiences_hash ON experiences(params_hash)`);
398
- db.run(`CREATE INDEX IF NOT EXISTS idx_experiences_created ON experiences(created_at)`);
399
- db.run(`CREATE INDEX IF NOT EXISTS idx_patterns_antecedent ON experience_patterns(antecedent_tool)`);
400
- db.run(`CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON experience_patterns(confidence DESC)`);
401
- db.run(`CREATE INDEX IF NOT EXISTS idx_research_scope ON research_cache(scope)`);
402
- db.run(`CREATE INDEX IF NOT EXISTS idx_research_updated ON research_cache(updated_at)`);
403
- db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_session ON change_log(session_id)`);
404
- db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_file ON change_log(file_path)`);
405
- db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_created ON change_log(created_at)`);
406
- db.run(`CREATE INDEX IF NOT EXISTS idx_health_created ON health_snapshots(created_at)`);
407
- db.run(`CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status)`);
408
- db.run(`CREATE INDEX IF NOT EXISTS idx_todos_scope ON todos(scope)`);
409
- db.run(`CREATE INDEX IF NOT EXISTS idx_security_file ON security_findings(file_path)`);
410
- db.run(`CREATE INDEX IF NOT EXISTS idx_security_severity ON security_findings(severity)`);
411
- db.run(`CREATE INDEX IF NOT EXISTS idx_api_path ON api_endpoints(path)`);
412
- db.run(`CREATE INDEX IF NOT EXISTS idx_context_notes_scope ON context_notes(scope)`);
413
- db.run(`CREATE INDEX IF NOT EXISTS idx_benchmarks_label ON benchmarks(label)`);
414
- db.run(`CREATE INDEX IF NOT EXISTS idx_decision_status ON decision_log(status)`);
415
- db.run(`CREATE INDEX IF NOT EXISTS idx_file_summaries_path ON file_summaries(file_path)`);
416
- db.run(`CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON safety_audit(timestamp)`);
417
- db.run(`CREATE INDEX IF NOT EXISTS idx_audit_tool ON safety_audit(tool_name)`);
418
- db.run(`CREATE INDEX IF NOT EXISTS idx_audit_risk ON safety_audit(risk_level)`);
419
- db.run(`CREATE INDEX IF NOT EXISTS idx_audit_allowed ON safety_audit(allowed)`);
420
- }
421
- async function getResearchCache(scope) {
422
- try {
423
- const db = await getDb();
424
- const stmt = db.prepare("SELECT record, content_hash, updated_at FROM research_cache WHERE scope = ?");
425
- stmt.bind([scope]);
426
- if (stmt.step()) {
427
- const row = stmt.getAsObject();
428
- stmt.free();
429
- return row.record;
430
- }
431
- stmt.free();
432
- return null;
433
- } catch {
434
- return null;
435
- }
436
- }
437
- async function saveResearchCache(scope, record, contentHash, confidence) {
438
- try {
439
- const db = await getDb();
440
- const stmt = db.prepare("SELECT id FROM research_cache WHERE scope = ?");
441
- stmt.bind([scope]);
442
- const hasExisting = stmt.step();
443
- stmt.free();
444
- if (hasExisting) {
445
- db.run(
446
- `UPDATE research_cache SET record = ?, content_hash = COALESCE(?, content_hash), confidence = COALESCE(?, confidence), version = version + 1, updated_at = strftime('%s','now') WHERE scope = ?`,
447
- [record, contentHash || null, confidence ?? null, scope]
448
- );
449
- } else {
450
- db.run(
451
- `INSERT INTO research_cache (scope, record, content_hash, confidence, version) VALUES (?, ?, ?, ?, 1)`,
452
- [scope, record, contentHash || null, confidence ?? null]
453
- );
454
- }
455
- saveDb();
456
- } catch (err) {
457
- console.error(`[KumaDB] Failed to save research cache: ${err}`);
458
- }
459
- }
460
- async function listResearchCache() {
461
- try {
462
- const db = await getDb();
463
- const stmt = db.prepare(`SELECT scope, version, confidence, created_at, updated_at FROM research_cache ORDER BY updated_at DESC`);
464
- const results = [];
465
- while (stmt.step()) results.push(stmt.getAsObject());
466
- stmt.free();
467
- if (results.length === 0) return "\u{1F4DA} **Research Cache** \u2014 No cached research found.";
468
- const lines = [`\u{1F4DA} **Research Cache** \u2014 ${results.length} scope(s)
469
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
470
- `];
471
- for (const r of results) {
472
- const conf = (r.confidence || 0) * 100;
473
- const updatedAt = new Date(r.updated_at * 1e3);
474
- const ageDays = Math.floor((Date.now() - updatedAt.getTime()) / 864e5);
475
- const ageStr = ageDays > 0 ? `${ageDays}d ago` : `${Math.floor((Date.now() - updatedAt.getTime()) / 36e5)}h ago`;
476
- const freshness = ageDays > 7 ? "\u{1F7E1}" : ageDays > 1 ? "\u{1F7E2}" : "\u{1F195}";
477
- lines.push(` ${freshness} **${r.scope}** (v${r.version}) \u2014 ${conf.toFixed(0)}% | ${ageStr}`);
478
- }
479
- return lines.join("\n");
480
- } catch (err) {
481
- return `Error: ${err}`;
482
- }
483
- }
484
- async function recordChange(entry) {
485
- try {
486
- const db = await getDb();
487
- const root = process.cwd();
488
- const fullPath = path.resolve(root, entry.filePath);
489
- let previousContent = null;
490
- if (entry.changeType === "modified" && fs.existsSync(fullPath)) {
491
- try {
492
- previousContent = fs.readFileSync(fullPath, "utf-8");
493
- } catch {
494
- }
495
- } else if (entry.changeType === "created") {
496
- previousContent = "";
497
- }
498
- db.run(
499
- `INSERT INTO change_log (session_id, file_path, change_type, symbol, diff_summary, git_commit_hash, previous_content) VALUES (?, ?, ?, ?, ?, ?, ?)`,
500
- [entry.sessionId ?? null, entry.filePath, entry.changeType, entry.symbol ?? null, entry.diffSummary ?? null, entry.gitCommitHash ?? null, previousContent]
501
- );
502
- saveDb();
503
- } catch (err) {
504
- console.error(`[KumaDB] Failed to record change: ${err}`);
505
- }
506
- }
507
- async function rollbackChange(changeId) {
508
- try {
509
- const db = await getDb();
510
- const stmt = db.prepare("SELECT * FROM change_log WHERE id = ?");
511
- stmt.bind([changeId]);
512
- if (!stmt.step()) {
513
- stmt.free();
514
- return `\u274C Change #${changeId} not found.`;
515
- }
516
- const row = stmt.getAsObject();
517
- stmt.free();
518
- const filePath = row.file_path;
519
- const changeType = row.change_type;
520
- const previousContent = row.previous_content;
521
- const root = process.cwd();
522
- const fullPath = path.resolve(root, filePath);
523
- if (changeType === "deleted") {
524
- if (previousContent === null) return `\u274C Cannot rollback deletion \u2014 no content stored.`;
525
- const dir = path.dirname(fullPath);
526
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
527
- fs.writeFileSync(fullPath, previousContent, "utf-8");
528
- await recordChange({ filePath, changeType: "created", diffSummary: `Rollback of #${changeId} (was deleted)` });
529
- return `\u2705 Rollback \u2014 restored deleted file "${filePath}" (#${changeId})`;
530
- }
531
- if (previousContent === null) return `\u274C Cannot rollback #${changeId} \u2014 no content captured.`;
532
- if (!fs.existsSync(fullPath) && changeType === "modified") return `\u274C File "${filePath}" no longer exists.`;
533
- if (changeType === "created" && previousContent === "") {
534
- if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
535
- await recordChange({ filePath, changeType: "deleted", diffSummary: `Rollback of #${changeId} (was created)` });
536
- return `\u2705 Rollback \u2014 deleted "${filePath}" (#${changeId})`;
537
- }
538
- fs.writeFileSync(fullPath, previousContent, "utf-8");
539
- await recordChange({ filePath, changeType, diffSummary: `Rollback of #${changeId} (restored previous content)` });
540
- return `\u2705 Rollback \u2014 reverted "${filePath}" to state before #${changeId}`;
541
- } catch (err) {
542
- return `\u274C Rollback failed: ${err}`;
543
- }
544
- }
545
- async function getChanges(params) {
546
- try {
547
- const db = await getDb();
548
- const { sessionId, filePath, since, limit = 50 } = params;
549
- let sql = `SELECT cl.*, s.goal FROM change_log cl LEFT JOIN sessions s ON s.id = cl.session_id WHERE 1=1`;
550
- const bind = [];
551
- if (sessionId) {
552
- sql += ` AND cl.session_id = ?`;
553
- bind.push(sessionId);
554
- }
555
- if (filePath) {
556
- sql += ` AND cl.file_path LIKE ?`;
557
- bind.push(`%${filePath}%`);
558
- }
559
- if (since) {
560
- sql += ` AND cl.created_at >= ?`;
561
- bind.push(since);
562
- }
563
- sql += ` ORDER BY cl.created_at DESC LIMIT ?`;
564
- bind.push(limit);
565
- const stmt = db.prepare(sql);
566
- stmt.bind(bind);
567
- const results = [];
568
- while (stmt.step()) results.push(stmt.getAsObject());
569
- stmt.free();
570
- if (results.length === 0) return "No changes found.";
571
- const lines = [`\u{1F4CB} Change Log \u2014 ${results.length} change(s)`];
572
- for (const r of results) {
573
- const icon = r.change_type === "deleted" ? "\u274C" : r.change_type === "created" ? "\u2728" : "\u{1F4DD}";
574
- lines.push(` ${icon} [#${r.id}] ${r.file_path} (${r.change_type})${r.previous_content ? " \u{1F504}" : ""}`);
575
- if (r.symbol) lines.push(` Symbol: ${r.symbol}`);
576
- if (r.goal) lines.push(` Goal: ${r.goal}`);
577
- }
578
- return lines.join("\n");
579
- } catch (err) {
580
- return `Error: ${err}`;
581
- }
582
- }
583
- async function addTodo(params) {
584
- try {
585
- const db = await getDb();
586
- db.run(
587
- `INSERT INTO todos (title, description, scope, deps, success_criteria) VALUES (?, ?, ?, ?, ?)`,
588
- [params.title, params.description || "", params.scope || "", params.deps || "[]", params.successCriteria || ""]
589
- );
590
- saveDb();
591
- return `\u2705 Todo added: "${params.title}"`;
592
- } catch (err) {
593
- return `\u274C Failed to add todo: ${err}`;
594
- }
595
- }
596
- async function listTodos(scope, status) {
597
- try {
598
- const db = await getDb();
599
- let sql = `SELECT * FROM todos WHERE 1=1`;
600
- const bind = [];
601
- if (scope) {
602
- sql += ` AND scope = ?`;
603
- bind.push(scope);
604
- }
605
- if (status) {
606
- sql += ` AND status = ?`;
607
- bind.push(status);
608
- }
609
- sql += ` ORDER BY created_at DESC LIMIT 50`;
610
- const stmt = db.prepare(sql);
611
- stmt.bind(bind);
612
- const results = [];
613
- while (stmt.step()) results.push(stmt.getAsObject());
614
- stmt.free();
615
- if (results.length === 0) return `\u{1F4CB} No todos found.`;
616
- const lines = [`\u{1F4CB} Todo List \u2014 ${results.length} item(s)
617
- `];
618
- for (const t of results) {
619
- const icon = t.status === "done" ? "\u2705" : t.status === "active" ? "\u{1F504}" : t.status === "cancelled" ? "\u274C" : "\u23F3";
620
- lines.push(` ${icon} [#${t.id}] ${t.title} (${t.status})`);
621
- if (t.scope) lines.push(` Scope: ${t.scope}`);
622
- if (t.success_criteria) lines.push(` Success: ${t.success_criteria}`);
623
- }
624
- return lines.join("\n");
625
- } catch (err) {
626
- return `Error: ${err}`;
627
- }
628
- }
629
- async function updateTodoStatus(id, status) {
630
- try {
631
- const db = await getDb();
632
- db.run(`UPDATE todos SET status = ?, updated_at = strftime('%s','now') WHERE id = ?`, [status, id]);
633
- saveDb();
634
- return `\u2705 Todo #${id} updated to "${status}"`;
635
- } catch (err) {
636
- return `\u274C Failed: ${err}`;
637
- }
638
- }
639
- async function addSecurityFinding(finding) {
640
- try {
641
- const db = await getDb();
642
- db.run(
643
- `INSERT INTO security_findings (file_path, line_number, pattern, severity, message, suggestion) VALUES (?, ?, ?, ?, ?, ?)`,
644
- [finding.filePath, finding.lineNumber || null, finding.pattern, finding.severity, finding.message, finding.suggestion || null]
645
- );
646
- saveDb();
647
- } catch {
648
- }
649
- }
650
- async function getSecurityFindings() {
651
- try {
652
- const db = await getDb();
653
- const stmt = db.prepare(`SELECT * FROM security_findings ORDER BY severity DESC, created_at DESC LIMIT 50`);
654
- const results = [];
655
- while (stmt.step()) results.push(stmt.getAsObject());
656
- stmt.free();
657
- if (results.length === 0) return "\u{1F512} No security findings.";
658
- const lines = [`\u{1F512} Security Findings \u2014 ${results.length}
659
- `];
660
- for (const f of results) {
661
- const icon = f.severity === "critical" ? "\u{1F534}" : f.severity === "high" ? "\u{1F7E0}" : f.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
662
- lines.push(` ${icon} [${f.severity}] ${f.message} \u2014 ${f.file_path}${f.line_number ? `:${f.line_number}` : ""}`);
663
- }
664
- return lines.join("\n");
665
- } catch (err) {
666
- return `Error: ${err}`;
667
- }
668
- }
669
- async function addContextNote(note) {
670
- try {
671
- const db = await getDb();
672
- db.run(
673
- `INSERT INTO context_notes (source, content, scope, file_paths) VALUES (?, ?, ?, ?)`,
674
- [note.source, note.content, note.scope || "", note.filePaths || "[]"]
675
- );
676
- saveDb();
677
- return `\u2705 Context note added from "${note.source}"`;
678
- } catch (err) {
679
- return `\u274C Failed: ${err}`;
680
- }
681
- }
682
- async function listContextNotes(scope) {
683
- try {
684
- const db = await getDb();
685
- let sql = `SELECT * FROM context_notes WHERE 1=1`;
686
- const bind = [];
687
- if (scope) {
688
- sql += ` AND scope = ?`;
689
- bind.push(scope);
690
- }
691
- sql += ` ORDER BY created_at DESC LIMIT 20`;
692
- const stmt = db.prepare(sql);
693
- stmt.bind(bind);
694
- const results = [];
695
- while (stmt.step()) results.push(stmt.getAsObject());
696
- stmt.free();
697
- if (results.length === 0) return "\u{1F4DD} No context notes.";
698
- const lines = [`\u{1F4DD} Context Notes \u2014 ${results.length}
699
- `];
700
- for (const n of results) {
701
- lines.push(` \u{1F4CC} From: ${n.source} | Scope: ${n.scope || "general"}`);
702
- lines.push(` ${n.content.substring(0, 200)}`);
703
- }
704
- return lines.join("\n");
705
- } catch (err) {
706
- return `Error: ${err}`;
707
- }
708
- }
709
- async function saveBenchmark(label, metrics) {
710
- try {
711
- const db = await getDb();
712
- db.run(
713
- `INSERT INTO benchmarks (label, timestamp, metrics) VALUES (?, ?, ?)`,
714
- [label, Math.floor(Date.now() / 1e3), JSON.stringify(metrics)]
715
- );
716
- saveDb();
717
- return `\u2705 Benchmark "${label}" saved.`;
718
- } catch (err) {
719
- return `\u274C Failed: ${err}`;
720
- }
721
- }
722
- async function getBenchmarkDiff(labelA, labelB) {
723
- try {
724
- const db = await getDb();
725
- const stmt = db.prepare(`SELECT * FROM benchmarks WHERE label = ? ORDER BY timestamp DESC LIMIT 1`);
726
- stmt.bind([labelA]);
727
- if (!stmt.step()) {
728
- stmt.free();
729
- return `\u274C Benchmark "${labelA}" not found.`;
730
- }
731
- const a = stmt.getAsObject();
732
- stmt.free();
733
- const metricsA = JSON.parse(a.metrics || "{}");
734
- let diffLines = [`\u{1F4CA} Benchmark: ${labelA}
735
- `];
736
- for (const [key, val] of Object.entries(metricsA)) {
737
- diffLines.push(` ${key}: ${val}`);
738
- }
739
- if (labelB) {
740
- const stmt2 = db.prepare(`SELECT * FROM benchmarks WHERE label = ? ORDER BY timestamp DESC LIMIT 1`);
741
- stmt2.bind([labelB]);
742
- if (stmt2.step()) {
743
- const b = stmt2.getAsObject();
744
- const metricsB = JSON.parse(b.metrics || "{}");
745
- diffLines.push(`
746
- \u{1F4CA} Diff: ${labelA} \u2192 ${labelB}
747
- `);
748
- for (const key of Object.keys({ ...metricsA, ...metricsB })) {
749
- const va = metricsA[key];
750
- const vb = metricsB[key];
751
- if (va !== void 0 && vb !== void 0 && typeof va === "number") {
752
- const change = ((vb - va) / va * 100).toFixed(1);
753
- const icon = parseFloat(change) > 0 ? "\u{1F4C8}" : parseFloat(change) < 0 ? "\u{1F4C9}" : "\u27A1\uFE0F";
754
- diffLines.push(` ${icon} ${key}: ${va} \u2192 ${vb} (${change}%)`);
755
- }
756
- }
757
- }
758
- stmt2.free();
759
- }
760
- return diffLines.join("\n");
761
- } catch (err) {
762
- return `Error: ${err}`;
763
- }
764
- }
765
- async function recordDecisionLog(entry) {
766
- try {
767
- const db = await getDb();
768
- db.run(
769
- `INSERT INTO decision_log (title, context, rationale, outcome, status) VALUES (?, ?, ?, ?, ?)`,
770
- [entry.title, entry.context || "", entry.rationale || "", entry.outcome || "", entry.status || "active"]
771
- );
772
- saveDb();
773
- return `\u2705 Decision "${entry.title}" logged as ${entry.status || "active"}.`;
774
- } catch (err) {
775
- return `\u274C Failed: ${err}`;
776
- }
777
- }
778
- async function listDecisionLog(status) {
779
- try {
780
- const db = await getDb();
781
- let sql = `SELECT * FROM decision_log WHERE 1=1`;
782
- const bind = [];
783
- if (status) {
784
- sql += ` AND status = ?`;
785
- bind.push(status);
786
- }
787
- sql += ` ORDER BY created_at DESC LIMIT 50`;
788
- const stmt = db.prepare(sql);
789
- stmt.bind(bind);
790
- const results = [];
791
- while (stmt.step()) results.push(stmt.getAsObject());
792
- stmt.free();
793
- if (results.length === 0) return "\u{1F4CB} No decisions logged.";
794
- const lines = [`\u{1F4CB} Decision Log \u2014 ${results.length}
795
- `];
796
- for (const d of results) {
797
- const icon = d.status === "active" ? "\u2705" : d.status === "superseded" ? "\u{1F504}" : d.status === "deprecated" ? "\u274C" : "\u{1F4DD}";
798
- lines.push(` ${icon} ${d.title} (${d.status})`);
799
- if (d.context) lines.push(` Context: ${d.context.substring(0, 150)}`);
800
- }
801
- return lines.join("\n");
802
- } catch (err) {
803
- return `Error: ${err}`;
804
- }
805
- }
806
- async function updateDecisionStatus(id, status) {
807
- try {
808
- const db = await getDb();
809
- db.run(`UPDATE decision_log SET status = ? WHERE id = ?`, [status, id]);
810
- saveDb();
811
- return `\u2705 Decision #${id} updated to "${status}"`;
812
- } catch (err) {
813
- return `\u274C Failed: ${err}`;
814
- }
815
- }
816
- async function getFileSummary(filePath) {
817
- try {
818
- const db = await getDb();
819
- const stmt = db.prepare("SELECT summary, exports, imports, content_hash FROM file_summaries WHERE file_path = ?");
820
- stmt.bind([filePath]);
821
- if (stmt.step()) {
822
- const row = stmt.getAsObject();
823
- stmt.free();
824
- return JSON.stringify(row);
825
- }
826
- stmt.free();
827
- return null;
828
- } catch {
829
- return null;
830
- }
831
- }
832
- async function saveFileSummary(params) {
833
- try {
834
- const db = await getDb();
835
- db.run(
836
- `INSERT OR REPLACE INTO file_summaries (file_path, summary, exports, imports, content_hash, file_size, updated_at) VALUES (?, ?, ?, ?, ?, ?, strftime('%s','now'))`,
837
- [params.filePath, params.summary, params.exports || "[]", params.imports || "[]", params.contentHash || "", params.fileSize || 0]
838
- );
839
- saveDb();
840
- } catch {
841
- }
842
- }
843
- async function runGarbageCollection() {
844
- try {
845
- const db = await getDb();
846
- let removed = 0;
847
- const orphanResult = db.exec(`DELETE FROM nodes WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges) AND file_path IS NULL AND updated_at < strftime('%s','now','-30 days')`);
848
- removed += orphanResult[0]?.values?.length || 0;
849
- db.exec(`DELETE FROM tool_calls WHERE created_at < strftime('%s','now','-90 days')`);
850
- db.exec(`DELETE FROM experiences WHERE created_at < strftime('%s','now','-90 days')`);
851
- try {
852
- db.exec("VACUUM");
853
- } catch {
854
- }
855
- saveDb();
856
- return `\u{1F9F9} GC complete. Removed orphaned data. Database vacuumed.`;
857
- } catch (err) {
858
- return `\u274C GC failed: ${err}`;
859
- }
860
- }
861
- async function runDoctor() {
862
- try {
863
- const db = await getDb();
864
- const checks = [
865
- "\u{1FA7A} **Kuma Doctor Report**",
866
- `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
867
- ""
868
- ];
869
- try {
870
- const integrity = db.exec("PRAGMA integrity_check");
871
- const result = integrity[0]?.values[0]?.[0] || "ok";
872
- checks.push(`**Database Integrity**: ${result === "ok" ? "\u2705" : "\u274C " + result}`);
873
- } catch {
874
- checks.push("**Database Integrity**: \u274C check failed");
875
- }
876
- const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "decision_log", "file_summaries", "verifications", "health_snapshots", "tool_calls", "experiences", "experience_patterns", "patterns", "api_endpoints", "otel_config", "cost_tracking", "scratch_entries", "portability_entries", "blackboard_events", "known_gotchas", "trajectories", "distilled_skills"];
877
- let existing = 0;
878
- for (const t of allTables) {
879
- try {
880
- if (db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='${t}'`)[0]?.values?.length) existing++;
881
- } catch {
882
- }
883
- }
884
- const schemaHealth = existing === allTables.length ? "\u2705" : existing >= allTables.length * 0.8 ? "\u26A0\uFE0F" : "\u274C";
885
- checks.push(`**Schema Health**: ${schemaHealth} ${existing}/${allTables.length} tables present`);
886
- try {
887
- const verifStmt = db.exec("SELECT scope, passed, created_at, duration_ms FROM verifications ORDER BY created_at DESC LIMIT 5");
888
- if (verifStmt[0]?.values?.length) {
889
- checks.push("");
890
- checks.push("**Recent Verifications:**");
891
- for (const v of verifStmt[0].values) {
892
- const icon = v[1] ? "\u2705" : "\u{1F534}";
893
- const time = new Date(v[2] * 1e3).toLocaleTimeString();
894
- checks.push(` ${icon} ${v[0]} (${v[3]}ms) \u2014 ${time}`);
895
- }
896
- }
897
- } catch {
898
- }
899
- try {
900
- const { execSync } = await import("child_process");
901
- const psOutput = execSync(
902
- `ps -eo pid,ppid,command | grep -E "(jest|pnpm test|npm test|yarn test)" | grep -v grep | head -10`,
903
- { encoding: "utf-8", timeout: 5e3 }
904
- ).trim();
905
- if (psOutput) {
906
- const processes = psOutput.split("\n").filter(Boolean);
907
- checks.push("");
908
- checks.push(`**Running Test Processes**: ${processes.length} found`);
909
- for (const p of processes) {
910
- checks.push(` \u26A1 ${p.substring(0, 120)}`);
911
- }
912
- checks.push(" \u{1F4A1} Run `kuma_safety({ action: 'gc' })` to clean up stale processes");
913
- } else {
914
- checks.push("");
915
- checks.push("**Running Test Processes**: \u2705 None detected");
916
- }
917
- } catch {
918
- checks.push("");
919
- checks.push("**Running Test Processes**: \u26A0\uFE0F Could not check");
920
- }
921
- try {
922
- const hourAgo = Math.floor((Date.now() - 36e5) / 1e3);
923
- const recentVerifs = db.exec(`SELECT COUNT(*) as c FROM verifications WHERE created_at > ${hourAgo}`);
924
- const count = recentVerifs[0]?.values[0]?.[0] || 0;
925
- if (count > 10) {
926
- checks.push("");
927
- checks.push(`\u26A0\uFE0F **High Verification Rate**: ${count} verifications in the last hour \u2014 this is abnormal.`);
928
- checks.push(` \u{1F4A1} If you didn't request these, run \`pkill -f "pnpm test"\` to kill orphaned processes.`);
929
- } else if (count > 0) {
930
- checks.push("");
931
- checks.push(`**Verification Rate**: ${count} verifications in the last hour (normal)`);
932
- }
933
- } catch {
934
- }
935
- return checks.join("\n");
936
- } catch (err) {
937
- return `\u274C Doctor failed: ${err}`;
938
- }
939
- }
940
- async function checkPortability() {
941
- try {
942
- const root = process.cwd();
943
- const issues = [];
944
- const db = await getDb();
945
- try {
946
- const stmt = db.prepare(`SELECT id, file_path FROM change_log WHERE file_path LIKE ? LIMIT 10`);
947
- stmt.bind([`${root}%`]);
948
- if (stmt.step()) {
949
- issues.push("\u26A0\uFE0F Change log contains absolute paths (migration needed)");
950
- }
951
- stmt.free();
952
- } catch {
953
- }
954
- issues.push(`\u2705 All paths relative to: ${root.split("/").pop()}`);
955
- return issues.join("\n");
956
- } catch (err) {
957
- return `\u274C Portability check failed: ${err}`;
958
- }
959
- }
960
- async function ensureGitignore() {
961
- try {
962
- const root = process.cwd();
963
- const gitignorePath = path.join(root, ".gitignore");
964
- const kumaEntry = ".kuma/";
965
- if (fs.existsSync(gitignorePath)) {
966
- const content = fs.readFileSync(gitignorePath, "utf-8");
967
- if (content.includes(kumaEntry)) {
968
- return "\u2705 .kuma/ already in .gitignore";
969
- }
970
- fs.writeFileSync(gitignorePath, content.trimEnd() + "\n" + kumaEntry + "\n", "utf-8");
971
- } else {
972
- fs.writeFileSync(gitignorePath, kumaEntry + "\n", "utf-8");
973
- }
974
- return "\u2705 Added .kuma/ to .gitignore";
975
- } catch (err) {
976
- return `\u274C Failed: ${err}`;
977
- }
978
- }
979
- async function saveHealthSnapshot(score, riskLevel, checks, summary) {
980
- try {
981
- const db = await getDb();
982
- db.run(
983
- `INSERT INTO health_snapshots (score, risk_level, checks, summary) VALUES (?, ?, ?, ?)`,
984
- [score, riskLevel, checks, summary]
985
- );
986
- saveDb();
987
- } catch (err) {
988
- console.error(`[KumaDB] Failed to save health snapshot: ${err}`);
989
- }
990
- }
991
- async function saveVerification(scope, runner, command, passed, output, durationMs = 0) {
992
- try {
993
- const db = await getDb();
994
- db.run(
995
- `INSERT INTO verifications (scope, runner, test_command, passed, output, duration_ms) VALUES (?, ?, ?, ?, ?, ?)`,
996
- [scope, runner, command, passed ? 1 : 0, output, durationMs]
997
- );
998
- saveDb();
999
- } catch (err) {
1000
- console.error(`[KumaDB] Failed to save verification: ${err}`);
1001
- }
1002
- }
1003
- async function getLatestVerifications(limit = 10) {
1004
- try {
1005
- const db = await getDb();
1006
- const res = db.exec(`SELECT id, scope, runner, test_command, passed, output, duration_ms, created_at FROM verifications ORDER BY created_at DESC LIMIT ${limit}`);
1007
- if (!res[0] || !res[0].values) return [];
1008
- return res[0].values.map((row) => ({
1009
- id: row[0],
1010
- scope: row[1],
1011
- runner: row[2],
1012
- test_command: row[3],
1013
- passed: Boolean(row[4]),
1014
- output: row[5],
1015
- duration_ms: row[6],
1016
- created_at: row[7]
1017
- }));
1018
- } catch (err) {
1019
- console.error(`[KumaDB] Failed to get verifications: ${err}`);
1020
- return [];
1021
- }
1022
- }
1023
-
1024
- export {
1025
- resetDbInstance,
1026
- getDb,
1027
- saveDb,
1028
- getResearchCache,
1029
- saveResearchCache,
1030
- listResearchCache,
1031
- recordChange,
1032
- rollbackChange,
1033
- getChanges,
1034
- addTodo,
1035
- listTodos,
1036
- updateTodoStatus,
1037
- addSecurityFinding,
1038
- getSecurityFindings,
1039
- addContextNote,
1040
- listContextNotes,
1041
- saveBenchmark,
1042
- getBenchmarkDiff,
1043
- recordDecisionLog,
1044
- listDecisionLog,
1045
- updateDecisionStatus,
1046
- getFileSummary,
1047
- saveFileSummary,
1048
- runGarbageCollection,
1049
- runDoctor,
1050
- checkPortability,
1051
- ensureGitignore,
1052
- saveHealthSnapshot,
1053
- saveVerification,
1054
- getLatestVerifications
1055
- };