@plumpslabs/kuma 2.3.3 → 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 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` | Integrated auto-verification — auto-detect runner & execute scoped tests. |
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-N6QMWZGQ.js";
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-NHEAXC2V.js");
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-MTH4DRP7.js";
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-45V6XJRH.js");
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-7VSQ4QLW.js");
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-7VSQ4QLW.js");
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 {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-N6QMWZGQ.js";
3
+ } from "./chunk-77G3APNG.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -764,23 +764,92 @@ async function runGarbageCollection() {
764
764
  async function runDoctor() {
765
765
  try {
766
766
  const db = await getDb();
767
- 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
+ ];
768
772
  try {
769
773
  const integrity = db.exec("PRAGMA integrity_check");
770
774
  const result = integrity[0]?.values[0]?.[0] || "ok";
771
- checks.push(`Integrity: ${result === "ok" ? "\u2705" : "\u274C " + result}`);
775
+ checks.push(`**Database Integrity**: ${result === "ok" ? "\u2705" : "\u274C " + result}`);
772
776
  } catch {
773
- checks.push("Integrity: \u274C check failed");
777
+ checks.push("**Database Integrity**: \u274C check failed");
774
778
  }
775
- const tables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "decision_log", "file_summaries"];
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"];
776
780
  let existing = 0;
777
- for (const t of tables) {
781
+ for (const t of allTables) {
778
782
  try {
779
783
  if (db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='${t}'`)[0]?.values?.length) existing++;
780
784
  } catch {
781
785
  }
782
786
  }
783
- checks.push(`Schema: ${existing}/${tables.length} tables present`);
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
+ }
784
853
  return checks.join("\n");
785
854
  } catch (err) {
786
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-FSDW4F3U.js";
18
- import {
19
- buildDriftMessages,
20
- getGitDiffStat,
21
- getSessionStats,
22
- getUnresolvedCount
23
- } from "./chunk-MSKQRKME.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-MTH4DRP7.js";
43
+ } from "./chunk-NKNXJG2E.js";
50
44
  import {
51
45
  formatDecisionTemplate,
52
46
  getProactiveMemories,
53
47
  recordDecision,
54
48
  scoreMemoryRelevance
55
- } from "./chunk-RWPJ5RWM.js";
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-N6QMWZGQ.js";
58
+ } from "./chunk-77G3APNG.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-GX2YMCQT.js");
200
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-7VSQ4QLW.js");
199
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-ZAOUPG6A.js");
200
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-I4AJ5A7H.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-GX2YMCQT.js");
374
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-7VSQ4QLW.js");
373
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-ZAOUPG6A.js");
374
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-I4AJ5A7H.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-UFRSJTWL.js");
504
+ const { shouldRecordDecision } = await import("./kumaMemory-2HW4JLJU.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-NHEAXC2V.js");
582
+ const { searchGraph: searchGraph2 } = await import("./kumaGraph-KXI3Q7QF.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-UFDDVBRP.js");
657
+ const { mineHistoricalDecisions } = await import("./kumaMiner-HZUBNYYP.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-GX2YMCQT.js");
1794
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-ZAOUPG6A.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);
@@ -1895,9 +1895,17 @@ async function handleClean(_params) {
1895
1895
  return `${result}
1896
1896
  \u{1F4A1} Drift warnings have been reset. Any temporary debug artifacts are now cleared.`;
1897
1897
  }
1898
+ var _lastVerifyCall = 0;
1899
+ var VERIFY_COOLDOWN_MS = 3e4;
1898
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;
1899
1907
  sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
