@plumpslabs/kuma 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ALL_CONFIG_TYPES,
4
+ ensureBackupDir,
5
+ formatInitResults,
6
+ getBackupPath,
7
+ getProjectRoot,
8
+ runInit,
9
+ validateFileExtension,
10
+ validateFilePath
11
+ } from "./chunk-P5Y7IAUC.js";
2
12
 
3
13
  // src/index.ts
4
14
  import { readFileSync } from "fs";
@@ -11,157 +21,8 @@ import { z } from "zod";
11
21
 
12
22
  // src/tools/smartGrep.ts
13
23
  import fg from "fast-glob";
14
- import fs3 from "fs";
15
- import path3 from "path";
16
-
17
- // src/utils/pathValidator.ts
18
- import path from "path";
19
- import fs from "fs";
20
- function validateFilePath(filePath, projectRoot) {
21
- try {
22
- const root = projectRoot ?? getProjectRoot();
23
- const resolvedRoot = path.resolve(root);
24
- const normalizedRoot = path.normalize(resolvedRoot).toLowerCase();
25
- let resolvedPath;
26
- if (!path.isAbsolute(filePath)) {
27
- const cwdPath = path.resolve(process.cwd(), filePath);
28
- const normalizedCwdPath = path.normalize(cwdPath).toLowerCase();
29
- if (fs.existsSync(cwdPath) && normalizedCwdPath.startsWith(normalizedRoot)) {
30
- resolvedPath = cwdPath;
31
- } else {
32
- resolvedPath = path.resolve(resolvedRoot, filePath);
33
- }
34
- } else {
35
- resolvedPath = path.resolve(resolvedRoot, filePath);
36
- }
37
- const normalizedPath = path.normalize(resolvedPath).toLowerCase();
38
- if (!normalizedPath.startsWith(normalizedRoot)) {
39
- return {
40
- valid: false,
41
- error: new Error(
42
- `PATH_TRAVERSAL: Access denied. Path "${filePath}" resolves outside project root "${resolvedRoot}".`
43
- )
44
- };
45
- }
46
- if (normalizedPath.includes("..")) {
47
- return {
48
- valid: false,
49
- error: new Error(
50
- `PATH_TRAVERSAL: Path traversal detected in "${filePath}". Relative paths with ".." are not allowed.`
51
- )
52
- };
53
- }
54
- const blockedPatterns = [
55
- "/etc/",
56
- "/sys/",
57
- "/proc/",
58
- "/dev/",
59
- "/boot/",
60
- "/usr/",
61
- "C:\\Windows",
62
- "C:\\Program Files",
63
- "C:\\Program Files (x86)",
64
- "C:\\System32",
65
- "~/.ssh",
66
- "/root/"
67
- ];
68
- for (const pattern of blockedPatterns) {
69
- if (normalizedPath.includes(pattern.toLowerCase())) {
70
- return {
71
- valid: false,
72
- error: new Error(
73
- `PATH_TRAVERSAL: Access to system directory "${pattern}" is blocked.`
74
- )
75
- };
76
- }
77
- }
78
- try {
79
- if (fs.existsSync(resolvedPath)) {
80
- const realPath = fs.realpathSync(resolvedPath);
81
- const normalizedRealPath = path.normalize(realPath).toLowerCase();
82
- if (!normalizedRealPath.startsWith(normalizedRoot)) {
83
- return {
84
- valid: false,
85
- error: new Error(
86
- `PATH_TRAVERSAL: Symlink escape detected. Path "${filePath}" resolves to "${realPath}" which is outside project root "${resolvedRoot}".`
87
- )
88
- };
89
- }
90
- }
91
- } catch {
92
- }
93
- if (normalizedPath.includes("node_modules")) {
94
- return {
95
- valid: true,
96
- resolvedPath
97
- // Warning will be handled by caller
98
- };
99
- }
100
- return { valid: true, resolvedPath };
101
- } catch (err) {
102
- return {
103
- valid: false,
104
- error: err instanceof Error ? err : new Error(`Path validation error: ${String(err)}`)
105
- };
106
- }
107
- }
108
- function validateFileExtension(filePath) {
109
- const allowedExtensions = [
110
- ".ts",
111
- ".tsx",
112
- ".js",
113
- ".jsx",
114
- ".mjs",
115
- ".cjs",
116
- ".json",
117
- ".md",
118
- ".css",
119
- ".html",
120
- ".htm",
121
- ".env",
122
- ".env.example",
123
- ".env.local",
124
- ".yml",
125
- ".yaml",
126
- ".toml",
127
- ".svg",
128
- ".png",
129
- ".jpg",
130
- ".gif",
131
- ".sh",
132
- ".bat",
133
- ".ps1",
134
- ".sql",
135
- ".graphql",
136
- ".gql",
137
- ".vue",
138
- ".svelte",
139
- ".astro",
140
- ".prisma",
141
- ".proto",
142
- ".txt",
143
- ".log"
144
- ];
145
- const ext = path.extname(filePath).toLowerCase();
146
- return allowedExtensions.includes(ext);
147
- }
148
- function getProjectRoot() {
149
- return process.env.AGENT_PROJECT_ROOT ?? process.cwd();
150
- }
151
- function getBackupPath(filePath, timestamp) {
152
- const ts = timestamp ?? Date.now();
153
- const root = getProjectRoot();
154
- const relativePath = path.relative(root, filePath);
155
- return path.join(root, ".agent-backups", String(ts), relativePath);
156
- }
157
- function ensureBackupDir() {
158
- const root = getProjectRoot();
159
- const backupDir = path.join(root, ".agent-backups");
160
- if (!fs.existsSync(backupDir)) {
161
- fs.mkdirSync(backupDir, { recursive: true });
162
- }
163
- return backupDir;
164
- }
24
+ import fs2 from "fs";
25
+ import path2 from "path";
165
26
 
166
27
  // src/utils/tokenCounter.ts
