@plumpslabs/kuma 2.1.3 → 2.1.5

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-2BFTJMBP.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);
@@ -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) {
@@ -2440,11 +2375,12 @@ function isTestFile(filePath) {
2440
2375
  return /\.(test|spec)\.[a-z]+$/i.test(filePath) || /__tests__/.test(filePath);
2441
2376
  }
2442
2377
  function isTsLike(filePath) {
2443
- const ext = path9.extname(filePath).toLowerCase();
2378
+ const ext = path8.extname(filePath).toLowerCase();
2444
2379
  return ext === ".ts" || ext === ".tsx";
2445
2380
  }
2446
2381
  function checkGeneral(filePath, content, issues) {
2447
2382
  const lines = content.split("\n");
2383
+ const ext = path8.extname(filePath).toLowerCase();
2448
2384
  if (content.length > 0 && !content.endsWith("\n")) {
2449
2385
  issues.push({
2450
2386
  file: filePath,
@@ -2465,6 +2401,9 @@ function checkGeneral(filePath, content, issues) {
2465
2401
  suggestion: "Extract cohesive sections into separate modules"
2466
2402
  });
2467
2403
  }
2404
+ if (CONFIG_EXTENSIONS.includes(ext)) {
2405
+ checkConfigFile(filePath, content, issues);
2406
+ }
2468
2407
  }
2469
2408
  function checkCorrectness(filePath, content, issues) {
2470
2409
  const lines = content.split("\n");
@@ -2627,6 +2566,282 @@ function checkCorrectness(filePath, content, issues) {
2627
2566
  });
2628
2567
  }
2629
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
+ }
2630
2845
  function checkConventions(filePath, content, issues) {
2631
2846
  const lines = content.split("\n");
2632
2847
  let hasTabs = false;
@@ -3235,8 +3450,8 @@ async function handleGitDiff(params) {
3235
3450
  }
3236
3451
 
3237
3452
  // src/tools/projectStructure.ts
3238
- import fs10 from "fs";
3239
- import path10 from "path";
3453
+ import fs9 from "fs";
3454
+ import path9 from "path";
3240
3455
  var DEFAULT_IGNORE = [
3241
3456
  "node_modules",
3242
3457
  ".git",
@@ -3273,7 +3488,7 @@ async function handleProjectStructure(params) {
3273
3488
  sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
3274
3489
  try {
3275
3490
  const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
3276
- const projectName = path10.basename(root);
3491
+ const projectName = path9.basename(root);
3277
3492
  const lines = [
3278
3493
  "[Project Structure] - " + projectName,
3279
3494
  "Depth: " + clampedDepth + " | " + (folderOnly ? "Folders only" : "Files and folders"),
@@ -3296,7 +3511,7 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3296
3511
  const lines = [];
3297
3512
  let entries = [];
3298
3513
  try {
3299
- entries = fs10.readdirSync(currentDir, { withFileTypes: true });
3514
+ entries = fs9.readdirSync(currentDir, { withFileTypes: true });
3300
3515
  } catch {
3301
3516
  return lines;
3302
3517
  }
@@ -3305,12 +3520,12 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3305
3520
  if (!a.isDirectory() && b.isDirectory()) return 1;
3306
3521
  return a.name.localeCompare(b.name);
3307
3522
  });
3308
- const relativeDir = path10.relative(root, currentDir) || ".";
3523
+ const relativeDir = path9.relative(root, currentDir) || ".";
3309
3524
  const prefix = getPrefix(currentDepth);
3310
3525
  for (const entry of entries) {
3311
3526
  if (shouldIgnore(entry.name, relativeDir, root)) continue;
3312
- const fullPath = path10.join(currentDir, entry.name);
3313
- const relativePath = path10.relative(root, fullPath);
3527
+ const fullPath = path9.join(currentDir, entry.name);
3528
+ const relativePath = path9.relative(root, fullPath);
3314
3529
  if (includePattern && !entry.name.includes(includePattern) && !relativePath.includes(includePattern)) {
3315
3530
  if (!entry.isDirectory()) continue;
3316
3531
  }
@@ -3321,11 +3536,11 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3321
3536
  lines.push(prefix + "[D] " + entry.name + "/");
3322
3537
  let subEntries = [];
3323
3538
  try {
3324
- subEntries = fs10.readdirSync(fullPath, { withFileTypes: true });
3539
+ subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
3325
3540
  } catch {
3326
3541
  }
3327
3542
  const hasVisibleContent = subEntries.some(
3328
- (e) => !shouldIgnore(e.name, path10.relative(root, fullPath), root)
3543
+ (e) => !shouldIgnore(e.name, path9.relative(root, fullPath), root)
3329
3544
  );
3330
3545
  if (hasVisibleContent) {
3331
3546
  const subLines = buildTree(root, fullPath, maxDepth, currentDepth + 1, folderOnly, includePattern, excludePattern);
@@ -3356,7 +3571,7 @@ function getPrefix(depth) {
3356
3571
  }
3357
3572
  function getFileSize(fullPath) {
3358
3573
  try {
3359
- const stat = fs10.statSync(fullPath);
3574
+ const stat = fs9.statSync(fullPath);
3360
3575
  if (stat.size < 1024) return stat.size + "B";
3361
3576
  if (stat.size < 1024 * 1024) return (stat.size / 1024).toFixed(0) + "KB";
3362
3577
  const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
@@ -3367,8 +3582,8 @@ function getFileSize(fullPath) {
3367
3582
  }
3368
3583
 
3369
3584
  // src/tools/staticAnalysis.ts
3370
- import fs11 from "fs";
3371
- import path11 from "path";
3585
+ import fs10 from "fs";
3586
+ import path10 from "path";
3372
3587
  async function handleStaticAnalysis(params) {
3373
3588
  const { tool = "all", files, autoFix = false, timeout = 60 } = params;
3374
3589
  const root = getProjectRoot();
@@ -3386,7 +3601,26 @@ async function handleStaticAnalysis(params) {
3386
3601
  for (const t of toolsToRun) {
3387
3602
  const output = await runTool(t, root, files, autoFix, timeout);
3388
3603
  if (output === null) {
3389
- 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);
3390
3624
  continue;
3391
3625
  }
3392
3626
  const issues = parseToolOutput(t, output.stdout, output.stderr, root);
@@ -3402,6 +3636,22 @@ async function handleStaticAnalysis(params) {
3402
3636
  };
3403
3637
  results.push(result);
3404
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
+ }
3405
3655
  if (result.summary.errors > 0 || result.summary.warnings > 0) {
3406
3656
  const errorMsg = result.summary.errors > 0 ? result.summary.errors + " error(s)" : result.summary.warnings + " warning(s)";
3407
3657
  sessionMemory.addFailedFile("static_analysis:" + t, errorMsg + " found");
@@ -3423,11 +3673,11 @@ function detectAvailableTools(root) {
3423
3673
  "eslint.config.js",
3424
3674
  "eslint.config.mjs"
3425
3675
  ];
3426
- const hasEslintConfig = eslintConfigs.some((cfg) => fs11.existsSync(path11.join(root, cfg)));
3676
+ const hasEslintConfig = eslintConfigs.some((cfg) => fs10.existsSync(path10.join(root, cfg)));
3427
3677
  if (hasEslintConfig && depNames.has("eslint")) {
3428
3678
  tools.push("eslint");
3429
3679
  }
3430
- if (fs11.existsSync(path11.join(root, "tsconfig.json")) && depNames.has("typescript")) {
3680
+ if (fs10.existsSync(path10.join(root, "tsconfig.json")) && depNames.has("typescript")) {
3431
3681
  tools.push("tsc");
3432
3682
  }
3433
3683
  const prettierConfigs = [
@@ -3438,20 +3688,20 @@ function detectAvailableTools(root) {
3438
3688
  ".prettierrc.toml",
3439
3689
  "prettier.config.js"
3440
3690
  ];
3441
- const hasPrettierConfig = prettierConfigs.some((cfg) => fs11.existsSync(path11.join(root, cfg)));
3691
+ const hasPrettierConfig = prettierConfigs.some((cfg) => fs10.existsSync(path10.join(root, cfg)));
3442
3692
  if (hasPrettierConfig && depNames.has("prettier")) {
3443
3693
  tools.push("prettier");
3444
3694
  }
3445
- 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"))) {
3446
3696
  tools.push("ruff");
3447
3697
  }
3448
3698
  return tools;
3449
3699
  }
3450
3700
  function readPackageJson(root) {
3451
3701
  try {
3452
- const pkgPath = path11.join(root, "package.json");
3453
- if (fs11.existsSync(pkgPath)) {
3454
- 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"));
3455
3705
  }
3456
3706
  } catch {
3457
3707
  }
@@ -3471,10 +3721,16 @@ async function runTool(tool, cwd, files, autoFix, timeoutSeconds) {
3471
3721
  }
3472
3722
  function buildToolCommand(tool, files, autoFix) {
3473
3723
  const pm = detectPackageManagerPrefix();
3724
+ const root = getProjectRoot();
3474
3725
  switch (tool) {
3475
3726
  case "eslint": {
3476
3727
  const fixFlag = autoFix ? " --fix" : "";
3477
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
+ }
3478
3734
  return pm + "eslint" + fixFlag + " " + target + " --format unix";
3479
3735
  }
3480
3736
  case "tsc": {
@@ -3483,6 +3739,11 @@ function buildToolCommand(tool, files, autoFix) {
3483
3739
  case "prettier": {
3484
3740
  const fixFlag = autoFix ? " --write" : " --check";
3485
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
+ }
3486
3747
  return pm + "prettier" + fixFlag + " " + target;
3487
3748
  }
3488
3749
  case "ruff": {
@@ -3496,10 +3757,25 @@ function buildToolCommand(tool, files, autoFix) {
3496
3757
  }
3497
3758
  function detectPackageManagerPrefix() {
3498
3759
  const root = getProjectRoot();
3499
- if (fs11.existsSync(path11.join(root, "pnpm-lock.yaml"))) return "pnpm ";
3500
- 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 ";
3501
3762
  return "npx --no-install ";
3502
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
+ }
3503
3779
  function parseToolOutput(tool, stdout, stderr, projectRoot) {
3504
3780
  switch (tool) {
3505
3781
  case "eslint":
@@ -3516,7 +3792,7 @@ function parseToolOutput(tool, stdout, stderr, projectRoot) {
3516
3792
  }
3517
3793
  function resolveToolPath(filePath, projectRoot) {
3518
3794
  const trimmed = filePath.trim();
3519
- return path11.isAbsolute(trimmed) ? path11.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
3795
+ return path10.isAbsolute(trimmed) ? path10.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
3520
3796
  }
3521
3797
  function parseEslintOutput(output, projectRoot) {
3522
3798
  const issues = [];
@@ -3629,10 +3905,13 @@ function formatResults2(results, allIssues, toolsRun) {
3629
3905
  if (!hasIssues) {
3630
3906
  const failedTools = results.filter((r) => r.exitCode !== 0);
3631
3907
  if (failedTools.length > 0) {
3632
- lines.push("Tool(s) executed but reported no parseable issues.");
3633
- 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
+ }
3634
3912
  lines.push("");
3635
- 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.");
3636
3915
  } else {
3637
3916
  lines.push("All checks passed \u2014 no issues found.");
3638
3917
  }
@@ -3674,13 +3953,13 @@ function formatNoToolsDetected() {
3674
3953
  "No linters or checkers detected for this project.",
3675
3954
  "",
3676
3955
  "Detected tools include:",
3677
- " - ESLint (check .eslintrc or eslint.config.*)",
3678
- " - TypeScript (check tsconfig.json)",
3679
- " - Prettier (check .prettierrc)",
3680
- " - 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",
3681
3960
  "",
3682
- "Install and configure a linter to use this tool.",
3683
- "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."
3684
3963
  ].join("\n");
3685
3964
  }
3686
3965
  function formatToolNotAvailable(requested, available) {
@@ -3793,8 +4072,8 @@ async function handleReflect(params) {
3793
4072
  import { execSync as execSync6 } from "child_process";
3794
4073
 
3795
4074
  // src/guards/antiPatternDetector.ts
3796
- import fs12 from "fs";
3797
- import path12 from "path";
4075
+ import fs11 from "fs";
4076
+ import path11 from "path";
3798
4077
  import { execSync as execSync4 } from "child_process";
3799
4078
  var SCRIPT_PATCH_PATTERNS = [
3800
4079
  "writeFileSync",
@@ -3821,20 +4100,20 @@ function findPatchScripts(projectRoot) {
3821
4100
  const warnings = [];
3822
4101
  const recentFiles = scanRecentFiles(projectRoot);
3823
4102
  for (const file of recentFiles) {
3824
- const ext = path12.extname(file).toLowerCase();
4103
+ const ext = path11.extname(file).toLowerCase();
3825
4104
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
3826
- const relativePath = path12.relative(projectRoot, file);
4105
+ const relativePath = path11.relative(projectRoot, file);
3827
4106
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
3828
4107
  continue;
3829
4108
  }
3830
4109
  try {
3831
- const content = fs12.readFileSync(file, "utf-8").toLowerCase();
4110
+ const content = fs11.readFileSync(file, "utf-8").toLowerCase();
3832
4111
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
3833
4112
  if (matchedPattern) {
3834
4113
  warnings.push({
3835
4114
  severity: "high",
3836
4115
  pattern: "script-patching",
3837
- message: `Created script file that modifies other files: ${path12.basename(file)}`,
4116
+ message: `Created script file that modifies other files: ${path11.basename(file)}`,
3838
4117
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
3839
4118
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
3840
4119
  filePath: relativePath
@@ -3868,15 +4147,15 @@ function detectBashGrepUsage() {
3868
4147
  function scanRecentFiles(projectRoot) {
3869
4148
  const recent = [];
3870
4149
  try {
3871
- const entries = fs12.readdirSync(projectRoot, { withFileTypes: true });
4150
+ const entries = fs11.readdirSync(projectRoot, { withFileTypes: true });
3872
4151
  const now = Date.now();
3873
4152
  const recentThreshold = 30 * 60 * 1e3;
3874
4153
  for (const entry of entries) {
3875
4154
  if (!entry.isFile()) continue;
3876
4155
  try {
3877
- const stat = fs12.statSync(path12.join(projectRoot, entry.name));
4156
+ const stat = fs11.statSync(path11.join(projectRoot, entry.name));
3878
4157
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
3879
- recent.push(path12.join(projectRoot, entry.name));
4158
+ recent.push(path11.join(projectRoot, entry.name));
3880
4159
  }
3881
4160
  } catch {
3882
4161
  continue;
@@ -3900,14 +4179,14 @@ function detectGitPatchScripts(projectRoot) {
3900
4179
  const prefix = line.substring(0, 2);
3901
4180
  if (prefix !== "??" && prefix !== "A ") continue;
3902
4181
  const file = line.substring(3).trim();
3903
- const ext = path12.extname(file).toLowerCase();
4182
+ const ext = path11.extname(file).toLowerCase();
3904
4183
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
3905
4184
  const isRootLevel = !file.includes("/");
3906
4185
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
3907
4186
  if (!isRootLevel && !isScriptsDir) continue;
3908
- const fullPath = path12.join(projectRoot, file);
3909
- if (fs12.existsSync(fullPath)) {
3910
- 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();
3911
4190
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
3912
4191
  if (matchedPattern) {
3913
4192
  warnings.push({
@@ -3930,7 +4209,7 @@ function detectGitPatchScripts(projectRoot) {
3930
4209
  if (deletedStdout) {
3931
4210
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
3932
4211
  for (const file of deletedFiles) {
3933
- const ext = path12.extname(file).toLowerCase();
4212
+ const ext = path11.extname(file).toLowerCase();
3934
4213
  if (SCRIPT_EXTENSIONS.includes(ext)) {
3935
4214
  warnings.push({
3936
4215
  severity: "high",
@@ -3990,21 +4269,21 @@ function detectAllAntiPatterns() {
3990
4269
  }
3991
4270
 
3992
4271
  // src/engine/contextSnapshot.ts
3993
- import fs13 from "fs";
3994
- import path13 from "path";
4272
+ import fs12 from "fs";
4273
+ import path12 from "path";
3995
4274
  import { execSync as execSync5 } from "child_process";
3996
4275
  var SNAPSHOT_DIR = "context-snapshots";
3997
4276
  function snapshotDir() {
3998
- return path13.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
4277
+ return path12.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
3999
4278
  }
4000
4279
  function ensureSnapshotDir() {
4001
4280
  const dir = snapshotDir();
4002
- if (!fs13.existsSync(dir)) {
4003
- fs13.mkdirSync(dir, { recursive: true });
4281
+ if (!fs12.existsSync(dir)) {
4282
+ fs12.mkdirSync(dir, { recursive: true });
4004
4283
  }
4005
4284
  }
4006
4285
  function snapshotFilePath(timestamp) {
4007
- return path13.join(snapshotDir(), `${timestamp}.json`);
4286
+ return path12.join(snapshotDir(), `${timestamp}.json`);
4008
4287
  }
4009
4288
  function saveSnapshot(goal) {
4010
4289
  try {
@@ -4025,7 +4304,7 @@ function saveSnapshot(goal) {
4025
4304
  hasConventions: !!summary.hasConventions
4026
4305
  };
4027
4306
  try {
4028
- 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");
4029
4308
  } catch {
4030
4309
  }
4031
4310
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -4033,12 +4312,12 @@ function saveSnapshot(goal) {
4033
4312
  }
4034
4313
  function listSnapshots() {
4035
4314
  const dir = snapshotDir();
4036
- if (!fs13.existsSync(dir)) return [];
4315
+ if (!fs12.existsSync(dir)) return [];
4037
4316
  try {
4038
- 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));
4039
4318
  return files.map((f) => {
4040
4319
  try {
4041
- const content = fs13.readFileSync(path13.join(dir, f), "utf-8");
4320
+ const content = fs12.readFileSync(path12.join(dir, f), "utf-8");
4042
4321
  return JSON.parse(content);
4043
4322
  } catch {
4044
4323
  return null;
@@ -4244,11 +4523,73 @@ async function handleKumaContext(params) {
4244
4523
  }
4245
4524
  }
4246
4525
 
4247
- // src/engine/lspClient.ts
4248
- import { spawn as spawn2 } from "child_process";
4249
- import path14 from "path";
4250
- import fs14 from "fs";
4251
- var LSPClient = class {
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
+
4588
+ // src/engine/lspClient.ts
4589
+ import { spawn as spawn2 } from "child_process";
4590
+ import path14 from "path";
4591
+ import fs14 from "fs";
4592
+ var LSPClient = class {
4252
4593
  process = null;
4253
4594
  requestId = 0;
4254
4595
  pending = /* @__PURE__ */ new Map();
@@ -4366,7 +4707,8 @@ var LSPClient = class {
4366
4707
  }
4367
4708
  } catch {
4368
4709
  }
4369
- return null;
4710
+ console.error(`[LSP] typescript-language-server not found locally. Trying npx fallback.`);
4711
+ return "npx --yes typescript-language-server";
4370
4712
  }
4371
4713
  toUri(filePath) {
4372
4714
  return `file://${filePath}`;
@@ -4612,19 +4954,22 @@ async function handleFindReferences(params) {
4612
4954
  }
4613
4955
  if (!lspClient.isAvailable()) {
4614
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.`);
4615
4958
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4616
4959
  if (!symbolName) {
4617
- 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.
4618
4962
 
4619
- \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`;
4620
4965
  }
4621
4966
  return fallbackGrepReferences(symbolName, resolvedPath, line, character);
4622
4967
  }
4623
4968
  try {
4624
4969
  const references = await lspClient.findReferences(resolvedPath, line, character);
4625
4970
  if (references.length === 0) {
4626
- return `\u{1F50D} **Find References** \u2014 "${filePath}:${line + 1}:${character + 1}"
4627
- \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.`;
4628
4973
  }
4629
4974
  const enrichedRefs = references.map((ref) => {
4630
4975
  let lineContent = "";
@@ -4644,7 +4989,7 @@ async function handleFindReferences(params) {
4644
4989
  }
4645
4990
  const projectRoot = getProjectRoot();
4646
4991
  const lines = [
4647
- `\u{1F50D} **Find References** \u2014 ${enrichedRefs.length} references found`,
4992
+ `**Find References** \u2014 ${enrichedRefs.length} references found`,
4648
4993
  `\u{1F4CD} File: ${path15.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
4649
4994
  ""
4650
4995
  ];
@@ -4660,7 +5005,7 @@ async function handleFindReferences(params) {
4660
5005
  lines.push("\u{1F4A1} Use smart_file_picker to read specific files.");
4661
5006
  return lines.join("\n");
4662
5007
  } catch (err) {
4663
- 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)}`;
4664
5009
  }
4665
5010
  }
4666
5011
  async function handleGoToDefinition(params) {
@@ -4675,19 +5020,22 @@ async function handleGoToDefinition(params) {
4675
5020
  }
4676
5021
  if (!lspClient.isAvailable()) {
4677
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.`);
4678
5024
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4679
5025
  if (!symbolName) {
4680
- 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.
4681
5028
 
4682
- \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`;
4683
5031
  }
4684
5032
  return fallbackGrepDefinition(symbolName);
4685
5033
  }
4686
5034
  try {
4687
5035
  const definition = await lspClient.goToDefinition(resolvedPath, line, character);
4688
5036
  if (!definition) {
4689
- return `\u{1F50D} **Go to Definition** \u2014 "${filePath}:${line + 1}:${character + 1}"
4690
- \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.`;
4691
5039
  }
4692
5040
  const projectRoot = getProjectRoot();
4693
5041
  const relPath = path15.relative(projectRoot, definition.filePath);
@@ -4712,7 +5060,7 @@ async function handleGoToDefinition(params) {
4712
5060
  ];
4713
5061
  return lines.join("\n");
4714
5062
  } catch (err) {
4715
- 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)}`;
4716
5064
  }
4717
5065
  }
4718
5066
  async function handleRenameSymbol(params) {
@@ -4730,11 +5078,13 @@ async function handleRenameSymbol(params) {
4730
5078
  }
4731
5079
  if (!lspClient.isAvailable()) {
4732
5080
  sessionMemory.recordToolCall("lsp_query", { action: "rename", filePath, line, character, newName, fallback: "none" });
4733
- return `\u26A0\uFE0F **Rename Symbol** unavailable without LSP server.
4734
- Rename requires typescript-language-server to track references across all files.
4735
- \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
4736
5086
 
4737
- 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.`;
4738
5088
  }
4739
5089
  try {
4740
5090
  const result = await lspClient.renameSymbol(resolvedPath, line, character, newName);
@@ -4747,7 +5097,7 @@ Make sure:
4747
5097
  \`\`\``;
4748
5098
  }
4749
5099
  if (result.changes.length === 0) {
4750
- return "\u26A0\uFE0F No changes needed.";
5100
+ return "No changes needed.";
4751
5101
  }
4752
5102
  const projectRoot = getProjectRoot();
4753
5103
  let totalEdits = 0;
@@ -4803,12 +5153,13 @@ async function handleGetTypeInfo(params) {
4803
5153
  }
4804
5154
  if (!lspClient.isAvailable()) {
4805
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.`);
4806
5157
  try {
4807
5158
  const fileContent = fs15.readFileSync(resolvedPath, "utf-8");
4808
5159
  const fileLines = fileContent.split("\n");
4809
5160
  const targetLine = fileLines[line];
4810
5161
  if (!targetLine) {
4811
- 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}".`;
4812
5163
  }
4813
5164
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
4814
5165
  const projectRoot = getProjectRoot();
@@ -4825,29 +5176,26 @@ async function handleGetTypeInfo(params) {
4825
5176
  }
4826
5177
  }
4827
5178
  const resultLines = [
4828
- `\u{1F4CB} **Type Info** (fallback \u2014 LSP unavailable)`,
4829
- `\u{1F4C4} \`${relPath}:${line + 1}:${character + 1}\``,
4830
- 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.`,
4831
5181
  "",
4832
5182
  "\u{1F4CE} Context:",
4833
5183
  "```",
4834
5184
  ...contextLines,
4835
5185
  "```",
4836
5186
  "",
4837
- `\u26A0\uFE0F Full type info requires typescript-language-server.`,
4838
- `\u{1F4A1} Install: npm install typescript-language-server --save-dev`,
4839
- `\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."
4840
5188
  ];
4841
5189
  return resultLines.join("\n");
4842
5190
  } catch (err) {
4843
- 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)}`;
4844
5192
  }
4845
5193
  }
4846
5194
  try {
4847
5195
  const hoverInfo = await lspClient.getTypeInfo(resolvedPath, line, character);
4848
5196
  if (!hoverInfo || !hoverInfo.contents) {
4849
- return `\u{1F4CB} **Type Info** \u2014 "${filePath}:${line + 1}:${character + 1}"
4850
- \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.`;
4851
5199
  }
4852
5200
  const projectRoot = getProjectRoot();
4853
5201
  const relPath = path15.relative(projectRoot, resolvedPath);
@@ -4935,8 +5283,8 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4935
5283
  }
4936
5284
  }
4937
5285
  if (results.length === 0) {
4938
- return `\u{1F50D} **Find References** (regex fallback) \u2014 "${symbolName}"
4939
- \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.`;
4940
5288
  }
4941
5289
  const grouped = /* @__PURE__ */ new Map();
4942
5290
  for (const r of results) {
@@ -4946,17 +5294,14 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4946
5294
  }
4947
5295
  const projectRoot = getProjectRoot();
4948
5296
  const lines = [
4949
- `\u{1F50D} **Find References** (regex fallback) \u2014 ${results.length} references found`,
5297
+ `**Find References** \u2014 ${results.length} references found`,
4950
5298
  `\u{1F4CD} Symbol: "${symbolName}"`,
4951
- `\u26A0\uFE0F Regex results may be less accurate than LSP (includes comments/strings).`,
4952
5299
  ""
4953
5300
  ];
4954
5301
  if (grouped.size > 1) {
4955
5302
  const fileList = [...grouped.keys()].map((f) => path15.relative(projectRoot, f)).join(", ");
4956
- lines.push(`\u26A0\uFE0F **Ambiguity warning:** Found matches across ${grouped.size} different files (${fileList}).`);
4957
- lines.push(` Regex fallback cannot distinguish between different scopes with the same symbol name.`);
4958
- lines.push(` If you intended only one scope, clarify which file or location you mean.`);
4959
- 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.`);
4960
5305
  lines.push("");
4961
5306
  } else if (results.length > 1) {
4962
5307
  const filePath = [...grouped.keys()][0];
@@ -4965,9 +5310,8 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4965
5310
  const minLine = Math.min(...matchLines);
4966
5311
  const maxLine = Math.max(...matchLines);
4967
5312
  if (maxLine - minLine > 15) {
4968
- lines.push(`\u26A0\uFE0F **Ambiguity warning:** Found ${results.length} matches for "${symbolName}" spanning lines ${minLine}-${maxLine} in the same file.`);
4969
- lines.push(` They may be in different function scopes \u2014 regex cannot distinguish them.`);
4970
- 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.`);
4971
5315
  lines.push("");
4972
5316
  }
4973
5317
  }
@@ -4979,7 +5323,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
4979
5323
  }
4980
5324
  lines.push("");
4981
5325
  }
4982
- lines.push("\u{1F4A1} Install typescript-language-server for more accurate results.");
5326
+ console.error(`[LSP] Regex fallback: found ${results.length} references for "${symbolName}".`);
4983
5327
  return lines.join("\n");
4984
5328
  } catch (err) {
4985
5329
  return `Error in fallback grep references: ${err instanceof Error ? err.message : String(err)}`;
@@ -5015,12 +5359,10 @@ async function fallbackGrepDefinition(symbolName) {
5015
5359
  const projectRoot = getProjectRoot();
5016
5360
  const relPath = path15.relative(projectRoot, file);
5017
5361
  return [
5018
- `\u{1F4CD} **Go to Definition** (regex fallback)`,
5362
+ `\u{1F4CD} **Go to Definition**`,
5019
5363
  `\u{1F4C4} File: \`${relPath}\``,
5020
5364
  `\u{1F4CF} Line: ${i + 1}`,
5021
- `\u2514 ${trimmed.substring(0, 120)}`,
5022
- "",
5023
- `\u{1F4A1} Install typescript-language-server for more accurate results.`
5365
+ `\u2514 ${trimmed.substring(0, 120)}`
5024
5366
  ].join("\n");
5025
5367
  }
5026
5368
  }
@@ -5029,9 +5371,9 @@ async function fallbackGrepDefinition(symbolName) {
5029
5371
  continue;
5030
5372
  }
5031
5373
  }
5032
- return `\u{1F4CD} **Go to Definition** (regex fallback) \u2014 "${symbolName}"
5033
- \u26A0\uFE0F Cannot find definition.
5034
- \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.`;
5035
5377
  } catch (err) {
5036
5378
  return `Error in fallback grep definition: ${err instanceof Error ? err.message : String(err)}`;
5037
5379
  }
@@ -5361,819 +5703,22 @@ function registerAllTools(server) {
5361
5703
  }
5362
5704
  }
5363
5705
  );
5364
- console.error("[Manifest] Registered 17 tools.");
5365
- }
5366
-
5367
- // src/cli/init.ts
5368
- import fs16 from "fs";
5369
- import path16 from "path";
5370
- var ALL_CONFIG_TYPES = [
5371
- "claude",
5372
- "cursor",
5373
- "windsurf",
5374
- "copilot",
5375
- "cline",
5376
- "aider",
5377
- "antigravity",
5378
- "opencode",
5379
- "codex",
5380
- "qwen",
5381
- "kiro",
5382
- "openclaw",
5383
- "codewhale"
5384
- ];
5385
- var CONFIG_LABELS = {
5386
- claude: "Claude Code (CLAUDE.md / plugin)",
5387
- cursor: "Cursor (.cursor/rules/*.mdc)",
5388
- windsurf: "Windsurf (.windsurfrules)",
5389
- copilot: "GitHub Copilot Editor (AGENTS.md + Skill)",
5390
- cline: "Cline (.clinerules/*.md)",
5391
- aider: "Aider (CONVENTIONS.md via .aider.conf.yml)",
5392
- antigravity: "Antigravity CLI (.agents/skills/)",
5393
- opencode: "OpenCode (opencode.json)",
5394
- codex: "Codex CLI (AGENTS.md + .codex/config.toml)",
5395
- qwen: "Qwen Code (AGENTS.md + settings.json)",
5396
- kiro: "Kiro (.kiro/steering/*.md)",
5397
- openclaw: "OpenClaw (skills/)",
5398
- codewhale: "CodeWhale (skills/ + .codewhale/mcp.json)"
5399
- };
5400
- function configFilePath(type) {
5401
- switch (type) {
5402
- case "claude":
5403
- return "CLAUDE.md";
5404
- case "cursor":
5405
- return ".cursor/rules/kuma.mdc";
5406
- case "windsurf":
5407
- return ".windsurfrules";
5408
- case "copilot":
5409
- return "AGENTS.md";
5410
- case "cline":
5411
- return ".clinerules/kuma.md";
5412
- case "aider":
5413
- return "CONVENTIONS.md";
5414
- case "antigravity":
5415
- return ".agents/skills/kuma/SKILL.md";
5416
- case "opencode":
5417
- return "opencode.json";
5418
- case "codex":
5419
- return "AGENTS.md";
5420
- case "qwen":
5421
- return "AGENTS.md";
5422
- case "kiro":
5423
- return ".kiro/steering/kuma.md";
5424
- case "openclaw":
5425
- return "skills/kuma/SKILL.md";
5426
- case "codewhale":
5427
- return "skills/kuma/SKILL.md";
5428
- }
5429
- }
5430
- var CORE_RULES = [
5431
- "## AI Agent Usage Guidelines",
5432
- "",
5433
- "Kuma MCP tools are available. Use them correctly:",
5434
- "",
5435
- "### Code Search",
5436
- "- Use the **smart_grep** tool to search code - NOT bash grep/ripgrep manually",
5437
- "- smart_grep returns line numbers + context, caches results, respects .gitignore",
5438
- `- **Example:** smart_grep({ query: "function handleAuth", extensions: ['ts'] })`,
5439
- "",
5440
- "### Reading Code",
5441
- "- Use the **smart_file_picker** tool to read files with smart chunking",
5442
- "- For large files, use startLine/endLine to read specific ranges",
5443
- '- **Example:** smart_file_picker({ filePath: "src/index.ts", chunkStrategy: "outline" })',
5444
- '- **Example:** smart_file_picker({ filePath: "src/index.ts", startLine: 10, endLine: 30 })',
5445
- "",
5446
- "### Editing Code",
5447
- "- Use the **precise_diff_editor** tool to edit files (fuzzy matching + auto-backup)",
5448
- "- DO NOT create Python/Node scripts to patch files; use precise_diff_editor directly",
5449
- "- DO NOT use bash sed/cat/awk to modify source files",
5450
- '- **Example:** precise_diff_editor({ filePath: "src/app.ts", edits: [{ searchBlock: "old code", replaceBlock: "new code" }] })',
5451
- '- **Example:** precise_diff_editor({ filePath: "src/app.ts", dryRun: true, edits: [...] })',
5452
- '- **Example:** precise_diff_editor({ filePath: "src/app.ts", action: "rollback" })',
5453
- "",
5454
- "### Creating Files",
5455
- "- Use the **batch_file_writer** tool to create new files (up to 15 at once)",
5456
- '- **Example:** batch_file_writer({ files: [{ filePath: "src/util.ts", content: "// code", instructions: "reason for creating" }] })',
5457
- "",
5458
- "### Running Tasks",
5459
- "- Use the **execute_safe_test** tool for test/build/lint/typecheck",
5460
- "- Always run typecheck after editing TypeScript files",
5461
- '- **Example:** execute_safe_test({ task: "typecheck" })',
5462
- '- **Example:** execute_safe_test({ task: "custom", customCommand: "npm run lint" })',
5463
- "",
5464
- "### Code Review",
5465
- "- Use the **code_reviewer** tool after changes",
5466
- "- Supports focus: correctness, security, performance, over-engineering",
5467
- '- **Example:** code_reviewer({ focus: "security" })',
5468
- '- **Example:** code_reviewer({ files: ["src/auth.ts"], format: "json" })',
5469
- "",
5470
- "### Git Operations",
5471
- "- Use the **git_diff** tool for structured diff output",
5472
- "- Use the **git_log** tool for commit history",
5473
- "- **Example:** git_log({ maxCount: 5 })",
5474
- "- **Example:** git_diff({ staged: true })",
5475
- "",
5476
- "### Session Awareness",
5477
- "- Use the **kuma_reflect** tool to check on-track/drift/loops",
5478
- "- Use the **kuma_guard** tool for deeper safety checks (anti-patterns, auto-detection)",
5479
- "- Use the **get_session_memory** tool to recall session state",
5480
- '- **Example:** kuma_reflect({ goal: "refactor auth" })',
5481
- '- **Example:** kuma_guard({ check: "all", goal: "refactor auth" })',
5482
- "",
5483
- "### LSP / Code Intelligence",
5484
- "- Use the **lsp_query** tool for go-to-definition, find references, type info",
5485
- '- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "def" })',
5486
- '- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "refs" })',
5487
- "",
5488
- "### Static Analysis",
5489
- "- Use the **static_analysis** tool to run ESLint/TSC/Prettier/Ruff",
5490
- '- **Example:** static_analysis({ tool: "eslint", autoFix: true })',
5491
- "",
5492
- "### Project Structure",
5493
- "- Use the **project_structure** tool to see project layout",
5494
- "- **Example:** project_structure({ depth: 2, folderOnly: true })",
5495
- "",
5496
- "### Write Memory",
5497
- "- Use the **write_memory** tool to persist decisions and glossary",
5498
- '- **Example:** write_memory({ topic: "decisions", content: "## Reason for using X" })',
5499
- "",
5500
- "### General Rules",
5501
- "- When you error, READ the error carefully before acting",
5502
- "- After 3+ edits without running tests, stop and verify",
5503
- "- If a tool fails, check the message - don't retry blindly",
5504
- "- Detect conventions first with the **project_conventions** tool",
5505
- "- **Example:** project_conventions({ forceRescan: true })"
5506
- ].join("\n");
5507
- var KUMA_CORE_INSTRUCTIONS = CORE_RULES;
5508
- function claudeTemplate() {
5509
- return [
5510
- "# Kuma AI Agent Guidelines",
5511
- "",
5512
- KUMA_CORE_INSTRUCTIONS,
5513
- "",
5514
- "## Workflow Pipeline",
5515
- "",
5516
- "For best results:",
5517
- "1. **project_conventions** - detect stack",
5518
- "2. **smart_grep** / **smart_file_picker** - understand code",
5519
- "3. **precise_diff_editor** / **batch_file_writer** - make changes",
5520
- "4. **execute_safe_test** - verify (typecheck + test)",
5521
- "5. **code_reviewer** - review changes"
5522
- ].join("\n");
5523
- }
5524
- function cursorRulesTemplate() {
5525
- return [
5526
- "---",
5527
- "description: Kuma MCP tool usage rules for AI coding agents",
5528
- "alwaysApply: true",
5529
- "---",
5530
- "",
5531
- "You are an expert engineer. Kuma MCP tools are available.",
5532
- "",
5533
- "## Critical Rules",
5534
- "",
5535
- KUMA_CORE_INSTRUCTIONS,
5536
- "",
5537
- "## NEVER",
5538
- "- Never create Python/Node scripts to patch code",
5539
- "- Never use bash sed/cat/awk to edit source files",
5540
- "- Never run git push/git commit through bash"
5541
- ].join("\n");
5542
- }
5543
- function windsurfRulesTemplate() {
5544
- return [
5545
- "# Windsurf Cascade Rules with Kuma",
5546
- "",
5547
- KUMA_CORE_INSTRUCTIONS
5548
- ].join("\n");
5549
- }
5550
- function copilotTemplate() {
5551
- return [
5552
- "## GitHub Copilot Editor",
5553
- "",
5554
- "Kuma MCP tools are available. Use them correctly:",
5555
- "",
5556
- KUMA_CORE_INSTRUCTIONS,
5557
- "",
5558
- "### Copilot Editor-Specific",
5559
- "- Copilot Editor reads AGENTS.md at project root for persistent instructions",
5560
- "- Configure MCP servers via VS Code settings (cmd+shift+P \u2192 Developer: Reload Window after adding Kuma)",
5561
- "- Use kuma_guard periodically to check for anti-patterns"
5562
- ].join("\n");
5563
- }
5564
- function clineRulesTemplate() {
5565
- return [
5566
- "---",
5567
- "description: Kuma MCP tool usage rules for AI coding agents",
5568
- "paths:",
5569
- ' - "*"',
5570
- "---",
5571
- "",
5572
- KUMA_CORE_INSTRUCTIONS
5573
- ].join("\n");
5574
- }
5575
- function aiderTemplate() {
5576
- return [
5577
- "# Kuma MCP - Aider Coding Conventions",
5578
- "",
5579
- "These conventions are loaded by Aider via the `read:` field in .aider.conf.yml",
5580
- "",
5581
- KUMA_CORE_INSTRUCTIONS
5582
- ].join("\n");
5583
- }
5584
- function opencodeTemplate() {
5585
- const config = {
5586
- mcp: {
5587
- kuma: {
5588
- type: "local",
5589
- command: ["npx", "-y", "@plumpslabs/kuma"],
5590
- enabled: true
5591
- }
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)")
5592
5711
  },
5593
- instructions: ["CLAUDE.md"]
5594
- };
5595
- const header = [
5596
- "// Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5597
- "// OpenCode config with Kuma MCP tools. Edit opencode.json to customize.",
5598
- ""
5599
- ].join("\n");
5600
- return header + JSON.stringify(config, null, 2) + "\n";
5601
- }
5602
- function codexTemplate() {
5603
- return [
5604
- "## Codex CLI (OpenAI)",
5605
- "",
5606
- "Kuma MCP tools are available. Use them correctly:",
5607
- "",
5608
- KUMA_CORE_INSTRUCTIONS,
5609
- "",
5610
- "### Codex-Specific",
5611
- "- Codex uses cascading AGENTS.md files (global ~/.codex/AGENTS.md -> project AGENTS.md)",
5612
- "- MCP config is in .codex/config.toml (auto-generated by kuma init)",
5613
- "- Use kuma_guard periodically to check for anti-patterns"
5614
- ].join("\n");
5615
- }
5616
- function codexConfigTomlTemplate() {
5617
- return [
5618
- "# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5619
- "# Kuma MCP server config for Codex CLI",
5620
- "",
5621
- "[mcp_servers.kuma]",
5622
- 'command = "npx"',
5623
- 'args = ["-y", "@plumpslabs/kuma"]',
5624
- ""
5625
- ].join("\n");
5626
- }
5627
- function qwenTemplate() {
5628
- return [
5629
- "## Qwen Code",
5630
- "",
5631
- "Kuma MCP tools are available. Use them correctly:",
5632
- "",
5633
- KUMA_CORE_INSTRUCTIONS,
5634
- "",
5635
- "### Qwen-Specific",
5636
- "- Qwen reads AGENTS.md at project root for persistent instructions",
5637
- "- MCP config is in settings.json (auto-generated by kuma init)",
5638
- "- Use kuma_guard periodically to check for anti-patterns"
5639
- ].join("\n");
5640
- }
5641
- function qwenSettingsTemplate() {
5642
- const config = {
5643
- mcpServers: {
5644
- kuma: {
5645
- command: "npx",
5646
- args: ["-y", "@plumpslabs/kuma"],
5647
- env: {}
5648
- }
5649
- }
5650
- };
5651
- return JSON.stringify(config, null, 2) + "\n";
5652
- }
5653
- function kiroRulesTemplate() {
5654
- return [
5655
- "---",
5656
- "name: kuma-mcp",
5657
- "description: Kuma safety toolkit - use smart_grep for search, precise_diff_editor for edits",
5658
- "inclusion: always",
5659
- "---",
5660
- "",
5661
- "# Kuma MCP - Kiro Steering",
5662
- "",
5663
- KUMA_CORE_INSTRUCTIONS,
5664
- "",
5665
- "## Kiro-Specific",
5666
- "- Kiro reads steering files from .kiro/steering/ for project instructions",
5667
- "- Configure MCP servers via IDE settings or global Kiro config",
5668
- "- Use kuma_guard periodically to check for anti-patterns"
5669
- ].join("\n");
5670
- }
5671
- function openclawSkillTemplate() {
5672
- return [
5673
- "---",
5674
- "name: kuma-mcp",
5675
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5676
- "---",
5677
- "",
5678
- "# Kuma MCP - OpenClaw Skill",
5679
- "",
5680
- KUMA_CORE_INSTRUCTIONS,
5681
- "",
5682
- "## OpenClaw-Specific",
5683
- "- OpenClaw loads skills/ from workspace root or ~/.openclaw/skills for global",
5684
- "- Configure Kuma MCP server via ~/.openclaw/openclaw.json or agents standard",
5685
- "- Use kuma_guard periodically to check for anti-patterns",
5686
- "",
5687
- "## Verification",
5688
- "- After edits, run execute_safe_test to verify no breakage",
5689
- "- Use code_reviewer for correctness/security review",
5690
- "- Check kuma_reflect to confirm on-track"
5691
- ].join("\n");
5692
- }
5693
- function codewhaleTemplate() {
5694
- return [
5695
- "---",
5696
- "name: kuma-mcp",
5697
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5698
- "---",
5699
- "",
5700
- "# Kuma MCP - CodeWhale Skill",
5701
- "",
5702
- KUMA_CORE_INSTRUCTIONS,
5703
- "",
5704
- "## CodeWhale-Specific",
5705
- "- CodeWhale loads SKILL.md from skills/ (workspace-local), .agents/skills/, or ~/.codewhale/skills/",
5706
- "- MCP config is in ~/.codewhale/mcp.json or ~/.deepseek/mcp.json",
5707
- "- Use kuma_guard periodically to check for anti-patterns",
5708
- "",
5709
- "## Verification",
5710
- "- After edits, run execute_safe_test to verify no breakage",
5711
- "- Use code_reviewer for correctness/security review",
5712
- "- Check kuma_reflect to confirm on-track"
5713
- ].join("\n");
5714
- }
5715
- function antigravitySkillTemplate() {
5716
- return [
5717
- "---",
5718
- "name: kuma-mcp",
5719
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5720
- "---",
5721
- "",
5722
- "# Kuma MCP - Antigravity Skill",
5723
- "",
5724
- KUMA_CORE_INSTRUCTIONS,
5725
- "",
5726
- "## Verification",
5727
- "- After edits, run execute_safe_test to verify no breakage",
5728
- "- Use code_reviewer for correctness/security review",
5729
- "- Check kuma_reflect to confirm on-track"
5730
- ].join("\n");
5731
- }
5732
- function antigravityMcpConfigTemplate() {
5733
- const config = {
5734
- mcpServers: {
5735
- kuma: {
5736
- command: "npx",
5737
- args: ["-y", "@plumpslabs/kuma"],
5738
- env: {}
5739
- }
5740
- }
5741
- };
5742
- return JSON.stringify(config, null, 2) + "\n";
5743
- }
5744
- var TEMPLATES = {
5745
- claude: claudeTemplate,
5746
- cursor: cursorRulesTemplate,
5747
- windsurf: windsurfRulesTemplate,
5748
- copilot: copilotTemplate,
5749
- cline: clineRulesTemplate,
5750
- aider: aiderTemplate,
5751
- antigravity: antigravitySkillTemplate,
5752
- opencode: opencodeTemplate,
5753
- codex: codexTemplate,
5754
- qwen: qwenTemplate,
5755
- kiro: kiroRulesTemplate,
5756
- openclaw: openclawSkillTemplate,
5757
- codewhale: codewhaleTemplate
5758
- };
5759
- var APPEND_SEPARATOR = "\n\n---\n_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_\n\n";
5760
- function handleOpencodeSecondary(root, results) {
5761
- const claudePath = path16.resolve(root, "CLAUDE.md");
5762
- if (!fs16.existsSync(claudePath)) {
5763
- try {
5764
- fs16.writeFileSync(claudePath, [
5765
- "# Kuma MCP - OpenCode Instructions",
5766
- "",
5767
- KUMA_CORE_INSTRUCTIONS
5768
- ].join("\n"), "utf-8");
5769
- results.push({ type: "opencode", filePath: "CLAUDE.md", action: "created" });
5770
- } catch (err) {
5771
- results.push({
5772
- type: "opencode",
5773
- filePath: "CLAUDE.md",
5774
- action: "error",
5775
- error: err instanceof Error ? err.message : String(err)
5776
- });
5777
- }
5778
- }
5779
- }
5780
- function handleCodexSecondary(root, results) {
5781
- const tomlPath = path16.resolve(root, ".codex/config.toml");
5782
- if (results.some((r) => r.filePath === ".codex/config.toml")) return;
5783
- try {
5784
- const dir = path16.dirname(tomlPath);
5785
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5786
- if (fs16.existsSync(tomlPath)) {
5787
- const existingContent = fs16.readFileSync(tomlPath, "utf-8");
5788
- if (existingContent.includes("kuma")) {
5789
- results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
5790
- return;
5791
- }
5792
- fs16.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
5793
- results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
5794
- } else {
5795
- fs16.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
5796
- results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
5797
- }
5798
- } catch (err) {
5799
- results.push({
5800
- type: "codex",
5801
- filePath: ".codex/config.toml",
5802
- action: "error",
5803
- error: err instanceof Error ? err.message : String(err)
5804
- });
5805
- }
5806
- }
5807
- function handleQwenSecondary(root, results) {
5808
- const settingsPath = path16.resolve(root, "settings.json");
5809
- if (results.some((r) => r.filePath === "settings.json")) return;
5810
- try {
5811
- if (fs16.existsSync(settingsPath)) {
5812
- const existingContent = fs16.readFileSync(settingsPath, "utf-8");
5813
- if (existingContent.includes("kuma")) {
5814
- if (!existingContent.includes("_Generated by Kuma MCP_")) {
5815
- try {
5816
- const parsed = JSON.parse(existingContent);
5817
- parsed.mcpServers = parsed.mcpServers || {};
5818
- if (!parsed.mcpServers.kuma) {
5819
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5820
- fs16.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5821
- results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
5822
- return;
5823
- }
5824
- } catch {
5825
- }
5826
- }
5827
- results.push({ type: "qwen", filePath: "settings.json", action: "skipped" });
5828
- return;
5829
- }
5712
+ async (params) => {
5830
5713
  try {
5831
- const parsed = JSON.parse(existingContent);
5832
- parsed.mcpServers = parsed.mcpServers || {};
5833
- if (!parsed.mcpServers.kuma) {
5834
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5835
- fs16.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5836
- results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
5837
- }
5838
- } catch {
5839
- }
5840
- } else {
5841
- fs16.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
5842
- results.push({ type: "qwen", filePath: "settings.json", action: "created" });
5843
- }
5844
- } catch (err) {
5845
- results.push({
5846
- type: "qwen",
5847
- filePath: "settings.json",
5848
- action: "error",
5849
- error: err instanceof Error ? err.message : String(err)
5850
- });
5851
- }
5852
- }
5853
- var AGENTS_MD_TYPES = ["codex", "qwen", "copilot"];
5854
- function getAgentsMdHeader() {
5855
- return [
5856
- "# Kuma MCP - Combined Agent Instructions",
5857
- "",
5858
- "This file contains instructions for AI coding agents that read AGENTS.md.",
5859
- "Each section applies to a specific agent. Unused sections can be safely removed.",
5860
- "",
5861
- "---",
5862
- "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_",
5863
- ""
5864
- ].join("\n");
5865
- }
5866
- function getCombinedAgentsMd(selectedTypes) {
5867
- const sections = [getAgentsMdHeader()];
5868
- const agentOrder = ["codex", "qwen", "copilot"];
5869
- for (const t of agentOrder) {
5870
- if (selectedTypes.has(t)) {
5871
- sections.push(TEMPLATES[t]());
5872
- }
5873
- }
5874
- return sections.join("\n\n---\n\n");
5875
- }
5876
- function handleAntigravityMcpConfig(root, results) {
5877
- const mcpPath = path16.resolve(root, ".agents/mcp_config.json");
5878
- if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
5879
- try {
5880
- const mcpDir = path16.dirname(mcpPath);
5881
- if (fs16.existsSync(mcpPath)) {
5882
- const existingContent = fs16.readFileSync(mcpPath, "utf-8");
5883
- if (existingContent.includes("kuma")) {
5884
- if (!existingContent.includes("_Generated by Kuma MCP_")) {
5885
- const trimmed = existingContent.trimEnd();
5886
- if (trimmed.endsWith("}")) {
5887
- const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
5888
- fs16.writeFileSync(mcpPath, updated, "utf-8");
5889
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
5890
- }
5891
- }
5892
- return;
5893
- }
5894
- const parsed = JSON.parse(existingContent);
5895
- parsed.mcpServers = parsed.mcpServers || {};
5896
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5897
- fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5898
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
5899
- } else {
5900
- if (!fs16.existsSync(mcpDir)) fs16.mkdirSync(mcpDir, { recursive: true });
5901
- fs16.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
5902
- results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
5903
- }
5904
- } catch (err) {
5905
- results.push({
5906
- type: "antigravity",
5907
- filePath: ".agents/mcp_config.json",
5908
- action: "error",
5909
- error: err instanceof Error ? err.message : String(err)
5910
- });
5911
- }
5912
- }
5913
- function handleAiderSecondary(root, results) {
5914
- const ymlPath = path16.resolve(root, ".aider.conf.yml");
5915
- if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
5916
- try {
5917
- const conventionsRef = "read: CONVENTIONS.md";
5918
- if (fs16.existsSync(ymlPath)) {
5919
- const existingContent = fs16.readFileSync(ymlPath, "utf-8");
5920
- if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
5921
- results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
5922
- return;
5923
- }
5924
- const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
5925
- fs16.writeFileSync(ymlPath, newContent, "utf-8");
5926
- results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
5927
- } else {
5928
- const content = [
5929
- "# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
5930
- "# Aider will read CONVENTIONS.md for coding conventions",
5931
- "",
5932
- conventionsRef,
5933
- ""
5934
- ].join("\n");
5935
- fs16.writeFileSync(ymlPath, content, "utf-8");
5936
- results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
5937
- }
5938
- } catch (err) {
5939
- results.push({
5940
- type: "aider",
5941
- filePath: ".aider.conf.yml",
5942
- action: "error",
5943
- error: err instanceof Error ? err.message : String(err)
5944
- });
5945
- }
5946
- }
5947
- function handleCopilotSecondary(root, results) {
5948
- const skillPath = path16.resolve(root, ".github/skills/kuma/SKILL.md");
5949
- if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
5950
- try {
5951
- const dir = path16.dirname(skillPath);
5952
- const content = [
5953
- "---",
5954
- "name: kuma-mcp",
5955
- "description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
5956
- "---",
5957
- "",
5958
- "# Kuma MCP - Copilot Editor Skill",
5959
- "",
5960
- KUMA_CORE_INSTRUCTIONS
5961
- ].join("\n");
5962
- if (fs16.existsSync(skillPath)) {
5963
- const existingContent = fs16.readFileSync(skillPath, "utf-8");
5964
- if (existingContent.includes("kuma")) {
5965
- results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
5966
- return;
5967
- }
5968
- fs16.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
5969
- results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
5970
- } else {
5971
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5972
- fs16.writeFileSync(skillPath, content, "utf-8");
5973
- results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
5974
- }
5975
- } catch (err) {
5976
- results.push({
5977
- type: "copilot",
5978
- filePath: ".github/skills/kuma/SKILL.md",
5979
- action: "error",
5980
- error: err instanceof Error ? err.message : String(err)
5981
- });
5982
- }
5983
- }
5984
- function handleOpenclawSecondary(root, results) {
5985
- const mcpPath = path16.resolve(root, ".agents/mcp_config.json");
5986
- if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
5987
- try {
5988
- const dir = path16.dirname(mcpPath);
5989
- if (fs16.existsSync(mcpPath)) {
5990
- const existingContent = fs16.readFileSync(mcpPath, "utf-8");
5991
- if (existingContent.includes("kuma")) return;
5992
- const parsed = JSON.parse(existingContent);
5993
- parsed.mcpServers = parsed.mcpServers || {};
5994
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
5995
- fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5996
- results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
5997
- } else {
5998
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
5999
- fs16.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
6000
- results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
6001
- }
6002
- } catch (err) {
6003
- results.push({
6004
- type: "openclaw",
6005
- filePath: ".agents/mcp_config.json",
6006
- action: "error",
6007
- error: err instanceof Error ? err.message : String(err)
6008
- });
6009
- }
6010
- }
6011
- function handleCodewhaleSecondary(root, results) {
6012
- const mcpPath = path16.resolve(root, ".codewhale/mcp.json");
6013
- if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
6014
- try {
6015
- const dir = path16.dirname(mcpPath);
6016
- if (fs16.existsSync(mcpPath)) {
6017
- const existingContent = fs16.readFileSync(mcpPath, "utf-8");
6018
- if (existingContent.includes("kuma")) return;
6019
- const parsed = JSON.parse(existingContent);
6020
- parsed.mcpServers = parsed.mcpServers || {};
6021
- parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
6022
- fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
6023
- results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
6024
- } else {
6025
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
6026
- const config = {
6027
- mcpServers: {
6028
- kuma: {
6029
- command: "npx",
6030
- args: ["-y", "@plumpslabs/kuma"],
6031
- env: {}
6032
- }
6033
- }
6034
- };
6035
- fs16.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
6036
- results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
6037
- }
6038
- } catch (err) {
6039
- results.push({
6040
- type: "codewhale",
6041
- filePath: ".codewhale/mcp.json",
6042
- action: "error",
6043
- error: err instanceof Error ? err.message : String(err)
6044
- });
6045
- }
6046
- }
6047
- function runInit(options) {
6048
- const root = options.projectRoot ?? getProjectRoot();
6049
- const selected = options.types.length > 0 ? options.types : ALL_CONFIG_TYPES;
6050
- const results = [];
6051
- const selectedSet = new Set(selected);
6052
- const agentsMdSelected = AGENTS_MD_TYPES.filter((t) => selectedSet.has(t));
6053
- let agentsMdHandled = false;
6054
- for (const type of selected) {
6055
- const relativePath = configFilePath(type);
6056
- const fullPath = path16.resolve(root, relativePath);
6057
- const getTemplate = TEMPLATES[type];
6058
- try {
6059
- if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
6060
- agentsMdHandled = true;
6061
- const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
6062
- if (fs16.existsSync(fullPath)) {
6063
- if (options.skipExisting) {
6064
- results.push({ type, filePath: relativePath, action: "skipped" });
6065
- } else {
6066
- const existingContent = fs16.readFileSync(fullPath, "utf-8");
6067
- if (existingContent.includes("_Generated by Kuma MCP_")) {
6068
- results.push({ type, filePath: relativePath, action: "skipped" });
6069
- } else {
6070
- fs16.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
6071
- results.push({ type, filePath: relativePath, action: "appended" });
6072
- }
6073
- }
6074
- } else {
6075
- const dir = path16.dirname(fullPath);
6076
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
6077
- fs16.writeFileSync(fullPath, combinedContent, "utf-8");
6078
- results.push({ type, filePath: relativePath, action: "created" });
6079
- }
6080
- if (selectedSet.has("codex")) handleCodexSecondary(root, results);
6081
- if (selectedSet.has("qwen")) handleQwenSecondary(root, results);
6082
- if (selectedSet.has("copilot")) handleCopilotSecondary(root, results);
6083
- } else if (AGENTS_MD_TYPES.includes(type) && agentsMdHandled) {
6084
- results.push({ type, filePath: relativePath, action: "skipped" });
6085
- continue;
6086
- } else {
6087
- const template = getTemplate();
6088
- if (fs16.existsSync(fullPath)) {
6089
- if (options.skipExisting) {
6090
- results.push({ type, filePath: relativePath, action: "skipped" });
6091
- continue;
6092
- }
6093
- const existingContent = fs16.readFileSync(fullPath, "utf-8");
6094
- if (existingContent.includes("_Generated by Kuma MCP_")) {
6095
- if (type === "antigravity") {
6096
- handleAntigravityMcpConfig(root, results);
6097
- } else if (type === "openclaw") {
6098
- handleOpenclawSecondary(root, results);
6099
- } else if (type === "codewhale") {
6100
- handleCodewhaleSecondary(root, results);
6101
- }
6102
- results.push({ type, filePath: relativePath, action: "skipped" });
6103
- continue;
6104
- }
6105
- const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
6106
- fs16.writeFileSync(fullPath, newContent, "utf-8");
6107
- results.push({ type, filePath: relativePath, action: "appended" });
6108
- } else {
6109
- const dir = path16.dirname(fullPath);
6110
- if (!fs16.existsSync(dir)) {
6111
- fs16.mkdirSync(dir, { recursive: true });
6112
- }
6113
- fs16.writeFileSync(fullPath, template, "utf-8");
6114
- results.push({ type, filePath: relativePath, action: "created" });
6115
- }
6116
- if (type === "antigravity") {
6117
- handleAntigravityMcpConfig(root, results);
6118
- } else if (type === "openclaw") {
6119
- handleOpenclawSecondary(root, results);
6120
- } else if (type === "codewhale") {
6121
- handleCodewhaleSecondary(root, results);
6122
- } else if (type === "aider") {
6123
- handleAiderSecondary(root, results);
6124
- } else if (type === "opencode") {
6125
- handleOpencodeSecondary(root, results);
6126
- }
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 };
6127
5718
  }
6128
- } catch (err) {
6129
- results.push({
6130
- type,
6131
- filePath: relativePath,
6132
- action: "error",
6133
- error: err instanceof Error ? err.message : String(err)
6134
- });
6135
- }
6136
- }
6137
- return results;
6138
- }
6139
- function formatInitResults(results) {
6140
- const lines = [
6141
- "\u{1F43B} **Kuma Init - AI Agent Config Generator**",
6142
- ""
6143
- ];
6144
- for (const r of results) {
6145
- const label = CONFIG_LABELS[r.type];
6146
- switch (r.action) {
6147
- case "created":
6148
- lines.push(" \u2705 " + label);
6149
- lines.push(" \u2192 Created: " + r.filePath);
6150
- break;
6151
- case "appended":
6152
- lines.push(" \u2795 " + label);
6153
- lines.push(" \u2192 Appended to: " + r.filePath);
6154
- break;
6155
- case "skipped":
6156
- lines.push(" \u23ED " + label);
6157
- lines.push(" \u2192 Skipped (already has Kuma): " + r.filePath);
6158
- break;
6159
- case "error":
6160
- lines.push(" \u274C " + label);
6161
- lines.push(" \u2192 Error: " + (r.error ?? "unknown"));
6162
- break;
6163
5719
  }
6164
- }
6165
- const created = results.filter((r) => r.action === "created").length;
6166
- const appended = results.filter((r) => r.action === "appended").length;
6167
- const skipped = results.filter((r) => r.action === "skipped").length;
6168
- const errors = results.filter((r) => r.action === "error").length;
6169
- lines.push(
6170
- "",
6171
- "\u{1F4CA} Summary: " + created + " created, " + appended + " appended, " + skipped + " skipped, " + errors + " errors",
6172
- "",
6173
- "\u{1F4A1} Config files teach your AI how to use Kuma tools.",
6174
- "\u{1F4A1} Run again to generate additional config files anytime."
6175
5720
  );
6176
- return lines.join("\n");
5721
+ console.error("[Manifest] Registered 18 tools.");
6177
5722
  }
6178
5723
 
6179
5724
  // src/index.ts
@@ -6259,24 +5804,40 @@ async function main() {
6259
5804
  }
6260
5805
  }
6261
5806
  if (selectedTypes.length === 0) {
6262
- 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
+ );
6263
5810
  process.exit(1);
6264
5811
  }
6265
5812
  }
6266
5813
  }
6267
5814
  const skipExisting = requestedFlags.includes("--skip-existing");
6268
- const merge = requestedFlags.includes("--merge");
6269
- const results = runInit({ types: selectedTypes, projectRoot: process.cwd(), skipExisting });
5815
+ const results = runInit({
5816
+ types: selectedTypes,
5817
+ projectRoot: process.cwd(),
5818
+ skipExisting
5819
+ });
6270
5820
  const output = formatInitResults(results);
6271
5821
  console.log(output);
6272
- const fs17 = await import("fs");
6273
- const path17 = await import("path");
6274
- const matchaSkills = path17.resolve(process.cwd(), "skills/matcha/SKILL.md");
6275
- const matchaAgents = path17.resolve(process.cwd(), ".agents/skills/matcha/SKILL.md");
6276
- const matchaCursor = path17.resolve(process.cwd(), ".cursor/rules/matcha.mdc");
6277
- const matchaWindsurf = path17.resolve(process.cwd(), ".windsurf/rules/matcha.md");
6278
- if (fs17.existsSync(matchaSkills) || fs17.existsSync(matchaAgents) || fs17.existsSync(matchaCursor) || fs17.existsSync(matchaWindsurf)) {
6279
- console.error("\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!");
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
+ );
6280
5841
  }
6281
5842
  process.exit(0);
6282
5843
  }
@@ -6284,6 +5845,22 @@ async function main() {
6284
5845
  projectRoot: process.cwd(),
6285
5846
  startTime: Date.now()
6286
5847
  });
5848
+ (async () => {
5849
+ try {
5850
+ const { generateInitMdContent } = await import("./init-AMRMKI4X.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
+ })();
6287
5864
  const server = new McpServer(
6288
5865
  {
6289
5866
  name: SERVER_NAME,
@@ -6303,6 +5880,12 @@ async function main() {
6303
5880
  console.error(
6304
5881
  `[${SERVER_NAME}] Session started: ${(/* @__PURE__ */ new Date()).toISOString()}`
6305
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
+ );
6306
5889
  await server.connect(transport);
6307
5890
  console.error(
6308
5891
  `[${SERVER_NAME}] Server connected via stdio. Waiting for requests...`
@@ -6311,18 +5894,42 @@ async function main() {
6311
5894
  function interactiveSelect() {
6312
5895
  const labels = [
6313
5896
  { type: "claude", label: "1) Claude Code (CLAUDE.md)" },
6314
- { type: "cursor", label: "2) Cursor (.cursor/rules/kuma.mdc)" },
5897
+ {
5898
+ type: "cursor",
5899
+ label: "2) Cursor (.cursor/rules/kuma.mdc)"
5900
+ },
6315
5901
  { type: "windsurf", label: "3) Windsurf (.windsurfrules)" },
6316
- { type: "copilot", label: "4) GitHub Copilot Editor (AGENTS.md + Skill)" },
5902
+ {
5903
+ type: "copilot",
5904
+ label: "4) GitHub Copilot Editor (AGENTS.md + Skill)"
5905
+ },
6317
5906
  { type: "cline", label: "5) Cline (.clinerules/kuma.md)" },
6318
- { type: "aider", label: "6) Aider (CONVENTIONS.md via .aider.conf.yml)" },
6319
- { 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
+ },
6320
5915
  { type: "opencode", label: "8) OpenCode (opencode.json)" },
6321
- { type: "codex", label: "9) Codex CLI - OpenAI (AGENTS.md + .codex/config.toml)" },
6322
- { 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
+ },
6323
5924
  { type: "kiro", label: "11) Kiro (.kiro/steering/kuma.md)" },
6324
- { type: "openclaw", label: "12) OpenClaw (skills/kuma/SKILL.md)" },
6325
- { 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
+ }
6326
5933
  ];
6327
5934
  const rl = readline.createInterface({
6328
5935
  input: process.stdin,
@@ -6334,38 +5941,41 @@ function interactiveSelect() {
6334
5941
  console.error(l.label);
6335
5942
  }
6336
5943
  console.error("");
6337
- rl.question("Enter numbers separated by space (e.g. '1 3 5'), or 'all': ", (answer) => {
6338
- rl.close();
6339
- const input = answer.trim().toLowerCase();
6340
- if (input === "all") {
6341
- resolve(ALL_CONFIG_TYPES);
6342
- return;
6343
- }
6344
- const nums = input.split(/\s+/).map(Number).filter((n) => n >= 1 && n <= 13);
6345
- const typeMap = {
6346
- 1: "claude",
6347
- 2: "cursor",
6348
- 3: "windsurf",
6349
- 4: "copilot",
6350
- 5: "cline",
6351
- 6: "aider",
6352
- 7: "antigravity",
6353
- 8: "opencode",
6354
- 9: "codex",
6355
- 10: "qwen",
6356
- 11: "kiro",
6357
- 12: "openclaw",
6358
- 13: "codewhale"
6359
- };
6360
- const selected = [];
6361
- for (const n of nums) {
6362
- const t = typeMap[n];
6363
- if (t && !selected.includes(t)) {
6364
- 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;
6365
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);
6366
5977
  }
6367
- resolve(selected);
6368
- });
5978
+ );
6369
5979
  });
6370
5980
  }
6371
5981
  main().catch((err) => {