@plumpslabs/kuma 2.3.4 → 2.3.5
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-NKNXJG2E.js → chunk-2NBESJTH.js} +0 -15
- package/dist/{chunk-4CZSPSDW.js → chunk-6QX4LBHV.js} +2 -2
- package/dist/{chunk-5YCQG7V7.js → chunk-LLAPHR5W.js} +2 -2
- package/dist/{chunk-77G3APNG.js → chunk-PH4JKL6T.js} +2 -2
- package/dist/{chunk-BKEDQFEH.js → chunk-ZIFLGJ5R.js} +1 -1
- package/dist/index.js +57 -25
- package/dist/{kumaDb-I4AJ5A7H.js → kumaDb-FQ3YKJ6K.js} +1 -1
- package/dist/{kumaGraph-KXI3Q7QF.js → kumaGraph-ABVEVF4M.js} +2 -2
- package/dist/{kumaMemory-2HW4JLJU.js → kumaMemory-DDZQXMFZ.js} +2 -2
- package/dist/{kumaMiner-HZUBNYYP.js → kumaMiner-GP6BITKV.js} +3 -3
- package/dist/kumaVerifier-7TFWLEPG.js +235 -0
- package/dist/{safetyScore-ZAOUPG6A.js → safetyScore-VYT6OMVG.js} +5 -5
- package/dist/{sessionMemory-SKKYUGB6.js → sessionMemory-PGYVS3BK.js} +1 -1
- package/package.json +1 -1
- package/dist/kumaVerifier-SSS4ZTJY.js +0 -243
|
@@ -835,21 +835,6 @@ async function runDoctor() {
|
|
|
835
835
|
}
|
|
836
836
|
} catch {
|
|
837
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
|
-
}
|
|
853
838
|
return checks.join("\n");
|
|
854
839
|
} catch (err) {
|
|
855
840
|
return `\u274C Doctor failed: ${err}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionMemory
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PH4JKL6T.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-ABVEVF4M.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-2NBESJTH.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-PGYVS3BK.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-FQ3YKJ6K.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-FQ3YKJ6K.js");
|
|
601
601
|
const gitHash = this.getGitHead();
|
|
602
602
|
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
603
603
|
} catch {
|
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-LLAPHR5W.js";
|
|
18
|
+
import {
|
|
19
|
+
buildDriftMessages,
|
|
20
|
+
getGitDiffStat,
|
|
21
|
+
getSessionStats,
|
|
22
|
+
getUnresolvedCount
|
|
23
|
+
} from "./chunk-ZIFLGJ5R.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-2NBESJTH.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-6QX4LBHV.js";
|
|
56
56
|
import {
|
|
57
57
|
sessionMemory
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-PH4JKL6T.js";
|
|
59
59
|
import {
|
|
60
60
|
getProjectRoot,
|
|
61
61
|
validateFilePath
|
|
@@ -196,8 +196,8 @@ async function handleInit(_params) {
|
|
|
196
196
|
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
197
197
|
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
198
198
|
try {
|
|
199
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
200
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
199
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-VYT6OMVG.js");
|
|
200
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-FQ3YKJ6K.js");
|
|
201
201
|
const score = await computeSafetyScore(_params.goal);
|
|
202
202
|
const checksStr = JSON.stringify(score.checks);
|
|
203
203
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -370,8 +370,8 @@ async function handleResearches(_params) {
|
|
|
370
370
|
async function handleHealth(_params) {
|
|
371
371
|
sessionMemory.recordToolCall("kuma_context_health", {});
|
|
372
372
|
try {
|
|
373
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
374
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
373
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-VYT6OMVG.js");
|
|
374
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-FQ3YKJ6K.js");
|
|
375
375
|
const score = await computeSafetyScore();
|
|
376
376
|
const checksStr = JSON.stringify(score.checks);
|
|
377
377
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -501,7 +501,7 @@ async function handleDecision(params) {
|
|
|
501
501
|
case "template":
|
|
502
502
|
return formatDecisionTemplate();
|
|
503
503
|
case "suggest": {
|
|
504
|
-
const { shouldRecordDecision } = await import("./kumaMemory-
|
|
504
|
+
const { shouldRecordDecision } = await import("./kumaMemory-DDZQXMFZ.js");
|
|
505
505
|
const check = shouldRecordDecision();
|
|
506
506
|
return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
|
|
507
507
|
Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
|
|
@@ -579,7 +579,7 @@ async function handleSearch(params) {
|
|
|
579
579
|
if (!query) return "\u26A0\uFE0F query or scope parameter required.";
|
|
580
580
|
const limit = params.limit || 20;
|
|
581
581
|
const memResults = sessionMemory.searchMemory(query, limit);
|
|
582
|
-
const { searchGraph: searchGraph2 } = await import("./kumaGraph-
|
|
582
|
+
const { searchGraph: searchGraph2 } = await import("./kumaGraph-ABVEVF4M.js");
|
|
583
583
|
const graphResults = await searchGraph2(query, Math.min(limit, 10));
|
|
584
584
|
const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
|
|
585
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
|
|
@@ -654,7 +654,7 @@ async function handleDecisionLog(params) {
|
|
|
654
654
|
}
|
|
655
655
|
async function handleMine(params) {
|
|
656
656
|
sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
|
|
657
|
-
const { mineHistoricalDecisions } = await import("./kumaMiner-
|
|
657
|
+
const { mineHistoricalDecisions } = await import("./kumaMiner-GP6BITKV.js");
|
|
658
658
|
return await mineHistoricalDecisions({
|
|
659
659
|
scope: params.scope,
|
|
660
660
|
since: typeof params.since === "string" ? params.since : void 0,
|
|
@@ -1791,7 +1791,7 @@ async function handleLock(params) {
|
|
|
1791
1791
|
}
|
|
1792
1792
|
async function handleHealth2(_params) {
|
|
1793
1793
|
try {
|
|
1794
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
1794
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-VYT6OMVG.js");
|
|
1795
1795
|
const score = await computeSafetyScore();
|
|
1796
1796
|
await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
|
|
1797
1797
|
return formatSafetyScore(score);
|
|
@@ -1905,7 +1905,7 @@ async function handleVerify(params) {
|
|
|
1905
1905
|
}
|
|
1906
1906
|
_lastVerifyCall = now;
|
|
1907
1907
|
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
1908
|
-
const { runAutoVerification } = await import("./kumaVerifier-
|
|
1908
|
+
const { runAutoVerification } = await import("./kumaVerifier-7TFWLEPG.js");
|
|
1909
1909
|
return await runAutoVerification({
|
|
1910
1910
|
scope: params.scope || params.filePath,
|
|
1911
1911
|
force: params.force,
|
|
@@ -2102,8 +2102,9 @@ async function main() {
|
|
|
2102
2102
|
if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
|
|
2103
2103
|
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
2104
2104
|
console.error("");
|
|
2105
|
+
let killedCount = 0;
|
|
2105
2106
|
try {
|
|
2106
|
-
const { getRunningVerificationPid } = await import("./kumaVerifier-
|
|
2107
|
+
const { getRunningVerificationPid } = await import("./kumaVerifier-7TFWLEPG.js");
|
|
2107
2108
|
const pid = getRunningVerificationPid();
|
|
2108
2109
|
if (pid) {
|
|
2109
2110
|
try {
|
|
@@ -2114,10 +2115,41 @@ async function main() {
|
|
|
2114
2115
|
process.kill(pid, "SIGKILL");
|
|
2115
2116
|
} catch {
|
|
2116
2117
|
}
|
|
2117
|
-
console.error(`\u2705 Killed verification process (PID: ${pid})`);
|
|
2118
|
+
console.error(`\u2705 Killed local verification process (PID: ${pid})`);
|
|
2119
|
+
killedCount++;
|
|
2118
2120
|
}
|
|
2119
|
-
} catch
|
|
2120
|
-
|
|
2121
|
+
} catch {
|
|
2122
|
+
}
|
|
2123
|
+
try {
|
|
2124
|
+
const path7 = await import("path");
|
|
2125
|
+
const fs7 = await import("fs");
|
|
2126
|
+
const lockDir = path7.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
2127
|
+
if (fs7.existsSync(lockDir)) {
|
|
2128
|
+
const pidFile = path7.join(lockDir, "pid");
|
|
2129
|
+
if (fs7.existsSync(pidFile)) {
|
|
2130
|
+
try {
|
|
2131
|
+
const pid = parseInt(fs7.readFileSync(pidFile, "utf-8"), 10);
|
|
2132
|
+
if (pid && pid !== process.pid) {
|
|
2133
|
+
try {
|
|
2134
|
+
process.kill(-pid, "SIGKILL");
|
|
2135
|
+
} catch {
|
|
2136
|
+
}
|
|
2137
|
+
try {
|
|
2138
|
+
process.kill(pid, "SIGKILL");
|
|
2139
|
+
} catch {
|
|
2140
|
+
}
|
|
2141
|
+
console.error(`\u2705 Killed other instance verification (PID: ${pid})`);
|
|
2142
|
+
killedCount++;
|
|
2143
|
+
}
|
|
2144
|
+
} catch {
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
fs7.rmSync(lockDir, { recursive: true, force: true });
|
|
2148
|
+
}
|
|
2149
|
+
if (killedCount === 0) {
|
|
2150
|
+
console.error("\u2705 No running verifications found to kill.");
|
|
2151
|
+
}
|
|
2152
|
+
} catch {
|
|
2121
2153
|
}
|
|
2122
2154
|
try {
|
|
2123
2155
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -2311,7 +2343,7 @@ async function main() {
|
|
|
2311
2343
|
console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
|
|
2312
2344
|
}
|
|
2313
2345
|
try {
|
|
2314
|
-
const { buildFromSessionMemory } = await import("./kumaGraph-
|
|
2346
|
+
const { buildFromSessionMemory } = await import("./kumaGraph-ABVEVF4M.js");
|
|
2315
2347
|
const edgeCount = await buildFromSessionMemory();
|
|
2316
2348
|
if (edgeCount > 0) {
|
|
2317
2349
|
console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
|
|
@@ -2331,7 +2363,7 @@ async function main() {
|
|
|
2331
2363
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
|
|
2332
2364
|
}
|
|
2333
2365
|
try {
|
|
2334
|
-
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-
|
|
2366
|
+
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-FQ3YKJ6K.js");
|
|
2335
2367
|
const db = await getDb2();
|
|
2336
2368
|
db.run(
|
|
2337
2369
|
`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-6QX4LBHV.js";
|
|
8
|
+
import "./chunk-PH4JKL6T.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-2NBESJTH.js";
|
|
4
4
|
import {
|
|
5
5
|
recordDecision
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-6QX4LBHV.js";
|
|
7
|
+
import "./chunk-PH4JKL6T.js";
|
|
8
8
|
import "./chunk-E2KFPEBT.js";
|
|
9
9
|
|
|
10
10
|
// src/engine/kumaMiner.ts
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLatestVerifications,
|
|
3
|
+
saveVerification
|
|
4
|
+
} from "./chunk-2NBESJTH.js";
|
|
5
|
+
import {
|
|
6
|
+
sessionMemory
|
|
7
|
+
} from "./chunk-PH4JKL6T.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-ZIFLGJ5R.js";
|
|
6
6
|
import {
|
|
7
7
|
sessionMemory
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PH4JKL6T.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-FQ3YKJ6K.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-FQ3YKJ6K.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-FQ3YKJ6K.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.5",
|
|
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
|
-
};
|