@plumpslabs/kuma 2.3.32 → 2.3.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.3.32",
3
+ "version": "2.3.34",
4
4
  "description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@ import { spawn } from "child_process";
11
11
  import { Hono } from "hono";
12
12
 
13
13
  // src/db.ts
14
+ import crypto from "crypto";
14
15
  import fs from "fs";
15
16
  import path from "path";
16
17
  import { createRequire } from "module";
@@ -18,7 +19,8 @@ var require2 = createRequire(import.meta.url);
18
19
  var sqlJs = null;
19
20
  async function getSqlJs() {
20
21
  if (!sqlJs) {
21
- sqlJs = await require2("sql.js");
22
+ const initSqlJs = await require2("sql.js");
23
+ sqlJs = await initSqlJs();
22
24
  }
23
25
  return sqlJs;
24
26
  }
@@ -75,7 +77,7 @@ function detectStaleNodes(db, dbPath) {
75
77
  try {
76
78
  const root = projectRootFromDb(dbPath);
77
79
  const stmt = db.prepare(
78
- `SELECT file_path, COUNT(*) as cnt FROM nodes
80
+ `SELECT id, file_path, metadata, COUNT(*) as cnt FROM nodes
79
81
  WHERE file_path IS NOT NULL AND file_path != '' AND file_path NOT LIKE '%::%'
80
82
  GROUP BY file_path ORDER BY cnt DESC LIMIT 500`
81
83
  );
@@ -85,11 +87,28 @@ function detectStaleNodes(db, dbPath) {
85
87
  while (stmt.step()) {
86
88
  const row = stmt.getAsObject();
87
89
  const fp = row.file_path;
90
+ const metadata = row.metadata || "{}";
88
91
  checked++;
89
- if (!fs.existsSync(path.join(root, fp))) {
92
+ const fullPath = path.join(root, fp);
93
+ if (!fs.existsSync(fullPath)) {
90
94
  const count = Number(row.cnt ?? 1);
91
- missing.push({ filePath: fp, nodeCount: count });
95
+ missing.push({ filePath: fp, nodeCount: count, reason: "file_missing" });
92
96
  staleNodes += count;
97
+ } else {
98
+ try {
99
+ const parsed = JSON.parse(metadata);
100
+ const storedHash = parsed.contentHash;
101
+ if (storedHash) {
102
+ const content = fs.readFileSync(fullPath, "utf-8");
103
+ const currentHash = crypto.createHash("md5").update(content).digest("hex");
104
+ if (currentHash !== storedHash) {
105
+ const count = Number(row.cnt ?? 1);
106
+ missing.push({ filePath: fp, nodeCount: count, reason: "content_changed" });
107
+ staleNodes += count;
108
+ }
109
+ }
110
+ } catch {
111
+ }
93
112
  }
94
113
  }
95
114
  stmt.free();
@@ -135,34 +154,48 @@ async function getDashboardData() {
135
154
  const stats = {
136
155
  node_count: first(`SELECT COUNT(*) as c FROM nodes`, "c"),
137
156
  edge_count: first(`SELECT COUNT(*) as c FROM edges`, "c"),
138
- gotcha_count: first(`SELECT COUNT(*) as c FROM known_gotchas`, "c"),
139
- feature_count: first(`SELECT COUNT(*) as c FROM nodes WHERE type = 'feature'`, "c"),
140
- health_score: first(
141
- `SELECT COALESCE((SELECT score FROM health_snapshots ORDER BY created_at DESC LIMIT 1), 0) as c`,
142
- "c"
143
- )
157
+ gotcha_count: first(`SELECT COUNT(*) as c FROM known_gotchas`, "c")
144
158
  };
145
159
  const nodes = jsonRows(
146
- `SELECT json_group_array(json_object('id',id,'name',name,'type',type,'file_path',file_path,'severity',COALESCE(severity,'medium'),'confidence',COALESCE(confidence,0.8),'metadata',COALESCE(metadata,'{}'))) FROM nodes ORDER BY updated_at DESC`
160
+ `SELECT json_group_array(json_object('id',id,'name',name,'type',type,'file_path',file_path,'severity',COALESCE(severity,'medium'),'confidence',COALESCE(confidence,0.8),'metadata',COALESCE(metadata,'{}'))) FROM (SELECT * FROM nodes ORDER BY updated_at DESC)`
147
161
  );
148
162
  const edges = jsonRows(
149
163
  `SELECT json_group_array(json_object('source',source_id,'target',target_id,'relation',type,'weight',weight)) FROM edges`
150
164
  );
151
165
  const gotchas = jsonRows(
152
- `SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',REPLACE(REPLACE(description,char(10),' '),char(13),''),'severity',severity,'workaround',REPLACE(REPLACE(COALESCE(workaround,''),char(10),' '),char(13),''),'added_by',COALESCE(added_by,'agent'),'created_at',created_at)) FROM known_gotchas ORDER BY severity DESC`
166
+ `SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',REPLACE(REPLACE(description,char(10),' '),char(13),''),'severity',severity,'workaround',REPLACE(REPLACE(COALESCE(workaround,''),char(10),' '),char(13),''),'added_by',COALESCE(added_by,'agent'),'created_at',created_at)) FROM (SELECT * FROM known_gotchas ORDER BY created_at DESC)`
153
167
  );
154
168
  const features = jsonRows(
155
- `SELECT json_group_array(json_object('id',id,'name',name,'metadata',COALESCE(metadata,'{}'))) FROM nodes WHERE type = 'feature' ORDER BY name`
156
- );
157
- const health = jsonRows(
158
- `SELECT json_group_array(json_object('score',score,'summary',REPLACE(REPLACE(COALESCE(summary,''),char(10),' '),char(13),''),'risk_level',risk_level,'created_at',created_at)) FROM health_snapshots ORDER BY created_at DESC LIMIT 10`
169
+ `SELECT json_group_array(json_object('id',id,'name',name,'metadata',COALESCE(metadata,'{}'))) FROM (SELECT * FROM nodes WHERE type = 'feature' ORDER BY updated_at DESC)`
159
170
  );
171
+ let injectionCount = 0;
172
+ let injectionSavedMs = 0;
173
+ try {
174
+ const root = projectRootFromDb(dbPath);
175
+ const injPath = path.join(root, ".kuma", "injections.jsonl");
176
+ if (fs.existsSync(injPath)) {
177
+ const lines = fs.readFileSync(injPath, "utf-8").split("\n").filter(Boolean);
178
+ const now = Date.now();
179
+ for (const line of lines.slice(-200)) {
180
+ try {
181
+ const entry = JSON.parse(line);
182
+ const age = now - (entry.ts || 0);
183
+ if (age < 24 * 60 * 60 * 1e3) {
184
+ injectionCount++;
185
+ injectionSavedMs += entry.saved_ms || 5e3;
186
+ }
187
+ } catch {
188
+ }
189
+ }
190
+ }
191
+ } catch {
192
+ }
160
193
  const sessionMetrics = readSessionMetrics(dbPath);
161
194
  const verifTotal = first(`SELECT COUNT(*) as c FROM verifications`, "c");
162
195
  const verifPassed = first(`SELECT COUNT(*) as c FROM verifications WHERE passed = 1`, "c");
163
196
  const recentSessions = run(
164
197
  `SELECT started_at, COALESCE(goal,'') as goal, tool_calls, edits, rollbacks, failures, safety_score
165
- FROM sessions ORDER BY started_at DESC LIMIT 8`
198
+ FROM (SELECT * FROM sessions ORDER BY started_at DESC LIMIT 8)`
166
199
  ).map((r) => ({
167
200
  startedAt: Number(r.started_at ?? 0),
168
201
  goal: r.goal || "",
@@ -175,11 +208,10 @@ async function getDashboardData() {
175
208
  const efficiency = {
176
209
  sessions: first(`SELECT COUNT(*) as c FROM sessions`, "c"),
177
210
  toolCalls: first(`SELECT COUNT(*) as c FROM tool_calls`, "c"),
178
- totalEdits: first(`SELECT COALESCE(SUM(edits),0) as c FROM sessions`, "c"),
179
- totalRollbacks: first(`SELECT COALESCE(SUM(rollbacks),0) as c FROM sessions`, "c"),
180
- totalFailures: first(`SELECT COALESCE(SUM(failures),0) as c FROM sessions`, "c"),
211
+ gotchas: first(`SELECT COUNT(*) as c FROM known_gotchas`, "c"),
212
+ archFlows: sessionMetrics.recordings.archFlows,
213
+ decisions: first(`SELECT COUNT(*) as c FROM (SELECT id FROM decision_log UNION SELECT id FROM nodes WHERE type = 'decision')`, "c"),
181
214
  researchCacheScopes: first(`SELECT COUNT(*) as c FROM research_cache`, "c"),
182
- decisions: first(`SELECT COUNT(*) as c FROM decision_log`, "c"),
183
215
  verifications: verifTotal,
184
216
  verificationPassRate: verifTotal > 0 ? Math.round(verifPassed / verifTotal * 100) : null,
185
217
  metrics: sessionMetrics.metrics,
@@ -187,7 +219,7 @@ async function getDashboardData() {
187
219
  recentSessions
188
220
  };
189
221
  const staleness = detectStaleNodes(db, dbPath);
190
- return { stats, nodes, edges, gotchas, features, health, efficiency, staleness };
222
+ return { stats, nodes, edges, gotchas, efficiency, staleness, injections: { count: injectionCount, savedMs: injectionSavedMs, savedFormatted: Math.round(injectionSavedMs / 6e4) + " min" } };
191
223
  } finally {
192
224
  db.close();
193
225
  }