@plumpslabs/kuma 2.3.4 → 2.3.6
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/{chunk-77G3APNG.js → chunk-BI7KD3SG.js} +15 -7
- package/dist/{chunk-BKEDQFEH.js → chunk-FOQQ2CSL.js} +1 -1
- package/dist/{chunk-NKNXJG2E.js → chunk-GDNAWLHF.js} +20 -24
- package/dist/{chunk-5YCQG7V7.js → chunk-K64NSHBR.js} +8 -7
- package/dist/{chunk-4CZSPSDW.js → chunk-X5TPBDKO.js} +2 -2
- package/dist/index.js +116 -27
- package/dist/{kumaDb-I4AJ5A7H.js → kumaDb-DJUDLYBJ.js} +1 -1
- package/dist/{kumaGraph-KXI3Q7QF.js → kumaGraph-35TAIBWD.js} +2 -2
- package/dist/{kumaMemory-2HW4JLJU.js → kumaMemory-5SR3TGRL.js} +2 -2
- package/dist/{kumaMiner-HZUBNYYP.js → kumaMiner-BIDSZE3Q.js} +40 -6
- package/dist/kumaVerifier-B4D7NOFT.js +235 -0
- package/dist/{safetyScore-ZAOUPG6A.js → safetyScore-PJGRRBWP.js} +14 -6
- package/dist/{sessionMemory-SKKYUGB6.js → sessionMemory-HBBXUSIP.js} +1 -1
- package/package.json +1 -1
- package/dist/kumaVerifier-SSS4ZTJY.js +0 -243
|
@@ -320,7 +320,7 @@ No unresolved issues.`;
|
|
|
320
320
|
*/
|
|
321
321
|
async autoTrackToDb(toolName, params, timestamp) {
|
|
322
322
|
try {
|
|
323
|
-
const { getDb, saveDb } = await import("./kumaDb-
|
|
323
|
+
const { getDb, saveDb } = await import("./kumaDb-DJUDLYBJ.js");
|
|
324
324
|
const db = await getDb();
|
|
325
325
|
db.run(
|
|
326
326
|
`INSERT INTO tool_calls (tool_name, params, success, duration_ms, created_at) VALUES (?, ?, 1, 0, ?)`,
|
|
@@ -466,14 +466,14 @@ No unresolved issues.`;
|
|
|
466
466
|
const memoriesDir = this.memoriesDir();
|
|
467
467
|
if (fs.existsSync(memoriesDir)) {
|
|
468
468
|
try {
|
|
469
|
-
const files = fs.readdirSync(memoriesDir);
|
|
469
|
+
const files = fs.readdirSync(memoriesDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
470
470
|
for (const file of files) {
|
|
471
|
-
if (!file.endsWith(".md")) continue;
|
|
472
471
|
const filePath = path.join(memoriesDir, file);
|
|
473
472
|
try {
|
|
474
473
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
475
474
|
const lines = content.split("\n");
|
|
476
|
-
|
|
475
|
+
const maxLinesPerFile = 500;
|
|
476
|
+
for (let i = 0; i < Math.min(lines.length, maxLinesPerFile); i++) {
|
|
477
477
|
if (lines[i].toLowerCase().includes(q)) {
|
|
478
478
|
const topicName = file.replace(/\.md$/, "");
|
|
479
479
|
results.push({
|
|
@@ -571,10 +571,18 @@ No unresolved issues.`;
|
|
|
571
571
|
if (recentCalls.length < 6) return { isLooping: false };
|
|
572
572
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
573
573
|
for (const call of recentCalls) {
|
|
574
|
-
|
|
574
|
+
const existing = toolCounts.get(call.toolName);
|
|
575
|
+
const action = call.params.action || "";
|
|
576
|
+
if (existing) {
|
|
577
|
+
existing.count++;
|
|
578
|
+
existing.actions.add(action);
|
|
579
|
+
} else {
|
|
580
|
+
toolCounts.set(call.toolName, { count: 1, actions: /* @__PURE__ */ new Set([action]) });
|
|
581
|
+
}
|
|
575
582
|
}
|
|
576
|
-
for (const [toolName, count] of toolCounts) {
|
|
583
|
+
for (const [toolName, { count, actions }] of toolCounts) {
|
|
577
584
|
if (count >= 4) {
|
|
585
|
+
if (actions.size >= 3) continue;
|
|
578
586
|
return {
|
|
579
587
|
isLooping: true,
|
|
580
588
|
toolName,
|
|
@@ -597,7 +605,7 @@ No unresolved issues.`;
|
|
|
597
605
|
this.addModifiedFile(filePath);
|
|
598
606
|
}
|
|
599
607
|
try {
|
|
600
|
-
const { recordChange } = await import("./kumaDb-
|
|
608
|
+
const { recordChange } = await import("./kumaDb-DJUDLYBJ.js");
|
|
601
609
|
const gitHash = this.getGitHead();
|
|
602
610
|
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
603
611
|
} catch {
|
|
@@ -36,18 +36,29 @@ async function initDb() {
|
|
|
36
36
|
dbInstance = db;
|
|
37
37
|
return db;
|
|
38
38
|
}
|
|
39
|
+
var _saveTimeout = null;
|
|
40
|
+
var _pendingDb = null;
|
|
41
|
+
var SAVE_DEBOUNCE_MS = 100;
|
|
39
42
|
function saveDb(db) {
|
|
40
43
|
const d = db ?? dbInstance;
|
|
41
44
|
if (!d) return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
_pendingDb = d;
|
|
46
|
+
if (_saveTimeout) return;
|
|
47
|
+
_saveTimeout = setTimeout(() => {
|
|
48
|
+
_saveTimeout = null;
|
|
49
|
+
const targetDb = _pendingDb;
|
|
50
|
+
_pendingDb = null;
|
|
51
|
+
if (!targetDb) return;
|
|
52
|
+
try {
|
|
53
|
+
const kumaDir = getKumaDir();
|
|
54
|
+
const dbPath = path.join(kumaDir, DB_FILENAME);
|
|
55
|
+
const data = targetDb.export();
|
|
56
|
+
const buffer = Buffer.from(data);
|
|
57
|
+
fs.writeFileSync(dbPath, buffer);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`[KumaDB] Failed to save database: ${err}`);
|
|
60
|
+
}
|
|
61
|
+
}, SAVE_DEBOUNCE_MS);
|
|
51
62
|
}
|
|
52
63
|
function createSchema(db) {
|
|
53
64
|
db.run(`CREATE TABLE IF NOT EXISTS nodes (
|
|
@@ -835,21 +846,6 @@ async function runDoctor() {
|
|
|
835
846
|
}
|
|
836
847
|
} catch {
|
|
837
848
|
}
|
|
838
|
-
try {
|
|
839
|
-
const { isVerificationRunning, msSinceLastVerification, getRecentCallCount } = await import("./kumaVerifier-SSS4ZTJY.js");
|
|
840
|
-
const running = isVerificationRunning();
|
|
841
|
-
const sinceLast = msSinceLastVerification();
|
|
842
|
-
const callCount = getRecentCallCount();
|
|
843
|
-
checks.push("");
|
|
844
|
-
checks.push("**Verifier Status:**");
|
|
845
|
-
checks.push(` ${running ? "\u{1F504}" : "\u{1F4A4}"} Currently ${running ? "running" : "idle"}`);
|
|
846
|
-
checks.push(` \u23F1\uFE0F Last run: ${sinceLast < 0 ? "never" : `${Math.floor(sinceLast / 1e3)}s ago`}`);
|
|
847
|
-
checks.push(` \u{1F4CA} Calls in 5min: ${callCount} (threshold: 3)`);
|
|
848
|
-
if (callCount >= 3) {
|
|
849
|
-
checks.push(` \u{1F534} **Runaway protection active** \u2014 verifier is blocking further calls`);
|
|
850
|
-
}
|
|
851
|
-
} catch {
|
|
852
|
-
}
|
|
853
849
|
return checks.join("\n");
|
|
854
850
|
} catch (err) {
|
|
855
851
|
return `\u274C Doctor failed: ${err}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb,
|
|
3
3
|
saveDb
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GDNAWLHF.js";
|
|
5
5
|
import {
|
|
6
6
|
getProjectRoot
|
|
7
7
|
} from "./chunk-E2KFPEBT.js";
|
|
@@ -458,7 +458,7 @@ The two nodes may not be connected in the knowledge graph yet. Use more tools to
|
|
|
458
458
|
}
|
|
459
459
|
async function buildFromSessionMemory() {
|
|
460
460
|
try {
|
|
461
|
-
const { sessionMemory } = await import("./sessionMemory-
|
|
461
|
+
const { sessionMemory } = await import("./sessionMemory-HBBXUSIP.js");
|
|
462
462
|
const toolCalls = sessionMemory.getToolCallHistory(50);
|
|
463
463
|
let edgeCount = 0;
|
|
464
464
|
for (const call of toolCalls) {
|
|
@@ -586,8 +586,8 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
586
586
|
return lines.join("\n");
|
|
587
587
|
} catch {
|
|
588
588
|
try {
|
|
589
|
-
let walk2 = function(dir) {
|
|
590
|
-
if (results.length >= limit) return;
|
|
589
|
+
let walk2 = function(dir, depth = 0) {
|
|
590
|
+
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
591
591
|
let entries = [];
|
|
592
592
|
try {
|
|
593
593
|
entries = fs2.readdirSync(dir);
|
|
@@ -600,7 +600,7 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
600
600
|
try {
|
|
601
601
|
const stat = fs2.statSync(full);
|
|
602
602
|
if (stat.isDirectory()) {
|
|
603
|
-
walk2(full);
|
|
603
|
+
walk2(full, depth + 1);
|
|
604
604
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
605
605
|
results.push(path2.relative(root, full));
|
|
606
606
|
}
|
|
@@ -613,7 +613,8 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
613
613
|
const path2 = await import("path");
|
|
614
614
|
const root = process.cwd();
|
|
615
615
|
const results = [];
|
|
616
|
-
|
|
616
|
+
const MAX_WALK_DEPTH = 8;
|
|
617
|
+
walk2(root, 0);
|
|
617
618
|
if (results.length === 0) {
|
|
618
619
|
return `\u{1F50D} **Codebase Search** \u2014 No results for "${query}". Try a different search term.`;
|
|
619
620
|
}
|
|
@@ -744,7 +745,7 @@ async function codebaseImpactFallback(target) {
|
|
|
744
745
|
const root = process.cwd();
|
|
745
746
|
const grepResult = execSync2(
|
|
746
747
|
`grep -rn --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" --include="*.json" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
|
|
747
|
-
{ cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer:
|
|
748
|
+
{ cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
|
|
748
749
|
).trim();
|
|
749
750
|
const files = grepResult ? grepResult.split("\n").filter(Boolean).filter((f) => !f.startsWith("./")) : [];
|
|
750
751
|
if (files.length === 0) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionMemory
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BI7KD3SG.js";
|
|
4
4
|
import {
|
|
5
5
|
getProjectRoot
|
|
6
6
|
} from "./chunk-E2KFPEBT.js";
|
|
@@ -82,7 +82,7 @@ function recordDecision(decision) {
|
|
|
82
82
|
}
|
|
83
83
|
async function recordDecisionToGraph(decision) {
|
|
84
84
|
try {
|
|
85
|
-
const { upsertNode, addEdge } = await import("./kumaGraph-
|
|
85
|
+
const { upsertNode, addEdge } = await import("./kumaGraph-35TAIBWD.js");
|
|
86
86
|
const decisionId = `decision::${decision.title.replace(/[^a-zA-Z0-9_\-\s]/g, "").trim().replace(/\s+/g, "-")}`;
|
|
87
87
|
await upsertNode({
|
|
88
88
|
id: decisionId,
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,13 @@ import {
|
|
|
14
14
|
getGraphStats,
|
|
15
15
|
searchGraph,
|
|
16
16
|
traceFlow
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-K64NSHBR.js";
|
|
18
|
+
import {
|
|
19
|
+
buildDriftMessages,
|
|
20
|
+
getGitDiffStat,
|
|
21
|
+
getSessionStats,
|
|
22
|
+
getUnresolvedCount
|
|
23
|
+
} from "./chunk-FOQQ2CSL.js";
|
|
18
24
|
import {
|
|
19
25
|
addContextNote,
|
|
20
26
|
addSecurityFinding,
|
|
@@ -40,22 +46,16 @@ import {
|
|
|
40
46
|
saveResearchCache,
|
|
41
47
|
updateDecisionStatus,
|
|
42
48
|
updateTodoStatus
|
|
43
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-GDNAWLHF.js";
|
|
44
50
|
import {
|
|
45
51
|
formatDecisionTemplate,
|
|
46
52
|
getProactiveMemories,
|
|
47
53
|
recordDecision,
|
|
48
54
|
scoreMemoryRelevance
|
|
49
|
-
} from "./chunk-
|
|
50
|
-
import {
|
|
51
|
-
buildDriftMessages,
|
|
52
|
-
getGitDiffStat,
|
|
53
|
-
getSessionStats,
|
|
54
|
-
getUnresolvedCount
|
|
55
|
-
} from "./chunk-BKEDQFEH.js";
|
|
55
|
+
} from "./chunk-X5TPBDKO.js";
|
|
56
56
|
import {
|
|
57
57
|
sessionMemory
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-BI7KD3SG.js";
|
|
59
59
|
import {
|
|
60
60
|
getProjectRoot,
|
|
61
61
|
validateFilePath
|
|
@@ -111,7 +111,13 @@ var CONTEXT_ALIASES = {
|
|
|
111
111
|
// Researches synonyms
|
|
112
112
|
"researches": "researches",
|
|
113
113
|
"research-list": "researches",
|
|
114
|
-
"list-research": "researches"
|
|
114
|
+
"list-research": "researches",
|
|
115
|
+
// Sync/Batch synonyms (Issue #12)
|
|
116
|
+
"sync": "sync",
|
|
117
|
+
"batch": "sync",
|
|
118
|
+
"kuma_sync": "sync",
|
|
119
|
+
"unified": "sync",
|
|
120
|
+
"state": "sync"
|
|
115
121
|
};
|
|
116
122
|
async function handleContext(params) {
|
|
117
123
|
const rawAction = params.action || "init";
|
|
@@ -134,8 +140,10 @@ async function handleContext(params) {
|
|
|
134
140
|
return handleResearches(params);
|
|
135
141
|
case "health":
|
|
136
142
|
return handleHealth(params);
|
|
143
|
+
case "sync":
|
|
144
|
+
return handleSync(params);
|
|
137
145
|
default:
|
|
138
|
-
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health`;
|
|
146
|
+
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync`;
|
|
139
147
|
}
|
|
140
148
|
}
|
|
141
149
|
async function handleInit(_params) {
|
|
@@ -196,8 +204,8 @@ async function handleInit(_params) {
|
|
|
196
204
|
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
197
205
|
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
198
206
|
try {
|
|
199
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
200
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
207
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
|
|
208
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-DJUDLYBJ.js");
|
|
201
209
|
const score = await computeSafetyScore(_params.goal);
|
|
202
210
|
const checksStr = JSON.stringify(score.checks);
|
|
203
211
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -370,8 +378,8 @@ async function handleResearches(_params) {
|
|
|
370
378
|
async function handleHealth(_params) {
|
|
371
379
|
sessionMemory.recordToolCall("kuma_context_health", {});
|
|
372
380
|
try {
|
|
373
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
374
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
381
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
|
|
382
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-DJUDLYBJ.js");
|
|
375
383
|
const score = await computeSafetyScore();
|
|
376
384
|
const checksStr = JSON.stringify(score.checks);
|
|
377
385
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -380,6 +388,55 @@ async function handleHealth(_params) {
|
|
|
380
388
|
return `Error computing health: ${err}`;
|
|
381
389
|
}
|
|
382
390
|
}
|
|
391
|
+
async function handleSync(params) {
|
|
392
|
+
sessionMemory.setGoal(params.goal || "Sync session");
|
|
393
|
+
sessionMemory.recordToolCall("kuma_context_sync", { goal: params.goal });
|
|
394
|
+
const lines = [
|
|
395
|
+
"\u{1F504} **Kuma Sync \u2014 Unified State**",
|
|
396
|
+
`\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`,
|
|
397
|
+
"",
|
|
398
|
+
`\u{1F4C1} Project: ${getProjectRoot().split("/").pop() || "unknown"}`,
|
|
399
|
+
`\u{1F550} Session: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
400
|
+
""
|
|
401
|
+
];
|
|
402
|
+
const summary = sessionMemory.getSummary();
|
|
403
|
+
lines.push("**Session State**");
|
|
404
|
+
lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
|
|
405
|
+
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
406
|
+
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
407
|
+
lines.push("");
|
|
408
|
+
try {
|
|
409
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
|
|
410
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-DJUDLYBJ.js");
|
|
411
|
+
const score = await computeSafetyScore(params.goal);
|
|
412
|
+
const checksStr = JSON.stringify(score.checks);
|
|
413
|
+
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
414
|
+
lines.push("**Health Score**");
|
|
415
|
+
lines.push(formatSafetyScore(score));
|
|
416
|
+
} catch {
|
|
417
|
+
lines.push("**Health Score**: \u26A0\uFE0F Could not compute");
|
|
418
|
+
}
|
|
419
|
+
lines.push("");
|
|
420
|
+
try {
|
|
421
|
+
const { getGraphStats: getGraphStats2 } = await import("./kumaGraph-35TAIBWD.js");
|
|
422
|
+
lines.push("**Knowledge Graph**");
|
|
423
|
+
lines.push(await getGraphStats2());
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
lines.push("");
|
|
427
|
+
try {
|
|
428
|
+
const { getProactiveMemories: getProactiveMemories2 } = await import("./kumaMemory-5SR3TGRL.js");
|
|
429
|
+
const memories = getProactiveMemories2();
|
|
430
|
+
if (memories) {
|
|
431
|
+
lines.push("**Relevant Memories**");
|
|
432
|
+
lines.push(memories);
|
|
433
|
+
lines.push("");
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
lines.push("\u{1F4A1} Sync complete \u2014 all state captured in a single roundtrip.");
|
|
438
|
+
return lines.join("\n");
|
|
439
|
+
}
|
|
383
440
|
function computeProjectHash(scope) {
|
|
384
441
|
try {
|
|
385
442
|
const root = getProjectRoot();
|
|
@@ -501,7 +558,7 @@ async function handleDecision(params) {
|
|
|
501
558
|
case "template":
|
|
502
559
|
return formatDecisionTemplate();
|
|
503
560
|
case "suggest": {
|
|
504
|
-
const { shouldRecordDecision } = await import("./kumaMemory-
|
|
561
|
+
const { shouldRecordDecision } = await import("./kumaMemory-5SR3TGRL.js");
|
|
505
562
|
const check = shouldRecordDecision();
|
|
506
563
|
return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
|
|
507
564
|
Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
|
|
@@ -579,7 +636,7 @@ async function handleSearch(params) {
|
|
|
579
636
|
if (!query) return "\u26A0\uFE0F query or scope parameter required.";
|
|
580
637
|
const limit = params.limit || 20;
|
|
581
638
|
const memResults = sessionMemory.searchMemory(query, limit);
|
|
582
|
-
const { searchGraph: searchGraph2 } = await import("./kumaGraph-
|
|
639
|
+
const { searchGraph: searchGraph2 } = await import("./kumaGraph-35TAIBWD.js");
|
|
583
640
|
const graphResults = await searchGraph2(query, Math.min(limit, 10));
|
|
584
641
|
const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
|
|
585
642
|
\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
|
|
@@ -654,7 +711,7 @@ async function handleDecisionLog(params) {
|
|
|
654
711
|
}
|
|
655
712
|
async function handleMine(params) {
|
|
656
713
|
sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
|
|
657
|
-
const { mineHistoricalDecisions } = await import("./kumaMiner-
|
|
714
|
+
const { mineHistoricalDecisions } = await import("./kumaMiner-BIDSZE3Q.js");
|
|
658
715
|
return await mineHistoricalDecisions({
|
|
659
716
|
scope: params.scope,
|
|
660
717
|
since: typeof params.since === "string" ? params.since : void 0,
|
|
@@ -1791,7 +1848,7 @@ async function handleLock(params) {
|
|
|
1791
1848
|
}
|
|
1792
1849
|
async function handleHealth2(_params) {
|
|
1793
1850
|
try {
|
|
1794
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
1851
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-PJGRRBWP.js");
|
|
1795
1852
|
const score = await computeSafetyScore();
|
|
1796
1853
|
await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
|
|
1797
1854
|
return formatSafetyScore(score);
|
|
@@ -1905,7 +1962,7 @@ async function handleVerify(params) {
|
|
|
1905
1962
|
}
|
|
1906
1963
|
_lastVerifyCall = now;
|
|
1907
1964
|
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
1908
|
-
const { runAutoVerification } = await import("./kumaVerifier-
|
|
1965
|
+
const { runAutoVerification } = await import("./kumaVerifier-B4D7NOFT.js");
|
|
1909
1966
|
return await runAutoVerification({
|
|
1910
1967
|
scope: params.scope || params.filePath,
|
|
1911
1968
|
force: params.force,
|
|
@@ -2102,8 +2159,9 @@ async function main() {
|
|
|
2102
2159
|
if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
|
|
2103
2160
|
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
2104
2161
|
console.error("");
|
|
2162
|
+
let killedCount = 0;
|
|
2105
2163
|
try {
|
|
2106
|
-
const { getRunningVerificationPid } = await import("./kumaVerifier-
|
|
2164
|
+
const { getRunningVerificationPid } = await import("./kumaVerifier-B4D7NOFT.js");
|
|
2107
2165
|
const pid = getRunningVerificationPid();
|
|
2108
2166
|
if (pid) {
|
|
2109
2167
|
try {
|
|
@@ -2114,10 +2172,41 @@ async function main() {
|
|
|
2114
2172
|
process.kill(pid, "SIGKILL");
|
|
2115
2173
|
} catch {
|
|
2116
2174
|
}
|
|
2117
|
-
console.error(`\u2705 Killed verification process (PID: ${pid})`);
|
|
2175
|
+
console.error(`\u2705 Killed local verification process (PID: ${pid})`);
|
|
2176
|
+
killedCount++;
|
|
2118
2177
|
}
|
|
2119
|
-
} catch
|
|
2120
|
-
|
|
2178
|
+
} catch {
|
|
2179
|
+
}
|
|
2180
|
+
try {
|
|
2181
|
+
const path7 = await import("path");
|
|
2182
|
+
const fs7 = await import("fs");
|
|
2183
|
+
const lockDir = path7.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
2184
|
+
if (fs7.existsSync(lockDir)) {
|
|
2185
|
+
const pidFile = path7.join(lockDir, "pid");
|
|
2186
|
+
if (fs7.existsSync(pidFile)) {
|
|
2187
|
+
try {
|
|
2188
|
+
const pid = parseInt(fs7.readFileSync(pidFile, "utf-8"), 10);
|
|
2189
|
+
if (pid && pid !== process.pid) {
|
|
2190
|
+
try {
|
|
2191
|
+
process.kill(-pid, "SIGKILL");
|
|
2192
|
+
} catch {
|
|
2193
|
+
}
|
|
2194
|
+
try {
|
|
2195
|
+
process.kill(pid, "SIGKILL");
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
console.error(`\u2705 Killed other instance verification (PID: ${pid})`);
|
|
2199
|
+
killedCount++;
|
|
2200
|
+
}
|
|
2201
|
+
} catch {
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
fs7.rmSync(lockDir, { recursive: true, force: true });
|
|
2205
|
+
}
|
|
2206
|
+
if (killedCount === 0) {
|
|
2207
|
+
console.error("\u2705 No running verifications found to kill.");
|
|
2208
|
+
}
|
|
2209
|
+
} catch {
|
|
2121
2210
|
}
|
|
2122
2211
|
try {
|
|
2123
2212
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -2311,7 +2400,7 @@ async function main() {
|
|
|
2311
2400
|
console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
|
|
2312
2401
|
}
|
|
2313
2402
|
try {
|
|
2314
|
-
const { buildFromSessionMemory } = await import("./kumaGraph-
|
|
2403
|
+
const { buildFromSessionMemory } = await import("./kumaGraph-35TAIBWD.js");
|
|
2315
2404
|
const edgeCount = await buildFromSessionMemory();
|
|
2316
2405
|
if (edgeCount > 0) {
|
|
2317
2406
|
console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
|
|
@@ -2331,7 +2420,7 @@ async function main() {
|
|
|
2331
2420
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
|
|
2332
2421
|
}
|
|
2333
2422
|
try {
|
|
2334
|
-
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-
|
|
2423
|
+
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-DJUDLYBJ.js");
|
|
2335
2424
|
const db = await getDb2();
|
|
2336
2425
|
db.run(
|
|
2337
2426
|
`INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
recordDecision,
|
|
5
5
|
scoreMemoryRelevance,
|
|
6
6
|
shouldRecordDecision
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-X5TPBDKO.js";
|
|
8
|
+
import "./chunk-BI7KD3SG.js";
|
|
9
9
|
import "./chunk-E2KFPEBT.js";
|
|
10
10
|
export {
|
|
11
11
|
formatDecisionTemplate,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
recordDecisionLog
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GDNAWLHF.js";
|
|
4
4
|
import {
|
|
5
5
|
recordDecision
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-X5TPBDKO.js";
|
|
7
|
+
import "./chunk-BI7KD3SG.js";
|
|
8
8
|
import "./chunk-E2KFPEBT.js";
|
|
9
9
|
|
|
10
10
|
// src/engine/kumaMiner.ts
|
|
@@ -19,9 +19,10 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
19
19
|
try {
|
|
20
20
|
const sinceFlag = options.since ? `--since="${options.since}"` : `--since="1 year"`;
|
|
21
21
|
const gitCmd = `git log ${sinceFlag} -n 100 --pretty=format:"%h|%an|%ad|%s" --date=short`;
|
|
22
|
-
const gitOutput = execSync(gitCmd, { cwd: root, encoding: "utf-8" });
|
|
22
|
+
const gitOutput = execSync(gitCmd, { cwd: root, encoding: "utf-8", timeout: 15e3 });
|
|
23
23
|
const keywords = ["fix", "revert", "hack", "workaround", "urgent", "hotfix", "don't touch", "temporary", "deprecated", "workaround"];
|
|
24
24
|
const lines = gitOutput.split("\n").filter(Boolean);
|
|
25
|
+
const matchingHashes = [];
|
|
25
26
|
for (const line of lines) {
|
|
26
27
|
const parts = line.split("|");
|
|
27
28
|
if (parts.length < 4) continue;
|
|
@@ -29,9 +30,42 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
29
30
|
const lowerSubj = subject.toLowerCase();
|
|
30
31
|
const matchedKeyword = keywords.find((kw) => lowerSubj.includes(kw));
|
|
31
32
|
if (matchedKeyword) {
|
|
32
|
-
|
|
33
|
+
matchingHashes.push({ hash, author, date, subject, keyword: matchedKeyword });
|
|
34
|
+
if (matchingHashes.length >= limit) break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
let changedFilesMap = /* @__PURE__ */ new Map();
|
|
38
|
+
if (matchingHashes.length > 0) {
|
|
39
|
+
try {
|
|
40
|
+
const hashesStr = matchingHashes.map((h) => h.hash).join(" ");
|
|
41
|
+
const batchOutput = execSync(
|
|
42
|
+
`git show --name-only --oneline ${hashesStr} 2>/dev/null | head -200`,
|
|
43
|
+
{ cwd: root, encoding: "utf-8", timeout: 1e4, maxBuffer: 256 * 1024 }
|
|
44
|
+
);
|
|
45
|
+
let currentHash = "";
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const line of batchOutput.split("\n")) {
|
|
48
|
+
if (matchingHashes.find((h) => line.startsWith(h.hash))) {
|
|
49
|
+
if (currentHash && files.length > 0) {
|
|
50
|
+
changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
|
|
51
|
+
}
|
|
52
|
+
currentHash = line.split(" ")[0];
|
|
53
|
+
files.length = 0;
|
|
54
|
+
} else if (line.trim() && !line.startsWith("diff")) {
|
|
55
|
+
files.push(line.trim());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (currentHash && files.length > 0) {
|
|
59
|
+
changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const { hash, author, date, subject, keyword: matchedKeyword } of matchingHashes) {
|
|
65
|
+
let changedFiles = changedFilesMap.get(hash) || "";
|
|
66
|
+
if (!changedFiles) {
|
|
33
67
|
try {
|
|
34
|
-
changedFiles = execSync(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8" }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
|
|
68
|
+
changedFiles = execSync(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8", timeout: 5e3 }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
|
|
35
69
|
} catch {
|
|
36
70
|
}
|
|
37
71
|
candidates.push({
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLatestVerifications,
|
|
3
|
+
saveVerification
|
|
4
|
+
} from "./chunk-GDNAWLHF.js";
|
|
5
|
+
import {
|
|
6
|
+
sessionMemory
|
|
7
|
+
} from "./chunk-BI7KD3SG.js";
|
|
8
|
+
import "./chunk-E2KFPEBT.js";
|
|
9
|
+
|
|
10
|
+
// src/engine/kumaVerifier.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { exec } from "child_process";
|
|
14
|
+
var LOCK_DIR_NAME = ".kuma/verifier.lock";
|
|
15
|
+
function getLockDir(root = process.cwd()) {
|
|
16
|
+
return path.resolve(root, LOCK_DIR_NAME);
|
|
17
|
+
}
|
|
18
|
+
function getPidFile(root = process.cwd()) {
|
|
19
|
+
return path.join(getLockDir(root), "pid");
|
|
20
|
+
}
|
|
21
|
+
function acquireFileLock(root) {
|
|
22
|
+
const lockDir = getLockDir(root);
|
|
23
|
+
try {
|
|
24
|
+
fs.mkdirSync(lockDir, { recursive: false });
|
|
25
|
+
fs.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(getPidFile(root))) {
|
|
30
|
+
const pid = parseInt(fs.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
31
|
+
try {
|
|
32
|
+
process.kill(pid, 0);
|
|
33
|
+
return false;
|
|
34
|
+
} catch {
|
|
35
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
36
|
+
return acquireFileLock(root);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
43
|
+
return acquireFileLock(root);
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function releaseFileLock(root) {
|
|
50
|
+
try {
|
|
51
|
+
fs.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
var _localRunning = false;
|
|
56
|
+
var _currentProcess = null;
|
|
57
|
+
var STALE_RESULT_MS = 3e5;
|
|
58
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
59
|
+
function getRunningVerificationPid() {
|
|
60
|
+
return _currentProcess?.pid ?? null;
|
|
61
|
+
}
|
|
62
|
+
function detectTestRunner(root = process.cwd()) {
|
|
63
|
+
const pkgPath = path.join(root, "package.json");
|
|
64
|
+
if (fs.existsSync(pkgPath)) {
|
|
65
|
+
try {
|
|
66
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
67
|
+
if (pkg.scripts && pkg.scripts.test) {
|
|
68
|
+
if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
69
|
+
if (fs.existsSync(path.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
70
|
+
return { runner: "npm", baseCommand: "npm test" };
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (fs.existsSync(path.join(root, "pytest.ini")) || fs.existsSync(path.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
76
|
+
if (fs.existsSync(path.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
77
|
+
if (fs.existsSync(path.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
78
|
+
if (fs.existsSync(path.join(root, "Makefile"))) return { runner: "make", baseCommand: "make test" };
|
|
79
|
+
if (fs.existsSync(path.join(root, "package.json"))) {
|
|
80
|
+
try {
|
|
81
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
|
|
82
|
+
if (pkg.dependencies || pkg.devDependencies) {
|
|
83
|
+
if (fs.existsSync(path.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
84
|
+
const srcDir = path.join(root, "src");
|
|
85
|
+
if (fs.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { runner: "unknown", baseCommand: "npm test || echo 'No test runner configured'" };
|
|
91
|
+
}
|
|
92
|
+
function checkAllowed(root) {
|
|
93
|
+
if (_localRunning) return "\u23F3 Verification already in progress (this instance).";
|
|
94
|
+
if (!acquireFileLock(root)) return "\u23F3 Another Kuma instance is running verification.";
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
async function checkStaleness(scope) {
|
|
98
|
+
if (scope !== "session-impact" && scope !== void 0) return null;
|
|
99
|
+
try {
|
|
100
|
+
const recent = await getLatestVerifications(1);
|
|
101
|
+
if (recent.length === 0) return null;
|
|
102
|
+
const age = Date.now() - recent[0].created_at * 1e3;
|
|
103
|
+
if (age >= STALE_RESULT_MS) return null;
|
|
104
|
+
const ageSeconds = Math.floor(age / 1e3);
|
|
105
|
+
return [
|
|
106
|
+
`\u23E9 **Using cached verification** (${ageSeconds}s old, < 5 min threshold)`,
|
|
107
|
+
`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
108
|
+
"",
|
|
109
|
+
`\u{1F6E0}\uFE0F Runner: \`${recent[0].runner}\``,
|
|
110
|
+
`\u{1F4BB} Command: \`${recent[0].test_command}\``,
|
|
111
|
+
`\u{1F3AF} Scope: \`${recent[0].scope}\``,
|
|
112
|
+
`\u23F1\uFE0F Original duration: ${recent[0].duration_ms}ms`,
|
|
113
|
+
"",
|
|
114
|
+
recent[0].passed ? "\u2705 **Result: PASSED** (served from cache)" : "\u{1F534} **Result: FAILED** (served from cache)",
|
|
115
|
+
"",
|
|
116
|
+
"\u{1F4A1} Use `force: true` to bypass cache and run tests again."
|
|
117
|
+
].join("\n");
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function runAutoVerification(options = {}) {
|
|
123
|
+
const startTime = Date.now();
|
|
124
|
+
const root = process.cwd();
|
|
125
|
+
const scope = options.scope || "session-impact";
|
|
126
|
+
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
127
|
+
const denial = checkAllowed(root);
|
|
128
|
+
if (denial) return denial;
|
|
129
|
+
try {
|
|
130
|
+
if (!options.force) {
|
|
131
|
+
const cached = await checkStaleness(scope);
|
|
132
|
+
if (cached) {
|
|
133
|
+
releaseFileLock(root);
|
|
134
|
+
return cached;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
_localRunning = true;
|
|
138
|
+
const { runner, baseCommand } = detectTestRunner(root);
|
|
139
|
+
let testFiles = [];
|
|
140
|
+
const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
141
|
+
if (options.scope) {
|
|
142
|
+
const term = options.scope.toLowerCase();
|
|
143
|
+
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
144
|
+
if (testFiles.length === 0) {
|
|
145
|
+
try {
|
|
146
|
+
const { default: glob } = await import("fast-glob");
|
|
147
|
+
testFiles = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
153
|
+
}
|
|
154
|
+
let fullCommand = baseCommand;
|
|
155
|
+
if (testFiles.length > 0 && runner === "npm") {
|
|
156
|
+
fullCommand = `${baseCommand} -- ${testFiles.map((f) => `"${f}"`).join(" ")}`;
|
|
157
|
+
}
|
|
158
|
+
return await new Promise((resolve) => {
|
|
159
|
+
const killTimer = setTimeout(() => {
|
|
160
|
+
if (_currentProcess && !_currentProcess.killed) {
|
|
161
|
+
try {
|
|
162
|
+
const childPid = _currentProcess.pid;
|
|
163
|
+
if (childPid) {
|
|
164
|
+
try {
|
|
165
|
+
process.kill(-childPid, "SIGKILL");
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
_currentProcess.kill("SIGKILL");
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}, timeoutMs + 5e3);
|
|
177
|
+
const heartbeatTimer = setInterval(() => {
|
|
178
|
+
if (getLockDir(root)) {
|
|
179
|
+
try {
|
|
180
|
+
fs.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}, 15e3);
|
|
185
|
+
const child = exec(fullCommand, { cwd: root, timeout: timeoutMs }, async (error, stdout, stderr) => {
|
|
186
|
+
const durationMs = Date.now() - startTime;
|
|
187
|
+
const rawOutput = (stdout + "\n" + stderr).trim();
|
|
188
|
+
const passed = !error;
|
|
189
|
+
const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
|
|
190
|
+
await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
|
|
191
|
+
_localRunning = false;
|
|
192
|
+
_currentProcess = null;
|
|
193
|
+
releaseFileLock(root);
|
|
194
|
+
clearInterval(heartbeatTimer);
|
|
195
|
+
const statusSymbol = passed ? "\u2705" : "\u{1F534}";
|
|
196
|
+
const lines = [
|
|
197
|
+
`${statusSymbol} **Verification ${passed ? "PASSED" : "FAILED"}**`,
|
|
198
|
+
`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
199
|
+
"",
|
|
200
|
+
`\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
|
|
201
|
+
`\u{1F4BB} **Command**: \`${fullCommand}\``,
|
|
202
|
+
`\u{1F3AF} **Scope**: \`${scope}\`${testFiles.length > 0 ? ` (${testFiles.length} file(s) matched)` : ""}`,
|
|
203
|
+
`\u23F1\uFE0F **Duration**: ${durationMs}ms`,
|
|
204
|
+
""
|
|
205
|
+
];
|
|
206
|
+
if (!passed) {
|
|
207
|
+
lines.push("\u26A0\uFE0F **Verification failed!** Please fix test failures before shipping.");
|
|
208
|
+
if (error && error.killed) {
|
|
209
|
+
lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout**`);
|
|
210
|
+
}
|
|
211
|
+
lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
212
|
+
} else {
|
|
213
|
+
lines.push("\u{1F389} All scoped tests passed!");
|
|
214
|
+
if (rawOutput) lines.push("```text", rawOutput.substring(0, 800), "```");
|
|
215
|
+
}
|
|
216
|
+
resolve(lines.join("\n"));
|
|
217
|
+
});
|
|
218
|
+
_currentProcess = child;
|
|
219
|
+
_localRunning = true;
|
|
220
|
+
child.on("close", () => {
|
|
221
|
+
clearTimeout(killTimer);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
} catch (err) {
|
|
225
|
+
releaseFileLock(root);
|
|
226
|
+
_localRunning = false;
|
|
227
|
+
_currentProcess = null;
|
|
228
|
+
return `\u274C Verification failed with error: ${err}`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
detectTestRunner,
|
|
233
|
+
getRunningVerificationPid,
|
|
234
|
+
runAutoVerification
|
|
235
|
+
};
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getGitDiffStat,
|
|
3
3
|
getSessionStats,
|
|
4
4
|
getUnresolvedCount
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FOQQ2CSL.js";
|
|
6
6
|
import {
|
|
7
7
|
sessionMemory
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BI7KD3SG.js";
|
|
9
9
|
import "./chunk-E2KFPEBT.js";
|
|
10
10
|
|
|
11
11
|
// src/engine/safetyScore.ts
|
|
@@ -15,7 +15,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
15
15
|
let totalScore = 0;
|
|
16
16
|
const maxScore = 100;
|
|
17
17
|
const gitStat = getGitDiffStat();
|
|
18
|
-
if (gitStat) {
|
|
18
|
+
if (gitStat && gitStat !== "clean") {
|
|
19
19
|
const lines = gitStat.split("\n").filter(Boolean).length;
|
|
20
20
|
if (lines === 0) {
|
|
21
21
|
checks.push({
|
|
@@ -42,6 +42,14 @@ async function computeSafetyScore(inputGoal) {
|
|
|
42
42
|
});
|
|
43
43
|
totalScore += 10;
|
|
44
44
|
}
|
|
45
|
+
} else if (gitStat === "clean") {
|
|
46
|
+
checks.push({
|
|
47
|
+
label: "Git Clean",
|
|
48
|
+
status: "pass",
|
|
49
|
+
message: "Working tree is clean",
|
|
50
|
+
weight: 20
|
|
51
|
+
});
|
|
52
|
+
totalScore += 20;
|
|
45
53
|
} else {
|
|
46
54
|
checks.push({
|
|
47
55
|
label: "Git Status",
|
|
@@ -52,7 +60,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
52
60
|
totalScore += 20;
|
|
53
61
|
}
|
|
54
62
|
try {
|
|
55
|
-
const { getDb } = await import("./kumaDb-
|
|
63
|
+
const { getDb } = await import("./kumaDb-DJUDLYBJ.js");
|
|
56
64
|
const db = await getDb();
|
|
57
65
|
const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
|
|
58
66
|
const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
|
|
@@ -83,7 +91,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
83
91
|
totalScore += 5;
|
|
84
92
|
}
|
|
85
93
|
try {
|
|
86
|
-
const { getDb } = await import("./kumaDb-
|
|
94
|
+
const { getDb } = await import("./kumaDb-DJUDLYBJ.js");
|
|
87
95
|
const db = await getDb();
|
|
88
96
|
const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
|
|
89
97
|
checks.push({
|
|
@@ -108,7 +116,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
108
116
|
const hasRunTests = stats.hasRunTests;
|
|
109
117
|
let latestVerif = null;
|
|
110
118
|
try {
|
|
111
|
-
const { getLatestVerifications } = await import("./kumaDb-
|
|
119
|
+
const { getLatestVerifications } = await import("./kumaDb-DJUDLYBJ.js");
|
|
112
120
|
const verifs = await getLatestVerifications(1);
|
|
113
121
|
if (verifs.length > 0) latestVerif = verifs[0];
|
|
114
122
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
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,243 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getLatestVerifications,
|
|
3
|
-
saveVerification
|
|
4
|
-
} from "./chunk-NKNXJG2E.js";
|
|
5
|
-
import {
|
|
6
|
-
sessionMemory
|
|
7
|
-
} from "./chunk-77G3APNG.js";
|
|
8
|
-
import "./chunk-E2KFPEBT.js";
|
|
9
|
-
|
|
10
|
-
// src/engine/kumaVerifier.ts
|
|
11
|
-
import fs from "fs";
|
|
12
|
-
import path from "path";
|
|
13
|
-
import { exec } from "child_process";
|
|
14
|
-
var _isRunning = false;
|
|
15
|
-
var _lastCompletedAt = 0;
|
|
16
|
-
var _callHistory = [];
|
|
17
|
-
var RUNAWAY_WINDOW_MS = 5 * 60 * 1e3;
|
|
18
|
-
var RUNAWAY_THRESHOLD = 3;
|
|
19
|
-
var MIN_INTERVAL_MS = 6e4;
|
|
20
|
-
var STALE_RESULT_MS = 3e5;
|
|
21
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
22
|
-
var _currentProcess = null;
|
|
23
|
-
function getRunningVerificationPid() {
|
|
24
|
-
return _currentProcess?.pid ?? null;
|
|
25
|
-
}
|
|
26
|
-
function isVerificationRunning() {
|
|
27
|
-
return _isRunning;
|
|
28
|
-
}
|
|
29
|
-
function msSinceLastVerification() {
|
|
30
|
-
if (_lastCompletedAt === 0) return -1;
|
|
31
|
-
return Date.now() - _lastCompletedAt;
|
|
32
|
-
}
|
|
33
|
-
function getRecentCallCount() {
|
|
34
|
-
const cutoff = Date.now() - RUNAWAY_WINDOW_MS;
|
|
35
|
-
return _callHistory.filter((t) => t > cutoff).length;
|
|
36
|
-
}
|
|
37
|
-
function detectTestRunner(root = process.cwd()) {
|
|
38
|
-
const pkgPath = path.join(root, "package.json");
|
|
39
|
-
if (fs.existsSync(pkgPath)) {
|
|
40
|
-
try {
|
|
41
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
42
|
-
if (pkg.scripts && pkg.scripts.test) {
|
|
43
|
-
if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
44
|
-
return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
45
|
-
}
|
|
46
|
-
if (fs.existsSync(path.join(root, "yarn.lock"))) {
|
|
47
|
-
return { runner: "yarn", baseCommand: "yarn test" };
|
|
48
|
-
}
|
|
49
|
-
return { runner: "npm", baseCommand: "npm test" };
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (fs.existsSync(path.join(root, "pytest.ini")) || fs.existsSync(path.join(root, "pyproject.toml"))) {
|
|
55
|
-
return { runner: "pytest", baseCommand: "pytest" };
|
|
56
|
-
}
|
|
57
|
-
if (fs.existsSync(path.join(root, "Cargo.toml"))) {
|
|
58
|
-
return { runner: "cargo", baseCommand: "cargo test" };
|
|
59
|
-
}
|
|
60
|
-
if (fs.existsSync(path.join(root, "go.mod"))) {
|
|
61
|
-
return { runner: "go", baseCommand: "go test ./..." };
|
|
62
|
-
}
|
|
63
|
-
if (fs.existsSync(path.join(root, "Makefile"))) {
|
|
64
|
-
return { runner: "make", baseCommand: "make test" };
|
|
65
|
-
}
|
|
66
|
-
if (fs.existsSync(path.join(root, "package.json"))) {
|
|
67
|
-
try {
|
|
68
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
|
|
69
|
-
if (pkg.dependencies || pkg.devDependencies) {
|
|
70
|
-
if (fs.existsSync(path.join(root, "tsconfig.json"))) {
|
|
71
|
-
return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
72
|
-
}
|
|
73
|
-
const srcDir = path.join(root, "src");
|
|
74
|
-
if (fs.existsSync(srcDir)) {
|
|
75
|
-
return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return { runner: "unknown", baseCommand: "npm test || echo 'No test runner configured'" };
|
|
82
|
-
}
|
|
83
|
-
function checkRateLimit() {
|
|
84
|
-
if (_isRunning) {
|
|
85
|
-
return "\u23F3 Verification already in progress \u2014 only 1 verification at a time. Wait for it to complete, or try again later.";
|
|
86
|
-
}
|
|
87
|
-
const cutoff = Date.now() - RUNAWAY_WINDOW_MS;
|
|
88
|
-
const recent = _callHistory.filter((t) => t > cutoff);
|
|
89
|
-
if (recent.length >= RUNAWAY_THRESHOLD) {
|
|
90
|
-
return `\u{1F534} **Runaway Detection Triggered**
|
|
91
|
-
|
|
92
|
-
Verification has been called ${recent.length} times in the last 5 minutes. This looks like an uncontrolled loop. Verification has been **blocked** to prevent resource exhaustion.
|
|
93
|
-
|
|
94
|
-
\u{1F4A1} If you need to verify, wait ${Math.ceil((RUNAWAY_WINDOW_MS - (Date.now() - recent[0])) / 6e4)} minutes, or run \`kuma_safety({ action: "doctor" })\` to check system status.
|
|
95
|
-
|
|
96
|
-
\u{1F6A8} If this is unexpected, run \`pkill -f "pnpm test"\` to kill orphaned processes.`;
|
|
97
|
-
}
|
|
98
|
-
if (_lastCompletedAt > 0) {
|
|
99
|
-
const elapsed = Date.now() - _lastCompletedAt;
|
|
100
|
-
if (elapsed < MIN_INTERVAL_MS) {
|
|
101
|
-
const remaining = Math.ceil((MIN_INTERVAL_MS - elapsed) / 1e3);
|
|
102
|
-
return `\u23F3 Rate limit: verification was run ${Math.floor(elapsed / 1e3)}s ago. Please wait ${remaining}s before running verification again. (Minimum interval: ${MIN_INTERVAL_MS / 1e3}s)`;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
async function checkStaleness(scope) {
|
|
108
|
-
if (scope === "session-impact" || scope === void 0) {
|
|
109
|
-
try {
|
|
110
|
-
const recent = await getLatestVerifications(1);
|
|
111
|
-
if (recent.length > 0) {
|
|
112
|
-
const age = Date.now() - recent[0].created_at * 1e3;
|
|
113
|
-
if (age < STALE_RESULT_MS) {
|
|
114
|
-
const ageSeconds = Math.floor(age / 1e3);
|
|
115
|
-
return [
|
|
116
|
-
`\u23E9 **Using cached verification** (${ageSeconds}s old, < 5 min threshold)`,
|
|
117
|
-
`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
118
|
-
"",
|
|
119
|
-
`\u{1F6E0}\uFE0F Runner: \`${recent[0].runner}\``,
|
|
120
|
-
`\u{1F4BB} Command: \`${recent[0].test_command}\``,
|
|
121
|
-
`\u{1F3AF} Scope: \`${recent[0].scope}\``,
|
|
122
|
-
`\u23F1\uFE0F Original duration: ${recent[0].duration_ms}ms`,
|
|
123
|
-
"",
|
|
124
|
-
recent[0].passed ? "\u2705 **Result: PASSED** (served from cache)" : "\u{1F534} **Result: FAILED** (served from cache)",
|
|
125
|
-
"",
|
|
126
|
-
"\u{1F4A1} Use `force: true` to bypass cache and run tests again."
|
|
127
|
-
].join("\n");
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
async function runAutoVerification(options = {}) {
|
|
136
|
-
const startTime = Date.now();
|
|
137
|
-
const root = process.cwd();
|
|
138
|
-
const scope = options.scope || "session-impact";
|
|
139
|
-
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
140
|
-
const denial = checkRateLimit();
|
|
141
|
-
if (denial) {
|
|
142
|
-
return denial;
|
|
143
|
-
}
|
|
144
|
-
if (!options.force) {
|
|
145
|
-
const cached = await checkStaleness(scope);
|
|
146
|
-
if (cached) return cached;
|
|
147
|
-
}
|
|
148
|
-
_isRunning = true;
|
|
149
|
-
const { runner, baseCommand } = detectTestRunner(root);
|
|
150
|
-
let testFiles = [];
|
|
151
|
-
const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
152
|
-
if (options.scope) {
|
|
153
|
-
const term = options.scope.toLowerCase();
|
|
154
|
-
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
155
|
-
if (testFiles.length === 0) {
|
|
156
|
-
try {
|
|
157
|
-
const { default: glob } = await import("fast-glob");
|
|
158
|
-
const found = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
159
|
-
testFiles = found;
|
|
160
|
-
} catch {
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
165
|
-
}
|
|
166
|
-
let fullCommand = baseCommand;
|
|
167
|
-
if (testFiles.length > 0 && runner === "npm") {
|
|
168
|
-
fullCommand = `${baseCommand} -- ${testFiles.map((f) => `"${f}"`).join(" ")}`;
|
|
169
|
-
}
|
|
170
|
-
return new Promise((resolve) => {
|
|
171
|
-
const child = exec(fullCommand, { cwd: root, timeout: timeoutMs }, async (error, stdout, stderr) => {
|
|
172
|
-
const durationMs = Date.now() - startTime;
|
|
173
|
-
const rawOutput = (stdout + "\n" + stderr).trim();
|
|
174
|
-
const passed = !error;
|
|
175
|
-
const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
|
|
176
|
-
await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
|
|
177
|
-
_isRunning = false;
|
|
178
|
-
_lastCompletedAt = Date.now();
|
|
179
|
-
_callHistory.push(Date.now());
|
|
180
|
-
while (_callHistory.length > 100) _callHistory.shift();
|
|
181
|
-
_currentProcess = null;
|
|
182
|
-
const statusSymbol = passed ? "\u2705" : "\u{1F534}";
|
|
183
|
-
const summaryHeader = `${statusSymbol} **Verification ${passed ? "PASSED" : "FAILED"}**
|
|
184
|
-
\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
|
|
185
|
-
const details = [
|
|
186
|
-
summaryHeader,
|
|
187
|
-
`\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
|
|
188
|
-
`\u{1F4BB} **Command**: \`${fullCommand}\``,
|
|
189
|
-
`\u{1F3AF} **Scope**: \`${scope}\`${testFiles.length > 0 ? ` (${testFiles.length} file(s) matched)` : ""}`,
|
|
190
|
-
`\u23F1\uFE0F **Duration**: ${durationMs}ms`,
|
|
191
|
-
""
|
|
192
|
-
];
|
|
193
|
-
if (!passed) {
|
|
194
|
-
details.push("\u26A0\uFE0F **Verification failed!** Please fix test failures before shipping.");
|
|
195
|
-
if (error && error.killed) {
|
|
196
|
-
details.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout**`);
|
|
197
|
-
}
|
|
198
|
-
details.push("```text");
|
|
199
|
-
details.push(rawOutput.substring(0, 1500));
|
|
200
|
-
details.push("```");
|
|
201
|
-
} else {
|
|
202
|
-
details.push("\u{1F389} All scoped tests passed!");
|
|
203
|
-
if (rawOutput) {
|
|
204
|
-
details.push("```text");
|
|
205
|
-
details.push(rawOutput.substring(0, 800));
|
|
206
|
-
details.push("```");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
resolve(details.join("\n"));
|
|
210
|
-
});
|
|
211
|
-
_currentProcess = child;
|
|
212
|
-
_isRunning = true;
|
|
213
|
-
const killTimer = setTimeout(() => {
|
|
214
|
-
if (_currentProcess && !_currentProcess.killed) {
|
|
215
|
-
try {
|
|
216
|
-
const childPid = _currentProcess.pid;
|
|
217
|
-
if (childPid) {
|
|
218
|
-
try {
|
|
219
|
-
process.kill(-childPid, "SIGKILL");
|
|
220
|
-
} catch {
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
_currentProcess.kill("SIGKILL");
|
|
224
|
-
} catch {
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
} catch {
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}, timeoutMs + 5e3);
|
|
231
|
-
child.on("close", () => {
|
|
232
|
-
clearTimeout(killTimer);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
export {
|
|
237
|
-
detectTestRunner,
|
|
238
|
-
getRecentCallCount,
|
|
239
|
-
getRunningVerificationPid,
|
|
240
|
-
isVerificationRunning,
|
|
241
|
-
msSinceLastVerification,
|
|
242
|
-
runAutoVerification
|
|
243
|
-
};
|