@plumpslabs/kuma 2.3.7 → 2.3.9
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 +25 -3
- package/dist/{chunk-GDNAWLHF.js → chunk-3TX6Q37T.js} +89 -2
- package/dist/{chunk-X5TPBDKO.js → chunk-OJALFQ4H.js} +2 -2
- package/dist/chunk-Q2444HWO.js +282 -0
- package/dist/{chunk-BI7KD3SG.js → chunk-R6B3VUSB.js} +2 -2
- package/dist/chunk-RI3DKY62.js +155 -0
- package/dist/{chunk-FOQQ2CSL.js → chunk-SPHI2BOO.js} +1 -1
- package/dist/{chunk-K64NSHBR.js → chunk-ZKDYTYMS.js} +108 -2
- package/dist/contextDigest-5JXLH56Z.js +177 -0
- package/dist/domainRules-ED6EOFVY.js +20 -0
- package/dist/index.js +393 -198
- package/dist/kumaAstValidator-CNM7FHYA.js +150 -0
- package/dist/kumaCheckpoint-XIQWRMGV.js +207 -0
- package/dist/kumaCodeScanner-LFJGPJNH.js +566 -0
- package/dist/kumaContractEngine-KX27T4N7.js +305 -0
- package/dist/{kumaDb-DJUDLYBJ.js → kumaDb-IC7UX7PU.js} +3 -1
- package/dist/kumaDriftDetector-QBY6OJOO.js +237 -0
- package/dist/kumaGotchas-5HODT5RI.js +151 -0
- package/dist/{kumaGraph-35TAIBWD.js → kumaGraph-GUQM75WL.js} +8 -2
- package/dist/{kumaMemory-5SR3TGRL.js → kumaMemory-FMOWIODH.js} +2 -2
- package/dist/{kumaMiner-P5KCUSF7.js → kumaMiner-3RXEOCBP.js} +3 -3
- package/dist/kumaPolicyEngine-ORBWL5PT.js +311 -0
- package/dist/kumaProgressiveContext-CVSNPY3W.js +231 -0
- package/dist/{kumaSearch-LG2OYEJY.js → kumaSearch-B5WVYHHD.js} +1 -1
- package/dist/kumaTrajectory-T6DXGOG4.js +460 -0
- package/dist/{kumaVerifier-LFRNIGSL.js → kumaVerifier-KAYJHR5K.js} +25 -2
- package/dist/{kumaVisualize-ODCWFSUY.js → kumaVisualize-AFL7STLL.js} +1 -1
- package/dist/safetyAudit-32WSA4Z5.js +12 -0
- package/dist/{safetyScore-PJGRRBWP.js → safetyScore-WU27EOG4.js} +5 -5
- package/dist/{sessionMemory-HBBXUSIP.js → sessionMemory-4UWA3E5J.js} +1 -1
- package/package.json +18 -13
- package/packages/ide/studio/dist/index.js +140 -0
- package/packages/ide/studio/public/index.html +500 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendToLayer,
|
|
3
|
+
checkFileGotchas,
|
|
4
|
+
getActiveGotchas
|
|
5
|
+
} from "./chunk-Q2444HWO.js";
|
|
6
|
+
import {
|
|
7
|
+
sessionMemory
|
|
8
|
+
} from "./chunk-R6B3VUSB.js";
|
|
9
|
+
import {
|
|
10
|
+
getDb,
|
|
11
|
+
saveDb
|
|
12
|
+
} from "./chunk-3TX6Q37T.js";
|
|
13
|
+
import "./chunk-E2KFPEBT.js";
|
|
14
|
+
|
|
15
|
+
// src/engine/kumaGotchas.ts
|
|
16
|
+
async function ensureGotchasSchema() {
|
|
17
|
+
const db = await getDb();
|
|
18
|
+
db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS known_gotchas (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
file_path TEXT NOT NULL,
|
|
22
|
+
description TEXT NOT NULL,
|
|
23
|
+
severity TEXT NOT NULL DEFAULT 'medium'
|
|
24
|
+
CHECK(severity IN ('low','medium','high','critical')),
|
|
25
|
+
workaround TEXT,
|
|
26
|
+
added_by TEXT DEFAULT 'agent',
|
|
27
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
28
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
29
|
+
);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_gotchas_file ON known_gotchas(file_path);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_gotchas_severity ON known_gotchas(severity);
|
|
32
|
+
`);
|
|
33
|
+
saveDb();
|
|
34
|
+
}
|
|
35
|
+
async function addGotcha(entry) {
|
|
36
|
+
try {
|
|
37
|
+
await ensureGotchasSchema();
|
|
38
|
+
const db = await getDb();
|
|
39
|
+
const severity = entry.severity || "medium";
|
|
40
|
+
const formatted = [
|
|
41
|
+
`### ${entry.filePath} \u2014 ${entry.description}`,
|
|
42
|
+
`- **Issue**: ${entry.description}`,
|
|
43
|
+
`- **Severity**: ${severity}`,
|
|
44
|
+
entry.workaround ? `- **Workaround**: ${entry.workaround}` : "",
|
|
45
|
+
`- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
46
|
+
].filter(Boolean).join("\n");
|
|
47
|
+
db.run(
|
|
48
|
+
`INSERT INTO known_gotchas (file_path, description, severity, workaround) VALUES (?, ?, ?, ?)`,
|
|
49
|
+
[entry.filePath, entry.description, severity, entry.workaround || null]
|
|
50
|
+
);
|
|
51
|
+
const mdResult = appendToLayer("gotcha", formatted);
|
|
52
|
+
saveDb();
|
|
53
|
+
sessionMemory.recordToolCall("kuma_gotcha_add", {
|
|
54
|
+
filePath: entry.filePath,
|
|
55
|
+
severity
|
|
56
|
+
});
|
|
57
|
+
return `\u2705 **Gotcha recorded**: ${entry.filePath} \u2014 ${entry.description}
|
|
58
|
+
${mdResult}`;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return `\u274C Failed to add gotcha: ${err}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function listGotchas(params) {
|
|
64
|
+
try {
|
|
65
|
+
await ensureGotchasSchema();
|
|
66
|
+
const db = await getDb();
|
|
67
|
+
let sql = "SELECT * FROM known_gotchas WHERE 1=1";
|
|
68
|
+
const bind = [];
|
|
69
|
+
if (params.filePath) {
|
|
70
|
+
sql += " AND file_path LIKE ?";
|
|
71
|
+
bind.push(`%${params.filePath}%`);
|
|
72
|
+
}
|
|
73
|
+
if (params.severity) {
|
|
74
|
+
sql += " AND severity = ?";
|
|
75
|
+
bind.push(params.severity);
|
|
76
|
+
}
|
|
77
|
+
sql += " ORDER BY severity DESC, created_at DESC LIMIT 50";
|
|
78
|
+
const stmt = db.prepare(sql);
|
|
79
|
+
stmt.bind(bind);
|
|
80
|
+
const results = [];
|
|
81
|
+
while (stmt.step()) results.push(stmt.getAsObject());
|
|
82
|
+
stmt.free();
|
|
83
|
+
if (results.length === 0) {
|
|
84
|
+
return "\u2705 **No gotchas recorded** \u2014 legacy codebase looks clean.";
|
|
85
|
+
}
|
|
86
|
+
const lines = [
|
|
87
|
+
`\u26A0\uFE0F **Known Gotchas** \u2014 ${results.length} recorded`,
|
|
88
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
89
|
+
""
|
|
90
|
+
];
|
|
91
|
+
for (const g of results) {
|
|
92
|
+
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
93
|
+
lines.push(`${icon} [${g.severity}] ${g.file_path}`);
|
|
94
|
+
lines.push(` \u{1F4DD} ${g.description?.toString().substring(0, 100)}`);
|
|
95
|
+
if (g.workaround) lines.push(` \u{1F4A1} ${g.workaround.substring(0, 100)}`);
|
|
96
|
+
lines.push("");
|
|
97
|
+
}
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
} catch (err) {
|
|
100
|
+
return `Error: ${err}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function checkGotchasForFile(filePath) {
|
|
104
|
+
const warnings = [];
|
|
105
|
+
const markdownWarnings = checkFileGotchas(filePath);
|
|
106
|
+
try {
|
|
107
|
+
const gotchas = getActiveGotchas();
|
|
108
|
+
for (const g of gotchas) {
|
|
109
|
+
if (filePath.includes(g.filePath) || g.filePath.includes(filePath)) {
|
|
110
|
+
if (!markdownWarnings.some((w) => w.includes(g.description))) {
|
|
111
|
+
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
112
|
+
warnings.push(`${icon} **Known Gotcha**: ${g.description} (${g.severity})`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
return [.../* @__PURE__ */ new Set([...markdownWarnings, ...warnings])];
|
|
119
|
+
}
|
|
120
|
+
async function syncGotchasToDb() {
|
|
121
|
+
try {
|
|
122
|
+
await ensureGotchasSchema();
|
|
123
|
+
const db = await getDb();
|
|
124
|
+
const gotchas = getActiveGotchas();
|
|
125
|
+
let synced = 0;
|
|
126
|
+
for (const g of gotchas) {
|
|
127
|
+
const checkStmt = db.prepare(
|
|
128
|
+
`SELECT id FROM known_gotchas WHERE file_path = ? AND description = ?`
|
|
129
|
+
);
|
|
130
|
+
checkStmt.bind([g.filePath, g.description]);
|
|
131
|
+
const exists = checkStmt.step();
|
|
132
|
+
checkStmt.free();
|
|
133
|
+
if (exists) continue;
|
|
134
|
+
db.run(
|
|
135
|
+
`INSERT INTO known_gotchas (file_path, description, severity) VALUES (?, ?, ?)`,
|
|
136
|
+
[g.filePath, g.description, g.severity]
|
|
137
|
+
);
|
|
138
|
+
synced++;
|
|
139
|
+
}
|
|
140
|
+
saveDb();
|
|
141
|
+
return { synced };
|
|
142
|
+
} catch {
|
|
143
|
+
return { synced: 0 };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export {
|
|
147
|
+
addGotcha,
|
|
148
|
+
checkGotchasForFile,
|
|
149
|
+
listGotchas,
|
|
150
|
+
syncGotchasToDb
|
|
151
|
+
};
|
|
@@ -5,17 +5,20 @@ import {
|
|
|
5
5
|
formatFlow,
|
|
6
6
|
formatImpact,
|
|
7
7
|
getGraphStats,
|
|
8
|
+
listFederatedReferences,
|
|
9
|
+
nodeId,
|
|
8
10
|
queryGraph,
|
|
9
11
|
recordApiRoute,
|
|
10
12
|
recordFileDefinition,
|
|
11
13
|
recordFunctionCall,
|
|
12
14
|
recordImport,
|
|
13
15
|
recordTestRelation,
|
|
16
|
+
resolveFederatedNode,
|
|
14
17
|
searchGraph,
|
|
15
18
|
traceFlow,
|
|
16
19
|
upsertNode
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-ZKDYTYMS.js";
|
|
21
|
+
import "./chunk-3TX6Q37T.js";
|
|
19
22
|
import "./chunk-E2KFPEBT.js";
|
|
20
23
|
export {
|
|
21
24
|
addEdge,
|
|
@@ -24,12 +27,15 @@ export {
|
|
|
24
27
|
formatFlow,
|
|
25
28
|
formatImpact,
|
|
26
29
|
getGraphStats,
|
|
30
|
+
listFederatedReferences,
|
|
31
|
+
nodeId,
|
|
27
32
|
queryGraph,
|
|
28
33
|
recordApiRoute,
|
|
29
34
|
recordFileDefinition,
|
|
30
35
|
recordFunctionCall,
|
|
31
36
|
recordImport,
|
|
32
37
|
recordTestRelation,
|
|
38
|
+
resolveFederatedNode,
|
|
33
39
|
searchGraph,
|
|
34
40
|
traceFlow,
|
|
35
41
|
upsertNode
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
recordDecision,
|
|
5
5
|
scoreMemoryRelevance,
|
|
6
6
|
shouldRecordDecision
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-OJALFQ4H.js";
|
|
8
|
+
import "./chunk-R6B3VUSB.js";
|
|
9
9
|
import "./chunk-E2KFPEBT.js";
|
|
10
10
|
export {
|
|
11
11
|
formatDecisionTemplate,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
recordDecision
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-OJALFQ4H.js";
|
|
4
|
+
import "./chunk-R6B3VUSB.js";
|
|
5
5
|
import {
|
|
6
6
|
recordDecisionLog
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-3TX6Q37T.js";
|
|
8
8
|
import "./chunk-E2KFPEBT.js";
|
|
9
9
|
|
|
10
10
|
// src/engine/kumaMiner.ts
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionMemory
|
|
3
|
+
} from "./chunk-R6B3VUSB.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-32WSA4Z5.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
|
+
};
|