@plumpslabs/kuma 2.3.32 → 2.3.33
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 +321 -184
- package/dist/index.js +224 -13
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +31 -13
- package/packages/ide/studio/public/index.html +350 -75
package/dist/index.js
CHANGED
|
@@ -1427,7 +1427,7 @@ var init_sessionMemory = __esm({
|
|
|
1427
1427
|
dependencyGraph: new Map(parsed.dependencyGraph || []),
|
|
1428
1428
|
toolCalls: parsed.toolCalls || [],
|
|
1429
1429
|
conventions: parsed.conventions,
|
|
1430
|
-
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
|
|
1430
|
+
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
|
|
1431
1431
|
metrics: parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
|
|
1432
1432
|
};
|
|
1433
1433
|
this.initialized = true;
|
|
@@ -1447,7 +1447,7 @@ var init_sessionMemory = __esm({
|
|
|
1447
1447
|
searchResults: /* @__PURE__ */ new Map(),
|
|
1448
1448
|
dependencyGraph: /* @__PURE__ */ new Map(),
|
|
1449
1449
|
toolCalls: [],
|
|
1450
|
-
recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
|
|
1450
|
+
recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
|
|
1451
1451
|
metrics: { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
|
|
1452
1452
|
};
|
|
1453
1453
|
this.initialized = true;
|
|
@@ -1610,6 +1610,18 @@ No unresolved issues.`;
|
|
|
1610
1610
|
this.state.currentGoal = goal;
|
|
1611
1611
|
this.save();
|
|
1612
1612
|
}
|
|
1613
|
+
setGoalProgress(percentage, milestone) {
|
|
1614
|
+
this.ensureInit();
|
|
1615
|
+
this.state.goalProgress = {
|
|
1616
|
+
percentage: Math.min(100, Math.max(0, percentage)),
|
|
1617
|
+
milestone,
|
|
1618
|
+
updatedAt: Date.now()
|
|
1619
|
+
};
|
|
1620
|
+
this.save();
|
|
1621
|
+
}
|
|
1622
|
+
getGoalProgress() {
|
|
1623
|
+
return this.state.goalProgress || null;
|
|
1624
|
+
}
|
|
1613
1625
|
addCompletedStep(step) {
|
|
1614
1626
|
this.ensureInit();
|
|
1615
1627
|
if (!this.state.completedSteps.includes(step)) {
|
|
@@ -2031,6 +2043,9 @@ No unresolved issues.`;
|
|
|
2031
2043
|
case "research_save":
|
|
2032
2044
|
this.state.recordings.researchSaves++;
|
|
2033
2045
|
break;
|
|
2046
|
+
case "feature":
|
|
2047
|
+
this.state.recordings.features++;
|
|
2048
|
+
break;
|
|
2034
2049
|
}
|
|
2035
2050
|
this.state.recordings.total++;
|
|
2036
2051
|
this.save();
|
|
@@ -2047,6 +2062,7 @@ No unresolved issues.`;
|
|
|
2047
2062
|
if (r.gotchas === 0) missing.push("gotcha");
|
|
2048
2063
|
if (r.decisions === 0) missing.push("decision");
|
|
2049
2064
|
if (r.researchSaves === 0) missing.push("research_save");
|
|
2065
|
+
if (r.features === 0) missing.push("feature");
|
|
2050
2066
|
return {
|
|
2051
2067
|
...r,
|
|
2052
2068
|
hasAnyRecordings: r.total > 0,
|
|
@@ -7465,6 +7481,102 @@ function mineSessionInsights() {
|
|
|
7465
7481
|
}
|
|
7466
7482
|
}
|
|
7467
7483
|
}
|
|
7484
|
+
const dirReads = {};
|
|
7485
|
+
for (const call of toolCalls) {
|
|
7486
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7487
|
+
const file = call.params.filePath || "";
|
|
7488
|
+
if (file) {
|
|
7489
|
+
const dir = file.split("/").slice(0, -1).join("/") || "root";
|
|
7490
|
+
if (!dirReads[dir]) dirReads[dir] = [];
|
|
7491
|
+
dirReads[dir].push(file.split("/").pop() || file);
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
for (const [dir, files] of Object.entries(dirReads)) {
|
|
7496
|
+
if (files.length >= 3) {
|
|
7497
|
+
const dirName = dir.split("/").pop() || dir;
|
|
7498
|
+
insights.push({
|
|
7499
|
+
type: "feature",
|
|
7500
|
+
scope: dir,
|
|
7501
|
+
content: `${dirName} \u2014 ${files.length} files explored: ${files.slice(0, 5).join(", ")}${files.length > 5 ? "..." : ""}`,
|
|
7502
|
+
confidence: 0.6,
|
|
7503
|
+
source: `directory_exploration:${files.length}_files`
|
|
7504
|
+
});
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
for (let i = 0; i < toolCalls.length - 1; i++) {
|
|
7508
|
+
const a = toolCalls[i];
|
|
7509
|
+
const b = toolCalls[i + 1];
|
|
7510
|
+
if (a.toolName === "test" && b.toolName === "edit") {
|
|
7511
|
+
const file = b.params.filePath || "";
|
|
7512
|
+
if (file) {
|
|
7513
|
+
insights.push({
|
|
7514
|
+
type: "gotcha",
|
|
7515
|
+
scope: file,
|
|
7516
|
+
content: `Test failed then ${file.split("/").pop()} was edited \u2014 likely a bug fix needed`,
|
|
7517
|
+
confidence: 0.7,
|
|
7518
|
+
source: `test_fail_fix`
|
|
7519
|
+
});
|
|
7520
|
+
}
|
|
7521
|
+
}
|
|
7522
|
+
}
|
|
7523
|
+
const importChain = [];
|
|
7524
|
+
for (const call of toolCalls) {
|
|
7525
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7526
|
+
const file = call.params.filePath || "";
|
|
7527
|
+
if (file && file.endsWith(".ts")) {
|
|
7528
|
+
importChain.push(file);
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
if (importChain.length >= 4) {
|
|
7533
|
+
const uniqueFiles = [...new Set(importChain)];
|
|
7534
|
+
if (uniqueFiles.length >= 3) {
|
|
7535
|
+
insights.push({
|
|
7536
|
+
type: "arch_flow",
|
|
7537
|
+
scope: uniqueFiles[0],
|
|
7538
|
+
content: `Import chain traced: ${uniqueFiles.slice(0, 5).map((f) => f.split("/").pop() || f).join(" \u2192 ")}`,
|
|
7539
|
+
confidence: 0.6,
|
|
7540
|
+
source: `import_chain:${uniqueFiles.length}_files`
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
const routeFiles = [];
|
|
7545
|
+
for (const call of toolCalls) {
|
|
7546
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7547
|
+
const file = call.params.filePath || "";
|
|
7548
|
+
if (file && (file.includes("route") || file.includes("api") || file.includes("controller"))) {
|
|
7549
|
+
routeFiles.push(file.split("/").pop() || file);
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
if (routeFiles.length >= 2) {
|
|
7554
|
+
insights.push({
|
|
7555
|
+
type: "feature",
|
|
7556
|
+
scope: "api",
|
|
7557
|
+
content: `API routes explored: ${routeFiles.slice(0, 5).join(", ")}`,
|
|
7558
|
+
confidence: 0.5,
|
|
7559
|
+
source: `api_route_discovery`
|
|
7560
|
+
});
|
|
7561
|
+
}
|
|
7562
|
+
const componentFiles = [];
|
|
7563
|
+
for (const call of toolCalls) {
|
|
7564
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7565
|
+
const file = call.params.filePath || "";
|
|
7566
|
+
if (file && (file.includes("component") || file.includes(".tsx") || file.includes(".jsx"))) {
|
|
7567
|
+
componentFiles.push(file.split("/").pop() || file);
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
if (componentFiles.length >= 3) {
|
|
7572
|
+
insights.push({
|
|
7573
|
+
type: "arch_flow",
|
|
7574
|
+
scope: "ui",
|
|
7575
|
+
content: `UI components explored: ${componentFiles.slice(0, 5).join(", ")}`,
|
|
7576
|
+
confidence: 0.6,
|
|
7577
|
+
source: `component_discovery:${componentFiles.length}_files`
|
|
7578
|
+
});
|
|
7579
|
+
}
|
|
7468
7580
|
return insights;
|
|
7469
7581
|
}
|
|
7470
7582
|
function formatMinedInsights(insights) {
|
|
@@ -7480,12 +7592,12 @@ function formatMinedInsights(insights) {
|
|
|
7480
7592
|
];
|
|
7481
7593
|
for (let i = 0; i < insights.length; i++) {
|
|
7482
7594
|
const ins = insights[i];
|
|
7483
|
-
const icon = ins.type === "gotcha" ? "\u26A0\uFE0F" : ins.type === "decision" ? "\u{1F4CC}" : "\u{1F500}";
|
|
7595
|
+
const icon = ins.type === "gotcha" ? "\u26A0\uFE0F" : ins.type === "decision" ? "\u{1F4CC}" : ins.type === "feature" ? "\u2B50" : "\u{1F500}";
|
|
7484
7596
|
const conf = Math.round(ins.confidence * 100);
|
|
7485
7597
|
lines.push(`${i + 1}. ${icon} **${ins.type}** (${conf}% confidence)`);
|
|
7486
7598
|
lines.push(` Scope: ${ins.scope}`);
|
|
7487
7599
|
lines.push(` ${ins.content}`);
|
|
7488
|
-
lines.push(` \u2192 \`kuma_memory({ action: "${ins.type}", ${ins.type === "arch_flow" ? "content" : 'scope: \\"${ins.scope}\\", content'}: "${ins.content.substring(0, 60)}..." })\``);
|
|
7600
|
+
lines.push(` \u2192 \`kuma_memory({ action: "${ins.type}", ${ins.type === "arch_flow" ? "content" : ins.type === "feature" ? `title: "\${ins.scope.split('/').pop() || ins.scope}", content` : 'scope: \\"${ins.scope}\\", content'}: "${ins.content.substring(0, 60)}..." })\``);
|
|
7489
7601
|
lines.push("");
|
|
7490
7602
|
}
|
|
7491
7603
|
lines.push("\u{1F4A1} Approve by recording the ones you agree with. Skip the rest.");
|
|
@@ -9858,8 +9970,9 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9858
9970
|
"1. `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
9859
9971
|
"",
|
|
9860
9972
|
"## Record Rules",
|
|
9861
|
-
"- **MUST:** decision, gotcha",
|
|
9973
|
+
"- **MUST:** decision, gotcha, arch_flow, research_save, feature",
|
|
9862
9974
|
"- **SKIP:** function, class, import, route",
|
|
9975
|
+
"- **RELATIONSHIPS:** Every node MUST have edges. No orphan nodes.",
|
|
9863
9976
|
"",
|
|
9864
9977
|
"## Platform Tool Names",
|
|
9865
9978
|
"- OpenCode / Antigravity: `kuma_kuma_*`",
|
|
@@ -11066,6 +11179,11 @@ var CONTEXT_ALIASES = {
|
|
|
11066
11179
|
"init": "init",
|
|
11067
11180
|
"start": "init",
|
|
11068
11181
|
"load": "init",
|
|
11182
|
+
// Resume synonyms
|
|
11183
|
+
"resume": "resume",
|
|
11184
|
+
"continue": "resume",
|
|
11185
|
+
"restore": "resume",
|
|
11186
|
+
"reload": "resume",
|
|
11069
11187
|
"brief": "init",
|
|
11070
11188
|
"project": "init",
|
|
11071
11189
|
"summary": "init",
|
|
@@ -11147,9 +11265,77 @@ async function handleContext(params) {
|
|
|
11147
11265
|
return handleDrift(params);
|
|
11148
11266
|
case "progressive":
|
|
11149
11267
|
return handleProgressive(params);
|
|
11268
|
+
case "resume":
|
|
11269
|
+
return handleResume(params);
|
|
11150
11270
|
default:
|
|
11151
|
-
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize, digest, drift, progressive`;
|
|
11271
|
+
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize, digest, drift, progressive, resume`;
|
|
11272
|
+
}
|
|
11273
|
+
}
|
|
11274
|
+
async function handleResume(_params) {
|
|
11275
|
+
sessionMemory.recordToolCall("kuma_context_resume", {});
|
|
11276
|
+
const lines = [
|
|
11277
|
+
"\u{1F504} **Kuma \u2014 Session Resume**",
|
|
11278
|
+
`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
11279
|
+
""
|
|
11280
|
+
];
|
|
11281
|
+
try {
|
|
11282
|
+
const db = await getDb();
|
|
11283
|
+
const res = db.exec(
|
|
11284
|
+
"SELECT goal, tool_calls, edits, rollbacks, failures, safety_score, started_at FROM sessions ORDER BY started_at DESC LIMIT 1"
|
|
11285
|
+
);
|
|
11286
|
+
if (res[0]?.values.length) {
|
|
11287
|
+
const row = res[0].values[0];
|
|
11288
|
+
const goal = row[0] || "no goal";
|
|
11289
|
+
const calls = row[1] || 0;
|
|
11290
|
+
const edits = row[2] || 0;
|
|
11291
|
+
const rollbacks = row[3] || 0;
|
|
11292
|
+
const failures2 = row[4] || 0;
|
|
11293
|
+
const safety = row[5] == null ? null : row[5];
|
|
11294
|
+
const time = new Date(row[6] * 1e3).toLocaleString();
|
|
11295
|
+
lines.push("**Last Session**");
|
|
11296
|
+
lines.push(` \u{1F3AF} Goal: ${goal.substring(0, 80)}`);
|
|
11297
|
+
lines.push(` \u{1F550} ${time}`);
|
|
11298
|
+
lines.push(` \u{1F527} ${calls} calls \xB7 \u{1F4DD} ${edits} edits \xB7 \u21A9 ${rollbacks} rollbacks \xB7 \u274C ${failures2} failures${safety != null ? ` \xB7 \u{1F6E1} safety ${safety}` : ""}`);
|
|
11299
|
+
lines.push("");
|
|
11300
|
+
}
|
|
11301
|
+
} catch {
|
|
11302
|
+
}
|
|
11303
|
+
const summary = sessionMemory.getSummary();
|
|
11304
|
+
lines.push("**Current State**");
|
|
11305
|
+
lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
|
|
11306
|
+
const progress = sessionMemory.getGoalProgress();
|
|
11307
|
+
if (progress) {
|
|
11308
|
+
lines.push(` \u{1F4CA} Progress: ${progress.percentage}%${progress.milestone ? ` \u2014 ${progress.milestone}` : ""}`);
|
|
11309
|
+
}
|
|
11310
|
+
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
11311
|
+
lines.push(` \u2705 Completed steps: ${summary.completedSteps?.length || 0}`);
|
|
11312
|
+
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
11313
|
+
lines.push(` \u23F1\uFE0F Session duration: ${summary.sessionDuration}`);
|
|
11314
|
+
const failures = summary.unresolvedFailures;
|
|
11315
|
+
if (failures?.length) {
|
|
11316
|
+
lines.push(` \u26A0\uFE0F Unresolved failures: ${failures.length}`);
|
|
11317
|
+
}
|
|
11318
|
+
lines.push("");
|
|
11319
|
+
try {
|
|
11320
|
+
const changes = await getChanges({ limit: 5 });
|
|
11321
|
+
if (changes && !changes.startsWith("No changes")) {
|
|
11322
|
+
lines.push("**Recent Changes**");
|
|
11323
|
+
lines.push(changes);
|
|
11324
|
+
lines.push("");
|
|
11325
|
+
}
|
|
11326
|
+
} catch {
|
|
11327
|
+
}
|
|
11328
|
+
const history = sessionMemory.getToolCallHistory(5);
|
|
11329
|
+
if (history.length) {
|
|
11330
|
+
lines.push("**Recent Tool Calls**");
|
|
11331
|
+
for (const h of history) {
|
|
11332
|
+
const action = h.params?.action || "";
|
|
11333
|
+
lines.push(` \u{1F6E0} ${h.toolName}${action ? ` (${action})` : ""}`);
|
|
11334
|
+
}
|
|
11335
|
+
lines.push("");
|
|
11152
11336
|
}
|
|
11337
|
+
lines.push("\u{1F4A1} Continue: kuma_context({ action: 'research', scope: '<area>' }) to pick up where you left off.");
|
|
11338
|
+
return lines.join("\n");
|
|
11153
11339
|
}
|
|
11154
11340
|
async function handleInit(_params) {
|
|
11155
11341
|
sessionMemory.setGoal(_params.goal || "Exploring project");
|
|
@@ -11781,6 +11967,8 @@ async function handleMemory(params) {
|
|
|
11781
11967
|
return handleAddNode(params);
|
|
11782
11968
|
case "feature":
|
|
11783
11969
|
return handleFeature(params);
|
|
11970
|
+
case "goal_progress":
|
|
11971
|
+
return handleGoalProgress(params);
|
|
11784
11972
|
case "delete_node":
|
|
11785
11973
|
return handleDeleteNode(params);
|
|
11786
11974
|
case "graph_health":
|
|
@@ -11792,8 +11980,6 @@ async function handleMemory(params) {
|
|
|
11792
11980
|
}
|
|
11793
11981
|
case "harvest":
|
|
11794
11982
|
return handleHarvest(params);
|
|
11795
|
-
case "mine":
|
|
11796
|
-
return handleMine(params);
|
|
11797
11983
|
case "session_mine":
|
|
11798
11984
|
return handleSessionMine(params);
|
|
11799
11985
|
case "noise_policy":
|
|
@@ -12347,13 +12533,22 @@ async function handleFeature(params) {
|
|
|
12347
12533
|
tags,
|
|
12348
12534
|
risk
|
|
12349
12535
|
});
|
|
12350
|
-
sessionMemory.recordMemoryAction("
|
|
12536
|
+
sessionMemory.recordMemoryAction("feature");
|
|
12351
12537
|
return `\u2705 **Feature "${params.title}" recorded** \u2014 ${result.nodeCount} node(s), ${result.edgeCount} edge(s)
|
|
12352
12538
|
\u2B50 Level: feature
|
|
12353
12539
|
\u{1F4C1} Files: ${files.length}
|
|
12354
12540
|
\u{1F3F7}\uFE0F Tags: ${tags.length}
|
|
12355
12541
|
\u26A0\uFE0F Risk: ${risk}`;
|
|
12356
12542
|
}
|
|
12543
|
+
async function handleGoalProgress(params) {
|
|
12544
|
+
const pct = params.confidence ?? 0;
|
|
12545
|
+
const ms = params.content || params.title;
|
|
12546
|
+
sessionMemory.setGoalProgress(pct, ms);
|
|
12547
|
+
const p = sessionMemory.getGoalProgress();
|
|
12548
|
+
if (!p) return "Error";
|
|
12549
|
+
const bar = "\u2588".repeat(Math.floor(p.percentage / 10)) + "\u2591".repeat(10 - Math.floor(p.percentage / 10));
|
|
12550
|
+
return `\u{1F4CA} ${bar} ${p.percentage}%${p.milestone ? " \u2014 " + p.milestone : ""}`;
|
|
12551
|
+
}
|
|
12357
12552
|
async function handleGraphHealth(_params) {
|
|
12358
12553
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
12359
12554
|
const db = await getDb2();
|
|
@@ -13417,6 +13612,14 @@ async function handleKumaGuard(params) {
|
|
|
13417
13612
|
suggestion: "After tracing a flow, record it:\nkuma_memory({ action: 'arch_flow', content: 'domain: <Name> | hops: <file1> \u2192 <file2> \u2192 <file3>' })"
|
|
13418
13613
|
});
|
|
13419
13614
|
}
|
|
13615
|
+
if (readCalls >= 5 && recordingSummary.features === 0) {
|
|
13616
|
+
warnings.push({
|
|
13617
|
+
severity: "low",
|
|
13618
|
+
pattern: "explore-without-feature",
|
|
13619
|
+
message: `${readCalls} file reads but 0 features recorded. High-level module understanding helps future sessions.`,
|
|
13620
|
+
suggestion: "After exploring a module/feature, record it:\nkuma_memory({ action: 'feature', title: '<FeatureName>', content: 'description', scope: 'file1.ts,file2.ts' })"
|
|
13621
|
+
});
|
|
13622
|
+
}
|
|
13420
13623
|
}
|
|
13421
13624
|
if (check === "context") {
|
|
13422
13625
|
const snapshot = saveSnapshot(stats.goal);
|
|
@@ -13774,7 +13977,7 @@ async function handleVerify(params) {
|
|
|
13774
13977
|
} else if (recordingSummary.total > 0) {
|
|
13775
13978
|
recordingWarning = `
|
|
13776
13979
|
|
|
13777
|
-
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
|
|
13980
|
+
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save, ${recordingSummary.features} feature)`;
|
|
13778
13981
|
}
|
|
13779
13982
|
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
13780
13983
|
const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
|
|
@@ -13855,7 +14058,7 @@ function registerAllTools(server) {
|
|
|
13855
14058
|
"kuma_context",
|
|
13856
14059
|
"**Call FIRST every session.** Understand your project before making changes.\n\nWORKFLOW:\n\u2022 STEP 1 (ALWAYS): `init` \u2014 load project brief, restore session\n\u2022 STEP 3 (BEFORE edits): `research` \u2014 5-step pipeline (cache\u2192staleness\u2192graph\u2192impact\u2192decision)\n\u2022 STEP 8 (END of session): `changes` \u2014 review what you modified\n\nOther actions: impact (analyze changes), navigate (trace flow), health (score 0-100), digest (compact briefing), drift (detect staleness).",
|
|
13857
14060
|
{
|
|
13858
|
-
action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize", "digest", "drift", "progressive"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, sync=unified batch state, visualize=Mermaid knowledge graph diagram, digest=ultra-compact <500 token project briefing (Issue #18), drift=detect memory staleness & code drift (Issue #20), progressive=progressive context loading (Issue #25), health=project health score"),
|
|
14061
|
+
action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize", "digest", "drift", "progressive", "resume"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, sync=unified batch state, visualize=Mermaid knowledge graph diagram, digest=ultra-compact <500 token project briefing (Issue #18), drift=detect memory staleness & code drift (Issue #20), progressive=progressive context loading (Issue #25), health=project health score, resume=load previous session context (goal, progress, changes)"),
|
|
13859
14062
|
scope: z.string().optional().describe("Research scope for research/progressive action"),
|
|
13860
14063
|
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
|
|
13861
14064
|
goal: z.string().optional().describe("Current goal (for init/health)"),
|
|
@@ -13883,9 +14086,9 @@ function registerAllTools(server) {
|
|
|
13883
14086
|
);
|
|
13884
14087
|
server.tool(
|
|
13885
14088
|
"kuma_memory",
|
|
13886
|
-
"**Call after research/editing** (including research-only). Record what matters, skip what doesn't.\n\n\u{1F534} MUST RECORD \u2014 High Impact (saves agent time next session):\n\u2022 STEP 5: `research_save` \u2014 after exploring area (creates search cache).\n\u2022 STEP 6: `gotcha` \u2014 IMMEDIATELY when you discover bugs/quirks. No re-research.\n\u2022 STEP 7: `arch_flow` \u2014 AFTER tracing COMPLETE flow (max 5 core files).\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\n\u{1F7E2} SKIP using MCP (agent native tools are faster):\n\u2022 Function/class nodes \u2192 grep\n\u2022 Component/route nodes \u2192 glob or check directly\n\u2022 Import edges \u2192 read imports directly\n\u2022 Visual graph \u2192 for humans, not AI agents\n\nOther actions: session, heal, search, changes, benchmark, layers.",
|
|
14089
|
+
"**Call after research/editing** (including research-only). Record what matters, skip what doesn't.\n\n\u{1F534} MUST RECORD \u2014 High Impact (saves agent time next session):\n\u2022 STEP 5: `research_save` \u2014 after exploring area (creates search cache).\n\u2022 STEP 6: `gotcha` \u2014 IMMEDIATELY when you discover bugs/quirks. No re-research.\n\u2022 STEP 7: `arch_flow` \u2014 AFTER tracing COMPLETE flow (max 5 core files).\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\u2022 STEP 9: `feature` \u2014 WHEN identifying a high-level feature/module (e.g. Auth, Billing). Creates owns edges to files.\n\n\u{1F7E2} SKIP using MCP (agent native tools are faster):\n\u2022 Function/class nodes \u2192 grep\n\u2022 Component/route nodes \u2192 glob or check directly\n\u2022 Import edges \u2192 read imports directly\n\u2022 Visual graph \u2192 for humans, not AI agents\n\nOther actions: session, heal, search, changes, benchmark, layers.",
|
|
13887
14090
|
{
|
|
13888
|
-
action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "add_node", "delete_node", "clear", "feature"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search (now with task retrieval + impact analysis), changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules, arch_flow=Layer 2 architecture flow (now auto-creates affects edges), gotcha=Layer 3 known gotchas, layers=all 3 layers summary, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas, feature=record high-level feature with owns edges to files"),
|
|
14091
|
+
action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "add_node", "delete_node", "clear", "feature", "goal_progress"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search (now with task retrieval + impact analysis), changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules, arch_flow=Layer 2 architecture flow (now auto-creates affects edges), gotcha=Layer 3 known gotchas, layers=all 3 layers summary, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas, feature=record high-level feature with owns edges to files, goal_progress=update goal completion percentage"),
|
|
13889
14092
|
scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
|
|
13890
14093
|
query: z.string().optional().describe("Search query for search action"),
|
|
13891
14094
|
content: z.string().optional().describe("Content/notes for research_save / context"),
|
|
@@ -14056,6 +14259,14 @@ If no flags specified, you'll be prompted to select files interactively.
|
|
|
14056
14259
|
}
|
|
14057
14260
|
async function main() {
|
|
14058
14261
|
const args = process.argv.slice(2);
|
|
14262
|
+
if (args[0] === "--version" || args[0] === "-v" || args[0] === "version") {
|
|
14263
|
+
console.log(`\u{1F43B} Kuma v${SERVER_VERSION}`);
|
|
14264
|
+
process.exit(0);
|
|
14265
|
+
}
|
|
14266
|
+
if (args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
|
|
14267
|
+
printHelp();
|
|
14268
|
+
process.exit(0);
|
|
14269
|
+
}
|
|
14059
14270
|
if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
|
|
14060
14271
|
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
14061
14272
|
console.error("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.33",
|
|
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();
|
|
@@ -143,26 +162,26 @@ async function getDashboardData() {
|
|
|
143
162
|
)
|
|
144
163
|
};
|
|
145
164
|
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`
|
|
165
|
+
`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
166
|
);
|
|
148
167
|
const edges = jsonRows(
|
|
149
168
|
`SELECT json_group_array(json_object('source',source_id,'target',target_id,'relation',type,'weight',weight)) FROM edges`
|
|
150
169
|
);
|
|
151
170
|
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
|
|
171
|
+
`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
172
|
);
|
|
154
173
|
const features = jsonRows(
|
|
155
|
-
`SELECT json_group_array(json_object('id',id,'name',name,'metadata',COALESCE(metadata,'{}'))) FROM nodes WHERE type = 'feature' ORDER BY
|
|
174
|
+
`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)`
|
|
156
175
|
);
|
|
157
176
|
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`
|
|
177
|
+
`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 (SELECT * FROM health_snapshots ORDER BY created_at DESC LIMIT 10)`
|
|
159
178
|
);
|
|
160
179
|
const sessionMetrics = readSessionMetrics(dbPath);
|
|
161
180
|
const verifTotal = first(`SELECT COUNT(*) as c FROM verifications`, "c");
|
|
162
181
|
const verifPassed = first(`SELECT COUNT(*) as c FROM verifications WHERE passed = 1`, "c");
|
|
163
182
|
const recentSessions = run(
|
|
164
183
|
`SELECT started_at, COALESCE(goal,'') as goal, tool_calls, edits, rollbacks, failures, safety_score
|
|
165
|
-
FROM sessions ORDER BY started_at DESC LIMIT 8`
|
|
184
|
+
FROM (SELECT * FROM sessions ORDER BY started_at DESC LIMIT 8)`
|
|
166
185
|
).map((r) => ({
|
|
167
186
|
startedAt: Number(r.started_at ?? 0),
|
|
168
187
|
goal: r.goal || "",
|
|
@@ -175,11 +194,10 @@ async function getDashboardData() {
|
|
|
175
194
|
const efficiency = {
|
|
176
195
|
sessions: first(`SELECT COUNT(*) as c FROM sessions`, "c"),
|
|
177
196
|
toolCalls: first(`SELECT COUNT(*) as c FROM tool_calls`, "c"),
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
gotchas: first(`SELECT COUNT(*) as c FROM known_gotchas`, "c"),
|
|
198
|
+
archFlows: sessionMetrics.recordings.archFlows,
|
|
199
|
+
decisions: first(`SELECT COUNT(*) as c FROM (SELECT id FROM decision_log UNION SELECT id FROM nodes WHERE type = 'decision')`, "c"),
|
|
181
200
|
researchCacheScopes: first(`SELECT COUNT(*) as c FROM research_cache`, "c"),
|
|
182
|
-
decisions: first(`SELECT COUNT(*) as c FROM decision_log`, "c"),
|
|
183
201
|
verifications: verifTotal,
|
|
184
202
|
verificationPassRate: verifTotal > 0 ? Math.round(verifPassed / verifTotal * 100) : null,
|
|
185
203
|
metrics: sessionMetrics.metrics,
|