@plumpslabs/kuma 2.3.6 → 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.
package/README.md CHANGED
@@ -108,7 +108,7 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
108
108
 
109
109
  ### 🧠 `kuma_context` — Context & Research
110
110
 
111
- | Action | Pipelina | Use case |
111
+ | Action | Pipeline | Use case |
112
112
  |--------|----------|----------|
113
113
  | `init` | Project brief | **Call first every session.** Load graph, detect stack, show structure. |
114
114
  | `research` | 5-step pipeline | **WAJIB before editing.** Cache → staleness → graph → impact → decisions. |
@@ -116,6 +116,12 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
116
116
  | `navigate` | BFS traversal | "How does login work?" — full call chain from route to database. |
117
117
  | `changes` | Change log query | "What changed this session?" — selective undo support. |
118
118
  | `health` | Aggregate scoring | Project health dashboard 0-100 across 9 dimensions. |
119
+ | `rollback` | File restore | Rollback a specific change by ID from the change log. |
120
+ | `researches` | Cache list | List all cached research scopes with confidence & age. |
121
+ | `sync` | Unified batch | Combines init + health + memory state in single roundtrip (~60-70% token savings). |
122
+ | `visualize` | Mermaid diagram | Generate interactive knowledge graph diagrams (flowchart, dependency, mindmap). |
123
+ | `digest` | Ultra-compact brief | <500 token project briefing — fastest way to bootstrap context (Issue #18). |
124
+ | `drift` | Staleness detection | Detect memory staleness & code drift between graph and actual filesystem (Issue #20). |
119
125
 
120
126
  ### 📝 `kuma_memory` — Decision & Knowledge
121
127
 
@@ -126,20 +132,36 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
126
132
  | `research_save` | Persist research to graph + `.kuma/research/<scope>.json`. |
127
133
  | `session` | "What happened this session?" — files, failures, progress. |
128
134
  | `heal` | Self-heal knowledge graph — stale detection, git repair. |
129
- | `search` | Search across memories + knowledge graph. |
135
+ | `search` | Search across memories + knowledge graph (with TF-IDF hybrid semantic search). |
130
136
  | `changes` | Change log for selective undo. |
137
+ | `todo` | Persistent todo CRUD — add, list, update status with scope & success criteria. |
138
+ | `context` | Inject context notes from external sources (Slack, Jira, meetings). |
139
+ | `benchmark` | Before/after metric capture & diff (e.g., type errors, test count). |
140
+ | `decision_log` | Living decision document with status tracking (active/superseded/deprecated). |
141
+ | `domain_rules` | Layer 1 — Business/domain rules knowledge (Issue #17). |
142
+ | `arch_flow` | Layer 2 — Architecture flow mapping (Issue #17). |
143
+ | `gotcha` | Layer 3 — Known gotchas & anti-regression shield (Issue #17/#21). |
144
+ | `layers` | Show all 3 memory layers summary. |
131
145
 
132
146
  ### 🛡️ `kuma_safety` — Safety & Policy
133
147
 
134
148
  | Action | Use case |
135
149
  |--------|----------|
136
150
  | `guard` | Anti-pattern, drift, tool-loop, and failure checks. |
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" })`. |
151
+ | `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 (30s handler + 60s verifier cooldown), concurrency-locked (intra-process + cross-process file lock), cached (< 5 min returns stale result), runaway-protected (> 3 calls in 5 min auto-blocks), hard timeout (30s + SIGKILL). ⚠️ NEVER auto-triggered — only via explicit `kuma_safety({ action: "verify" })`. |
138
152
  | `check` | Pre-execution safety: policy, path, lock, risk level. |
139
153
  | `audit` | Query audit trail + stats + override log. |
140
154
  | `lock` | Multi-agent file locking. |
141
155
  | `health` | Safety score 0-100 with dimension breakdown. |
142
156
  | `override` | Logged safety bypass with reason. |
157
+ | `security` | Security leak scanner — regex-based credential/token detection. |
158
+ | `gc` | Kuma garbage collection — orphan cleanup, VACUUM, index maintenance. |
159
+ | `doctor` | Kuma health diagnostics — DB integrity, schema health, process status, verification history. |
160
+ | `portability` | Check path portability — no absolute paths in stored data. |
161
+ | `gitignore` | Auto-configure `.gitignore` to include `.kuma/`. |
162
+ | `clean` | Purge scratch directory + reset drift warnings. |
163
+ | `policy` | Policy-as-Code engine — evaluate commands against `.kuma/policy.yml` (Issue #24). |
164
+ | `ast` / `validate` | AST-based code validation — validate JS/TS code structure & patterns (Issue #22). |
143
165
 
144
166
  ---
145
167
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getDb,
3
3
  saveDb
4
- } from "./chunk-GDNAWLHF.js";
4
+ } from "./chunk-NAM7SCBT.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-HBBXUSIP.js");
461
+ const { sessionMemory } = await import("./sessionMemory-CA34TCPF.js");
462
462
  const toolCalls = sessionMemory.getToolCallHistory(50);
463
463
  let edgeCount = 0;
464
464
  for (const call of toolCalls) {
@@ -0,0 +1,282 @@
1
+ import {
2
+ sessionMemory
3
+ } from "./chunk-SLRRDKQ2.js";
4
+ import {
5
+ getProjectRoot
6
+ } from "./chunk-E2KFPEBT.js";
7
+
8
+ // src/engine/domainRules.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ var LAYER_FILES = {
12
+ domain_rules: { filename: "DOMAIN_RULES.md", label: "Domain & Business Rules" },
13
+ arch_flow: { filename: "ARCHITECTURE_FLOW.md", label: "Architecture & Sequence Flow" },
14
+ gotcha: { filename: "KNOWN_GOTCHAS.md", label: "Known Code Gotchas" }
15
+ };
16
+ var DOMAIN_RULES_TEMPLATE = `# Domain & Business Rules (Layer 1)
17
+
18
+ Auto-generated by Kuma 3-Layer Memory Engine.
19
+ Update this file with core business logic, workflows, and boundary rules.
20
+
21
+ ## Who
22
+ <!-- Who are the users/actors? -->
23
+
24
+ ## What
25
+ <!-- What are the core features and business capabilities? -->
26
+
27
+ ## When
28
+ <!-- What are the time-based rules, schedules, deadlines? -->
29
+
30
+ ## Where
31
+ <!-- Where does the business operate (environments, regions)? -->
32
+
33
+ ## Why
34
+ <!-- Why were key architectural decisions made? -->
35
+
36
+ ## How
37
+ <!-- How do core workflows execute? -->
38
+
39
+ ## Business Rules
40
+ <!-- Critical business logic constraints -->
41
+ - Rule 1:
42
+ - Rule 2:
43
+
44
+ ## Workflows
45
+ <!-- Ordered sequence of operations -->
46
+ 1.
47
+ 2.
48
+
49
+ ## Boundary Conditions
50
+ <!-- Edge cases, error states, validation rules -->
51
+ -
52
+ `;
53
+ var ARCH_FLOW_TEMPLATE = `# Architecture & Sequence Flow Map (Layer 2)
54
+
55
+ Auto-generated by Kuma 3-Layer Memory Engine.
56
+ Document execution call chains and architectural dependencies.
57
+
58
+ ## System Overview
59
+ <!-- High-level description of the system architecture -->
60
+
61
+ ## Entry Points
62
+ <!-- Main entry points for the application -->
63
+ -
64
+
65
+ ## Execution Chains
66
+ <!-- Route -> Middleware -> Controller -> Service -> DB -->
67
+ \`\`\`
68
+ Request Flow:
69
+ 1. [Entry] -> [Middleware] -> [Handler] -> [Service] -> [DB]
70
+ \`\`\`
71
+
72
+ ## Module Dependency Map
73
+ <!-- How modules depend on each other -->
74
+ - Module A depends on: Module B, Module C
75
+ - Module B depends on: Module D
76
+
77
+ ## Data Flow
78
+ <!-- How data flows through the system -->
79
+ 1. Input:
80
+ 2. Process:
81
+ 3. Storage:
82
+ 4. Output:
83
+
84
+ ## External Integrations
85
+ <!-- External services, APIs, databases -->
86
+ -
87
+ `;
88
+ var GOTCHAS_TEMPLATE = `# Known Code Gotchas (Layer 3)
89
+
90
+ Auto-generated by Kuma 3-Layer Memory Engine.
91
+ Catalog of fragile legacy contracts, edge cases, and unexpected framework quirks.
92
+
93
+ ## How to Add a Gotcha
94
+
95
+ \`\`\`
96
+ kuma_memory({
97
+ action: 'gotcha',
98
+ scope: 'auth_middleware',
99
+ content: 'The auth middleware expects req.user after JWT decode. Missing middleware causes silent 500.',
100
+ filePath: 'src/middleware/auth.ts'
101
+ })
102
+ \`\`\`
103
+
104
+ ## Active Gotchas
105
+
106
+ <!-- Format:
107
+ ### [File Path] \u2014 [Short Description]
108
+ - **Issue**: [What happens]
109
+ - **Fix**: [How to handle it correctly]
110
+ - **Severity**: [low | medium | high | critical]
111
+ - **Added**: [Date]
112
+ -->
113
+
114
+ `;
115
+ function layerFilePath(layer) {
116
+ return path.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
117
+ }
118
+ function ensureLayerFile(layer) {
119
+ const fp = layerFilePath(layer);
120
+ if (!fs.existsSync(fp)) {
121
+ const template = layer === "domain_rules" ? DOMAIN_RULES_TEMPLATE : layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
122
+ const dir = path.dirname(fp);
123
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
124
+ fs.writeFileSync(fp, template, "utf-8");
125
+ console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
126
+ }
127
+ return fp;
128
+ }
129
+ function readLayer(layer) {
130
+ try {
131
+ const fp = ensureLayerFile(layer);
132
+ return fs.readFileSync(fp, "utf-8");
133
+ } catch (err) {
134
+ return `Error reading ${layer}: ${err}`;
135
+ }
136
+ }
137
+ function writeLayer(layer, content) {
138
+ try {
139
+ const fp = ensureLayerFile(layer);
140
+ fs.writeFileSync(fp, content, "utf-8");
141
+ const label = LAYER_FILES[layer].label;
142
+ sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
143
+ return `\u2705 **${label}** updated successfully.`;
144
+ } catch (err) {
145
+ return `\u274C Failed to update ${layer}: ${err}`;
146
+ }
147
+ }
148
+ function appendToLayer(layer, entry) {
149
+ try {
150
+ const fp = ensureLayerFile(layer);
151
+ const existing = fs.readFileSync(fp, "utf-8");
152
+ const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
153
+ fs.writeFileSync(fp, newContent, "utf-8");
154
+ return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
155
+ } catch (err) {
156
+ return `\u274C Failed to append: ${err}`;
157
+ }
158
+ }
159
+ function getLayersSummary() {
160
+ const lines = ["\u{1F4DA} **3-Layer Memory Engine**", "\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"];
161
+ for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
162
+ const fp = layerFilePath(layer);
163
+ const exists = fs.existsSync(fp);
164
+ const label = LAYER_FILES[layer].label;
165
+ if (exists) {
166
+ try {
167
+ const content = fs.readFileSync(fp, "utf-8");
168
+ const lineCount = content.split("\n").length;
169
+ const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
170
+ lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
171
+ } catch {
172
+ lines.push(` \u2753 **${label}** \u2014 could not read`);
173
+ }
174
+ } else {
175
+ lines.push(` \u{1F4C4} **${label}** \u2014 not yet created`);
176
+ }
177
+ }
178
+ return lines.join("\n");
179
+ }
180
+ function getActiveGotchas() {
181
+ const results = [];
182
+ try {
183
+ const fp = ensureLayerFile("gotcha");
184
+ const content = fs.readFileSync(fp, "utf-8");
185
+ const lines = content.split("\n");
186
+ let currentSection = "";
187
+ let currentFilePath = "";
188
+ let currentDesc = "";
189
+ let currentSeverity = "";
190
+ for (const line of lines) {
191
+ const fileMatch = line.match(/^###\s+(.+?)\s*[—–-]+\s*(.+)/);
192
+ if (fileMatch) {
193
+ if (currentFilePath) {
194
+ results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
195
+ }
196
+ currentFilePath = fileMatch[1].trim();
197
+ currentDesc = fileMatch[2].trim();
198
+ currentSeverity = "medium";
199
+ currentSection = line + "\n";
200
+ continue;
201
+ }
202
+ const sevMatch = line.match(/- \*\*Severity\*\*:\s*(\w+)/);
203
+ if (sevMatch) currentSeverity = sevMatch[1].toLowerCase();
204
+ currentSection += line + "\n";
205
+ }
206
+ if (currentFilePath) {
207
+ results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
208
+ }
209
+ } catch {
210
+ }
211
+ return results;
212
+ }
213
+ function checkFileGotchas(filePath) {
214
+ const gotchas = getActiveGotchas();
215
+ const warnings = [];
216
+ for (const g of gotchas) {
217
+ if (filePath.includes(g.filePath) || g.filePath.includes(filePath)) {
218
+ const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
219
+ warnings.push(`${icon} **Gotcha**: ${g.description} (${g.severity})`);
220
+ }
221
+ }
222
+ return warnings;
223
+ }
224
+ function generateDigest() {
225
+ const lines = [
226
+ "\u{1F4CB} **Kuma Project Digest**",
227
+ "\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",
228
+ ""
229
+ ];
230
+ try {
231
+ const fp1 = ensureLayerFile("domain_rules");
232
+ const content = fs.readFileSync(fp1, "utf-8");
233
+ const rules = [];
234
+ const workflows = [];
235
+ for (const line of content.split("\n")) {
236
+ const r = line.match(/^- Rule \d+:?\s*(.+)/);
237
+ if (r) rules.push(r[1].substring(0, 60));
238
+ const w = line.match(/^\d+\.\s*(.+)/);
239
+ if (w) workflows.push(w[1].substring(0, 60));
240
+ }
241
+ if (rules.length > 0) lines.push(`\u{1F4CB} **Domain Rules** (${rules.length}): ${rules.slice(0, 3).join("; ")}`);
242
+ if (workflows.length > 0) lines.push(`\u{1F504} **Workflows**: ${workflows.slice(0, 3).join(" \u2192 ")}`);
243
+ } catch {
244
+ lines.push("\u{1F4CB} **Domain Rules**: not set");
245
+ }
246
+ try {
247
+ const fp2 = ensureLayerFile("arch_flow");
248
+ const content = fs.readFileSync(fp2, "utf-8");
249
+ const chains = [];
250
+ for (const line of content.split("\n")) {
251
+ if (line.includes("->") && line.includes("[")) chains.push(line.trim());
252
+ }
253
+ if (chains.length > 0) lines.push(`\u{1F3D7}\uFE0F **Architecture**: ${chains[0].substring(0, 80)}`);
254
+ else lines.push("\u{1F3D7}\uFE0F **Architecture**: see .kuma/ARCHITECTURE_FLOW.md");
255
+ } catch {
256
+ lines.push("\u{1F3D7}\uFE0F **Architecture**: not documented");
257
+ }
258
+ const gotchas = getActiveGotchas();
259
+ if (gotchas.length > 0) {
260
+ const highCount = gotchas.filter((g) => g.severity === "high" || g.severity === "critical").length;
261
+ lines.push(`\u26A0\uFE0F **Gotchas**: ${gotchas.length} active (${highCount} high/critical)`);
262
+ for (const g of gotchas.slice(0, 3)) {
263
+ lines.push(` \u2022 ${g.filePath}: ${g.description.substring(0, 60)}`);
264
+ }
265
+ } else {
266
+ lines.push("\u26A0\uFE0F **Gotchas**: none recorded");
267
+ }
268
+ const summary = sessionMemory.getSummary();
269
+ lines.push(`\u{1F3AF} **Goal**: ${summary.currentGoal?.substring(0, 60) || "not set"}`);
270
+ lines.push(`\u{1F4DD} **Modified**: ${summary.modifiedFiles?.length || 0} files | \u{1F6E0}\uFE0F ${summary.toolCallCount} calls`);
271
+ return lines.join("\n");
272
+ }
273
+
274
+ export {
275
+ readLayer,
276
+ writeLayer,
277
+ appendToLayer,
278
+ getLayersSummary,
279
+ getActiveGotchas,
280
+ checkFileGotchas,
281
+ generateDigest
282
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-BI7KD3SG.js";
3
+ } from "./chunk-SLRRDKQ2.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -283,6 +283,68 @@ function createSchema(db) {
283
283
  duration_ms INTEGER,
284
284
  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
285
285
  )`);
286
+ db.exec(`
287
+ CREATE TABLE IF NOT EXISTS blackboard_events (
288
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
289
+ type TEXT NOT NULL,
290
+ source TEXT NOT NULL,
291
+ topic TEXT NOT NULL,
292
+ payload TEXT NOT NULL DEFAULT '{}',
293
+ severity TEXT NOT NULL DEFAULT 'info'
294
+ CHECK(severity IN ('info','warning','critical')),
295
+ tags TEXT DEFAULT '[]',
296
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
297
+ );
298
+ `);
299
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_type ON blackboard_events(type)`);
300
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_topic ON blackboard_events(topic)`);
301
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_severity ON blackboard_events(severity)`);
302
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_bb_created ON blackboard_events(created_at)`);
303
+ db.exec(`
304
+ CREATE TABLE IF NOT EXISTS known_gotchas (
305
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
306
+ file_path TEXT NOT NULL,
307
+ description TEXT NOT NULL,
308
+ severity TEXT NOT NULL DEFAULT 'medium'
309
+ CHECK(severity IN ('low','medium','high','critical')),
310
+ workaround TEXT,
311
+ added_by TEXT DEFAULT 'agent',
312
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
313
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
314
+ );
315
+ `);
316
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_gotchas_file ON known_gotchas(file_path)`);
317
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_gotchas_severity ON known_gotchas(severity)`);
318
+ db.exec(`
319
+ CREATE TABLE IF NOT EXISTS trajectories (
320
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
321
+ goal TEXT NOT NULL,
322
+ steps TEXT NOT NULL DEFAULT '[]',
323
+ total_duration_ms INTEGER DEFAULT 0,
324
+ success_rate REAL DEFAULT 0.0,
325
+ complexity INTEGER DEFAULT 0,
326
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
327
+ );
328
+ `);
329
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_traj_goal ON trajectories(goal)`);
330
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_traj_complexity ON trajectories(complexity)`);
331
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_traj_created ON trajectories(created_at)`);
332
+ db.exec(`
333
+ CREATE TABLE IF NOT EXISTS distilled_skills (
334
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
335
+ name TEXT NOT NULL UNIQUE,
336
+ description TEXT NOT NULL,
337
+ pattern TEXT NOT NULL,
338
+ parameters TEXT DEFAULT '[]',
339
+ success_count INTEGER DEFAULT 1,
340
+ avg_duration_ms INTEGER DEFAULT 0,
341
+ source_trajectory_id INTEGER,
342
+ last_used_at INTEGER,
343
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
344
+ );
345
+ `);
346
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_skills_name ON distilled_skills(name)`);
347
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_skills_used ON distilled_skills(last_used_at)`);
286
348
  db.run(`CREATE TABLE IF NOT EXISTS portability_entries (
287
349
  id INTEGER PRIMARY KEY AUTOINCREMENT,
288
350
  entry_type TEXT NOT NULL,
@@ -787,7 +849,7 @@ async function runDoctor() {
787
849
  } catch {
788
850
  checks.push("**Database Integrity**: \u274C check failed");
789
851
  }
790
- 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"];
852
+ 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", "blackboard_events", "known_gotchas", "trajectories", "distilled_skills"];
791
853
  let existing = 0;
792
854
  for (const t of allTables) {
793
855
  try {
@@ -0,0 +1,155 @@
1
+ import {
2
+ getDb,
3
+ saveDb
4
+ } from "./chunk-NAM7SCBT.js";
5
+
6
+ // src/engine/safetyAudit.ts
7
+ var SCHEMA_SQL = `
8
+ CREATE TABLE IF NOT EXISTS safety_audit (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ timestamp INTEGER NOT NULL,
11
+ tool_name TEXT NOT NULL,
12
+ action TEXT NOT NULL,
13
+ file_path TEXT,
14
+ risk_level TEXT NOT NULL DEFAULT 'low',
15
+ policy_violations INTEGER DEFAULT 0,
16
+ allowed INTEGER NOT NULL DEFAULT 1,
17
+ duration_ms INTEGER DEFAULT 0,
18
+ metadata TEXT DEFAULT '{}'
19
+ );
20
+ CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON safety_audit(timestamp);
21
+ CREATE INDEX IF NOT EXISTS idx_audit_tool ON safety_audit(tool_name);
22
+ CREATE INDEX IF NOT EXISTS idx_audit_risk ON safety_audit(risk_level);
23
+ CREATE INDEX IF NOT EXISTS idx_audit_allowed ON safety_audit(allowed);
24
+ `;
25
+ var schemaEnsured = false;
26
+ async function ensureSchema() {
27
+ if (schemaEnsured) return;
28
+ try {
29
+ const db = await getDb();
30
+ db.exec(SCHEMA_SQL);
31
+ saveDb();
32
+ schemaEnsured = true;
33
+ } catch (err) {
34
+ console.error(`[SafetyAudit] Failed to ensure schema: ${err}`);
35
+ }
36
+ }
37
+ async function recordAudit(entry) {
38
+ try {
39
+ await ensureSchema();
40
+ const db = await getDb();
41
+ db.run(`
42
+ INSERT INTO safety_audit (timestamp, tool_name, action, file_path, risk_level, policy_violations, allowed, duration_ms, metadata)
43
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
44
+ `, [
45
+ entry.timestamp,
46
+ entry.toolName,
47
+ entry.action,
48
+ entry.filePath || null,
49
+ entry.riskLevel,
50
+ entry.policyViolations,
51
+ entry.allowed ? 1 : 0,
52
+ entry.durationMs,
53
+ JSON.stringify(entry.metadata || {})
54
+ ]);
55
+ saveDb();
56
+ } catch (err) {
57
+ console.error(`[SafetyAudit] Failed to record audit: ${err}`);
58
+ }
59
+ }
60
+ async function queryAudit(params) {
61
+ try {
62
+ await ensureSchema();
63
+ const db = await getDb();
64
+ const { toolName, riskLevel, allowed, limit = 20, since } = params;
65
+ let sql = "SELECT * FROM safety_audit WHERE 1=1";
66
+ const bindParams = [];
67
+ if (toolName) {
68
+ sql += " AND tool_name = ?";
69
+ bindParams.push(toolName);
70
+ }
71
+ if (riskLevel) {
72
+ sql += " AND risk_level = ?";
73
+ bindParams.push(riskLevel);
74
+ }
75
+ if (allowed !== void 0) {
76
+ sql += " AND allowed = ?";
77
+ bindParams.push(allowed ? 1 : 0);
78
+ }
79
+ if (since) {
80
+ sql += " AND timestamp >= ?";
81
+ bindParams.push(since);
82
+ }
83
+ sql += " ORDER BY timestamp DESC LIMIT ?";
84
+ bindParams.push(limit);
85
+ const stmt = db.prepare(sql);
86
+ stmt.bind(bindParams);
87
+ const results = [];
88
+ while (stmt.step()) {
89
+ results.push(stmt.getAsObject());
90
+ }
91
+ stmt.free();
92
+ if (results.length === 0) {
93
+ return "\u{1F4CB} **Safety Audit** \u2014 No records found for the given filters.";
94
+ }
95
+ const lines = [
96
+ `\u{1F4CB} **Safety Audit** \u2014 ${results.length} record(s)`,
97
+ `\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`,
98
+ ""
99
+ ];
100
+ for (const r of results) {
101
+ const riskIcon = r.risk_level === "critical" ? "\u{1F534}" : r.risk_level === "high" ? "\u{1F7E0}" : r.risk_level === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
102
+ const allowIcon = r.allowed ? "\u2705" : "\u26D4";
103
+ const time = new Date(r.timestamp * 1e3).toLocaleTimeString();
104
+ lines.push(`${allowIcon} ${riskIcon} **${r.tool_name}** \u2014 ${r.action}`);
105
+ if (r.file_path) lines.push(` \u{1F4CD} ${r.file_path}`);
106
+ lines.push(` \u{1F550} ${time} | ${r.duration_ms}ms | ${r.policy_violations} policy violation(s)`);
107
+ lines.push("");
108
+ }
109
+ return lines.join("\n");
110
+ } catch (err) {
111
+ return `Error querying audit: ${err}`;
112
+ }
113
+ }
114
+ async function auditStats() {
115
+ try {
116
+ await ensureSchema();
117
+ const db = await getDb();
118
+ const total = db.exec("SELECT COUNT(*) as c FROM safety_audit")[0]?.values[0][0] ?? 0;
119
+ const blocked = db.exec("SELECT COUNT(*) as c FROM safety_audit WHERE allowed = 0")[0]?.values[0][0] ?? 0;
120
+ const critical = db.exec("SELECT COUNT(*) as c FROM safety_audit WHERE risk_level IN ('high','critical')")[0]?.values[0][0] ?? 0;
121
+ let topBlocked = [];
122
+ try {
123
+ const stmt = db.prepare("SELECT tool_name, COUNT(*) as cnt FROM safety_audit WHERE allowed = 0 GROUP BY tool_name ORDER BY cnt DESC LIMIT 5");
124
+ while (stmt.step()) {
125
+ topBlocked.push(stmt.getAsObject());
126
+ }
127
+ stmt.free();
128
+ } catch {
129
+ }
130
+ const lines = [
131
+ `\u{1F4CA} **Safety Audit Stats**`,
132
+ `\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`,
133
+ "",
134
+ `\u{1F4DD} Total operations: ${total}`,
135
+ `\u26D4 Blocked: ${blocked} (${total > 0 ? (blocked / total * 100).toFixed(1) : 0}%)`,
136
+ `\u{1F534} High/Critical risk: ${critical}`,
137
+ ""
138
+ ];
139
+ if (topBlocked.length > 0) {
140
+ lines.push("**Most blocked tools:**");
141
+ for (const t of topBlocked) {
142
+ lines.push(` \u2022 ${t.tool_name}: ${t.cnt}x blocked`);
143
+ }
144
+ }
145
+ return lines.join("\n");
146
+ } catch (err) {
147
+ return `Error getting audit stats: ${err}`;
148
+ }
149
+ }
150
+
151
+ export {
152
+ recordAudit,
153
+ queryAudit,
154
+ auditStats
155
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-BI7KD3SG.js";
3
+ } from "./chunk-SLRRDKQ2.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-35TAIBWD.js");
85
+ const { upsertNode, addEdge } = await import("./kumaGraph-AWP743TJ.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,
@@ -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-DJUDLYBJ.js");
323
+ const { getDb, saveDb } = await import("./kumaDb-6D53ERFP.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, ?)`,
@@ -605,7 +605,7 @@ No unresolved issues.`;
605
605
  this.addModifiedFile(filePath);
606
606
  }
607
607
  try {
608
- const { recordChange } = await import("./kumaDb-DJUDLYBJ.js");
608
+ const { recordChange } = await import("./kumaDb-6D53ERFP.js");
609
609
  const gitHash = this.getGitHead();
610
610
  await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
611
611
  } catch {