@plumpslabs/kuma 2.3.7 → 2.3.8

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.
@@ -0,0 +1,311 @@
1
+ import {
2
+ sessionMemory
3
+ } from "./chunk-SLRRDKQ2.js";
4
+ import {
5
+ getProjectRoot
6
+ } from "./chunk-E2KFPEBT.js";
7
+
8
+ // src/engine/kumaPolicyEngine.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ var DEFAULT_POLICY_CONFIG = {
12
+ version: 1,
13
+ name: "Kuma Default Security Policy",
14
+ description: "Default safety policy for AI coding agents",
15
+ rules: [
16
+ {
17
+ id: "block-rm-rf",
18
+ description: "Block recursive force deletion",
19
+ severity: "error",
20
+ patterns: ["rm -rf", "rm -fr", "rm --recursive --force"],
21
+ action: "block",
22
+ requireOverride: true
23
+ },
24
+ {
25
+ id: "block-git-force-push",
26
+ description: "Block force push to git",
27
+ severity: "error",
28
+ patterns: ["git push --force", "git push -f"],
29
+ action: "block",
30
+ requireOverride: true
31
+ },
32
+ {
33
+ id: "block-npm-publish",
34
+ description: "Block package publishing",
35
+ severity: "error",
36
+ patterns: ["npm publish", "yarn publish", "pnpm publish"],
37
+ action: "block",
38
+ requireOverride: true
39
+ },
40
+ {
41
+ id: "block-pipe-to-shell",
42
+ description: "Block curl/wget pipe to shell",
43
+ severity: "error",
44
+ patterns: ["curl | bash", "curl | sh", "wget -O - | bash", "curl | sudo"],
45
+ action: "block",
46
+ requireOverride: true
47
+ },
48
+ {
49
+ id: "warn-destructive-db",
50
+ description: "Warn about destructive database operations",
51
+ severity: "warning",
52
+ patterns: ["DROP DATABASE", "DROP TABLE", "TRUNCATE", "DELETE FROM"],
53
+ action: "warn"
54
+ },
55
+ {
56
+ id: "block-prod-deploy",
57
+ description: "Block production deployments without override",
58
+ severity: "error",
59
+ patterns: ["deploy --production", "deploy --prod", "deploy:production"],
60
+ action: "block",
61
+ requireOverride: true
62
+ }
63
+ ],
64
+ block_commands: [
65
+ "rm -rf",
66
+ "rm -fr",
67
+ "git push --force",
68
+ "git push -f",
69
+ "npm publish",
70
+ "yarn publish",
71
+ "pnpm publish",
72
+ "curl | bash",
73
+ "curl | sh"
74
+ ],
75
+ never_touch: [
76
+ ".env",
77
+ ".env.local",
78
+ ".env.production",
79
+ ".env.development",
80
+ "package-lock.json",
81
+ "yarn.lock",
82
+ "pnpm-lock.yaml",
83
+ "node_modules/**"
84
+ ]
85
+ };
86
+ function loadPolicyConfig() {
87
+ try {
88
+ const root = getProjectRoot();
89
+ const policyPath = path.join(root, ".kuma", "POLICY.json");
90
+ if (fs.existsSync(policyPath)) {
91
+ const content = fs.readFileSync(policyPath, "utf-8");
92
+ const config = JSON.parse(content);
93
+ return { ...DEFAULT_POLICY_CONFIG, ...config };
94
+ }
95
+ const ymlPath = path.join(root, ".kuma", "policy.yml");
96
+ if (fs.existsSync(ymlPath)) {
97
+ console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
98
+ }
99
+ } catch (err) {
100
+ console.error(`[PolicyEngine] Failed to load policy config: ${err}`);
101
+ }
102
+ return DEFAULT_POLICY_CONFIG;
103
+ }
104
+ function savePolicyConfig(config) {
105
+ try {
106
+ const root = getProjectRoot();
107
+ const policyPath = path.join(root, ".kuma", "POLICY.json");
108
+ const dir = path.dirname(policyPath);
109
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
110
+ fs.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
111
+ return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
112
+ } catch (err) {
113
+ return `\u274C Failed to save policy: ${err}`;
114
+ }
115
+ }
116
+ function evaluateCommand(command) {
117
+ const config = loadPolicyConfig();
118
+ const warnings = [];
119
+ const cmdLower = command.toLowerCase();
120
+ for (const rule of config.rules) {
121
+ const matches = rule.patterns.some((p) => cmdLower.includes(p.toLowerCase()));
122
+ if (!matches) continue;
123
+ if (rule.action === "block" || rule.severity === "error") {
124
+ return {
125
+ allowed: false,
126
+ blockedBy: rule,
127
+ warnings,
128
+ requiresOverride: rule.requireOverride ?? false,
129
+ message: `\u26D4 Policy violation: "${rule.description}" (rule: ${rule.id})`
130
+ };
131
+ }
132
+ if (rule.action === "warn") {
133
+ warnings.push(rule);
134
+ }
135
+ }
136
+ for (const cmd of config.block_commands || []) {
137
+ if (cmdLower.includes(cmd.toLowerCase())) {
138
+ return {
139
+ allowed: false,
140
+ blockedBy: {
141
+ id: "block-command",
142
+ description: `Blocked command: ${cmd}`,
143
+ severity: "error",
144
+ patterns: [cmd],
145
+ action: "block",
146
+ requireOverride: true
147
+ },
148
+ warnings,
149
+ requiresOverride: true,
150
+ message: `\u26D4 Command matches blocked pattern: "${cmd}"`
151
+ };
152
+ }
153
+ }
154
+ return {
155
+ allowed: true,
156
+ blockedBy: null,
157
+ warnings,
158
+ requiresOverride: false,
159
+ message: warnings.length > 0 ? `\u26A0\uFE0F Command allowed with ${warnings.length} warning(s)` : "\u2705 Command allowed by policy"
160
+ };
161
+ }
162
+ function evaluateFilePath(filePath) {
163
+ const config = loadPolicyConfig();
164
+ const filesToCheck = config.never_touch || DEFAULT_POLICY_CONFIG.never_touch || [];
165
+ for (const pattern of filesToCheck) {
166
+ if (matchesGlob(pattern, filePath)) {
167
+ return {
168
+ allowed: false,
169
+ blockedBy: {
170
+ id: "never-touch",
171
+ description: `File matches never_touch pattern: ${pattern}`,
172
+ severity: "error",
173
+ patterns: [pattern],
174
+ action: "block",
175
+ requireOverride: true
176
+ },
177
+ warnings: [],
178
+ requiresOverride: true,
179
+ message: `\u26D4 File "${filePath}" is protected by never_touch policy (pattern: ${pattern}). Use kuma_safety({ action: 'override' }) to bypass.`
180
+ };
181
+ }
182
+ }
183
+ return {
184
+ allowed: true,
185
+ blockedBy: null,
186
+ warnings: [],
187
+ requiresOverride: false,
188
+ message: "\u2705 File allowed by policy"
189
+ };
190
+ }
191
+ function evaluateDatabaseAction(action) {
192
+ const cmdLower = action.toLowerCase();
193
+ if ((cmdLower.includes("drop") || cmdLower.includes("truncate")) && (cmdLower.includes("database") || cmdLower.includes("table") || cmdLower.includes("schema"))) {
194
+ return {
195
+ allowed: false,
196
+ blockedBy: {
197
+ id: "block-destructive-db",
198
+ description: "Destructive database operation blocked",
199
+ severity: "error",
200
+ patterns: ["DROP DATABASE", "DROP TABLE", "TRUNCATE", "DELETE FROM"],
201
+ action: "block",
202
+ requireOverride: true
203
+ },
204
+ warnings: [],
205
+ requiresOverride: true,
206
+ message: `\u26D4 Destructive database action blocked: "${action}". Use kuma_safety({ action: 'override' }) with a clear reason.`
207
+ };
208
+ }
209
+ return {
210
+ allowed: true,
211
+ blockedBy: null,
212
+ warnings: [],
213
+ requiresOverride: false,
214
+ message: "\u2705 Database action allowed by policy"
215
+ };
216
+ }
217
+ async function processOverride(request) {
218
+ const { recordAudit } = await import("./safetyAudit-55IVCG5B.js");
219
+ await recordAudit({
220
+ timestamp: Math.floor(Date.now() / 1e3),
221
+ toolName: request.toolName,
222
+ action: "policy_override",
223
+ filePath: request.filePath,
224
+ riskLevel: "high",
225
+ policyViolations: 1,
226
+ allowed: true,
227
+ durationMs: 0,
228
+ metadata: {
229
+ override: true,
230
+ reason: request.reason,
231
+ command: request.command
232
+ }
233
+ });
234
+ sessionMemory.recordToolCall("kuma_policy_override", {
235
+ toolName: request.toolName,
236
+ reason: request.reason,
237
+ filePath: request.filePath,
238
+ command: request.command
239
+ });
240
+ return [
241
+ `\u26A0\uFE0F **Policy Override** \u2014 ${request.toolName}`,
242
+ `\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`,
243
+ "",
244
+ `\u{1F4DD} Reason: ${request.reason}`,
245
+ request.filePath ? `\u{1F4C4} File: ${request.filePath}` : "",
246
+ request.command ? `\u{1F4BB} Command: ${request.command}` : "",
247
+ "",
248
+ "\u{1F534} This override is recorded in the safety audit trail.",
249
+ "\u{1F534} Overrides reduce project safety \u2014 use sparingly."
250
+ ].filter(Boolean).join("\n");
251
+ }
252
+ function formatPolicyStatus() {
253
+ const config = loadPolicyConfig();
254
+ const lines = [
255
+ "\u{1F4DC} **Policy-as-Code Engine**",
256
+ "\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",
257
+ "",
258
+ `\u{1F4CB} Policy: ${config.name}`,
259
+ `\u{1F522} Version: ${config.version}`,
260
+ `\u{1F4DD} Rules: ${config.rules.length}`,
261
+ "",
262
+ "**Rules:**"
263
+ ];
264
+ for (const rule of config.rules) {
265
+ const icon = rule.action === "block" ? "\u{1F534}" : rule.action === "warn" ? "\u{1F7E1}" : "\u{1F7E2}";
266
+ const override = rule.requireOverride ? " \u{1F511} override" : "";
267
+ lines.push(` ${icon} [${rule.action}] ${rule.description}${override}`);
268
+ lines.push(` Patterns: ${rule.patterns.slice(0, 3).join(", ")}`);
269
+ }
270
+ const neverTouch = config.never_touch || [];
271
+ if (neverTouch.length > 0) {
272
+ lines.push("", "**Protected Files (never_touch):**");
273
+ for (const p of neverTouch) {
274
+ lines.push(` \u{1F6E1}\uFE0F ${p}`);
275
+ }
276
+ }
277
+ return lines.join("\n");
278
+ }
279
+ function matchesGlob(pattern, filePath) {
280
+ const normalizedPattern = pattern.replace(/\\/g, "/");
281
+ const normalizedPath = filePath.replace(/\\/g, "/");
282
+ let regexStr = "^";
283
+ for (let i = 0; i < normalizedPattern.length; i++) {
284
+ const ch = normalizedPattern[i];
285
+ if (ch === "*" && normalizedPattern[i + 1] === "*" && normalizedPattern[i + 2] === "/") {
286
+ regexStr += "(.+/)?";
287
+ i += 2;
288
+ } else if (ch === "*") {
289
+ regexStr += "[^/]*";
290
+ } else if (ch === "?") {
291
+ regexStr += "[^/]";
292
+ } else {
293
+ regexStr += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
294
+ }
295
+ }
296
+ regexStr += "$";
297
+ try {
298
+ return new RegExp(regexStr).test(normalizedPath);
299
+ } catch {
300
+ return false;
301
+ }
302
+ }
303
+ export {
304
+ evaluateCommand,
305
+ evaluateDatabaseAction,
306
+ evaluateFilePath,
307
+ formatPolicyStatus,
308
+ loadPolicyConfig,
309
+ processOverride,
310
+ savePolicyConfig
311
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-GDNAWLHF.js";
3
+ } from "./chunk-NAM7SCBT.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-BI7KD3SG.js";
3
+ } from "./chunk-SLRRDKQ2.js";
4
4
  import {
5
5
  getLatestVerifications,
6
6
  saveVerification
7
- } from "./chunk-GDNAWLHF.js";
7
+ } from "./chunk-NAM7SCBT.js";
8
8
  import "./chunk-E2KFPEBT.js";