167
28
  function estimateTokens(text) {
@@ -186,33 +47,33 @@ function limitLines(text, maxLines) {
186
47
  }
187
48
 
188
49
  // src/engine/sessionMemory.ts
189
- import fs2 from "fs";
190
- import path2 from "path";
50
+ import fs from "fs";
51
+ import path from "path";
191
52
  var SessionMemory = class {
192
53
  state;
193
54
  initialized = false;
194
55
  init(config) {
195
- const kumaDir = path2.join(config.projectRoot, ".kuma");
196
- const sessionFile = path2.join(kumaDir, "memory.json");
197
- const oldFile = path2.join(kumaDir, ".kuma-memory.json");
198
- if (fs2.existsSync(oldFile) && !fs2.existsSync(sessionFile)) {
56
+ const kumaDir = path.join(config.projectRoot, ".kuma");
57
+ const sessionFile = path.join(kumaDir, "memory.json");
58
+ const oldFile = path.join(kumaDir, ".kuma-memory.json");
59
+ if (fs.existsSync(oldFile) && !fs.existsSync(sessionFile)) {
199
60
  try {
200
- fs2.renameSync(oldFile, sessionFile);
61
+ fs.renameSync(oldFile, sessionFile);
201
62
  console.error("[SessionMemory] Migrated .kuma-memory.json \u2192 memory.json");
202
63
  } catch {
203
64
  }
204
65
  }
205
- const oldSessionFile = path2.join(kumaDir, "session.json");
206
- if (fs2.existsSync(oldSessionFile) && !fs2.existsSync(sessionFile)) {
66
+ const oldSessionFile = path.join(kumaDir, "session.json");
67
+ if (fs.existsSync(oldSessionFile) && !fs.existsSync(sessionFile)) {
207
68
  try {
208
- fs2.renameSync(oldSessionFile, sessionFile);
69
+ fs.renameSync(oldSessionFile, sessionFile);
209
70
  console.error("[SessionMemory] Migrated session.json \u2192 memory.json");
210
71
  } catch {
211
72
  }
212
73
  }
213
- if (fs2.existsSync(sessionFile)) {
74
+ if (fs.existsSync(sessionFile)) {
214
75
  try {
215
- const raw = fs2.readFileSync(sessionFile, "utf-8");
76
+ const raw = fs.readFileSync(sessionFile, "utf-8");
216
77
  const parsed = JSON.parse(raw);
217
78
  this.state = {
218
79
  projectRoot: parsed.projectRoot || config.projectRoot,
@@ -250,23 +111,23 @@ var SessionMemory = class {
250
111
  console.error(`[SessionMemory] Initialized at ${new Date(config.startTime).toISOString()}`);
251
112
  }
252
113
  memoriesDir() {
253
- return path2.join(this.state.projectRoot, ".kuma", "memories");
114
+ return path.join(this.state.projectRoot, ".kuma", "memories");
254
115
  }
255
116
  ensureMemoriesDir() {
256
117
  const dir = this.memoriesDir();
257
- if (!fs2.existsSync(dir)) {
258
- fs2.mkdirSync(dir, { recursive: true });
118
+ if (!fs.existsSync(dir)) {
119
+ fs.mkdirSync(dir, { recursive: true });
259
120
  }
260
121
  }
261
122
  memoryFilePath(topic) {
262
- return path2.join(this.memoriesDir(), `${topic}.md`);
123
+ return path.join(this.memoriesDir(), `${topic}.md`);
263
124
  }
264
125
  getMemoryContent(topic) {
265
126
  this.ensureInit();
266
127
  this.ensureMemoriesDir();
267
128
  const filePath = this.memoryFilePath(topic);
268
- if (fs2.existsSync(filePath)) {
269
- return fs2.readFileSync(filePath, "utf-8");
129
+ if (fs.existsSync(filePath)) {
130
+ return fs.readFileSync(filePath, "utf-8");
270
131
  }
271
132
  return this.generateMemory(topic);
272
133
  }
@@ -277,7 +138,7 @@ var SessionMemory = class {
277
138
  }
278
139
  writeMemoryFile(topic, content) {
279
140
  const filePath = this.memoryFilePath(topic);
280
- fs2.writeFileSync(filePath, content, "utf-8");
141
+ fs.writeFileSync(filePath, content, "utf-8");
281
142
  }
282
143
  generateMemory(topic) {
283
144
  switch (topic) {
@@ -376,9 +237,9 @@ No unresolved issues.`;
376
237
  save() {
377
238
  if (!this.initialized || !this.state) return;
378
239
  try {
379
- const kumaDir = path2.join(this.state.projectRoot, ".kuma");
380
- if (!fs2.existsSync(kumaDir)) {
381
- fs2.mkdirSync(kumaDir, { recursive: true });
240
+ const kumaDir = path.join(this.state.projectRoot, ".kuma");
241
+ if (!fs.existsSync(kumaDir)) {
242
+ fs.mkdirSync(kumaDir, { recursive: true });
382
243
  }
383
244
  const serialized = {
384
245
  projectRoot: this.state.projectRoot,
@@ -392,7 +253,7 @@ No unresolved issues.`;
392
253
  toolCalls: this.state.toolCalls,
393
254
  conventions: this.state.conventions
394
255
  };
395
- fs2.writeFileSync(path2.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
256
+ fs.writeFileSync(path.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
396
257
  } catch (err) {
397
258
  console.error(`[SessionMemory] Failed to save session: ${err}`);
398
259
  }
@@ -507,6 +368,68 @@ No unresolved issues.`;
507
368
  // ============================================================
508
369
  // KEYWORD SEARCH — Search session memory content
509
370
  // ============================================================
371
+ /**
372
+ * Load previous session data into current session.
373
+ * Called by kuma_init() to hydrate conventions, failures, and modified files
374
+ * from a prior session stored in memory.json.
375
+ */
376
+ loadSession() {
377
+ this.ensureInit();
378
+ const kumaDir = path.join(this.state.projectRoot, ".kuma");
379
+ const sessionFile = path.join(kumaDir, "memory.json");
380
+ if (!fs.existsSync(sessionFile)) {
381
+ return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
382
+ }
383
+ try {
384
+ const raw = fs.readFileSync(sessionFile, "utf-8");
385
+ const parsed = JSON.parse(raw);
386
+ if (!this.state.conventions && parsed.conventions) {
387
+ this.state.conventions = parsed.conventions;
388
+ }
389
+ const prevFailed = parsed.failedFiles;
390
+ if (prevFailed) {
391
+ for (const [task, failures] of prevFailed) {
392
+ for (const f of failures) {
393
+ if (!f.resolved) {
394
+ const existing = this.state.failedFiles.get(task);
395
+ if (!existing?.some((ef) => ef.error === f.error)) {
396
+ this.state.failedFiles.set(task, [...existing || [], f]);
397
+ }
398
+ }
399
+ }
400
+ }
401
+ }
402
+ const prevModified = parsed.modifiedFiles;
403
+ if (prevModified) {
404
+ for (const [filePath, mod] of prevModified) {
405
+ if (!this.state.modifiedFiles.has(filePath)) {
406
+ this.state.modifiedFiles.set(filePath, {
407
+ filePath,
408
+ modifiedAt: mod.modifiedAt,
409
+ status: mod.status
410
+ });
411
+ }
412
+ }
413
+ }
414
+ const prevSearch = parsed.searchResults;
415
+ if (prevSearch) {
416
+ for (const [query, files] of prevSearch) {
417
+ if (!this.state.searchResults.has(query)) {
418
+ this.state.searchResults.set(query, files);
419
+ }
420
+ }
421
+ }
422
+ const toolCallCount = parsed.toolCalls?.length ?? 0;
423
+ this.save();
424
+ return {
425
+ hasPrevSession: true,
426
+ toolCallCount,
427
+ hasConventions: !!this.state.conventions
428
+ };
429
+ } catch {
430
+ return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
431
+ }
432
+ }
510
433
  /**
511
434
  * Search through tool call history, memory files, search results,
512
435
  * errors, and file modifications for a keyword.
@@ -562,14 +485,14 @@ No unresolved issues.`;
562
485
  }
563
486
  }
564
487
  const memoriesDir = this.memoriesDir();
565
- if (fs2.existsSync(memoriesDir)) {
488
+ if (fs.existsSync(memoriesDir)) {
566
489
  try {
567
- const files = fs2.readdirSync(memoriesDir);
490
+ const files = fs.readdirSync(memoriesDir);
568
491
  for (const file of files) {
569
492
  if (!file.endsWith(".md")) continue;
570
- const filePath = path2.join(memoriesDir, file);
493
+ const filePath = path.join(memoriesDir, file);
571
494
  try {
572
- const content = fs2.readFileSync(filePath, "utf-8");
495
+ const content = fs.readFileSync(filePath, "utf-8");
573
496
  const lines = content.split("\n");
574
497
  for (let i = 0; i < lines.length; i++) {
575
498
  if (lines[i].toLowerCase().includes(q)) {
@@ -625,11 +548,11 @@ No unresolved issues.`;
625
548
  unresolvedFailures.push({ task: f.task, error });
626
549
  }
627
550
  }
628
- const hasMemories = fs2.existsSync(this.memoriesDir());
551
+ const hasMemories = fs.existsSync(this.memoriesDir());
629
552
  let availableMemories = [];
630
553
  if (hasMemories) {
631
554
  try {
632
- availableMemories = fs2.readdirSync(this.memoriesDir()).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
555
+ availableMemories = fs.readdirSync(this.memoriesDir()).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
633
556
  } catch {
634
557
  }
635
558
  }
@@ -771,7 +694,7 @@ async function handleSmartGrep(params) {
771
694
  }
772
695
  const projectRoot = getProjectRoot();
773
696
  try {
774
- const searchPattern = targetFolder ? path3.join(targetFolder, "**/*").replace(/\\/g, "/") : "**/*";
697
+ const searchPattern = targetFolder ? path2.join(targetFolder, "**/*").replace(/\\/g, "/") : "**/*";
775
698
  let entries = await fg(searchPattern, {
776
699
  cwd: projectRoot,
777
700
  ignore: IGNORE_PATTERNS,
@@ -787,7 +710,7 @@ async function handleSmartGrep(params) {
787
710
  (e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
788
711
  );
789
712
  entries = entries.filter((entry) => {
790
- const ext = path3.extname(entry).toLowerCase();
713
+ const ext = path2.extname(entry).toLowerCase();
791
714
  return normalizedExts.includes(ext);
792
715
  });
793
716
  }
@@ -803,11 +726,11 @@ async function handleSmartGrep(params) {
803
726
  if (results.length >= maxResults) break;
804
727
  filesScanned++;
805
728
  try {
806
- const fullPath = path3.join(projectRoot, entry);
807
- const stat = fs3.statSync(fullPath);
729
+ const fullPath = path2.join(projectRoot, entry);
730
+ const stat = fs2.statSync(fullPath);
808
731
  if (stat.size > 5e5) continue;
809
732
  if (isBinaryFile(fullPath)) continue;
810
- const content = fs3.readFileSync(fullPath, "utf-8");
733
+ const content = fs2.readFileSync(fullPath, "utf-8");
811
734
  const lines = content.split("\n");
812
735
  for (let i = 0; i < lines.length; i++) {
813
736
  if (results.length >= maxResults) break;
@@ -876,9 +799,9 @@ ${r.content.split("\n").map((l) => ` ${l}`).join("\n")}`;
876
799
  function isBinaryFile(filePath) {
877
800
  try {
878
801
  const buffer = Buffer.alloc(512);
879
- const fd = fs3.openSync(filePath, "r");
880
- const bytesRead = fs3.readSync(fd, buffer, 0, 512, 0);
881
- fs3.closeSync(fd);
802
+ const fd = fs2.openSync(filePath, "r");
803
+ const bytesRead = fs2.readSync(fd, buffer, 0, 512, 0);
804
+ fs2.closeSync(fd);
882
805
  for (let i = 0; i < bytesRead; i++) {
883
806
  if (buffer[i] === 0) return true;
884
807
  }
@@ -889,8 +812,8 @@ function isBinaryFile(filePath) {
889
812
  }
890
813
 
891
814
  // src/tools/smartFilePicker.ts
892
- import fs4 from "fs";
893
- import path4 from "path";
815
+ import fs3 from "fs";
816
+ import path3 from "path";
894
817
  var MAX_FILE_SIZE = 1e6;
895
818
  var CHUNK_THRESHOLD = 300;
896
819
  async function handleSmartFilePicker(params) {
@@ -900,23 +823,23 @@ async function handleSmartFilePicker(params) {
900
823
  return "Error: " + validation.error.message;
901
824
  }
902
825
  const resolvedPath = validation.resolvedPath;
903
- if (!fs4.existsSync(resolvedPath)) {
826
+ if (!fs3.existsSync(resolvedPath)) {
904
827
  const projectRoot = getProjectRoot();
905
828
  let suggestion;
906
- if (!path4.isAbsolute(filePath)) {
907
- const cwdPath = path4.resolve(process.cwd(), filePath);
829
+ if (!path3.isAbsolute(filePath)) {
830
+ const cwdPath = path3.resolve(process.cwd(), filePath);
908
831
  suggestion = "Path resolved to: " + resolvedPath + "\nCWD: " + process.cwd() + "\nProject root: " + projectRoot + "\nHint: Try path relative to project root, or relative to CWD (" + cwdPath + ")\nTry smart_grep first to locate the correct file.";
909
832
  } else {
910
833
  suggestion = "File does not exist. Try smart_grep to locate it.";
911
834
  }
912
835
  return 'Error: File not found: "' + filePath + '".\n' + suggestion;
913
836
  }
914
- const stat = fs4.statSync(resolvedPath);
837
+ const stat = fs3.statSync(resolvedPath);
915
838
  if (stat.size > MAX_FILE_SIZE) {
916
839
  return "Error: File too large (" + (stat.size / 1024 / 1024).toFixed(1) + "MB). Max 1MB.\nUse smart_grep to find specific content instead.";
917
840
  }
918
841
  try {
919
- const content = fs4.readFileSync(resolvedPath, "utf-8");
842
+ const content = fs3.readFileSync(resolvedPath, "utf-8");
920
843
  const lines = content.split("\n");
921
844
  const totalLines = lines.length;
922
845
  sessionMemory.recordToolCall("smart_file_picker", {
@@ -1066,8 +989,8 @@ function extractKeyDeclarations(lines) {
1066
989
  }
1067
990
 
1068
991
  // src/tools/preciseDiffEditor.ts
1069
- import fs5 from "fs";
1070
- import path5 from "path";
992
+ import fs4 from "fs";
993
+ import path4 from "path";
1071
994
 
1072
995
  // src/utils/errorHandler.ts
1073
996
  var CircuitBreakerStore = class {
@@ -1142,12 +1065,12 @@ async function handlePreciseDiffEditor(params) {
1142
1065
 
1143
1066
  Try reading the file first with smart_file_picker to verify current content.`;
1144
1067
  }
1145
- if (!fs5.existsSync(resolvedPath)) {
1068
+ if (!fs4.existsSync(resolvedPath)) {
1146
1069
  return `Error: File not found: "${filePath}".
1147
1070
  Use batch_file_writer to create a new file.`;
1148
1071
  }
1149
1072
  try {
1150
- const originalContent = fs5.readFileSync(resolvedPath, "utf-8");
1073
+ const originalContent = fs4.readFileSync(resolvedPath, "utf-8");
1151
1074
  let currentContent = originalContent;
1152
1075
  const results = [];
1153
1076
  for (let i = 0; i < edits.length; i++) {
@@ -1155,7 +1078,7 @@ Use batch_file_writer to create a new file.`;
1155
1078
  const result = applyEdit(currentContent, edit, resolvedPath, i, dryRun);
1156
1079
  if (result.success) {
1157
1080
  if (!dryRun) {
1158
- fs5.writeFileSync(resolvedPath, result.details, "utf-8");
1081
+ fs4.writeFileSync(resolvedPath, result.details, "utf-8");
1159
1082
  sessionMemory.recordToolCall("precise_diff_editor", {
1160
1083
  filePath,
1161
1084
  editIndex: i,
@@ -1345,9 +1268,9 @@ function findNearestLine(content, search) {
1345
1268
  function createBackup(filePath) {
1346
1269
  ensureBackupDir();
1347
1270
  const backupPath = getBackupPath(filePath);
1348
- const backupDir = path5.dirname(backupPath);
1349
- fs5.mkdirSync(backupDir, { recursive: true });
1350
- fs5.copyFileSync(filePath, backupPath);
1271
+ const backupDir = path4.dirname(backupPath);
1272
+ fs4.mkdirSync(backupDir, { recursive: true });
1273
+ fs4.copyFileSync(filePath, backupPath);
1351
1274
  return backupPath;
1352
1275
  }
1353
1276
  function formatDryRunResult(results, filePath, originalContent) {
@@ -1433,7 +1356,7 @@ function formatDiffResult(results, filePath) {
1433
1356
  if (r.success) {
1434
1357
  lines.push(`[${i + 1}] \u2705 Matched: ${r.matched}x, Replaced: ${r.replaced}x`);
1435
1358
  if (r.backupPath) {
1436
- const relativePath = path5.relative(getProjectRoot(), r.backupPath);
1359
+ const relativePath = path4.relative(getProjectRoot(), r.backupPath);
1437
1360
  lines.push(` Backup: ${relativePath}`);
1438
1361
  }
1439
1362
  } else {
@@ -1468,15 +1391,15 @@ async function handleRollbackEdit(params) {
1468
1391
  }
1469
1392
  const resolvedPath = validation.resolvedPath;
1470
1393
  const root = getProjectRoot();
1471
- const relativePath = path5.relative(root, resolvedPath);
1472
- const backupRoot = path5.join(root, ".agent-backups");
1473
- if (!fs5.existsSync(backupRoot)) {
1394
+ const relativePath = path4.relative(root, resolvedPath);
1395
+ const backupRoot = path4.join(root, ".agent-backups");
1396
+ if (!fs4.existsSync(backupRoot)) {
1474
1397
  return `Error: No backup folder (.agent-backups) found in project root.`;
1475
1398
  }
1476
1399
  try {
1477
- const dirs = fs5.readdirSync(backupRoot).filter((name) => {
1478
- const fullPath = path5.join(backupRoot, name);
1479
- return fs5.statSync(fullPath).isDirectory() && /^\d+$/.test(name);
1400
+ const dirs = fs4.readdirSync(backupRoot).filter((name) => {
1401
+ const fullPath = path4.join(backupRoot, name);
1402
+ return fs4.statSync(fullPath).isDirectory() && /^\d+$/.test(name);
1480
1403
  });
1481
1404
  if (dirs.length === 0) {
1482
1405
  return `Error: No valid backup found in .agent-backups folder.`;
@@ -1484,8 +1407,8 @@ async function handleRollbackEdit(params) {
1484
1407
  dirs.sort((a, b) => Number(b) - Number(a));
1485
1408
  const backupVersions = [];
1486
1409
  for (const dir of dirs) {
1487
- const potentialBackupPath = path5.join(backupRoot, dir, relativePath);
1488
- if (fs5.existsSync(potentialBackupPath)) {
1410
+ const potentialBackupPath = path4.join(backupRoot, dir, relativePath);
1411
+ if (fs4.existsSync(potentialBackupPath)) {
1489
1412
  backupVersions.push({ dir, backupPath: potentialBackupPath });
1490
1413
  }
1491
1414
  }
@@ -1512,14 +1435,14 @@ async function handleRollbackEdit(params) {
1512
1435
  selectedIndex = version - 1;
1513
1436
  }
1514
1437
  const selected = backupVersions[selectedIndex];
1515
- fs5.copyFileSync(selected.backupPath, resolvedPath);
1438
+ fs4.copyFileSync(selected.backupPath, resolvedPath);
1516
1439
  sessionMemory.recordToolCall("rollback_last_edit", {
1517
1440
  filePath,
1518
1441
  backupTimestamp: selected.dir,
1519
1442
  version: version ?? 1,
1520
1443
  success: true
1521
1444
  });
1522
- const relBackupPath = path5.relative(root, selected.backupPath);
1445
+ const relBackupPath = path4.relative(root, selected.backupPath);
1523
1446
  return `\u2705 Rollback Successful!
1524
1447
  File "${filePath}" restored from backup: "${relBackupPath}".`;
1525
1448
  } catch (err) {
@@ -1528,8 +1451,8 @@ File "${filePath}" restored from backup: "${relBackupPath}".`;
1528
1451
  }
1529
1452
 
1530
1453
  // src/tools/batchFileWriter.ts
1531
- import fs6 from "fs";
1532
- import path6 from "path";
1454
+ import fs5 from "fs";
1455
+ import path5 from "path";
1533
1456
  async function handleBatchFileWriter(params) {
1534
1457
  const { files } = params;
1535
1458
  if (files.length > 15) {
@@ -1556,22 +1479,22 @@ async function handleBatchFileWriter(params) {
1556
1479
  results.push({
1557
1480
  filePath: file.filePath,
1558
1481
  success: false,
1559
- error: `File extension not allowed: "${path6.extname(file.filePath)}". Allowed extensions: .ts, .js, .tsx, .jsx, .json, .md, .css, .html, .yml, .yaml, .toml, .sh, .env`
1482
+ error: `File extension not allowed: "${path5.extname(file.filePath)}". Allowed extensions: .ts, .js, .tsx, .jsx, .json, .md, .css, .html, .yml, .yaml, .toml, .sh, .env`
1560
1483
  });
1561
1484
  continue;
1562
1485
  }
1563
- if (fs6.existsSync(resolvedPath)) {
1486
+ if (fs5.existsSync(resolvedPath)) {
1564
1487
  ensureBackupDir();
1565
1488
  const backupPath = getBackupPath(file.filePath);
1566
- const backupDir = path6.dirname(backupPath);
1567
- fs6.mkdirSync(backupDir, { recursive: true });
1568
- fs6.copyFileSync(resolvedPath, backupPath);
1489
+ const backupDir = path5.dirname(backupPath);
1490
+ fs5.mkdirSync(backupDir, { recursive: true });
1491
+ fs5.copyFileSync(resolvedPath, backupPath);
1569
1492
  }
1570
- const dir = path6.dirname(resolvedPath);
1571
- if (!fs6.existsSync(dir)) {
1572
- fs6.mkdirSync(dir, { recursive: true });
1493
+ const dir = path5.dirname(resolvedPath);
1494
+ if (!fs5.existsSync(dir)) {
1495
+ fs5.mkdirSync(dir, { recursive: true });
1573
1496
  }
1574
- fs6.writeFileSync(resolvedPath, file.content, "utf-8");
1497
+ fs5.writeFileSync(resolvedPath, file.content, "utf-8");
1575
1498
  sessionMemory.recordToolCall("batch_file_writer", {
1576
1499
  filePath: file.filePath,
1577
1500
  instructions: file.instructions,
@@ -1614,12 +1537,12 @@ function formatBatchResult(results, totalRequested) {
1614
1537
  }
1615
1538
 
1616
1539
  // src/tools/safeTerminalExec.ts
1617
- import fs8 from "fs";
1618
- import path8 from "path";
1619
-
1620
- // src/utils/conventionsDetector.ts
1621
1540
  import fs7 from "fs";
1622
1541
  import path7 from "path";
1542
+
1543
+ // src/utils/conventionsDetector.ts
1544
+ import fs6 from "fs";
1545
+ import path6 from "path";
1623
1546
  var cachedConventions = null;
1624
1547
  async function detectConventions(forceRescan = false) {
1625
1548
  if (cachedConventions && !forceRescan) {
@@ -1645,7 +1568,7 @@ async function detectConventions(forceRescan = false) {
1645
1568
  return conventions;
1646
1569
  }
1647
1570
  function detectFramework(root) {
1648
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1571
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1649
1572
  if (pkg) {
1650
1573
  const pkgDeps = pkg.dependencies ?? {};
1651
1574
  const pkgDevDeps = pkg.devDependencies ?? {};
@@ -1690,11 +1613,11 @@ function detectFramework(root) {
1690
1613
  function scanSubProjectFrameworks(root) {
1691
1614
  const frameworks = [];
1692
1615
  try {
1693
- const entries = fs7.readdirSync(root, { withFileTypes: true });
1616
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
1694
1617
  for (const entry of entries) {
1695
1618
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
1696
- const subDir = path7.join(root, entry.name);
1697
- const subPkg = readJsonSafe(path7.join(subDir, "package.json"));
1619
+ const subDir = path6.join(root, entry.name);
1620
+ const subPkg = readJsonSafe(path6.join(subDir, "package.json"));
1698
1621
  if (!subPkg) continue;
1699
1622
  const subDeps = { ...subPkg.dependencies ?? {}, ...subPkg.devDependencies ?? {} };
1700
1623
  if (subDeps.next) frameworks.push("Next.js");
@@ -1713,7 +1636,7 @@ function scanSubProjectFrameworks(root) {
1713
1636
  return frameworks;
1714
1637
  }
1715
1638
  function detectProjectType(root) {
1716
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1639
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1717
1640
  if (pkg) {
1718
1641
  const pkgDeps = pkg.dependencies ?? {};
1719
1642
  const pkgDevDeps = pkg.devDependencies ?? {};
@@ -1737,8 +1660,8 @@ function detectProjectType(root) {
1737
1660
  }
1738
1661
  function detectWorkspaces(root) {
1739
1662
  const results = [];
1740
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1741
- const pnpmWorkspace = readYamlLite(path7.join(root, "pnpm-workspace.yaml"));
1663
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1664
+ const pnpmWorkspace = readYamlLite(path6.join(root, "pnpm-workspace.yaml"));
1742
1665
  const patterns = /* @__PURE__ */ new Set();
1743
1666
  const pkgWorkspaces = pkg?.workspaces;
1744
1667
  if (Array.isArray(pkgWorkspaces)) {
@@ -1758,22 +1681,22 @@ function detectWorkspaces(root) {
1758
1681
  for (const pattern of patterns) {
1759
1682
  const match = pattern.match(/^([^*]+)\/\*$/);
1760
1683
  if (!match) continue;
1761
- const dir = path7.join(root, match[1]);
1762
- if (!fs7.existsSync(dir)) continue;
1684
+ const dir = path6.join(root, match[1]);
1685
+ if (!fs6.existsSync(dir)) continue;
1763
1686
  let entries = [];
1764
1687
  try {
1765
- entries = fs7.readdirSync(dir, { withFileTypes: true });
1688
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1766
1689
  } catch {
1767
1690
  continue;
1768
1691
  }
1769
1692
  for (const entry of entries) {
1770
1693
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
1771
- const pkgPath = path7.join(dir, entry.name, "package.json");
1694
+ const pkgPath = path6.join(dir, entry.name, "package.json");
1772
1695
  const subPkg = readJsonSafe(pkgPath);
1773
1696
  if (!subPkg) continue;
1774
- const workspacePath = path7.join(dir, entry.name);
1697
+ const workspacePath = path6.join(dir, entry.name);
1775
1698
  results.push({
1776
- path: path7.relative(root, workspacePath),
1699
+ path: path6.relative(root, workspacePath),
1777
1700
  name: subPkg.name ?? entry.name,
1778
1701
  framework: detectFramework(workspacePath),
1779
1702
  packageManager: detectPackageManagerForDir(workspacePath)
@@ -1789,8 +1712,8 @@ function detectWorkspaces(root) {
1789
1712
  }
1790
1713
  function readYamlLite(filePath) {
1791
1714
  try {
1792
- if (!fs7.existsSync(filePath)) return null;
1793
- const text = fs7.readFileSync(filePath, "utf-8");
1715
+ if (!fs6.existsSync(filePath)) return null;
1716
+ const text = fs6.readFileSync(filePath, "utf-8");
1794
1717
  const packages = [];
1795
1718
  let inPackages = false;
1796
1719
  for (const rawLine of text.split("\n")) {
@@ -1811,7 +1734,7 @@ function readYamlLite(filePath) {
1811
1734
  }
1812
1735
  }
1813
1736
  function detectTestRunner(root) {
1814
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1737
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1815
1738
  if (!pkg) return "unknown";
1816
1739
  const pkgDeps2 = pkg.dependencies ?? {};
1817
1740
  const pkgDevDeps2 = pkg.devDependencies ?? {};
@@ -1832,7 +1755,7 @@ function detectTestRunner(root) {
1832
1755
  return "unknown";
1833
1756
  }
1834
1757
  function detectStyling(root) {
1835
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1758
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1836
1759
  if (!pkg) return "unknown";
1837
1760
  const pkgDeps3 = pkg.dependencies ?? {};
1838
1761
  const pkgDevDeps3 = pkg.devDependencies ?? {};
@@ -1845,22 +1768,22 @@ function detectStyling(root) {
1845
1768
  if (deps.less) return "Less";
1846
1769
  if (deps["@vanilla-extract/css"]) return "Vanilla Extract";
1847
1770
  if (deps.cssmodules || hasCSSModules(root)) return "CSS Modules";
1848
- const srcDir = path7.join(root, "src");
1849
- if (fs7.existsSync(srcDir)) {
1771
+ const srcDir = path6.join(root, "src");
1772
+ if (fs6.existsSync(srcDir)) {
1850
1773
  const cssFiles = findFiles(srcDir, [".css", ".scss", ".less"]);
1851
1774
  if (cssFiles.length > 0) return "Plain CSS/SCSS";
1852
1775
  }
1853
1776
  return "unknown";
1854
1777
  }
1855
1778
  function detectImportAlias(root) {
1856
- const tsconfig = readJsonSafe(path7.join(root, "tsconfig.json"));
1779
+ const tsconfig = readJsonSafe(path6.join(root, "tsconfig.json"));
1857
1780
  const tsconfigPaths = tsconfig?.compilerOptions;
1858
1781
  if (tsconfigPaths?.paths) {
1859
1782
  const paths = tsconfigPaths.paths;
1860
1783
  const alias = Object.keys(paths)[0];
1861
1784
  if (alias) return alias.replace("/*", "");
1862
1785
  }
1863
- const jsconfig = readJsonSafe(path7.join(root, "jsconfig.json"));
1786
+ const jsconfig = readJsonSafe(path6.join(root, "jsconfig.json"));
1864
1787
  const jsconfigPaths = jsconfig?.compilerOptions;
1865
1788
  if (jsconfigPaths?.paths) {
1866
1789
  const paths = jsconfigPaths.paths;
@@ -1871,15 +1794,15 @@ function detectImportAlias(root) {
1871
1794
  }
1872
1795
  function detectLintRules(root) {
1873
1796
  const rules = [];
1874
- if (fs7.existsSync(path7.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
1875
- if (fs7.existsSync(path7.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
1876
- if (fs7.existsSync(path7.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
1877
- if (fs7.existsSync(path7.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
1878
- if (fs7.existsSync(path7.join(root, ".prettierrc"))) rules.push("Prettier");
1879
- if (fs7.existsSync(path7.join(root, ".prettierrc.json"))) rules.push("Prettier");
1880
- if (fs7.existsSync(path7.join(root, ".prettierrc.js"))) rules.push("Prettier");
1881
- if (fs7.existsSync(path7.join(root, ".stylelintrc"))) rules.push("StyleLint");
1882
- if (fs7.existsSync(path7.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
1797
+ if (fs6.existsSync(path6.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
1798
+ if (fs6.existsSync(path6.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
1799
+ if (fs6.existsSync(path6.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
1800
+ if (fs6.existsSync(path6.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
1801
+ if (fs6.existsSync(path6.join(root, ".prettierrc"))) rules.push("Prettier");
1802
+ if (fs6.existsSync(path6.join(root, ".prettierrc.json"))) rules.push("Prettier");
1803
+ if (fs6.existsSync(path6.join(root, ".prettierrc.js"))) rules.push("Prettier");
1804
+ if (fs6.existsSync(path6.join(root, ".stylelintrc"))) rules.push("StyleLint");
1805
+ if (fs6.existsSync(path6.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
1883
1806
  return rules;
1884
1807
  }
1885
1808
  function detectPackageManager() {
@@ -1888,26 +1811,26 @@ function detectPackageManager() {
1888
1811
  }
1889
1812
  function detectPackageManagerForDir(dir) {
1890
1813
  const root = getProjectRoot();
1891
- let currentDir = path7.resolve(dir);
1814
+ let currentDir = path6.resolve(dir);
1892
1815
  while (currentDir.startsWith(root)) {
1893
- if (fs7.existsSync(path7.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
1894
- if (fs7.existsSync(path7.join(currentDir, "yarn.lock"))) return "yarn";
1895
- if (fs7.existsSync(path7.join(currentDir, "package-lock.json"))) return "npm";
1896
- if (fs7.existsSync(path7.join(currentDir, "bun.lockb"))) return "bun";
1816
+ if (fs6.existsSync(path6.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
1817
+ if (fs6.existsSync(path6.join(currentDir, "yarn.lock"))) return "yarn";
1818
+ if (fs6.existsSync(path6.join(currentDir, "package-lock.json"))) return "npm";
1819
+ if (fs6.existsSync(path6.join(currentDir, "bun.lockb"))) return "bun";
1897
1820
  if (currentDir === root) break;
1898
- currentDir = path7.dirname(currentDir);
1821
+ currentDir = path6.dirname(currentDir);
1899
1822
  }
1900
1823
  return "npm";
1901
1824
  }
1902
1825
  function detectModuleSystem(root) {
1903
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1826
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1904
1827
  if (pkg?.type === "module") return "esm";
1905
- const srcDir = path7.join(root, "src");
1906
- if (fs7.existsSync(srcDir)) {
1828
+ const srcDir = path6.join(root, "src");
1829
+ if (fs6.existsSync(srcDir)) {
1907
1830
  const tsFiles = findFiles(srcDir, [".ts", ".tsx", ".js", ".jsx", ".mjs"]);
1908
1831
  for (const file of tsFiles.slice(0, 20)) {
1909
1832
  try {
1910
- const content = fs7.readFileSync(file, "utf-8");
1833
+ const content = fs6.readFileSync(file, "utf-8");
1911
1834
  if (content.includes("import ") || content.includes("export ")) return "esm";
1912
1835
  } catch {
1913
1836
  continue;
@@ -1917,9 +1840,9 @@ function detectModuleSystem(root) {
1917
1840
  return "cjs";
1918
1841
  }
1919
1842
  function detectLanguage(root) {
1920
- if (fs7.existsSync(path7.join(root, "tsconfig.json"))) return "typescript";
1921
- const srcDir = path7.join(root, "src");
1922
- if (fs7.existsSync(srcDir)) {
1843
+ if (fs6.existsSync(path6.join(root, "tsconfig.json"))) return "typescript";
1844
+ const srcDir = path6.join(root, "src");
1845
+ if (fs6.existsSync(srcDir)) {
1923
1846
  const tsFiles = findFiles(srcDir, [".ts", ".tsx"]);
1924
1847
  if (tsFiles.length > 0) return "typescript";
1925
1848
  }
@@ -1927,7 +1850,7 @@ function detectLanguage(root) {
1927
1850
  }
1928
1851
  function detectFeatures(root) {
1929
1852
  const features = [];
1930
- const pkg = readJsonSafe(path7.join(root, "package.json"));
1853
+ const pkg = readJsonSafe(path6.join(root, "package.json"));
1931
1854
  if (!pkg) return features;
1932
1855
  const pkgDeps4 = pkg.dependencies ?? {};
1933
1856
  const pkgDevDeps4 = pkg.devDependencies ?? {};
@@ -1951,25 +1874,25 @@ function detectFeatures(root) {
1951
1874
  }
1952
1875
  function readJsonSafe(filePath) {
1953
1876
  try {
1954
- if (fs7.existsSync(filePath)) {
1955
- return JSON.parse(fs7.readFileSync(filePath, "utf-8"));
1877
+ if (fs6.existsSync(filePath)) {
1878
+ return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
1956
1879
  }
1957
1880
  } catch {
1958
1881
  }
1959
1882
  return null;
1960
1883
  }
1961
1884
  function hasCSSModules(root) {
1962
- const srcDir = path7.join(root, "src");
1963
- if (!fs7.existsSync(srcDir)) return false;
1885
+ const srcDir = path6.join(root, "src");
1886
+ if (!fs6.existsSync(srcDir)) return false;
1964
1887
  const files = findFiles(srcDir, [".module.css", ".module.scss", ".module.less"]);
1965
1888
  return files.length > 0;
1966
1889
  }
1967
1890
  function findFiles(dir, extensions) {
1968
1891
  const results = [];
1969
1892
  try {
1970
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
1893
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
1971
1894
  for (const entry of entries) {
1972
- const fullPath = path7.join(dir, entry.name);
1895
+ const fullPath = path6.join(dir, entry.name);
1973
1896
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
1974
1897
  results.push(...findFiles(fullPath, extensions));
1975
1898
  } else if (entry.isFile()) {
@@ -2160,7 +2083,7 @@ async function handleSafeTerminalExec(params) {
2160
2083
  const workspaces = conventions?.workspaces;
2161
2084
  const matched = workspaces?.find((w) => w.name === workspace || w.path === workspace);
2162
2085
  if (matched) {
2163
- workingDir = path8.resolve(projectRoot, matched.path);
2086
+ workingDir = path7.resolve(projectRoot, matched.path);
2164
2087
  resolvedFrom = `workspace "${matched.name}" \u2192 ${matched.path}`;
2165
2088
  } else {
2166
2089
  return `\u26A0\uFE0F Workspace "${workspace}" not found. Run project_conventions first to detect workspaces, or use 'cwd' parameter with a direct path.
@@ -2168,13 +2091,13 @@ async function handleSafeTerminalExec(params) {
2168
2091
  Available workspaces: ${workspaces?.map((w) => `"${w.name}" (${w.path})`).join(", ") || "none detected"}`;
2169
2092
  }
2170
2093
  } else if (inputCwd) {
2171
- const resolved = path8.resolve(projectRoot, inputCwd);
2172
- const normalizedResolved = path8.normalize(resolved).toLowerCase();
2173
- const normalizedRoot = path8.normalize(projectRoot).toLowerCase();
2094
+ const resolved = path7.resolve(projectRoot, inputCwd);
2095
+ const normalizedResolved = path7.normalize(resolved).toLowerCase();
2096
+ const normalizedRoot = path7.normalize(projectRoot).toLowerCase();
2174
2097
  if (!normalizedResolved.startsWith(normalizedRoot)) {
2175
2098
  return `\u{1F6AB} BLOCKED: Path "${inputCwd}" resolves outside project root "${projectRoot}".`;
2176
2099
  }
2177
- if (!fs8.existsSync(resolved)) {
2100
+ if (!fs7.existsSync(resolved)) {
2178
2101
  return `\u26A0\uFE0F Directory "${inputCwd}" does not exist at "${resolved}".`;
2179
2102
  }
2180
2103
  workingDir = resolved;
@@ -2260,8 +2183,8 @@ function formatTimeoutResult(command, timeout) {
2260
2183
  }
2261
2184
 
2262
2185
  // src/agents/codeReviewer.ts
2263
- import fs9 from "fs";
2264
- import path9 from "path";
2186
+ import fs8 from "fs";
2187
+ import path8 from "path";
2265
2188
  import { execSync } from "child_process";
2266
2189
  var CODE_EXTENSIONS = [
2267
2190
  ".ts",
@@ -2275,7 +2198,19 @@ var CODE_EXTENSIONS = [
2275
2198
  ".rs",
2276
2199
  ".php",
2277
2200
  ".cs",
2278
- ".java"
2201
+ ".java",
2202
+ ".json",
2203
+ ".jsonc",
2204
+ ".yaml",
2205
+ ".yml",
2206
+ ".toml"
2207
+ ];
2208
+ var CONFIG_EXTENSIONS = [
2209
+ ".json",
2210
+ ".jsonc",
2211
+ ".yaml",
2212
+ ".yml",
2213
+ ".toml"
2279
2214
  ];
2280
2215
  function getGitChangedFiles() {
2281
2216
  try {
@@ -2293,7 +2228,7 @@ function getGitChangedFiles() {
2293
2228
  if (filePath.startsWith('"') && filePath.endsWith('"')) {
2294
2229
  filePath = filePath.substring(1, filePath.length - 1);
2295
2230
  }
2296
- const ext = path9.extname(filePath).toLowerCase();
2231
+ const ext = path8.extname(filePath).toLowerCase();
2297
2232
  if (CODE_EXTENSIONS.includes(ext)) files.push(filePath);
2298
2233
  }
2299
2234
  return files.slice(0, 10);
@@ -2303,7 +2238,7 @@ function getGitChangedFiles() {
2303
2238
  }
2304
2239
  }
2305
2240
  async function handleCodeReviewer(params) {
2306
- const { files: inputFiles, focus = "correctness", customCriteria, format = "text" } = params;
2241
+ const { files: inputFiles, focus = "correctness", customCriteria, format = "text", convention } = params;
2307
2242
  let files = inputFiles ?? [];
2308
2243
  let isAutoDetected = false;
2309
2244
  if (files.length === 0) {
@@ -2330,7 +2265,7 @@ Pass an explicit 'files' array to review specific files.`;
2330
2265
  continue;
2331
2266
  }
2332
2267
  const resolvedPath = validation.resolvedPath;
2333
- if (!fs9.existsSync(resolvedPath)) {
2268
+ if (!fs8.existsSync(resolvedPath)) {
2334
2269
  allIssues.push({
2335
2270
  file: filePath,
2336
2271
  line: 0,
@@ -2342,7 +2277,7 @@ Pass an explicit 'files' array to review specific files.`;
2342
2277
  continue;
2343
2278
  }
2344
2279
  try {
2345
- const content = fs9.readFileSync(resolvedPath, "utf-8");
2280
+ const content = fs8.readFileSync(resolvedPath, "utf-8");
2346
2281
  filesReviewed++;
2347
2282
  checkGeneral(filePath, content, allIssues);
2348
2283
  switch (focus) {
@@ -2362,6 +2297,9 @@ Pass an explicit 'files' array to review specific files.`;
2362
2297
  checkOverEngineering(filePath, content, allIssues);
2363
2298
  break;
2364
2299
  }
2300
+ if (convention === "matcha") {
2301
+ checkMatchaConventions(filePath, content, allIssues);
2302
+ }
2365
2303
  } catch (err) {
2366
2304
  allIssues.push({
2367
2305
  file: filePath,
@@ -2389,6 +2327,7 @@ Pass an explicit 'files' array to review specific files.`;
2389
2327
  filesReviewed,
2390
2328
  filesRequested: files.length,
2391
2329
  focus,
2330
+ convention,
2392
2331
  autoDetected: isAutoDetected,
2393
2332
  ...customCriteria ? { customCriteria } : {}
2394
2333
  };
@@ -2436,11 +2375,12 @@ function isTestFile(filePath) {
2436
2375
  return /\.(test|spec)\.[a-z]+$/i.test(filePath) || /__tests__/.test(filePath);
2437
2376
  }
2438
2377
  function isTsLike(filePath) {
2439
- const ext = path9.extname(filePath).toLowerCase();
2378
+ const ext = path8.extname(filePath).toLowerCase();
2440
2379
  return ext === ".ts" || ext === ".tsx";
2441
2380
  }
2442
2381
  function checkGeneral(filePath, content, issues) {
2443
2382
  const lines = content.split("\n");
2383
+ const ext = path8.extname(filePath).toLowerCase();
2444
2384
  if (content.length > 0 && !content.endsWith("\n")) {
2445
2385
  issues.push({
2446
2386
  file: filePath,
@@ -2461,6 +2401,9 @@ function checkGeneral(filePath, content, issues) {
2461
2401
  suggestion: "Extract cohesive sections into separate modules"
2462
2402
  });
2463
2403
  }
2404
+ if (CONFIG_EXTENSIONS.includes(ext)) {
2405
+ checkConfigFile(filePath, content, issues);
2406
+ }
2464
2407
  }
2465
2408
  function checkCorrectness(filePath, content, issues) {
2466
2409
  const lines = content.split("\n");
@@ -2623,6 +2566,282 @@ function checkCorrectness(filePath, content, issues) {
2623
2566
  });
2624
2567
  }
2625
2568
  }
2569
+ function stripJsonComments(text) {
2570
+ let result = text.replace(/\/\*[\s\S]*?\*\//g, "");
2571
+ const lines = result.split("\n");
2572
+ const cleaned = lines.map((line) => {
2573
+ const commentIdx = line.indexOf("//");
2574
+ if (commentIdx === -1) return line;
2575
+ const before = line.substring(0, commentIdx);
2576
+ const quoteCount = (before.match(/"/g) || []).length;
2577
+ if (quoteCount % 2 === 0) {
2578
+ return before;
2579
+ }
2580
+ return line;
2581
+ });
2582
+ return cleaned.join("\n");
2583
+ }
2584
+ function checkConfigFile(filePath, content, issues) {
2585
+ const ext = path8.extname(filePath).toLowerCase();
2586
+ const lines = content.split("\n");
2587
+ if (ext === ".json" || ext === ".jsonc") {
2588
+ checkJson(filePath, content, lines, issues);
2589
+ } else if (ext === ".yaml" || ext === ".yml") {
2590
+ checkYaml(filePath, content, lines, issues);
2591
+ } else if (ext === ".toml") {
2592
+ checkToml(filePath, content, lines, issues);
2593
+ }
2594
+ }
2595
+ function checkJson(filePath, content, lines, issues) {
2596
+ const ext = path8.extname(filePath).toLowerCase();
2597
+ const isJsonc = ext === ".jsonc";
2598
+ const contentToParse = isJsonc ? stripJsonComments(content) : content;
2599
+ try {
2600
+ JSON.parse(contentToParse);
2601
+ } catch (err) {
2602
+ const msg = err instanceof Error ? err.message : String(err);
2603
+ if (isJsonc && /\/\/|\/\*/.test(content)) {
2604
+ issues.push({
2605
+ file: filePath,
2606
+ line: 1,
2607
+ severity: "info",
2608
+ rule: "jsonc/comments-stripped",
2609
+ message: "JSON with Comments \u2014 comments were stripped for validation. Check the remaining syntax.",
2610
+ suggestion: "If issues remain, check quotes, commas, braces after comment removal"
2611
+ });
2612
+ return;
2613
+ }
2614
+ const posMatch = msg.match(/position\s+(\d+)/i);
2615
+ const lineMatch = msg.match(/line\s+(\d+)/i);
2616
+ let errorLine = 1;
2617
+ if (lineMatch) {
2618
+ errorLine = parseInt(lineMatch[1], 10);
2619
+ } else if (posMatch) {
2620
+ const pos = parseInt(posMatch[1], 10);
2621
+ let charCount = 0;
2622
+ for (let i = 0; i < lines.length; i++) {
2623
+ charCount += lines[i].length + 1;
2624
+ if (charCount >= pos) {
2625
+ errorLine = i + 1;
2626
+ break;
2627
+ }
2628
+ }
2629
+ }
2630
+ issues.push({
2631
+ file: filePath,
2632
+ line: errorLine,
2633
+ severity: "error",
2634
+ rule: "json/parse-error",
2635
+ message: `Invalid JSON: ${msg.substring(0, 120)}`,
2636
+ suggestion: "Fix the syntax error \u2014 check quotes, commas, braces, and brackets"
2637
+ });
2638
+ }
2639
+ for (let i = 0; i < lines.length; i++) {
2640
+ const trimmed = lines[i].trim();
2641
+ if (!/,\s*$/.test(trimmed)) continue;
2642
+ let nextNonEmpty = "";
2643
+ for (let j = i + 1; j < lines.length; j++) {
2644
+ const t = lines[j].trim();
2645
+ if (t && !t.startsWith("//") && !t.startsWith("*") && !t.startsWith("#")) {
2646
+ nextNonEmpty = t;
2647
+ break;
2648
+ }
2649
+ }
2650
+ if (nextNonEmpty === "]" || nextNonEmpty === "}" || nextNonEmpty === "]," || nextNonEmpty === "},") {
2651
+ issues.push({
2652
+ file: filePath,
2653
+ line: i + 1,
2654
+ severity: "error",
2655
+ rule: "json/trailing-comma",
2656
+ message: "Trailing comma before closing bracket/brace",
2657
+ suggestion: "Remove the trailing comma after the last element"
2658
+ });
2659
+ }
2660
+ }
2661
+ try {
2662
+ const parsed = JSON.parse(content);
2663
+ findDuplicateKeys(parsed, "", filePath, issues, lines);
2664
+ } catch {
2665
+ }
2666
+ for (let i = 0; i < lines.length; i++) {
2667
+ const line = lines[i];
2668
+ if ((/\s+'[a-zA-Z_][a-zA-Z0-9_]*'\s*:/.test(line) || /:\s*'[^']*'\s*[,}\]$]/.test(line)) && !/\/\//.test(line)) {
2669
+ issues.push({
2670
+ file: filePath,
2671
+ line: i + 1,
2672
+ severity: "warning",
2673
+ rule: "json/single-quote",
2674
+ message: "Single quotes used \u2014 JSON requires double quotes",
2675
+ suggestion: `Replace ' with "`
2676
+ });
2677
+ break;
2678
+ }
2679
+ }
2680
+ }
2681
+ function findDuplicateKeys(obj, nestedPath, filePath, issues, lines) {
2682
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return;
2683
+ const keys = Object.keys(obj);
2684
+ const seen = /* @__PURE__ */ new Map();
2685
+ for (const key of keys) {
2686
+ if (seen.has(key)) {
2687
+ const searchStr = `"${key}"`;
2688
+ let lineNum = 1;
2689
+ for (let i = 0; i < lines.length; i++) {
2690
+ if (lines[i].includes(searchStr)) {
2691
+ lineNum = i + 1;
2692
+ break;
2693
+ }
2694
+ }
2695
+ issues.push({
2696
+ file: filePath,
2697
+ line: lineNum,
2698
+ severity: "warning",
2699
+ rule: "json/duplicate-key",
2700
+ message: `Duplicate key "${key}" in ${nestedPath || "root"}`,
2701
+ suggestion: "Remove or rename the duplicate \u2014 last value wins in JSON"
2702
+ });
2703
+ }
2704
+ seen.set(key, 1);
2705
+ }
2706
+ for (const key of keys) {
2707
+ const deeperPath = nestedPath ? `${nestedPath}.${key}` : key;
2708
+ findDuplicateKeys(obj[key], deeperPath, filePath, issues, lines);
2709
+ }
2710
+ }
2711
+ function checkYaml(filePath, _content, lines, issues) {
2712
+ let hasTabs = false;
2713
+ let hasSpaces = false;
2714
+ let inconsistentIndent = false;
2715
+ for (let i = 0; i < lines.length; i++) {
2716
+ const rawLine = lines[i];
2717
+ const trimmed = rawLine.trim();
2718
+ if (!trimmed || trimmed.startsWith("#")) continue;
2719
+ if (rawLine.startsWith(" ")) hasTabs = true;
2720
+ const spaceMatch = rawLine.match(/^( +)/);
2721
+ if (spaceMatch) {
2722
+ hasSpaces = true;
2723
+ const indent = spaceMatch[1].length;
2724
+ if (indent % 2 !== 0 && indent !== 0) {
2725
+ inconsistentIndent = true;
2726
+ }
2727
+ }
2728
+ }
2729
+ if (hasTabs) {
2730
+ issues.push({
2731
+ file: filePath,
2732
+ line: 1,
2733
+ severity: "error",
2734
+ rule: "yaml/tab-indent",
2735
+ message: "YAML uses spaces for indentation \u2014 tabs are not allowed",
2736
+ suggestion: "Replace tabs with spaces (2 spaces per indent level)"
2737
+ });
2738
+ }
2739
+ if (inconsistentIndent) {
2740
+ issues.push({
2741
+ file: filePath,
2742
+ line: 1,
2743
+ severity: "warning",
2744
+ rule: "yaml/inconsistent-indent",
2745
+ message: "Indentation depth is not a multiple of 2 \u2014 may cause parsing issues",
2746
+ suggestion: "Use consistent 2-space indentation throughout the file"
2747
+ });
2748
+ }
2749
+ for (let i = 0; i < lines.length; i++) {
2750
+ const trimmed = lines[i].trim();
2751
+ if (/^https?:\/\//.test(trimmed) || /:\s*https?:\/\//.test(trimmed)) {
2752
+ issues.push({
2753
+ file: filePath,
2754
+ line: i + 1,
2755
+ severity: "info",
2756
+ rule: "yaml/unquoted-url",
2757
+ message: "URL should be quoted to avoid colon parsing ambiguity",
2758
+ suggestion: 'Wrap URL in quotes: "https://..."'
2759
+ });
2760
+ }
2761
+ if (/^\s*\w+:\s*(yes|no|on|off)\s*$/.test(lines[i])) {
2762
+ issues.push({
2763
+ file: filePath,
2764
+ line: i + 1,
2765
+ severity: "info",
2766
+ rule: "yaml/ambiguous-bool",
2767
+ message: "Ambiguous boolean value \u2014 YAML interprets yes/no/on/off as booleans",
2768
+ suggestion: "Use true/false instead for clarity, or quote the value"
2769
+ });
2770
+ }
2771
+ }
2772
+ if (hasTabs && hasSpaces) {
2773
+ issues.push({
2774
+ file: filePath,
2775
+ line: 1,
2776
+ severity: "error",
2777
+ rule: "yaml/mixed-indent",
2778
+ message: "Mixed tabs and spaces in indentation",
2779
+ suggestion: "Use spaces only (2 spaces per level)"
2780
+ });
2781
+ }
2782
+ }
2783
+ function checkToml(filePath, _content, lines, issues) {
2784
+ const seenKeys = /* @__PURE__ */ new Map();
2785
+ const arrayTables = /* @__PURE__ */ new Set();
2786
+ let currentTable = "";
2787
+ for (let i = 0; i < lines.length; i++) {
2788
+ const trimmed = lines[i].trim();
2789
+ if (!trimmed || trimmed.startsWith("#")) continue;
2790
+ const isArrayTable = trimmed.startsWith("[[");
2791
+ const tableMatch = trimmed.match(/^\[\[?(.+?)\]?\]$/);
2792
+ if (tableMatch) {
2793
+ currentTable = tableMatch[1];
2794
+ if (isArrayTable) {
2795
+ arrayTables.add(currentTable);
2796
+ }
2797
+ continue;
2798
+ }
2799
+ const kvMatch = trimmed.match(/^(\w+)\s*=/);
2800
+ if (kvMatch) {
2801
+ const fullKey = currentTable ? `${currentTable}.${kvMatch[1]}` : kvMatch[1];
2802
+ const parentTable = currentTable.split(".").slice(0, -1).join(".");
2803
+ const isInArrayTable = arrayTables.has(currentTable) || arrayTables.has(parentTable);
2804
+ if (seenKeys.has(fullKey)) {
2805
+ if (!isInArrayTable) {
2806
+ const prevLine = seenKeys.get(fullKey);
2807
+ issues.push({
2808
+ file: filePath,
2809
+ line: i + 1,
2810
+ severity: "warning",
2811
+ rule: "toml/duplicate-key",
2812
+ message: `Duplicate key "${fullKey}" (first defined at line ${prevLine})`,
2813
+ suggestion: "Remove or rename the duplicate \u2014 only last value is kept"
2814
+ });
2815
+ }
2816
+ } else {
2817
+ seenKeys.set(fullKey, i + 1);
2818
+ }
2819
+ }
2820
+ if (/\{[^}]*\}/.test(trimmed)) {
2821
+ const inlineContent = trimmed.match(/\{([^}]*)\}/);
2822
+ if (inlineContent) {
2823
+ const inlineKeys = inlineContent[1].match(/(\w+)\s*=/g);
2824
+ if (inlineKeys) {
2825
+ const inlineSeen = /* @__PURE__ */ new Set();
2826
+ for (const k of inlineKeys) {
2827
+ const keyName = k.replace(/\s*=\s*$/, "").trim();
2828
+ if (inlineSeen.has(keyName)) {
2829
+ issues.push({
2830
+ file: filePath,
2831
+ line: i + 1,
2832
+ severity: "warning",
2833
+ rule: "toml/duplicate-inline-key",
2834
+ message: `Duplicate key "${keyName}" in inline table`,
2835
+ suggestion: "Remove the duplicate key"
2836
+ });
2837
+ }
2838
+ inlineSeen.add(keyName);
2839
+ }
2840
+ }
2841
+ }
2842
+ }
2843
+ }
2844
+ }
2626
2845
  function checkConventions(filePath, content, issues) {
2627
2846
  const lines = content.split("\n");
2628
2847
  let hasTabs = false;
@@ -2915,8 +3134,8 @@ function collectBlockLines(lines, startIdx) {
2915
3134
  let depth = 0;
2916
3135
  let started = false;
2917
3136
  const block = [];
2918
- for (let j = startIdx; j < lines.length; j++) {
2919
- const line = lines[j];
3137
+ for (let i = startIdx; i < lines.length; i++) {
3138
+ const line = lines[i];
2920
3139
  block.push(line);
2921
3140
  for (const ch of line) {
2922
3141
  if (ch === "{") {
@@ -2933,6 +3152,73 @@ function extractBodyAfter(lines, match) {
2933
3152
  if (startIdx < 0) return "";
2934
3153
  return collectBlockLines(lines, startIdx).join("\n");
2935
3154
  }
3155
+ function checkMatchaConventions(filePath, content, issues) {
3156
+ const lines = content.split("\n");
3157
+ const text = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
3158
+ for (let i = 0; i < lines.length; i++) {
3159
+ const line = stripCommentsAndStrings(lines[i]);
3160
+ const lineNum = i + 1;
3161
+ if (/\b(?:const|let|var)\s+\w+\s*=\s*(?:[2-9]|\d{2,})\b/.test(line)) {
3162
+ if (!/eslint-disable/.test(lines[i])) {
3163
+ issues.push({
3164
+ file: filePath,
3165
+ line: lineNum,
3166
+ severity: "info",
3167
+ rule: "matcha/no-hardcoded-values",
3168
+ message: "Possible hardcoded numeric value (magic number)",
3169
+ suggestion: "Extract to a named constant"
3170
+ });
3171
+ }
3172
+ }
3173
+ const envMatch = line.match(/process\.env\.([A-Z0-9_]+)/);
3174
+ if (envMatch) {
3175
+ const envName = envMatch[1];
3176
+ if (!envName.includes("_")) {
3177
+ issues.push({
3178
+ file: filePath,
3179
+ line: lineNum,
3180
+ severity: "warning",
3181
+ rule: "matcha/env-prefix",
3182
+ message: `Environment variable "${envName}" doesn't seem to have a prefix`,
3183
+ suggestion: "Use an APPNAME_ prefix for environment variables"
3184
+ });
3185
+ }
3186
+ }
3187
+ }
3188
+ const interfaceNames = [];
3189
+ for (let i = 0; i < lines.length; i++) {
3190
+ const m = lines[i].match(/^\s*(export\s+)?interface\s+(\w+)/);
3191
+ if (m) interfaceNames.push(m[2]);
3192
+ }
3193
+ for (const iface of interfaceNames) {
3194
+ const implCount = (text.match(new RegExp(`implements\\s+.*\\b${iface}\\b`, "g")) || []).length;
3195
+ if (implCount <= 1) {
3196
+ const lineNum = lines.findIndex((l) => l.includes(`interface ${iface}`)) + 1;
3197
+ issues.push({
3198
+ file: filePath,
3199
+ line: lineNum,
3200
+ severity: "info",
3201
+ rule: "matcha/no-premature-abstraction",
3202
+ message: `Interface "${iface}" has only 1 implementation \u2014 premature abstraction?`,
3203
+ suggestion: "Wait for a 2nd use case before abstracting"
3204
+ });
3205
+ }
3206
+ }
3207
+ const funcBodies = findBlockBodies(lines, /^\s*(export\s+)?(async\s+)?function\s+\w+/);
3208
+ for (const { body, nameLine, name } of funcBodies) {
3209
+ const nonEmptyLines = body.split("\n").filter((l) => l.trim()).length;
3210
+ if (nonEmptyLines > 40) {
3211
+ issues.push({
3212
+ file: filePath,
3213
+ line: nameLine,
3214
+ severity: "warning",
3215
+ rule: "matcha/single-responsibility",
3216
+ message: `Function "${name}" is ${nonEmptyLines} lines long \u2014 might be doing >1 thing`,
3217
+ suggestion: "Extract into smaller, focused functions"
3218
+ });
3219
+ }
3220
+ }
3221
+ }
2936
3222
  function formatReviewOutput(issues, filesReviewed, totalFiles, focus, customCriteria, autoDetected) {
2937
3223
  const errors = issues.filter((i) => i.severity === "error");
2938
3224
  const warnings = issues.filter((i) => i.severity === "warning");
@@ -3164,9 +3450,9 @@ async function handleGitDiff(params) {
3164
3450
  }
3165
3451
 
3166
3452
  // src/tools/projectStructure.ts
3167
- import fs10 from "fs";
3168
- import path10 from "path";
3169
- var DEFAULT_IGNORE = [
3453
+ import fs9 from "fs";
3454
+ import path9 from "path";
3455
+ var DEFAULT_IGNORE = [
3170
3456
  "node_modules",
3171
3457
  ".git",
3172
3458
  ".kuma",
@@ -3202,7 +3488,7 @@ async function handleProjectStructure(params) {
3202
3488
  sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
3203
3489
  try {
3204
3490
  const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
3205
- const projectName = path10.basename(root);
3491
+ const projectName = path9.basename(root);
3206
3492
  const lines = [
3207
3493
  "[Project Structure] - " + projectName,
3208
3494
  "Depth: " + clampedDepth + " | " + (folderOnly ? "Folders only" : "Files and folders"),
@@ -3225,7 +3511,7 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3225
3511
  const lines = [];
3226
3512
  let entries = [];
3227
3513
  try {
3228
- entries = fs10.readdirSync(currentDir, { withFileTypes: true });
3514
+ entries = fs9.readdirSync(currentDir, { withFileTypes: true });
3229
3515
  } catch {
3230
3516
  return lines;
3231
3517
  }
@@ -3234,12 +3520,12 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3234
3520
  if (!a.isDirectory() && b.isDirectory()) return 1;
3235
3521
  return a.name.localeCompare(b.name);
3236
3522
  });
3237
- const relativeDir = path10.relative(root, currentDir) || ".";
3523
+ const relativeDir = path9.relative(root, currentDir) || ".";
3238
3524
  const prefix = getPrefix(currentDepth);
3239
3525
  for (const entry of entries) {
3240
3526
  if (shouldIgnore(entry.name, relativeDir, root)) continue;
3241
- const fullPath = path10.join(currentDir, entry.name);
3242
- const relativePath = path10.relative(root, fullPath);
3527
+ const fullPath = path9.join(currentDir, entry.name);
3528
+ const relativePath = path9.relative(root, fullPath);
3243
3529
  if (includePattern && !entry.name.includes(includePattern) && !relativePath.includes(includePattern)) {
3244
3530
  if (!entry.isDirectory()) continue;
3245
3531
  }
@@ -3250,11 +3536,11 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3250
3536
  lines.push(prefix + "[D] " + entry.name + "/");
3251
3537
  let subEntries = [];
3252
3538
  try {
3253
- subEntries = fs10.readdirSync(fullPath, { withFileTypes: true });
3539
+ subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
3254
3540
  } catch {
3255
3541
  }
3256
3542
  const hasVisibleContent = subEntries.some(
3257
- (e) => !shouldIgnore(e.name, path10.relative(root, fullPath), root)
3543
+ (e) => !shouldIgnore(e.name, path9.relative(root, fullPath), root)
3258
3544
  );
3259
3545
  if (hasVisibleContent) {
3260
3546
  const subLines = buildTree(root, fullPath, maxDepth, currentDepth + 1, folderOnly, includePattern, excludePattern);
@@ -3285,7 +3571,7 @@ function getPrefix(depth) {
3285
3571
  }
3286
3572
  function getFileSize(fullPath) {
3287
3573
  try {
3288
- const stat = fs10.statSync(fullPath);
3574
+ const stat = fs9.statSync(fullPath);
3289
3575
  if (stat.size < 1024) return stat.size + "B";
3290
3576
  if (stat.size < 1024 * 1024) return (stat.size / 1024).toFixed(0) + "KB";
3291
3577
  const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
@@ -3296,8 +3582,8 @@ function getFileSize(fullPath) {
3296
3582
  }
3297
3583
 
3298
3584
  // src/tools/staticAnalysis.ts
3299
- import fs11 from "fs";
3300
- import path11 from "path";
3585
+ import fs10 from "fs";
3586
+ import path10 from "path";
3301
3587
  async function handleStaticAnalysis(params) {
3302
3588
  const { tool = "all", files, autoFix = false, timeout = 60 } = params;
3303
3589
  const root = getProjectRoot();
@@ -3315,7 +3601,26 @@ async function handleStaticAnalysis(params) {
3315
3601
  for (const t of toolsToRun) {
3316
3602
  const output = await runTool(t, root, files, autoFix, timeout);
3317
3603
  if (output === null) {
3318
- results.push({ tool: t, exitCode: -1, issues: [], summary: { errors: 0, warnings: 0, info: 0 } });
3604
+ const cmd = buildToolCommand(t, files, autoFix);
3605
+ const reason = cmd === null ? `Could not build command for tool "${t}" (unknown tool type)` : `Command "${cmd}" returned no output (tool may not be installed)`;
3606
+ console.error(`[StaticAnalysis] ${reason}`);
3607
+ allIssues.push({
3608
+ file: `(${t} error)`,
3609
+ line: 0,
3610
+ column: 0,
3611
+ severity: "error",
3612
+ message: reason,
3613
+ rule: t,
3614
+ source: t
3615
+ });
3616
+ const result2 = {
3617
+ tool: t,
3618
+ exitCode: -1,
3619
+ issues: [{ file: `(${t} error)`, line: 0, column: 0, severity: "error", message: reason, rule: t, source: t }],
3620
+ summary: { errors: 1, warnings: 0, info: 0 }
3621
+ };
3622
+ results.push(result2);
3623
+ sessionMemory.addFailedFile("static_analysis:" + t, reason);
3319
3624
  continue;
3320
3625
  }
3321
3626
  const issues = parseToolOutput(t, output.stdout, output.stderr, root);
@@ -3331,6 +3636,22 @@ async function handleStaticAnalysis(params) {
3331
3636
  };
3332
3637
  results.push(result);
3333
3638
  allIssues.push(...issues);
3639
+ if (result.exitCode !== 0 && issues.length === 0 && output.stderr.trim()) {
3640
+ const stderrLines = output.stderr.trim().split("\n").slice(0, 8);
3641
+ console.error(`[StaticAnalysis] ${t} exited with code ${result.exitCode} (no parseable output):`);
3642
+ for (const errLine of stderrLines) {
3643
+ console.error(`[StaticAnalysis] ${errLine}`);
3644
+ }
3645
+ allIssues.push({
3646
+ file: `(${t} error)`,
3647
+ line: 0,
3648
+ column: 0,
3649
+ severity: "error",
3650
+ message: `Tool "${t}" exited with code ${result.exitCode}. Check stderr for details.`,
3651
+ rule: t,
3652
+ source: t
3653
+ });
3654
+ }
3334
3655
  if (result.summary.errors > 0 || result.summary.warnings > 0) {
3335
3656
  const errorMsg = result.summary.errors > 0 ? result.summary.errors + " error(s)" : result.summary.warnings + " warning(s)";
3336
3657
  sessionMemory.addFailedFile("static_analysis:" + t, errorMsg + " found");
@@ -3352,11 +3673,11 @@ function detectAvailableTools(root) {
3352
3673
  "eslint.config.js",
3353
3674
  "eslint.config.mjs"
3354
3675
  ];
3355
- const hasEslintConfig = eslintConfigs.some((cfg) => fs11.existsSync(path11.join(root, cfg)));
3676
+ const hasEslintConfig = eslintConfigs.some((cfg) => fs10.existsSync(path10.join(root, cfg)));
3356
3677
  if (hasEslintConfig && depNames.has("eslint")) {
3357
3678
  tools.push("eslint");
3358
3679
  }
3359
- if (fs11.existsSync(path11.join(root, "tsconfig.json")) && depNames.has("typescript")) {
3680
+ if (fs10.existsSync(path10.join(root, "tsconfig.json")) && depNames.has("typescript")) {
3360
3681
  tools.push("tsc");
3361
3682
  }
3362
3683
  const prettierConfigs = [
@@ -3367,20 +3688,20 @@ function detectAvailableTools(root) {
3367
3688
  ".prettierrc.toml",
3368
3689
  "prettier.config.js"
3369
3690
  ];
3370
- const hasPrettierConfig = prettierConfigs.some((cfg) => fs11.existsSync(path11.join(root, cfg)));
3691
+ const hasPrettierConfig = prettierConfigs.some((cfg) => fs10.existsSync(path10.join(root, cfg)));
3371
3692
  if (hasPrettierConfig && depNames.has("prettier")) {
3372
3693
  tools.push("prettier");
3373
3694
  }
3374
- if (fs11.existsSync(path11.join(root, "ruff.toml")) || fs11.existsSync(path11.join(root, ".ruff.toml"))) {
3695
+ if (fs10.existsSync(path10.join(root, "ruff.toml")) || fs10.existsSync(path10.join(root, ".ruff.toml"))) {
3375
3696
  tools.push("ruff");
3376
3697
  }
3377
3698
  return tools;
3378
3699
  }
3379
3700
  function readPackageJson(root) {
3380
3701
  try {
3381
- const pkgPath = path11.join(root, "package.json");
3382
- if (fs11.existsSync(pkgPath)) {
3383
- return JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
3702
+ const pkgPath = path10.join(root, "package.json");
3703
+ if (fs10.existsSync(pkgPath)) {
3704
+ return JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
3384
3705
  }
3385
3706
  } catch {
3386
3707
  }
@@ -3400,10 +3721,16 @@ async function runTool(tool, cwd, files, autoFix, timeoutSeconds) {
3400
3721
  }
3401
3722
  function buildToolCommand(tool, files, autoFix) {
3402
3723
  const pm = detectPackageManagerPrefix();
3724
+ const root = getProjectRoot();
3403
3725
  switch (tool) {
3404
3726
  case "eslint": {
3405
3727
  const fixFlag = autoFix ? " --fix" : "";
3406
3728
  const target = files ? files.join(" ") : ".";
3729
+ const eslintBin = findBinary(root, "eslint");
3730
+ if (!eslintBin) {
3731
+ console.error(`[StaticAnalysis] eslint not found locally. Trying npx (auto-install).`);
3732
+ return "npx eslint" + fixFlag + " " + target + " --format unix";
3733
+ }
3407
3734
  return pm + "eslint" + fixFlag + " " + target + " --format unix";
3408
3735
  }
3409
3736
  case "tsc": {
@@ -3412,6 +3739,11 @@ function buildToolCommand(tool, files, autoFix) {
3412
3739
  case "prettier": {
3413
3740
  const fixFlag = autoFix ? " --write" : " --check";
3414
3741
  const target = files ? files.join(" ") : ".";
3742
+ const prettierBin = findBinary(root, "prettier");
3743
+ if (!prettierBin) {
3744
+ console.error(`[StaticAnalysis] prettier not found locally. Trying npx (auto-install).`);
3745
+ return "npx prettier" + fixFlag + " " + target;
3746
+ }
3415
3747
  return pm + "prettier" + fixFlag + " " + target;
3416
3748
  }
3417
3749
  case "ruff": {
@@ -3425,10 +3757,25 @@ function buildToolCommand(tool, files, autoFix) {
3425
3757
  }
3426
3758
  function detectPackageManagerPrefix() {
3427
3759
  const root = getProjectRoot();
3428
- if (fs11.existsSync(path11.join(root, "pnpm-lock.yaml"))) return "pnpm ";
3429
- if (fs11.existsSync(path11.join(root, "yarn.lock"))) return "yarn ";
3760
+ if (fs10.existsSync(path10.join(root, "pnpm-lock.yaml"))) return "pnpm ";
3761
+ if (fs10.existsSync(path10.join(root, "yarn.lock"))) return "yarn ";
3430
3762
  return "npx --no-install ";
3431
3763
  }
3764
+ function findBinary(root, binName) {
3765
+ const candidates = [
3766
+ path10.join(root, "node_modules", ".bin", binName),
3767
+ path10.join(root, "..", "node_modules", ".bin", binName)
3768
+ ];
3769
+ for (const candidate of candidates) {
3770
+ try {
3771
+ if (fs10.existsSync(candidate)) {
3772
+ return candidate;
3773
+ }
3774
+ } catch {
3775
+ }
3776
+ }
3777
+ return null;
3778
+ }
3432
3779
  function parseToolOutput(tool, stdout, stderr, projectRoot) {
3433
3780
  switch (tool) {
3434
3781
  case "eslint":
@@ -3445,7 +3792,7 @@ function parseToolOutput(tool, stdout, stderr, projectRoot) {
3445
3792
  }
3446
3793
  function resolveToolPath(filePath, projectRoot) {
3447
3794
  const trimmed = filePath.trim();
3448
- return path11.isAbsolute(trimmed) ? path11.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
3795
+ return path10.isAbsolute(trimmed) ? path10.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
3449
3796
  }
3450
3797
  function parseEslintOutput(output, projectRoot) {
3451
3798
  const issues = [];
@@ -3558,10 +3905,13 @@ function formatResults2(results, allIssues, toolsRun) {
3558
3905
  if (!hasIssues) {
3559
3906
  const failedTools = results.filter((r) => r.exitCode !== 0);
3560
3907
  if (failedTools.length > 0) {
3561
- lines.push("Tool(s) executed but reported no parseable issues.");
3562
- lines.push("Failed tools: " + failedTools.map((t) => t.tool + " (exit: " + t.exitCode + ")").join(", "));
3908
+ lines.push("Tool(s) executed but failed:");
3909
+ for (const ft of failedTools) {
3910
+ lines.push(` - ${ft.tool} (exit code: ${ft.exitCode})`);
3911
+ }
3563
3912
  lines.push("");
3564
- lines.push("The tool may have encountered an error. Run it manually to see full output.");
3913
+ lines.push("The tools may not be installed correctly or encountered an error.");
3914
+ lines.push("Run the tool manually to see the full output.");
3565
3915
  } else {
3566
3916
  lines.push("All checks passed \u2014 no issues found.");
3567
3917
  }
@@ -3603,13 +3953,13 @@ function formatNoToolsDetected() {
3603
3953
  "No linters or checkers detected for this project.",
3604
3954
  "",
3605
3955
  "Detected tools include:",
3606
- " - ESLint (check .eslintrc or eslint.config.*)",
3607
- " - TypeScript (check tsconfig.json)",
3608
- " - Prettier (check .prettierrc)",
3609
- " - Ruff (check ruff.toml)",
3956
+ " - ESLint \u2014 check .eslintrc or eslint.config.*",
3957
+ " - tsc \u2014 check tsconfig.json",
3958
+ " - Prettier \u2014 check .prettierrc",
3959
+ " - Ruff \u2014 check ruff.toml",
3610
3960
  "",
3611
- "Install and configure a linter to use this tool.",
3612
- "Then run project_conventions to refresh detection."
3961
+ "Each needs both a config file AND the npm package installed.",
3962
+ "Install a linter, then run project_conventions to refresh."
3613
3963
  ].join("\n");
3614
3964
  }
3615
3965
  function formatToolNotAvailable(requested, available) {
@@ -3722,8 +4072,8 @@ async function handleReflect(params) {
3722
4072
  import { execSync as execSync6 } from "child_process";
3723
4073
 
3724
4074
  // src/guards/antiPatternDetector.ts
3725
- import fs12 from "fs";
3726
- import path12 from "path";
4075
+ import fs11 from "fs";
4076
+ import path11 from "path";
3727
4077
  import { execSync as execSync4 } from "child_process";
3728
4078
  var SCRIPT_PATCH_PATTERNS = [
3729
4079
  "writeFileSync",
@@ -3750,20 +4100,20 @@ function findPatchScripts(projectRoot) {
3750
4100
  const warnings = [];
3751
4101
  const recentFiles = scanRecentFiles(projectRoot);
3752
4102
  for (const file of recentFiles) {
3753
- const ext = path12.extname(file).toLowerCase();
4103
+ const ext = path11.extname(file).toLowerCase();
3754
4104
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
3755
- const relativePath = path12.relative(projectRoot, file);
4105
+ const relativePath = path11.relative(projectRoot, file);
3756
4106
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
3757
4107
  continue;
3758
4108
  }
3759
4109
  try {
3760
- const content = fs12.readFileSync(file, "utf-8").toLowerCase();
4110
+ const content = fs11.readFileSync(file, "utf-8").toLowerCase();
3761
4111
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
3762
4112
  if (matchedPattern) {
3763
4113
  warnings.push({
3764
4114
  severity: "high",
3765
4115
  pattern: "script-patching",
3766
- message: `Created script file that modifies other files: ${path12.basename(file)}`,
4116
+ message: `Created script file that modifies other files: ${path11.basename(file)}`,
3767
4117
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
3768
4118
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
3769
4119
  filePath: relativePath
@@ -3797,15 +4147,15 @@ function detectBashGrepUsage() {
3797
4147
  function scanRecentFiles(projectRoot) {
3798
4148
  const recent = [];
3799
4149
  try {
3800
- const entries = fs12.readdirSync(projectRoot, { withFileTypes: true });
4150
+ const entries = fs11.readdirSync(projectRoot, { withFileTypes: true });
3801
4151
  const now = Date.now();
3802
4152
  const recentThreshold = 30 * 60 * 1e3;
3803
4153
  for (const entry of entries) {
3804
4154
  if (!entry.isFile()) continue;
3805
4155
  try {
3806
- const stat = fs12.statSync(path12.join(projectRoot, entry.name));
4156
+ const stat = fs11.statSync(path11.join(projectRoot, entry.name));
3807
4157
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
3808
- recent.push(path12.join(projectRoot, entry.name));
4158
+ recent.push(path11.join(projectRoot, entry.name));
3809
4159
  }
3810
4160
  } catch {
3811
4161
  continue;
@@ -3829,14 +4179,14 @@ function detectGitPatchScripts(projectRoot) {
3829
4179
  const prefix = line.substring(0, 2);
3830
4180
  if (prefix !== "??" && prefix !== "A ") continue;
3831
4181
  const file = line.substring(3).trim();
3832
- const ext = path12.extname(file).toLowerCase();
4182
+ const ext = path11.extname(file).toLowerCase();
3833
4183
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
3834
4184
  const isRootLevel = !file.includes("/");
3835
4185
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
3836
4186
  if (!isRootLevel && !isScriptsDir) continue;
3837
- const fullPath = path12.join(projectRoot, file);
3838
- if (fs12.existsSync(fullPath)) {
3839
- const content = fs12.readFileSync(fullPath, "utf-8").toLowerCase();
4187
+ const fullPath = path11.join(projectRoot, file);
4188
+ if (fs11.existsSync(fullPath)) {
4189
+ const content = fs11.readFileSync(fullPath, "utf-8").toLowerCase();
3840
4190
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
3841
4191
  if (matchedPattern) {
3842
4192
  warnings.push({
@@ -3859,7 +4209,7 @@ function detectGitPatchScripts(projectRoot) {
3859
4209
  if (deletedStdout) {
3860
4210
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
3861
4211
  for (const file of deletedFiles) {
3862
- const ext = path12.extname(file).toLowerCase();
4212
+ const ext = path11.extname(file).toLowerCase();
3863
4213
  if (SCRIPT_EXTENSIONS.includes(ext)) {
3864
4214
  warnings.push({
3865
4215
  severity: "high",
@@ -3919,21 +4269,21 @@ function detectAllAntiPatterns() {
3919
4269
  }
3920
4270
 
3921
4271
  // src/engine/contextSnapshot.ts
3922
- import fs13 from "fs";
3923
- import path13 from "path";
4272
+ import fs12 from "fs";
4273
+ import path12 from "path";
3924
4274
  import { execSync as execSync5 } from "child_process";
3925
4275
  var SNAPSHOT_DIR = "context-snapshots";
3926
4276
  function snapshotDir() {
3927
- return path13.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
4277
+ return path12.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
3928
4278
  }
3929
4279
  function ensureSnapshotDir() {
3930
4280
  const dir = snapshotDir();
3931
- if (!fs13.existsSync(dir)) {
3932
- fs13.mkdirSync(dir, { recursive: true });
4281
+ if (!fs12.existsSync(dir)) {
4282
+ fs12.mkdirSync(dir, { recursive: true });
3933
4283
  }
3934
4284
  }
3935
4285
  function snapshotFilePath(timestamp) {
3936
- return path13.join(snapshotDir(), `${timestamp}.json`);
4286
+ return path12.join(snapshotDir(), `${timestamp}.json`);
3937
4287
  }
3938
4288
  function saveSnapshot(goal) {
3939
4289
  try {
@@ -3954,7 +4304,7 @@ function saveSnapshot(goal) {
3954
4304
  hasConventions: !!summary.hasConventions
3955
4305
  };
3956
4306
  try {
3957
- fs13.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
4307
+ fs12.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
3958
4308
  } catch {
3959
4309
  }
3960
4310
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -3962,12 +4312,12 @@ function saveSnapshot(goal) {
3962
4312
  }
3963
4313
  function listSnapshots() {
3964
4314
  const dir = snapshotDir();
3965
- if (!fs13.existsSync(dir)) return [];
4315
+ if (!fs12.existsSync(dir)) return [];
3966
4316
  try {
3967
- const files = fs13.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
4317
+ const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
3968
4318
  return files.map((f) => {
3969
4319
  try {
3970
- const content = fs13.readFileSync(path13.join(dir, f), "utf-8");
4320
+ const content = fs12.readFileSync(path12.join(dir, f), "utf-8");
3971
4321
  return JSON.parse(content);
3972
4322
  } catch {
3973
4323
  return null;
@@ -4173,6 +4523,68 @@ async function handleKumaContext(params) {
4173
4523
  }
4174
4524
  }
4175
4525
 
4526
+ // src/tools/kumaInit.ts
4527
+ import fs13 from "fs";
4528
+ import path13 from "path";
4529
+ async function handleKumaInit(params) {
4530
+ const root = params.projectRoot ?? getProjectRoot();
4531
+ const kumaDir = path13.join(root, ".kuma");
4532
+ const memoriesDir = path13.join(kumaDir, "memories");
4533
+ const initMdPath = path13.join(kumaDir, "init.md");
4534
+ sessionMemory.recordToolCall("kuma_init", { projectRoot: root });
4535
+ let rules = "";
4536
+ if (fs13.existsSync(initMdPath)) {
4537
+ rules = fs13.readFileSync(initMdPath, "utf-8");
4538
+ }
4539
+ const memories = [];
4540
+ if (fs13.existsSync(memoriesDir)) {
4541
+ try {
4542
+ const files = fs13.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
4543
+ for (const file of files) {
4544
+ try {
4545
+ const content = fs13.readFileSync(path13.join(memoriesDir, file), "utf-8");
4546
+ memories.push({ topic: file.replace(/\.md$/, ""), content });
4547
+ } catch {
4548
+ }
4549
+ }
4550
+ } catch {
4551
+ }
4552
+ }
4553
+ const sessionLoad = sessionMemory.loadSession();
4554
+ const sections = [
4555
+ "\u{1F9E0} **Kuma Context Loaded**",
4556
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
4557
+ ];
4558
+ const rulesLineCount = rules ? rules.split("\n").filter((l) => l.trim()).length : 0;
4559
+ sections.push(`\u{1F4CB} **Rules:** ${rulesLineCount} lines from .kuma/init.md`);
4560
+ if (memories.length > 0) {
4561
+ const memoryList = memories.map((m) => ` \u2022 **${m.topic}** \u2014 ${m.content.split("\n").filter((l) => l.trim()).length} lines`).join("\n");
4562
+ sections.push(`\u{1F4C1} **Memories (${memories.length}):**
4563
+ ${memoryList}`);
4564
+ } else {
4565
+ sections.push("\u{1F4C1} **Memories:** none yet \u2014 run project_conventions() to generate");
4566
+ }
4567
+ if (sessionLoad.hasPrevSession) {
4568
+ sections.push(
4569
+ "\u{1F4CA} **Previous Session:**",
4570
+ ` \u2022 \u{1F6E0}\uFE0F ${sessionLoad.toolCallCount} tool calls`,
4571
+ ` \u2022 ${sessionLoad.hasConventions ? "\u2705 conventions detected" : "\u274C no conventions"}`
4572
+ );
4573
+ } else {
4574
+ sections.push("\u{1F4CA} **Previous Session:** none \u2014 first session for this project");
4575
+ }
4576
+ sections.push(
4577
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
4578
+ "",
4579
+ "\u{1F4A1} **Next Steps:**",
4580
+ " \u2022 Previous session context has been loaded into session memory",
4581
+ ' \u2022 Call `get_session_memory({topic: "..."})` for detailed memory',
4582
+ " \u2022 If project hasn't been scanned, run `project_conventions()`",
4583
+ " \u2022 Or continue working \u2014 session state is ready."
4584
+ );
4585
+ return sections.join("\n");
4586
+ }
4587
+
4176
4588
  // src/engine/lspClient.ts
4177
4589
  import { spawn as spawn2 } from "child_process";
4178
4590
  import path14 from "path";
@@ -4295,7 +4707,8 @@ var LSPClient = class {
4295
4707
  }
4296
4708
  } catch {
4297
4709
  }
4298
- return null;
4710
+ console.error(`[LSP] typescript-language-server not found locally. Trying npx fallback.`);
4711
+ return "npx --yes typescript-language-server";
4299
4712
  }
4300
4713
  toUri(filePath) {
4301
4714
  return `file://${filePath}`;
@@ -4541,19 +4954,22 @@ async function handleFindReferences(params) {
4541
4954
  }
4542
4955
  if (!lspClient.isAvailable()) {
4543
4956
  sessionMemory.recordToolCall("lsp_query", { action: "refs", filePath, line, character, fallback: "regex" });
4957
+ console.error(`[LSP] typescript-language-server not found. Using regex fallback for find_references.`);
4544
4958
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4545
4959
  if (!symbolName) {
4546
- return `\u26A0\uFE0F LSP unavailable and cannot read symbol at that position for fallback grep.
4960
+ return `**Find References** \u2014 "${filePath}:${line + 1}:${character + 1}"
4961
+ Could not read symbol at this position.
4547
4962
 
4548
- \u{1F4A1} Install typescript-language-server: npm install typescript-language-server --save-dev`;
4963
+ Try selecting a smaller/cleaner position, or install typescript-language-server for better results:
4964
+ npm install typescript-language-server --save-dev`;
4549
4965
  }
4550
4966
  return fallbackGrepReferences(symbolName, resolvedPath, line, character);
4551
4967
  }
4552
4968
  try {
4553
4969
  const references = await lspClient.findReferences(resolvedPath, line, character);
4554
4970
  if (references.length === 0) {
4555
- return `\u{1F50D} **Find References** \u2014 "${filePath}:${line + 1}:${character + 1}"
4556
- \u26A0\uFE0F No references found for symbol at this position.`;
4971
+ return `**Find References** \u2014 "${filePath}:${line + 1}:${character + 1}"
4972
+ No references found for symbol at this position.`;
4557
4973
  }
4558
4974
  const enrichedRefs = references.map((ref) => {
4559
4975
  let lineContent = "";
@@ -4573,7 +4989,7 @@ async function handleFindReferences(params) {
4573
4989
  }
4574
4990
  const projectRoot = getProjectRoot();
4575
4991
  const lines = [
4576
- `\u{1F50D} **Find References** \u2014 ${enrichedRefs.length} references found`,
4992
+ `**Find References** \u2014 ${enrichedRefs.length} references found`,
4577
4993
  `\u{1F4CD} File: ${path15.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
4578
4994
  ""
4579
4995
  ];
@@ -4589,7 +5005,7 @@ async function handleFindReferences(params) {
4589
5005
  lines.push("\u{1F4A1} Use smart_file_picker to read specific files.");
4590
5006
  return lines.join("\n");
4591
5007
  } catch (err) {
4592
- return `Error finding references: ${err instanceof Error ? err.message : String(err)}`;
5008
+ return `**Find References** \u2014 error during lookup: ${err instanceof Error ? err.message : String(err)}`;
4593
5009
  }
4594
5010
  }
4595
5011
  async function handleGoToDefinition(params) {
@@ -4604,19 +5020,22 @@ async function handleGoToDefinition(params) {
4604
5020
  }
4605
5021
  if (!lspClient.isAvailable()) {
4606
5022
  sessionMemory.recordToolCall("lsp_query", { action: "def", filePath, line, character, fallback: "regex" });
5023
+ console.error(`[LSP] typescript-language-server not found. Using regex fallback for go_to_definition.`);
4607
5024
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4608
5025
  if (!symbolName) {
4609
- return `\u26A0\uFE0F LSP unavailable and cannot read symbol at that position for fallback.
5026
+ return `**Go to Definition** \u2014 "${filePath}:${line + 1}:${character + 1}"
5027
+ Could not read symbol at this position.
4610
5028
 
4611
- \u{1F4A1} Install typescript-language-server: npm install typescript-language-server --save-dev`;
5029
+ Try selecting a smaller/cleaner position, or install typescript-language-server for better results:
5030
+ npm install typescript-language-server --save-dev`;
4612
5031
  }
4613
5032
  return fallbackGrepDefinition(symbolName);
4614
5033
  }
4615
5034
  try {
4616
5035
  const definition = await lspClient.goToDefinition(resolvedPath, line, character);
4617
5036
  if (!definition) {
4618
- return `\u{1F50D} **Go to Definition** \u2014 "${filePath}:${line + 1}:${character + 1}"
4619
- \u26A0\uFE0F Cannot find definition for symbol at this position.`;
5037
+ return `**Go to Definition** \u2014 "${filePath}:${line + 1}:${character + 1}"
5038
+ Cannot find definition for symbol at this position.`;
4620
5039
  }
4621
5040
  const projectRoot = getProjectRoot();
4622
5041
  const relPath = path15.relative(projectRoot, definition.filePath);
@@ -4641,7 +5060,7 @@ async function handleGoToDefinition(params) {
4641
5060
  ];
4642
5061
  return lines.join("\n");
4643
5062
  } catch (err) {
4644
- return `Error finding definition: ${err instanceof Error ? err.message : String(err)}`;
5063
+ return `**Go to Definition** \u2014 error during lookup: ${err instanceof Error ? err.message : String(err)}`;
4645
5064
  }
4646
5065
  }
4647
5066
  async function handleRenameSymbol(params) {
@@ -4659,11 +5078,13 @@ async function handleRenameSymbol(params) {
4659
5078
  }
4660
5079
  if (!lspClient.isAvailable()) {
4661
5080
  sessionMemory.recordToolCall("lsp_query", { action: "rename", filePath, line, character, newName, fallback: "none" });
4662
- return `\u26A0\uFE0F **Rename Symbol** unavailable without LSP server.
4663
- Rename requires typescript-language-server to track references across all files.
4664
- \u{1F4A1} Install: npm install typescript-language-server --save-dev
5081
+ console.error(`[LSP] typescript-language-server not found. Rename requires LSP server.`);
5082
+ return `**Rename Symbol** \u2014 cannot rename without language server.
5083
+ Rename needs typescript-language-server to track references across all files.
5084
+
5085
+ Install: npm install typescript-language-server --save-dev
4665
5086
 
4666
- Meanwhile, use smart_grep to find all references, then precise_diff_editor for manual editing.`;
5087
+ Meanwhile, use smart_grep to find all references, then precise_diff_editor to rename manually.`;
4667
5088
  }
4668
5089
  try {
4669
5090
  const result = await lspClient.renameSymbol(resolvedPath, line, character, newName);
@@ -4676,7 +5097,7 @@ Make sure:
4676
5097
  \`\`\``;
4677
5098
  }
4678
5099
  if (result.changes.length === 0) {
4679
- return "\u26A0\uFE0F No changes needed.";
5100
+ return "No changes needed.";
4680
5101
  }
4681
5102
  const projectRoot = getProjectRoot();
4682
5103
  let totalEdits = 0;
@@ -4732,12 +5153,13 @@ async function handleGetTypeInfo(params) {
4732
5153
  }
4733
5154
  if (!lspClient.isAvailable()) {
4734
5155
  sessionMemory.recordToolCall("lsp_query", { action: "type", filePath, line, character, fallback: "line-context" });
5156
+ console.error(`[LSP] typescript-language-server not found. Using file-read fallback for type info.`);
4735
5157
  try {
4736
5158
  const fileContent = fs15.readFileSync(resolvedPath, "utf-8");
4737
5159
  const fileLines = fileContent.split("\n");
4738
5160
  const targetLine = fileLines[line];
4739
5161
  if (!targetLine) {
4740
- return `\u26A0\uFE0F **Type Info** \u2014 Line ${line + 1} does not exist in "${filePath}".`;
5162
+ return `**Type Info** \u2014 Line ${line + 1} does not exist in "${filePath}".`;
4741
5163
  }
4742
5164
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4743
5165
  const projectRoot = getProjectRoot();
@@ -4754,29 +5176,26 @@ async function handleGetTypeInfo(params) {
4754
5176
  }
4755
5177
  }
4756
5178
  const resultLines = [
4757
- `\u{1F4CB} **Type Info** (fallback \u2014 LSP unavailable)`,
4758
- `\u{1F4C4} \`${relPath}:${line + 1}:${character + 1}\``,
4759
- symbolName ? `\u{1F524} **Symbol:** \`${symbolName}\`` : `\u26A0\uFE0F Could not extract symbol at this position.`,
5179
+ `\u{1F4CB} **Type Info** \u2014 \`${relPath}:${line + 1}:${character + 1}\``,
5180
+ symbolName ? `\u{1F524} **Symbol:** \`${symbolName}\`` : `Could not extract symbol at this position.`,
4760
5181
  "",
4761
5182
  "\u{1F4CE} Context:",
4762
5183
  "```",
4763
5184
  ...contextLines,
4764
5185
  "```",
4765
5186
  "",
4766
- `\u26A0\uFE0F Full type info requires typescript-language-server.`,
4767
- `\u{1F4A1} Install: npm install typescript-language-server --save-dev`,
4768
- `\u{1F4A1} Use smart_grep or smart_file_picker to read the file for more context.`
5187
+ "Use smart_grep or smart_file_picker to read more context."
4769
5188
  ];
4770
5189
  return resultLines.join("\n");
4771
5190
  } catch (err) {
4772
- return `\u26A0\uFE0F **Type Info** unavailable without LSP server, and could not read file for fallback: ${err instanceof Error ? err.message : String(err)}`;
5191
+ return `**Type Info** \u2014 could not read file for context: ${err instanceof Error ? err.message : String(err)}`;
4773
5192
  }
4774
5193
  }
4775
5194
  try {
4776
5195
  const hoverInfo = await lspClient.getTypeInfo(resolvedPath, line, character);
4777
5196
  if (!hoverInfo || !hoverInfo.contents) {
4778
- return `\u{1F4CB} **Type Info** \u2014 "${filePath}:${line + 1}:${character + 1}"
4779
- \u26A0\uFE0F No type info for this position.`;
5197
+ return `**Type Info** \u2014 "${filePath}:${line + 1}:${character + 1}"
5198
+ No type info for this position.`;
4780
5199
  }
4781
5200
  const projectRoot = getProjectRoot();
4782
5201
  const relPath = path15.relative(projectRoot, resolvedPath);
@@ -4864,8 +5283,8 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4864
5283
  }
4865
5284
  }
4866
5285
  if (results.length === 0) {
4867
- return `\u{1F50D} **Find References** (regex fallback) \u2014 "${symbolName}"
4868
- \u26A0\uFE0F No references found. Symbol may not be used in other files.`;
5286
+ return `**Find References** \u2014 "${symbolName}"
5287
+ No references found. Symbol may not be used in other files.`;
4869
5288
  }
4870
5289
  const grouped = /* @__PURE__ */ new Map();
4871
5290
  for (const r of results) {
@@ -4875,17 +5294,14 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4875
5294
  }
4876
5295
  const projectRoot = getProjectRoot();
4877
5296
  const lines = [
4878
- `\u{1F50D} **Find References** (regex fallback) \u2014 ${results.length} references found`,
5297
+ `**Find References** \u2014 ${results.length} references found`,
4879
5298
  `\u{1F4CD} Symbol: "${symbolName}"`,
4880
- `\u26A0\uFE0F Regex results may be less accurate than LSP (includes comments/strings).`,
4881
5299
  ""
4882
5300
  ];
4883
5301
  if (grouped.size > 1) {
4884
5302
  const fileList = [...grouped.keys()].map((f) => path15.relative(projectRoot, f)).join(", ");
4885
- lines.push(`\u26A0\uFE0F **Ambiguity warning:** Found matches across ${grouped.size} different files (${fileList}).`);
4886
- lines.push(` Regex fallback cannot distinguish between different scopes with the same symbol name.`);
4887
- lines.push(` If you intended only one scope, clarify which file or location you mean.`);
4888
- lines.push(` \u{1F4A1} Install typescript-language-server for scope-aware disambiguation.`);
5303
+ lines.push(`**Ambiguity note:** Found matches across ${grouped.size} different files (${fileList}).`);
5304
+ lines.push(` Verify each match is the right scope \u2014 results may include same-named symbols.`);
4889
5305
  lines.push("");
4890
5306
  } else if (results.length > 1) {
4891
5307
  const filePath = [...grouped.keys()][0];
@@ -4894,9 +5310,8 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4894
5310
  const minLine = Math.min(...matchLines);
4895
5311
  const maxLine = Math.max(...matchLines);
4896
5312
  if (maxLine - minLine > 15) {
4897
- lines.push(`\u26A0\uFE0F **Ambiguity warning:** Found ${results.length} matches for "${symbolName}" spanning lines ${minLine}-${maxLine} in the same file.`);
4898
- lines.push(` They may be in different function scopes \u2014 regex cannot distinguish them.`);
4899
- lines.push(` \u{1F4A1} Verify each match is the intended symbol before editing.`);
5313
+ lines.push(`**Ambiguity note:** Found ${results.length} matches for "${symbolName}" spanning lines ${minLine}-${maxLine} in the same file.`);
5314
+ lines.push(` Verify each match is the intended symbol.`);
4900
5315
  lines.push("");
4901
5316
  }
4902
5317
  }
@@ -4908,7 +5323,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4908
5323
  }
4909
5324
  lines.push("");
4910
5325
  }
4911
- lines.push("\u{1F4A1} Install typescript-language-server for more accurate results.");
5326
+ console.error(`[LSP] Regex fallback: found ${results.length} references for "${symbolName}".`);
4912
5327
  return lines.join("\n");
4913
5328
  } catch (err) {
4914
5329
  return `Error in fallback grep references: ${err instanceof Error ? err.message : String(err)}`;
@@ -4944,12 +5359,10 @@ async function fallbackGrepDefinition(symbolName) {
4944
5359
  const projectRoot = getProjectRoot();
4945
5360
  const relPath = path15.relative(projectRoot, file);
4946
5361
  return [
4947
- `\u{1F4CD} **Go to Definition** (regex fallback)`,
5362
+ `\u{1F4CD} **Go to Definition**`,
4948
5363
  `\u{1F4C4} File: \`${relPath}\``,
4949
5364
  `\u{1F4CF} Line: ${i + 1}`,
4950
- `\u2514 ${trimmed.substring(0, 120)}`,
4951
- "",
4952
- `\u{1F4A1} Install typescript-language-server for more accurate results.`
5365
+ `\u2514 ${trimmed.substring(0, 120)}`
4953
5366
  ].join("\n");
4954
5367
  }
4955
5368
  }
@@ -4958,9 +5371,9 @@ async function fallbackGrepDefinition(symbolName) {
4958
5371
  continue;
4959
5372
  }
4960
5373
  }
4961
- return `\u{1F4CD} **Go to Definition** (regex fallback) \u2014 "${symbolName}"
4962
- \u26A0\uFE0F Cannot find definition.
4963
- \u{1F4A1} Install typescript-language-server for more accurate results.`;
5374
+ console.error(`[LSP] Regex fallback: no definition found for "${symbolName}".`);
5375
+ return `**Go to Definition** \u2014 "${symbolName}"
5376
+ Cannot find definition.`;
4964
5377
  } catch (err) {
4965
5378
  return `Error in fallback grep definition: ${err instanceof Error ? err.message : String(err)}`;
4966
5379
  }
@@ -5290,811 +5703,22 @@ function registerAllTools(server) {
5290
5703
  }
5291
5704
  }
5292
5705
  );
5293
- console.error("[Manifest] Registered 17 tools.");
5294
- }
5295
-
5296
- // src/cli/init.ts
5297
- import fs16 from "fs";
5298
- import path16 from "path";
5299
- var ALL_CONFIG_TYPES = [
5300
- "claude",
5301
- "cursor",
5302
- "windsurf",
5303
- "copilot",
5304
- "cline",
5305
- "aider",
5306
- "antigravity",
5307
- "opencode",
5308
- "codex",
5309
- "qwen",
5310
- "kiro",
5311
- "openclaw",
5312
- "codewhale"
5313
- ];
5314
- var CONFIG_LABELS = {
5315
- claude: "Claude Code (CLAUDE.md / plugin)",
5316
- cursor: "Cursor (.cursor/rules/*.mdc)",
5317
- windsurf: "Windsurf (.windsurfrules)",
5318
- copilot: "GitHub Copilot Editor (AGENTS.md + Skill)",
5319
- cline: "Cline (.clinerules/*.md)",
5320
- aider: "Aider (CONVENTIONS.md via .aider.conf.yml)",
5321
- antigravity: "Antigravity CLI (.agents/skills/)",
5322
- opencode: "OpenCode (opencode.json)",
5323
- codex: "Codex CLI (AGENTS.md + .codex/config.toml)",
5324
- qwen: "Qwen Code (AGENTS.md + settings.json)",
5325
- kiro: "Kiro (.kiro/steering/*.md)",
5326
- openclaw: "OpenClaw (skills/)",
5327
- codewhale: "CodeWhale (skills/ + .codewhale/mcp.json)"
5328
- };
5329
- function configFilePath(type) {
5330
- switch (type) {
5331
- case "claude":
5332
- return "CLAUDE.md";
5333
- case "cursor":
5334
- return ".cursor/rules/kuma.mdc";
5335
- case "windsurf":
5336
- return ".windsurfrules";
5337
- case "copilot":
5338
- return "AGENTS.md";
5339
- case "cline":
5340
- return ".clinerules/kuma.md";
5341
- case "aider":
5342
- return "CONVENTIONS.md";
5343
- case "antigravity":
5344
- return ".agents/skills/kuma/SKILL.md";
5345
- case "opencode":
5346
- return "opencode.json";
5347
- case "codex":
5348
- return "AGENTS.md";
5349
- case "qwen":
5350
- return "AGENTS.md";
5351
- case "kiro":
5352
- return ".kiro/steering/kuma.md";
5353
- case "openclaw":
5354
- return "skills/kuma/SKILL.md";
5355
- case "codewhale":
5356
- return "skills/kuma/SKILL.md";
5357
- }
5358
- }
5359
- var CORE_RULES = [
5360
- "## AI Agent Usage Guidelines",
5361
- "",
5362
- "Kuma MCP tools are available. Use them correctly:",
5363
- "",
5364
- "### Code Search",
5365
- "- Use the **smart_grep** tool to search code - NOT bash grep/ripgrep manually",
5366
- "- smart_grep returns line numbers + context, caches results, respects .gitignore",
5367
- `- **Example:** smart_grep({ query: "function handleAuth", extensions: ['ts'] })`,
5368
- "",
5369
- "### Reading Code",
5370
- "- Use the **smart_file_picker** tool to read files with smart chunking",
5371
- "- For large files, use startLine/endLine to read specific ranges",
5372
- '- **Example:** smart_file_picker({ filePath: "src/index.ts", chunkStrategy: "outline" })',
5373
- '- **Example:** smart_file_picker({ filePath: "src/index.ts", startLine: 10, endLine: 30 })',
5374
- "",
5375
- "### Editing Code",
5376
- "- Use the **precise_diff_editor** tool to edit files (fuzzy matching + auto-backup)",
5377
- "- DO NOT create Python/Node scripts to patch files; use precise_diff_editor directly",
5378
- "- DO NOT use bash sed/cat/awk to modify source files",
5379
- '- **Example:** precise_diff_editor({ filePath: "src/app.ts", edits: [{ searchBlock: "old code", replaceBlock: "new code" }] })',
5380
- '- **Example:** precise_diff_editor({ filePath: "src/app.ts", dryRun: true, edits: [...] })',
5381
- '- **Example:** precise_diff_editor({ filePath: "src/app.ts", action: "rollback" })',
5382
- "",
5383
- "### Creating Files",
5384
- "- Use the **batch_file_writer** tool to create new files (up to 15 at once)",
5385
- '- **Example:** batch_file_writer({ files: [{ filePath: "src/util.ts", content: "// code", instructions: "reason for creating" }] })',
5386
- "",
5387
- "### Running Tasks",
5388
- "- Use the **execute_safe_test** tool for test/build/lint/typecheck",
5389
- "- Always run typecheck after editing TypeScript files",
5390
- '- **Example:** execute_safe_test({ task: "typecheck" })',
5391
- '- **Example:** execute_safe_test({ task: "custom", customCommand: "npm run lint" })',
5392
- "",
5393
- "### Code Review",
5394
- "- Use the **code_reviewer** tool after changes",
5395
- "- Supports focus: correctness, security, performance, over-engineering",
5396
- '- **Example:** code_reviewer({ focus: "security" })',
5397
- '- **Example:** code_reviewer({ files: ["src/auth.ts"], format: "json" })',
5398
- "",
5399
- "### Git Operations",
5400
- "- Use the **git_diff** tool for structured diff output",
5401
- "- Use the **git_log** tool for commit history",
5402
- "- **Example:** git_log({ maxCount: 5 })",
5403
- "- **Example:** git_diff({ staged: true })",
5404
- "",
5405
- "### Session Awareness",
5406
- "- Use the **kuma_reflect** tool to check on-track/drift/loops",
5407
- "- Use the **kuma_guard** tool for deeper safety checks (anti-patterns, auto-detection)",
5408
- "- Use the **get_session_memory** tool to recall session state",
5409
- '- **Example:** kuma_reflect({ goal: "refactor auth" })',
5410
- '- **Example:** kuma_guard({ check: "all", goal: "refactor auth" })',
5411
- "",
5412
- "### LSP / Code Intelligence",
5413
- "- Use the **lsp_query** tool for go-to-definition, find references, type info",
5414
- '- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "def" })',
5415
- '- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "refs" })',
5416
- "",
5417
- "### Static Analysis",
5418
- "- Use the **static_analysis** tool to run ESLint/TSC/Prettier/Ruff",
5419
- '- **Example:** static_analysis({ tool: "eslint", autoFix: true })',
5420
- "",
5421
- "### Project Structure",
5422
- "- Use the **project_structure** tool to see project layout",
5423
- "- **Example:** project_structure({ depth: 2, folderOnly: true })",
5424
- "",
5425
- "### Write Memory",
5426
- "- Use the **write_memory** tool to persist decisions and glossary",
5427
- '- **Example:** write_memory({ topic: "decisions", content: "## Reason for using X" })',
5428
- "",
5429
- "### General Rules",
5430
- "- When you error, READ the error carefully before acting",
5431
- "- After 3+ edits without running tests, stop and verify",
5432
- "- If a tool fails, check the message - don't retry blindly",
5433
- "- Detect conventions first with the **project_conventions** tool",
5434
- "- **Example:** project_conventions({ forceRescan: true })"
5435
- ].join("\n");
5436
- var KUMA_CORE_INSTRUCTIONS = CORE_RULES;
5437
- function claudeTemplate() {
5438
- return [
5439
- "# Kuma AI Agent Guidelines",
5440
- "",
5441
- KUMA_CORE_INSTRUCTIONS,
5442
- "",
5443
- "## Workflow Pipeline",
5444
- "",
5445
- "For best results:",
5446
- "1. **project_conventions** - detect stack",
5447
- "2. **smart_grep** / **smart_file_picker** - understand code",
5448
- "3. **precise_diff_editor** / **batch_file_writer** - make changes",
5449
- "4. **execute_safe_test** - verify (typecheck + test)",
5450
- "5. **code_reviewer** - review changes"
5451
- ].join("\n");
5452
- }
5453
- function cursorRulesTemplate() {
5454
- return [
5455
- "---",
5456
- "description: Kuma MCP tool usage rules for AI coding agents",
5457
- "alwaysApply: true",
5458
- "---",
5459
- "",
5460
- "You are an expert engineer. Kuma MCP tools are available.",
5461
- "",
5462
- "## Critical Rules",
5463
- "",
5464
- KUMA_CORE_INSTRUCTIONS,
5465
- "",
5466
- "## NEVER",
5467
- "- Never create Python/Node scripts to patch code",
5468
- "- Never use bash sed/cat/awk to edit source files",
5469
- "- Never run git push/git commit through bash"
5470
- ].join("\n");
5471
- }
5472
- function windsurfRulesTemplate() {
5473
- return [
5474
- "# Windsurf Cascade Rules with Kuma",
5475
- "",
5476
- KUMA_CORE_INSTRUCTIONS
5477
- ].join("\n");
5478
- }
5479
- function copilotTemplate() {
5480
- return [
5481
- "## GitHub Copilot Editor",
5482
- "",
5483
- "Kuma MCP tools are available. Use them correctly:",
5484
- "",
5485
- KUMA_CORE_INSTRUCTIONS,
5486
- "",
5487
- "### Copilot Editor-Specific",
5488
- "- Copilot Editor reads AGENTS.md at project root for persistent instructions",
5489
- "- Configure MCP servers via VS Code settings (cmd+shift+P \u2192 Developer: Reload Window after adding Kuma)",
5490
- "- Use kuma_guard periodically to check for anti-patterns"
5491
- ].join("\n");
5492
- }
5493
- function clineRulesTemplate() {
5494
- return [
5495
- "---",
5496
- "description: Kuma MCP tool usage rules for AI coding agents",
5497
- "paths:",
5498
- ' - "*"',
5499
- "---",
5500
- "",
5501
- KUMA_CORE_INSTRUCTIONS
5502
- ].join("\n");
5503
- }
5504
- function aiderTemplate() {
5505
- return [
5506
- "# Kuma MCP - Aider Coding Conventions",
5507
- "",
5508
- "These conventions are loaded by Aider via the `read:` field in .aider.conf.yml",
5509
- "",
5510
- KUMA_CORE_INSTRUCTIONS
5511
- ].join("\n");
5512
- }
5513
- function opencodeTemplate() {
5514
- const config = {
5515
- mcp: {
5516
- kuma: {
5517
- type: "local",
5518
- command: ["npx", "-y", "@plumpslabs/kuma"],
5519
- enabled: true
5520
- }
5706
+ server.tool(
5707
+ "kuma_init",
5708
+ "**Call this FIRST** in every new session. Loads project context: rules from .kuma/init.md, memories from .kuma/memories/, and previous session state. After this, you can work without re-detecting conventions.",
5709
+ {
5710
+ projectRoot: z.string().optional().describe("Project root path (auto-detected if omitted)")
5521
5711
  },
5522
- instructions: ["CLAUDE.md"]
5523
- };
5524
- const header = [
5525
- "// Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5526
- "// OpenCode config with Kuma MCP tools. Edit opencode.json to customize.",
5527
- ""
5528
- ].join("\n");
5529
- return header + JSON.stringify(config, null, 2) + "\n";
5530
- }
5531
- function codexTemplate() {
5532
- return [
5533
- "## Codex CLI (OpenAI)",
5534
- "",
5535
- "Kuma MCP tools are available. Use them correctly:",
5536
- "",
5537
- KUMA_CORE_INSTRUCTIONS,
5538
- "",
5539
- "### Codex-Specific",
5540
- "- Codex uses cascading AGENTS.md files (global ~/.codex/AGENTS.md -> project AGENTS.md)",
5541
- "- MCP config is in .codex/config.toml (auto-generated by kuma init)",
5542
- "- Use kuma_guard periodically to check for anti-patterns"
5543
- ].join("\n");
5544
- }
5545
- function codexConfigTomlTemplate() {
5546
- return [
5547
- "# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5548
- "# Kuma MCP server config for Codex CLI",
5549
- "",
5550
- "[mcp_servers.kuma]",
5551
- 'command = "npx"',
5552
- 'args = ["-y", "@plumpslabs/kuma"]',
5553
- ""
5554
- ].join("\n");
5555
- }
5556
- function qwenTemplate() {
5557
- return [
5558
- "## Qwen Code",
5559
- "",
5560
- "Kuma MCP tools are available. Use them correctly:",
5561
- "",
5562
- KUMA_CORE_INSTRUCTIONS,
5563
- "",
5564
- "### Qwen-Specific",
5565
- "- Qwen reads AGENTS.md at project root for persistent instructions",
5566
- "- MCP config is in settings.json (auto-generated by kuma init)",
5567
- "- Use kuma_guard periodically to check for anti-patterns"
5568
- ].join("\n");
5569
- }
5570
- function qwenSettingsTemplate() {
5571
- const config = {
5572
- mcpServers: {
5573
- kuma: {
5574
- command: "npx",
5575
- args: ["-y", "@plumpslabs/kuma"],
5576
- env: {}
5577
- }
5578
- }
5579
- };
5580
- return JSON.stringify(config, null, 2) + "\n";
5581
- }
5582
- function kiroRulesTemplate() {
5583
- return [
5584
- "---",
5585
- "name: kuma-mcp",
5586
- "description: Kuma safety toolkit - use smart_grep for search, precise_diff_editor for edits",
5587
- "inclusion: always",
5588
- "---",
5589
- "",
5590
- "# Kuma MCP - Kiro Steering",
5591
- "",
5592
- KUMA_CORE_INSTRUCTIONS,
5593
- "",
5594
- "## Kiro-Specific",
5595
- "- Kiro reads steering files from .kiro/steering/ for project instructions",
5596
- "- Configure MCP servers via IDE settings or global Kiro config",
5597
- "- Use kuma_guard periodically to check for anti-patterns"
5598
- ].join("\n");
5599
- }
5600
- function openclawSkillTemplate() {
5601
- return [
5602
- "---",
5603
- "name: kuma-mcp",
5604
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5605
- "---",
5606
- "",
5607
- "# Kuma MCP - OpenClaw Skill",
5608
- "",
5609
- KUMA_CORE_INSTRUCTIONS,
5610
- "",
5611
- "## OpenClaw-Specific",
5612
- "- OpenClaw loads skills/ from workspace root or ~/.openclaw/skills for global",
5613
- "- Configure Kuma MCP server via ~/.openclaw/openclaw.json or agents standard",
5614
- "- Use kuma_guard periodically to check for anti-patterns",
5615
- "",
5616
- "## Verification",
5617
- "- After edits, run execute_safe_test to verify no breakage",
5618
- "- Use code_reviewer for correctness/security review",
5619
- "- Check kuma_reflect to confirm on-track"
5620
- ].join("\n");
5621
- }
5622
- function codewhaleTemplate() {
5623
- return [
5624
- "---",
5625
- "name: kuma-mcp",
5626
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5627
- "---",
5628
- "",
5629
- "# Kuma MCP - CodeWhale Skill",
5630
- "",
5631
- KUMA_CORE_INSTRUCTIONS,
5632
- "",
5633
- "## CodeWhale-Specific",
5634
- "- CodeWhale loads SKILL.md from skills/ (workspace-local), .agents/skills/, or ~/.codewhale/skills/",
5635
- "- MCP config is in ~/.codewhale/mcp.json or ~/.deepseek/mcp.json",
5636
- "- Use kuma_guard periodically to check for anti-patterns",
5637
- "",
5638
- "## Verification",
5639
- "- After edits, run execute_safe_test to verify no breakage",
5640
- "- Use code_reviewer for correctness/security review",
5641
- "- Check kuma_reflect to confirm on-track"
5642
- ].join("\n");
5643
- }
5644
- function antigravitySkillTemplate() {
5645
- return [
5646
- "---",
5647
- "name: kuma-mcp",
5648
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5649
- "---",
5650
- "",
5651
- "# Kuma MCP - Antigravity Skill",
5652
- "",
5653
- KUMA_CORE_INSTRUCTIONS,
5654
- "",
5655
- "## Verification",
5656
- "- After edits, run execute_safe_test to verify no breakage",
5657
- "- Use code_reviewer for correctness/security review",
5658
- "- Check kuma_reflect to confirm on-track"
5659
- ].join("\n");
5660
- }
5661
- function antigravityMcpConfigTemplate() {
5662
- const config = {
5663
- mcpServers: {
5664
- kuma: {
5665
- command: "npx",
5666
- args: ["-y", "@plumpslabs/kuma"],
5667
- env: {}
5668
- }
5669
- }
5670
- };
5671
- return JSON.stringify(config, null, 2) + "\n";
5672
- }
5673
- var TEMPLATES = {
5674
- claude: claudeTemplate,
5675
- cursor: cursorRulesTemplate,
5676
- windsurf: windsurfRulesTemplate,
5677
- copilot: copilotTemplate,
5678
- cline: clineRulesTemplate,
5679
- aider: aiderTemplate,
5680
- antigravity: antigravitySkillTemplate,
5681
- opencode: opencodeTemplate,
5682
- codex: codexTemplate,
5683
- qwen: qwenTemplate,
5684
- kiro: kiroRulesTemplate,
5685
- openclaw: openclawSkillTemplate,
5686
- codewhale: codewhaleTemplate
5687
- };
5688
- var APPEND_SEPARATOR = "\n\n---\n_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_\n\n";
5689
- function handleOpencodeSecondary(root, results) {
5690
- const claudePath = path16.resolve(root, "CLAUDE.md");
5691
- if (!fs16.existsSync(claudePath)) {
5692
- try {
5693
- fs16.writeFileSync(claudePath, [
5694
- "# Kuma MCP - OpenCode Instructions",
5695
- "",
5696
- KUMA_CORE_INSTRUCTIONS
5697
- ].join("\n"), "utf-8");
5698
- results.push({ type: "opencode", filePath: "CLAUDE.md", action: "created" });
5699
- } catch (err) {
5700
- results.push({
5701
- type: "opencode",
5702
- filePath: "CLAUDE.md",
5703
- action: "error",
5704
- error: err instanceof Error ? err.message : String(err)
5705
- });
5706
- }
5707
- }
5708
- }
5709
- function handleCodexSecondary(root, results) {
5710
- const tomlPath = path16.resolve(root, ".codex/config.toml");
5711
- if (results.some((r) => r.filePath === ".codex/config.toml")) return;
5712
- try {
5713
- const dir = path16.dirname(tomlPath);
5714
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5715
- if (fs16.existsSync(tomlPath)) {
5716
- const existingContent = fs16.readFileSync(tomlPath, "utf-8");
5717
- if (existingContent.includes("kuma")) {
5718
- results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
5719
- return;
5720
- }
5721
- fs16.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
5722
- results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
5723
- } else {
5724
- fs16.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
5725
- results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
5726
- }
5727
- } catch (err) {
5728
- results.push({
5729
- type: "codex",
5730
- filePath: ".codex/config.toml",
5731
- action: "error",
5732
- error: err instanceof Error ? err.message : String(err)
5733
- });
5734
- }
5735
- }
5736
- function handleQwenSecondary(root, results) {
5737
- const settingsPath = path16.resolve(root, "settings.json");
5738
- if (results.some((r) => r.filePath === "settings.json")) return;
5739
- try {
5740
- if (fs16.existsSync(settingsPath)) {
5741
- const existingContent = fs16.readFileSync(settingsPath, "utf-8");
5742
- if (existingContent.includes("kuma")) {
5743
- if (!existingContent.includes("_Generated by Kuma MCP_")) {
5744
- try {
5745
- const parsed = JSON.parse(existingContent);
5746
- parsed.mcpServers = parsed.mcpServers || {};
5747
- if (!parsed.mcpServers.kuma) {
5748
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5749
- fs16.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5750
- results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
5751
- return;
5752
- }
5753
- } catch {
5754
- }
5755
- }
5756
- results.push({ type: "qwen", filePath: "settings.json", action: "skipped" });
5757
- return;
5758
- }
5712
+ async (params) => {
5759
5713
  try {
5760
- const parsed = JSON.parse(existingContent);
5761
- parsed.mcpServers = parsed.mcpServers || {};
5762
- if (!parsed.mcpServers.kuma) {
5763
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5764
- fs16.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5765
- results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
5766
- }
5767
- } catch {
5768
- }
5769
- } else {
5770
- fs16.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
5771
- results.push({ type: "qwen", filePath: "settings.json", action: "created" });
5772
- }
5773
- } catch (err) {
5774
- results.push({
5775
- type: "qwen",
5776
- filePath: "settings.json",
5777
- action: "error",
5778
- error: err instanceof Error ? err.message : String(err)
5779
- });
5780
- }
5781
- }
5782
- var AGENTS_MD_TYPES = ["codex", "qwen", "copilot"];
5783
- function getAgentsMdHeader() {
5784
- return [
5785
- "# Kuma MCP - Combined Agent Instructions",
5786
- "",
5787
- "This file contains instructions for AI coding agents that read AGENTS.md.",
5788
- "Each section applies to a specific agent. Unused sections can be safely removed.",
5789
- "",
5790
- "---",
5791
- "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_",
5792
- ""
5793
- ].join("\n");
5794
- }
5795
- function getCombinedAgentsMd(selectedTypes) {
5796
- const sections = [getAgentsMdHeader()];
5797
- const agentOrder = ["codex", "qwen", "copilot"];
5798
- for (const t of agentOrder) {
5799
- if (selectedTypes.has(t)) {
5800
- sections.push(TEMPLATES[t]());
5801
- }
5802
- }
5803
- return sections.join("\n\n---\n\n");
5804
- }
5805
- function handleAntigravityMcpConfig(root, results) {
5806
- const mcpPath = path16.resolve(root, ".agents/mcp_config.json");
5807
- if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
5808
- try {
5809
- const mcpDir = path16.dirname(mcpPath);
5810
- if (fs16.existsSync(mcpPath)) {
5811
- const existingContent = fs16.readFileSync(mcpPath, "utf-8");
5812
- if (existingContent.includes("kuma")) {
5813
- if (!existingContent.includes("_Generated by Kuma MCP_")) {
5814
- const trimmed = existingContent.trimEnd();
5815
- if (trimmed.endsWith("}")) {
5816
- const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
5817
- fs16.writeFileSync(mcpPath, updated, "utf-8");
5818
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
5819
- }
5820
- }
5821
- return;
5822
- }
5823
- const parsed = JSON.parse(existingContent);
5824
- parsed.mcpServers = parsed.mcpServers || {};
5825
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5826
- fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5827
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
5828
- } else {
5829
- if (!fs16.existsSync(mcpDir)) fs16.mkdirSync(mcpDir, { recursive: true });
5830
- fs16.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
5831
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
5832
- }
5833
- } catch (err) {
5834
- results.push({
5835
- type: "antigravity",
5836
- filePath: ".agents/mcp_config.json",
5837
- action: "error",
5838
- error: err instanceof Error ? err.message : String(err)
5839
- });
5840
- }
5841
- }
5842
- function handleAiderSecondary(root, results) {
5843
- const ymlPath = path16.resolve(root, ".aider.conf.yml");
5844
- if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
5845
- try {
5846
- const conventionsRef = "read: CONVENTIONS.md";
5847
- if (fs16.existsSync(ymlPath)) {
5848
- const existingContent = fs16.readFileSync(ymlPath, "utf-8");
5849
- if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
5850
- results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
5851
- return;
5852
- }
5853
- const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
5854
- fs16.writeFileSync(ymlPath, newContent, "utf-8");
5855
- results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
5856
- } else {
5857
- const content = [
5858
- "# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5859
- "# Aider will read CONVENTIONS.md for coding conventions",
5860
- "",
5861
- conventionsRef,
5862
- ""
5863
- ].join("\n");
5864
- fs16.writeFileSync(ymlPath, content, "utf-8");
5865
- results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
5866
- }
5867
- } catch (err) {
5868
- results.push({
5869
- type: "aider",
5870
- filePath: ".aider.conf.yml",
5871
- action: "error",
5872
- error: err instanceof Error ? err.message : String(err)
5873
- });
5874
- }
5875
- }
5876
- function handleCopilotSecondary(root, results) {
5877
- const skillPath = path16.resolve(root, ".github/skills/kuma/SKILL.md");
5878
- if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
5879
- try {
5880
- const dir = path16.dirname(skillPath);
5881
- const content = [
5882
- "---",
5883
- "name: kuma-mcp",
5884
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5885
- "---",
5886
- "",
5887
- "# Kuma MCP - Copilot Editor Skill",
5888
- "",
5889
- KUMA_CORE_INSTRUCTIONS
5890
- ].join("\n");
5891
- if (fs16.existsSync(skillPath)) {
5892
- const existingContent = fs16.readFileSync(skillPath, "utf-8");
5893
- if (existingContent.includes("kuma")) {
5894
- results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
5895
- return;
5896
- }
5897
- fs16.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
5898
- results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
5899
- } else {
5900
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5901
- fs16.writeFileSync(skillPath, content, "utf-8");
5902
- results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
5903
- }
5904
- } catch (err) {
5905
- results.push({
5906
- type: "copilot",
5907
- filePath: ".github/skills/kuma/SKILL.md",
5908
- action: "error",
5909
- error: err instanceof Error ? err.message : String(err)
5910
- });
5911
- }
5912
- }
5913
- function handleOpenclawSecondary(root, results) {
5914
- const mcpPath = path16.resolve(root, ".agents/mcp_config.json");
5915
- if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
5916
- try {
5917
- const dir = path16.dirname(mcpPath);
5918
- if (fs16.existsSync(mcpPath)) {
5919
- const existingContent = fs16.readFileSync(mcpPath, "utf-8");
5920
- if (existingContent.includes("kuma")) return;
5921
- const parsed = JSON.parse(existingContent);
5922
- parsed.mcpServers = parsed.mcpServers || {};
5923
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5924
- fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5925
- results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
5926
- } else {
5927
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5928
- fs16.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
5929
- results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
5930
- }
5931
- } catch (err) {
5932
- results.push({
5933
- type: "openclaw",
5934
- filePath: ".agents/mcp_config.json",
5935
- action: "error",
5936
- error: err instanceof Error ? err.message : String(err)
5937
- });
5938
- }
5939
- }
5940
- function handleCodewhaleSecondary(root, results) {
5941
- const mcpPath = path16.resolve(root, ".codewhale/mcp.json");
5942
- if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
5943
- try {
5944
- const dir = path16.dirname(mcpPath);
5945
- if (fs16.existsSync(mcpPath)) {
5946
- const existingContent = fs16.readFileSync(mcpPath, "utf-8");
5947
- if (existingContent.includes("kuma")) return;
5948
- const parsed = JSON.parse(existingContent);
5949
- parsed.mcpServers = parsed.mcpServers || {};
5950
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5951
- fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5952
- results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
5953
- } else {
5954
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5955
- const config = {
5956
- mcpServers: {
5957
- kuma: {
5958
- command: "npx",
5959
- args: ["-y", "@plumpslabs/kuma"],
5960
- env: {}
5961
- }
5962
- }
5963
- };
5964
- fs16.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
5965
- results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
5966
- }
5967
- } catch (err) {
5968
- results.push({
5969
- type: "codewhale",
5970
- filePath: ".codewhale/mcp.json",
5971
- action: "error",
5972
- error: err instanceof Error ? err.message : String(err)
5973
- });
5974
- }
5975
- }
5976
- function runInit(types, projectRoot) {
5977
- const root = projectRoot ?? getProjectRoot();
5978
- const selected = types.length > 0 ? types : ALL_CONFIG_TYPES;
5979
- const results = [];
5980
- const selectedSet = new Set(selected);
5981
- const agentsMdSelected = AGENTS_MD_TYPES.filter((t) => selectedSet.has(t));
5982
- let agentsMdHandled = false;
5983
- for (const type of selected) {
5984
- const relativePath = configFilePath(type);
5985
- const fullPath = path16.resolve(root, relativePath);
5986
- const getTemplate = TEMPLATES[type];
5987
- try {
5988
- if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
5989
- agentsMdHandled = true;
5990
- const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
5991
- if (fs16.existsSync(fullPath)) {
5992
- const existingContent = fs16.readFileSync(fullPath, "utf-8");
5993
- if (existingContent.includes("_Generated by Kuma MCP_")) {
5994
- results.push({ type, filePath: relativePath, action: "skipped" });
5995
- } else {
5996
- fs16.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
5997
- results.push({ type, filePath: relativePath, action: "appended" });
5998
- }
5999
- } else {
6000
- const dir = path16.dirname(fullPath);
6001
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
6002
- fs16.writeFileSync(fullPath, combinedContent, "utf-8");
6003
- results.push({ type, filePath: relativePath, action: "created" });
6004
- }
6005
- if (selectedSet.has("codex")) handleCodexSecondary(root, results);
6006
- if (selectedSet.has("qwen")) handleQwenSecondary(root, results);
6007
- if (selectedSet.has("copilot")) handleCopilotSecondary(root, results);
6008
- } else if (AGENTS_MD_TYPES.includes(type) && agentsMdHandled) {
6009
- results.push({ type, filePath: relativePath, action: "skipped" });
6010
- continue;
6011
- } else {
6012
- const template = getTemplate();
6013
- if (fs16.existsSync(fullPath)) {
6014
- const existingContent = fs16.readFileSync(fullPath, "utf-8");
6015
- if (existingContent.includes("_Generated by Kuma MCP_")) {
6016
- if (type === "antigravity") {
6017
- handleAntigravityMcpConfig(root, results);
6018
- } else if (type === "openclaw") {
6019
- handleOpenclawSecondary(root, results);
6020
- } else if (type === "codewhale") {
6021
- handleCodewhaleSecondary(root, results);
6022
- }
6023
- results.push({ type, filePath: relativePath, action: "skipped" });
6024
- continue;
6025
- }
6026
- const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
6027
- fs16.writeFileSync(fullPath, newContent, "utf-8");
6028
- results.push({ type, filePath: relativePath, action: "appended" });
6029
- } else {
6030
- const dir = path16.dirname(fullPath);
6031
- if (!fs16.existsSync(dir)) {
6032
- fs16.mkdirSync(dir, { recursive: true });
6033
- }
6034
- fs16.writeFileSync(fullPath, template, "utf-8");
6035
- results.push({ type, filePath: relativePath, action: "created" });
6036
- }
6037
- if (type === "antigravity") {
6038
- handleAntigravityMcpConfig(root, results);
6039
- } else if (type === "openclaw") {
6040
- handleOpenclawSecondary(root, results);
6041
- } else if (type === "codewhale") {
6042
- handleCodewhaleSecondary(root, results);
6043
- } else if (type === "aider") {
6044
- handleAiderSecondary(root, results);
6045
- } else if (type === "opencode") {
6046
- handleOpencodeSecondary(root, results);
6047
- }
5714
+ const result = await handleKumaInit(params);
5715
+ return { content: [{ type: "text", text: result }] };
5716
+ } catch (err) {
5717
+ return { content: [{ type: "text", text: `Error in kuma_init: ${err}` }], isError: true };
6048
5718
  }
6049
- } catch (err) {
6050
- results.push({
6051
- type,
6052
- filePath: relativePath,
6053
- action: "error",
6054
- error: err instanceof Error ? err.message : String(err)
6055
- });
6056
5719
  }
6057
- }
6058
- return results;
6059
- }
6060
- function formatInitResults(results) {
6061
- const lines = [
6062
- "\u{1F43B} **Kuma Init - AI Agent Config Generator**",
6063
- ""
6064
- ];
6065
- for (const r of results) {
6066
- const label = CONFIG_LABELS[r.type];
6067
- switch (r.action) {
6068
- case "created":
6069
- lines.push(" \u2705 " + label);
6070
- lines.push(" \u2192 Created: " + r.filePath);
6071
- break;
6072
- case "appended":
6073
- lines.push(" \u2795 " + label);
6074
- lines.push(" \u2192 Appended to: " + r.filePath);
6075
- break;
6076
- case "skipped":
6077
- lines.push(" \u23ED " + label);
6078
- lines.push(" \u2192 Skipped (already has Kuma): " + r.filePath);
6079
- break;
6080
- case "error":
6081
- lines.push(" \u274C " + label);
6082
- lines.push(" \u2192 Error: " + (r.error ?? "unknown"));
6083
- break;
6084
- }
6085
- }
6086
- const created = results.filter((r) => r.action === "created").length;
6087
- const appended = results.filter((r) => r.action === "appended").length;
6088
- const skipped = results.filter((r) => r.action === "skipped").length;
6089
- const errors = results.filter((r) => r.action === "error").length;
6090
- lines.push(
6091
- "",
6092
- "\u{1F4CA} Summary: " + created + " created, " + appended + " appended, " + skipped + " skipped, " + errors + " errors",
6093
- "",
6094
- "\u{1F4A1} Config files teach your AI how to use Kuma tools.",
6095
- "\u{1F4A1} Run again to generate additional config files anytime."
6096
5720
  );
6097
- return lines.join("\n");
5721
+ console.error("[Manifest] Registered 18 tools.");
6098
5722
  }
6099
5723
 
6100
5724
  // src/index.ts
@@ -6110,6 +5734,8 @@ Usage:
6110
5734
  npx @plumpslabs/kuma Start MCP server (default)
6111
5735
  npx @plumpslabs/kuma init Generate AI agent config files
6112
5736
  npx @plumpslabs/kuma init --all Generate ALL config files
5737
+ npx @plumpslabs/kuma init --merge Append to existing files (default)
5738
+ npx @plumpslabs/kuma init --skip-existing Skip generation if file exists
6113
5739
  npx @plumpslabs/kuma init --claude --cursor Generate specific files
6114
5740
  npx @plumpslabs/kuma init --help Show this help
6115
5741
 
@@ -6178,20 +5804,63 @@ async function main() {
6178
5804
  }
6179
5805
  }
6180
5806
  if (selectedTypes.length === 0) {
6181
- console.error("\u26A0\uFE0F No valid flags provided. Use --help to see options.");
5807
+ console.error(
5808
+ "\u26A0\uFE0F No valid flags provided. Use --help to see options."
5809
+ );
6182
5810
  process.exit(1);
6183
5811
  }
6184
5812
  }
6185
5813
  }
6186
- const results = runInit(selectedTypes, process.cwd());
5814
+ const skipExisting = requestedFlags.includes("--skip-existing");
5815
+ const results = runInit({
5816
+ types: selectedTypes,
5817
+ projectRoot: process.cwd(),
5818
+ skipExisting
5819
+ });
6187
5820
  const output = formatInitResults(results);
6188
5821
  console.log(output);
5822
+ const fs16 = await import("fs");
5823
+ const path16 = await import("path");
5824
+ const matchaSkills = path16.resolve(process.cwd(), "skills/matcha/SKILL.md");
5825
+ const matchaAgents = path16.resolve(
5826
+ process.cwd(),
5827
+ ".agents/skills/matcha/SKILL.md"
5828
+ );
5829
+ const matchaCursor = path16.resolve(
5830
+ process.cwd(),
5831
+ ".cursor/rules/matcha.mdc"
5832
+ );
5833
+ const matchaWindsurf = path16.resolve(
5834
+ process.cwd(),
5835
+ ".windsurf/rules/matcha.md"
5836
+ );
5837
+ if (fs16.existsSync(matchaSkills) || fs16.existsSync(matchaAgents) || fs16.existsSync(matchaCursor) || fs16.existsSync(matchaWindsurf)) {
5838
+ console.error(
5839
+ "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
5840
+ );
5841
+ }
6189
5842
  process.exit(0);
6190
5843
  }
6191
5844
  sessionMemory.init({
6192
5845
  projectRoot: process.cwd(),
6193
5846
  startTime: Date.now()
6194
5847
  });
5848
+ (async () => {
5849
+ try {
5850
+ const { generateInitMdContent } = await import("./init-ZRT7JP5F.js");
5851
+ const fs16 = await import("fs");
5852
+ const path16 = await import("path");
5853
+ const initMdPath = path16.resolve(process.cwd(), ".kuma/init.md");
5854
+ if (!fs16.existsSync(initMdPath)) {
5855
+ const kumaDir = path16.dirname(initMdPath);
5856
+ if (!fs16.existsSync(kumaDir)) fs16.mkdirSync(kumaDir, { recursive: true });
5857
+ fs16.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
5858
+ console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
5859
+ }
5860
+ } catch (err) {
5861
+ console.error(`[${SERVER_NAME}] Failed to auto-generate .kuma/init.md: ${err}`);
5862
+ }
5863
+ })();
6195
5864
  const server = new McpServer(
6196
5865
  {
6197
5866
  name: SERVER_NAME,
@@ -6211,6 +5880,12 @@ async function main() {
6211
5880
  console.error(
6212
5881
  `[${SERVER_NAME}] Session started: ${(/* @__PURE__ */ new Date()).toISOString()}`
6213
5882
  );
5883
+ console.error(
5884
+ `[${SERVER_NAME}] Kuma Core v3 \u2014 .kuma/init.md is the single source of truth`
5885
+ );
5886
+ console.error(
5887
+ `[${SERVER_NAME}] \u{1F9E0} Call kuma_init() at session start to load project context`
5888
+ );
6214
5889
  await server.connect(transport);
6215
5890
  console.error(
6216
5891
  `[${SERVER_NAME}] Server connected via stdio. Waiting for requests...`
@@ -6219,18 +5894,42 @@ async function main() {
6219
5894
  function interactiveSelect() {
6220
5895
  const labels = [
6221
5896
  { type: "claude", label: "1) Claude Code (CLAUDE.md)" },
6222
- { type: "cursor", label: "2) Cursor (.cursor/rules/kuma.mdc)" },
5897
+ {
5898
+ type: "cursor",
5899
+ label: "2) Cursor (.cursor/rules/kuma.mdc)"
5900
+ },
6223
5901
  { type: "windsurf", label: "3) Windsurf (.windsurfrules)" },
6224
- { type: "copilot", label: "4) GitHub Copilot Editor (AGENTS.md + Skill)" },
5902
+ {
5903
+ type: "copilot",
5904
+ label: "4) GitHub Copilot Editor (AGENTS.md + Skill)"
5905
+ },
6225
5906
  { type: "cline", label: "5) Cline (.clinerules/kuma.md)" },
6226
- { type: "aider", label: "6) Aider (CONVENTIONS.md via .aider.conf.yml)" },
6227
- { type: "antigravity", label: "7) Antigravity CLI (.agents/skills/)" },
5907
+ {
5908
+ type: "aider",
5909
+ label: "6) Aider (CONVENTIONS.md via .aider.conf.yml)"
5910
+ },
5911
+ {
5912
+ type: "antigravity",
5913
+ label: "7) Antigravity CLI (.agents/skills/)"
5914
+ },
6228
5915
  { type: "opencode", label: "8) OpenCode (opencode.json)" },
6229
- { type: "codex", label: "9) Codex CLI - OpenAI (AGENTS.md + .codex/config.toml)" },
6230
- { type: "qwen", label: "10) Qwen Code (AGENTS.md + settings.json)" },
5916
+ {
5917
+ type: "codex",
5918
+ label: "9) Codex CLI - OpenAI (AGENTS.md + .codex/config.toml)"
5919
+ },
5920
+ {
5921
+ type: "qwen",
5922
+ label: "10) Qwen Code (AGENTS.md + settings.json)"
5923
+ },
6231
5924
  { type: "kiro", label: "11) Kiro (.kiro/steering/kuma.md)" },
6232
- { type: "openclaw", label: "12) OpenClaw (skills/kuma/SKILL.md)" },
6233
- { type: "codewhale", label: "13) CodeWhale (skills/kuma/SKILL.md + .codewhale/mcp.json)" }
5925
+ {
5926
+ type: "openclaw",
5927
+ label: "12) OpenClaw (skills/kuma/SKILL.md)"
5928
+ },
5929
+ {
5930
+ type: "codewhale",
5931
+ label: "13) CodeWhale (skills/kuma/SKILL.md + .codewhale/mcp.json)"
5932
+ }
6234
5933
  ];
6235
5934
  const rl = readline.createInterface({
6236
5935
  input: process.stdin,
@@ -6242,38 +5941,41 @@ function interactiveSelect() {
6242
5941
  console.error(l.label);
6243
5942
  }
6244
5943
  console.error("");
6245
- rl.question("Enter numbers separated by space (e.g. '1 3 5'), or 'all': ", (answer) => {
6246
- rl.close();
6247
- const input = answer.trim().toLowerCase();
6248
- if (input === "all") {
6249
- resolve(ALL_CONFIG_TYPES);
6250
- return;
6251
- }
6252
- const nums = input.split(/\s+/).map(Number).filter((n) => n >= 1 && n <= 13);
6253
- const typeMap = {
6254
- 1: "claude",
6255
- 2: "cursor",
6256
- 3: "windsurf",
6257
- 4: "copilot",
6258
- 5: "cline",
6259
- 6: "aider",
6260
- 7: "antigravity",
6261
- 8: "opencode",
6262
- 9: "codex",
6263
- 10: "qwen",
6264
- 11: "kiro",
6265
- 12: "openclaw",
6266
- 13: "codewhale"
6267
- };
6268
- const selected = [];
6269
- for (const n of nums) {
6270
- const t = typeMap[n];
6271
- if (t && !selected.includes(t)) {
6272
- selected.push(t);
5944
+ rl.question(
5945
+ "Enter numbers separated by space (e.g. '1 3 5'), or 'all': ",
5946
+ (answer) => {
5947
+ rl.close();
5948
+ const input = answer.trim().toLowerCase();
5949
+ if (input === "all") {
5950
+ resolve(ALL_CONFIG_TYPES);
5951
+ return;
6273
5952
  }
5953
+ const nums = input.split(/\s+/).map(Number).filter((n) => n >= 1 && n <= 13);
5954
+ const typeMap = {
5955
+ 1: "claude",
5956
+ 2: "cursor",
5957
+ 3: "windsurf",
5958
+ 4: "copilot",
5959
+ 5: "cline",
5960
+ 6: "aider",
5961
+ 7: "antigravity",
5962
+ 8: "opencode",
5963
+ 9: "codex",
5964
+ 10: "qwen",
5965
+ 11: "kiro",
5966
+ 12: "openclaw",
5967
+ 13: "codewhale"
5968
+ };
5969
+ const selected = [];
5970
+ for (const n of nums) {
5971
+ const t = typeMap[n];
5972
+ if (t && !selected.includes(t)) {
5973
+ selected.push(t);
5974
+ }
5975
+ }
5976
+ resolve(selected);
6274
5977
  }
6275
- resolve(selected);
6276
- });
5978
+ );
6277
5979
  });
6278
5980
  }
6279
5981
  main().catch((err) => {