@plumpslabs/kuma 2.4.1 → 2.4.3

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/dist/index.js CHANGED
@@ -234,11 +234,9 @@ __export(kumaDb_exports, {
234
234
  getResearchCache: () => getResearchCache,
235
235
  getSecurityFindings: () => getSecurityFindings,
236
236
  listContextNotes: () => listContextNotes,
237
- listDecisionLog: () => listDecisionLog,
238
237
  listResearchCache: () => listResearchCache,
239
238
  rebuildFtsIndex: () => rebuildFtsIndex,
240
239
  recordChange: () => recordChange,
241
- recordDecisionLog: () => recordDecisionLog,
242
240
  resetDbInstance: () => resetDbInstance,
243
241
  rollbackChange: () => rollbackChange,
244
242
  runDoctor: () => runDoctor,
@@ -247,8 +245,7 @@ __export(kumaDb_exports, {
247
245
  saveDb: () => saveDb,
248
246
  saveFileSummary: () => saveFileSummary,
249
247
  saveResearchCache: () => saveResearchCache,
250
- saveVerification: () => saveVerification,
251
- updateDecisionStatus: () => updateDecisionStatus
248
+ saveVerification: () => saveVerification
252
249
  });
253
250
  import initSqlJs from "sql.js";
254
251
  import fs2 from "fs";
@@ -516,17 +513,10 @@ function createSchema(db) {
516
513
  comparison_with TEXT,
517
514
  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
518
515
  )`);
519
- db.run(`CREATE TABLE IF NOT EXISTS decision_log (
520
- id INTEGER PRIMARY KEY AUTOINCREMENT,
521
- title TEXT NOT NULL,
522
- context TEXT,
523
- rationale TEXT,
524
- outcome TEXT,
525
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','superseded','deprecated','proposed')),
526
- superseded_by INTEGER,
527
- file_paths TEXT DEFAULT '[]',
528
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
529
- )`);
516
+ try {
517
+ db.exec(`DROP TABLE IF EXISTS decision_log`);
518
+ } catch {
519
+ }
530
520
  db.run(`CREATE TABLE IF NOT EXISTS file_summaries (
531
521
  id INTEGER PRIMARY KEY AUTOINCREMENT,
532
522
  file_path TEXT NOT NULL UNIQUE,
@@ -555,12 +545,6 @@ function createSchema(db) {
555
545
  escalated INTEGER DEFAULT 0,
556
546
  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
557
547
  )`);
558
- db.run(`CREATE TABLE IF NOT EXISTS scratch_entries (
559
- id INTEGER PRIMARY KEY AUTOINCREMENT,
560
- file_path TEXT NOT NULL,
561
- reason TEXT,
562
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
563
- )`);
564
548
  db.run(`CREATE TABLE IF NOT EXISTS verifications (
565
549
  id INTEGER PRIMARY KEY AUTOINCREMENT,
566
550
  scope TEXT NOT NULL,
@@ -696,7 +680,6 @@ function createSchema(db) {
696
680
  db.run(`CREATE INDEX IF NOT EXISTS idx_api_path ON api_endpoints(path)`);
697
681
  db.run(`CREATE INDEX IF NOT EXISTS idx_context_notes_scope ON context_notes(scope)`);
698
682
  db.run(`CREATE INDEX IF NOT EXISTS idx_benchmarks_label ON benchmarks(label)`);
699
- db.run(`CREATE INDEX IF NOT EXISTS idx_decision_status ON decision_log(status)`);
700
683
  db.run(`CREATE INDEX IF NOT EXISTS idx_file_summaries_path ON file_summaries(file_path)`);
701
684
  db.run(`CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON safety_audit(timestamp)`);
702
685
  db.run(`CREATE INDEX IF NOT EXISTS idx_audit_tool ON safety_audit(tool_name)`);
@@ -989,57 +972,6 @@ async function getBenchmarkDiff(labelA, labelB) {
989
972
  return `Error: ${err}`;
990
973
  }
991
974
  }
992
- async function recordDecisionLog(entry) {
993
- try {
994
- const db = await getDb();
995
- db.run(
996
- `INSERT INTO decision_log (title, context, rationale, outcome, status) VALUES (?, ?, ?, ?, ?)`,
997
- [entry.title, entry.context || "", entry.rationale || "", entry.outcome || "", entry.status || "active"]
998
- );
999
- saveDb();
1000
- return `\u2705 Decision "${entry.title}" logged as ${entry.status || "active"}.`;
1001
- } catch (err) {
1002
- return `\u274C Failed: ${err}`;
1003
- }
1004
- }
1005
- async function listDecisionLog(status) {
1006
- try {
1007
- const db = await getDb();
1008
- let sql = `SELECT * FROM decision_log WHERE 1=1`;
1009
- const bind = [];
1010
- if (status) {
1011
- sql += ` AND status = ?`;
1012
- bind.push(status);
1013
- }
1014
- sql += ` ORDER BY created_at DESC LIMIT 50`;
1015
- const stmt = db.prepare(sql);
1016
- stmt.bind(bind);
1017
- const results = [];
1018
- while (stmt.step()) results.push(stmt.getAsObject());
1019
- stmt.free();
1020
- if (results.length === 0) return "\u{1F4CB} No decisions logged.";
1021
- const lines = [`\u{1F4CB} Decision Log \u2014 ${results.length}
1022
- `];
1023
- for (const d of results) {
1024
- const icon = d.status === "active" ? "\u2705" : d.status === "superseded" ? "\u{1F504}" : d.status === "deprecated" ? "\u274C" : "\u{1F4DD}";
1025
- lines.push(` ${icon} ${d.title} (${d.status})`);
1026
- if (d.context) lines.push(` Context: ${d.context.substring(0, 150)}`);
1027
- }
1028
- return lines.join("\n");
1029
- } catch (err) {
1030
- return `Error: ${err}`;
1031
- }
1032
- }
1033
- async function updateDecisionStatus(id, status) {
1034
- try {
1035
- const db = await getDb();
1036
- db.run(`UPDATE decision_log SET status = ? WHERE id = ?`, [status, id]);
1037
- saveDb();
1038
- return `\u2705 Decision #${id} updated to "${status}"`;
1039
- } catch (err) {
1040
- return `\u274C Failed: ${err}`;
1041
- }
1042
- }
1043
975
  async function getFileSummary(filePath) {
1044
976
  try {
1045
977
  const db = await getDb();
@@ -1081,7 +1013,7 @@ async function runGarbageCollection() {
1081
1013
  };
1082
1014
  const orphanResult = db.exec(`DELETE FROM nodes WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges) AND file_path IS NULL AND updated_at < strftime('%s','now','-30 days')`);
1083
1015
  removed += orphanResult[0]?.values?.length || 0;
1084
- const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND updated_at < strftime('%s','now','-30 days')`);
1016
+ const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND created_at < strftime('%s','now','-30 days')`);
1085
1017
  removed += staleEdges[0]?.values?.length || 0;
1086
1018
  db.exec(`DELETE FROM tool_calls WHERE created_at < strftime('%s','now','-90 days')`);
1087
1019
  db.exec(`DELETE FROM experiences WHERE created_at < strftime('%s','now','-90 days')`);
@@ -1095,7 +1027,7 @@ async function runGarbageCollection() {
1095
1027
  }
1096
1028
  const auditCount = getCount("SELECT COUNT(*) FROM safety_audit");
1097
1029
  if (auditCount > 200) {
1098
- db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY created_at ASC LIMIT ${auditCount - 200})`);
1030
+ db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY timestamp ASC LIMIT ${auditCount - 200})`);
1099
1031
  }
1100
1032
  db.exec(`DELETE FROM research_cache WHERE updated_at < strftime('%s','now','-60 days')`);
1101
1033
  const secCount = getCount("SELECT COUNT(*) FROM security_findings");
@@ -1110,12 +1042,7 @@ async function runGarbageCollection() {
1110
1042
  if (benchCount > 100) {
1111
1043
  db.exec(`DELETE FROM benchmarks WHERE id IN (SELECT id FROM benchmarks ORDER BY created_at ASC LIMIT ${benchCount - 100})`);
1112
1044
  }
1113
- const decCount = getCount("SELECT COUNT(*) FROM decision_log");
1114
- if (decCount > 200) {
1115
- db.exec(`DELETE FROM decision_log WHERE id IN (SELECT id FROM decision_log ORDER BY created_at ASC LIMIT ${decCount - 200})`);
1116
- }
1117
1045
  db.exec(`DELETE FROM known_gotchas WHERE file_path NOT IN (SELECT DISTINCT file_path FROM nodes WHERE file_path IS NOT NULL) AND file_path NOT LIKE '%::%'`);
1118
- db.exec(`DELETE FROM scratch_entries WHERE created_at < strftime('%s','now','-7 days')`);
1119
1046
  const costCount = getCount("SELECT COUNT(*) FROM cost_tracking");
1120
1047
  if (costCount > 500) {
1121
1048
  db.exec(`DELETE FROM cost_tracking WHERE id IN (SELECT id FROM cost_tracking ORDER BY created_at ASC LIMIT ${costCount - 500})`);
@@ -1149,7 +1076,7 @@ async function runDoctor() {
1149
1076
  } catch {
1150
1077
  checks.push("**Database Integrity**: \u274C check failed");
1151
1078
  }
1152
- const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "decision_log", "file_summaries", "verifications", "tool_calls", "experiences", "experience_patterns", "patterns", "api_endpoints", "otel_config", "cost_tracking", "scratch_entries", "portability_entries", "blackboard_events", "known_gotchas", "trajectories", "distilled_skills"];
1079
+ const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "file_summaries", "verifications", "tool_calls", "experiences", "experience_patterns", "patterns", "api_endpoints", "otel_config", "cost_tracking", "portability_entries", "blackboard_events", "known_gotchas", "trajectories", "distilled_skills"];
1153
1080
  let existing = 0;
1154
1081
  for (const t of allTables) {
1155
1082
  try {
@@ -1173,8 +1100,8 @@ async function runDoctor() {
1173
1100
  } catch {
1174
1101
  }
1175
1102
  try {
1176
- const { execSync: execSync7 } = await import("child_process");
1177
- const psOutput = execSync7(
1103
+ const { execSync: execSync6 } = await import("child_process");
1104
+ const psOutput = execSync6(
1178
1105
  `ps -eo pid,ppid,command | grep -E "(jest|pnpm test|npm test|yarn test)" | grep -v grep | head -10`,
1179
1106
  { encoding: "utf-8", timeout: 5e3 }
1180
1107
  ).trim();
@@ -1185,7 +1112,7 @@ async function runDoctor() {
1185
1112
  for (const p of processes) {
1186
1113
  checks.push(` \u26A1 ${p.substring(0, 120)}`);
1187
1114
  }
1188
- checks.push(" \u{1F4A1} Run `kuma_safety({ action: 'gc' })` to clean up stale processes");
1115
+ checks.push(" \u{1F4A1} Stale test processes may slow down test runs.");
1189
1116
  } else {
1190
1117
  checks.push("");
1191
1118
  checks.push("**Running Test Processes**: \u2705 None detected");
@@ -2106,12 +2033,6 @@ No unresolved issues.`;
2106
2033
  });
2107
2034
 
2108
2035
  // src/engine/kumaSelfHeal.ts
2109
- var kumaSelfHeal_exports = {};
2110
- __export(kumaSelfHeal_exports, {
2111
- formatGotchaStalenessReport: () => formatGotchaStalenessReport,
2112
- healOnQuery: () => healOnQuery,
2113
- verifyGotchaStaleness: () => verifyGotchaStaleness
2114
- });
2115
2036
  import { execSync as execSync2 } from "child_process";
2116
2037
  import fs4 from "fs";
2117
2038
  import path4 from "path";
@@ -2172,73 +2093,6 @@ async function healOnQuery(filePaths) {
2172
2093
  return { healed: 0 };
2173
2094
  }
2174
2095
  }
2175
- async function verifyGotchaStaleness() {
2176
- const db = await getDb();
2177
- const stale = [];
2178
- try {
2179
- const rows = db.exec(`
2180
- SELECT id, metadata FROM nodes WHERE type = 'gotcha'
2181
- `);
2182
- if (rows.length === 0) return stale;
2183
- const columns = rows[0].columns;
2184
- const filePathIdx = columns.indexOf("metadata");
2185
- for (const row of rows[0].values) {
2186
- const nodeId2 = row[0];
2187
- let metadata = {};
2188
- try {
2189
- metadata = JSON.parse(row[filePathIdx]);
2190
- } catch {
2191
- }
2192
- const filePath = metadata.file_path;
2193
- if (!filePath || filePath.startsWith("search::") || filePath.startsWith("api_route::")) continue;
2194
- const fullPath = path4.join(getProjectRoot(), filePath);
2195
- if (!fs4.existsSync(fullPath)) {
2196
- stale.push({ gotchaId: nodeId2, file_path: filePath, issue: "file_missing" });
2197
- } else {
2198
- const desc = metadata.description || "";
2199
- const symbolMatch = desc.match(/\b(function|class|method|const|let|var|export)\s+(\w+)/i);
2200
- if (symbolMatch) {
2201
- const symbolName = symbolMatch[2];
2202
- try {
2203
- const content = fs4.readFileSync(fullPath, "utf-8");
2204
- if (!content.includes(symbolName)) {
2205
- stale.push({ gotchaId: nodeId2, file_path: filePath, issue: "symbol_missing" });
2206
- }
2207
- } catch {
2208
- }
2209
- }
2210
- }
2211
- }
2212
- } catch {
2213
- }
2214
- return stale;
2215
- }
2216
- function formatGotchaStalenessReport(stale) {
2217
- if (stale.length === 0) {
2218
- return "\u2705 **Gotcha Staleness Check** \u2014 All gotcha references are valid.";
2219
- }
2220
- const fileMissing = stale.filter((s) => s.issue === "file_missing");
2221
- const symbolMissing = stale.filter((s) => s.issue === "symbol_missing");
2222
- const lines = [
2223
- `\u26A0\uFE0F **Gotcha Staleness Report**`,
2224
- `\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`,
2225
- "",
2226
- `\u{1F4CA} ${stale.length} stale gotcha${stale.length > 1 ? "s" : ""} found`,
2227
- fileMissing.length > 0 ? ` \u{1F5D1}\uFE0F ${fileMissing.length} reference${fileMissing.length > 1 ? "s" : ""} deleted file` : "",
2228
- symbolMissing.length > 0 ? ` \u{1F50D} ${symbolMissing.length} reference${symbolMissing.length > 1 ? "s" : ""} missing symbol` : "",
2229
- ""
2230
- ];
2231
- for (const s of fileMissing.slice(0, 5)) {
2232
- lines.push(` \u{1F5D1}\uFE0F \`${s.gotchaId}\` \u2192 file missing: \`${s.file_path}\``);
2233
- }
2234
- for (const s of symbolMissing.slice(0, 5)) {
2235
- lines.push(` \u{1F50D} \`${s.gotchaId}\` \u2192 symbol missing in \`${s.file_path}\``);
2236
- }
2237
- if (stale.length > 5) lines.push(` ... and ${stale.length - 5} more`);
2238
- lines.push("");
2239
- lines.push("\u{1F4A1} Use `kuma_memory({ action: 'delete_node', target: '<gotchaId>' })` to remove obsolete gotchas.");
2240
- return lines.join("\n");
2241
- }
2242
2096
  var init_kumaSelfHeal = __esm({
2243
2097
  "src/engine/kumaSelfHeal.ts"() {
2244
2098
  "use strict";
@@ -2517,39 +2371,6 @@ async function detectDrift() {
2517
2371
  }
2518
2372
  }
2519
2373
  fsStmt.free();
2520
- const dlStmt = db.prepare(
2521
- "SELECT id, title, file_paths, created_at FROM decision_log WHERE file_paths IS NOT NULL ORDER BY created_at DESC LIMIT 50"
2522
- );
2523
- while (dlStmt.step()) {
2524
- const row = dlStmt.getAsObject();
2525
- const filePaths = [];
2526
- try {
2527
- filePaths.push(...JSON.parse(row.file_paths));
2528
- } catch {
2529
- }
2530
- let hasStaleFile = false;
2531
- for (const fp of filePaths) {
2532
- if (!fp) continue;
2533
- const fullPath = path6.resolve(getProjectRoot(), fp);
2534
- if (!fs5.existsSync(fullPath)) {
2535
- hasStaleFile = true;
2536
- break;
2537
- }
2538
- }
2539
- if (hasStaleFile) {
2540
- records.push({
2541
- id: row.id,
2542
- source: "decision_log",
2543
- description: row.title,
2544
- filePath: filePaths[0] || null,
2545
- oldHash: "",
2546
- currentHash: "",
2547
- age: "\u2014",
2548
- severity: "stale"
2549
- });
2550
- }
2551
- }
2552
- dlStmt.free();
2553
2374
  } catch (err) {
2554
2375
  console.error(`[DriftDetector] Error: ${err}`);
2555
2376
  }
@@ -2612,12 +2433,6 @@ async function flagStaleRecords() {
2612
2433
  [record.id]
2613
2434
  );
2614
2435
  flagged++;
2615
- } else if (record.source === "decision_log") {
2616
- db.run(
2617
- `UPDATE decision_log SET status = 'deprecated' WHERE id = ?`,
2618
- [record.id]
2619
- );
2620
- flagged++;
2621
2436
  }
2622
2437
  }
2623
2438
  saveDb();
@@ -3327,7 +3142,7 @@ Try kuma_context({ action: 'research', scope: '<term>' }) to build related graph
3327
3142
  const parts = query.split("\u2192").map((s) => s.trim());
3328
3143
  if (parts.length !== 2) {
3329
3144
  return `\u26A0\uFE0F For path queries, use format: "sourceNodeID \u2192 targetNodeID"
3330
- Example: kuma_context({ action: 'impact', target: 'login' }) \u2192 traces connections between nodes.`;
3145
+ Example: kuma_context({ action: 'research', scope: 'login' }) \u2192 traces connections between nodes.`;
3331
3146
  }
3332
3147
  const [sourceId, targetId] = parts;
3333
3148
  const visited = /* @__PURE__ */ new Set();
@@ -3567,27 +3382,27 @@ async function codebaseSearchFallback(query, limit = 20) {
3567
3382
  if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
3568
3383
  let entries = [];
3569
3384
  try {
3570
- entries = fs28.readdirSync(dir);
3385
+ entries = fs24.readdirSync(dir);
3571
3386
  } catch {
3572
3387
  return;
3573
3388
  }
3574
3389
  for (const entry of entries) {
3575
3390
  if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
3576
- const full = path29.join(dir, entry);
3391
+ const full = path25.join(dir, entry);
3577
3392
  try {
3578
- const stat = fs28.statSync(full);
3393
+ const stat = fs24.statSync(full);
3579
3394
  if (stat.isDirectory()) {
3580
3395
  walk2(full, depth + 1);
3581
3396
  } else if (entry.toLowerCase().includes(query.toLowerCase())) {
3582
- results.push(path29.relative(root, full));
3397
+ results.push(path25.relative(root, full));
3583
3398
  }
3584
3399
  } catch {
3585
3400
  }
3586
3401
  }
3587
3402
  };
3588
3403
  var walk = walk2;
3589
- const fs28 = await import("fs");
3590
- const path29 = await import("path");
3404
+ const fs24 = await import("fs");
3405
+ const path25 = await import("path");
3591
3406
  const root = process.cwd();
3592
3407
  const results = [];
3593
3408
  const MAX_WALK_DEPTH = 8;
@@ -3727,9 +3542,9 @@ async function analyzeImpact(target) {
3727
3542
  }
3728
3543
  async function codebaseImpactFallback(target) {
3729
3544
  try {
3730
- const { execSync: execSync7 } = await import("child_process");
3545
+ const { execSync: execSync6 } = await import("child_process");
3731
3546
  const root = process.cwd();
3732
- const grepResult = execSync7(
3547
+ const grepResult = execSync6(
3733
3548
  `grep -rn ${grepIncludeFlags()} --include="*.json" --include="*.md" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
3734
3549
  { cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
3735
3550
  ).trim();
@@ -4142,9 +3957,7 @@ var domainRules_exports = {};
4142
3957
  __export(domainRules_exports, {
4143
3958
  appendToLayer: () => appendToLayer,
4144
3959
  checkFileGotchas: () => checkFileGotchas,
4145
- generateDigest: () => generateDigest,
4146
3960
  getActiveGotchas: () => getActiveGotchas,
4147
- getLayersSummary: () => getLayersSummary,
4148
3961
  readLayer: () => readLayer,
4149
3962
  writeLayer: () => writeLayer
4150
3963
  });
@@ -4156,7 +3969,7 @@ function layerFilePath(layer) {
4156
3969
  function ensureLayerFile(layer) {
4157
3970
  const fp = layerFilePath(layer);
4158
3971
  if (!fs7.existsSync(fp)) {
4159
- const template = layer === "domain_rules" ? DOMAIN_RULES_TEMPLATE : layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
3972
+ const template = layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
4160
3973
  const dir = path8.dirname(fp);
4161
3974
  if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
4162
3975
  fs7.writeFileSync(fp, template, "utf-8");
@@ -4194,27 +4007,6 @@ function appendToLayer(layer, entry) {
4194
4007
  return `\u274C Failed to append: ${err}`;
4195
4008
  }
4196
4009
  }
4197
- function getLayersSummary() {
4198
- 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"];
4199
- for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
4200
- const fp = layerFilePath(layer);
4201
- const exists = fs7.existsSync(fp);
4202
- const label = LAYER_FILES[layer].label;
4203
- if (exists) {
4204
- try {
4205
- const content = fs7.readFileSync(fp, "utf-8");
4206
- const lineCount = content.split("\n").length;
4207
- const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
4208
- lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
4209
- } catch {
4210
- lines.push(` \u2753 **${label}** \u2014 could not read`);
4211
- }
4212
- } else {
4213
- lines.push(` \u{1F4C4} **${label}** \u2014 not yet created`);
4214
- }
4215
- }
4216
- return lines.join("\n");
4217
- }
4218
4010
  function getActiveGotchas() {
4219
4011
  const results = [];
4220
4012
  try {
@@ -4259,106 +4051,19 @@ function checkFileGotchas(filePath) {
4259
4051
  }
4260
4052
  return warnings;
4261
4053
  }
4262
- function generateDigest() {
4263
- const lines = [
4264
- "\u{1F4CB} **Kuma Project Digest**",
4265
- "\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",
4266
- ""
4267
- ];
4268
- try {
4269
- const fp1 = ensureLayerFile("domain_rules");
4270
- const content = fs7.readFileSync(fp1, "utf-8");
4271
- const rules = [];
4272
- const workflows = [];
4273
- for (const line of content.split("\n")) {
4274
- const r = line.match(/^- Rule \d+:?\s*(.+)/);
4275
- if (r) rules.push(r[1].substring(0, 60));
4276
- const w = line.match(/^\d+\.\s*(.+)/);
4277
- if (w) workflows.push(w[1].substring(0, 60));
4278
- }
4279
- if (rules.length > 0) lines.push(`\u{1F4CB} **Domain Rules** (${rules.length}): ${rules.slice(0, 3).join("; ")}`);
4280
- if (workflows.length > 0) lines.push(`\u{1F504} **Workflows**: ${workflows.slice(0, 3).join(" \u2192 ")}`);
4281
- } catch {
4282
- lines.push("\u{1F4CB} **Domain Rules**: not set");
4283
- }
4284
- try {
4285
- const fp2 = ensureLayerFile("arch_flow");
4286
- const content = fs7.readFileSync(fp2, "utf-8");
4287
- const chains = [];
4288
- for (const line of content.split("\n")) {
4289
- if (line.includes("->") && line.includes("[")) chains.push(line.trim());
4290
- }
4291
- if (chains.length > 0) lines.push(`\u{1F3D7}\uFE0F **Architecture**: ${chains[0].substring(0, 80)}`);
4292
- else lines.push("\u{1F3D7}\uFE0F **Architecture**: see .kuma/ARCHITECTURE_FLOW.md");
4293
- } catch {
4294
- lines.push("\u{1F3D7}\uFE0F **Architecture**: not documented");
4295
- }
4296
- const gotchas = getActiveGotchas();
4297
- if (gotchas.length > 0) {
4298
- const highCount = gotchas.filter((g) => g.severity === "high" || g.severity === "critical").length;
4299
- lines.push(`\u26A0\uFE0F **Gotchas**: ${gotchas.length} active (${highCount} high/critical)`);
4300
- for (const g of gotchas.slice(0, 3)) {
4301
- lines.push(` \u2022 ${g.filePath}: ${g.description.substring(0, 60)}`);
4302
- }
4303
- } else {
4304
- lines.push("\u26A0\uFE0F **Gotchas**: none recorded");
4305
- }
4306
- const summary = sessionMemory.getSummary();
4307
- lines.push(`\u{1F3AF} **Goal**: ${summary.currentGoal?.substring(0, 60) || "not set"}`);
4308
- lines.push(`\u{1F4DD} **Modified**: ${summary.modifiedFiles?.length || 0} files | \u{1F6E0}\uFE0F ${summary.toolCallCount} calls`);
4309
- return lines.join("\n");
4310
- }
4311
- var LAYER_FILES, DOMAIN_RULES_TEMPLATE, ARCH_FLOW_TEMPLATE, GOTCHAS_TEMPLATE;
4054
+ var LAYER_FILES, ARCH_FLOW_TEMPLATE, GOTCHAS_TEMPLATE;
4312
4055
  var init_domainRules = __esm({
4313
4056
  "src/engine/domainRules.ts"() {
4314
4057
  "use strict";
4315
4058
  init_pathValidator();
4316
4059
  init_sessionMemory();
4317
4060
  LAYER_FILES = {
4318
- domain_rules: { filename: "DOMAIN_RULES.md", label: "Domain & Business Rules" },
4319
4061
  arch_flow: { filename: "ARCHITECTURE_FLOW.md", label: "Architecture & Sequence Flow" },
4320
4062
  gotcha: { filename: "KNOWN_GOTCHAS.md", label: "Known Code Gotchas" }
4321
4063
  };
4322
- DOMAIN_RULES_TEMPLATE = `# Domain & Business Rules (Layer 1)
4323
-
4324
- Auto-generated by Kuma 3-Layer Memory Engine.
4325
- Update this file with core business logic, workflows, and boundary rules.
4326
-
4327
- ## Who
4328
- <!-- Who are the users/actors? -->
4329
-
4330
- ## What
4331
- <!-- What are the core features and business capabilities? -->
4332
-
4333
- ## When
4334
- <!-- What are the time-based rules, schedules, deadlines? -->
4335
-
4336
- ## Where
4337
- <!-- Where does the business operate (environments, regions)? -->
4338
-
4339
- ## Why
4340
- <!-- Why were key architectural decisions made? -->
4341
-
4342
- ## How
4343
- <!-- How do core workflows execute? -->
4344
-
4345
- ## Business Rules
4346
- <!-- Critical business logic constraints -->
4347
- - Rule 1:
4348
- - Rule 2:
4064
+ ARCH_FLOW_TEMPLATE = `# Architecture & Sequence Flow Map
4349
4065
 
4350
- ## Workflows
4351
- <!-- Ordered sequence of operations -->
4352
- 1.
4353
- 2.
4354
-
4355
- ## Boundary Conditions
4356
- <!-- Edge cases, error states, validation rules -->
4357
- -
4358
- `;
4359
- ARCH_FLOW_TEMPLATE = `# Architecture & Sequence Flow Map (Layer 2)
4360
-
4361
- Auto-generated by Kuma 3-Layer Memory Engine.
4066
+ Auto-generated by Kuma Memory Engine.
4362
4067
  Document execution call chains and architectural dependencies.
4363
4068
 
4364
4069
  ## System Overview
@@ -4391,9 +4096,9 @@ Request Flow:
4391
4096
  <!-- External services, APIs, databases -->
4392
4097
  -
4393
4098
  `;
4394
- GOTCHAS_TEMPLATE = `# Known Code Gotchas (Layer 3)
4099
+ GOTCHAS_TEMPLATE = `# Known Code Gotchas
4395
4100
 
4396
- Auto-generated by Kuma 3-Layer Memory Engine.
4101
+ Auto-generated by Kuma Memory Engine.
4397
4102
  Catalog of fragile legacy contracts, edge cases, and unexpected framework quirks.
4398
4103
 
4399
4104
  ## How to Add a Gotcha
@@ -5816,7 +5521,7 @@ function formatScanResult(result) {
5816
5521
  lines.push(
5817
5522
  "",
5818
5523
  "\u{1F4A1} The knowledge graph now has richer code structure data.",
5819
- "\u{1F4A1} Use kuma_context({ action: 'digest' }) to see the graph overview."
5524
+ "\u{1F4A1} Use kuma_context({ action: 'research', scope: '<area>' }) to see the graph overview."
5820
5525
  );
5821
5526
  return lines.join("\n");
5822
5527
  }
@@ -5997,181 +5702,6 @@ var init_kumaFlowCache = __esm({
5997
5702
  }
5998
5703
  });
5999
5704
 
6000
- // src/engine/contextDigest.ts
6001
- var contextDigest_exports = {};
6002
- __export(contextDigest_exports, {
6003
- generateContextDigest: () => generateContextDigest,
6004
- generateDigest: () => generateContextDigest
6005
- });
6006
- import fs14 from "fs";
6007
- import path15 from "path";
6008
- function detectTechStack() {
6009
- const root = getProjectRoot();
6010
- const stack = {
6011
- languages: [],
6012
- framework: null,
6013
- database: null,
6014
- testRunner: null,
6015
- packageManager: null,
6016
- monorepo: false
6017
- };
6018
- try {
6019
- const pkgPath = path15.join(root, "package.json");
6020
- if (fs14.existsSync(pkgPath)) {
6021
- const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
6022
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
6023
- if (fs14.existsSync(path15.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
6024
- else if (fs14.existsSync(path15.join(root, "yarn.lock"))) stack.packageManager = "yarn";
6025
- else stack.packageManager = "npm";
6026
- stack.monorepo = !!pkg.workspaces;
6027
- if (deps.next) stack.framework = "Next.js";
6028
- else if (deps.react) stack.framework = "React";
6029
- else if (deps.vue) stack.framework = "Vue";
6030
- else if (deps.express) stack.framework = "Express";
6031
- else if (deps.fastify) stack.framework = "Fastify";
6032
- else if (deps.nest) stack.framework = "NestJS";
6033
- else if (deps["@remix-run/react"]) stack.framework = "Remix";
6034
- else if (deps.svelte) stack.framework = "Svelte";
6035
- else if (deps.angular) stack.framework = "Angular";
6036
- if (deps.prisma) stack.database = "Prisma";
6037
- else if (deps.typeorm) stack.database = "TypeORM";
6038
- else if (deps.mongoose) stack.database = "MongoDB";
6039
- else if (deps.pg || deps["@neondatabase/serverless"]) stack.database = "PostgreSQL";
6040
- else if (deps.redis) stack.database = "Redis";
6041
- else if (deps.better || deps.drizzle) stack.database = "Drizzle";
6042
- if (deps.jest || deps["@jest/core"]) stack.testRunner = "Jest";
6043
- else if (deps.vitest) stack.testRunner = "Vitest";
6044
- else if (deps.mocha) stack.testRunner = "Mocha";
6045
- else if (deps.playwright) stack.testRunner = "Playwright";
6046
- else if (deps.cypress) stack.testRunner = "Cypress";
6047
- if (fs14.existsSync(path15.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
6048
- if (deps.typescript) stack.languages.push("TypeScript");
6049
- if (fs14.existsSync(path15.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
6050
- if (fs14.existsSync(path15.join(root, "go.mod"))) stack.languages.push("Go");
6051
- if (fs14.existsSync(path15.join(root, "Cargo.toml"))) stack.languages.push("Rust");
6052
- if (fs14.existsSync(path15.join(root, "pyproject.toml"))) stack.languages.push("Python");
6053
- if (fs14.existsSync(path15.join(root, "Gemfile"))) stack.languages.push("Ruby");
6054
- if (stack.languages.length === 0) stack.languages.push("JavaScript");
6055
- }
6056
- } catch {
6057
- }
6058
- return stack;
6059
- }
6060
- function detectEntryPoints() {
6061
- const root = getProjectRoot();
6062
- const entryPoints = [];
6063
- const candidates = [
6064
- "src/index.ts",
6065
- "src/index.js",
6066
- "src/app.ts",
6067
- "src/app.js",
6068
- "index.ts",
6069
- "index.js",
6070
- "src/main.ts",
6071
- "src/main.js",
6072
- "src/server.ts",
6073
- "src/server.js",
6074
- "app.ts",
6075
- "app.js",
6076
- "pages/index.tsx",
6077
- "pages/index.jsx",
6078
- "src/App.tsx",
6079
- "src/App.js",
6080
- "lib/main.ts",
6081
- "lib/index.ts"
6082
- ];
6083
- for (const c of candidates) {
6084
- if (fs14.existsSync(path15.join(root, c))) {
6085
- entryPoints.push(c);
6086
- }
6087
- }
6088
- try {
6089
- const pkg = JSON.parse(fs14.readFileSync(path15.join(root, "package.json"), "utf-8"));
6090
- if (pkg.main && fs14.existsSync(path15.join(root, pkg.main))) {
6091
- if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
6092
- }
6093
- } catch {
6094
- }
6095
- return entryPoints;
6096
- }
6097
- async function getActiveADRs() {
6098
- try {
6099
- const db = await getDb();
6100
- const stmt = db.prepare(
6101
- "SELECT title FROM decision_log WHERE status = 'active' ORDER BY created_at DESC LIMIT 5"
6102
- );
6103
- const results = [];
6104
- while (stmt.step()) {
6105
- const row = stmt.getAsObject();
6106
- results.push(row.title);
6107
- }
6108
- stmt.free();
6109
- return results;
6110
- } catch {
6111
- return [];
6112
- }
6113
- }
6114
- async function generateContextDigest() {
6115
- const stack = detectTechStack();
6116
- const entryPoints = detectEntryPoints();
6117
- const adrs = await getActiveADRs();
6118
- const gotchas = getActiveGotchas();
6119
- const summary = sessionMemory.getSummary();
6120
- const lines = [
6121
- "\u{1F4CB} **Kuma Digest** \u2014 <500 token briefing",
6122
- "\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",
6123
- ""
6124
- ];
6125
- const langStr = stack.languages.join("/");
6126
- const frameworkStr = stack.framework || "\u2014";
6127
- const dbStr = stack.database || "\u2014";
6128
- const testStr = stack.testRunner || "\u2014";
6129
- const pmStr = stack.packageManager || "\u2014";
6130
- lines.push(`\u{1F527} **Stack**: ${langStr} | ${frameworkStr} | ${dbStr} | ${testStr} | ${pmStr}${stack.monorepo ? " (monorepo)" : ""}`);
6131
- lines.push(`\u{1F4C1} **Root**: ${getProjectRoot().split("/").pop() || "?"}`);
6132
- if (entryPoints.length > 0) {
6133
- lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
6134
- }
6135
- try {
6136
- const ruleFile = path15.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
6137
- if (fs14.existsSync(ruleFile)) {
6138
- const content = fs14.readFileSync(ruleFile, "utf-8");
6139
- const rules = content.split("\n").filter((l) => /^- Rule \d+:?\s*/i.test(l)).map((l) => l.replace(/^- Rule \d+:?\s*/i, "").trim()).filter(Boolean);
6140
- if (rules.length > 0) {
6141
- lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
6142
- }
6143
- }
6144
- } catch {
6145
- }
6146
- if (gotchas.length > 0) {
6147
- const critical = gotchas.filter((g) => g.severity === "critical" || g.severity === "high");
6148
- if (critical.length > 0) {
6149
- lines.push(`\u26A0\uFE0F **Gotchas**: ${critical.length} high/critical \u2014 ${critical.slice(0, 3).map((g) => g.filePath).join(", ")}`);
6150
- } else {
6151
- lines.push(`\u26A0\uFE0F **Gotchas**: ${gotchas.length} recorded`);
6152
- }
6153
- }
6154
- if (adrs.length > 0) {
6155
- lines.push(`\u{1F4DD} **ADRs**: ${adrs.slice(0, 3).join(" | ")}`);
6156
- }
6157
- lines.push(`\u{1F3AF} **Goal**: ${summary.currentGoal?.substring(0, 80) || "not set"}`);
6158
- const text = lines.join("\n");
6159
- const estimatedTokens = Math.ceil(text.length / 4);
6160
- if (estimatedTokens > 450) {
6161
- lines.push(`\u{1F4CA} *~${estimatedTokens} tokens, fits under 500 limit*`);
6162
- }
6163
- return lines.join("\n") + "\n\n\u{1F4A1} Use kuma_context({ action: 'digest' }) for the full state.";
6164
- }
6165
- var init_contextDigest = __esm({
6166
- "src/engine/contextDigest.ts"() {
6167
- "use strict";
6168
- init_pathValidator();
6169
- init_sessionMemory();
6170
- init_kumaDb();
6171
- init_domainRules();
6172
- }
6173
- });
6174
-
6175
5705
  // src/engine/kumaSearch.ts
6176
5706
  var kumaSearch_exports = {};
6177
5707
  __export(kumaSearch_exports, {
@@ -6184,8 +5714,8 @@ __export(kumaSearch_exports, {
6184
5714
  formatHybridResults: () => formatHybridResults,
6185
5715
  hybridSearch: () => hybridSearch
6186
5716
  });
6187
- import fs16 from "fs";
6188
- import path17 from "path";
5717
+ import fs15 from "fs";
5718
+ import path16 from "path";
6189
5719
  function expandQueryTerms(query) {
6190
5720
  const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
6191
5721
  const expanded = /* @__PURE__ */ new Map();
@@ -6248,13 +5778,13 @@ async function buildSearchVectors() {
6248
5778
  }
6249
5779
  try {
6250
5780
  const root = getProjectRoot();
6251
- const memDir = path17.join(root, ".kuma", "memories");
6252
- if (fs16.existsSync(memDir)) {
6253
- const files = fs16.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
5781
+ const memDir = path16.join(root, ".kuma", "memories");
5782
+ if (fs15.existsSync(memDir)) {
5783
+ const files = fs15.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
6254
5784
  for (const file of files) {
6255
5785
  try {
6256
- const fullPath = path17.join(memDir, file);
6257
- const content = fs16.readFileSync(fullPath, "utf-8");
5786
+ const fullPath = path16.join(memDir, file);
5787
+ const content = fs15.readFileSync(fullPath, "utf-8");
6258
5788
  const text = content.toLowerCase();
6259
5789
  const rawTerms = extractTerms(text);
6260
5790
  const termCounts = /* @__PURE__ */ new Map();
@@ -6265,7 +5795,7 @@ async function buildSearchVectors() {
6265
5795
  terms: termCounts,
6266
5796
  source: file.replace(/\.md$/, ""),
6267
5797
  sourceType: "memory",
6268
- recency: recencyScore(Math.floor(fs16.statSync(fullPath).mtimeMs / 1e3))
5798
+ recency: recencyScore(Math.floor(fs15.statSync(fullPath).mtimeMs / 1e3))
6269
5799
  });
6270
5800
  } catch {
6271
5801
  }
@@ -6575,368 +6105,45 @@ var init_kumaSearch = __esm({
6575
6105
  }
6576
6106
  });
6577
6107
 
6578
- // src/engine/kumaMiner.ts
6579
- var kumaMiner_exports = {};
6580
- __export(kumaMiner_exports, {
6581
- mineHistoricalDecisions: () => mineHistoricalDecisions
6108
+ // src/engine/kumaCheckpoint.ts
6109
+ var kumaCheckpoint_exports = {};
6110
+ __export(kumaCheckpoint_exports, {
6111
+ createCheckpoint: () => createCheckpoint,
6112
+ listCheckpoints: () => listCheckpoints,
6113
+ pruneCheckpoints: () => pruneCheckpoints,
6114
+ rollbackToCheckpoint: () => rollbackToCheckpoint
6582
6115
  });
6583
- import fs17 from "fs";
6584
- import path18 from "path";
6585
- import { execSync as execSync4 } from "child_process";
6586
- import fastGlob2 from "fast-glob";
6587
- async function mineHistoricalDecisions(options = {}) {
6588
- const root = process.cwd();
6589
- const limit = options.limit || 15;
6590
- const candidates = [];
6116
+ import fs19 from "fs";
6117
+ import path20 from "path";
6118
+ async function createCheckpoint(label, description) {
6591
6119
  try {
6592
- const sinceFlag = options.since ? `--since="${options.since}"` : `--since="1 year"`;
6593
- const gitCmd = `git log ${sinceFlag} -n 100 --pretty=format:"%h|%an|%ad|%s" --date=short`;
6594
- const gitOutput = execSync4(gitCmd, { cwd: root, encoding: "utf-8", timeout: 15e3 });
6595
- const keywords = ["fix", "revert", "hack", "workaround", "urgent", "hotfix", "don't touch", "temporary", "deprecated", "workaround"];
6596
- const lines = gitOutput.split("\n").filter(Boolean);
6597
- const matchingHashes = [];
6598
- for (const line of lines) {
6599
- const parts = line.split("|");
6600
- if (parts.length < 4) continue;
6601
- const [hash, author, date, subject] = parts;
6602
- const lowerSubj = subject.toLowerCase();
6603
- const matchedKeyword = keywords.find((kw) => lowerSubj.includes(kw));
6604
- if (matchedKeyword) {
6605
- matchingHashes.push({ hash, author, date, subject, keyword: matchedKeyword });
6606
- if (matchingHashes.length >= limit) break;
6607
- }
6608
- }
6609
- let changedFilesMap = /* @__PURE__ */ new Map();
6610
- if (matchingHashes.length > 0) {
6611
- try {
6612
- const hashesStr = matchingHashes.map((h) => h.hash).join(" ");
6613
- const batchOutput = execSync4(
6614
- `git show --name-only --oneline ${hashesStr} 2>/dev/null | head -200`,
6615
- { cwd: root, encoding: "utf-8", timeout: 1e4, maxBuffer: 256 * 1024 }
6616
- );
6617
- let currentHash = "";
6618
- const files = [];
6619
- for (const line of batchOutput.split("\n")) {
6620
- if (matchingHashes.find((h) => line.startsWith(h.hash))) {
6621
- if (currentHash && files.length > 0) {
6622
- changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
6623
- }
6624
- currentHash = line.split(" ")[0];
6625
- files.length = 0;
6626
- } else if (line.trim() && !line.startsWith("diff")) {
6627
- files.push(line.trim());
6628
- }
6629
- }
6630
- if (currentHash && files.length > 0) {
6631
- changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
6632
- }
6633
- } catch {
6634
- }
6635
- }
6636
- for (const { hash, author, date, subject, keyword: matchedKeyword } of matchingHashes) {
6637
- let changedFiles = changedFilesMap.get(hash) || "";
6638
- if (!changedFiles) {
6639
- try {
6640
- changedFiles = execSync4(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8", timeout: 5e3 }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
6641
- } catch {
6642
- }
6643
- candidates.push({
6644
- id: `git-${hash}`,
6645
- type: "git_commit",
6646
- title: `[Git Commit] ${subject}`,
6647
- context: `Commit ${hash} by ${author} on ${date}.${changedFiles ? ` Files touched: ${changedFiles}` : ""}`,
6648
- rationale: `Historical signal pattern "${matchedKeyword}" found in commit message.`,
6649
- commitHash: hash,
6650
- pattern: matchedKeyword
6651
- });
6652
- if (candidates.length >= limit) break;
6653
- }
6120
+ const root = getProjectRoot();
6121
+ const cpDir = path20.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
6122
+ if (fs19.existsSync(cpDir)) {
6123
+ return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
6654
6124
  }
6655
- } catch {
6656
- }
6657
- if (candidates.length < limit) {
6658
- try {
6659
- const scopePattern = options.scope ? `**/*${options.scope}*.*` : `**/*.{${SOURCE_EXT_GLOB.slice(1, -1)},md}`;
6660
- const files = await fastGlob2(scopePattern, {
6661
- cwd: root,
6662
- ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
6663
- onlyFiles: true
6664
- });
6665
- const commentPatterns = [
6666
- { tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)/i },
6667
- { tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)/i },
6668
- { tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)/i },
6669
- { tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)/i },
6670
- { tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)/i }
6671
- ];
6672
- for (const file of files.slice(0, 50)) {
6673
- if (candidates.length >= limit) break;
6674
- const fullPath = path18.join(root, file);
6675
- if (!fs17.existsSync(fullPath)) continue;
6676
- const content = fs17.readFileSync(fullPath, "utf-8");
6677
- const lines = content.split("\n");
6678
- for (let i = 0; i < lines.length; i++) {
6679
- const l = lines[i];
6680
- for (const cp of commentPatterns) {
6681
- const match = cp.regex.exec(l);
6682
- if (match) {
6683
- const text = (match[1] || match[2] || l).trim();
6684
- candidates.push({
6685
- id: `comment-${file}-${i + 1}`,
6686
- type: "inline_comment",
6687
- title: `[${cp.tag}] ${file}:${i + 1}`,
6688
- context: `Inline comment marker "${cp.tag}" found in file "${file}" at line ${i + 1}.`,
6689
- rationale: text || l.trim(),
6690
- filePath: file,
6691
- lineNumber: i + 1,
6692
- pattern: cp.tag
6693
- });
6694
- if (candidates.length >= limit) break;
6695
- }
6696
- }
6697
- if (candidates.length >= limit) break;
6698
- }
6699
- }
6700
- } catch {
6701
- }
6702
- }
6703
- if (options.confirm && candidates.length > 0) {
6704
- let recordedCount = 0;
6705
- for (const c of candidates) {
6706
- await recordDecision({
6707
- title: c.title,
6708
- context: c.context,
6709
- options: [],
6710
- rationale: c.rationale,
6711
- outcome: `Mined from ${c.type}`,
6712
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6713
- });
6714
- recordedCount++;
6715
- }
6716
- return `\u26CF\uFE0F **Mined & Recorded ${recordedCount} Decisions** into decision log & graph.
6717
- Use \`kuma_memory({ action: 'decision_log' })\` to view all active & proposed decisions.`;
6718
- }
6719
- if (candidates.length === 0) {
6720
- return "\u26CF\uFE0F **Decision Mining**: No significant historical decision markers (HACK/FIXME/git fix) found for the requested scope.";
6721
- }
6722
- const report = [
6723
- `\u26CF\uFE0F **Decision Mining Candidates** (${candidates.length} candidate(s) found)`,
6724
- "\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",
6725
- "Review these draft historical decisions mined from git history & inline comments:",
6726
- ""
6727
- ];
6728
- candidates.forEach((c, idx) => {
6729
- report.push(`**${idx + 1}. ${c.title}**`);
6730
- report.push(` \u2022 **Type**: \`${c.type}\` | **Signal**: \`${c.pattern}\``);
6731
- report.push(` \u2022 **Context**: ${c.context}`);
6732
- report.push(` \u2022 **Rationale**: ${c.rationale}`);
6733
- report.push("");
6734
- });
6735
- report.push("\u{1F4A1} *To accept and record these mined decisions into knowledge graph & decision.md:*");
6736
- report.push(`Run: \`kuma_memory({ action: 'mine', confirm: true${options.scope ? `, scope: '${options.scope}'` : ""} })\``);
6737
- return report.join("\n");
6738
- }
6739
- var init_kumaMiner = __esm({
6740
- "src/engine/kumaMiner.ts"() {
6741
- "use strict";
6742
- init_kumaMemory();
6743
- init_languageSupport();
6744
- }
6745
- });
6746
-
6747
- // src/engine/kumaAstValidator.ts
6748
- var kumaAstValidator_exports = {};
6749
- __export(kumaAstValidator_exports, {
6750
- formatValidationFindings: () => formatValidationFindings,
6751
- validateCodeContent: () => validateCodeContent,
6752
- validateDiff: () => validateDiff,
6753
- validateFile: () => validateFile
6754
- });
6755
- import fs22 from "fs";
6756
- import path23 from "path";
6757
- function loadImportWhitelist() {
6758
- try {
6759
- const root = getProjectRoot();
6760
- const whitelistPath = path23.join(root, ".kuma", "import-whitelist.json");
6761
- if (fs22.existsSync(whitelistPath)) {
6762
- return JSON.parse(fs22.readFileSync(whitelistPath, "utf-8"));
6763
- }
6764
- } catch {
6765
- }
6766
- return {
6767
- allowed: [],
6768
- blocked: ["sql.js", "better-sqlite3", "mysql", "mssql"]
6769
- // dangerous native deps
6770
- };
6771
- }
6772
- function validateCodeContent(code, filePath) {
6773
- const findings = [];
6774
- const lines = code.split("\n");
6775
- const whitelist = loadImportWhitelist();
6776
- for (let i = 0; i < lines.length; i++) {
6777
- const line = lines[i];
6778
- const lineNum = i + 1;
6779
- if (/\/\/\s*(expect|assert|should|test|it\.)\s*\(/.test(line) || /(\/\*[\s\S]*?\*\/)/.test(line) && /(expect|assert|should|test)/.test(line)) {
6780
- findings.push({
6781
- line: lineNum,
6782
- severity: "error",
6783
- category: "assertion-deletion",
6784
- message: `Test assertion appears to be commented out on line ${lineNum}`,
6785
- suggestion: "Restore the assertion or write an equivalent test for the new behavior"
6786
- });
6787
- }
6788
- const emptyCatch = /catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}" || // Python: bare/empty except blocks (except:, except Exception: pass)
6789
- /except\s*([^:]*)\s*:\s*(pass|\.\.\.)\s*(#.*)?$/.test(line) || /except\s*([^:]*)\s*:\s*$/.test(line) && /^pass(\s*#.*)?$/.test(lines[i + 1]?.trim() ?? "");
6790
- if (emptyCatch) {
6791
- findings.push({
6792
- line: lineNum,
6793
- severity: "error",
6794
- category: "exception-swallowing",
6795
- message: `Empty catch block on line ${lineNum} \u2014 exceptions are being swallowed`,
6796
- suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
6797
- });
6798
- }
6799
- if (isTestFile(filePath || "")) {
6800
- if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\breturn\s+(True|False|None)\b/.test(line) || /\breturn\s+0\b/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
6801
- findings.push({
6802
- line: lineNum,
6803
- severity: "warning",
6804
- category: "reward-hacking",
6805
- message: `Possible hardcoded return in test file on line ${lineNum}: ${line.trim().substring(0, 60)}`,
6806
- suggestion: "Implement the actual logic instead of returning a hardcoded value to pass tests"
6807
- });
6808
- }
6809
- }
6810
- if (!isTestFile(filePath || "") && /\breturn\s+(true|false|null)\s*;/.test(line) && ((lines[i - 1] || "").includes("TODO") || (lines[i - 1] || "").includes("FIXME") || (lines[i - 1] || "").includes("HACK"))) {
6811
- findings.push({
6812
- line: lineNum,
6813
- severity: "warning",
6814
- category: "hardcoded-return",
6815
- message: `Hardcoded return value on line ${lineNum} (possibly temporary/hack)`,
6816
- suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
6817
- });
6818
- }
6819
- const imported = matchImportPath(line);
6820
- if (imported && whitelist.blocked.some((b) => imported.includes(b))) {
6821
- findings.push({
6822
- line: lineNum,
6823
- severity: "error",
6824
- category: "import-violation",
6825
- message: `Blocked import: "${imported}" is not allowed per security whitelist`,
6826
- suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
6827
- });
6828
- }
6829
- }
6830
- return findings;
6831
- }
6832
- function validateFile(filePath) {
6833
- try {
6834
- const root = getProjectRoot();
6835
- const fullPath = path23.resolve(root, filePath);
6836
- if (!fs22.existsSync(fullPath)) return [];
6837
- const content = fs22.readFileSync(fullPath, "utf-8");
6838
- return validateCodeContent(content, filePath);
6839
- } catch {
6840
- return [];
6841
- }
6842
- }
6843
- function formatValidationFindings(findings, filePath) {
6844
- if (findings.length === 0) {
6845
- return "\u2705 **AST Validation** \u2014 No structural issues found.";
6846
- }
6847
- const errors = findings.filter((f) => f.severity === "error");
6848
- const warnings = findings.filter((f) => f.severity === "warning");
6849
- const lines = [
6850
- "\u{1F52C} **AST Code Validation Report**",
6851
- "\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",
6852
- "",
6853
- `${findings.length} finding(s): ${errors.length} errors, ${warnings.length} warnings`,
6854
- filePath ? `\u{1F4C4} ${filePath}` : "",
6855
- ""
6856
- ];
6857
- for (const f of findings) {
6858
- const icon = f.severity === "error" ? "\u{1F534}" : f.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
6859
- lines.push(`${icon} L${f.line} [${f.category}]`);
6860
- lines.push(` ${f.message}`);
6861
- lines.push(` \u{1F4A1} ${f.suggestion}`);
6862
- lines.push("");
6863
- }
6864
- return lines.join("\n");
6865
- }
6866
- function validateDiff(oldContent, newContent, filePath) {
6867
- const findings = [];
6868
- const oldFindings = validateCodeContent(oldContent, filePath);
6869
- const newFindings = validateCodeContent(newContent, filePath);
6870
- const oldAssertions = oldFindings.filter((f) => f.category === "assertion-deletion");
6871
- const newAssertions = newFindings.filter((f) => f.category === "assertion-deletion");
6872
- for (const oldAssertion of oldAssertions) {
6873
- if (!newAssertions.some((f) => f.line === oldAssertion.line && f.message === oldAssertion.message)) {
6874
- continue;
6875
- }
6876
- }
6877
- const oldLines = oldContent.split("\n");
6878
- const newLines = newContent.split("\n");
6879
- for (let i = 0; i < newLines.length; i++) {
6880
- if (i >= oldLines.length || newLines[i] !== oldLines[i]) {
6881
- const changedLineFindings = validateCodeContent(newLines[i], filePath);
6882
- for (const f of changedLineFindings) {
6883
- findings.push({
6884
- ...f,
6885
- line: i + 1,
6886
- message: `[NEW] ${f.message}`
6887
- });
6888
- }
6889
- }
6890
- }
6891
- return findings;
6892
- }
6893
- var init_kumaAstValidator = __esm({
6894
- "src/engine/kumaAstValidator.ts"() {
6895
- "use strict";
6896
- init_pathValidator();
6897
- init_languageSupport();
6898
- }
6899
- });
6900
-
6901
- // src/engine/kumaCheckpoint.ts
6902
- var kumaCheckpoint_exports = {};
6903
- __export(kumaCheckpoint_exports, {
6904
- createCheckpoint: () => createCheckpoint,
6905
- listCheckpoints: () => listCheckpoints,
6906
- pruneCheckpoints: () => pruneCheckpoints,
6907
- rollbackToCheckpoint: () => rollbackToCheckpoint
6908
- });
6909
- import fs23 from "fs";
6910
- import path24 from "path";
6911
- async function createCheckpoint(label, description) {
6912
- try {
6913
- const root = getProjectRoot();
6914
- const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
6915
- if (fs23.existsSync(cpDir)) {
6916
- return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
6917
- }
6918
- fs23.mkdirSync(cpDir, { recursive: true });
6919
- const db = await getDb();
6920
- const filesStmt = db.prepare(
6921
- "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
6922
- );
6923
- const files = [];
6924
- while (filesStmt.step()) {
6925
- const row = filesStmt.getAsObject();
6926
- const fp = row.file_path;
6927
- const fullPath = path24.resolve(root, fp);
6928
- if (fs23.existsSync(fullPath)) {
6929
- const content = fs23.readFileSync(fullPath, "utf-8");
6930
- const hash = simpleHash2(content);
6931
- const fileDir = path24.dirname(path24.join(cpDir, "files", fp));
6932
- if (!fs23.existsSync(fileDir)) fs23.mkdirSync(fileDir, { recursive: true });
6933
- fs23.writeFileSync(path24.join(cpDir, "files", fp), content, "utf-8");
6934
- files.push({ path: fp, hash });
6125
+ fs19.mkdirSync(cpDir, { recursive: true });
6126
+ const db = await getDb();
6127
+ const filesStmt = db.prepare(
6128
+ "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
6129
+ );
6130
+ const files = [];
6131
+ while (filesStmt.step()) {
6132
+ const row = filesStmt.getAsObject();
6133
+ const fp = row.file_path;
6134
+ const fullPath = path20.resolve(root, fp);
6135
+ if (fs19.existsSync(fullPath)) {
6136
+ const content = fs19.readFileSync(fullPath, "utf-8");
6137
+ const hash = simpleHash2(content);
6138
+ const fileDir = path20.dirname(path20.join(cpDir, "files", fp));
6139
+ if (!fs19.existsSync(fileDir)) fs19.mkdirSync(fileDir, { recursive: true });
6140
+ fs19.writeFileSync(path20.join(cpDir, "files", fp), content, "utf-8");
6141
+ files.push({ path: fp, hash });
6935
6142
  }
6936
6143
  }
6937
6144
  filesStmt.free();
6938
6145
  const dbData = db.export();
6939
- fs23.writeFileSync(path24.join(cpDir, "kuma.db"), Buffer.from(dbData));
6146
+ fs19.writeFileSync(path20.join(cpDir, "kuma.db"), Buffer.from(dbData));
6940
6147
  const manifest = {
6941
6148
  label,
6942
6149
  timestamp: Date.now(),
@@ -6944,8 +6151,8 @@ async function createCheckpoint(label, description) {
6944
6151
  dbSnapshot: true,
6945
6152
  description
6946
6153
  };
6947
- fs23.writeFileSync(
6948
- path24.join(cpDir, "manifest.json"),
6154
+ fs19.writeFileSync(
6155
+ path20.join(cpDir, "manifest.json"),
6949
6156
  JSON.stringify(manifest, null, 2),
6950
6157
  "utf-8"
6951
6158
  );
@@ -6961,7 +6168,7 @@ async function createCheckpoint(label, description) {
6961
6168
  `\u{1F5C4}\uFE0F **Database** snapshotted`,
6962
6169
  `\u{1F4CD} ${cpDir}`,
6963
6170
  ``,
6964
- `\u{1F4A1} To restore: kuma_safety({ action: 'rollback', label: '${label}' })`,
6171
+ `\u{1F4A1} To restore: kuma_safety({ action: 'rollback_label', label: '${label}' })`,
6965
6172
  description ? `\u{1F4DD} ${description}` : ""
6966
6173
  ].filter(Boolean).join("\n");
6967
6174
  } catch (err) {
@@ -6971,37 +6178,39 @@ async function createCheckpoint(label, description) {
6971
6178
  async function rollbackToCheckpoint(label) {
6972
6179
  try {
6973
6180
  const root = getProjectRoot();
6974
- const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
6975
- const manifestPath = path24.join(cpDir, "manifest.json");
6976
- if (!fs23.existsSync(manifestPath)) {
6977
- return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
6181
+ const cpDir = path20.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
6182
+ const manifestPath = path20.join(cpDir, "manifest.json");
6183
+ if (!fs19.existsSync(manifestPath)) {
6184
+ return `\u274C Checkpoint "${label}" not found. Available checkpoints:
6185
+
6186
+ ${listCheckpoints()}`;
6978
6187
  }
6979
6188
  const manifest = JSON.parse(
6980
- fs23.readFileSync(manifestPath, "utf-8")
6189
+ fs19.readFileSync(manifestPath, "utf-8")
6981
6190
  );
6982
6191
  let restored = 0;
6983
6192
  let failed = 0;
6984
6193
  for (const f of manifest.files) {
6985
- const snapshotPath = path24.join(cpDir, "files", f.path);
6986
- if (fs23.existsSync(snapshotPath)) {
6194
+ const snapshotPath = path20.join(cpDir, "files", f.path);
6195
+ if (fs19.existsSync(snapshotPath)) {
6987
6196
  try {
6988
- const content = fs23.readFileSync(snapshotPath, "utf-8");
6989
- const targetPath = path24.resolve(root, f.path);
6990
- const targetDir = path24.dirname(targetPath);
6991
- if (!fs23.existsSync(targetDir)) fs23.mkdirSync(targetDir, { recursive: true });
6992
- fs23.writeFileSync(targetPath, content, "utf-8");
6197
+ const content = fs19.readFileSync(snapshotPath, "utf-8");
6198
+ const targetPath = path20.resolve(root, f.path);
6199
+ const targetDir = path20.dirname(targetPath);
6200
+ if (!fs19.existsSync(targetDir)) fs19.mkdirSync(targetDir, { recursive: true });
6201
+ fs19.writeFileSync(targetPath, content, "utf-8");
6993
6202
  restored++;
6994
6203
  } catch {
6995
6204
  failed++;
6996
6205
  }
6997
6206
  }
6998
6207
  }
6999
- const dbSnapshotPath = path24.join(cpDir, "kuma.db");
7000
- if (fs23.existsSync(dbSnapshotPath)) {
7001
- const kumaDir = path24.join(root, ".kuma");
7002
- const dbPath = path24.join(kumaDir, "kuma.db");
7003
- const snapshotData = fs23.readFileSync(dbSnapshotPath);
7004
- fs23.writeFileSync(dbPath, snapshotData);
6208
+ const dbSnapshotPath = path20.join(cpDir, "kuma.db");
6209
+ if (fs19.existsSync(dbSnapshotPath)) {
6210
+ const kumaDir = path20.join(root, ".kuma");
6211
+ const dbPath = path20.join(kumaDir, "kuma.db");
6212
+ const snapshotData = fs19.readFileSync(dbSnapshotPath);
6213
+ fs19.writeFileSync(dbPath, snapshotData);
7005
6214
  resetDbInstance();
7006
6215
  }
7007
6216
  sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
@@ -7026,18 +6235,18 @@ async function rollbackToCheckpoint(label) {
7026
6235
  function listCheckpoints() {
7027
6236
  try {
7028
6237
  const root = getProjectRoot();
7029
- const cpDir = path24.join(root, CHECKPOINT_DIR);
7030
- if (!fs23.existsSync(cpDir)) {
6238
+ const cpDir = path20.join(root, CHECKPOINT_DIR);
6239
+ if (!fs19.existsSync(cpDir)) {
7031
6240
  return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
7032
6241
  }
7033
- const entries = fs23.readdirSync(cpDir);
6242
+ const entries = fs19.readdirSync(cpDir);
7034
6243
  const checkpoints = [];
7035
6244
  for (const entry of entries) {
7036
- const manifestPath = path24.join(cpDir, entry, "manifest.json");
7037
- if (fs23.existsSync(manifestPath)) {
6245
+ const manifestPath = path20.join(cpDir, entry, "manifest.json");
6246
+ if (fs19.existsSync(manifestPath)) {
7038
6247
  try {
7039
6248
  const manifest = JSON.parse(
7040
- fs23.readFileSync(manifestPath, "utf-8")
6249
+ fs19.readFileSync(manifestPath, "utf-8")
7041
6250
  );
7042
6251
  checkpoints.push({ label: entry, manifest });
7043
6252
  } catch {
@@ -7069,12 +6278,12 @@ function listCheckpoints() {
7069
6278
  function pruneCheckpoints(keep = 5) {
7070
6279
  try {
7071
6280
  const root = getProjectRoot();
7072
- const cpDir = path24.join(root, CHECKPOINT_DIR);
7073
- if (!fs23.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
7074
- const entries = fs23.readdirSync(cpDir).map((e) => ({ name: e, time: fs23.statSync(path24.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
6281
+ const cpDir = path20.join(root, CHECKPOINT_DIR);
6282
+ if (!fs19.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
6283
+ const entries = fs19.readdirSync(cpDir).map((e) => ({ name: e, time: fs19.statSync(path20.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
7075
6284
  let removed = 0;
7076
6285
  for (let i = keep; i < entries.length; i++) {
7077
- fs23.rmSync(path24.join(cpDir, entries[i].name), { recursive: true, force: true });
6286
+ fs19.rmSync(path20.join(cpDir, entries[i].name), { recursive: true, force: true });
7078
6287
  removed++;
7079
6288
  }
7080
6289
  return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
@@ -7115,16 +6324,16 @@ __export(kumaAutoGotcha_exports, {
7115
6324
  saveAutoGotchaState: () => saveAutoGotchaState,
7116
6325
  trackVerificationResult: () => trackVerificationResult
7117
6326
  });
7118
- import fs24 from "fs";
7119
- import path25 from "path";
6327
+ import fs20 from "fs";
6328
+ import path21 from "path";
7120
6329
  function statePath() {
7121
- return path25.join(getProjectRoot(), STATE_FILE);
6330
+ return path21.join(getProjectRoot(), STATE_FILE);
7122
6331
  }
7123
6332
  function loadAutoGotchaState() {
7124
6333
  try {
7125
6334
  const fp = statePath();
7126
- if (fs24.existsSync(fp)) {
7127
- const parsed = JSON.parse(fs24.readFileSync(fp, "utf-8"));
6335
+ if (fs20.existsSync(fp)) {
6336
+ const parsed = JSON.parse(fs20.readFileSync(fp, "utf-8"));
7128
6337
  return {
7129
6338
  failures: parsed?.failures ?? {},
7130
6339
  lastRecorded: parsed?.lastRecorded ?? {}
@@ -7137,9 +6346,9 @@ function loadAutoGotchaState() {
7137
6346
  function saveAutoGotchaState(state) {
7138
6347
  try {
7139
6348
  const fp = statePath();
7140
- const dir = path25.dirname(fp);
7141
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
7142
- fs24.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
6349
+ const dir = path21.dirname(fp);
6350
+ if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
6351
+ fs20.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
7143
6352
  } catch {
7144
6353
  }
7145
6354
  }
@@ -7222,37 +6431,37 @@ __export(kumaVerifier_exports, {
7222
6431
  getRunningVerificationPid: () => getRunningVerificationPid,
7223
6432
  runAutoVerification: () => runAutoVerification
7224
6433
  });
7225
- import fs25 from "fs";
7226
- import path26 from "path";
6434
+ import fs21 from "fs";
6435
+ import path22 from "path";
7227
6436
  import { exec } from "child_process";
7228
6437
  function getLockDir(root = process.cwd()) {
7229
- return path26.resolve(root, LOCK_DIR_NAME);
6438
+ return path22.resolve(root, LOCK_DIR_NAME);
7230
6439
  }
7231
6440
  function getPidFile(root = process.cwd()) {
7232
- return path26.join(getLockDir(root), "pid");
6441
+ return path22.join(getLockDir(root), "pid");
7233
6442
  }
7234
6443
  function acquireFileLock(root) {
7235
6444
  const lockDir = getLockDir(root);
7236
6445
  try {
7237
- fs25.mkdirSync(lockDir, { recursive: false });
7238
- fs25.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
6446
+ fs21.mkdirSync(lockDir, { recursive: false });
6447
+ fs21.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7239
6448
  return true;
7240
6449
  } catch {
7241
6450
  try {
7242
- if (fs25.existsSync(getPidFile(root))) {
7243
- const pid = parseInt(fs25.readFileSync(getPidFile(root), "utf-8"), 10);
6451
+ if (fs21.existsSync(getPidFile(root))) {
6452
+ const pid = parseInt(fs21.readFileSync(getPidFile(root), "utf-8"), 10);
7244
6453
  try {
7245
6454
  process.kill(pid, 0);
7246
6455
  return false;
7247
6456
  } catch {
7248
- fs25.rmSync(lockDir, { recursive: true, force: true });
6457
+ fs21.rmSync(lockDir, { recursive: true, force: true });
7249
6458
  return acquireFileLock(root);
7250
6459
  }
7251
6460
  }
7252
6461
  } catch {
7253
6462
  }
7254
6463
  try {
7255
- fs25.rmSync(lockDir, { recursive: true, force: true });
6464
+ fs21.rmSync(lockDir, { recursive: true, force: true });
7256
6465
  return acquireFileLock(root);
7257
6466
  } catch {
7258
6467
  return false;
@@ -7261,7 +6470,7 @@ function acquireFileLock(root) {
7261
6470
  }
7262
6471
  function releaseFileLock(root) {
7263
6472
  try {
7264
- fs25.rmSync(getLockDir(root), { recursive: true, force: true });
6473
+ fs21.rmSync(getLockDir(root), { recursive: true, force: true });
7265
6474
  } catch {
7266
6475
  }
7267
6476
  }
@@ -7284,36 +6493,36 @@ function getRunningVerificationPid() {
7284
6493
  return _currentProcess?.pid ?? null;
7285
6494
  }
7286
6495
  function detectTestRunner(root = process.cwd()) {
7287
- const pkgPath = path26.join(root, "package.json");
7288
- if (fs25.existsSync(pkgPath)) {
6496
+ const pkgPath = path22.join(root, "package.json");
6497
+ if (fs21.existsSync(pkgPath)) {
7289
6498
  try {
7290
- const pkg = JSON.parse(fs25.readFileSync(pkgPath, "utf-8"));
6499
+ const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
7291
6500
  if (pkg.scripts && pkg.scripts.test) {
7292
- if (fs25.existsSync(path26.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
7293
- if (fs25.existsSync(path26.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
6501
+ if (fs21.existsSync(path22.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
6502
+ if (fs21.existsSync(path22.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
7294
6503
  return { runner: "npm", baseCommand: "npm test" };
7295
6504
  }
7296
6505
  } catch {
7297
6506
  }
7298
6507
  }
7299
- if (fs25.existsSync(path26.join(root, "pytest.ini")) || fs25.existsSync(path26.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
7300
- if (fs25.existsSync(path26.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
7301
- if (fs25.existsSync(path26.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
7302
- if (fs25.existsSync(path26.join(root, "Makefile"))) {
6508
+ if (fs21.existsSync(path22.join(root, "pytest.ini")) || fs21.existsSync(path22.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
6509
+ if (fs21.existsSync(path22.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
6510
+ if (fs21.existsSync(path22.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
6511
+ if (fs21.existsSync(path22.join(root, "Makefile"))) {
7303
6512
  try {
7304
- const makefileContent = fs25.readFileSync(path26.join(root, "Makefile"), "utf-8");
6513
+ const makefileContent = fs21.readFileSync(path22.join(root, "Makefile"), "utf-8");
7305
6514
  const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
7306
6515
  if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
7307
6516
  } catch {
7308
6517
  }
7309
6518
  }
7310
- if (fs25.existsSync(path26.join(root, "package.json"))) {
6519
+ if (fs21.existsSync(path22.join(root, "package.json"))) {
7311
6520
  try {
7312
- const pkg = JSON.parse(fs25.readFileSync(path26.join(root, "package.json"), "utf-8"));
6521
+ const pkg = JSON.parse(fs21.readFileSync(path22.join(root, "package.json"), "utf-8"));
7313
6522
  if (pkg.dependencies || pkg.devDependencies) {
7314
- if (fs25.existsSync(path26.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
7315
- const srcDir = path26.join(root, "src");
7316
- if (fs25.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
6523
+ if (fs21.existsSync(path22.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
6524
+ const srcDir = path22.join(root, "src");
6525
+ if (fs21.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
7317
6526
  }
7318
6527
  } catch {
7319
6528
  }
@@ -7432,7 +6641,7 @@ async function runAutoVerification(options = {}) {
7432
6641
  const heartbeatTimer = setInterval(() => {
7433
6642
  if (getLockDir(root)) {
7434
6643
  try {
7435
- fs25.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
6644
+ fs21.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7436
6645
  } catch {
7437
6646
  }
7438
6647
  }
@@ -7552,8 +6761,8 @@ __export(init_exports, {
7552
6761
  generateInitMdContent: () => generateInitMdContent,
7553
6762
  runInit: () => runInit
7554
6763
  });
7555
- import fs26 from "fs";
7556
- import path27 from "path";
6764
+ import fs22 from "fs";
6765
+ import path23 from "path";
7557
6766
  function configFilePath(type) {
7558
6767
  switch (type) {
7559
6768
  case "claude":
@@ -7595,7 +6804,7 @@ function claudeTemplate() {
7595
6804
  " \u26A0\uFE0F Requires `kuma` on PATH (global install). A silent hook means no gotchas for that file.",
7596
6805
  "",
7597
6806
  "\u{1F4D6} Rules: `.kuma/init.md`",
7598
- "\u{1F9E0} Memories: `.kuma/memories/*.md`"
6807
+ "\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
7599
6808
  ].join("\n");
7600
6809
  }
7601
6810
  function cursorRulesTemplate() {
@@ -7617,7 +6826,7 @@ function windsurfRulesTemplate() {
7617
6826
  "Windsurf Cascade agent: Kuma MCP tools are installed.",
7618
6827
  "All behavioral rules are in `.kuma/init.md`.",
7619
6828
  '**Before coding, call `kuma_context({ action: "init" })`** to load project context and session memory.',
7620
- "Project knowledge persists in `.kuma/memories/*.md` across sessions.",
6829
+ "Project knowledge persists across sessions (`.kuma/` \u2014 kuma.db + markdown layers).",
7621
6830
  "",
7622
6831
  "Also auto-detected as SKILL.md in `.windsurf/skills/` and `.agents/skills/`."
7623
6832
  ].join("\n");
@@ -7654,12 +6863,22 @@ function opencodeAgentsMdTemplate() {
7654
6863
  "Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
7655
6864
  "Record what matters, skip what doesn't.",
7656
6865
  "",
6866
+ "<kuma_obedience>",
6867
+ "## \u{1F512} Obedience (non-negotiable)",
6868
+ '- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
6869
+ "- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
6870
+ '- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
6871
+ "- MUST NOT call actions outside the 13 core actions.",
6872
+ "</kuma_obedience>",
6873
+ "",
6874
+ "<kuma_workflow>",
7657
6875
  '\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
7658
6876
  '\u{1F52C} **Unfamiliar code:** `kuma_kuma_context({ action: "research", scope: "<area>" })`',
7659
6877
  '\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
7660
6878
  '\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
7661
6879
  '\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
7662
6880
  '\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
6881
+ "</kuma_workflow>",
7663
6882
  "",
7664
6883
  "\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` prefix.",
7665
6884
  ""
@@ -7736,7 +6955,7 @@ function openclawSkillTemplate() {
7736
6955
  KUMA_CORE_INSTRUCTIONS,
7737
6956
  "",
7738
6957
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
7739
- "\u{1F9E0} Memories: `.kuma/memories/*.md`"
6958
+ "\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
7740
6959
  ].join("\n");
7741
6960
  }
7742
6961
  function codewhaleTemplate() {
@@ -7749,7 +6968,7 @@ function codewhaleTemplate() {
7749
6968
  KUMA_CORE_INSTRUCTIONS,
7750
6969
  "",
7751
6970
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
7752
- "\u{1F9E0} Memories: `.kuma/memories/*.md`"
6971
+ "\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
7753
6972
  ].join("\n");
7754
6973
  }
7755
6974
  function antigravitySkillTemplate() {
@@ -7762,7 +6981,7 @@ function antigravitySkillTemplate() {
7762
6981
  KUMA_CORE_INSTRUCTIONS,
7763
6982
  "",
7764
6983
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
7765
- "\u{1F9E0} Memories: `.kuma/memories/*.md`"
6984
+ "\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
7766
6985
  ].join("\n");
7767
6986
  }
7768
6987
  function antigravityMcpConfigTemplate() {
@@ -7791,16 +7010,36 @@ function generateInitMdContent() {
7791
7010
  "",
7792
7011
  "---",
7793
7012
  "",
7013
+ "<kuma_obedience>",
7014
+ "## \u{1F512} OBEDIENCE (non-negotiable)",
7015
+ "",
7016
+ '1. **MUST call `kuma_context({ action: "init" })`** at session start \u2014 before any code edit.',
7017
+ '2. **MUST record gotchas IMMEDIATELY** when a bug/quirk is found (`kuma_memory({ action: "gotcha" })`).',
7018
+ '3. **MUST call `kuma_safety({ action: "verify" })`** after code edits.',
7019
+ "4. **MUST NOT call actions outside the 13 core actions** in this file \u2014 anything else is rejected by the MCP schema.",
7020
+ "5. If a Kuma call fails or returns an error, **re-read this file and retry** \u2014 do not silently continue.",
7021
+ "",
7022
+ "> These rules are enforced: the MCP schema rejects unknown actions, and Claude Code hooks inject gotchas before every edit.",
7023
+ "</kuma_obedience>",
7024
+ "",
7025
+ "---",
7026
+ "",
7027
+ "<kuma_workflow>",
7794
7028
  "## SESSION WORKFLOW",
7795
7029
  "",
7796
- "### 6 Core Actions",
7030
+ "### Core Actions (13)",
7797
7031
  "",
7798
7032
  '\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
7799
7033
  '\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
7034
+ '\u{1F570}\uFE0F **Why this file?** `kuma_context({ action: "history", target: "<file>" })`',
7035
+ '\u{1F5C2}\uFE0F **Read a flow:** `kuma_context({ action: "flow", target: "<domain>" })`',
7800
7036
  '\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
7801
7037
  '\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
7802
7038
  '\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
7039
+ '\u{1F4BE} **Saved findings:** `kuma_memory({ action: "research_save" })` \xB7 \u{1F50D} **Quick lookup:** `kuma_memory({ action: "search" })`',
7803
7040
  '\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
7041
+ '\u{1F4F8} **Rollback (one mechanism):** `kuma_safety({ action: "checkpoint", label: "pre-x" })` \u2192 `rollback_label` to restore',
7042
+ "</kuma_workflow>",
7804
7043
  "",
7805
7044
  "### Recording Rules",
7806
7045
  "",
@@ -7838,7 +7077,7 @@ function generateInitMdContent() {
7838
7077
  "",
7839
7078
  "## \u26A0\uFE0F HONEST LIMITATIONS",
7840
7079
  "",
7841
- "- **Node IDs are text-based** \u2014 same file recorded with slightly different paths can create duplicates. Use `delete_node` to clean up.",
7080
+ "- **Node IDs are text-based** \u2014 same file recorded with slightly different paths can create duplicates.",
7842
7081
  "- **Metadata is JSON** \u2014 format varies per node type. Studio displays all fields dynamically.",
7843
7082
  "- **Gotchas have 2 stores** \u2014 `known_gotchas` table + `nodes` graph. `syncGotchasGraph()` keeps them in sync.",
7844
7083
  "- **Edge weights are static** \u2014 `contains`=2.0, `flows_through`=1.0. Dynamic ranking not yet implemented.",
@@ -7852,15 +7091,9 @@ function generateInitMdContent() {
7852
7091
  "| Action | Description |",
7853
7092
  "|--------|-------------|",
7854
7093
  "| `init` | Lean project brief, restore session |",
7094
+ "| `research` | 5-step pipeline before editing unfamiliar code |",
7855
7095
  "| `history` | Why is this file written this way (cross-session trace) |",
7856
- "| `flow` | Hash-verified derived flow cache (F13) |",
7857
- "| `impact` | Analyze change effects |",
7858
- "| `navigate` | Trace code flow |",
7859
- "| `changes` | View session change log |",
7860
- "| `digest` | Ultra-compact project briefing |",
7861
- "| `drift` | Detect memory staleness |",
7862
- "| `rollback` | Undo a change by change ID |",
7863
- "| `resume` | Load previous session context |",
7096
+ "| `flow` | Read a recorded architecture flow |",
7864
7097
  "",
7865
7098
  "### kuma_memory \u2014 Knowledge Recording",
7866
7099
  "",
@@ -7870,11 +7103,7 @@ function generateInitMdContent() {
7870
7103
  "| `arch_flow` | Record architecture flow (max 5 core files) |",
7871
7104
  "| `decision` | Record ADR / decision rationale |",
7872
7105
  "| `research_save` | Cache research findings |",
7873
- "| `search` | Search knowledge graph |",
7874
- "| `session` | Session summary |",
7875
- "| `mine` | Mine decisions from git history |",
7876
- "| `delete_node` | Remove node + graph + table entries |",
7877
- "| `goal_progress` | Track goal completion |",
7106
+ "| `search` | Quick lookup of memory + knowledge graph |",
7878
7107
  "",
7879
7108
  "### kuma_safety \u2014 Safety & Verification",
7880
7109
  "",
@@ -7882,12 +7111,31 @@ function generateInitMdContent() {
7882
7111
  "|--------|-------------|",
7883
7112
  "| `guard` | Anti-pattern detection before edits |",
7884
7113
  "| `verify` | Auto-run scoped tests after edits |",
7885
- "| `check` | Pre-execution safety check |",
7886
- "| `audit` | Query audit trail |",
7887
- "| `security` | Security leak scanner |",
7888
- "| `gc` | Garbage collection |",
7889
- "| `ast`/`validate` | AST-based code validation |",
7890
- "| `checkpoint` | Atomic snapshot before refactors |",
7114
+ "| `checkpoint` | Labeled snapshot before risky work |",
7115
+ "| `rollback_label` | Restore a labeled snapshot |",
7116
+ "",
7117
+ "---",
7118
+ "",
7119
+ "<kuma_storage>",
7120
+ "## \u{1F4C2} What Kuma Stores (.kuma/)",
7121
+ "",
7122
+ "Kuma persists project knowledge in `.kuma/`. You can READ these files directly ",
7123
+ "(no MCP call needed) \u2014 but never edit them by hand; record via the actions instead.",
7124
+ "",
7125
+ "| File | Contents | Read via |",
7126
+ "|------|----------|----------|",
7127
+ "| `init.md` | This file \u2014 behavioral rules | read directly |",
7128
+ '| `KNOWN_GOTCHAS.md` | Gotchas (human-readable) | `kuma_context({ action: "history" })` or read directly |',
7129
+ '| `ARCHITECTURE_FLOW.md` | Recorded flows | `kuma_context({ action: "flow" })` or read directly |',
7130
+ '| `memories/decisions.md` | Decision log (ADR-style) | `kuma_context({ action: "history" })` or read directly |',
7131
+ '| `kuma.db` | SQLite graph (nodes, edges, research cache) | `kuma_memory({ action: "search" })` |',
7132
+ '| `memory.json` | Session memory (internal) | `kuma_context({ action: "init" })` auto-restores |',
7133
+ "| `auto-gotcha.json` | Loop auto-capture state (internal) | \u2014 |",
7134
+ "",
7135
+ "> Rules of thumb: `gotcha`/`arch_flow`/`decision` are the WRITE paths; `init`/`history`/",
7136
+ "> `flow`/`search` are the READ paths. Keep recordings small and high-signal \u2014 each one",
7137
+ "> is re-injected on future sessions.",
7138
+ "</kuma_storage>",
7891
7139
  "",
7892
7140
  "---",
7893
7141
  "",
@@ -7916,27 +7164,16 @@ function generateInitMdContent() {
7916
7164
  "",
7917
7165
  "---",
7918
7166
  "",
7919
- "## \u{1F5D1}\uFE0F Graph Management",
7920
- "",
7921
- '**Delete:** `kuma_memory({ action: "delete_node", target: "<node_id_or_type>" })`',
7922
- "- Deletes from graph nodes, edges, AND known_gotchas table (unified delete)",
7923
- "",
7924
- '**Clear entire graph:** `kuma_memory({ action: "clear" })`',
7925
- "",
7926
- '**Self-heal:** `kuma_safety({ action: "gotcha_staleness" })`',
7927
- "- Verifies gotcha file/symbol references still exist",
7928
- "- Removes obsolete gotchas (deleted files)",
7929
- "",
7930
7167
  "---",
7931
7168
  "",
7932
7169
  "_Generated by Kuma MCP_"
7933
7170
  ].join("\n");
7934
7171
  }
7935
7172
  function handleOpencodeSecondary(root, results) {
7936
- const skillPath = path27.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
7173
+ const skillPath = path23.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
7937
7174
  if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
7938
7175
  try {
7939
- const dir = path27.dirname(skillPath);
7176
+ const dir = path23.dirname(skillPath);
7940
7177
  const content = [
7941
7178
  "---",
7942
7179
  "name: kuma-mcp",
@@ -7946,29 +7183,39 @@ function handleOpencodeSecondary(root, results) {
7946
7183
  "Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
7947
7184
  "Record what matters, skip what doesn't.",
7948
7185
  "",
7186
+ "<kuma_obedience>",
7187
+ "## \u{1F512} Obedience (non-negotiable)",
7188
+ '- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
7189
+ "- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
7190
+ '- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
7191
+ "- MUST NOT call actions outside the 13 core actions.",
7192
+ "</kuma_obedience>",
7193
+ "",
7194
+ "<kuma_workflow>",
7949
7195
  '\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
7950
7196
  '\u{1F52C} **Unfamiliar code:** `kuma_kuma_context({ action: "research", scope: "<area>" })`',
7951
7197
  '\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
7952
7198
  '\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
7953
7199
  '\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
7954
7200
  '\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
7201
+ "</kuma_workflow>",
7955
7202
  "",
7956
7203
  "\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` prefix.",
7957
7204
  "",
7958
7205
  "\u{1F4D6} Full rules: `.kuma/init.md`"
7959
7206
  ].join("\n");
7960
- if (fs26.existsSync(skillPath)) {
7961
- const existing = fs26.readFileSync(skillPath, "utf-8");
7207
+ if (fs22.existsSync(skillPath)) {
7208
+ const existing = fs22.readFileSync(skillPath, "utf-8");
7962
7209
  if (existing.includes("kuma-mcp")) {
7963
7210
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
7964
7211
  return;
7965
7212
  }
7966
7213
  const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
7967
- fs26.writeFileSync(skillPath, newContent, "utf-8");
7214
+ fs22.writeFileSync(skillPath, newContent, "utf-8");
7968
7215
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
7969
7216
  } else {
7970
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
7971
- fs26.writeFileSync(skillPath, content, "utf-8");
7217
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
7218
+ fs22.writeFileSync(skillPath, content, "utf-8");
7972
7219
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
7973
7220
  }
7974
7221
  } catch (err) {
@@ -7981,21 +7228,21 @@ function handleOpencodeSecondary(root, results) {
7981
7228
  }
7982
7229
  }
7983
7230
  function handleCodexSecondary(root, results) {
7984
- const tomlPath = path27.resolve(root, ".codex/config.toml");
7231
+ const tomlPath = path23.resolve(root, ".codex/config.toml");
7985
7232
  if (results.some((r) => r.filePath === ".codex/config.toml")) return;
7986
7233
  try {
7987
- const dir = path27.dirname(tomlPath);
7988
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
7989
- if (fs26.existsSync(tomlPath)) {
7990
- const existingContent = fs26.readFileSync(tomlPath, "utf-8");
7234
+ const dir = path23.dirname(tomlPath);
7235
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
7236
+ if (fs22.existsSync(tomlPath)) {
7237
+ const existingContent = fs22.readFileSync(tomlPath, "utf-8");
7991
7238
  if (existingContent.includes("kuma")) {
7992
7239
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
7993
7240
  return;
7994
7241
  }
7995
- fs26.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
7242
+ fs22.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
7996
7243
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
7997
7244
  } else {
7998
- fs26.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
7245
+ fs22.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
7999
7246
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
8000
7247
  }
8001
7248
  } catch (err) {
@@ -8008,11 +7255,11 @@ function handleCodexSecondary(root, results) {
8008
7255
  }
8009
7256
  }
8010
7257
  function handleQwenSecondary(root, results) {
8011
- const settingsPath = path27.resolve(root, "settings.json");
7258
+ const settingsPath = path23.resolve(root, "settings.json");
8012
7259
  if (results.some((r) => r.filePath === "settings.json")) return;
8013
7260
  try {
8014
- if (fs26.existsSync(settingsPath)) {
8015
- const existingContent = fs26.readFileSync(settingsPath, "utf-8");
7261
+ if (fs22.existsSync(settingsPath)) {
7262
+ const existingContent = fs22.readFileSync(settingsPath, "utf-8");
8016
7263
  if (existingContent.includes("kuma")) {
8017
7264
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
8018
7265
  try {
@@ -8020,7 +7267,7 @@ function handleQwenSecondary(root, results) {
8020
7267
  parsed.mcpServers = parsed.mcpServers || {};
8021
7268
  if (!parsed.mcpServers.kuma) {
8022
7269
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8023
- fs26.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
7270
+ fs22.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8024
7271
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
8025
7272
  return;
8026
7273
  }
@@ -8035,13 +7282,13 @@ function handleQwenSecondary(root, results) {
8035
7282
  parsed.mcpServers = parsed.mcpServers || {};
8036
7283
  if (!parsed.mcpServers.kuma) {
8037
7284
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8038
- fs26.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
7285
+ fs22.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8039
7286
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
8040
7287
  }
8041
7288
  } catch {
8042
7289
  }
8043
7290
  } else {
8044
- fs26.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
7291
+ fs22.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
8045
7292
  results.push({ type: "qwen", filePath: "settings.json", action: "created" });
8046
7293
  }
8047
7294
  } catch (err) {
@@ -8063,16 +7310,28 @@ function getCombinedAgentsMd(selectedTypes) {
8063
7310
  "",
8064
7311
  "## SESSION WORKFLOW",
8065
7312
  "",
8066
- "### 6 Core Actions",
7313
+ "<kuma_obedience>",
7314
+ "## \u{1F512} Obedience (non-negotiable)",
7315
+ '- MUST call `[kuma_]context({ action: "init" })` at session start.',
7316
+ "- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
7317
+ '- MUST call `[kuma_]safety({ action: "verify" })` after edits.',
7318
+ "- MUST NOT call actions outside the 13 core actions.",
7319
+ "</kuma_obedience>",
7320
+ "",
7321
+ "<kuma_workflow>",
7322
+ "### Core Actions (13)",
8067
7323
  "",
8068
7324
  '\u{1F9E0} **Before coding:** `[kuma_]context({ action: "init" })`',
8069
7325
  '\u{1F52C} **Unfamiliar code:** `[kuma_]context({ action: "research", scope: "<area>" })`',
7326
+ '\u{1F570}\uFE0F **Why this file?** `[kuma_]context({ action: "history", target: "<file>" })`',
7327
+ '\u{1F5C2}\uFE0F **Read a flow:** `[kuma_]context({ action: "flow", target: "<domain>" })`',
8070
7328
  '\u{1F41B} **Found a bug/quirk:** `[kuma_]memory({ action: "gotcha" })` (IMMEDIATELY)',
8071
7329
  '\u{1F9ED} **Chose between options:** `[kuma_]memory({ action: "decision" })`',
8072
7330
  '\u{1F500} **Traced a flow:** `[kuma_]memory({ action: "arch_flow" })` (max 5 files)',
7331
+ '\u{1F4BE} **Saved findings:** `[kuma_]memory({ action: "research_save" })` \xB7 \u{1F50D} **Quick lookup:** `[kuma_]memory({ action: "search" })`',
8073
7332
  '\u{1F6E1}\uFE0F **Before risky work / after edits:** `[kuma_]safety({ action: "guard" | "verify" })`',
8074
- "",
8075
- "\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` (delete node/gotcha/todo/decision) | `clear` (wipe entire graph)",
7333
+ '\u{1F4F8} **Rollback:** `[kuma_]safety({ action: "checkpoint", label: "pre-x" })` \u2192 `rollback_label` to restore',
7334
+ "</kuma_workflow>",
8076
7335
  "",
8077
7336
  "Replace `[kuma_]` with the correct prefix for your platform (see below).",
8078
7337
  "",
@@ -8093,7 +7352,7 @@ function getCombinedAgentsMd(selectedTypes) {
8093
7352
  '| Research | `kuma_kuma_context({ action: "research", scope: "..." })` |',
8094
7353
  '| Save | `kuma_kuma_memory({ action: "research_save", ... })` |',
8095
7354
  '| Verify | `kuma_kuma_safety({ action: "verify", ... })` |',
8096
- '| Changes | `kuma_kuma_context({ action: "changes" })` |',
7355
+ '| Checkpoint | `kuma_kuma_safety({ action: "checkpoint", label: "pre-x" })` |',
8097
7356
  "",
8098
7357
  "\u{1F4D6} Skill: `.agents/skills/kuma/SKILL.md`",
8099
7358
  "",
@@ -8117,7 +7376,7 @@ function getCombinedAgentsMd(selectedTypes) {
8117
7376
  '| Research | `kuma_context({ action: "research", scope: "..." })` |',
8118
7377
  '| Save | `kuma_memory({ action: "research_save", ... })` |',
8119
7378
  '| Verify | `kuma_safety({ action: "verify", ... })` |',
8120
- '| Changes | `kuma_context({ action: "changes" })` |',
7379
+ '| Checkpoint | `kuma_safety({ action: "checkpoint", label: "pre-x" })` |',
8121
7380
  "",
8122
7381
  "---",
8123
7382
  ""
@@ -8125,38 +7384,35 @@ function getCombinedAgentsMd(selectedTypes) {
8125
7384
  }
8126
7385
  lines.push(
8127
7386
  "\u{1F4D6} Rules: `.kuma/init.md`",
8128
- "\u{1F9E0} Memories: `.kuma/memories/*.md`",
7387
+ "\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)",
8129
7388
  "",
8130
7389
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
8131
7390
  );
8132
7391
  return lines.join("\n");
8133
7392
  }
8134
7393
  function handleAntigravityMcpConfig(root, results) {
8135
- const mcpPath = path27.resolve(root, ".agents/mcp_config.json");
7394
+ const mcpPath = path23.resolve(root, ".agents/mcp_config.json");
8136
7395
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
8137
7396
  try {
8138
- const mcpDir = path27.dirname(mcpPath);
8139
- if (fs26.existsSync(mcpPath)) {
8140
- const existingContent = fs26.readFileSync(mcpPath, "utf-8");
7397
+ const mcpDir = path23.dirname(mcpPath);
7398
+ if (fs22.existsSync(mcpPath)) {
7399
+ const existingContent = fs22.readFileSync(mcpPath, "utf-8");
8141
7400
  if (existingContent.includes("kuma")) {
8142
- if (!existingContent.includes("_Generated by Kuma MCP_")) {
8143
- const trimmed = existingContent.trimEnd();
8144
- if (trimmed.endsWith("}")) {
8145
- const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
8146
- fs26.writeFileSync(mcpPath, updated, "utf-8");
8147
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
8148
- }
8149
- }
7401
+ results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "skipped" });
8150
7402
  return;
8151
7403
  }
8152
- const parsed = JSON.parse(existingContent);
8153
- parsed.mcpServers = parsed.mcpServers || {};
8154
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8155
- fs26.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8156
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
7404
+ try {
7405
+ const parsed = JSON.parse(existingContent);
7406
+ parsed.mcpServers = parsed.mcpServers || {};
7407
+ parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
7408
+ fs22.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
7409
+ results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
7410
+ } catch {
7411
+ results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "error", error: "Invalid JSON in existing mcp_config.json" });
7412
+ }
8157
7413
  } else {
8158
- if (!fs26.existsSync(mcpDir)) fs26.mkdirSync(mcpDir, { recursive: true });
8159
- fs26.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
7414
+ if (!fs22.existsSync(mcpDir)) fs22.mkdirSync(mcpDir, { recursive: true });
7415
+ fs22.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8160
7416
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
8161
7417
  }
8162
7418
  } catch (err) {
@@ -8169,18 +7425,18 @@ function handleAntigravityMcpConfig(root, results) {
8169
7425
  }
8170
7426
  }
8171
7427
  function handleAiderSecondary(root, results) {
8172
- const ymlPath = path27.resolve(root, ".aider.conf.yml");
7428
+ const ymlPath = path23.resolve(root, ".aider.conf.yml");
8173
7429
  if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
8174
7430
  try {
8175
7431
  const conventionsRef = "read: CONVENTIONS.md";
8176
- if (fs26.existsSync(ymlPath)) {
8177
- const existingContent = fs26.readFileSync(ymlPath, "utf-8");
7432
+ if (fs22.existsSync(ymlPath)) {
7433
+ const existingContent = fs22.readFileSync(ymlPath, "utf-8");
8178
7434
  if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
8179
7435
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
8180
7436
  return;
8181
7437
  }
8182
7438
  const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
8183
- fs26.writeFileSync(ymlPath, newContent, "utf-8");
7439
+ fs22.writeFileSync(ymlPath, newContent, "utf-8");
8184
7440
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
8185
7441
  } else {
8186
7442
  const content = [
@@ -8190,7 +7446,7 @@ function handleAiderSecondary(root, results) {
8190
7446
  conventionsRef,
8191
7447
  ""
8192
7448
  ].join("\n");
8193
- fs26.writeFileSync(ymlPath, content, "utf-8");
7449
+ fs22.writeFileSync(ymlPath, content, "utf-8");
8194
7450
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
8195
7451
  }
8196
7452
  } catch (err) {
@@ -8203,10 +7459,10 @@ function handleAiderSecondary(root, results) {
8203
7459
  }
8204
7460
  }
8205
7461
  function handleCopilotSecondary(root, results) {
8206
- const skillPath = path27.resolve(root, ".github/skills/kuma/SKILL.md");
7462
+ const skillPath = path23.resolve(root, ".github/skills/kuma/SKILL.md");
8207
7463
  if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
8208
7464
  try {
8209
- const dir = path27.dirname(skillPath);
7465
+ const dir = path23.dirname(skillPath);
8210
7466
  const content = [
8211
7467
  "---",
8212
7468
  "name: kuma-mcp",
@@ -8217,17 +7473,17 @@ function handleCopilotSecondary(root, results) {
8217
7473
  "",
8218
7474
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules."
8219
7475
  ].join("\n");
8220
- if (fs26.existsSync(skillPath)) {
8221
- const existingContent = fs26.readFileSync(skillPath, "utf-8");
7476
+ if (fs22.existsSync(skillPath)) {
7477
+ const existingContent = fs22.readFileSync(skillPath, "utf-8");
8222
7478
  if (existingContent.includes("kuma")) {
8223
7479
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
8224
7480
  return;
8225
7481
  }
8226
- fs26.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
7482
+ fs22.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
8227
7483
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
8228
7484
  } else {
8229
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
8230
- fs26.writeFileSync(skillPath, content, "utf-8");
7485
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
7486
+ fs22.writeFileSync(skillPath, content, "utf-8");
8231
7487
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
8232
7488
  }
8233
7489
  } catch (err) {
@@ -8240,21 +7496,21 @@ function handleCopilotSecondary(root, results) {
8240
7496
  }
8241
7497
  }
8242
7498
  function handleOpenclawSecondary(root, results) {
8243
- const mcpPath = path27.resolve(root, ".agents/mcp_config.json");
7499
+ const mcpPath = path23.resolve(root, ".agents/mcp_config.json");
8244
7500
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
8245
7501
  try {
8246
- const dir = path27.dirname(mcpPath);
8247
- if (fs26.existsSync(mcpPath)) {
8248
- const existingContent = fs26.readFileSync(mcpPath, "utf-8");
7502
+ const dir = path23.dirname(mcpPath);
7503
+ if (fs22.existsSync(mcpPath)) {
7504
+ const existingContent = fs22.readFileSync(mcpPath, "utf-8");
8249
7505
  if (existingContent.includes("kuma")) return;
8250
7506
  const parsed = JSON.parse(existingContent);
8251
7507
  parsed.mcpServers = parsed.mcpServers || {};
8252
7508
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8253
- fs26.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
7509
+ fs22.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8254
7510
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
8255
7511
  } else {
8256
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
8257
- fs26.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
7512
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
7513
+ fs22.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8258
7514
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
8259
7515
  }
8260
7516
  } catch (err) {
@@ -8267,20 +7523,20 @@ function handleOpenclawSecondary(root, results) {
8267
7523
  }
8268
7524
  }
8269
7525
  function handleInitMdGeneration(root, results) {
8270
- const initMdPath = path27.resolve(root, ".kuma/init.md");
7526
+ const initMdPath = path23.resolve(root, ".kuma/init.md");
8271
7527
  try {
8272
- const kumaDir = path27.dirname(initMdPath);
8273
- if (!fs26.existsSync(kumaDir)) fs26.mkdirSync(kumaDir, { recursive: true });
8274
- if (fs26.existsSync(initMdPath)) {
8275
- const existing = fs26.readFileSync(initMdPath, "utf-8");
7528
+ const kumaDir = path23.dirname(initMdPath);
7529
+ if (!fs22.existsSync(kumaDir)) fs22.mkdirSync(kumaDir, { recursive: true });
7530
+ if (fs22.existsSync(initMdPath)) {
7531
+ const existing = fs22.readFileSync(initMdPath, "utf-8");
8276
7532
  if (existing.includes("_Generated by Kuma MCP_")) {
8277
7533
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
8278
7534
  return;
8279
7535
  }
8280
- fs26.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
7536
+ fs22.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
8281
7537
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
8282
7538
  } else {
8283
- fs26.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
7539
+ fs22.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
8284
7540
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
8285
7541
  }
8286
7542
  } catch (err) {
@@ -8293,20 +7549,20 @@ function handleInitMdGeneration(root, results) {
8293
7549
  }
8294
7550
  }
8295
7551
  function handleCodewhaleSecondary(root, results) {
8296
- const mcpPath = path27.resolve(root, ".codewhale/mcp.json");
7552
+ const mcpPath = path23.resolve(root, ".codewhale/mcp.json");
8297
7553
  if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
8298
7554
  try {
8299
- const dir = path27.dirname(mcpPath);
8300
- if (fs26.existsSync(mcpPath)) {
8301
- const existingContent = fs26.readFileSync(mcpPath, "utf-8");
7555
+ const dir = path23.dirname(mcpPath);
7556
+ if (fs22.existsSync(mcpPath)) {
7557
+ const existingContent = fs22.readFileSync(mcpPath, "utf-8");
8302
7558
  if (existingContent.includes("kuma")) return;
8303
7559
  const parsed = JSON.parse(existingContent);
8304
7560
  parsed.mcpServers = parsed.mcpServers || {};
8305
7561
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8306
- fs26.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
7562
+ fs22.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8307
7563
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
8308
7564
  } else {
8309
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
7565
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
8310
7566
  const config = {
8311
7567
  mcpServers: {
8312
7568
  kuma: {
@@ -8316,7 +7572,7 @@ function handleCodewhaleSecondary(root, results) {
8316
7572
  }
8317
7573
  }
8318
7574
  };
8319
- fs26.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
7575
+ fs22.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
8320
7576
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
8321
7577
  }
8322
7578
  } catch (err) {
@@ -8329,7 +7585,7 @@ function handleCodewhaleSecondary(root, results) {
8329
7585
  }
8330
7586
  }
8331
7587
  function handleClaudeSecondary(root, results) {
8332
- const settingsPath = path27.resolve(root, ".claude", "settings.json");
7588
+ const settingsPath = path23.resolve(root, ".claude", "settings.json");
8333
7589
  const hookBlock = {
8334
7590
  hooks: {
8335
7591
  PreToolUse: [
@@ -8345,8 +7601,8 @@ function handleClaudeSecondary(root, results) {
8345
7601
  }
8346
7602
  };
8347
7603
  try {
8348
- if (fs26.existsSync(settingsPath)) {
8349
- const existing = JSON.parse(fs26.readFileSync(settingsPath, "utf-8"));
7604
+ if (fs22.existsSync(settingsPath)) {
7605
+ const existing = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
8350
7606
  const merged = {
8351
7607
  ...existing,
8352
7608
  hooks: {
@@ -8361,12 +7617,12 @@ function handleClaudeSecondary(root, results) {
8361
7617
  matchers.add(entry.matcher);
8362
7618
  }
8363
7619
  }
8364
- fs26.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
7620
+ fs22.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8365
7621
  results.push({ type: "claude", filePath: ".claude/settings.json", action: "appended" });
8366
7622
  } else {
8367
- const dir = path27.dirname(settingsPath);
8368
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
8369
- fs26.writeFileSync(settingsPath, JSON.stringify(hookBlock, null, 2) + "\n", "utf-8");
7623
+ const dir = path23.dirname(settingsPath);
7624
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
7625
+ fs22.writeFileSync(settingsPath, JSON.stringify(hookBlock, null, 2) + "\n", "utf-8");
8370
7626
  results.push({ type: "claude", filePath: ".claude/settings.json", action: "created" });
8371
7627
  }
8372
7628
  } catch (err) {
@@ -8379,18 +7635,18 @@ function handleClaudeSecondary(root, results) {
8379
7635
  }
8380
7636
  }
8381
7637
  function handleCursorGotchaRules(root, results) {
8382
- const rulesDir2 = path27.resolve(root, ".cursor", "rules", "kuma-gotchas");
7638
+ const rulesDir2 = path23.resolve(root, ".cursor", "rules", "kuma-gotchas");
8383
7639
  try {
8384
7640
  let gotchas = [];
8385
7641
  try {
8386
7642
  gotchas = getActiveGotchas().filter((g) => g.severity === "high" || g.severity === "critical");
8387
7643
  } catch {
8388
7644
  }
8389
- if (fs26.existsSync(rulesDir2)) {
8390
- for (const f of fs26.readdirSync(rulesDir2)) {
7645
+ if (fs22.existsSync(rulesDir2)) {
7646
+ for (const f of fs22.readdirSync(rulesDir2)) {
8391
7647
  if (f.endsWith(".mdc")) {
8392
7648
  try {
8393
- fs26.rmSync(path27.join(rulesDir2, f), { force: true });
7649
+ fs22.rmSync(path23.join(rulesDir2, f), { force: true });
8394
7650
  } catch {
8395
7651
  }
8396
7652
  }
@@ -8400,7 +7656,7 @@ function handleCursorGotchaRules(root, results) {
8400
7656
  results.push({ type: "cursor", filePath: ".cursor/rules/kuma-gotchas/", action: "skipped" });
8401
7657
  return;
8402
7658
  }
8403
- fs26.mkdirSync(rulesDir2, { recursive: true });
7659
+ fs22.mkdirSync(rulesDir2, { recursive: true });
8404
7660
  const seen = /* @__PURE__ */ new Set();
8405
7661
  let created = 0;
8406
7662
  for (const g of gotchas) {
@@ -8421,7 +7677,7 @@ function handleCursorGotchaRules(root, results) {
8421
7677
  "`kuma_context({ action: 'history', target: '<file>' })`",
8422
7678
  ""
8423
7679
  ].join("\n");
8424
- fs26.writeFileSync(path27.join(rulesDir2, `${slug}.mdc`), md, "utf-8");
7680
+ fs22.writeFileSync(path23.join(rulesDir2, `${slug}.mdc`), md, "utf-8");
8425
7681
  created++;
8426
7682
  }
8427
7683
  results.push({
@@ -8448,28 +7704,28 @@ function runInit(options) {
8448
7704
  let agentsMdHandled = false;
8449
7705
  for (const type of selected) {
8450
7706
  const relativePath = configFilePath(type);
8451
- const fullPath = path27.resolve(root, relativePath);
7707
+ const fullPath = path23.resolve(root, relativePath);
8452
7708
  const getTemplate = TEMPLATES[type];
8453
7709
  try {
8454
7710
  if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
8455
7711
  agentsMdHandled = true;
8456
7712
  const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
8457
- if (fs26.existsSync(fullPath)) {
7713
+ if (fs22.existsSync(fullPath)) {
8458
7714
  if (options.skipExisting) {
8459
7715
  results.push({ type, filePath: relativePath, action: "skipped" });
8460
7716
  } else {
8461
- const existingContent = fs26.readFileSync(fullPath, "utf-8");
7717
+ const existingContent = fs22.readFileSync(fullPath, "utf-8");
8462
7718
  if (existingContent.includes("_Generated by Kuma MCP_")) {
8463
7719
  results.push({ type, filePath: relativePath, action: "skipped" });
8464
7720
  } else {
8465
- fs26.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
7721
+ fs22.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
8466
7722
  results.push({ type, filePath: relativePath, action: "appended" });
8467
7723
  }
8468
7724
  }
8469
7725
  } else {
8470
- const dir = path27.dirname(fullPath);
8471
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
8472
- fs26.writeFileSync(fullPath, combinedContent, "utf-8");
7726
+ const dir = path23.dirname(fullPath);
7727
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
7728
+ fs22.writeFileSync(fullPath, combinedContent, "utf-8");
8473
7729
  results.push({ type, filePath: relativePath, action: "created" });
8474
7730
  }
8475
7731
  if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
@@ -8481,12 +7737,12 @@ function runInit(options) {
8481
7737
  continue;
8482
7738
  } else {
8483
7739
  const template = getTemplate();
8484
- if (fs26.existsSync(fullPath)) {
7740
+ if (fs22.existsSync(fullPath)) {
8485
7741
  if (options.skipExisting) {
8486
7742
  results.push({ type, filePath: relativePath, action: "skipped" });
8487
7743
  continue;
8488
7744
  }
8489
- const existingContent = fs26.readFileSync(fullPath, "utf-8");
7745
+ const existingContent = fs22.readFileSync(fullPath, "utf-8");
8490
7746
  if (existingContent.includes("_Generated by Kuma MCP_")) {
8491
7747
  if (type === "antigravity") {
8492
7748
  handleAntigravityMcpConfig(root, results);
@@ -8499,14 +7755,14 @@ function runInit(options) {
8499
7755
  continue;
8500
7756
  }
8501
7757
  const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
8502
- fs26.writeFileSync(fullPath, newContent, "utf-8");
7758
+ fs22.writeFileSync(fullPath, newContent, "utf-8");
8503
7759
  results.push({ type, filePath: relativePath, action: "appended" });
8504
7760
  } else {
8505
- const dir = path27.dirname(fullPath);
8506
- if (!fs26.existsSync(dir)) {
8507
- fs26.mkdirSync(dir, { recursive: true });
7761
+ const dir = path23.dirname(fullPath);
7762
+ if (!fs22.existsSync(dir)) {
7763
+ fs22.mkdirSync(dir, { recursive: true });
8508
7764
  }
8509
- fs26.writeFileSync(fullPath, template, "utf-8");
7765
+ fs22.writeFileSync(fullPath, template, "utf-8");
8510
7766
  results.push({ type, filePath: relativePath, action: "created" });
8511
7767
  }
8512
7768
  if (type === "antigravity") {
@@ -8616,12 +7872,22 @@ var init_init = __esm({
8616
7872
  "Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
8617
7873
  "Record what matters, skip what doesn't.",
8618
7874
  "",
7875
+ "<kuma_obedience>",
7876
+ "## \u{1F512} Obedience (non-negotiable)",
7877
+ '- MUST call `kuma_context({ action: "init" })` at session start.',
7878
+ "- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
7879
+ '- MUST call `kuma_safety({ action: "verify" })` after edits.',
7880
+ "- MUST NOT call actions outside the 13 core actions.",
7881
+ "</kuma_obedience>",
7882
+ "",
7883
+ "<kuma_workflow>",
8619
7884
  '\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
8620
7885
  '\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
8621
7886
  '\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
8622
7887
  '\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
8623
7888
  '\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
8624
7889
  '\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
7890
+ "</kuma_workflow>",
8625
7891
  "",
8626
7892
  "\u{1FA84} **Auto-inject (Claude Code):** gotcha/decision/history injected automatically before edits",
8627
7893
  " via the PreToolUse hook \u2014 an empty hook output means the file has no gotchas.",
@@ -8657,19 +7923,19 @@ __export(agentDetector_exports, {
8657
7923
  getSkillPath: () => getSkillPath,
8658
7924
  skillExists: () => skillExists
8659
7925
  });
8660
- import fs27 from "fs";
8661
- import path28 from "path";
7926
+ import fs23 from "fs";
7927
+ import path24 from "path";
8662
7928
  function checkFile(root, filePath) {
8663
7929
  try {
8664
- return fs27.existsSync(path28.join(root, filePath));
7930
+ return fs23.existsSync(path24.join(root, filePath));
8665
7931
  } catch {
8666
7932
  return false;
8667
7933
  }
8668
7934
  }
8669
7935
  function checkDir(root, dirPath) {
8670
7936
  try {
8671
- const fullPath = path28.join(root, dirPath);
8672
- return fs27.existsSync(fullPath) && fs27.statSync(fullPath).isDirectory();
7937
+ const fullPath = path24.join(root, dirPath);
7938
+ return fs23.existsSync(fullPath) && fs23.statSync(fullPath).isDirectory();
8673
7939
  } catch {
8674
7940
  return false;
8675
7941
  }
@@ -8737,7 +8003,7 @@ function getAgentLabel(type) {
8737
8003
  function skillExists(type, projectRoot) {
8738
8004
  const root = projectRoot ?? getProjectRoot();
8739
8005
  const skillPath = getSkillPath(type);
8740
- return fs27.existsSync(path28.join(root, skillPath));
8006
+ return fs23.existsSync(path24.join(root, skillPath));
8741
8007
  }
8742
8008
  var AGENT_DETECTORS;
8743
8009
  var init_agentDetector = __esm({
@@ -8906,7 +8172,7 @@ function generateClaudeSkill() {
8906
8172
  ' \u2022 `kuma_safety({ action: "guard" })` \u2014 safety check before risky ops',
8907
8173
  ' \u2022 `kuma_safety({ action: "verify", scope: "<area>" })` \u2014 verify after edits',
8908
8174
  " \u2022 `.kuma/init.md` \u2014 full behavioral rules",
8909
- " \u2022 `.kuma/memories/` \u2014 persistent project knowledge"
8175
+ " \u2022 `.kuma/KNOWN_GOTCHAS.md` \u2014 gotchas \xB7 `.kuma/memories/decisions.md` \u2014 decisions (readable)"
8910
8176
  ].join("\n");
8911
8177
  }
8912
8178
  function generateCursorSkill() {
@@ -8923,7 +8189,7 @@ function generateCursorSkill() {
8923
8189
  ' \u2022 `kuma_safety({ action: "guard" })` \u2014 safety check before risky ops',
8924
8190
  ' \u2022 `kuma_safety({ action: "verify", scope: "<area>" })` \u2014 verify after edits',
8925
8191
  " \u2022 `.kuma/init.md` \u2014 full behavioral rules",
8926
- " \u2022 `.kuma/memories/` \u2014 persistent project knowledge"
8192
+ " \u2022 `.kuma/KNOWN_GOTCHAS.md` \u2014 gotchas \xB7 `.kuma/memories/decisions.md` \u2014 decisions (readable)"
8927
8193
  ].join("\n");
8928
8194
  }
8929
8195
  function generateClineSkill() {
@@ -9128,12 +8394,22 @@ var init_skillGenerator = __esm({
9128
8394
  "src/utils/skillGenerator.ts"() {
9129
8395
  "use strict";
9130
8396
  CORE_ACTIONS = [
8397
+ "<kuma_obedience>",
8398
+ "## \u{1F512} Obedience (non-negotiable)",
8399
+ '- MUST call `kuma_context({ action: "init" })` at session start.',
8400
+ "- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
8401
+ '- MUST call `kuma_safety({ action: "verify" })` after edits.',
8402
+ "- MUST NOT call actions outside the 13 core actions.",
8403
+ "</kuma_obedience>",
8404
+ "",
8405
+ "<kuma_workflow>",
9131
8406
  '\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
9132
8407
  '\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
9133
8408
  '\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
9134
8409
  '\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
9135
8410
  '\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
9136
- '\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`'
8411
+ '\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
8412
+ "</kuma_workflow>"
9137
8413
  ].join("\n");
9138
8414
  BOOTSTRAP = [
9139
8415
  "Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
@@ -9170,21 +8446,14 @@ init_kumaDb();
9170
8446
  init_kumaGraph();
9171
8447
  init_kumaMemory();
9172
8448
  init_pathValidator();
9173
- import fs15 from "fs";
9174
- import path16 from "path";
8449
+ import fs14 from "fs";
8450
+ import path15 from "path";
9175
8451
  import crypto3 from "crypto";
9176
8452
  var CONTEXT_ALIASES = {
9177
8453
  "research": "research",
9178
8454
  "search": "research",
9179
8455
  "explore": "research",
9180
8456
  "inspect": "research",
9181
- "impact": "impact",
9182
- "analyze": "impact",
9183
- "whatif": "impact",
9184
- "refactor": "impact",
9185
- "navigate": "navigate",
9186
- "trace": "navigate",
9187
- "flow-trace": "navigate",
9188
8457
  "flow": "flow",
9189
8458
  "domain-flow": "flow",
9190
8459
  "domain_flow": "flow",
@@ -9194,28 +8463,10 @@ var CONTEXT_ALIASES = {
9194
8463
  "load": "init",
9195
8464
  "brief": "init",
9196
8465
  "project": "init",
9197
- "resume": "resume",
9198
- "continue": "resume",
9199
- "restore": "resume",
9200
- "reload": "resume",
9201
- "changes": "changes",
9202
- "log": "changes",
9203
- "changelog": "changes",
9204
8466
  "history": "history",
9205
8467
  "why": "history",
9206
8468
  "touched": "history",
9207
- "provenance": "history",
9208
- "rollback": "rollback",
9209
- "undo": "rollback",
9210
- "revert": "rollback",
9211
- "digest": "digest",
9212
- "bootstrap": "digest",
9213
- "briefing": "digest",
9214
- "overview": "digest",
9215
- "drift": "drift",
9216
- "staleness": "drift",
9217
- "code-drift": "drift",
9218
- "freshness": "drift"
8469
+ "provenance": "history"
9219
8470
  };
9220
8471
  async function handleContext(params) {
9221
8472
  const rawAction = params.action || "init";
@@ -9226,87 +8477,13 @@ async function handleContext(params) {
9226
8477
  return handleInit(params);
9227
8478
  case "research":
9228
8479
  return handleResearch(params);
9229
- case "impact":
9230
- return handleImpact(params);
9231
- case "navigate":
9232
- return handleNavigate(params);
9233
- case "flow":
9234
- return handleFlow(params);
9235
8480
  case "history":
9236
8481
  return handleHistory(params);
9237
- case "changes":
9238
- return handleChanges(params);
9239
- case "rollback":
9240
- return handleRollback(params);
9241
- case "digest":
9242
- return handleDigest(params);
9243
- case "drift":
9244
- return handleDrift(params);
9245
- case "resume":
9246
- return handleResume(params);
8482
+ case "flow":
8483
+ return handleFlow(params);
9247
8484
  default:
9248
- return `Unknown action "${action}". Use: init, research, impact, navigate, flow, history, changes, rollback, digest, drift, resume`;
9249
- }
9250
- }
9251
- async function handleResume(_params) {
9252
- sessionMemory.recordToolCall("kuma_context_resume", {});
9253
- const lines = [
9254
- "\u{1F504} **Kuma \u2014 Session Resume**",
9255
- `\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`,
9256
- ""
9257
- ];
9258
- try {
9259
- const db = await getDb();
9260
- const res = db.exec("SELECT goal, tool_calls, edits, rollbacks, failures, safety_score, started_at FROM sessions ORDER BY started_at DESC LIMIT 1");
9261
- if (res[0]?.values.length) {
9262
- const row = res[0].values[0];
9263
- const goal = row[0] || "no goal";
9264
- const calls = row[1] || 0;
9265
- const edits = row[2] || 0;
9266
- const rollbacks = row[3] || 0;
9267
- const failures2 = row[4] || 0;
9268
- const safety = row[5] == null ? null : row[5];
9269
- const time = new Date(row[6] * 1e3).toLocaleString();
9270
- lines.push("**Last Session**");
9271
- lines.push(` \u{1F3AF} Goal: ${goal.substring(0, 80)}`);
9272
- lines.push(` \u{1F550} ${time}`);
9273
- lines.push(` \u{1F527} ${calls} calls \xB7 \u{1F4DD} ${edits} edits \xB7 \u21A9 ${rollbacks} rollbacks \xB7 \u274C ${failures2} failures${safety != null ? ` \xB7 \u{1F6E1} safety ${safety}` : ""}`);
9274
- lines.push("");
9275
- }
9276
- } catch {
8485
+ return `Unknown action "${action}". Use: init, research, history, flow`;
9277
8486
  }
9278
- const summary = sessionMemory.getSummary();
9279
- lines.push("**Current State**");
9280
- lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
9281
- const progress = sessionMemory.getGoalProgress();
9282
- if (progress) lines.push(` \u{1F4CA} Progress: ${progress.percentage}%${progress.milestone ? ` \u2014 ${progress.milestone}` : ""}`);
9283
- lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
9284
- lines.push(` \u2705 Completed steps: ${summary.completedSteps?.length || 0}`);
9285
- lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
9286
- lines.push(` \u23F1\uFE0F Session duration: ${summary.sessionDuration}`);
9287
- const failures = summary.unresolvedFailures;
9288
- if (failures?.length) lines.push(` \u26A0\uFE0F Unresolved failures: ${failures.length}`);
9289
- lines.push("");
9290
- try {
9291
- const changes = await getChanges({ limit: 5 });
9292
- if (changes && !changes.startsWith("No changes")) {
9293
- lines.push("**Recent Changes**");
9294
- lines.push(changes);
9295
- lines.push("");
9296
- }
9297
- } catch {
9298
- }
9299
- const history = sessionMemory.getToolCallHistory(5);
9300
- if (history.length) {
9301
- lines.push("**Recent Tool Calls**");
9302
- for (const h of history) {
9303
- const action = h.params?.action || "";
9304
- lines.push(` \u{1F6E0} ${h.toolName}${action ? ` (${action})` : ""}`);
9305
- }
9306
- lines.push("");
9307
- }
9308
- lines.push("\u{1F4A1} Continue: kuma_context({ action: 'research', scope: '<area>' }) to pick up where you left off.");
9309
- return lines.join("\n");
9310
8487
  }
9311
8488
  async function handleInit(_params) {
9312
8489
  sessionMemory.setGoal(_params.goal || "Exploring project");
@@ -9375,7 +8552,7 @@ async function handleInit(_params) {
9375
8552
  }
9376
8553
  } catch {
9377
8554
  }
9378
- lines.push("\u{1F4A1} Full view: kuma_context({ action: 'digest' }) \xB7 Research: kuma_context({ action: 'research', scope: '<area>' })");
8555
+ lines.push("\u{1F4A1} Deeper: kuma_context({ action: 'history', target: '<file>' }) \xB7 Flow: kuma_context({ action: 'flow', target: '<domain>' }) \xB7 Research: kuma_context({ action: 'research', scope: '<area>' })");
9379
8556
  return lines.join("\n");
9380
8557
  }
9381
8558
  function detectSessionFocus(goal) {
@@ -9504,32 +8681,6 @@ async function handleResearch(params) {
9504
8681
  lines.push("\u{1F4DD} Research saved to cache. Call kuma_memory({ action: 'research_save' }) to save custom notes.");
9505
8682
  return lines.join("\n");
9506
8683
  }
9507
- async function handleImpact(params) {
9508
- const target = params.target || params.scope;
9509
- if (!target) return "\u26A0\uFE0F target or scope parameter required.";
9510
- sessionMemory.recordToolCall("kuma_context_impact", { target });
9511
- const impact = await analyzeImpact(target);
9512
- return formatImpact(impact);
9513
- }
9514
- async function handleNavigate(params) {
9515
- const entryPoint = params.target || params.scope;
9516
- if (!entryPoint) return "\u26A0\uFE0F target or scope parameter required.";
9517
- sessionMemory.recordToolCall("kuma_context_navigate", { entryPoint });
9518
- const steps = await traceFlow(entryPoint);
9519
- return formatFlow(entryPoint, steps);
9520
- }
9521
- async function handleChanges(params) {
9522
- const filePath = params.target;
9523
- const since = params.since;
9524
- return await getChanges({ filePath, since });
9525
- }
9526
- async function handleFlow(params) {
9527
- const domain = params.target || params.scope;
9528
- sessionMemory.recordToolCall("kuma_context_flow", { domain });
9529
- if (!domain) return "\u2139\uFE0F **kuma_context({ action: 'flow' })** \u2014 requires a `target` (domain name).\n\n kuma_context({ action: 'flow', target: 'WhatsApp Omnichannel' })\n\nServes the domain flow from cache, re-deriving it from imports when stale (F13).";
9530
- const { getFreshDomainFlow: getFreshDomainFlow2 } = await Promise.resolve().then(() => (init_kumaFlowCache(), kumaFlowCache_exports));
9531
- return await getFreshDomainFlow2(domain);
9532
- }
9533
8684
  async function handleHistory(params) {
9534
8685
  const target = params.target || params.scope;
9535
8686
  sessionMemory.recordToolCall("kuma_context_history", { target });
@@ -9555,7 +8706,7 @@ async function handleHistory(params) {
9555
8706
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
9556
8707
  const db = await getDb2();
9557
8708
  const stmt = db.prepare(`SELECT description, last_verified_at FROM known_gotchas WHERE status = 'resolved' AND file_path LIKE ? ORDER BY last_verified_at DESC LIMIT 3`);
9558
- stmt.bind([`%${path16.basename(target)}%`]);
8709
+ stmt.bind([`%${path15.basename(target)}%`]);
9559
8710
  const resolved = [];
9560
8711
  while (stmt.step()) resolved.push(stmt.getAsObject());
9561
8712
  stmt.free();
@@ -9577,48 +8728,22 @@ async function handleHistory(params) {
9577
8728
  lines.push("\u{1F4A1} This content is injected automatically before edits via the Claude Code PreToolUse hook (`kuma hook pre-edit`).");
9578
8729
  return lines.join("\n");
9579
8730
  }
9580
- async function handleRollback(params) {
9581
- const target = params.target;
9582
- if (!target) return "\u26A0\uFE0F target parameter required (change ID, e.g. '5').";
9583
- const changeId = parseInt(target, 10);
9584
- if (isNaN(changeId)) return `\u26A0\uFE0F Invalid change ID: "${target}". Use a numeric ID from kuma_context({ action: 'changes' }).`;
9585
- sessionMemory.recordToolCall("kuma_context_rollback", { changeId });
9586
- return await rollbackChange(changeId);
9587
- }
9588
- async function handleDigest(_params) {
9589
- sessionMemory.recordToolCall("kuma_context_digest", {});
9590
- try {
9591
- const { generateContextDigest: generateContextDigest2 } = await Promise.resolve().then(() => (init_contextDigest(), contextDigest_exports));
9592
- return await generateContextDigest2();
9593
- } catch (err) {
9594
- try {
9595
- const { generateDigest: generateDigest2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
9596
- return generateDigest2();
9597
- } catch {
9598
- return `Error generating digest: ${err}`;
9599
- }
9600
- }
9601
- }
9602
- async function handleDrift(_params) {
9603
- sessionMemory.recordToolCall("kuma_context_drift", {});
9604
- try {
9605
- const { detectDrift: detectDrift2, formatDriftReport: formatDriftReport2, flagStaleRecords: flagStaleRecords2 } = await Promise.resolve().then(() => (init_kumaDriftDetector(), kumaDriftDetector_exports));
9606
- await flagStaleRecords2();
9607
- const records = await detectDrift2();
9608
- return formatDriftReport2(records);
9609
- } catch (err) {
9610
- return `Error detecting drift: ${err}`;
9611
- }
8731
+ async function handleFlow(params) {
8732
+ const domain = params.target || params.scope;
8733
+ sessionMemory.recordToolCall("kuma_context_flow", { domain });
8734
+ if (!domain) return "\u2139\uFE0F **kuma_context({ action: 'flow' })** \u2014 requires a `target` (domain name).\n\n kuma_context({ action: 'flow', target: 'WhatsApp Omnichannel' })\n\nServes the domain flow from cache, re-deriving it from imports when stale (F13).";
8735
+ const { getFreshDomainFlow: getFreshDomainFlow2 } = await Promise.resolve().then(() => (init_kumaFlowCache(), kumaFlowCache_exports));
8736
+ return await getFreshDomainFlow2(domain);
9612
8737
  }
9613
8738
  function computeProjectHash(scope) {
9614
8739
  try {
9615
8740
  const root = getProjectRoot();
9616
- const files = fs15.readdirSync(root).slice(0, 20);
8741
+ const files = fs14.readdirSync(root).slice(0, 20);
9617
8742
  const hash = crypto3.createHash("md5");
9618
8743
  hash.update(scope);
9619
8744
  for (const f of files) {
9620
8745
  try {
9621
- const stat = fs15.statSync(path16.join(root, f));
8746
+ const stat = fs14.statSync(path15.join(root, f));
9622
8747
  hash.update(`${f}:${stat.mtimeMs}`);
9623
8748
  } catch {
9624
8749
  }
@@ -9635,76 +8760,43 @@ init_kumaDb();
9635
8760
  init_kumaMemory();
9636
8761
  init_pathValidator();
9637
8762
  init_pathValidator();
9638
- import fs18 from "fs";
9639
- import path19 from "path";
8763
+ import fs16 from "fs";
8764
+ import path17 from "path";
9640
8765
  var MEMORY_ALIASES = {
9641
- "session": "session",
9642
- "summary": "session",
9643
- "status": "session",
9644
- "get": "session",
9645
8766
  "decision": "decision",
9646
8767
  "adr": "decision",
9647
8768
  "research_save": "research_save",
9648
8769
  "save": "research_save",
9649
8770
  "store": "research_save",
9650
- "mine": "mine",
9651
- "dig": "mine",
9652
8771
  "search": "search",
9653
8772
  "find": "search",
9654
8773
  "query": "search",
9655
8774
  "lookup": "search",
9656
- "changes": "changes",
9657
- "change-log": "changes",
9658
- "history": "changes",
9659
8775
  "arch_flow": "arch_flow",
9660
8776
  "arch-flow": "arch_flow",
9661
8777
  "architecture": "arch_flow",
9662
8778
  "gotcha": "gotcha",
9663
8779
  "gotchas": "gotcha",
9664
- "quirk": "gotcha",
9665
- "delete_node": "delete_node",
9666
- "delete-node": "delete_node",
9667
- "remove-node": "delete_node",
9668
- "clear": "clear",
9669
- "clear-graph": "clear",
9670
- "wipe": "clear",
9671
- "reset": "clear",
9672
- "goal_progress": "goal_progress",
9673
- "progress": "goal_progress"
8780
+ "quirk": "gotcha"
9674
8781
  };
9675
8782
  async function handleMemory(params) {
9676
- const rawAction = params.action || "session";
8783
+ const rawAction = params.action || "search";
9677
8784
  const key = rawAction.toLowerCase().replace(/[\s_-]+/g, "-");
9678
8785
  const action = MEMORY_ALIASES[key] || rawAction;
9679
8786
  sessionMemory.recordToolCall("kuma_memory", { action: rawAction });
9680
8787
  switch (action) {
9681
8788
  case "decision":
9682
8789
  return handleDecision(params);
9683
- case "mine":
9684
- return handleMine(params);
9685
8790
  case "research_save":
9686
8791
  return handleResearchSave(params);
9687
- case "session":
9688
- return handleSession(params);
9689
8792
  case "search":
9690
8793
  return handleSearch(params);
9691
- case "changes":
9692
- return handleChanges2(params);
9693
8794
  case "arch_flow":
9694
8795
  return handleArchFlow(params);
9695
8796
  case "gotcha":
9696
8797
  return handleGotchaAction(params);
9697
- case "goal_progress":
9698
- return handleGoalProgress(params);
9699
- case "delete_node":
9700
- return handleDeleteNode(params);
9701
- case "clear": {
9702
- const { clearGraph: clearGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
9703
- await clearGraph2();
9704
- return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges, and gotchas have been wiped from disk and memory.";
9705
- }
9706
8798
  default:
9707
- return `Unknown action "${action}".`;
8799
+ return `Unknown action "${action}". Use: gotcha, decision, arch_flow, research_save, search`;
9708
8800
  }
9709
8801
  }
9710
8802
  async function handleDecision(params) {
@@ -9752,50 +8844,14 @@ async function handleResearchSave(params) {
9752
8844
  } catch {
9753
8845
  }
9754
8846
  try {
9755
- const researchDir = path19.join(getProjectRoot(), ".kuma", "research");
9756
- if (!fs18.existsSync(researchDir)) fs18.mkdirSync(researchDir, { recursive: true });
9757
- fs18.writeFileSync(path19.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
8847
+ const researchDir = path17.join(getProjectRoot(), ".kuma", "research");
8848
+ if (!fs16.existsSync(researchDir)) fs16.mkdirSync(researchDir, { recursive: true });
8849
+ fs16.writeFileSync(path17.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
9758
8850
  } catch {
9759
8851
  }
9760
8852
  sessionMemory.recordMemoryAction("research_save");
9761
8853
  return `\u2705 Research "${scope}" saved.`;
9762
8854
  }
9763
- async function handleSession(params) {
9764
- const topic = params.topic;
9765
- const summary = sessionMemory.getSummary(topic);
9766
- if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
9767
- const modifiedFiles = summary.modifiedFiles || [];
9768
- const failures = summary.unresolvedFailures || [];
9769
- const recordingSummary = sessionMemory.getRecordingSummary();
9770
- const metricsSummary = sessionMemory.getMetricsSummary();
9771
- const lines = [
9772
- "\u{1F4CB} **Session Summary**\n\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\n",
9773
- `\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
9774
- `\u{1F550} Duration: ${summary.sessionDuration}`,
9775
- `\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
9776
- `,
9777
- `\u{1F9E0} **Recordings:** ${recordingSummary.total} total \u2014 ${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save`,
9778
- `\u{1F4CA} **Metrics:** ${metricsSummary.filesRead} files read, ${metricsSummary.filesEdited} files edited`,
9779
- `\u23F1\uFE0F **Time saved:** ~${metricsSummary.researchTimeSavedFormatted} (from research cache)`
9780
- ];
9781
- if (recordingSummary.missingRecordings.length > 0 && recordingSummary.total === 0) {
9782
- lines.push(`
9783
- \u26A0\uFE0F **No recordings yet!** Missing: ${recordingSummary.missingRecordings.join(", ")}`);
9784
- lines.push(`\u{1F4A1} Tip: Record findings after reading files. arch_flow + gotcha are exponential value.`);
9785
- }
9786
- if (modifiedFiles.length > 0) {
9787
- lines.push(`**Modified Files** (${modifiedFiles.length}):`);
9788
- for (const f of modifiedFiles.slice(0, 10)) {
9789
- lines.push(` ${f.status === "created" ? "\u2728" : f.status === "modified" ? "\u{1F4DD}" : "\u274C"} ${f.filePath}`);
9790
- }
9791
- if (modifiedFiles.length > 10) lines.push(` ... and ${modifiedFiles.length - 10} more`);
9792
- }
9793
- if (failures.length > 0) {
9794
- lines.push(`**Unresolved Issues** (${failures.length}):`);
9795
- for (const f of failures.slice(0, 5)) lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
9796
- }
9797
- return lines.join("\n");
9798
- }
9799
8855
  async function handleSearch(params) {
9800
8856
  const query = params.query || params.scope;
9801
8857
  if (!query) return "\u26A0\uFE0F query or scope parameter required.";
@@ -9855,20 +8911,6 @@ ${impactLines.join("\n")}`;
9855
8911
  }
9856
8912
  return lines.join("\n");
9857
8913
  }
9858
- async function handleChanges2(params) {
9859
- const sinceNum = typeof params.since === "number" ? params.since : typeof params.since === "string" ? parseInt(params.since, 10) || void 0 : void 0;
9860
- return await getChanges({ filePath: params.target, since: sinceNum });
9861
- }
9862
- async function handleMine(params) {
9863
- sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
9864
- const { mineHistoricalDecisions: mineHistoricalDecisions2 } = await Promise.resolve().then(() => (init_kumaMiner(), kumaMiner_exports));
9865
- return await mineHistoricalDecisions2({
9866
- scope: params.scope,
9867
- since: typeof params.since === "string" ? params.since : void 0,
9868
- confirm: params.confirm,
9869
- limit: params.limit
9870
- });
9871
- }
9872
8914
  async function handleArchFlow(params) {
9873
8915
  const { readLayer: readLayer2, writeLayer: writeLayer2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
9874
8916
  if (params.content) {
@@ -9924,659 +8966,33 @@ async function handleArchFlow(params) {
9924
8966
  return readLayer2("arch_flow");
9925
8967
  }
9926
8968
  async function handleGotchaAction(params) {
9927
- if (params.content && params.scope) {
9928
- if (!params.scope.trim()) return "\u274C **gotcha format error:** scope (file path) is required.";
9929
- if (!params.content.trim()) return "\u274C **gotcha format error:** content (description) is required.";
9930
- const normalizedScope = normalizeScope(params.scope) || params.scope;
8969
+ const content = (params.content ?? params.description ?? "").trim();
8970
+ const scope = (params.scope ?? "").trim();
8971
+ if (content && scope) {
8972
+ const normalizedScope = normalizeScope(scope) || scope;
9931
8973
  const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
9932
8974
  const result = await addGotcha2({
9933
8975
  filePath: normalizedScope,
9934
- description: params.content,
8976
+ description: content,
9935
8977
  severity: params.status || "medium",
9936
- workaround: params.description,
8978
+ // `description` doubles as workaround only when the canonical `content` field was used.
8979
+ workaround: params.content ? params.description : void 0,
9937
8980
  triggerCommand: params.trigger_command
9938
8981
  });
9939
8982
  sessionMemory.recordMemoryAction("gotcha");
9940
8983
  return result;
9941
8984
  }
8985
+ if (scope || params.title || params.status || params.trigger_command || params.description) {
8986
+ return '\u274C **Gotcha NOT saved** \u2014 missing `content` (bug description).\n\u2705 Use: kuma_memory({ action: "gotcha", scope: "<file_path>", content: "<what went wrong>", status: "high" })\n- `scope` \u2014 file path where the bug was found\n- `content` \u2014 bug description (required to save)\n- `status` \u2014 low | medium | high | critical';
8987
+ }
9942
8988
  const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
9943
8989
  await syncGotchasToDb2();
9944
8990
  return await listGotchas2({ filePath: params.scope, severity: params.status });
9945
8991
  }
9946
- async function handleDeleteNode(params) {
9947
- sessionMemory.recordToolCall("kuma_memory_delete", { scope: params.scope, target: params.target });
9948
- const { getDb: getDb2, saveDb: saveDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
9949
- const db = await getDb2();
9950
- if (params.scope && params.target) {
9951
- const id = parseInt(params.target, 10);
9952
- if (isNaN(id)) return `\u26A0\uFE0F target "${params.target}" is not a valid numeric ID.`;
9953
- switch (params.scope) {
9954
- case "gotcha":
9955
- case "gotchas": {
9956
- const gotchaRow = db.prepare("SELECT file_path, description FROM known_gotchas WHERE id = ?");
9957
- gotchaRow.bind([id]);
9958
- let gotchaFilePath = "", gotchaDesc = "";
9959
- if (gotchaRow.step()) {
9960
- const row = gotchaRow.getAsObject();
9961
- gotchaFilePath = row.file_path;
9962
- gotchaDesc = row.description;
9963
- }
9964
- gotchaRow.free();
9965
- db.run("DELETE FROM known_gotchas WHERE id = ?", [id]);
9966
- if (gotchaFilePath && gotchaDesc) {
9967
- const gotchaNodeId = `gotcha::${gotchaFilePath}::${gotchaDesc.substring(0, 30)}`;
9968
- db.run("DELETE FROM edges WHERE source_id = ? OR target_id = ?", [gotchaNodeId, gotchaNodeId]);
9969
- db.run("DELETE FROM nodes WHERE id = ?", [gotchaNodeId]);
9970
- }
9971
- saveDb2(db);
9972
- return `\u{1F5D1}\uFE0F **Gotcha #${id} deleted** (table + graph).`;
9973
- }
9974
- default:
9975
- return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha`;
9976
- }
9977
- }
9978
- if (params.target || params.scope) {
9979
- const { flushDb: flushDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
9980
- const targetStr = params.target || "";
9981
- const targetId = params.scope && !targetStr.includes("::") ? `${targetStr}::${params.scope}` : targetStr;
9982
- if (targetId && (targetId.includes("::") || targetId.includes(":") || targetId.includes("-") || isNaN(parseInt(targetId, 10)))) {
9983
- try {
9984
- if (targetId.startsWith("feature_domain::")) {
9985
- const domainName = targetId.replace("feature_domain::", "");
9986
- db.run("DELETE FROM nodes WHERE id LIKE ? OR id LIKE ? OR id LIKE ?", [`feature_domain::${domainName}`, `cross_service_link::${domainName}::%`, `gotcha::${domainName}::%`]);
9987
- db.run("DELETE FROM edges WHERE source_id LIKE ? OR target_id LIKE ?", [`%${domainName}%`, `%${domainName}%`]);
9988
- } else if (targetId.startsWith("gotcha::")) {
9989
- db.run("DELETE FROM edges WHERE source_id = ? OR target_id = ?", [targetId, targetId]);
9990
- db.run("DELETE FROM nodes WHERE id = ?", [targetId]);
9991
- const parts = targetId.split("::");
9992
- if (parts.length >= 3) {
9993
- db.run("DELETE FROM known_gotchas WHERE file_path = ? AND description LIKE ?", [parts[1], `${parts.slice(2).join("::")}%`]);
9994
- }
9995
- } else {
9996
- db.run("DELETE FROM edges WHERE source_id = ? OR target_id = ?", [targetId, targetId]);
9997
- db.run("DELETE FROM nodes WHERE id = ?", [targetId]);
9998
- }
9999
- flushDb2(db);
10000
- return `\u{1F5D1}\uFE0F **Node & relations deleted:** ${targetId}`;
10001
- } catch (err) {
10002
- return `\u274C Failed to delete node: ${err}`;
10003
- }
10004
- }
10005
- const id = parseInt(targetId, 10);
10006
- if (!isNaN(id)) {
10007
- db.run("DELETE FROM nodes WHERE rowid = ?", [id]);
10008
- flushDb2(db);
10009
- return `\u{1F5D1}\uFE0F **Node #${id} deleted.**`;
10010
- }
10011
- }
10012
- return "\u26A0\uFE0F Provide `target` (node ID) to delete.\n\nExamples:\n- `delete_node`, target: 'function::sendMessage'\n- `delete_node`, scope: 'gotcha', target: '42'";
10013
- }
10014
- async function handleGoalProgress(params) {
10015
- const pct = params.confidence ?? 0;
10016
- const ms = params.content || params.title;
10017
- sessionMemory.setGoalProgress(pct, ms);
10018
- const p = sessionMemory.getGoalProgress();
10019
- if (!p) return "Error";
10020
- const bar = "\u2588".repeat(Math.floor(p.percentage / 10)) + "\u2591".repeat(10 - Math.floor(p.percentage / 10));
10021
- return `\u{1F4CA} ${bar} ${p.percentage}%${p.milestone ? " \u2014 " + p.milestone : ""}`;
10022
- }
10023
8992
 
10024
8993
  // src/tools/kumaSafetyTool.ts
10025
8994
  init_sessionMemory();
10026
8995
 
10027
- // src/engine/kumaSafetyLayer.ts
10028
- init_kumaDb();
10029
- init_sessionMemory();
10030
-
10031
- // src/engine/safetyAudit.ts
10032
- init_kumaDb();
10033
- var SCHEMA_SQL = `
10034
- CREATE TABLE IF NOT EXISTS safety_audit (
10035
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10036
- timestamp INTEGER NOT NULL,
10037
- tool_name TEXT NOT NULL,
10038
- action TEXT NOT NULL,
10039
- file_path TEXT,
10040
- risk_level TEXT NOT NULL DEFAULT 'low',
10041
- policy_violations INTEGER DEFAULT 0,
10042
- allowed INTEGER NOT NULL DEFAULT 1,
10043
- duration_ms INTEGER DEFAULT 0,
10044
- metadata TEXT DEFAULT '{}'
10045
- );
10046
- CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON safety_audit(timestamp);
10047
- CREATE INDEX IF NOT EXISTS idx_audit_tool ON safety_audit(tool_name);
10048
- CREATE INDEX IF NOT EXISTS idx_audit_risk ON safety_audit(risk_level);
10049
- CREATE INDEX IF NOT EXISTS idx_audit_allowed ON safety_audit(allowed);
10050
- `;
10051
- var schemaEnsured = false;
10052
- async function ensureSchema() {
10053
- if (schemaEnsured) return;
10054
- try {
10055
- const db = await getDb();
10056
- db.exec(SCHEMA_SQL);
10057
- saveDb();
10058
- schemaEnsured = true;
10059
- } catch (err) {
10060
- console.error(`[SafetyAudit] Failed to ensure schema: ${err}`);
10061
- }
10062
- }
10063
- async function recordAudit(entry) {
10064
- try {
10065
- await ensureSchema();
10066
- const db = await getDb();
10067
- db.run(`
10068
- INSERT INTO safety_audit (timestamp, tool_name, action, file_path, risk_level, policy_violations, allowed, duration_ms, metadata)
10069
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
10070
- `, [
10071
- entry.timestamp,
10072
- entry.toolName,
10073
- entry.action,
10074
- entry.filePath || null,
10075
- entry.riskLevel,
10076
- entry.policyViolations,
10077
- entry.allowed ? 1 : 0,
10078
- entry.durationMs,
10079
- JSON.stringify(entry.metadata || {})
10080
- ]);
10081
- saveDb();
10082
- } catch (err) {
10083
- console.error(`[SafetyAudit] Failed to record audit: ${err}`);
10084
- }
10085
- }
10086
- async function queryAudit(params) {
10087
- try {
10088
- await ensureSchema();
10089
- const db = await getDb();
10090
- const { toolName, riskLevel, allowed, limit = 20, since } = params;
10091
- let sql = "SELECT * FROM safety_audit WHERE 1=1";
10092
- const bindParams = [];
10093
- if (toolName) {
10094
- sql += " AND tool_name = ?";
10095
- bindParams.push(toolName);
10096
- }
10097
- if (riskLevel) {
10098
- sql += " AND risk_level = ?";
10099
- bindParams.push(riskLevel);
10100
- }
10101
- if (allowed !== void 0) {
10102
- sql += " AND allowed = ?";
10103
- bindParams.push(allowed ? 1 : 0);
10104
- }
10105
- if (since) {
10106
- sql += " AND timestamp >= ?";
10107
- bindParams.push(since);
10108
- }
10109
- sql += " ORDER BY timestamp DESC LIMIT ?";
10110
- bindParams.push(limit);
10111
- const stmt = db.prepare(sql);
10112
- stmt.bind(bindParams);
10113
- const results = [];
10114
- while (stmt.step()) {
10115
- results.push(stmt.getAsObject());
10116
- }
10117
- stmt.free();
10118
- if (results.length === 0) {
10119
- return "\u{1F4CB} **Safety Audit** \u2014 No records found for the given filters.";
10120
- }
10121
- const lines = [
10122
- `\u{1F4CB} **Safety Audit** \u2014 ${results.length} record(s)`,
10123
- `\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`,
10124
- ""
10125
- ];
10126
- for (const r of results) {
10127
- const riskIcon = r.risk_level === "critical" ? "\u{1F534}" : r.risk_level === "high" ? "\u{1F7E0}" : r.risk_level === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
10128
- const allowIcon = r.allowed ? "\u2705" : "\u26D4";
10129
- const time = new Date(r.timestamp * 1e3).toLocaleTimeString();
10130
- lines.push(`${allowIcon} ${riskIcon} **${r.tool_name}** \u2014 ${r.action}`);
10131
- if (r.file_path) lines.push(` \u{1F4CD} ${r.file_path}`);
10132
- lines.push(` \u{1F550} ${time} | ${r.duration_ms}ms | ${r.policy_violations} policy violation(s)`);
10133
- lines.push("");
10134
- }
10135
- return lines.join("\n");
10136
- } catch (err) {
10137
- return `Error querying audit: ${err}`;
10138
- }
10139
- }
10140
- async function auditStats() {
10141
- try {
10142
- await ensureSchema();
10143
- const db = await getDb();
10144
- const total = db.exec("SELECT COUNT(*) as c FROM safety_audit")[0]?.values[0][0] ?? 0;
10145
- const blocked = db.exec("SELECT COUNT(*) as c FROM safety_audit WHERE allowed = 0")[0]?.values[0][0] ?? 0;
10146
- const critical = db.exec("SELECT COUNT(*) as c FROM safety_audit WHERE risk_level IN ('high','critical')")[0]?.values[0][0] ?? 0;
10147
- let topBlocked = [];
10148
- try {
10149
- 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");
10150
- while (stmt.step()) {
10151
- topBlocked.push(stmt.getAsObject());
10152
- }
10153
- stmt.free();
10154
- } catch {
10155
- }
10156
- const lines = [
10157
- `\u{1F4CA} **Safety Audit Stats**`,
10158
- `\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`,
10159
- "",
10160
- `\u{1F4DD} Total operations: ${total}`,
10161
- `\u26D4 Blocked: ${blocked} (${total > 0 ? (blocked / total * 100).toFixed(1) : 0}%)`,
10162
- `\u{1F534} High/Critical risk: ${critical}`,
10163
- ""
10164
- ];
10165
- if (topBlocked.length > 0) {
10166
- lines.push("**Most blocked tools:**");
10167
- for (const t of topBlocked) {
10168
- lines.push(` \u2022 ${t.tool_name}: ${t.cnt}x blocked`);
10169
- }
10170
- }
10171
- return lines.join("\n");
10172
- } catch (err) {
10173
- return `Error getting audit stats: ${err}`;
10174
- }
10175
- }
10176
-
10177
- // src/tools/kumaPolicy.ts
10178
- init_pathValidator();
10179
- import fs19 from "fs";
10180
- import path20 from "path";
10181
- var DEFAULT_POLICY = {
10182
- never_touch: [
10183
- ".env",
10184
- ".env.local",
10185
- ".env.production",
10186
- ".env.development",
10187
- "package-lock.json",
10188
- "yarn.lock",
10189
- "pnpm-lock.yaml",
10190
- "node_modules/**"
10191
- ],
10192
- block_commands: [
10193
- "rm -rf",
10194
- "rm -fr",
10195
- "git push --force",
10196
- "git push -f",
10197
- "npm publish",
10198
- "yarn publish",
10199
- "pnpm publish",
10200
- "curl | bash",
10201
- "curl | sh"
10202
- ]
10203
- };
10204
- function loadPolicy() {
10205
- const root = getProjectRoot();
10206
- const ymlPath = path20.join(root, ".kuma", "policy.yml");
10207
- const yamlPath = path20.join(root, ".kuma", "policy.yaml");
10208
- let policyContent = null;
10209
- let policyPath = null;
10210
- if (fs19.existsSync(ymlPath)) {
10211
- policyContent = fs19.readFileSync(ymlPath, "utf-8");
10212
- policyPath = ymlPath;
10213
- } else if (fs19.existsSync(yamlPath)) {
10214
- policyContent = fs19.readFileSync(yamlPath, "utf-8");
10215
- policyPath = yamlPath;
10216
- }
10217
- if (!policyContent) {
10218
- return { ...DEFAULT_POLICY };
10219
- }
10220
- try {
10221
- return parseSimpleYaml(policyContent);
10222
- } catch (err) {
10223
- console.error(`[Policy] Failed to parse ${policyPath}: ${err}. Using defaults.`);
10224
- return { ...DEFAULT_POLICY };
10225
- }
10226
- }
10227
- function parseSimpleYaml(content) {
10228
- const policy = {};
10229
- const lines = content.split("\n");
10230
- let currentKey = null;
10231
- let currentArray = [];
10232
- for (const rawLine of lines) {
10233
- const line = rawLine.trim();
10234
- if (!line || line.startsWith("#")) continue;
10235
- const keyMatch = line.match(/^(\w[\w_-]*):\s*(.*)/);
10236
- if (keyMatch) {
10237
- if (currentKey && currentArray.length > 0) {
10238
- setPolicyValue(policy, currentKey, currentArray);
10239
- currentArray = [];
10240
- }
10241
- currentKey = keyMatch[1];
10242
- const value = keyMatch[2].trim();
10243
- if (value === "" || value === "|") {
10244
- currentArray = [];
10245
- } else {
10246
- if (value.startsWith("[") && value.endsWith("]")) {
10247
- const items = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
10248
- setPolicyValue(policy, currentKey, items);
10249
- } else {
10250
- setPolicyValue(policy, currentKey, value.replace(/^['"]|['"]$/g, ""));
10251
- }
10252
- currentKey = null;
10253
- }
10254
- } else if (currentKey && line.startsWith("- ")) {
10255
- currentArray.push(line.slice(2).trim().replace(/^['"]|['"]$/g, ""));
10256
- }
10257
- }
10258
- if (currentKey && currentArray.length > 0) {
10259
- setPolicyValue(policy, currentKey, currentArray);
10260
- }
10261
- return policy;
10262
- }
10263
- function setPolicyValue(policy, key, value) {
10264
- switch (key) {
10265
- case "never_touch":
10266
- policy.never_touch = value;
10267
- break;
10268
- case "require_review":
10269
- policy.require_review = value;
10270
- break;
10271
- case "require_tests":
10272
- policy.require_tests = value;
10273
- break;
10274
- case "max_file_size":
10275
- policy.max_file_size = Number(value);
10276
- break;
10277
- case "block_commands":
10278
- policy.block_commands = value;
10279
- break;
10280
- }
10281
- }
10282
- function matchesGlob(pattern, filePath) {
10283
- const normalizedPattern = pattern.replace(/\\/g, "/");
10284
- const normalizedPath = filePath.replace(/\\/g, "/");
10285
- let regexStr = "^";
10286
- for (let i = 0; i < normalizedPattern.length; i++) {
10287
- const ch = normalizedPattern[i];
10288
- if (ch === "*" && normalizedPattern[i + 1] === "*" && normalizedPattern[i + 2] === "/") {
10289
- regexStr += "(.+/)?";
10290
- i += 2;
10291
- } else if (ch === "*") {
10292
- regexStr += "[^/]*";
10293
- } else if (ch === "?") {
10294
- regexStr += "[^/]";
10295
- } else {
10296
- regexStr += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
10297
- }
10298
- }
10299
- regexStr += "$";
10300
- try {
10301
- return new RegExp(regexStr).test(normalizedPath);
10302
- } catch {
10303
- return false;
10304
- }
10305
- }
10306
- function checkFilePathPolicy(filePath, policy) {
10307
- const violations = [];
10308
- const warnings = [];
10309
- if (policy.never_touch) {
10310
- for (const pattern of policy.never_touch) {
10311
- if (matchesGlob(pattern, filePath)) {
10312
- violations.push({
10313
- rule: "never_touch",
10314
- pattern,
10315
- message: `File "${filePath}" matches never_touch pattern "${pattern}"`,
10316
- severity: "error"
10317
- });
10318
- }
10319
- }
10320
- }
10321
- if (policy.max_file_size && policy.max_file_size > 0) {
10322
- try {
10323
- const root = getProjectRoot();
10324
- const fullPath = path20.join(root, filePath);
10325
- if (fs19.existsSync(fullPath)) {
10326
- const sizeKB = fs19.statSync(fullPath).size / 1024;
10327
- if (sizeKB > policy.max_file_size) {
10328
- violations.push({
10329
- rule: "max_file_size",
10330
- pattern: `${policy.max_file_size}KB`,
10331
- message: `File "${filePath}" is ${Math.round(sizeKB)}KB, exceeds max_file_size of ${policy.max_file_size}KB`,
10332
- severity: "error"
10333
- });
10334
- }
10335
- }
10336
- } catch {
10337
- }
10338
- }
10339
- if (policy.require_review) {
10340
- for (const pattern of policy.require_review) {
10341
- if (matchesGlob(pattern, filePath)) {
10342
- warnings.push({
10343
- rule: "require_review",
10344
- message: `File "${filePath}" requires review (matches "${pattern}")`
10345
- });
10346
- }
10347
- }
10348
- }
10349
- return { violations, warnings };
10350
- }
10351
-
10352
- // src/engine/kumaSafetyProxy.ts
10353
- init_pathValidator();
10354
- init_sessionMemory();
10355
- init_kumaDb();
10356
- async function preCheck(_toolName, params, opts = {}) {
10357
- const messages = [];
10358
- let policyViolations = 0;
10359
- let highestRisk = "low";
10360
- const blockThreshold = opts.blockRiskThreshold || "high";
10361
- const filePath = opts.extractFilePath?.(params);
10362
- if (filePath) {
10363
- const validation = validateFilePath(filePath);
10364
- if (!validation.valid) {
10365
- messages.push(`\u{1F6AB} Path blocked: ${validation.error.message}`);
10366
- policyViolations++;
10367
- highestRisk = "critical";
10368
- }
10369
- }
10370
- const policy = loadPolicy();
10371
- if (filePath) {
10372
- const { violations, warnings } = checkFilePathPolicy(filePath, policy);
10373
- policyViolations += violations.length;
10374
- for (const v of violations) {
10375
- messages.push(`\u{1F4DC} Policy violation (${v.rule}): ${v.message}`);
10376
- highestRisk = "critical";
10377
- }
10378
- for (const w of warnings) {
10379
- messages.push(`\u26A0\uFE0F Policy warning (${w.rule}): ${w.message}`);
10380
- if (highestRisk === "low") highestRisk = "medium";
10381
- }
10382
- }
10383
- const command = opts.extractCommand?.(params);
10384
- if (command) {
10385
- const dangerousPatterns = [
10386
- "rm -rf",
10387
- "rm -fr",
10388
- "git push",
10389
- "git commit",
10390
- "npm publish",
10391
- "yarn publish",
10392
- "pnpm publish",
10393
- "| bash",
10394
- "| sh",
10395
- "eval ",
10396
- "exec ",
10397
- "mkfs",
10398
- "dd if=",
10399
- "shred"
10400
- ];
10401
- const matched = dangerousPatterns.find((p) => command.toLowerCase().includes(p));
10402
- if (matched) {
10403
- messages.push(`\u{1F6AB} Command blocked: contains dangerous pattern "${matched}"`);
10404
- policyViolations++;
10405
- highestRisk = "critical";
10406
- }
10407
- }
10408
- try {
10409
- const db = await getDb();
10410
- const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
10411
- const action = params.action || "";
10412
- if (researchCount === 0 && (action === "edit" || action === "impact" || action === "navigate")) {
10413
- messages.push("\u{1F4A1} No research cached \u2014 run kuma_context({ action: 'research', scope: '...' }) first");
10414
- if (highestRisk === "low") highestRisk = "medium";
10415
- }
10416
- } catch {
10417
- }
10418
- try {
10419
- const summary = sessionMemory.getSummary();
10420
- const goal = summary.currentGoal;
10421
- if (!goal) {
10422
- messages.push("\u{1F4A1} No goal set \u2014 kuma_context({ action: 'init' }) sets one automatically");
10423
- }
10424
- } catch {
10425
- }
10426
- const riskLevel = highestRisk;
10427
- const thresholdLevels = ["low", "medium", "high", "critical"];
10428
- const thresholdIndex = thresholdLevels.indexOf(blockThreshold);
10429
- const riskIndex = thresholdLevels.indexOf(riskLevel);
10430
- const allowed = opts.blockOnViolation !== false ? riskIndex < thresholdIndex : policyViolations === 0;
10431
- return { allowed, riskLevel, policyViolations, messages };
10432
- }
10433
-
10434
- // src/engine/kumaSafetyLayer.ts
10435
- async function safetyCheck(action, filePath, command) {
10436
- try {
10437
- const checks = [];
10438
- const db = await getDb();
10439
- let changes = 0;
10440
- try {
10441
- const summary = sessionMemory.getSummary();
10442
- const modified = summary.modifiedFiles?.length || 0;
10443
- changes = modified;
10444
- checks.push({
10445
- name: "Uncommitted Changes",
10446
- passed: changes < 10,
10447
- detail: changes > 0 ? `\u{1F4DD} ${changes} uncommitted change(s)` : "\u2705 Clean working tree"
10448
- });
10449
- } catch {
10450
- checks.push({ name: "Uncommitted Changes", passed: true, detail: "\u2705 Could not check" });
10451
- }
10452
- if (action && filePath) {
10453
- const verdict = await preCheck(action, { filePath, action }, {
10454
- extractFilePath: (p) => p.filePath
10455
- });
10456
- checks.push({
10457
- name: "Policy & Path Safety",
10458
- passed: verdict.allowed,
10459
- detail: verdict.allowed ? `\u2705 ${verdict.policyViolations} violations` : `\u274C ${verdict.policyViolations} violation(s) \u2014 ${verdict.messages[0] || "blocked"}`
10460
- });
10461
- }
10462
- let recentChanges = 0;
10463
- try {
10464
- const changeResult = db.exec("SELECT COUNT(*) as c FROM change_log WHERE created_at > strftime('%s','now','-1 hour')");
10465
- recentChanges = changeResult[0]?.values[0][0] || 0;
10466
- } catch {
10467
- }
10468
- checks.push({
10469
- name: "Recent Changes",
10470
- passed: recentChanges < 20,
10471
- detail: recentChanges > 0 ? `\u{1F4DD} ${recentChanges} change(s) in last hour` : "\u2705 No recent changes"
10472
- });
10473
- try {
10474
- const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] || 0;
10475
- checks.push({
10476
- name: "Knowledge Graph Health",
10477
- passed: nodeCount > 0,
10478
- detail: nodeCount > 0 ? `\u2705 ${nodeCount} nodes in graph` : "\u26A0\uFE0F Empty knowledge graph"
10479
- });
10480
- } catch {
10481
- checks.push({ name: "Knowledge Graph Health", passed: true, detail: "\u26A0\uFE0F Could not check" });
10482
- }
10483
- if (filePath) {
10484
- try {
10485
- const fs28 = await import("fs");
10486
- const path29 = await import("path");
10487
- const root = process.cwd();
10488
- const fullPath = path29.resolve(root, filePath);
10489
- if (fs28.existsSync(fullPath)) {
10490
- const stat = fs28.statSync(fullPath);
10491
- const sizeKB = Math.round(stat.size / 1024);
10492
- if (sizeKB > 500) {
10493
- checks.push({
10494
- name: "File Size Warning",
10495
- passed: false,
10496
- detail: `\u{1F4CF} File is ${sizeKB}KB \u2014 large file edits are risky`
10497
- });
10498
- }
10499
- if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
10500
- const baseName = path29.basename(filePath, path29.extname(filePath));
10501
- const testPatterns = [
10502
- filePath.replace(/\.(ts|js)$/, ".test.$1"),
10503
- filePath.replace(/\.(ts|js)$/, ".spec.$1"),
10504
- filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
10505
- `**/__tests__/**/${baseName}.test.${path29.extname(filePath).slice(1)}`
10506
- ];
10507
- let hasTests = false;
10508
- const fg = await import("fast-glob");
10509
- for (const pattern of testPatterns) {
10510
- const matches = await fg.default(pattern, { cwd: root, ignore: ["**/node_modules/**"] });
10511
- if (matches.length > 0) {
10512
- hasTests = true;
10513
- break;
10514
- }
10515
- }
10516
- if (!hasTests) {
10517
- checks.push({
10518
- name: "Tests Check",
10519
- passed: true,
10520
- // Not a blocker, just informative
10521
- detail: `\u{1F9EA} No test file found for "${path29.basename(filePath)}" \u2014 consider adding tests`
10522
- });
10523
- } else {
10524
- checks.push({
10525
- name: "Tests Check",
10526
- passed: true,
10527
- detail: "\u2705 Test file found"
10528
- });
10529
- }
10530
- }
10531
- }
10532
- } catch {
10533
- }
10534
- }
10535
- const failed = checks.filter((c) => !c.passed);
10536
- const riskLevel = failed.length >= 3 ? "critical" : failed.length >= 2 ? "high" : failed.length >= 1 ? "medium" : "low";
10537
- const allowed = failed.length === 0;
10538
- const recommendation = allowed ? "\u{1F7E2} All checks passed \u2014 proceed safely." : riskLevel === "critical" ? "\u{1F534} Critical \u2014 Do not proceed without resolving all issues." : riskLevel === "high" ? "\u{1F7E0} High risk \u2014 Resolve issues before proceeding." : "\u{1F7E1} Medium risk \u2014 Proceed with caution.";
10539
- const result = { action, filePath, allowed, checks, riskLevel, recommendation };
10540
- await recordAudit({
10541
- timestamp: Math.floor(Date.now() / 1e3),
10542
- toolName: "kuma_safety_check",
10543
- action,
10544
- filePath,
10545
- riskLevel,
10546
- policyViolations: failed.length,
10547
- allowed,
10548
- durationMs: 0,
10549
- metadata: { checkType: "full" }
10550
- });
10551
- return formatSafetyCheck(result, command);
10552
- } catch (err) {
10553
- return `Error in safety check: ${err}`;
10554
- }
10555
- }
10556
- function formatSafetyCheck(r, command) {
10557
- const emoji = r.riskLevel === "critical" ? "\u{1F534}" : r.riskLevel === "high" ? "\u{1F7E0}" : r.riskLevel === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
10558
- const lines = [
10559
- `${emoji} **Safety AI Layer** \u2014 ${r.allowed ? "\u2705 Allowed" : "\u26D4 Blocked"}`,
10560
- `\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`,
10561
- "",
10562
- `\u{1F3AF} Action: \`${r.action}\``,
10563
- r.filePath ? `\u{1F4C4} File: \`${r.filePath}\`` : "",
10564
- command ? `\u{1F4BB} Command: \`${command}\`` : "",
10565
- `\u26A0\uFE0F Risk Level: **${r.riskLevel.toUpperCase()}**`,
10566
- "",
10567
- r.recommendation,
10568
- "",
10569
- "**Checks:**"
10570
- ].filter(Boolean);
10571
- for (const c of r.checks) {
10572
- lines.push(` ${c.passed ? "\u2705" : "\u274C"} **${c.name}**: ${c.detail}`);
10573
- }
10574
- return lines.join("\n");
10575
- }
10576
-
10577
- // src/tools/kumaSafetyTool.ts
10578
- init_kumaDb();
10579
-
10580
8996
  // src/tools/kumaGuard.ts
10581
8997
  init_sessionMemory();
10582
8998
 
@@ -10584,9 +9000,9 @@ init_sessionMemory();
10584
9000
  init_pathValidator();
10585
9001
  init_sessionMemory();
10586
9002
  init_kumaShared();
10587
- import fs20 from "fs";
10588
- import path21 from "path";
10589
- import { execSync as execSync5 } from "child_process";
9003
+ import fs17 from "fs";
9004
+ import path18 from "path";
9005
+ import { execSync as execSync4 } from "child_process";
10590
9006
  var SCRIPT_PATCH_PATTERNS = [
10591
9007
  "writeFileSync",
10592
9008
  "writeFile",
@@ -10612,20 +9028,20 @@ function findPatchScripts(projectRoot) {
10612
9028
  const warnings = [];
10613
9029
  const recentFiles = scanRecentFiles(projectRoot);
10614
9030
  for (const file of recentFiles) {
10615
- const ext = path21.extname(file).toLowerCase();
9031
+ const ext = path18.extname(file).toLowerCase();
10616
9032
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
10617
- const relativePath = path21.relative(projectRoot, file);
9033
+ const relativePath = path18.relative(projectRoot, file);
10618
9034
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
10619
9035
  continue;
10620
9036
  }
10621
9037
  try {
10622
- const content = fs20.readFileSync(file, "utf-8").toLowerCase();
9038
+ const content = fs17.readFileSync(file, "utf-8").toLowerCase();
10623
9039
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
10624
9040
  if (matchedPattern) {
10625
9041
  warnings.push({
10626
9042
  severity: "high",
10627
9043
  pattern: "script-patching",
10628
- message: `Created script file that modifies other files: ${path21.basename(file)}`,
9044
+ message: `Created script file that modifies other files: ${path18.basename(file)}`,
10629
9045
  suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
10630
9046
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
10631
9047
  filePath: relativePath
@@ -10659,15 +9075,15 @@ function detectBashGrepUsage() {
10659
9075
  function scanRecentFiles(projectRoot) {
10660
9076
  const recent = [];
10661
9077
  try {
10662
- const entries = fs20.readdirSync(projectRoot, { withFileTypes: true });
9078
+ const entries = fs17.readdirSync(projectRoot, { withFileTypes: true });
10663
9079
  const now = Date.now();
10664
9080
  const recentThreshold = 30 * 60 * 1e3;
10665
9081
  for (const entry of entries) {
10666
9082
  if (!entry.isFile()) continue;
10667
9083
  try {
10668
- const stat = fs20.statSync(path21.join(projectRoot, entry.name));
9084
+ const stat = fs17.statSync(path18.join(projectRoot, entry.name));
10669
9085
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
10670
- recent.push(path21.join(projectRoot, entry.name));
9086
+ recent.push(path18.join(projectRoot, entry.name));
10671
9087
  }
10672
9088
  } catch {
10673
9089
  continue;
@@ -10680,7 +9096,7 @@ function scanRecentFiles(projectRoot) {
10680
9096
  function detectGitPatchScripts(projectRoot) {
10681
9097
  const warnings = [];
10682
9098
  try {
10683
- const statusStdout = execSync5("git status --porcelain", {
9099
+ const statusStdout = execSync4("git status --porcelain", {
10684
9100
  cwd: projectRoot,
10685
9101
  encoding: "utf-8",
10686
9102
  timeout: 3e3
@@ -10691,14 +9107,14 @@ function detectGitPatchScripts(projectRoot) {
10691
9107
  const prefix = line.substring(0, 2);
10692
9108
  if (prefix !== "??" && prefix !== "A ") continue;
10693
9109
  const file = line.substring(3).trim();
10694
- const ext = path21.extname(file).toLowerCase();
9110
+ const ext = path18.extname(file).toLowerCase();
10695
9111
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
10696
9112
  const isRootLevel = !file.includes("/");
10697
9113
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
10698
9114
  if (!isRootLevel && !isScriptsDir) continue;
10699
- const fullPath = path21.join(projectRoot, file);
10700
- if (fs20.existsSync(fullPath)) {
10701
- const content = fs20.readFileSync(fullPath, "utf-8").toLowerCase();
9115
+ const fullPath = path18.join(projectRoot, file);
9116
+ if (fs17.existsSync(fullPath)) {
9117
+ const content = fs17.readFileSync(fullPath, "utf-8").toLowerCase();
10702
9118
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
10703
9119
  if (matchedPattern) {
10704
9120
  warnings.push({
@@ -10713,7 +9129,7 @@ function detectGitPatchScripts(projectRoot) {
10713
9129
  }
10714
9130
  }
10715
9131
  }
10716
- const deletedStdout = execSync5("git diff --name-only --diff-filter=D HEAD", {
9132
+ const deletedStdout = execSync4("git diff --name-only --diff-filter=D HEAD", {
10717
9133
  cwd: projectRoot,
10718
9134
  encoding: "utf-8",
10719
9135
  timeout: 3e3
@@ -10721,7 +9137,7 @@ function detectGitPatchScripts(projectRoot) {
10721
9137
  if (deletedStdout) {
10722
9138
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
10723
9139
  for (const file of deletedFiles) {
10724
- const ext = path21.extname(file).toLowerCase();
9140
+ const ext = path18.extname(file).toLowerCase();
10725
9141
  if (SCRIPT_EXTENSIONS.includes(ext)) {
10726
9142
  warnings.push({
10727
9143
  severity: "high",
@@ -10783,21 +9199,21 @@ function detectAllAntiPatterns() {
10783
9199
  // src/engine/contextSnapshot.ts
10784
9200
  init_sessionMemory();
10785
9201
  init_pathValidator();
10786
- import fs21 from "fs";
10787
- import path22 from "path";
10788
- import { execSync as execSync6 } from "child_process";
9202
+ import fs18 from "fs";
9203
+ import path19 from "path";
9204
+ import { execSync as execSync5 } from "child_process";
10789
9205
  var SNAPSHOT_DIR = "context-snapshots";
10790
9206
  function snapshotDir() {
10791
- return path22.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
9207
+ return path19.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
10792
9208
  }
10793
9209
  function ensureSnapshotDir() {
10794
9210
  const dir = snapshotDir();
10795
- if (!fs21.existsSync(dir)) {
10796
- fs21.mkdirSync(dir, { recursive: true });
9211
+ if (!fs18.existsSync(dir)) {
9212
+ fs18.mkdirSync(dir, { recursive: true });
10797
9213
  }
10798
9214
  }
10799
9215
  function snapshotFilePath(timestamp) {
10800
- return path22.join(snapshotDir(), `${timestamp}.json`);
9216
+ return path19.join(snapshotDir(), `${timestamp}.json`);
10801
9217
  }
10802
9218
  function saveSnapshot(goal) {
10803
9219
  try {
@@ -10818,7 +9234,7 @@ function saveSnapshot(goal) {
10818
9234
  hasConventions: !!summary.hasConventions
10819
9235
  };
10820
9236
  try {
10821
- fs21.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
9237
+ fs18.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
10822
9238
  } catch {
10823
9239
  }
10824
9240
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -10857,7 +9273,7 @@ function formatSnapshot(snapshot) {
10857
9273
  function getGitDiffStat2() {
10858
9274
  try {
10859
9275
  const root = getProjectRoot();
10860
- const stdout = execSync6("git diff --stat", {
9276
+ const stdout = execSync5("git diff --stat", {
10861
9277
  cwd: root,
10862
9278
  encoding: "utf-8",
10863
9279
  timeout: 3e3
@@ -11033,27 +9449,12 @@ async function handleSafety(params) {
11033
9449
  return handleGuard(params);
11034
9450
  case "verify":
11035
9451
  return handleVerify(params);
11036
- case "check":
11037
- return handleCheck(params);
11038
- case "audit":
11039
- return handleAudit(params);
11040
- case "security":
11041
- return handleSecurity(params);
11042
- case "gc":
11043
- return handleGc(params);
11044
- case "ast":
11045
- case "validate":
11046
- return handleAstValidation(params);
11047
9452
  case "checkpoint":
11048
9453
  return handleCheckpoint(params);
11049
9454
  case "rollback_label":
11050
9455
  return handleRollbackLabel(params);
11051
- case "checkpoint_list":
11052
- return handleCheckpointList(params);
11053
- case "gotcha_staleness":
11054
- return handleGotchaStaleness(params);
11055
9456
  default:
11056
- return `Unknown action "${action}".`;
9457
+ return `Unknown action "${action}". Use: guard, verify, checkpoint, rollback_label`;
11057
9458
  }
11058
9459
  }
11059
9460
  async function handleGuard(params) {
@@ -11062,73 +9463,6 @@ async function handleGuard(params) {
11062
9463
  check: params.guardCheck || "all"
11063
9464
  });
11064
9465
  }
11065
- async function handleCheck(params) {
11066
- return await safetyCheck("check", params.filePath, params.command);
11067
- }
11068
- async function handleAudit(params) {
11069
- if (params.limit === 0) return await auditStats();
11070
- return await queryAudit({
11071
- toolName: params.toolName,
11072
- riskLevel: params.riskLevel,
11073
- allowed: params.allowed,
11074
- limit: params.limit || 20,
11075
- since: params.since
11076
- });
11077
- }
11078
- async function handleSecurity(params) {
11079
- sessionMemory.recordToolCall("kuma_safety_security", {});
11080
- if (params.filePath) {
11081
- try {
11082
- const fs28 = await import("fs");
11083
- const path29 = await import("path");
11084
- const root = process.cwd();
11085
- const fullPath = path29.resolve(root, params.filePath);
11086
- if (fs28.existsSync(fullPath) && fs28.statSync(fullPath).isFile()) {
11087
- const content = fs28.readFileSync(fullPath, "utf-8");
11088
- const lines = content.split("\n");
11089
- const findings = [];
11090
- for (let i = 0; i < lines.length; i++) {
11091
- const line = lines[i];
11092
- const lc = line.toLowerCase();
11093
- if (lc.includes("credentials: true")) {
11094
- findings.push({ line: i + 1, pattern: "credentials-leak", severity: "high", message: "`credentials: true` in Prisma select may leak sensitive fields" });
11095
- }
11096
- if (lc.includes("console.log") && (lc.includes("token") || lc.includes("secret") || lc.includes("key") || lc.includes("password"))) {
11097
- findings.push({ line: i + 1, pattern: "console-log-secret", severity: "high", message: "Potential secret logged to console" });
11098
- }
11099
- if (lc.includes("process.env") && (lc.includes("api_key") || lc.includes("secret") || lc.includes("token"))) {
11100
- findings.push({ line: i + 1, pattern: "env-in-code", severity: "medium", message: "process.env.KEY referenced in code \u2014 ensure not in client bundle" });
11101
- }
11102
- }
11103
- for (const f of findings) {
11104
- await addSecurityFinding({ filePath: params.filePath, lineNumber: f.line, pattern: f.pattern, severity: f.severity, message: f.message });
11105
- }
11106
- if (findings.length === 0) return `\u{1F512} No security issues found in "${params.filePath}".`;
11107
- return `\u{1F512} **Security Scan** \u2014 ${findings.length} finding(s) in "${params.filePath}":
11108
- ${findings.map((f) => ` ${f.severity === "high" ? "\u{1F534}" : "\u{1F7E1}"} L${f.line}: ${f.message}`).join("\n")}`;
11109
- }
11110
- } catch {
11111
- }
11112
- }
11113
- return await getSecurityFindings();
11114
- }
11115
- async function handleGc(_params) {
11116
- sessionMemory.recordToolCall("kuma_safety_gc", {});
11117
- return await runGarbageCollection();
11118
- }
11119
- async function handleAstValidation(params) {
11120
- sessionMemory.recordToolCall("kuma_safety_ast", { scope: params.scope });
11121
- const { validateCodeContent: validateCodeContent2, validateFile: validateFile2, formatValidationFindings: formatValidationFindings2 } = await Promise.resolve().then(() => (init_kumaAstValidator(), kumaAstValidator_exports));
11122
- if (params.command) {
11123
- const findings = validateCodeContent2(params.command, params.scope);
11124
- return formatValidationFindings2(findings, params.scope);
11125
- }
11126
- if (params.scope) {
11127
- const findings = validateFile2(params.scope);
11128
- return formatValidationFindings2(findings, params.scope);
11129
- }
11130
- return "\u26A0\uFE0F Provide a scope (file path) or command (code content) to validate.";
11131
- }
11132
9466
  async function handleCheckpoint(params) {
11133
9467
  sessionMemory.recordToolCall("kuma_safety_checkpoint", { label: params.label });
11134
9468
  if (!params.label) return "\u26A0\uFE0F label parameter required. Example: kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' })";
@@ -11141,11 +9475,6 @@ async function handleRollbackLabel(params) {
11141
9475
  const { rollbackToCheckpoint: rollbackToCheckpoint2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
11142
9476
  return await rollbackToCheckpoint2(params.label);
11143
9477
  }
11144
- async function handleCheckpointList(_params) {
11145
- sessionMemory.recordToolCall("kuma_safety_checkpoint_list", {});
11146
- const { listCheckpoints: listCheckpoints2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
11147
- return listCheckpoints2();
11148
- }
11149
9478
  var _lastVerifyCall = 0;
11150
9479
  var VERIFY_COOLDOWN_MS = 3e4;
11151
9480
  async function handleVerify(params) {
@@ -11171,20 +9500,15 @@ async function handleVerify(params) {
11171
9500
 
11172
9501
  \u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
11173
9502
  }
11174
- sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
9503
+ sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope });
11175
9504
  const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
11176
9505
  const verifyResult = await runAutoVerification2({
11177
- scope: params.scope || params.filePath,
9506
+ scope: params.scope,
11178
9507
  force: params.force,
11179
9508
  timeoutMs: 3e4
11180
9509
  });
11181
9510
  return verifyResult + recordingWarning;
11182
9511
  }
11183
- async function handleGotchaStaleness(_params) {
11184
- const { verifyGotchaStaleness: verifyGotchaStaleness2, formatGotchaStalenessReport: formatGotchaStalenessReport2 } = await Promise.resolve().then(() => (init_kumaSelfHeal(), kumaSelfHeal_exports));
11185
- const stale = await verifyGotchaStaleness2();
11186
- return formatGotchaStalenessReport2(stale);
11187
- }
11188
9512
 
11189
9513
  // src/engine/kumaAutoInit.ts
11190
9514
  init_sessionMemory();
@@ -11249,15 +9573,14 @@ var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
11249
9573
  function registerAllTools(server) {
11250
9574
  server.tool(
11251
9575
  "kuma_context",
11252
- "Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way). Other actions are internal: flow, impact, navigate, digest, drift, rollback, changes, resume.",
9576
+ "Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow).",
11253
9577
  {
11254
- action: z.enum(["init", "research", "impact", "navigate", "flow", "history", "changes", "rollback", "digest", "drift", "resume"]).describe(
11255
- "init=project brief + session restore (CORE), research=research pipeline (CORE), history=file rationale + fresh gotchas (CORE); internal (avoid unless needed): impact, navigate, flow, changes, rollback, digest, drift, resume"
9578
+ action: z.enum(["init", "research", "history", "flow"]).describe(
9579
+ "init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow"
11256
9580
  ),
11257
9581
  scope: z.string().optional().describe("Research scope for research action"),
11258
- target: z.string().optional().describe("Target symbol/file for impact/navigate/changes/history"),
11259
- goal: z.string().optional().describe("Current goal (for init)"),
11260
- since: z.number().optional().describe("Unix timestamp filter for changes")
9582
+ target: z.string().optional().describe("Target symbol/file for history/flow"),
9583
+ goal: z.string().optional().describe("Current goal (for init)")
11261
9584
  },
11262
9585
  async (params) => {
11263
9586
  try {
@@ -11266,8 +9589,7 @@ function registerAllTools(server) {
11266
9589
  action: params.action,
11267
9590
  scope: params.scope,
11268
9591
  target: params.target,
11269
- goal: params.goal,
11270
- since: params.since
9592
+ goal: params.goal
11271
9593
  });
11272
9594
  return { content: [{ type: "text", text }] };
11273
9595
  } catch (err) {
@@ -11277,28 +9599,24 @@ function registerAllTools(server) {
11277
9599
  );
11278
9600
  server.tool(
11279
9601
  "kuma_memory",
11280
- "Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area). Other actions are internal: session, search, changes, mine, delete_node, clear, goal_progress.",
9602
+ "Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area), `search` (quick lookup of memory + knowledge graph).",
11281
9603
  {
11282
- action: z.enum(["decision", "mine", "research_save", "session", "search", "changes", "arch_flow", "gotcha", "delete_node", "clear", "goal_progress"]).describe(
11283
- "gotcha=record bug/quirk (CORE), decision=ADR (CORE), arch_flow=record architecture flow (CORE), research_save=save findings (CORE); internal (avoid unless needed): mine, session, search, changes, delete_node, clear, goal_progress"
9604
+ action: z.enum(["gotcha", "decision", "arch_flow", "research_save", "search"]).describe(
9605
+ "gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup"
11284
9606
  ),
11285
- scope: z.string().optional().describe("Scope for research_save/search/mine \u2014 or file path for gotcha/delete_node"),
11286
- trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha. Injected by the Bash PreToolUse hook"),
9607
+ scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
9608
+ trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
11287
9609
  query: z.string().optional().describe("Search query for search action"),
11288
9610
  content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
11289
9611
  record: z.string().optional().describe("JSON record string for research_save"),
11290
9612
  confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
11291
- confirm: z.boolean().optional().describe("Confirm and record candidates automatically for mine action"),
11292
9613
  title: z.string().optional().describe("Decision title (required for decision)"),
11293
9614
  context: z.string().optional().describe("Decision context"),
11294
9615
  rationale: z.string().optional().describe("Decision rationale (required for decision)"),
11295
9616
  outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
11296
9617
  status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
11297
9618
  description: z.string().optional().describe("Gotcha workaround"),
11298
- topic: z.string().optional().describe("Memory topic for session action"),
11299
- limit: z.number().min(1).max(100).optional().describe("Result limit for search/mine"),
11300
- target: z.string().optional().describe("File path for changes / node ID for delete_node"),
11301
- since: z.number().optional().describe("Timestamp filter for changes/mine")
9619
+ limit: z.number().min(1).max(100).optional().describe("Result limit for search")
11302
9620
  },
11303
9621
  async (params) => {
11304
9622
  try {
@@ -11310,15 +9628,11 @@ function registerAllTools(server) {
11310
9628
  content: params.content,
11311
9629
  record: params.record,
11312
9630
  confidence: params.confidence,
11313
- confirm: params.confirm,
11314
9631
  title: params.title,
11315
9632
  context: params.context,
11316
9633
  rationale: params.rationale,
11317
9634
  outcome: params.outcome,
11318
- topic: params.topic,
11319
9635
  limit: params.limit,
11320
- target: params.target,
11321
- since: params.since,
11322
9636
  status: params.status,
11323
9637
  description: params.description,
11324
9638
  trigger_command: params.trigger_command
@@ -11331,28 +9645,16 @@ function registerAllTools(server) {
11331
9645
  );
11332
9646
  server.tool(
11333
9647
  "kuma_safety",
11334
- "Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation). Other actions are internal/deprecated: check, audit, security, gc, ast, validate, checkpoint, rollback_label, checkpoint_list, gotcha_staleness.",
9648
+ "Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation), `checkpoint` + `rollback_label` (the one rollback mechanism: snapshot before risky work, restore after).",
11335
9649
  {
11336
- action: z.enum(["guard", "verify", "check", "audit", "security", "gc", "ast", "validate", "checkpoint", "rollback_label", "checkpoint_list", "gotcha_staleness"]).describe(
11337
- "guard=anti-patterns/drift/loops before risky work (CORE), verify=scoped tests after edits (CORE); internal/deprecated (avoid unless needed): check, audit, security, gc, ast, validate, checkpoint, rollback_label, checkpoint_list, gotcha_staleness"
9650
+ action: z.enum(["guard", "verify", "checkpoint", "rollback_label"]).describe(
9651
+ "guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
11338
9652
  ),
11339
- // Verify params
11340
- scope: z.string().optional().describe("Scope for verify/ast/validate (e.g. 'auth', file path)"),
11341
- // Guard params
9653
+ scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
9654
+ force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
11342
9655
  guardGoal: z.string().optional().describe("Goal for guard check"),
11343
9656
  guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
11344
- // Check params
11345
- filePath: z.string().optional().describe("File path for check/security scan"),
11346
- command: z.string().optional().describe("Command for check"),
11347
- // Audit params
11348
- toolName: z.string().optional().describe("Filter audit by tool name"),
11349
- riskLevel: z.string().optional().describe("Filter audit by risk level"),
11350
- allowed: z.boolean().optional().describe("Filter audit by allowed/blocked"),
11351
- limit: z.number().min(1).max(100).optional().describe("Audit result limit"),
11352
- since: z.number().optional().describe("Timestamp filter for audit"),
11353
- force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
11354
- // Checkpoint params
11355
- label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label actions"),
9657
+ label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
11356
9658
  description: z.string().optional().describe("Description for checkpoint")
11357
9659
  },
11358
9660
  async (params) => {
@@ -11362,14 +9664,7 @@ function registerAllTools(server) {
11362
9664
  action: params.action,
11363
9665
  guardGoal: params.guardGoal,
11364
9666
  guardCheck: params.guardCheck,
11365
- filePath: params.filePath,
11366
9667
  scope: params.scope,
11367
- command: params.command,
11368
- toolName: params.toolName,
11369
- riskLevel: params.riskLevel,
11370
- allowed: params.allowed,
11371
- limit: params.limit,
11372
- since: params.since,
11373
9668
  force: params.force,
11374
9669
  label: params.label,
11375
9670
  description: params.description
@@ -11380,7 +9675,7 @@ function registerAllTools(server) {
11380
9675
  }
11381
9676
  }
11382
9677
  );
11383
- console.error("[Manifest] Registered 3 coarse-grained tools (kuma_context, kuma_memory, kuma_safety).");
9678
+ console.error("[Manifest] Registered 3 coarse-grained tools with 13 core actions (init/research/history/flow \xB7 gotcha/decision/arch_flow/research_save/search \xB7 guard/verify/checkpoint/rollback_label).");
11384
9679
  console.error("[Manifest] Auto-init hooks installed on all tools.");
11385
9680
  console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
11386
9681
  }
@@ -11459,14 +9754,14 @@ async function main() {
11459
9754
  } catch {
11460
9755
  }
11461
9756
  try {
11462
- const path29 = await import("path");
11463
- const fs28 = await import("fs");
11464
- const lockDir = path29.resolve(process.cwd(), ".kuma/verifier.lock");
11465
- if (fs28.existsSync(lockDir)) {
11466
- const pidFile = path29.join(lockDir, "pid");
11467
- if (fs28.existsSync(pidFile)) {
9757
+ const path25 = await import("path");
9758
+ const fs24 = await import("fs");
9759
+ const lockDir = path25.resolve(process.cwd(), ".kuma/verifier.lock");
9760
+ if (fs24.existsSync(lockDir)) {
9761
+ const pidFile = path25.join(lockDir, "pid");
9762
+ if (fs24.existsSync(pidFile)) {
11468
9763
  try {
11469
- const pid = parseInt(fs28.readFileSync(pidFile, "utf-8"), 10);
9764
+ const pid = parseInt(fs24.readFileSync(pidFile, "utf-8"), 10);
11470
9765
  if (pid && pid !== process.pid) {
11471
9766
  try {
11472
9767
  process.kill(-pid, "SIGKILL");
@@ -11482,7 +9777,7 @@ async function main() {
11482
9777
  } catch {
11483
9778
  }
11484
9779
  }
11485
- fs28.rmSync(lockDir, { recursive: true, force: true });
9780
+ fs24.rmSync(lockDir, { recursive: true, force: true });
11486
9781
  }
11487
9782
  if (killedCount === 0) {
11488
9783
  console.error("\u2705 No running verifications found to kill.");
@@ -11490,25 +9785,25 @@ async function main() {
11490
9785
  } catch {
11491
9786
  }
11492
9787
  try {
11493
- const { execSync: execSync7 } = await import("child_process");
9788
+ const { execSync: execSync6 } = await import("child_process");
11494
9789
  try {
11495
- execSync7("pkill -f 'jest' 2>/dev/null || true");
9790
+ execSync6("pkill -f 'jest' 2>/dev/null || true");
11496
9791
  } catch {
11497
9792
  }
11498
9793
  try {
11499
- execSync7("pkill -f 'jest-worker' 2>/dev/null || true");
9794
+ execSync6("pkill -f 'jest-worker' 2>/dev/null || true");
11500
9795
  } catch {
11501
9796
  }
11502
9797
  try {
11503
- execSync7("pkill -f 'pnpm test' 2>/dev/null || true");
9798
+ execSync6("pkill -f 'pnpm test' 2>/dev/null || true");
11504
9799
  } catch {
11505
9800
  }
11506
9801
  try {
11507
- execSync7("pkill -f 'npm test' 2>/dev/null || true");
9802
+ execSync6("pkill -f 'npm test' 2>/dev/null || true");
11508
9803
  } catch {
11509
9804
  }
11510
9805
  try {
11511
- execSync7("pkill -f 'yarn test' 2>/dev/null || true");
9806
+ execSync6("pkill -f 'yarn test' 2>/dev/null || true");
11512
9807
  } catch {
11513
9808
  }
11514
9809
  console.error("\u2705 Killed orphaned test processes");
@@ -11681,23 +9976,23 @@ async function main() {
11681
9976
  });
11682
9977
  const output = formatInitResults(results);
11683
9978
  console.log(output);
11684
- const fs28 = await import("fs");
11685
- const path29 = await import("path");
11686
- const matchaSkills = path29.resolve(process.cwd(), "skills/matcha/SKILL.md");
11687
- const matchaAgents = path29.resolve(
9979
+ const fs24 = await import("fs");
9980
+ const path25 = await import("path");
9981
+ const matchaSkills = path25.resolve(process.cwd(), "skills/matcha/SKILL.md");
9982
+ const matchaAgents = path25.resolve(
11688
9983
  process.cwd(),
11689
9984
  ".agents/skills/matcha/SKILL.md"
11690
9985
  );
11691
- const matchaRootSkills = path29.resolve(
9986
+ const matchaRootSkills = path25.resolve(
11692
9987
  process.cwd(),
11693
9988
  "skills/matcha/SKILL.md"
11694
9989
  );
11695
- const matchaAgentsMd = path29.resolve(process.cwd(), "AGENTS.md");
11696
- const matchaWindsurfRules = path29.resolve(
9990
+ const matchaAgentsMd = path25.resolve(process.cwd(), "AGENTS.md");
9991
+ const matchaWindsurfRules = path25.resolve(
11697
9992
  process.cwd(),
11698
9993
  ".windsurf"
11699
9994
  );
11700
- if (fs28.existsSync(matchaSkills) || fs28.existsSync(matchaAgents) || fs28.existsSync(matchaRootSkills) || fs28.existsSync(matchaAgentsMd) || fs28.existsSync(matchaWindsurfRules)) {
9995
+ if (fs24.existsSync(matchaSkills) || fs24.existsSync(matchaAgents) || fs24.existsSync(matchaRootSkills) || fs24.existsSync(matchaAgentsMd) || fs24.existsSync(matchaWindsurfRules)) {
11701
9996
  console.error(
11702
9997
  "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
11703
9998
  );
@@ -11711,13 +10006,13 @@ async function main() {
11711
10006
  (async () => {
11712
10007
  try {
11713
10008
  const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
11714
- const fs28 = await import("fs");
11715
- const path29 = await import("path");
11716
- const initMdPath = path29.resolve(process.cwd(), ".kuma/init.md");
11717
- if (!fs28.existsSync(initMdPath)) {
11718
- const kumaDir = path29.dirname(initMdPath);
11719
- if (!fs28.existsSync(kumaDir)) fs28.mkdirSync(kumaDir, { recursive: true });
11720
- fs28.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
10009
+ const fs24 = await import("fs");
10010
+ const path25 = await import("path");
10011
+ const initMdPath = path25.resolve(process.cwd(), ".kuma/init.md");
10012
+ if (!fs24.existsSync(initMdPath)) {
10013
+ const kumaDir = path25.dirname(initMdPath);
10014
+ if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
10015
+ fs24.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
11721
10016
  console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
11722
10017
  }
11723
10018
  } catch (err) {
@@ -11728,8 +10023,8 @@ async function main() {
11728
10023
  try {
11729
10024
  const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
11730
10025
  const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
11731
- const fs28 = await import("fs");
11732
- const path29 = await import("path");
10026
+ const fs24 = await import("fs");
10027
+ const path25 = await import("path");
11733
10028
  const detection = detectAgent2();
11734
10029
  if (!detection.primary) {
11735
10030
  console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
@@ -11737,22 +10032,22 @@ async function main() {
11737
10032
  }
11738
10033
  const agentType = detection.primary;
11739
10034
  const skillPath = getSkillPath2(agentType);
11740
- const fullPath = path29.resolve(process.cwd(), skillPath);
11741
- if (fs28.existsSync(fullPath)) {
10035
+ const fullPath = path25.resolve(process.cwd(), skillPath);
10036
+ if (fs24.existsSync(fullPath)) {
11742
10037
  console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
11743
10038
  return;
11744
10039
  }
11745
- const dir = path29.dirname(fullPath);
11746
- if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
11747
- fs28.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
10040
+ const dir = path25.dirname(fullPath);
10041
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
10042
+ fs24.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
11748
10043
  console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
11749
10044
  const secondaryFiles = getSecondaryFiles2(agentType);
11750
10045
  for (const sf of secondaryFiles) {
11751
- const sfPath = path29.resolve(process.cwd(), sf.path);
11752
- const sfDir = path29.dirname(sfPath);
11753
- if (!fs28.existsSync(sfPath)) {
11754
- if (!fs28.existsSync(sfDir)) fs28.mkdirSync(sfDir, { recursive: true });
11755
- fs28.writeFileSync(sfPath, sf.content, "utf-8");
10046
+ const sfPath = path25.resolve(process.cwd(), sf.path);
10047
+ const sfDir = path25.dirname(sfPath);
10048
+ if (!fs24.existsSync(sfPath)) {
10049
+ if (!fs24.existsSync(sfDir)) fs24.mkdirSync(sfDir, { recursive: true });
10050
+ fs24.writeFileSync(sfPath, sf.content, "utf-8");
11756
10051
  console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
11757
10052
  }
11758
10053
  }
@@ -11776,17 +10071,6 @@ async function main() {
11776
10071
  } catch (err) {
11777
10072
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
11778
10073
  }
11779
- try {
11780
- const fs28 = await import("fs");
11781
- const path29 = await import("path");
11782
- const scratchDir = path29.resolve(process.cwd(), ".kuma", "scratch");
11783
- if (!fs28.existsSync(scratchDir)) {
11784
- fs28.mkdirSync(scratchDir, { recursive: true });
11785
- console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
11786
- }
11787
- } catch (err) {
11788
- console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
11789
- }
11790
10074
  try {
11791
10075
  const { getDb: getDb2, saveDb: saveDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
11792
10076
  const db = await getDb2();
@@ -11839,22 +10123,7 @@ async function main() {
11839
10123
  `[${SERVER_NAME}] Session started: ${(/* @__PURE__ */ new Date()).toISOString()}`
11840
10124
  );
11841
10125
  console.error(
11842
- `[${SERVER_NAME}] Kuma v2.0 \u2014 Zero-Overhead Autonomous Brain Engine`
11843
- );
11844
- console.error(
11845
- `[${SERVER_NAME}] \u26A1 Auto-init: no manual init needed (Pilar 1)`
11846
- );
11847
- console.error(
11848
- `[${SERVER_NAME}] \u{1F4DB} Namespace normalized: kuma_kuma_* \u2192 kuma_* (Pilar 2)`
11849
- );
11850
- console.error(
11851
- `[${SERVER_NAME}] \u{1F9F9} Noise filter: AST node types rejected (Pilar 4)`
11852
- );
11853
- console.error(
11854
- `[${SERVER_NAME}] \u{1F419} Git auto-harvester installed (Pilar 5)`
11855
- );
11856
- console.error(
11857
- `[${SERVER_NAME}] \u{1F6E1}\uFE0F 3 coarse-grained tools: kuma_context, kuma_memory, kuma_safety`
10126
+ `[${SERVER_NAME}] \u{1F6E1}\uFE0F 3 coarse-grained tools: kuma_context, kuma_memory, kuma_safety \u2014 13 core actions`
11858
10127
  );
11859
10128
  await server.connect(transport);
11860
10129
  console.error(