@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/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("research_save");
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.32",
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
- 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();
@@ -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 severity DESC`
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 name`
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
- 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"),
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,