@plumpslabs/kuma 2.2.7 → 2.3.0

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getProjectRoot
3
- } from "./chunk-T55NCW63.js";
3
+ } from "./chunk-IXMWW5WA.js";
4
4
 
5
5
  // src/utils/agentDetector.ts
6
6
  import fs from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getProjectRoot
3
- } from "./chunk-T55NCW63.js";
3
+ } from "./chunk-IXMWW5WA.js";
4
4
 
5
5
  // src/cli/init.ts
6
6
  import fs from "fs";
@@ -252,50 +252,68 @@ function generateInitMdContent() {
252
252
  "",
253
253
  "_(Auto-generated by `kuma init` \u2014 edit this file directly to customize rules)_",
254
254
  "",
255
- "## Code Search",
255
+ "## Kuma V3 \u2014 3 Coarse-Grained Tools",
256
256
  "",
257
- "- Use the **smart_grep** tool to search code \u2014 NOT bash grep/ripgrep manually",
258
- "- smart_grep returns line numbers + context, caches results, respects .gitignore",
257
+ "Kuma provides 3 pipeline-driven tools. Each action triggers a multi-step deterministic workflow internally.",
259
258
  "",
260
- "## Reading Code",
259
+ "### \u{1F9E0} kuma_context \u2014 Context & Research (call FIRST every session)",
261
260
  "",
262
- "- Use the **smart_file_picker** tool to read files with smart chunking",
263
- "- For large files, use startLine/endLine to read specific ranges",
261
+ '- `kuma_context({ action: "init" })` \u2014 Load project brief, detect stack, show structure',
262
+ '- **`kuma_context({ action: "research", scope: "..." })` \u2014 WAJIB before editing unfamiliar code**',
263
+ " - 5-step pipeline: load cache \u2192 check staleness \u2192 query graph \u2192 impact analysis \u2192 decision lookup",
264
+ '- `kuma_context({ action: "impact", target: "symbol" })` \u2014 Analyze change effects',
265
+ '- `kuma_context({ action: "navigate", target: "flow" })` \u2014 Trace code flow',
266
+ '- `kuma_context({ action: "changes" })` \u2014 View session change log',
267
+ '- `kuma_context({ action: "health" })` \u2014 Project health score 0-100',
264
268
  "",
265
- "## Editing Code",
269
+ "### \u{1F4DD} kuma_memory \u2014 Decision & Knowledge",
266
270
  "",
267
- "- Use the **precise_diff_editor** tool to edit files (fuzzy matching + auto-backup)",
268
- "- DO NOT create Python/Node scripts to patch files; use precise_diff_editor directly",
269
- "- DO NOT use bash sed/cat/awk to modify source files",
271
+ '- `kuma_memory({ action: "research_save", scope: "...", ... })` \u2014 Save research results',
272
+ '- `kuma_memory({ action: "decision", decisionAction: "record", ... })` \u2014 ADR-style decision',
273
+ '- `kuma_memory({ action: "session" })` \u2014 Session summary',
274
+ '- `kuma_memory({ action: "heal" })` \u2014 Self-heal knowledge graph',
275
+ '- `kuma_memory({ action: "search", query: "..." })` \u2014 Search memories + graph',
276
+ '- `kuma_memory({ action: "changes" })` \u2014 View change log',
270
277
  "",
271
- "## Creating Files",
278
+ "### \u{1F6E1}\uFE0F kuma_safety \u2014 Safety & Policy",
272
279
  "",
273
- "- Use the **batch_file_writer** tool to create new files (up to 15 at once)",
274
- "- Before creating a file, ask: does this need to exist, or can it merge into an existing module?",
280
+ '- `kuma_safety({ action: "guard", guardGoal: "..." })` \u2014 Anti-pattern, drift, loop detection',
281
+ '- `kuma_safety({ action: "check", ... })` \u2014 Pre-execution safety check',
282
+ '- `kuma_safety({ action: "audit" })` \u2014 Query safety audit trail',
283
+ '- `kuma_safety({ action: "lock", lockAction: "acquire", ... })` \u2014 Multi-agent file lock',
284
+ '- `kuma_safety({ action: "health" })` \u2014 Safety score 0-100',
285
+ '- `kuma_safety({ action: "override", ... })` \u2014 Logged safety bypass',
275
286
  "",
276
- "## Running Tasks",
287
+ "## Mandatory Research Pipeline",
277
288
  "",
278
- "- Use the **execute_safe_test** tool for test/build/lint/typecheck",
279
- "- Always run typecheck after editing TypeScript files",
289
+ "**Before editing any unfamiliar code, you MUST call:**",
280
290
  "",
281
- "## Code Review",
291
+ ' kuma_context({ action: "research", scope: "<area>" })',
282
292
  "",
283
- "- Use the **code_reviewer** tool after making changes",
284
- "- Supports focus: correctness, security, performance, over-engineering",
293
+ "This runs: cache load \u2192 staleness check \u2192 graph query \u2192 impact analysis \u2192 decision lookup.",
294
+ "Only after reviewing the structured result should you make changes.",
285
295
  "",
286
- "## Session Awareness",
296
+ "After editing, save what you learned:",
287
297
  "",
288
- "- Start each session by calling **kuma_init()** to load context",
289
- "- Use **get_session_memory()** to recall what happened before",
290
- "- Use **kuma_reflect** / **kuma_guard** to stay on track",
291
- "- Use **write_memory()** to persist decisions and glossary terms",
298
+ ' kuma_memory({ action: "research_save", scope: "<area>", confidence: 0-1 })',
292
299
  "",
293
300
  "## General Rules",
294
301
  "",
295
- "- When a tool errors, READ the error carefully before acting",
296
- "- After 3+ edits without running tests, stop and verify",
297
- "- If a tool fails, check the message \u2014 don't retry blindly",
298
- "- Detect conventions first with **project_conventions()**",
302
+ '- **Call `kuma_context({ action: "init" })` at session start** \u2014 always',
303
+ '- **Research before edit** \u2014 always call `kuma_context({ action: "research" })` first',
304
+ '- **Save after research** \u2014 use `kuma_memory({ action: "research_save" })` to persist',
305
+ '- **Record decisions** \u2014 use `kuma_memory({ action: "decision" })` for significant changes',
306
+ '- **Verify with guard** \u2014 use `kuma_safety({ action: "guard" })` after editing',
307
+ '- **Check changes** \u2014 use `kuma_context({ action: "changes" })` to track what you modified',
308
+ "",
309
+ "## What Kuma Does NOT Do (Use Agent Native Tools)",
310
+ "",
311
+ "- Editing files \u2014 use your AI agent's native edit tools",
312
+ "- Searching code \u2014 use your agent's native grep/search",
313
+ "- Running commands \u2014 use your agent's native terminal",
314
+ "- Creating files \u2014 use your agent's native file creation",
315
+ "- Git operations \u2014 use your agent's native git tools",
316
+ "- Linting/testing \u2014 use your agent's native run commands",
299
317
  "",
300
318
  "---",
301
319
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
@@ -0,0 +1,304 @@
1
+ import {
2
+ getKumaDir
3
+ } from "./chunk-IXMWW5WA.js";
4
+
5
+ // src/engine/kumaDb.ts
6
+ import initSqlJs from "sql.js";
7
+ import fs from "fs";
8
+ import path from "path";
9
+ var DB_FILENAME = "kuma.db";
10
+ var dbInstance = null;
11
+ var initPromise = null;
12
+ async function getDb() {
13
+ if (dbInstance) return dbInstance;
14
+ if (initPromise) return initPromise;
15
+ initPromise = initDb();
16
+ return initPromise;
17
+ }
18
+ async function initDb() {
19
+ const SQL = await initSqlJs();
20
+ const kumaDir = getKumaDir();
21
+ const dbPath = path.join(kumaDir, DB_FILENAME);
22
+ if (!fs.existsSync(kumaDir)) {
23
+ fs.mkdirSync(kumaDir, { recursive: true });
24
+ }
25
+ let db;
26
+ if (fs.existsSync(dbPath)) {
27
+ const buffer = fs.readFileSync(dbPath);
28
+ db = new SQL.Database(buffer);
29
+ } else {
30
+ db = new SQL.Database();
31
+ }
32
+ db.run("PRAGMA journal_mode=WAL");
33
+ db.run("PRAGMA synchronous=NORMAL");
34
+ createSchema(db);
35
+ saveDb(db);
36
+ dbInstance = db;
37
+ return db;
38
+ }
39
+ function saveDb(db) {
40
+ const d = db ?? dbInstance;
41
+ if (!d) return;
42
+ try {
43
+ const kumaDir = getKumaDir();
44
+ const dbPath = path.join(kumaDir, DB_FILENAME);
45
+ const data = d.export();
46
+ const buffer = Buffer.from(data);
47
+ fs.writeFileSync(dbPath, buffer);
48
+ } catch (err) {
49
+ console.error(`[KumaDB] Failed to save database: ${err}`);
50
+ }
51
+ }
52
+ function createSchema(db) {
53
+ db.run(`
54
+ CREATE TABLE IF NOT EXISTS nodes (
55
+ id TEXT PRIMARY KEY,
56
+ type TEXT NOT NULL CHECK(type IN ('function','file','api_route','db_table','test','class','interface','type','module','variable')),
57
+ name TEXT NOT NULL,
58
+ file_path TEXT,
59
+ metadata TEXT DEFAULT '{}',
60
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
61
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
62
+ )
63
+ `);
64
+ db.run(`
65
+ CREATE TABLE IF NOT EXISTS edges (
66
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
67
+ source_id TEXT NOT NULL REFERENCES nodes(id),
68
+ target_id TEXT NOT NULL REFERENCES nodes(id),
69
+ type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by')),
70
+ weight REAL DEFAULT 1.0,
71
+ metadata TEXT DEFAULT '{}',
72
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
73
+ UNIQUE(source_id, target_id, type)
74
+ )
75
+ `);
76
+ db.run(`
77
+ CREATE TABLE IF NOT EXISTS sessions (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ started_at INTEGER NOT NULL,
80
+ ended_at INTEGER,
81
+ goal TEXT,
82
+ tool_calls INTEGER DEFAULT 0,
83
+ edits INTEGER DEFAULT 0,
84
+ rollbacks INTEGER DEFAULT 0,
85
+ failures INTEGER DEFAULT 0,
86
+ safety_score INTEGER
87
+ )
88
+ `);
89
+ db.run(`
90
+ CREATE TABLE IF NOT EXISTS tool_calls (
91
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
92
+ session_id INTEGER REFERENCES sessions(id),
93
+ tool_name TEXT NOT NULL,
94
+ params TEXT,
95
+ success INTEGER DEFAULT 1,
96
+ duration_ms INTEGER,
97
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
98
+ )
99
+ `);
100
+ db.run(`
101
+ CREATE TABLE IF NOT EXISTS experiences (
102
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
103
+ tool_name TEXT NOT NULL,
104
+ params_hash TEXT NOT NULL,
105
+ success INTEGER NOT NULL DEFAULT 1,
106
+ duration_ms INTEGER,
107
+ error_pattern TEXT,
108
+ context_file TEXT,
109
+ context_action TEXT,
110
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
111
+ )
112
+ `);
113
+ db.run(`
114
+ CREATE TABLE IF NOT EXISTS experience_patterns (
115
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
116
+ antecedent_tool TEXT NOT NULL,
117
+ antecedent_hash TEXT NOT NULL,
118
+ consequent_tool TEXT NOT NULL,
119
+ confidence REAL DEFAULT 0.0,
120
+ count INTEGER DEFAULT 1,
121
+ avg_duration_ms INTEGER DEFAULT 0,
122
+ success_rate REAL DEFAULT 1.0,
123
+ last_seen_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
124
+ UNIQUE(antecedent_tool, antecedent_hash, consequent_tool)
125
+ )
126
+ `);
127
+ try {
128
+ db.run(`
129
+ CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
130
+ name,
131
+ metadata,
132
+ content='nodes',
133
+ content_rowid='rowid'
134
+ )
135
+ `);
136
+ } catch {
137
+ console.warn("[KumaDB] FTS5 not available, full-text search disabled");
138
+ }
139
+ db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type)`);
140
+ db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name)`);
141
+ db.run(`CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id)`);
142
+ db.run(`CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id)`);
143
+ db.run(`CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type)`);
144
+ db.run(`CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id)`);
145
+ db.run(`CREATE INDEX IF NOT EXISTS idx_tool_calls_created ON tool_calls(created_at)`);
146
+ db.run(`CREATE INDEX IF NOT EXISTS idx_experiences_tool ON experiences(tool_name)`);
147
+ db.run(`CREATE INDEX IF NOT EXISTS idx_experiences_hash ON experiences(params_hash)`);
148
+ db.run(`CREATE INDEX IF NOT EXISTS idx_experiences_created ON experiences(created_at)`);
149
+ db.run(`CREATE INDEX IF NOT EXISTS idx_patterns_antecedent ON experience_patterns(antecedent_tool)`);
150
+ db.run(`CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON experience_patterns(confidence DESC)`);
151
+ db.run(`
152
+ CREATE TABLE IF NOT EXISTS research_cache (
153
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
154
+ scope TEXT NOT NULL UNIQUE,
155
+ version INTEGER DEFAULT 1,
156
+ confidence REAL DEFAULT 0.0,
157
+ content_hash TEXT,
158
+ record TEXT NOT NULL,
159
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
160
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
161
+ )
162
+ `);
163
+ db.run(`
164
+ CREATE TABLE IF NOT EXISTS change_log (
165
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
166
+ session_id INTEGER REFERENCES sessions(id),
167
+ file_path TEXT NOT NULL,
168
+ change_type TEXT NOT NULL CHECK(change_type IN ('modified','created','deleted','renamed')),
169
+ symbol TEXT,
170
+ diff_summary TEXT,
171
+ git_commit_hash TEXT,
172
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
173
+ )
174
+ `);
175
+ db.run(`
176
+ CREATE TABLE IF NOT EXISTS health_snapshots (
177
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
178
+ score INTEGER NOT NULL,
179
+ risk_level TEXT NOT NULL DEFAULT 'low',
180
+ checks TEXT DEFAULT '[]',
181
+ summary TEXT,
182
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
183
+ )
184
+ `);
185
+ db.run(`CREATE INDEX IF NOT EXISTS idx_research_scope ON research_cache(scope)`);
186
+ db.run(`CREATE INDEX IF NOT EXISTS idx_research_updated ON research_cache(updated_at)`);
187
+ db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_session ON change_log(session_id)`);
188
+ db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_file ON change_log(file_path)`);
189
+ db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_created ON change_log(created_at)`);
190
+ db.run(`CREATE INDEX IF NOT EXISTS idx_health_created ON health_snapshots(created_at)`);
191
+ }
192
+ async function getResearchCache(scope) {
193
+ try {
194
+ const db = await getDb();
195
+ const stmt = db.prepare("SELECT record, content_hash, updated_at FROM research_cache WHERE scope = ?");
196
+ stmt.bind([scope]);
197
+ if (stmt.step()) {
198
+ const row = stmt.getAsObject();
199
+ stmt.free();
200
+ return row.record;
201
+ }
202
+ stmt.free();
203
+ return null;
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+ async function saveResearchCache(scope, record, contentHash, confidence) {
209
+ try {
210
+ const db = await getDb();
211
+ const stmt = db.prepare("SELECT id FROM research_cache WHERE scope = ?");
212
+ stmt.bind([scope]);
213
+ const hasExisting = stmt.step();
214
+ stmt.free();
215
+ if (hasExisting) {
216
+ db.run(
217
+ `UPDATE research_cache SET record = ?, content_hash = COALESCE(?, content_hash), confidence = COALESCE(?, confidence), version = version + 1, updated_at = strftime('%s','now') WHERE scope = ?`,
218
+ [record, contentHash || null, confidence ?? null, scope]
219
+ );
220
+ } else {
221
+ db.run(
222
+ `INSERT INTO research_cache (scope, record, content_hash, confidence, version) VALUES (?, ?, ?, ?, 1)`,
223
+ [scope, record, contentHash || null, confidence ?? null]
224
+ );
225
+ }
226
+ saveDb();
227
+ } catch (err) {
228
+ console.error(`[KumaDB] Failed to save research cache: ${err}`);
229
+ }
230
+ }
231
+ async function recordChange(entry) {
232
+ try {
233
+ const db = await getDb();
234
+ db.run(
235
+ `INSERT INTO change_log (session_id, file_path, change_type, symbol, diff_summary, git_commit_hash) VALUES (?, ?, ?, ?, ?, ?)`,
236
+ [entry.sessionId ?? null, entry.filePath, entry.changeType, entry.symbol ?? null, entry.diffSummary ?? null, entry.gitCommitHash ?? null]
237
+ );
238
+ saveDb();
239
+ } catch (err) {
240
+ console.error(`[KumaDB] Failed to record change: ${err}`);
241
+ }
242
+ }
243
+ async function getChanges(params) {
244
+ try {
245
+ const db = await getDb();
246
+ const { sessionId, filePath, since, limit = 50 } = params;
247
+ let sql = `SELECT cl.*, s.goal FROM change_log cl LEFT JOIN sessions s ON s.id = cl.session_id WHERE 1=1`;
248
+ const bind = [];
249
+ if (sessionId) {
250
+ sql += ` AND cl.session_id = ?`;
251
+ bind.push(sessionId);
252
+ }
253
+ if (filePath) {
254
+ sql += ` AND cl.file_path LIKE ?`;
255
+ bind.push(`%${filePath}%`);
256
+ }
257
+ if (since) {
258
+ sql += ` AND cl.created_at >= ?`;
259
+ bind.push(since);
260
+ }
261
+ sql += ` ORDER BY cl.created_at DESC LIMIT ?`;
262
+ bind.push(limit);
263
+ const stmt = db.prepare(sql);
264
+ stmt.bind(bind);
265
+ const results = [];
266
+ while (stmt.step()) {
267
+ results.push(stmt.getAsObject());
268
+ }
269
+ stmt.free();
270
+ if (results.length === 0) return "No changes found.";
271
+ const lines = [`\u{1F4CB} **Change Log** \u2014 ${results.length} change(s)`];
272
+ for (const r of results) {
273
+ const icon = r.change_type === "deleted" ? "\u274C" : r.change_type === "created" ? "\u2728" : "\u{1F4DD}";
274
+ lines.push(` ${icon} **${r.file_path}** (${r.change_type})`);
275
+ if (r.symbol) lines.push(` Symbol: ${r.symbol}`);
276
+ if (r.goal) lines.push(` Goal: ${r.goal}`);
277
+ }
278
+ return lines.join("\n");
279
+ } catch (err) {
280
+ return `Error getting changes: ${err}`;
281
+ }
282
+ }
283
+ async function saveHealthSnapshot(score, riskLevel, checks, summary) {
284
+ try {
285
+ const db = await getDb();
286
+ db.run(
287
+ `INSERT INTO health_snapshots (score, risk_level, checks, summary) VALUES (?, ?, ?, ?)`,
288
+ [score, riskLevel, checks, summary]
289
+ );
290
+ saveDb();
291
+ } catch (err) {
292
+ console.error(`[KumaDB] Failed to save health snapshot: ${err}`);
293
+ }
294
+ }
295
+
296
+ export {
297
+ getDb,
298
+ saveDb,
299
+ getResearchCache,
300
+ saveResearchCache,
301
+ recordChange,
302
+ getChanges,
303
+ saveHealthSnapshot
304
+ };