1900
- const { runAutoVerification } = await import("./kumaVerifier-Y4FQ2LOD.js");
1908
+ const { runAutoVerification } = await import("./kumaVerifier-SSS4ZTJY.js");
1901
1909
  return await runAutoVerification({
1902
1910
  scope: params.scope || params.filePath,
1903
1911
  force: params.force,
@@ -2091,6 +2099,71 @@ If no flags specified, you'll be prompted to select files interactively.
2091
2099
  }
2092
2100
  async function main() {
2093
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
+ }
2094
2167
  if (args[0] === "init") {
2095
2168
  const flags = args.slice(1);
2096
2169
  if (flags.includes("--help") || flags.includes("-h")) {
@@ -2238,7 +2311,7 @@ async function main() {
2238
2311
  console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
2239
2312
  }
2240
2313
  try {
2241
- const { buildFromSessionMemory } = await import("./kumaGraph-NHEAXC2V.js");
2314
+ const { buildFromSessionMemory } = await import("./kumaGraph-KXI3Q7QF.js");
2242
2315
  const edgeCount = await buildFromSessionMemory();
2243
2316
  if (edgeCount > 0) {
2244
2317
  console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
@@ -2258,7 +2331,7 @@ async function main() {
2258
2331
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
2259
2332
  }
2260
2333
  try {
2261
- const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-7VSQ4QLW.js");
2334
+ const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-I4AJ5A7H.js");
2262
2335
  const db = await getDb2();
2263
2336
  db.run(
2264
2337
  `INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
@@ -28,7 +28,7 @@ import {
28
28
  saveVerification,
29
29
  updateDecisionStatus,
30
30
  updateTodoStatus
31
- } from "./chunk-MTH4DRP7.js";
31
+ } from "./chunk-NKNXJG2E.js";
32
32
  import "./chunk-E2KFPEBT.js";
33
33
  export {
34
34
  addContextNote,
@@ -14,8 +14,8 @@ import {
14
14
  searchGraph,
15
15
  traceFlow,
16
16
  upsertNode
17
- } from "./chunk-FSDW4F3U.js";
18
- import "./chunk-MTH4DRP7.js";
17
+ } from "./chunk-5YCQG7V7.js";
18
+ import "./chunk-NKNXJG2E.js";
19
19
  import "./chunk-E2KFPEBT.js";
20
20
  export {
21
21
  addEdge,
@@ -4,8 +4,8 @@ import {
4
4
  recordDecision,
5
5
  scoreMemoryRelevance,
6
6
  shouldRecordDecision
7
- } from "./chunk-RWPJ5RWM.js";
8
- import "./chunk-N6QMWZGQ.js";
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-MTH4DRP7.js";
3
+ } from "./chunk-NKNXJG2E.js";
4
4
  import {
5
5
  recordDecision
6
- } from "./chunk-RWPJ5RWM.js";
7
- import "./chunk-N6QMWZGQ.js";
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-MSKQRKME.js";
5
+ } from "./chunk-BKEDQFEH.js";
6
6
  import {
7
7
  sessionMemory
8
- } from "./chunk-N6QMWZGQ.js";
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-7VSQ4QLW.js");
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-7VSQ4QLW.js");
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-7VSQ4QLW.js");
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 {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-N6QMWZGQ.js";
3
+ } from "./chunk-77G3APNG.js";
4
4
  export {
5
5
  sessionMemory
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.3.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,123 +0,0 @@
1
- import {
2
- saveVerification
3
- } from "./chunk-MTH4DRP7.js";
4
- import {
5
- sessionMemory
6
- } from "./chunk-N6QMWZGQ.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
- if (fs.existsSync(path.join(root, "package.json"))) {
43
- try {
44
- const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
45
- if (pkg.dependencies || pkg.devDependencies) {
46
- if (fs.existsSync(path.join(root, "tsconfig.json"))) {
47
- return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
48
- }
49
- const srcDir = path.join(root, "src");
50
- if (fs.existsSync(srcDir)) {
51
- return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
52
- }
53
- }
54
- } catch {
55
- }
56
- }
57
- return { runner: "unknown", baseCommand: "npm test || echo 'No test runner configured'" };
58
- }
59
- async function runAutoVerification(options = {}) {
60
- const startTime = Date.now();
61
- const root = process.cwd();
62
- const scope = options.scope || "session-impact";
63
- const timeoutMs = options.timeoutMs || 3e4;
64
- const { runner, baseCommand } = detectTestRunner(root);
65
- let testFiles = [];
66
- const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
67
- if (options.scope) {
68
- const term = options.scope.toLowerCase();
69
- testFiles = modified.filter((f) => f.toLowerCase().includes(term));
70
- if (testFiles.length === 0) {
71
- try {
72
- const { default: glob } = await import("fast-glob");
73
- const found = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
74
- testFiles = found;
75
- } catch {
76
- }
77
- }
78
- } else {
79
- testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
80
- }
81
- let fullCommand = baseCommand;
82
- if (testFiles.length > 0 && runner === "npm") {
83
- fullCommand = `${baseCommand} -- ${testFiles.map((f) => `"${f}"`).join(" ")}`;
84
- }
85
- return new Promise((resolve) => {
86
- exec(fullCommand, { cwd: root, timeout: timeoutMs }, async (error, stdout, stderr) => {
87
- const durationMs = Date.now() - startTime;
88
- const rawOutput = (stdout + "\n" + stderr).trim();
89
- const passed = !error;
90
- const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
91
- await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
92
- const statusSymbol = passed ? "\u2705" : "\u{1F534}";
93
- const summaryHeader = `${statusSymbol} **Auto-Verification ${passed ? "PASSED" : "FAILED"}**
94
- \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`;
95
- const details = [
96
- summaryHeader,
97
- `\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
98
- `\u{1F4BB} **Command**: \`${fullCommand}\``,
99
- `\u{1F3AF} **Scope**: \`${scope}\`${testFiles.length > 0 ? ` (${testFiles.length} file(s) matched)` : ""}`,
100
- `\u23F1\uFE0F **Duration**: ${durationMs}ms`,
101
- ""
102
- ];
103
- if (!passed) {
104
- details.push("\u26A0\uFE0F **BLOCKER**: Verification failed! Please fix test failures before shipping or continuing edits.");
105
- details.push("```text");
106
- details.push(rawOutput.substring(0, 1500));
107
- details.push("```");
108
- } else {
109
- details.push("\u{1F389} All scoped tests passed cleanly!");
110
- if (rawOutput) {
111
- details.push("```text");
112
- details.push(rawOutput.substring(0, 800));
113
- details.push("```");
114
- }
115
- }
116
- resolve(details.join("\n"));
117
- });
118
- });
119
- }
120
- export {
121
- detectTestRunner,
122
- runAutoVerification
123
- };