@plumpslabs/kuma 2.3.20 → 2.3.23

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.
Files changed (39) hide show
  1. package/README.md +18 -0
  2. package/dist/index.js +10316 -380
  3. package/package.json +2 -2
  4. package/packages/ide/studio/dist/index.js +90 -9
  5. package/packages/ide/studio/public/index.html +528 -233
  6. package/dist/agentDetector-YOWQVJFR.js +0 -186
  7. package/dist/chunk-3BRBJZ7P.js +0 -1055
  8. package/dist/chunk-3OHYYXYN.js +0 -71
  9. package/dist/chunk-ABKE45T4.js +0 -1264
  10. package/dist/chunk-E2KFPEBT.js +0 -183
  11. package/dist/chunk-FKRSI5U5.js +0 -282
  12. package/dist/chunk-GFLSAXAH.js +0 -155
  13. package/dist/chunk-L7F67KUP.js +0 -172
  14. package/dist/chunk-LVKOGXLC.js +0 -658
  15. package/dist/chunk-PRUTTZBS.js +0 -1113
  16. package/dist/contextDigest-QB5XHPXE.js +0 -177
  17. package/dist/domainRules-QLPAQASB.js +0 -20
  18. package/dist/init-PL4XL662.js +0 -15
  19. package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
  20. package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
  21. package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
  22. package/dist/kumaContractEngine-KX27T4N7.js +0 -305
  23. package/dist/kumaDb-4XZ5S2LH.js +0 -65
  24. package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
  25. package/dist/kumaGotchas-XRGFFBTA.js +0 -151
  26. package/dist/kumaGraph-UMXZNGYF.js +0 -44
  27. package/dist/kumaMemory-FBJMV77G.js +0 -16
  28. package/dist/kumaMiner-XJETL7TL.js +0 -176
  29. package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
  30. package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
  31. package/dist/kumaSearch-PV4QTKE7.js +0 -321
  32. package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
  33. package/dist/kumaVerifier-6YEGC77M.js +0 -265
  34. package/dist/kumaVisualize-264OEBGJ.js +0 -264
  35. package/dist/pathValidator-V4DC6U6Z.js +0 -22
  36. package/dist/safetyAudit-O45SPNTS.js +0 -12
  37. package/dist/safetyScore-TMMRD2MV.js +0 -333
  38. package/dist/sessionMemory-YPKVIOMV.js +0 -6
  39. package/dist/skillGenerator-PWEJKZNX.js +0 -304
