@plumpslabs/kuma 2.3.0 → 2.3.1
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 +1 -1
- package/dist/{agentDetector-FDRVUNJY.js → agentDetector-G4RBMZ6X.js} +1 -1
- package/dist/chunk-2IIDVJPW.js +836 -0
- package/dist/{chunk-OUPFJQKW.js → chunk-4CKTTORL.js} +43 -2
- package/dist/{chunk-5A6AKUJZ.js → chunk-5WSPGLXS.js} +1 -1
- package/dist/{chunk-TT37TE4P.js → chunk-6W7YV4AF.js} +37 -2
- package/dist/{chunk-IXMWW5WA.js → chunk-E2KFPEBT.js} +80 -1
- package/dist/{chunk-WP3WRPDC.js → chunk-WG47POMW.js} +2 -2
- package/dist/{chunk-IMO4QAFJ.js → chunk-ZDQVSBFB.js} +120 -5
- package/dist/index.js +355 -98
- package/dist/{init-O4MBXFZL.js → init-3MBHSTWD.js} +2 -2
- package/dist/kumaDb-PA7XVERC.js +59 -0
- package/dist/{kumaGraph-GW5SWOHQ.js → kumaGraph-VUTLTOFY.js} +3 -3
- package/dist/{kumaMemory-IO7TVE7W.js → kumaMemory-YEIEXGNW.js} +3 -3
- package/dist/pathValidator-V4DC6U6Z.js +22 -0
- package/dist/{safetyScore-7FU7XSY5.js → safetyScore-OQFYLLP7.js} +5 -5
- package/dist/{sessionMemory-52ECPXZ6.js → sessionMemory-JQT5GEWO.js} +1 -1
- package/package.json +1 -1
- package/dist/chunk-6NEMSUKP.js +0 -304
- package/dist/kumaDb-KN7Z4B5V.js +0 -19
package/dist/index.js
CHANGED
|
@@ -9,39 +9,57 @@ import {
|
|
|
9
9
|
getGraphStats,
|
|
10
10
|
searchGraph,
|
|
11
11
|
traceFlow
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ZDQVSBFB.js";
|
|
13
13
|
import {
|
|
14
|
+
addContextNote,
|
|
15
|
+
addSecurityFinding,
|
|
16
|
+
addTodo,
|
|
17
|
+
checkPortability,
|
|
18
|
+
ensureGitignore,
|
|
19
|
+
getBenchmarkDiff,
|
|
14
20
|
getChanges,
|
|
15
21
|
getDb,
|
|
16
22
|
getResearchCache,
|
|
23
|
+
getSecurityFindings,
|
|
24
|
+
listContextNotes,
|
|
25
|
+
listDecisionLog,
|
|
26
|
+
listResearchCache,
|
|
27
|
+
listTodos,
|
|
28
|
+
recordDecisionLog,
|
|
29
|
+
rollbackChange,
|
|
30
|
+
runDoctor,
|
|
31
|
+
runGarbageCollection,
|
|
32
|
+
saveBenchmark,
|
|
17
33
|
saveDb,
|
|
18
34
|
saveHealthSnapshot,
|
|
19
|
-
saveResearchCache
|
|
20
|
-
|
|
35
|
+
saveResearchCache,
|
|
36
|
+
updateDecisionStatus,
|
|
37
|
+
updateTodoStatus
|
|
38
|
+
} from "./chunk-2IIDVJPW.js";
|
|
21
39
|
import {
|
|
22
40
|
formatDecisionTemplate,
|
|
23
41
|
getProactiveMemories,
|
|
24
42
|
recordDecision,
|
|
25
43
|
scoreMemoryRelevance
|
|
26
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-4CKTTORL.js";
|
|
27
45
|
import {
|
|
28
46
|
buildDriftMessages,
|
|
29
47
|
getGitDiffStat,
|
|
30
48
|
getSessionStats,
|
|
31
49
|
getUnresolvedCount
|
|
32
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-WG47POMW.js";
|
|
33
51
|
import {
|
|
34
52
|
sessionMemory
|
|
35
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-6W7YV4AF.js";
|
|
36
54
|
import {
|
|
37
55
|
ALL_CONFIG_TYPES,
|
|
38
56
|
formatInitResults,
|
|
39
57
|
runInit
|
|
40
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-5WSPGLXS.js";
|
|
41
59
|
import {
|
|
42
60
|
getProjectRoot,
|
|
43
61
|
validateFilePath
|
|
44
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-E2KFPEBT.js";
|
|
45
63
|
|
|
46
64
|
// src/index.ts
|
|
47
65
|
import { readFileSync } from "fs";
|
|
@@ -69,10 +87,14 @@ async function handleContext(params) {
|
|
|
69
87
|
return handleNavigate(params);
|
|
70
88
|
case "changes":
|
|
71
89
|
return handleChanges(params);
|
|
90
|
+
case "rollback":
|
|
91
|
+
return handleRollback(params);
|
|
92
|
+
case "researches":
|
|
93
|
+
return handleResearches(params);
|
|
72
94
|
case "health":
|
|
73
95
|
return handleHealth(params);
|
|
74
96
|
default:
|
|
75
|
-
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, health`;
|
|
97
|
+
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health`;
|
|
76
98
|
}
|
|
77
99
|
}
|
|
78
100
|
async function handleInit(_params) {
|
|
@@ -132,6 +154,17 @@ async function handleInit(_params) {
|
|
|
132
154
|
lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
|
|
133
155
|
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
134
156
|
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
157
|
+
try {
|
|
158
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
|
|
159
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.js");
|
|
160
|
+
const score = await computeSafetyScore(_params.goal);
|
|
161
|
+
const checksStr = JSON.stringify(score.checks);
|
|
162
|
+
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push("**Health Score**");
|
|
165
|
+
lines.push(formatSafetyScore(score));
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
135
168
|
lines.push("", "\u{1F4A1} Call kuma_context({ action: 'research', scope: '<area>' }) to research a specific area.");
|
|
136
169
|
return lines.join("\n");
|
|
137
170
|
}
|
|
@@ -154,8 +187,15 @@ async function handleResearch(params) {
|
|
|
154
187
|
}
|
|
155
188
|
}
|
|
156
189
|
if (record) {
|
|
157
|
-
const
|
|
158
|
-
|
|
190
|
+
const ageSeconds = Math.floor((Date.now() - (record.validatedAt ? new Date(record.validatedAt).getTime() : 0)) / 1e3);
|
|
191
|
+
const ageStr = ageSeconds > 86400 ? `${Math.floor(ageSeconds / 86400)}d` : ageSeconds > 3600 ? `${Math.floor(ageSeconds / 3600)}h` : `${Math.floor(ageSeconds / 60)}m`;
|
|
192
|
+
lines.push(` \u2705 Found cached research (${ageStr} old)`);
|
|
193
|
+
if (ageSeconds > 86400) {
|
|
194
|
+
lines.push(` ${ageSeconds > 604800 ? "\u{1F534}" : "\u{1F7E1}"} **Staleness:** Cache is ${ageStr} old \u2014 may be stale`);
|
|
195
|
+
if (ageSeconds > 604800) {
|
|
196
|
+
lines.push(" \u{1F4A1} Consider re-researching: cache is over a week old");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
159
199
|
} else {
|
|
160
200
|
lines.push(" \u23F3 No cached research \u2014 starting fresh");
|
|
161
201
|
}
|
|
@@ -182,13 +222,30 @@ async function handleResearch(params) {
|
|
|
182
222
|
try {
|
|
183
223
|
const graphResult = await searchGraph(scope, 15);
|
|
184
224
|
const graphLines = graphResult.split("\n");
|
|
185
|
-
if (graphLines.length > 1) {
|
|
225
|
+
if (graphLines.length > 1 && !graphResult.includes("No results")) {
|
|
186
226
|
lines.push(` \u{1F4CA} ${graphLines.filter((l) => l.includes("\u2022")).length} relevant node(s) found`);
|
|
187
227
|
for (const l of graphLines.slice(0, 8)) {
|
|
188
228
|
if (l.includes("\u2022")) lines.push(` ${l}`);
|
|
189
229
|
}
|
|
190
230
|
} else {
|
|
191
|
-
lines.push(" \u23F3 No graph data
|
|
231
|
+
lines.push(" \u23F3 No graph data \u2014 searching codebase...");
|
|
232
|
+
try {
|
|
233
|
+
const fg = (await import("fast-glob")).default;
|
|
234
|
+
const root = getProjectRoot();
|
|
235
|
+
const ignorePatterns = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", "**/.kuma/**"];
|
|
236
|
+
const files = await fg([`**/*${scope}*`], { cwd: root, ignore: ignorePatterns, onlyFiles: true, deep: 6 });
|
|
237
|
+
if (files.length > 0) {
|
|
238
|
+
lines.push(` \u{1F4C1} Found ${files.length} file(s) in codebase matching "${scope}":`);
|
|
239
|
+
for (const f of files.slice(0, 8)) {
|
|
240
|
+
lines.push(` \u{1F4C4} ${f}`);
|
|
241
|
+
}
|
|
242
|
+
if (files.length > 8) lines.push(` ... and ${files.length - 8} more`);
|
|
243
|
+
} else {
|
|
244
|
+
lines.push(" \u23F3 No codebase matches \u2014 build graph by using more tools");
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
lines.push(" \u23F3 No graph data yet \u2014 build by using more tools");
|
|
248
|
+
}
|
|
192
249
|
}
|
|
193
250
|
} catch {
|
|
194
251
|
lines.push(" \u26A0\uFE0F Graph query failed");
|
|
@@ -257,11 +314,23 @@ async function handleChanges(params) {
|
|
|
257
314
|
const since = params.since;
|
|
258
315
|
return await getChanges({ filePath, since });
|
|
259
316
|
}
|
|
317
|
+
async function handleRollback(params) {
|
|
318
|
+
const target = params.target;
|
|
319
|
+
if (!target) return "\u26A0\uFE0F target parameter required (change ID, e.g. '5').";
|
|
320
|
+
const changeId = parseInt(target, 10);
|
|
321
|
+
if (isNaN(changeId)) return `\u26A0\uFE0F Invalid change ID: "${target}". Use a numeric ID from kuma_context({ action: 'changes' }).`;
|
|
322
|
+
sessionMemory.recordToolCall("kuma_context_rollback", { changeId });
|
|
323
|
+
return await rollbackChange(changeId);
|
|
324
|
+
}
|
|
325
|
+
async function handleResearches(_params) {
|
|
326
|
+
sessionMemory.recordToolCall("kuma_context_researches", {});
|
|
327
|
+
return await listResearchCache();
|
|
328
|
+
}
|
|
260
329
|
async function handleHealth(_params) {
|
|
261
330
|
sessionMemory.recordToolCall("kuma_context_health", {});
|
|
262
331
|
try {
|
|
263
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
264
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
332
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
|
|
333
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.js");
|
|
265
334
|
const score = await computeSafetyScore();
|
|
266
335
|
const checksStr = JSON.stringify(score.checks);
|
|
267
336
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -308,8 +377,16 @@ async function handleMemory(params) {
|
|
|
308
377
|
return handleSearch(params);
|
|
309
378
|
case "changes":
|
|
310
379
|
return handleChanges2(params);
|
|
380
|
+
case "todo":
|
|
381
|
+
return handleTodo(params);
|
|
382
|
+
case "context":
|
|
383
|
+
return handleContext2(params);
|
|
384
|
+
case "benchmark":
|
|
385
|
+
return handleBenchmark(params);
|
|
386
|
+
case "decision_log":
|
|
387
|
+
return handleDecisionLog(params);
|
|
311
388
|
default:
|
|
312
|
-
return `Unknown action "${action}"
|
|
389
|
+
return `Unknown action "${action}".`;
|
|
313
390
|
}
|
|
314
391
|
}
|
|
315
392
|
async function handleDecision(params) {
|
|
@@ -318,15 +395,13 @@ async function handleDecision(params) {
|
|
|
318
395
|
case "template":
|
|
319
396
|
return formatDecisionTemplate();
|
|
320
397
|
case "suggest": {
|
|
321
|
-
const { shouldRecordDecision } = await import("./kumaMemory-
|
|
398
|
+
const { shouldRecordDecision } = await import("./kumaMemory-YEIEXGNW.js");
|
|
322
399
|
const check = shouldRecordDecision();
|
|
323
400
|
return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
|
|
324
401
|
Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
|
|
325
402
|
}
|
|
326
403
|
case "record":
|
|
327
|
-
if (!params.title || !params.rationale)
|
|
328
|
-
return "\u26A0\uFE0F title and rationale are required. Optionally: context, outcome.";
|
|
329
|
-
}
|
|
404
|
+
if (!params.title || !params.rationale) return "\u26A0\uFE0F title and rationale are required.";
|
|
330
405
|
return recordDecision({
|
|
331
406
|
title: params.title,
|
|
332
407
|
context: params.context || "",
|
|
@@ -350,68 +425,44 @@ async function handleResearchSave(params) {
|
|
|
350
425
|
await saveResearchCache(scope, record, void 0, params.confidence);
|
|
351
426
|
try {
|
|
352
427
|
const researchDir = path2.join(getProjectRoot(), ".kuma", "research");
|
|
353
|
-
if (!fs2.existsSync(researchDir)) {
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
fs2.writeFileSync(
|
|
357
|
-
path2.join(researchDir, `${scope}.json`),
|
|
358
|
-
JSON.stringify(JSON.parse(record), null, 2),
|
|
359
|
-
"utf-8"
|
|
360
|
-
);
|
|
428
|
+
if (!fs2.existsSync(researchDir)) fs2.mkdirSync(researchDir, { recursive: true });
|
|
429
|
+
fs2.writeFileSync(path2.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
361
430
|
} catch {
|
|
362
431
|
}
|
|
363
|
-
return `\u2705 Research "${scope}" saved
|
|
432
|
+
return `\u2705 Research "${scope}" saved.`;
|
|
364
433
|
}
|
|
365
434
|
async function handleSession(params) {
|
|
366
435
|
const topic = params.topic;
|
|
367
436
|
const summary = sessionMemory.getSummary(topic);
|
|
368
|
-
if (topic)
|
|
369
|
-
return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
|
|
370
|
-
}
|
|
437
|
+
if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
|
|
371
438
|
const modifiedFiles = summary.modifiedFiles || [];
|
|
372
439
|
const failures = summary.unresolvedFailures || [];
|
|
373
440
|
const lines = [
|
|
374
|
-
"\u{1F4CB} **Session Summary
|
|
375
|
-
`\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`,
|
|
376
|
-
"",
|
|
441
|
+
"\u{1F4CB} **Session Summary**\n\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\n",
|
|
377
442
|
`\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
|
|
378
443
|
`\u{1F550} Duration: ${summary.sessionDuration}`,
|
|
379
|
-
`\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
|
|
380
|
-
|
|
444
|
+
`\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
|
|
445
|
+
`
|
|
381
446
|
];
|
|
382
447
|
if (modifiedFiles.length > 0) {
|
|
383
448
|
lines.push(`**Modified Files** (${modifiedFiles.length}):`);
|
|
384
449
|
for (const f of modifiedFiles.slice(0, 10)) {
|
|
385
|
-
|
|
386
|
-
lines.push(` ${icon} ${f.filePath}`);
|
|
387
|
-
}
|
|
388
|
-
if (modifiedFiles.length > 10) {
|
|
389
|
-
lines.push(` ... and ${modifiedFiles.length - 10} more`);
|
|
450
|
+
lines.push(` ${f.status === "created" ? "\u2728" : f.status === "modified" ? "\u{1F4DD}" : "\u274C"} ${f.filePath}`);
|
|
390
451
|
}
|
|
452
|
+
if (modifiedFiles.length > 10) lines.push(` ... and ${modifiedFiles.length - 10} more`);
|
|
391
453
|
lines.push("");
|
|
392
454
|
}
|
|
393
455
|
if (failures.length > 0) {
|
|
394
456
|
lines.push(`**Unresolved Issues** (${failures.length}):`);
|
|
395
|
-
for (const f of failures.slice(0, 5)) {
|
|
396
|
-
lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
|
|
397
|
-
}
|
|
398
|
-
lines.push("");
|
|
399
|
-
}
|
|
400
|
-
const completed = summary.completedSteps || [];
|
|
401
|
-
if (completed.length > 0) {
|
|
402
|
-
lines.push(`**Completed Steps**:`);
|
|
403
|
-
for (const s of completed) {
|
|
404
|
-
lines.push(` \u2705 ${s}`);
|
|
405
|
-
}
|
|
457
|
+
for (const f of failures.slice(0, 5)) lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
|
|
406
458
|
lines.push("");
|
|
407
459
|
}
|
|
408
|
-
lines.push("\u{1F4A1} Use kuma_memory({ action: 'decision', decisionAction: 'record', ... }) to document decisions.");
|
|
409
460
|
return lines.join("\n");
|
|
410
461
|
}
|
|
411
462
|
async function handleHeal(params) {
|
|
412
463
|
if (params.healAction === "check") {
|
|
413
464
|
const stale = await detectStaleNodes();
|
|
414
|
-
if (stale.length === 0) return "\u2705 No stale entries found
|
|
465
|
+
if (stale.length === 0) return "\u2705 No stale entries found.";
|
|
415
466
|
return `\u{1F50D} ${stale.length} stale entr${stale.length > 1 ? "ies" : "y"} found. Use kuma_memory({ action: 'heal' }) to repair.`;
|
|
416
467
|
}
|
|
417
468
|
const result = await autoHeal();
|
|
@@ -422,31 +473,77 @@ async function handleSearch(params) {
|
|
|
422
473
|
if (!query) return "\u26A0\uFE0F query or scope parameter required.";
|
|
423
474
|
const limit = params.limit || 20;
|
|
424
475
|
const memResults = sessionMemory.searchMemory(query, limit);
|
|
425
|
-
const { searchGraph: searchGraph2 } = await import("./kumaGraph-
|
|
476
|
+
const { searchGraph: searchGraph2 } = await import("./kumaGraph-VUTLTOFY.js");
|
|
426
477
|
const graphResults = await searchGraph2(query, Math.min(limit, 10));
|
|
427
|
-
const lines = [
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
""
|
|
431
|
-
];
|
|
478
|
+
const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
|
|
479
|
+
\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
|
|
480
|
+
`];
|
|
432
481
|
if (memResults.length > 0) {
|
|
433
482
|
lines.push(`**Session Memory** (${memResults.length}):`);
|
|
434
|
-
for (const r of memResults.slice(0, 5)) {
|
|
435
|
-
lines.push(` \u2022 ${r.content.substring(0, 120)}`);
|
|
436
|
-
}
|
|
483
|
+
for (const r of memResults.slice(0, 5)) lines.push(` \u2022 ${r.content.substring(0, 120)}`);
|
|
437
484
|
lines.push("");
|
|
438
485
|
}
|
|
439
|
-
lines.push("**Knowledge Graph
|
|
440
|
-
lines.push(graphResults);
|
|
441
|
-
if (memResults.length === 0 && graphResults.includes("No results")) {
|
|
442
|
-
lines.push("", "\u{1F4A1} No results found. Try a different query or research the topic first.");
|
|
443
|
-
}
|
|
486
|
+
lines.push("**Knowledge Graph:**\n" + graphResults);
|
|
444
487
|
return lines.join("\n");
|
|
445
488
|
}
|
|
446
489
|
async function handleChanges2(params) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
490
|
+
return await getChanges({ filePath: params.target, since: params.since });
|
|
491
|
+
}
|
|
492
|
+
async function handleTodo(params) {
|
|
493
|
+
if (params.todoId && params.status) {
|
|
494
|
+
return await updateTodoStatus(params.todoId, params.status);
|
|
495
|
+
}
|
|
496
|
+
if (params.title) {
|
|
497
|
+
return await addTodo({
|
|
498
|
+
title: params.title,
|
|
499
|
+
description: params.description,
|
|
500
|
+
scope: params.scope,
|
|
501
|
+
deps: params.deps,
|
|
502
|
+
successCriteria: params.success_criteria
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return await listTodos(params.scope, params.status);
|
|
506
|
+
}
|
|
507
|
+
async function handleContext2(params) {
|
|
508
|
+
if (params.content && params.source) {
|
|
509
|
+
return await addContextNote({
|
|
510
|
+
source: params.source,
|
|
511
|
+
content: params.content,
|
|
512
|
+
scope: params.scope,
|
|
513
|
+
filePaths: params.target ? JSON.stringify([params.target]) : void 0
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
return await listContextNotes(params.scope);
|
|
517
|
+
}
|
|
518
|
+
async function handleBenchmark(params) {
|
|
519
|
+
if (params.label) {
|
|
520
|
+
if (params.metrics) {
|
|
521
|
+
try {
|
|
522
|
+
const metrics = JSON.parse(params.metrics);
|
|
523
|
+
return await saveBenchmark(params.label, metrics);
|
|
524
|
+
} catch {
|
|
525
|
+
return `\u26A0\uFE0F Invalid metrics JSON: "${params.metrics.substring(0, 100)}"`;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return await getBenchmarkDiff(params.label, params.labelB);
|
|
529
|
+
}
|
|
530
|
+
return `\u26A0\uFE0F label required. Use: kuma_memory({ action: 'benchmark', label: 'phase-3', metrics: '{"tsc_errors": 245}' })`;
|
|
531
|
+
}
|
|
532
|
+
async function handleDecisionLog(params) {
|
|
533
|
+
if (params.target && params.status) {
|
|
534
|
+
const id = parseInt(params.target, 10);
|
|
535
|
+
if (!isNaN(id)) return await updateDecisionStatus(id, params.status);
|
|
536
|
+
}
|
|
537
|
+
if (params.title && params.rationale) {
|
|
538
|
+
return await recordDecisionLog({
|
|
539
|
+
title: params.title,
|
|
540
|
+
context: params.context,
|
|
541
|
+
rationale: params.rationale,
|
|
542
|
+
outcome: params.outcome,
|
|
543
|
+
status: params.status
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return await listDecisionLog(params.status);
|
|
450
547
|
}
|
|
451
548
|
|
|
452
549
|
// src/engine/kumaLock.ts
|
|
@@ -1011,6 +1108,58 @@ async function safetyCheck(action, filePath, command) {
|
|
|
1011
1108
|
} catch {
|
|
1012
1109
|
checks.push({ name: "Knowledge Graph Health", passed: true, detail: "\u26A0\uFE0F Could not check" });
|
|
1013
1110
|
}
|
|
1111
|
+
if (filePath) {
|
|
1112
|
+
try {
|
|
1113
|
+
const fs7 = await import("fs");
|
|
1114
|
+
const path7 = await import("path");
|
|
1115
|
+
const root = process.cwd();
|
|
1116
|
+
const fullPath = path7.resolve(root, filePath);
|
|
1117
|
+
if (fs7.existsSync(fullPath)) {
|
|
1118
|
+
const stat = fs7.statSync(fullPath);
|
|
1119
|
+
const sizeKB = Math.round(stat.size / 1024);
|
|
1120
|
+
if (sizeKB > 500) {
|
|
1121
|
+
checks.push({
|
|
1122
|
+
name: "File Size Warning",
|
|
1123
|
+
passed: false,
|
|
1124
|
+
detail: `\u{1F4CF} File is ${sizeKB}KB \u2014 large file edits are risky`
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
1128
|
+
const baseName = path7.basename(filePath, path7.extname(filePath));
|
|
1129
|
+
const testPatterns = [
|
|
1130
|
+
filePath.replace(/\.(ts|js)$/, ".test.$1"),
|
|
1131
|
+
filePath.replace(/\.(ts|js)$/, ".spec.$1"),
|
|
1132
|
+
filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
|
|
1133
|
+
`**/__tests__/**/${baseName}.test.${path7.extname(filePath).slice(1)}`
|
|
1134
|
+
];
|
|
1135
|
+
let hasTests = false;
|
|
1136
|
+
const fg = await import("fast-glob");
|
|
1137
|
+
for (const pattern of testPatterns) {
|
|
1138
|
+
const matches = await fg.default(pattern, { cwd: root, ignore: ["**/node_modules/**"] });
|
|
1139
|
+
if (matches.length > 0) {
|
|
1140
|
+
hasTests = true;
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (!hasTests) {
|
|
1145
|
+
checks.push({
|
|
1146
|
+
name: "Tests Check",
|
|
1147
|
+
passed: true,
|
|
1148
|
+
// Not a blocker, just informative
|
|
1149
|
+
detail: `\u{1F9EA} No test file found for "${path7.basename(filePath)}" \u2014 consider adding tests`
|
|
1150
|
+
});
|
|
1151
|
+
} else {
|
|
1152
|
+
checks.push({
|
|
1153
|
+
name: "Tests Check",
|
|
1154
|
+
passed: true,
|
|
1155
|
+
detail: "\u2705 Test file found"
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1014
1163
|
const failed = checks.filter((c) => !c.passed);
|
|
1015
1164
|
const riskLevel = failed.length >= 3 ? "critical" : failed.length >= 2 ? "high" : failed.length >= 1 ? "medium" : "low";
|
|
1016
1165
|
const allowed = failed.length === 0;
|
|
@@ -1468,8 +1617,18 @@ async function handleSafety(params) {
|
|
|
1468
1617
|
return handleHealth2(params);
|
|
1469
1618
|
case "override":
|
|
1470
1619
|
return handleOverride(params);
|
|
1620
|
+
case "security":
|
|
1621
|
+
return handleSecurity(params);
|
|
1622
|
+
case "gc":
|
|
1623
|
+
return handleGc(params);
|
|
1624
|
+
case "doctor":
|
|
1625
|
+
return handleDoctor(params);
|
|
1626
|
+
case "portability":
|
|
1627
|
+
return handlePortability(params);
|
|
1628
|
+
case "gitignore":
|
|
1629
|
+
return handleGitignore(params);
|
|
1471
1630
|
default:
|
|
1472
|
-
return `Unknown action "${action}"
|
|
1631
|
+
return `Unknown action "${action}".`;
|
|
1473
1632
|
}
|
|
1474
1633
|
}
|
|
1475
1634
|
async function handleGuard(params) {
|
|
@@ -1479,15 +1638,10 @@ async function handleGuard(params) {
|
|
|
1479
1638
|
});
|
|
1480
1639
|
}
|
|
1481
1640
|
async function handleCheck(params) {
|
|
1482
|
-
|
|
1483
|
-
const filePath = params.filePath;
|
|
1484
|
-
const command = params.command;
|
|
1485
|
-
return await safetyCheck(action, filePath, command);
|
|
1641
|
+
return await safetyCheck("check", params.filePath, params.command);
|
|
1486
1642
|
}
|
|
1487
1643
|
async function handleAudit(params) {
|
|
1488
|
-
if (params.limit === 0)
|
|
1489
|
-
return await auditStats();
|
|
1490
|
-
}
|
|
1644
|
+
if (params.limit === 0) return await auditStats();
|
|
1491
1645
|
return await queryAudit({
|
|
1492
1646
|
toolName: params.toolName,
|
|
1493
1647
|
riskLevel: params.riskLevel,
|
|
@@ -1516,18 +1670,77 @@ async function handleLock(params) {
|
|
|
1516
1670
|
}
|
|
1517
1671
|
async function handleHealth2(_params) {
|
|
1518
1672
|
try {
|
|
1519
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
1673
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
|
|
1520
1674
|
const score = await computeSafetyScore();
|
|
1521
|
-
|
|
1522
|
-
await saveHealthSnapshot(score.score, score.risk, checksStr, score.summary);
|
|
1675
|
+
await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
|
|
1523
1676
|
return formatSafetyScore(score);
|
|
1524
1677
|
} catch (err) {
|
|
1525
1678
|
return `Error computing health: ${err}`;
|
|
1526
1679
|
}
|
|
1527
1680
|
}
|
|
1528
1681
|
function handleOverride(params) {
|
|
1529
|
-
|
|
1530
|
-
|
|
1682
|
+
return safetyOverride(params.toolName || "unknown", params.reason);
|
|
1683
|
+
}
|
|
1684
|
+
async function handleSecurity(params) {
|
|
1685
|
+
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
1686
|
+
if (params.filePath) {
|
|
1687
|
+
try {
|
|
1688
|
+
const fs7 = await import("fs");
|
|
1689
|
+
const path7 = await import("path");
|
|
1690
|
+
const root = process.cwd();
|
|
1691
|
+
const fullPath = path7.resolve(root, params.filePath);
|
|
1692
|
+
if (fs7.existsSync(fullPath) && fs7.statSync(fullPath).isFile()) {
|
|
1693
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
1694
|
+
const lines = content.split("\n");
|
|
1695
|
+
const findings = [];
|
|
1696
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1697
|
+
const line = lines[i];
|
|
1698
|
+
const lc = line.toLowerCase();
|
|
1699
|
+
if (lc.includes("credentials: true")) {
|
|
1700
|
+
findings.push({ line: i + 1, pattern: "credentials-leak", severity: "high", message: "`credentials: true` in Prisma select may leak sensitive fields" });
|
|
1701
|
+
}
|
|
1702
|
+
if (lc.includes("console.log") && (lc.includes("token") || lc.includes("secret") || lc.includes("key") || lc.includes("password"))) {
|
|
1703
|
+
findings.push({ line: i + 1, pattern: "console-log-secret", severity: "high", message: "Potential secret logged to console" });
|
|
1704
|
+
}
|
|
1705
|
+
if (lc.includes("process.env") && (lc.includes("api_key") || lc.includes("secret") || lc.includes("token"))) {
|
|
1706
|
+
findings.push({ line: i + 1, pattern: "env-in-code", severity: "medium", message: "process.env.KEY referenced in code \u2014 ensure not in client bundle" });
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
for (const f of findings) {
|
|
1710
|
+
await addSecurityFinding({
|
|
1711
|
+
filePath: params.filePath,
|
|
1712
|
+
lineNumber: f.line,
|
|
1713
|
+
pattern: f.pattern,
|
|
1714
|
+
severity: f.severity,
|
|
1715
|
+
message: f.message
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
if (findings.length === 0) {
|
|
1719
|
+
return `\u{1F512} No security issues found in "${params.filePath}".`;
|
|
1720
|
+
}
|
|
1721
|
+
return `\u{1F512} **Security Scan** \u2014 ${findings.length} finding(s) in "${params.filePath}":
|
|
1722
|
+
${findings.map((f) => ` ${f.severity === "high" ? "\u{1F534}" : "\u{1F7E1}"} L${f.line}: ${f.message}`).join("\n")}`;
|
|
1723
|
+
}
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return await getSecurityFindings();
|
|
1728
|
+
}
|
|
1729
|
+
async function handleGc(_params) {
|
|
1730
|
+
sessionMemory.recordToolCall("kuma_safety_gc", {});
|
|
1731
|
+
return await runGarbageCollection();
|
|
1732
|
+
}
|
|
1733
|
+
async function handleDoctor(_params) {
|
|
1734
|
+
sessionMemory.recordToolCall("kuma_safety_doctor", {});
|
|
1735
|
+
return await runDoctor();
|
|
1736
|
+
}
|
|
1737
|
+
async function handlePortability(_params) {
|
|
1738
|
+
sessionMemory.recordToolCall("kuma_safety_portability", {});
|
|
1739
|
+
return await checkPortability();
|
|
1740
|
+
}
|
|
1741
|
+
async function handleGitignore(_params) {
|
|
1742
|
+
sessionMemory.recordToolCall("kuma_safety_gitignore", {});
|
|
1743
|
+
return await ensureGitignore();
|
|
1531
1744
|
}
|
|
1532
1745
|
|
|
1533
1746
|
// src/manifest.ts
|
|
@@ -1536,7 +1749,7 @@ function registerAllTools(server) {
|
|
|
1536
1749
|
"kuma_context",
|
|
1537
1750
|
"**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.",
|
|
1538
1751
|
{
|
|
1539
|
-
action: z.enum(["init", "research", "impact", "navigate", "changes", "health"]).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, health=project health score"),
|
|
1752
|
+
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"),
|
|
1540
1753
|
scope: z.string().optional().describe("Research scope for research action (e.g. 'auth', 'database', 'api')"),
|
|
1541
1754
|
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
|
|
1542
1755
|
goal: z.string().optional().describe("Current goal (for init/health)"),
|
|
@@ -1561,17 +1774,17 @@ function registerAllTools(server) {
|
|
|
1561
1774
|
);
|
|
1562
1775
|
server.tool(
|
|
1563
1776
|
"kuma_memory",
|
|
1564
|
-
"Record and retrieve project knowledge. Actions: decision (ADR-style record/template/suggest), research_save (save research
|
|
1777
|
+
"Record and retrieve project knowledge. Actions: decision (ADR-style record/template/suggest), research_save (save research), session (session summary), heal (graph repair), search (search all), changes (change log), todo (persistent todo CRUD), context (inject context notes), benchmark (capture/diff metrics), decision_log (living document with status tracking).",
|
|
1565
1778
|
{
|
|
1566
|
-
action: z.enum(["decision", "research_save", "session", "heal", "search", "changes"]).describe("Memory action: decision=
|
|
1567
|
-
scope: z.string().optional().describe("Scope for research_save/search"),
|
|
1779
|
+
action: z.enum(["decision", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log"]).describe("Memory action: decision=ADR, research_save=save, session=summary, heal=repair, search=search, changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions"),
|
|
1780
|
+
scope: z.string().optional().describe("Scope for research_save/search/todo/context"),
|
|
1568
1781
|
query: z.string().optional().describe("Search query for search action"),
|
|
1569
|
-
content: z.string().optional().describe("Content/notes for research_save"),
|
|
1782
|
+
content: z.string().optional().describe("Content/notes for research_save / context"),
|
|
1570
1783
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
1571
1784
|
confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
|
|
1572
1785
|
// Decision params
|
|
1573
1786
|
decisionAction: z.enum(["template", "suggest", "record"]).optional().describe("Decision sub-action"),
|
|
1574
|
-
title: z.string().optional().describe("Decision title"),
|
|
1787
|
+
title: z.string().optional().describe("Decision/todo/benchmark title"),
|
|
1575
1788
|
context: z.string().optional().describe("Decision context"),
|
|
1576
1789
|
rationale: z.string().optional().describe("Decision rationale"),
|
|
1577
1790
|
outcome: z.string().optional().describe("Decision outcome"),
|
|
@@ -1579,9 +1792,21 @@ function registerAllTools(server) {
|
|
|
1579
1792
|
healAction: z.enum(["check", "heal"]).optional().describe("Heal sub-action"),
|
|
1580
1793
|
topic: z.string().optional().describe("Memory topic for session"),
|
|
1581
1794
|
limit: z.number().min(1).max(100).optional().describe("Result limit"),
|
|
1582
|
-
target: z.string().optional().describe("File path for changes"),
|
|
1795
|
+
target: z.string().optional().describe("File path for changes / decision_log ID"),
|
|
1583
1796
|
since: z.number().optional().describe("Timestamp filter for changes"),
|
|
1584
|
-
compact: z.boolean().optional().default(false).describe("Compact output mode")
|
|
1797
|
+
compact: z.boolean().optional().default(false).describe("Compact output mode"),
|
|
1798
|
+
// Todo params
|
|
1799
|
+
description: z.string().optional().describe("Todo description"),
|
|
1800
|
+
deps: z.string().optional().describe("Todo dependencies (JSON array)"),
|
|
1801
|
+
success_criteria: z.string().optional().describe("Todo success criteria"),
|
|
1802
|
+
status: z.string().optional().describe("Status for todo/decision_log"),
|
|
1803
|
+
todoId: z.number().optional().describe("Todo ID for status update"),
|
|
1804
|
+
// Context params
|
|
1805
|
+
source: z.string().optional().describe("Context source (e.g. 'slack', 'jira', 'meeting')"),
|
|
1806
|
+
// Benchmark params
|
|
1807
|
+
label: z.string().optional().describe("Benchmark label"),
|
|
1808
|
+
metrics: z.string().optional().describe("Benchmark metrics JSON"),
|
|
1809
|
+
labelB: z.string().optional().describe("Second benchmark label for diff")
|
|
1585
1810
|
},
|
|
1586
1811
|
async (params) => {
|
|
1587
1812
|
try {
|
|
@@ -1611,14 +1836,14 @@ function registerAllTools(server) {
|
|
|
1611
1836
|
);
|
|
1612
1837
|
server.tool(
|
|
1613
1838
|
"kuma_safety",
|
|
1614
|
-
"Safety checks, policy enforcement, and
|
|
1839
|
+
"Safety checks, policy enforcement, security scanning, and project hygiene. Actions: guard (anti-pattern detection), check (pre-exec safety), audit (query trail), lock (multi-agent), health (score), security (scan for leaks), gc (garbage collection), doctor (health check), portability (path check), gitignore (auto-config), override (bypass).",
|
|
1615
1840
|
{
|
|
1616
|
-
action: z.enum(["guard", "check", "audit", "lock", "health", "override"]).describe("Safety action: guard=anti-patterns, check=pre-exec safety, audit=query trail, lock=multi-agent, health=score, override=
|
|
1841
|
+
action: z.enum(["guard", "check", "audit", "lock", "health", "override", "security", "gc", "doctor", "portability", "gitignore"]).describe("Safety action: guard=anti-patterns, check=pre-exec safety, audit=query trail, lock=multi-agent, health=score, security=scan leaks, gc=garbage collect, doctor=health check, portability=paths, gitignore=config, override=bypass"),
|
|
1617
1842
|
// Guard params
|
|
1618
1843
|
guardGoal: z.string().optional().describe("Goal for guard check"),
|
|
1619
1844
|
guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
|
|
1620
1845
|
// Check params
|
|
1621
|
-
filePath: z.string().optional().describe("File path for check/lock"),
|
|
1846
|
+
filePath: z.string().optional().describe("File path for check/lock/security scan"),
|
|
1622
1847
|
command: z.string().optional().describe("Command for check"),
|
|
1623
1848
|
// Audit params
|
|
1624
1849
|
toolName: z.string().optional().describe("Filter audit by tool name"),
|
|
@@ -1788,7 +2013,7 @@ async function main() {
|
|
|
1788
2013
|
});
|
|
1789
2014
|
(async () => {
|
|
1790
2015
|
try {
|
|
1791
|
-
const { generateInitMdContent } = await import("./init-
|
|
2016
|
+
const { generateInitMdContent } = await import("./init-3MBHSTWD.js");
|
|
1792
2017
|
const fs7 = await import("fs");
|
|
1793
2018
|
const path7 = await import("path");
|
|
1794
2019
|
const initMdPath = path7.resolve(process.cwd(), ".kuma/init.md");
|
|
@@ -1804,7 +2029,7 @@ async function main() {
|
|
|
1804
2029
|
})();
|
|
1805
2030
|
(async () => {
|
|
1806
2031
|
try {
|
|
1807
|
-
const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-
|
|
2032
|
+
const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-G4RBMZ6X.js");
|
|
1808
2033
|
const { generateSkill, getSecondaryFiles } = await import("./skillGenerator-F6YO4H5D.js");
|
|
1809
2034
|
const fs7 = await import("fs");
|
|
1810
2035
|
const path7 = await import("path");
|
|
@@ -1838,6 +2063,38 @@ async function main() {
|
|
|
1838
2063
|
console.error(`[${SERVER_NAME}] Failed to auto-create skill file: ${err}`);
|
|
1839
2064
|
}
|
|
1840
2065
|
})();
|
|
2066
|
+
(async () => {
|
|
2067
|
+
try {
|
|
2068
|
+
console.error(`[${SERVER_NAME}] \u{1F504} Running cold start bootstrap...`);
|
|
2069
|
+
const sessionInfo = sessionMemory.loadSession();
|
|
2070
|
+
if (sessionInfo.hasPrevSession) {
|
|
2071
|
+
console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
|
|
2072
|
+
}
|
|
2073
|
+
try {
|
|
2074
|
+
const { buildFromSessionMemory } = await import("./kumaGraph-VUTLTOFY.js");
|
|
2075
|
+
const edgeCount = await buildFromSessionMemory();
|
|
2076
|
+
if (edgeCount > 0) {
|
|
2077
|
+
console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
|
|
2078
|
+
}
|
|
2079
|
+
} catch (err) {
|
|
2080
|
+
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-PA7XVERC.js");
|
|
2084
|
+
const db = await getDb2();
|
|
2085
|
+
db.run(
|
|
2086
|
+
`INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
|
|
2087
|
+
[Math.floor(Date.now() / 1e3), sessionMemory.getSummary().currentGoal || "Session start", sessionInfo.toolCallCount]
|
|
2088
|
+
);
|
|
2089
|
+
saveDb2(db);
|
|
2090
|
+
} catch (err) {
|
|
2091
|
+
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
|
|
2092
|
+
}
|
|
2093
|
+
console.error(`[${SERVER_NAME}] \u2705 Cold start bootstrap complete`);
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Cold start bootstrap error: ${err}`);
|
|
2096
|
+
}
|
|
2097
|
+
})();
|
|
1841
2098
|
const server = new McpServer(
|
|
1842
2099
|
{
|
|
1843
2100
|
name: SERVER_NAME,
|