@plumpslabs/kuma 2.3.31 → 2.3.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1427,7 +1427,7 @@ var init_sessionMemory = __esm({
1427
1427
  dependencyGraph: new Map(parsed.dependencyGraph || []),
1428
1428
  toolCalls: parsed.toolCalls || [],
1429
1429
  conventions: parsed.conventions,
1430
- recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
1430
+ recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
1431
1431
  metrics: parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
1432
1432
  };
1433
1433
  this.initialized = true;
@@ -1447,7 +1447,7 @@ var init_sessionMemory = __esm({
1447
1447
  searchResults: /* @__PURE__ */ new Map(),
1448
1448
  dependencyGraph: /* @__PURE__ */ new Map(),
1449
1449
  toolCalls: [],
1450
- recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
1450
+ recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
1451
1451
  metrics: { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
1452
1452
  };
1453
1453
  this.initialized = true;
@@ -1610,6 +1610,18 @@ No unresolved issues.`;
1610
1610
  this.state.currentGoal = goal;
1611
1611
  this.save();
1612
1612
  }
1613
+ setGoalProgress(percentage, milestone) {
1614
+ this.ensureInit();
1615
+ this.state.goalProgress = {
1616
+ percentage: Math.min(100, Math.max(0, percentage)),
1617
+ milestone,
1618
+ updatedAt: Date.now()
1619
+ };
1620
+ this.save();
1621
+ }
1622
+ getGoalProgress() {
1623
+ return this.state.goalProgress || null;
1624
+ }
1613
1625
  addCompletedStep(step) {
1614
1626
  this.ensureInit();
1615
1627
  if (!this.state.completedSteps.includes(step)) {
@@ -2031,6 +2043,9 @@ No unresolved issues.`;
2031
2043
  case "research_save":
2032
2044
  this.state.recordings.researchSaves++;
2033
2045
  break;
2046
+ case "feature":
2047
+ this.state.recordings.features++;
2048
+ break;
2034
2049
  }
2035
2050
  this.state.recordings.total++;
2036
2051
  this.save();
@@ -2047,6 +2062,7 @@ No unresolved issues.`;
2047
2062
  if (r.gotchas === 0) missing.push("gotcha");
2048
2063
  if (r.decisions === 0) missing.push("decision");
2049
2064
  if (r.researchSaves === 0) missing.push("research_save");
2065
+ if (r.features === 0) missing.push("feature");
2050
2066
  return {
2051
2067
  ...r,
2052
2068
  hasAnyRecordings: r.total > 0,
@@ -2509,6 +2525,223 @@ var init_kumaSelfHeal = __esm({
2509
2525
  }
2510
2526
  });
2511
2527
 
2528
+ // src/engine/languageSupport.ts
2529
+ import path5 from "path";
2530
+ function grepIncludeFlags() {
2531
+ return SOURCE_EXTENSIONS.map((e) => `--include="*${e}"`).join(" ");
2532
+ }
2533
+ function isTestFile(filePath) {
2534
+ const lower = filePath.toLowerCase();
2535
+ const base = path5.basename(filePath);
2536
+ const ext = path5.extname(base);
2537
+ const stem = ext ? base.slice(0, -ext.length) : base;
2538
+ const segments = lower.split(/[\\/]/);
2539
+ for (const lang of LANGUAGES) {
2540
+ if (!lang.extensions.includes(ext.toLowerCase())) continue;
2541
+ for (const p of lang.testPrefixes) {
2542
+ if (p && stem.startsWith(p)) return true;
2543
+ }
2544
+ for (const s of lang.testSuffixes) {
2545
+ if (s && stem.endsWith(s)) return true;
2546
+ }
2547
+ for (const d of lang.testDirs) {
2548
+ const dLower = d.toLowerCase();
2549
+ if (dLower.includes("/")) {
2550
+ if (lower.includes(`${dLower}/`) || lower.includes(`${dLower}\\`)) return true;
2551
+ } else if (segments.includes(dLower)) {
2552
+ return true;
2553
+ }
2554
+ }
2555
+ }
2556
+ return /(\.test\.|\.spec\.|_test\.|_spec\.)/.test(lower) || /(\/|^)(__tests__|tests?|specs?)(\/|$)/.test(lower);
2557
+ }
2558
+ function getTestCandidates(sourcePath) {
2559
+ const ext = path5.extname(sourcePath).toLowerCase();
2560
+ const dir = path5.dirname(sourcePath);
2561
+ const base = path5.basename(sourcePath, ext);
2562
+ const lang = LANGUAGES.find((l) => l.extensions.includes(ext));
2563
+ const candidates = /* @__PURE__ */ new Set();
2564
+ const push = (p) => {
2565
+ if (p && !candidates.has(p)) candidates.add(p);
2566
+ };
2567
+ if (lang) {
2568
+ for (const suffix of lang.testSuffixes) {
2569
+ if (!suffix) continue;
2570
+ push(path5.join(dir, `${base}${suffix}${ext}`));
2571
+ }
2572
+ for (const prefix of lang.testPrefixes) {
2573
+ if (prefix) push(path5.join(dir, `${prefix}${base}${ext}`));
2574
+ }
2575
+ for (const td of lang.testDirs) {
2576
+ if (!td.includes("/")) {
2577
+ push(path5.join(dir, td, `${base}${ext}`));
2578
+ }
2579
+ }
2580
+ }
2581
+ push(path5.join(dir, `${base}.test${ext}`));
2582
+ push(path5.join(dir, `${base}.spec${ext}`));
2583
+ push(path5.join(dir, `${base}_test${ext}`));
2584
+ push(path5.join(dir, `test_${base}${ext}`));
2585
+ push(path5.join(dir, "__tests__", `${base}${ext}`));
2586
+ push(path5.join(dir, "tests", `${base}${ext}`));
2587
+ return [...candidates];
2588
+ }
2589
+ function matchImportPath(line) {
2590
+ for (const re of IMPORT_PATTERNS) {
2591
+ re.lastIndex = 0;
2592
+ const m = re.exec(line);
2593
+ if (m) return (m[1] || m[2] || "").trim();
2594
+ }
2595
+ return null;
2596
+ }
2597
+ var LANGUAGES, SOURCE_EXTENSIONS, SOURCE_EXT_GLOB, DEFAULT_SOURCE_INCLUDES, IMPORT_PATTERNS;
2598
+ var init_languageSupport = __esm({
2599
+ "src/engine/languageSupport.ts"() {
2600
+ "use strict";
2601
+ LANGUAGES = [
2602
+ {
2603
+ name: "typescript",
2604
+ extensions: [".ts", ".tsx", ".mts", ".cts"],
2605
+ testPrefixes: [],
2606
+ testSuffixes: [".test", ".spec"],
2607
+ testDirs: ["__tests__", "tests", "test", "spec"]
2608
+ },
2609
+ {
2610
+ name: "javascript",
2611
+ extensions: [".js", ".jsx", ".mjs", ".cjs"],
2612
+ testPrefixes: [],
2613
+ testSuffixes: [".test", ".spec"],
2614
+ testDirs: ["__tests__", "tests", "test", "spec"]
2615
+ },
2616
+ {
2617
+ name: "python",
2618
+ extensions: [".py", ".pyw"],
2619
+ testPrefixes: ["test_"],
2620
+ testSuffixes: ["_test"],
2621
+ testDirs: ["tests", "test"]
2622
+ },
2623
+ {
2624
+ name: "go",
2625
+ extensions: [".go"],
2626
+ testPrefixes: [],
2627
+ testSuffixes: ["_test"],
2628
+ testDirs: ["testdata"]
2629
+ },
2630
+ {
2631
+ name: "rust",
2632
+ extensions: [".rs"],
2633
+ testPrefixes: [],
2634
+ testSuffixes: ["_test"],
2635
+ testDirs: ["tests"]
2636
+ },
2637
+ {
2638
+ name: "java",
2639
+ extensions: [".java"],
2640
+ testPrefixes: ["Test"],
2641
+ testSuffixes: ["Test", "Tests"],
2642
+ testDirs: ["src/test", "test", "tests"]
2643
+ },
2644
+ {
2645
+ name: "kotlin",
2646
+ extensions: [".kt", ".kts"],
2647
+ testPrefixes: ["Test"],
2648
+ testSuffixes: ["Test", "Tests"],
2649
+ testDirs: ["src/test"]
2650
+ },
2651
+ {
2652
+ name: "csharp",
2653
+ extensions: [".cs"],
2654
+ testPrefixes: [],
2655
+ testSuffixes: ["Tests", "Test"],
2656
+ testDirs: ["Tests", "test", "tests"]
2657
+ },
2658
+ {
2659
+ name: "ruby",
2660
+ extensions: [".rb"],
2661
+ testPrefixes: [],
2662
+ testSuffixes: ["_spec", "_test"],
2663
+ testDirs: ["spec", "test", "tests"]
2664
+ },
2665
+ {
2666
+ name: "php",
2667
+ extensions: [".php"],
2668
+ testPrefixes: [],
2669
+ testSuffixes: ["Test"],
2670
+ testDirs: ["tests", "test"]
2671
+ },
2672
+ {
2673
+ name: "cpp",
2674
+ extensions: [".c", ".cc", ".cpp", ".h", ".hpp", ".hxx"],
2675
+ testPrefixes: [],
2676
+ testSuffixes: ["_test"],
2677
+ testDirs: ["tests", "test"]
2678
+ },
2679
+ {
2680
+ name: "swift",
2681
+ extensions: [".swift"],
2682
+ testPrefixes: [],
2683
+ testSuffixes: ["Tests"],
2684
+ testDirs: ["Tests", "test"]
2685
+ },
2686
+ {
2687
+ name: "scala",
2688
+ extensions: [".scala"],
2689
+ testPrefixes: [],
2690
+ testSuffixes: ["Spec", "Test", "Tests"],
2691
+ testDirs: ["src/test"]
2692
+ },
2693
+ {
2694
+ name: "dart",
2695
+ extensions: [".dart"],
2696
+ testPrefixes: [],
2697
+ testSuffixes: ["_test"],
2698
+ testDirs: ["test", "tests"]
2699
+ },
2700
+ {
2701
+ name: "shell",
2702
+ extensions: [".sh", ".bash", ".zsh"],
2703
+ testPrefixes: [],
2704
+ testSuffixes: [],
2705
+ testDirs: ["test", "tests"]
2706
+ }
2707
+ ];
2708
+ SOURCE_EXTENSIONS = [
2709
+ ...new Set(LANGUAGES.flatMap((l) => l.extensions))
2710
+ ];
2711
+ SOURCE_EXT_GLOB = `{${SOURCE_EXTENSIONS.map((e) => e.slice(1)).join(",")}}`;
2712
+ DEFAULT_SOURCE_INCLUDES = [
2713
+ `src/**/*.${SOURCE_EXT_GLOB}`,
2714
+ `app/**/*.${SOURCE_EXT_GLOB}`,
2715
+ `lib/**/*.${SOURCE_EXT_GLOB}`,
2716
+ `packages/**/*.${SOURCE_EXT_GLOB}`,
2717
+ `server/**/*.${SOURCE_EXT_GLOB}`,
2718
+ `api/**/*.${SOURCE_EXT_GLOB}`
2719
+ ];
2720
+ IMPORT_PATTERNS = [
2721
+ // JS/TS: import x from 'y' | import {a} from 'y' | import * as x from 'y'
2722
+ /import\s+(?:\{[^}]*\}\s+from\s+)?(?:[^'"\n]*\s+from\s+)?['"]([^'"]+)['"]/,
2723
+ // JS/TS (CJS): const x = require('y')
2724
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/,
2725
+ // Python: import y | import y.z | from y import x | from .y import x
2726
+ /^\s*(?:from\s+([.\w]+)\s+import\s+\S+|import\s+([.\w]+))/m,
2727
+ // Go: import "y" | import alias "y"
2728
+ /^\s*import\s+(?:\w+\s+)?"([^"]+)"/m,
2729
+ // Rust: use y::z | extern crate y
2730
+ /^\s*use\s+([\w:]+)/m,
2731
+ // C/C++: #include <y> | #include "y"
2732
+ /^\s*#\s*include\s*[<"]([^>"]+)[>"]/m,
2733
+ // Ruby: require 'y' | require_relative 'y'
2734
+ /^\s*require(?:_relative)?\s+['"]([^'"]+)['"]/m,
2735
+ // PHP: require 'y' | include 'y' | use y\z;
2736
+ /^\s*(?:require(?:_once)?|include(?:_once)?)\s*\(?\s*['"]([^'"]+)['"]/m,
2737
+ // Java: import y.z.w;
2738
+ /^\s*import\s+([\w.]+)\s*;/m,
2739
+ // C#: using y.z.w;
2740
+ /^\s*using\s+([\w.]+)\s*;/m
2741
+ ];
2742
+ }
2743
+ });
2744
+
2512
2745
  // src/engine/kumaGraph.ts
2513
2746
  var kumaGraph_exports = {};
2514
2747
  __export(kumaGraph_exports, {
@@ -3105,7 +3338,7 @@ async function queryGraph(params) {
3105
3338
  if (results.length === 0) {
3106
3339
  return `\u{1F50D} **Graph Query** \u2014 No nodes found for "${query}".
3107
3340
 
3108
- Try a different search term or check if the graph has been built by using tools like smart_grep or lsp_query.`;
3341
+ Try a different search term or use kuma_context({ action: 'research', scope: '<term>' }) to build graph context.`;
3109
3342
  }
3110
3343
  const lines = [
3111
3344
  `\u{1F50D} **Graph Nodes** \u2014 ${results.length} result(s) for "${query}"`,
@@ -3139,7 +3372,7 @@ Try a different search term or check if the graph has been built by using tools
3139
3372
  if (results.length === 0) {
3140
3373
  return `\u{1F50D} **Graph Edges** \u2014 No connections found for node "${query}".
3141
3374
 
3142
- Try kuma_graph_query({ type: 'nodes', query: '<search>' }) to find node IDs first.`;
3375
+ Try kuma_context({ action: 'research', scope: '<term>' }) to build related graph nodes first.`;
3143
3376
  }
3144
3377
  const lines = [
3145
3378
  `\u{1F517} **Graph Edges** \u2014 ${results.length} connection(s) for "${query}"`,
@@ -3156,7 +3389,7 @@ Try kuma_graph_query({ type: 'nodes', query: '<search>' }) to find node IDs firs
3156
3389
  const parts = query.split("\u2192").map((s) => s.trim());
3157
3390
  if (parts.length !== 2) {
3158
3391
  return `\u26A0\uFE0F For path queries, use format: "sourceNodeID \u2192 targetNodeID"
3159
- Example: kuma_graph_query({ type: 'paths', query: 'function::login \u2192 function::validatePassword' })`;
3392
+ Example: kuma_context({ action: 'impact', target: 'login' }) \u2192 traces connections between nodes.`;
3160
3393
  }
3161
3394
  const [sourceId, targetId] = parts;
3162
3395
  const visited = /* @__PURE__ */ new Set();
@@ -3396,27 +3629,27 @@ async function codebaseSearchFallback(query, limit = 20) {
3396
3629
  if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
3397
3630
  let entries = [];
3398
3631
  try {
3399
- entries = fs27.readdirSync(dir);
3632
+ entries = fs30.readdirSync(dir);
3400
3633
  } catch {
3401
3634
  return;
3402
3635
  }
3403
3636
  for (const entry of entries) {
3404
3637
  if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
3405
- const full = path27.join(dir, entry);
3638
+ const full = path31.join(dir, entry);
3406
3639
  try {
3407
- const stat = fs27.statSync(full);
3640
+ const stat = fs30.statSync(full);
3408
3641
  if (stat.isDirectory()) {
3409
3642
  walk2(full, depth + 1);
3410
3643
  } else if (entry.toLowerCase().includes(query.toLowerCase())) {
3411
- results.push(path27.relative(root, full));
3644
+ results.push(path31.relative(root, full));
3412
3645
  }
3413
3646
  } catch {
3414
3647
  }
3415
3648
  }
3416
3649
  };
3417
3650
  var walk = walk2;
3418
- const fs27 = await import("fs");
3419
- const path27 = await import("path");
3651
+ const fs30 = await import("fs");
3652
+ const path31 = await import("path");
3420
3653
  const root = process.cwd();
3421
3654
  const results = [];
3422
3655
  const MAX_WALK_DEPTH = 8;
@@ -3559,7 +3792,7 @@ async function codebaseImpactFallback(target) {
3559
3792
  const { execSync: execSync8 } = await import("child_process");
3560
3793
  const root = process.cwd();
3561
3794
  const grepResult = execSync8(
3562
- `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`,
3795
+ `grep -rn ${grepIncludeFlags()} --include="*.json" --include="*.md" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
3563
3796
  { cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
3564
3797
  ).trim();
3565
3798
  const files = grepResult ? grepResult.split("\n").filter(Boolean).filter((f) => !f.startsWith("./")) : [];
@@ -3567,7 +3800,7 @@ async function codebaseImpactFallback(target) {
3567
3800
  return { symbol: target, references: 0, files: 0, testFiles: 0, entryPoints: [], risk: "low" };
3568
3801
  }
3569
3802
  const fileCount = files.length;
3570
- const testFiles = files.filter((f) => f.includes("test") || f.includes("spec") || f.includes("__tests__")).length;
3803
+ const testFiles = files.filter((f) => isTestFile(f)).length;
3571
3804
  const risk = fileCount > 20 ? "high" : fileCount > 5 ? "medium" : "low";
3572
3805
  return {
3573
3806
  symbol: target,
@@ -3666,6 +3899,7 @@ var init_kumaGraph = __esm({
3666
3899
  "use strict";
3667
3900
  init_kumaDb();
3668
3901
  init_kumaSelfHeal();
3902
+ init_languageSupport();
3669
3903
  GRAPH_LIMITS = {
3670
3904
  maxNodes: 5e3,
3671
3905
  // Max nodes before pruning
@@ -3689,10 +3923,10 @@ __export(kumaMemory_exports, {
3689
3923
  shouldRecordDecision: () => shouldRecordDecision
3690
3924
  });
3691
3925
  import fs5 from "fs";
3692
- import path5 from "path";
3926
+ import path6 from "path";
3693
3927
  function scoreMemoryRelevance(context, limit = 5) {
3694
3928
  const results = [];
3695
- const kumaDir = path5.join(getProjectRoot(), MEMORY_DIR);
3929
+ const kumaDir = path6.join(getProjectRoot(), MEMORY_DIR);
3696
3930
  if (!fs5.existsSync(kumaDir)) return [];
3697
3931
  const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
3698
3932
  if (terms.length === 0) return [];
@@ -3700,7 +3934,7 @@ function scoreMemoryRelevance(context, limit = 5) {
3700
3934
  const files = fs5.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
3701
3935
  for (const file of files) {
3702
3936
  try {
3703
- const content = fs5.readFileSync(path5.join(kumaDir, file), "utf-8");
3937
+ const content = fs5.readFileSync(path6.join(kumaDir, file), "utf-8");
3704
3938
  const lower = content.toLowerCase();
3705
3939
  let matchCount = 0;
3706
3940
  for (const term of terms) {
@@ -4250,6 +4484,136 @@ var init_safetyScore = __esm({
4250
4484
  }
4251
4485
  });
4252
4486
 
4487
+ // src/engine/kumaPathRules.ts
4488
+ var kumaPathRules_exports = {};
4489
+ __export(kumaPathRules_exports, {
4490
+ ensureRulesDir: () => ensureRulesDir,
4491
+ formatPathRules: () => formatPathRules,
4492
+ getRulesForScope: () => getRulesForScope,
4493
+ hasPathRules: () => hasPathRules,
4494
+ loadPathRules: () => loadPathRules,
4495
+ matchRulePath: () => matchRulePath
4496
+ });
4497
+ import fs6 from "fs";
4498
+ import path7 from "path";
4499
+ function rulesDir() {
4500
+ return path7.join(getProjectRoot(), RULES_DIR);
4501
+ }
4502
+ function ensureRulesDir() {
4503
+ try {
4504
+ const dir = rulesDir();
4505
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
4506
+ } catch {
4507
+ }
4508
+ }
4509
+ function parseRuleFile(filePath) {
4510
+ try {
4511
+ const raw = fs6.readFileSync(filePath, "utf-8");
4512
+ const id = path7.basename(filePath, ".md");
4513
+ const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
4514
+ if (!fm) {
4515
+ return { id, description: id, paths: ["*"], content: raw.trim(), filePath };
4516
+ }
4517
+ const meta = fm[1];
4518
+ const body = fm[2].trim();
4519
+ const description = meta.match(/description:\s*(.+)/)?.[1]?.trim() || id;
4520
+ const paths = [];
4521
+ const pathSection = meta.match(/paths:\s*\r?\n([\s\S]*?)(?=\r?\n\S|$)/);
4522
+ if (pathSection) {
4523
+ for (const line of pathSection[1].split("\n")) {
4524
+ const m = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/);
4525
+ if (m) paths.push(m[1].trim());
4526
+ }
4527
+ }
4528
+ return { id, description, paths: paths.length ? paths : ["*"], content: body, filePath };
4529
+ } catch {
4530
+ return null;
4531
+ }
4532
+ }
4533
+ function loadPathRules() {
4534
+ ensureRulesDir();
4535
+ try {
4536
+ const dir = rulesDir();
4537
+ return fs6.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => parseRuleFile(path7.join(dir, f))).filter((r) => r !== null);
4538
+ } catch {
4539
+ return [];
4540
+ }
4541
+ }
4542
+ function matchRulePath(pattern, target) {
4543
+ if (pattern === "*" || pattern === "**") return true;
4544
+ const p = pattern.replace(/\\/g, "/").toLowerCase();
4545
+ const t = target.replace(/\\/g, "/").toLowerCase();
4546
+ if (p.endsWith("/**")) {
4547
+ const prefix = p.slice(0, -3);
4548
+ return t === prefix || t.startsWith(prefix + "/");
4549
+ }
4550
+ if (!/[*?]/.test(p) && (t === p || t.startsWith(p + "/"))) return true;
4551
+ let re = "";
4552
+ for (let i = 0; i < p.length; i++) {
4553
+ const c = p[i];
4554
+ if (c === "*") {
4555
+ if (p[i + 1] === "*") {
4556
+ re += i === 0 ? "(?:.*/)?" : ".*";
4557
+ if (p[i + 2] === "/") i += 2;
4558
+ } else {
4559
+ re += "[^/]*";
4560
+ }
4561
+ } else if (c === "?") {
4562
+ re += "[^/]";
4563
+ } else if (/[.()*+?^${}()|[\]\\]/.test(c)) {
4564
+ re += `\\${c}`;
4565
+ } else {
4566
+ re += c;
4567
+ }
4568
+ }
4569
+ try {
4570
+ const rx = new RegExp(`^${re}$`);
4571
+ return rx.test(t);
4572
+ } catch {
4573
+ return t === p;
4574
+ }
4575
+ }
4576
+ function getRulesForScope(scope) {
4577
+ const rules = loadPathRules();
4578
+ if (rules.length === 0) return [];
4579
+ const target = (scope || "").toLowerCase();
4580
+ if (!target) return rules.filter((r) => r.paths.includes("*"));
4581
+ return rules.filter((rule) => rule.paths.some((p) => matchRulePath(p, target)));
4582
+ }
4583
+ function formatPathRules(scope) {
4584
+ const matches = getRulesForScope(scope);
4585
+ if (matches.length === 0) return "";
4586
+ const lines = [
4587
+ `\u{1F4CC} **Path Rules** (${matches.length} rule(s) apply to "${scope || "project"}")`,
4588
+ `\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`,
4589
+ ""
4590
+ ];
4591
+ for (const rule of matches) {
4592
+ lines.push(`**${rule.description}**`);
4593
+ const bodyLines = rule.content.split("\n").filter((l) => l.trim()).slice(0, 6);
4594
+ for (const bl of bodyLines) {
4595
+ lines.push(` ${bl.substring(0, 140)}`);
4596
+ }
4597
+ if (rule.content.split("\n").filter((l) => l.trim()).length > 6) {
4598
+ lines.push(` \u2026 (full rule in ${rule.filePath})`);
4599
+ }
4600
+ lines.push("");
4601
+ }
4602
+ lines.push(`\u{1F4A1} Path rules load on-demand from ${RULES_DIR}/ \u2014 add/remove .md files anytime.`);
4603
+ return lines.join("\n");
4604
+ }
4605
+ function hasPathRules() {
4606
+ return loadPathRules().length > 0;
4607
+ }
4608
+ var RULES_DIR;
4609
+ var init_kumaPathRules = __esm({
4610
+ "src/engine/kumaPathRules.ts"() {
4611
+ "use strict";
4612
+ init_pathValidator();
4613
+ RULES_DIR = ".kuma/rules";
4614
+ }
4615
+ });
4616
+
4253
4617
  // src/engine/kumaCodeScanner.ts
4254
4618
  var kumaCodeScanner_exports = {};
4255
4619
  __export(kumaCodeScanner_exports, {
@@ -4258,8 +4622,8 @@ __export(kumaCodeScanner_exports, {
4258
4622
  scanCodebase: () => scanCodebase
4259
4623
  });
4260
4624
  import fastGlob from "fast-glob";
4261
- import fs6 from "fs";
4262
- import path6 from "path";
4625
+ import fs7 from "fs";
4626
+ import path8 from "path";
4263
4627
  function getRoot() {
4264
4628
  if (!_cachedRoot) _cachedRoot = getProjectRoot();
4265
4629
  return _cachedRoot;
@@ -4270,10 +4634,10 @@ function lineAt(content, index) {
4270
4634
  function getDirectoryDirs(filePaths) {
4271
4635
  const dirs = /* @__PURE__ */ new Set();
4272
4636
  for (const fp of filePaths) {
4273
- let dir = path6.dirname(fp);
4637
+ let dir = path8.dirname(fp);
4274
4638
  while (dir && dir !== "." && dir !== "/") {
4275
4639
  dirs.add(dir);
4276
- dir = path6.dirname(dir);
4640
+ dir = path8.dirname(dir);
4277
4641
  }
4278
4642
  }
4279
4643
  return [...dirs].sort();
@@ -4283,21 +4647,14 @@ async function scanCodebase(options = {}) {
4283
4647
  const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
4284
4648
  const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
4285
4649
  const result = { nodeCount: 0, edgeCount: 0, filesScanned: 0, errors: [] };
4286
- const includePatterns = options.include || [
4287
- "src/**/*.{ts,tsx,js,jsx}",
4288
- "app/**/*.{ts,tsx,js,jsx}",
4289
- "lib/**/*.{ts,tsx,js,jsx}",
4290
- "packages/**/*.{ts,tsx,js,jsx}",
4291
- "server/**/*.{ts,tsx,js,jsx}",
4292
- "api/**/*.{ts,tsx,js,jsx}"
4293
- ];
4650
+ const includePatterns = options.include || [...DEFAULT_SOURCE_INCLUDES];
4294
4651
  if (options.scope) {
4295
4652
  includePatterns.length = 0;
4296
4653
  if (options.scope.includes("/") || options.scope.includes(".")) {
4297
4654
  includePatterns.push(options.scope);
4298
4655
  } else {
4299
- includePatterns.push(`**/*${options.scope}*/**/*.{ts,tsx,js,jsx}`);
4300
- includePatterns.push(`**/*${options.scope}*.{ts,tsx,js,jsx}`);
4656
+ includePatterns.push(`**/*${options.scope}*/**/*.${SOURCE_EXT_GLOB}`);
4657
+ includePatterns.push(`**/*${options.scope}*.${SOURCE_EXT_GLOB}`);
4301
4658
  }
4302
4659
  }
4303
4660
  const ignorePatterns = [
@@ -4330,16 +4687,16 @@ async function scanCodebase(options = {}) {
4330
4687
  const scannedFilePaths = [];
4331
4688
  for (let i = 0; i < scannedCount; i++) {
4332
4689
  const filePath = files[i];
4333
- const fullPath = path6.join(root, filePath);
4334
- if (!fs6.existsSync(fullPath)) continue;
4335
- const stat = fs6.statSync(fullPath);
4690
+ const fullPath = path8.join(root, filePath);
4691
+ if (!fs7.existsSync(fullPath)) continue;
4692
+ const stat = fs7.statSync(fullPath);
4336
4693
  if (stat.size > maxFileSize) continue;
4337
4694
  const mtime = stat.mtimeMs;
4338
4695
  const cached = fileCache.get(filePath);
4339
4696
  if (cached === mtime && !options.force) continue;
4340
4697
  fileCache.set(filePath, mtime);
4341
4698
  try {
4342
- const content = fs6.readFileSync(fullPath, "utf-8");
4699
+ const content = fs7.readFileSync(fullPath, "utf-8");
4343
4700
  const parsed = parseFile(filePath, content);
4344
4701
  allParsed.push({ filePath, parsed, content });
4345
4702
  scannedFilePaths.push(filePath);
@@ -4382,7 +4739,7 @@ async function scanCodebase(options = {}) {
4382
4739
  });
4383
4740
  result.nodeCount++;
4384
4741
  for (const fp of scannedFilePaths) {
4385
- if (path6.dirname(fp) === dir) {
4742
+ if (path8.dirname(fp) === dir) {
4386
4743
  const fileId = nodeId("file", fp);
4387
4744
  try {
4388
4745
  await addEdge({ sourceId: moduleId, targetId: fileId, type: "owns" });
@@ -4407,7 +4764,7 @@ function parseFile(filePath, content) {
4407
4764
  routes: [],
4408
4765
  imports: [],
4409
4766
  exports: [],
4410
- isTest: TEST_PATTERNS.some((p) => filePath.includes(p))
4767
+ isTest: isTestFile(filePath)
4411
4768
  };
4412
4769
  const lines = content.split("\n");
4413
4770
  const isTsx = /\.(tsx|jsx)$/.test(filePath);
@@ -4739,17 +5096,20 @@ async function recordParsedFile(filePath, parsed, content, result) {
4739
5096
  }
4740
5097
  function resolveImportPath(fromFile, importPath, root) {
4741
5098
  if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
4742
- const fromDir = path6.dirname(path6.join(root, fromFile));
4743
- const exts = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
5099
+ const fromDir = path8.dirname(path8.join(root, fromFile));
5100
+ const exts = [
5101
+ ...SOURCE_EXTENSIONS,
5102
+ ...SOURCE_EXTENSIONS.map((e) => `/index${e}`)
5103
+ ];
4744
5104
  for (const ext of exts) {
4745
- const resolved = path6.resolve(fromDir, importPath + ext);
4746
- if (fs6.existsSync(resolved)) return path6.relative(root, resolved);
5105
+ const resolved = path8.resolve(fromDir, importPath + ext);
5106
+ if (fs7.existsSync(resolved)) return path8.relative(root, resolved);
4747
5107
  }
4748
- const dirPath = path6.resolve(fromDir, importPath);
4749
- if (fs6.existsSync(dirPath) && fs6.statSync(dirPath).isDirectory()) {
4750
- for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
4751
- const indexPath = path6.join(dirPath, `index${ext}`);
4752
- if (fs6.existsSync(indexPath)) return path6.relative(root, indexPath);
5108
+ const dirPath = path8.resolve(fromDir, importPath);
5109
+ if (fs7.existsSync(dirPath) && fs7.statSync(dirPath).isDirectory()) {
5110
+ for (const ext of SOURCE_EXTENSIONS) {
5111
+ const indexPath = path8.join(dirPath, `index${ext}`);
5112
+ if (fs7.existsSync(indexPath)) return path8.relative(root, indexPath);
4753
5113
  }
4754
5114
  }
4755
5115
  return null;
@@ -4784,12 +5144,13 @@ async function scanAndFormat(options = {}) {
4784
5144
  const result = await scanCodebase(options);
4785
5145
  return formatScanResult(result);
4786
5146
  }
4787
- var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache, _cachedRoot, FUNCTION_DECL_RE, ARROW_FN_RE, TYPED_ARROW_RE, CLASS_RE, IMPORT_RE, JSX_RETURN_RE, JSX_ELEMENT_RE, JSX_IMPLICIT_RE, EXPRESS_ROUTE_RE, HONO_ROUTE_RE, EXPORT_RE, CALL_RE, TEST_PATTERNS, knownComponents, knownFunctions, symbolLocations;
5147
+ var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache, _cachedRoot, FUNCTION_DECL_RE, ARROW_FN_RE, TYPED_ARROW_RE, CLASS_RE, IMPORT_RE, JSX_RETURN_RE, JSX_ELEMENT_RE, JSX_IMPLICIT_RE, EXPRESS_ROUTE_RE, HONO_ROUTE_RE, EXPORT_RE, CALL_RE, knownComponents, knownFunctions, symbolLocations;
4788
5148
  var init_kumaCodeScanner = __esm({
4789
5149
  "src/engine/kumaCodeScanner.ts"() {
4790
5150
  "use strict";
4791
5151
  init_pathValidator();
4792
5152
  init_kumaGraph();
5153
+ init_languageSupport();
4793
5154
  DEFAULT_MAX_FILES = 200;
4794
5155
  DEFAULT_MAX_FILE_SIZE = 100 * 1024;
4795
5156
  fileCache = /* @__PURE__ */ new Map();
@@ -4806,7 +5167,6 @@ var init_kumaCodeScanner = __esm({
4806
5167
  HONO_ROUTE_RE = /c\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/g;
4807
5168
  EXPORT_RE = /export\s+(?:default\s+)?(\w+)/g;
4808
5169
  CALL_RE = /(\w+)\s*\(/g;
4809
- TEST_PATTERNS = [".test.", ".spec.", "__tests__", "/test/"];
4810
5170
  knownComponents = /* @__PURE__ */ new Set();
4811
5171
  knownFunctions = /* @__PURE__ */ new Set();
4812
5172
  symbolLocations = /* @__PURE__ */ new Map();
@@ -5091,18 +5451,18 @@ __export(domainRules_exports, {
5091
5451
  readLayer: () => readLayer,
5092
5452
  writeLayer: () => writeLayer
5093
5453
  });
5094
- import fs7 from "fs";
5095
- import path7 from "path";
5454
+ import fs8 from "fs";
5455
+ import path9 from "path";
5096
5456
  function layerFilePath(layer) {
5097
- return path7.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
5457
+ return path9.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
5098
5458
  }
5099
5459
  function ensureLayerFile(layer) {
5100
5460
  const fp = layerFilePath(layer);
5101
- if (!fs7.existsSync(fp)) {
5461
+ if (!fs8.existsSync(fp)) {
5102
5462
  const template = layer === "domain_rules" ? DOMAIN_RULES_TEMPLATE : layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
5103
- const dir = path7.dirname(fp);
5104
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
5105
- fs7.writeFileSync(fp, template, "utf-8");
5463
+ const dir = path9.dirname(fp);
5464
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
5465
+ fs8.writeFileSync(fp, template, "utf-8");
5106
5466
  console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
5107
5467
  }
5108
5468
  return fp;
@@ -5110,7 +5470,7 @@ function ensureLayerFile(layer) {
5110
5470
  function readLayer(layer) {
5111
5471
  try {
5112
5472
  const fp = ensureLayerFile(layer);
5113
- return fs7.readFileSync(fp, "utf-8");
5473
+ return fs8.readFileSync(fp, "utf-8");
5114
5474
  } catch (err) {
5115
5475
  return `Error reading ${layer}: ${err}`;
5116
5476
  }
@@ -5118,7 +5478,7 @@ function readLayer(layer) {
5118
5478
  function writeLayer(layer, content) {
5119
5479
  try {
5120
5480
  const fp = ensureLayerFile(layer);
5121
- fs7.writeFileSync(fp, content, "utf-8");
5481
+ fs8.writeFileSync(fp, content, "utf-8");
5122
5482
  const label = LAYER_FILES[layer].label;
5123
5483
  sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
5124
5484
  return `\u2705 **${label}** updated successfully.`;
@@ -5129,9 +5489,9 @@ function writeLayer(layer, content) {
5129
5489
  function appendToLayer(layer, entry) {
5130
5490
  try {
5131
5491
  const fp = ensureLayerFile(layer);
5132
- const existing = fs7.readFileSync(fp, "utf-8");
5492
+ const existing = fs8.readFileSync(fp, "utf-8");
5133
5493
  const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
5134
- fs7.writeFileSync(fp, newContent, "utf-8");
5494
+ fs8.writeFileSync(fp, newContent, "utf-8");
5135
5495
  return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
5136
5496
  } catch (err) {
5137
5497
  return `\u274C Failed to append: ${err}`;
@@ -5141,11 +5501,11 @@ function getLayersSummary() {
5141
5501
  const lines = ["\u{1F4DA} **3-Layer Memory Engine**", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
5142
5502
  for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
5143
5503
  const fp = layerFilePath(layer);
5144
- const exists = fs7.existsSync(fp);
5504
+ const exists = fs8.existsSync(fp);
5145
5505
  const label = LAYER_FILES[layer].label;
5146
5506
  if (exists) {
5147
5507
  try {
5148
- const content = fs7.readFileSync(fp, "utf-8");
5508
+ const content = fs8.readFileSync(fp, "utf-8");
5149
5509
  const lineCount = content.split("\n").length;
5150
5510
  const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
5151
5511
  lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
@@ -5162,7 +5522,7 @@ function getActiveGotchas() {
5162
5522
  const results = [];
5163
5523
  try {
5164
5524
  const fp = ensureLayerFile("gotcha");
5165
- const content = fs7.readFileSync(fp, "utf-8");
5525
+ const content = fs8.readFileSync(fp, "utf-8");
5166
5526
  const lines = content.split("\n");
5167
5527
  let currentSection = "";
5168
5528
  let currentFilePath = "";
@@ -5210,7 +5570,7 @@ function generateDigest() {
5210
5570
  ];
5211
5571
  try {
5212
5572
  const fp1 = ensureLayerFile("domain_rules");
5213
- const content = fs7.readFileSync(fp1, "utf-8");
5573
+ const content = fs8.readFileSync(fp1, "utf-8");
5214
5574
  const rules = [];
5215
5575
  const workflows = [];
5216
5576
  for (const line of content.split("\n")) {
@@ -5226,7 +5586,7 @@ function generateDigest() {
5226
5586
  }
5227
5587
  try {
5228
5588
  const fp2 = ensureLayerFile("arch_flow");
5229
- const content = fs7.readFileSync(fp2, "utf-8");
5589
+ const content = fs8.readFileSync(fp2, "utf-8");
5230
5590
  const chains = [];
5231
5591
  for (const line of content.split("\n")) {
5232
5592
  if (line.includes("->") && line.includes("[")) chains.push(line.trim());
@@ -5370,8 +5730,8 @@ __export(contextDigest_exports, {
5370
5730
  generateContextDigest: () => generateContextDigest,
5371
5731
  generateDigest: () => generateContextDigest
5372
5732
  });
5373
- import fs8 from "fs";
5374
- import path8 from "path";
5733
+ import fs9 from "fs";
5734
+ import path10 from "path";
5375
5735
  function detectTechStack() {
5376
5736
  const root = getProjectRoot();
5377
5737
  const stack = {
@@ -5383,12 +5743,12 @@ function detectTechStack() {
5383
5743
  monorepo: false
5384
5744
  };
5385
5745
  try {
5386
- const pkgPath = path8.join(root, "package.json");
5387
- if (fs8.existsSync(pkgPath)) {
5388
- const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
5746
+ const pkgPath = path10.join(root, "package.json");
5747
+ if (fs9.existsSync(pkgPath)) {
5748
+ const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
5389
5749
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
5390
- if (fs8.existsSync(path8.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
5391
- else if (fs8.existsSync(path8.join(root, "yarn.lock"))) stack.packageManager = "yarn";
5750
+ if (fs9.existsSync(path10.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
5751
+ else if (fs9.existsSync(path10.join(root, "yarn.lock"))) stack.packageManager = "yarn";
5392
5752
  else stack.packageManager = "npm";
5393
5753
  stack.monorepo = !!pkg.workspaces;
5394
5754
  if (deps.next) stack.framework = "Next.js";
@@ -5411,13 +5771,13 @@ function detectTechStack() {
5411
5771
  else if (deps.mocha) stack.testRunner = "Mocha";
5412
5772
  else if (deps.playwright) stack.testRunner = "Playwright";
5413
5773
  else if (deps.cypress) stack.testRunner = "Cypress";
5414
- if (fs8.existsSync(path8.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
5774
+ if (fs9.existsSync(path10.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
5415
5775
  if (deps.typescript) stack.languages.push("TypeScript");
5416
- if (fs8.existsSync(path8.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
5417
- if (fs8.existsSync(path8.join(root, "go.mod"))) stack.languages.push("Go");
5418
- if (fs8.existsSync(path8.join(root, "Cargo.toml"))) stack.languages.push("Rust");
5419
- if (fs8.existsSync(path8.join(root, "pyproject.toml"))) stack.languages.push("Python");
5420
- if (fs8.existsSync(path8.join(root, "Gemfile"))) stack.languages.push("Ruby");
5776
+ if (fs9.existsSync(path10.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
5777
+ if (fs9.existsSync(path10.join(root, "go.mod"))) stack.languages.push("Go");
5778
+ if (fs9.existsSync(path10.join(root, "Cargo.toml"))) stack.languages.push("Rust");
5779
+ if (fs9.existsSync(path10.join(root, "pyproject.toml"))) stack.languages.push("Python");
5780
+ if (fs9.existsSync(path10.join(root, "Gemfile"))) stack.languages.push("Ruby");
5421
5781
  if (stack.languages.length === 0) stack.languages.push("JavaScript");
5422
5782
  }
5423
5783
  } catch {
@@ -5448,13 +5808,13 @@ function detectEntryPoints() {
5448
5808
  "lib/index.ts"
5449
5809
  ];
5450
5810
  for (const c of candidates) {
5451
- if (fs8.existsSync(path8.join(root, c))) {
5811
+ if (fs9.existsSync(path10.join(root, c))) {
5452
5812
  entryPoints.push(c);
5453
5813
  }
5454
5814
  }
5455
5815
  try {
5456
- const pkg = JSON.parse(fs8.readFileSync(path8.join(root, "package.json"), "utf-8"));
5457
- if (pkg.main && fs8.existsSync(path8.join(root, pkg.main))) {
5816
+ const pkg = JSON.parse(fs9.readFileSync(path10.join(root, "package.json"), "utf-8"));
5817
+ if (pkg.main && fs9.existsSync(path10.join(root, pkg.main))) {
5458
5818
  if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
5459
5819
  }
5460
5820
  } catch {
@@ -5500,9 +5860,9 @@ async function generateContextDigest() {
5500
5860
  lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
5501
5861
  }
5502
5862
  try {
5503
- const ruleFile = path8.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
5504
- if (fs8.existsSync(ruleFile)) {
5505
- const content = fs8.readFileSync(ruleFile, "utf-8");
5863
+ const ruleFile = path10.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
5864
+ if (fs9.existsSync(ruleFile)) {
5865
+ const content = fs9.readFileSync(ruleFile, "utf-8");
5506
5866
  const rules = content.split("\n").filter((l) => /^- Rule \d+:?\s*/i.test(l)).map((l) => l.replace(/^- Rule \d+:?\s*/i, "").trim()).filter(Boolean);
5507
5867
  if (rules.length > 0) {
5508
5868
  lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
@@ -5548,15 +5908,15 @@ __export(kumaDriftDetector_exports, {
5548
5908
  getDriftSummary: () => getDriftSummary,
5549
5909
  hashFile: () => hashFile
5550
5910
  });
5551
- import fs9 from "fs";
5552
- import path9 from "path";
5911
+ import fs10 from "fs";
5912
+ import path11 from "path";
5553
5913
  import crypto3 from "crypto";
5554
5914
  function hashFile(filePath) {
5555
5915
  try {
5556
5916
  const root = getProjectRoot();
5557
- const fullPath = path9.resolve(root, filePath);
5558
- if (!fs9.existsSync(fullPath)) return null;
5559
- const content = fs9.readFileSync(fullPath, "utf-8");
5917
+ const fullPath = path11.resolve(root, filePath);
5918
+ if (!fs10.existsSync(fullPath)) return null;
5919
+ const content = fs10.readFileSync(fullPath, "utf-8");
5560
5920
  return crypto3.createHash("sha256").update(content).digest("hex");
5561
5921
  } catch {
5562
5922
  return null;
@@ -5636,8 +5996,8 @@ async function detectDrift() {
5636
5996
  let hasStaleFile = false;
5637
5997
  for (const fp of filePaths) {
5638
5998
  if (!fp) continue;
5639
- const fullPath = path9.resolve(getProjectRoot(), fp);
5640
- if (!fs9.existsSync(fullPath)) {
5999
+ const fullPath = path11.resolve(getProjectRoot(), fp);
6000
+ if (!fs10.existsSync(fullPath)) {
5641
6001
  hasStaleFile = true;
5642
6002
  break;
5643
6003
  }
@@ -5668,12 +6028,12 @@ function computeScopeHash(scope) {
5668
6028
  hash.update(scope);
5669
6029
  let found = false;
5670
6030
  try {
5671
- const files = fs9.readdirSync(root);
6031
+ const files = fs10.readdirSync(root);
5672
6032
  for (const file of files) {
5673
6033
  if (file.toLowerCase().includes(scope.toLowerCase())) {
5674
6034
  try {
5675
- if (fs9.statSync(path9.join(root, file)).isFile()) {
5676
- const content = fs9.readFileSync(path9.join(root, file), "utf-8");
6035
+ if (fs10.statSync(path11.join(root, file)).isFile()) {
6036
+ const content = fs10.readFileSync(path11.join(root, file), "utf-8");
5677
6037
  hash.update(file);
5678
6038
  hash.update(content.substring(0, 1e4));
5679
6039
  found = true;
@@ -5684,11 +6044,11 @@ function computeScopeHash(scope) {
5684
6044
  }
5685
6045
  } catch {
5686
6046
  }
5687
- const researchDir = path9.join(root, ".kuma", "research");
5688
- if (fs9.existsSync(researchDir)) {
5689
- const scopeFile = path9.join(researchDir, `${scope}.json`);
5690
- if (fs9.existsSync(scopeFile)) {
5691
- const content = fs9.readFileSync(scopeFile, "utf-8");
6047
+ const researchDir = path11.join(root, ".kuma", "research");
6048
+ if (fs10.existsSync(researchDir)) {
6049
+ const scopeFile = path11.join(researchDir, `${scope}.json`);
6050
+ if (fs10.existsSync(scopeFile)) {
6051
+ const content = fs10.readFileSync(scopeFile, "utf-8");
5692
6052
  hash.update(content);
5693
6053
  found = true;
5694
6054
  }
@@ -5786,26 +6146,26 @@ __export(kumaProgressiveContext_exports, {
5786
6146
  getSkillBoundary: () => getSkillBoundary,
5787
6147
  loadSection: () => loadSection
5788
6148
  });
5789
- import fs10 from "fs";
5790
- import path10 from "path";
6149
+ import fs11 from "fs";
6150
+ import path12 from "path";
5791
6151
  async function getProjectMeta() {
5792
6152
  const root = getProjectRoot();
5793
6153
  const name = root.split("/").pop() || "unknown";
5794
6154
  const stack = [];
5795
- if (fs10.existsSync(path10.join(root, "package.json"))) {
6155
+ if (fs11.existsSync(path12.join(root, "package.json"))) {
5796
6156
  try {
5797
- const pkg = JSON.parse(fs10.readFileSync(path10.join(root, "package.json"), "utf-8"));
6157
+ const pkg = JSON.parse(fs11.readFileSync(path12.join(root, "package.json"), "utf-8"));
5798
6158
  if (pkg.dependencies?.next) stack.push("Next.js");
5799
6159
  else if (pkg.dependencies?.react) stack.push("React");
5800
6160
  if (pkg.devDependencies?.typescript) stack.push("TypeScript");
5801
- if (fs10.existsSync(path10.join(root, "pnpm-lock.yaml"))) stack.push("pnpm");
5802
- else if (fs10.existsSync(path10.join(root, "yarn.lock"))) stack.push("yarn");
6161
+ if (fs11.existsSync(path12.join(root, "pnpm-lock.yaml"))) stack.push("pnpm");
6162
+ else if (fs11.existsSync(path12.join(root, "yarn.lock"))) stack.push("yarn");
5803
6163
  } catch {
5804
6164
  }
5805
6165
  }
5806
- if (fs10.existsSync(path10.join(root, "go.mod"))) stack.push("Go");
5807
- if (fs10.existsSync(path10.join(root, "Cargo.toml"))) stack.push("Rust");
5808
- if (fs10.existsSync(path10.join(root, "pyproject.toml"))) stack.push("Python");
6166
+ if (fs11.existsSync(path12.join(root, "go.mod"))) stack.push("Go");
6167
+ if (fs11.existsSync(path12.join(root, "Cargo.toml"))) stack.push("Rust");
6168
+ if (fs11.existsSync(path12.join(root, "pyproject.toml"))) stack.push("Python");
5809
6169
  let nodeCount = 0;
5810
6170
  let edgeCount = 0;
5811
6171
  try {
@@ -5816,10 +6176,10 @@ async function getProjectMeta() {
5816
6176
  edgeCount = edgeRes[0]?.values[0]?.[0] ?? 0;
5817
6177
  } catch {
5818
6178
  }
5819
- const kumaDir = path10.join(root, ".kuma");
6179
+ const kumaDir = path12.join(root, ".kuma");
5820
6180
  const layerStatus = {
5821
- domainRules: fs10.existsSync(path10.join(kumaDir, "DOMAIN_RULES.md")),
5822
- archFlow: fs10.existsSync(path10.join(kumaDir, "ARCHITECTURE_FLOW.md")),
6181
+ domainRules: fs11.existsSync(path12.join(kumaDir, "DOMAIN_RULES.md")),
6182
+ archFlow: fs11.existsSync(path12.join(kumaDir, "ARCHITECTURE_FLOW.md")),
5823
6183
  gotchas: getActiveGotchas().length
5824
6184
  };
5825
6185
  let hasDecisions = false;
@@ -6018,8 +6378,8 @@ __export(kumaSearch_exports, {
6018
6378
  formatHybridResults: () => formatHybridResults,
6019
6379
  hybridSearch: () => hybridSearch
6020
6380
  });
6021
- import fs12 from "fs";
6022
- import path12 from "path";
6381
+ import fs13 from "fs";
6382
+ import path14 from "path";
6023
6383
  function expandQueryTerms(query) {
6024
6384
  const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
6025
6385
  const expanded = /* @__PURE__ */ new Map();
@@ -6040,6 +6400,11 @@ function expandQueryTerms(query) {
6040
6400
  }
6041
6401
  return expanded;
6042
6402
  }
6403
+ function recencyScore(epochSec) {
6404
+ if (!epochSec) return 0.5;
6405
+ const ageDays = Math.max(0, (Date.now() / 1e3 - epochSec) / 86400);
6406
+ return Math.max(0, Math.min(1, 1 - ageDays / 90));
6407
+ }
6043
6408
  async function buildSearchVectors() {
6044
6409
  if (_vectorCache && Date.now() - _vectorCache.builtAt < VECTOR_CACHE_TTL) {
6045
6410
  return _vectorCache.vectors;
@@ -6047,7 +6412,7 @@ async function buildSearchVectors() {
6047
6412
  const vectors = [];
6048
6413
  try {
6049
6414
  const db = await getDb();
6050
- const stmt = db.prepare("SELECT id, name, metadata, file_path FROM nodes ORDER BY updated_at DESC LIMIT 500");
6415
+ const stmt = db.prepare("SELECT id, name, metadata, file_path, updated_at FROM nodes ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxNodes);
6051
6416
  while (stmt.step()) {
6052
6417
  const row = stmt.getAsObject();
6053
6418
  const name = row.name || "";
@@ -6068,7 +6433,8 @@ async function buildSearchVectors() {
6068
6433
  vectors.push({
6069
6434
  terms: termCounts,
6070
6435
  source: name || row.id,
6071
- sourceType: "graph"
6436
+ sourceType: "graph",
6437
+ recency: recencyScore(row.updated_at)
6072
6438
  });
6073
6439
  }
6074
6440
  stmt.free();
@@ -6076,12 +6442,13 @@ async function buildSearchVectors() {
6076
6442
  }
6077
6443
  try {
6078
6444
  const root = getProjectRoot();
6079
- const memDir = path12.join(root, ".kuma", "memories");
6080
- if (fs12.existsSync(memDir)) {
6081
- const files = fs12.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, 10);
6445
+ const memDir = path14.join(root, ".kuma", "memories");
6446
+ if (fs13.existsSync(memDir)) {
6447
+ const files = fs13.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
6082
6448
  for (const file of files) {
6083
6449
  try {
6084
- const content = fs12.readFileSync(path12.join(memDir, file), "utf-8");
6450
+ const fullPath = path14.join(memDir, file);
6451
+ const content = fs13.readFileSync(fullPath, "utf-8");
6085
6452
  const text = content.toLowerCase();
6086
6453
  const rawTerms = extractTerms(text);
6087
6454
  const termCounts = /* @__PURE__ */ new Map();
@@ -6091,7 +6458,8 @@ async function buildSearchVectors() {
6091
6458
  vectors.push({
6092
6459
  terms: termCounts,
6093
6460
  source: file.replace(/\.md$/, ""),
6094
- sourceType: "memory"
6461
+ sourceType: "memory",
6462
+ recency: recencyScore(Math.floor(fs13.statSync(fullPath).mtimeMs / 1e3))
6095
6463
  });
6096
6464
  } catch {
6097
6465
  }
@@ -6101,7 +6469,7 @@ async function buildSearchVectors() {
6101
6469
  }
6102
6470
  try {
6103
6471
  const db = await getDb();
6104
- const stmt = db.prepare("SELECT scope, record FROM research_cache ORDER BY updated_at DESC LIMIT 50");
6472
+ const stmt = db.prepare("SELECT scope, record, updated_at FROM research_cache ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxResearch);
6105
6473
  while (stmt.step()) {
6106
6474
  const row = stmt.getAsObject();
6107
6475
  const scope = row.scope || "";
@@ -6115,7 +6483,8 @@ async function buildSearchVectors() {
6115
6483
  vectors.push({
6116
6484
  terms: termCounts,
6117
6485
  source: scope,
6118
- sourceType: "research"
6486
+ sourceType: "research",
6487
+ recency: recencyScore(row.updated_at)
6119
6488
  });
6120
6489
  }
6121
6490
  stmt.free();
@@ -6217,7 +6586,8 @@ async function hybridSearch(query, limit = 10) {
6217
6586
  const existing = results.find((r) => r.source === vec.source);
6218
6587
  if (existing) {
6219
6588
  existing.vectorScore = vectorScore;
6220
- existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4));
6589
+ const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
6590
+ existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4 + recencyBoost));
6221
6591
  for (const [term] of vec.terms) {
6222
6592
  if (queryVector.has(term) && !existing.matchedTerms.includes(term)) {
6223
6593
  existing.matchedTerms.push(term);
@@ -6228,10 +6598,11 @@ async function hybridSearch(query, limit = 10) {
6228
6598
  for (const [term] of vec.terms) {
6229
6599
  if (queryVector.has(term)) matchedTerms.push(term);
6230
6600
  }
6601
+ const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
6231
6602
  results.push({
6232
6603
  source: vec.source,
6233
6604
  sourceType: vec.sourceType,
6234
- score: Math.round(vectorScore * 0.4),
6605
+ score: Math.min(100, Math.round(vectorScore * 0.4 + recencyBoost)),
6235
6606
  keywordScore: 0,
6236
6607
  vectorScore,
6237
6608
  matchedTerms
@@ -6286,7 +6657,7 @@ async function buildGraphConnectivity() {
6286
6657
  let totalEdges = 0;
6287
6658
  try {
6288
6659
  const db = await getDb();
6289
- const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT 2000");
6660
+ const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT " + SCALE_LIMITS.maxEdges);
6290
6661
  while (stmt.step()) {
6291
6662
  const row = stmt.getAsObject();
6292
6663
  const src = row.source_id || "";
@@ -6336,7 +6707,7 @@ function clearSearchCache() {
6336
6707
  _vectorCache = null;
6337
6708
  clearConnectivityCache();
6338
6709
  }
6339
- var SYNONYM_MAP, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
6710
+ var SYNONYM_MAP, SCALE_LIMITS, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
6340
6711
  var init_kumaSearch = __esm({
6341
6712
  "src/engine/kumaSearch.ts"() {
6342
6713
  "use strict";
@@ -6381,6 +6752,16 @@ var init_kumaSearch = __esm({
6381
6752
  update: ["update", "modify", "change", "edit", "patch"],
6382
6753
  delete: ["delete", "remove", "drop", "clear", "purge"]
6383
6754
  };
6755
+ SCALE_LIMITS = {
6756
+ maxNodes: 5e3,
6757
+ // was 500 — graphs grow with project size
6758
+ maxMemoryFiles: 30,
6759
+ // was 10
6760
+ maxResearch: 300,
6761
+ // was 50
6762
+ maxEdges: 2e4
6763
+ // was 2000 (graph connectivity)
6764
+ };
6384
6765
  _vectorCache = null;
6385
6766
  VECTOR_CACHE_TTL = 3e5;
6386
6767
  _connectivityCache = null;
@@ -6393,8 +6774,8 @@ var kumaMiner_exports = {};
6393
6774
  __export(kumaMiner_exports, {
6394
6775
  mineHistoricalDecisions: () => mineHistoricalDecisions
6395
6776
  });
6396
- import fs13 from "fs";
6397
- import path13 from "path";
6777
+ import fs14 from "fs";
6778
+ import path15 from "path";
6398
6779
  import { execSync as execSync4 } from "child_process";
6399
6780
  import fastGlob2 from "fast-glob";
6400
6781
  async function mineHistoricalDecisions(options = {}) {
@@ -6469,7 +6850,7 @@ async function mineHistoricalDecisions(options = {}) {
6469
6850
  }
6470
6851
  if (candidates.length < limit) {
6471
6852
  try {
6472
- const scopePattern = options.scope ? `**/*${options.scope}*.*` : "**/*.{ts,js,py,go,rs,java,md}";
6853
+ const scopePattern = options.scope ? `**/*${options.scope}*.*` : `**/*.{${SOURCE_EXT_GLOB.slice(1, -1)},md}`;
6473
6854
  const files = await fastGlob2(scopePattern, {
6474
6855
  cwd: root,
6475
6856
  ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
@@ -6484,9 +6865,9 @@ async function mineHistoricalDecisions(options = {}) {
6484
6865
  ];
6485
6866
  for (const file of files.slice(0, 50)) {
6486
6867
  if (candidates.length >= limit) break;
6487
- const fullPath = path13.join(root, file);
6488
- if (!fs13.existsSync(fullPath)) continue;
6489
- const content = fs13.readFileSync(fullPath, "utf-8");
6868
+ const fullPath = path15.join(root, file);
6869
+ if (!fs14.existsSync(fullPath)) continue;
6870
+ const content = fs14.readFileSync(fullPath, "utf-8");
6490
6871
  const lines = content.split("\n");
6491
6872
  for (let i = 0; i < lines.length; i++) {
6492
6873
  const l = lines[i];
@@ -6561,6 +6942,7 @@ var init_kumaMiner = __esm({
6561
6942
  "use strict";
6562
6943
  init_kumaDb();
6563
6944
  init_kumaMemory();
6945
+ init_languageSupport();
6564
6946
  }
6565
6947
  });
6566
6948
 
@@ -6793,8 +7175,8 @@ __export(kumaGitHarvester_exports, {
6793
7175
  installGitHook: () => installGitHook
6794
7176
  });
6795
7177
  import { execSync as execSync5 } from "child_process";
6796
- import fs14 from "fs";
6797
- import path14 from "path";
7178
+ import fs15 from "fs";
7179
+ import path16 from "path";
6798
7180
  function getRecentCommits(count = 5) {
6799
7181
  const root = getProjectRoot();
6800
7182
  try {
@@ -6974,11 +7356,11 @@ async function harvestGitHistory(options) {
6974
7356
  }
6975
7357
  function installGitHook() {
6976
7358
  const root = getProjectRoot();
6977
- const hooksDir = path14.join(root, ".git", "hooks");
6978
- if (!fs14.existsSync(hooksDir)) {
7359
+ const hooksDir = path16.join(root, ".git", "hooks");
7360
+ if (!fs15.existsSync(hooksDir)) {
6979
7361
  return "\u26A0\uFE0F Not a git repository \u2014 no .git/hooks/ directory found.";
6980
7362
  }
6981
- const hookPath = path14.join(hooksDir, "post-commit");
7363
+ const hookPath = path16.join(hooksDir, "post-commit");
6982
7364
  const hookContent = `#!/bin/bash
6983
7365
  # Kuma auto-harvester hook (post-commit)
6984
7366
  # Automatically extracts decisions and gotchas from commits
@@ -6986,23 +7368,23 @@ if command -v npx &> /dev/null; then
6986
7368
  npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
6987
7369
  fi
6988
7370
  `;
6989
- if (fs14.existsSync(hookPath)) {
6990
- const existing = fs14.readFileSync(hookPath, "utf-8");
7371
+ if (fs15.existsSync(hookPath)) {
7372
+ const existing = fs15.readFileSync(hookPath, "utf-8");
6991
7373
  if (existing.includes("kuma")) {
6992
7374
  return "\u2705 Kuma git hook already installed.";
6993
7375
  }
6994
7376
  const backupPath = hookPath + ".backup." + Date.now();
6995
- fs14.copyFileSync(hookPath, backupPath);
6996
- fs14.writeFileSync(hookPath, hookContent, "utf-8");
7377
+ fs15.copyFileSync(hookPath, backupPath);
7378
+ fs15.writeFileSync(hookPath, hookContent, "utf-8");
6997
7379
  try {
6998
- fs14.chmodSync(hookPath, 493);
7380
+ fs15.chmodSync(hookPath, 493);
6999
7381
  } catch {
7000
7382
  }
7001
- return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path14.basename(backupPath)})`;
7383
+ return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path16.basename(backupPath)})`;
7002
7384
  }
7003
- fs14.writeFileSync(hookPath, hookContent, "utf-8");
7385
+ fs15.writeFileSync(hookPath, hookContent, "utf-8");
7004
7386
  try {
7005
- fs14.chmodSync(hookPath, 493);
7387
+ fs15.chmodSync(hookPath, 493);
7006
7388
  } catch {
7007
7389
  }
7008
7390
  return "\u2705 Installed kuma post-commit hook.";
@@ -7099,6 +7481,102 @@ function mineSessionInsights() {
7099
7481
  }
7100
7482
  }
7101
7483
  }
7484
+ const dirReads = {};
7485
+ for (const call of toolCalls) {
7486
+ if (call.toolName === "read" || call.toolName === "browser_read") {
7487
+ const file = call.params.filePath || "";
7488
+ if (file) {
7489
+ const dir = file.split("/").slice(0, -1).join("/") || "root";
7490
+ if (!dirReads[dir]) dirReads[dir] = [];
7491
+ dirReads[dir].push(file.split("/").pop() || file);
7492
+ }
7493
+ }
7494
+ }
7495
+ for (const [dir, files] of Object.entries(dirReads)) {
7496
+ if (files.length >= 3) {
7497
+ const dirName = dir.split("/").pop() || dir;
7498
+ insights.push({
7499
+ type: "feature",
7500
+ scope: dir,
7501
+ content: `${dirName} \u2014 ${files.length} files explored: ${files.slice(0, 5).join(", ")}${files.length > 5 ? "..." : ""}`,
7502
+ confidence: 0.6,
7503
+ source: `directory_exploration:${files.length}_files`
7504
+ });
7505
+ }
7506
+ }
7507
+ for (let i = 0; i < toolCalls.length - 1; i++) {
7508
+ const a = toolCalls[i];
7509
+ const b = toolCalls[i + 1];
7510
+ if (a.toolName === "test" && b.toolName === "edit") {
7511
+ const file = b.params.filePath || "";
7512
+ if (file) {
7513
+ insights.push({
7514
+ type: "gotcha",
7515
+ scope: file,
7516
+ content: `Test failed then ${file.split("/").pop()} was edited \u2014 likely a bug fix needed`,
7517
+ confidence: 0.7,
7518
+ source: `test_fail_fix`
7519
+ });
7520
+ }
7521
+ }
7522
+ }
7523
+ const importChain = [];
7524
+ for (const call of toolCalls) {
7525
+ if (call.toolName === "read" || call.toolName === "browser_read") {
7526
+ const file = call.params.filePath || "";
7527
+ if (file && file.endsWith(".ts")) {
7528
+ importChain.push(file);
7529
+ }
7530
+ }
7531
+ }
7532
+ if (importChain.length >= 4) {
7533
+ const uniqueFiles = [...new Set(importChain)];
7534
+ if (uniqueFiles.length >= 3) {
7535
+ insights.push({
7536
+ type: "arch_flow",
7537
+ scope: uniqueFiles[0],
7538
+ content: `Import chain traced: ${uniqueFiles.slice(0, 5).map((f) => f.split("/").pop() || f).join(" \u2192 ")}`,
7539
+ confidence: 0.6,
7540
+ source: `import_chain:${uniqueFiles.length}_files`
7541
+ });
7542
+ }
7543
+ }
7544
+ const routeFiles = [];
7545
+ for (const call of toolCalls) {
7546
+ if (call.toolName === "read" || call.toolName === "browser_read") {
7547
+ const file = call.params.filePath || "";
7548
+ if (file && (file.includes("route") || file.includes("api") || file.includes("controller"))) {
7549
+ routeFiles.push(file.split("/").pop() || file);
7550
+ }
7551
+ }
7552
+ }
7553
+ if (routeFiles.length >= 2) {
7554
+ insights.push({
7555
+ type: "feature",
7556
+ scope: "api",
7557
+ content: `API routes explored: ${routeFiles.slice(0, 5).join(", ")}`,
7558
+ confidence: 0.5,
7559
+ source: `api_route_discovery`
7560
+ });
7561
+ }
7562
+ const componentFiles = [];
7563
+ for (const call of toolCalls) {
7564
+ if (call.toolName === "read" || call.toolName === "browser_read") {
7565
+ const file = call.params.filePath || "";
7566
+ if (file && (file.includes("component") || file.includes(".tsx") || file.includes(".jsx"))) {
7567
+ componentFiles.push(file.split("/").pop() || file);
7568
+ }
7569
+ }
7570
+ }
7571
+ if (componentFiles.length >= 3) {
7572
+ insights.push({
7573
+ type: "arch_flow",
7574
+ scope: "ui",
7575
+ content: `UI components explored: ${componentFiles.slice(0, 5).join(", ")}`,
7576
+ confidence: 0.6,
7577
+ source: `component_discovery:${componentFiles.length}_files`
7578
+ });
7579
+ }
7102
7580
  return insights;
7103
7581
  }
7104
7582
  function formatMinedInsights(insights) {
@@ -7114,12 +7592,12 @@ function formatMinedInsights(insights) {
7114
7592
  ];
7115
7593
  for (let i = 0; i < insights.length; i++) {
7116
7594
  const ins = insights[i];
7117
- const icon = ins.type === "gotcha" ? "\u26A0\uFE0F" : ins.type === "decision" ? "\u{1F4CC}" : "\u{1F500}";
7595
+ const icon = ins.type === "gotcha" ? "\u26A0\uFE0F" : ins.type === "decision" ? "\u{1F4CC}" : ins.type === "feature" ? "\u2B50" : "\u{1F500}";
7118
7596
  const conf = Math.round(ins.confidence * 100);
7119
7597
  lines.push(`${i + 1}. ${icon} **${ins.type}** (${conf}% confidence)`);
7120
7598
  lines.push(` Scope: ${ins.scope}`);
7121
7599
  lines.push(` ${ins.content}`);
7122
- lines.push(` \u2192 \`kuma_memory({ action: "${ins.type}", ${ins.type === "arch_flow" ? "content" : 'scope: \\"${ins.scope}\\", content'}: "${ins.content.substring(0, 60)}..." })\``);
7600
+ lines.push(` \u2192 \`kuma_memory({ action: "${ins.type}", ${ins.type === "arch_flow" ? "content" : ins.type === "feature" ? `title: "\${ins.scope.split('/').pop() || ins.scope}", content` : 'scope: \\"${ins.scope}\\", content'}: "${ins.content.substring(0, 60)}..." })\``);
7123
7601
  lines.push("");
7124
7602
  }
7125
7603
  lines.push("\u{1F4A1} Approve by recording the ones you agree with. Skip the rest.");
@@ -7301,19 +7779,19 @@ __export(kumaPolicyEngine_exports, {
7301
7779
  processOverride: () => processOverride,
7302
7780
  savePolicyConfig: () => savePolicyConfig
7303
7781
  });
7304
- import fs20 from "fs";
7305
- import path20 from "path";
7782
+ import fs21 from "fs";
7783
+ import path22 from "path";
7306
7784
  function loadPolicyConfig() {
7307
7785
  try {
7308
7786
  const root = getProjectRoot();
7309
- const policyPath = path20.join(root, ".kuma", "POLICY.json");
7310
- if (fs20.existsSync(policyPath)) {
7311
- const content = fs20.readFileSync(policyPath, "utf-8");
7787
+ const policyPath = path22.join(root, ".kuma", "POLICY.json");
7788
+ if (fs21.existsSync(policyPath)) {
7789
+ const content = fs21.readFileSync(policyPath, "utf-8");
7312
7790
  const config = JSON.parse(content);
7313
7791
  return { ...DEFAULT_POLICY_CONFIG, ...config };
7314
7792
  }
7315
- const ymlPath = path20.join(root, ".kuma", "policy.yml");
7316
- if (fs20.existsSync(ymlPath)) {
7793
+ const ymlPath = path22.join(root, ".kuma", "policy.yml");
7794
+ if (fs21.existsSync(ymlPath)) {
7317
7795
  console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
7318
7796
  }
7319
7797
  } catch (err) {
@@ -7324,10 +7802,10 @@ function loadPolicyConfig() {
7324
7802
  function savePolicyConfig(config) {
7325
7803
  try {
7326
7804
  const root = getProjectRoot();
7327
- const policyPath = path20.join(root, ".kuma", "POLICY.json");
7328
- const dir = path20.dirname(policyPath);
7329
- if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
7330
- fs20.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
7805
+ const policyPath = path22.join(root, ".kuma", "POLICY.json");
7806
+ const dir = path22.dirname(policyPath);
7807
+ if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
7808
+ fs21.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
7331
7809
  return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
7332
7810
  } catch (err) {
7333
7811
  return `\u274C Failed to save policy: ${err}`;
@@ -7612,14 +8090,14 @@ __export(kumaAstValidator_exports, {
7612
8090
  validateDiff: () => validateDiff,
7613
8091
  validateFile: () => validateFile
7614
8092
  });
7615
- import fs21 from "fs";
7616
- import path21 from "path";
8093
+ import fs22 from "fs";
8094
+ import path23 from "path";
7617
8095
  function loadImportWhitelist() {
7618
8096
  try {
7619
8097
  const root = getProjectRoot();
7620
- const whitelistPath = path21.join(root, ".kuma", "import-whitelist.json");
7621
- if (fs21.existsSync(whitelistPath)) {
7622
- return JSON.parse(fs21.readFileSync(whitelistPath, "utf-8"));
8098
+ const whitelistPath = path23.join(root, ".kuma", "import-whitelist.json");
8099
+ if (fs22.existsSync(whitelistPath)) {
8100
+ return JSON.parse(fs22.readFileSync(whitelistPath, "utf-8"));
7623
8101
  }
7624
8102
  } catch {
7625
8103
  }
@@ -7645,7 +8123,9 @@ function validateCodeContent(code, filePath) {
7645
8123
  suggestion: "Restore the assertion or write an equivalent test for the new behavior"
7646
8124
  });
7647
8125
  }
7648
- if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}") {
8126
+ const emptyCatch = /catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}" || // Python: bare/empty except blocks (except:, except Exception: pass)
8127
+ /except\s*([^:]*)\s*:\s*(pass|\.\.\.)\s*(#.*)?$/.test(line) || /except\s*([^:]*)\s*:\s*$/.test(line) && /^pass(\s*#.*)?$/.test(lines[i + 1]?.trim() ?? "");
8128
+ if (emptyCatch) {
7649
8129
  findings.push({
7650
8130
  line: lineNum,
7651
8131
  severity: "error",
@@ -7654,8 +8134,8 @@ function validateCodeContent(code, filePath) {
7654
8134
  suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
7655
8135
  });
7656
8136
  }
7657
- if (filePath?.includes(".test.") || filePath?.includes(".spec.")) {
7658
- if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
8137
+ if (isTestFile(filePath || "")) {
8138
+ if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\breturn\s+(True|False|None)\b/.test(line) || /\breturn\s+0\b/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
7659
8139
  findings.push({
7660
8140
  line: lineNum,
7661
8141
  severity: "warning",
@@ -7665,7 +8145,7 @@ function validateCodeContent(code, filePath) {
7665
8145
  });
7666
8146
  }
7667
8147
  }
7668
- if (!filePath?.includes(".test.") && !filePath?.includes(".spec.") && /\breturn\s+(true|false|null)\s*;/.test(line) && ((lines[i - 1] || "").includes("TODO") || (lines[i - 1] || "").includes("FIXME") || (lines[i - 1] || "").includes("HACK"))) {
8148
+ if (!isTestFile(filePath || "") && /\breturn\s+(true|false|null)\s*;/.test(line) && ((lines[i - 1] || "").includes("TODO") || (lines[i - 1] || "").includes("FIXME") || (lines[i - 1] || "").includes("HACK"))) {
7669
8149
  findings.push({
7670
8150
  line: lineNum,
7671
8151
  severity: "warning",
@@ -7674,18 +8154,15 @@ function validateCodeContent(code, filePath) {
7674
8154
  suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
7675
8155
  });
7676
8156
  }
7677
- const importMatch = line.match(/import\s+(?:\{[^}]*\}\s+from\s+)?['"]([^'"]+)['"]/);
7678
- if (importMatch) {
7679
- const imported = importMatch[1];
7680
- if (whitelist.blocked.some((b) => imported.includes(b))) {
7681
- findings.push({
7682
- line: lineNum,
7683
- severity: "error",
7684
- category: "import-violation",
7685
- message: `Blocked import: "${imported}" is not allowed per security whitelist`,
7686
- suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
7687
- });
7688
- }
8157
+ const imported = matchImportPath(line);
8158
+ if (imported && whitelist.blocked.some((b) => imported.includes(b))) {
8159
+ findings.push({
8160
+ line: lineNum,
8161
+ severity: "error",
8162
+ category: "import-violation",
8163
+ message: `Blocked import: "${imported}" is not allowed per security whitelist`,
8164
+ suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
8165
+ });
7689
8166
  }
7690
8167
  }
7691
8168
  return findings;
@@ -7693,9 +8170,9 @@ function validateCodeContent(code, filePath) {
7693
8170
  function validateFile(filePath) {
7694
8171
  try {
7695
8172
  const root = getProjectRoot();
7696
- const fullPath = path21.resolve(root, filePath);
7697
- if (!fs21.existsSync(fullPath)) return [];
7698
- const content = fs21.readFileSync(fullPath, "utf-8");
8173
+ const fullPath = path23.resolve(root, filePath);
8174
+ if (!fs22.existsSync(fullPath)) return [];
8175
+ const content = fs22.readFileSync(fullPath, "utf-8");
7699
8176
  return validateCodeContent(content, filePath);
7700
8177
  } catch {
7701
8178
  return [];
@@ -7755,6 +8232,7 @@ var init_kumaAstValidator = __esm({
7755
8232
  "src/engine/kumaAstValidator.ts"() {
7756
8233
  "use strict";
7757
8234
  init_pathValidator();
8235
+ init_languageSupport();
7758
8236
  }
7759
8237
  });
7760
8238
 
@@ -7766,16 +8244,16 @@ __export(kumaCheckpoint_exports, {
7766
8244
  pruneCheckpoints: () => pruneCheckpoints,
7767
8245
  rollbackToCheckpoint: () => rollbackToCheckpoint
7768
8246
  });
7769
- import fs22 from "fs";
7770
- import path22 from "path";
8247
+ import fs23 from "fs";
8248
+ import path24 from "path";
7771
8249
  async function createCheckpoint(label, description) {
7772
8250
  try {
7773
8251
  const root = getProjectRoot();
7774
- const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7775
- if (fs22.existsSync(cpDir)) {
8252
+ const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
8253
+ if (fs23.existsSync(cpDir)) {
7776
8254
  return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
7777
8255
  }
7778
- fs22.mkdirSync(cpDir, { recursive: true });
8256
+ fs23.mkdirSync(cpDir, { recursive: true });
7779
8257
  const db = await getDb();
7780
8258
  const filesStmt = db.prepare(
7781
8259
  "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
@@ -7784,19 +8262,19 @@ async function createCheckpoint(label, description) {
7784
8262
  while (filesStmt.step()) {
7785
8263
  const row = filesStmt.getAsObject();
7786
8264
  const fp = row.file_path;
7787
- const fullPath = path22.resolve(root, fp);
7788
- if (fs22.existsSync(fullPath)) {
7789
- const content = fs22.readFileSync(fullPath, "utf-8");
8265
+ const fullPath = path24.resolve(root, fp);
8266
+ if (fs23.existsSync(fullPath)) {
8267
+ const content = fs23.readFileSync(fullPath, "utf-8");
7790
8268
  const hash = simpleHash2(content);
7791
- const fileDir = path22.dirname(path22.join(cpDir, "files", fp));
7792
- if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
7793
- fs22.writeFileSync(path22.join(cpDir, "files", fp), content, "utf-8");
8269
+ const fileDir = path24.dirname(path24.join(cpDir, "files", fp));
8270
+ if (!fs23.existsSync(fileDir)) fs23.mkdirSync(fileDir, { recursive: true });
8271
+ fs23.writeFileSync(path24.join(cpDir, "files", fp), content, "utf-8");
7794
8272
  files.push({ path: fp, hash });
7795
8273
  }
7796
8274
  }
7797
8275
  filesStmt.free();
7798
8276
  const dbData = db.export();
7799
- fs22.writeFileSync(path22.join(cpDir, "kuma.db"), Buffer.from(dbData));
8277
+ fs23.writeFileSync(path24.join(cpDir, "kuma.db"), Buffer.from(dbData));
7800
8278
  const manifest = {
7801
8279
  label,
7802
8280
  timestamp: Date.now(),
@@ -7804,8 +8282,8 @@ async function createCheckpoint(label, description) {
7804
8282
  dbSnapshot: true,
7805
8283
  description
7806
8284
  };
7807
- fs22.writeFileSync(
7808
- path22.join(cpDir, "manifest.json"),
8285
+ fs23.writeFileSync(
8286
+ path24.join(cpDir, "manifest.json"),
7809
8287
  JSON.stringify(manifest, null, 2),
7810
8288
  "utf-8"
7811
8289
  );
@@ -7831,37 +8309,37 @@ async function createCheckpoint(label, description) {
7831
8309
  async function rollbackToCheckpoint(label) {
7832
8310
  try {
7833
8311
  const root = getProjectRoot();
7834
- const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7835
- const manifestPath = path22.join(cpDir, "manifest.json");
7836
- if (!fs22.existsSync(manifestPath)) {
8312
+ const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
8313
+ const manifestPath = path24.join(cpDir, "manifest.json");
8314
+ if (!fs23.existsSync(manifestPath)) {
7837
8315
  return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
7838
8316
  }
7839
8317
  const manifest = JSON.parse(
7840
- fs22.readFileSync(manifestPath, "utf-8")
8318
+ fs23.readFileSync(manifestPath, "utf-8")
7841
8319
  );
7842
8320
  let restored = 0;
7843
8321
  let failed = 0;
7844
8322
  for (const f of manifest.files) {
7845
- const snapshotPath = path22.join(cpDir, "files", f.path);
7846
- if (fs22.existsSync(snapshotPath)) {
8323
+ const snapshotPath = path24.join(cpDir, "files", f.path);
8324
+ if (fs23.existsSync(snapshotPath)) {
7847
8325
  try {
7848
- const content = fs22.readFileSync(snapshotPath, "utf-8");
7849
- const targetPath = path22.resolve(root, f.path);
7850
- const targetDir = path22.dirname(targetPath);
7851
- if (!fs22.existsSync(targetDir)) fs22.mkdirSync(targetDir, { recursive: true });
7852
- fs22.writeFileSync(targetPath, content, "utf-8");
8326
+ const content = fs23.readFileSync(snapshotPath, "utf-8");
8327
+ const targetPath = path24.resolve(root, f.path);
8328
+ const targetDir = path24.dirname(targetPath);
8329
+ if (!fs23.existsSync(targetDir)) fs23.mkdirSync(targetDir, { recursive: true });
8330
+ fs23.writeFileSync(targetPath, content, "utf-8");
7853
8331
  restored++;
7854
8332
  } catch {
7855
8333
  failed++;
7856
8334
  }
7857
8335
  }
7858
8336
  }
7859
- const dbSnapshotPath = path22.join(cpDir, "kuma.db");
7860
- if (fs22.existsSync(dbSnapshotPath)) {
7861
- const kumaDir = path22.join(root, ".kuma");
7862
- const dbPath = path22.join(kumaDir, "kuma.db");
7863
- const snapshotData = fs22.readFileSync(dbSnapshotPath);
7864
- fs22.writeFileSync(dbPath, snapshotData);
8337
+ const dbSnapshotPath = path24.join(cpDir, "kuma.db");
8338
+ if (fs23.existsSync(dbSnapshotPath)) {
8339
+ const kumaDir = path24.join(root, ".kuma");
8340
+ const dbPath = path24.join(kumaDir, "kuma.db");
8341
+ const snapshotData = fs23.readFileSync(dbSnapshotPath);
8342
+ fs23.writeFileSync(dbPath, snapshotData);
7865
8343
  resetDbInstance();
7866
8344
  }
7867
8345
  sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
@@ -7886,18 +8364,18 @@ async function rollbackToCheckpoint(label) {
7886
8364
  function listCheckpoints() {
7887
8365
  try {
7888
8366
  const root = getProjectRoot();
7889
- const cpDir = path22.join(root, CHECKPOINT_DIR);
7890
- if (!fs22.existsSync(cpDir)) {
8367
+ const cpDir = path24.join(root, CHECKPOINT_DIR);
8368
+ if (!fs23.existsSync(cpDir)) {
7891
8369
  return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
7892
8370
  }
7893
- const entries = fs22.readdirSync(cpDir);
8371
+ const entries = fs23.readdirSync(cpDir);
7894
8372
  const checkpoints = [];
7895
8373
  for (const entry of entries) {
7896
- const manifestPath = path22.join(cpDir, entry, "manifest.json");
7897
- if (fs22.existsSync(manifestPath)) {
8374
+ const manifestPath = path24.join(cpDir, entry, "manifest.json");
8375
+ if (fs23.existsSync(manifestPath)) {
7898
8376
  try {
7899
8377
  const manifest = JSON.parse(
7900
- fs22.readFileSync(manifestPath, "utf-8")
8378
+ fs23.readFileSync(manifestPath, "utf-8")
7901
8379
  );
7902
8380
  checkpoints.push({ label: entry, manifest });
7903
8381
  } catch {
@@ -7929,12 +8407,12 @@ function listCheckpoints() {
7929
8407
  function pruneCheckpoints(keep = 5) {
7930
8408
  try {
7931
8409
  const root = getProjectRoot();
7932
- const cpDir = path22.join(root, CHECKPOINT_DIR);
7933
- if (!fs22.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
7934
- const entries = fs22.readdirSync(cpDir).map((e) => ({ name: e, time: fs22.statSync(path22.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
8410
+ const cpDir = path24.join(root, CHECKPOINT_DIR);
8411
+ if (!fs23.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
8412
+ const entries = fs23.readdirSync(cpDir).map((e) => ({ name: e, time: fs23.statSync(path24.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
7935
8413
  let removed = 0;
7936
8414
  for (let i = keep; i < entries.length; i++) {
7937
- fs22.rmSync(path22.join(cpDir, entries[i].name), { recursive: true, force: true });
8415
+ fs23.rmSync(path24.join(cpDir, entries[i].name), { recursive: true, force: true });
7938
8416
  removed++;
7939
8417
  }
7940
8418
  return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
@@ -7974,18 +8452,18 @@ __export(kumaContractEngine_exports, {
7974
8452
  loadContracts: () => loadContracts,
7975
8453
  runContractChecks: () => runContractChecks
7976
8454
  });
7977
- import fs23 from "fs";
7978
- import path23 from "path";
8455
+ import fs24 from "fs";
8456
+ import path25 from "path";
7979
8457
  function contractsDir() {
7980
- return path23.join(getProjectRoot(), ".kuma", "contracts");
8458
+ return path25.join(getProjectRoot(), ".kuma", "contracts");
7981
8459
  }
7982
8460
  function ensureContractsDir() {
7983
8461
  const dir = contractsDir();
7984
- if (!fs23.existsSync(dir)) {
7985
- fs23.mkdirSync(dir, { recursive: true });
7986
- const defaultPath = path23.join(dir, "default.json");
7987
- if (!fs23.existsSync(defaultPath)) {
7988
- fs23.writeFileSync(
8462
+ if (!fs24.existsSync(dir)) {
8463
+ fs24.mkdirSync(dir, { recursive: true });
8464
+ const defaultPath = path25.join(dir, "default.json");
8465
+ if (!fs24.existsSync(defaultPath)) {
8466
+ fs24.writeFileSync(
7989
8467
  defaultPath,
7990
8468
  JSON.stringify(DEFAULT_CONTRACTS, null, 2),
7991
8469
  "utf-8"
@@ -7998,10 +8476,10 @@ function loadContracts() {
7998
8476
  const dir = contractsDir();
7999
8477
  const contracts = [];
8000
8478
  try {
8001
- const files = fs23.readdirSync(dir).filter((f) => f.endsWith(".json"));
8479
+ const files = fs24.readdirSync(dir).filter((f) => f.endsWith(".json"));
8002
8480
  for (const file of files) {
8003
8481
  try {
8004
- const content = fs23.readFileSync(path23.join(dir, file), "utf-8");
8482
+ const content = fs24.readFileSync(path25.join(dir, file), "utf-8");
8005
8483
  const config = JSON.parse(content);
8006
8484
  contracts.push(config);
8007
8485
  } catch (err) {
@@ -8074,7 +8552,7 @@ function evaluateCondition(condition, contextPath) {
8074
8552
  switch (condition.type) {
8075
8553
  case "file_exists": {
8076
8554
  const targetPath = resolveTarget(condition.target, contextPath, root);
8077
- const exists = targetPath.some((p) => fs23.existsSync(p));
8555
+ const exists = targetPath.some((p) => fs24.existsSync(p));
8078
8556
  return {
8079
8557
  passed: exists,
8080
8558
  message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
@@ -8082,7 +8560,7 @@ function evaluateCondition(condition, contextPath) {
8082
8560
  }
8083
8561
  case "file_not_exists": {
8084
8562
  const targetPath = resolveTarget(condition.target, contextPath, root);
8085
- const exists = targetPath.some((p) => fs23.existsSync(p));
8563
+ const exists = targetPath.some((p) => fs24.existsSync(p));
8086
8564
  return {
8087
8565
  passed: !exists,
8088
8566
  message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
@@ -8090,9 +8568,9 @@ function evaluateCondition(condition, contextPath) {
8090
8568
  }
8091
8569
  case "contains": {
8092
8570
  if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
8093
- const fullPath = path23.resolve(root, contextPath);
8094
- if (!fs23.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
8095
- const content = fs23.readFileSync(fullPath, "utf-8");
8571
+ const fullPath = path25.resolve(root, contextPath);
8572
+ if (!fs24.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
8573
+ const content = fs24.readFileSync(fullPath, "utf-8");
8096
8574
  const hasContent = condition.value ? content.includes(condition.value) : false;
8097
8575
  return {
8098
8576
  passed: hasContent,
@@ -8104,12 +8582,12 @@ function evaluateCondition(condition, contextPath) {
8104
8582
  const root2 = getProjectRoot();
8105
8583
  const targets = resolveTarget(condition.target, contextPath, root2);
8106
8584
  for (const t of targets) {
8107
- if (fs23.existsSync(t)) {
8108
- const content = fs23.readFileSync(t, "utf-8");
8585
+ if (fs24.existsSync(t)) {
8586
+ const content = fs24.readFileSync(t, "utf-8");
8109
8587
  if (condition.value && content.includes(condition.value)) {
8110
8588
  return {
8111
8589
  passed: false,
8112
- message: `\u274C Found forbidden pattern "${condition.value}" in ${path23.relative(root2, t)} (${condition.description})`
8590
+ message: `\u274C Found forbidden pattern "${condition.value}" in ${path25.relative(root2, t)} (${condition.description})`
8113
8591
  };
8114
8592
  }
8115
8593
  }
@@ -8118,15 +8596,11 @@ function evaluateCondition(condition, contextPath) {
8118
8596
  }
8119
8597
  case "has_test_file": {
8120
8598
  if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
8121
- const basename = path23.basename(contextPath, path23.extname(contextPath));
8122
- const dir = path23.dirname(contextPath);
8123
- const possibleTests = [
8124
- path23.join(dir, `${basename}.test.ts`),
8125
- path23.join(dir, `${basename}.spec.ts`),
8126
- path23.join(dir, `__tests__/${basename}.test.ts`),
8127
- path23.join(dir, `__tests__/${basename}.spec.ts`)
8128
- ];
8129
- const hasTest = possibleTests.some((t) => fs23.existsSync(t));
8599
+ const candidates = getTestCandidates(contextPath);
8600
+ const hasTest = candidates.some((t) => {
8601
+ const abs = path25.isAbsolute(t) ? t : path25.resolve(root, t);
8602
+ return fs24.existsSync(abs);
8603
+ });
8130
8604
  return {
8131
8605
  passed: hasTest,
8132
8606
  message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
@@ -8150,18 +8624,24 @@ function resolveTarget(pattern, contextPath, root) {
8150
8624
  const r = root || getProjectRoot();
8151
8625
  if (pattern.startsWith("*")) {
8152
8626
  if (contextPath) {
8153
- const dir = path23.dirname(path23.resolve(r, contextPath));
8627
+ const dir = path25.dirname(path25.resolve(r, contextPath));
8154
8628
  const suffix = pattern.replace("*", "");
8155
8629
  try {
8156
- const files = fs23.readdirSync(dir);
8157
- return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path23.join(dir, f));
8630
+ const files = fs24.readdirSync(dir);
8631
+ return files.filter((f) => {
8632
+ try {
8633
+ return fs24.statSync(path25.join(dir, f)).isFile();
8634
+ } catch {
8635
+ return false;
8636
+ }
8637
+ }).filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path25.join(dir, f));
8158
8638
  } catch {
8159
- return [path23.join(dir, pattern.replace("*", ""))];
8639
+ return [path25.join(dir, pattern.replace("*", ""))];
8160
8640
  }
8161
8641
  }
8162
8642
  return [];
8163
8643
  }
8164
- return [path23.resolve(r, pattern)];
8644
+ return [path25.resolve(r, pattern)];
8165
8645
  }
8166
8646
  async function runContractChecks(toolName, filePath, phase = "pre") {
8167
8647
  const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
@@ -8223,6 +8703,7 @@ var init_kumaContractEngine = __esm({
8223
8703
  "src/engine/kumaContractEngine.ts"() {
8224
8704
  "use strict";
8225
8705
  init_pathValidator();
8706
+ init_languageSupport();
8226
8707
  DEFAULT_CONTRACTS = {
8227
8708
  version: 1,
8228
8709
  contracts: [
@@ -8249,15 +8730,21 @@ var init_kumaContractEngine = __esm({
8249
8730
  postConditions: [
8250
8731
  {
8251
8732
  type: "not_contains",
8252
- target: "*.ts",
8733
+ target: "*",
8253
8734
  value: "catch {}",
8254
8735
  description: "Empty catch blocks suppress errors silently"
8255
8736
  },
8256
8737
  {
8257
8738
  type: "not_contains",
8258
- target: "*.ts",
8739
+ target: "*",
8259
8740
  value: "catch (e) {}",
8260
8741
  description: "Empty catch (e) {} suppresses errors silently"
8742
+ },
8743
+ {
8744
+ type: "not_contains",
8745
+ target: "*",
8746
+ value: "except:",
8747
+ description: "Bare except blocks suppress errors silently (Python)"
8261
8748
  }
8262
8749
  ],
8263
8750
  severity: "error"
@@ -8275,6 +8762,116 @@ var init_kumaContractEngine = __esm({
8275
8762
  }
8276
8763
  });
8277
8764
 
8765
+ // src/engine/kumaAutoGotcha.ts
8766
+ var kumaAutoGotcha_exports = {};
8767
+ __export(kumaAutoGotcha_exports, {
8768
+ AUTO_GOTCHA_ESCALATE_AT: () => AUTO_GOTCHA_ESCALATE_AT,
8769
+ AUTO_GOTCHA_THRESHOLD: () => AUTO_GOTCHA_THRESHOLD,
8770
+ loadAutoGotchaState: () => loadAutoGotchaState,
8771
+ resetScopeFailures: () => resetScopeFailures,
8772
+ saveAutoGotchaState: () => saveAutoGotchaState,
8773
+ trackVerificationResult: () => trackVerificationResult
8774
+ });
8775
+ import fs25 from "fs";
8776
+ import path26 from "path";
8777
+ function statePath() {
8778
+ return path26.join(getProjectRoot(), STATE_FILE);
8779
+ }
8780
+ function loadAutoGotchaState() {
8781
+ try {
8782
+ const fp = statePath();
8783
+ if (fs25.existsSync(fp)) {
8784
+ const parsed = JSON.parse(fs25.readFileSync(fp, "utf-8"));
8785
+ return {
8786
+ failures: parsed?.failures ?? {},
8787
+ lastRecorded: parsed?.lastRecorded ?? {}
8788
+ };
8789
+ }
8790
+ } catch {
8791
+ }
8792
+ return { failures: {}, lastRecorded: {} };
8793
+ }
8794
+ function saveAutoGotchaState(state) {
8795
+ try {
8796
+ const fp = statePath();
8797
+ const dir = path26.dirname(fp);
8798
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8799
+ fs25.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
8800
+ } catch {
8801
+ }
8802
+ }
8803
+ function resetScopeFailures(scope) {
8804
+ const state = loadAutoGotchaState();
8805
+ if (state.failures[scope] !== void 0 || state.lastRecorded?.[scope] !== void 0) {
8806
+ delete state.failures[scope];
8807
+ if (state.lastRecorded) delete state.lastRecorded[scope];
8808
+ saveAutoGotchaState(state);
8809
+ }
8810
+ }
8811
+ async function trackVerificationResult(scope, passed) {
8812
+ if (!scope || scope === "session-impact") return { recorded: false, failureCount: 0 };
8813
+ const state = loadAutoGotchaState();
8814
+ const prev = state.failures[scope] ?? 0;
8815
+ state.lastRecorded = state.lastRecorded || {};
8816
+ if (passed) {
8817
+ if (prev > 0 || state.lastRecorded[scope] !== void 0) {
8818
+ delete state.failures[scope];
8819
+ delete state.lastRecorded[scope];
8820
+ saveAutoGotchaState(state);
8821
+ }
8822
+ return { recorded: false, failureCount: 0 };
8823
+ }
8824
+ const failureCount = prev + 1;
8825
+ state.failures[scope] = failureCount;
8826
+ saveAutoGotchaState(state);
8827
+ const lastRecordedAt = state.lastRecorded[scope] ?? 0;
8828
+ const shouldRecord = failureCount === AUTO_GOTCHA_THRESHOLD || failureCount === AUTO_GOTCHA_ESCALATE_AT;
8829
+ if (!shouldRecord || failureCount <= lastRecordedAt) {
8830
+ return { recorded: false, failureCount };
8831
+ }
8832
+ try {
8833
+ const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
8834
+ const severity = failureCount >= AUTO_GOTCHA_ESCALATE_AT ? "high" : "medium";
8835
+ const result = await addGotcha2({
8836
+ filePath: scope,
8837
+ description: `Verification keeps failing for scope "${scope}"`,
8838
+ severity,
8839
+ workaround: `Run tests locally before touching this area. Failing scope: "${scope}" \u2014 ${failureCount} consecutive failures. Verify with kuma_safety({ action: "verify", scope: "${scope}" }) after fixes.`
8840
+ });
8841
+ state.lastRecorded[scope] = failureCount;
8842
+ saveAutoGotchaState(state);
8843
+ return {
8844
+ recorded: true,
8845
+ failureCount,
8846
+ message: [
8847
+ `\u{1F9E0} **Auto-Gotcha recorded** (self-learning loop)`,
8848
+ `\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`,
8849
+ ``,
8850
+ `\u26A0\uFE0F Scope \`${scope}\` failed verification ${failureCount} consecutive times.`,
8851
+ `A gotcha has been recorded automatically so future sessions avoid re-discovering this.`,
8852
+ ``,
8853
+ result
8854
+ ].join("\n")
8855
+ };
8856
+ } catch (err) {
8857
+ return {
8858
+ recorded: false,
8859
+ failureCount,
8860
+ message: `\u26A0\uFE0F Auto-gotcha recording failed: ${err}`
8861
+ };
8862
+ }
8863
+ }
8864
+ var STATE_FILE, AUTO_GOTCHA_THRESHOLD, AUTO_GOTCHA_ESCALATE_AT;
8865
+ var init_kumaAutoGotcha = __esm({
8866
+ "src/engine/kumaAutoGotcha.ts"() {
8867
+ "use strict";
8868
+ init_pathValidator();
8869
+ STATE_FILE = ".kuma/auto-gotcha.json";
8870
+ AUTO_GOTCHA_THRESHOLD = 2;
8871
+ AUTO_GOTCHA_ESCALATE_AT = 4;
8872
+ }
8873
+ });
8874
+
8278
8875
  // src/engine/kumaVerifier.ts
8279
8876
  var kumaVerifier_exports = {};
8280
8877
  __export(kumaVerifier_exports, {
@@ -8282,37 +8879,37 @@ __export(kumaVerifier_exports, {
8282
8879
  getRunningVerificationPid: () => getRunningVerificationPid,
8283
8880
  runAutoVerification: () => runAutoVerification
8284
8881
  });
8285
- import fs24 from "fs";
8286
- import path24 from "path";
8882
+ import fs26 from "fs";
8883
+ import path27 from "path";
8287
8884
  import { exec } from "child_process";
8288
8885
  function getLockDir(root = process.cwd()) {
8289
- return path24.resolve(root, LOCK_DIR_NAME);
8886
+ return path27.resolve(root, LOCK_DIR_NAME);
8290
8887
  }
8291
8888
  function getPidFile(root = process.cwd()) {
8292
- return path24.join(getLockDir(root), "pid");
8889
+ return path27.join(getLockDir(root), "pid");
8293
8890
  }
8294
8891
  function acquireFileLock(root) {
8295
8892
  const lockDir = getLockDir(root);
8296
8893
  try {
8297
- fs24.mkdirSync(lockDir, { recursive: false });
8298
- fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8894
+ fs26.mkdirSync(lockDir, { recursive: false });
8895
+ fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8299
8896
  return true;
8300
8897
  } catch {
8301
8898
  try {
8302
- if (fs24.existsSync(getPidFile(root))) {
8303
- const pid = parseInt(fs24.readFileSync(getPidFile(root), "utf-8"), 10);
8899
+ if (fs26.existsSync(getPidFile(root))) {
8900
+ const pid = parseInt(fs26.readFileSync(getPidFile(root), "utf-8"), 10);
8304
8901
  try {
8305
8902
  process.kill(pid, 0);
8306
8903
  return false;
8307
8904
  } catch {
8308
- fs24.rmSync(lockDir, { recursive: true, force: true });
8905
+ fs26.rmSync(lockDir, { recursive: true, force: true });
8309
8906
  return acquireFileLock(root);
8310
8907
  }
8311
8908
  }
8312
8909
  } catch {
8313
8910
  }
8314
8911
  try {
8315
- fs24.rmSync(lockDir, { recursive: true, force: true });
8912
+ fs26.rmSync(lockDir, { recursive: true, force: true });
8316
8913
  return acquireFileLock(root);
8317
8914
  } catch {
8318
8915
  return false;
@@ -8321,7 +8918,7 @@ function acquireFileLock(root) {
8321
8918
  }
8322
8919
  function releaseFileLock(root) {
8323
8920
  try {
8324
- fs24.rmSync(getLockDir(root), { recursive: true, force: true });
8921
+ fs26.rmSync(getLockDir(root), { recursive: true, force: true });
8325
8922
  } catch {
8326
8923
  }
8327
8924
  }
@@ -8344,36 +8941,36 @@ function getRunningVerificationPid() {
8344
8941
  return _currentProcess?.pid ?? null;
8345
8942
  }
8346
8943
  function detectTestRunner(root = process.cwd()) {
8347
- const pkgPath = path24.join(root, "package.json");
8348
- if (fs24.existsSync(pkgPath)) {
8944
+ const pkgPath = path27.join(root, "package.json");
8945
+ if (fs26.existsSync(pkgPath)) {
8349
8946
  try {
8350
- const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
8947
+ const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
8351
8948
  if (pkg.scripts && pkg.scripts.test) {
8352
- if (fs24.existsSync(path24.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
8353
- if (fs24.existsSync(path24.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
8949
+ if (fs26.existsSync(path27.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
8950
+ if (fs26.existsSync(path27.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
8354
8951
  return { runner: "npm", baseCommand: "npm test" };
8355
8952
  }
8356
8953
  } catch {
8357
8954
  }
8358
8955
  }
8359
- if (fs24.existsSync(path24.join(root, "pytest.ini")) || fs24.existsSync(path24.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
8360
- if (fs24.existsSync(path24.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
8361
- if (fs24.existsSync(path24.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
8362
- if (fs24.existsSync(path24.join(root, "Makefile"))) {
8956
+ if (fs26.existsSync(path27.join(root, "pytest.ini")) || fs26.existsSync(path27.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
8957
+ if (fs26.existsSync(path27.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
8958
+ if (fs26.existsSync(path27.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
8959
+ if (fs26.existsSync(path27.join(root, "Makefile"))) {
8363
8960
  try {
8364
- const makefileContent = fs24.readFileSync(path24.join(root, "Makefile"), "utf-8");
8961
+ const makefileContent = fs26.readFileSync(path27.join(root, "Makefile"), "utf-8");
8365
8962
  const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
8366
8963
  if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
8367
8964
  } catch {
8368
8965
  }
8369
8966
  }
8370
- if (fs24.existsSync(path24.join(root, "package.json"))) {
8967
+ if (fs26.existsSync(path27.join(root, "package.json"))) {
8371
8968
  try {
8372
- const pkg = JSON.parse(fs24.readFileSync(path24.join(root, "package.json"), "utf-8"));
8969
+ const pkg = JSON.parse(fs26.readFileSync(path27.join(root, "package.json"), "utf-8"));
8373
8970
  if (pkg.dependencies || pkg.devDependencies) {
8374
- if (fs24.existsSync(path24.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
8375
- const srcDir = path24.join(root, "src");
8376
- 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)` };
8971
+ if (fs26.existsSync(path27.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
8972
+ const srcDir = path27.join(root, "src");
8973
+ if (fs26.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
8377
8974
  }
8378
8975
  } catch {
8379
8976
  }
@@ -8492,7 +9089,7 @@ async function runAutoVerification(options = {}) {
8492
9089
  const heartbeatTimer = setInterval(() => {
8493
9090
  if (getLockDir(root)) {
8494
9091
  try {
8495
- fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
9092
+ fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8496
9093
  } catch {
8497
9094
  }
8498
9095
  }
@@ -8512,8 +9109,24 @@ async function runAutoVerification(options = {}) {
8512
9109
  if (passed && fakePassPatterns.some((p) => p.test(rawOutput))) {
8513
9110
  passed = false;
8514
9111
  }
9112
+ const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
8515
9113
  const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
8516
9114
  await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
9115
+ let autoGotchaMessage = null;
9116
+ if (scope !== "session-impact" && !isNoTests) {
9117
+ try {
9118
+ const { trackVerificationResult: trackVerificationResult2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
9119
+ const autoResult = await trackVerificationResult2(scope, passed);
9120
+ if (autoResult.message) autoGotchaMessage = autoResult.message;
9121
+ } catch {
9122
+ }
9123
+ } else if (passed) {
9124
+ try {
9125
+ const { resetScopeFailures: resetScopeFailures2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
9126
+ resetScopeFailures2(scope);
9127
+ } catch {
9128
+ }
9129
+ }
8517
9130
  _localRunning = false;
8518
9131
  _currentProcess = null;
8519
9132
  releaseFileLock(root);
@@ -8530,7 +9143,6 @@ async function runAutoVerification(options = {}) {
8530
9143
  ""
8531
9144
  ];
8532
9145
  if (!passed) {
8533
- const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
8534
9146
  if (isNoTests) {
8535
9147
  lines[0] = `\u26AA **Verification: NO TESTS**`;
8536
9148
  lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
@@ -8545,6 +9157,9 @@ async function runAutoVerification(options = {}) {
8545
9157
  lines.push("\u{1F389} All scoped tests passed!");
8546
9158
  if (rawOutput) lines.push("```text", rawOutput.substring(0, 800), "```");
8547
9159
  }
9160
+ if (autoGotchaMessage) {
9161
+ lines.push("", autoGotchaMessage);
9162
+ }
8548
9163
  resolve(lines.join("\n"));
8549
9164
  });
8550
9165
  _currentProcess = child;
@@ -8586,8 +9201,8 @@ __export(init_exports, {
8586
9201
  generateInitMdContent: () => generateInitMdContent,
8587
9202
  runInit: () => runInit
8588
9203
  });
8589
- import fs25 from "fs";
8590
- import path25 from "path";
9204
+ import fs27 from "fs";
9205
+ import path28 from "path";
8591
9206
  function configFilePath(type) {
8592
9207
  switch (type) {
8593
9208
  case "claude":
@@ -8997,10 +9612,10 @@ function generateInitMdContent() {
8997
9612
  ].join("\n");
8998
9613
  }
8999
9614
  function handleOpencodeSecondary(root, results) {
9000
- const skillPath = path25.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
9615
+ const skillPath = path28.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
9001
9616
  if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
9002
9617
  try {
9003
- const dir = path25.dirname(skillPath);
9618
+ const dir = path28.dirname(skillPath);
9004
9619
  const content = [
9005
9620
  "---",
9006
9621
  "name: kuma-mcp",
@@ -9024,18 +9639,18 @@ function handleOpencodeSecondary(root, results) {
9024
9639
  "",
9025
9640
  "\u{1F4D6} Full rules: `.kuma/init.md`"
9026
9641
  ].join("\n");
9027
- if (fs25.existsSync(skillPath)) {
9028
- const existing = fs25.readFileSync(skillPath, "utf-8");
9642
+ if (fs27.existsSync(skillPath)) {
9643
+ const existing = fs27.readFileSync(skillPath, "utf-8");
9029
9644
  if (existing.includes("kuma-mcp")) {
9030
9645
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
9031
9646
  return;
9032
9647
  }
9033
9648
  const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
9034
- fs25.writeFileSync(skillPath, newContent, "utf-8");
9649
+ fs27.writeFileSync(skillPath, newContent, "utf-8");
9035
9650
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
9036
9651
  } else {
9037
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9038
- fs25.writeFileSync(skillPath, content, "utf-8");
9652
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9653
+ fs27.writeFileSync(skillPath, content, "utf-8");
9039
9654
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
9040
9655
  }
9041
9656
  } catch (err) {
@@ -9048,21 +9663,21 @@ function handleOpencodeSecondary(root, results) {
9048
9663
  }
9049
9664
  }
9050
9665
  function handleCodexSecondary(root, results) {
9051
- const tomlPath = path25.resolve(root, ".codex/config.toml");
9666
+ const tomlPath = path28.resolve(root, ".codex/config.toml");
9052
9667
  if (results.some((r) => r.filePath === ".codex/config.toml")) return;
9053
9668
  try {
9054
- const dir = path25.dirname(tomlPath);
9055
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9056
- if (fs25.existsSync(tomlPath)) {
9057
- const existingContent = fs25.readFileSync(tomlPath, "utf-8");
9669
+ const dir = path28.dirname(tomlPath);
9670
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9671
+ if (fs27.existsSync(tomlPath)) {
9672
+ const existingContent = fs27.readFileSync(tomlPath, "utf-8");
9058
9673
  if (existingContent.includes("kuma")) {
9059
9674
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
9060
9675
  return;
9061
9676
  }
9062
- fs25.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
9677
+ fs27.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
9063
9678
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
9064
9679
  } else {
9065
- fs25.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
9680
+ fs27.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
9066
9681
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
9067
9682
  }
9068
9683
  } catch (err) {
@@ -9075,11 +9690,11 @@ function handleCodexSecondary(root, results) {
9075
9690
  }
9076
9691
  }
9077
9692
  function handleQwenSecondary(root, results) {
9078
- const settingsPath = path25.resolve(root, "settings.json");
9693
+ const settingsPath = path28.resolve(root, "settings.json");
9079
9694
  if (results.some((r) => r.filePath === "settings.json")) return;
9080
9695
  try {
9081
- if (fs25.existsSync(settingsPath)) {
9082
- const existingContent = fs25.readFileSync(settingsPath, "utf-8");
9696
+ if (fs27.existsSync(settingsPath)) {
9697
+ const existingContent = fs27.readFileSync(settingsPath, "utf-8");
9083
9698
  if (existingContent.includes("kuma")) {
9084
9699
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
9085
9700
  try {
@@ -9087,7 +9702,7 @@ function handleQwenSecondary(root, results) {
9087
9702
  parsed.mcpServers = parsed.mcpServers || {};
9088
9703
  if (!parsed.mcpServers.kuma) {
9089
9704
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9090
- fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9705
+ fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9091
9706
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
9092
9707
  return;
9093
9708
  }
@@ -9102,13 +9717,13 @@ function handleQwenSecondary(root, results) {
9102
9717
  parsed.mcpServers = parsed.mcpServers || {};
9103
9718
  if (!parsed.mcpServers.kuma) {
9104
9719
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9105
- fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9720
+ fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9106
9721
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
9107
9722
  }
9108
9723
  } catch {
9109
9724
  }
9110
9725
  } else {
9111
- fs25.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
9726
+ fs27.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
9112
9727
  results.push({ type: "qwen", filePath: "settings.json", action: "created" });
9113
9728
  }
9114
9729
  } catch (err) {
@@ -9206,18 +9821,18 @@ function getCombinedAgentsMd(selectedTypes) {
9206
9821
  return lines.join("\n");
9207
9822
  }
9208
9823
  function handleAntigravityMcpConfig(root, results) {
9209
- const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
9824
+ const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
9210
9825
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
9211
9826
  try {
9212
- const mcpDir = path25.dirname(mcpPath);
9213
- if (fs25.existsSync(mcpPath)) {
9214
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
9827
+ const mcpDir = path28.dirname(mcpPath);
9828
+ if (fs27.existsSync(mcpPath)) {
9829
+ const existingContent = fs27.readFileSync(mcpPath, "utf-8");
9215
9830
  if (existingContent.includes("kuma")) {
9216
9831
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
9217
9832
  const trimmed = existingContent.trimEnd();
9218
9833
  if (trimmed.endsWith("}")) {
9219
9834
  const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
9220
- fs25.writeFileSync(mcpPath, updated, "utf-8");
9835
+ fs27.writeFileSync(mcpPath, updated, "utf-8");
9221
9836
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
9222
9837
  }
9223
9838
  }
@@ -9226,11 +9841,11 @@ function handleAntigravityMcpConfig(root, results) {
9226
9841
  const parsed = JSON.parse(existingContent);
9227
9842
  parsed.mcpServers = parsed.mcpServers || {};
9228
9843
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9229
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9844
+ fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9230
9845
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
9231
9846
  } else {
9232
- if (!fs25.existsSync(mcpDir)) fs25.mkdirSync(mcpDir, { recursive: true });
9233
- fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9847
+ if (!fs27.existsSync(mcpDir)) fs27.mkdirSync(mcpDir, { recursive: true });
9848
+ fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9234
9849
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
9235
9850
  }
9236
9851
  } catch (err) {
@@ -9243,18 +9858,18 @@ function handleAntigravityMcpConfig(root, results) {
9243
9858
  }
9244
9859
  }
9245
9860
  function handleAiderSecondary(root, results) {
9246
- const ymlPath = path25.resolve(root, ".aider.conf.yml");
9861
+ const ymlPath = path28.resolve(root, ".aider.conf.yml");
9247
9862
  if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
9248
9863
  try {
9249
9864
  const conventionsRef = "read: CONVENTIONS.md";
9250
- if (fs25.existsSync(ymlPath)) {
9251
- const existingContent = fs25.readFileSync(ymlPath, "utf-8");
9865
+ if (fs27.existsSync(ymlPath)) {
9866
+ const existingContent = fs27.readFileSync(ymlPath, "utf-8");
9252
9867
  if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
9253
9868
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
9254
9869
  return;
9255
9870
  }
9256
9871
  const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
9257
- fs25.writeFileSync(ymlPath, newContent, "utf-8");
9872
+ fs27.writeFileSync(ymlPath, newContent, "utf-8");
9258
9873
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
9259
9874
  } else {
9260
9875
  const content = [
@@ -9264,7 +9879,7 @@ function handleAiderSecondary(root, results) {
9264
9879
  conventionsRef,
9265
9880
  ""
9266
9881
  ].join("\n");
9267
- fs25.writeFileSync(ymlPath, content, "utf-8");
9882
+ fs27.writeFileSync(ymlPath, content, "utf-8");
9268
9883
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
9269
9884
  }
9270
9885
  } catch (err) {
@@ -9277,10 +9892,10 @@ function handleAiderSecondary(root, results) {
9277
9892
  }
9278
9893
  }
9279
9894
  function handleCopilotSecondary(root, results) {
9280
- const skillPath = path25.resolve(root, ".github/skills/kuma/SKILL.md");
9895
+ const skillPath = path28.resolve(root, ".github/skills/kuma/SKILL.md");
9281
9896
  if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
9282
9897
  try {
9283
- const dir = path25.dirname(skillPath);
9898
+ const dir = path28.dirname(skillPath);
9284
9899
  const content = [
9285
9900
  "---",
9286
9901
  "name: kuma-mcp",
@@ -9291,17 +9906,17 @@ function handleCopilotSecondary(root, results) {
9291
9906
  "",
9292
9907
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules."
9293
9908
  ].join("\n");
9294
- if (fs25.existsSync(skillPath)) {
9295
- const existingContent = fs25.readFileSync(skillPath, "utf-8");
9909
+ if (fs27.existsSync(skillPath)) {
9910
+ const existingContent = fs27.readFileSync(skillPath, "utf-8");
9296
9911
  if (existingContent.includes("kuma")) {
9297
9912
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
9298
9913
  return;
9299
9914
  }
9300
- fs25.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
9915
+ fs27.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
9301
9916
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
9302
9917
  } else {
9303
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9304
- fs25.writeFileSync(skillPath, content, "utf-8");
9918
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9919
+ fs27.writeFileSync(skillPath, content, "utf-8");
9305
9920
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
9306
9921
  }
9307
9922
  } catch (err) {
@@ -9314,21 +9929,21 @@ function handleCopilotSecondary(root, results) {
9314
9929
  }
9315
9930
  }
9316
9931
  function handleOpenclawSecondary(root, results) {
9317
- const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
9932
+ const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
9318
9933
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
9319
9934
  try {
9320
- const dir = path25.dirname(mcpPath);
9321
- if (fs25.existsSync(mcpPath)) {
9322
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
9935
+ const dir = path28.dirname(mcpPath);
9936
+ if (fs27.existsSync(mcpPath)) {
9937
+ const existingContent = fs27.readFileSync(mcpPath, "utf-8");
9323
9938
  if (existingContent.includes("kuma")) return;
9324
9939
  const parsed = JSON.parse(existingContent);
9325
9940
  parsed.mcpServers = parsed.mcpServers || {};
9326
9941
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9327
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9942
+ fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9328
9943
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
9329
9944
  } else {
9330
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9331
- fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9945
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9946
+ fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9332
9947
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
9333
9948
  }
9334
9949
  } catch (err) {
@@ -9341,7 +9956,7 @@ function handleOpenclawSecondary(root, results) {
9341
9956
  }
9342
9957
  }
9343
9958
  function handleQuickrefGeneration(root, results) {
9344
- const quickrefPath = path25.resolve(root, ".kuma/quickref.md");
9959
+ const quickrefPath = path28.resolve(root, ".kuma/quickref.md");
9345
9960
  const content = [
9346
9961
  "# Kuma Quick Reference",
9347
9962
  "",
@@ -9355,8 +9970,9 @@ function handleQuickrefGeneration(root, results) {
9355
9970
  "1. `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
9356
9971
  "",
9357
9972
  "## Record Rules",
9358
- "- **MUST:** decision, gotcha",
9973
+ "- **MUST:** decision, gotcha, arch_flow, research_save, feature",
9359
9974
  "- **SKIP:** function, class, import, route",
9975
+ "- **RELATIONSHIPS:** Every node MUST have edges. No orphan nodes.",
9360
9976
  "",
9361
9977
  "## Platform Tool Names",
9362
9978
  "- OpenCode / Antigravity: `kuma_kuma_*`",
@@ -9366,16 +9982,16 @@ function handleQuickrefGeneration(root, results) {
9366
9982
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9367
9983
  ].join("\n");
9368
9984
  try {
9369
- const kumaDir = path25.dirname(quickrefPath);
9370
- if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
9371
- if (fs25.existsSync(quickrefPath)) {
9372
- const existing = fs25.readFileSync(quickrefPath, "utf-8");
9985
+ const kumaDir = path28.dirname(quickrefPath);
9986
+ if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
9987
+ if (fs27.existsSync(quickrefPath)) {
9988
+ const existing = fs27.readFileSync(quickrefPath, "utf-8");
9373
9989
  if (existing.includes("_Generated by Kuma MCP_")) {
9374
9990
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
9375
9991
  return;
9376
9992
  }
9377
9993
  }
9378
- fs25.writeFileSync(quickrefPath, content, "utf-8");
9994
+ fs27.writeFileSync(quickrefPath, content, "utf-8");
9379
9995
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
9380
9996
  } catch (err) {
9381
9997
  results.push({
@@ -9387,7 +10003,7 @@ function handleQuickrefGeneration(root, results) {
9387
10003
  }
9388
10004
  }
9389
10005
  function handleModeMdGeneration(root, results) {
9390
- const filePath = path25.resolve(root, ".kuma/MODE.md");
10006
+ const filePath = path28.resolve(root, ".kuma/MODE.md");
9391
10007
  const content = [
9392
10008
  "# Adaptive Mode Selector",
9393
10009
  "",
@@ -9430,10 +10046,10 @@ function handleModeMdGeneration(root, results) {
9430
10046
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9431
10047
  ].join("\n");
9432
10048
  try {
9433
- const dir = path25.dirname(filePath);
9434
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9435
- if (!fs25.existsSync(filePath)) {
9436
- fs25.writeFileSync(filePath, content, "utf-8");
10049
+ const dir = path28.dirname(filePath);
10050
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
10051
+ if (!fs27.existsSync(filePath)) {
10052
+ fs27.writeFileSync(filePath, content, "utf-8");
9437
10053
  results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
9438
10054
  }
9439
10055
  } catch (err) {
@@ -9441,7 +10057,7 @@ function handleModeMdGeneration(root, results) {
9441
10057
  }
9442
10058
  }
9443
10059
  function handleSkipRulesMdGeneration(root, results) {
9444
- const filePath = path25.resolve(root, ".kuma/SKIP_RULES.md");
10060
+ const filePath = path28.resolve(root, ".kuma/SKIP_RULES.md");
9445
10061
  const content = [
9446
10062
  "# Skip Rules \u2014 What NOT to Record",
9447
10063
  "",
@@ -9478,10 +10094,10 @@ function handleSkipRulesMdGeneration(root, results) {
9478
10094
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9479
10095
  ].join("\n");
9480
10096
  try {
9481
- const dir = path25.dirname(filePath);
9482
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9483
- if (!fs25.existsSync(filePath)) {
9484
- fs25.writeFileSync(filePath, content, "utf-8");
10097
+ const dir = path28.dirname(filePath);
10098
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
10099
+ if (!fs27.existsSync(filePath)) {
10100
+ fs27.writeFileSync(filePath, content, "utf-8");
9485
10101
  results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
9486
10102
  }
9487
10103
  } catch (err) {
@@ -9489,7 +10105,7 @@ function handleSkipRulesMdGeneration(root, results) {
9489
10105
  }
9490
10106
  }
9491
10107
  function handleStalenessMdGeneration(root, results) {
9492
- const filePath = path25.resolve(root, ".kuma/STALENESS.md");
10108
+ const filePath = path28.resolve(root, ".kuma/STALENESS.md");
9493
10109
  const content = [
9494
10110
  "# Auto-Staleness Detection",
9495
10111
  "",
@@ -9520,10 +10136,10 @@ function handleStalenessMdGeneration(root, results) {
9520
10136
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9521
10137
  ].join("\n");
9522
10138
  try {
9523
- const dir = path25.dirname(filePath);
9524
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9525
- if (!fs25.existsSync(filePath)) {
9526
- fs25.writeFileSync(filePath, content, "utf-8");
10139
+ const dir = path28.dirname(filePath);
10140
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
10141
+ if (!fs27.existsSync(filePath)) {
10142
+ fs27.writeFileSync(filePath, content, "utf-8");
9527
10143
  results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
9528
10144
  }
9529
10145
  } catch (err) {
@@ -9531,20 +10147,20 @@ function handleStalenessMdGeneration(root, results) {
9531
10147
  }
9532
10148
  }
9533
10149
  function handleInitMdGeneration(root, results) {
9534
- const initMdPath = path25.resolve(root, ".kuma/init.md");
10150
+ const initMdPath = path28.resolve(root, ".kuma/init.md");
9535
10151
  try {
9536
- const kumaDir = path25.dirname(initMdPath);
9537
- if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
9538
- if (fs25.existsSync(initMdPath)) {
9539
- const existing = fs25.readFileSync(initMdPath, "utf-8");
10152
+ const kumaDir = path28.dirname(initMdPath);
10153
+ if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
10154
+ if (fs27.existsSync(initMdPath)) {
10155
+ const existing = fs27.readFileSync(initMdPath, "utf-8");
9540
10156
  if (existing.includes("_Generated by Kuma MCP_")) {
9541
10157
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
9542
10158
  return;
9543
10159
  }
9544
- fs25.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
10160
+ fs27.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
9545
10161
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
9546
10162
  } else {
9547
- fs25.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
10163
+ fs27.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
9548
10164
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
9549
10165
  }
9550
10166
  } catch (err) {
@@ -9561,20 +10177,20 @@ function handleInitMdGeneration(root, results) {
9561
10177
  handleStalenessMdGeneration(root, results);
9562
10178
  }
9563
10179
  function handleCodewhaleSecondary(root, results) {
9564
- const mcpPath = path25.resolve(root, ".codewhale/mcp.json");
10180
+ const mcpPath = path28.resolve(root, ".codewhale/mcp.json");
9565
10181
  if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
9566
10182
  try {
9567
- const dir = path25.dirname(mcpPath);
9568
- if (fs25.existsSync(mcpPath)) {
9569
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
10183
+ const dir = path28.dirname(mcpPath);
10184
+ if (fs27.existsSync(mcpPath)) {
10185
+ const existingContent = fs27.readFileSync(mcpPath, "utf-8");
9570
10186
  if (existingContent.includes("kuma")) return;
9571
10187
  const parsed = JSON.parse(existingContent);
9572
10188
  parsed.mcpServers = parsed.mcpServers || {};
9573
10189
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9574
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
10190
+ fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9575
10191
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
9576
10192
  } else {
9577
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
10193
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9578
10194
  const config = {
9579
10195
  mcpServers: {
9580
10196
  kuma: {
@@ -9584,7 +10200,7 @@ function handleCodewhaleSecondary(root, results) {
9584
10200
  }
9585
10201
  }
9586
10202
  };
9587
- fs25.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
10203
+ fs27.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9588
10204
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
9589
10205
  }
9590
10206
  } catch (err) {
@@ -9606,28 +10222,28 @@ function runInit(options) {
9606
10222
  let agentsMdHandled = false;
9607
10223
  for (const type of selected) {
9608
10224
  const relativePath = configFilePath(type);
9609
- const fullPath = path25.resolve(root, relativePath);
10225
+ const fullPath = path28.resolve(root, relativePath);
9610
10226
  const getTemplate = TEMPLATES[type];
9611
10227
  try {
9612
10228
  if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
9613
10229
  agentsMdHandled = true;
9614
10230
  const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
9615
- if (fs25.existsSync(fullPath)) {
10231
+ if (fs27.existsSync(fullPath)) {
9616
10232
  if (options.skipExisting) {
9617
10233
  results.push({ type, filePath: relativePath, action: "skipped" });
9618
10234
  } else {
9619
- const existingContent = fs25.readFileSync(fullPath, "utf-8");
10235
+ const existingContent = fs27.readFileSync(fullPath, "utf-8");
9620
10236
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9621
10237
  results.push({ type, filePath: relativePath, action: "skipped" });
9622
10238
  } else {
9623
- fs25.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
10239
+ fs27.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
9624
10240
  results.push({ type, filePath: relativePath, action: "appended" });
9625
10241
  }
9626
10242
  }
9627
10243
  } else {
9628
- const dir = path25.dirname(fullPath);
9629
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9630
- fs25.writeFileSync(fullPath, combinedContent, "utf-8");
10244
+ const dir = path28.dirname(fullPath);
10245
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
10246
+ fs27.writeFileSync(fullPath, combinedContent, "utf-8");
9631
10247
  results.push({ type, filePath: relativePath, action: "created" });
9632
10248
  }
9633
10249
  if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
@@ -9639,12 +10255,12 @@ function runInit(options) {
9639
10255
  continue;
9640
10256
  } else {
9641
10257
  const template = getTemplate();
9642
- if (fs25.existsSync(fullPath)) {
10258
+ if (fs27.existsSync(fullPath)) {
9643
10259
  if (options.skipExisting) {
9644
10260
  results.push({ type, filePath: relativePath, action: "skipped" });
9645
10261
  continue;
9646
10262
  }
9647
- const existingContent = fs25.readFileSync(fullPath, "utf-8");
10263
+ const existingContent = fs27.readFileSync(fullPath, "utf-8");
9648
10264
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9649
10265
  if (type === "antigravity") {
9650
10266
  handleAntigravityMcpConfig(root, results);
@@ -9657,14 +10273,14 @@ function runInit(options) {
9657
10273
  continue;
9658
10274
  }
9659
10275
  const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
9660
- fs25.writeFileSync(fullPath, newContent, "utf-8");
10276
+ fs27.writeFileSync(fullPath, newContent, "utf-8");
9661
10277
  results.push({ type, filePath: relativePath, action: "appended" });
9662
10278
  } else {
9663
- const dir = path25.dirname(fullPath);
9664
- if (!fs25.existsSync(dir)) {
9665
- fs25.mkdirSync(dir, { recursive: true });
10279
+ const dir = path28.dirname(fullPath);
10280
+ if (!fs27.existsSync(dir)) {
10281
+ fs27.mkdirSync(dir, { recursive: true });
9666
10282
  }
9667
- fs25.writeFileSync(fullPath, template, "utf-8");
10283
+ fs27.writeFileSync(fullPath, template, "utf-8");
9668
10284
  results.push({ type, filePath: relativePath, action: "created" });
9669
10285
  }
9670
10286
  if (type === "antigravity") {
@@ -9806,6 +10422,209 @@ var init_init = __esm({
9806
10422
  }
9807
10423
  });
9808
10424
 
10425
+ // src/engine/kumaLegacyOnboard.ts
10426
+ var kumaLegacyOnboard_exports = {};
10427
+ __export(kumaLegacyOnboard_exports, {
10428
+ runLegacyOnboarding: () => runLegacyOnboarding
10429
+ });
10430
+ import fs28 from "fs";
10431
+ import path29 from "path";
10432
+ async function harvestGit(commitCount) {
10433
+ try {
10434
+ const { harvestGitHistory: harvestGitHistory2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
10435
+ return await harvestGitHistory2({ commitCount });
10436
+ } catch (err) {
10437
+ return `\u26A0\uFE0F Git harvest skipped: ${err}`;
10438
+ }
10439
+ }
10440
+ async function mineInlineMarkers(root, maxFiles) {
10441
+ const markers = [];
10442
+ try {
10443
+ const { default: fg } = await import("fast-glob");
10444
+ const files = await fg(
10445
+ ["**/*.{ts,tsx,js,jsx,py,go,rs,java,kt,cs,rb,php,c,cpp,h,hpp}"],
10446
+ {
10447
+ cwd: root,
10448
+ ignore: ["node_modules/**", "dist/**", "build/**", ".git/**", ".kuma/**", "coverage/**", "vendor/**"],
10449
+ onlyFiles: true,
10450
+ deep: 8
10451
+ }
10452
+ );
10453
+ for (const file of files.slice(0, maxFiles)) {
10454
+ try {
10455
+ const fullPath = path29.join(root, file);
10456
+ const content = fs28.readFileSync(fullPath, "utf-8");
10457
+ const lines = content.split("\n");
10458
+ for (let i = 0; i < lines.length && markers.length < 100; i++) {
10459
+ const line = lines[i];
10460
+ for (const { tag, regex } of MARKER_PATTERNS) {
10461
+ const m = regex.exec(line);
10462
+ if (m) {
10463
+ const text = (m[1] || m[2] || m[3] || line.trim()).trim().substring(0, 160);
10464
+ markers.push({ filePath: file, line: i + 1, tag, text });
10465
+ break;
10466
+ }
10467
+ }
10468
+ }
10469
+ } catch {
10470
+ }
10471
+ }
10472
+ } catch {
10473
+ }
10474
+ return markers;
10475
+ }
10476
+ async function buildFeatureNodes(root) {
10477
+ const names = [];
10478
+ try {
10479
+ const { recordFeature: recordFeature2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10480
+ const pkgPath = path29.join(root, "package.json");
10481
+ if (fs28.existsSync(pkgPath)) {
10482
+ try {
10483
+ const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
10484
+ const candidates = [
10485
+ pkg.main,
10486
+ pkg.module,
10487
+ pkg.bin ? Object.keys(pkg.bin)[0] : null,
10488
+ ...Array.isArray(pkg.exports) ? pkg.exports : []
10489
+ ].filter(Boolean);
10490
+ for (const c of candidates.slice(0, 5)) {
10491
+ const name = path29.basename(c, path29.extname(c)).replace(/[^a-zA-Z0-9]/g, " ").trim();
10492
+ if (name && !names.includes(name)) {
10493
+ await recordFeature2({ name, description: `Detected entry point during legacy onboarding (${c})`, files: [c] });
10494
+ names.push(name);
10495
+ }
10496
+ }
10497
+ } catch {
10498
+ }
10499
+ }
10500
+ const srcDir = path29.join(root, "src");
10501
+ if (fs28.existsSync(srcDir)) {
10502
+ try {
10503
+ const entries = fs28.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((n) => !n.startsWith(".") && !["node_modules", "dist", "build", "__tests__", "test", "tests", "assets", "styles", "types", "utils"].includes(n));
10504
+ for (const dir of entries.slice(0, 15)) {
10505
+ if (!names.includes(dir)) {
10506
+ const files = [path29.join("src", dir)];
10507
+ await recordFeature2({
10508
+ name: dir,
10509
+ description: `Detected top-level module during legacy onboarding`,
10510
+ files,
10511
+ risk: "medium"
10512
+ });
10513
+ names.push(dir);
10514
+ }
10515
+ }
10516
+ } catch {
10517
+ }
10518
+ }
10519
+ } catch {
10520
+ }
10521
+ return { created: names.length, names };
10522
+ }
10523
+ function buildArchitectureDigest(root, featureNames) {
10524
+ const lines = [];
10525
+ try {
10526
+ const pkgPath = path29.join(root, "package.json");
10527
+ if (fs28.existsSync(pkgPath)) {
10528
+ const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
10529
+ lines.push(`\u{1F3D7}\uFE0F **Stack**: ${[pkg.framework, pkg.orm, pkg.testFramework].filter(Boolean).join(" + ") || "detected"}`);
10530
+ }
10531
+ if (featureNames.length > 0) {
10532
+ lines.push(`\u{1F9E9} **Detected Modules**: ${featureNames.slice(0, 8).join(", ")}`);
10533
+ }
10534
+ lines.push(`\u{1F4C4} **Entry Points**: check package.json main/bin or src/index.*`);
10535
+ } catch {
10536
+ }
10537
+ return lines.join("\n");
10538
+ }
10539
+ async function runLegacyOnboarding(options = {}) {
10540
+ const root = getProjectRoot();
10541
+ const commitCount = options.commitCount ?? 25;
10542
+ const maxFiles = options.maxFiles ?? 200;
10543
+ const lines = [
10544
+ "\u{1F680} **Kuma Legacy Onboarding**",
10545
+ "\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",
10546
+ "",
10547
+ `\u{1F4C1} Project: ${root.split("/").pop() || "unknown"}`,
10548
+ "",
10549
+ "\u23F3 Running bulk analysis \u2014 this may take a few seconds...",
10550
+ ""
10551
+ ];
10552
+ lines.push("### 1\uFE0F\u20E3 Git History Harvest");
10553
+ try {
10554
+ const harvest = await harvestGit(commitCount);
10555
+ lines.push(harvest);
10556
+ } catch (err) {
10557
+ lines.push(`\u26A0\uFE0F ${err}`);
10558
+ }
10559
+ lines.push("");
10560
+ lines.push("### 2\uFE0F\u20E3 Inline Marker Mining (HACK/FIXME/TODO \u2192 gotchas)");
10561
+ try {
10562
+ const markers = await mineInlineMarkers(root, maxFiles);
10563
+ if (markers.length === 0) {
10564
+ lines.push("\u2705 No HACK/FIXME/TODO markers found \u2014 clean codebase.");
10565
+ } else {
10566
+ const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
10567
+ let recorded = 0;
10568
+ for (const m of markers.slice(0, 40)) {
10569
+ try {
10570
+ const severity = m.tag === "HACK" || m.tag === "WARNING" ? "high" : m.tag === "FIXME" ? "medium" : "low";
10571
+ await addGotcha2({
10572
+ filePath: m.filePath,
10573
+ description: `[${m.tag}] ${m.text} (line ${m.line})`,
10574
+ severity,
10575
+ workaround: `Legacy marker ${m.tag} at ${m.filePath}:${m.line} \u2014 review before modifying this file.`
10576
+ });
10577
+ recorded++;
10578
+ } catch {
10579
+ }
10580
+ }
10581
+ lines.push(`\u26A0\uFE0F Found ${markers.length} marker(s); recorded ${recorded} as gotchas (capped at 40).`);
10582
+ lines.push(`\u{1F4A1} Remaining ${Math.max(0, markers.length - 40)} \u2014 re-run or use kuma_memory({ action: 'gotcha' }) inline.`);
10583
+ }
10584
+ } catch (err) {
10585
+ lines.push(`\u26A0\uFE0F Marker mining failed: ${err}`);
10586
+ }
10587
+ lines.push("");
10588
+ lines.push("### 3\uFE0F\u20E3 Feature Graph Bootstrap");
10589
+ let featureNames = [];
10590
+ try {
10591
+ const feat = await buildFeatureNodes(root);
10592
+ featureNames = feat.names;
10593
+ lines.push(feat.created > 0 ? `\u{1F9E9} Built ${feat.created} feature node(s): ${feat.names.slice(0, 8).join(", ")}` : "\u2139\uFE0F No obvious top-level features detected \u2014 record manually via kuma_memory({ action: 'feature' }).");
10594
+ } catch (err) {
10595
+ lines.push(`\u26A0\uFE0F Feature bootstrap failed: ${err}`);
10596
+ }
10597
+ lines.push("");
10598
+ lines.push("### 4\uFE0F\u20E3 Architecture Digest");
10599
+ try {
10600
+ const digest = buildArchitectureDigest(root, featureNames);
10601
+ lines.push(digest);
10602
+ } catch {
10603
+ }
10604
+ lines.push("");
10605
+ lines.push("\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");
10606
+ lines.push("\u2705 **Onboarding complete.** Suggested next steps:");
10607
+ lines.push(" \u2022 `kuma_context({ action: 'digest' })` \u2014 compact briefing");
10608
+ lines.push(" \u2022 `kuma_context({ action: 'research', scope: '<area>' })` \u2014 deep-dive first area");
10609
+ lines.push(" \u2022 `kuma_memory({ action: 'arch_flow', content: 'domain: X | hops: a \u2192 b \u2192 c' })` \u2014 map core flows");
10610
+ lines.push(" \u2022 `kuma_safety({ action: 'heal' })` \u2014 repair any stale graph entries");
10611
+ return lines.join("\n");
10612
+ }
10613
+ var MARKER_PATTERNS;
10614
+ var init_kumaLegacyOnboard = __esm({
10615
+ "src/engine/kumaLegacyOnboard.ts"() {
10616
+ "use strict";
10617
+ init_pathValidator();
10618
+ MARKER_PATTERNS = [
10619
+ { tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)|<!--\s*HACK:?\s*(.*)-->/i },
10620
+ { tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)|<!--\s*FIXME:?\s*(.*)-->/i },
10621
+ { tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)|<!--\s*TODO:?\s*(.*)-->/i },
10622
+ { tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)|<!--\s*WARNING:?\s*(.*)-->/i },
10623
+ { tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)|<!--\s*XXX:?\s*(.*)-->/i }
10624
+ ];
10625
+ }
10626
+ });
10627
+
9809
10628
  // src/utils/agentDetector.ts
9810
10629
  var agentDetector_exports = {};
9811
10630
  __export(agentDetector_exports, {
@@ -9814,19 +10633,19 @@ __export(agentDetector_exports, {
9814
10633
  getSkillPath: () => getSkillPath,
9815
10634
  skillExists: () => skillExists
9816
10635
  });
9817
- import fs26 from "fs";
9818
- import path26 from "path";
10636
+ import fs29 from "fs";
10637
+ import path30 from "path";
9819
10638
  function checkFile(root, filePath) {
9820
10639
  try {
9821
- return fs26.existsSync(path26.join(root, filePath));
10640
+ return fs29.existsSync(path30.join(root, filePath));
9822
10641
  } catch {
9823
10642
  return false;
9824
10643
  }
9825
10644
  }
9826
10645
  function checkDir(root, dirPath) {
9827
10646
  try {
9828
- const fullPath = path26.join(root, dirPath);
9829
- return fs26.existsSync(fullPath) && fs26.statSync(fullPath).isDirectory();
10647
+ const fullPath = path30.join(root, dirPath);
10648
+ return fs29.existsSync(fullPath) && fs29.statSync(fullPath).isDirectory();
9830
10649
  } catch {
9831
10650
  return false;
9832
10651
  }
@@ -9894,7 +10713,7 @@ function getAgentLabel(type) {
9894
10713
  function skillExists(type, projectRoot) {
9895
10714
  const root = projectRoot ?? getProjectRoot();
9896
10715
  const skillPath = getSkillPath(type);
9897
- return fs26.existsSync(path26.join(root, skillPath));
10716
+ return fs29.existsSync(path30.join(root, skillPath));
9898
10717
  }
9899
10718
  var AGENT_DETECTORS;
9900
10719
  var init_agentDetector = __esm({
@@ -10338,8 +11157,8 @@ init_kumaDb();
10338
11157
  init_kumaGraph();
10339
11158
  init_kumaMemory();
10340
11159
  init_pathValidator();
10341
- import fs11 from "fs";
10342
- import path11 from "path";
11160
+ import fs12 from "fs";
11161
+ import path13 from "path";
10343
11162
  import crypto4 from "crypto";
10344
11163
  var CONTEXT_ALIASES = {
10345
11164
  // Research synonyms
@@ -10360,6 +11179,11 @@ var CONTEXT_ALIASES = {
10360
11179
  "init": "init",
10361
11180
  "start": "init",
10362
11181
  "load": "init",
11182
+ // Resume synonyms
11183
+ "resume": "resume",
11184
+ "continue": "resume",
11185
+ "restore": "resume",
11186
+ "reload": "resume",
10363
11187
  "brief": "init",
10364
11188
  "project": "init",
10365
11189
  "summary": "init",
@@ -10441,9 +11265,77 @@ async function handleContext(params) {
10441
11265
  return handleDrift(params);
10442
11266
  case "progressive":
10443
11267
  return handleProgressive(params);
11268
+ case "resume":
11269
+ return handleResume(params);
10444
11270
  default:
10445
- return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize, digest, drift, progressive`;
11271
+ return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize, digest, drift, progressive, resume`;
11272
+ }
11273
+ }
11274
+ async function handleResume(_params) {
11275
+ sessionMemory.recordToolCall("kuma_context_resume", {});
11276
+ const lines = [
11277
+ "\u{1F504} **Kuma \u2014 Session Resume**",
11278
+ `\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`,
11279
+ ""
11280
+ ];
11281
+ try {
11282
+ const db = await getDb();
11283
+ const res = db.exec(
11284
+ "SELECT goal, tool_calls, edits, rollbacks, failures, safety_score, started_at FROM sessions ORDER BY started_at DESC LIMIT 1"
11285
+ );
11286
+ if (res[0]?.values.length) {
11287
+ const row = res[0].values[0];
11288
+ const goal = row[0] || "no goal";
11289
+ const calls = row[1] || 0;
11290
+ const edits = row[2] || 0;
11291
+ const rollbacks = row[3] || 0;
11292
+ const failures2 = row[4] || 0;
11293
+ const safety = row[5] == null ? null : row[5];
11294
+ const time = new Date(row[6] * 1e3).toLocaleString();
11295
+ lines.push("**Last Session**");
11296
+ lines.push(` \u{1F3AF} Goal: ${goal.substring(0, 80)}`);
11297
+ lines.push(` \u{1F550} ${time}`);
11298
+ lines.push(` \u{1F527} ${calls} calls \xB7 \u{1F4DD} ${edits} edits \xB7 \u21A9 ${rollbacks} rollbacks \xB7 \u274C ${failures2} failures${safety != null ? ` \xB7 \u{1F6E1} safety ${safety}` : ""}`);
11299
+ lines.push("");
11300
+ }
11301
+ } catch {
11302
+ }
11303
+ const summary = sessionMemory.getSummary();
11304
+ lines.push("**Current State**");
11305
+ lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
11306
+ const progress = sessionMemory.getGoalProgress();
11307
+ if (progress) {
11308
+ lines.push(` \u{1F4CA} Progress: ${progress.percentage}%${progress.milestone ? ` \u2014 ${progress.milestone}` : ""}`);
11309
+ }
11310
+ lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
11311
+ lines.push(` \u2705 Completed steps: ${summary.completedSteps?.length || 0}`);
11312
+ lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
11313
+ lines.push(` \u23F1\uFE0F Session duration: ${summary.sessionDuration}`);
11314
+ const failures = summary.unresolvedFailures;
11315
+ if (failures?.length) {
11316
+ lines.push(` \u26A0\uFE0F Unresolved failures: ${failures.length}`);
11317
+ }
11318
+ lines.push("");
11319
+ try {
11320
+ const changes = await getChanges({ limit: 5 });
11321
+ if (changes && !changes.startsWith("No changes")) {
11322
+ lines.push("**Recent Changes**");
11323
+ lines.push(changes);
11324
+ lines.push("");
11325
+ }
11326
+ } catch {
10446
11327
  }
11328
+ const history = sessionMemory.getToolCallHistory(5);
11329
+ if (history.length) {
11330
+ lines.push("**Recent Tool Calls**");
11331
+ for (const h of history) {
11332
+ const action = h.params?.action || "";
11333
+ lines.push(` \u{1F6E0} ${h.toolName}${action ? ` (${action})` : ""}`);
11334
+ }
11335
+ lines.push("");
11336
+ }
11337
+ lines.push("\u{1F4A1} Continue: kuma_context({ action: 'research', scope: '<area>' }) to pick up where you left off.");
11338
+ return lines.join("\n");
10447
11339
  }
10448
11340
  async function handleInit(_params) {
10449
11341
  sessionMemory.setGoal(_params.goal || "Exploring project");
@@ -10513,6 +11405,15 @@ async function handleInit(_params) {
10513
11405
  lines.push(formatSafetyScore2(score));
10514
11406
  } catch {
10515
11407
  }
11408
+ try {
11409
+ const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
11410
+ const rulesBlock = formatPathRules2(_params.goal);
11411
+ if (rulesBlock) {
11412
+ lines.push("");
11413
+ lines.push(rulesBlock);
11414
+ }
11415
+ } catch {
11416
+ }
10516
11417
  lines.push("", "\u{1F4A1} Call kuma_context({ action: 'research', scope: '<area>' }) to research a specific area.");
10517
11418
  return lines.join("\n");
10518
11419
  }
@@ -10635,6 +11536,15 @@ async function handleResearch(params) {
10635
11536
  lines.push(" \u26A0\uFE0F Memory lookup failed");
10636
11537
  }
10637
11538
  lines.push("");
11539
+ try {
11540
+ const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
11541
+ const rulesBlock = formatPathRules2(scope);
11542
+ if (rulesBlock) {
11543
+ lines.push(rulesBlock);
11544
+ lines.push("");
11545
+ }
11546
+ } catch {
11547
+ }
10638
11548
  const newRecord = {
10639
11549
  scope,
10640
11550
  confidence: record?.confidence || 0.7,
@@ -10817,12 +11727,12 @@ async function handleProgressive(params) {
10817
11727
  function computeProjectHash(scope) {
10818
11728
  try {
10819
11729
  const root = getProjectRoot();
10820
- const files = fs11.readdirSync(root).slice(0, 20);
11730
+ const files = fs12.readdirSync(root).slice(0, 20);
10821
11731
  const hash = crypto4.createHash("md5");
10822
11732
  hash.update(scope);
10823
11733
  for (const f of files) {
10824
11734
  try {
10825
- const stat = fs11.statSync(path11.join(root, f));
11735
+ const stat = fs12.statSync(path13.join(root, f));
10826
11736
  hash.update(`${f}:${stat.mtimeMs}`);
10827
11737
  } catch {
10828
11738
  }
@@ -10891,8 +11801,8 @@ function getNoiseFilterPolicy() {
10891
11801
  // src/tools/kumaMemoryTool.ts
10892
11802
  init_pathValidator();
10893
11803
  init_pathValidator();
10894
- import fs15 from "fs";
10895
- import path15 from "path";
11804
+ import fs16 from "fs";
11805
+ import path17 from "path";
10896
11806
  var MEMORY_ALIASES = {
10897
11807
  // Session synonyms
10898
11808
  "session": "session",
@@ -11057,6 +11967,8 @@ async function handleMemory(params) {
11057
11967
  return handleAddNode(params);
11058
11968
  case "feature":
11059
11969
  return handleFeature(params);
11970
+ case "goal_progress":
11971
+ return handleGoalProgress(params);
11060
11972
  case "delete_node":
11061
11973
  return handleDeleteNode(params);
11062
11974
  case "graph_health":
@@ -11068,8 +11980,6 @@ async function handleMemory(params) {
11068
11980
  }
11069
11981
  case "harvest":
11070
11982
  return handleHarvest(params);
11071
- case "mine":
11072
- return handleMine(params);
11073
11983
  case "session_mine":
11074
11984
  return handleSessionMine(params);
11075
11985
  case "noise_policy":
@@ -11157,9 +12067,9 @@ async function handleResearchSave(params) {
11157
12067
  } catch {
11158
12068
  }
11159
12069
  try {
11160
- const researchDir = path15.join(getProjectRoot(), ".kuma", "research");
11161
- if (!fs15.existsSync(researchDir)) fs15.mkdirSync(researchDir, { recursive: true });
11162
- fs15.writeFileSync(path15.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
12070
+ const researchDir = path17.join(getProjectRoot(), ".kuma", "research");
12071
+ if (!fs16.existsSync(researchDir)) fs16.mkdirSync(researchDir, { recursive: true });
12072
+ fs16.writeFileSync(path17.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
11163
12073
  } catch {
11164
12074
  }
11165
12075
  sessionMemory.recordMemoryAction("research_save");
@@ -11623,13 +12533,22 @@ async function handleFeature(params) {
11623
12533
  tags,
11624
12534
  risk
11625
12535
  });
11626
- sessionMemory.recordMemoryAction("research_save");
12536
+ sessionMemory.recordMemoryAction("feature");
11627
12537
  return `\u2705 **Feature "${params.title}" recorded** \u2014 ${result.nodeCount} node(s), ${result.edgeCount} edge(s)
11628
12538
  \u2B50 Level: feature
11629
12539
  \u{1F4C1} Files: ${files.length}
11630
12540
  \u{1F3F7}\uFE0F Tags: ${tags.length}
11631
12541
  \u26A0\uFE0F Risk: ${risk}`;
11632
12542
  }
12543
+ async function handleGoalProgress(params) {
12544
+ const pct = params.confidence ?? 0;
12545
+ const ms = params.content || params.title;
12546
+ sessionMemory.setGoalProgress(pct, ms);
12547
+ const p = sessionMemory.getGoalProgress();
12548
+ if (!p) return "Error";
12549
+ const bar = "\u2588".repeat(Math.floor(p.percentage / 10)) + "\u2591".repeat(10 - Math.floor(p.percentage / 10));
12550
+ return `\u{1F4CA} ${bar} ${p.percentage}%${p.milestone ? " \u2014 " + p.milestone : ""}`;
12551
+ }
11633
12552
  async function handleGraphHealth(_params) {
11634
12553
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
11635
12554
  const db = await getDb2();
@@ -11760,72 +12679,72 @@ init_sessionMemory();
11760
12679
 
11761
12680
  // src/engine/kumaLock.ts
11762
12681
  init_pathValidator();
11763
- import fs16 from "fs";
11764
- import path16 from "path";
12682
+ import fs17 from "fs";
12683
+ import path18 from "path";
11765
12684
  var LOCKS_DIR = ".kuma/locks";
11766
12685
  function locksDir() {
11767
- return path16.join(getProjectRoot(), LOCKS_DIR);
12686
+ return path18.join(getProjectRoot(), LOCKS_DIR);
11768
12687
  }
11769
12688
  function ensureLocksDir() {
11770
12689
  const dir = locksDir();
11771
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
12690
+ if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
11772
12691
  }
11773
12692
  function lockPath(filePath) {
11774
12693
  const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
11775
- return path16.join(locksDir(), `${safeName}.lock.json`);
12694
+ return path18.join(locksDir(), `${safeName}.lock.json`);
11776
12695
  }
11777
12696
  function acquireLock(filePath, agentId) {
11778
12697
  ensureLocksDir();
11779
12698
  const lp = lockPath(filePath);
11780
12699
  const id = agentId || `agent-${process.pid}`;
11781
- if (fs16.existsSync(lp)) {
12700
+ if (fs17.existsSync(lp)) {
11782
12701
  try {
11783
- const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
12702
+ const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
11784
12703
  if (existing.agentId === id) {
11785
12704
  return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
11786
12705
  }
11787
12706
  const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
11788
12707
  if (elapsed > 300) {
11789
- fs16.unlinkSync(lp);
12708
+ fs17.unlinkSync(lp);
11790
12709
  } else {
11791
12710
  return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
11792
12711
  }
11793
12712
  } catch {
11794
- fs16.unlinkSync(lp);
12713
+ fs17.unlinkSync(lp);
11795
12714
  }
11796
12715
  }
11797
12716
  const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
11798
- fs16.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
12717
+ fs17.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
11799
12718
  return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
11800
12719
  }
11801
12720
  function releaseLock(filePath, agentId) {
11802
12721
  ensureLocksDir();
11803
12722
  const lp = lockPath(filePath);
11804
12723
  const id = agentId || `agent-${process.pid}`;
11805
- if (!fs16.existsSync(lp)) {
12724
+ if (!fs17.existsSync(lp)) {
11806
12725
  return `\u26A0\uFE0F No lock found for \`${filePath}\``;
11807
12726
  }
11808
12727
  try {
11809
- const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
12728
+ const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
11810
12729
  if (existing.agentId !== id) {
11811
12730
  return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
11812
12731
  }
11813
- fs16.unlinkSync(lp);
12732
+ fs17.unlinkSync(lp);
11814
12733
  return `\u{1F513} **Lock released** on \`${filePath}\``;
11815
12734
  } catch {
11816
- fs16.unlinkSync(lp);
12735
+ fs17.unlinkSync(lp);
11817
12736
  return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
11818
12737
  }
11819
12738
  }
11820
12739
  function listLocks() {
11821
12740
  ensureLocksDir();
11822
12741
  const dir = locksDir();
11823
- const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
12742
+ const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
11824
12743
  if (files.length === 0) return "\u{1F513} No active locks.";
11825
12744
  const lines = ["\u{1F512} **Active Locks:**", ""];
11826
12745
  for (const f of files) {
11827
12746
  try {
11828
- const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
12747
+ const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
11829
12748
  const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
11830
12749
  lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
11831
12750
  } catch {
@@ -11836,9 +12755,9 @@ function listLocks() {
11836
12755
  }
11837
12756
  function isLocked(filePath) {
11838
12757
  const lp = lockPath(filePath);
11839
- if (!fs16.existsSync(lp)) return { locked: false };
12758
+ if (!fs17.existsSync(lp)) return { locked: false };
11840
12759
  try {
11841
- const entry = JSON.parse(fs16.readFileSync(lp, "utf-8"));
12760
+ const entry = JSON.parse(fs17.readFileSync(lp, "utf-8"));
11842
12761
  return { locked: true, by: entry.agentId, since: entry.acquiredAt };
11843
12762
  } catch {
11844
12763
  return { locked: false };
@@ -11848,16 +12767,16 @@ function cleanStaleLocks() {
11848
12767
  ensureLocksDir();
11849
12768
  const dir = locksDir();
11850
12769
  let count = 0;
11851
- for (const f of fs16.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
12770
+ for (const f of fs17.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
11852
12771
  try {
11853
- const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
12772
+ const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
11854
12773
  if (Date.now() - entry.acquiredAt > 3e5) {
11855
- fs16.unlinkSync(path16.join(dir, f));
12774
+ fs17.unlinkSync(path18.join(dir, f));
11856
12775
  count++;
11857
12776
  }
11858
12777
  } catch {
11859
12778
  try {
11860
- fs16.unlinkSync(path16.join(dir, f));
12779
+ fs17.unlinkSync(path18.join(dir, f));
11861
12780
  count++;
11862
12781
  } catch {
11863
12782
  }
@@ -11873,8 +12792,8 @@ init_safetyAudit();
11873
12792
 
11874
12793
  // src/tools/kumaPolicy.ts
11875
12794
  init_pathValidator();
11876
- import fs17 from "fs";
11877
- import path17 from "path";
12795
+ import fs18 from "fs";
12796
+ import path19 from "path";
11878
12797
  var DEFAULT_POLICY = {
11879
12798
  never_touch: [
11880
12799
  ".env",
@@ -11900,15 +12819,15 @@ var DEFAULT_POLICY = {
11900
12819
  };
11901
12820
  function loadPolicy() {
11902
12821
  const root = getProjectRoot();
11903
- const ymlPath = path17.join(root, ".kuma", "policy.yml");
11904
- const yamlPath = path17.join(root, ".kuma", "policy.yaml");
12822
+ const ymlPath = path19.join(root, ".kuma", "policy.yml");
12823
+ const yamlPath = path19.join(root, ".kuma", "policy.yaml");
11905
12824
  let policyContent = null;
11906
12825
  let policyPath = null;
11907
- if (fs17.existsSync(ymlPath)) {
11908
- policyContent = fs17.readFileSync(ymlPath, "utf-8");
12826
+ if (fs18.existsSync(ymlPath)) {
12827
+ policyContent = fs18.readFileSync(ymlPath, "utf-8");
11909
12828
  policyPath = ymlPath;
11910
- } else if (fs17.existsSync(yamlPath)) {
11911
- policyContent = fs17.readFileSync(yamlPath, "utf-8");
12829
+ } else if (fs18.existsSync(yamlPath)) {
12830
+ policyContent = fs18.readFileSync(yamlPath, "utf-8");
11912
12831
  policyPath = yamlPath;
11913
12832
  }
11914
12833
  if (!policyContent) {
@@ -12018,9 +12937,9 @@ function checkFilePathPolicy(filePath, policy) {
12018
12937
  if (policy.max_file_size && policy.max_file_size > 0) {
12019
12938
  try {
12020
12939
  const root = getProjectRoot();
12021
- const fullPath = path17.join(root, filePath);
12022
- if (fs17.existsSync(fullPath)) {
12023
- const sizeKB = fs17.statSync(fullPath).size / 1024;
12940
+ const fullPath = path19.join(root, filePath);
12941
+ if (fs18.existsSync(fullPath)) {
12942
+ const sizeKB = fs18.statSync(fullPath).size / 1024;
12024
12943
  if (sizeKB > policy.max_file_size) {
12025
12944
  violations.push({
12026
12945
  rule: "max_file_size",
@@ -12187,12 +13106,12 @@ async function safetyCheck(action, filePath, command) {
12187
13106
  }
12188
13107
  if (filePath) {
12189
13108
  try {
12190
- const fs27 = await import("fs");
12191
- const path27 = await import("path");
13109
+ const fs30 = await import("fs");
13110
+ const path31 = await import("path");
12192
13111
  const root = process.cwd();
12193
- const fullPath = path27.resolve(root, filePath);
12194
- if (fs27.existsSync(fullPath)) {
12195
- const stat = fs27.statSync(fullPath);
13112
+ const fullPath = path31.resolve(root, filePath);
13113
+ if (fs30.existsSync(fullPath)) {
13114
+ const stat = fs30.statSync(fullPath);
12196
13115
  const sizeKB = Math.round(stat.size / 1024);
12197
13116
  if (sizeKB > 500) {
12198
13117
  checks.push({
@@ -12202,12 +13121,12 @@ async function safetyCheck(action, filePath, command) {
12202
13121
  });
12203
13122
  }
12204
13123
  if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
12205
- const baseName = path27.basename(filePath, path27.extname(filePath));
13124
+ const baseName = path31.basename(filePath, path31.extname(filePath));
12206
13125
  const testPatterns = [
12207
13126
  filePath.replace(/\.(ts|js)$/, ".test.$1"),
12208
13127
  filePath.replace(/\.(ts|js)$/, ".spec.$1"),
12209
13128
  filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
12210
- `**/__tests__/**/${baseName}.test.${path27.extname(filePath).slice(1)}`
13129
+ `**/__tests__/**/${baseName}.test.${path31.extname(filePath).slice(1)}`
12211
13130
  ];
12212
13131
  let hasTests = false;
12213
13132
  const fg = await import("fast-glob");
@@ -12223,7 +13142,7 @@ async function safetyCheck(action, filePath, command) {
12223
13142
  name: "Tests Check",
12224
13143
  passed: true,
12225
13144
  // Not a blocker, just informative
12226
- detail: `\u{1F9EA} No test file found for "${path27.basename(filePath)}" \u2014 consider adding tests`
13145
+ detail: `\u{1F9EA} No test file found for "${path31.basename(filePath)}" \u2014 consider adding tests`
12227
13146
  });
12228
13147
  } else {
12229
13148
  checks.push({
@@ -12312,8 +13231,8 @@ init_sessionMemory();
12312
13231
  // src/guards/antiPatternDetector.ts
12313
13232
  init_pathValidator();
12314
13233
  init_sessionMemory();
12315
- import fs18 from "fs";
12316
- import path18 from "path";
13234
+ import fs19 from "fs";
13235
+ import path20 from "path";
12317
13236
  import { execSync as execSync6 } from "child_process";
12318
13237
  var SCRIPT_PATCH_PATTERNS = [
12319
13238
  "writeFileSync",
@@ -12340,20 +13259,20 @@ function findPatchScripts(projectRoot) {
12340
13259
  const warnings = [];
12341
13260
  const recentFiles = scanRecentFiles(projectRoot);
12342
13261
  for (const file of recentFiles) {
12343
- const ext = path18.extname(file).toLowerCase();
13262
+ const ext = path20.extname(file).toLowerCase();
12344
13263
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
12345
- const relativePath = path18.relative(projectRoot, file);
13264
+ const relativePath = path20.relative(projectRoot, file);
12346
13265
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
12347
13266
  continue;
12348
13267
  }
12349
13268
  try {
12350
- const content = fs18.readFileSync(file, "utf-8").toLowerCase();
13269
+ const content = fs19.readFileSync(file, "utf-8").toLowerCase();
12351
13270
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
12352
13271
  if (matchedPattern) {
12353
13272
  warnings.push({
12354
13273
  severity: "high",
12355
13274
  pattern: "script-patching",
12356
- message: `Created script file that modifies other files: ${path18.basename(file)}`,
13275
+ message: `Created script file that modifies other files: ${path20.basename(file)}`,
12357
13276
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
12358
13277
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
12359
13278
  filePath: relativePath
@@ -12387,15 +13306,15 @@ function detectBashGrepUsage() {
12387
13306
  function scanRecentFiles(projectRoot) {
12388
13307
  const recent = [];
12389
13308
  try {
12390
- const entries = fs18.readdirSync(projectRoot, { withFileTypes: true });
13309
+ const entries = fs19.readdirSync(projectRoot, { withFileTypes: true });
12391
13310
  const now = Date.now();
12392
13311
  const recentThreshold = 30 * 60 * 1e3;
12393
13312
  for (const entry of entries) {
12394
13313
  if (!entry.isFile()) continue;
12395
13314
  try {
12396
- const stat = fs18.statSync(path18.join(projectRoot, entry.name));
13315
+ const stat = fs19.statSync(path20.join(projectRoot, entry.name));
12397
13316
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
12398
- recent.push(path18.join(projectRoot, entry.name));
13317
+ recent.push(path20.join(projectRoot, entry.name));
12399
13318
  }
12400
13319
  } catch {
12401
13320
  continue;
@@ -12419,14 +13338,14 @@ function detectGitPatchScripts(projectRoot) {
12419
13338
  const prefix = line.substring(0, 2);
12420
13339
  if (prefix !== "??" && prefix !== "A ") continue;
12421
13340
  const file = line.substring(3).trim();
12422
- const ext = path18.extname(file).toLowerCase();
13341
+ const ext = path20.extname(file).toLowerCase();
12423
13342
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
12424
13343
  const isRootLevel = !file.includes("/");
12425
13344
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
12426
13345
  if (!isRootLevel && !isScriptsDir) continue;
12427
- const fullPath = path18.join(projectRoot, file);
12428
- if (fs18.existsSync(fullPath)) {
12429
- const content = fs18.readFileSync(fullPath, "utf-8").toLowerCase();
13346
+ const fullPath = path20.join(projectRoot, file);
13347
+ if (fs19.existsSync(fullPath)) {
13348
+ const content = fs19.readFileSync(fullPath, "utf-8").toLowerCase();
12430
13349
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
12431
13350
  if (matchedPattern) {
12432
13351
  warnings.push({
@@ -12449,7 +13368,7 @@ function detectGitPatchScripts(projectRoot) {
12449
13368
  if (deletedStdout) {
12450
13369
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
12451
13370
  for (const file of deletedFiles) {
12452
- const ext = path18.extname(file).toLowerCase();
13371
+ const ext = path20.extname(file).toLowerCase();
12453
13372
  if (SCRIPT_EXTENSIONS.includes(ext)) {
12454
13373
  warnings.push({
12455
13374
  severity: "high",
@@ -12511,21 +13430,21 @@ function detectAllAntiPatterns() {
12511
13430
  // src/engine/contextSnapshot.ts
12512
13431
  init_sessionMemory();
12513
13432
  init_pathValidator();
12514
- import fs19 from "fs";
12515
- import path19 from "path";
13433
+ import fs20 from "fs";
13434
+ import path21 from "path";
12516
13435
  import { execSync as execSync7 } from "child_process";
12517
13436
  var SNAPSHOT_DIR = "context-snapshots";
12518
13437
  function snapshotDir() {
12519
- return path19.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
13438
+ return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
12520
13439
  }
12521
13440
  function ensureSnapshotDir() {
12522
13441
  const dir = snapshotDir();
12523
- if (!fs19.existsSync(dir)) {
12524
- fs19.mkdirSync(dir, { recursive: true });
13442
+ if (!fs20.existsSync(dir)) {
13443
+ fs20.mkdirSync(dir, { recursive: true });
12525
13444
  }
12526
13445
  }
12527
13446
  function snapshotFilePath(timestamp) {
12528
- return path19.join(snapshotDir(), `${timestamp}.json`);
13447
+ return path21.join(snapshotDir(), `${timestamp}.json`);
12529
13448
  }
12530
13449
  function saveSnapshot(goal) {
12531
13450
  try {
@@ -12546,7 +13465,7 @@ function saveSnapshot(goal) {
12546
13465
  hasConventions: !!summary.hasConventions
12547
13466
  };
12548
13467
  try {
12549
- fs19.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
13468
+ fs20.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
12550
13469
  } catch {
12551
13470
  }
12552
13471
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -12693,6 +13612,14 @@ async function handleKumaGuard(params) {
12693
13612
  suggestion: "After tracing a flow, record it:\nkuma_memory({ action: 'arch_flow', content: 'domain: <Name> | hops: <file1> \u2192 <file2> \u2192 <file3>' })"
12694
13613
  });
12695
13614
  }
13615
+ if (readCalls >= 5 && recordingSummary.features === 0) {
13616
+ warnings.push({
13617
+ severity: "low",
13618
+ pattern: "explore-without-feature",
13619
+ message: `${readCalls} file reads but 0 features recorded. High-level module understanding helps future sessions.`,
13620
+ suggestion: "After exploring a module/feature, record it:\nkuma_memory({ action: 'feature', title: '<FeatureName>', content: 'description', scope: 'file1.ts,file2.ts' })"
13621
+ });
13622
+ }
12696
13623
  }
12697
13624
  if (check === "context") {
12698
13625
  const snapshot = saveSnapshot(stats.goal);
@@ -12859,12 +13786,12 @@ async function handleSecurity(params) {
12859
13786
  sessionMemory.recordToolCall("kuma_safety_security", {});
12860
13787
  if (params.filePath) {
12861
13788
  try {
12862
- const fs27 = await import("fs");
12863
- const path27 = await import("path");
13789
+ const fs30 = await import("fs");
13790
+ const path31 = await import("path");
12864
13791
  const root = process.cwd();
12865
- const fullPath = path27.resolve(root, params.filePath);
12866
- if (fs27.existsSync(fullPath) && fs27.statSync(fullPath).isFile()) {
12867
- const content = fs27.readFileSync(fullPath, "utf-8");
13792
+ const fullPath = path31.resolve(root, params.filePath);
13793
+ if (fs30.existsSync(fullPath) && fs30.statSync(fullPath).isFile()) {
13794
+ const content = fs30.readFileSync(fullPath, "utf-8");
12868
13795
  const lines = content.split("\n");
12869
13796
  const findings = [];
12870
13797
  for (let i = 0; i < lines.length; i++) {
@@ -12918,23 +13845,23 @@ async function handleGitignore(_params) {
12918
13845
  }
12919
13846
  async function handleClean(_params) {
12920
13847
  sessionMemory.recordToolCall("kuma_safety_clean", {});
12921
- const fs27 = await import("fs");
12922
- const path27 = await import("path");
13848
+ const fs30 = await import("fs");
13849
+ const path31 = await import("path");
12923
13850
  const root = process.cwd();
12924
- const scratchDir = path27.resolve(root, ".kuma", "scratch");
13851
+ const scratchDir = path31.resolve(root, ".kuma", "scratch");
12925
13852
  let removed = 0;
12926
- if (fs27.existsSync(scratchDir)) {
13853
+ if (fs30.existsSync(scratchDir)) {
12927
13854
  try {
12928
- const entries = fs27.readdirSync(scratchDir);
13855
+ const entries = fs30.readdirSync(scratchDir);
12929
13856
  for (const entry of entries) {
12930
- const fullPath = path27.join(scratchDir, entry);
13857
+ const fullPath = path31.join(scratchDir, entry);
12931
13858
  try {
12932
- const stat = fs27.statSync(fullPath);
13859
+ const stat = fs30.statSync(fullPath);
12933
13860
  if (stat.isFile()) {
12934
- fs27.unlinkSync(fullPath);
13861
+ fs30.unlinkSync(fullPath);
12935
13862
  removed++;
12936
13863
  } else if (stat.isDirectory()) {
12937
- fs27.rmSync(fullPath, { recursive: true, force: true });
13864
+ fs30.rmSync(fullPath, { recursive: true, force: true });
12938
13865
  removed++;
12939
13866
  }
12940
13867
  } catch {
@@ -13050,7 +13977,7 @@ async function handleVerify(params) {
13050
13977
  } else if (recordingSummary.total > 0) {
13051
13978
  recordingWarning = `
13052
13979
 
13053
- \u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
13980
+ \u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save, ${recordingSummary.features} feature)`;
13054
13981
  }
13055
13982
  sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
13056
13983
  const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
@@ -13131,7 +14058,7 @@ function registerAllTools(server) {
13131
14058
  "kuma_context",
13132
14059
  "**Call FIRST every session.** Understand your project before making changes.\n\nWORKFLOW:\n\u2022 STEP 1 (ALWAYS): `init` \u2014 load project brief, restore session\n\u2022 STEP 3 (BEFORE edits): `research` \u2014 5-step pipeline (cache\u2192staleness\u2192graph\u2192impact\u2192decision)\n\u2022 STEP 8 (END of session): `changes` \u2014 review what you modified\n\nOther actions: impact (analyze changes), navigate (trace flow), health (score 0-100), digest (compact briefing), drift (detect staleness).",
13133
14060
  {
13134
- action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize", "digest", "drift", "progressive"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, sync=unified batch state, visualize=Mermaid knowledge graph diagram, digest=ultra-compact <500 token project briefing (Issue #18), drift=detect memory staleness & code drift (Issue #20), progressive=progressive context loading (Issue #25), health=project health score"),
14061
+ action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize", "digest", "drift", "progressive", "resume"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, sync=unified batch state, visualize=Mermaid knowledge graph diagram, digest=ultra-compact <500 token project briefing (Issue #18), drift=detect memory staleness & code drift (Issue #20), progressive=progressive context loading (Issue #25), health=project health score, resume=load previous session context (goal, progress, changes)"),
13135
14062
  scope: z.string().optional().describe("Research scope for research/progressive action"),
13136
14063
  target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
13137
14064
  goal: z.string().optional().describe("Current goal (for init/health)"),
@@ -13159,9 +14086,9 @@ function registerAllTools(server) {
13159
14086
  );
13160
14087
  server.tool(
13161
14088
  "kuma_memory",
13162
- "**Call after research/editing** (including research-only). Record what matters, skip what doesn't.\n\n\u{1F534} MUST RECORD \u2014 High Impact (saves agent time next session):\n\u2022 STEP 5: `research_save` \u2014 after exploring area (creates search cache).\n\u2022 STEP 6: `gotcha` \u2014 IMMEDIATELY when you discover bugs/quirks. No re-research.\n\u2022 STEP 7: `arch_flow` \u2014 AFTER tracing COMPLETE flow (max 5 core files).\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\n\u{1F7E2} SKIP using MCP (agent native tools are faster):\n\u2022 Function/class nodes \u2192 grep\n\u2022 Component/route nodes \u2192 glob or check directly\n\u2022 Import edges \u2192 read imports directly\n\u2022 Visual graph \u2192 for humans, not AI agents\n\nOther actions: session, heal, search, changes, benchmark, layers.",
14089
+ "**Call after research/editing** (including research-only). Record what matters, skip what doesn't.\n\n\u{1F534} MUST RECORD \u2014 High Impact (saves agent time next session):\n\u2022 STEP 5: `research_save` \u2014 after exploring area (creates search cache).\n\u2022 STEP 6: `gotcha` \u2014 IMMEDIATELY when you discover bugs/quirks. No re-research.\n\u2022 STEP 7: `arch_flow` \u2014 AFTER tracing COMPLETE flow (max 5 core files).\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\u2022 STEP 9: `feature` \u2014 WHEN identifying a high-level feature/module (e.g. Auth, Billing). Creates owns edges to files.\n\n\u{1F7E2} SKIP using MCP (agent native tools are faster):\n\u2022 Function/class nodes \u2192 grep\n\u2022 Component/route nodes \u2192 glob or check directly\n\u2022 Import edges \u2192 read imports directly\n\u2022 Visual graph \u2192 for humans, not AI agents\n\nOther actions: session, heal, search, changes, benchmark, layers.",
13163
14090
  {
13164
- action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "add_node", "delete_node", "clear", "feature"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search (now with task retrieval + impact analysis), changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules, arch_flow=Layer 2 architecture flow (now auto-creates affects edges), gotcha=Layer 3 known gotchas, layers=all 3 layers summary, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas, feature=record high-level feature with owns edges to files"),
14091
+ action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "add_node", "delete_node", "clear", "feature", "goal_progress"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search (now with task retrieval + impact analysis), changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules, arch_flow=Layer 2 architecture flow (now auto-creates affects edges), gotcha=Layer 3 known gotchas, layers=all 3 layers summary, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas, feature=record high-level feature with owns edges to files, goal_progress=update goal completion percentage"),
13165
14092
  scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
13166
14093
  query: z.string().optional().describe("Search query for search action"),
13167
14094
  content: z.string().optional().describe("Content/notes for research_save / context"),
@@ -13309,6 +14236,7 @@ Usage:
13309
14236
  npx @plumpslabs/kuma init --skip-existing Skip generation if file exists
13310
14237
  npx @plumpslabs/kuma init --claude --cursor Generate specific files
13311
14238
  npx @plumpslabs/kuma studio Start Kuma Studio web dashboard (knowledge graph visualizer)
14239
+ npx @plumpslabs/kuma init --legacy Bulk-onboard an existing (legacy) codebase
13312
14240
  npx @plumpslabs/kuma init --help Show this help
13313
14241
 
13314
14242
  Available config files:
@@ -13331,6 +14259,14 @@ If no flags specified, you'll be prompted to select files interactively.
13331
14259
  }
13332
14260
  async function main() {
13333
14261
  const args = process.argv.slice(2);
14262
+ if (args[0] === "--version" || args[0] === "-v" || args[0] === "version") {
14263
+ console.log(`\u{1F43B} Kuma v${SERVER_VERSION}`);
14264
+ process.exit(0);
14265
+ }
14266
+ if (args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
14267
+ printHelp();
14268
+ process.exit(0);
14269
+ }
13334
14270
  if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
13335
14271
  console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
13336
14272
  console.error("");
@@ -13353,14 +14289,14 @@ async function main() {
13353
14289
  } catch {
13354
14290
  }
13355
14291
  try {
13356
- const path27 = await import("path");
13357
- const fs27 = await import("fs");
13358
- const lockDir = path27.resolve(process.cwd(), ".kuma/verifier.lock");
13359
- if (fs27.existsSync(lockDir)) {
13360
- const pidFile = path27.join(lockDir, "pid");
13361
- if (fs27.existsSync(pidFile)) {
14292
+ const path31 = await import("path");
14293
+ const fs30 = await import("fs");
14294
+ const lockDir = path31.resolve(process.cwd(), ".kuma/verifier.lock");
14295
+ if (fs30.existsSync(lockDir)) {
14296
+ const pidFile = path31.join(lockDir, "pid");
14297
+ if (fs30.existsSync(pidFile)) {
13362
14298
  try {
13363
- const pid = parseInt(fs27.readFileSync(pidFile, "utf-8"), 10);
14299
+ const pid = parseInt(fs30.readFileSync(pidFile, "utf-8"), 10);
13364
14300
  if (pid && pid !== process.pid) {
13365
14301
  try {
13366
14302
  process.kill(-pid, "SIGKILL");
@@ -13376,7 +14312,7 @@ async function main() {
13376
14312
  } catch {
13377
14313
  }
13378
14314
  }
13379
- fs27.rmSync(lockDir, { recursive: true, force: true });
14315
+ fs30.rmSync(lockDir, { recursive: true, force: true });
13380
14316
  }
13381
14317
  if (killedCount === 0) {
13382
14318
  console.error("\u2705 No running verifications found to kill.");
@@ -13470,6 +14406,18 @@ async function main() {
13470
14406
  printHelp();
13471
14407
  process.exit(0);
13472
14408
  }
14409
+ if (flags.includes("--legacy")) {
14410
+ try {
14411
+ const { runLegacyOnboarding: runLegacyOnboarding2 } = await Promise.resolve().then(() => (init_kumaLegacyOnboard(), kumaLegacyOnboard_exports));
14412
+ console.error("\u{1F43B} Kuma Legacy Onboarding \u2014 making your existing codebase agent-ready...");
14413
+ const report = await runLegacyOnboarding2();
14414
+ console.log(report);
14415
+ } catch (err) {
14416
+ console.error(`\u274C Legacy onboarding failed: ${err}`);
14417
+ process.exit(1);
14418
+ }
14419
+ process.exit(0);
14420
+ }
13473
14421
  const requestedFlags = flags.filter((f) => f.startsWith("--"));
13474
14422
  let selectedTypes;
13475
14423
  if (requestedFlags.length === 0) {
@@ -13524,23 +14472,23 @@ async function main() {
13524
14472
  });
13525
14473
  const output = formatInitResults(results);
13526
14474
  console.log(output);
13527
- const fs27 = await import("fs");
13528
- const path27 = await import("path");
13529
- const matchaSkills = path27.resolve(process.cwd(), "skills/matcha/SKILL.md");
13530
- const matchaAgents = path27.resolve(
14475
+ const fs30 = await import("fs");
14476
+ const path31 = await import("path");
14477
+ const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
14478
+ const matchaAgents = path31.resolve(
13531
14479
  process.cwd(),
13532
14480
  ".agents/skills/matcha/SKILL.md"
13533
14481
  );
13534
- const matchaRootSkills = path27.resolve(
14482
+ const matchaRootSkills = path31.resolve(
13535
14483
  process.cwd(),
13536
14484
  "skills/matcha/SKILL.md"
13537
14485
  );
13538
- const matchaAgentsMd = path27.resolve(process.cwd(), "AGENTS.md");
13539
- const matchaWindsurfRules = path27.resolve(
14486
+ const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
14487
+ const matchaWindsurfRules = path31.resolve(
13540
14488
  process.cwd(),
13541
14489
  ".windsurf"
13542
14490
  );
13543
- if (fs27.existsSync(matchaSkills) || fs27.existsSync(matchaAgents) || fs27.existsSync(matchaRootSkills) || fs27.existsSync(matchaAgentsMd) || fs27.existsSync(matchaWindsurfRules)) {
14491
+ if (fs30.existsSync(matchaSkills) || fs30.existsSync(matchaAgents) || fs30.existsSync(matchaRootSkills) || fs30.existsSync(matchaAgentsMd) || fs30.existsSync(matchaWindsurfRules)) {
13544
14492
  console.error(
13545
14493
  "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
13546
14494
  );
@@ -13554,13 +14502,13 @@ async function main() {
13554
14502
  (async () => {
13555
14503
  try {
13556
14504
  const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
13557
- const fs27 = await import("fs");
13558
- const path27 = await import("path");
13559
- const initMdPath = path27.resolve(process.cwd(), ".kuma/init.md");
13560
- if (!fs27.existsSync(initMdPath)) {
13561
- const kumaDir = path27.dirname(initMdPath);
13562
- if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
13563
- fs27.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
14505
+ const fs30 = await import("fs");
14506
+ const path31 = await import("path");
14507
+ const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
14508
+ if (!fs30.existsSync(initMdPath)) {
14509
+ const kumaDir = path31.dirname(initMdPath);
14510
+ if (!fs30.existsSync(kumaDir)) fs30.mkdirSync(kumaDir, { recursive: true });
14511
+ fs30.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
13564
14512
  console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
13565
14513
  }
13566
14514
  } catch (err) {
@@ -13571,8 +14519,8 @@ async function main() {
13571
14519
  try {
13572
14520
  const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
13573
14521
  const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
13574
- const fs27 = await import("fs");
13575
- const path27 = await import("path");
14522
+ const fs30 = await import("fs");
14523
+ const path31 = await import("path");
13576
14524
  const detection = detectAgent2();
13577
14525
  if (!detection.primary) {
13578
14526
  console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
@@ -13580,22 +14528,22 @@ async function main() {
13580
14528
  }
13581
14529
  const agentType = detection.primary;
13582
14530
  const skillPath = getSkillPath2(agentType);
13583
- const fullPath = path27.resolve(process.cwd(), skillPath);
13584
- if (fs27.existsSync(fullPath)) {
14531
+ const fullPath = path31.resolve(process.cwd(), skillPath);
14532
+ if (fs30.existsSync(fullPath)) {
13585
14533
  console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
13586
14534
  return;
13587
14535
  }
13588
- const dir = path27.dirname(fullPath);
13589
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
13590
- fs27.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
14536
+ const dir = path31.dirname(fullPath);
14537
+ if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
14538
+ fs30.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
13591
14539
  console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
13592
14540
  const secondaryFiles = getSecondaryFiles2(agentType);
13593
14541
  for (const sf of secondaryFiles) {
13594
- const sfPath = path27.resolve(process.cwd(), sf.path);
13595
- const sfDir = path27.dirname(sfPath);
13596
- if (!fs27.existsSync(sfPath)) {
13597
- if (!fs27.existsSync(sfDir)) fs27.mkdirSync(sfDir, { recursive: true });
13598
- fs27.writeFileSync(sfPath, sf.content, "utf-8");
14542
+ const sfPath = path31.resolve(process.cwd(), sf.path);
14543
+ const sfDir = path31.dirname(sfPath);
14544
+ if (!fs30.existsSync(sfPath)) {
14545
+ if (!fs30.existsSync(sfDir)) fs30.mkdirSync(sfDir, { recursive: true });
14546
+ fs30.writeFileSync(sfPath, sf.content, "utf-8");
13599
14547
  console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
13600
14548
  }
13601
14549
  }
@@ -13620,11 +14568,11 @@ async function main() {
13620
14568
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
13621
14569
  }
13622
14570
  try {
13623
- const fs27 = await import("fs");
13624
- const path27 = await import("path");
13625
- const scratchDir = path27.resolve(process.cwd(), ".kuma", "scratch");
13626
- if (!fs27.existsSync(scratchDir)) {
13627
- fs27.mkdirSync(scratchDir, { recursive: true });
14571
+ const fs30 = await import("fs");
14572
+ const path31 = await import("path");
14573
+ const scratchDir = path31.resolve(process.cwd(), ".kuma", "scratch");
14574
+ if (!fs30.existsSync(scratchDir)) {
14575
+ fs30.mkdirSync(scratchDir, { recursive: true });
13628
14576
  console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
13629
14577
  }
13630
14578
  } catch (err) {