@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/README.md +284 -188
- package/dist/index.js +4003 -6202
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +54 -22
- package/packages/ide/studio/public/index.html +370 -90
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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,
|
|
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
|
}
|