@plumpslabs/kuma 2.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +41 -15
  2. package/dist/index.js +1696 -243
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync } from "fs";
5
+ import * as readline from "readline";
5
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
8
 
@@ -899,7 +900,11 @@ async function handleSmartFilePicker(params) {
899
900
  const content = fs4.readFileSync(resolvedPath, "utf-8");
900
901
  const lines = content.split("\n");
901
902
  const totalLines = lines.length;
902
- sessionMemory.recordToolCall("smart_file_picker", { filePath, chunkStrategy, totalLines });
903
+ sessionMemory.recordToolCall("smart_file_picker", {
904
+ filePath,
905
+ chunkStrategy,
906
+ totalLines
907
+ });
903
908
  if (startLine !== void 0 || endLine !== void 0) {
904
909
  const start = startLine ?? 1;
905
910
  const end = endLine ?? totalLines;
@@ -915,7 +920,13 @@ async function handleSmartFilePicker(params) {
915
920
  case "smart":
916
921
  return handleSmartStrategy(filePath, lines, totalLines);
917
922
  default:
918
- return formatOutput(filePath, lines.slice(0, CHUNK_THRESHOLD), 1, totalLines, true);
923
+ return formatOutput(
924
+ filePath,
925
+ lines.slice(0, CHUNK_THRESHOLD),
926
+ 1,
927
+ totalLines,
928
+ true
929
+ );
919
930
  }
920
931
  } catch (err) {
921
932
  return 'Error reading file "' + filePath + '": ' + (err instanceof Error ? err.message : String(err));
@@ -940,7 +951,8 @@ async function handleOutlineStrategy(filePath, lines, totalLines) {
940
951
  const exportLines = [];
941
952
  for (let i = 0; i < lines.length; i++) {
942
953
  const line = lines[i].trim();
943
- if (line.startsWith("//") || line.startsWith("#") || line.startsWith("/*") || line.startsWith("*")) continue;
954
+ if (line.startsWith("//") || line.startsWith("#") || line.startsWith("/*") || line.startsWith("*"))
955
+ continue;
944
956
  if (line.startsWith("import ") || line.startsWith("from ") || line.startsWith("require(")) {
945
957
  importLines.push(line);
946
958
  continue;
@@ -974,10 +986,15 @@ async function handleSmartStrategy(filePath, lines, totalLines) {
974
986
  }
975
987
  if (headerEnd < tailStart) {
976
988
  smartLines.push({ line: -1, text: "" });
977
- smartLines.push({ line: -1, text: " ... " + (tailStart - headerEnd) + " lines hidden ..." });
989
+ smartLines.push({
990
+ line: -1,
991
+ text: " ... " + (tailStart - headerEnd) + " lines hidden ..."
992
+ });
978
993
  smartLines.push({ line: -1, text: "" });
979
994
  }
980
- const keyDeclarations = extractKeyDeclarations(lines.slice(headerEnd, tailStart));
995
+ const keyDeclarations = extractKeyDeclarations(
996
+ lines.slice(headerEnd, tailStart)
997
+ );
981
998
  for (const decl of keyDeclarations) {
982
999
  smartLines.push({ line: decl.line + 1 + headerEnd, text: decl.text });
983
1000
  }
@@ -1020,7 +1037,9 @@ function extractKeyDeclarations(lines) {
1020
1037
  for (let i = 0; i < lines.length; i++) {
1021
1038
  const line = lines[i].trim();
1022
1039
  if (!line || line.startsWith("//") || line.startsWith("#")) continue;
1023
- if (/^(export\s+)?(async\s+)?(function|class|interface|type|enum|const|let|var|def)\s/.test(line)) {
1040
+ if (/^(export\s+)?(async\s+)?(function|class|interface|type|enum|const|let|var|def)\s/.test(
1041
+ line
1042
+ )) {
1024
1043
  decls.push({ line: i, text: line.substring(0, 150) });
1025
1044
  }
1026
1045
  }
@@ -1575,8 +1594,80 @@ function formatBatchResult(results, totalRequested) {
1575
1594
  return lines.join("\n");
1576
1595
  }
1577
1596
 
1578
- // src/tools/safeTerminalExec.ts
1597
+ // src/utils/processRunner.ts
1579
1598
  import { spawn } from "child_process";
1599
+ var DEFAULT_MAX_STDOUT = 5e3;
1600
+ var DEFAULT_MAX_STDERR = 2e3;
1601
+ var DEFAULT_TIMEOUT = 60;
1602
+ function spawnProcess(command, options) {
1603
+ const {
1604
+ cwd,
1605
+ timeoutSeconds = DEFAULT_TIMEOUT,
1606
+ useShell = false,
1607
+ maxStdout = DEFAULT_MAX_STDOUT,
1608
+ maxStderr = DEFAULT_MAX_STDERR
1609
+ } = options;
1610
+ return new Promise((resolve) => {
1611
+ const parts = command.split(" ");
1612
+ const cmd = parts[0];
1613
+ const args = parts.slice(1);
1614
+ const proc = spawn(cmd, args, {
1615
+ cwd,
1616
+ shell: useShell,
1617
+ stdio: ["pipe", "pipe", "pipe"]
1618
+ });
1619
+ let stdout = "";
1620
+ let stderr = "";
1621
+ let timedOut = false;
1622
+ const timeoutId = setTimeout(() => {
1623
+ timedOut = true;
1624
+ proc.kill("SIGTERM");
1625
+ if (process.platform === "win32") {
1626
+ try {
1627
+ spawn("taskkill", ["/pid", String(proc.pid), "/f", "/t"], {
1628
+ stdio: "ignore"
1629
+ });
1630
+ } catch {
1631
+ }
1632
+ }
1633
+ }, timeoutSeconds * 1e3);
1634
+ proc.stdout?.on("data", (data) => {
1635
+ stdout += data.toString();
1636
+ });
1637
+ proc.stderr?.on("data", (data) => {
1638
+ stderr += data.toString();
1639
+ });
1640
+ proc.on("close", (code) => {
1641
+ clearTimeout(timeoutId);
1642
+ resolve({
1643
+ stdout: truncateOutput(stdout, maxStdout),
1644
+ stderr: truncateOutput(stderr, maxStderr),
1645
+ exitCode: code ?? -1,
1646
+ timedOut
1647
+ });
1648
+ });
1649
+ proc.on("error", () => {
1650
+ clearTimeout(timeoutId);
1651
+ resolve({
1652
+ stdout,
1653
+ stderr: `Failed to spawn process: ${cmd}`,
1654
+ exitCode: -1,
1655
+ timedOut: false
1656
+ });
1657
+ });
1658
+ });
1659
+ }
1660
+ function spawnShell(command, options) {
1661
+ return spawnProcess(command, { ...options, useShell: true });
1662
+ }
1663
+ function truncateOutput(output, maxChars) {
1664
+ if (output.length <= maxChars) return output;
1665
+ return output.slice(0, maxChars) + `
1666
+
1667
+ [...truncated, ${output.length - maxChars} more characters]`;
1668
+ }
1669
+
1670
+ // src/tools/safeTerminalExec.ts
1580
1671
  var TASK_COMMANDS = {
1581
1672
  test: "npm test",
1582
1673
  build: "npm run build",
@@ -1600,7 +1691,6 @@ var DANGEROUS_PATTERNS = [
1600
1691
  "mkfs",
1601
1692
  "dd if=",
1602
1693
  ":(){ :|:& };:",
1603
- // fork bomb
1604
1694
  "curl ",
1605
1695
  "wget "
1606
1696
  ];
@@ -1609,12 +1699,7 @@ async function handleSafeTerminalExec(params) {
1609
1699
  if (task === "custom" && !customCommand) {
1610
1700
  return "Error: Task 'custom' requires the 'customCommand' parameter.";
1611
1701
  }
1612
- let command;
1613
- if (task === "custom") {
1614
- command = customCommand;
1615
- } else {
1616
- command = TASK_COMMANDS[task];
1617
- }
1702
+ const command = task === "custom" ? customCommand : TASK_COMMANDS[task];
1618
1703
  const cbResult = circuitBreaker.check("safe_terminal_exec", { task, command });
1619
1704
  if (!cbResult.allowed) {
1620
1705
  return `\u26A0\uFE0F Circuit breaker: ${cbResult.reason}
@@ -1629,7 +1714,10 @@ This command is not permitted.`;
1629
1714
  const projectRoot = getProjectRoot();
1630
1715
  try {
1631
1716
  sessionMemory.recordToolCall("execute_safe_test", { task, command });
1632
- const result = await executeWithTimeout(command, projectRoot, timeout);
1717
+ const result = await spawnShell(command, {
1718
+ cwd: projectRoot,
1719
+ timeoutSeconds: timeout
1720
+ });
1633
1721
  const output = formatExecResult(result, command, task);
1634
1722
  if (result.exitCode !== 0) {
1635
1723
  sessionMemory.addFailedFile(task, result.stderr || result.stdout);
@@ -1637,65 +1725,12 @@ This command is not permitted.`;
1637
1725
  return output;
1638
1726
  } catch (err) {
1639
1727
  const errorMsg = err instanceof Error ? err.message : String(err);
1640
- const isTimeout = errorMsg.toLowerCase().includes("timeout");
1641
- if (isTimeout) {
1728
+ if (errorMsg.toLowerCase().includes("timeout")) {
1642
1729
  return formatTimeoutResult(command, timeout);
1643
1730
  }
1644
1731
  return `Error running "${command}": ${errorMsg}`;
1645
1732
  }
1646
1733
  }
1647
- async function executeWithTimeout(command, cwd, timeoutSeconds) {
1648
- return new Promise((resolve, reject) => {
1649
- const parts = command.split(" ");
1650
- const cmd = parts[0];
1651
- const args = parts.slice(1);
1652
- const proc = spawn(cmd, args, {
1653
- cwd,
1654
- shell: process.platform === "win32",
1655
- // Use shell on Windows
1656
- stdio: ["pipe", "pipe", "pipe"],
1657
- timeout: timeoutSeconds * 1e3
1658
- });
1659
- let stdout = "";
1660
- let stderr = "";
1661
- let timedOut = false;
1662
- const timeoutId = setTimeout(() => {
1663
- timedOut = true;
1664
- proc.kill("SIGTERM");
1665
- if (process.platform === "win32") {
1666
- try {
1667
- spawn("taskkill", ["/pid", String(proc.pid), "/f", "/t"], { stdio: "ignore" });
1668
- } catch {
1669
- }
1670
- }
1671
- }, timeoutSeconds * 1e3);
1672
- proc.stdout?.on("data", (data) => {
1673
- stdout += data.toString();
1674
- });
1675
- proc.stderr?.on("data", (data) => {
1676
- stderr += data.toString();
1677
- });
1678
- proc.on("close", (code) => {
1679
- clearTimeout(timeoutId);
1680
- resolve({
1681
- stdout: truncateOutput(stdout, 5e3),
1682
- stderr: truncateOutput(stderr, 2e3),
1683
- exitCode: code ?? -1,
1684
- timedOut
1685
- });
1686
- });
1687
- proc.on("error", (err) => {
1688
- clearTimeout(timeoutId);
1689
- reject(err);
1690
- });
1691
- });
1692
- }
1693
- function truncateOutput(output, maxChars) {
1694
- if (output.length <= maxChars) return output;
1695
- return output.slice(0, maxChars) + `
1696
-
1697
- [...truncated, ${output.length - maxChars} more characters]`;
1698
- }
1699
1734
  function formatExecResult(result, command, task) {
1700
1735
  const status = result.exitCode === 0 ? "\u2705 PASS" : "\u274C FAIL";
1701
1736
  const lines = [
@@ -2868,20 +2903,26 @@ function formatConventionsOutput(conventions) {
2868
2903
  return lines.join("\n");
2869
2904
  }
2870
2905
 
2906
+ // src/utils/gitUtils.ts
2907
+ import { execSync as execSync2 } from "child_process";
2908
+ function runGitCommand(command) {
2909
+ const root = getProjectRoot();
2910
+ return execSync2(command, {
2911
+ cwd: root,
2912
+ encoding: "utf-8",
2913
+ maxBuffer: 2 * 1024 * 1024
2914
+ });
2915
+ }
2916
+
2871
2917
  // src/tools/gitLog.ts
2872
- import child_process from "child_process";
2873
2918
  async function handleGitLog(params) {
2874
2919
  const { maxCount = 10, filePath } = params;
2875
- const root = getProjectRoot();
2876
2920
  try {
2877
2921
  let command = `git log -n ${maxCount} --oneline`;
2878
2922
  if (filePath) {
2879
2923
  command += ` -- "${filePath}"`;
2880
2924
  }
2881
- const stdout = child_process.execSync(command, {
2882
- cwd: root,
2883
- encoding: "utf-8"
2884
- });
2925
+ const stdout = runGitCommand(command);
2885
2926
  sessionMemory.recordToolCall("git_log", { maxCount, filePath });
2886
2927
  if (!stdout.trim()) {
2887
2928
  return `\u2139\uFE0F No commit history found${filePath ? ` for file "${filePath}"` : ""}.`;
@@ -2895,10 +2936,8 @@ ${stdout}`;
2895
2936
  }
2896
2937
 
2897
2938
  // src/tools/gitDiff.ts
2898
- import child_process2 from "child_process";
2899
2939
  async function handleGitDiff(params) {
2900
2940
  const { filePath, staged = false, contextLines = 3, baseRef, targetRef } = params;
2901
- const root = getProjectRoot();
2902
2941
  try {
2903
2942
  let command = "git diff";
2904
2943
  if (staged) {
@@ -2914,11 +2953,7 @@ async function handleGitDiff(params) {
2914
2953
  if (filePath) {
2915
2954
  command += ' -- "' + filePath + '"';
2916
2955
  }
2917
- const stdout = child_process2.execSync(command, {
2918
- cwd: root,
2919
- encoding: "utf-8",
2920
- maxBuffer: 2 * 1024 * 1024
2921
- });
2956
+ const stdout = runGitCommand(command);
2922
2957
  sessionMemory.recordToolCall("git_diff", { filePath, staged, baseRef, targetRef });
2923
2958
  if (!stdout.trim()) {
2924
2959
  if (staged) {
@@ -3018,6 +3053,8 @@ var DEFAULT_IGNORE = [
3018
3053
  ".DS_Store",
3019
3054
  "*.log"
3020
3055
  ];
3056
+ var treeCache = /* @__PURE__ */ new Map();
3057
+ var CACHE_TTL_MS2 = 6e4;
3021
3058
  async function handleProjectStructure(params) {
3022
3059
  const {
3023
3060
  depth = 3,
@@ -3027,6 +3064,12 @@ async function handleProjectStructure(params) {
3027
3064
  } = params;
3028
3065
  const root = getProjectRoot();
3029
3066
  const clampedDepth = Math.max(1, Math.min(depth, 6));
3067
+ const cacheKey = `${root}:${clampedDepth}:${folderOnly}:${includePattern ?? ""}:${excludePattern ?? ""}`;
3068
+ const cached = treeCache.get(cacheKey);
3069
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS2) {
3070
+ sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly, cached: true });
3071
+ return cached.result;
3072
+ }
3030
3073
  sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
3031
3074
  try {
3032
3075
  const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
@@ -3041,7 +3084,9 @@ async function handleProjectStructure(params) {
3041
3084
  "Increase depth (max 6) for deeper structure.",
3042
3085
  "Use folderOnly: true for high-level overview."
3043
3086
  ];
3044
- return lines.join("\n");
3087
+ const result = lines.join("\n");
3088
+ treeCache.set(cacheKey, { result, timestamp: Date.now() });
3089
+ return result;
3045
3090
  } catch (err) {
3046
3091
  return "Error building project structure: " + (err instanceof Error ? err.message : String(err));
3047
3092
  }
@@ -3122,7 +3167,6 @@ function getFileSize(fullPath) {
3122
3167
  }
3123
3168
 
3124
3169
  // src/tools/staticAnalysis.ts
3125
- import { spawn as spawn2 } from "child_process";
3126
3170
  import fs10 from "fs";
3127
3171
  import path10 from "path";
3128
3172
  async function handleStaticAnalysis(params) {
@@ -3216,45 +3260,14 @@ function readPackageJson(root) {
3216
3260
  async function runTool(tool, cwd, files, autoFix, timeoutSeconds) {
3217
3261
  const cmd = buildToolCommand(tool, files, autoFix);
3218
3262
  if (!cmd) return null;
3219
- return new Promise((resolve) => {
3220
- const parts = cmd.split(" ");
3221
- const command = parts[0];
3222
- const args = parts.slice(1);
3223
- const proc = spawn2(command, args, {
3224
- cwd,
3225
- shell: process.platform === "win32",
3226
- stdio: ["pipe", "pipe", "pipe"]
3227
- });
3228
- let stdout = "";
3229
- let stderr = "";
3230
- const timeoutId = setTimeout(() => {
3231
- proc.kill("SIGTERM");
3232
- if (process.platform === "win32") {
3233
- try {
3234
- spawn2("taskkill", ["/pid", String(proc.pid), "/f", "/t"], { stdio: "ignore" });
3235
- } catch {
3236
- }
3237
- }
3238
- }, (timeoutSeconds ?? 60) * 1e3);
3239
- proc.stdout?.on("data", (data) => {
3240
- stdout += data.toString();
3241
- });
3242
- proc.stderr?.on("data", (data) => {
3243
- stderr += data.toString();
3244
- });
3245
- proc.on("close", (code) => {
3246
- clearTimeout(timeoutId);
3247
- resolve({
3248
- stdout,
3249
- stderr,
3250
- exitCode: code ?? -1
3251
- });
3252
- });
3253
- proc.on("error", () => {
3254
- clearTimeout(timeoutId);
3255
- resolve(null);
3256
- });
3263
+ const result = await spawnProcess(cmd, {
3264
+ cwd,
3265
+ timeoutSeconds: timeoutSeconds ?? 60,
3266
+ maxStdout: 1e5,
3267
+ // Tool output can be large
3268
+ maxStderr: 5e4
3257
3269
  });
3270
+ return result;
3258
3271
  }
3259
3272
  function buildToolCommand(tool, files, autoFix) {
3260
3273
  const pm = detectPackageManagerPrefix();
@@ -3483,7 +3496,7 @@ function formatToolNotAvailable(requested, available) {
3483
3496
  }
3484
3497
 
3485
3498
  // src/tools/kumaReflect.ts
3486
- import { execSync as execSync2 } from "child_process";
3499
+ import { execSync as execSync3 } from "child_process";
3487
3500
  async function handleReflect(params) {
3488
3501
  const summary = sessionMemory.getSummary();
3489
3502
  const goal = params.goal || summary.currentGoal || "";
@@ -3505,7 +3518,7 @@ async function handleReflect(params) {
3505
3518
  let gitStat = "";
3506
3519
  try {
3507
3520
  const root = getProjectRoot();
3508
- gitStat = execSync2("git diff --stat", {
3521
+ gitStat = execSync3("git diff --stat", {
3509
3522
  cwd: root,
3510
3523
  encoding: "utf-8",
3511
3524
  timeout: 5e3
@@ -3576,86 +3589,541 @@ async function handleReflect(params) {
3576
3589
  );
3577
3590
  }
3578
3591
 
3579
- // src/engine/lspClient.ts
3580
- import { spawn as spawn3 } from "child_process";
3581
- import path11 from "path";
3592
+ // src/tools/kumaGuard.ts
3593
+ import { execSync as execSync6 } from "child_process";
3594
+
3595
+ // src/guards/antiPatternDetector.ts
3582
3596
  import fs11 from "fs";
3583
- var LSPClient = class {
3584
- process = null;
3585
- requestId = 0;
3586
- pending = /* @__PURE__ */ new Map();
3587
- buffer = Buffer.alloc(0);
3588
- initialized = false;
3589
- initPromise = null;
3590
- openDocuments = /* @__PURE__ */ new Set();
3591
- _isAvailable = true;
3592
- /** Check whether LSP is available (binary installed) */
3593
- isAvailable() {
3594
- return this._isAvailable;
3597
+ import path11 from "path";
3598
+ import { execSync as execSync4 } from "child_process";
3599
+ var SCRIPT_PATCH_PATTERNS = [
3600
+ "writeFileSync",
3601
+ "writeFile",
3602
+ "replace(",
3603
+ "replaceAll(",
3604
+ "sed",
3605
+ "awk",
3606
+ "fs.write",
3607
+ "patch",
3608
+ "modify"
3609
+ ];
3610
+ var BASH_GREP_PATTERNS = [
3611
+ "grep -rn",
3612
+ "grep -r",
3613
+ "grep -n",
3614
+ "| grep",
3615
+ "ripgrep",
3616
+ "rg ",
3617
+ "ag "
3618
+ ];
3619
+ var SCRIPT_EXTENSIONS = [".py", ".js", ".mjs", ".cjs", ".ts"];
3620
+ function findPatchScripts(projectRoot) {
3621
+ const warnings = [];
3622
+ const recentFiles = scanRecentFiles(projectRoot);
3623
+ for (const file of recentFiles) {
3624
+ const ext = path11.extname(file).toLowerCase();
3625
+ if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
3626
+ const relativePath = path11.relative(projectRoot, file);
3627
+ if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
3628
+ continue;
3629
+ }
3630
+ try {
3631
+ const content = fs11.readFileSync(file, "utf-8").toLowerCase();
3632
+ const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
3633
+ if (matchedPattern) {
3634
+ warnings.push({
3635
+ severity: "high",
3636
+ pattern: "script-patching",
3637
+ message: `Created script file that modifies other files: ${path11.basename(file)}`,
3638
+ suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
3639
+ evidence: `File: ${relativePath} contains '${matchedPattern}'`,
3640
+ filePath: relativePath
3641
+ });
3642
+ }
3643
+ } catch {
3644
+ }
3595
3645
  }
3596
- async ensureInitialized() {
3597
- if (this._isAvailable !== void 0 && !this._isAvailable) return;
3598
- if (this.initialized && this.process) return;
3599
- if (this.initPromise) {
3646
+ return warnings;
3647
+ }
3648
+ function detectBashGrepUsage() {
3649
+ const warnings = [];
3650
+ const toolCalls = sessionMemory.getToolCallHistory(100);
3651
+ for (const call of toolCalls) {
3652
+ if (call.toolName !== "execute_safe_test") continue;
3653
+ const cmd = call.params?.customCommand || call.params?.command || "";
3654
+ const matchedPattern = BASH_GREP_PATTERNS.find((p) => cmd.toLowerCase().includes(p));
3655
+ if (matchedPattern) {
3656
+ warnings.push({
3657
+ severity: "medium",
3658
+ pattern: "bash-grep",
3659
+ message: `Used bash grep instead of smart_grep`,
3660
+ suggestion: "Use **smart_grep** \u2014 it returns line numbers + context, caches results, respects .gitignore",
3661
+ evidence: `Command: ${cmd.substring(0, 120)}`
3662
+ });
3663
+ break;
3664
+ }
3665
+ }
3666
+ return warnings;
3667
+ }
3668
+ function scanRecentFiles(projectRoot) {
3669
+ const recent = [];
3670
+ try {
3671
+ const entries = fs11.readdirSync(projectRoot, { withFileTypes: true });
3672
+ const now = Date.now();
3673
+ const recentThreshold = 30 * 60 * 1e3;
3674
+ for (const entry of entries) {
3675
+ if (!entry.isFile()) continue;
3600
3676
  try {
3601
- await this.initPromise;
3602
- return;
3677
+ const stat = fs11.statSync(path11.join(projectRoot, entry.name));
3678
+ if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
3679
+ recent.push(path11.join(projectRoot, entry.name));
3680
+ }
3603
3681
  } catch {
3604
- this.initPromise = null;
3682
+ continue;
3605
3683
  }
3606
3684
  }
3607
- this.initPromise = this.initialize();
3608
- return this.initPromise;
3685
+ } catch {
3609
3686
  }
3610
- async initialize() {
3611
- const root = getProjectRoot();
3612
- const lspBinary = this.resolveLspBinary(root);
3613
- if (!lspBinary) {
3614
- this._isAvailable = false;
3615
- this.initialized = false;
3616
- this.initPromise = null;
3617
- console.error(`[LSP] typescript-language-server not found. LSP features will fallback to regex.`);
3618
- return;
3619
- }
3620
- const tsconfigPath = path11.join(root, "tsconfig.json");
3621
- if (!fs11.existsSync(tsconfigPath)) {
3622
- console.error(`[LSP] Warning: tsconfig.json not found at "${root}". Running in implicit project mode.`);
3623
- }
3624
- this.process = spawn3(lspBinary, ["--stdio", "--log-level=4"], {
3625
- cwd: root,
3626
- stdio: ["pipe", "pipe", "pipe"],
3627
- env: { ...process.env }
3628
- });
3629
- this.process.stdout?.on("data", (data) => {
3630
- this.buffer = Buffer.concat([this.buffer, data]);
3631
- this.processBuffer();
3632
- });
3633
- this.process.stderr?.on("data", (data) => {
3634
- console.error(`[LSP] ${data.toString().trim()}`);
3635
- });
3636
- this.process.on("exit", (code) => {
3637
- console.error(`[LSP] Process exited with code ${code}`);
3638
- this.initialized = false;
3639
- this.process = null;
3640
- this.initPromise = null;
3641
- this.openDocuments.clear();
3642
- this.buffer = Buffer.alloc(0);
3643
- for (const [, pending] of this.pending) {
3644
- pending.reject(new Error(`LSP server exited (code ${code})`));
3687
+ return recent;
3688
+ }
3689
+ function detectGitPatchScripts(projectRoot) {
3690
+ const warnings = [];
3691
+ try {
3692
+ const statusStdout = execSync4("git status --porcelain", {
3693
+ cwd: projectRoot,
3694
+ encoding: "utf-8",
3695
+ timeout: 3e3
3696
+ }).trim();
3697
+ if (statusStdout) {
3698
+ const lines = statusStdout.split("\n").filter(Boolean);
3699
+ for (const line of lines) {
3700
+ const prefix = line.substring(0, 2);
3701
+ if (prefix !== "??" && prefix !== "A ") continue;
3702
+ const file = line.substring(3).trim();
3703
+ const ext = path11.extname(file).toLowerCase();
3704
+ if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
3705
+ const isRootLevel = !file.includes("/");
3706
+ const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
3707
+ if (!isRootLevel && !isScriptsDir) continue;
3708
+ const fullPath = path11.join(projectRoot, file);
3709
+ if (fs11.existsSync(fullPath)) {
3710
+ const content = fs11.readFileSync(fullPath, "utf-8").toLowerCase();
3711
+ const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
3712
+ if (matchedPattern) {
3713
+ warnings.push({
3714
+ severity: "high",
3715
+ pattern: "script-patching",
3716
+ message: `Patch script detected: ${file}`,
3717
+ suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
3718
+ evidence: `File: ${file} (contains '${matchedPattern}')`,
3719
+ filePath: file
3720
+ });
3721
+ }
3722
+ }
3645
3723
  }
3646
- this.pending.clear();
3647
- });
3648
- this.process.on("error", (err) => {
3649
- console.error(`[LSP] Process error: ${err.message}. LSP features will fallback to regex.`);
3650
- this._isAvailable = false;
3651
- this.process = null;
3652
- this.initialized = false;
3653
- this.initPromise = null;
3654
- this.openDocuments.clear();
3655
- for (const [, pending] of this.pending) {
3656
- pending.reject(new Error(`LSP server error: ${err.message}`));
3724
+ }
3725
+ const deletedStdout = execSync4("git diff --name-only --diff-filter=D HEAD", {
3726
+ cwd: projectRoot,
3727
+ encoding: "utf-8",
3728
+ timeout: 3e3
3729
+ }).trim();
3730
+ if (deletedStdout) {
3731
+ const deletedFiles = deletedStdout.split("\n").filter(Boolean);
3732
+ for (const file of deletedFiles) {
3733
+ const ext = path11.extname(file).toLowerCase();
3734
+ if (SCRIPT_EXTENSIONS.includes(ext)) {
3735
+ warnings.push({
3736
+ severity: "high",
3737
+ pattern: "script-patching",
3738
+ message: `Patch script was tracked then deleted: ${file}`,
3739
+ suggestion: "Use **precise_diff_editor** instead of creating disposable scripts. It has auto-backup + rollback.",
3740
+ evidence: `Git shows ${file} was deleted`,
3741
+ filePath: file
3742
+ });
3743
+ }
3657
3744
  }
3658
- this.pending.clear();
3745
+ }
3746
+ } catch {
3747
+ }
3748
+ return warnings;
3749
+ }
3750
+ function detectBashSedUsage() {
3751
+ const warnings = [];
3752
+ const toolCalls = sessionMemory.getToolCallHistory(100);
3753
+ const SED_PATTERNS = [
3754
+ "sed -i",
3755
+ "sed \\'",
3756
+ "sed '",
3757
+ "awk '",
3758
+ "awk \\'",
3759
+ "cat <<",
3760
+ '>> "$file',
3761
+ ">> '$file",
3762
+ 'echo " >> ',
3763
+ "echo ' >> ",
3764
+ "printf '%s' >"
3765
+ ];
3766
+ for (const call of toolCalls) {
3767
+ if (call.toolName !== "execute_safe_test") continue;
3768
+ const cmd = call.params?.customCommand || call.params?.command || "";
3769
+ if (SED_PATTERNS.some((p) => cmd.includes(p))) {
3770
+ warnings.push({
3771
+ severity: "high",
3772
+ pattern: "bash-sed-editing",
3773
+ message: "Used bash sed/awk to edit source files instead of precise_diff_editor",
3774
+ suggestion: 'Use **precise_diff_editor** for all file modifications \u2014 it has fuzzy matching, auto-backup, and rollback.\n\n\u2705 Correct format:\nprecise_diff_editor({\n filePath: "src/file.ts",\n edits: [\n { searchBlock: "old code", replaceBlock: "new code" }\n ]\n})',
3775
+ evidence: `Command: ${cmd.substring(0, 150)}`
3776
+ });
3777
+ break;
3778
+ }
3779
+ }
3780
+ return warnings;
3781
+ }
3782
+ function detectAllAntiPatterns() {
3783
+ const projectRoot = getProjectRoot();
3784
+ const warnings = [];
3785
+ warnings.push(...findPatchScripts(projectRoot));
3786
+ warnings.push(...detectBashGrepUsage());
3787
+ warnings.push(...detectGitPatchScripts(projectRoot));
3788
+ warnings.push(...detectBashSedUsage());
3789
+ return warnings;
3790
+ }
3791
+
3792
+ // src/engine/contextSnapshot.ts
3793
+ import fs12 from "fs";
3794
+ import path12 from "path";
3795
+ import { execSync as execSync5 } from "child_process";
3796
+ var SNAPSHOT_DIR = "context-snapshots";
3797
+ function snapshotDir() {
3798
+ return path12.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
3799
+ }
3800
+ function ensureSnapshotDir() {
3801
+ const dir = snapshotDir();
3802
+ if (!fs12.existsSync(dir)) {
3803
+ fs12.mkdirSync(dir, { recursive: true });
3804
+ }
3805
+ }
3806
+ function snapshotFilePath(timestamp) {
3807
+ return path12.join(snapshotDir(), `${timestamp}.json`);
3808
+ }
3809
+ function saveSnapshot(goal) {
3810
+ try {
3811
+ ensureSnapshotDir();
3812
+ } catch {
3813
+ return null;
3814
+ }
3815
+ const summary = sessionMemory.getSummary();
3816
+ const snapshot = {
3817
+ version: 1,
3818
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3819
+ goal: goal || summary.currentGoal || "",
3820
+ modifiedFiles: summary.modifiedFiles || [],
3821
+ unresolvedFailures: summary.unresolvedFailures || [],
3822
+ toolCallCount: summary.toolCallCount || 0,
3823
+ gitDiffStat: getGitDiffStat(),
3824
+ completedSteps: summary.completedSteps || [],
3825
+ hasConventions: !!summary.hasConventions
3826
+ };
3827
+ try {
3828
+ fs12.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
3829
+ } catch {
3830
+ }
3831
+ sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
3832
+ return snapshot;
3833
+ }
3834
+ function listSnapshots() {
3835
+ const dir = snapshotDir();
3836
+ if (!fs12.existsSync(dir)) return [];
3837
+ try {
3838
+ const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
3839
+ return files.map((f) => {
3840
+ try {
3841
+ const content = fs12.readFileSync(path12.join(dir, f), "utf-8");
3842
+ return JSON.parse(content);
3843
+ } catch {
3844
+ return null;
3845
+ }
3846
+ }).filter((s) => s !== null);
3847
+ } catch {
3848
+ return [];
3849
+ }
3850
+ }
3851
+ function formatSnapshot(snapshot) {
3852
+ const lines = [
3853
+ `\u{1F4F8} **Context Snapshot** \u2014 ${snapshot.timestamp}`,
3854
+ "",
3855
+ `\u{1F3AF} **Goal:** ${snapshot.goal || "(not set)"}`,
3856
+ `\u{1F4C1} **Modified Files:** ${snapshot.modifiedFiles.length}`,
3857
+ `\u274C **Unresolved Failures:** ${snapshot.unresolvedFailures.length}`,
3858
+ `\u{1F527} **Tool Calls:** ${snapshot.toolCallCount}`,
3859
+ `\u{1F4CA} **Git Diff:** ${snapshot.gitDiffStat || "clean"}`,
3860
+ `\u2705 **Completed Steps:** ${snapshot.completedSteps.length}`,
3861
+ `\u{1F4D0} **Conventions:** ${snapshot.hasConventions ? "detected" : "not detected"}`,
3862
+ ""
3863
+ ];
3864
+ if (snapshot.modifiedFiles.length > 0) {
3865
+ lines.push("**Modified Files:**");
3866
+ for (const f of snapshot.modifiedFiles) {
3867
+ lines.push(` - ${f.filePath} (${f.status})`);
3868
+ }
3869
+ lines.push("");
3870
+ }
3871
+ if (snapshot.unresolvedFailures.length > 0) {
3872
+ lines.push("**Unresolved Failures:**");
3873
+ for (const f of snapshot.unresolvedFailures) {
3874
+ lines.push(` - [${f.task}] ${f.error.substring(0, 150)}`);
3875
+ }
3876
+ lines.push("");
3877
+ }
3878
+ lines.push('\u{1F4A1} Use kuma_guard({ check: "context" }) to create a new snapshot.');
3879
+ return lines.join("\n");
3880
+ }
3881
+ function getGitDiffStat() {
3882
+ try {
3883
+ const root = getProjectRoot();
3884
+ const stdout = execSync5("git diff --stat", {
3885
+ cwd: root,
3886
+ encoding: "utf-8",
3887
+ timeout: 3e3
3888
+ }).trim();
3889
+ return stdout || "clean";
3890
+ } catch {
3891
+ return "clean";
3892
+ }
3893
+ }
3894
+
3895
+ // src/tools/kumaGuard.ts
3896
+ async function handleKumaGuard(params) {
3897
+ const { check = "all", goal: inputGoal } = params;
3898
+ const summary = sessionMemory.getSummary();
3899
+ const goal = inputGoal || summary.currentGoal || "";
3900
+ sessionMemory.recordToolCall("kuma_guard", { check, goal });
3901
+ const warnings = [];
3902
+ if (check === "all" || check === "anti-pattern") {
3903
+ warnings.push(...detectAllAntiPatterns());
3904
+ }
3905
+ const loop = check === "all" || check === "loop" ? sessionMemory.detectLoop() : { isLooping: false };
3906
+ if (loop.isLooping) {
3907
+ warnings.push({
3908
+ severity: "high",
3909
+ pattern: "tool-loop",
3910
+ message: loop.message ?? "Detected potential tool call loop",
3911
+ suggestion: "Switch approach \u2014 try reading the file first with smart_file_picker"
3912
+ });
3913
+ }
3914
+ const drifts = [];
3915
+ const toolCalls = sessionMemory.getToolCallHistory(50);
3916
+ const hasRunTests = toolCalls.some((c) => c.toolName === "execute_safe_test");
3917
+ if (check === "all" || check === "drift") {
3918
+ const modifiedFiles = sessionMemory.getModifiedFiles();
3919
+ const failedFiles = sessionMemory.getFailedFiles();
3920
+ let unresolvedCount = 0;
3921
+ for (const f of failedFiles) {
3922
+ for (const ff of f.failures) {
3923
+ if (!ff.resolved) unresolvedCount++;
3924
+ }
3925
+ }
3926
+ if (modifiedFiles.length > 0 && !hasRunTests) {
3927
+ drifts.push(`${modifiedFiles.length} file(s) edited but no test run`);
3928
+ warnings.push({
3929
+ severity: "medium",
3930
+ pattern: "no-test-after-edit",
3931
+ message: `${modifiedFiles.length} file(s) modified without running tests`,
3932
+ suggestion: 'Run execute_safe_test({ task: "typecheck" }) to verify changes'
3933
+ });
3934
+ }
3935
+ if (unresolvedCount > 0) {
3936
+ drifts.push(`${unresolvedCount} unresolved failure(s)`);
3937
+ }
3938
+ try {
3939
+ const root = getProjectRoot();
3940
+ const gitStat = execSync6("git diff --stat", {
3941
+ cwd: root,
3942
+ encoding: "utf-8",
3943
+ timeout: 3e3
3944
+ }).trim();
3945
+ if (gitStat) {
3946
+ drifts.push(`Git diff: ${gitStat}`);
3947
+ }
3948
+ } catch {
3949
+ }
3950
+ const editCalls = toolCalls.filter(
3951
+ (c) => c.toolName === "precise_diff_editor" || c.toolName === "batch_file_writer"
3952
+ ).length;
3953
+ if (editCalls > 5) {
3954
+ warnings.push({
3955
+ severity: "low",
3956
+ pattern: "excessive-edits",
3957
+ message: `${editCalls} file operations in a row`,
3958
+ suggestion: "Consider if all edits are needed. Run tests before making more changes."
3959
+ });
3960
+ }
3961
+ }
3962
+ if (check === "context") {
3963
+ const snapshot = saveSnapshot(goal);
3964
+ if (!snapshot) {
3965
+ return "\u26A0\uFE0F Could not create context snapshot. The .kuma directory might not be accessible.";
3966
+ }
3967
+ return formatSnapshot(snapshot);
3968
+ }
3969
+ const hasWarnings = warnings.length > 0;
3970
+ const hasDrifts = drifts.length > 0;
3971
+ const onTrack = !hasWarnings && !hasDrifts;
3972
+ let suggestion;
3973
+ if (warnings.some((w) => w.severity === "high" && w.pattern === "script-patching")) {
3974
+ suggestion = "Remove patch scripts and use precise_diff_editor for all file modifications";
3975
+ } else if (warnings.some((w) => w.pattern === "tool-loop")) {
3976
+ suggestion = "Switch approach \u2014 current tool is not making progress";
3977
+ } else if (warnings.some((w) => w.pattern === "no-test-after-edit")) {
3978
+ suggestion = "Run tests to verify your changes before continuing";
3979
+ } else if (warnings.some((w) => w.pattern === "bash-grep")) {
3980
+ suggestion = "Use smart_grep for code search instead of bash grep";
3981
+ } else if (warnings.some((w) => w.pattern === "excessive-edits")) {
3982
+ suggestion = "Pause and review: are all these edits necessary?";
3983
+ } else if (!goal) {
3984
+ suggestion = "No goal set \u2014 use goal parameter or setGoal to track intent";
3985
+ } else {
3986
+ suggestion = "On track \u2014 continue with current approach";
3987
+ }
3988
+ const report = {
3989
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3990
+ onTrack,
3991
+ warnings,
3992
+ drifts,
3993
+ suggestion,
3994
+ stats: {
3995
+ goal,
3996
+ modifiedFiles: summary.modifiedFiles.length,
3997
+ toolCalls: summary.toolCallCount ?? 0,
3998
+ unresolvedFailures: summary.unresolvedFailures ? summary.unresolvedFailures.length : 0,
3999
+ hasLoop: loop.isLooping,
4000
+ hasRunTests
4001
+ }
4002
+ };
4003
+ return JSON.stringify(report, null, 2);
4004
+ }
4005
+
4006
+ // src/tools/kumaContext.ts
4007
+ async function handleKumaContext(params) {
4008
+ const { action, goal } = params;
4009
+ switch (action) {
4010
+ case "save": {
4011
+ const snapshot = saveSnapshot(goal);
4012
+ if (!snapshot) {
4013
+ return "\u26A0\uFE0F Could not create context snapshot. The .kuma directory might not be accessible.";
4014
+ }
4015
+ return formatSnapshot(snapshot);
4016
+ }
4017
+ case "list": {
4018
+ const snapshots = listSnapshots();
4019
+ if (snapshots.length === 0) {
4020
+ return '\u{1F4F8} **No snapshots found.**\n\nRun `kuma_guard({ check: "context" })` or `kuma_context({ action: "save" })` to create one.';
4021
+ }
4022
+ const lines = [
4023
+ `\u{1F4F8} **Context Snapshots** \u2014 ${snapshots.length} available`,
4024
+ ""
4025
+ ];
4026
+ for (let i = 0; i < snapshots.length; i++) {
4027
+ const s = snapshots[i];
4028
+ const fileCount = s.modifiedFiles.length;
4029
+ const failureCount = s.unresolvedFailures.length;
4030
+ lines.push(
4031
+ `[${i + 1}] ${s.timestamp}`,
4032
+ ` \u{1F3AF} Goal: ${s.goal || "(not set)"}`,
4033
+ ` \u{1F4C1} ${fileCount} files, \u274C ${failureCount} failures, \u{1F527} ${s.toolCallCount} tool calls`,
4034
+ ` \u{1F4CA} Git: ${s.gitDiffStat || "clean"}`,
4035
+ ""
4036
+ );
4037
+ }
4038
+ lines.push('\u{1F4A1} Use kuma_context({ action: "save" }) to create a new snapshot.');
4039
+ lines.push('\u{1F4A1} Or kuma_guard({ check: "context" }) to save and get full context.');
4040
+ return lines.join("\n");
4041
+ }
4042
+ default:
4043
+ return `Error: Action "${action}" not supported. Use "save" or "list".`;
4044
+ }
4045
+ }
4046
+
4047
+ // src/engine/lspClient.ts
4048
+ import { spawn as spawn2 } from "child_process";
4049
+ import path13 from "path";
4050
+ import fs13 from "fs";
4051
+ var LSPClient = class {
4052
+ process = null;
4053
+ requestId = 0;
4054
+ pending = /* @__PURE__ */ new Map();
4055
+ buffer = Buffer.alloc(0);
4056
+ initialized = false;
4057
+ initPromise = null;
4058
+ openDocuments = /* @__PURE__ */ new Set();
4059
+ _isAvailable = true;
4060
+ /** Check whether LSP is available (binary installed) */
4061
+ isAvailable() {
4062
+ return this._isAvailable;
4063
+ }
4064
+ async ensureInitialized() {
4065
+ if (this._isAvailable !== void 0 && !this._isAvailable) return;
4066
+ if (this.initialized && this.process) return;
4067
+ if (this.initPromise) {
4068
+ try {
4069
+ await this.initPromise;
4070
+ return;
4071
+ } catch {
4072
+ this.initPromise = null;
4073
+ }
4074
+ }
4075
+ this.initPromise = this.initialize();
4076
+ return this.initPromise;
4077
+ }
4078
+ async initialize() {
4079
+ const root = getProjectRoot();
4080
+ const lspBinary = this.resolveLspBinary(root);
4081
+ if (!lspBinary) {
4082
+ this._isAvailable = false;
4083
+ this.initialized = false;
4084
+ this.initPromise = null;
4085
+ console.error(`[LSP] typescript-language-server not found. LSP features will fallback to regex.`);
4086
+ return;
4087
+ }
4088
+ const tsconfigPath = path13.join(root, "tsconfig.json");
4089
+ if (!fs13.existsSync(tsconfigPath)) {
4090
+ console.error(`[LSP] Warning: tsconfig.json not found at "${root}". Running in implicit project mode.`);
4091
+ }
4092
+ this.process = spawn2(lspBinary, ["--stdio", "--log-level=4"], {
4093
+ cwd: root,
4094
+ stdio: ["pipe", "pipe", "pipe"],
4095
+ env: { ...process.env }
4096
+ });
4097
+ this.process.stdout?.on("data", (data) => {
4098
+ this.buffer = Buffer.concat([this.buffer, data]);
4099
+ this.processBuffer();
4100
+ });
4101
+ this.process.stderr?.on("data", (data) => {
4102
+ console.error(`[LSP] ${data.toString().trim()}`);
4103
+ });
4104
+ this.process.on("exit", (code) => {
4105
+ console.error(`[LSP] Process exited with code ${code}`);
4106
+ this.initialized = false;
4107
+ this.process = null;
4108
+ this.initPromise = null;
4109
+ this.openDocuments.clear();
4110
+ this.buffer = Buffer.alloc(0);
4111
+ for (const [, pending] of this.pending) {
4112
+ pending.reject(new Error(`LSP server exited (code ${code})`));
4113
+ }
4114
+ this.pending.clear();
4115
+ });
4116
+ this.process.on("error", (err) => {
4117
+ console.error(`[LSP] Process error: ${err.message}. LSP features will fallback to regex.`);
4118
+ this._isAvailable = false;
4119
+ this.process = null;
4120
+ this.initialized = false;
4121
+ this.initPromise = null;
4122
+ this.openDocuments.clear();
4123
+ for (const [, pending] of this.pending) {
4124
+ pending.reject(new Error(`LSP server error: ${err.message}`));
4125
+ }
4126
+ this.pending.clear();
3659
4127
  });
3660
4128
  const rootUri = this.toUri(root);
3661
4129
  const initResult = await this.sendRequestRaw("initialize", {
@@ -3680,19 +4148,19 @@ var LSPClient = class {
3680
4148
  /** Resolve the typescript-language-server binary from local or global install */
3681
4149
  resolveLspBinary(projectRoot) {
3682
4150
  const candidates = [
3683
- path11.join(projectRoot, "node_modules", ".bin", "typescript-language-server"),
3684
- path11.join(projectRoot, "..", "node_modules", ".bin", "typescript-language-server")
4151
+ path13.join(projectRoot, "node_modules", ".bin", "typescript-language-server"),
4152
+ path13.join(projectRoot, "..", "node_modules", ".bin", "typescript-language-server")
3685
4153
  ];
3686
4154
  for (const candidate of candidates) {
3687
- if (fs11.existsSync(candidate)) {
4155
+ if (fs13.existsSync(candidate)) {
3688
4156
  return candidate;
3689
4157
  }
3690
4158
  }
3691
4159
  try {
3692
4160
  const envPath = process.env.PATH ?? "";
3693
- for (const dir of envPath.split(path11.delimiter)) {
3694
- const binPath = path11.join(dir, "typescript-language-server");
3695
- if (fs11.existsSync(binPath)) {
4161
+ for (const dir of envPath.split(path13.delimiter)) {
4162
+ const binPath = path13.join(dir, "typescript-language-server");
4163
+ if (fs13.existsSync(binPath)) {
3696
4164
  return binPath;
3697
4165
  }
3698
4166
  }
@@ -3713,7 +4181,7 @@ var LSPClient = class {
3713
4181
  this.openDocuments.add(filePath);
3714
4182
  let content;
3715
4183
  try {
3716
- content = fs11.readFileSync(filePath, "utf-8");
4184
+ content = fs13.readFileSync(filePath, "utf-8");
3717
4185
  } catch {
3718
4186
  content = "";
3719
4187
  }
@@ -3930,8 +4398,8 @@ var LSPClient = class {
3930
4398
  var lspClient = new LSPClient();
3931
4399
 
3932
4400
  // src/tools/lspTools.ts
3933
- import fs12 from "fs";
3934
- import path12 from "path";
4401
+ import fs14 from "fs";
4402
+ import path14 from "path";
3935
4403
  async function handleFindReferences(params) {
3936
4404
  const { filePath, line, character } = params;
3937
4405
  const validation = validateFilePath(filePath);
@@ -3939,7 +4407,7 @@ async function handleFindReferences(params) {
3939
4407
  return `Error: ${validation.error.message}`;
3940
4408
  }
3941
4409
  const resolvedPath = validation.resolvedPath;
3942
- if (!fs12.existsSync(resolvedPath)) {
4410
+ if (!fs14.existsSync(resolvedPath)) {
3943
4411
  return `Error: File not found: "${filePath}".`;
3944
4412
  }
3945
4413
  if (!lspClient.isAvailable()) {
@@ -3961,7 +4429,7 @@ async function handleFindReferences(params) {
3961
4429
  const enrichedRefs = references.map((ref) => {
3962
4430
  let lineContent = "";
3963
4431
  try {
3964
- const content = fs12.readFileSync(ref.filePath, "utf-8");
4432
+ const content = fs14.readFileSync(ref.filePath, "utf-8");
3965
4433
  const lines2 = content.split("\n");
3966
4434
  lineContent = lines2[ref.line]?.trim() ?? "";
3967
4435
  } catch {
@@ -3977,11 +4445,11 @@ async function handleFindReferences(params) {
3977
4445
  const projectRoot = getProjectRoot();
3978
4446
  const lines = [
3979
4447
  `\u{1F50D} **Find References** \u2014 ${enrichedRefs.length} references found`,
3980
- `\u{1F4CD} File: ${path12.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
4448
+ `\u{1F4CD} File: ${path14.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
3981
4449
  ""
3982
4450
  ];
3983
4451
  for (const [file, refs] of grouped) {
3984
- const relPath = path12.relative(projectRoot, file);
4452
+ const relPath = path14.relative(projectRoot, file);
3985
4453
  lines.push(`**\u{1F4C4} ${relPath}:**`);
3986
4454
  for (const ref of refs) {
3987
4455
  const loc = `L${ref.line + 1}:${ref.character + 1}`;
@@ -4002,7 +4470,7 @@ async function handleGoToDefinition(params) {
4002
4470
  return `Error: ${validation.error.message}`;
4003
4471
  }
4004
4472
  const resolvedPath = validation.resolvedPath;
4005
- if (!fs12.existsSync(resolvedPath)) {
4473
+ if (!fs14.existsSync(resolvedPath)) {
4006
4474
  return `Error: File not found: "${filePath}".`;
4007
4475
  }
4008
4476
  if (!lspClient.isAvailable()) {
@@ -4022,10 +4490,10 @@ async function handleGoToDefinition(params) {
4022
4490
  \u26A0\uFE0F Cannot find definition for symbol at this position.`;
4023
4491
  }
4024
4492
  const projectRoot = getProjectRoot();
4025
- const relPath = path12.relative(projectRoot, definition.filePath);
4493
+ const relPath = path14.relative(projectRoot, definition.filePath);
4026
4494
  let lineContent = "";
4027
4495
  try {
4028
- const content = fs12.readFileSync(definition.filePath, "utf-8");
4496
+ const content = fs14.readFileSync(definition.filePath, "utf-8");
4029
4497
  const lines2 = content.split("\n");
4030
4498
  lineContent = lines2[definition.line]?.trim() ?? "";
4031
4499
  } catch {
@@ -4057,7 +4525,7 @@ async function handleRenameSymbol(params) {
4057
4525
  return `Error: ${validation.error.message}`;
4058
4526
  }
4059
4527
  const resolvedPath = validation.resolvedPath;
4060
- if (!fs12.existsSync(resolvedPath)) {
4528
+ if (!fs14.existsSync(resolvedPath)) {
4061
4529
  return `Error: File not found: "${filePath}".`;
4062
4530
  }
4063
4531
  if (!lspClient.isAvailable()) {
@@ -4086,7 +4554,7 @@ Make sure:
4086
4554
  const fileChanges = [];
4087
4555
  for (const change of result.changes) {
4088
4556
  try {
4089
- const content = fs12.readFileSync(change.filePath, "utf-8");
4557
+ const content = fs14.readFileSync(change.filePath, "utf-8");
4090
4558
  const lines2 = content.split("\n");
4091
4559
  const sortedEdits = [...change.edits].sort((a, b) => {
4092
4560
  if (b.line !== a.line) return b.line - a.line;
@@ -4100,10 +4568,10 @@ Make sure:
4100
4568
  lines2[edit.line] = before + edit.newText + after;
4101
4569
  }
4102
4570
  }
4103
- fs12.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
4571
+ fs14.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
4104
4572
  totalEdits += change.edits.length;
4105
4573
  fileChanges.push({
4106
- filePath: path12.relative(projectRoot, change.filePath),
4574
+ filePath: path14.relative(projectRoot, change.filePath),
4107
4575
  editCount: change.edits.length
4108
4576
  });
4109
4577
  } catch (err) {
@@ -4130,13 +4598,13 @@ async function handleGetTypeInfo(params) {
4130
4598
  return `Error: ${validation.error.message}`;
4131
4599
  }
4132
4600
  const resolvedPath = validation.resolvedPath;
4133
- if (!fs12.existsSync(resolvedPath)) {
4601
+ if (!fs14.existsSync(resolvedPath)) {
4134
4602
  return `Error: File not found: "${filePath}".`;
4135
4603
  }
4136
4604
  if (!lspClient.isAvailable()) {
4137
4605
  sessionMemory.recordToolCall("lsp_query", { action: "type", filePath, line, character, fallback: "line-context" });
4138
4606
  try {
4139
- const fileContent = fs12.readFileSync(resolvedPath, "utf-8");
4607
+ const fileContent = fs14.readFileSync(resolvedPath, "utf-8");
4140
4608
  const fileLines = fileContent.split("\n");
4141
4609
  const targetLine = fileLines[line];
4142
4610
  if (!targetLine) {
@@ -4144,7 +4612,7 @@ async function handleGetTypeInfo(params) {
4144
4612
  }
4145
4613
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4146
4614
  const projectRoot = getProjectRoot();
4147
- const relPath = path12.relative(projectRoot, resolvedPath);
4615
+ const relPath = path14.relative(projectRoot, resolvedPath);
4148
4616
  const contextStart = Math.max(0, line - 3);
4149
4617
  const contextEnd = Math.min(fileLines.length, line + 4);
4150
4618
  const contextLines = [];
@@ -4182,7 +4650,7 @@ async function handleGetTypeInfo(params) {
4182
4650
  \u26A0\uFE0F No type info for this position.`;
4183
4651
  }
4184
4652
  const projectRoot = getProjectRoot();
4185
- const relPath = path12.relative(projectRoot, resolvedPath);
4653
+ const relPath = path14.relative(projectRoot, resolvedPath);
4186
4654
  const lines = [
4187
4655
  `\u{1F4CB} **Type Info** \u2014 \`${relPath}:${line + 1}:${character + 1}\``,
4188
4656
  "",
@@ -4223,7 +4691,7 @@ async function handleLspQuery(params) {
4223
4691
  }
4224
4692
  function extractSymbolAtPosition(filePath, line, character) {
4225
4693
  try {
4226
- const content = fs12.readFileSync(filePath, "utf-8");
4694
+ const content = fs14.readFileSync(filePath, "utf-8");
4227
4695
  const lines = content.split("\n");
4228
4696
  const targetLine = lines[line];
4229
4697
  if (!targetLine) return null;
@@ -4254,7 +4722,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4254
4722
  const regex = new RegExp(`\\b${escapedSymbol}\\b`, "g");
4255
4723
  for (const file of tsFiles.slice(0, 100)) {
4256
4724
  try {
4257
- const content = fs12.readFileSync(file, "utf-8");
4725
+ const content = fs14.readFileSync(file, "utf-8");
4258
4726
  const lines2 = content.split("\n");
4259
4727
  for (let i = 0; i < lines2.length; i++) {
4260
4728
  if (regex.test(lines2[i])) {
@@ -4284,7 +4752,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4284
4752
  ""
4285
4753
  ];
4286
4754
  for (const [file, refs] of grouped) {
4287
- const relPath = path12.relative(projectRoot, file);
4755
+ const relPath = path14.relative(projectRoot, file);
4288
4756
  lines.push(`**\u{1F4C4} ${relPath}:**`);
4289
4757
  for (const ref of refs) {
4290
4758
  lines.push(` \u2514 L${ref.line} \u2014 ${ref.content}`);
@@ -4318,14 +4786,14 @@ async function fallbackGrepDefinition(symbolName) {
4318
4786
  ];
4319
4787
  for (const file of tsFiles) {
4320
4788
  try {
4321
- const content = fs12.readFileSync(file, "utf-8");
4789
+ const content = fs14.readFileSync(file, "utf-8");
4322
4790
  const lines = content.split("\n");
4323
4791
  for (let i = 0; i < lines.length; i++) {
4324
4792
  const trimmed = lines[i].trim();
4325
4793
  for (const pattern of declPatterns) {
4326
4794
  if (pattern.test(trimmed)) {
4327
4795
  const projectRoot = getProjectRoot();
4328
- const relPath = path12.relative(projectRoot, file);
4796
+ const relPath = path14.relative(projectRoot, file);
4329
4797
  return [
4330
4798
  `\u{1F4CD} **Go to Definition** (regex fallback)`,
4331
4799
  `\u{1F4C4} File: \`${relPath}\``,
@@ -4356,7 +4824,7 @@ function registerAllTools(server) {
4356
4824
  "smart_grep",
4357
4825
  "Narrow down to the specific code you need. Locates functions or text patterns \u2014 returns filename, line number, and 3 lines of context.",
4358
4826
  {
4359
- query: z.string().min(1).describe("Regex pattern to search for"),
4827
+ query: z.string({ invalid_type_error: 'smart_grep: "query" must be a string regex pattern.\n\n\u2705 Example: { query: "function handleUser" }' }).min(1).describe("Regex pattern to search for. Example: 'function handleAuth' or 'console\\.log'"),
4360
4828
  targetFolder: z.string().optional().describe("Target folder (default: project root, max depth 3)"),
4361
4829
  maxResults: z.number().min(1).max(100).optional().default(30).describe("Max results to return"),
4362
4830
  extensions: z.array(z.string()).optional().describe("Filter results by file extensions (e.g. ['ts', 'js'])")
@@ -4392,15 +4860,17 @@ function registerAllTools(server) {
4392
4860
  "precise_diff_editor",
4393
4861
  "Edit code with safety net. Search-and-replace with fuzzy fallback + automatic versioned backup. Use action:'rollback' to undo edits.",
4394
4862
  {
4395
- filePath: z.string().min(1).describe("Path to the file to edit"),
4396
- action: z.enum(["rollback"]).optional().describe("Set to 'rollback' to restore from backup"),
4863
+ filePath: z.string({ invalid_type_error: 'precise_diff_editor: "filePath" must be a string path relative to project root.\n\n\u2705 Example: "src/example.ts"' }).min(1).describe("Path to the file to edit"),
4864
+ action: z.enum(["rollback"]).optional().describe("Set to 'rollback' to restore from backup. Only use when restoring from backup."),
4397
4865
  edits: z.array(z.object({
4398
- searchBlock: z.string().min(1).describe("Code block to replace (MUST be exact or fuzzy match)"),
4399
- replaceBlock: z.string().describe("Replacement code block"),
4866
+ searchBlock: z.string({ invalid_type_error: '"searchBlock" must be a string of the exact code to replace' }).min(1).describe("Code block to replace (MUST be exact or fuzzy match)"),
4867
+ replaceBlock: z.string({ invalid_type_error: '"replaceBlock" must be a string of the replacement code' }).describe("Replacement code block"),
4400
4868
  allowMultiple: z.boolean().optional().default(false).describe("Allow multiple replacements"),
4401
4869
  fuzzyThreshold: z.number().min(0).max(1).optional().default(0.85).describe("Fuzzy match threshold (0.0-1.0)")
4402
- })).min(1).max(10).optional().describe("Array of edits (max 10)"),
4403
- dryRun: z.boolean().optional().default(false).describe("Preview changes without writing to disk"),
4870
+ }), {
4871
+ invalid_type_error: 'precise_diff_editor: "edits" must be an ARRAY of { searchBlock, replaceBlock } objects.\n\n\u2705 Correct format:\n{\n edits: [\n { searchBlock: "old code", replaceBlock: "new code" }\n ]\n}'
4872
+ }).min(1).max(10).optional().describe("Array of edits (max 10). Each edit has: searchBlock (code to find), replaceBlock (new code)"),
4873
+ dryRun: z.boolean().optional().default(false).describe("Preview changes without writing to disk. Set dryRun: true to preview first."),
4404
4874
  version: z.union([z.number().min(1), z.literal("list")]).optional().describe("Backup version to restore (1=newest, omit=latest, 'list'=show versions)")
4405
4875
  },
4406
4876
  async (params) => {
@@ -4420,7 +4890,10 @@ function registerAllTools(server) {
4420
4890
  filePath: z.string().min(1).describe("File path to create"),
4421
4891
  content: z.string().describe("File content"),
4422
4892
  instructions: z.string().min(1).describe("Reason for creating the file")
4423
- })).min(1).max(15).describe("Array of files to create (max 15)")
4893
+ }), {
4894
+ invalid_type_error: 'batch_file_writer: "files" must be an ARRAY of objects.\n\n\u2705 Correct format:\n{\n files: [\n { filePath: "src/example.ts", content: "// code", instructions: "reason" }\n ]\n}',
4895
+ required_error: 'batch_file_writer: "files" is required.\n\n\u2705 Correct format:\n{\n files: [\n { filePath: "src/example.ts", content: "// code", instructions: "reason" }\n ]\n}'
4896
+ }).min(1).max(15).describe("Array of files to create (max 15). Example: [{ filePath: 'src/x.ts', content: '...', instructions: 'why' }]")
4424
4897
  },
4425
4898
  async (params) => {
4426
4899
  try {
@@ -4435,9 +4908,9 @@ function registerAllTools(server) {
4435
4908
  "execute_safe_test",
4436
4909
  "Run tests, lint, or typecheck with timeout protection and circuit breaker. Use after every edit to verify you didn't break anything.",
4437
4910
  {
4438
- task: z.enum(["test", "build", "lint", "typecheck", "custom"]).describe("Task to execute"),
4439
- customCommand: z.string().optional().describe("Custom command (only if task='custom')"),
4440
- timeout: z.number().min(5).max(180).optional().default(60).describe("Timeout in seconds (default: 60s, max: 180s)")
4911
+ task: z.enum(["test", "build", "lint", "typecheck", "custom"]).describe("Task to execute: test, build, lint, typecheck, or custom"),
4912
+ customCommand: z.string({ invalid_type_error: '"customCommand" must be a string command like "npm run my-script"' }).optional().describe("Custom command (required only if task='custom')"),
4913
+ timeout: z.number({ invalid_type_error: '"timeout" must be a number in seconds (5-180)' }).min(5).max(180).optional().default(60).describe("Timeout in seconds (default: 60s, max: 180s)")
4441
4914
  },
4442
4915
  async (params) => {
4443
4916
  try {
@@ -4485,7 +4958,7 @@ function registerAllTools(server) {
4485
4958
  "get_session_memory",
4486
4959
  "Check what you've done this session: modified files, unresolved failures, tool history, memories. Accepts optional topic to load a specific memory file.",
4487
4960
  {
4488
- topic: z.enum(MEMORY_TOPICS).optional().describe("Load a specific memory topic (decisions, glossary, architecture, conventions, known-issues)")
4961
+ topic: z.enum(MEMORY_TOPICS).optional().describe("Load a specific memory topic: decisions, glossary, architecture, conventions, known-issues. Example: { topic: 'decisions' }")
4489
4962
  },
4490
4963
  async (params) => {
4491
4964
  try {
@@ -4500,11 +4973,11 @@ function registerAllTools(server) {
4500
4973
  "lsp_query",
4501
4974
  "Jump to definition, find references, inspect types, or rename symbols. Uses TypeScript Language Server \u2014 falls back to regex when LSP is unavailable.",
4502
4975
  {
4503
- filePath: z.string().min(1).describe("Path to the file containing the symbol"),
4504
- line: z.number().min(0).describe("Line number (0-indexed)"),
4505
- character: z.number().min(0).describe("Character position (0-indexed)"),
4506
- action: z.enum(["def", "refs", "type", "rename"]).describe("Action: def/go to definition, refs/find references, type/get type info, rename/rename symbol"),
4507
- newName: z.string().optional().describe("New name (required for action:'rename')")
4976
+ filePath: z.string({ invalid_type_error: 'lsp_query: "filePath" must be a string path.\n\n\u2705 Example: { filePath: "src/index.ts", line: 10, character: 5, action: "def" }' }).min(1).describe("Path to the file containing the symbol"),
4977
+ line: z.number({ invalid_type_error: '"line" must be a number (0-indexed). Line 0 = first line.' }).min(0).describe("Line number (0-indexed)"),
4978
+ character: z.number({ invalid_type_error: '"character" must be a number (0-indexed). Position within the line.' }).min(0).describe("Character position (0-indexed)"),
4979
+ action: z.enum(["def", "refs", "type", "rename"]).describe("Action: def=go to definition, refs=find references, type=get type info, rename=rename symbol"),
4980
+ newName: z.string().optional().describe("New name (required for action:'rename'). Example: { action: 'rename', newName: 'newFunctionName' }")
4508
4981
  },
4509
4982
  async (params) => {
4510
4983
  try {
@@ -4616,6 +5089,22 @@ function registerAllTools(server) {
4616
5089
  }
4617
5090
  }
4618
5091
  );
5092
+ server.tool(
5093
+ "kuma_guard",
5094
+ "Context safety net. Checks for anti-patterns (script patching, bash grep), loops, drift (edits without tests, unresolved failures). Run this after every few edits to stay on track.",
5095
+ {
5096
+ check: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().default("all").describe("Check type: all=everything, anti-pattern=script/grep detection, loop=loop detection, drift=edit vs test balance"),
5097
+ goal: z.string({ invalid_type_error: 'kuma_guard: "goal" must be a string describing what you are working on.\n\n\u2705 Example: { goal: "refactor auth module" }' }).optional().describe("Optional goal to check against. Example: 'refactor auth module'")
5098
+ },
5099
+ async (params) => {
5100
+ try {
5101
+ const result = await handleKumaGuard(params);
5102
+ return { content: [{ type: "text", text: result }] };
5103
+ } catch (err) {
5104
+ return { content: [{ type: "text", text: `Error in kuma_guard: ${err}` }], isError: true };
5105
+ }
5106
+ }
5107
+ );
4619
5108
  server.tool(
4620
5109
  "static_analysis",
4621
5110
  "Runs available linters/checkers (ESLint, TypeScript, Prettier, Ruff) and parses output into structured results. Auto-detects tools from project config.",
@@ -4634,7 +5123,827 @@ function registerAllTools(server) {
4634
5123
  }
4635
5124
  }
4636
5125
  );
4637
- console.error("[Manifest] Registered 16 tools.");
5126
+ server.tool(
5127
+ "kuma_context",
5128
+ "Context snapshot manager. Save a snapshot of current project state (modified files, errors, git diff) or list previous snapshots. Run this before risky operations to have a restore point.",
5129
+ {
5130
+ action: z.enum(["save", "list"]).describe("Action: save=create a snapshot, list=show all snapshots"),
5131
+ goal: z.string().optional().describe("Optional goal to associate with the snapshot")
5132
+ },
5133
+ async (params) => {
5134
+ try {
5135
+ const result = await handleKumaContext(params);
5136
+ return { content: [{ type: "text", text: result }] };
5137
+ } catch (err) {
5138
+ return { content: [{ type: "text", text: `Error in kuma_context: ${err}` }], isError: true };
5139
+ }
5140
+ }
5141
+ );
5142
+ console.error("[Manifest] Registered 17 tools.");
5143
+ }
5144
+
5145
+ // src/cli/init.ts
5146
+ import fs15 from "fs";
5147
+ import path15 from "path";
5148
+ var ALL_CONFIG_TYPES = [
5149
+ "claude",
5150
+ "cursor",
5151
+ "windsurf",
5152
+ "copilot",
5153
+ "cline",
5154
+ "aider",
5155
+ "antigravity",
5156
+ "opencode",
5157
+ "codex",
5158
+ "qwen",
5159
+ "kiro",
5160
+ "openclaw",
5161
+ "codewhale"
5162
+ ];
5163
+ var CONFIG_LABELS = {
5164
+ claude: "Claude Code (CLAUDE.md / plugin)",
5165
+ cursor: "Cursor (.cursor/rules/*.mdc)",
5166
+ windsurf: "Windsurf (.windsurfrules)",
5167
+ copilot: "GitHub Copilot Editor (AGENTS.md + Skill)",
5168
+ cline: "Cline (.clinerules/*.md)",
5169
+ aider: "Aider (CONVENTIONS.md via .aider.conf.yml)",
5170
+ antigravity: "Antigravity CLI (.agents/skills/)",
5171
+ opencode: "OpenCode (opencode.json)",
5172
+ codex: "Codex CLI (AGENTS.md + .codex/config.toml)",
5173
+ qwen: "Qwen Code (AGENTS.md + settings.json)",
5174
+ kiro: "Kiro (.kiro/steering/*.md)",
5175
+ openclaw: "OpenClaw (skills/)",
5176
+ codewhale: "CodeWhale (skills/ + .codewhale/mcp.json)"
5177
+ };
5178
+ function configFilePath(type) {
5179
+ switch (type) {
5180
+ case "claude":
5181
+ return "CLAUDE.md";
5182
+ case "cursor":
5183
+ return ".cursor/rules/kuma.mdc";
5184
+ case "windsurf":
5185
+ return ".windsurfrules";
5186
+ case "copilot":
5187
+ return "AGENTS.md";
5188
+ case "cline":
5189
+ return ".clinerules/kuma.md";
5190
+ case "aider":
5191
+ return "CONVENTIONS.md";
5192
+ case "antigravity":
5193
+ return ".agents/skills/kuma/SKILL.md";
5194
+ case "opencode":
5195
+ return "opencode.json";
5196
+ case "codex":
5197
+ return "AGENTS.md";
5198
+ case "qwen":
5199
+ return "AGENTS.md";
5200
+ case "kiro":
5201
+ return ".kiro/steering/kuma.md";
5202
+ case "openclaw":
5203
+ return "skills/kuma/SKILL.md";
5204
+ case "codewhale":
5205
+ return "skills/kuma/SKILL.md";
5206
+ }
5207
+ }
5208
+ var CORE_RULES = [
5209
+ "## AI Agent Usage Guidelines",
5210
+ "",
5211
+ "Kuma MCP tools are available. Use them correctly:",
5212
+ "",
5213
+ "### Code Search",
5214
+ "- Use the **smart_grep** tool to search code - NOT bash grep/ripgrep manually",
5215
+ "- smart_grep returns line numbers + context, caches results, respects .gitignore",
5216
+ `- **Example:** smart_grep({ query: "function handleAuth", extensions: ['ts'] })`,
5217
+ "",
5218
+ "### Reading Code",
5219
+ "- Use the **smart_file_picker** tool to read files with smart chunking",
5220
+ "- For large files, use startLine/endLine to read specific ranges",
5221
+ '- **Example:** smart_file_picker({ filePath: "src/index.ts", chunkStrategy: "outline" })',
5222
+ '- **Example:** smart_file_picker({ filePath: "src/index.ts", startLine: 10, endLine: 30 })',
5223
+ "",
5224
+ "### Editing Code",
5225
+ "- Use the **precise_diff_editor** tool to edit files (fuzzy matching + auto-backup)",
5226
+ "- DO NOT create Python/Node scripts to patch files; use precise_diff_editor directly",
5227
+ "- DO NOT use bash sed/cat/awk to modify source files",
5228
+ '- **Example:** precise_diff_editor({ filePath: "src/app.ts", edits: [{ searchBlock: "old code", replaceBlock: "new code" }] })',
5229
+ '- **Example:** precise_diff_editor({ filePath: "src/app.ts", dryRun: true, edits: [...] })',
5230
+ '- **Example:** precise_diff_editor({ filePath: "src/app.ts", action: "rollback" })',
5231
+ "",
5232
+ "### Creating Files",
5233
+ "- Use the **batch_file_writer** tool to create new files (up to 15 at once)",
5234
+ '- **Example:** batch_file_writer({ files: [{ filePath: "src/util.ts", content: "// code", instructions: "reason for creating" }] })',
5235
+ "",
5236
+ "### Running Tasks",
5237
+ "- Use the **execute_safe_test** tool for test/build/lint/typecheck",
5238
+ "- Always run typecheck after editing TypeScript files",
5239
+ '- **Example:** execute_safe_test({ task: "typecheck" })',
5240
+ '- **Example:** execute_safe_test({ task: "custom", customCommand: "npm run lint" })',
5241
+ "",
5242
+ "### Code Review",
5243
+ "- Use the **code_reviewer** tool after changes",
5244
+ "- Supports focus: correctness, security, performance, over-engineering",
5245
+ '- **Example:** code_reviewer({ focus: "security" })',
5246
+ '- **Example:** code_reviewer({ files: ["src/auth.ts"], format: "json" })',
5247
+ "",
5248
+ "### Git Operations",
5249
+ "- Use the **git_diff** tool for structured diff output",
5250
+ "- Use the **git_log** tool for commit history",
5251
+ "- **Example:** git_log({ maxCount: 5 })",
5252
+ "- **Example:** git_diff({ staged: true })",
5253
+ "",
5254
+ "### Session Awareness",
5255
+ "- Use the **kuma_reflect** tool to check on-track/drift/loops",
5256
+ "- Use the **kuma_guard** tool for deeper safety checks (anti-patterns, auto-detection)",
5257
+ "- Use the **get_session_memory** tool to recall session state",
5258
+ '- **Example:** kuma_reflect({ goal: "refactor auth" })',
5259
+ '- **Example:** kuma_guard({ check: "all", goal: "refactor auth" })',
5260
+ "",
5261
+ "### LSP / Code Intelligence",
5262
+ "- Use the **lsp_query** tool for go-to-definition, find references, type info",
5263
+ '- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "def" })',
5264
+ '- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "refs" })',
5265
+ "",
5266
+ "### Static Analysis",
5267
+ "- Use the **static_analysis** tool to run ESLint/TSC/Prettier/Ruff",
5268
+ '- **Example:** static_analysis({ tool: "eslint", autoFix: true })',
5269
+ "",
5270
+ "### Project Structure",
5271
+ "- Use the **project_structure** tool to see project layout",
5272
+ "- **Example:** project_structure({ depth: 2, folderOnly: true })",
5273
+ "",
5274
+ "### Write Memory",
5275
+ "- Use the **write_memory** tool to persist decisions and glossary",
5276
+ '- **Example:** write_memory({ topic: "decisions", content: "## Reason for using X" })',
5277
+ "",
5278
+ "### General Rules",
5279
+ "- When you error, READ the error carefully before acting",
5280
+ "- After 3+ edits without running tests, stop and verify",
5281
+ "- If a tool fails, check the message - don't retry blindly",
5282
+ "- Detect conventions first with the **project_conventions** tool",
5283
+ "- **Example:** project_conventions({ forceRescan: true })"
5284
+ ].join("\n");
5285
+ var KUMA_CORE_INSTRUCTIONS = CORE_RULES;
5286
+ function claudeTemplate() {
5287
+ return [
5288
+ "# Kuma AI Agent Guidelines",
5289
+ "",
5290
+ KUMA_CORE_INSTRUCTIONS,
5291
+ "",
5292
+ "## Workflow Pipeline",
5293
+ "",
5294
+ "For best results:",
5295
+ "1. **project_conventions** - detect stack",
5296
+ "2. **smart_grep** / **smart_file_picker** - understand code",
5297
+ "3. **precise_diff_editor** / **batch_file_writer** - make changes",
5298
+ "4. **execute_safe_test** - verify (typecheck + test)",
5299
+ "5. **code_reviewer** - review changes"
5300
+ ].join("\n");
5301
+ }
5302
+ function cursorRulesTemplate() {
5303
+ return [
5304
+ "---",
5305
+ "description: Kuma MCP tool usage rules for AI coding agents",
5306
+ "alwaysApply: true",
5307
+ "---",
5308
+ "",
5309
+ "You are an expert engineer. Kuma MCP tools are available.",
5310
+ "",
5311
+ "## Critical Rules",
5312
+ "",
5313
+ KUMA_CORE_INSTRUCTIONS,
5314
+ "",
5315
+ "## NEVER",
5316
+ "- Never create Python/Node scripts to patch code",
5317
+ "- Never use bash sed/cat/awk to edit source files",
5318
+ "- Never run git push/git commit through bash"
5319
+ ].join("\n");
5320
+ }
5321
+ function windsurfRulesTemplate() {
5322
+ return [
5323
+ "# Windsurf Cascade Rules with Kuma",
5324
+ "",
5325
+ KUMA_CORE_INSTRUCTIONS
5326
+ ].join("\n");
5327
+ }
5328
+ function copilotTemplate() {
5329
+ return [
5330
+ "## GitHub Copilot Editor",
5331
+ "",
5332
+ "Kuma MCP tools are available. Use them correctly:",
5333
+ "",
5334
+ KUMA_CORE_INSTRUCTIONS,
5335
+ "",
5336
+ "### Copilot Editor-Specific",
5337
+ "- Copilot Editor reads AGENTS.md at project root for persistent instructions",
5338
+ "- Configure MCP servers via VS Code settings (cmd+shift+P \u2192 Developer: Reload Window after adding Kuma)",
5339
+ "- Use kuma_guard periodically to check for anti-patterns"
5340
+ ].join("\n");
5341
+ }
5342
+ function clineRulesTemplate() {
5343
+ return [
5344
+ "---",
5345
+ "description: Kuma MCP tool usage rules for AI coding agents",
5346
+ "paths:",
5347
+ ' - "*"',
5348
+ "---",
5349
+ "",
5350
+ KUMA_CORE_INSTRUCTIONS
5351
+ ].join("\n");
5352
+ }
5353
+ function aiderTemplate() {
5354
+ return [
5355
+ "# Kuma MCP - Aider Coding Conventions",
5356
+ "",
5357
+ "These conventions are loaded by Aider via the `read:` field in .aider.conf.yml",
5358
+ "",
5359
+ KUMA_CORE_INSTRUCTIONS
5360
+ ].join("\n");
5361
+ }
5362
+ function opencodeTemplate() {
5363
+ const config = {
5364
+ mcp: {
5365
+ kuma: {
5366
+ type: "local",
5367
+ command: ["npx", "-y", "@plumpslabs/kuma"],
5368
+ enabled: true
5369
+ }
5370
+ },
5371
+ instructions: ["CLAUDE.md"]
5372
+ };
5373
+ const header = [
5374
+ "// Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5375
+ "// OpenCode config with Kuma MCP tools. Edit opencode.json to customize.",
5376
+ ""
5377
+ ].join("\n");
5378
+ return header + JSON.stringify(config, null, 2) + "\n";
5379
+ }
5380
+ function codexTemplate() {
5381
+ return [
5382
+ "## Codex CLI (OpenAI)",
5383
+ "",
5384
+ "Kuma MCP tools are available. Use them correctly:",
5385
+ "",
5386
+ KUMA_CORE_INSTRUCTIONS,
5387
+ "",
5388
+ "### Codex-Specific",
5389
+ "- Codex uses cascading AGENTS.md files (global ~/.codex/AGENTS.md -> project AGENTS.md)",
5390
+ "- MCP config is in .codex/config.toml (auto-generated by kuma init)",
5391
+ "- Use kuma_guard periodically to check for anti-patterns"
5392
+ ].join("\n");
5393
+ }
5394
+ function codexConfigTomlTemplate() {
5395
+ return [
5396
+ "# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5397
+ "# Kuma MCP server config for Codex CLI",
5398
+ "",
5399
+ "[mcp_servers.kuma]",
5400
+ 'command = "npx"',
5401
+ 'args = ["-y", "@plumpslabs/kuma"]',
5402
+ ""
5403
+ ].join("\n");
5404
+ }
5405
+ function qwenTemplate() {
5406
+ return [
5407
+ "## Qwen Code",
5408
+ "",
5409
+ "Kuma MCP tools are available. Use them correctly:",
5410
+ "",
5411
+ KUMA_CORE_INSTRUCTIONS,
5412
+ "",
5413
+ "### Qwen-Specific",
5414
+ "- Qwen reads AGENTS.md at project root for persistent instructions",
5415
+ "- MCP config is in settings.json (auto-generated by kuma init)",
5416
+ "- Use kuma_guard periodically to check for anti-patterns"
5417
+ ].join("\n");
5418
+ }
5419
+ function qwenSettingsTemplate() {
5420
+ const config = {
5421
+ mcpServers: {
5422
+ kuma: {
5423
+ command: "npx",
5424
+ args: ["-y", "@plumpslabs/kuma"],
5425
+ env: {}
5426
+ }
5427
+ }
5428
+ };
5429
+ return JSON.stringify(config, null, 2) + "\n";
5430
+ }
5431
+ function kiroRulesTemplate() {
5432
+ return [
5433
+ "---",
5434
+ "name: kuma-mcp",
5435
+ "description: Kuma safety toolkit - use smart_grep for search, precise_diff_editor for edits",
5436
+ "inclusion: always",
5437
+ "---",
5438
+ "",
5439
+ "# Kuma MCP - Kiro Steering",
5440
+ "",
5441
+ KUMA_CORE_INSTRUCTIONS,
5442
+ "",
5443
+ "## Kiro-Specific",
5444
+ "- Kiro reads steering files from .kiro/steering/ for project instructions",
5445
+ "- Configure MCP servers via IDE settings or global Kiro config",
5446
+ "- Use kuma_guard periodically to check for anti-patterns"
5447
+ ].join("\n");
5448
+ }
5449
+ function openclawSkillTemplate() {
5450
+ return [
5451
+ "---",
5452
+ "name: kuma-mcp",
5453
+ "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5454
+ "---",
5455
+ "",
5456
+ "# Kuma MCP - OpenClaw Skill",
5457
+ "",
5458
+ KUMA_CORE_INSTRUCTIONS,
5459
+ "",
5460
+ "## OpenClaw-Specific",
5461
+ "- OpenClaw loads skills/ from workspace root or ~/.openclaw/skills for global",
5462
+ "- Configure Kuma MCP server via ~/.openclaw/openclaw.json or agents standard",
5463
+ "- Use kuma_guard periodically to check for anti-patterns",
5464
+ "",
5465
+ "## Verification",
5466
+ "- After edits, run execute_safe_test to verify no breakage",
5467
+ "- Use code_reviewer for correctness/security review",
5468
+ "- Check kuma_reflect to confirm on-track"
5469
+ ].join("\n");
5470
+ }
5471
+ function codewhaleTemplate() {
5472
+ return [
5473
+ "---",
5474
+ "name: kuma-mcp",
5475
+ "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5476
+ "---",
5477
+ "",
5478
+ "# Kuma MCP - CodeWhale Skill",
5479
+ "",
5480
+ KUMA_CORE_INSTRUCTIONS,
5481
+ "",
5482
+ "## CodeWhale-Specific",
5483
+ "- CodeWhale loads SKILL.md from skills/ (workspace-local), .agents/skills/, or ~/.codewhale/skills/",
5484
+ "- MCP config is in ~/.codewhale/mcp.json or ~/.deepseek/mcp.json",
5485
+ "- Use kuma_guard periodically to check for anti-patterns",
5486
+ "",
5487
+ "## Verification",
5488
+ "- After edits, run execute_safe_test to verify no breakage",
5489
+ "- Use code_reviewer for correctness/security review",
5490
+ "- Check kuma_reflect to confirm on-track"
5491
+ ].join("\n");
5492
+ }
5493
+ function antigravitySkillTemplate() {
5494
+ return [
5495
+ "---",
5496
+ "name: kuma-mcp",
5497
+ "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5498
+ "---",
5499
+ "",
5500
+ "# Kuma MCP - Antigravity Skill",
5501
+ "",
5502
+ KUMA_CORE_INSTRUCTIONS,
5503
+ "",
5504
+ "## Verification",
5505
+ "- After edits, run execute_safe_test to verify no breakage",
5506
+ "- Use code_reviewer for correctness/security review",
5507
+ "- Check kuma_reflect to confirm on-track"
5508
+ ].join("\n");
5509
+ }
5510
+ function antigravityMcpConfigTemplate() {
5511
+ const config = {
5512
+ mcpServers: {
5513
+ kuma: {
5514
+ command: "npx",
5515
+ args: ["-y", "@plumpslabs/kuma"],
5516
+ env: {}
5517
+ }
5518
+ }
5519
+ };
5520
+ return JSON.stringify(config, null, 2) + "\n";
5521
+ }
5522
+ var TEMPLATES = {
5523
+ claude: claudeTemplate,
5524
+ cursor: cursorRulesTemplate,
5525
+ windsurf: windsurfRulesTemplate,
5526
+ copilot: copilotTemplate,
5527
+ cline: clineRulesTemplate,
5528
+ aider: aiderTemplate,
5529
+ antigravity: antigravitySkillTemplate,
5530
+ opencode: opencodeTemplate,
5531
+ codex: codexTemplate,
5532
+ qwen: qwenTemplate,
5533
+ kiro: kiroRulesTemplate,
5534
+ openclaw: openclawSkillTemplate,
5535
+ codewhale: codewhaleTemplate
5536
+ };
5537
+ var APPEND_SEPARATOR = "\n\n---\n_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_\n\n";
5538
+ function handleOpencodeSecondary(root, results) {
5539
+ const claudePath = path15.resolve(root, "CLAUDE.md");
5540
+ if (!fs15.existsSync(claudePath)) {
5541
+ try {
5542
+ fs15.writeFileSync(claudePath, [
5543
+ "# Kuma MCP - OpenCode Instructions",
5544
+ "",
5545
+ KUMA_CORE_INSTRUCTIONS
5546
+ ].join("\n"), "utf-8");
5547
+ results.push({ type: "opencode", filePath: "CLAUDE.md", action: "created" });
5548
+ } catch (err) {
5549
+ results.push({
5550
+ type: "opencode",
5551
+ filePath: "CLAUDE.md",
5552
+ action: "error",
5553
+ error: err instanceof Error ? err.message : String(err)
5554
+ });
5555
+ }
5556
+ }
5557
+ }
5558
+ function handleCodexSecondary(root, results) {
5559
+ const tomlPath = path15.resolve(root, ".codex/config.toml");
5560
+ if (results.some((r) => r.filePath === ".codex/config.toml")) return;
5561
+ try {
5562
+ const dir = path15.dirname(tomlPath);
5563
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
5564
+ if (fs15.existsSync(tomlPath)) {
5565
+ const existingContent = fs15.readFileSync(tomlPath, "utf-8");
5566
+ if (existingContent.includes("kuma")) {
5567
+ results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
5568
+ return;
5569
+ }
5570
+ fs15.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
5571
+ results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
5572
+ } else {
5573
+ fs15.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
5574
+ results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
5575
+ }
5576
+ } catch (err) {
5577
+ results.push({
5578
+ type: "codex",
5579
+ filePath: ".codex/config.toml",
5580
+ action: "error",
5581
+ error: err instanceof Error ? err.message : String(err)
5582
+ });
5583
+ }
5584
+ }
5585
+ function handleQwenSecondary(root, results) {
5586
+ const settingsPath = path15.resolve(root, "settings.json");
5587
+ if (results.some((r) => r.filePath === "settings.json")) return;
5588
+ try {
5589
+ if (fs15.existsSync(settingsPath)) {
5590
+ const existingContent = fs15.readFileSync(settingsPath, "utf-8");
5591
+ if (existingContent.includes("kuma")) {
5592
+ if (!existingContent.includes("_Generated by Kuma MCP_")) {
5593
+ try {
5594
+ const parsed = JSON.parse(existingContent);
5595
+ parsed.mcpServers = parsed.mcpServers || {};
5596
+ if (!parsed.mcpServers.kuma) {
5597
+ parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5598
+ fs15.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5599
+ results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
5600
+ return;
5601
+ }
5602
+ } catch {
5603
+ }
5604
+ }
5605
+ results.push({ type: "qwen", filePath: "settings.json", action: "skipped" });
5606
+ return;
5607
+ }
5608
+ try {
5609
+ const parsed = JSON.parse(existingContent);
5610
+ parsed.mcpServers = parsed.mcpServers || {};
5611
+ if (!parsed.mcpServers.kuma) {
5612
+ parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5613
+ fs15.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5614
+ results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
5615
+ }
5616
+ } catch {
5617
+ }
5618
+ } else {
5619
+ fs15.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
5620
+ results.push({ type: "qwen", filePath: "settings.json", action: "created" });
5621
+ }
5622
+ } catch (err) {
5623
+ results.push({
5624
+ type: "qwen",
5625
+ filePath: "settings.json",
5626
+ action: "error",
5627
+ error: err instanceof Error ? err.message : String(err)
5628
+ });
5629
+ }
5630
+ }
5631
+ var AGENTS_MD_TYPES = ["codex", "qwen", "copilot"];
5632
+ function getAgentsMdHeader() {
5633
+ return [
5634
+ "# Kuma MCP - Combined Agent Instructions",
5635
+ "",
5636
+ "This file contains instructions for AI coding agents that read AGENTS.md.",
5637
+ "Each section applies to a specific agent. Unused sections can be safely removed.",
5638
+ "",
5639
+ "---",
5640
+ "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_",
5641
+ ""
5642
+ ].join("\n");
5643
+ }
5644
+ function getCombinedAgentsMd(selectedTypes) {
5645
+ const sections = [getAgentsMdHeader()];
5646
+ const agentOrder = ["codex", "qwen", "copilot"];
5647
+ for (const t of agentOrder) {
5648
+ if (selectedTypes.has(t)) {
5649
+ sections.push(TEMPLATES[t]());
5650
+ }
5651
+ }
5652
+ return sections.join("\n\n---\n\n");
5653
+ }
5654
+ function handleAntigravityMcpConfig(root, results) {
5655
+ const mcpPath = path15.resolve(root, ".agents/mcp_config.json");
5656
+ if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
5657
+ try {
5658
+ const mcpDir = path15.dirname(mcpPath);
5659
+ if (fs15.existsSync(mcpPath)) {
5660
+ const existingContent = fs15.readFileSync(mcpPath, "utf-8");
5661
+ if (existingContent.includes("kuma")) {
5662
+ if (!existingContent.includes("_Generated by Kuma MCP_")) {
5663
+ const trimmed = existingContent.trimEnd();
5664
+ if (trimmed.endsWith("}")) {
5665
+ const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
5666
+ fs15.writeFileSync(mcpPath, updated, "utf-8");
5667
+ results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
5668
+ }
5669
+ }
5670
+ return;
5671
+ }
5672
+ const parsed = JSON.parse(existingContent);
5673
+ parsed.mcpServers = parsed.mcpServers || {};
5674
+ parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5675
+ fs15.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5676
+ results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
5677
+ } else {
5678
+ if (!fs15.existsSync(mcpDir)) fs15.mkdirSync(mcpDir, { recursive: true });
5679
+ fs15.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
5680
+ results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
5681
+ }
5682
+ } catch (err) {
5683
+ results.push({
5684
+ type: "antigravity",
5685
+ filePath: ".agents/mcp_config.json",
5686
+ action: "error",
5687
+ error: err instanceof Error ? err.message : String(err)
5688
+ });
5689
+ }
5690
+ }
5691
+ function handleAiderSecondary(root, results) {
5692
+ const ymlPath = path15.resolve(root, ".aider.conf.yml");
5693
+ if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
5694
+ try {
5695
+ const conventionsRef = "read: CONVENTIONS.md";
5696
+ if (fs15.existsSync(ymlPath)) {
5697
+ const existingContent = fs15.readFileSync(ymlPath, "utf-8");
5698
+ if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
5699
+ results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
5700
+ return;
5701
+ }
5702
+ const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
5703
+ fs15.writeFileSync(ymlPath, newContent, "utf-8");
5704
+ results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
5705
+ } else {
5706
+ const content = [
5707
+ "# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5708
+ "# Aider will read CONVENTIONS.md for coding conventions",
5709
+ "",
5710
+ conventionsRef,
5711
+ ""
5712
+ ].join("\n");
5713
+ fs15.writeFileSync(ymlPath, content, "utf-8");
5714
+ results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
5715
+ }
5716
+ } catch (err) {
5717
+ results.push({
5718
+ type: "aider",
5719
+ filePath: ".aider.conf.yml",
5720
+ action: "error",
5721
+ error: err instanceof Error ? err.message : String(err)
5722
+ });
5723
+ }
5724
+ }
5725
+ function handleCopilotSecondary(root, results) {
5726
+ const skillPath = path15.resolve(root, ".github/skills/kuma/SKILL.md");
5727
+ if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
5728
+ try {
5729
+ const dir = path15.dirname(skillPath);
5730
+ const content = [
5731
+ "---",
5732
+ "name: kuma-mcp",
5733
+ "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5734
+ "---",
5735
+ "",
5736
+ "# Kuma MCP - Copilot Editor Skill",
5737
+ "",
5738
+ KUMA_CORE_INSTRUCTIONS
5739
+ ].join("\n");
5740
+ if (fs15.existsSync(skillPath)) {
5741
+ const existingContent = fs15.readFileSync(skillPath, "utf-8");
5742
+ if (existingContent.includes("kuma")) {
5743
+ results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
5744
+ return;
5745
+ }
5746
+ fs15.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
5747
+ results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
5748
+ } else {
5749
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
5750
+ fs15.writeFileSync(skillPath, content, "utf-8");
5751
+ results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
5752
+ }
5753
+ } catch (err) {
5754
+ results.push({
5755
+ type: "copilot",
5756
+ filePath: ".github/skills/kuma/SKILL.md",
5757
+ action: "error",
5758
+ error: err instanceof Error ? err.message : String(err)
5759
+ });
5760
+ }
5761
+ }
5762
+ function handleOpenclawSecondary(root, results) {
5763
+ const mcpPath = path15.resolve(root, ".agents/mcp_config.json");
5764
+ if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
5765
+ try {
5766
+ const dir = path15.dirname(mcpPath);
5767
+ if (fs15.existsSync(mcpPath)) {
5768
+ const existingContent = fs15.readFileSync(mcpPath, "utf-8");
5769
+ if (existingContent.includes("kuma")) return;
5770
+ const parsed = JSON.parse(existingContent);
5771
+ parsed.mcpServers = parsed.mcpServers || {};
5772
+ parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5773
+ fs15.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5774
+ results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
5775
+ } else {
5776
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
5777
+ fs15.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
5778
+ results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
5779
+ }
5780
+ } catch (err) {
5781
+ results.push({
5782
+ type: "openclaw",
5783
+ filePath: ".agents/mcp_config.json",
5784
+ action: "error",
5785
+ error: err instanceof Error ? err.message : String(err)
5786
+ });
5787
+ }
5788
+ }
5789
+ function handleCodewhaleSecondary(root, results) {
5790
+ const mcpPath = path15.resolve(root, ".codewhale/mcp.json");
5791
+ if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
5792
+ try {
5793
+ const dir = path15.dirname(mcpPath);
5794
+ if (fs15.existsSync(mcpPath)) {
5795
+ const existingContent = fs15.readFileSync(mcpPath, "utf-8");
5796
+ if (existingContent.includes("kuma")) return;
5797
+ const parsed = JSON.parse(existingContent);
5798
+ parsed.mcpServers = parsed.mcpServers || {};
5799
+ parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5800
+ fs15.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5801
+ results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
5802
+ } else {
5803
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
5804
+ const config = {
5805
+ mcpServers: {
5806
+ kuma: {
5807
+ command: "npx",
5808
+ args: ["-y", "@plumpslabs/kuma"],
5809
+ env: {}
5810
+ }
5811
+ }
5812
+ };
5813
+ fs15.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
5814
+ results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
5815
+ }
5816
+ } catch (err) {
5817
+ results.push({
5818
+ type: "codewhale",
5819
+ filePath: ".codewhale/mcp.json",
5820
+ action: "error",
5821
+ error: err instanceof Error ? err.message : String(err)
5822
+ });
5823
+ }
5824
+ }
5825
+ function runInit(types, projectRoot) {
5826
+ const root = projectRoot ?? getProjectRoot();
5827
+ const selected = types.length > 0 ? types : ALL_CONFIG_TYPES;
5828
+ const results = [];
5829
+ const selectedSet = new Set(selected);
5830
+ const agentsMdSelected = AGENTS_MD_TYPES.filter((t) => selectedSet.has(t));
5831
+ let agentsMdHandled = false;
5832
+ for (const type of selected) {
5833
+ const relativePath = configFilePath(type);
5834
+ const fullPath = path15.resolve(root, relativePath);
5835
+ const getTemplate = TEMPLATES[type];
5836
+ try {
5837
+ if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
5838
+ agentsMdHandled = true;
5839
+ const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
5840
+ if (fs15.existsSync(fullPath)) {
5841
+ const existingContent = fs15.readFileSync(fullPath, "utf-8");
5842
+ if (existingContent.includes("_Generated by Kuma MCP_")) {
5843
+ results.push({ type, filePath: relativePath, action: "skipped" });
5844
+ } else {
5845
+ fs15.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
5846
+ results.push({ type, filePath: relativePath, action: "appended" });
5847
+ }
5848
+ } else {
5849
+ const dir = path15.dirname(fullPath);
5850
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
5851
+ fs15.writeFileSync(fullPath, combinedContent, "utf-8");
5852
+ results.push({ type, filePath: relativePath, action: "created" });
5853
+ }
5854
+ if (selectedSet.has("codex")) handleCodexSecondary(root, results);
5855
+ if (selectedSet.has("qwen")) handleQwenSecondary(root, results);
5856
+ if (selectedSet.has("copilot")) handleCopilotSecondary(root, results);
5857
+ } else if (AGENTS_MD_TYPES.includes(type) && agentsMdHandled) {
5858
+ results.push({ type, filePath: relativePath, action: "skipped" });
5859
+ continue;
5860
+ } else {
5861
+ const template = getTemplate();
5862
+ if (fs15.existsSync(fullPath)) {
5863
+ const existingContent = fs15.readFileSync(fullPath, "utf-8");
5864
+ if (existingContent.includes("_Generated by Kuma MCP_")) {
5865
+ if (type === "antigravity") {
5866
+ handleAntigravityMcpConfig(root, results);
5867
+ } else if (type === "openclaw") {
5868
+ handleOpenclawSecondary(root, results);
5869
+ } else if (type === "codewhale") {
5870
+ handleCodewhaleSecondary(root, results);
5871
+ }
5872
+ results.push({ type, filePath: relativePath, action: "skipped" });
5873
+ continue;
5874
+ }
5875
+ const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
5876
+ fs15.writeFileSync(fullPath, newContent, "utf-8");
5877
+ results.push({ type, filePath: relativePath, action: "appended" });
5878
+ } else {
5879
+ const dir = path15.dirname(fullPath);
5880
+ if (!fs15.existsSync(dir)) {
5881
+ fs15.mkdirSync(dir, { recursive: true });
5882
+ }
5883
+ fs15.writeFileSync(fullPath, template, "utf-8");
5884
+ results.push({ type, filePath: relativePath, action: "created" });
5885
+ }
5886
+ if (type === "antigravity") {
5887
+ handleAntigravityMcpConfig(root, results);
5888
+ } else if (type === "openclaw") {
5889
+ handleOpenclawSecondary(root, results);
5890
+ } else if (type === "codewhale") {
5891
+ handleCodewhaleSecondary(root, results);
5892
+ } else if (type === "aider") {
5893
+ handleAiderSecondary(root, results);
5894
+ } else if (type === "opencode") {
5895
+ handleOpencodeSecondary(root, results);
5896
+ }
5897
+ }
5898
+ } catch (err) {
5899
+ results.push({
5900
+ type,
5901
+ filePath: relativePath,
5902
+ action: "error",
5903
+ error: err instanceof Error ? err.message : String(err)
5904
+ });
5905
+ }
5906
+ }
5907
+ return results;
5908
+ }
5909
+ function formatInitResults(results) {
5910
+ const lines = [
5911
+ "\u{1F43B} **Kuma Init - AI Agent Config Generator**",
5912
+ ""
5913
+ ];
5914
+ for (const r of results) {
5915
+ const label = CONFIG_LABELS[r.type];
5916
+ switch (r.action) {
5917
+ case "created":
5918
+ lines.push(" \u2705 " + label);
5919
+ lines.push(" \u2192 Created: " + r.filePath);
5920
+ break;
5921
+ case "appended":
5922
+ lines.push(" \u2795 " + label);
5923
+ lines.push(" \u2192 Appended to: " + r.filePath);
5924
+ break;
5925
+ case "skipped":
5926
+ lines.push(" \u23ED " + label);
5927
+ lines.push(" \u2192 Skipped (already has Kuma): " + r.filePath);
5928
+ break;
5929
+ case "error":
5930
+ lines.push(" \u274C " + label);
5931
+ lines.push(" \u2192 Error: " + (r.error ?? "unknown"));
5932
+ break;
5933
+ }
5934
+ }
5935
+ const created = results.filter((r) => r.action === "created").length;
5936
+ const appended = results.filter((r) => r.action === "appended").length;
5937
+ const skipped = results.filter((r) => r.action === "skipped").length;
5938
+ const errors = results.filter((r) => r.action === "error").length;
5939
+ lines.push(
5940
+ "",
5941
+ "\u{1F4CA} Summary: " + created + " created, " + appended + " appended, " + skipped + " skipped, " + errors + " errors",
5942
+ "",
5943
+ "\u{1F4A1} Config files teach your AI how to use Kuma tools.",
5944
+ "\u{1F4A1} Run again to generate additional config files anytime."
5945
+ );
5946
+ return lines.join("\n");
4638
5947
  }
4639
5948
 
4640
5949
  // src/index.ts
@@ -4642,7 +5951,92 @@ var SERVER_NAME = "kuma";
4642
5951
  var SERVER_VERSION = JSON.parse(
4643
5952
  readFileSync(new URL("../package.json", import.meta.url), "utf-8")
4644
5953
  ).version;
5954
+ function printHelp() {
5955
+ console.error(`
5956
+ \u{1F43B} Kuma v${SERVER_VERSION} \u2014 Zero-setup safety toolkit for AI coding agents
5957
+
5958
+ Usage:
5959
+ npx @plumpslabs/kuma Start MCP server (default)
5960
+ npx @plumpslabs/kuma init Generate AI agent config files
5961
+ npx @plumpslabs/kuma init --all Generate ALL config files
5962
+ npx @plumpslabs/kuma init --claude --cursor Generate specific files
5963
+ npx @plumpslabs/kuma init --help Show this help
5964
+
5965
+ Available config files:
5966
+ --claude CLAUDE.md (Claude Code)
5967
+ --cursor .cursor/rules/kuma.mdc (Cursor)
5968
+ --windsurf .windsurfrules (Windsurf)
5969
+ --copilot AGENTS.md + .github/skills/ (GitHub Copilot Editor)
5970
+ --cline .clinerules/kuma.md (Cline)
5971
+ --aider CONVENTIONS.md + .aider.conf.yml (Aider)
5972
+ --antigravity .agents/skills/kuma/SKILL.md (Antigravity CLI)
5973
+ --opencode opencode.json (OpenCode)
5974
+ --codex AGENTS.md + .codex/ (Codex CLI - OpenAI)
5975
+ --qwen AGENTS.md + settings.json (Qwen Code)
5976
+ --kiro .kiro/steering/kuma.md (Kiro)
5977
+ --openclaw skills/kuma/SKILL.md (OpenClaw)
5978
+ --codewhale skills/kuma/SKILL.md + .codewhale/ (CodeWhale)
5979
+
5980
+ If no flags specified, you'll be prompted to select files interactively.
5981
+ `);
5982
+ }
4645
5983
  async function main() {
5984
+ const args = process.argv.slice(2);
5985
+ if (args[0] === "init") {
5986
+ const flags = args.slice(1);
5987
+ if (flags.includes("--help") || flags.includes("-h")) {
5988
+ printHelp();
5989
+ process.exit(0);
5990
+ }
5991
+ const requestedFlags = flags.filter((f) => f.startsWith("--"));
5992
+ let selectedTypes;
5993
+ if (requestedFlags.length === 0) {
5994
+ console.error("\u{1F43B} Kuma Init \u2014 AI Agent Config Generator");
5995
+ console.error("");
5996
+ console.error("Select config files to generate. Press Ctrl+C to skip.");
5997
+ console.error("");
5998
+ selectedTypes = await interactiveSelect();
5999
+ if (selectedTypes.length === 0) {
6000
+ console.error("\n\u26A0\uFE0F No files selected. Exiting.");
6001
+ process.exit(0);
6002
+ }
6003
+ } else {
6004
+ if (requestedFlags.includes("--all")) {
6005
+ selectedTypes = ALL_CONFIG_TYPES;
6006
+ } else {
6007
+ const flagToType = {
6008
+ "--claude": "claude",
6009
+ "--cursor": "cursor",
6010
+ "--windsurf": "windsurf",
6011
+ "--copilot": "copilot",
6012
+ "--cline": "cline",
6013
+ "--aider": "aider",
6014
+ "--antigravity": "antigravity",
6015
+ "--opencode": "opencode",
6016
+ "--codex": "codex",
6017
+ "--qwen": "qwen",
6018
+ "--kiro": "kiro",
6019
+ "--openclaw": "openclaw",
6020
+ "--codewhale": "codewhale"
6021
+ };
6022
+ selectedTypes = [];
6023
+ for (const flag of requestedFlags) {
6024
+ const type = flagToType[flag];
6025
+ if (type) {
6026
+ selectedTypes.push(type);
6027
+ }
6028
+ }
6029
+ if (selectedTypes.length === 0) {
6030
+ console.error("\u26A0\uFE0F No valid flags provided. Use --help to see options.");
6031
+ process.exit(1);
6032
+ }
6033
+ }
6034
+ }
6035
+ const results = runInit(selectedTypes, process.cwd());
6036
+ const output = formatInitResults(results);
6037
+ console.log(output);
6038
+ process.exit(0);
6039
+ }
4646
6040
  sessionMemory.init({
4647
6041
  projectRoot: process.cwd(),
4648
6042
  startTime: Date.now()
@@ -4653,7 +6047,6 @@ async function main() {
4653
6047
  version: SERVER_VERSION
4654
6048
  },
4655
6049
  {
4656
- // Capabilities declaration
4657
6050
  capabilities: {
4658
6051
  tools: {},
4659
6052
  resources: {}
@@ -4672,6 +6065,66 @@ async function main() {
4672
6065
  `[${SERVER_NAME}] Server connected via stdio. Waiting for requests...`
4673
6066
  );
4674
6067
  }
6068
+ function interactiveSelect() {
6069
+ const labels = [
6070
+ { type: "claude", label: "1) Claude Code (CLAUDE.md)" },
6071
+ { type: "cursor", label: "2) Cursor (.cursor/rules/kuma.mdc)" },
6072
+ { type: "windsurf", label: "3) Windsurf (.windsurfrules)" },
6073
+ { type: "copilot", label: "4) GitHub Copilot Editor (AGENTS.md + Skill)" },
6074
+ { type: "cline", label: "5) Cline (.clinerules/kuma.md)" },
6075
+ { type: "aider", label: "6) Aider (CONVENTIONS.md via .aider.conf.yml)" },
6076
+ { type: "antigravity", label: "7) Antigravity CLI (.agents/skills/)" },
6077
+ { type: "opencode", label: "8) OpenCode (opencode.json)" },
6078
+ { type: "codex", label: "9) Codex CLI - OpenAI (AGENTS.md + .codex/config.toml)" },
6079
+ { type: "qwen", label: "10) Qwen Code (AGENTS.md + settings.json)" },
6080
+ { type: "kiro", label: "11) Kiro (.kiro/steering/kuma.md)" },
6081
+ { type: "openclaw", label: "12) OpenClaw (skills/kuma/SKILL.md)" },
6082
+ { type: "codewhale", label: "13) CodeWhale (skills/kuma/SKILL.md + .codewhale/mcp.json)" }
6083
+ ];
6084
+ const rl = readline.createInterface({
6085
+ input: process.stdin,
6086
+ output: process.stderr
6087
+ });
6088
+ return new Promise((resolve) => {
6089
+ console.error("");
6090
+ for (const l of labels) {
6091
+ console.error(l.label);
6092
+ }
6093
+ console.error("");
6094
+ rl.question("Enter numbers separated by space (e.g. '1 3 5'), or 'all': ", (answer) => {
6095
+ rl.close();
6096
+ const input = answer.trim().toLowerCase();
6097
+ if (input === "all") {
6098
+ resolve(ALL_CONFIG_TYPES);
6099
+ return;
6100
+ }
6101
+ const nums = input.split(/\s+/).map(Number).filter((n) => n >= 1 && n <= 13);
6102
+ const typeMap = {
6103
+ 1: "claude",
6104
+ 2: "cursor",
6105
+ 3: "windsurf",
6106
+ 4: "copilot",
6107
+ 5: "cline",
6108
+ 6: "aider",
6109
+ 7: "antigravity",
6110
+ 8: "opencode",
6111
+ 9: "codex",
6112
+ 10: "qwen",
6113
+ 11: "kiro",
6114
+ 12: "openclaw",
6115
+ 13: "codewhale"
6116
+ };
6117
+ const selected = [];
6118
+ for (const n of nums) {
6119
+ const t = typeMap[n];
6120
+ if (t && !selected.includes(t)) {
6121
+ selected.push(t);
6122
+ }
6123
+ }
6124
+ resolve(selected);
6125
+ });
6126
+ });
6127
+ }
4675
6128
  main().catch((err) => {
4676
6129
  console.error(`[${SERVER_NAME}] Fatal error:`, err);
4677
6130
  process.exit(1);