@plumpslabs/kuma 2.3.20 → 2.3.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/index.js +10316 -380
- package/package.json +2 -2
- package/packages/ide/studio/dist/index.js +90 -9
- package/packages/ide/studio/public/index.html +528 -233
- package/dist/agentDetector-YOWQVJFR.js +0 -186
- package/dist/chunk-3BRBJZ7P.js +0 -1055
- package/dist/chunk-3OHYYXYN.js +0 -71
- package/dist/chunk-ABKE45T4.js +0 -1264
- package/dist/chunk-E2KFPEBT.js +0 -183
- package/dist/chunk-FKRSI5U5.js +0 -282
- package/dist/chunk-GFLSAXAH.js +0 -155
- package/dist/chunk-L7F67KUP.js +0 -172
- package/dist/chunk-LVKOGXLC.js +0 -658
- package/dist/chunk-PRUTTZBS.js +0 -1113
- package/dist/contextDigest-QB5XHPXE.js +0 -177
- package/dist/domainRules-QLPAQASB.js +0 -20
- package/dist/init-PL4XL662.js +0 -15
- package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
- package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
- package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
- package/dist/kumaContractEngine-KX27T4N7.js +0 -305
- package/dist/kumaDb-4XZ5S2LH.js +0 -65
- package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
- package/dist/kumaGotchas-XRGFFBTA.js +0 -151
- package/dist/kumaGraph-UMXZNGYF.js +0 -44
- package/dist/kumaMemory-FBJMV77G.js +0 -16
- package/dist/kumaMiner-XJETL7TL.js +0 -176
- package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
- package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
- package/dist/kumaSearch-PV4QTKE7.js +0 -321
- package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
- package/dist/kumaVerifier-6YEGC77M.js +0 -265
- package/dist/kumaVisualize-264OEBGJ.js +0 -264
- package/dist/pathValidator-V4DC6U6Z.js +0 -22
- package/dist/safetyAudit-O45SPNTS.js +0 -12
- package/dist/safetyScore-TMMRD2MV.js +0 -333
- package/dist/sessionMemory-YPKVIOMV.js +0 -6
- package/dist/skillGenerator-PWEJKZNX.js +0 -304
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getActiveGotchas,
|
|
3
|
-
readLayer
|
|
4
|
-
} from "./chunk-FKRSI5U5.js";
|
|
5
|
-
import "./chunk-LVKOGXLC.js";
|
|
6
|
-
import {
|
|
7
|
-
getDb
|
|
8
|
-
} from "./chunk-3BRBJZ7P.js";
|
|
9
|
-
import {
|
|
10
|
-
getProjectRoot
|
|
11
|
-
} from "./chunk-E2KFPEBT.js";
|
|
12
|
-
|
|
13
|
-
// src/engine/kumaProgressiveContext.ts
|
|
14
|
-
import fs from "fs";
|
|
15
|
-
import path from "path";
|
|
16
|
-
async function getProjectMeta() {
|
|
17
|
-
const root = getProjectRoot();
|
|
18
|
-
const name = root.split("/").pop() || "unknown";
|
|
19
|
-
const stack = [];
|
|
20
|
-
if (fs.existsSync(path.join(root, "package.json"))) {
|
|
21
|
-
try {
|
|
22
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
|
|
23
|
-
if (pkg.dependencies?.next) stack.push("Next.js");
|
|
24
|
-
else if (pkg.dependencies?.react) stack.push("React");
|
|
25
|
-
if (pkg.devDependencies?.typescript) stack.push("TypeScript");
|
|
26
|
-
if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) stack.push("pnpm");
|
|
27
|
-
else if (fs.existsSync(path.join(root, "yarn.lock"))) stack.push("yarn");
|
|
28
|
-
} catch {
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (fs.existsSync(path.join(root, "go.mod"))) stack.push("Go");
|
|
32
|
-
if (fs.existsSync(path.join(root, "Cargo.toml"))) stack.push("Rust");
|
|
33
|
-
if (fs.existsSync(path.join(root, "pyproject.toml"))) stack.push("Python");
|
|
34
|
-
let nodeCount = 0;
|
|
35
|
-
let edgeCount = 0;
|
|
36
|
-
try {
|
|
37
|
-
const db = await getDb();
|
|
38
|
-
const nodeRes = db.exec("SELECT COUNT(*) as c FROM nodes");
|
|
39
|
-
nodeCount = nodeRes[0]?.values[0]?.[0] ?? 0;
|
|
40
|
-
const edgeRes = db.exec("SELECT COUNT(*) as c FROM edges");
|
|
41
|
-
edgeCount = edgeRes[0]?.values[0]?.[0] ?? 0;
|
|
42
|
-
} catch {
|
|
43
|
-
}
|
|
44
|
-
const kumaDir = path.join(root, ".kuma");
|
|
45
|
-
const layerStatus = {
|
|
46
|
-
domainRules: fs.existsSync(path.join(kumaDir, "DOMAIN_RULES.md")),
|
|
47
|
-
archFlow: fs.existsSync(path.join(kumaDir, "ARCHITECTURE_FLOW.md")),
|
|
48
|
-
gotchas: getActiveGotchas().length
|
|
49
|
-
};
|
|
50
|
-
let hasDecisions = false;
|
|
51
|
-
let hasTodos = false;
|
|
52
|
-
try {
|
|
53
|
-
const db = await getDb();
|
|
54
|
-
const decRes = db.exec("SELECT COUNT(*) as c FROM decision_log");
|
|
55
|
-
hasDecisions = (decRes[0]?.values[0]?.[0] ?? 0) > 0;
|
|
56
|
-
const todoRes = db.exec("SELECT COUNT(*) as c FROM todos WHERE status != 'done'");
|
|
57
|
-
hasTodos = (todoRes[0]?.values[0]?.[0] ?? 0) > 0;
|
|
58
|
-
} catch {
|
|
59
|
-
}
|
|
60
|
-
return { name, stack, entryPoints: [], nodeCount, edgeCount, layerStatus, hasDecisions, hasTodos };
|
|
61
|
-
}
|
|
62
|
-
function formatProjectMeta(meta) {
|
|
63
|
-
const parts = [
|
|
64
|
-
`\u{1F4C1} ${meta.name}`,
|
|
65
|
-
meta.stack.length > 0 ? `\u26A1 ${meta.stack.join("/")}` : "",
|
|
66
|
-
`\u{1F4CA} ${meta.nodeCount}n/${meta.edgeCount}e`,
|
|
67
|
-
meta.layerStatus.domainRules ? "\u{1F4CB}R" : "",
|
|
68
|
-
meta.layerStatus.archFlow ? "\u{1F3D7}\uFE0FA" : "",
|
|
69
|
-
meta.layerStatus.gotchas > 0 ? `\u26A0\uFE0F${meta.layerStatus.gotchas}g` : "",
|
|
70
|
-
meta.hasDecisions ? "\u{1F4DD}D" : "",
|
|
71
|
-
meta.hasTodos ? "\u{1F4CC}T" : ""
|
|
72
|
-
].filter(Boolean);
|
|
73
|
-
return parts.join(" | ");
|
|
74
|
-
}
|
|
75
|
-
async function loadSection(section, scope) {
|
|
76
|
-
switch (section) {
|
|
77
|
-
case "domain_rules": {
|
|
78
|
-
try {
|
|
79
|
-
const content = readLayer("domain_rules");
|
|
80
|
-
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("<!--"));
|
|
81
|
-
return `\u{1F4CB} **Domain Rules**
|
|
82
|
-
${lines.slice(0, 20).join("\n")}`;
|
|
83
|
-
} catch {
|
|
84
|
-
return "\u{1F4CB} **Domain Rules**: not configured";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
case "architecture": {
|
|
88
|
-
try {
|
|
89
|
-
const content = readLayer("arch_flow");
|
|
90
|
-
const relevant = scope ? content.split("\n").filter((l) => l.toLowerCase().includes(scope.toLowerCase())).slice(0, 15) : content.split("\n").filter((l) => l.trim() && !l.startsWith("<!--")).slice(0, 20);
|
|
91
|
-
return `\u{1F3D7}\uFE0F **Architecture${scope ? `: ${scope}` : ""}**
|
|
92
|
-
${relevant.join("\n")}`;
|
|
93
|
-
} catch {
|
|
94
|
-
return "\u{1F3D7}\uFE0F **Architecture**: not documented";
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
case "gotchas": {
|
|
98
|
-
const gotchas = getActiveGotchas();
|
|
99
|
-
if (gotchas.length === 0) return "\u26A0\uFE0F **Gotchas**: none recorded";
|
|
100
|
-
const filtered = scope ? gotchas.filter((g) => g.filePath.toLowerCase().includes(scope.toLowerCase())) : gotchas;
|
|
101
|
-
const lines = filtered.slice(0, 5).map((g) => {
|
|
102
|
-
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : "\u{1F7E1}";
|
|
103
|
-
return `${icon} ${g.filePath}: ${g.description.substring(0, 80)}`;
|
|
104
|
-
});
|
|
105
|
-
return `\u26A0\uFE0F **Gotchas** (${filtered.length} active)
|
|
106
|
-
${lines.join("\n")}`;
|
|
107
|
-
}
|
|
108
|
-
case "decisions": {
|
|
109
|
-
try {
|
|
110
|
-
const db = await getDb();
|
|
111
|
-
const stmt = db.prepare(
|
|
112
|
-
`SELECT title, status, created_at FROM decision_log ORDER BY created_at DESC LIMIT 10`
|
|
113
|
-
);
|
|
114
|
-
const results = [];
|
|
115
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
116
|
-
stmt.free();
|
|
117
|
-
if (results.length === 0) return "\u{1F4DD} **Decisions**: none recorded";
|
|
118
|
-
const lines = results.map(
|
|
119
|
-
(d) => ` ${d.status === "active" ? "\u2705" : "\u{1F4DD}"} ${d.title} (${d.status})`
|
|
120
|
-
);
|
|
121
|
-
return `\u{1F4DD} **Decisions** (${results.length})
|
|
122
|
-
${lines.join("\n")}`;
|
|
123
|
-
} catch {
|
|
124
|
-
return "\u{1F4DD} **Decisions**: unavailable";
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
case "graph": {
|
|
128
|
-
try {
|
|
129
|
-
const db = await getDb();
|
|
130
|
-
const nodeRes = db.exec("SELECT type, COUNT(*) as cnt FROM nodes GROUP BY type ORDER BY cnt DESC LIMIT 8");
|
|
131
|
-
if (!nodeRes[0]?.values) return "\u{1F4CA} **Graph**: empty";
|
|
132
|
-
const lines = nodeRes[0].values.map((r) => ` ${r[0]}: ${r[1]}`);
|
|
133
|
-
return `\u{1F4CA} **Knowledge Graph**
|
|
134
|
-
${lines.join("\n")}`;
|
|
135
|
-
} catch {
|
|
136
|
-
return "\u{1F4CA} **Graph**: unavailable";
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
case "changes": {
|
|
140
|
-
try {
|
|
141
|
-
const db = await getDb();
|
|
142
|
-
const stmt = db.prepare("SELECT file_path, change_type FROM change_log ORDER BY id DESC LIMIT 8");
|
|
143
|
-
const results = [];
|
|
144
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
145
|
-
stmt.free();
|
|
146
|
-
if (results.length === 0) return "\u{1F4DD} **Changes**: none in this session";
|
|
147
|
-
const lines = results.map(
|
|
148
|
-
(r) => ` ${r.change_type === "modified" ? "\u{1F4DD}" : r.change_type === "created" ? "\u2728" : "\u274C"} ${r.file_path}`
|
|
149
|
-
);
|
|
150
|
-
return `\u{1F4DD} **Recent Changes** (${results.length})
|
|
151
|
-
${lines.join("\n")}`;
|
|
152
|
-
} catch {
|
|
153
|
-
return "\u{1F4DD} **Changes**: unavailable";
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
case "health": {
|
|
157
|
-
try {
|
|
158
|
-
const { computeSafetyScore } = await import("./safetyScore-TMMRD2MV.js");
|
|
159
|
-
const score = await computeSafetyScore();
|
|
160
|
-
return `\u{1F3E5} **Health**: ${score.score}/100 (${score.risk})`;
|
|
161
|
-
} catch {
|
|
162
|
-
return "\u{1F3E5} **Health**: unavailable";
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
default:
|
|
166
|
-
return `Unknown section: ${section}`;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function getSkillBoundary(skillArea) {
|
|
170
|
-
const area = skillArea.toLowerCase();
|
|
171
|
-
if (area.includes("auth") || area.includes("login") || area.includes("security")) {
|
|
172
|
-
return {
|
|
173
|
-
name: "auth",
|
|
174
|
-
relatedSections: ["domain_rules", "architecture", "gotchas", "decisions"],
|
|
175
|
-
relatedPaths: ["src/auth", "src/middleware", "src/guards"],
|
|
176
|
-
description: "Authentication & authorization context"
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
if (area.includes("test") || area.includes("spec")) {
|
|
180
|
-
return {
|
|
181
|
-
name: "testing",
|
|
182
|
-
relatedSections: ["gotchas", "health"],
|
|
183
|
-
relatedPaths: ["__tests__", "*.test.*", "*.spec.*"],
|
|
184
|
-
description: "Testing & verification context"
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
if (area.includes("db") || area.includes("database") || area.includes("schema")) {
|
|
188
|
-
return {
|
|
189
|
-
name: "database",
|
|
190
|
-
relatedSections: ["architecture", "gotchas", "decisions"],
|
|
191
|
-
relatedPaths: ["prisma", "src/db", "schema", "migrations"],
|
|
192
|
-
description: "Database & schema context"
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
if (area.includes("api") || area.includes("route") || area.includes("endpoint")) {
|
|
196
|
-
return {
|
|
197
|
-
name: "api",
|
|
198
|
-
relatedSections: ["architecture", "gotchas", "decisions", "changes"],
|
|
199
|
-
relatedPaths: ["src/api", "src/routes", "src/controllers"],
|
|
200
|
-
description: "API & routing context"
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
name: "general",
|
|
205
|
-
relatedSections: ["domain_rules", "gotchas"],
|
|
206
|
-
relatedPaths: [],
|
|
207
|
-
description: "General project context"
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
async function getProgressiveContext(skillArea) {
|
|
211
|
-
const meta = await getProjectMeta();
|
|
212
|
-
const boundary = getSkillBoundary(skillArea || "general");
|
|
213
|
-
const sections = await Promise.all(
|
|
214
|
-
boundary.relatedSections.map(async (section) => ({
|
|
215
|
-
name: section,
|
|
216
|
-
content: await loadSection(section, skillArea)
|
|
217
|
-
}))
|
|
218
|
-
);
|
|
219
|
-
return {
|
|
220
|
-
meta: formatProjectMeta(meta),
|
|
221
|
-
sections,
|
|
222
|
-
boundary
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
export {
|
|
226
|
-
formatProjectMeta,
|
|
227
|
-
getProgressiveContext,
|
|
228
|
-
getProjectMeta,
|
|
229
|
-
getSkillBoundary,
|
|
230
|
-
loadSection
|
|
231
|
-
};
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getDb
|
|
3
|
-
} from "./chunk-3BRBJZ7P.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
|
-
};
|