@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-
|
|
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-
|
|
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-
|
|
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.
|
|
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
|