@plumpslabs/kuma 2.3.6 → 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.
package/dist/index.js CHANGED
@@ -15,12 +15,21 @@ import {
15
15
  searchGraph,
16
16
  traceFlow
17
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
29
  } from "./chunk-FOQQ2CSL.js";
30
+ import {
31
+ sessionMemory
32
+ } from "./chunk-BI7KD3SG.js";
24
33
  import {
25
34
  addContextNote,
26
35
  addSecurityFinding,
@@ -47,15 +56,6 @@ import {
47
56
  updateDecisionStatus,
48
57
  updateTodoStatus
49
58
  } from "./chunk-GDNAWLHF.js";
50
- import {
51
- formatDecisionTemplate,
52
- getProactiveMemories,
53
- recordDecision,
54
- scoreMemoryRelevance
55
- } from "./chunk-X5TPBDKO.js";
56
- import {
57
- sessionMemory
58
- } from "./chunk-BI7KD3SG.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",
@@ -117,7 +117,14 @@ var CONTEXT_ALIASES = {
117
117
  "batch": "sync",
118
118
  "kuma_sync": "sync",
119
119
  "unified": "sync",
120
- "state": "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"
121
128
  };
122
129
  async function handleContext(params) {
123
130
  const rawAction = params.action || "init";
@@ -142,8 +149,10 @@ async function handleContext(params) {
142
149
  return handleHealth(params);
143
150
  case "sync":
144
151
  return handleSync(params);
152
+ case "visualize":
153
+ return handleVisualize(params);
145
154
  default:
146
- return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync`;
155
+ return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize`;
147
156
  }
148
157
  }
149
158
  async function handleInit(_params) {
@@ -437,6 +446,15 @@ async function handleSync(params) {
437
446
  lines.push("\u{1F4A1} Sync complete \u2014 all state captured in a single roundtrip.");
438
447
  return lines.join("\n");
439
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
+ }
440
458
  function computeProjectHash(scope) {
441
459
  try {
442
460
  const root = getProjectRoot();
@@ -638,6 +656,15 @@ async function handleSearch(params) {
638
656
  const memResults = sessionMemory.searchMemory(query, limit);
639
657
  const { searchGraph: searchGraph2 } = await import("./kumaGraph-35TAIBWD.js");
640
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
+ }
641
668
  const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
642
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
643
670
  `];
@@ -647,6 +674,10 @@ async function handleSearch(params) {
647
674
  lines.push("");
648
675
  }
649
676
  lines.push("**Knowledge Graph:**\n" + graphResults);
677
+ if (hybridResults) {
678
+ lines.push("");
679
+ lines.push(hybridResults);
680
+ }
650
681
  return lines.join("\n");
651
682
  }
652
683
  async function handleChanges2(params) {
@@ -711,7 +742,7 @@ async function handleDecisionLog(params) {
711
742
  }
712
743
  async function handleMine(params) {
713
744
  sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
714
- const { mineHistoricalDecisions } = await import("./kumaMiner-BIDSZE3Q.js");
745
+ const { mineHistoricalDecisions } = await import("./kumaMiner-P5KCUSF7.js");
715
746
  return await mineHistoricalDecisions({
716
747
  scope: params.scope,
717
748
  since: typeof params.since === "string" ? params.since : void 0,
@@ -1962,7 +1993,7 @@ async function handleVerify(params) {
1962
1993
  }
1963
1994
  _lastVerifyCall = now;
1964
1995
  sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
1965
- const { runAutoVerification } = await import("./kumaVerifier-B4D7NOFT.js");
1996
+ const { runAutoVerification } = await import("./kumaVerifier-LFRNIGSL.js");
1966
1997
  return await runAutoVerification({
1967
1998
  scope: params.scope || params.filePath,
1968
1999
  force: params.force,
@@ -1976,7 +2007,7 @@ function registerAllTools(server) {
1976
2007
  "kuma_context",
1977
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.",
1978
2009
  {
1979
- 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"),
1980
2011
  scope: z.string().optional().describe("Research scope for research action (e.g. 'auth', 'database', 'api')"),
1981
2012
  target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
1982
2013
  goal: z.string().optional().describe("Current goal (for init/health)"),
@@ -2161,7 +2192,7 @@ async function main() {
2161
2192
  console.error("");
2162
2193
  let killedCount = 0;
2163
2194
  try {
2164
- const { getRunningVerificationPid } = await import("./kumaVerifier-B4D7NOFT.js");
2195
+ const { getRunningVerificationPid } = await import("./kumaVerifier-LFRNIGSL.js");
2165
2196
  const pid = getRunningVerificationPid();
2166
2197
  if (pid) {
2167
2198
  try {
@@ -2253,6 +2284,9 @@ async function main() {
2253
2284
  console.error("");
2254
2285
  process.exit(0);
2255
2286
  }
2287
+ if (args[0] === "--hook") {
2288
+ process.exit(0);
2289
+ }
2256
2290
  if (args[0] === "init") {
2257
2291
  const flags = args.slice(1);
2258
2292
  if (flags.includes("--help") || flags.includes("-h")) {
@@ -2430,6 +2464,44 @@ async function main() {
2430
2464
  } catch (err) {
2431
2465
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
2432
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
+ }
2433
2505
  console.error(`[${SERVER_NAME}] \u2705 Cold start bootstrap complete`);
2434
2506
  } catch (err) {
2435
2507
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Cold start bootstrap error: ${err}`);
@@ -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
+ };
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.3.6",
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",
@@ -1,10 +1,10 @@
1
- import {
2
- recordDecisionLog
3
- } from "./chunk-GDNAWLHF.js";
4
1
  import {
5
2
  recordDecision
6
3
  } from "./chunk-X5TPBDKO.js";
7
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
@@ -1,10 +1,10 @@
1
+ import {
2
+ sessionMemory
3
+ } from "./chunk-BI7KD3SG.js";
1
4
  import {
2
5
  getLatestVerifications,
3
6
  saveVerification
4
7
  } from "./chunk-GDNAWLHF.js";
5
- import {
6
- sessionMemory
7
- } from "./chunk-BI7KD3SG.js";
8
8
  import "./chunk-E2KFPEBT.js";
9
9
 
10
10
  // src/engine/kumaVerifier.ts