@plumpslabs/kuma 2.3.27 → 2.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +838 -409
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1181,8 +1181,8 @@ async function runDoctor() {
1181
1181
  } catch {
1182
1182
  }
1183
1183
  try {
1184
- const { execSync: execSync7 } = await import("child_process");
1185
- const psOutput = execSync7(
1184
+ const { execSync: execSync8 } = await import("child_process");
1185
+ const psOutput = execSync8(
1186
1186
  `ps -eo pid,ppid,command | grep -E "(jest|pnpm test|npm test|yarn test)" | grep -v grep | head -10`,
1187
1187
  { encoding: "utf-8", timeout: 5e3 }
1188
1188
  ).trim();
@@ -3169,27 +3169,27 @@ async function codebaseSearchFallback(query, limit = 20) {
3169
3169
  if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
3170
3170
  let entries = [];
3171
3171
  try {
3172
- entries = fs26.readdirSync(dir);
3172
+ entries = fs27.readdirSync(dir);
3173
3173
  } catch {
3174
3174
  return;
3175
3175
  }
3176
3176
  for (const entry of entries) {
3177
3177
  if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
3178
- const full = path26.join(dir, entry);
3178
+ const full = path27.join(dir, entry);
3179
3179
  try {
3180
- const stat = fs26.statSync(full);
3180
+ const stat = fs27.statSync(full);
3181
3181
  if (stat.isDirectory()) {
3182
3182
  walk2(full, depth + 1);
3183
3183
  } else if (entry.toLowerCase().includes(query.toLowerCase())) {
3184
- results.push(path26.relative(root, full));
3184
+ results.push(path27.relative(root, full));
3185
3185
  }
3186
3186
  } catch {
3187
3187
  }
3188
3188
  }
3189
3189
  };
3190
3190
  var walk = walk2;
3191
- const fs26 = await import("fs");
3192
- const path26 = await import("path");
3191
+ const fs27 = await import("fs");
3192
+ const path27 = await import("path");
3193
3193
  const root = process.cwd();
3194
3194
  const results = [];
3195
3195
  const MAX_WALK_DEPTH = 8;
@@ -3329,9 +3329,9 @@ async function analyzeImpact(target) {
3329
3329
  }
3330
3330
  async function codebaseImpactFallback(target) {
3331
3331
  try {
3332
- const { execSync: execSync7 } = await import("child_process");
3332
+ const { execSync: execSync8 } = await import("child_process");
3333
3333
  const root = process.cwd();
3334
- const grepResult = execSync7(
3334
+ const grepResult = execSync8(
3335
3335
  `grep -rn --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" --include="*.json" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
3336
3336
  { cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
3337
3337
  ).trim();
@@ -5782,8 +5782,11 @@ var init_kumaProgressiveContext = __esm({
5782
5782
  // src/engine/kumaSearch.ts
5783
5783
  var kumaSearch_exports = {};
5784
5784
  __export(kumaSearch_exports, {
5785
+ buildGraphConnectivity: () => buildGraphConnectivity,
5785
5786
  buildSearchVectors: () => buildSearchVectors,
5787
+ clearConnectivityCache: () => clearConnectivityCache,
5786
5788
  clearSearchCache: () => clearSearchCache,
5789
+ enhancedHybridSearch: () => enhancedHybridSearch,
5787
5790
  expandQueryTerms: () => expandQueryTerms,
5788
5791
  formatHybridResults: () => formatHybridResults,
5789
5792
  hybridSearch: () => hybridSearch
@@ -6050,10 +6053,63 @@ function formatHybridResults(query, results) {
6050
6053
  lines.push("Hybrid search combines keyword matching with semantic synonym expansion.");
6051
6054
  return lines.join("\n");
6052
6055
  }
6056
+ async function buildGraphConnectivity() {
6057
+ if (_connectivityCache) return _connectivityCache;
6058
+ const nodeEdges = /* @__PURE__ */ new Map();
6059
+ let totalEdges = 0;
6060
+ try {
6061
+ const db = await getDb();
6062
+ const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT 2000");
6063
+ while (stmt.step()) {
6064
+ const row = stmt.getAsObject();
6065
+ const src = row.source_id || "";
6066
+ const tgt = row.target_id || "";
6067
+ if (src) nodeEdges.set(src, (nodeEdges.get(src) || 0) + 1);
6068
+ if (tgt) nodeEdges.set(tgt, (nodeEdges.get(tgt) || 0) + 1);
6069
+ totalEdges++;
6070
+ }
6071
+ stmt.free();
6072
+ } catch {
6073
+ }
6074
+ _connectivityCache = { nodeEdges, totalEdges };
6075
+ setTimeout(() => {
6076
+ _connectivityCache = null;
6077
+ }, CONNECTIVITY_CACHE_TTL);
6078
+ return _connectivityCache;
6079
+ }
6080
+ function connectivityScore(nodeId2, connectivity) {
6081
+ if (connectivity.totalEdges === 0) return 0;
6082
+ const edges = connectivity.nodeEdges.get(nodeId2) || 0;
6083
+ return Math.min(1, Math.log(1 + edges) / Math.log(1 + 20));
6084
+ }
6085
+ function clearConnectivityCache() {
6086
+ _connectivityCache = null;
6087
+ }
6088
+ async function enhancedHybridSearch(query, limit = 10) {
6089
+ const basicResults = await hybridSearch(query, limit * 2);
6090
+ const connectivity = await buildGraphConnectivity();
6091
+ for (const result of basicResults) {
6092
+ try {
6093
+ const db = await getDb();
6094
+ const stmt = db.prepare("SELECT id FROM nodes WHERE name LIKE ? LIMIT 1");
6095
+ stmt.bind([`%${result.source}%`]);
6096
+ if (stmt.step()) {
6097
+ const row = stmt.getAsObject();
6098
+ const nodeId2 = row.id || "";
6099
+ const connScore = connectivityScore(nodeId2, connectivity);
6100
+ result.score = Math.min(100, Math.round(result.score * 0.6 + connScore * 40));
6101
+ }
6102
+ stmt.free();
6103
+ } catch {
6104
+ }
6105
+ }
6106
+ return basicResults.sort((a, b) => b.score - a.score).slice(0, limit);
6107
+ }
6053
6108
  function clearSearchCache() {
6054
6109
  _vectorCache = null;
6110
+ clearConnectivityCache();
6055
6111
  }
6056
- var SYNONYM_MAP, _vectorCache, VECTOR_CACHE_TTL;
6112
+ var SYNONYM_MAP, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
6057
6113
  var init_kumaSearch = __esm({
6058
6114
  "src/engine/kumaSearch.ts"() {
6059
6115
  "use strict";
@@ -6100,6 +6156,8 @@ var init_kumaSearch = __esm({
6100
6156
  };
6101
6157
  _vectorCache = null;
6102
6158
  VECTOR_CACHE_TTL = 3e5;
6159
+ _connectivityCache = null;
6160
+ CONNECTIVITY_CACHE_TTL = 3e5;
6103
6161
  }
6104
6162
  });
6105
6163
 
@@ -6441,6 +6499,236 @@ var init_kumaGotchas = __esm({
6441
6499
  }
6442
6500
  });
6443
6501
 
6502
+ // src/engine/kumaGitHarvester.ts
6503
+ var kumaGitHarvester_exports = {};
6504
+ __export(kumaGitHarvester_exports, {
6505
+ detectSignificantCommits: () => detectSignificantCommits,
6506
+ getRecentCommits: () => getRecentCommits,
6507
+ harvestGitHistory: () => harvestGitHistory,
6508
+ installGitHook: () => installGitHook
6509
+ });
6510
+ import { execSync as execSync5 } from "child_process";
6511
+ import fs14 from "fs";
6512
+ import path14 from "path";
6513
+ function getRecentCommits(count = 5) {
6514
+ const root = getProjectRoot();
6515
+ try {
6516
+ const format = JSON.stringify({
6517
+ hash: "%H",
6518
+ message: "%s",
6519
+ author: "%an",
6520
+ date: "%ai"
6521
+ });
6522
+ const log = execSync5(
6523
+ `git log -n ${count} --pretty=format:'${format.replace(/'/g, "'\\''")}' --name-only`,
6524
+ { cwd: root, encoding: "utf-8", timeout: 5e3 }
6525
+ );
6526
+ const commits = [];
6527
+ const entries = log.split("\n\n").filter(Boolean);
6528
+ for (const entry of entries) {
6529
+ const lines = entry.split("\n");
6530
+ if (lines.length < 1) continue;
6531
+ try {
6532
+ const meta = JSON.parse(lines[0]);
6533
+ const files = lines.slice(1).filter((f) => f.trim() && !f.startsWith("commit "));
6534
+ let insertions = 0;
6535
+ let deletions = 0;
6536
+ try {
6537
+ const stats = execSync5(
6538
+ `git diff --shortstat ${meta.hash}^..${meta.hash}`,
6539
+ { cwd: root, encoding: "utf-8", timeout: 3e3 }
6540
+ );
6541
+ const insMatch = stats.match(/(\d+) insertion/);
6542
+ const delMatch = stats.match(/(\d+) deletion/);
6543
+ if (insMatch) insertions = parseInt(insMatch[1], 10);
6544
+ if (delMatch) deletions = parseInt(delMatch[1], 10);
6545
+ } catch {
6546
+ }
6547
+ commits.push({
6548
+ hash: meta.hash?.substring(0, 8) || "",
6549
+ message: meta.message || "",
6550
+ author: meta.author || "",
6551
+ date: meta.date || "",
6552
+ files,
6553
+ insertions,
6554
+ deletions
6555
+ });
6556
+ } catch {
6557
+ }
6558
+ }
6559
+ return commits;
6560
+ } catch {
6561
+ return [];
6562
+ }
6563
+ }
6564
+ function detectSignificantCommits(commits) {
6565
+ const decisionKeywords = [
6566
+ "refactor",
6567
+ "migrate",
6568
+ "switch to",
6569
+ "replace",
6570
+ "use .* instead",
6571
+ "remove",
6572
+ "drop",
6573
+ "deprecate",
6574
+ "upgrade",
6575
+ "update to",
6576
+ "architect",
6577
+ "design",
6578
+ "implement",
6579
+ "add feature"
6580
+ ];
6581
+ const gotchaKeywords = [
6582
+ "fix",
6583
+ "bug",
6584
+ "hotfix",
6585
+ "patch",
6586
+ "workaround",
6587
+ "revert",
6588
+ "broken",
6589
+ "issue",
6590
+ "error",
6591
+ "crash",
6592
+ "race condition",
6593
+ "memory leak",
6594
+ "performance"
6595
+ ];
6596
+ const decisions = [];
6597
+ const gotchas = [];
6598
+ const regular = [];
6599
+ for (const commit of commits) {
6600
+ const msg = commit.message.toLowerCase();
6601
+ const isLargeChange = commit.files.length > 3 || commit.insertions + commit.deletions > 100;
6602
+ const isDecision = decisionKeywords.some((kw) => new RegExp(kw, "i").test(msg)) || isLargeChange;
6603
+ const isGotcha = gotchaKeywords.some((kw) => new RegExp(kw, "i").test(msg));
6604
+ if (isDecision && !isGotcha) {
6605
+ decisions.push(commit);
6606
+ } else if (isGotcha) {
6607
+ gotchas.push(commit);
6608
+ } else {
6609
+ regular.push(commit);
6610
+ }
6611
+ }
6612
+ return { decisions, gotchas, regular };
6613
+ }
6614
+ async function harvestGitHistory(options) {
6615
+ const count = options?.commitCount || 10;
6616
+ const dryRun = options?.dryRun || false;
6617
+ const commits = getRecentCommits(count);
6618
+ if (commits.length === 0) {
6619
+ return "\u{1F4ED} No git commits found. Is this a git repository?";
6620
+ }
6621
+ const { decisions, gotchas } = detectSignificantCommits(commits);
6622
+ const lines = [
6623
+ "\u{1F419} **Git Auto-Harvester**",
6624
+ "\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",
6625
+ "",
6626
+ `\u{1F4CA} Scanned ${commits.length} commit(s)`,
6627
+ `\u2696\uFE0F ${decisions.length} significant change(s) \u2192 decisions`,
6628
+ `\u26A0\uFE0F ${gotchas.length} fix/revert(s) \u2192 gotchas`,
6629
+ ""
6630
+ ];
6631
+ if (dryRun) {
6632
+ lines.push("**Dry Run \u2014 no nodes created:**");
6633
+ lines.push("");
6634
+ if (decisions.length > 0) {
6635
+ lines.push("**Potential Decisions:**");
6636
+ for (const c of decisions) {
6637
+ lines.push(` \u2696\uFE0F ${c.hash} \u2014 ${c.message}`);
6638
+ lines.push(` Files: ${c.files.length}, +${c.insertions}/-${c.deletions}`);
6639
+ }
6640
+ lines.push("");
6641
+ }
6642
+ if (gotchas.length > 0) {
6643
+ lines.push("**Potential Gotchas:**");
6644
+ for (const c of gotchas) {
6645
+ lines.push(` \u26A0\uFE0F ${c.hash} \u2014 ${c.message}`);
6646
+ lines.push(` Files: ${c.files.join(", ")}`);
6647
+ }
6648
+ lines.push("");
6649
+ }
6650
+ lines.push("\u{1F4A1} Run without dryRun to create nodes.");
6651
+ return lines.join("\n");
6652
+ }
6653
+ let decisionCount = 0;
6654
+ for (const commit of decisions) {
6655
+ try {
6656
+ const { recordDecision: recordDecision2 } = await Promise.resolve().then(() => (init_kumaMemory(), kumaMemory_exports));
6657
+ await recordDecision2({
6658
+ title: commit.message,
6659
+ context: `Auto-harvested from git commit ${commit.hash}`,
6660
+ options: [],
6661
+ rationale: `Files changed: ${commit.files.slice(0, 5).join(", ")}${commit.files.length > 5 ? ` +${commit.files.length - 5} more` : ""}`,
6662
+ outcome: "implemented",
6663
+ timestamp: new Date(commit.date).toISOString()
6664
+ });
6665
+ decisionCount++;
6666
+ } catch {
6667
+ }
6668
+ }
6669
+ let gotchaCount = 0;
6670
+ for (const commit of gotchas) {
6671
+ try {
6672
+ const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
6673
+ const primaryFile = commit.files[0] || "unknown";
6674
+ await addGotcha2({
6675
+ filePath: primaryFile,
6676
+ description: `Git commit ${commit.hash}: ${commit.message}`,
6677
+ severity: commit.message.toLowerCase().includes("revert") ? "high" : "medium",
6678
+ workaround: `See commit ${commit.hash} for details`
6679
+ });
6680
+ gotchaCount++;
6681
+ } catch {
6682
+ }
6683
+ }
6684
+ lines.push(`\u2705 Harvested: ${decisionCount} decision(s), ${gotchaCount} gotcha(s)`);
6685
+ if (decisionCount + gotchaCount === 0) {
6686
+ lines.push("\u{1F4ED} No significant commits to harvest.");
6687
+ }
6688
+ return lines.join("\n");
6689
+ }
6690
+ function installGitHook() {
6691
+ const root = getProjectRoot();
6692
+ const hooksDir = path14.join(root, ".git", "hooks");
6693
+ if (!fs14.existsSync(hooksDir)) {
6694
+ return "\u26A0\uFE0F Not a git repository \u2014 no .git/hooks/ directory found.";
6695
+ }
6696
+ const hookPath = path14.join(hooksDir, "post-commit");
6697
+ const hookContent = `#!/bin/bash
6698
+ # Kuma auto-harvester hook (post-commit)
6699
+ # Automatically extracts decisions and gotchas from commits
6700
+ if command -v npx &> /dev/null; then
6701
+ npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
6702
+ fi
6703
+ `;
6704
+ if (fs14.existsSync(hookPath)) {
6705
+ const existing = fs14.readFileSync(hookPath, "utf-8");
6706
+ if (existing.includes("kuma")) {
6707
+ return "\u2705 Kuma git hook already installed.";
6708
+ }
6709
+ const backupPath = hookPath + ".backup." + Date.now();
6710
+ fs14.copyFileSync(hookPath, backupPath);
6711
+ fs14.writeFileSync(hookPath, hookContent, "utf-8");
6712
+ try {
6713
+ fs14.chmodSync(hookPath, 493);
6714
+ } catch {
6715
+ }
6716
+ return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path14.basename(backupPath)})`;
6717
+ }
6718
+ fs14.writeFileSync(hookPath, hookContent, "utf-8");
6719
+ try {
6720
+ fs14.chmodSync(hookPath, 493);
6721
+ } catch {
6722
+ }
6723
+ return "\u2705 Installed kuma post-commit hook.";
6724
+ }
6725
+ var init_kumaGitHarvester = __esm({
6726
+ "src/engine/kumaGitHarvester.ts"() {
6727
+ "use strict";
6728
+ init_pathValidator();
6729
+ }
6730
+ });
6731
+
6444
6732
  // src/engine/safetyAudit.ts
6445
6733
  var safetyAudit_exports = {};
6446
6734
  __export(safetyAudit_exports, {
@@ -6610,19 +6898,19 @@ __export(kumaPolicyEngine_exports, {
6610
6898
  processOverride: () => processOverride,
6611
6899
  savePolicyConfig: () => savePolicyConfig
6612
6900
  });
6613
- import fs19 from "fs";
6614
- import path19 from "path";
6901
+ import fs20 from "fs";
6902
+ import path20 from "path";
6615
6903
  function loadPolicyConfig() {
6616
6904
  try {
6617
6905
  const root = getProjectRoot();
6618
- const policyPath = path19.join(root, ".kuma", "POLICY.json");
6619
- if (fs19.existsSync(policyPath)) {
6620
- const content = fs19.readFileSync(policyPath, "utf-8");
6906
+ const policyPath = path20.join(root, ".kuma", "POLICY.json");
6907
+ if (fs20.existsSync(policyPath)) {
6908
+ const content = fs20.readFileSync(policyPath, "utf-8");
6621
6909
  const config = JSON.parse(content);
6622
6910
  return { ...DEFAULT_POLICY_CONFIG, ...config };
6623
6911
  }
6624
- const ymlPath = path19.join(root, ".kuma", "policy.yml");
6625
- if (fs19.existsSync(ymlPath)) {
6912
+ const ymlPath = path20.join(root, ".kuma", "policy.yml");
6913
+ if (fs20.existsSync(ymlPath)) {
6626
6914
  console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
6627
6915
  }
6628
6916
  } catch (err) {
@@ -6633,10 +6921,10 @@ function loadPolicyConfig() {
6633
6921
  function savePolicyConfig(config) {
6634
6922
  try {
6635
6923
  const root = getProjectRoot();
6636
- const policyPath = path19.join(root, ".kuma", "POLICY.json");
6637
- const dir = path19.dirname(policyPath);
6638
- if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
6639
- fs19.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
6924
+ const policyPath = path20.join(root, ".kuma", "POLICY.json");
6925
+ const dir = path20.dirname(policyPath);
6926
+ if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
6927
+ fs20.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
6640
6928
  return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
6641
6929
  } catch (err) {
6642
6930
  return `\u274C Failed to save policy: ${err}`;
@@ -6921,14 +7209,14 @@ __export(kumaAstValidator_exports, {
6921
7209
  validateDiff: () => validateDiff,
6922
7210
  validateFile: () => validateFile
6923
7211
  });
6924
- import fs20 from "fs";
6925
- import path20 from "path";
7212
+ import fs21 from "fs";
7213
+ import path21 from "path";
6926
7214
  function loadImportWhitelist() {
6927
7215
  try {
6928
7216
  const root = getProjectRoot();
6929
- const whitelistPath = path20.join(root, ".kuma", "import-whitelist.json");
6930
- if (fs20.existsSync(whitelistPath)) {
6931
- return JSON.parse(fs20.readFileSync(whitelistPath, "utf-8"));
7217
+ const whitelistPath = path21.join(root, ".kuma", "import-whitelist.json");
7218
+ if (fs21.existsSync(whitelistPath)) {
7219
+ return JSON.parse(fs21.readFileSync(whitelistPath, "utf-8"));
6932
7220
  }
6933
7221
  } catch {
6934
7222
  }
@@ -7002,9 +7290,9 @@ function validateCodeContent(code, filePath) {
7002
7290
  function validateFile(filePath) {
7003
7291
  try {
7004
7292
  const root = getProjectRoot();
7005
- const fullPath = path20.resolve(root, filePath);
7006
- if (!fs20.existsSync(fullPath)) return [];
7007
- const content = fs20.readFileSync(fullPath, "utf-8");
7293
+ const fullPath = path21.resolve(root, filePath);
7294
+ if (!fs21.existsSync(fullPath)) return [];
7295
+ const content = fs21.readFileSync(fullPath, "utf-8");
7008
7296
  return validateCodeContent(content, filePath);
7009
7297
  } catch {
7010
7298
  return [];
@@ -7075,16 +7363,16 @@ __export(kumaCheckpoint_exports, {
7075
7363
  pruneCheckpoints: () => pruneCheckpoints,
7076
7364
  rollbackToCheckpoint: () => rollbackToCheckpoint
7077
7365
  });
7078
- import fs21 from "fs";
7079
- import path21 from "path";
7366
+ import fs22 from "fs";
7367
+ import path22 from "path";
7080
7368
  async function createCheckpoint(label, description) {
7081
7369
  try {
7082
7370
  const root = getProjectRoot();
7083
- const cpDir = path21.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7084
- if (fs21.existsSync(cpDir)) {
7371
+ const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7372
+ if (fs22.existsSync(cpDir)) {
7085
7373
  return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
7086
7374
  }
7087
- fs21.mkdirSync(cpDir, { recursive: true });
7375
+ fs22.mkdirSync(cpDir, { recursive: true });
7088
7376
  const db = await getDb();
7089
7377
  const filesStmt = db.prepare(
7090
7378
  "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
@@ -7093,19 +7381,19 @@ async function createCheckpoint(label, description) {
7093
7381
  while (filesStmt.step()) {
7094
7382
  const row = filesStmt.getAsObject();
7095
7383
  const fp = row.file_path;
7096
- const fullPath = path21.resolve(root, fp);
7097
- if (fs21.existsSync(fullPath)) {
7098
- const content = fs21.readFileSync(fullPath, "utf-8");
7384
+ const fullPath = path22.resolve(root, fp);
7385
+ if (fs22.existsSync(fullPath)) {
7386
+ const content = fs22.readFileSync(fullPath, "utf-8");
7099
7387
  const hash = simpleHash2(content);
7100
- const fileDir = path21.dirname(path21.join(cpDir, "files", fp));
7101
- if (!fs21.existsSync(fileDir)) fs21.mkdirSync(fileDir, { recursive: true });
7102
- fs21.writeFileSync(path21.join(cpDir, "files", fp), content, "utf-8");
7388
+ const fileDir = path22.dirname(path22.join(cpDir, "files", fp));
7389
+ if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
7390
+ fs22.writeFileSync(path22.join(cpDir, "files", fp), content, "utf-8");
7103
7391
  files.push({ path: fp, hash });
7104
7392
  }
7105
7393
  }
7106
7394
  filesStmt.free();
7107
7395
  const dbData = db.export();
7108
- fs21.writeFileSync(path21.join(cpDir, "kuma.db"), Buffer.from(dbData));
7396
+ fs22.writeFileSync(path22.join(cpDir, "kuma.db"), Buffer.from(dbData));
7109
7397
  const manifest = {
7110
7398
  label,
7111
7399
  timestamp: Date.now(),
@@ -7113,8 +7401,8 @@ async function createCheckpoint(label, description) {
7113
7401
  dbSnapshot: true,
7114
7402
  description
7115
7403
  };
7116
- fs21.writeFileSync(
7117
- path21.join(cpDir, "manifest.json"),
7404
+ fs22.writeFileSync(
7405
+ path22.join(cpDir, "manifest.json"),
7118
7406
  JSON.stringify(manifest, null, 2),
7119
7407
  "utf-8"
7120
7408
  );
@@ -7140,37 +7428,37 @@ async function createCheckpoint(label, description) {
7140
7428
  async function rollbackToCheckpoint(label) {
7141
7429
  try {
7142
7430
  const root = getProjectRoot();
7143
- const cpDir = path21.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7144
- const manifestPath = path21.join(cpDir, "manifest.json");
7145
- if (!fs21.existsSync(manifestPath)) {
7431
+ const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7432
+ const manifestPath = path22.join(cpDir, "manifest.json");
7433
+ if (!fs22.existsSync(manifestPath)) {
7146
7434
  return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
7147
7435
  }
7148
7436
  const manifest = JSON.parse(
7149
- fs21.readFileSync(manifestPath, "utf-8")
7437
+ fs22.readFileSync(manifestPath, "utf-8")
7150
7438
  );
7151
7439
  let restored = 0;
7152
7440
  let failed = 0;
7153
7441
  for (const f of manifest.files) {
7154
- const snapshotPath = path21.join(cpDir, "files", f.path);
7155
- if (fs21.existsSync(snapshotPath)) {
7442
+ const snapshotPath = path22.join(cpDir, "files", f.path);
7443
+ if (fs22.existsSync(snapshotPath)) {
7156
7444
  try {
7157
- const content = fs21.readFileSync(snapshotPath, "utf-8");
7158
- const targetPath = path21.resolve(root, f.path);
7159
- const targetDir = path21.dirname(targetPath);
7160
- if (!fs21.existsSync(targetDir)) fs21.mkdirSync(targetDir, { recursive: true });
7161
- fs21.writeFileSync(targetPath, content, "utf-8");
7445
+ const content = fs22.readFileSync(snapshotPath, "utf-8");
7446
+ const targetPath = path22.resolve(root, f.path);
7447
+ const targetDir = path22.dirname(targetPath);
7448
+ if (!fs22.existsSync(targetDir)) fs22.mkdirSync(targetDir, { recursive: true });
7449
+ fs22.writeFileSync(targetPath, content, "utf-8");
7162
7450
  restored++;
7163
7451
  } catch {
7164
7452
  failed++;
7165
7453
  }
7166
7454
  }
7167
7455
  }
7168
- const dbSnapshotPath = path21.join(cpDir, "kuma.db");
7169
- if (fs21.existsSync(dbSnapshotPath)) {
7170
- const kumaDir = path21.join(root, ".kuma");
7171
- const dbPath = path21.join(kumaDir, "kuma.db");
7172
- const snapshotData = fs21.readFileSync(dbSnapshotPath);
7173
- fs21.writeFileSync(dbPath, snapshotData);
7456
+ const dbSnapshotPath = path22.join(cpDir, "kuma.db");
7457
+ if (fs22.existsSync(dbSnapshotPath)) {
7458
+ const kumaDir = path22.join(root, ".kuma");
7459
+ const dbPath = path22.join(kumaDir, "kuma.db");
7460
+ const snapshotData = fs22.readFileSync(dbSnapshotPath);
7461
+ fs22.writeFileSync(dbPath, snapshotData);
7174
7462
  resetDbInstance();
7175
7463
  }
7176
7464
  sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
@@ -7195,18 +7483,18 @@ async function rollbackToCheckpoint(label) {
7195
7483
  function listCheckpoints() {
7196
7484
  try {
7197
7485
  const root = getProjectRoot();
7198
- const cpDir = path21.join(root, CHECKPOINT_DIR);
7199
- if (!fs21.existsSync(cpDir)) {
7486
+ const cpDir = path22.join(root, CHECKPOINT_DIR);
7487
+ if (!fs22.existsSync(cpDir)) {
7200
7488
  return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
7201
7489
  }
7202
- const entries = fs21.readdirSync(cpDir);
7490
+ const entries = fs22.readdirSync(cpDir);
7203
7491
  const checkpoints = [];
7204
7492
  for (const entry of entries) {
7205
- const manifestPath = path21.join(cpDir, entry, "manifest.json");
7206
- if (fs21.existsSync(manifestPath)) {
7493
+ const manifestPath = path22.join(cpDir, entry, "manifest.json");
7494
+ if (fs22.existsSync(manifestPath)) {
7207
7495
  try {
7208
7496
  const manifest = JSON.parse(
7209
- fs21.readFileSync(manifestPath, "utf-8")
7497
+ fs22.readFileSync(manifestPath, "utf-8")
7210
7498
  );
7211
7499
  checkpoints.push({ label: entry, manifest });
7212
7500
  } catch {
@@ -7238,12 +7526,12 @@ function listCheckpoints() {
7238
7526
  function pruneCheckpoints(keep = 5) {
7239
7527
  try {
7240
7528
  const root = getProjectRoot();
7241
- const cpDir = path21.join(root, CHECKPOINT_DIR);
7242
- if (!fs21.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
7243
- const entries = fs21.readdirSync(cpDir).map((e) => ({ name: e, time: fs21.statSync(path21.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
7529
+ const cpDir = path22.join(root, CHECKPOINT_DIR);
7530
+ if (!fs22.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
7531
+ const entries = fs22.readdirSync(cpDir).map((e) => ({ name: e, time: fs22.statSync(path22.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
7244
7532
  let removed = 0;
7245
7533
  for (let i = keep; i < entries.length; i++) {
7246
- fs21.rmSync(path21.join(cpDir, entries[i].name), { recursive: true, force: true });
7534
+ fs22.rmSync(path22.join(cpDir, entries[i].name), { recursive: true, force: true });
7247
7535
  removed++;
7248
7536
  }
7249
7537
  return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
@@ -7283,18 +7571,18 @@ __export(kumaContractEngine_exports, {
7283
7571
  loadContracts: () => loadContracts,
7284
7572
  runContractChecks: () => runContractChecks
7285
7573
  });
7286
- import fs22 from "fs";
7287
- import path22 from "path";
7574
+ import fs23 from "fs";
7575
+ import path23 from "path";
7288
7576
  function contractsDir() {
7289
- return path22.join(getProjectRoot(), ".kuma", "contracts");
7577
+ return path23.join(getProjectRoot(), ".kuma", "contracts");
7290
7578
  }
7291
7579
  function ensureContractsDir() {
7292
7580
  const dir = contractsDir();
7293
- if (!fs22.existsSync(dir)) {
7294
- fs22.mkdirSync(dir, { recursive: true });
7295
- const defaultPath = path22.join(dir, "default.json");
7296
- if (!fs22.existsSync(defaultPath)) {
7297
- fs22.writeFileSync(
7581
+ if (!fs23.existsSync(dir)) {
7582
+ fs23.mkdirSync(dir, { recursive: true });
7583
+ const defaultPath = path23.join(dir, "default.json");
7584
+ if (!fs23.existsSync(defaultPath)) {
7585
+ fs23.writeFileSync(
7298
7586
  defaultPath,
7299
7587
  JSON.stringify(DEFAULT_CONTRACTS, null, 2),
7300
7588
  "utf-8"
@@ -7307,10 +7595,10 @@ function loadContracts() {
7307
7595
  const dir = contractsDir();
7308
7596
  const contracts = [];
7309
7597
  try {
7310
- const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".json"));
7598
+ const files = fs23.readdirSync(dir).filter((f) => f.endsWith(".json"));
7311
7599
  for (const file of files) {
7312
7600
  try {
7313
- const content = fs22.readFileSync(path22.join(dir, file), "utf-8");
7601
+ const content = fs23.readFileSync(path23.join(dir, file), "utf-8");
7314
7602
  const config = JSON.parse(content);
7315
7603
  contracts.push(config);
7316
7604
  } catch (err) {
@@ -7383,7 +7671,7 @@ function evaluateCondition(condition, contextPath) {
7383
7671
  switch (condition.type) {
7384
7672
  case "file_exists": {
7385
7673
  const targetPath = resolveTarget(condition.target, contextPath, root);
7386
- const exists = targetPath.some((p) => fs22.existsSync(p));
7674
+ const exists = targetPath.some((p) => fs23.existsSync(p));
7387
7675
  return {
7388
7676
  passed: exists,
7389
7677
  message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
@@ -7391,7 +7679,7 @@ function evaluateCondition(condition, contextPath) {
7391
7679
  }
7392
7680
  case "file_not_exists": {
7393
7681
  const targetPath = resolveTarget(condition.target, contextPath, root);
7394
- const exists = targetPath.some((p) => fs22.existsSync(p));
7682
+ const exists = targetPath.some((p) => fs23.existsSync(p));
7395
7683
  return {
7396
7684
  passed: !exists,
7397
7685
  message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
@@ -7399,9 +7687,9 @@ function evaluateCondition(condition, contextPath) {
7399
7687
  }
7400
7688
  case "contains": {
7401
7689
  if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
7402
- const fullPath = path22.resolve(root, contextPath);
7403
- if (!fs22.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
7404
- const content = fs22.readFileSync(fullPath, "utf-8");
7690
+ const fullPath = path23.resolve(root, contextPath);
7691
+ if (!fs23.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
7692
+ const content = fs23.readFileSync(fullPath, "utf-8");
7405
7693
  const hasContent = condition.value ? content.includes(condition.value) : false;
7406
7694
  return {
7407
7695
  passed: hasContent,
@@ -7413,12 +7701,12 @@ function evaluateCondition(condition, contextPath) {
7413
7701
  const root2 = getProjectRoot();
7414
7702
  const targets = resolveTarget(condition.target, contextPath, root2);
7415
7703
  for (const t of targets) {
7416
- if (fs22.existsSync(t)) {
7417
- const content = fs22.readFileSync(t, "utf-8");
7704
+ if (fs23.existsSync(t)) {
7705
+ const content = fs23.readFileSync(t, "utf-8");
7418
7706
  if (condition.value && content.includes(condition.value)) {
7419
7707
  return {
7420
7708
  passed: false,
7421
- message: `\u274C Found forbidden pattern "${condition.value}" in ${path22.relative(root2, t)} (${condition.description})`
7709
+ message: `\u274C Found forbidden pattern "${condition.value}" in ${path23.relative(root2, t)} (${condition.description})`
7422
7710
  };
7423
7711
  }
7424
7712
  }
@@ -7427,15 +7715,15 @@ function evaluateCondition(condition, contextPath) {
7427
7715
  }
7428
7716
  case "has_test_file": {
7429
7717
  if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
7430
- const basename = path22.basename(contextPath, path22.extname(contextPath));
7431
- const dir = path22.dirname(contextPath);
7718
+ const basename = path23.basename(contextPath, path23.extname(contextPath));
7719
+ const dir = path23.dirname(contextPath);
7432
7720
  const possibleTests = [
7433
- path22.join(dir, `${basename}.test.ts`),
7434
- path22.join(dir, `${basename}.spec.ts`),
7435
- path22.join(dir, `__tests__/${basename}.test.ts`),
7436
- path22.join(dir, `__tests__/${basename}.spec.ts`)
7721
+ path23.join(dir, `${basename}.test.ts`),
7722
+ path23.join(dir, `${basename}.spec.ts`),
7723
+ path23.join(dir, `__tests__/${basename}.test.ts`),
7724
+ path23.join(dir, `__tests__/${basename}.spec.ts`)
7437
7725
  ];
7438
- const hasTest = possibleTests.some((t) => fs22.existsSync(t));
7726
+ const hasTest = possibleTests.some((t) => fs23.existsSync(t));
7439
7727
  return {
7440
7728
  passed: hasTest,
7441
7729
  message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
@@ -7459,18 +7747,18 @@ function resolveTarget(pattern, contextPath, root) {
7459
7747
  const r = root || getProjectRoot();
7460
7748
  if (pattern.startsWith("*")) {
7461
7749
  if (contextPath) {
7462
- const dir = path22.dirname(path22.resolve(r, contextPath));
7750
+ const dir = path23.dirname(path23.resolve(r, contextPath));
7463
7751
  const suffix = pattern.replace("*", "");
7464
7752
  try {
7465
- const files = fs22.readdirSync(dir);
7466
- return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path22.join(dir, f));
7753
+ const files = fs23.readdirSync(dir);
7754
+ return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path23.join(dir, f));
7467
7755
  } catch {
7468
- return [path22.join(dir, pattern.replace("*", ""))];
7756
+ return [path23.join(dir, pattern.replace("*", ""))];
7469
7757
  }
7470
7758
  }
7471
7759
  return [];
7472
7760
  }
7473
- return [path22.resolve(r, pattern)];
7761
+ return [path23.resolve(r, pattern)];
7474
7762
  }
7475
7763
  async function runContractChecks(toolName, filePath, phase = "pre") {
7476
7764
  const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
@@ -7591,37 +7879,37 @@ __export(kumaVerifier_exports, {
7591
7879
  getRunningVerificationPid: () => getRunningVerificationPid,
7592
7880
  runAutoVerification: () => runAutoVerification
7593
7881
  });
7594
- import fs23 from "fs";
7595
- import path23 from "path";
7882
+ import fs24 from "fs";
7883
+ import path24 from "path";
7596
7884
  import { exec } from "child_process";
7597
7885
  function getLockDir(root = process.cwd()) {
7598
- return path23.resolve(root, LOCK_DIR_NAME);
7886
+ return path24.resolve(root, LOCK_DIR_NAME);
7599
7887
  }
7600
7888
  function getPidFile(root = process.cwd()) {
7601
- return path23.join(getLockDir(root), "pid");
7889
+ return path24.join(getLockDir(root), "pid");
7602
7890
  }
7603
7891
  function acquireFileLock(root) {
7604
7892
  const lockDir = getLockDir(root);
7605
7893
  try {
7606
- fs23.mkdirSync(lockDir, { recursive: false });
7607
- fs23.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7894
+ fs24.mkdirSync(lockDir, { recursive: false });
7895
+ fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7608
7896
  return true;
7609
7897
  } catch {
7610
7898
  try {
7611
- if (fs23.existsSync(getPidFile(root))) {
7612
- const pid = parseInt(fs23.readFileSync(getPidFile(root), "utf-8"), 10);
7899
+ if (fs24.existsSync(getPidFile(root))) {
7900
+ const pid = parseInt(fs24.readFileSync(getPidFile(root), "utf-8"), 10);
7613
7901
  try {
7614
7902
  process.kill(pid, 0);
7615
7903
  return false;
7616
7904
  } catch {
7617
- fs23.rmSync(lockDir, { recursive: true, force: true });
7905
+ fs24.rmSync(lockDir, { recursive: true, force: true });
7618
7906
  return acquireFileLock(root);
7619
7907
  }
7620
7908
  }
7621
7909
  } catch {
7622
7910
  }
7623
7911
  try {
7624
- fs23.rmSync(lockDir, { recursive: true, force: true });
7912
+ fs24.rmSync(lockDir, { recursive: true, force: true });
7625
7913
  return acquireFileLock(root);
7626
7914
  } catch {
7627
7915
  return false;
@@ -7630,7 +7918,7 @@ function acquireFileLock(root) {
7630
7918
  }
7631
7919
  function releaseFileLock(root) {
7632
7920
  try {
7633
- fs23.rmSync(getLockDir(root), { recursive: true, force: true });
7921
+ fs24.rmSync(getLockDir(root), { recursive: true, force: true });
7634
7922
  } catch {
7635
7923
  }
7636
7924
  }
@@ -7653,36 +7941,36 @@ function getRunningVerificationPid() {
7653
7941
  return _currentProcess?.pid ?? null;
7654
7942
  }
7655
7943
  function detectTestRunner(root = process.cwd()) {
7656
- const pkgPath = path23.join(root, "package.json");
7657
- if (fs23.existsSync(pkgPath)) {
7944
+ const pkgPath = path24.join(root, "package.json");
7945
+ if (fs24.existsSync(pkgPath)) {
7658
7946
  try {
7659
- const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf-8"));
7947
+ const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
7660
7948
  if (pkg.scripts && pkg.scripts.test) {
7661
- if (fs23.existsSync(path23.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
7662
- if (fs23.existsSync(path23.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
7949
+ if (fs24.existsSync(path24.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
7950
+ if (fs24.existsSync(path24.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
7663
7951
  return { runner: "npm", baseCommand: "npm test" };
7664
7952
  }
7665
7953
  } catch {
7666
7954
  }
7667
7955
  }
7668
- if (fs23.existsSync(path23.join(root, "pytest.ini")) || fs23.existsSync(path23.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
7669
- if (fs23.existsSync(path23.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
7670
- if (fs23.existsSync(path23.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
7671
- if (fs23.existsSync(path23.join(root, "Makefile"))) {
7956
+ if (fs24.existsSync(path24.join(root, "pytest.ini")) || fs24.existsSync(path24.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
7957
+ if (fs24.existsSync(path24.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
7958
+ if (fs24.existsSync(path24.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
7959
+ if (fs24.existsSync(path24.join(root, "Makefile"))) {
7672
7960
  try {
7673
- const makefileContent = fs23.readFileSync(path23.join(root, "Makefile"), "utf-8");
7961
+ const makefileContent = fs24.readFileSync(path24.join(root, "Makefile"), "utf-8");
7674
7962
  const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
7675
7963
  if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
7676
7964
  } catch {
7677
7965
  }
7678
7966
  }
7679
- if (fs23.existsSync(path23.join(root, "package.json"))) {
7967
+ if (fs24.existsSync(path24.join(root, "package.json"))) {
7680
7968
  try {
7681
- const pkg = JSON.parse(fs23.readFileSync(path23.join(root, "package.json"), "utf-8"));
7969
+ const pkg = JSON.parse(fs24.readFileSync(path24.join(root, "package.json"), "utf-8"));
7682
7970
  if (pkg.dependencies || pkg.devDependencies) {
7683
- if (fs23.existsSync(path23.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
7684
- const srcDir = path23.join(root, "src");
7685
- if (fs23.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
7971
+ if (fs24.existsSync(path24.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
7972
+ const srcDir = path24.join(root, "src");
7973
+ if (fs24.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
7686
7974
  }
7687
7975
  } catch {
7688
7976
  }
@@ -7782,7 +8070,7 @@ async function runAutoVerification(options = {}) {
7782
8070
  const heartbeatTimer = setInterval(() => {
7783
8071
  if (getLockDir(root)) {
7784
8072
  try {
7785
- fs23.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8073
+ fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7786
8074
  } catch {
7787
8075
  }
7788
8076
  }
@@ -7859,8 +8147,8 @@ __export(init_exports, {
7859
8147
  generateInitMdContent: () => generateInitMdContent,
7860
8148
  runInit: () => runInit
7861
8149
  });
7862
- import fs24 from "fs";
7863
- import path24 from "path";
8150
+ import fs25 from "fs";
8151
+ import path25 from "path";
7864
8152
  function configFilePath(type) {
7865
8153
  switch (type) {
7866
8154
  case "claude":
@@ -8431,12 +8719,14 @@ function generateInitMdContent() {
8431
8719
  " --- EDIT/READ (use native tools) ---",
8432
8720
  " 5. kuma_memory \u2192 research_save \u2014 After exploring area (creates search cache)",
8433
8721
  " 6. kuma_memory \u2192 gotcha \u2014 \u{1F525} IMMEDIATELY when bug found (exponential)",
8434
- " 7. kuma_memory \u2192 arch_flow \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential)",
8435
- " 8. kuma_memory \u2192 decision \u2014 When choosing (preserves rationale)",
8436
- " 9. kuma_safety \u2192 verify \u2014 Run tests / confirm nothing broken",
8437
- " 10. kuma_context \u2192 changes \u2014 Review session activity",
8722
+ " 7. kuma_memory \u2192 arch_flow \u2014 After tracing COMPLETE flow (max 5 core files)",
8723
+ " 8. kuma_memory \u2192 feature \u2014 \u2B50 When discovering a module/feature boundary (Authentication, Billing)",
8724
+ " 9. kuma_memory \u2192 decision \u2014 When choosing (preserves rationale)",
8725
+ " 10. kuma_safety \u2192 verify \u2014 Run tests / confirm nothing broken",
8726
+ " 11. kuma_context \u2192 changes \u2014 Review session activity",
8438
8727
  "",
8439
8728
  "\u{1F525} `arch_flow` + `gotcha` are EXPONENTIAL \u2014 each record saves 5-10 files next session.",
8729
+ "\u2B50 `feature` captures module ownership \u2014 use when you identify a cohesive feature boundary.",
8440
8730
  "\u{1F7E2} SKIP recording function/class/component nodes \u2014 grep/glob is faster.",
8441
8731
  "\u{1F9E0} Graph is persistent \u2014 nodes/edges accumulate across sessions.",
8442
8732
  "",
@@ -8546,10 +8836,10 @@ function generateInitMdContent() {
8546
8836
  ].join("\n");
8547
8837
  }
8548
8838
  function handleOpencodeSecondary(root, results) {
8549
- const skillPath = path24.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
8839
+ const skillPath = path25.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
8550
8840
  if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
8551
8841
  try {
8552
- const dir = path24.dirname(skillPath);
8842
+ const dir = path25.dirname(skillPath);
8553
8843
  const content = [
8554
8844
  "---",
8555
8845
  "name: kuma-mcp",
@@ -8568,7 +8858,7 @@ function handleOpencodeSecondary(root, results) {
8568
8858
  " 4. *(edit/read using native tools)*",
8569
8859
  ' 5. `kuma_kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
8570
8860
  ' 6. `kuma_kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential)',
8571
- ' 7. `kuma_kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential)',
8861
+ ' 7. `kuma_kuma_memory({ action: "arch_flow" })` \u2014 After tracing COMPLETE flow, max 5 core files',
8572
8862
  ' 8. `kuma_kuma_memory({ action: "decision" })` \u2014 When choosing (preserves rationale)',
8573
8863
  ' 9. `kuma_kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
8574
8864
  ' 10. `kuma_kuma_context({ action: "changes" })` \u2014 Review session activity',
@@ -8582,18 +8872,18 @@ function handleOpencodeSecondary(root, results) {
8582
8872
  "",
8583
8873
  "\u{1F4D6} Full rules: `.kuma/init.md`"
8584
8874
  ].join("\n");
8585
- if (fs24.existsSync(skillPath)) {
8586
- const existing = fs24.readFileSync(skillPath, "utf-8");
8875
+ if (fs25.existsSync(skillPath)) {
8876
+ const existing = fs25.readFileSync(skillPath, "utf-8");
8587
8877
  if (existing.includes("kuma-mcp")) {
8588
8878
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
8589
8879
  return;
8590
8880
  }
8591
8881
  const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
8592
- fs24.writeFileSync(skillPath, newContent, "utf-8");
8882
+ fs25.writeFileSync(skillPath, newContent, "utf-8");
8593
8883
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
8594
8884
  } else {
8595
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8596
- fs24.writeFileSync(skillPath, content, "utf-8");
8885
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8886
+ fs25.writeFileSync(skillPath, content, "utf-8");
8597
8887
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
8598
8888
  }
8599
8889
  } catch (err) {
@@ -8606,21 +8896,21 @@ function handleOpencodeSecondary(root, results) {
8606
8896
  }
8607
8897
  }
8608
8898
  function handleCodexSecondary(root, results) {
8609
- const tomlPath = path24.resolve(root, ".codex/config.toml");
8899
+ const tomlPath = path25.resolve(root, ".codex/config.toml");
8610
8900
  if (results.some((r) => r.filePath === ".codex/config.toml")) return;
8611
8901
  try {
8612
- const dir = path24.dirname(tomlPath);
8613
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8614
- if (fs24.existsSync(tomlPath)) {
8615
- const existingContent = fs24.readFileSync(tomlPath, "utf-8");
8902
+ const dir = path25.dirname(tomlPath);
8903
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8904
+ if (fs25.existsSync(tomlPath)) {
8905
+ const existingContent = fs25.readFileSync(tomlPath, "utf-8");
8616
8906
  if (existingContent.includes("kuma")) {
8617
8907
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
8618
8908
  return;
8619
8909
  }
8620
- fs24.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
8910
+ fs25.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
8621
8911
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
8622
8912
  } else {
8623
- fs24.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
8913
+ fs25.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
8624
8914
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
8625
8915
  }
8626
8916
  } catch (err) {
@@ -8633,11 +8923,11 @@ function handleCodexSecondary(root, results) {
8633
8923
  }
8634
8924
  }
8635
8925
  function handleQwenSecondary(root, results) {
8636
- const settingsPath = path24.resolve(root, "settings.json");
8926
+ const settingsPath = path25.resolve(root, "settings.json");
8637
8927
  if (results.some((r) => r.filePath === "settings.json")) return;
8638
8928
  try {
8639
- if (fs24.existsSync(settingsPath)) {
8640
- const existingContent = fs24.readFileSync(settingsPath, "utf-8");
8929
+ if (fs25.existsSync(settingsPath)) {
8930
+ const existingContent = fs25.readFileSync(settingsPath, "utf-8");
8641
8931
  if (existingContent.includes("kuma")) {
8642
8932
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
8643
8933
  try {
@@ -8645,7 +8935,7 @@ function handleQwenSecondary(root, results) {
8645
8935
  parsed.mcpServers = parsed.mcpServers || {};
8646
8936
  if (!parsed.mcpServers.kuma) {
8647
8937
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8648
- fs24.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8938
+ fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8649
8939
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
8650
8940
  return;
8651
8941
  }
@@ -8660,13 +8950,13 @@ function handleQwenSecondary(root, results) {
8660
8950
  parsed.mcpServers = parsed.mcpServers || {};
8661
8951
  if (!parsed.mcpServers.kuma) {
8662
8952
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8663
- fs24.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8953
+ fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8664
8954
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
8665
8955
  }
8666
8956
  } catch {
8667
8957
  }
8668
8958
  } else {
8669
- fs24.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
8959
+ fs25.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
8670
8960
  results.push({ type: "qwen", filePath: "settings.json", action: "created" });
8671
8961
  }
8672
8962
  } catch (err) {
@@ -8699,7 +8989,7 @@ function getCombinedAgentsMd(selectedTypes) {
8699
8989
  " 4. *(edit/read using native tools)*",
8700
8990
  ' 5. `[kuma_]memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
8701
8991
  ' 6. `[kuma_]memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential)',
8702
- ' 7. `[kuma_]memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential)',
8992
+ ' 7. `[kuma_]memory({ action: "arch_flow" })` \u2014 After tracing COMPLETE flow (max 5 core files)',
8703
8993
  ' 8. `[kuma_]memory({ action: "decision" })` \u2014 When choosing (preserves rationale)',
8704
8994
  ' 9. `[kuma_]safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
8705
8995
  ' 10. `[kuma_]context({ action: "changes" })` \u2014 Review session activity',
@@ -8769,18 +9059,18 @@ function getCombinedAgentsMd(selectedTypes) {
8769
9059
  return lines.join("\n");
8770
9060
  }
8771
9061
  function handleAntigravityMcpConfig(root, results) {
8772
- const mcpPath = path24.resolve(root, ".agents/mcp_config.json");
9062
+ const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
8773
9063
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
8774
9064
  try {
8775
- const mcpDir = path24.dirname(mcpPath);
8776
- if (fs24.existsSync(mcpPath)) {
8777
- const existingContent = fs24.readFileSync(mcpPath, "utf-8");
9065
+ const mcpDir = path25.dirname(mcpPath);
9066
+ if (fs25.existsSync(mcpPath)) {
9067
+ const existingContent = fs25.readFileSync(mcpPath, "utf-8");
8778
9068
  if (existingContent.includes("kuma")) {
8779
9069
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
8780
9070
  const trimmed = existingContent.trimEnd();
8781
9071
  if (trimmed.endsWith("}")) {
8782
9072
  const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
8783
- fs24.writeFileSync(mcpPath, updated, "utf-8");
9073
+ fs25.writeFileSync(mcpPath, updated, "utf-8");
8784
9074
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
8785
9075
  }
8786
9076
  }
@@ -8789,11 +9079,11 @@ function handleAntigravityMcpConfig(root, results) {
8789
9079
  const parsed = JSON.parse(existingContent);
8790
9080
  parsed.mcpServers = parsed.mcpServers || {};
8791
9081
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8792
- fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9082
+ fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8793
9083
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
8794
9084
  } else {
8795
- if (!fs24.existsSync(mcpDir)) fs24.mkdirSync(mcpDir, { recursive: true });
8796
- fs24.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9085
+ if (!fs25.existsSync(mcpDir)) fs25.mkdirSync(mcpDir, { recursive: true });
9086
+ fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8797
9087
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
8798
9088
  }
8799
9089
  } catch (err) {
@@ -8806,18 +9096,18 @@ function handleAntigravityMcpConfig(root, results) {
8806
9096
  }
8807
9097
  }
8808
9098
  function handleAiderSecondary(root, results) {
8809
- const ymlPath = path24.resolve(root, ".aider.conf.yml");
9099
+ const ymlPath = path25.resolve(root, ".aider.conf.yml");
8810
9100
  if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
8811
9101
  try {
8812
9102
  const conventionsRef = "read: CONVENTIONS.md";
8813
- if (fs24.existsSync(ymlPath)) {
8814
- const existingContent = fs24.readFileSync(ymlPath, "utf-8");
9103
+ if (fs25.existsSync(ymlPath)) {
9104
+ const existingContent = fs25.readFileSync(ymlPath, "utf-8");
8815
9105
  if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
8816
9106
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
8817
9107
  return;
8818
9108
  }
8819
9109
  const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
8820
- fs24.writeFileSync(ymlPath, newContent, "utf-8");
9110
+ fs25.writeFileSync(ymlPath, newContent, "utf-8");
8821
9111
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
8822
9112
  } else {
8823
9113
  const content = [
@@ -8827,7 +9117,7 @@ function handleAiderSecondary(root, results) {
8827
9117
  conventionsRef,
8828
9118
  ""
8829
9119
  ].join("\n");
8830
- fs24.writeFileSync(ymlPath, content, "utf-8");
9120
+ fs25.writeFileSync(ymlPath, content, "utf-8");
8831
9121
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
8832
9122
  }
8833
9123
  } catch (err) {
@@ -8840,10 +9130,10 @@ function handleAiderSecondary(root, results) {
8840
9130
  }
8841
9131
  }
8842
9132
  function handleCopilotSecondary(root, results) {
8843
- const skillPath = path24.resolve(root, ".github/skills/kuma/SKILL.md");
9133
+ const skillPath = path25.resolve(root, ".github/skills/kuma/SKILL.md");
8844
9134
  if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
8845
9135
  try {
8846
- const dir = path24.dirname(skillPath);
9136
+ const dir = path25.dirname(skillPath);
8847
9137
  const content = [
8848
9138
  "---",
8849
9139
  "name: kuma-mcp",
@@ -8854,17 +9144,17 @@ function handleCopilotSecondary(root, results) {
8854
9144
  "",
8855
9145
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules."
8856
9146
  ].join("\n");
8857
- if (fs24.existsSync(skillPath)) {
8858
- const existingContent = fs24.readFileSync(skillPath, "utf-8");
9147
+ if (fs25.existsSync(skillPath)) {
9148
+ const existingContent = fs25.readFileSync(skillPath, "utf-8");
8859
9149
  if (existingContent.includes("kuma")) {
8860
9150
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
8861
9151
  return;
8862
9152
  }
8863
- fs24.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
9153
+ fs25.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
8864
9154
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
8865
9155
  } else {
8866
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8867
- fs24.writeFileSync(skillPath, content, "utf-8");
9156
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9157
+ fs25.writeFileSync(skillPath, content, "utf-8");
8868
9158
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
8869
9159
  }
8870
9160
  } catch (err) {
@@ -8877,21 +9167,21 @@ function handleCopilotSecondary(root, results) {
8877
9167
  }
8878
9168
  }
8879
9169
  function handleOpenclawSecondary(root, results) {
8880
- const mcpPath = path24.resolve(root, ".agents/mcp_config.json");
9170
+ const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
8881
9171
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
8882
9172
  try {
8883
- const dir = path24.dirname(mcpPath);
8884
- if (fs24.existsSync(mcpPath)) {
8885
- const existingContent = fs24.readFileSync(mcpPath, "utf-8");
9173
+ const dir = path25.dirname(mcpPath);
9174
+ if (fs25.existsSync(mcpPath)) {
9175
+ const existingContent = fs25.readFileSync(mcpPath, "utf-8");
8886
9176
  if (existingContent.includes("kuma")) return;
8887
9177
  const parsed = JSON.parse(existingContent);
8888
9178
  parsed.mcpServers = parsed.mcpServers || {};
8889
9179
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8890
- fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9180
+ fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8891
9181
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
8892
9182
  } else {
8893
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8894
- fs24.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9183
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9184
+ fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8895
9185
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
8896
9186
  }
8897
9187
  } catch (err) {
@@ -8904,7 +9194,7 @@ function handleOpenclawSecondary(root, results) {
8904
9194
  }
8905
9195
  }
8906
9196
  function handleQuickrefGeneration(root, results) {
8907
- const quickrefPath = path24.resolve(root, ".kuma/quickref.md");
9197
+ const quickrefPath = path25.resolve(root, ".kuma/quickref.md");
8908
9198
  const content = [
8909
9199
  "# Kuma Quick Reference",
8910
9200
  "",
@@ -8929,16 +9219,16 @@ function handleQuickrefGeneration(root, results) {
8929
9219
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
8930
9220
  ].join("\n");
8931
9221
  try {
8932
- const kumaDir = path24.dirname(quickrefPath);
8933
- if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
8934
- if (fs24.existsSync(quickrefPath)) {
8935
- const existing = fs24.readFileSync(quickrefPath, "utf-8");
9222
+ const kumaDir = path25.dirname(quickrefPath);
9223
+ if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
9224
+ if (fs25.existsSync(quickrefPath)) {
9225
+ const existing = fs25.readFileSync(quickrefPath, "utf-8");
8936
9226
  if (existing.includes("_Generated by Kuma MCP_")) {
8937
9227
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
8938
9228
  return;
8939
9229
  }
8940
9230
  }
8941
- fs24.writeFileSync(quickrefPath, content, "utf-8");
9231
+ fs25.writeFileSync(quickrefPath, content, "utf-8");
8942
9232
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
8943
9233
  } catch (err) {
8944
9234
  results.push({
@@ -8950,7 +9240,7 @@ function handleQuickrefGeneration(root, results) {
8950
9240
  }
8951
9241
  }
8952
9242
  function handleModeMdGeneration(root, results) {
8953
- const filePath = path24.resolve(root, ".kuma/MODE.md");
9243
+ const filePath = path25.resolve(root, ".kuma/MODE.md");
8954
9244
  const content = [
8955
9245
  "# Adaptive Mode Selector",
8956
9246
  "",
@@ -8993,10 +9283,10 @@ function handleModeMdGeneration(root, results) {
8993
9283
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
8994
9284
  ].join("\n");
8995
9285
  try {
8996
- const dir = path24.dirname(filePath);
8997
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8998
- if (!fs24.existsSync(filePath)) {
8999
- fs24.writeFileSync(filePath, content, "utf-8");
9286
+ const dir = path25.dirname(filePath);
9287
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9288
+ if (!fs25.existsSync(filePath)) {
9289
+ fs25.writeFileSync(filePath, content, "utf-8");
9000
9290
  results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
9001
9291
  }
9002
9292
  } catch (err) {
@@ -9004,7 +9294,7 @@ function handleModeMdGeneration(root, results) {
9004
9294
  }
9005
9295
  }
9006
9296
  function handleSkipRulesMdGeneration(root, results) {
9007
- const filePath = path24.resolve(root, ".kuma/SKIP_RULES.md");
9297
+ const filePath = path25.resolve(root, ".kuma/SKIP_RULES.md");
9008
9298
  const content = [
9009
9299
  "# Skip Rules \u2014 What NOT to Record",
9010
9300
  "",
@@ -9041,10 +9331,10 @@ function handleSkipRulesMdGeneration(root, results) {
9041
9331
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9042
9332
  ].join("\n");
9043
9333
  try {
9044
- const dir = path24.dirname(filePath);
9045
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
9046
- if (!fs24.existsSync(filePath)) {
9047
- fs24.writeFileSync(filePath, content, "utf-8");
9334
+ const dir = path25.dirname(filePath);
9335
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9336
+ if (!fs25.existsSync(filePath)) {
9337
+ fs25.writeFileSync(filePath, content, "utf-8");
9048
9338
  results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
9049
9339
  }
9050
9340
  } catch (err) {
@@ -9052,7 +9342,7 @@ function handleSkipRulesMdGeneration(root, results) {
9052
9342
  }
9053
9343
  }
9054
9344
  function handleStalenessMdGeneration(root, results) {
9055
- const filePath = path24.resolve(root, ".kuma/STALENESS.md");
9345
+ const filePath = path25.resolve(root, ".kuma/STALENESS.md");
9056
9346
  const content = [
9057
9347
  "# Auto-Staleness Detection",
9058
9348
  "",
@@ -9083,10 +9373,10 @@ function handleStalenessMdGeneration(root, results) {
9083
9373
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9084
9374
  ].join("\n");
9085
9375
  try {
9086
- const dir = path24.dirname(filePath);
9087
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
9088
- if (!fs24.existsSync(filePath)) {
9089
- fs24.writeFileSync(filePath, content, "utf-8");
9376
+ const dir = path25.dirname(filePath);
9377
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9378
+ if (!fs25.existsSync(filePath)) {
9379
+ fs25.writeFileSync(filePath, content, "utf-8");
9090
9380
  results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
9091
9381
  }
9092
9382
  } catch (err) {
@@ -9094,20 +9384,20 @@ function handleStalenessMdGeneration(root, results) {
9094
9384
  }
9095
9385
  }
9096
9386
  function handleInitMdGeneration(root, results) {
9097
- const initMdPath = path24.resolve(root, ".kuma/init.md");
9387
+ const initMdPath = path25.resolve(root, ".kuma/init.md");
9098
9388
  try {
9099
- const kumaDir = path24.dirname(initMdPath);
9100
- if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
9101
- if (fs24.existsSync(initMdPath)) {
9102
- const existing = fs24.readFileSync(initMdPath, "utf-8");
9389
+ const kumaDir = path25.dirname(initMdPath);
9390
+ if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
9391
+ if (fs25.existsSync(initMdPath)) {
9392
+ const existing = fs25.readFileSync(initMdPath, "utf-8");
9103
9393
  if (existing.includes("_Generated by Kuma MCP_")) {
9104
9394
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
9105
9395
  return;
9106
9396
  }
9107
- fs24.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
9397
+ fs25.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
9108
9398
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
9109
9399
  } else {
9110
- fs24.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
9400
+ fs25.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
9111
9401
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
9112
9402
  }
9113
9403
  } catch (err) {
@@ -9124,20 +9414,20 @@ function handleInitMdGeneration(root, results) {
9124
9414
  handleStalenessMdGeneration(root, results);
9125
9415
  }
9126
9416
  function handleCodewhaleSecondary(root, results) {
9127
- const mcpPath = path24.resolve(root, ".codewhale/mcp.json");
9417
+ const mcpPath = path25.resolve(root, ".codewhale/mcp.json");
9128
9418
  if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
9129
9419
  try {
9130
- const dir = path24.dirname(mcpPath);
9131
- if (fs24.existsSync(mcpPath)) {
9132
- const existingContent = fs24.readFileSync(mcpPath, "utf-8");
9420
+ const dir = path25.dirname(mcpPath);
9421
+ if (fs25.existsSync(mcpPath)) {
9422
+ const existingContent = fs25.readFileSync(mcpPath, "utf-8");
9133
9423
  if (existingContent.includes("kuma")) return;
9134
9424
  const parsed = JSON.parse(existingContent);
9135
9425
  parsed.mcpServers = parsed.mcpServers || {};
9136
9426
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9137
- fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9427
+ fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9138
9428
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
9139
9429
  } else {
9140
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
9430
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9141
9431
  const config = {
9142
9432
  mcpServers: {
9143
9433
  kuma: {
@@ -9147,7 +9437,7 @@ function handleCodewhaleSecondary(root, results) {
9147
9437
  }
9148
9438
  }
9149
9439
  };
9150
- fs24.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9440
+ fs25.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9151
9441
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
9152
9442
  }
9153
9443
  } catch (err) {
@@ -9169,28 +9459,28 @@ function runInit(options) {
9169
9459
  let agentsMdHandled = false;
9170
9460
  for (const type of selected) {
9171
9461
  const relativePath = configFilePath(type);
9172
- const fullPath = path24.resolve(root, relativePath);
9462
+ const fullPath = path25.resolve(root, relativePath);
9173
9463
  const getTemplate = TEMPLATES[type];
9174
9464
  try {
9175
9465
  if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
9176
9466
  agentsMdHandled = true;
9177
9467
  const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
9178
- if (fs24.existsSync(fullPath)) {
9468
+ if (fs25.existsSync(fullPath)) {
9179
9469
  if (options.skipExisting) {
9180
9470
  results.push({ type, filePath: relativePath, action: "skipped" });
9181
9471
  } else {
9182
- const existingContent = fs24.readFileSync(fullPath, "utf-8");
9472
+ const existingContent = fs25.readFileSync(fullPath, "utf-8");
9183
9473
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9184
9474
  results.push({ type, filePath: relativePath, action: "skipped" });
9185
9475
  } else {
9186
- fs24.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
9476
+ fs25.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
9187
9477
  results.push({ type, filePath: relativePath, action: "appended" });
9188
9478
  }
9189
9479
  }
9190
9480
  } else {
9191
- const dir = path24.dirname(fullPath);
9192
- if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
9193
- fs24.writeFileSync(fullPath, combinedContent, "utf-8");
9481
+ const dir = path25.dirname(fullPath);
9482
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9483
+ fs25.writeFileSync(fullPath, combinedContent, "utf-8");
9194
9484
  results.push({ type, filePath: relativePath, action: "created" });
9195
9485
  }
9196
9486
  if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
@@ -9202,12 +9492,12 @@ function runInit(options) {
9202
9492
  continue;
9203
9493
  } else {
9204
9494
  const template = getTemplate();
9205
- if (fs24.existsSync(fullPath)) {
9495
+ if (fs25.existsSync(fullPath)) {
9206
9496
  if (options.skipExisting) {
9207
9497
  results.push({ type, filePath: relativePath, action: "skipped" });
9208
9498
  continue;
9209
9499
  }
9210
- const existingContent = fs24.readFileSync(fullPath, "utf-8");
9500
+ const existingContent = fs25.readFileSync(fullPath, "utf-8");
9211
9501
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9212
9502
  if (type === "antigravity") {
9213
9503
  handleAntigravityMcpConfig(root, results);
@@ -9220,14 +9510,14 @@ function runInit(options) {
9220
9510
  continue;
9221
9511
  }
9222
9512
  const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
9223
- fs24.writeFileSync(fullPath, newContent, "utf-8");
9513
+ fs25.writeFileSync(fullPath, newContent, "utf-8");
9224
9514
  results.push({ type, filePath: relativePath, action: "appended" });
9225
9515
  } else {
9226
- const dir = path24.dirname(fullPath);
9227
- if (!fs24.existsSync(dir)) {
9228
- fs24.mkdirSync(dir, { recursive: true });
9516
+ const dir = path25.dirname(fullPath);
9517
+ if (!fs25.existsSync(dir)) {
9518
+ fs25.mkdirSync(dir, { recursive: true });
9229
9519
  }
9230
- fs24.writeFileSync(fullPath, template, "utf-8");
9520
+ fs25.writeFileSync(fullPath, template, "utf-8");
9231
9521
  results.push({ type, filePath: relativePath, action: "created" });
9232
9522
  }
9233
9523
  if (type === "antigravity") {
@@ -9383,19 +9673,19 @@ __export(agentDetector_exports, {
9383
9673
  getSkillPath: () => getSkillPath,
9384
9674
  skillExists: () => skillExists
9385
9675
  });
9386
- import fs25 from "fs";
9387
- import path25 from "path";
9676
+ import fs26 from "fs";
9677
+ import path26 from "path";
9388
9678
  function checkFile(root, filePath) {
9389
9679
  try {
9390
- return fs25.existsSync(path25.join(root, filePath));
9680
+ return fs26.existsSync(path26.join(root, filePath));
9391
9681
  } catch {
9392
9682
  return false;
9393
9683
  }
9394
9684
  }
9395
9685
  function checkDir(root, dirPath) {
9396
9686
  try {
9397
- const fullPath = path25.join(root, dirPath);
9398
- return fs25.existsSync(fullPath) && fs25.statSync(fullPath).isDirectory();
9687
+ const fullPath = path26.join(root, dirPath);
9688
+ return fs26.existsSync(fullPath) && fs26.statSync(fullPath).isDirectory();
9399
9689
  } catch {
9400
9690
  return false;
9401
9691
  }
@@ -9463,7 +9753,7 @@ function getAgentLabel(type) {
9463
9753
  function skillExists(type, projectRoot) {
9464
9754
  const root = projectRoot ?? getProjectRoot();
9465
9755
  const skillPath = getSkillPath(type);
9466
- return fs25.existsSync(path25.join(root, skillPath));
9756
+ return fs26.existsSync(path26.join(root, skillPath));
9467
9757
  }
9468
9758
  var AGENT_DETECTORS;
9469
9759
  var init_agentDetector = __esm({
@@ -9863,11 +10153,11 @@ var init_skillGenerator = __esm({
9863
10153
  "Project knowledge persists in `.kuma/memories/*.md` across sessions.",
9864
10154
  "",
9865
10155
  "\u26A1 **ADAPTIVE WORKFLOW (Lean Mode by Default):**",
9866
- " \u2022 **Lean Mode (Default, < 3 files):** `init` \u2192 edit/read \u2192 record (`decision`/`gotcha` if needed)",
10156
+ " \u2022 **Lean Mode (Default, < 3 files):** `init` \u2192 edit/read \u2192 record (`decision`/`gotcha`/`feature` if needed)",
9867
10157
  " \u2022 **Standard Mode (3-10 files/unfamiliar):** `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
9868
10158
  " \u2022 **Full Mode (> 10 files/refactor):** `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
9869
10159
  "",
9870
- "\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow)",
10160
+ "\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow), `feature` (high-level module ownership)",
9871
10161
  "\u{1F7E2} **SKIP RECORDING:** function, class, import, route (use native grep/glob instead)",
9872
10162
  "",
9873
10163
  "\u{1F4D6} Full rules & modes: `.kuma/init.md` | `.kuma/MODE.md` | `.kuma/SKIP_RULES.md`",
@@ -9883,7 +10173,7 @@ var init_skillGenerator = __esm({
9883
10173
  " \u2022 **Standard Mode (3-10 files/unfamiliar):** `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
9884
10174
  " \u2022 **Full Mode (> 10 files/refactor):** `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
9885
10175
  "",
9886
- "\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow)",
10176
+ "\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow), `feature` (high-level module ownership)",
9887
10177
  "\u{1F7E2} **SKIP RECORDING:** function, class, import, route (use native grep/glob instead)",
9888
10178
  "",
9889
10179
  "\u{1F4D6} Full rules & modes: `.kuma/init.md` | `.kuma/MODE.md` | `.kuma/SKIP_RULES.md`",
@@ -10407,9 +10697,58 @@ init_sessionMemory();
10407
10697
  init_kumaDb();
10408
10698
  init_kumaSelfHeal();
10409
10699
  init_kumaMemory();
10700
+
10701
+ // src/engine/kumaNoiseFilter.ts
10702
+ var ALLOWED_NODE_TYPES = /* @__PURE__ */ new Set([
10703
+ "arch_flow",
10704
+ // Architecture flow (Layer 2)
10705
+ "gotcha",
10706
+ // Known gotchas / edge cases (Layer 3)
10707
+ "decision",
10708
+ // Architecture decisions / ADRs
10709
+ "cross_service_link",
10710
+ // Backend ↔ Frontend integration links
10711
+ "feature_domain",
10712
+ // High-level feature domains
10713
+ "file",
10714
+ // File references (needed for graph connectivity)
10715
+ "research"
10716
+ // Research cache nodes (needed for search)
10717
+ ]);
10718
+ function isNodeTypeAllowed(type) {
10719
+ return ALLOWED_NODE_TYPES.has(type);
10720
+ }
10721
+ function getNoiseFilterPolicy() {
10722
+ return [
10723
+ "\u{1F9F9} **Kuma Noise Filter \u2014 Anti-AST Guard**",
10724
+ "\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",
10725
+ "",
10726
+ "**Allowed High-Value Node Types:**",
10727
+ " \u{1F3DB}\uFE0F `arch_flow` \u2014 Architecture flow (max 5 core files per flow)",
10728
+ " \u{1F4CC} `gotcha` \u2014 Known bugs, edge cases, gotchas",
10729
+ " \u2696\uFE0F `decision` \u2014 Architecture decisions / ADRs",
10730
+ " \u{1F517} `cross_service_link` \u2014 Backend \u2194 Frontend integration links",
10731
+ " \u{1F3D7}\uFE0F `feature_domain` \u2014 High-level feature domains",
10732
+ " \u{1F4C4} `file` \u2014 File references (graph connectivity)",
10733
+ " \u{1F52C} `research` \u2014 Research cache nodes",
10734
+ "",
10735
+ "**Rejected Noise Types:**",
10736
+ " \u274C `function`, `class`, `component`, `variable`, `method`",
10737
+ " \u274C `interface`, `type`, `enum`, `const`, `hook`",
10738
+ " \u274C `route`, `api_route`, `test`",
10739
+ "",
10740
+ "**Why?** AST-level nodes create graph junk that slows retrieval.",
10741
+ "Use `arch_flow` to describe HOW files connect, not individual functions.",
10742
+ "",
10743
+ "**Tip:** Instead of adding function nodes, record an `arch_flow`:",
10744
+ " kuma_memory({ action: 'arch_flow', content: 'domain: AuthFlow | hops: auth.ts \u2192 middleware.ts \u2192 session.ts' })"
10745
+ ].join("\n");
10746
+ }
10747
+
10748
+ // src/tools/kumaMemoryTool.ts
10410
10749
  init_pathValidator();
10411
- import fs14 from "fs";
10412
- import path14 from "path";
10750
+ import fs15 from "fs";
10751
+ import path15 from "path";
10413
10752
  var MEMORY_ALIASES = {
10414
10753
  // Session synonyms
10415
10754
  "session": "session",
@@ -10515,7 +10854,18 @@ var MEMORY_ALIASES = {
10515
10854
  "clear-graph": "clear",
10516
10855
  "wipe": "clear",
10517
10856
  "reset": "clear",
10518
- "purge-all": "clear"
10857
+ "purge-all": "clear",
10858
+ // Harvest synonyms (Pilar 5)
10859
+ "harvest": "harvest",
10860
+ "git-harvest": "harvest",
10861
+ "auto-harvest": "harvest",
10862
+ "git": "harvest",
10863
+ // Noise policy synonyms (Pilar 4)
10864
+ "noise_policy": "noise_policy",
10865
+ "noise-policy": "noise_policy",
10866
+ "noise": "noise_policy",
10867
+ "anti-ast": "noise_policy",
10868
+ "filter": "noise_policy"
10519
10869
  };
10520
10870
  async function handleMemory(params) {
10521
10871
  const rawAction = params.action || "session";
@@ -10566,6 +10916,10 @@ async function handleMemory(params) {
10566
10916
  await clearGraph2();
10567
10917
  return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges, and gotchas have been wiped from disk and memory.";
10568
10918
  }
10919
+ case "harvest":
10920
+ return handleHarvest(params);
10921
+ case "noise_policy":
10922
+ return getNoiseFilterPolicy();
10569
10923
  default:
10570
10924
  return `Unknown action "${action}".`;
10571
10925
  }
@@ -10640,9 +10994,9 @@ async function handleResearchSave(params) {
10640
10994
  } catch {
10641
10995
  }
10642
10996
  try {
10643
- const researchDir = path14.join(getProjectRoot(), ".kuma", "research");
10644
- if (!fs14.existsSync(researchDir)) fs14.mkdirSync(researchDir, { recursive: true });
10645
- fs14.writeFileSync(path14.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
10997
+ const researchDir = path15.join(getProjectRoot(), ".kuma", "research");
10998
+ if (!fs15.existsSync(researchDir)) fs15.mkdirSync(researchDir, { recursive: true });
10999
+ fs15.writeFileSync(path15.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
10646
11000
  } catch {
10647
11001
  }
10648
11002
  sessionMemory.recordMemoryAction("research_save");
@@ -10731,8 +11085,8 @@ ${impactLines.join("\n")}`;
10731
11085
  }
10732
11086
  let hybridResults = "";
10733
11087
  try {
10734
- const { hybridSearch: hybridSearch2, formatHybridResults: formatHybridResults2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
10735
- const semanticResults = await hybridSearch2(query, 8);
11088
+ const { enhancedHybridSearch: enhancedHybridSearch2, formatHybridResults: formatHybridResults2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
11089
+ const semanticResults = await enhancedHybridSearch2(query, 8);
10736
11090
  if (semanticResults.length > 0) {
10737
11091
  hybridResults = "\n" + formatHybridResults2(query, semanticResults);
10738
11092
  }
@@ -11009,11 +11363,14 @@ async function handleAddNode(params) {
11009
11363
  const name = params.content;
11010
11364
  const filePath = params.scope;
11011
11365
  if (!type || !name) {
11012
- return "\u26A0\uFE0F `title` (node type: function/class/component/file/api_route/test) and `content` (node name) required.\nExample: kuma_memory({ action: 'add_node', title: 'function', content: 'sendMessage', scope: 'ChatService.ts' })";
11366
+ return "\u26A0\uFE0F `title` (node type) and `content` (node name) required.\n\n\u2705 Allowed types: arch_flow, gotcha, decision, cross_service_link, feature_domain, file, research\n\u274C Rejected: function, class, component, variable, method\n\nExample: kuma_memory({ action: 'arch_flow', content: 'domain: AuthFlow | hops: auth.ts \u2192 middleware.ts' })";
11013
11367
  }
11014
- const validTypes = ["function", "class", "component", "file", "api_route", "test"];
11015
- if (!validTypes.includes(type)) {
11016
- return `\u26A0\uFE0F Invalid type "${type}". Valid types: ${validTypes.join(", ")}`;
11368
+ if (!isNodeTypeAllowed(type)) {
11369
+ return [
11370
+ `\u274C **Noise Filter:** Type "${type}" is not allowed.`,
11371
+ "",
11372
+ getNoiseFilterPolicy()
11373
+ ].join("\n");
11017
11374
  }
11018
11375
  try {
11019
11376
  const { upsertNode: upsertNode2, nodeId: nodeId2, addEdge: addEdge2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
@@ -11126,78 +11483,88 @@ async function handleLayersSummary(_params) {
11126
11483
  const { getLayersSummary: getLayersSummary2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
11127
11484
  return getLayersSummary2();
11128
11485
  }
11486
+ async function handleHarvest(params) {
11487
+ sessionMemory.recordToolCall("kuma_memory_harvest", { scope: params.scope });
11488
+ const { harvestGitHistory: harvestGitHistory2, installGitHook: installGitHook2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
11489
+ if (params.scope === "install") {
11490
+ return installGitHook2();
11491
+ }
11492
+ const count = params.limit || 10;
11493
+ const dryRun = params.status === "dry-run" || params.status === "preview";
11494
+ return await harvestGitHistory2({ commitCount: count, dryRun });
11495
+ }
11129
11496
 
11130
11497
  // src/tools/kumaSafetyTool.ts
11131
11498
  init_sessionMemory();
11132
11499
 
11133
11500
  // src/engine/kumaLock.ts
11134
11501
  init_pathValidator();
11135
- import fs15 from "fs";
11136
- import path15 from "path";
11502
+ import fs16 from "fs";
11503
+ import path16 from "path";
11137
11504
  var LOCKS_DIR = ".kuma/locks";
11138
11505
  function locksDir() {
11139
- return path15.join(getProjectRoot(), LOCKS_DIR);
11506
+ return path16.join(getProjectRoot(), LOCKS_DIR);
11140
11507
  }
11141
11508
  function ensureLocksDir() {
11142
11509
  const dir = locksDir();
11143
- if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
11510
+ if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
11144
11511
  }
11145
11512
  function lockPath(filePath) {
11146
11513
  const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
11147
- return path15.join(locksDir(), `${safeName}.lock.json`);
11514
+ return path16.join(locksDir(), `${safeName}.lock.json`);
11148
11515
  }
11149
11516
  function acquireLock(filePath, agentId) {
11150
11517
  ensureLocksDir();
11151
11518
  const lp = lockPath(filePath);
11152
11519
  const id = agentId || `agent-${process.pid}`;
11153
- if (fs15.existsSync(lp)) {
11520
+ if (fs16.existsSync(lp)) {
11154
11521
  try {
11155
- const existing = JSON.parse(fs15.readFileSync(lp, "utf-8"));
11522
+ const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
11156
11523
  if (existing.agentId === id) {
11157
11524
  return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
11158
11525
  }
11159
11526
  const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
11160
11527
  if (elapsed > 300) {
11161
- fs15.unlinkSync(lp);
11528
+ fs16.unlinkSync(lp);
11162
11529
  } else {
11163
11530
  return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
11164
11531
  }
11165
11532
  } catch {
11166
- fs15.unlinkSync(lp);
11533
+ fs16.unlinkSync(lp);
11167
11534
  }
11168
11535
  }
11169
11536
  const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
11170
- fs15.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
11537
+ fs16.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
11171
11538
  return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
11172
11539
  }
11173
11540
  function releaseLock(filePath, agentId) {
11174
11541
  ensureLocksDir();
11175
11542
  const lp = lockPath(filePath);
11176
11543
  const id = agentId || `agent-${process.pid}`;
11177
- if (!fs15.existsSync(lp)) {
11544
+ if (!fs16.existsSync(lp)) {
11178
11545
  return `\u26A0\uFE0F No lock found for \`${filePath}\``;
11179
11546
  }
11180
11547
  try {
11181
- const existing = JSON.parse(fs15.readFileSync(lp, "utf-8"));
11548
+ const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
11182
11549
  if (existing.agentId !== id) {
11183
11550
  return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
11184
11551
  }
11185
- fs15.unlinkSync(lp);
11552
+ fs16.unlinkSync(lp);
11186
11553
  return `\u{1F513} **Lock released** on \`${filePath}\``;
11187
11554
  } catch {
11188
- fs15.unlinkSync(lp);
11555
+ fs16.unlinkSync(lp);
11189
11556
  return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
11190
11557
  }
11191
11558
  }
11192
11559
  function listLocks() {
11193
11560
  ensureLocksDir();
11194
11561
  const dir = locksDir();
11195
- const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
11562
+ const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
11196
11563
  if (files.length === 0) return "\u{1F513} No active locks.";
11197
11564
  const lines = ["\u{1F512} **Active Locks:**", ""];
11198
11565
  for (const f of files) {
11199
11566
  try {
11200
- const entry = JSON.parse(fs15.readFileSync(path15.join(dir, f), "utf-8"));
11567
+ const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
11201
11568
  const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
11202
11569
  lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
11203
11570
  } catch {
@@ -11208,9 +11575,9 @@ function listLocks() {
11208
11575
  }
11209
11576
  function isLocked(filePath) {
11210
11577
  const lp = lockPath(filePath);
11211
- if (!fs15.existsSync(lp)) return { locked: false };
11578
+ if (!fs16.existsSync(lp)) return { locked: false };
11212
11579
  try {
11213
- const entry = JSON.parse(fs15.readFileSync(lp, "utf-8"));
11580
+ const entry = JSON.parse(fs16.readFileSync(lp, "utf-8"));
11214
11581
  return { locked: true, by: entry.agentId, since: entry.acquiredAt };
11215
11582
  } catch {
11216
11583
  return { locked: false };
@@ -11220,16 +11587,16 @@ function cleanStaleLocks() {
11220
11587
  ensureLocksDir();
11221
11588
  const dir = locksDir();
11222
11589
  let count = 0;
11223
- for (const f of fs15.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
11590
+ for (const f of fs16.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
11224
11591
  try {
11225
- const entry = JSON.parse(fs15.readFileSync(path15.join(dir, f), "utf-8"));
11592
+ const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
11226
11593
  if (Date.now() - entry.acquiredAt > 3e5) {
11227
- fs15.unlinkSync(path15.join(dir, f));
11594
+ fs16.unlinkSync(path16.join(dir, f));
11228
11595
  count++;
11229
11596
  }
11230
11597
  } catch {
11231
11598
  try {
11232
- fs15.unlinkSync(path15.join(dir, f));
11599
+ fs16.unlinkSync(path16.join(dir, f));
11233
11600
  count++;
11234
11601
  } catch {
11235
11602
  }
@@ -11245,8 +11612,8 @@ init_safetyAudit();
11245
11612
 
11246
11613
  // src/tools/kumaPolicy.ts
11247
11614
  init_pathValidator();
11248
- import fs16 from "fs";
11249
- import path16 from "path";
11615
+ import fs17 from "fs";
11616
+ import path17 from "path";
11250
11617
  var DEFAULT_POLICY = {
11251
11618
  never_touch: [
11252
11619
  ".env",
@@ -11272,15 +11639,15 @@ var DEFAULT_POLICY = {
11272
11639
  };
11273
11640
  function loadPolicy() {
11274
11641
  const root = getProjectRoot();
11275
- const ymlPath = path16.join(root, ".kuma", "policy.yml");
11276
- const yamlPath = path16.join(root, ".kuma", "policy.yaml");
11642
+ const ymlPath = path17.join(root, ".kuma", "policy.yml");
11643
+ const yamlPath = path17.join(root, ".kuma", "policy.yaml");
11277
11644
  let policyContent = null;
11278
11645
  let policyPath = null;
11279
- if (fs16.existsSync(ymlPath)) {
11280
- policyContent = fs16.readFileSync(ymlPath, "utf-8");
11646
+ if (fs17.existsSync(ymlPath)) {
11647
+ policyContent = fs17.readFileSync(ymlPath, "utf-8");
11281
11648
  policyPath = ymlPath;
11282
- } else if (fs16.existsSync(yamlPath)) {
11283
- policyContent = fs16.readFileSync(yamlPath, "utf-8");
11649
+ } else if (fs17.existsSync(yamlPath)) {
11650
+ policyContent = fs17.readFileSync(yamlPath, "utf-8");
11284
11651
  policyPath = yamlPath;
11285
11652
  }
11286
11653
  if (!policyContent) {
@@ -11390,9 +11757,9 @@ function checkFilePathPolicy(filePath, policy) {
11390
11757
  if (policy.max_file_size && policy.max_file_size > 0) {
11391
11758
  try {
11392
11759
  const root = getProjectRoot();
11393
- const fullPath = path16.join(root, filePath);
11394
- if (fs16.existsSync(fullPath)) {
11395
- const sizeKB = fs16.statSync(fullPath).size / 1024;
11760
+ const fullPath = path17.join(root, filePath);
11761
+ if (fs17.existsSync(fullPath)) {
11762
+ const sizeKB = fs17.statSync(fullPath).size / 1024;
11396
11763
  if (sizeKB > policy.max_file_size) {
11397
11764
  violations.push({
11398
11765
  rule: "max_file_size",
@@ -11559,12 +11926,12 @@ async function safetyCheck(action, filePath, command) {
11559
11926
  }
11560
11927
  if (filePath) {
11561
11928
  try {
11562
- const fs26 = await import("fs");
11563
- const path26 = await import("path");
11929
+ const fs27 = await import("fs");
11930
+ const path27 = await import("path");
11564
11931
  const root = process.cwd();
11565
- const fullPath = path26.resolve(root, filePath);
11566
- if (fs26.existsSync(fullPath)) {
11567
- const stat = fs26.statSync(fullPath);
11932
+ const fullPath = path27.resolve(root, filePath);
11933
+ if (fs27.existsSync(fullPath)) {
11934
+ const stat = fs27.statSync(fullPath);
11568
11935
  const sizeKB = Math.round(stat.size / 1024);
11569
11936
  if (sizeKB > 500) {
11570
11937
  checks.push({
@@ -11574,12 +11941,12 @@ async function safetyCheck(action, filePath, command) {
11574
11941
  });
11575
11942
  }
11576
11943
  if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
11577
- const baseName = path26.basename(filePath, path26.extname(filePath));
11944
+ const baseName = path27.basename(filePath, path27.extname(filePath));
11578
11945
  const testPatterns = [
11579
11946
  filePath.replace(/\.(ts|js)$/, ".test.$1"),
11580
11947
  filePath.replace(/\.(ts|js)$/, ".spec.$1"),
11581
11948
  filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
11582
- `**/__tests__/**/${baseName}.test.${path26.extname(filePath).slice(1)}`
11949
+ `**/__tests__/**/${baseName}.test.${path27.extname(filePath).slice(1)}`
11583
11950
  ];
11584
11951
  let hasTests = false;
11585
11952
  const fg = await import("fast-glob");
@@ -11595,7 +11962,7 @@ async function safetyCheck(action, filePath, command) {
11595
11962
  name: "Tests Check",
11596
11963
  passed: true,
11597
11964
  // Not a blocker, just informative
11598
- detail: `\u{1F9EA} No test file found for "${path26.basename(filePath)}" \u2014 consider adding tests`
11965
+ detail: `\u{1F9EA} No test file found for "${path27.basename(filePath)}" \u2014 consider adding tests`
11599
11966
  });
11600
11967
  } else {
11601
11968
  checks.push({
@@ -11684,9 +12051,9 @@ init_sessionMemory();
11684
12051
  // src/guards/antiPatternDetector.ts
11685
12052
  init_pathValidator();
11686
12053
  init_sessionMemory();
11687
- import fs17 from "fs";
11688
- import path17 from "path";
11689
- import { execSync as execSync5 } from "child_process";
12054
+ import fs18 from "fs";
12055
+ import path18 from "path";
12056
+ import { execSync as execSync6 } from "child_process";
11690
12057
  var SCRIPT_PATCH_PATTERNS = [
11691
12058
  "writeFileSync",
11692
12059
  "writeFile",
@@ -11712,20 +12079,20 @@ function findPatchScripts(projectRoot) {
11712
12079
  const warnings = [];
11713
12080
  const recentFiles = scanRecentFiles(projectRoot);
11714
12081
  for (const file of recentFiles) {
11715
- const ext = path17.extname(file).toLowerCase();
12082
+ const ext = path18.extname(file).toLowerCase();
11716
12083
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
11717
- const relativePath = path17.relative(projectRoot, file);
12084
+ const relativePath = path18.relative(projectRoot, file);
11718
12085
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
11719
12086
  continue;
11720
12087
  }
11721
12088
  try {
11722
- const content = fs17.readFileSync(file, "utf-8").toLowerCase();
12089
+ const content = fs18.readFileSync(file, "utf-8").toLowerCase();
11723
12090
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
11724
12091
  if (matchedPattern) {
11725
12092
  warnings.push({
11726
12093
  severity: "high",
11727
12094
  pattern: "script-patching",
11728
- message: `Created script file that modifies other files: ${path17.basename(file)}`,
12095
+ message: `Created script file that modifies other files: ${path18.basename(file)}`,
11729
12096
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
11730
12097
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
11731
12098
  filePath: relativePath
@@ -11759,15 +12126,15 @@ function detectBashGrepUsage() {
11759
12126
  function scanRecentFiles(projectRoot) {
11760
12127
  const recent = [];
11761
12128
  try {
11762
- const entries = fs17.readdirSync(projectRoot, { withFileTypes: true });
12129
+ const entries = fs18.readdirSync(projectRoot, { withFileTypes: true });
11763
12130
  const now = Date.now();
11764
12131
  const recentThreshold = 30 * 60 * 1e3;
11765
12132
  for (const entry of entries) {
11766
12133
  if (!entry.isFile()) continue;
11767
12134
  try {
11768
- const stat = fs17.statSync(path17.join(projectRoot, entry.name));
12135
+ const stat = fs18.statSync(path18.join(projectRoot, entry.name));
11769
12136
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
11770
- recent.push(path17.join(projectRoot, entry.name));
12137
+ recent.push(path18.join(projectRoot, entry.name));
11771
12138
  }
11772
12139
  } catch {
11773
12140
  continue;
@@ -11780,7 +12147,7 @@ function scanRecentFiles(projectRoot) {
11780
12147
  function detectGitPatchScripts(projectRoot) {
11781
12148
  const warnings = [];
11782
12149
  try {
11783
- const statusStdout = execSync5("git status --porcelain", {
12150
+ const statusStdout = execSync6("git status --porcelain", {
11784
12151
  cwd: projectRoot,
11785
12152
  encoding: "utf-8",
11786
12153
  timeout: 3e3
@@ -11791,14 +12158,14 @@ function detectGitPatchScripts(projectRoot) {
11791
12158
  const prefix = line.substring(0, 2);
11792
12159
  if (prefix !== "??" && prefix !== "A ") continue;
11793
12160
  const file = line.substring(3).trim();
11794
- const ext = path17.extname(file).toLowerCase();
12161
+ const ext = path18.extname(file).toLowerCase();
11795
12162
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
11796
12163
  const isRootLevel = !file.includes("/");
11797
12164
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
11798
12165
  if (!isRootLevel && !isScriptsDir) continue;
11799
- const fullPath = path17.join(projectRoot, file);
11800
- if (fs17.existsSync(fullPath)) {
11801
- const content = fs17.readFileSync(fullPath, "utf-8").toLowerCase();
12166
+ const fullPath = path18.join(projectRoot, file);
12167
+ if (fs18.existsSync(fullPath)) {
12168
+ const content = fs18.readFileSync(fullPath, "utf-8").toLowerCase();
11802
12169
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
11803
12170
  if (matchedPattern) {
11804
12171
  warnings.push({
@@ -11813,7 +12180,7 @@ function detectGitPatchScripts(projectRoot) {
11813
12180
  }
11814
12181
  }
11815
12182
  }
11816
- const deletedStdout = execSync5("git diff --name-only --diff-filter=D HEAD", {
12183
+ const deletedStdout = execSync6("git diff --name-only --diff-filter=D HEAD", {
11817
12184
  cwd: projectRoot,
11818
12185
  encoding: "utf-8",
11819
12186
  timeout: 3e3
@@ -11821,7 +12188,7 @@ function detectGitPatchScripts(projectRoot) {
11821
12188
  if (deletedStdout) {
11822
12189
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
11823
12190
  for (const file of deletedFiles) {
11824
- const ext = path17.extname(file).toLowerCase();
12191
+ const ext = path18.extname(file).toLowerCase();
11825
12192
  if (SCRIPT_EXTENSIONS.includes(ext)) {
11826
12193
  warnings.push({
11827
12194
  severity: "high",
@@ -11883,21 +12250,21 @@ function detectAllAntiPatterns() {
11883
12250
  // src/engine/contextSnapshot.ts
11884
12251
  init_sessionMemory();
11885
12252
  init_pathValidator();
11886
- import fs18 from "fs";
11887
- import path18 from "path";
11888
- import { execSync as execSync6 } from "child_process";
12253
+ import fs19 from "fs";
12254
+ import path19 from "path";
12255
+ import { execSync as execSync7 } from "child_process";
11889
12256
  var SNAPSHOT_DIR = "context-snapshots";
11890
12257
  function snapshotDir() {
11891
- return path18.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
12258
+ return path19.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
11892
12259
  }
11893
12260
  function ensureSnapshotDir() {
11894
12261
  const dir = snapshotDir();
11895
- if (!fs18.existsSync(dir)) {
11896
- fs18.mkdirSync(dir, { recursive: true });
12262
+ if (!fs19.existsSync(dir)) {
12263
+ fs19.mkdirSync(dir, { recursive: true });
11897
12264
  }
11898
12265
  }
11899
12266
  function snapshotFilePath(timestamp) {
11900
- return path18.join(snapshotDir(), `${timestamp}.json`);
12267
+ return path19.join(snapshotDir(), `${timestamp}.json`);
11901
12268
  }
11902
12269
  function saveSnapshot(goal) {
11903
12270
  try {
@@ -11918,7 +12285,7 @@ function saveSnapshot(goal) {
11918
12285
  hasConventions: !!summary.hasConventions
11919
12286
  };
11920
12287
  try {
11921
- fs18.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
12288
+ fs19.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
11922
12289
  } catch {
11923
12290
  }
11924
12291
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -11957,7 +12324,7 @@ function formatSnapshot(snapshot) {
11957
12324
  function getGitDiffStat2() {
11958
12325
  try {
11959
12326
  const root = getProjectRoot();
11960
- const stdout = execSync6("git diff --stat", {
12327
+ const stdout = execSync7("git diff --stat", {
11961
12328
  cwd: root,
11962
12329
  encoding: "utf-8",
11963
12330
  timeout: 3e3
@@ -12229,12 +12596,12 @@ async function handleSecurity(params) {
12229
12596
  sessionMemory.recordToolCall("kuma_safety_security", {});
12230
12597
  if (params.filePath) {
12231
12598
  try {
12232
- const fs26 = await import("fs");
12233
- const path26 = await import("path");
12599
+ const fs27 = await import("fs");
12600
+ const path27 = await import("path");
12234
12601
  const root = process.cwd();
12235
- const fullPath = path26.resolve(root, params.filePath);
12236
- if (fs26.existsSync(fullPath) && fs26.statSync(fullPath).isFile()) {
12237
- const content = fs26.readFileSync(fullPath, "utf-8");
12602
+ const fullPath = path27.resolve(root, params.filePath);
12603
+ if (fs27.existsSync(fullPath) && fs27.statSync(fullPath).isFile()) {
12604
+ const content = fs27.readFileSync(fullPath, "utf-8");
12238
12605
  const lines = content.split("\n");
12239
12606
  const findings = [];
12240
12607
  for (let i = 0; i < lines.length; i++) {
@@ -12288,23 +12655,23 @@ async function handleGitignore(_params) {
12288
12655
  }
12289
12656
  async function handleClean(_params) {
12290
12657
  sessionMemory.recordToolCall("kuma_safety_clean", {});
12291
- const fs26 = await import("fs");
12292
- const path26 = await import("path");
12658
+ const fs27 = await import("fs");
12659
+ const path27 = await import("path");
12293
12660
  const root = process.cwd();
12294
- const scratchDir = path26.resolve(root, ".kuma", "scratch");
12661
+ const scratchDir = path27.resolve(root, ".kuma", "scratch");
12295
12662
  let removed = 0;
12296
- if (fs26.existsSync(scratchDir)) {
12663
+ if (fs27.existsSync(scratchDir)) {
12297
12664
  try {
12298
- const entries = fs26.readdirSync(scratchDir);
12665
+ const entries = fs27.readdirSync(scratchDir);
12299
12666
  for (const entry of entries) {
12300
- const fullPath = path26.join(scratchDir, entry);
12667
+ const fullPath = path27.join(scratchDir, entry);
12301
12668
  try {
12302
- const stat = fs26.statSync(fullPath);
12669
+ const stat = fs27.statSync(fullPath);
12303
12670
  if (stat.isFile()) {
12304
- fs26.unlinkSync(fullPath);
12671
+ fs27.unlinkSync(fullPath);
12305
12672
  removed++;
12306
12673
  } else if (stat.isDirectory()) {
12307
- fs26.rmSync(fullPath, { recursive: true, force: true });
12674
+ fs27.rmSync(fullPath, { recursive: true, force: true });
12308
12675
  removed++;
12309
12676
  }
12310
12677
  } catch {
@@ -12432,6 +12799,64 @@ async function handleVerify(params) {
12432
12799
  return verifyResult + recordingWarning;
12433
12800
  }
12434
12801
 
12802
+ // src/engine/kumaAutoInit.ts
12803
+ init_sessionMemory();
12804
+ var _initialized = false;
12805
+ var _initPromise = null;
12806
+ async function ensureInitialized() {
12807
+ if (_initialized) return false;
12808
+ if (_initPromise) {
12809
+ await _initPromise;
12810
+ return false;
12811
+ }
12812
+ _initPromise = doInit();
12813
+ await _initPromise;
12814
+ return true;
12815
+ }
12816
+ async function doInit() {
12817
+ try {
12818
+ sessionMemory.init({
12819
+ projectRoot: process.cwd(),
12820
+ startTime: Date.now()
12821
+ });
12822
+ try {
12823
+ const sessionInfo = sessionMemory.loadSession();
12824
+ if (sessionInfo.hasPrevSession) {
12825
+ console.error(`[kuma:auto-init] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
12826
+ }
12827
+ } catch {
12828
+ }
12829
+ try {
12830
+ const { buildFromSessionMemory: buildFromSessionMemory2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
12831
+ const edgeCount = await buildFromSessionMemory2();
12832
+ if (edgeCount > 0) {
12833
+ console.error(`[kuma:auto-init] \u2705 Graph auto-populated with ${edgeCount} entries`);
12834
+ }
12835
+ } catch {
12836
+ }
12837
+ try {
12838
+ const { buildSearchVectors: buildSearchVectors2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
12839
+ await buildSearchVectors2();
12840
+ } catch {
12841
+ }
12842
+ try {
12843
+ const { getDb: getDb2, saveDb: saveDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
12844
+ const db = await getDb2();
12845
+ db.run(
12846
+ `INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
12847
+ [Math.floor(Date.now() / 1e3), "Auto-initialized", 0]
12848
+ );
12849
+ saveDb2(db);
12850
+ } catch {
12851
+ }
12852
+ _initialized = true;
12853
+ console.error(`[kuma:auto-init] \u2705 Zero-call auto-init complete`);
12854
+ } catch (err) {
12855
+ console.error(`[kuma:auto-init] \u26A0\uFE0F Auto-init error: ${err}`);
12856
+ _initialized = true;
12857
+ }
12858
+ }
12859
+
12435
12860
  // src/manifest.ts
12436
12861
  function registerAllTools(server) {
12437
12862
  server.tool(
@@ -12448,6 +12873,7 @@ function registerAllTools(server) {
12448
12873
  },
12449
12874
  async (params) => {
12450
12875
  try {
12876
+ await ensureInitialized();
12451
12877
  const text = await handleContext({
12452
12878
  action: params.action,
12453
12879
  scope: params.scope,
@@ -12504,6 +12930,7 @@ function registerAllTools(server) {
12504
12930
  },
12505
12931
  async (params) => {
12506
12932
  try {
12933
+ await ensureInitialized();
12507
12934
  const text = await handleMemory({
12508
12935
  action: params.action,
12509
12936
  scope: params.scope,
@@ -12563,6 +12990,7 @@ function registerAllTools(server) {
12563
12990
  },
12564
12991
  async (params) => {
12565
12992
  try {
12993
+ await ensureInitialized();
12566
12994
  const text = await handleSafety({
12567
12995
  action: params.action,
12568
12996
  guardGoal: params.guardGoal,
@@ -12590,6 +13018,8 @@ function registerAllTools(server) {
12590
13018
  }
12591
13019
  );
12592
13020
  console.error("[Manifest] Registered 3 V3 coarse-grained tools (kuma_context, kuma_memory, kuma_safety).");
13021
+ console.error("[Manifest] Pilar 1: Auto-init hooks installed on all tools.");
13022
+ console.error("[Manifest] Pilar 2: Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
12593
13023
  }
12594
13024
 
12595
13025
  // src/index.ts
@@ -12655,14 +13085,14 @@ async function main() {
12655
13085
  } catch {
12656
13086
  }
12657
13087
  try {
12658
- const path26 = await import("path");
12659
- const fs26 = await import("fs");
12660
- const lockDir = path26.resolve(process.cwd(), ".kuma/verifier.lock");
12661
- if (fs26.existsSync(lockDir)) {
12662
- const pidFile = path26.join(lockDir, "pid");
12663
- if (fs26.existsSync(pidFile)) {
13088
+ const path27 = await import("path");
13089
+ const fs27 = await import("fs");
13090
+ const lockDir = path27.resolve(process.cwd(), ".kuma/verifier.lock");
13091
+ if (fs27.existsSync(lockDir)) {
13092
+ const pidFile = path27.join(lockDir, "pid");
13093
+ if (fs27.existsSync(pidFile)) {
12664
13094
  try {
12665
- const pid = parseInt(fs26.readFileSync(pidFile, "utf-8"), 10);
13095
+ const pid = parseInt(fs27.readFileSync(pidFile, "utf-8"), 10);
12666
13096
  if (pid && pid !== process.pid) {
12667
13097
  try {
12668
13098
  process.kill(-pid, "SIGKILL");
@@ -12678,7 +13108,7 @@ async function main() {
12678
13108
  } catch {
12679
13109
  }
12680
13110
  }
12681
- fs26.rmSync(lockDir, { recursive: true, force: true });
13111
+ fs27.rmSync(lockDir, { recursive: true, force: true });
12682
13112
  }
12683
13113
  if (killedCount === 0) {
12684
13114
  console.error("\u2705 No running verifications found to kill.");
@@ -12686,25 +13116,25 @@ async function main() {
12686
13116
  } catch {
12687
13117
  }
12688
13118
  try {
12689
- const { execSync: execSync7 } = await import("child_process");
13119
+ const { execSync: execSync8 } = await import("child_process");
12690
13120
  try {
12691
- execSync7("pkill -f 'jest' 2>/dev/null || true");
13121
+ execSync8("pkill -f 'jest' 2>/dev/null || true");
12692
13122
  } catch {
12693
13123
  }
12694
13124
  try {
12695
- execSync7("pkill -f 'jest-worker' 2>/dev/null || true");
13125
+ execSync8("pkill -f 'jest-worker' 2>/dev/null || true");
12696
13126
  } catch {
12697
13127
  }
12698
13128
  try {
12699
- execSync7("pkill -f 'pnpm test' 2>/dev/null || true");
13129
+ execSync8("pkill -f 'pnpm test' 2>/dev/null || true");
12700
13130
  } catch {
12701
13131
  }
12702
13132
  try {
12703
- execSync7("pkill -f 'npm test' 2>/dev/null || true");
13133
+ execSync8("pkill -f 'npm test' 2>/dev/null || true");
12704
13134
  } catch {
12705
13135
  }
12706
13136
  try {
12707
- execSync7("pkill -f 'yarn test' 2>/dev/null || true");
13137
+ execSync8("pkill -f 'yarn test' 2>/dev/null || true");
12708
13138
  } catch {
12709
13139
  }
12710
13140
  console.error("\u2705 Killed orphaned test processes");
@@ -12756,6 +13186,14 @@ async function main() {
12756
13186
  return;
12757
13187
  }
12758
13188
  if (args[0] === "--hook") {
13189
+ const hookType = args[1] || "post-commit";
13190
+ if (hookType === "post-commit") {
13191
+ try {
13192
+ const { harvestGitHistory: harvestGitHistory2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
13193
+ await harvestGitHistory2({ commitCount: 1 });
13194
+ } catch {
13195
+ }
13196
+ }
12759
13197
  process.exit(0);
12760
13198
  }
12761
13199
  if (args[0] === "init") {
@@ -12818,23 +13256,23 @@ async function main() {
12818
13256
  });
12819
13257
  const output = formatInitResults(results);
12820
13258
  console.log(output);
12821
- const fs26 = await import("fs");
12822
- const path26 = await import("path");
12823
- const matchaSkills = path26.resolve(process.cwd(), "skills/matcha/SKILL.md");
12824
- const matchaAgents = path26.resolve(
13259
+ const fs27 = await import("fs");
13260
+ const path27 = await import("path");
13261
+ const matchaSkills = path27.resolve(process.cwd(), "skills/matcha/SKILL.md");
13262
+ const matchaAgents = path27.resolve(
12825
13263
  process.cwd(),
12826
13264
  ".agents/skills/matcha/SKILL.md"
12827
13265
  );
12828
- const matchaRootSkills = path26.resolve(
13266
+ const matchaRootSkills = path27.resolve(
12829
13267
  process.cwd(),
12830
13268
  "skills/matcha/SKILL.md"
12831
13269
  );
12832
- const matchaAgentsMd = path26.resolve(process.cwd(), "AGENTS.md");
12833
- const matchaWindsurfRules = path26.resolve(
13270
+ const matchaAgentsMd = path27.resolve(process.cwd(), "AGENTS.md");
13271
+ const matchaWindsurfRules = path27.resolve(
12834
13272
  process.cwd(),
12835
13273
  ".windsurf"
12836
13274
  );
12837
- if (fs26.existsSync(matchaSkills) || fs26.existsSync(matchaAgents) || fs26.existsSync(matchaRootSkills) || fs26.existsSync(matchaAgentsMd) || fs26.existsSync(matchaWindsurfRules)) {
13275
+ if (fs27.existsSync(matchaSkills) || fs27.existsSync(matchaAgents) || fs27.existsSync(matchaRootSkills) || fs27.existsSync(matchaAgentsMd) || fs27.existsSync(matchaWindsurfRules)) {
12838
13276
  console.error(
12839
13277
  "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
12840
13278
  );
@@ -12848,13 +13286,13 @@ async function main() {
12848
13286
  (async () => {
12849
13287
  try {
12850
13288
  const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
12851
- const fs26 = await import("fs");
12852
- const path26 = await import("path");
12853
- const initMdPath = path26.resolve(process.cwd(), ".kuma/init.md");
12854
- if (!fs26.existsSync(initMdPath)) {
12855
- const kumaDir = path26.dirname(initMdPath);
12856
- if (!fs26.existsSync(kumaDir)) fs26.mkdirSync(kumaDir, { recursive: true });
12857
- fs26.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
13289
+ const fs27 = await import("fs");
13290
+ const path27 = await import("path");
13291
+ const initMdPath = path27.resolve(process.cwd(), ".kuma/init.md");
13292
+ if (!fs27.existsSync(initMdPath)) {
13293
+ const kumaDir = path27.dirname(initMdPath);
13294
+ if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
13295
+ fs27.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
12858
13296
  console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
12859
13297
  }
12860
13298
  } catch (err) {
@@ -12865,8 +13303,8 @@ async function main() {
12865
13303
  try {
12866
13304
  const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
12867
13305
  const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
12868
- const fs26 = await import("fs");
12869
- const path26 = await import("path");
13306
+ const fs27 = await import("fs");
13307
+ const path27 = await import("path");
12870
13308
  const detection = detectAgent2();
12871
13309
  if (!detection.primary) {
12872
13310
  console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
@@ -12874,22 +13312,22 @@ async function main() {
12874
13312
  }
12875
13313
  const agentType = detection.primary;
12876
13314
  const skillPath = getSkillPath2(agentType);
12877
- const fullPath = path26.resolve(process.cwd(), skillPath);
12878
- if (fs26.existsSync(fullPath)) {
13315
+ const fullPath = path27.resolve(process.cwd(), skillPath);
13316
+ if (fs27.existsSync(fullPath)) {
12879
13317
  console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
12880
13318
  return;
12881
13319
  }
12882
- const dir = path26.dirname(fullPath);
12883
- if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
12884
- fs26.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
13320
+ const dir = path27.dirname(fullPath);
13321
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
13322
+ fs27.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
12885
13323
  console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
12886
13324
  const secondaryFiles = getSecondaryFiles2(agentType);
12887
13325
  for (const sf of secondaryFiles) {
12888
- const sfPath = path26.resolve(process.cwd(), sf.path);
12889
- const sfDir = path26.dirname(sfPath);
12890
- if (!fs26.existsSync(sfPath)) {
12891
- if (!fs26.existsSync(sfDir)) fs26.mkdirSync(sfDir, { recursive: true });
12892
- fs26.writeFileSync(sfPath, sf.content, "utf-8");
13326
+ const sfPath = path27.resolve(process.cwd(), sf.path);
13327
+ const sfDir = path27.dirname(sfPath);
13328
+ if (!fs27.existsSync(sfPath)) {
13329
+ if (!fs27.existsSync(sfDir)) fs27.mkdirSync(sfDir, { recursive: true });
13330
+ fs27.writeFileSync(sfPath, sf.content, "utf-8");
12893
13331
  console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
12894
13332
  }
12895
13333
  }
@@ -12914,11 +13352,11 @@ async function main() {
12914
13352
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
12915
13353
  }
12916
13354
  try {
12917
- const fs26 = await import("fs");
12918
- const path26 = await import("path");
12919
- const scratchDir = path26.resolve(process.cwd(), ".kuma", "scratch");
12920
- if (!fs26.existsSync(scratchDir)) {
12921
- fs26.mkdirSync(scratchDir, { recursive: true });
13355
+ const fs27 = await import("fs");
13356
+ const path27 = await import("path");
13357
+ const scratchDir = path27.resolve(process.cwd(), ".kuma", "scratch");
13358
+ if (!fs27.existsSync(scratchDir)) {
13359
+ fs27.mkdirSync(scratchDir, { recursive: true });
12922
13360
  console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
12923
13361
  }
12924
13362
  } catch (err) {
@@ -12936,33 +13374,11 @@ async function main() {
12936
13374
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
12937
13375
  }
12938
13376
  try {
12939
- const fs26 = await import("fs");
12940
- const path26 = await import("path");
12941
- const hooksDir = path26.resolve(process.cwd(), ".kuma", "hooks");
12942
- if (!fs26.existsSync(hooksDir)) {
12943
- fs26.mkdirSync(hooksDir, { recursive: true });
12944
- const hookContent = `#!/bin/bash
12945
- # Kuma auto-capture hook (post-commit)
12946
- # Auto-updates knowledge graph after git commits
12947
- if command -v npx &> /dev/null; then
12948
- npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
12949
- fi
12950
- `;
12951
- const gitHooksDir = path26.resolve(process.cwd(), ".git", "hooks");
12952
- if (fs26.existsSync(gitHooksDir)) {
12953
- const postCommitPath = path26.join(gitHooksDir, "post-commit");
12954
- if (!fs26.existsSync(postCommitPath)) {
12955
- fs26.writeFileSync(postCommitPath, hookContent, "utf-8");
12956
- try {
12957
- fs26.chmodSync(postCommitPath, 493);
12958
- } catch {
12959
- }
12960
- console.error(`[${SERVER_NAME}] \u2705 Created .git/hooks/post-commit for auto-capture`);
12961
- }
12962
- }
12963
- }
13377
+ const { installGitHook: installGitHook2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
13378
+ const hookResult = installGitHook2();
13379
+ console.error(`[${SERVER_NAME}] ${hookResult}`);
12964
13380
  } catch (err) {
12965
- console.error(`[${SERVER_NAME}] \u26A0\uFE0F Auto-capture hook setup: ${err}`);
13381
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Git hook setup: ${err}`);
12966
13382
  }
12967
13383
  try {
12968
13384
  const { buildSearchVectors: buildSearchVectors2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
@@ -12973,6 +13389,13 @@ fi
12973
13389
  } catch (err) {
12974
13390
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Search vector cache: ${err}`);
12975
13391
  }
13392
+ try {
13393
+ const { buildGraphConnectivity: buildGraphConnectivity2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
13394
+ await buildGraphConnectivity2();
13395
+ console.error(`[${SERVER_NAME}] \u2705 Graph connectivity index built`);
13396
+ } catch (err) {
13397
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph connectivity: ${err}`);
13398
+ }
12976
13399
  console.error(`[${SERVER_NAME}] \u2705 Cold start bootstrap complete`);
12977
13400
  } catch (err) {
12978
13401
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Cold start bootstrap error: ${err}`);
@@ -12998,13 +13421,19 @@ fi
12998
13421
  `[${SERVER_NAME}] Session started: ${(/* @__PURE__ */ new Date()).toISOString()}`
12999
13422
  );
13000
13423
  console.error(
13001
- `[${SERVER_NAME}] Kuma V3 \u2014 Safety-first context & orchestration engine`
13424
+ `[${SERVER_NAME}] Kuma v2.0 \u2014 Zero-Overhead Autonomous Brain Engine`
13425
+ );
13426
+ console.error(
13427
+ `[${SERVER_NAME}] \u26A1 Auto-init: no manual init needed (Pilar 1)`
13428
+ );
13429
+ console.error(
13430
+ `[${SERVER_NAME}] \u{1F4DB} Namespace normalized: kuma_kuma_* \u2192 kuma_* (Pilar 2)`
13002
13431
  );
13003
13432
  console.error(
13004
- `[${SERVER_NAME}] \u{1F9E0} Call kuma_context({ action: "init" }) at session start`
13433
+ `[${SERVER_NAME}] \u{1F9F9} Noise filter: AST node types rejected (Pilar 4)`
13005
13434
  );
13006
13435
  console.error(
13007
- `[${SERVER_NAME}] \u{1F52C} Call kuma_context({ action: "research", scope: "..." }) before editing`
13436
+ `[${SERVER_NAME}] \u{1F419} Git auto-harvester installed (Pilar 5)`
13008
13437
  );
13009
13438
  console.error(
13010
13439
  `[${SERVER_NAME}] \u{1F6E1}\uFE0F 3 coarse-grained tools: kuma_context, kuma_memory, kuma_safety`