@plumpslabs/kuma 2.3.31 → 2.3.32

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
@@ -2509,6 +2509,223 @@ var init_kumaSelfHeal = __esm({
2509
2509
  }
2510
2510
  });
2511
2511
 
2512
+ // src/engine/languageSupport.ts
2513
+ import path5 from "path";
2514
+ function grepIncludeFlags() {
2515
+ return SOURCE_EXTENSIONS.map((e) => `--include="*${e}"`).join(" ");
2516
+ }
2517
+ function isTestFile(filePath) {
2518
+ const lower = filePath.toLowerCase();
2519
+ const base = path5.basename(filePath);
2520
+ const ext = path5.extname(base);
2521
+ const stem = ext ? base.slice(0, -ext.length) : base;
2522
+ const segments = lower.split(/[\\/]/);
2523
+ for (const lang of LANGUAGES) {
2524
+ if (!lang.extensions.includes(ext.toLowerCase())) continue;
2525
+ for (const p of lang.testPrefixes) {
2526
+ if (p && stem.startsWith(p)) return true;
2527
+ }
2528
+ for (const s of lang.testSuffixes) {
2529
+ if (s && stem.endsWith(s)) return true;
2530
+ }
2531
+ for (const d of lang.testDirs) {
2532
+ const dLower = d.toLowerCase();
2533
+ if (dLower.includes("/")) {
2534
+ if (lower.includes(`${dLower}/`) || lower.includes(`${dLower}\\`)) return true;
2535
+ } else if (segments.includes(dLower)) {
2536
+ return true;
2537
+ }
2538
+ }
2539
+ }
2540
+ return /(\.test\.|\.spec\.|_test\.|_spec\.)/.test(lower) || /(\/|^)(__tests__|tests?|specs?)(\/|$)/.test(lower);
2541
+ }
2542
+ function getTestCandidates(sourcePath) {
2543
+ const ext = path5.extname(sourcePath).toLowerCase();
2544
+ const dir = path5.dirname(sourcePath);
2545
+ const base = path5.basename(sourcePath, ext);
2546
+ const lang = LANGUAGES.find((l) => l.extensions.includes(ext));
2547
+ const candidates = /* @__PURE__ */ new Set();
2548
+ const push = (p) => {
2549
+ if (p && !candidates.has(p)) candidates.add(p);
2550
+ };
2551
+ if (lang) {
2552
+ for (const suffix of lang.testSuffixes) {
2553
+ if (!suffix) continue;
2554
+ push(path5.join(dir, `${base}${suffix}${ext}`));
2555
+ }
2556
+ for (const prefix of lang.testPrefixes) {
2557
+ if (prefix) push(path5.join(dir, `${prefix}${base}${ext}`));
2558
+ }
2559
+ for (const td of lang.testDirs) {
2560
+ if (!td.includes("/")) {
2561
+ push(path5.join(dir, td, `${base}${ext}`));
2562
+ }
2563
+ }
2564
+ }
2565
+ push(path5.join(dir, `${base}.test${ext}`));
2566
+ push(path5.join(dir, `${base}.spec${ext}`));
2567
+ push(path5.join(dir, `${base}_test${ext}`));
2568
+ push(path5.join(dir, `test_${base}${ext}`));
2569
+ push(path5.join(dir, "__tests__", `${base}${ext}`));
2570
+ push(path5.join(dir, "tests", `${base}${ext}`));
2571
+ return [...candidates];
2572
+ }
2573
+ function matchImportPath(line) {
2574
+ for (const re of IMPORT_PATTERNS) {
2575
+ re.lastIndex = 0;
2576
+ const m = re.exec(line);
2577
+ if (m) return (m[1] || m[2] || "").trim();
2578
+ }
2579
+ return null;
2580
+ }
2581
+ var LANGUAGES, SOURCE_EXTENSIONS, SOURCE_EXT_GLOB, DEFAULT_SOURCE_INCLUDES, IMPORT_PATTERNS;
2582
+ var init_languageSupport = __esm({
2583
+ "src/engine/languageSupport.ts"() {
2584
+ "use strict";
2585
+ LANGUAGES = [
2586
+ {
2587
+ name: "typescript",
2588
+ extensions: [".ts", ".tsx", ".mts", ".cts"],
2589
+ testPrefixes: [],
2590
+ testSuffixes: [".test", ".spec"],
2591
+ testDirs: ["__tests__", "tests", "test", "spec"]
2592
+ },
2593
+ {
2594
+ name: "javascript",
2595
+ extensions: [".js", ".jsx", ".mjs", ".cjs"],
2596
+ testPrefixes: [],
2597
+ testSuffixes: [".test", ".spec"],
2598
+ testDirs: ["__tests__", "tests", "test", "spec"]
2599
+ },
2600
+ {
2601
+ name: "python",
2602
+ extensions: [".py", ".pyw"],
2603
+ testPrefixes: ["test_"],
2604
+ testSuffixes: ["_test"],
2605
+ testDirs: ["tests", "test"]
2606
+ },
2607
+ {
2608
+ name: "go",
2609
+ extensions: [".go"],
2610
+ testPrefixes: [],
2611
+ testSuffixes: ["_test"],
2612
+ testDirs: ["testdata"]
2613
+ },
2614
+ {
2615
+ name: "rust",
2616
+ extensions: [".rs"],
2617
+ testPrefixes: [],
2618
+ testSuffixes: ["_test"],
2619
+ testDirs: ["tests"]
2620
+ },
2621
+ {
2622
+ name: "java",
2623
+ extensions: [".java"],
2624
+ testPrefixes: ["Test"],
2625
+ testSuffixes: ["Test", "Tests"],
2626
+ testDirs: ["src/test", "test", "tests"]
2627
+ },
2628
+ {
2629
+ name: "kotlin",
2630
+ extensions: [".kt", ".kts"],
2631
+ testPrefixes: ["Test"],
2632
+ testSuffixes: ["Test", "Tests"],
2633
+ testDirs: ["src/test"]
2634
+ },
2635
+ {
2636
+ name: "csharp",
2637
+ extensions: [".cs"],
2638
+ testPrefixes: [],
2639
+ testSuffixes: ["Tests", "Test"],
2640
+ testDirs: ["Tests", "test", "tests"]
2641
+ },
2642
+ {
2643
+ name: "ruby",
2644
+ extensions: [".rb"],
2645
+ testPrefixes: [],
2646
+ testSuffixes: ["_spec", "_test"],
2647
+ testDirs: ["spec", "test", "tests"]
2648
+ },
2649
+ {
2650
+ name: "php",
2651
+ extensions: [".php"],
2652
+ testPrefixes: [],
2653
+ testSuffixes: ["Test"],
2654
+ testDirs: ["tests", "test"]
2655
+ },
2656
+ {
2657
+ name: "cpp",
2658
+ extensions: [".c", ".cc", ".cpp", ".h", ".hpp", ".hxx"],
2659
+ testPrefixes: [],
2660
+ testSuffixes: ["_test"],
2661
+ testDirs: ["tests", "test"]
2662
+ },
2663
+ {
2664
+ name: "swift",
2665
+ extensions: [".swift"],
2666
+ testPrefixes: [],
2667
+ testSuffixes: ["Tests"],
2668
+ testDirs: ["Tests", "test"]
2669
+ },
2670
+ {
2671
+ name: "scala",
2672
+ extensions: [".scala"],
2673
+ testPrefixes: [],
2674
+ testSuffixes: ["Spec", "Test", "Tests"],
2675
+ testDirs: ["src/test"]
2676
+ },
2677
+ {
2678
+ name: "dart",
2679
+ extensions: [".dart"],
2680
+ testPrefixes: [],
2681
+ testSuffixes: ["_test"],
2682
+ testDirs: ["test", "tests"]
2683
+ },
2684
+ {
2685
+ name: "shell",
2686
+ extensions: [".sh", ".bash", ".zsh"],
2687
+ testPrefixes: [],
2688
+ testSuffixes: [],
2689
+ testDirs: ["test", "tests"]
2690
+ }
2691
+ ];
2692
+ SOURCE_EXTENSIONS = [
2693
+ ...new Set(LANGUAGES.flatMap((l) => l.extensions))
2694
+ ];
2695
+ SOURCE_EXT_GLOB = `{${SOURCE_EXTENSIONS.map((e) => e.slice(1)).join(",")}}`;
2696
+ DEFAULT_SOURCE_INCLUDES = [
2697
+ `src/**/*.${SOURCE_EXT_GLOB}`,
2698
+ `app/**/*.${SOURCE_EXT_GLOB}`,
2699
+ `lib/**/*.${SOURCE_EXT_GLOB}`,
2700
+ `packages/**/*.${SOURCE_EXT_GLOB}`,
2701
+ `server/**/*.${SOURCE_EXT_GLOB}`,
2702
+ `api/**/*.${SOURCE_EXT_GLOB}`
2703
+ ];
2704
+ IMPORT_PATTERNS = [
2705
+ // JS/TS: import x from 'y' | import {a} from 'y' | import * as x from 'y'
2706
+ /import\s+(?:\{[^}]*\}\s+from\s+)?(?:[^'"\n]*\s+from\s+)?['"]([^'"]+)['"]/,
2707
+ // JS/TS (CJS): const x = require('y')
2708
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/,
2709
+ // Python: import y | import y.z | from y import x | from .y import x
2710
+ /^\s*(?:from\s+([.\w]+)\s+import\s+\S+|import\s+([.\w]+))/m,
2711
+ // Go: import "y" | import alias "y"
2712
+ /^\s*import\s+(?:\w+\s+)?"([^"]+)"/m,
2713
+ // Rust: use y::z | extern crate y
2714
+ /^\s*use\s+([\w:]+)/m,
2715
+ // C/C++: #include <y> | #include "y"
2716
+ /^\s*#\s*include\s*[<"]([^>"]+)[>"]/m,
2717
+ // Ruby: require 'y' | require_relative 'y'
2718
+ /^\s*require(?:_relative)?\s+['"]([^'"]+)['"]/m,
2719
+ // PHP: require 'y' | include 'y' | use y\z;
2720
+ /^\s*(?:require(?:_once)?|include(?:_once)?)\s*\(?\s*['"]([^'"]+)['"]/m,
2721
+ // Java: import y.z.w;
2722
+ /^\s*import\s+([\w.]+)\s*;/m,
2723
+ // C#: using y.z.w;
2724
+ /^\s*using\s+([\w.]+)\s*;/m
2725
+ ];
2726
+ }
2727
+ });
2728
+
2512
2729
  // src/engine/kumaGraph.ts
2513
2730
  var kumaGraph_exports = {};