9
9
 
10
10
  // src/engine/kumaVerifier.ts
@@ -56,6 +56,24 @@ var _localRunning = false;
56
56
  var _currentProcess = null;
57
57
  var STALE_RESULT_MS = 3e5;
58
58
  var DEFAULT_TIMEOUT_MS = 3e4;
59
+ var RUNAWAY_WINDOW_MS = 3e5;
60
+ var RUNAWAY_MAX_CALLS = 3;
61
+ var _verifyCallTimestamps = [];
62
+ function checkRunaway() {
63
+ const now = Date.now();
64
+ while (_verifyCallTimestamps.length > 0 && _verifyCallTimestamps[0] < now - RUNAWAY_WINDOW_MS) {
65
+ _verifyCallTimestamps.shift();
66
+ }
67
+ if (_verifyCallTimestamps.length >= RUNAWAY_MAX_CALLS) {
68
+ const oldestInWindow = _verifyCallTimestamps[0];
69
+ const waitMs = RUNAWAY_WINDOW_MS - (now - oldestInWindow);
70
+ const waitSec = Math.ceil(waitMs / 1e3);
71
+ return `\u26D4 **Runaway protection active** \u2014 ${RUNAWAY_MAX_CALLS}+ verify calls in the last 5 minutes. Please wait ${waitSec}s before trying again.
72
+ \u{1F4A1} This prevents resource exhaustion (CPU/RAM) from uncontrolled test spawning.`;
73
+ }
74
+ _verifyCallTimestamps.push(now);
75
+ return null;
76
+ }
59
77
  function getRunningVerificationPid() {
60
78
  return _currentProcess?.pid ?? null;
61
79
  }
