@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 +25 -3
- package/dist/{chunk-K64NSHBR.js → chunk-4OB3XMHT.js} +2 -2
- package/dist/chunk-53J56QCQ.js +282 -0
- package/dist/{chunk-FOQQ2CSL.js → chunk-FL2TLLMX.js} +1 -1
- package/dist/{chunk-GDNAWLHF.js → chunk-NAM7SCBT.js} +63 -1
- package/dist/chunk-PTEPJK6M.js +155 -0
- package/dist/{chunk-X5TPBDKO.js → chunk-RTGLQDMI.js} +2 -2
- package/dist/{chunk-BI7KD3SG.js → chunk-SLRRDKQ2.js} +2 -2
- package/dist/contextDigest-4PCK3DSM.js +177 -0
- package/dist/domainRules-LDZPOIGZ.js +20 -0
- package/dist/index.js +261 -182
- package/dist/kumaAstValidator-CNM7FHYA.js +150 -0
- package/dist/{kumaDb-DJUDLYBJ.js → kumaDb-6D53ERFP.js} +1 -1
- package/dist/kumaDriftDetector-OESI5GTC.js +237 -0
- package/dist/kumaGotchas-DU5H6N7X.js +151 -0
- package/dist/{kumaGraph-35TAIBWD.js → kumaGraph-AWP743TJ.js} +2 -2
- package/dist/{kumaMemory-5SR3TGRL.js → kumaMemory-7TCUDVZX.js} +2 -2
- package/dist/{kumaMiner-BIDSZE3Q.js → kumaMiner-FFXSRHAO.js} +5 -5
- package/dist/kumaPolicyEngine-S2PXN3C7.js +311 -0
- package/dist/kumaSearch-QGB4QVXS.js +321 -0
- package/dist/{kumaVerifier-B4D7NOFT.js → kumaVerifier-ARSGPZAM.js} +27 -4
- package/dist/kumaVisualize-TECABHUY.js +248 -0
- package/dist/safetyAudit-55IVCG5B.js +12 -0
- package/dist/{safetyScore-PJGRRBWP.js → safetyScore-GR5N55CU.js} +5 -5
- package/dist/{sessionMemory-HBBXUSIP.js → sessionMemory-CA34TCPF.js} +1 -1
- package/package.json +1 -1
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 |
|
|
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
|
|
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-
|
|
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-
|
|
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
|
+
};
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|