@@ -1,151 +0,0 @@
1
- import {
2
- appendToLayer,
3
- checkFileGotchas,
4
- getActiveGotchas
5
- } from "./chunk-FKRSI5U5.js";
6
- import {
7
- sessionMemory
8
- } from "./chunk-LVKOGXLC.js";
9
- import {
10
- getDb,
11
- saveDb
12
- } from "./chunk-3BRBJZ7P.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
- };
@@ -1,44 +0,0 @@
1
- import {
2
- addEdge,
3
- analyzeImpact,
4
- buildFromSessionMemory,
5
- formatFlow,
6
- formatImpact,
7
- getGraphStats,
8
- listFederatedReferences,
9
- nodeId,
10
- queryGraph,
11
- recordApiRoute,
12
- recordDomainFlow,
13
- recordFileDefinition,
14
- recordFunctionCall,
15
- recordImport,
16
- recordTestRelation,
17
- resolveFederatedNode,
18
- searchGraph,
19
- traceFlow,
20
- upsertNode
21
- } from "./chunk-PRUTTZBS.js";
22
- import "./chunk-3BRBJZ7P.js";
23
- import "./chunk-E2KFPEBT.js";
24
- export {
25
- addEdge,
26
- analyzeImpact,
27
- buildFromSessionMemory,
28
- formatFlow,
29
- formatImpact,
30
- getGraphStats,
31
- listFederatedReferences,
32
- nodeId,
33
- queryGraph,
34
- recordApiRoute,
35
- recordDomainFlow,
36
- recordFileDefinition,
37
- recordFunctionCall,
38
- recordImport,
39
- recordTestRelation,
40
- resolveFederatedNode,
41
- searchGraph,
42
- traceFlow,
43
- upsertNode
44
- };
@@ -1,16 +0,0 @@
1
- import {
2
- formatDecisionTemplate,
3
- getProactiveMemories,
4
- recordDecision,
5
- scoreMemoryRelevance,
6
- shouldRecordDecision
7
- } from "./chunk-L7F67KUP.js";
8
- import "./chunk-LVKOGXLC.js";
9
- import "./chunk-E2KFPEBT.js";
10
- export {
11
- formatDecisionTemplate,
12
- getProactiveMemories,
13
- recordDecision,
14
- scoreMemoryRelevance,
15
- shouldRecordDecision
16
- };
@@ -1,176 +0,0 @@
1
- import {
2
- recordDecision
3
- } from "./chunk-L7F67KUP.js";
4
- import "./chunk-LVKOGXLC.js";
5
- import {
6
- recordDecisionLog
7
- } from "./chunk-3BRBJZ7P.js";
8
- import "./chunk-E2KFPEBT.js";
9
-
10
- // src/engine/kumaMiner.ts
11
- import fs from "fs";
12
- import path from "path";
13
- import { execSync } from "child_process";
14
- import fastGlob from "fast-glob";
15
- async function mineHistoricalDecisions(options = {}) {
16
- const root = process.cwd();
17
- const limit = options.limit || 15;
18
- const candidates = [];
19
- try {
20
- const sinceFlag = options.since ? `--since="${options.since}"` : `--since="1 year"`;
21
- const gitCmd = `git log ${sinceFlag} -n 100 --pretty=format:"%h|%an|%ad|%s" --date=short`;
22
- const gitOutput = execSync(gitCmd, { cwd: root, encoding: "utf-8", timeout: 15e3 });
23
- const keywords = ["fix", "revert", "hack", "workaround", "urgent", "hotfix", "don't touch", "temporary", "deprecated", "workaround"];
24
- const lines = gitOutput.split("\n").filter(Boolean);
25
- const matchingHashes = [];
26
- for (const line of lines) {
27
- const parts = line.split("|");
28
- if (parts.length < 4) continue;
29
- const [hash, author, date, subject] = parts;
30
- const lowerSubj = subject.toLowerCase();
31
- const matchedKeyword = keywords.find((kw) => lowerSubj.includes(kw));
32
- if (matchedKeyword) {
33
- matchingHashes.push({ hash, author, date, subject, keyword: matchedKeyword });
34
- if (matchingHashes.length >= limit) break;
35
- }
36
- }
37
- let changedFilesMap = /* @__PURE__ */ new Map();
38
- if (matchingHashes.length > 0) {
39
- try {
40
- const hashesStr = matchingHashes.map((h) => h.hash).join(" ");
41
- const batchOutput = execSync(
42
- `git show --name-only --oneline ${hashesStr} 2>/dev/null | head -200`,
43
- { cwd: root, encoding: "utf-8", timeout: 1e4, maxBuffer: 256 * 1024 }
44
- );
45
- let currentHash = "";
46
- const files = [];
47
- for (const line of batchOutput.split("\n")) {
48
- if (matchingHashes.find((h) => line.startsWith(h.hash))) {
49
- if (currentHash && files.length > 0) {
50
- changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
51
- }
52
- currentHash = line.split(" ")[0];
53
- files.length = 0;
54
- } else if (line.trim() && !line.startsWith("diff")) {
55
- files.push(line.trim());
56
- }
57
- }
58
- if (currentHash && files.length > 0) {
59
- changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
60
- }
61
- } catch {
62
- }
63
- }
64
- for (const { hash, author, date, subject, keyword: matchedKeyword } of matchingHashes) {
65
- let changedFiles = changedFilesMap.get(hash) || "";
66
- if (!changedFiles) {
67
- try {
68
- changedFiles = execSync(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8", timeout: 5e3 }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
69
- } catch {
70
- }
71
- candidates.push({
72
- id: `git-${hash}`,
73
- type: "git_commit",
74
- title: `[Git Commit] ${subject}`,
75
- context: `Commit ${hash} by ${author} on ${date}.${changedFiles ? ` Files touched: ${changedFiles}` : ""}`,
76
- rationale: `Historical signal pattern "${matchedKeyword}" found in commit message.`,
77
- commitHash: hash,
78
- pattern: matchedKeyword
79
- });
80
- if (candidates.length >= limit) break;
81
- }
82
- }
83
- } catch {
84
- }
85
- if (candidates.length < limit) {
86
- try {
87
- const scopePattern = options.scope ? `**/*${options.scope}*.*` : "**/*.{ts,js,py,go,rs,java,md}";
88
- const files = await fastGlob(scopePattern, {
89
- cwd: root,
90
- ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
91
- onlyFiles: true
92
- });
93
- const commentPatterns = [
94
- { tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)/i },
95
- { tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)/i },
96
- { tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)/i },
97
- { tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)/i },
98
- { tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)/i }
99
- ];
100
- for (const file of files.slice(0, 50)) {
101
- if (candidates.length >= limit) break;
102
- const fullPath = path.join(root, file);
103
- if (!fs.existsSync(fullPath)) continue;
104
- const content = fs.readFileSync(fullPath, "utf-8");
105
- const lines = content.split("\n");
106
- for (let i = 0; i < lines.length; i++) {
107
- const l = lines[i];
108
- for (const cp of commentPatterns) {
109
- const match = cp.regex.exec(l);
110
- if (match) {
111
- const text = (match[1] || match[2] || l).trim();
112
- candidates.push({
113
- id: `comment-${file}-${i + 1}`,
114
- type: "inline_comment",
115
- title: `[${cp.tag}] ${file}:${i + 1}`,
116
- context: `Inline comment marker "${cp.tag}" found in file "${file}" at line ${i + 1}.`,
117
- rationale: text || l.trim(),
118
- filePath: file,
119
- lineNumber: i + 1,
120
- pattern: cp.tag
121
- });
122
- if (candidates.length >= limit) break;
123
- }
124
- }
125
- if (candidates.length >= limit) break;
126
- }
127
- }
128
- } catch {
129
- }
130
- }
131
- if (options.confirm && candidates.length > 0) {
132
- let recordedCount = 0;
133
- for (const c of candidates) {
134
- await recordDecisionLog({
135
- title: c.title,
136
- context: c.context,
137
- rationale: c.rationale,
138
- outcome: `Mined from ${c.type} (${c.pattern})`,
139
- status: "proposed"
140
- });
141
- await recordDecision({
142
- title: c.title,
143
- context: c.context,
144
- options: [],
145
- rationale: c.rationale,
146
- outcome: `Mined from ${c.type}`,
147
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
148
- });
149
- recordedCount++;
150
- }
151
- return `\u26CF\uFE0F **Mined & Recorded ${recordedCount} Decisions** into decision log & graph.
152
- Use \`kuma_memory({ action: 'decision_log' })\` to view all active & proposed decisions.`;
153
- }
154
- if (candidates.length === 0) {
155
- return "\u26CF\uFE0F **Decision Mining**: No significant historical decision markers (HACK/FIXME/git fix) found for the requested scope.";
156
- }
157
- const report = [
158
- `\u26CF\uFE0F **Decision Mining Candidates** (${candidates.length} candidate(s) found)`,
159
- "\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
160
- "Review these draft historical decisions mined from git history & inline comments:",
161
- ""
162
- ];
163
- candidates.forEach((c, idx) => {
164
- report.push(`**${idx + 1}. ${c.title}**`);
165
- report.push(` \u2022 **Type**: \`${c.type}\` | **Signal**: \`${c.pattern}\``);
166
- report.push(` \u2022 **Context**: ${c.context}`);
167
- report.push(` \u2022 **Rationale**: ${c.rationale}`);
168
- report.push("");
169
- });
170
- report.push("\u{1F4A1} *To accept and record these mined decisions into knowledge graph & decision.md:*");
171
- report.push(`Run: \`kuma_memory({ action: 'mine', confirm: true${options.scope ? `, scope: '${options.scope}'` : ""} })\``);
172
- return report.join("\n");
173
- }
174
- export {
175
- mineHistoricalDecisions
176
- };
@@ -1,311 +0,0 @@
1
- import {
2
- sessionMemory
3
- } from "./chunk-LVKOGXLC.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-O45SPNTS.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
- };