@plumpslabs/kuma 2.3.2 → 2.3.4
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/{chunk-EIK7T746.js → chunk-4CZSPSDW.js} +2 -2
- package/dist/{chunk-EQ2CE4UC.js → chunk-5YCQG7V7.js} +2 -2
- package/dist/{chunk-SU7BTOND.js → chunk-77G3APNG.js} +2 -2
- package/dist/{chunk-JIL533AM.js → chunk-BKEDQFEH.js} +1 -1
- package/dist/{chunk-5N6KXACT.js → chunk-NKNXJG2E.js} +98 -6
- package/dist/index.js +248 -26
- package/dist/{kumaDb-RC2EO3OB.js → kumaDb-I4AJ5A7H.js} +1 -1
- package/dist/{kumaGraph-5IZ4FDZA.js → kumaGraph-KXI3Q7QF.js} +2 -2
- package/dist/{kumaMemory-ME4XWD5Z.js → kumaMemory-2HW4JLJU.js} +2 -2
- package/dist/{kumaMiner-GFEGUWGH.js → kumaMiner-HZUBNYYP.js} +3 -3
- package/dist/kumaVerifier-SSS4ZTJY.js +243 -0
- package/dist/{safetyScore-QX2LBJ7H.js → safetyScore-ZAOUPG6A.js} +5 -5
- package/dist/{sessionMemory-LI4FIAMF.js → sessionMemory-SKKYUGB6.js} +1 -1
- package/package.json +1 -1
- package/dist/kumaVerifier-VYW5WH6V.js +0 -108
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
|
|
|
134
134
|
| Action | Use case |
|
|
135
135
|
|--------|----------|
|
|
136
136
|
| `guard` | Anti-pattern, drift, tool-loop, and failure checks. |
|
|
137
|
-
| `verify` |
|
|
137
|
+
| `verify` | **SAFETY-GUARDED** on-demand test verification — only runs via explicit tool call. Auto-detects runner (pnpm/npm/yarn/pytest/cargo/go), scopes to session changes, rate-limited (60s min interval), concurrency-locked (1 at a time), cached (< 5 min returns stale result), runaway-protected (> 3 calls in 5 min auto-blocks). ⚠️ NEVER auto-triggered — only via explicit `kuma_safety({ action: "verify" })`. |
|
|
138
138
|
| `check` | Pre-execution safety: policy, path, lock, risk level. |
|
|
139
139
|
| `audit` | Query audit trail + stats + override log. |
|
|
140
140
|
| `lock` | Multi-agent file locking. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionMemory
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-77G3APNG.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-KXI3Q7QF.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,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb,
|
|
3
3
|
saveDb
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NKNXJG2E.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-SKKYUGB6.js");
|
|
462
462
|
const toolCalls = sessionMemory.getToolCallHistory(50);
|
|
463
463
|
let edgeCount = 0;
|
|
464
464
|
for (const call of toolCalls) {
|
|
@@ -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-I4AJ5A7H.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, ?)`,
|
|
@@ -597,7 +597,7 @@ No unresolved issues.`;
|
|
|
597
597
|
this.addModifiedFile(filePath);
|
|
598
598
|
}
|
|
599
599
|
try {
|
|
600
|
-
const { recordChange } = await import("./kumaDb-
|
|
600
|
+
const { recordChange } = await import("./kumaDb-I4AJ5A7H.js");
|
|
601
601
|
const gitHash = this.getGitHead();
|
|
602
602
|
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
603
603
|
} catch {
|
|
@@ -239,6 +239,29 @@ function createSchema(db) {
|
|
|
239
239
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
240
240
|
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
241
241
|
)`);
|
|
242
|
+
db.run(`CREATE TABLE IF NOT EXISTS otel_config (
|
|
243
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
244
|
+
endpoint TEXT,
|
|
245
|
+
service_name TEXT DEFAULT 'kuma',
|
|
246
|
+
enabled INTEGER DEFAULT 0,
|
|
247
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
248
|
+
)`);
|
|
249
|
+
db.run(`CREATE TABLE IF NOT EXISTS cost_tracking (
|
|
250
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
251
|
+
session_id INTEGER REFERENCES sessions(id),
|
|
252
|
+
tool_name TEXT NOT NULL,
|
|
253
|
+
token_estimate INTEGER DEFAULT 0,
|
|
254
|
+
cost_estimate REAL DEFAULT 0.0,
|
|
255
|
+
budget_limit REAL DEFAULT 0.0,
|
|
256
|
+
escalated INTEGER DEFAULT 0,
|
|
257
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
258
|
+
)`);
|
|
259
|
+
db.run(`CREATE TABLE IF NOT EXISTS scratch_entries (
|
|
260
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
261
|
+
file_path TEXT NOT NULL,
|
|
262
|
+
reason TEXT,
|
|
263
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
264
|
+
)`);
|
|
242
265
|
db.run(`CREATE TABLE IF NOT EXISTS verifications (
|
|
243
266
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
244
267
|
scope TEXT NOT NULL,
|
|
@@ -741,23 +764,92 @@ async function runGarbageCollection() {
|
|
|
741
764
|
async function runDoctor() {
|
|
742
765
|
try {
|
|
743
766
|
const db = await getDb();
|
|
744
|
-
const checks = [
|
|
767
|
+
const checks = [
|
|
768
|
+
"\u{1FA7A} **Kuma Doctor Report**",
|
|
769
|
+
`\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`,
|
|
770
|
+
""
|
|
771
|
+
];
|
|
745
772
|
try {
|
|
746
773
|
const integrity = db.exec("PRAGMA integrity_check");
|
|
747
774
|
const result = integrity[0]?.values[0]?.[0] || "ok";
|
|
748
|
-
checks.push(
|
|
775
|
+
checks.push(`**Database Integrity**: ${result === "ok" ? "\u2705" : "\u274C " + result}`);
|
|
749
776
|
} catch {
|
|
750
|
-
checks.push("Integrity
|
|
777
|
+
checks.push("**Database Integrity**: \u274C check failed");
|
|
751
778
|
}
|
|
752
|
-
const
|
|
779
|
+
const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "decision_log", "file_summaries", "verifications", "health_snapshots", "tool_calls", "experiences", "experience_patterns", "patterns", "api_endpoints", "otel_config", "cost_tracking", "scratch_entries", "portability_entries"];
|
|
753
780
|
let existing = 0;
|
|
754
|
-
for (const t of
|
|
781
|
+
for (const t of allTables) {
|
|
755
782
|
try {
|
|
756
783
|
if (db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='${t}'`)[0]?.values?.length) existing++;
|
|
757
784
|
} catch {
|
|
758
785
|
}
|
|
759
786
|
}
|
|
760
|
-
|
|
787
|
+
const schemaHealth = existing === allTables.length ? "\u2705" : existing >= allTables.length * 0.8 ? "\u26A0\uFE0F" : "\u274C";
|
|
788
|
+
checks.push(`**Schema Health**: ${schemaHealth} ${existing}/${allTables.length} tables present`);
|
|
789
|
+
try {
|
|
790
|
+
const verifStmt = db.exec("SELECT scope, passed, created_at, duration_ms FROM verifications ORDER BY created_at DESC LIMIT 5");
|
|
791
|
+
if (verifStmt[0]?.values?.length) {
|
|
792
|
+
checks.push("");
|
|
793
|
+
checks.push("**Recent Verifications:**");
|
|
794
|
+
for (const v of verifStmt[0].values) {
|
|
795
|
+
const icon = v[1] ? "\u2705" : "\u{1F534}";
|
|
796
|
+
const time = new Date(v[2] * 1e3).toLocaleTimeString();
|
|
797
|
+
checks.push(` ${icon} ${v[0]} (${v[3]}ms) \u2014 ${time}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const { execSync } = await import("child_process");
|
|
804
|
+
const psOutput = execSync(
|
|
805
|
+
`ps -eo pid,ppid,command | grep -E "(jest|pnpm test|npm test|yarn test)" | grep -v grep | head -10`,
|
|
806
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
807
|
+
).trim();
|
|
808
|
+
if (psOutput) {
|
|
809
|
+
const processes = psOutput.split("\n").filter(Boolean);
|
|
810
|
+
checks.push("");
|
|
811
|
+
checks.push(`**Running Test Processes**: ${processes.length} found`);
|
|
812
|
+
for (const p of processes) {
|
|
813
|
+
checks.push(` \u26A1 ${p.substring(0, 120)}`);
|
|
814
|
+
}
|
|
815
|
+
checks.push(" \u{1F4A1} Run `kuma_safety({ action: 'gc' })` to clean up stale processes");
|
|
816
|
+
} else {
|
|
817
|
+
checks.push("");
|
|
818
|
+
checks.push("**Running Test Processes**: \u2705 None detected");
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
checks.push("");
|
|
822
|
+
checks.push("**Running Test Processes**: \u26A0\uFE0F Could not check");
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
const hourAgo = Math.floor((Date.now() - 36e5) / 1e3);
|
|
826
|
+
const recentVerifs = db.exec(`SELECT COUNT(*) as c FROM verifications WHERE created_at > ${hourAgo}`);
|
|
827
|
+
const count = recentVerifs[0]?.values[0]?.[0] || 0;
|
|
828
|
+
if (count > 10) {
|
|
829
|
+
checks.push("");
|
|
830
|
+
checks.push(`\u26A0\uFE0F **High Verification Rate**: ${count} verifications in the last hour \u2014 this is abnormal.`);
|
|
831
|
+
checks.push(` \u{1F4A1} If you didn't request these, run \`pkill -f "pnpm test"\` to kill orphaned processes.`);
|
|
832
|
+
} else if (count > 0) {
|
|
833
|
+
checks.push("");
|
|
834
|
+
checks.push(`**Verification Rate**: ${count} verifications in the last hour (normal)`);
|
|
835
|
+
}
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
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
|
+
}
|
|
761
853
|
return checks.join("\n");
|
|
762
854
|
} catch (err) {
|
|
763
855
|
return `\u274C Doctor failed: ${err}`;
|
package/dist/index.js
CHANGED
|
@@ -14,13 +14,7 @@ import {
|
|
|
14
14
|
getGraphStats,
|
|
15
15
|
searchGraph,
|
|
16
16
|
traceFlow
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import {
|
|
19
|
-
buildDriftMessages,
|
|
20
|
-
getGitDiffStat,
|
|
21
|
-
getSessionStats,
|
|
22
|
-
getUnresolvedCount
|
|
23
|
-
} from "./chunk-JIL533AM.js";
|
|
17
|
+
} from "./chunk-5YCQG7V7.js";
|
|
24
18
|
import {
|
|
25
19
|
addContextNote,
|
|
26
20
|
addSecurityFinding,
|
|
@@ -46,16 +40,22 @@ import {
|
|
|
46
40
|
saveResearchCache,
|
|
47
41
|
updateDecisionStatus,
|
|
48
42
|
updateTodoStatus
|
|
49
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-NKNXJG2E.js";
|
|
50
44
|
import {
|
|
51
45
|
formatDecisionTemplate,
|
|
52
46
|
getProactiveMemories,
|
|
53
47
|
recordDecision,
|
|
54
48
|
scoreMemoryRelevance
|
|
55
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-4CZSPSDW.js";
|
|
50
|
+
import {
|
|
51
|
+
buildDriftMessages,
|
|
52
|
+
getGitDiffStat,
|
|
53
|
+
getSessionStats,
|
|
54
|
+
getUnresolvedCount
|
|
55
|
+
} from "./chunk-BKEDQFEH.js";
|
|
56
56
|
import {
|
|
57
57
|
sessionMemory
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-77G3APNG.js";
|
|
59
59
|
import {
|
|
60
60
|
getProjectRoot,
|
|
61
61
|
validateFilePath
|
|
@@ -74,8 +74,49 @@ import { z } from "zod";
|
|
|
74
74
|
import fs from "fs";
|
|
75
75
|
import path from "path";
|
|
76
76
|
import crypto from "crypto";
|
|
77
|
+
var CONTEXT_ALIASES = {
|
|
78
|
+
// Research synonyms
|
|
79
|
+
"research": "research",
|
|
80
|
+
"search": "research",
|
|
81
|
+
"explore": "research",
|
|
82
|
+
"inspect": "research",
|
|
83
|
+
// Impact synonyms
|
|
84
|
+
"analyze": "impact",
|
|
85
|
+
"impact": "impact",
|
|
86
|
+
"whatif": "impact",
|
|
87
|
+
"refactor": "impact",
|
|
88
|
+
// Navigate synonyms
|
|
89
|
+
"trace": "navigate",
|
|
90
|
+
"flow": "navigate",
|
|
91
|
+
"navigate": "navigate",
|
|
92
|
+
// Init synonyms
|
|
93
|
+
"init": "init",
|
|
94
|
+
"start": "init",
|
|
95
|
+
"load": "init",
|
|
96
|
+
"brief": "init",
|
|
97
|
+
"project": "init",
|
|
98
|
+
"summary": "init",
|
|
99
|
+
// Changes synonyms
|
|
100
|
+
"changes": "changes",
|
|
101
|
+
"log": "changes",
|
|
102
|
+
"history": "changes",
|
|
103
|
+
// Health synonyms
|
|
104
|
+
"health": "health",
|
|
105
|
+
"score": "health",
|
|
106
|
+
"status": "health",
|
|
107
|
+
// Rollback synonyms
|
|
108
|
+
"rollback": "rollback",
|
|
109
|
+
"undo": "rollback",
|
|
110
|
+
"revert": "rollback",
|
|
111
|
+
// Researches synonyms
|
|
112
|
+
"researches": "researches",
|
|
113
|
+
"research-list": "researches",
|
|
114
|
+
"list-research": "researches"
|
|
115
|
+
};
|
|
77
116
|
async function handleContext(params) {
|
|
78
|
-
const
|
|
117
|
+
const rawAction = params.action || "init";
|
|
118
|
+
const resolvedAction = CONTEXT_ALIASES[rawAction.toLowerCase()] || rawAction;
|
|
119
|
+
const action = resolvedAction;
|
|
79
120
|
switch (action) {
|
|
80
121
|
case "init":
|
|
81
122
|
return handleInit(params);
|
|
@@ -155,8 +196,8 @@ async function handleInit(_params) {
|
|
|
155
196
|
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
156
197
|
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
157
198
|
try {
|
|
158
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
159
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
199
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-ZAOUPG6A.js");
|
|
200
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-I4AJ5A7H.js");
|
|
160
201
|
const score = await computeSafetyScore(_params.goal);
|
|
161
202
|
const checksStr = JSON.stringify(score.checks);
|
|
162
203
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -329,8 +370,8 @@ async function handleResearches(_params) {
|
|
|
329
370
|
async function handleHealth(_params) {
|
|
330
371
|
sessionMemory.recordToolCall("kuma_context_health", {});
|
|
331
372
|
try {
|
|
332
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
333
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
373
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-ZAOUPG6A.js");
|
|
374
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-I4AJ5A7H.js");
|
|
334
375
|
const score = await computeSafetyScore();
|
|
335
376
|
const checksStr = JSON.stringify(score.checks);
|
|
336
377
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -361,9 +402,72 @@ function computeProjectHash(scope) {
|
|
|
361
402
|
// src/tools/kumaMemoryTool.ts
|
|
362
403
|
import fs2 from "fs";
|
|
363
404
|
import path2 from "path";
|
|
405
|
+
var MEMORY_ALIASES = {
|
|
406
|
+
// Session synonyms
|
|
407
|
+
"session": "session",
|
|
408
|
+
"summary": "session",
|
|
409
|
+
"status": "session",
|
|
410
|
+
"get": "session",
|
|
411
|
+
"read": "session",
|
|
412
|
+
"fetch": "session",
|
|
413
|
+
"current": "session",
|
|
414
|
+
// Decision synonyms
|
|
415
|
+
"decision": "decision",
|
|
416
|
+
"adr": "decision",
|
|
417
|
+
"record-decision": "decision",
|
|
418
|
+
// Research save synonyms
|
|
419
|
+
"research_save": "research_save",
|
|
420
|
+
"save": "research_save",
|
|
421
|
+
"store": "research_save",
|
|
422
|
+
"write": "research_save",
|
|
423
|
+
"persist": "research_save",
|
|
424
|
+
"cache": "research_save",
|
|
425
|
+
// Mine synonyms
|
|
426
|
+
"mine": "mine",
|
|
427
|
+
"mine-decisions": "mine",
|
|
428
|
+
"git-mine": "mine",
|
|
429
|
+
"dig": "mine",
|
|
430
|
+
// Heal synonyms
|
|
431
|
+
"heal": "heal",
|
|
432
|
+
"repair": "heal",
|
|
433
|
+
"fix-graph": "heal",
|
|
434
|
+
"clean-graph": "heal",
|
|
435
|
+
// Search synonyms
|
|
436
|
+
"search": "search",
|
|
437
|
+
"find": "search",
|
|
438
|
+
"query": "search",
|
|
439
|
+
"lookup": "search",
|
|
440
|
+
// Todo synonyms
|
|
441
|
+
"todo": "todo",
|
|
442
|
+
"task": "todo",
|
|
443
|
+
"todos": "todo",
|
|
444
|
+
"tasks": "todo",
|
|
445
|
+
// Context notes synonyms
|
|
446
|
+
"context": "context",
|
|
447
|
+
"notes": "context",
|
|
448
|
+
"note": "context",
|
|
449
|
+
"inject": "context",
|
|
450
|
+
"context-note": "context",
|
|
451
|
+
// Benchmark synonyms
|
|
452
|
+
"benchmark": "benchmark",
|
|
453
|
+
"perf": "benchmark",
|
|
454
|
+
"metrics": "benchmark",
|
|
455
|
+
"measure": "benchmark",
|
|
456
|
+
// Decision log synonyms
|
|
457
|
+
"decision_log": "decision_log",
|
|
458
|
+
"decisions": "decision_log",
|
|
459
|
+
"log": "decision_log",
|
|
460
|
+
"decision-log": "decision_log",
|
|
461
|
+
// Changes synonyms
|
|
462
|
+
"changes": "changes",
|
|
463
|
+
"change-log": "changes",
|
|
464
|
+
"history": "changes"
|
|
465
|
+
};
|
|
364
466
|
async function handleMemory(params) {
|
|
365
|
-
const
|
|
366
|
-
|
|
467
|
+
const rawAction = params.action || "session";
|
|
468
|
+
const key = rawAction.toLowerCase().replace(/[\s_-]+/g, "-");
|
|
469
|
+
const action = MEMORY_ALIASES[key] || rawAction;
|
|
470
|
+
sessionMemory.recordToolCall("kuma_memory", { action: rawAction });
|
|
367
471
|
switch (action) {
|
|
368
472
|
case "decision":
|
|
369
473
|
return handleDecision(params);
|
|
@@ -397,7 +501,7 @@ async function handleDecision(params) {
|
|
|
397
501
|
case "template":
|
|
398
502
|
return formatDecisionTemplate();
|
|
399
503
|
case "suggest": {
|
|
400
|
-
const { shouldRecordDecision } = await import("./kumaMemory-
|
|
504
|
+
const { shouldRecordDecision } = await import("./kumaMemory-2HW4JLJU.js");
|
|
401
505
|
const check = shouldRecordDecision();
|
|
402
506
|
return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
|
|
403
507
|
Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
|
|
@@ -475,7 +579,7 @@ async function handleSearch(params) {
|
|
|
475
579
|
if (!query) return "\u26A0\uFE0F query or scope parameter required.";
|
|
476
580
|
const limit = params.limit || 20;
|
|
477
581
|
const memResults = sessionMemory.searchMemory(query, limit);
|
|
478
|
-
const { searchGraph: searchGraph2 } = await import("./kumaGraph-
|
|
582
|
+
const { searchGraph: searchGraph2 } = await import("./kumaGraph-KXI3Q7QF.js");
|
|
479
583
|
const graphResults = await searchGraph2(query, Math.min(limit, 10));
|
|
480
584
|
const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
|
|
481
585
|
\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
|
|
@@ -550,7 +654,7 @@ async function handleDecisionLog(params) {
|
|
|
550
654
|
}
|
|
551
655
|
async function handleMine(params) {
|
|
552
656
|
sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
|
|
553
|
-
const { mineHistoricalDecisions } = await import("./kumaMiner-
|
|
657
|
+
const { mineHistoricalDecisions } = await import("./kumaMiner-HZUBNYYP.js");
|
|
554
658
|
return await mineHistoricalDecisions({
|
|
555
659
|
scope: params.scope,
|
|
556
660
|
since: typeof params.since === "string" ? params.since : void 0,
|
|
@@ -1270,7 +1374,7 @@ function findPatchScripts(projectRoot) {
|
|
|
1270
1374
|
const ext = path5.extname(file).toLowerCase();
|
|
1271
1375
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
1272
1376
|
const relativePath = path5.relative(projectRoot, file);
|
|
1273
|
-
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
|
|
1377
|
+
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
1274
1378
|
continue;
|
|
1275
1379
|
}
|
|
1276
1380
|
try {
|
|
@@ -1642,6 +1746,8 @@ async function handleSafety(params) {
|
|
|
1642
1746
|
return handlePortability(params);
|
|
1643
1747
|
case "gitignore":
|
|
1644
1748
|
return handleGitignore(params);
|
|
1749
|
+
case "clean":
|
|
1750
|
+
return handleClean(params);
|
|
1645
1751
|
default:
|
|
1646
1752
|
return `Unknown action "${action}".`;
|
|
1647
1753
|
}
|
|
@@ -1685,7 +1791,7 @@ async function handleLock(params) {
|
|
|
1685
1791
|
}
|
|
1686
1792
|
async function handleHealth2(_params) {
|
|
1687
1793
|
try {
|
|
1688
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
1794
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-ZAOUPG6A.js");
|
|
1689
1795
|
const score = await computeSafetyScore();
|
|
1690
1796
|
await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
|
|
1691
1797
|
return formatSafetyScore(score);
|
|
@@ -1757,9 +1863,49 @@ async function handleGitignore(_params) {
|
|
|
1757
1863
|
sessionMemory.recordToolCall("kuma_safety_gitignore", {});
|
|
1758
1864
|
return await ensureGitignore();
|
|
1759
1865
|
}
|
|
1866
|
+
async function handleClean(_params) {
|
|
1867
|
+
sessionMemory.recordToolCall("kuma_safety_clean", {});
|
|
1868
|
+
const fs7 = await import("fs");
|
|
1869
|
+
const path7 = await import("path");
|
|
1870
|
+
const root = process.cwd();
|
|
1871
|
+
const scratchDir = path7.resolve(root, ".kuma", "scratch");
|
|
1872
|
+
let removed = 0;
|
|
1873
|
+
if (fs7.existsSync(scratchDir)) {
|
|
1874
|
+
try {
|
|
1875
|
+
const entries = fs7.readdirSync(scratchDir);
|
|
1876
|
+
for (const entry of entries) {
|
|
1877
|
+
const fullPath = path7.join(scratchDir, entry);
|
|
1878
|
+
try {
|
|
1879
|
+
const stat = fs7.statSync(fullPath);
|
|
1880
|
+
if (stat.isFile()) {
|
|
1881
|
+
fs7.unlinkSync(fullPath);
|
|
1882
|
+
removed++;
|
|
1883
|
+
} else if (stat.isDirectory()) {
|
|
1884
|
+
fs7.rmSync(fullPath, { recursive: true, force: true });
|
|
1885
|
+
removed++;
|
|
1886
|
+
}
|
|
1887
|
+
} catch {
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
} catch {
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
sessionMemory.setGoal(sessionMemory.getSummary().currentGoal || "cleaned");
|
|
1894
|
+
const result = removed > 0 ? `\u{1F9F9} **Scratch Clean** \u2014 Removed ${removed} item(s) from .kuma/scratch/` : `\u{1F9F9} **Scratch Clean** \u2014 No scratch files to clean.`;
|
|
1895
|
+
return `${result}
|
|
1896
|
+
\u{1F4A1} Drift warnings have been reset. Any temporary debug artifacts are now cleared.`;
|
|
1897
|
+
}
|
|
1898
|
+
var _lastVerifyCall = 0;
|
|
1899
|
+
var VERIFY_COOLDOWN_MS = 3e4;
|
|
1760
1900
|
async function handleVerify(params) {
|
|
1901
|
+
const now = Date.now();
|
|
1902
|
+
if (_lastVerifyCall > 0 && now - _lastVerifyCall < VERIFY_COOLDOWN_MS) {
|
|
1903
|
+
const remaining = Math.ceil((VERIFY_COOLDOWN_MS - (now - _lastVerifyCall)) / 1e3);
|
|
1904
|
+
return `\u23F3 **Handler rate limit** \u2014 verify was just called ${Math.floor((now - _lastVerifyCall) / 1e3)}s ago. Please wait ${remaining}s before calling verify again.`;
|
|
1905
|
+
}
|
|
1906
|
+
_lastVerifyCall = now;
|
|
1761
1907
|
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
1762
|
-
const { runAutoVerification } = await import("./kumaVerifier-
|
|
1908
|
+
const { runAutoVerification } = await import("./kumaVerifier-SSS4ZTJY.js");
|
|
1763
1909
|
return await runAutoVerification({
|
|
1764
1910
|
scope: params.scope || params.filePath,
|
|
1765
1911
|
force: params.force,
|
|
@@ -1864,7 +2010,7 @@ function registerAllTools(server) {
|
|
|
1864
2010
|
"kuma_safety",
|
|
1865
2011
|
"Safety checks, policy enforcement, security scanning, integrated auto-verification, and project hygiene. Actions: guard (anti-pattern detection), verify (auto-run scoped tests), 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).",
|
|
1866
2012
|
{
|
|
1867
|
-
action: z.enum(["guard", "verify", "check", "audit", "lock", "health", "override", "security", "gc", "doctor", "portability", "gitignore"]).describe("Safety action: guard=anti-patterns, verify=auto-run scoped tests, 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"),
|
|
2013
|
+
action: z.enum(["guard", "verify", "check", "audit", "lock", "health", "override", "security", "gc", "doctor", "portability", "gitignore", "clean"]).describe("Safety action: guard=anti-patterns, verify=auto-run scoped tests, 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, clean=purge scratch dir & drift, override=bypass"),
|
|
1868
2014
|
// Verify params
|
|
1869
2015
|
scope: z.string().optional().describe("Scope for verify or context (e.g. 'auth', file path)"),
|
|
1870
2016
|
// Guard params
|
|
@@ -1953,6 +2099,71 @@ If no flags specified, you'll be prompted to select files interactively.
|
|
|
1953
2099
|
}
|
|
1954
2100
|
async function main() {
|
|
1955
2101
|
const args = process.argv.slice(2);
|
|
2102
|
+
if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
|
|
2103
|
+
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
2104
|
+
console.error("");
|
|
2105
|
+
try {
|
|
2106
|
+
const { getRunningVerificationPid } = await import("./kumaVerifier-SSS4ZTJY.js");
|
|
2107
|
+
const pid = getRunningVerificationPid();
|
|
2108
|
+
if (pid) {
|
|
2109
|
+
try {
|
|
2110
|
+
process.kill(-pid, "SIGKILL");
|
|
2111
|
+
} catch {
|
|
2112
|
+
}
|
|
2113
|
+
try {
|
|
2114
|
+
process.kill(pid, "SIGKILL");
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
console.error(`\u2705 Killed verification process (PID: ${pid})`);
|
|
2118
|
+
}
|
|
2119
|
+
} catch (err) {
|
|
2120
|
+
console.error(`\u26A0\uFE0F Could not check verifier: ${err}`);
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
2124
|
+
try {
|
|
2125
|
+
execSync3("pkill -f 'jest' 2>/dev/null || true");
|
|
2126
|
+
} catch {
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
2129
|
+
execSync3("pkill -f 'jest-worker' 2>/dev/null || true");
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
try {
|
|
2133
|
+
execSync3("pkill -f 'pnpm test' 2>/dev/null || true");
|
|
2134
|
+
} catch {
|
|
2135
|
+
}
|
|
2136
|
+
try {
|
|
2137
|
+
execSync3("pkill -f 'npm test' 2>/dev/null || true");
|
|
2138
|
+
} catch {
|
|
2139
|
+
}
|
|
2140
|
+
try {
|
|
2141
|
+
execSync3("pkill -f 'yarn test' 2>/dev/null || true");
|
|
2142
|
+
} catch {
|
|
2143
|
+
}
|
|
2144
|
+
console.error("\u2705 Killed orphaned test processes");
|
|
2145
|
+
} catch {
|
|
2146
|
+
console.error("\u26A0\uFE0F Could not clean up orphaned processes \u2014 try `pkill -f jest` manually");
|
|
2147
|
+
}
|
|
2148
|
+
console.error("");
|
|
2149
|
+
console.error(`\u{1F6E1}\uFE0F Kill switch complete. You can safely restart Kuma.`);
|
|
2150
|
+
process.exit(0);
|
|
2151
|
+
}
|
|
2152
|
+
if (args[0] === "stop") {
|
|
2153
|
+
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
2154
|
+
console.error("");
|
|
2155
|
+
console.error("\u26A0\uFE0F Use `kuma stop --force` to kill all child processes (verifier, tests).");
|
|
2156
|
+
console.error("\u{1F4A1} This kills orphaned Jest/worker/test processes spawned by Kuma.");
|
|
2157
|
+
console.error("");
|
|
2158
|
+
process.exit(0);
|
|
2159
|
+
}
|
|
2160
|
+
if (args[0] === "kill") {
|
|
2161
|
+
console.error(`\u{1F43B} Kuma v${SERVER_VERSION}`);
|
|
2162
|
+
console.error("");
|
|
2163
|
+
console.error("\u{1F4A1} Use `kuma stop --force` instead.");
|
|
2164
|
+
console.error("");
|
|
2165
|
+
process.exit(0);
|
|
2166
|
+
}
|
|
1956
2167
|
if (args[0] === "init") {
|
|
1957
2168
|
const flags = args.slice(1);
|
|
1958
2169
|
if (flags.includes("--help") || flags.includes("-h")) {
|
|
@@ -2100,7 +2311,7 @@ async function main() {
|
|
|
2100
2311
|
console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
|
|
2101
2312
|
}
|
|
2102
2313
|
try {
|
|
2103
|
-
const { buildFromSessionMemory } = await import("./kumaGraph-
|
|
2314
|
+
const { buildFromSessionMemory } = await import("./kumaGraph-KXI3Q7QF.js");
|
|
2104
2315
|
const edgeCount = await buildFromSessionMemory();
|
|
2105
2316
|
if (edgeCount > 0) {
|
|
2106
2317
|
console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
|
|
@@ -2109,7 +2320,18 @@ async function main() {
|
|
|
2109
2320
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
2110
2321
|
}
|
|
2111
2322
|
try {
|
|
2112
|
-
const
|
|
2323
|
+
const fs7 = await import("fs");
|
|
2324
|
+
const path7 = await import("path");
|
|
2325
|
+
const scratchDir = path7.resolve(process.cwd(), ".kuma", "scratch");
|
|
2326
|
+
if (!fs7.existsSync(scratchDir)) {
|
|
2327
|
+
fs7.mkdirSync(scratchDir, { recursive: true });
|
|
2328
|
+
console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
|
|
2329
|
+
}
|
|
2330
|
+
} catch (err) {
|
|
2331
|
+
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
|
|
2332
|
+
}
|
|
2333
|
+
try {
|
|
2334
|
+
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-I4AJ5A7H.js");
|
|
2113
2335
|
const db = await getDb2();
|
|
2114
2336
|
db.run(
|
|
2115
2337
|
`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-4CZSPSDW.js";
|
|
8
|
+
import "./chunk-77G3APNG.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-NKNXJG2E.js";
|
|
4
4
|
import {
|
|
5
5
|
recordDecision
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-4CZSPSDW.js";
|
|
7
|
+
import "./chunk-77G3APNG.js";
|
|
8
8
|
import "./chunk-E2KFPEBT.js";
|
|
9
9
|
|
|
10
10
|
// src/engine/kumaMiner.ts
|
|
@@ -0,0 +1,243 @@
|
|
|
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
|
+
};
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getGitDiffStat,
|
|
3
3
|
getSessionStats,
|
|
4
4
|
getUnresolvedCount
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-BKEDQFEH.js";
|
|
6
6
|
import {
|
|
7
7
|
sessionMemory
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-77G3APNG.js";
|
|
9
9
|
import "./chunk-E2KFPEBT.js";
|
|
10
10
|
|
|
11
11
|
// src/engine/safetyScore.ts
|
|
@@ -52,7 +52,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
52
52
|
totalScore += 20;
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
55
|
-
const { getDb } = await import("./kumaDb-
|
|
55
|
+
const { getDb } = await import("./kumaDb-I4AJ5A7H.js");
|
|
56
56
|
const db = await getDb();
|
|
57
57
|
const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
|
|
58
58
|
const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
|
|
@@ -83,7 +83,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
83
83
|
totalScore += 5;
|
|
84
84
|
}
|
|
85
85
|
try {
|
|
86
|
-
const { getDb } = await import("./kumaDb-
|
|
86
|
+
const { getDb } = await import("./kumaDb-I4AJ5A7H.js");
|
|
87
87
|
const db = await getDb();
|
|
88
88
|
const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
|
|
89
89
|
checks.push({
|
|
@@ -108,7 +108,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
108
108
|
const hasRunTests = stats.hasRunTests;
|
|
109
109
|
let latestVerif = null;
|
|
110
110
|
try {
|
|
111
|
-
const { getLatestVerifications } = await import("./kumaDb-
|
|
111
|
+
const { getLatestVerifications } = await import("./kumaDb-I4AJ5A7H.js");
|
|
112
112
|
const verifs = await getLatestVerifications(1);
|
|
113
113
|
if (verifs.length > 0) latestVerif = verifs[0];
|
|
114
114
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.4",
|
|
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,108 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
saveVerification
|
|
3
|
-
} from "./chunk-5N6KXACT.js";
|
|
4
|
-
import {
|
|
5
|
-
sessionMemory
|
|
6
|
-
} from "./chunk-SU7BTOND.js";
|
|
7
|
-
import "./chunk-E2KFPEBT.js";
|
|
8
|
-
|
|
9
|
-
// src/engine/kumaVerifier.ts
|
|
10
|
-
import fs from "fs";
|
|
11
|
-
import path from "path";
|
|
12
|
-
import { exec } from "child_process";
|
|
13
|
-
function detectTestRunner(root = process.cwd()) {
|
|
14
|
-
const pkgPath = path.join(root, "package.json");
|
|
15
|
-
if (fs.existsSync(pkgPath)) {
|
|
16
|
-
try {
|
|
17
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
18
|
-
if (pkg.scripts && pkg.scripts.test) {
|
|
19
|
-
if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
20
|
-
return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
21
|
-
}
|
|
22
|
-
if (fs.existsSync(path.join(root, "yarn.lock"))) {
|
|
23
|
-
return { runner: "yarn", baseCommand: "yarn test" };
|
|
24
|
-
}
|
|
25
|
-
return { runner: "npm", baseCommand: "npm test" };
|
|
26
|
-
}
|
|
27
|
-
} catch {
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (fs.existsSync(path.join(root, "pytest.ini")) || fs.existsSync(path.join(root, "pyproject.toml"))) {
|
|
31
|
-
return { runner: "pytest", baseCommand: "pytest" };
|
|
32
|
-
}
|
|
33
|
-
if (fs.existsSync(path.join(root, "Cargo.toml"))) {
|
|
34
|
-
return { runner: "cargo", baseCommand: "cargo test" };
|
|
35
|
-
}
|
|
36
|
-
if (fs.existsSync(path.join(root, "go.mod"))) {
|
|
37
|
-
return { runner: "go", baseCommand: "go test ./..." };
|
|
38
|
-
}
|
|
39
|
-
if (fs.existsSync(path.join(root, "Makefile"))) {
|
|
40
|
-
return { runner: "make", baseCommand: "make test" };
|
|
41
|
-
}
|
|
42
|
-
return { runner: "unknown", baseCommand: "npm test" };
|
|
43
|
-
}
|
|
44
|
-
async function runAutoVerification(options = {}) {
|
|
45
|
-
const startTime = Date.now();
|
|
46
|
-
const root = process.cwd();
|
|
47
|
-
const scope = options.scope || "session-impact";
|
|
48
|
-
const timeoutMs = options.timeoutMs || 3e4;
|
|
49
|
-
const { runner, baseCommand } = detectTestRunner(root);
|
|
50
|
-
let testFiles = [];
|
|
51
|
-
const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
52
|
-
if (options.scope) {
|
|
53
|
-
const term = options.scope.toLowerCase();
|
|
54
|
-
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
55
|
-
if (testFiles.length === 0) {
|
|
56
|
-
try {
|
|
57
|
-
const { default: glob } = await import("fast-glob");
|
|
58
|
-
const found = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
59
|
-
testFiles = found;
|
|
60
|
-
} catch {
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
} else {
|
|
64
|
-
testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
65
|
-
}
|
|
66
|
-
let fullCommand = baseCommand;
|
|
67
|
-
if (testFiles.length > 0 && runner === "npm") {
|
|
68
|
-
fullCommand = `${baseCommand} -- ${testFiles.map((f) => `"${f}"`).join(" ")}`;
|
|
69
|
-
}
|
|
70
|
-
return new Promise((resolve) => {
|
|
71
|
-
exec(fullCommand, { cwd: root, timeout: timeoutMs }, async (error, stdout, stderr) => {
|
|
72
|
-
const durationMs = Date.now() - startTime;
|
|
73
|
-
const rawOutput = (stdout + "\n" + stderr).trim();
|
|
74
|
-
const passed = !error;
|
|
75
|
-
const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
|
|
76
|
-
await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
|
|
77
|
-
const statusSymbol = passed ? "\u2705" : "\u{1F534}";
|
|
78
|
-
const summaryHeader = `${statusSymbol} **Auto-Verification ${passed ? "PASSED" : "FAILED"}**
|
|
79
|
-
\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`;
|
|
80
|
-
const details = [
|
|
81
|
-
summaryHeader,
|
|
82
|
-
`\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
|
|
83
|
-
`\u{1F4BB} **Command**: \`${fullCommand}\``,
|
|
84
|
-
`\u{1F3AF} **Scope**: \`${scope}\`${testFiles.length > 0 ? ` (${testFiles.length} file(s) matched)` : ""}`,
|
|
85
|
-
`\u23F1\uFE0F **Duration**: ${durationMs}ms`,
|
|
86
|
-
""
|
|
87
|
-
];
|
|
88
|
-
if (!passed) {
|
|
89
|
-
details.push("\u26A0\uFE0F **BLOCKER**: Verification failed! Please fix test failures before shipping or continuing edits.");
|
|
90
|
-
details.push("```text");
|
|
91
|
-
details.push(rawOutput.substring(0, 1500));
|
|
92
|
-
details.push("```");
|
|
93
|
-
} else {
|
|
94
|
-
details.push("\u{1F389} All scoped tests passed cleanly!");
|
|
95
|
-
if (rawOutput) {
|
|
96
|
-
details.push("```text");
|
|
97
|
-
details.push(rawOutput.substring(0, 800));
|
|
98
|
-
details.push("```");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
resolve(details.join("\n"));
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
export {
|
|
106
|
-
detectTestRunner,
|
|
107
|
-
runAutoVerification
|
|
108
|
-
};
|