@@ -126,6 +144,11 @@ async function runAutoVerification(options = {}) {
126
144
  const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
127
145
  const denial = checkAllowed(root);
128
146
  if (denial) return denial;
147
+ const runawayBlock = checkRunaway();
148
+ if (runawayBlock) {
149
+ releaseFileLock(root);
150
+ return runawayBlock;
151
+ }
129
152
  try {
130
153
  if (!options.force) {
131
154
  const cached = await checkStaleness(scope);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-GDNAWLHF.js";
3
+ } from "./chunk-NAM7SCBT.js";
4
4
  import "./chunk-E2KFPEBT.js";
5
5
 
6
6
  // src/engine/kumaVisualize.ts
@@ -0,0 +1,12 @@
1
+ import {
2
+ auditStats,
3
+ queryAudit,
4
+ recordAudit
5
+ } from "./chunk-PTEPJK6M.js";
6
+ import "./chunk-NAM7SCBT.js";
7
+ import "./chunk-E2KFPEBT.js";
8
+ export {
9
+ auditStats,
10
+ queryAudit,
11
+ recordAudit
12
+ };
@@ -2,10 +2,10 @@ import {
2
2
  getGitDiffStat,
3
3
  getSessionStats,
4
4
  getUnresolvedCount
5
- } from "./chunk-FOQQ2CSL.js";
5
+ } from "./chunk-FL2TLLMX.js";
6
6
  import {
7
7
  sessionMemory
8
- } from "./chunk-BI7KD3SG.js";
8
+ } from "./chunk-SLRRDKQ2.js";
9
9
  import "./chunk-E2KFPEBT.js";