2514
2731
  __export(kumaGraph_exports, {
@@ -3105,7 +3322,7 @@ async function queryGraph(params) {
3105
3322
  if (results.length === 0) {
3106
3323
  return `\u{1F50D} **Graph Query** \u2014 No nodes found for "${query}".
3107
3324
 
3108
- Try a different search term or check if the graph has been built by using tools like smart_grep or lsp_query.`;
3325
+ Try a different search term or use kuma_context({ action: 'research', scope: '<term>' }) to build graph context.`;
3109
3326
  }
3110
3327
  const lines = [
3111
3328
  `\u{1F50D} **Graph Nodes** \u2014 ${results.length} result(s) for "${query}"`,
@@ -3139,7 +3356,7 @@ Try a different search term or check if the graph has been built by using tools
3139
3356
  if (results.length === 0) {
3140
3357
  return `\u{1F50D} **Graph Edges** \u2014 No connections found for node "${query}".
3141
3358
 
3142
- Try kuma_graph_query({ type: 'nodes', query: '<search>' }) to find node IDs first.`;
3359
+ Try kuma_context({ action: 'research', scope: '<term>' }) to build related graph nodes first.`;
3143
3360
  }
3144
3361
  const lines = [
3145
3362
  `\u{1F517} **Graph Edges** \u2014 ${results.length} connection(s) for "${query}"`,
@@ -3156,7 +3373,7 @@ Try kuma_graph_query({ type: 'nodes', query: '<search>' }) to find node IDs firs
3156
3373
  const parts = query.split("\u2192").map((s) => s.trim());
3157
3374
  if (parts.length !== 2) {
3158
3375
  return `\u26A0\uFE0F For path queries, use format: "sourceNodeID \u2192 targetNodeID"
3159
- Example: kuma_graph_query({ type: 'paths', query: 'function::login \u2192 function::validatePassword' })`;
3376
+ Example: kuma_context({ action: 'impact', target: 'login' }) \u2192 traces connections between nodes.`;
3160
3377
  }
3161
3378
  const [sourceId, targetId] = parts;
3162
3379
  const visited = /* @__PURE__ */ new Set();
@@ -3396,27 +3613,27 @@ async function codebaseSearchFallback(query, limit = 20) {
3396
3613
  if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
3397
3614
  let entries = [];
3398
3615
  try {
3399
- entries = fs27.readdirSync(dir);
3616
+ entries = fs30.readdirSync(dir);
3400
3617
  } catch {
3401
3618
  return;
3402
3619
  }
3403
3620
  for (const entry of entries) {
3404
3621
  if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
3405
- const full = path27.join(dir, entry);
3622
+ const full = path31.join(dir, entry);
3406
3623
  try {
3407
- const stat = fs27.statSync(full);
3624
+ const stat = fs30.statSync(full);
3408
3625
  if (stat.isDirectory()) {
3409
3626
  walk2(full, depth + 1);
3410
3627
  } else if (entry.toLowerCase().includes(query.toLowerCase())) {
3411
- results.push(path27.relative(root, full));
3628
+ results.push(path31.relative(root, full));
3412
3629
  }
3413
3630
  } catch {
3414
3631
  }
3415
3632
  }
3416
3633
  };
3417
3634
  var walk = walk2;
3418
- const fs27 = await import("fs");
3419
- const path27 = await import("path");
3635
+ const fs30 = await import("fs");
3636
+ const path31 = await import("path");
3420
3637
  const root = process.cwd();
3421
3638
  const results = [];
3422
3639
  const MAX_WALK_DEPTH = 8;
@@ -3559,7 +3776,7 @@ async function codebaseImpactFallback(target) {
3559
3776
  const { execSync: execSync8 } = await import("child_process");
3560
3777
  const root = process.cwd();
3561
3778
  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`,
3779
+ `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
3780
  { cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
3564
3781
  ).trim();
3565
3782
  const files = grepResult ? grepResult.split("\n").filter(Boolean).filter((f) => !f.startsWith("./")) : [];
@@ -3567,7 +3784,7 @@ async function codebaseImpactFallback(target) {
3567
3784
  return { symbol: target, references: 0, files: 0, testFiles: 0, entryPoints: [], risk: "low" };
3568
3785
  }
3569
3786
  const fileCount = files.length;
3570
- const testFiles = files.filter((f) => f.includes("test") || f.includes("spec") || f.includes("__tests__")).length;
3787
+ const testFiles = files.filter((f) => isTestFile(f)).length;
3571
3788
  const risk = fileCount > 20 ? "high" : fileCount > 5 ? "medium" : "low";
3572
3789
  return {
3573
3790
  symbol: target,
@@ -3666,6 +3883,7 @@ var init_kumaGraph = __esm({
3666
3883
  "use strict";
3667
3884
  init_kumaDb();
3668
3885
  init_kumaSelfHeal();
3886
+ init_languageSupport();
3669
3887
  GRAPH_LIMITS = {
3670
3888
  maxNodes: 5e3,
3671
3889
  // Max nodes before pruning
@@ -3689,10 +3907,10 @@ __export(kumaMemory_exports, {
3689
3907
  shouldRecordDecision: () => shouldRecordDecision
3690
3908
  });
3691
3909
  import fs5 from "fs";
3692
- import path5 from "path";
3910
+ import path6 from "path";
3693
3911
  function scoreMemoryRelevance(context, limit = 5) {
3694
3912
  const results = [];
3695
- const kumaDir = path5.join(getProjectRoot(), MEMORY_DIR);
3913
+ const kumaDir = path6.join(getProjectRoot(), MEMORY_DIR);
3696
3914
  if (!fs5.existsSync(kumaDir)) return [];
3697
3915
  const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
3698
3916
  if (terms.length === 0) return [];
@@ -3700,7 +3918,7 @@ function scoreMemoryRelevance(context, limit = 5) {
3700
3918
  const files = fs5.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
3701
3919
  for (const file of files) {
3702
3920
  try {
3703
- const content = fs5.readFileSync(path5.join(kumaDir, file), "utf-8");
3921
+ const content = fs5.readFileSync(path6.join(kumaDir, file), "utf-8");
3704
3922
  const lower = content.toLowerCase();
3705
3923
  let matchCount = 0;
3706
3924
  for (const term of terms) {
@@ -4250,6 +4468,136 @@ var init_safetyScore = __esm({
4250
4468
  }
4251
4469
  });
4252
4470
 
4471
+ // src/engine/kumaPathRules.ts
4472
+ var kumaPathRules_exports = {};
4473
+ __export(kumaPathRules_exports, {
4474
+ ensureRulesDir: () => ensureRulesDir,
4475
+ formatPathRules: () => formatPathRules,
4476
+ getRulesForScope: () => getRulesForScope,
4477
+ hasPathRules: () => hasPathRules,
4478
+ loadPathRules: () => loadPathRules,
4479
+ matchRulePath: () => matchRulePath
4480
+ });
4481
+ import fs6 from "fs";
4482
+ import path7 from "path";
4483
+ function rulesDir() {
4484
+ return path7.join(getProjectRoot(), RULES_DIR);
4485
+ }
4486
+ function ensureRulesDir() {
4487
+ try {
4488
+ const dir = rulesDir();
4489
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
4490
+ } catch {
4491
+ }
4492
+ }
4493
+ function parseRuleFile(filePath) {
4494
+ try {
4495
+ const raw = fs6.readFileSync(filePath, "utf-8");
4496
+ const id = path7.basename(filePath, ".md");
4497
+ const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
4498
+ if (!fm) {
4499
+ return { id, description: id, paths: ["*"], content: raw.trim(), filePath };
4500
+ }
4501
+ const meta = fm[1];
4502
+ const body = fm[2].trim();
4503
+ const description = meta.match(/description:\s*(.+)/)?.[1]?.trim() || id;
4504
+ const paths = [];
4505
+ const pathSection = meta.match(/paths:\s*\r?\n([\s\S]*?)(?=\r?\n\S|$)/);
4506
+ if (pathSection) {
4507
+ for (const line of pathSection[1].split("\n")) {
4508
+ const m = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/);
4509
+ if (m) paths.push(m[1].trim());
4510
+ }
4511
+ }
4512
+ return { id, description, paths: paths.length ? paths : ["*"], content: body, filePath };
4513
+ } catch {
4514
+ return null;
4515
+ }
4516
+ }
4517
+ function loadPathRules() {
4518
+ ensureRulesDir();
4519
+ try {
4520
+ const dir = rulesDir();
4521
+ return fs6.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => parseRuleFile(path7.join(dir, f))).filter((r) => r !== null);
4522
+ } catch {
4523
+ return [];
4524
+ }
4525
+ }
4526
+ function matchRulePath(pattern, target) {
4527
+ if (pattern === "*" || pattern === "**") return true;
4528
+ const p = pattern.replace(/\\/g, "/").toLowerCase();
4529
+ const t = target.replace(/\\/g, "/").toLowerCase();
4530
+ if (p.endsWith("/**")) {
4531
+ const prefix = p.slice(0, -3);
4532
+ return t === prefix || t.startsWith(prefix + "/");
4533
+ }
4534
+ if (!/[*?]/.test(p) && (t === p || t.startsWith(p + "/"))) return true;
4535
+ let re = "";
4536
+ for (let i = 0; i < p.length; i++) {
4537
+ const c = p[i];
4538
+ if (c === "*") {
4539
+ if (p[i + 1] === "*") {
4540
+ re += i === 0 ? "(?:.*/)?" : ".*";
4541
+ if (p[i + 2] === "/") i += 2;
4542
+ } else {
4543
+ re += "[^/]*";
4544
+ }
4545
+ } else if (c === "?") {
4546
+ re += "[^/]";
4547
+ } else if (/[.()*+?^${}()|[\]\\]/.test(c)) {
4548
+ re += `\\${c}`;
4549
+ } else {
4550
+ re += c;
4551
+ }
4552
+ }
4553
+ try {
4554
+ const rx = new RegExp(`^${re}$`);
4555
+ return rx.test(t);
4556
+ } catch {
4557
+ return t === p;
4558
+ }
4559
+ }
4560
+ function getRulesForScope(scope) {
4561
+ const rules = loadPathRules();
4562
+ if (rules.length === 0) return [];
4563
+ const target = (scope || "").toLowerCase();
4564
+ if (!target) return rules.filter((r) => r.paths.includes("*"));
4565
+ return rules.filter((rule) => rule.paths.some((p) => matchRulePath(p, target)));
4566
+ }
4567
+ function formatPathRules(scope) {
4568
+ const matches = getRulesForScope(scope);
4569
+ if (matches.length === 0) return "";
4570
+ const lines = [
4571
+ `\u{1F4CC} **Path Rules** (${matches.length} rule(s) apply to "${scope || "project"}")`,
4572
+ `\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`,
4573
+ ""
4574
+ ];
4575
+ for (const rule of matches) {
4576
+ lines.push(`**${rule.description}**`);
4577
+ const bodyLines = rule.content.split("\n").filter((l) => l.trim()).slice(0, 6);
4578
+ for (const bl of bodyLines) {
4579
+ lines.push(` ${bl.substring(0, 140)}`);
4580
+ }
4581
+ if (rule.content.split("\n").filter((l) => l.trim()).length > 6) {
4582
+ lines.push(` \u2026 (full rule in ${rule.filePath})`);
4583
+ }
4584
+ lines.push("");
4585
+ }
4586
+ lines.push(`\u{1F4A1} Path rules load on-demand from ${RULES_DIR}/ \u2014 add/remove .md files anytime.`);
4587
+ return lines.join("\n");
4588
+ }
4589
+ function hasPathRules() {
4590
+ return loadPathRules().length > 0;
4591
+ }
4592
+ var RULES_DIR;
4593
+ var init_kumaPathRules = __esm({
4594
+ "src/engine/kumaPathRules.ts"() {
4595
+ "use strict";
4596
+ init_pathValidator();
4597
+ RULES_DIR = ".kuma/rules";
4598
+ }
4599
+ });
4600
+
4253
4601
  // src/engine/kumaCodeScanner.ts
4254
4602
  var kumaCodeScanner_exports = {};
4255
4603
  __export(kumaCodeScanner_exports, {
@@ -4258,8 +4606,8 @@ __export(kumaCodeScanner_exports, {
4258
4606
  scanCodebase: () => scanCodebase
4259
4607
  });
4260
4608
  import fastGlob from "fast-glob";
4261
- import fs6 from "fs";
4262
- import path6 from "path";
4609
+ import fs7 from "fs";
4610
+ import path8 from "path";
4263
4611
  function getRoot() {
4264
4612
  if (!_cachedRoot) _cachedRoot = getProjectRoot();
4265
4613
  return _cachedRoot;
@@ -4270,10 +4618,10 @@ function lineAt(content, index) {
4270
4618
  function getDirectoryDirs(filePaths) {
4271
4619
  const dirs = /* @__PURE__ */ new Set();
4272
4620
  for (const fp of filePaths) {
4273
- let dir = path6.dirname(fp);
4621
+ let dir = path8.dirname(fp);
4274
4622
  while (dir && dir !== "." && dir !== "/") {
4275
4623
  dirs.add(dir);
4276
- dir = path6.dirname(dir);
4624
+ dir = path8.dirname(dir);
4277
4625
  }
4278
4626
  }
4279
4627
  return [...dirs].sort();
@@ -4283,21 +4631,14 @@ async function scanCodebase(options = {}) {
4283
4631
  const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
4284
4632
  const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
4285
4633
  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
- ];
4634
+ const includePatterns = options.include || [...DEFAULT_SOURCE_INCLUDES];
4294
4635
  if (options.scope) {
4295
4636
  includePatterns.length = 0;
4296
4637
  if (options.scope.includes("/") || options.scope.includes(".")) {
4297
4638
  includePatterns.push(options.scope);
4298
4639
  } else {
4299
- includePatterns.push(`**/*${options.scope}*/**/*.{ts,tsx,js,jsx}`);
4300
- includePatterns.push(`**/*${options.scope}*.{ts,tsx,js,jsx}`);
4640
+ includePatterns.push(`**/*${options.scope}*/**/*.${SOURCE_EXT_GLOB}`);
4641
+ includePatterns.push(`**/*${options.scope}*.${SOURCE_EXT_GLOB}`);
4301
4642
  }
4302
4643
  }
4303
4644
  const ignorePatterns = [
@@ -4330,16 +4671,16 @@ async function scanCodebase(options = {}) {
4330
4671
  const scannedFilePaths = [];
4331
4672
  for (let i = 0; i < scannedCount; i++) {
4332
4673
  const filePath = files[i];
4333
- const fullPath = path6.join(root, filePath);
4334
- if (!fs6.existsSync(fullPath)) continue;
4335
- const stat = fs6.statSync(fullPath);
4674
+ const fullPath = path8.join(root, filePath);
4675
+ if (!fs7.existsSync(fullPath)) continue;
4676
+ const stat = fs7.statSync(fullPath);
4336
4677
  if (stat.size > maxFileSize) continue;
4337
4678
  const mtime = stat.mtimeMs;
4338
4679
  const cached = fileCache.get(filePath);
4339
4680
  if (cached === mtime && !options.force) continue;
4340
4681
  fileCache.set(filePath, mtime);
4341
4682
  try {
4342
- const content = fs6.readFileSync(fullPath, "utf-8");
4683
+ const content = fs7.readFileSync(fullPath, "utf-8");
4343
4684
  const parsed = parseFile(filePath, content);
4344
4685
  allParsed.push({ filePath, parsed, content });
4345
4686
  scannedFilePaths.push(filePath);
@@ -4382,7 +4723,7 @@ async function scanCodebase(options = {}) {
4382
4723
  });
4383
4724
  result.nodeCount++;
4384
4725
  for (const fp of scannedFilePaths) {
4385
- if (path6.dirname(fp) === dir) {
4726
+ if (path8.dirname(fp) === dir) {
4386
4727
  const fileId = nodeId("file", fp);
4387
4728
  try {
4388
4729
  await addEdge({ sourceId: moduleId, targetId: fileId, type: "owns" });
@@ -4407,7 +4748,7 @@ function parseFile(filePath, content) {
4407
4748
  routes: [],
4408
4749
  imports: [],
4409
4750
  exports: [],
4410
- isTest: TEST_PATTERNS.some((p) => filePath.includes(p))
4751
+ isTest: isTestFile(filePath)
4411
4752
  };
4412
4753
  const lines = content.split("\n");
4413
4754
  const isTsx = /\.(tsx|jsx)$/.test(filePath);
@@ -4739,17 +5080,20 @@ async function recordParsedFile(filePath, parsed, content, result) {
4739
5080
  }
4740
5081
  function resolveImportPath(fromFile, importPath, root) {
4741
5082
  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"];
5083
+ const fromDir = path8.dirname(path8.join(root, fromFile));
5084
+ const exts = [
5085
+ ...SOURCE_EXTENSIONS,
5086
+ ...SOURCE_EXTENSIONS.map((e) => `/index${e}`)
5087
+ ];
4744
5088
  for (const ext of exts) {
4745
- const resolved = path6.resolve(fromDir, importPath + ext);
4746
- if (fs6.existsSync(resolved)) return path6.relative(root, resolved);
5089
+ const resolved = path8.resolve(fromDir, importPath + ext);
5090
+ if (fs7.existsSync(resolved)) return path8.relative(root, resolved);
4747
5091
  }
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);
5092
+ const dirPath = path8.resolve(fromDir, importPath);
5093
+ if (fs7.existsSync(dirPath) && fs7.statSync(dirPath).isDirectory()) {
5094
+ for (const ext of SOURCE_EXTENSIONS) {
5095
+ const indexPath = path8.join(dirPath, `index${ext}`);
5096
+ if (fs7.existsSync(indexPath)) return path8.relative(root, indexPath);
4753
5097
  }
4754
5098
  }
4755
5099
  return null;
@@ -4784,12 +5128,13 @@ async function scanAndFormat(options = {}) {
4784
5128
  const result = await scanCodebase(options);
4785
5129
  return formatScanResult(result);
4786
5130
  }
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;
5131
+ 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
5132
  var init_kumaCodeScanner = __esm({
4789
5133
  "src/engine/kumaCodeScanner.ts"() {
4790
5134
  "use strict";
4791
5135
  init_pathValidator();
4792
5136
  init_kumaGraph();
5137
+ init_languageSupport();
4793
5138
  DEFAULT_MAX_FILES = 200;
4794
5139
  DEFAULT_MAX_FILE_SIZE = 100 * 1024;
4795
5140
  fileCache = /* @__PURE__ */ new Map();
@@ -4806,7 +5151,6 @@ var init_kumaCodeScanner = __esm({
4806
5151
  HONO_ROUTE_RE = /c\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/g;
4807
5152
  EXPORT_RE = /export\s+(?:default\s+)?(\w+)/g;
4808
5153
  CALL_RE = /(\w+)\s*\(/g;
4809
- TEST_PATTERNS = [".test.", ".spec.", "__tests__", "/test/"];
4810
5154
  knownComponents = /* @__PURE__ */ new Set();
4811
5155
  knownFunctions = /* @__PURE__ */ new Set();
4812
5156
  symbolLocations = /* @__PURE__ */ new Map();
@@ -5091,18 +5435,18 @@ __export(domainRules_exports, {
5091
5435
  readLayer: () => readLayer,
5092
5436
  writeLayer: () => writeLayer
5093
5437
  });
5094
- import fs7 from "fs";
5095
- import path7 from "path";
5438
+ import fs8 from "fs";
5439
+ import path9 from "path";
5096
5440
  function layerFilePath(layer) {
5097
- return path7.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
5441
+ return path9.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
5098
5442
  }
5099
5443
  function ensureLayerFile(layer) {
5100
5444
  const fp = layerFilePath(layer);
5101
- if (!fs7.existsSync(fp)) {
5445
+ if (!fs8.existsSync(fp)) {
5102
5446
  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");
5447
+ const dir = path9.dirname(fp);
5448
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
5449
+ fs8.writeFileSync(fp, template, "utf-8");
5106
5450
  console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
5107
5451
  }
5108
5452
  return fp;
@@ -5110,7 +5454,7 @@ function ensureLayerFile(layer) {
5110
5454
  function readLayer(layer) {
5111
5455
  try {
5112
5456
  const fp = ensureLayerFile(layer);
5113
- return fs7.readFileSync(fp, "utf-8");
5457
+ return fs8.readFileSync(fp, "utf-8");
5114
5458
  } catch (err) {
5115
5459
  return `Error reading ${layer}: ${err}`;
5116
5460
  }
@@ -5118,7 +5462,7 @@ function readLayer(layer) {
5118
5462
  function writeLayer(layer, content) {
5119
5463
  try {
5120
5464
  const fp = ensureLayerFile(layer);
5121
- fs7.writeFileSync(fp, content, "utf-8");
5465
+ fs8.writeFileSync(fp, content, "utf-8");
5122
5466
  const label = LAYER_FILES[layer].label;
5123
5467
  sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
5124
5468
  return `\u2705 **${label}** updated successfully.`;
@@ -5129,9 +5473,9 @@ function writeLayer(layer, content) {
5129
5473
  function appendToLayer(layer, entry) {
5130
5474
  try {
5131
5475
  const fp = ensureLayerFile(layer);
5132
- const existing = fs7.readFileSync(fp, "utf-8");
5476
+ const existing = fs8.readFileSync(fp, "utf-8");
5133
5477
  const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
5134
- fs7.writeFileSync(fp, newContent, "utf-8");
5478
+ fs8.writeFileSync(fp, newContent, "utf-8");
5135
5479
  return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
5136
5480
  } catch (err) {
5137
5481
  return `\u274C Failed to append: ${err}`;
@@ -5141,11 +5485,11 @@ function getLayersSummary() {
5141
5485
  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
5486
  for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
5143
5487
  const fp = layerFilePath(layer);
5144
- const exists = fs7.existsSync(fp);
5488
+ const exists = fs8.existsSync(fp);
5145
5489
  const label = LAYER_FILES[layer].label;
5146
5490
  if (exists) {
5147
5491
  try {
5148
- const content = fs7.readFileSync(fp, "utf-8");
5492
+ const content = fs8.readFileSync(fp, "utf-8");
5149
5493
  const lineCount = content.split("\n").length;
5150
5494
  const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
5151
5495
  lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
@@ -5162,7 +5506,7 @@ function getActiveGotchas() {
5162
5506
  const results = [];
5163
5507
  try {
5164
5508
  const fp = ensureLayerFile("gotcha");
5165
- const content = fs7.readFileSync(fp, "utf-8");
5509
+ const content = fs8.readFileSync(fp, "utf-8");
5166
5510
  const lines = content.split("\n");
5167
5511
  let currentSection = "";
5168
5512
  let currentFilePath = "";
@@ -5210,7 +5554,7 @@ function generateDigest() {
5210
5554
  ];
5211
5555
  try {
5212
5556
  const fp1 = ensureLayerFile("domain_rules");
5213
- const content = fs7.readFileSync(fp1, "utf-8");
5557
+ const content = fs8.readFileSync(fp1, "utf-8");
5214
5558
  const rules = [];
5215
5559
  const workflows = [];
5216
5560
  for (const line of content.split("\n")) {
@@ -5226,7 +5570,7 @@ function generateDigest() {
5226
5570
  }
5227
5571
  try {
5228
5572
  const fp2 = ensureLayerFile("arch_flow");
5229
- const content = fs7.readFileSync(fp2, "utf-8");
5573
+ const content = fs8.readFileSync(fp2, "utf-8");
5230
5574
  const chains = [];
5231
5575
  for (const line of content.split("\n")) {
5232
5576
  if (line.includes("->") && line.includes("[")) chains.push(line.trim());
@@ -5370,8 +5714,8 @@ __export(contextDigest_exports, {
5370
5714
  generateContextDigest: () => generateContextDigest,
5371
5715
  generateDigest: () => generateContextDigest
5372
5716
  });
5373
- import fs8 from "fs";
5374
- import path8 from "path";
5717
+ import fs9 from "fs";
5718
+ import path10 from "path";
5375
5719
  function detectTechStack() {
5376
5720
  const root = getProjectRoot();
5377
5721
  const stack = {
@@ -5383,12 +5727,12 @@ function detectTechStack() {
5383
5727
  monorepo: false
5384
5728
  };
5385
5729
  try {
5386
- const pkgPath = path8.join(root, "package.json");
5387
- if (fs8.existsSync(pkgPath)) {
5388
- const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
5730
+ const pkgPath = path10.join(root, "package.json");
5731
+ if (fs9.existsSync(pkgPath)) {
5732
+ const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
5389
5733
  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";
5734
+ if (fs9.existsSync(path10.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
5735
+ else if (fs9.existsSync(path10.join(root, "yarn.lock"))) stack.packageManager = "yarn";
5392
5736
  else stack.packageManager = "npm";
5393
5737
  stack.monorepo = !!pkg.workspaces;
5394
5738
  if (deps.next) stack.framework = "Next.js";
@@ -5411,13 +5755,13 @@ function detectTechStack() {
5411
5755
  else if (deps.mocha) stack.testRunner = "Mocha";
5412
5756
  else if (deps.playwright) stack.testRunner = "Playwright";
5413
5757
  else if (deps.cypress) stack.testRunner = "Cypress";
5414
- if (fs8.existsSync(path8.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
5758
+ if (fs9.existsSync(path10.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
5415
5759
  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");
5760
+ if (fs9.existsSync(path10.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
5761
+ if (fs9.existsSync(path10.join(root, "go.mod"))) stack.languages.push("Go");
5762
+ if (fs9.existsSync(path10.join(root, "Cargo.toml"))) stack.languages.push("Rust");
5763
+ if (fs9.existsSync(path10.join(root, "pyproject.toml"))) stack.languages.push("Python");
5764
+ if (fs9.existsSync(path10.join(root, "Gemfile"))) stack.languages.push("Ruby");
5421
5765
  if (stack.languages.length === 0) stack.languages.push("JavaScript");
5422
5766
  }
5423
5767
  } catch {
@@ -5448,13 +5792,13 @@ function detectEntryPoints() {
5448
5792
  "lib/index.ts"
5449
5793
  ];
5450
5794
  for (const c of candidates) {
5451
- if (fs8.existsSync(path8.join(root, c))) {
5795
+ if (fs9.existsSync(path10.join(root, c))) {
5452
5796
  entryPoints.push(c);
5453
5797
  }
5454
5798
  }
5455
5799
  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))) {
5800
+ const pkg = JSON.parse(fs9.readFileSync(path10.join(root, "package.json"), "utf-8"));
5801
+ if (pkg.main && fs9.existsSync(path10.join(root, pkg.main))) {
5458
5802
  if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
5459
5803
  }
5460
5804
  } catch {
@@ -5500,9 +5844,9 @@ async function generateContextDigest() {
5500
5844
  lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
5501
5845
  }
5502
5846
  try {
5503
- const ruleFile = path8.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
5504
- if (fs8.existsSync(ruleFile)) {
5505
- const content = fs8.readFileSync(ruleFile, "utf-8");
5847
+ const ruleFile = path10.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
5848
+ if (fs9.existsSync(ruleFile)) {
5849
+ const content = fs9.readFileSync(ruleFile, "utf-8");
5506
5850
  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
5851
  if (rules.length > 0) {
5508
5852
  lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
@@ -5548,15 +5892,15 @@ __export(kumaDriftDetector_exports, {
5548
5892
  getDriftSummary: () => getDriftSummary,
5549
5893
  hashFile: () => hashFile
5550
5894
  });
5551
- import fs9 from "fs";
5552
- import path9 from "path";
5895
+ import fs10 from "fs";
5896
+ import path11 from "path";
5553
5897
  import crypto3 from "crypto";
5554
5898
  function hashFile(filePath) {
5555
5899
  try {
5556
5900
  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");
5901
+ const fullPath = path11.resolve(root, filePath);
5902
+ if (!fs10.existsSync(fullPath)) return null;
5903
+ const content = fs10.readFileSync(fullPath, "utf-8");
5560
5904
  return crypto3.createHash("sha256").update(content).digest("hex");
5561
5905
  } catch {
5562
5906
  return null;
@@ -5636,8 +5980,8 @@ async function detectDrift() {
5636
5980
  let hasStaleFile = false;
5637
5981
  for (const fp of filePaths) {
5638
5982
  if (!fp) continue;
5639
- const fullPath = path9.resolve(getProjectRoot(), fp);
5640
- if (!fs9.existsSync(fullPath)) {
5983
+ const fullPath = path11.resolve(getProjectRoot(), fp);
5984
+ if (!fs10.existsSync(fullPath)) {
5641
5985
  hasStaleFile = true;
5642
5986
  break;
5643
5987
  }
@@ -5668,12 +6012,12 @@ function computeScopeHash(scope) {
5668
6012
  hash.update(scope);
5669
6013
  let found = false;
5670
6014
  try {
5671
- const files = fs9.readdirSync(root);
6015
+ const files = fs10.readdirSync(root);
5672
6016
  for (const file of files) {
5673
6017
  if (file.toLowerCase().includes(scope.toLowerCase())) {
5674
6018
  try {
5675
- if (fs9.statSync(path9.join(root, file)).isFile()) {
5676
- const content = fs9.readFileSync(path9.join(root, file), "utf-8");
6019
+ if (fs10.statSync(path11.join(root, file)).isFile()) {
6020
+ const content = fs10.readFileSync(path11.join(root, file), "utf-8");
5677
6021
  hash.update(file);
5678
6022
  hash.update(content.substring(0, 1e4));
5679
6023
  found = true;
@@ -5684,11 +6028,11 @@ function computeScopeHash(scope) {
5684
6028
  }
5685
6029
  } catch {
5686
6030
  }
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");
6031
+ const researchDir = path11.join(root, ".kuma", "research");
6032
+ if (fs10.existsSync(researchDir)) {
6033
+ const scopeFile = path11.join(researchDir, `${scope}.json`);
6034
+ if (fs10.existsSync(scopeFile)) {
6035
+ const content = fs10.readFileSync(scopeFile, "utf-8");
5692
6036
  hash.update(content);
5693
6037
  found = true;
5694
6038
  }
@@ -5786,26 +6130,26 @@ __export(kumaProgressiveContext_exports, {
5786
6130
  getSkillBoundary: () => getSkillBoundary,
5787
6131
  loadSection: () => loadSection
5788
6132
  });
5789
- import fs10 from "fs";
5790
- import path10 from "path";
6133
+ import fs11 from "fs";
6134
+ import path12 from "path";
5791
6135
  async function getProjectMeta() {
5792
6136
  const root = getProjectRoot();
5793
6137
  const name = root.split("/").pop() || "unknown";
5794
6138
  const stack = [];
5795
- if (fs10.existsSync(path10.join(root, "package.json"))) {
6139
+ if (fs11.existsSync(path12.join(root, "package.json"))) {
5796
6140
  try {
5797
- const pkg = JSON.parse(fs10.readFileSync(path10.join(root, "package.json"), "utf-8"));
6141
+ const pkg = JSON.parse(fs11.readFileSync(path12.join(root, "package.json"), "utf-8"));
5798
6142
  if (pkg.dependencies?.next) stack.push("Next.js");
5799
6143
  else if (pkg.dependencies?.react) stack.push("React");
5800
6144
  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");
6145
+ if (fs11.existsSync(path12.join(root, "pnpm-lock.yaml"))) stack.push("pnpm");
6146
+ else if (fs11.existsSync(path12.join(root, "yarn.lock"))) stack.push("yarn");
5803
6147
  } catch {
5804
6148
  }
5805
6149
  }
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");
6150
+ if (fs11.existsSync(path12.join(root, "go.mod"))) stack.push("Go");
6151
+ if (fs11.existsSync(path12.join(root, "Cargo.toml"))) stack.push("Rust");
6152
+ if (fs11.existsSync(path12.join(root, "pyproject.toml"))) stack.push("Python");
5809
6153
  let nodeCount = 0;
5810
6154
  let edgeCount = 0;
5811
6155
  try {
@@ -5816,10 +6160,10 @@ async function getProjectMeta() {
5816
6160
  edgeCount = edgeRes[0]?.values[0]?.[0] ?? 0;
5817
6161
  } catch {
5818
6162
  }
5819
- const kumaDir = path10.join(root, ".kuma");
6163
+ const kumaDir = path12.join(root, ".kuma");
5820
6164
  const layerStatus = {
5821
- domainRules: fs10.existsSync(path10.join(kumaDir, "DOMAIN_RULES.md")),
5822
- archFlow: fs10.existsSync(path10.join(kumaDir, "ARCHITECTURE_FLOW.md")),
6165
+ domainRules: fs11.existsSync(path12.join(kumaDir, "DOMAIN_RULES.md")),
6166
+ archFlow: fs11.existsSync(path12.join(kumaDir, "ARCHITECTURE_FLOW.md")),
5823
6167
  gotchas: getActiveGotchas().length
5824
6168
  };
5825
6169
  let hasDecisions = false;
@@ -6018,8 +6362,8 @@ __export(kumaSearch_exports, {
6018
6362
  formatHybridResults: () => formatHybridResults,
6019
6363
  hybridSearch: () => hybridSearch
6020
6364
  });
6021
- import fs12 from "fs";
6022
- import path12 from "path";
6365
+ import fs13 from "fs";
6366
+ import path14 from "path";
6023
6367
  function expandQueryTerms(query) {
6024
6368
  const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
6025
6369
  const expanded = /* @__PURE__ */ new Map();
@@ -6040,6 +6384,11 @@ function expandQueryTerms(query) {
6040
6384
  }
6041
6385
  return expanded;
6042
6386
  }
6387
+ function recencyScore(epochSec) {
6388
+ if (!epochSec) return 0.5;
6389
+ const ageDays = Math.max(0, (Date.now() / 1e3 - epochSec) / 86400);
6390
+ return Math.max(0, Math.min(1, 1 - ageDays / 90));
6391
+ }
6043
6392
  async function buildSearchVectors() {
6044
6393
  if (_vectorCache && Date.now() - _vectorCache.builtAt < VECTOR_CACHE_TTL) {
6045
6394
  return _vectorCache.vectors;
@@ -6047,7 +6396,7 @@ async function buildSearchVectors() {
6047
6396
  const vectors = [];
6048
6397
  try {
6049
6398
  const db = await getDb();
6050
- const stmt = db.prepare("SELECT id, name, metadata, file_path FROM nodes ORDER BY updated_at DESC LIMIT 500");
6399
+ const stmt = db.prepare("SELECT id, name, metadata, file_path, updated_at FROM nodes ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxNodes);
6051
6400
  while (stmt.step()) {
6052
6401
  const row = stmt.getAsObject();
6053
6402
  const name = row.name || "";
@@ -6068,7 +6417,8 @@ async function buildSearchVectors() {
6068
6417
  vectors.push({
6069
6418
  terms: termCounts,
6070
6419
  source: name || row.id,
6071
- sourceType: "graph"
6420
+ sourceType: "graph",
6421
+ recency: recencyScore(row.updated_at)
6072
6422
  });
6073
6423
  }
6074
6424
  stmt.free();
@@ -6076,12 +6426,13 @@ async function buildSearchVectors() {
6076
6426
  }
6077
6427
  try {
6078
6428
  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);
6429
+ const memDir = path14.join(root, ".kuma", "memories");
6430
+ if (fs13.existsSync(memDir)) {
6431
+ const files = fs13.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
6082
6432
  for (const file of files) {
6083
6433
  try {
6084
- const content = fs12.readFileSync(path12.join(memDir, file), "utf-8");
6434
+ const fullPath = path14.join(memDir, file);
6435
+ const content = fs13.readFileSync(fullPath, "utf-8");
6085
6436
  const text = content.toLowerCase();
6086
6437
  const rawTerms = extractTerms(text);
6087
6438
  const termCounts = /* @__PURE__ */ new Map();
@@ -6091,7 +6442,8 @@ async function buildSearchVectors() {
6091
6442
  vectors.push({
6092
6443
  terms: termCounts,
6093
6444
  source: file.replace(/\.md$/, ""),
6094
- sourceType: "memory"
6445
+ sourceType: "memory",
6446
+ recency: recencyScore(Math.floor(fs13.statSync(fullPath).mtimeMs / 1e3))
6095
6447
  });
6096
6448
  } catch {
6097
6449
  }
@@ -6101,7 +6453,7 @@ async function buildSearchVectors() {
6101
6453
  }
6102
6454
  try {
6103
6455
  const db = await getDb();
6104
- const stmt = db.prepare("SELECT scope, record FROM research_cache ORDER BY updated_at DESC LIMIT 50");
6456
+ const stmt = db.prepare("SELECT scope, record, updated_at FROM research_cache ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxResearch);
6105
6457
  while (stmt.step()) {
6106
6458
  const row = stmt.getAsObject();
6107
6459
  const scope = row.scope || "";
@@ -6115,7 +6467,8 @@ async function buildSearchVectors() {
6115
6467
  vectors.push({
6116
6468
  terms: termCounts,
6117
6469
  source: scope,
6118
- sourceType: "research"
6470
+ sourceType: "research",
6471
+ recency: recencyScore(row.updated_at)
6119
6472
  });
6120
6473
  }
6121
6474
  stmt.free();
@@ -6217,7 +6570,8 @@ async function hybridSearch(query, limit = 10) {
6217
6570
  const existing = results.find((r) => r.source === vec.source);
6218
6571
  if (existing) {
6219
6572
  existing.vectorScore = vectorScore;
6220
- existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4));
6573
+ const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
6574
+ existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4 + recencyBoost));
6221
6575
  for (const [term] of vec.terms) {
6222
6576
  if (queryVector.has(term) && !existing.matchedTerms.includes(term)) {
6223
6577
  existing.matchedTerms.push(term);
@@ -6228,10 +6582,11 @@ async function hybridSearch(query, limit = 10) {
6228
6582
  for (const [term] of vec.terms) {
6229
6583
  if (queryVector.has(term)) matchedTerms.push(term);
6230
6584
  }
6585
+ const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
6231
6586
  results.push({
6232
6587
  source: vec.source,
6233
6588
  sourceType: vec.sourceType,
6234
- score: Math.round(vectorScore * 0.4),
6589
+ score: Math.min(100, Math.round(vectorScore * 0.4 + recencyBoost)),
6235
6590
  keywordScore: 0,
6236
6591
  vectorScore,
6237
6592
  matchedTerms
@@ -6286,7 +6641,7 @@ async function buildGraphConnectivity() {
6286
6641
  let totalEdges = 0;
6287
6642
  try {
6288
6643
  const db = await getDb();
6289
- const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT 2000");
6644
+ const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT " + SCALE_LIMITS.maxEdges);
6290
6645
  while (stmt.step()) {
6291
6646
  const row = stmt.getAsObject();
6292
6647
  const src = row.source_id || "";
@@ -6336,7 +6691,7 @@ function clearSearchCache() {
6336
6691
  _vectorCache = null;
6337
6692
  clearConnectivityCache();
6338
6693
  }
6339
- var SYNONYM_MAP, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
6694
+ var SYNONYM_MAP, SCALE_LIMITS, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
6340
6695
  var init_kumaSearch = __esm({
6341
6696
  "src/engine/kumaSearch.ts"() {
6342
6697
  "use strict";
@@ -6381,6 +6736,16 @@ var init_kumaSearch = __esm({
6381
6736
  update: ["update", "modify", "change", "edit", "patch"],
6382
6737
  delete: ["delete", "remove", "drop", "clear", "purge"]
6383
6738
  };
6739
+ SCALE_LIMITS = {
6740
+ maxNodes: 5e3,
6741
+ // was 500 — graphs grow with project size
6742
+ maxMemoryFiles: 30,
6743
+ // was 10
6744
+ maxResearch: 300,
6745
+ // was 50
6746
+ maxEdges: 2e4
6747
+ // was 2000 (graph connectivity)
6748
+ };
6384
6749
  _vectorCache = null;
6385
6750
  VECTOR_CACHE_TTL = 3e5;
6386
6751
  _connectivityCache = null;
@@ -6393,8 +6758,8 @@ var kumaMiner_exports = {};
6393
6758
  __export(kumaMiner_exports, {
6394
6759
  mineHistoricalDecisions: () => mineHistoricalDecisions
6395
6760
  });
6396
- import fs13 from "fs";
6397
- import path13 from "path";
6761
+ import fs14 from "fs";
6762
+ import path15 from "path";
6398
6763
  import { execSync as execSync4 } from "child_process";
6399
6764
  import fastGlob2 from "fast-glob";
6400
6765
  async function mineHistoricalDecisions(options = {}) {
@@ -6469,7 +6834,7 @@ async function mineHistoricalDecisions(options = {}) {
6469
6834
  }
6470
6835
  if (candidates.length < limit) {
6471
6836
  try {
6472
- const scopePattern = options.scope ? `**/*${options.scope}*.*` : "**/*.{ts,js,py,go,rs,java,md}";
6837
+ const scopePattern = options.scope ? `**/*${options.scope}*.*` : `**/*.{${SOURCE_EXT_GLOB.slice(1, -1)},md}`;
6473
6838
  const files = await fastGlob2(scopePattern, {
6474
6839
  cwd: root,
6475
6840
  ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
@@ -6484,9 +6849,9 @@ async function mineHistoricalDecisions(options = {}) {
6484
6849
  ];
6485
6850
  for (const file of files.slice(0, 50)) {
6486
6851
  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");
6852
+ const fullPath = path15.join(root, file);
6853
+ if (!fs14.existsSync(fullPath)) continue;
6854
+ const content = fs14.readFileSync(fullPath, "utf-8");
6490
6855
  const lines = content.split("\n");
6491
6856
  for (let i = 0; i < lines.length; i++) {
6492
6857
  const l = lines[i];
@@ -6561,6 +6926,7 @@ var init_kumaMiner = __esm({
6561
6926
  "use strict";
6562
6927
  init_kumaDb();
6563
6928
  init_kumaMemory();
6929
+ init_languageSupport();
6564
6930
  }
6565
6931
  });
6566
6932
 
@@ -6793,8 +7159,8 @@ __export(kumaGitHarvester_exports, {
6793
7159
  installGitHook: () => installGitHook
6794
7160
  });
6795
7161
  import { execSync as execSync5 } from "child_process";
6796
- import fs14 from "fs";
6797
- import path14 from "path";
7162
+ import fs15 from "fs";
7163
+ import path16 from "path";
6798
7164
  function getRecentCommits(count = 5) {
6799
7165
  const root = getProjectRoot();
6800
7166
  try {
@@ -6974,11 +7340,11 @@ async function harvestGitHistory(options) {
6974
7340
  }
6975
7341
  function installGitHook() {
6976
7342
  const root = getProjectRoot();
6977
- const hooksDir = path14.join(root, ".git", "hooks");
6978
- if (!fs14.existsSync(hooksDir)) {
7343
+ const hooksDir = path16.join(root, ".git", "hooks");
7344
+ if (!fs15.existsSync(hooksDir)) {
6979
7345
  return "\u26A0\uFE0F Not a git repository \u2014 no .git/hooks/ directory found.";
6980
7346
  }
6981
- const hookPath = path14.join(hooksDir, "post-commit");
7347
+ const hookPath = path16.join(hooksDir, "post-commit");
6982
7348
  const hookContent = `#!/bin/bash
6983
7349
  # Kuma auto-harvester hook (post-commit)
6984
7350
  # Automatically extracts decisions and gotchas from commits
@@ -6986,23 +7352,23 @@ if command -v npx &> /dev/null; then
6986
7352
  npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
6987
7353
  fi
6988
7354
  `;
6989
- if (fs14.existsSync(hookPath)) {
6990
- const existing = fs14.readFileSync(hookPath, "utf-8");
7355
+ if (fs15.existsSync(hookPath)) {
7356
+ const existing = fs15.readFileSync(hookPath, "utf-8");
6991
7357
  if (existing.includes("kuma")) {
6992
7358
  return "\u2705 Kuma git hook already installed.";
6993
7359
  }
6994
7360
  const backupPath = hookPath + ".backup." + Date.now();
6995
- fs14.copyFileSync(hookPath, backupPath);
6996
- fs14.writeFileSync(hookPath, hookContent, "utf-8");
7361
+ fs15.copyFileSync(hookPath, backupPath);
7362
+ fs15.writeFileSync(hookPath, hookContent, "utf-8");
6997
7363
  try {
6998
- fs14.chmodSync(hookPath, 493);
7364
+ fs15.chmodSync(hookPath, 493);
6999
7365
  } catch {
7000
7366
  }
7001
- return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path14.basename(backupPath)})`;
7367
+ return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path16.basename(backupPath)})`;
7002
7368
  }
7003
- fs14.writeFileSync(hookPath, hookContent, "utf-8");
7369
+ fs15.writeFileSync(hookPath, hookContent, "utf-8");
7004
7370
  try {
7005
- fs14.chmodSync(hookPath, 493);
7371
+ fs15.chmodSync(hookPath, 493);
7006
7372
  } catch {
7007
7373
  }
7008
7374
  return "\u2705 Installed kuma post-commit hook.";
@@ -7301,19 +7667,19 @@ __export(kumaPolicyEngine_exports, {
7301
7667
  processOverride: () => processOverride,
7302
7668
  savePolicyConfig: () => savePolicyConfig
7303
7669
  });
7304
- import fs20 from "fs";
7305
- import path20 from "path";
7670
+ import fs21 from "fs";
7671
+ import path22 from "path";
7306
7672
  function loadPolicyConfig() {
7307
7673
  try {
7308
7674
  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");
7675
+ const policyPath = path22.join(root, ".kuma", "POLICY.json");
7676
+ if (fs21.existsSync(policyPath)) {
7677
+ const content = fs21.readFileSync(policyPath, "utf-8");
7312
7678
  const config = JSON.parse(content);
7313
7679
  return { ...DEFAULT_POLICY_CONFIG, ...config };
7314
7680
  }
7315
- const ymlPath = path20.join(root, ".kuma", "policy.yml");
7316
- if (fs20.existsSync(ymlPath)) {
7681
+ const ymlPath = path22.join(root, ".kuma", "policy.yml");
7682
+ if (fs21.existsSync(ymlPath)) {
7317
7683
  console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
7318
7684
  }
7319
7685
  } catch (err) {
@@ -7324,10 +7690,10 @@ function loadPolicyConfig() {
7324
7690
  function savePolicyConfig(config) {
7325
7691
  try {
7326
7692
  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");
7693
+ const policyPath = path22.join(root, ".kuma", "POLICY.json");
7694
+ const dir = path22.dirname(policyPath);
7695
+ if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
7696
+ fs21.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
7331
7697
  return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
7332
7698
  } catch (err) {
7333
7699
  return `\u274C Failed to save policy: ${err}`;
@@ -7612,14 +7978,14 @@ __export(kumaAstValidator_exports, {
7612
7978
  validateDiff: () => validateDiff,
7613
7979
  validateFile: () => validateFile
7614
7980
  });
7615
- import fs21 from "fs";
7616
- import path21 from "path";
7981
+ import fs22 from "fs";
7982
+ import path23 from "path";
7617
7983
  function loadImportWhitelist() {
7618
7984
  try {
7619
7985
  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"));
7986
+ const whitelistPath = path23.join(root, ".kuma", "import-whitelist.json");
7987
+ if (fs22.existsSync(whitelistPath)) {
7988
+ return JSON.parse(fs22.readFileSync(whitelistPath, "utf-8"));
7623
7989
  }
7624
7990
  } catch {
7625
7991
  }
@@ -7645,7 +8011,9 @@ function validateCodeContent(code, filePath) {
7645
8011
  suggestion: "Restore the assertion or write an equivalent test for the new behavior"
7646
8012
  });
7647
8013
  }
7648
- if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}") {
8014
+ 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)
8015
+ /except\s*([^:]*)\s*:\s*(pass|\.\.\.)\s*(#.*)?$/.test(line) || /except\s*([^:]*)\s*:\s*$/.test(line) && /^pass(\s*#.*)?$/.test(lines[i + 1]?.trim() ?? "");
8016
+ if (emptyCatch) {
7649
8017
  findings.push({
7650
8018
  line: lineNum,
7651
8019
  severity: "error",
@@ -7654,8 +8022,8 @@ function validateCodeContent(code, filePath) {
7654
8022
  suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
7655
8023
  });
7656
8024
  }
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)) {
8025
+ if (isTestFile(filePath || "")) {
8026
+ 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
8027
  findings.push({
7660
8028
  line: lineNum,
7661
8029
  severity: "warning",
@@ -7665,7 +8033,7 @@ function validateCodeContent(code, filePath) {
7665
8033
  });
7666
8034
  }
7667
8035
  }
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"))) {
8036
+ 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
8037
  findings.push({
7670
8038
  line: lineNum,
7671
8039
  severity: "warning",
@@ -7674,18 +8042,15 @@ function validateCodeContent(code, filePath) {
7674
8042
  suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
7675
8043
  });
7676
8044
  }
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
- }
8045
+ const imported = matchImportPath(line);
8046
+ if (imported && whitelist.blocked.some((b) => imported.includes(b))) {
8047
+ findings.push({
8048
+ line: lineNum,
8049
+ severity: "error",
8050
+ category: "import-violation",
8051
+ message: `Blocked import: "${imported}" is not allowed per security whitelist`,
8052
+ suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
8053
+ });
7689
8054
  }
7690
8055
  }
7691
8056
  return findings;
@@ -7693,9 +8058,9 @@ function validateCodeContent(code, filePath) {
7693
8058
  function validateFile(filePath) {
7694
8059
  try {
7695
8060
  const root = getProjectRoot();
7696
- const fullPath = path21.resolve(root, filePath);
7697
- if (!fs21.existsSync(fullPath)) return [];
7698
- const content = fs21.readFileSync(fullPath, "utf-8");
8061
+ const fullPath = path23.resolve(root, filePath);
8062
+ if (!fs22.existsSync(fullPath)) return [];
8063
+ const content = fs22.readFileSync(fullPath, "utf-8");
7699
8064
  return validateCodeContent(content, filePath);
7700
8065
  } catch {
7701
8066
  return [];
@@ -7755,6 +8120,7 @@ var init_kumaAstValidator = __esm({
7755
8120
  "src/engine/kumaAstValidator.ts"() {
7756
8121
  "use strict";
7757
8122
  init_pathValidator();
8123
+ init_languageSupport();
7758
8124
  }
7759
8125
  });
7760
8126
 
@@ -7766,16 +8132,16 @@ __export(kumaCheckpoint_exports, {
7766
8132
  pruneCheckpoints: () => pruneCheckpoints,
7767
8133
  rollbackToCheckpoint: () => rollbackToCheckpoint
7768
8134
  });
7769
- import fs22 from "fs";
7770
- import path22 from "path";
8135
+ import fs23 from "fs";
8136
+ import path24 from "path";
7771
8137
  async function createCheckpoint(label, description) {
7772
8138
  try {
7773
8139
  const root = getProjectRoot();
7774
- const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7775
- if (fs22.existsSync(cpDir)) {
8140
+ const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
8141
+ if (fs23.existsSync(cpDir)) {
7776
8142
  return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
7777
8143
  }
7778
- fs22.mkdirSync(cpDir, { recursive: true });
8144
+ fs23.mkdirSync(cpDir, { recursive: true });
7779
8145
  const db = await getDb();
7780
8146
  const filesStmt = db.prepare(
7781
8147
  "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
@@ -7784,19 +8150,19 @@ async function createCheckpoint(label, description) {
7784
8150
  while (filesStmt.step()) {
7785
8151
  const row = filesStmt.getAsObject();
7786
8152
  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");
8153
+ const fullPath = path24.resolve(root, fp);
8154
+ if (fs23.existsSync(fullPath)) {
8155
+ const content = fs23.readFileSync(fullPath, "utf-8");
7790
8156
  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");
8157
+ const fileDir = path24.dirname(path24.join(cpDir, "files", fp));
8158
+ if (!fs23.existsSync(fileDir)) fs23.mkdirSync(fileDir, { recursive: true });
8159
+ fs23.writeFileSync(path24.join(cpDir, "files", fp), content, "utf-8");
7794
8160
  files.push({ path: fp, hash });
7795
8161
  }
7796
8162
  }
7797
8163
  filesStmt.free();
7798
8164
  const dbData = db.export();
7799
- fs22.writeFileSync(path22.join(cpDir, "kuma.db"), Buffer.from(dbData));
8165
+ fs23.writeFileSync(path24.join(cpDir, "kuma.db"), Buffer.from(dbData));
7800
8166
  const manifest = {
7801
8167
  label,
7802
8168
  timestamp: Date.now(),
@@ -7804,8 +8170,8 @@ async function createCheckpoint(label, description) {
7804
8170
  dbSnapshot: true,
7805
8171
  description
7806
8172
  };
7807
- fs22.writeFileSync(
7808
- path22.join(cpDir, "manifest.json"),
8173
+ fs23.writeFileSync(
8174
+ path24.join(cpDir, "manifest.json"),
7809
8175
  JSON.stringify(manifest, null, 2),
7810
8176
  "utf-8"
7811
8177
  );
@@ -7831,37 +8197,37 @@ async function createCheckpoint(label, description) {
7831
8197
  async function rollbackToCheckpoint(label) {
7832
8198
  try {
7833
8199
  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)) {
8200
+ const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
8201
+ const manifestPath = path24.join(cpDir, "manifest.json");
8202
+ if (!fs23.existsSync(manifestPath)) {
7837
8203
  return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
7838
8204
  }
7839
8205
  const manifest = JSON.parse(
7840
- fs22.readFileSync(manifestPath, "utf-8")
8206
+ fs23.readFileSync(manifestPath, "utf-8")
7841
8207
  );
7842
8208
  let restored = 0;
7843
8209
  let failed = 0;
7844
8210
  for (const f of manifest.files) {
7845
- const snapshotPath = path22.join(cpDir, "files", f.path);
7846
- if (fs22.existsSync(snapshotPath)) {
8211
+ const snapshotPath = path24.join(cpDir, "files", f.path);
8212
+ if (fs23.existsSync(snapshotPath)) {
7847
8213
  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");
8214
+ const content = fs23.readFileSync(snapshotPath, "utf-8");
8215
+ const targetPath = path24.resolve(root, f.path);
8216
+ const targetDir = path24.dirname(targetPath);
8217
+ if (!fs23.existsSync(targetDir)) fs23.mkdirSync(targetDir, { recursive: true });
8218
+ fs23.writeFileSync(targetPath, content, "utf-8");
7853
8219
  restored++;
7854
8220
  } catch {
7855
8221
  failed++;
7856
8222
  }
7857
8223
  }
7858
8224
  }
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);
8225
+ const dbSnapshotPath = path24.join(cpDir, "kuma.db");
8226
+ if (fs23.existsSync(dbSnapshotPath)) {
8227
+ const kumaDir = path24.join(root, ".kuma");
8228
+ const dbPath = path24.join(kumaDir, "kuma.db");
8229
+ const snapshotData = fs23.readFileSync(dbSnapshotPath);
8230
+ fs23.writeFileSync(dbPath, snapshotData);
7865
8231
  resetDbInstance();
7866
8232
  }
7867
8233
  sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
@@ -7886,18 +8252,18 @@ async function rollbackToCheckpoint(label) {
7886
8252
  function listCheckpoints() {
7887
8253
  try {
7888
8254
  const root = getProjectRoot();
7889
- const cpDir = path22.join(root, CHECKPOINT_DIR);
7890
- if (!fs22.existsSync(cpDir)) {
8255
+ const cpDir = path24.join(root, CHECKPOINT_DIR);
8256
+ if (!fs23.existsSync(cpDir)) {
7891
8257
  return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
7892
8258
  }
7893
- const entries = fs22.readdirSync(cpDir);
8259
+ const entries = fs23.readdirSync(cpDir);
7894
8260
  const checkpoints = [];
7895
8261
  for (const entry of entries) {
7896
- const manifestPath = path22.join(cpDir, entry, "manifest.json");
7897
- if (fs22.existsSync(manifestPath)) {
8262
+ const manifestPath = path24.join(cpDir, entry, "manifest.json");
8263
+ if (fs23.existsSync(manifestPath)) {
7898
8264
  try {
7899
8265
  const manifest = JSON.parse(
7900
- fs22.readFileSync(manifestPath, "utf-8")
8266
+ fs23.readFileSync(manifestPath, "utf-8")
7901
8267
  );
7902
8268
  checkpoints.push({ label: entry, manifest });
7903
8269
  } catch {
@@ -7929,12 +8295,12 @@ function listCheckpoints() {
7929
8295
  function pruneCheckpoints(keep = 5) {
7930
8296
  try {
7931
8297
  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);
8298
+ const cpDir = path24.join(root, CHECKPOINT_DIR);
8299
+ if (!fs23.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
8300
+ 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
8301
  let removed = 0;
7936
8302
  for (let i = keep; i < entries.length; i++) {
7937
- fs22.rmSync(path22.join(cpDir, entries[i].name), { recursive: true, force: true });
8303
+ fs23.rmSync(path24.join(cpDir, entries[i].name), { recursive: true, force: true });
7938
8304
  removed++;
7939
8305
  }
7940
8306
  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 +8340,18 @@ __export(kumaContractEngine_exports, {
7974
8340
  loadContracts: () => loadContracts,
7975
8341
  runContractChecks: () => runContractChecks
7976
8342
  });
7977
- import fs23 from "fs";
7978
- import path23 from "path";
8343
+ import fs24 from "fs";
8344
+ import path25 from "path";
7979
8345
  function contractsDir() {
7980
- return path23.join(getProjectRoot(), ".kuma", "contracts");
8346
+ return path25.join(getProjectRoot(), ".kuma", "contracts");
7981
8347
  }
7982
8348
  function ensureContractsDir() {
7983
8349
  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(
8350
+ if (!fs24.existsSync(dir)) {
8351
+ fs24.mkdirSync(dir, { recursive: true });
8352
+ const defaultPath = path25.join(dir, "default.json");
8353
+ if (!fs24.existsSync(defaultPath)) {
8354
+ fs24.writeFileSync(
7989
8355
  defaultPath,
7990
8356
  JSON.stringify(DEFAULT_CONTRACTS, null, 2),
7991
8357
  "utf-8"
@@ -7998,10 +8364,10 @@ function loadContracts() {
7998
8364
  const dir = contractsDir();
7999
8365
  const contracts = [];
8000
8366
  try {
8001
- const files = fs23.readdirSync(dir).filter((f) => f.endsWith(".json"));
8367
+ const files = fs24.readdirSync(dir).filter((f) => f.endsWith(".json"));
8002
8368
  for (const file of files) {
8003
8369
  try {
8004
- const content = fs23.readFileSync(path23.join(dir, file), "utf-8");
8370
+ const content = fs24.readFileSync(path25.join(dir, file), "utf-8");
8005
8371
  const config = JSON.parse(content);
8006
8372
  contracts.push(config);
8007
8373
  } catch (err) {
@@ -8074,7 +8440,7 @@ function evaluateCondition(condition, contextPath) {
8074
8440
  switch (condition.type) {
8075
8441
  case "file_exists": {
8076
8442
  const targetPath = resolveTarget(condition.target, contextPath, root);
8077
- const exists = targetPath.some((p) => fs23.existsSync(p));
8443
+ const exists = targetPath.some((p) => fs24.existsSync(p));
8078
8444
  return {
8079
8445
  passed: exists,
8080
8446
  message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
@@ -8082,7 +8448,7 @@ function evaluateCondition(condition, contextPath) {
8082
8448
  }
8083
8449
  case "file_not_exists": {
8084
8450
  const targetPath = resolveTarget(condition.target, contextPath, root);
8085
- const exists = targetPath.some((p) => fs23.existsSync(p));
8451
+ const exists = targetPath.some((p) => fs24.existsSync(p));
8086
8452
  return {
8087
8453
  passed: !exists,
8088
8454
  message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
@@ -8090,9 +8456,9 @@ function evaluateCondition(condition, contextPath) {
8090
8456
  }
8091
8457
  case "contains": {
8092
8458
  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");
8459
+ const fullPath = path25.resolve(root, contextPath);
8460
+ if (!fs24.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
8461
+ const content = fs24.readFileSync(fullPath, "utf-8");
8096
8462
  const hasContent = condition.value ? content.includes(condition.value) : false;
8097
8463
  return {
8098
8464
  passed: hasContent,
@@ -8104,12 +8470,12 @@ function evaluateCondition(condition, contextPath) {
8104
8470
  const root2 = getProjectRoot();
8105
8471
  const targets = resolveTarget(condition.target, contextPath, root2);
8106
8472
  for (const t of targets) {
8107
- if (fs23.existsSync(t)) {
8108
- const content = fs23.readFileSync(t, "utf-8");
8473
+ if (fs24.existsSync(t)) {
8474
+ const content = fs24.readFileSync(t, "utf-8");
8109
8475
  if (condition.value && content.includes(condition.value)) {
8110
8476
  return {
8111
8477
  passed: false,
8112
- message: `\u274C Found forbidden pattern "${condition.value}" in ${path23.relative(root2, t)} (${condition.description})`
8478
+ message: `\u274C Found forbidden pattern "${condition.value}" in ${path25.relative(root2, t)} (${condition.description})`
8113
8479
  };
8114
8480
  }
8115
8481
  }
@@ -8118,15 +8484,11 @@ function evaluateCondition(condition, contextPath) {
8118
8484
  }
8119
8485
  case "has_test_file": {
8120
8486
  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));
8487
+ const candidates = getTestCandidates(contextPath);
8488
+ const hasTest = candidates.some((t) => {
8489
+ const abs = path25.isAbsolute(t) ? t : path25.resolve(root, t);
8490
+ return fs24.existsSync(abs);
8491
+ });
8130
8492
  return {
8131
8493
  passed: hasTest,
8132
8494
  message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
@@ -8150,18 +8512,24 @@ function resolveTarget(pattern, contextPath, root) {
8150
8512
  const r = root || getProjectRoot();
8151
8513
  if (pattern.startsWith("*")) {
8152
8514
  if (contextPath) {
8153
- const dir = path23.dirname(path23.resolve(r, contextPath));
8515
+ const dir = path25.dirname(path25.resolve(r, contextPath));
8154
8516
  const suffix = pattern.replace("*", "");
8155
8517
  try {
8156
- const files = fs23.readdirSync(dir);
8157
- return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path23.join(dir, f));
8518
+ const files = fs24.readdirSync(dir);
8519
+ return files.filter((f) => {
8520
+ try {
8521
+ return fs24.statSync(path25.join(dir, f)).isFile();
8522
+ } catch {
8523
+ return false;
8524
+ }
8525
+ }).filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path25.join(dir, f));
8158
8526
  } catch {
8159
- return [path23.join(dir, pattern.replace("*", ""))];
8527
+ return [path25.join(dir, pattern.replace("*", ""))];
8160
8528
  }
8161
8529
  }
8162
8530
  return [];
8163
8531
  }
8164
- return [path23.resolve(r, pattern)];
8532
+ return [path25.resolve(r, pattern)];
8165
8533
  }
8166
8534
  async function runContractChecks(toolName, filePath, phase = "pre") {
8167
8535
  const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
@@ -8223,6 +8591,7 @@ var init_kumaContractEngine = __esm({
8223
8591
  "src/engine/kumaContractEngine.ts"() {
8224
8592
  "use strict";
8225
8593
  init_pathValidator();
8594
+ init_languageSupport();
8226
8595
  DEFAULT_CONTRACTS = {
8227
8596
  version: 1,
8228
8597
  contracts: [
@@ -8249,15 +8618,21 @@ var init_kumaContractEngine = __esm({
8249
8618
  postConditions: [
8250
8619
  {
8251
8620
  type: "not_contains",
8252
- target: "*.ts",
8621
+ target: "*",
8253
8622
  value: "catch {}",
8254
8623
  description: "Empty catch blocks suppress errors silently"
8255
8624
  },
8256
8625
  {
8257
8626
  type: "not_contains",
8258
- target: "*.ts",
8627
+ target: "*",
8259
8628
  value: "catch (e) {}",
8260
8629
  description: "Empty catch (e) {} suppresses errors silently"
8630
+ },
8631
+ {
8632
+ type: "not_contains",
8633
+ target: "*",
8634
+ value: "except:",
8635
+ description: "Bare except blocks suppress errors silently (Python)"
8261
8636
  }
8262
8637
  ],
8263
8638
  severity: "error"
@@ -8275,6 +8650,116 @@ var init_kumaContractEngine = __esm({
8275
8650
  }
8276
8651
  });
8277
8652
 
8653
+ // src/engine/kumaAutoGotcha.ts
8654
+ var kumaAutoGotcha_exports = {};
8655
+ __export(kumaAutoGotcha_exports, {
8656
+ AUTO_GOTCHA_ESCALATE_AT: () => AUTO_GOTCHA_ESCALATE_AT,
8657
+ AUTO_GOTCHA_THRESHOLD: () => AUTO_GOTCHA_THRESHOLD,
8658
+ loadAutoGotchaState: () => loadAutoGotchaState,
8659
+ resetScopeFailures: () => resetScopeFailures,
8660
+ saveAutoGotchaState: () => saveAutoGotchaState,
8661
+ trackVerificationResult: () => trackVerificationResult
8662
+ });
8663
+ import fs25 from "fs";
8664
+ import path26 from "path";
8665
+ function statePath() {
8666
+ return path26.join(getProjectRoot(), STATE_FILE);
8667
+ }
8668
+ function loadAutoGotchaState() {
8669
+ try {
8670
+ const fp = statePath();
8671
+ if (fs25.existsSync(fp)) {
8672
+ const parsed = JSON.parse(fs25.readFileSync(fp, "utf-8"));
8673
+ return {
8674
+ failures: parsed?.failures ?? {},
8675
+ lastRecorded: parsed?.lastRecorded ?? {}
8676
+ };
8677
+ }
8678
+ } catch {
8679
+ }
8680
+ return { failures: {}, lastRecorded: {} };
8681
+ }
8682
+ function saveAutoGotchaState(state) {
8683
+ try {
8684
+ const fp = statePath();
8685
+ const dir = path26.dirname(fp);
8686
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8687
+ fs25.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
8688
+ } catch {
8689
+ }
8690
+ }
8691
+ function resetScopeFailures(scope) {
8692
+ const state = loadAutoGotchaState();
8693
+ if (state.failures[scope] !== void 0 || state.lastRecorded?.[scope] !== void 0) {
8694
+ delete state.failures[scope];
8695
+ if (state.lastRecorded) delete state.lastRecorded[scope];
8696
+ saveAutoGotchaState(state);
8697
+ }
8698
+ }
8699
+ async function trackVerificationResult(scope, passed) {
8700
+ if (!scope || scope === "session-impact") return { recorded: false, failureCount: 0 };
8701
+ const state = loadAutoGotchaState();
8702
+ const prev = state.failures[scope] ?? 0;
8703
+ state.lastRecorded = state.lastRecorded || {};
8704
+ if (passed) {
8705
+ if (prev > 0 || state.lastRecorded[scope] !== void 0) {
8706
+ delete state.failures[scope];
8707
+ delete state.lastRecorded[scope];
8708
+ saveAutoGotchaState(state);
8709
+ }
8710
+ return { recorded: false, failureCount: 0 };
8711
+ }
8712
+ const failureCount = prev + 1;
8713
+ state.failures[scope] = failureCount;
8714
+ saveAutoGotchaState(state);
8715
+ const lastRecordedAt = state.lastRecorded[scope] ?? 0;
8716
+ const shouldRecord = failureCount === AUTO_GOTCHA_THRESHOLD || failureCount === AUTO_GOTCHA_ESCALATE_AT;
8717
+ if (!shouldRecord || failureCount <= lastRecordedAt) {
8718
+ return { recorded: false, failureCount };
8719
+ }
8720
+ try {
8721
+ const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
8722
+ const severity = failureCount >= AUTO_GOTCHA_ESCALATE_AT ? "high" : "medium";
8723
+ const result = await addGotcha2({
8724
+ filePath: scope,
8725
+ description: `Verification keeps failing for scope "${scope}"`,
8726
+ severity,
8727
+ 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.`
8728
+ });
8729
+ state.lastRecorded[scope] = failureCount;
8730
+ saveAutoGotchaState(state);
8731
+ return {
8732
+ recorded: true,
8733
+ failureCount,
8734
+ message: [
8735
+ `\u{1F9E0} **Auto-Gotcha recorded** (self-learning loop)`,
8736
+ `\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`,
8737
+ ``,
8738
+ `\u26A0\uFE0F Scope \`${scope}\` failed verification ${failureCount} consecutive times.`,
8739
+ `A gotcha has been recorded automatically so future sessions avoid re-discovering this.`,
8740
+ ``,
8741
+ result
8742
+ ].join("\n")
8743
+ };
8744
+ } catch (err) {
8745
+ return {
8746
+ recorded: false,
8747
+ failureCount,
8748
+ message: `\u26A0\uFE0F Auto-gotcha recording failed: ${err}`
8749
+ };
8750
+ }
8751
+ }
8752
+ var STATE_FILE, AUTO_GOTCHA_THRESHOLD, AUTO_GOTCHA_ESCALATE_AT;
8753
+ var init_kumaAutoGotcha = __esm({
8754
+ "src/engine/kumaAutoGotcha.ts"() {
8755
+ "use strict";
8756
+ init_pathValidator();
8757
+ STATE_FILE = ".kuma/auto-gotcha.json";
8758
+ AUTO_GOTCHA_THRESHOLD = 2;
8759
+ AUTO_GOTCHA_ESCALATE_AT = 4;
8760
+ }
8761
+ });
8762
+
8278
8763
  // src/engine/kumaVerifier.ts
8279
8764
  var kumaVerifier_exports = {};
8280
8765
  __export(kumaVerifier_exports, {
@@ -8282,37 +8767,37 @@ __export(kumaVerifier_exports, {
8282
8767
  getRunningVerificationPid: () => getRunningVerificationPid,
8283
8768
  runAutoVerification: () => runAutoVerification
8284
8769
  });
8285
- import fs24 from "fs";
8286
- import path24 from "path";
8770
+ import fs26 from "fs";
8771
+ import path27 from "path";
8287
8772
  import { exec } from "child_process";
8288
8773
  function getLockDir(root = process.cwd()) {
8289
- return path24.resolve(root, LOCK_DIR_NAME);
8774
+ return path27.resolve(root, LOCK_DIR_NAME);
8290
8775
  }
8291
8776
  function getPidFile(root = process.cwd()) {
8292
- return path24.join(getLockDir(root), "pid");
8777
+ return path27.join(getLockDir(root), "pid");
8293
8778
  }
8294
8779
  function acquireFileLock(root) {
8295
8780
  const lockDir = getLockDir(root);
8296
8781
  try {
8297
- fs24.mkdirSync(lockDir, { recursive: false });
8298
- fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8782
+ fs26.mkdirSync(lockDir, { recursive: false });
8783
+ fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8299
8784
  return true;
8300
8785
  } catch {
8301
8786
  try {
8302
- if (fs24.existsSync(getPidFile(root))) {
8303
- const pid = parseInt(fs24.readFileSync(getPidFile(root), "utf-8"), 10);
8787
+ if (fs26.existsSync(getPidFile(root))) {
8788
+ const pid = parseInt(fs26.readFileSync(getPidFile(root), "utf-8"), 10);
8304
8789
  try {
8305
8790
  process.kill(pid, 0);
8306
8791
  return false;
8307
8792
  } catch {
8308
- fs24.rmSync(lockDir, { recursive: true, force: true });
8793
+ fs26.rmSync(lockDir, { recursive: true, force: true });
8309
8794
  return acquireFileLock(root);
8310
8795
  }
8311
8796
  }
8312
8797
  } catch {
8313
8798
  }
8314
8799
  try {
8315
- fs24.rmSync(lockDir, { recursive: true, force: true });
8800
+ fs26.rmSync(lockDir, { recursive: true, force: true });
8316
8801
  return acquireFileLock(root);
8317
8802
  } catch {
8318
8803
  return false;
@@ -8321,7 +8806,7 @@ function acquireFileLock(root) {
8321
8806
  }
8322
8807
  function releaseFileLock(root) {
8323
8808
  try {
8324
- fs24.rmSync(getLockDir(root), { recursive: true, force: true });
8809
+ fs26.rmSync(getLockDir(root), { recursive: true, force: true });
8325
8810
  } catch {
8326
8811
  }
8327
8812
  }
@@ -8344,36 +8829,36 @@ function getRunningVerificationPid() {
8344
8829
  return _currentProcess?.pid ?? null;
8345
8830
  }
8346
8831
  function detectTestRunner(root = process.cwd()) {
8347
- const pkgPath = path24.join(root, "package.json");
8348
- if (fs24.existsSync(pkgPath)) {
8832
+ const pkgPath = path27.join(root, "package.json");
8833
+ if (fs26.existsSync(pkgPath)) {
8349
8834
  try {
8350
- const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
8835
+ const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
8351
8836
  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" };
8837
+ if (fs26.existsSync(path27.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
8838
+ if (fs26.existsSync(path27.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
8354
8839
  return { runner: "npm", baseCommand: "npm test" };
8355
8840
  }
8356
8841
  } catch {
8357
8842
  }
8358
8843
  }
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"))) {
8844
+ if (fs26.existsSync(path27.join(root, "pytest.ini")) || fs26.existsSync(path27.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
8845
+ if (fs26.existsSync(path27.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
8846
+ if (fs26.existsSync(path27.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
8847
+ if (fs26.existsSync(path27.join(root, "Makefile"))) {
8363
8848
  try {
8364
- const makefileContent = fs24.readFileSync(path24.join(root, "Makefile"), "utf-8");
8849
+ const makefileContent = fs26.readFileSync(path27.join(root, "Makefile"), "utf-8");
8365
8850
  const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
8366
8851
  if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
8367
8852
  } catch {
8368
8853
  }
8369
8854
  }
8370
- if (fs24.existsSync(path24.join(root, "package.json"))) {
8855
+ if (fs26.existsSync(path27.join(root, "package.json"))) {
8371
8856
  try {
8372
- const pkg = JSON.parse(fs24.readFileSync(path24.join(root, "package.json"), "utf-8"));
8857
+ const pkg = JSON.parse(fs26.readFileSync(path27.join(root, "package.json"), "utf-8"));
8373
8858
  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)` };
8859
+ if (fs26.existsSync(path27.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
8860
+ const srcDir = path27.join(root, "src");
8861
+ 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
8862
  }
8378
8863
  } catch {
8379
8864
  }
@@ -8492,7 +8977,7 @@ async function runAutoVerification(options = {}) {
8492
8977
  const heartbeatTimer = setInterval(() => {
8493
8978
  if (getLockDir(root)) {
8494
8979
  try {
8495
- fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8980
+ fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
8496
8981
  } catch {
8497
8982
  }
8498
8983
  }
@@ -8512,8 +8997,24 @@ async function runAutoVerification(options = {}) {
8512
8997
  if (passed && fakePassPatterns.some((p) => p.test(rawOutput))) {
8513
8998
  passed = false;
8514
8999
  }
9000
+ const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
8515
9001
  const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
8516
9002
  await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
9003
+ let autoGotchaMessage = null;
9004
+ if (scope !== "session-impact" && !isNoTests) {
9005
+ try {
9006
+ const { trackVerificationResult: trackVerificationResult2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
9007
+ const autoResult = await trackVerificationResult2(scope, passed);
9008
+ if (autoResult.message) autoGotchaMessage = autoResult.message;
9009
+ } catch {
9010
+ }
9011
+ } else if (passed) {
9012
+ try {
9013
+ const { resetScopeFailures: resetScopeFailures2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
9014
+ resetScopeFailures2(scope);
9015
+ } catch {
9016
+ }
9017
+ }
8517
9018
  _localRunning = false;
8518
9019
  _currentProcess = null;
8519
9020
  releaseFileLock(root);
@@ -8530,7 +9031,6 @@ async function runAutoVerification(options = {}) {
8530
9031
  ""
8531
9032
  ];
8532
9033
  if (!passed) {
8533
- const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
8534
9034
  if (isNoTests) {
8535
9035
  lines[0] = `\u26AA **Verification: NO TESTS**`;
8536
9036
  lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
@@ -8545,6 +9045,9 @@ async function runAutoVerification(options = {}) {
8545
9045
  lines.push("\u{1F389} All scoped tests passed!");
8546
9046
  if (rawOutput) lines.push("```text", rawOutput.substring(0, 800), "```");
8547
9047
  }
9048
+ if (autoGotchaMessage) {
9049
+ lines.push("", autoGotchaMessage);
9050
+ }
8548
9051
  resolve(lines.join("\n"));
8549
9052
  });
8550
9053
  _currentProcess = child;
@@ -8586,8 +9089,8 @@ __export(init_exports, {
8586
9089
  generateInitMdContent: () => generateInitMdContent,
8587
9090
  runInit: () => runInit
8588
9091
  });
8589
- import fs25 from "fs";
8590
- import path25 from "path";
9092
+ import fs27 from "fs";
9093
+ import path28 from "path";
8591
9094
  function configFilePath(type) {
8592
9095
  switch (type) {
8593
9096
  case "claude":
@@ -8997,10 +9500,10 @@ function generateInitMdContent() {
8997
9500
  ].join("\n");
8998
9501
  }
8999
9502
  function handleOpencodeSecondary(root, results) {
9000
- const skillPath = path25.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
9503
+ const skillPath = path28.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
9001
9504
  if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
9002
9505
  try {
9003
- const dir = path25.dirname(skillPath);
9506
+ const dir = path28.dirname(skillPath);
9004
9507
  const content = [
9005
9508
  "---",
9006
9509
  "name: kuma-mcp",
@@ -9024,18 +9527,18 @@ function handleOpencodeSecondary(root, results) {
9024
9527
  "",
9025
9528
  "\u{1F4D6} Full rules: `.kuma/init.md`"
9026
9529
  ].join("\n");
9027
- if (fs25.existsSync(skillPath)) {
9028
- const existing = fs25.readFileSync(skillPath, "utf-8");
9530
+ if (fs27.existsSync(skillPath)) {
9531
+ const existing = fs27.readFileSync(skillPath, "utf-8");
9029
9532
  if (existing.includes("kuma-mcp")) {
9030
9533
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
9031
9534
  return;
9032
9535
  }
9033
9536
  const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
9034
- fs25.writeFileSync(skillPath, newContent, "utf-8");
9537
+ fs27.writeFileSync(skillPath, newContent, "utf-8");
9035
9538
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
9036
9539
  } else {
9037
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9038
- fs25.writeFileSync(skillPath, content, "utf-8");
9540
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9541
+ fs27.writeFileSync(skillPath, content, "utf-8");
9039
9542
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
9040
9543
  }
9041
9544
  } catch (err) {
@@ -9048,21 +9551,21 @@ function handleOpencodeSecondary(root, results) {
9048
9551
  }
9049
9552
  }
9050
9553
  function handleCodexSecondary(root, results) {
9051
- const tomlPath = path25.resolve(root, ".codex/config.toml");
9554
+ const tomlPath = path28.resolve(root, ".codex/config.toml");
9052
9555
  if (results.some((r) => r.filePath === ".codex/config.toml")) return;
9053
9556
  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");
9557
+ const dir = path28.dirname(tomlPath);
9558
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9559
+ if (fs27.existsSync(tomlPath)) {
9560
+ const existingContent = fs27.readFileSync(tomlPath, "utf-8");
9058
9561
  if (existingContent.includes("kuma")) {
9059
9562
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
9060
9563
  return;
9061
9564
  }
9062
- fs25.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
9565
+ fs27.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
9063
9566
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
9064
9567
  } else {
9065
- fs25.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
9568
+ fs27.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
9066
9569
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
9067
9570
  }
9068
9571
  } catch (err) {
@@ -9075,11 +9578,11 @@ function handleCodexSecondary(root, results) {
9075
9578
  }
9076
9579
  }
9077
9580
  function handleQwenSecondary(root, results) {
9078
- const settingsPath = path25.resolve(root, "settings.json");
9581
+ const settingsPath = path28.resolve(root, "settings.json");
9079
9582
  if (results.some((r) => r.filePath === "settings.json")) return;
9080
9583
  try {
9081
- if (fs25.existsSync(settingsPath)) {
9082
- const existingContent = fs25.readFileSync(settingsPath, "utf-8");
9584
+ if (fs27.existsSync(settingsPath)) {
9585
+ const existingContent = fs27.readFileSync(settingsPath, "utf-8");
9083
9586
  if (existingContent.includes("kuma")) {
9084
9587
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
9085
9588
  try {
@@ -9087,7 +9590,7 @@ function handleQwenSecondary(root, results) {
9087
9590
  parsed.mcpServers = parsed.mcpServers || {};
9088
9591
  if (!parsed.mcpServers.kuma) {
9089
9592
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9090
- fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9593
+ fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9091
9594
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
9092
9595
  return;
9093
9596
  }
@@ -9102,13 +9605,13 @@ function handleQwenSecondary(root, results) {
9102
9605
  parsed.mcpServers = parsed.mcpServers || {};
9103
9606
  if (!parsed.mcpServers.kuma) {
9104
9607
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9105
- fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9608
+ fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9106
9609
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
9107
9610
  }
9108
9611
  } catch {
9109
9612
  }
9110
9613
  } else {
9111
- fs25.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
9614
+ fs27.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
9112
9615
  results.push({ type: "qwen", filePath: "settings.json", action: "created" });
9113
9616
  }
9114
9617
  } catch (err) {
@@ -9206,18 +9709,18 @@ function getCombinedAgentsMd(selectedTypes) {
9206
9709
  return lines.join("\n");
9207
9710
  }
9208
9711
  function handleAntigravityMcpConfig(root, results) {
9209
- const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
9712
+ const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
9210
9713
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
9211
9714
  try {
9212
- const mcpDir = path25.dirname(mcpPath);
9213
- if (fs25.existsSync(mcpPath)) {
9214
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
9715
+ const mcpDir = path28.dirname(mcpPath);
9716
+ if (fs27.existsSync(mcpPath)) {
9717
+ const existingContent = fs27.readFileSync(mcpPath, "utf-8");
9215
9718
  if (existingContent.includes("kuma")) {
9216
9719
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
9217
9720
  const trimmed = existingContent.trimEnd();
9218
9721
  if (trimmed.endsWith("}")) {
9219
9722
  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");
9723
+ fs27.writeFileSync(mcpPath, updated, "utf-8");
9221
9724
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
9222
9725
  }
9223
9726
  }
@@ -9226,11 +9729,11 @@ function handleAntigravityMcpConfig(root, results) {
9226
9729
  const parsed = JSON.parse(existingContent);
9227
9730
  parsed.mcpServers = parsed.mcpServers || {};
9228
9731
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9229
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9732
+ fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9230
9733
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
9231
9734
  } else {
9232
- if (!fs25.existsSync(mcpDir)) fs25.mkdirSync(mcpDir, { recursive: true });
9233
- fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9735
+ if (!fs27.existsSync(mcpDir)) fs27.mkdirSync(mcpDir, { recursive: true });
9736
+ fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9234
9737
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
9235
9738
  }
9236
9739
  } catch (err) {
@@ -9243,18 +9746,18 @@ function handleAntigravityMcpConfig(root, results) {
9243
9746
  }
9244
9747
  }
9245
9748
  function handleAiderSecondary(root, results) {
9246
- const ymlPath = path25.resolve(root, ".aider.conf.yml");
9749
+ const ymlPath = path28.resolve(root, ".aider.conf.yml");
9247
9750
  if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
9248
9751
  try {
9249
9752
  const conventionsRef = "read: CONVENTIONS.md";
9250
- if (fs25.existsSync(ymlPath)) {
9251
- const existingContent = fs25.readFileSync(ymlPath, "utf-8");
9753
+ if (fs27.existsSync(ymlPath)) {
9754
+ const existingContent = fs27.readFileSync(ymlPath, "utf-8");
9252
9755
  if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
9253
9756
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
9254
9757
  return;
9255
9758
  }
9256
9759
  const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
9257
- fs25.writeFileSync(ymlPath, newContent, "utf-8");
9760
+ fs27.writeFileSync(ymlPath, newContent, "utf-8");
9258
9761
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
9259
9762
  } else {
9260
9763
  const content = [
@@ -9264,7 +9767,7 @@ function handleAiderSecondary(root, results) {
9264
9767
  conventionsRef,
9265
9768
  ""
9266
9769
  ].join("\n");
9267
- fs25.writeFileSync(ymlPath, content, "utf-8");
9770
+ fs27.writeFileSync(ymlPath, content, "utf-8");
9268
9771
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
9269
9772
  }
9270
9773
  } catch (err) {
@@ -9277,10 +9780,10 @@ function handleAiderSecondary(root, results) {
9277
9780
  }
9278
9781
  }
9279
9782
  function handleCopilotSecondary(root, results) {
9280
- const skillPath = path25.resolve(root, ".github/skills/kuma/SKILL.md");
9783
+ const skillPath = path28.resolve(root, ".github/skills/kuma/SKILL.md");
9281
9784
  if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
9282
9785
  try {
9283
- const dir = path25.dirname(skillPath);
9786
+ const dir = path28.dirname(skillPath);
9284
9787
  const content = [
9285
9788
  "---",
9286
9789
  "name: kuma-mcp",
@@ -9291,17 +9794,17 @@ function handleCopilotSecondary(root, results) {
9291
9794
  "",
9292
9795
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules."
9293
9796
  ].join("\n");
9294
- if (fs25.existsSync(skillPath)) {
9295
- const existingContent = fs25.readFileSync(skillPath, "utf-8");
9797
+ if (fs27.existsSync(skillPath)) {
9798
+ const existingContent = fs27.readFileSync(skillPath, "utf-8");
9296
9799
  if (existingContent.includes("kuma")) {
9297
9800
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
9298
9801
  return;
9299
9802
  }
9300
- fs25.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
9803
+ fs27.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
9301
9804
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
9302
9805
  } else {
9303
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9304
- fs25.writeFileSync(skillPath, content, "utf-8");
9806
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9807
+ fs27.writeFileSync(skillPath, content, "utf-8");
9305
9808
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
9306
9809
  }
9307
9810
  } catch (err) {
@@ -9314,21 +9817,21 @@ function handleCopilotSecondary(root, results) {
9314
9817
  }
9315
9818
  }
9316
9819
  function handleOpenclawSecondary(root, results) {
9317
- const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
9820
+ const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
9318
9821
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
9319
9822
  try {
9320
- const dir = path25.dirname(mcpPath);
9321
- if (fs25.existsSync(mcpPath)) {
9322
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
9823
+ const dir = path28.dirname(mcpPath);
9824
+ if (fs27.existsSync(mcpPath)) {
9825
+ const existingContent = fs27.readFileSync(mcpPath, "utf-8");
9323
9826
  if (existingContent.includes("kuma")) return;
9324
9827
  const parsed = JSON.parse(existingContent);
9325
9828
  parsed.mcpServers = parsed.mcpServers || {};
9326
9829
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9327
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9830
+ fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9328
9831
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
9329
9832
  } else {
9330
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9331
- fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9833
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9834
+ fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
9332
9835
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
9333
9836
  }
9334
9837
  } catch (err) {
@@ -9341,7 +9844,7 @@ function handleOpenclawSecondary(root, results) {
9341
9844
  }
9342
9845
  }
9343
9846
  function handleQuickrefGeneration(root, results) {
9344
- const quickrefPath = path25.resolve(root, ".kuma/quickref.md");
9847
+ const quickrefPath = path28.resolve(root, ".kuma/quickref.md");
9345
9848
  const content = [
9346
9849
  "# Kuma Quick Reference",
9347
9850
  "",
@@ -9366,16 +9869,16 @@ function handleQuickrefGeneration(root, results) {
9366
9869
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9367
9870
  ].join("\n");
9368
9871
  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");
9872
+ const kumaDir = path28.dirname(quickrefPath);
9873
+ if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
9874
+ if (fs27.existsSync(quickrefPath)) {
9875
+ const existing = fs27.readFileSync(quickrefPath, "utf-8");
9373
9876
  if (existing.includes("_Generated by Kuma MCP_")) {
9374
9877
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
9375
9878
  return;
9376
9879
  }
9377
9880
  }
9378
- fs25.writeFileSync(quickrefPath, content, "utf-8");
9881
+ fs27.writeFileSync(quickrefPath, content, "utf-8");
9379
9882
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
9380
9883
  } catch (err) {
9381
9884
  results.push({
@@ -9387,7 +9890,7 @@ function handleQuickrefGeneration(root, results) {
9387
9890
  }
9388
9891
  }
9389
9892
  function handleModeMdGeneration(root, results) {
9390
- const filePath = path25.resolve(root, ".kuma/MODE.md");
9893
+ const filePath = path28.resolve(root, ".kuma/MODE.md");
9391
9894
  const content = [
9392
9895
  "# Adaptive Mode Selector",
9393
9896
  "",
@@ -9430,10 +9933,10 @@ function handleModeMdGeneration(root, results) {
9430
9933
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9431
9934
  ].join("\n");
9432
9935
  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");
9936
+ const dir = path28.dirname(filePath);
9937
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9938
+ if (!fs27.existsSync(filePath)) {
9939
+ fs27.writeFileSync(filePath, content, "utf-8");
9437
9940
  results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
9438
9941
  }
9439
9942
  } catch (err) {
@@ -9441,7 +9944,7 @@ function handleModeMdGeneration(root, results) {
9441
9944
  }
9442
9945
  }
9443
9946
  function handleSkipRulesMdGeneration(root, results) {
9444
- const filePath = path25.resolve(root, ".kuma/SKIP_RULES.md");
9947
+ const filePath = path28.resolve(root, ".kuma/SKIP_RULES.md");
9445
9948
  const content = [
9446
9949
  "# Skip Rules \u2014 What NOT to Record",
9447
9950
  "",
@@ -9478,10 +9981,10 @@ function handleSkipRulesMdGeneration(root, results) {
9478
9981
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9479
9982
  ].join("\n");
9480
9983
  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");
9984
+ const dir = path28.dirname(filePath);
9985
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9986
+ if (!fs27.existsSync(filePath)) {
9987
+ fs27.writeFileSync(filePath, content, "utf-8");
9485
9988
  results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
9486
9989
  }
9487
9990
  } catch (err) {
@@ -9489,7 +9992,7 @@ function handleSkipRulesMdGeneration(root, results) {
9489
9992
  }
9490
9993
  }
9491
9994
  function handleStalenessMdGeneration(root, results) {
9492
- const filePath = path25.resolve(root, ".kuma/STALENESS.md");
9995
+ const filePath = path28.resolve(root, ".kuma/STALENESS.md");
9493
9996
  const content = [
9494
9997
  "# Auto-Staleness Detection",
9495
9998
  "",
@@ -9520,10 +10023,10 @@ function handleStalenessMdGeneration(root, results) {
9520
10023
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
9521
10024
  ].join("\n");
9522
10025
  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");
10026
+ const dir = path28.dirname(filePath);
10027
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
10028
+ if (!fs27.existsSync(filePath)) {
10029
+ fs27.writeFileSync(filePath, content, "utf-8");
9527
10030
  results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
9528
10031
  }
9529
10032
  } catch (err) {
@@ -9531,20 +10034,20 @@ function handleStalenessMdGeneration(root, results) {
9531
10034
  }
9532
10035
  }
9533
10036
  function handleInitMdGeneration(root, results) {
9534
- const initMdPath = path25.resolve(root, ".kuma/init.md");
10037
+ const initMdPath = path28.resolve(root, ".kuma/init.md");
9535
10038
  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");
10039
+ const kumaDir = path28.dirname(initMdPath);
10040
+ if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
10041
+ if (fs27.existsSync(initMdPath)) {
10042
+ const existing = fs27.readFileSync(initMdPath, "utf-8");
9540
10043
  if (existing.includes("_Generated by Kuma MCP_")) {
9541
10044
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
9542
10045
  return;
9543
10046
  }
9544
- fs25.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
10047
+ fs27.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
9545
10048
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
9546
10049
  } else {
9547
- fs25.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
10050
+ fs27.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
9548
10051
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
9549
10052
  }
9550
10053
  } catch (err) {
@@ -9561,20 +10064,20 @@ function handleInitMdGeneration(root, results) {
9561
10064
  handleStalenessMdGeneration(root, results);
9562
10065
  }
9563
10066
  function handleCodewhaleSecondary(root, results) {
9564
- const mcpPath = path25.resolve(root, ".codewhale/mcp.json");
10067
+ const mcpPath = path28.resolve(root, ".codewhale/mcp.json");
9565
10068
  if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
9566
10069
  try {
9567
- const dir = path25.dirname(mcpPath);
9568
- if (fs25.existsSync(mcpPath)) {
9569
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
10070
+ const dir = path28.dirname(mcpPath);
10071
+ if (fs27.existsSync(mcpPath)) {
10072
+ const existingContent = fs27.readFileSync(mcpPath, "utf-8");
9570
10073
  if (existingContent.includes("kuma")) return;
9571
10074
  const parsed = JSON.parse(existingContent);
9572
10075
  parsed.mcpServers = parsed.mcpServers || {};
9573
10076
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9574
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
10077
+ fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9575
10078
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
9576
10079
  } else {
9577
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
10080
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
9578
10081
  const config = {
9579
10082
  mcpServers: {
9580
10083
  kuma: {
@@ -9584,7 +10087,7 @@ function handleCodewhaleSecondary(root, results) {
9584
10087
  }
9585
10088
  }
9586
10089
  };
9587
- fs25.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
10090
+ fs27.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9588
10091
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
9589
10092
  }
9590
10093
  } catch (err) {
@@ -9606,28 +10109,28 @@ function runInit(options) {
9606
10109
  let agentsMdHandled = false;
9607
10110
  for (const type of selected) {
9608
10111
  const relativePath = configFilePath(type);
9609
- const fullPath = path25.resolve(root, relativePath);
10112
+ const fullPath = path28.resolve(root, relativePath);
9610
10113
  const getTemplate = TEMPLATES[type];
9611
10114
  try {
9612
10115
  if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
9613
10116
  agentsMdHandled = true;
9614
10117
  const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
9615
- if (fs25.existsSync(fullPath)) {
10118
+ if (fs27.existsSync(fullPath)) {
9616
10119
  if (options.skipExisting) {
9617
10120
  results.push({ type, filePath: relativePath, action: "skipped" });
9618
10121
  } else {
9619
- const existingContent = fs25.readFileSync(fullPath, "utf-8");
10122
+ const existingContent = fs27.readFileSync(fullPath, "utf-8");
9620
10123
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9621
10124
  results.push({ type, filePath: relativePath, action: "skipped" });
9622
10125
  } else {
9623
- fs25.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
10126
+ fs27.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
9624
10127
  results.push({ type, filePath: relativePath, action: "appended" });
9625
10128
  }
9626
10129
  }
9627
10130
  } else {
9628
- const dir = path25.dirname(fullPath);
9629
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9630
- fs25.writeFileSync(fullPath, combinedContent, "utf-8");
10131
+ const dir = path28.dirname(fullPath);
10132
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
10133
+ fs27.writeFileSync(fullPath, combinedContent, "utf-8");
9631
10134
  results.push({ type, filePath: relativePath, action: "created" });
9632
10135
  }
9633
10136
  if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
@@ -9639,12 +10142,12 @@ function runInit(options) {
9639
10142
  continue;
9640
10143
  } else {
9641
10144
  const template = getTemplate();
9642
- if (fs25.existsSync(fullPath)) {
10145
+ if (fs27.existsSync(fullPath)) {
9643
10146
  if (options.skipExisting) {
9644
10147
  results.push({ type, filePath: relativePath, action: "skipped" });
9645
10148
  continue;
9646
10149
  }
9647
- const existingContent = fs25.readFileSync(fullPath, "utf-8");
10150
+ const existingContent = fs27.readFileSync(fullPath, "utf-8");
9648
10151
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9649
10152
  if (type === "antigravity") {
9650
10153
  handleAntigravityMcpConfig(root, results);
@@ -9657,14 +10160,14 @@ function runInit(options) {
9657
10160
  continue;
9658
10161
  }
9659
10162
  const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
9660
- fs25.writeFileSync(fullPath, newContent, "utf-8");
10163
+ fs27.writeFileSync(fullPath, newContent, "utf-8");
9661
10164
  results.push({ type, filePath: relativePath, action: "appended" });
9662
10165
  } else {
9663
- const dir = path25.dirname(fullPath);
9664
- if (!fs25.existsSync(dir)) {
9665
- fs25.mkdirSync(dir, { recursive: true });
10166
+ const dir = path28.dirname(fullPath);
10167
+ if (!fs27.existsSync(dir)) {
10168
+ fs27.mkdirSync(dir, { recursive: true });
9666
10169
  }
9667
- fs25.writeFileSync(fullPath, template, "utf-8");
10170
+ fs27.writeFileSync(fullPath, template, "utf-8");
9668
10171
  results.push({ type, filePath: relativePath, action: "created" });
9669
10172
  }
9670
10173
  if (type === "antigravity") {
@@ -9806,6 +10309,209 @@ var init_init = __esm({
9806
10309
  }
9807
10310
  });
9808
10311
 
10312
+ // src/engine/kumaLegacyOnboard.ts
10313
+ var kumaLegacyOnboard_exports = {};
10314
+ __export(kumaLegacyOnboard_exports, {
10315
+ runLegacyOnboarding: () => runLegacyOnboarding
10316
+ });
10317
+ import fs28 from "fs";
10318
+ import path29 from "path";
10319
+ async function harvestGit(commitCount) {
10320
+ try {
10321
+ const { harvestGitHistory: harvestGitHistory2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
10322
+ return await harvestGitHistory2({ commitCount });
10323
+ } catch (err) {
10324
+ return `\u26A0\uFE0F Git harvest skipped: ${err}`;
10325
+ }
10326
+ }
10327
+ async function mineInlineMarkers(root, maxFiles) {
10328
+ const markers = [];
10329
+ try {
10330
+ const { default: fg } = await import("fast-glob");
10331
+ const files = await fg(
10332
+ ["**/*.{ts,tsx,js,jsx,py,go,rs,java,kt,cs,rb,php,c,cpp,h,hpp}"],
10333
+ {
10334
+ cwd: root,
10335
+ ignore: ["node_modules/**", "dist/**", "build/**", ".git/**", ".kuma/**", "coverage/**", "vendor/**"],
10336
+ onlyFiles: true,
10337
+ deep: 8
10338
+ }
10339
+ );
10340
+ for (const file of files.slice(0, maxFiles)) {
10341
+ try {
10342
+ const fullPath = path29.join(root, file);
10343
+ const content = fs28.readFileSync(fullPath, "utf-8");
10344
+ const lines = content.split("\n");
10345
+ for (let i = 0; i < lines.length && markers.length < 100; i++) {
10346
+ const line = lines[i];
10347
+ for (const { tag, regex } of MARKER_PATTERNS) {
10348
+ const m = regex.exec(line);
10349
+ if (m) {
10350
+ const text = (m[1] || m[2] || m[3] || line.trim()).trim().substring(0, 160);
10351
+ markers.push({ filePath: file, line: i + 1, tag, text });
10352
+ break;
10353
+ }
10354
+ }
10355
+ }
10356
+ } catch {
10357
+ }
10358
+ }
10359
+ } catch {
10360
+ }
10361
+ return markers;
10362
+ }
10363
+ async function buildFeatureNodes(root) {
10364
+ const names = [];
10365
+ try {
10366
+ const { recordFeature: recordFeature2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10367
+ const pkgPath = path29.join(root, "package.json");
10368
+ if (fs28.existsSync(pkgPath)) {
10369
+ try {
10370
+ const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
10371
+ const candidates = [
10372
+ pkg.main,
10373
+ pkg.module,
10374
+ pkg.bin ? Object.keys(pkg.bin)[0] : null,
10375
+ ...Array.isArray(pkg.exports) ? pkg.exports : []
10376
+ ].filter(Boolean);
10377
+ for (const c of candidates.slice(0, 5)) {
10378
+ const name = path29.basename(c, path29.extname(c)).replace(/[^a-zA-Z0-9]/g, " ").trim();
10379
+ if (name && !names.includes(name)) {
10380
+ await recordFeature2({ name, description: `Detected entry point during legacy onboarding (${c})`, files: [c] });
10381
+ names.push(name);
10382
+ }
10383
+ }
10384
+ } catch {
10385
+ }
10386
+ }
10387
+ const srcDir = path29.join(root, "src");
10388
+ if (fs28.existsSync(srcDir)) {
10389
+ try {
10390
+ 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));
10391
+ for (const dir of entries.slice(0, 15)) {
10392
+ if (!names.includes(dir)) {
10393
+ const files = [path29.join("src", dir)];
10394
+ await recordFeature2({
10395
+ name: dir,
10396
+ description: `Detected top-level module during legacy onboarding`,
10397
+ files,
10398
+ risk: "medium"
10399
+ });
10400
+ names.push(dir);
10401
+ }
10402
+ }
10403
+ } catch {
10404
+ }
10405
+ }
10406
+ } catch {
10407
+ }
10408
+ return { created: names.length, names };
10409
+ }
10410
+ function buildArchitectureDigest(root, featureNames) {
10411
+ const lines = [];
10412
+ try {
10413
+ const pkgPath = path29.join(root, "package.json");
10414
+ if (fs28.existsSync(pkgPath)) {
10415
+ const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
10416
+ lines.push(`\u{1F3D7}\uFE0F **Stack**: ${[pkg.framework, pkg.orm, pkg.testFramework].filter(Boolean).join(" + ") || "detected"}`);
10417
+ }
10418
+ if (featureNames.length > 0) {
10419
+ lines.push(`\u{1F9E9} **Detected Modules**: ${featureNames.slice(0, 8).join(", ")}`);
10420
+ }
10421
+ lines.push(`\u{1F4C4} **Entry Points**: check package.json main/bin or src/index.*`);
10422
+ } catch {
10423
+ }
10424
+ return lines.join("\n");
10425
+ }
10426
+ async function runLegacyOnboarding(options = {}) {
10427
+ const root = getProjectRoot();
10428
+ const commitCount = options.commitCount ?? 25;
10429
+ const maxFiles = options.maxFiles ?? 200;
10430
+ const lines = [
10431
+ "\u{1F680} **Kuma Legacy Onboarding**",
10432
+ "\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",
10433
+ "",
10434
+ `\u{1F4C1} Project: ${root.split("/").pop() || "unknown"}`,
10435
+ "",
10436
+ "\u23F3 Running bulk analysis \u2014 this may take a few seconds...",
10437
+ ""
10438
+ ];
10439
+ lines.push("### 1\uFE0F\u20E3 Git History Harvest");
10440
+ try {
10441
+ const harvest = await harvestGit(commitCount);
10442
+ lines.push(harvest);
10443
+ } catch (err) {
10444
+ lines.push(`\u26A0\uFE0F ${err}`);
10445
+ }
10446
+ lines.push("");
10447
+ lines.push("### 2\uFE0F\u20E3 Inline Marker Mining (HACK/FIXME/TODO \u2192 gotchas)");
10448
+ try {
10449
+ const markers = await mineInlineMarkers(root, maxFiles);
10450
+ if (markers.length === 0) {
10451
+ lines.push("\u2705 No HACK/FIXME/TODO markers found \u2014 clean codebase.");
10452
+ } else {
10453
+ const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
10454
+ let recorded = 0;
10455
+ for (const m of markers.slice(0, 40)) {
10456
+ try {
10457
+ const severity = m.tag === "HACK" || m.tag === "WARNING" ? "high" : m.tag === "FIXME" ? "medium" : "low";
10458
+ await addGotcha2({
10459
+ filePath: m.filePath,
10460
+ description: `[${m.tag}] ${m.text} (line ${m.line})`,
10461
+ severity,
10462
+ workaround: `Legacy marker ${m.tag} at ${m.filePath}:${m.line} \u2014 review before modifying this file.`
10463
+ });
10464
+ recorded++;
10465
+ } catch {
10466
+ }
10467
+ }
10468
+ lines.push(`\u26A0\uFE0F Found ${markers.length} marker(s); recorded ${recorded} as gotchas (capped at 40).`);
10469
+ lines.push(`\u{1F4A1} Remaining ${Math.max(0, markers.length - 40)} \u2014 re-run or use kuma_memory({ action: 'gotcha' }) inline.`);
10470
+ }
10471
+ } catch (err) {
10472
+ lines.push(`\u26A0\uFE0F Marker mining failed: ${err}`);
10473
+ }
10474
+ lines.push("");
10475
+ lines.push("### 3\uFE0F\u20E3 Feature Graph Bootstrap");
10476
+ let featureNames = [];
10477
+ try {
10478
+ const feat = await buildFeatureNodes(root);
10479
+ featureNames = feat.names;
10480
+ 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' }).");
10481
+ } catch (err) {
10482
+ lines.push(`\u26A0\uFE0F Feature bootstrap failed: ${err}`);
10483
+ }
10484
+ lines.push("");
10485
+ lines.push("### 4\uFE0F\u20E3 Architecture Digest");
10486
+ try {
10487
+ const digest = buildArchitectureDigest(root, featureNames);
10488
+ lines.push(digest);
10489
+ } catch {
10490
+ }
10491
+ lines.push("");
10492
+ 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");
10493
+ lines.push("\u2705 **Onboarding complete.** Suggested next steps:");
10494
+ lines.push(" \u2022 `kuma_context({ action: 'digest' })` \u2014 compact briefing");
10495
+ lines.push(" \u2022 `kuma_context({ action: 'research', scope: '<area>' })` \u2014 deep-dive first area");
10496
+ lines.push(" \u2022 `kuma_memory({ action: 'arch_flow', content: 'domain: X | hops: a \u2192 b \u2192 c' })` \u2014 map core flows");
10497
+ lines.push(" \u2022 `kuma_safety({ action: 'heal' })` \u2014 repair any stale graph entries");
10498
+ return lines.join("\n");
10499
+ }
10500
+ var MARKER_PATTERNS;
10501
+ var init_kumaLegacyOnboard = __esm({
10502
+ "src/engine/kumaLegacyOnboard.ts"() {
10503
+ "use strict";
10504
+ init_pathValidator();
10505
+ MARKER_PATTERNS = [
10506
+ { tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)|<!--\s*HACK:?\s*(.*)-->/i },
10507
+ { tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)|<!--\s*FIXME:?\s*(.*)-->/i },
10508
+ { tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)|<!--\s*TODO:?\s*(.*)-->/i },
10509
+ { tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)|<!--\s*WARNING:?\s*(.*)-->/i },
10510
+ { tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)|<!--\s*XXX:?\s*(.*)-->/i }
10511
+ ];
10512
+ }
10513
+ });
10514
+
9809
10515
  // src/utils/agentDetector.ts
9810
10516
  var agentDetector_exports = {};
9811
10517
  __export(agentDetector_exports, {
@@ -9814,19 +10520,19 @@ __export(agentDetector_exports, {
9814
10520
  getSkillPath: () => getSkillPath,
9815
10521
  skillExists: () => skillExists
9816
10522
  });
9817
- import fs26 from "fs";
9818
- import path26 from "path";
10523
+ import fs29 from "fs";
10524
+ import path30 from "path";
9819
10525
  function checkFile(root, filePath) {
9820
10526
  try {
9821
- return fs26.existsSync(path26.join(root, filePath));
10527
+ return fs29.existsSync(path30.join(root, filePath));
9822
10528
  } catch {
9823
10529
  return false;
9824
10530
  }
9825
10531
  }
9826
10532
  function checkDir(root, dirPath) {
9827
10533
  try {
9828
- const fullPath = path26.join(root, dirPath);
9829
- return fs26.existsSync(fullPath) && fs26.statSync(fullPath).isDirectory();
10534
+ const fullPath = path30.join(root, dirPath);
10535
+ return fs29.existsSync(fullPath) && fs29.statSync(fullPath).isDirectory();
9830
10536
  } catch {
9831
10537
  return false;
9832
10538
  }
@@ -9894,7 +10600,7 @@ function getAgentLabel(type) {
9894
10600
  function skillExists(type, projectRoot) {
9895
10601
  const root = projectRoot ?? getProjectRoot();
9896
10602
  const skillPath = getSkillPath(type);
9897
- return fs26.existsSync(path26.join(root, skillPath));
10603
+ return fs29.existsSync(path30.join(root, skillPath));
9898
10604
  }
9899
10605
  var AGENT_DETECTORS;
9900
10606
  var init_agentDetector = __esm({
@@ -10338,8 +11044,8 @@ init_kumaDb();
10338
11044
  init_kumaGraph();
10339
11045
  init_kumaMemory();
10340
11046
  init_pathValidator();
10341
- import fs11 from "fs";
10342
- import path11 from "path";
11047
+ import fs12 from "fs";
11048
+ import path13 from "path";
10343
11049
  import crypto4 from "crypto";
10344
11050
  var CONTEXT_ALIASES = {
10345
11051
  // Research synonyms
@@ -10513,6 +11219,15 @@ async function handleInit(_params) {
10513
11219
  lines.push(formatSafetyScore2(score));
10514
11220
  } catch {
10515
11221
  }
11222
+ try {
11223
+ const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
11224
+ const rulesBlock = formatPathRules2(_params.goal);
11225
+ if (rulesBlock) {
11226
+ lines.push("");
11227
+ lines.push(rulesBlock);
11228
+ }
11229
+ } catch {
11230
+ }
10516
11231
  lines.push("", "\u{1F4A1} Call kuma_context({ action: 'research', scope: '<area>' }) to research a specific area.");
10517
11232
  return lines.join("\n");
10518
11233
  }
@@ -10635,6 +11350,15 @@ async function handleResearch(params) {
10635
11350
  lines.push(" \u26A0\uFE0F Memory lookup failed");
10636
11351
  }
10637
11352
  lines.push("");
11353
+ try {
11354
+ const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
11355
+ const rulesBlock = formatPathRules2(scope);
11356
+ if (rulesBlock) {
11357
+ lines.push(rulesBlock);
11358
+ lines.push("");
11359
+ }
11360
+ } catch {
11361
+ }
10638
11362
  const newRecord = {
10639
11363
  scope,
10640
11364
  confidence: record?.confidence || 0.7,
@@ -10817,12 +11541,12 @@ async function handleProgressive(params) {
10817
11541
  function computeProjectHash(scope) {
10818
11542
  try {
10819
11543
  const root = getProjectRoot();
10820
- const files = fs11.readdirSync(root).slice(0, 20);
11544
+ const files = fs12.readdirSync(root).slice(0, 20);
10821
11545
  const hash = crypto4.createHash("md5");
10822
11546
  hash.update(scope);
10823
11547
  for (const f of files) {
10824
11548
  try {
10825
- const stat = fs11.statSync(path11.join(root, f));
11549
+ const stat = fs12.statSync(path13.join(root, f));
10826
11550
  hash.update(`${f}:${stat.mtimeMs}`);
10827
11551
  } catch {
10828
11552
  }
@@ -10891,8 +11615,8 @@ function getNoiseFilterPolicy() {
10891
11615
  // src/tools/kumaMemoryTool.ts
10892
11616
  init_pathValidator();
10893
11617
  init_pathValidator();
10894
- import fs15 from "fs";
10895
- import path15 from "path";
11618
+ import fs16 from "fs";
11619
+ import path17 from "path";
10896
11620
  var MEMORY_ALIASES = {
10897
11621
  // Session synonyms
10898
11622
  "session": "session",
@@ -11157,9 +11881,9 @@ async function handleResearchSave(params) {
11157
11881
  } catch {
11158
11882
  }
11159
11883
  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");
11884
+ const researchDir = path17.join(getProjectRoot(), ".kuma", "research");
11885
+ if (!fs16.existsSync(researchDir)) fs16.mkdirSync(researchDir, { recursive: true });
11886
+ fs16.writeFileSync(path17.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
11163
11887
  } catch {
11164
11888
  }
11165
11889
  sessionMemory.recordMemoryAction("research_save");
@@ -11760,72 +12484,72 @@ init_sessionMemory();
11760
12484
 
11761
12485
  // src/engine/kumaLock.ts
11762
12486
  init_pathValidator();
11763
- import fs16 from "fs";
11764
- import path16 from "path";
12487
+ import fs17 from "fs";
12488
+ import path18 from "path";
11765
12489
  var LOCKS_DIR = ".kuma/locks";
11766
12490
  function locksDir() {
11767
- return path16.join(getProjectRoot(), LOCKS_DIR);
12491
+ return path18.join(getProjectRoot(), LOCKS_DIR);
11768
12492
  }
11769
12493
  function ensureLocksDir() {
11770
12494
  const dir = locksDir();
11771
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
12495
+ if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
11772
12496
  }
11773
12497
  function lockPath(filePath) {
11774
12498
  const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
11775
- return path16.join(locksDir(), `${safeName}.lock.json`);
12499
+ return path18.join(locksDir(), `${safeName}.lock.json`);
11776
12500
  }
11777
12501
  function acquireLock(filePath, agentId) {
11778
12502
  ensureLocksDir();
11779
12503
  const lp = lockPath(filePath);
11780
12504
  const id = agentId || `agent-${process.pid}`;
11781
- if (fs16.existsSync(lp)) {
12505
+ if (fs17.existsSync(lp)) {
11782
12506
  try {
11783
- const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
12507
+ const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
11784
12508
  if (existing.agentId === id) {
11785
12509
  return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
11786
12510
  }
11787
12511
  const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
11788
12512
  if (elapsed > 300) {
11789
- fs16.unlinkSync(lp);
12513
+ fs17.unlinkSync(lp);
11790
12514
  } else {
11791
12515
  return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
11792
12516
  }
11793
12517
  } catch {
11794
- fs16.unlinkSync(lp);
12518
+ fs17.unlinkSync(lp);
11795
12519
  }
11796
12520
  }
11797
12521
  const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
11798
- fs16.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
12522
+ fs17.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
11799
12523
  return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
11800
12524
  }
11801
12525
  function releaseLock(filePath, agentId) {
11802
12526
  ensureLocksDir();
11803
12527
  const lp = lockPath(filePath);
11804
12528
  const id = agentId || `agent-${process.pid}`;
11805
- if (!fs16.existsSync(lp)) {
12529
+ if (!fs17.existsSync(lp)) {
11806
12530
  return `\u26A0\uFE0F No lock found for \`${filePath}\``;
11807
12531
  }
11808
12532
  try {
11809
- const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
12533
+ const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
11810
12534
  if (existing.agentId !== id) {
11811
12535
  return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
11812
12536
  }
11813
- fs16.unlinkSync(lp);
12537
+ fs17.unlinkSync(lp);
11814
12538
  return `\u{1F513} **Lock released** on \`${filePath}\``;
11815
12539
  } catch {
11816
- fs16.unlinkSync(lp);
12540
+ fs17.unlinkSync(lp);
11817
12541
  return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
11818
12542
  }
11819
12543
  }
11820
12544
  function listLocks() {
11821
12545
  ensureLocksDir();
11822
12546
  const dir = locksDir();
11823
- const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
12547
+ const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
11824
12548
  if (files.length === 0) return "\u{1F513} No active locks.";
11825
12549
  const lines = ["\u{1F512} **Active Locks:**", ""];
11826
12550
  for (const f of files) {
11827
12551
  try {
11828
- const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
12552
+ const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
11829
12553
  const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
11830
12554
  lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
11831
12555
  } catch {
@@ -11836,9 +12560,9 @@ function listLocks() {
11836
12560
  }
11837
12561
  function isLocked(filePath) {
11838
12562
  const lp = lockPath(filePath);
11839
- if (!fs16.existsSync(lp)) return { locked: false };
12563
+ if (!fs17.existsSync(lp)) return { locked: false };
11840
12564
  try {
11841
- const entry = JSON.parse(fs16.readFileSync(lp, "utf-8"));
12565
+ const entry = JSON.parse(fs17.readFileSync(lp, "utf-8"));
11842
12566
  return { locked: true, by: entry.agentId, since: entry.acquiredAt };
11843
12567
  } catch {
11844
12568
  return { locked: false };
@@ -11848,16 +12572,16 @@ function cleanStaleLocks() {
11848
12572
  ensureLocksDir();
11849
12573
  const dir = locksDir();
11850
12574
  let count = 0;
11851
- for (const f of fs16.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
12575
+ for (const f of fs17.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
11852
12576
  try {
11853
- const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
12577
+ const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
11854
12578
  if (Date.now() - entry.acquiredAt > 3e5) {
11855
- fs16.unlinkSync(path16.join(dir, f));
12579
+ fs17.unlinkSync(path18.join(dir, f));
11856
12580
  count++;
11857
12581
  }
11858
12582
  } catch {
11859
12583
  try {
11860
- fs16.unlinkSync(path16.join(dir, f));
12584
+ fs17.unlinkSync(path18.join(dir, f));
11861
12585
  count++;
11862
12586
  } catch {
11863
12587
  }
@@ -11873,8 +12597,8 @@ init_safetyAudit();
11873
12597
 
11874
12598
  // src/tools/kumaPolicy.ts
11875
12599
  init_pathValidator();
11876
- import fs17 from "fs";
11877
- import path17 from "path";
12600
+ import fs18 from "fs";
12601
+ import path19 from "path";
11878
12602
  var DEFAULT_POLICY = {
11879
12603
  never_touch: [
11880
12604
  ".env",
@@ -11900,15 +12624,15 @@ var DEFAULT_POLICY = {
11900
12624
  };
11901
12625
  function loadPolicy() {
11902
12626
  const root = getProjectRoot();
11903
- const ymlPath = path17.join(root, ".kuma", "policy.yml");
11904
- const yamlPath = path17.join(root, ".kuma", "policy.yaml");
12627
+ const ymlPath = path19.join(root, ".kuma", "policy.yml");
12628
+ const yamlPath = path19.join(root, ".kuma", "policy.yaml");
11905
12629
  let policyContent = null;
11906
12630
  let policyPath = null;
11907
- if (fs17.existsSync(ymlPath)) {
11908
- policyContent = fs17.readFileSync(ymlPath, "utf-8");
12631
+ if (fs18.existsSync(ymlPath)) {
12632
+ policyContent = fs18.readFileSync(ymlPath, "utf-8");
11909
12633
  policyPath = ymlPath;
11910
- } else if (fs17.existsSync(yamlPath)) {
11911
- policyContent = fs17.readFileSync(yamlPath, "utf-8");
12634
+ } else if (fs18.existsSync(yamlPath)) {
12635
+ policyContent = fs18.readFileSync(yamlPath, "utf-8");
11912
12636
  policyPath = yamlPath;
11913
12637
  }
11914
12638
  if (!policyContent) {
@@ -12018,9 +12742,9 @@ function checkFilePathPolicy(filePath, policy) {
12018
12742
  if (policy.max_file_size && policy.max_file_size > 0) {
12019
12743
  try {
12020
12744
  const root = getProjectRoot();
12021
- const fullPath = path17.join(root, filePath);
12022
- if (fs17.existsSync(fullPath)) {
12023
- const sizeKB = fs17.statSync(fullPath).size / 1024;
12745
+ const fullPath = path19.join(root, filePath);
12746
+ if (fs18.existsSync(fullPath)) {
12747
+ const sizeKB = fs18.statSync(fullPath).size / 1024;
12024
12748
  if (sizeKB > policy.max_file_size) {
12025
12749
  violations.push({
12026
12750
  rule: "max_file_size",
@@ -12187,12 +12911,12 @@ async function safetyCheck(action, filePath, command) {
12187
12911
  }
12188
12912
  if (filePath) {
12189
12913
  try {
12190
- const fs27 = await import("fs");
12191
- const path27 = await import("path");
12914
+ const fs30 = await import("fs");
12915
+ const path31 = await import("path");
12192
12916
  const root = process.cwd();
12193
- const fullPath = path27.resolve(root, filePath);
12194
- if (fs27.existsSync(fullPath)) {
12195
- const stat = fs27.statSync(fullPath);
12917
+ const fullPath = path31.resolve(root, filePath);
12918
+ if (fs30.existsSync(fullPath)) {
12919
+ const stat = fs30.statSync(fullPath);
12196
12920
  const sizeKB = Math.round(stat.size / 1024);
12197
12921
  if (sizeKB > 500) {
12198
12922
  checks.push({
@@ -12202,12 +12926,12 @@ async function safetyCheck(action, filePath, command) {
12202
12926
  });
12203
12927
  }
12204
12928
  if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
12205
- const baseName = path27.basename(filePath, path27.extname(filePath));
12929
+ const baseName = path31.basename(filePath, path31.extname(filePath));
12206
12930
  const testPatterns = [
12207
12931
  filePath.replace(/\.(ts|js)$/, ".test.$1"),
12208
12932
  filePath.replace(/\.(ts|js)$/, ".spec.$1"),
12209
12933
  filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
12210
- `**/__tests__/**/${baseName}.test.${path27.extname(filePath).slice(1)}`
12934
+ `**/__tests__/**/${baseName}.test.${path31.extname(filePath).slice(1)}`
12211
12935
  ];
12212
12936
  let hasTests = false;
12213
12937
  const fg = await import("fast-glob");
@@ -12223,7 +12947,7 @@ async function safetyCheck(action, filePath, command) {
12223
12947
  name: "Tests Check",
12224
12948
  passed: true,
12225
12949
  // Not a blocker, just informative
12226
- detail: `\u{1F9EA} No test file found for "${path27.basename(filePath)}" \u2014 consider adding tests`
12950
+ detail: `\u{1F9EA} No test file found for "${path31.basename(filePath)}" \u2014 consider adding tests`
12227
12951
  });
12228
12952
  } else {
12229
12953
  checks.push({
@@ -12312,8 +13036,8 @@ init_sessionMemory();
12312
13036
  // src/guards/antiPatternDetector.ts
12313
13037
  init_pathValidator();
12314
13038
  init_sessionMemory();
12315
- import fs18 from "fs";
12316
- import path18 from "path";
13039
+ import fs19 from "fs";
13040
+ import path20 from "path";
12317
13041
  import { execSync as execSync6 } from "child_process";
12318
13042
  var SCRIPT_PATCH_PATTERNS = [
12319
13043
  "writeFileSync",
@@ -12340,20 +13064,20 @@ function findPatchScripts(projectRoot) {
12340
13064
  const warnings = [];
12341
13065
  const recentFiles = scanRecentFiles(projectRoot);
12342
13066
  for (const file of recentFiles) {
12343
- const ext = path18.extname(file).toLowerCase();
13067
+ const ext = path20.extname(file).toLowerCase();
12344
13068
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
12345
- const relativePath = path18.relative(projectRoot, file);
13069
+ const relativePath = path20.relative(projectRoot, file);
12346
13070
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
12347
13071
  continue;
12348
13072
  }
12349
13073
  try {
12350
- const content = fs18.readFileSync(file, "utf-8").toLowerCase();
13074
+ const content = fs19.readFileSync(file, "utf-8").toLowerCase();
12351
13075
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
12352
13076
  if (matchedPattern) {
12353
13077
  warnings.push({
12354
13078
  severity: "high",
12355
13079
  pattern: "script-patching",
12356
- message: `Created script file that modifies other files: ${path18.basename(file)}`,
13080
+ message: `Created script file that modifies other files: ${path20.basename(file)}`,
12357
13081
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
12358
13082
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
12359
13083
  filePath: relativePath
@@ -12387,15 +13111,15 @@ function detectBashGrepUsage() {
12387
13111
  function scanRecentFiles(projectRoot) {
12388
13112
  const recent = [];
12389
13113
  try {
12390
- const entries = fs18.readdirSync(projectRoot, { withFileTypes: true });
13114
+ const entries = fs19.readdirSync(projectRoot, { withFileTypes: true });
12391
13115
  const now = Date.now();
12392
13116
  const recentThreshold = 30 * 60 * 1e3;
12393
13117
  for (const entry of entries) {
12394
13118
  if (!entry.isFile()) continue;
12395
13119
  try {
12396
- const stat = fs18.statSync(path18.join(projectRoot, entry.name));
13120
+ const stat = fs19.statSync(path20.join(projectRoot, entry.name));
12397
13121
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
12398
- recent.push(path18.join(projectRoot, entry.name));
13122
+ recent.push(path20.join(projectRoot, entry.name));
12399
13123
  }
12400
13124
  } catch {
12401
13125
  continue;
@@ -12419,14 +13143,14 @@ function detectGitPatchScripts(projectRoot) {
12419
13143
  const prefix = line.substring(0, 2);
12420
13144
  if (prefix !== "??" && prefix !== "A ") continue;
12421
13145
  const file = line.substring(3).trim();
12422
- const ext = path18.extname(file).toLowerCase();
13146
+ const ext = path20.extname(file).toLowerCase();
12423
13147
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
12424
13148
  const isRootLevel = !file.includes("/");
12425
13149
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
12426
13150
  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();
13151
+ const fullPath = path20.join(projectRoot, file);
13152
+ if (fs19.existsSync(fullPath)) {
13153
+ const content = fs19.readFileSync(fullPath, "utf-8").toLowerCase();
12430
13154
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
12431
13155
  if (matchedPattern) {
12432
13156
  warnings.push({
@@ -12449,7 +13173,7 @@ function detectGitPatchScripts(projectRoot) {
12449
13173
  if (deletedStdout) {
12450
13174
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
12451
13175
  for (const file of deletedFiles) {
12452
- const ext = path18.extname(file).toLowerCase();
13176
+ const ext = path20.extname(file).toLowerCase();
12453
13177
  if (SCRIPT_EXTENSIONS.includes(ext)) {
12454
13178
  warnings.push({
12455
13179
  severity: "high",
@@ -12511,21 +13235,21 @@ function detectAllAntiPatterns() {
12511
13235
  // src/engine/contextSnapshot.ts
12512
13236
  init_sessionMemory();
12513
13237
  init_pathValidator();
12514
- import fs19 from "fs";
12515
- import path19 from "path";
13238
+ import fs20 from "fs";
13239
+ import path21 from "path";
12516
13240
  import { execSync as execSync7 } from "child_process";
12517
13241
  var SNAPSHOT_DIR = "context-snapshots";
12518
13242
  function snapshotDir() {
12519
- return path19.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
13243
+ return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
12520
13244
  }
12521
13245
  function ensureSnapshotDir() {
12522
13246
  const dir = snapshotDir();
12523
- if (!fs19.existsSync(dir)) {
12524
- fs19.mkdirSync(dir, { recursive: true });
13247
+ if (!fs20.existsSync(dir)) {
13248
+ fs20.mkdirSync(dir, { recursive: true });
12525
13249
  }
12526
13250
  }
12527
13251
  function snapshotFilePath(timestamp) {
12528
- return path19.join(snapshotDir(), `${timestamp}.json`);
13252
+ return path21.join(snapshotDir(), `${timestamp}.json`);
12529
13253
  }
12530
13254
  function saveSnapshot(goal) {
12531
13255
  try {
@@ -12546,7 +13270,7 @@ function saveSnapshot(goal) {
12546
13270
  hasConventions: !!summary.hasConventions
12547
13271
  };
12548
13272
  try {
12549
- fs19.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
13273
+ fs20.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
12550
13274
  } catch {
12551
13275
  }
12552
13276
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -12859,12 +13583,12 @@ async function handleSecurity(params) {
12859
13583
  sessionMemory.recordToolCall("kuma_safety_security", {});
12860
13584
  if (params.filePath) {
12861
13585
  try {
12862
- const fs27 = await import("fs");
12863
- const path27 = await import("path");
13586
+ const fs30 = await import("fs");
13587
+ const path31 = await import("path");
12864
13588
  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");
13589
+ const fullPath = path31.resolve(root, params.filePath);
13590
+ if (fs30.existsSync(fullPath) && fs30.statSync(fullPath).isFile()) {
13591
+ const content = fs30.readFileSync(fullPath, "utf-8");
12868
13592
  const lines = content.split("\n");
12869
13593
  const findings = [];
12870
13594
  for (let i = 0; i < lines.length; i++) {
@@ -12918,23 +13642,23 @@ async function handleGitignore(_params) {
12918
13642
  }
12919
13643
  async function handleClean(_params) {
12920
13644
  sessionMemory.recordToolCall("kuma_safety_clean", {});
12921
- const fs27 = await import("fs");
12922
- const path27 = await import("path");
13645
+ const fs30 = await import("fs");
13646
+ const path31 = await import("path");
12923
13647
  const root = process.cwd();
12924
- const scratchDir = path27.resolve(root, ".kuma", "scratch");
13648
+ const scratchDir = path31.resolve(root, ".kuma", "scratch");
12925
13649
  let removed = 0;
12926
- if (fs27.existsSync(scratchDir)) {
13650
+ if (fs30.existsSync(scratchDir)) {
12927
13651
  try {
12928
- const entries = fs27.readdirSync(scratchDir);
13652
+ const entries = fs30.readdirSync(scratchDir);
12929
13653
  for (const entry of entries) {
12930
- const fullPath = path27.join(scratchDir, entry);
13654
+ const fullPath = path31.join(scratchDir, entry);
12931
13655
  try {
12932
- const stat = fs27.statSync(fullPath);
13656
+ const stat = fs30.statSync(fullPath);
12933
13657
  if (stat.isFile()) {
12934
- fs27.unlinkSync(fullPath);
13658
+ fs30.unlinkSync(fullPath);
12935
13659
  removed++;
12936
13660
  } else if (stat.isDirectory()) {
12937
- fs27.rmSync(fullPath, { recursive: true, force: true });
13661
+ fs30.rmSync(fullPath, { recursive: true, force: true });
12938
13662
  removed++;
12939
13663
  }
12940
13664
  } catch {
@@ -13309,6 +14033,7 @@ Usage:
13309
14033
  npx @plumpslabs/kuma init --skip-existing Skip generation if file exists
13310
14034
  npx @plumpslabs/kuma init --claude --cursor Generate specific files
13311
14035
  npx @plumpslabs/kuma studio Start Kuma Studio web dashboard (knowledge graph visualizer)
14036
+ npx @plumpslabs/kuma init --legacy Bulk-onboard an existing (legacy) codebase
13312
14037
  npx @plumpslabs/kuma init --help Show this help
13313
14038
 
13314
14039
  Available config files:
@@ -13353,14 +14078,14 @@ async function main() {
13353
14078
  } catch {
13354
14079
  }
13355
14080
  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)) {
14081
+ const path31 = await import("path");
14082
+ const fs30 = await import("fs");
14083
+ const lockDir = path31.resolve(process.cwd(), ".kuma/verifier.lock");
14084
+ if (fs30.existsSync(lockDir)) {
14085
+ const pidFile = path31.join(lockDir, "pid");
14086
+ if (fs30.existsSync(pidFile)) {
13362
14087
  try {
13363
- const pid = parseInt(fs27.readFileSync(pidFile, "utf-8"), 10);
14088
+ const pid = parseInt(fs30.readFileSync(pidFile, "utf-8"), 10);
13364
14089
  if (pid && pid !== process.pid) {
13365
14090
  try {
13366
14091
  process.kill(-pid, "SIGKILL");
@@ -13376,7 +14101,7 @@ async function main() {
13376
14101
  } catch {
13377
14102
  }
13378
14103
  }
13379
- fs27.rmSync(lockDir, { recursive: true, force: true });
14104
+ fs30.rmSync(lockDir, { recursive: true, force: true });
13380
14105
  }
13381
14106
  if (killedCount === 0) {
13382
14107
  console.error("\u2705 No running verifications found to kill.");
@@ -13470,6 +14195,18 @@ async function main() {
13470
14195
  printHelp();
13471
14196
  process.exit(0);
13472
14197
  }
14198
+ if (flags.includes("--legacy")) {
14199
+ try {
14200
+ const { runLegacyOnboarding: runLegacyOnboarding2 } = await Promise.resolve().then(() => (init_kumaLegacyOnboard(), kumaLegacyOnboard_exports));
14201
+ console.error("\u{1F43B} Kuma Legacy Onboarding \u2014 making your existing codebase agent-ready...");
14202
+ const report = await runLegacyOnboarding2();
14203
+ console.log(report);
14204
+ } catch (err) {
14205
+ console.error(`\u274C Legacy onboarding failed: ${err}`);
14206
+ process.exit(1);
14207
+ }
14208
+ process.exit(0);
14209
+ }
13473
14210
  const requestedFlags = flags.filter((f) => f.startsWith("--"));
13474
14211
  let selectedTypes;
13475
14212
  if (requestedFlags.length === 0) {
@@ -13524,23 +14261,23 @@ async function main() {
13524
14261
  });
13525
14262
  const output = formatInitResults(results);
13526
14263
  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(
14264
+ const fs30 = await import("fs");
14265
+ const path31 = await import("path");
14266
+ const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
14267
+ const matchaAgents = path31.resolve(
13531
14268
  process.cwd(),
13532
14269
  ".agents/skills/matcha/SKILL.md"
13533
14270
  );
13534
- const matchaRootSkills = path27.resolve(
14271
+ const matchaRootSkills = path31.resolve(
13535
14272
  process.cwd(),
13536
14273
  "skills/matcha/SKILL.md"
13537
14274
  );
13538
- const matchaAgentsMd = path27.resolve(process.cwd(), "AGENTS.md");
13539
- const matchaWindsurfRules = path27.resolve(
14275
+ const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
14276
+ const matchaWindsurfRules = path31.resolve(
13540
14277
  process.cwd(),
13541
14278
  ".windsurf"
13542
14279
  );
13543
- if (fs27.existsSync(matchaSkills) || fs27.existsSync(matchaAgents) || fs27.existsSync(matchaRootSkills) || fs27.existsSync(matchaAgentsMd) || fs27.existsSync(matchaWindsurfRules)) {
14280
+ if (fs30.existsSync(matchaSkills) || fs30.existsSync(matchaAgents) || fs30.existsSync(matchaRootSkills) || fs30.existsSync(matchaAgentsMd) || fs30.existsSync(matchaWindsurfRules)) {
13544
14281
  console.error(
13545
14282
  "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
13546
14283
  );
@@ -13554,13 +14291,13 @@ async function main() {
13554
14291
  (async () => {
13555
14292
  try {
13556
14293
  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");
14294
+ const fs30 = await import("fs");
14295
+ const path31 = await import("path");
14296
+ const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
14297
+ if (!fs30.existsSync(initMdPath)) {
14298
+ const kumaDir = path31.dirname(initMdPath);
14299
+ if (!fs30.existsSync(kumaDir)) fs30.mkdirSync(kumaDir, { recursive: true });
14300
+ fs30.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
13564
14301
  console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
13565
14302
  }
13566
14303
  } catch (err) {
@@ -13571,8 +14308,8 @@ async function main() {
13571
14308
  try {
13572
14309
  const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
13573
14310
  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");
14311
+ const fs30 = await import("fs");
14312
+ const path31 = await import("path");
13576
14313
  const detection = detectAgent2();
13577
14314
  if (!detection.primary) {
13578
14315
  console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
@@ -13580,22 +14317,22 @@ async function main() {
13580
14317
  }
13581
14318
  const agentType = detection.primary;
13582
14319
  const skillPath = getSkillPath2(agentType);
13583
- const fullPath = path27.resolve(process.cwd(), skillPath);
13584
- if (fs27.existsSync(fullPath)) {
14320
+ const fullPath = path31.resolve(process.cwd(), skillPath);
14321
+ if (fs30.existsSync(fullPath)) {
13585
14322
  console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
13586
14323
  return;
13587
14324
  }
13588
- const dir = path27.dirname(fullPath);
13589
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
13590
- fs27.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
14325
+ const dir = path31.dirname(fullPath);
14326
+ if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
14327
+ fs30.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
13591
14328
  console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
13592
14329
  const secondaryFiles = getSecondaryFiles2(agentType);
13593
14330
  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");
14331
+ const sfPath = path31.resolve(process.cwd(), sf.path);
14332
+ const sfDir = path31.dirname(sfPath);
14333
+ if (!fs30.existsSync(sfPath)) {
14334
+ if (!fs30.existsSync(sfDir)) fs30.mkdirSync(sfDir, { recursive: true });
14335
+ fs30.writeFileSync(sfPath, sf.content, "utf-8");
13599
14336
  console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
13600
14337
  }
13601
14338
  }
@@ -13620,11 +14357,11 @@ async function main() {
13620
14357
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
13621
14358
  }
13622
14359
  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 });
14360
+ const fs30 = await import("fs");
14361
+ const path31 = await import("path");
14362
+ const scratchDir = path31.resolve(process.cwd(), ".kuma", "scratch");
14363
+ if (!fs30.existsSync(scratchDir)) {
14364
+ fs30.mkdirSync(scratchDir, { recursive: true });
13628
14365
  console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
13629
14366
  }
13630
14367
  } catch (err) {