@plumpslabs/kuma 2.3.5 → 2.3.7

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.
@@ -320,7 +320,7 @@ No unresolved issues.`;
320
320
  */
321
321
  async autoTrackToDb(toolName, params, timestamp) {
322
322
  try {
323
- const { getDb, saveDb } = await import("./kumaDb-FQ3YKJ6K.js");
323
+ const { getDb, saveDb } = await import("./kumaDb-DJUDLYBJ.js");
324
324
  const db = await getDb();
325
325
  db.run(
326
326
  `INSERT INTO tool_calls (tool_name, params, success, duration_ms, created_at) VALUES (?, ?, 1, 0, ?)`,
@@ -466,14 +466,14 @@ No unresolved issues.`;
466
466
  const memoriesDir = this.memoriesDir();
467
467
  if (fs.existsSync(memoriesDir)) {
468
468
  try {
469
- const files = fs.readdirSync(memoriesDir);
469
+ const files = fs.readdirSync(memoriesDir).filter((f) => f.endsWith(".md")).slice(0, 3);
470
470
  for (const file of files) {
471
- if (!file.endsWith(".md")) continue;
472
471
  const filePath = path.join(memoriesDir, file);
473
472
  try {
474
473
  const content = fs.readFileSync(filePath, "utf-8");
475
474
  const lines = content.split("\n");
476
- for (let i = 0; i < lines.length; i++) {
475
+ const maxLinesPerFile = 500;
476
+ for (let i = 0; i < Math.min(lines.length, maxLinesPerFile); i++) {
477
477
  if (lines[i].toLowerCase().includes(q)) {
478
478
  const topicName = file.replace(/\.md$/, "");
479
479
  results.push({
@@ -571,10 +571,18 @@ No unresolved issues.`;
571
571
  if (recentCalls.length < 6) return { isLooping: false };
572
572
  const toolCounts = /* @__PURE__ */ new Map();
573
573
  for (const call of recentCalls) {
574
- toolCounts.set(call.toolName, (toolCounts.get(call.toolName) ?? 0) + 1);
574
+ const existing = toolCounts.get(call.toolName);
575
+ const action = call.params.action || "";
576
+ if (existing) {
577
+ existing.count++;
578
+ existing.actions.add(action);
579
+ } else {
580
+ toolCounts.set(call.toolName, { count: 1, actions: /* @__PURE__ */ new Set([action]) });
581
+ }
575
582
  }
576
- for (const [toolName, count] of toolCounts) {
583
+ for (const [toolName, { count, actions }] of toolCounts) {
577
584
  if (count >= 4) {
585
+ if (actions.size >= 3) continue;
578
586
  return {
579
587
  isLooping: true,
580
588
  toolName,
@@ -597,7 +605,7 @@ No unresolved issues.`;
597
605
  this.addModifiedFile(filePath);
598
606
  }
599
607
  try {
600
- const { recordChange } = await import("./kumaDb-FQ3YKJ6K.js");
608
+ const { recordChange } = await import("./kumaDb-DJUDLYBJ.js");
601
609
  const gitHash = this.getGitHead();
602
610
  await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
603
611
  } catch {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-PH4JKL6T.js";
3
+ } from "./chunk-BI7KD3SG.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -36,18 +36,29 @@ async function initDb() {
36
36
  dbInstance = db;
37
37
  return db;
38
38
  }
39
+ var _saveTimeout = null;
40
+ var _pendingDb = null;
41
+ var SAVE_DEBOUNCE_MS = 100;
39
42
  function saveDb(db) {
40
43
  const d = db ?? dbInstance;
41
44
  if (!d) return;
42
- try {
43
- const kumaDir = getKumaDir();
44
- const dbPath = path.join(kumaDir, DB_FILENAME);
45
- const data = d.export();
46
- const buffer = Buffer.from(data);
47
- fs.writeFileSync(dbPath, buffer);
48
- } catch (err) {
49
- console.error(`[KumaDB] Failed to save database: ${err}`);
50
- }
45
+ _pendingDb = d;
46
+ if (_saveTimeout) return;
47
+ _saveTimeout = setTimeout(() => {
48
+ _saveTimeout = null;
49
+ const targetDb = _pendingDb;
50
+ _pendingDb = null;
51
+ if (!targetDb) return;
52
+ try {
53
+ const kumaDir = getKumaDir();
54
+ const dbPath = path.join(kumaDir, DB_FILENAME);
55
+ const data = targetDb.export();
56
+ const buffer = Buffer.from(data);
57
+ fs.writeFileSync(dbPath, buffer);
58
+ } catch (err) {
59
+ console.error(`[KumaDB] Failed to save database: ${err}`);
60
+ }
61
+ }, SAVE_DEBOUNCE_MS);
51
62
  }
52
63
  function createSchema(db) {
53
64
  db.run(`CREATE TABLE IF NOT EXISTS nodes (
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getDb,
3
3
  saveDb
4
- } from "./chunk-2NBESJTH.js";
4
+ } from "./chunk-GDNAWLHF.js";
5
5
  import {
6
6
  getProjectRoot
7
7
  } from "./chunk-E2KFPEBT.js";
@@ -458,7 +458,7 @@ The two nodes may not be connected in the knowledge graph yet. Use more tools to
458
458
  }
459
459
  async function buildFromSessionMemory() {
460
460
  try {
461
- const { sessionMemory } = await import("./sessionMemory-PGYVS3BK.js");
461
+ const { sessionMemory } = await import("./sessionMemory-HBBXUSIP.js");
462
462
  const toolCalls = sessionMemory.getToolCallHistory(50);
463
463
  let edgeCount = 0;
464
464
  for (const call of toolCalls) {
@@ -586,8 +586,8 @@ async function codebaseSearchFallback(query, limit = 20) {
586
586
  return lines.join("\n");
587
587
  } catch {
588
588
  try {
589
- let walk2 = function(dir) {
590
- if (results.length >= limit) return;
589
+ let walk2 = function(dir, depth = 0) {
590
+ if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
591
591
  let entries = [];
592
592
  try {
593
593
  entries = fs2.readdirSync(dir);
@@ -600,7 +600,7 @@ async function codebaseSearchFallback(query, limit = 20) {
600
600
  try {
601
601
  const stat = fs2.statSync(full);
602
602
  if (stat.isDirectory()) {
603
- walk2(full);
603
+ walk2(full, depth + 1);
604
604
  } else if (entry.toLowerCase().includes(query.toLowerCase())) {
605
605
  results.push(path2.relative(root, full));
606
606
  }
@@ -613,7 +613,8 @@ async function codebaseSearchFallback(query, limit = 20) {
613
613
  const path2 = await import("path");
614
614
  const root = process.cwd();
615
615
  const results = [];
616
- walk2(root);
616
+ const MAX_WALK_DEPTH = 8;
617
+ walk2(root, 0);
617
618
  if (results.length === 0) {
618
619
  return `\u{1F50D} **Codebase Search** \u2014 No results for "${query}". Try a different search term.`;
619
620
  }
@@ -744,7 +745,7 @@ async function codebaseImpactFallback(target) {
744
745
  const root = process.cwd();
745
746
  const grepResult = execSync2(
746
747
  `grep -rn --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" --include="*.json" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
747
- { cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 1024 * 1024 }
748
+ { cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
748
749
  ).trim();
749
750
  const files = grepResult ? grepResult.split("\n").filter(Boolean).filter((f) => !f.startsWith("./")) : [];
750
751
  if (files.length === 0) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-PH4JKL6T.js";
3
+ } from "./chunk-BI7KD3SG.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -82,7 +82,7 @@ function recordDecision(decision) {
82
82
  }
83
83
  async function recordDecisionToGraph(decision) {
84
84
  try {
85
- const { upsertNode, addEdge } = await import("./kumaGraph-ABVEVF4M.js");
85
+ const { upsertNode, addEdge } = await import("./kumaGraph-35TAIBWD.js");
86
86
  const decisionId = `decision::${decision.title.replace(/[^a-zA-Z0-9_\-\s]/g, "").trim().replace(/\s+/g, "-")}`;
87
87
  await upsertNode({
88
88
  id: decisionId,
package/dist/index.js CHANGED
@@ -14,13 +14,22 @@ import {
14
14
  getGraphStats,
15
15
  searchGraph,
16
16
  traceFlow
17
- } from "./chunk-LLAPHR5W.js";
17
+ } from "./chunk-K64NSHBR.js";
18
+ import {
19
+ formatDecisionTemplate,
20
+ getProactiveMemories,
21
+ recordDecision,
22
+ scoreMemoryRelevance
23
+ } from "./chunk-X5TPBDKO.js";
18
24
  import {
19
25
  buildDriftMessages,
20
26
  getGitDiffStat,
21
27
  getSessionStats,
22
28
  getUnresolvedCount
23
- } from "./chunk-ZIFLGJ5R.js";
29
+ } from "./chunk-FOQQ2CSL.js";
30
+ import {
31
+ sessionMemory
32
+ } from "./chunk-BI7KD3SG.js";
24
33
  import {
25
34
  addContextNote,
26
35
  addSecurityFinding,
@@ -46,16 +55,7 @@ import {
46
55
  saveResearchCache,
47
56
  updateDecisionStatus,
48
57
  updateTodoStatus
49
- } from "./chunk-2NBESJTH.js";
50
- import {
51
- formatDecisionTemplate,
52
- getProactiveMemories,
53
- recordDecision,
54
- scoreMemoryRelevance
55
- } from "./chunk-6QX4LBHV.js";
56
- import {
57
- sessionMemory
58
- } from "./chunk-PH4JKL6T.js";
58
+ } from "./chunk-GDNAWLHF.js";
59
59
  import {
60
60
  getProjectRoot,
61
61
  validateFilePath
@@ -87,7 +87,7 @@ var CONTEXT_ALIASES = {
87
87
  "refactor": "impact",
88
88
  // Navigate synonyms
89
89
  "trace": "navigate",
90
- "flow": "navigate",
90
+ "flow-trace": "navigate",
91
91
  "navigate": "navigate",
92
92
  // Init synonyms
93
93
  "init": "init",
@@ -111,7 +111,20 @@ var CONTEXT_ALIASES = {
111
111
  // Researches synonyms
112
112
  "researches": "researches",
113
113
  "research-list": "researches",
114
- "list-research": "researches"
114
+ "list-research": "researches",
115
+ // Sync/Batch synonyms (Issue #12)
116
+ "sync": "sync",
117
+ "batch": "sync",
118
+ "kuma_sync": "sync",
119
+ "unified": "sync",
120
+ "state": "sync",
121
+ // Visualize synonyms (Issue #16)
122
+ "visualize": "visualize",
123
+ "graph": "visualize",
124
+ "diagram": "visualize",
125
+ "flow": "visualize",
126
+ "viz": "visualize",
127
+ "kuma_visualize": "visualize"
115
128
  };
116
129
  async function handleContext(params) {
117
130
  const rawAction = params.action || "init";
@@ -134,8 +147,12 @@ async function handleContext(params) {
134
147
  return handleResearches(params);
135
148
  case "health":
136
149
  return handleHealth(params);
150
+ case "sync":
151
+ return handleSync(params);
152
+ case "visualize":
153
+ return handleVisualize(params);
137
154
  default:
138
- return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health`;
155
+ return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize`;
139
156
  }
140
157
  }
141
158
  async function handleInit(_params) {
@@ -196,8 +213,8 @@ async function handleInit(_params) {
196
213
  lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
197
214
  lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
198
215
  try {
199
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-VYT6OMVG.js");
200
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-FQ3YKJ6K.js");
216
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
217
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-DJUDLYBJ.js");
201
218
  const score = await computeSafetyScore(_params.goal);
202
219
  const checksStr = JSON.stringify(score.checks);
203
220
  await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
@@ -370,8 +387,8 @@ async function handleResearches(_params) {
370
387
  async function handleHealth(_params) {
371
388
  sessionMemory.recordToolCall("kuma_context_health", {});
372
389
  try {
373
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-VYT6OMVG.js");
374
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-FQ3YKJ6K.js");
390
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
391
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-DJUDLYBJ.js");
375
392
  const score = await computeSafetyScore();
376
393
  const checksStr = JSON.stringify(score.checks);
377
394
  await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
@@ -380,6 +397,64 @@ async function handleHealth(_params) {
380
397
  return `Error computing health: ${err}`;
381
398
  }
382
399
  }
400
+ async function handleSync(params) {
401
+ sessionMemory.setGoal(params.goal || "Sync session");
402
+ sessionMemory.recordToolCall("kuma_context_sync", { goal: params.goal });
403
+ const lines = [
404
+ "\u{1F504} **Kuma Sync \u2014 Unified State**",
405
+ `\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`,
406
+ "",
407
+ `\u{1F4C1} Project: ${getProjectRoot().split("/").pop() || "unknown"}`,
408
+ `\u{1F550} Session: ${(/* @__PURE__ */ new Date()).toISOString()}`,
409
+ ""
410
+ ];
411
+ const summary = sessionMemory.getSummary();
412
+ lines.push("**Session State**");
413
+ lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
414
+ lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
415
+ lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
416
+ lines.push("");
417
+ try {
418
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
419
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-DJUDLYBJ.js");
420
+ const score = await computeSafetyScore(params.goal);
421
+ const checksStr = JSON.stringify(score.checks);
422
+ await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
423
+ lines.push("**Health Score**");
424
+ lines.push(formatSafetyScore(score));
425
+ } catch {
426
+ lines.push("**Health Score**: \u26A0\uFE0F Could not compute");
427
+ }
428
+ lines.push("");
429
+ try {
430
+ const { getGraphStats: getGraphStats2 } = await import("./kumaGraph-35TAIBWD.js");
431
+ lines.push("**Knowledge Graph**");
432
+ lines.push(await getGraphStats2());
433
+ } catch {
434
+ }
435
+ lines.push("");
436
+ try {
437
+ const { getProactiveMemories: getProactiveMemories2 } = await import("./kumaMemory-5SR3TGRL.js");
438
+ const memories = getProactiveMemories2();
439
+ if (memories) {
440
+ lines.push("**Relevant Memories**");
441
+ lines.push(memories);
442
+ lines.push("");
443
+ }
444
+ } catch {
445
+ }
446
+ lines.push("\u{1F4A1} Sync complete \u2014 all state captured in a single roundtrip.");
447
+ return lines.join("\n");
448
+ }
449
+ async function handleVisualize(params) {
450
+ const scope = params.scope;
451
+ const { generateVisualizeReport } = await import("./kumaVisualize-ODCWFSUY.js");
452
+ return await generateVisualizeReport({
453
+ scope,
454
+ type: "flowchart",
455
+ maxNodes: 40
456
+ });
457
+ }
383
458
  function computeProjectHash(scope) {
384
459
  try {
385
460
  const root = getProjectRoot();
@@ -501,7 +576,7 @@ async function handleDecision(params) {
501
576
  case "template":
502
577
  return formatDecisionTemplate();
503
578
  case "suggest": {
504
- const { shouldRecordDecision } = await import("./kumaMemory-DDZQXMFZ.js");
579
+ const { shouldRecordDecision } = await import("./kumaMemory-5SR3TGRL.js");
505
580
  const check = shouldRecordDecision();
506
581
  return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
507
582
  Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
@@ -579,8 +654,17 @@ async function handleSearch(params) {
579
654
  if (!query) return "\u26A0\uFE0F query or scope parameter required.";
580
655
  const limit = params.limit || 20;
581
656
  const memResults = sessionMemory.searchMemory(query, limit);
582
- const { searchGraph: searchGraph2 } = await import("./kumaGraph-ABVEVF4M.js");
657
+ const { searchGraph: searchGraph2 } = await import("./kumaGraph-35TAIBWD.js");
583
658
  const graphResults = await searchGraph2(query, Math.min(limit, 10));
659
+ let hybridResults = "";
660
+ try {
661
+ const { hybridSearch, formatHybridResults } = await import("./kumaSearch-LG2OYEJY.js");
662
+ const semanticResults = await hybridSearch(query, 8);
663
+ if (semanticResults.length > 0) {
664
+ hybridResults = "\n" + formatHybridResults(query, semanticResults);
665
+ }
666
+ } catch {
667
+ }
584
668
  const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
585
669
  \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
586
670
  `];
@@ -590,6 +674,10 @@ async function handleSearch(params) {
590
674
  lines.push("");
591
675
  }
592
676
  lines.push("**Knowledge Graph:**\n" + graphResults);
677
+ if (hybridResults) {
678
+ lines.push("");
679
+ lines.push(hybridResults);
680
+ }
593
681
  return lines.join("\n");
594
682
  }
595
683
  async function handleChanges2(params) {
@@ -654,7 +742,7 @@ async function handleDecisionLog(params) {
654
742
  }
655
743
  async function handleMine(params) {
656
744
  sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
657
- const { mineHistoricalDecisions } = await import("./kumaMiner-GP6BITKV.js");
745
+ const { mineHistoricalDecisions } = await import("./kumaMiner-P5KCUSF7.js");
658
746
  return await mineHistoricalDecisions({
659
747
  scope: params.scope,
660
748
  since: typeof params.since === "string" ? params.since : void 0,
@@ -1791,7 +1879,7 @@ async function handleLock(params) {
1791
1879
  }
1792
1880
  async function handleHealth2(_params) {
1793
1881
  try {
1794
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-VYT6OMVG.js");
1882
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
1795
1883
  const score = await computeSafetyScore();
1796
1884
  await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
1797
1885
  return formatSafetyScore(score);
@@ -1905,7 +1993,7 @@ async function handleVerify(params) {
1905
1993
  }
1906
1994
  _lastVerifyCall = now;
1907
1995
  sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
1908
- const { runAutoVerification } = await import("./kumaVerifier-7TFWLEPG.js");
1996
+ const { runAutoVerification } = await import("./kumaVerifier-LFRNIGSL.js");
1909
1997
  return await runAutoVerification({
1910
1998
  scope: params.scope || params.filePath,
1911
1999
  force: params.force,
@@ -1919,7 +2007,7 @@ function registerAllTools(server) {
1919
2007
  "kuma_context",
1920
2008
  "**Call FIRST every session.** Understand your project before making changes. Actions: init (load project brief), research (5-step pipeline: cache\u2192staleness\u2192graph\u2192impact\u2192decision), impact (analyze change effects), navigate (trace code flow), changes (view change log), health (project health score 0-100). RESEARCH IS REQUIRED before editing unfamiliar code.",
1921
2009
  {
1922
- action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches"]).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, health=project health score"),
2010
+ action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize"]).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, health=project health score"),
1923
2011
  scope: z.string().optional().describe("Research scope for research action (e.g. 'auth', 'database', 'api')"),
1924
2012
  target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
1925
2013
  goal: z.string().optional().describe("Current goal (for init/health)"),
@@ -2104,7 +2192,7 @@ async function main() {
2104
2192
  console.error("");
2105
2193
  let killedCount = 0;
2106
2194
  try {
2107
- const { getRunningVerificationPid } = await import("./kumaVerifier-7TFWLEPG.js");
2195
+ const { getRunningVerificationPid } = await import("./kumaVerifier-LFRNIGSL.js");
2108
2196
  const pid = getRunningVerificationPid();
2109
2197
  if (pid) {
2110
2198
  try {
@@ -2196,6 +2284,9 @@ async function main() {
2196
2284
  console.error("");
2197
2285
  process.exit(0);
2198
2286
  }
2287
+ if (args[0] === "--hook") {
2288
+ process.exit(0);
2289
+ }
2199
2290
  if (args[0] === "init") {
2200
2291
  const flags = args.slice(1);
2201
2292
  if (flags.includes("--help") || flags.includes("-h")) {
@@ -2343,7 +2434,7 @@ async function main() {
2343
2434
  console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
2344
2435
  }
2345
2436
  try {
2346
- const { buildFromSessionMemory } = await import("./kumaGraph-ABVEVF4M.js");
2437
+ const { buildFromSessionMemory } = await import("./kumaGraph-35TAIBWD.js");
2347
2438
  const edgeCount = await buildFromSessionMemory();
2348
2439
  if (edgeCount > 0) {
2349
2440
  console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
@@ -2363,7 +2454,7 @@ async function main() {
2363
2454
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
2364
2455
  }
2365
2456
  try {
2366
- const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-FQ3YKJ6K.js");
2457
+ const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-DJUDLYBJ.js");
2367
2458
  const db = await getDb2();
2368
2459
  db.run(
2369
2460
  `INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
@@ -2373,6 +2464,44 @@ async function main() {
2373
2464
  } catch (err) {
2374
2465
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
2375
2466
  }
2467
+ try {
2468
+ const fs7 = await import("fs");
2469
+ const path7 = await import("path");
2470
+ const hooksDir = path7.resolve(process.cwd(), ".kuma", "hooks");
2471
+ if (!fs7.existsSync(hooksDir)) {
2472
+ fs7.mkdirSync(hooksDir, { recursive: true });
2473
+ const hookContent = `#!/bin/bash
2474
+ # Kuma auto-capture hook (post-commit)
2475
+ # Auto-updates knowledge graph after git commits
2476
+ if command -v npx &> /dev/null; then
2477
+ npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
2478
+ fi
2479
+ `;
2480
+ const gitHooksDir = path7.resolve(process.cwd(), ".git", "hooks");
2481
+ if (fs7.existsSync(gitHooksDir)) {
2482
+ const postCommitPath = path7.join(gitHooksDir, "post-commit");
2483
+ if (!fs7.existsSync(postCommitPath)) {
2484
+ fs7.writeFileSync(postCommitPath, hookContent, "utf-8");
2485
+ try {
2486
+ fs7.chmodSync(postCommitPath, 493);
2487
+ } catch {
2488
+ }
2489
+ console.error(`[${SERVER_NAME}] \u2705 Created .git/hooks/post-commit for auto-capture`);
2490
+ }
2491
+ }
2492
+ }
2493
+ } catch (err) {
2494
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Auto-capture hook setup: ${err}`);
2495
+ }
2496
+ try {
2497
+ const { buildSearchVectors } = await import("./kumaSearch-LG2OYEJY.js");
2498
+ const vectors = await buildSearchVectors();
2499
+ if (vectors.length > 0) {
2500
+ console.error(`[${SERVER_NAME}] \u2705 Search vector cache pre-warmed (${vectors.length} documents)`);
2501
+ }
2502
+ } catch (err) {
2503
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Search vector cache: ${err}`);
2504
+ }
2376
2505
  console.error(`[${SERVER_NAME}] \u2705 Cold start bootstrap complete`);
2377
2506
  } catch (err) {
2378
2507
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Cold start bootstrap error: ${err}`);
@@ -28,7 +28,7 @@ import {
28
28
  saveVerification,
29
29
  updateDecisionStatus,
30
30
  updateTodoStatus
31
- } from "./chunk-2NBESJTH.js";
31
+ } from "./chunk-GDNAWLHF.js";
32
32
  import "./chunk-E2KFPEBT.js";
33
33
  export {
34
34
  addContextNote,
@@ -14,8 +14,8 @@ import {
14
14
  searchGraph,
15
15
  traceFlow,
16
16
  upsertNode
17
- } from "./chunk-LLAPHR5W.js";
18
- import "./chunk-2NBESJTH.js";
17
+ } from "./chunk-K64NSHBR.js";
18
+ import "./chunk-GDNAWLHF.js";
19
19
  import "./chunk-E2KFPEBT.js";
20
20
  export {
21
21
  addEdge,
@@ -4,8 +4,8 @@ import {
4
4
  recordDecision,
5
5
  scoreMemoryRelevance,
6
6
  shouldRecordDecision
7
- } from "./chunk-6QX4LBHV.js";
8
- import "./chunk-PH4JKL6T.js";
7
+ } from "./chunk-X5TPBDKO.js";
8
+ import "./chunk-BI7KD3SG.js";
9
9
  import "./chunk-E2KFPEBT.js";
10
10
  export {
11
11
  formatDecisionTemplate,
@@ -1,10 +1,10 @@
1
- import {
2
- recordDecisionLog
3
- } from "./chunk-2NBESJTH.js";
4
1
  import {
5
2
  recordDecision
6
- } from "./chunk-6QX4LBHV.js";
7
- import "./chunk-PH4JKL6T.js";
3
+ } from "./chunk-X5TPBDKO.js";
4
+ import "./chunk-BI7KD3SG.js";
5
+ import {
6
+ recordDecisionLog
7
+ } from "./chunk-GDNAWLHF.js";
8
8
  import "./chunk-E2KFPEBT.js";
9
9
 
10
10
  // src/engine/kumaMiner.ts
@@ -19,9 +19,10 @@ async function mineHistoricalDecisions(options = {}) {
19
19
  try {
20
20
  const sinceFlag = options.since ? `--since="${options.since}"` : `--since="1 year"`;
21
21
  const gitCmd = `git log ${sinceFlag} -n 100 --pretty=format:"%h|%an|%ad|%s" --date=short`;
22
- const gitOutput = execSync(gitCmd, { cwd: root, encoding: "utf-8" });
22
+ const gitOutput = execSync(gitCmd, { cwd: root, encoding: "utf-8", timeout: 15e3 });
23
23
  const keywords = ["fix", "revert", "hack", "workaround", "urgent", "hotfix", "don't touch", "temporary", "deprecated", "workaround"];
24
24
  const lines = gitOutput.split("\n").filter(Boolean);
25
+ const matchingHashes = [];
25
26
  for (const line of lines) {
26
27
  const parts = line.split("|");
27
28
  if (parts.length < 4) continue;
@@ -29,9 +30,42 @@ async function mineHistoricalDecisions(options = {}) {
29
30
  const lowerSubj = subject.toLowerCase();
30
31
  const matchedKeyword = keywords.find((kw) => lowerSubj.includes(kw));
31
32
  if (matchedKeyword) {
32
- let changedFiles = "";
33
+ matchingHashes.push({ hash, author, date, subject, keyword: matchedKeyword });
34
+ if (matchingHashes.length >= limit) break;
35
+ }
36
+ }
37
+ let changedFilesMap = /* @__PURE__ */ new Map();
38
+ if (matchingHashes.length > 0) {
39
+ try {
40
+ const hashesStr = matchingHashes.map((h) => h.hash).join(" ");
41
+ const batchOutput = execSync(
42
+ `git show --name-only --oneline ${hashesStr} 2>/dev/null | head -200`,
43
+ { cwd: root, encoding: "utf-8", timeout: 1e4, maxBuffer: 256 * 1024 }
44
+ );
45
+ let currentHash = "";
46
+ const files = [];
47
+ for (const line of batchOutput.split("\n")) {
48
+ if (matchingHashes.find((h) => line.startsWith(h.hash))) {
49
+ if (currentHash && files.length > 0) {
50
+ changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
51
+ }
52
+ currentHash = line.split(" ")[0];
53
+ files.length = 0;
54
+ } else if (line.trim() && !line.startsWith("diff")) {
55
+ files.push(line.trim());
56
+ }
57
+ }
58
+ if (currentHash && files.length > 0) {
59
+ changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
60
+ }
61
+ } catch {
62
+ }
63
+ }
64
+ for (const { hash, author, date, subject, keyword: matchedKeyword } of matchingHashes) {
65
+ let changedFiles = changedFilesMap.get(hash) || "";
66
+ if (!changedFiles) {
33
67
  try {
34
- changedFiles = execSync(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8" }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
68
+ changedFiles = execSync(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8", timeout: 5e3 }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
35
69
  } catch {
36
70
  }
37
71
  candidates.push({
@@ -0,0 +1,321 @@
1
+ import {
2
+ getDb
3
+ } from "./chunk-GDNAWLHF.js";
4
+ import {
5
+ getProjectRoot
6
+ } from "./chunk-E2KFPEBT.js";
7
+
8
+ // src/engine/kumaSearch.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ var SYNONYM_MAP = {
12
+ timeout: ["timeout", "duration", "expiry", "ttl", "deadline", "latency"],
13
+ duration: ["duration", "timeout", "length", "period", "interval"],
14
+ delay: ["delay", "wait", "latency", "defer", "throttle"],
15
+ latency: ["latency", "delay", "response time", "speed", "performance"],
16
+ error: ["error", "failure", "exception", "bug", "fault", "crash"],
17
+ crash: ["crash", "panic", "oom", "out of memory", "segfault"],
18
+ fail: ["fail", "error", "break", "regression", "broken"],
19
+ auth: ["auth", "authentication", "login", "oauth", "session", "token", "credential"],
20
+ login: ["login", "signin", "auth", "authenticate"],
21
+ token: ["token", "jwt", "session", "api key", "secret"],
22
+ permission: ["permission", "access", "role", "rbac", "authorization", "acl"],
23
+ database: ["database", "db", "storage", "persistence", "sql", "nosql"],
24
+ query: ["query", "search", "fetch", "select", "lookup"],
25
+ index: ["index", "key", "lookup", "search"],
26
+ cache: ["cache", "memcached", "redis", "buffer", "temporary storage"],
27
+ api: ["api", "endpoint", "route", "rest", "graphql", "service"],
28
+ route: ["route", "endpoint", "path", "handler"],
29
+ request: ["request", "http", "call", "fetch", "invocation"],
30
+ response: ["response", "reply", "result", "output"],
31
+ performance: ["performance", "speed", "latency", "throughput", "efficiency"],
32
+ memory: ["memory", "ram", "heap", "allocation", "leak"],
33
+ cpu: ["cpu", "processor", "compute", "thread", "worker"],
34
+ architecture: ["architecture", "design", "pattern", "structure", "system"],
35
+ service: ["service", "microservice", "module", "component", "layer"],
36
+ config: ["config", "configuration", "setting", "env", "environment"],
37
+ deploy: ["deploy", "release", "rollout", "publish", "ship"],
38
+ build: ["build", "compile", "bundle", "transpile", "package"],
39
+ test: ["test", "spec", "unit test", "integration", "e2e"],
40
+ lint: ["lint", "format", "style", "prettier", "eslint"],
41
+ redis: ["redis", "cache", "session store", "message queue"],
42
+ postgres: ["postgres", "postgresql", "psql", "relational db", "sql"],
43
+ docker: ["docker", "container", "image", "dockerfile", "compose"],
44
+ typescript: ["typescript", "ts", "type system", "compiler"],
45
+ prisma: ["prisma", "orm", "database client", "schema"],
46
+ create: ["create", "add", "new", "make", "generate"],
47
+ update: ["update", "modify", "change", "edit", "patch"],
48
+ delete: ["delete", "remove", "drop", "clear", "purge"]
49
+ };
50
+ function expandQueryTerms(query) {
51
+ const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
52
+ const expanded = /* @__PURE__ */ new Map();
53
+ for (const term of terms) {
54
+ const sources = /* @__PURE__ */ new Set();
55
+ sources.add(term);
56
+ if (SYNONYM_MAP[term]) {
57
+ for (const syn of SYNONYM_MAP[term]) {
58
+ sources.add(syn);
59
+ }
60
+ }
61
+ for (const [key, synonyms] of Object.entries(SYNONYM_MAP)) {
62
+ if (synonyms.includes(term)) {
63
+ sources.add(key);
64
+ }
65
+ }
66
+ expanded.set(term, sources);
67
+ }
68
+ return expanded;
69
+ }
70
+ var _vectorCache = null;
71
+ var VECTOR_CACHE_TTL = 3e5;
72
+ async function buildSearchVectors() {
73
+ if (_vectorCache && Date.now() - _vectorCache.builtAt < VECTOR_CACHE_TTL) {
74
+ return _vectorCache.vectors;
75
+ }
76
+ const vectors = [];
77
+ try {
78
+ const db = await getDb();
79
+ const stmt = db.prepare("SELECT id, name, metadata, file_path FROM nodes ORDER BY updated_at DESC LIMIT 500");
80
+ while (stmt.step()) {
81
+ const row = stmt.getAsObject();
82
+ const name = row.name || "";
83
+ const metaStr = row.metadata || "{}";
84
+ let metaText = "";
85
+ try {
86
+ const meta = JSON.parse(metaStr);
87
+ metaText = Object.values(meta).join(" ");
88
+ } catch {
89
+ }
90
+ const filePath = row.file_path || "";
91
+ const nodeText = `${name} ${metaText} ${filePath}`.toLowerCase();
92
+ const rawTerms = extractTerms(nodeText);
93
+ const termCounts = /* @__PURE__ */ new Map();
94
+ for (const t of rawTerms) {
95
+ termCounts.set(t, (termCounts.get(t) || 0) + 1);
96
+ }
97
+ vectors.push({
98
+ terms: termCounts,
99
+ source: name || row.id,
100
+ sourceType: "graph"
101
+ });
102
+ }
103
+ stmt.free();
104
+ } catch {
105
+ }
106
+ try {
107
+ const root = getProjectRoot();
108
+ const memDir = path.join(root, ".kuma", "memories");
109
+ if (fs.existsSync(memDir)) {
110
+ const files = fs.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, 10);
111
+ for (const file of files) {
112
+ try {
113
+ const content = fs.readFileSync(path.join(memDir, file), "utf-8");
114
+ const text = content.toLowerCase();
115
+ const rawTerms = extractTerms(text);
116
+ const termCounts = /* @__PURE__ */ new Map();
117
+ for (const t of rawTerms) {
118
+ termCounts.set(t, (termCounts.get(t) || 0) + 1);
119
+ }
120
+ vectors.push({
121
+ terms: termCounts,
122
+ source: file.replace(/\.md$/, ""),
123
+ sourceType: "memory"
124
+ });
125
+ } catch {
126
+ }
127
+ }
128
+ }
129
+ } catch {
130
+ }
131
+ try {
132
+ const db = await getDb();
133
+ const stmt = db.prepare("SELECT scope, record FROM research_cache ORDER BY updated_at DESC LIMIT 50");
134
+ while (stmt.step()) {
135
+ const row = stmt.getAsObject();
136
+ const scope = row.scope || "";
137
+ const record = row.record || "";
138
+ const text = `${scope} ${record}`.toLowerCase();
139
+ const rawTerms = extractTerms(text);
140
+ const termCounts = /* @__PURE__ */ new Map();
141
+ for (const t of rawTerms) {
142
+ termCounts.set(t, (termCounts.get(t) || 0) + 1);
143
+ }
144
+ vectors.push({
145
+ terms: termCounts,
146
+ source: scope,
147
+ sourceType: "research"
148
+ });
149
+ }
150
+ stmt.free();
151
+ } catch {
152
+ }
153
+ const docCount = vectors.length || 1;
154
+ const docFreq = /* @__PURE__ */ new Map();
155
+ for (const vec of vectors) {
156
+ const seen = /* @__PURE__ */ new Set();
157
+ for (const [term] of vec.terms) {
158
+ if (!seen.has(term)) {
159
+ docFreq.set(term, (docFreq.get(term) || 0) + 1);
160
+ seen.add(term);
161
+ }
162
+ }
163
+ }
164
+ for (const vec of vectors) {
165
+ const maxFreq = Math.max(1, ...Array.from(vec.terms.values()));
166
+ for (const [term, rawFreq] of vec.terms) {
167
+ const tf = 0.5 + 0.5 * rawFreq / maxFreq;
168
+ const docsWithTerm = docFreq.get(term) || 1;
169
+ const idf = Math.log(1 + (docCount - docsWithTerm + 0.5) / (docsWithTerm + 0.5));
170
+ vec.terms.set(term, tf * idf);
171
+ }
172
+ }
173
+ _vectorCache = { vectors, builtAt: Date.now() };
174
+ return vectors;
175
+ }
176
+ function extractTerms(text) {
177
+ return text.toLowerCase().split(/[\s,.;:!?()\[\]{}"'\/\\|@#$%^&*+=<>~`]+/).filter((t) => t.length > 2 && t.length < 50 && !/^\d+$/.test(t));
178
+ }
179
+ function cosineSimilarity(queryVec, docVec) {
180
+ let dotProduct = 0;
181
+ let queryMagnitude = 0;
182
+ let docMagnitude = 0;
183
+ for (const [term, qWeight] of queryVec) {
184
+ queryMagnitude += qWeight * qWeight;
185
+ const dWeight = docVec.get(term) || 0;
186
+ dotProduct += qWeight * dWeight;
187
+ }
188
+ for (const [, dWeight] of docVec) {
189
+ docMagnitude += dWeight * dWeight;
190
+ }
191
+ const mag = Math.sqrt(queryMagnitude) * Math.sqrt(docMagnitude);
192
+ if (mag === 0) return 0;
193
+ return dotProduct / mag;
194
+ }
195
+ async function hybridSearch(query, limit = 10) {
196
+ const results = [];
197
+ const expandedTerms = expandQueryTerms(query);
198
+ const allSearchTerms = /* @__PURE__ */ new Set();
199
+ for (const [, sources] of expandedTerms) {
200
+ for (const term of sources) {
201
+ allSearchTerms.add(term);
202
+ }
203
+ }
204
+ const searchTerms = Array.from(allSearchTerms);
205
+ const queryVector = /* @__PURE__ */ new Map();
206
+ for (const [originalTerm, sources] of expandedTerms) {
207
+ for (const term of sources) {
208
+ const weight = term === originalTerm ? 2 : 1;
209
+ queryVector.set(term, (queryVector.get(term) || 0) + weight);
210
+ }
211
+ }
212
+ try {
213
+ const db = await getDb();
214
+ const likeClauses = searchTerms.map(() => `name LIKE ?`).join(" OR ");
215
+ const sql = `SELECT id, name, type, file_path, metadata FROM nodes WHERE (${likeClauses}) LIMIT ${limit * 3}`;
216
+ const stmt = db.prepare(sql);
217
+ stmt.bind(searchTerms.map((t) => `%${t}%`));
218
+ const seen = /* @__PURE__ */ new Set();
219
+ while (stmt.step()) {
220
+ const row = stmt.getAsObject();
221
+ const name = row.name || "";
222
+ const filePath = row.file_path || "";
223
+ const sourceKey = name + filePath;
224
+ if (seen.has(sourceKey)) continue;
225
+ seen.add(sourceKey);
226
+ const lowerText = `${name} ${filePath}`.toLowerCase();
227
+ const matchedTerms = searchTerms.filter((t) => lowerText.includes(t));
228
+ const keywordScore = matchedTerms.length > 0 ? Math.round(matchedTerms.length / Math.max(1, searchTerms.length) * 100) : 0;
229
+ results.push({
230
+ source: name,
231
+ sourceType: "graph",
232
+ score: keywordScore,
233
+ keywordScore,
234
+ vectorScore: 0,
235
+ matchedTerms
236
+ });
237
+ }
238
+ stmt.free();
239
+ } catch (err) {
240
+ console.error(`[KumaSearch] DB search failed: ${err}`);
241
+ }
242
+ try {
243
+ const vectors = await buildSearchVectors();
244
+ for (const vec of vectors) {
245
+ const vectorScore = Math.round(cosineSimilarity(queryVector, vec.terms) * 100);
246
+ const existing = results.find((r) => r.source === vec.source);
247
+ if (existing) {
248
+ existing.vectorScore = vectorScore;
249
+ existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4));
250
+ for (const [term] of vec.terms) {
251
+ if (queryVector.has(term) && !existing.matchedTerms.includes(term)) {
252
+ existing.matchedTerms.push(term);
253
+ }
254
+ }
255
+ } else if (vectorScore > 0) {
256
+ const matchedTerms = [];
257
+ for (const [term] of vec.terms) {
258
+ if (queryVector.has(term)) matchedTerms.push(term);
259
+ }
260
+ results.push({
261
+ source: vec.source,
262
+ sourceType: vec.sourceType,
263
+ score: Math.round(vectorScore * 0.4),
264
+ keywordScore: 0,
265
+ vectorScore,
266
+ matchedTerms
267
+ });
268
+ }
269
+ }
270
+ } catch (err) {
271
+ console.error(`[KumaSearch] Vector search failed: ${err}`);
272
+ }
273
+ const uniqueResults = Array.from(
274
+ new Map(results.map((r) => [r.source, r])).values()
275
+ );
276
+ return uniqueResults.sort((a, b) => b.score - a.score).slice(0, limit);
277
+ }
278
+ function formatHybridResults(query, results) {
279
+ const expandedTerms = expandQueryTerms(query);
280
+ const originalTerms = Array.from(expandedTerms.keys()).join(", ");
281
+ const totalSynonyms = Array.from(expandedTerms.values()).reduce(
282
+ (sum, s) => sum + s.size,
283
+ 0
284
+ );
285
+ const lines = [
286
+ "**Hybrid Search** \u2014 " + query,
287
+ "----------------------------------------",
288
+ "",
289
+ "Expanded query: " + originalTerms + " (" + totalSynonyms + " total terms with synonyms)",
290
+ ""
291
+ ];
292
+ if (results.length === 0) {
293
+ lines.push("No results found. Try different keywords or research first.");
294
+ return lines.join("\n");
295
+ }
296
+ lines.push(results.length + " result(s) \u2014 ranked by hybrid relevance");
297
+ lines.push("");
298
+ for (let i = 0; i < results.length; i++) {
299
+ const r = results[i];
300
+ const typeIcon = r.sourceType === "graph" ? "file" : r.sourceType === "memory" ? "memory" : "research";
301
+ const matchType = r.vectorScore > r.keywordScore ? "semantic match" : r.keywordScore > 0 ? "keyword match" : "expanded match";
302
+ lines.push(i + 1 + ". [" + typeIcon + "] " + r.source + " \u2014 " + r.score + "%");
303
+ lines.push(" keyword: " + r.keywordScore + "% | vector: " + r.vectorScore + "% (" + matchType + ")");
304
+ if (r.matchedTerms.length > 0) {
305
+ lines.push(" Terms: " + r.matchedTerms.slice(0, 6).join(", "));
306
+ }
307
+ lines.push("");
308
+ }
309
+ lines.push("Hybrid search combines keyword matching with semantic synonym expansion.");
310
+ return lines.join("\n");
311
+ }
312
+ function clearSearchCache() {
313
+ _vectorCache = null;
314
+ }
315
+ export {
316
+ buildSearchVectors,
317
+ clearSearchCache,
318
+ expandQueryTerms,
319
+ formatHybridResults,
320
+ hybridSearch
321
+ };
@@ -1,10 +1,10 @@
1
+ import {
2
+ sessionMemory
3
+ } from "./chunk-BI7KD3SG.js";
1
4
  import {
2
5
  getLatestVerifications,
3
6
  saveVerification
4
- } from "./chunk-2NBESJTH.js";
5
- import {
6
- sessionMemory
7
- } from "./chunk-PH4JKL6T.js";
7
+ } from "./chunk-GDNAWLHF.js";
8
8
  import "./chunk-E2KFPEBT.js";
9
9
 
10
10
  // src/engine/kumaVerifier.ts
@@ -0,0 +1,248 @@
1
+ import {
2
+ getDb
3
+ } from "./chunk-GDNAWLHF.js";
4
+ import "./chunk-E2KFPEBT.js";
5
+
6
+ // src/engine/kumaVisualize.ts
7
+ async function visualizeGraph(options = {}) {
8
+ const {
9
+ type = "flowchart",
10
+ scope,
11
+ maxNodes = 30,
12
+ // depth unused: kept for API consistency
13
+ format = "mermaid"
14
+ } = options;
15
+ try {
16
+ const db = await getDb();
17
+ let nodeSql = `SELECT id, type, name, file_path FROM nodes WHERE 1=1`;
18
+ const nodeBind = [];
19
+ if (scope) {
20
+ nodeSql += ` AND (name LIKE ? OR file_path LIKE ? OR id LIKE ?)`;
21
+ nodeBind.push(`%${scope}%`, `%${scope}%`, `%${scope}%`);
22
+ }
23
+ nodeSql += ` ORDER BY updated_at DESC LIMIT ?`;
24
+ nodeBind.push(maxNodes);
25
+ const nodeStmt = db.prepare(nodeSql);
26
+ nodeStmt.bind(nodeBind);
27
+ const nodes = [];
28
+ while (nodeStmt.step()) {
29
+ nodes.push(nodeStmt.getAsObject());
30
+ }
31
+ nodeStmt.free();
32
+ if (nodes.length === 0) {
33
+ return format === "mermaid" ? "```mermaid\nflowchart TD\n Start[Empty Graph \u2014 No nodes found]\n```" : "\u{1F4ED} **Empty Graph** \u2014 No nodes found to visualize.";
34
+ }
35
+ const nodeIds = nodes.map((n) => n.id);
36
+ const edgeSql = `
37
+ SELECT e.source_id, e.target_id, e.type, e.weight,
38
+ sn.name AS source_name, tn.name AS target_name
39
+ FROM edges e
40
+ JOIN nodes sn ON sn.id = e.source_id
41
+ JOIN nodes tn ON tn.id = e.target_id
42
+ WHERE (e.source_id IN (${nodeIds.map(() => "?").join(",")})
43
+ OR e.target_id IN (${nodeIds.map(() => "?").join(",")}))
44
+ AND e.source_id != e.target_id
45
+ ORDER BY e.weight DESC
46
+ LIMIT 100
47
+ `;
48
+ const edgeBind = [...nodeIds, ...nodeIds];
49
+ const edgeStmt = db.prepare(edgeSql);
50
+ edgeStmt.bind(edgeBind);
51
+ const edges = [];
52
+ while (edgeStmt.step()) {
53
+ edges.push(edgeStmt.getAsObject());
54
+ }
55
+ edgeStmt.free();
56
+ switch (type) {
57
+ case "flowchart":
58
+ return formatFlowchart(nodes, edges, format);
59
+ case "dependency":
60
+ return formatDependencyGraph(nodes, edges, format);
61
+ case "mindmap":
62
+ return formatMindMap(nodes, edges, format);
63
+ default:
64
+ return formatFlowchart(nodes, edges, format);
65
+ }
66
+ } catch (err) {
67
+ return `Error generating visualization: ${err}`;
68
+ }
69
+ }
70
+ function formatFlowchart(nodes, edges, _format) {
71
+ void _format;
72
+ const lines = ["```mermaid", "flowchart TD"];
73
+ for (const node of nodes) {
74
+ const id = sanitizeId(node.id);
75
+ const name = truncate(node.name, 30);
76
+ const nodeType = node.type;
77
+ const shape = getNodeShape(nodeType);
78
+ if (shape) {
79
+ lines.push(` ${id}${shape}${name}${reverseShape(shape)}`);
80
+ } else {
81
+ lines.push(` ${id}[${name}]`);
82
+ }
83
+ }
84
+ for (const edge of edges) {
85
+ const sourceId = sanitizeId(edge.source_id);
86
+ const targetId = sanitizeId(edge.target_id);
87
+ const edgeType = edge.type;
88
+ void edge.weight;
89
+ const label = truncate(edgeType, 15);
90
+ const edgeStyle = getEdgeStyle(edgeType);
91
+ lines.push(` ${sourceId}${edgeStyle}|${label}|${targetId}`);
92
+ }
93
+ lines.push("```");
94
+ return lines.join("\n");
95
+ }
96
+ function formatDependencyGraph(nodes, edges, _format) {
97
+ void _format;
98
+ const lines = ["```mermaid", "flowchart LR"];
99
+ const grouped = /* @__PURE__ */ new Map();
100
+ for (const node of nodes) {
101
+ const type = node.type || "unknown";
102
+ if (!grouped.has(type)) grouped.set(type, []);
103
+ grouped.get(type).push(node);
104
+ }
105
+ let subgraphIndex = 0;
106
+ for (const [type, typeNodes] of grouped) {
107
+ if (typeNodes.length < 1) continue;
108
+ lines.push(` subgraph ${type.toUpperCase()}[${type.toUpperCase()}]`);
109
+ for (const node of typeNodes) {
110
+ const id = sanitizeId(node.id);
111
+ const name = truncate(node.name, 25);
112
+ lines.push(` ${id}(${name})`);
113
+ }
114
+ lines.push(" end");
115
+ subgraphIndex++;
116
+ }
117
+ for (const edge of edges) {
118
+ const sourceId = sanitizeId(edge.source_id);
119
+ const targetId = sanitizeId(edge.target_id);
120
+ const edgeType = edge.type;
121
+ const style = getEdgeStyle(edgeType);
122
+ lines.push(` ${sourceId}${style}|${edgeType}|${targetId}`);
123
+ }
124
+ lines.push("```");
125
+ return lines.join("\n");
126
+ }
127
+ function formatMindMap(nodes, edges, _format) {
128
+ void _format;
129
+ const lines = ["```mermaid", "mindmap"];
130
+ const hasIncoming = /* @__PURE__ */ new Set();
131
+ for (const edge of edges) {
132
+ hasIncoming.add(edge.target_id);
133
+ }
134
+ const roots = nodes.filter((n) => !hasIncoming.has(n.id));
135
+ if (roots.length === 0 && nodes.length > 0) {
136
+ const root = nodes[0];
137
+ lines.push(` root((${truncate(root.name, 25)}))`);
138
+ for (const node of nodes.slice(1)) {
139
+ lines.push(` ${sanitizeId(node.id)}(${truncate(node.name, 25)})`);
140
+ }
141
+ } else {
142
+ for (const root of roots.slice(0, 3)) {
143
+ lines.push(` root((${truncate(root.name, 25)}))`);
144
+ }
145
+ const others = nodes.filter((n) => !roots.includes(n));
146
+ for (const node of others.slice(0, 10)) {
147
+ const parent = edges.find((e) => e.source_id === node.id || e.target_id === node.id);
148
+ const indent = parent ? " " : " ";
149
+ lines.push(`${indent}${sanitizeId(node.id)}(${truncate(node.name, 25)})`);
150
+ }
151
+ }
152
+ lines.push("```");
153
+ return lines.join("\n");
154
+ }
155
+ function sanitizeId(id) {
156
+ const safe = id.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^(\d)/, "n$1").substring(0, 40);
157
+ return safe || "n0";
158
+ }
159
+ function truncate(str, maxLen) {
160
+ if (str.length <= maxLen) return str;
161
+ return str.substring(0, maxLen - 3) + "...";
162
+ }
163
+ function getNodeShape(type) {
164
+ switch (type) {
165
+ case "function":
166
+ return "([";
167
+ case "file":
168
+ return "[";
169
+ case "test":
170
+ return "{";
171
+ case "api_route":
172
+ return "[[";
173
+ case "db_table":
174
+ return ">";
175
+ // database shape approximation
176
+ case "class":
177
+ return "[";
178
+ case "interface":
179
+ return "(";
180
+ case "module":
181
+ return "[";
182
+ case "variable":
183
+ return "(";
184
+ default:
185
+ return "[";
186
+ }
187
+ }
188
+ function reverseShape(shape) {
189
+ const map = {
190
+ "([": "])",
191
+ "[": "]",
192
+ "{": "}",
193
+ "[[": "]]",
194
+ ">": "<]",
195
+ "(": ")"
196
+ };
197
+ return map[shape] || "]";
198
+ }
199
+ function getEdgeStyle(type) {
200
+ switch (type) {
201
+ case "calls":
202
+ return " ==> ";
203
+ case "imports":
204
+ return " -.-> ";
205
+ case "defines":
206
+ return " --- ";
207
+ case "tests":
208
+ return " -.-> ";
209
+ case "routes":
210
+ return " ==> ";
211
+ case "implements":
212
+ return " -.-> ";
213
+ case "extends":
214
+ return " ==> ";
215
+ case "depends_on":
216
+ return " -.-> ";
217
+ case "owns":
218
+ return " --- ";
219
+ case "modified_by":
220
+ return " -.-> ";
221
+ default:
222
+ return " --- ";
223
+ }
224
+ }
225
+ async function generateVisualizeReport(options = {}) {
226
+ const diagram = await visualizeGraph(options);
227
+ const lines = [
228
+ "\u{1F3A8} **Knowledge Graph Visualization**",
229
+ "\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",
230
+ "",
231
+ diagram,
232
+ "",
233
+ "\u{1F4CB} **Rendering Instructions:**",
234
+ "- Paste the Mermaid code above into any Mermaid-compatible viewer",
235
+ "- GitHub: Mermaid code blocks render automatically",
236
+ "- VS Code: Install 'Markdown Preview Mermaid Support' extension",
237
+ "- Online: https://mermaid.live",
238
+ "",
239
+ "\u{1F4A1} Use `kuma_visualize({ type: 'dependency' })` for clustered view",
240
+ "\u{1F4A1} Use `kuma_visualize({ type: 'mindmap' })` for overview",
241
+ "\u{1F4A1} Use `kuma_visualize({ scope: 'auth' })` to filter by topic"
242
+ ];
243
+ return lines.join("\n");
244
+ }
245
+ export {
246
+ generateVisualizeReport,
247
+ visualizeGraph
248
+ };
@@ -2,10 +2,10 @@ import {
2
2
  getGitDiffStat,
3
3
  getSessionStats,
4
4
  getUnresolvedCount
5
- } from "./chunk-ZIFLGJ5R.js";
5
+ } from "./chunk-FOQQ2CSL.js";
6
6
  import {
7
7
  sessionMemory
8
- } from "./chunk-PH4JKL6T.js";
8
+ } from "./chunk-BI7KD3SG.js";
9
9
  import "./chunk-E2KFPEBT.js";
10
10
 
11
11
  // src/engine/safetyScore.ts
@@ -15,7 +15,7 @@ async function computeSafetyScore(inputGoal) {
15
15
  let totalScore = 0;
16
16
  const maxScore = 100;
17
17
  const gitStat = getGitDiffStat();
18
- if (gitStat) {
18
+ if (gitStat && gitStat !== "clean") {
19
19
  const lines = gitStat.split("\n").filter(Boolean).length;
20
20
  if (lines === 0) {
21
21
  checks.push({
@@ -42,6 +42,14 @@ async function computeSafetyScore(inputGoal) {
42
42
  });
43
43
  totalScore += 10;
44
44
  }
45
+ } else if (gitStat === "clean") {
46
+ checks.push({
47
+ label: "Git Clean",
48
+ status: "pass",
49
+ message: "Working tree is clean",
50
+ weight: 20
51
+ });
52
+ totalScore += 20;
45
53
  } else {
46
54
  checks.push({
47
55
  label: "Git Status",
@@ -52,7 +60,7 @@ async function computeSafetyScore(inputGoal) {
52
60
  totalScore += 20;
53
61
  }
54
62
  try {
55
- const { getDb } = await import("./kumaDb-FQ3YKJ6K.js");
63
+ const { getDb } = await import("./kumaDb-DJUDLYBJ.js");
56
64
  const db = await getDb();
57
65
  const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
58
66
  const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
@@ -83,7 +91,7 @@ async function computeSafetyScore(inputGoal) {
83
91
  totalScore += 5;
84
92
  }
85
93
  try {
86
- const { getDb } = await import("./kumaDb-FQ3YKJ6K.js");
94
+ const { getDb } = await import("./kumaDb-DJUDLYBJ.js");
87
95
  const db = await getDb();
88
96
  const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
89
97
  checks.push({
@@ -108,7 +116,7 @@ async function computeSafetyScore(inputGoal) {
108
116
  const hasRunTests = stats.hasRunTests;
109
117
  let latestVerif = null;
110
118
  try {
111
- const { getLatestVerifications } = await import("./kumaDb-FQ3YKJ6K.js");
119
+ const { getLatestVerifications } = await import("./kumaDb-DJUDLYBJ.js");
112
120
  const verifs = await getLatestVerifications(1);
113
121
  if (verifs.length > 0) latestVerif = verifs[0];
114
122
  } catch {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-PH4JKL6T.js";
3
+ } from "./chunk-BI7KD3SG.js";
4
4
  export {
5
5
  sessionMemory
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.3.5",
3
+ "version": "2.3.7",
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",