10
10
 
11
11
  // src/engine/safetyScore.ts
@@ -60,7 +60,7 @@ async function computeSafetyScore(inputGoal) {
60
60
  totalScore += 20;
61
61
  }
62
62
  try {
63
- const { getDb } = await import("./kumaDb-DJUDLYBJ.js");
63
+ const { getDb } = await import("./kumaDb-6D53ERFP.js");
64
64
  const db = await getDb();
65
65
  const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
66
66
  const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
@@ -91,7 +91,7 @@ async function computeSafetyScore(inputGoal) {
91
91
  totalScore += 5;
92
92
  }
93
93
  try {
94
- const { getDb } = await import("./kumaDb-DJUDLYBJ.js");
94
+ const { getDb } = await import("./kumaDb-6D53ERFP.js");
95
95
  const db = await getDb();
96
96
  const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
97
97
  checks.push({
@@ -116,7 +116,7 @@ async function computeSafetyScore(inputGoal) {
116
116
  const hasRunTests = stats.hasRunTests;
117
117
  let latestVerif = null;
118
118
  try {
119
- const { getLatestVerifications } = await import("./kumaDb-DJUDLYBJ.js");
119
+ const { getLatestVerifications } = await import("./kumaDb-6D53ERFP.js");
120
120
  const verifs = await getLatestVerifications(1);
121
121
  if (verifs.length > 0) latestVerif = verifs[0];
122
122
  } catch {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-BI7KD3SG.js";
3
+ } from "./chunk-SLRRDKQ2.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.7",
3
+ "version": "2.3.8",
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",