@plumpslabs/kuma 2.2.5 → 2.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1315 -519
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ import { z } from "zod";
33
33
  import fg from "fast-glob";
34
34
  import fs from "fs";
35
35
  import path from "path";
36
+ import { execSync } from "child_process";
36
37
 
37
38
  // src/utils/tokenCounter.ts
38
39
  function estimateTokens(text) {
@@ -86,94 +87,315 @@ var IGNORE_PATTERNS = [
86
87
  ];
87
88
  var grepCache = /* @__PURE__ */ new Map();
88
89
  var CACHE_TTL_MS = 3e4;
90
+ var rgAvailable = null;
91
+ function isRipgrepAvailable() {
92
+ if (process.env.KUMA_DISABLE_RG === "1" || process.env.KUMA_DISABLE_RG === "true") {
93
+ return false;
94
+ }
95
+ if (rgAvailable !== null) return rgAvailable;
96
+ try {
97
+ execSync("rg --version", { stdio: "ignore", timeout: 2e3 });
98
+ rgAvailable = true;
99
+ } catch {
100
+ rgAvailable = false;
101
+ }
102
+ return rgAvailable;
103
+ }
104
+ function quotePath(p) {
105
+ return `"${p.replace(/"/g, '\\"')}"`;
106
+ }
89
107
  async function handleSmartGrep(params) {
90
- const { query, targetFolder, maxResults = 30, extensions } = params;
91
- if (!query || query.length < 1) {
92
- return "Error: 'query' parameter is required.";
108
+ const {
109
+ query,
110
+ queries,
111
+ targetFolder,
112
+ maxResults = 30,
113
+ extensions,
114
+ contextLines = 1,
115
+ filenamesOnly = false,
116
+ countOnly = false,
117
+ outputMode = "rich",
118
+ compact = false
119
+ } = params;
120
+ const patterns = queries && queries.length > 0 ? queries : query ? [query] : [];
121
+ if (patterns.length === 0 || patterns[0].length < 1) {
122
+ return compact ? "ERR:query required" : "Error: 'query' or 'queries' parameter is required.";
93
123
  }
94
- const cacheKey = `${query}:${targetFolder ?? "root"}:${maxResults}:${extensions ? extensions.join(",") : ""}`;
124
+ const cacheKey = `${patterns.join("||")}:${targetFolder ?? "root"}:${maxResults}:${contextLines}:${filenamesOnly}:${countOnly}:${outputMode}:${extensions ? extensions.join(",") : ""}`;
95
125
  const cached = grepCache.get(cacheKey);
96
126
  if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
97
- sessionMemory.recordToolCall("smart_grep", { query, cached: true });
127
+ sessionMemory.recordToolCall("smart_grep", { query: patterns[0], cached: true });
98
128
  return cached.results;
99
129
  }
100
130
  const projectRoot = getProjectRoot();
131
+ let result;
132
+ if (isRipgrepAvailable()) {
133
+ result = await tryRipgrep(patterns, {
134
+ projectRoot,
135
+ targetFolder,
136
+ maxResults,
137
+ extensions,
138
+ contextLines,
139
+ filenamesOnly,
140
+ countOnly,
141
+ outputMode,
142
+ compact
143
+ });
144
+ } else {
145
+ result = await jsGrep(patterns, {
146
+ projectRoot,
147
+ targetFolder,
148
+ maxResults,
149
+ extensions,
150
+ contextLines,
151
+ filenamesOnly,
152
+ countOnly,
153
+ outputMode,
154
+ compact
155
+ });
156
+ }
157
+ grepCache.set(cacheKey, { results: result, timestamp: Date.now() });
158
+ sessionMemory.recordToolCall("smart_grep", {
159
+ query: patterns[0],
160
+ matchCount: result.length,
161
+ engine: rgAvailable ? "ripgrep" : "js"
162
+ });
163
+ return result;
164
+ }
165
+ async function tryRipgrep(patterns, opts) {
166
+ const args = [];
167
+ args.push("rg", "--no-heading", "--color", "never", "-i");
168
+ if (opts.extensions && opts.extensions.length > 0) {
169
+ for (const ext of opts.extensions) {
170
+ const clean = ext.startsWith(".") ? ext.slice(1) : ext;
171
+ args.push("--type-add", `kuma:*.${clean}`);
172
+ args.push("--type", "kuma");
173
+ }
174
+ }
175
+ if (opts.contextLines > 0 && !opts.filenamesOnly && !opts.countOnly) {
176
+ args.push("-C", String(opts.contextLines));
177
+ }
178
+ if (!opts.filenamesOnly && !opts.countOnly) {
179
+ const perFileLimit = Math.min(opts.maxResults, 50);
180
+ args.push("-m", String(perFileLimit));
181
+ }
182
+ if (opts.filenamesOnly) {
183
+ args.push("-l");
184
+ }
185
+ if (opts.countOnly) {
186
+ args.push("-c");
187
+ }
188
+ for (const p of patterns) {
189
+ args.push("-e", p);
190
+ }
191
+ const searchDir = opts.targetFolder ? path.join(opts.projectRoot, opts.targetFolder) : opts.projectRoot;
192
+ args.push(quotePath(searchDir));
101
193
  try {
102
- const searchPattern = targetFolder ? path.join(targetFolder, "**/*").replace(/\\/g, "/") : "**/*";
103
- const targetDepth = targetFolder ? targetFolder.split("/").filter(Boolean).length : 0;
104
- const maxDepth = targetFolder ? Math.min(targetDepth + 5, 20) : 10;
105
- let entries = await fg(searchPattern, {
106
- cwd: projectRoot,
107
- ignore: IGNORE_PATTERNS,
108
- onlyFiles: true,
109
- absolute: false,
110
- deep: maxDepth,
111
- dot: false
112
- // Skip dotfiles
194
+ const output = execSync(args.join(" "), {
195
+ cwd: opts.projectRoot,
196
+ maxBuffer: 10 * 1024 * 1024,
197
+ // 10MB
198
+ timeout: 15e3,
199
+ encoding: "utf-8",
200
+ stdio: ["ignore", "pipe", "pipe"]
113
201
  });
114
- if (extensions && extensions.length > 0) {
115
- const normalizedExts = extensions.map(
116
- (e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
117
- );
118
- entries = entries.filter((entry) => {
119
- const ext = path.extname(entry).toLowerCase();
120
- return normalizedExts.includes(ext);
121
- });
202
+ if (!output || output.trim().length === 0) {
203
+ return formatEmpty(patterns[0], opts);
122
204
  }
123
- if (entries.length === 0) {
124
- const msg = `No files found${targetFolder ? ` in folder "${targetFolder}"` : ""}${extensions ? ` with extensions [${extensions.join(", ")}]` : ""}.`;
125
- grepCache.set(cacheKey, { results: msg, timestamp: Date.now() });
126
- return msg;
205
+ return formatRipgrepOutput(output, patterns, opts);
206
+ } catch (err) {
207
+ if (err.status === 1) {
208
+ return formatEmpty(patterns[0], opts);
127
209
  }
128
- const regex = createRegex(query);
129
- const results = [];
130
- let filesScanned = 0;
131
- for (const entry of entries) {
132
- if (results.length >= maxResults) break;
133
- filesScanned++;
134
- try {
135
- const fullPath = path.join(projectRoot, entry);
136
- const stat = fs.statSync(fullPath);
137
- if (stat.size > 5e5) continue;
138
- if (isBinaryFile(fullPath)) continue;
139
- const content = fs.readFileSync(fullPath, "utf-8");
140
- const lines = content.split("\n");
141
- for (let i = 0; i < lines.length; i++) {
142
- if (results.length >= maxResults) break;
143
- const line = lines[i];
144
- if (regex.test(line)) {
145
- const contextLines = [];
146
- if (i > 0)
147
- contextLines.push(`${i}: ${lines[i - 1].substring(0, 200)}`);
148
- contextLines.push(`${i + 1}: ${line.substring(0, 200)}`);
149
- if (i < lines.length - 1)
150
- contextLines.push(`${i + 2}: ${lines[i + 1].substring(0, 200)}`);
151
- results.push({
152
- file: entry,
153
- line: i + 1,
154
- content: contextLines.join("\n")
155
- });
156
- }
210
+ return await jsGrep(patterns, opts);
211
+ }
212
+ }
213
+ function formatRipgrepOutput(output, patterns, opts) {
214
+ const lines = output.trim().split("\n");
215
+ if (opts.countOnly) {
216
+ const counts = lines.map((l) => {
217
+ const parts = l.split(":");
218
+ if (parts.length >= 2) {
219
+ const count = parts.pop();
220
+ return `${parts.join(":")}:${count}`;
221
+ }
222
+ return l;
223
+ });
224
+ if (opts.outputMode === "json") {
225
+ const obj = {};
226
+ for (const c of counts) {
227
+ const idx = c.lastIndexOf(":");
228
+ if (idx > 0) {
229
+ obj[c.slice(0, idx)] = parseInt(c.slice(idx + 1), 10);
157
230
  }
158
- } catch {
159
- continue;
160
231
  }
232
+ return JSON.stringify(obj);
161
233
  }
162
- const formatted = formatResults(results, query, filesScanned);
163
- grepCache.set(cacheKey, { results: formatted, timestamp: Date.now() });
164
- sessionMemory.recordToolCall("smart_grep", {
165
- query,
166
- matchCount: results.length,
167
- filesScanned
234
+ if (opts.compact) return counts.join("\n");
235
+ return `\u{1F4CA} Count results for "${patterns[0]}":
236
+ ${counts.join("\n")}`;
237
+ }
238
+ if (opts.filenamesOnly) {
239
+ if (opts.outputMode === "json") return JSON.stringify(lines);
240
+ if (opts.compact) return lines.join("\n");
241
+ return `\u{1F4C1} Files matching "${patterns[0]}":
242
+ ${lines.join("\n")}`;
243
+ }
244
+ const results = parseRipgrepOutput(output, opts.projectRoot).slice(0, opts.maxResults);
245
+ if (results.length === 0) {
246
+ return formatEmpty(patterns[0], opts);
247
+ }
248
+ if (opts.compact || opts.outputMode === "raw") {
249
+ return results.map((r) => `${r.file}:${r.line}:${r.content}`).join("\n");
250
+ }
251
+ if (opts.outputMode === "json") {
252
+ return JSON.stringify(results);
253
+ }
254
+ const header = `\u{1F50D} Smart Grep: "${patterns[0]}"
255
+ \u{1F4C1} ${results.length} matches
256
+ `;
257
+ const body = results.map((r, i) => `[${i + 1}] \u{1F4C4} ${r.file}:${r.line}
258
+ ${r.content}`).join("\n");
259
+ return limitLines(`${header}${body}`, 150);
260
+ }
261
+ function parseRipgrepOutput(output, projectRoot) {
262
+ const results = [];
263
+ const lines = output.trim().split("\n");
264
+ const rootPrefix = projectRoot ? projectRoot.replace(/\/+$/, "") + "/" : "";
265
+ for (const line of lines) {
266
+ const lastColonIdx = line.lastIndexOf(":");
267
+ if (lastColonIdx <= 0) continue;
268
+ const beforeLastColon = line.slice(0, lastColonIdx);
269
+ const afterLastColon = line.slice(lastColonIdx + 1);
270
+ if (!afterLastColon.trim()) continue;
271
+ const secondLastColonIdx = beforeLastColon.lastIndexOf(":");
272
+ if (secondLastColonIdx <= 0) continue;
273
+ let potentialFile = beforeLastColon.slice(0, secondLastColonIdx);
274
+ const potentialLineStr = beforeLastColon.slice(secondLastColonIdx + 1);
275
+ const potentialLine = parseInt(potentialLineStr, 10);
276
+ if (isNaN(potentialLine) || potentialLine <= 0) continue;
277
+ if (rootPrefix && potentialFile.startsWith(rootPrefix)) {
278
+ potentialFile = potentialFile.slice(rootPrefix.length);
279
+ }
280
+ results.push({
281
+ file: potentialFile,
282
+ line: potentialLine,
283
+ content: afterLastColon.trim()
168
284
  });
169
- sessionMemory.addSearchResult(
170
- query,
171
- results.map((r) => r.file)
285
+ }
286
+ return results;
287
+ }
288
+ async function jsGrep(patterns, opts) {
289
+ const { projectRoot, targetFolder, maxResults, extensions, contextLines, filenamesOnly, countOnly, outputMode, compact } = opts;
290
+ const searchPattern = targetFolder ? path.join(targetFolder, "**/*").replace(/\\/g, "/") : "**/*";
291
+ const targetDepth = targetFolder ? targetFolder.split("/").filter(Boolean).length : 0;
292
+ const maxDepth = targetFolder ? Math.min(targetDepth + 5, 20) : 10;
293
+ let entries = await fg(searchPattern, {
294
+ cwd: projectRoot,
295
+ ignore: IGNORE_PATTERNS,
296
+ onlyFiles: true,
297
+ absolute: false,
298
+ deep: maxDepth,
299
+ dot: false
300
+ });
301
+ if (extensions && extensions.length > 0) {
302
+ const normalizedExts = extensions.map(
303
+ (e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
172
304
  );
173
- return formatted;
174
- } catch (err) {
175
- return `Error searching "${query}": ${err instanceof Error ? err.message : String(err)}`;
305
+ entries = entries.filter((entry) => {
306
+ const ext = path.extname(entry).toLowerCase();
307
+ return normalizedExts.includes(ext);
308
+ });
176
309
  }
310
+ if (entries.length === 0) {
311
+ return formatEmpty(patterns[0], opts);
312
+ }
313
+ const regex = createCombinedRegex(patterns);
314
+ const results = [];
315
+ const fileCounts = /* @__PURE__ */ new Map();
316
+ const matchedFiles = /* @__PURE__ */ new Set();
317
+ let filesScanned = 0;
318
+ for (const entry of entries) {
319
+ if (!filenamesOnly && results.length >= maxResults) break;
320
+ filesScanned++;
321
+ try {
322
+ const fullPath = path.join(projectRoot, entry);
323
+ const stat = fs.statSync(fullPath);
324
+ if (stat.size > 5e5) continue;
325
+ if (isBinaryFile(fullPath)) continue;
326
+ const content = fs.readFileSync(fullPath, "utf-8");
327
+ const lines = content.split("\n");
328
+ let fileMatchCount = 0;
329
+ for (let i = 0; i < lines.length; i++) {
330
+ if (!filenamesOnly && results.length >= maxResults) break;
331
+ if (regex.test(lines[i])) {
332
+ fileMatchCount++;
333
+ matchedFiles.add(entry);
334
+ if (filenamesOnly) {
335
+ break;
336
+ }
337
+ if (countOnly) {
338
+ fileCounts.set(entry, (fileCounts.get(entry) || 0) + 1);
339
+ continue;
340
+ }
341
+ const contextLinesArr = [];
342
+ const startCtx = Math.max(0, i - contextLines);
343
+ const endCtx = Math.min(lines.length - 1, i + contextLines);
344
+ for (let ci = startCtx; ci <= endCtx; ci++) {
345
+ const prefix = ci === i ? ">" : " ";
346
+ contextLinesArr.push(`${prefix}${ci + 1}: ${lines[ci].substring(0, 200)}`);
347
+ }
348
+ results.push({
349
+ file: entry,
350
+ line: i + 1,
351
+ content: contextLinesArr.join("\n")
352
+ });
353
+ }
354
+ }
355
+ } catch {
356
+ continue;
357
+ }
358
+ }
359
+ if (matchedFiles.size === 0) {
360
+ return formatEmpty(patterns[0], opts);
361
+ }
362
+ if (filenamesOnly) {
363
+ const files = Array.from(matchedFiles);
364
+ if (outputMode === "json") return JSON.stringify(files);
365
+ if (compact) return files.join("\n");
366
+ return `\u{1F4C1} Files matching "${patterns[0]}":
367
+ ${files.join("\n")}`;
368
+ }
369
+ if (countOnly) {
370
+ const counts = Array.from(fileCounts.entries()).map(([f, c]) => `${f}:${c}`);
371
+ if (outputMode === "json") return JSON.stringify(Object.fromEntries(fileCounts));
372
+ if (compact) return counts.join("\n");
373
+ return `\u{1F4CA} Count results for "${patterns[0]}":
374
+ ${counts.join("\n")}`;
375
+ }
376
+ if (compact || outputMode === "raw") {
377
+ return results.map((r) => `${r.file}:${r.line}:${r.content.split("\n")[0].replace(/^[>\s]+\d+:\s*/, "")}`).join("\n");
378
+ }
379
+ if (outputMode === "json") {
380
+ return JSON.stringify(results);
381
+ }
382
+ return formatResults(results, patterns[0], filesScanned);
383
+ }
384
+ function createCombinedRegex(patterns) {
385
+ if (patterns.length === 1) {
386
+ return createRegex(patterns[0]);
387
+ }
388
+ const combined = patterns.map((p) => {
389
+ try {
390
+ if (/[.\\+*?[\](){}^$|]/.test(p)) {
391
+ return `(?:${p})`;
392
+ }
393
+ return `(?:${p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`;
394
+ } catch {
395
+ return p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
396
+ }
397
+ }).join("|");
398
+ return new RegExp(combined, "i");
177
399
  }
178
400
  function createRegex(query) {
179
401
  const normalized = query.replace(/\\\|/g, "|");
@@ -185,23 +407,27 @@ function createRegex(query) {
185
407
  }
186
408
  return new RegExp(normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
187
409
  }
410
+ function formatEmpty(pattern, opts) {
411
+ if (opts.outputMode === "json") return JSON.stringify({ query: pattern, matches: 0, results: [] });
412
+ if (opts.compact) return `0:${pattern}`;
413
+ return `\u{1F50D} No matches for "${pattern}".`;
414
+ }
188
415
  function formatResults(results, query, filesScanned) {
189
416
  if (results.length === 0) {
190
417
  return `\u{1F50D} No matches for "${query}" (${filesScanned} files scanned).`;
191
418
  }
192
- const lines = [
419
+ const lines_out = [
193
420
  `\u{1F50D} Smart Grep: "${query}"`,
194
421
  `\u{1F4C1} ${results.length} matches from ${filesScanned} files scanned`,
195
422
  "",
196
423
  ...results.map((r, i) => {
197
- const filePath = r.file;
198
- return `[${i + 1}] \u{1F4C4} ${filePath}:${r.line}
424
+ return `[${i + 1}] \u{1F4C4} ${r.file}:${r.line}
199
425
  ${r.content.split("\n").map((l) => ` ${l}`).join("\n")}`;
200
426
  }),
201
427
  "",
202
428
  `\u{1F4A1} Use smart_file_picker to open a specific file.`
203
429
  ];
204
- return limitLines(lines.join("\n"), 150);
430
+ return limitLines(lines_out.join("\n"), 150);
205
431
  }
206
432
  function isBinaryFile(filePath) {
207
433
  try {
@@ -224,10 +450,54 @@ import path2 from "path";
224
450
  var MAX_FILE_SIZE = 1e6;
225
451
  var CHUNK_THRESHOLD = 300;
226
452
  async function handleSmartFilePicker(params) {
227
- const { filePath, startLine, endLine, chunkStrategy = "smart" } = params;
453
+ const {
454
+ filePath,
455
+ files,
456
+ startLine,
457
+ endLine,
458
+ contextLines = 5,
459
+ chunkStrategy = "smart",
460
+ outputMode = "rich",
461
+ compact = false
462
+ } = params;
463
+ const filePaths = [];
464
+ if (files && files.length > 0) {
465
+ filePaths.push(...files);
466
+ } else if (filePath) {
467
+ filePaths.push(filePath);
468
+ } else {
469
+ return compact ? "ERR:filePath required" : "Error: 'filePath' or 'files' parameter is required.";
470
+ }
471
+ const results = [];
472
+ for (const fp of filePaths) {
473
+ const result = await readSingleFile(fp, {
474
+ startLine,
475
+ endLine,
476
+ contextLines,
477
+ chunkStrategy,
478
+ outputMode,
479
+ compact
480
+ });
481
+ results.push(result);
482
+ }
483
+ if (results.length === 1) return results[0];
484
+ if (compact || outputMode === "raw") {
485
+ return results.join("\n---\n");
486
+ }
487
+ return results.join("\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n");
488
+ }
489
+ async function readSingleFile(filePath, opts) {
490
+ const {
491
+ startLine,
492
+ endLine,
493
+ contextLines,
494
+ chunkStrategy,
495
+ outputMode,
496
+ compact
497
+ } = opts;
228
498
  const validation = validateFilePath(filePath);
229
499
  if (!validation.valid) {
230
- return "Error: " + validation.error.message;
500
+ return compact ? `ERR:${filePath} - ${validation.error.message}` : `Error: ${validation.error.message}`;
231
501
  }
232
502
  const resolvedPath = validation.resolvedPath;
233
503
  if (!fs2.existsSync(resolvedPath)) {
@@ -235,15 +505,21 @@ async function handleSmartFilePicker(params) {
235
505
  let suggestion;
236
506
  if (!path2.isAbsolute(filePath)) {
237
507
  const cwdPath = path2.resolve(process.cwd(), filePath);
238
- 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.";
508
+ suggestion = `Path resolved to: ${resolvedPath}
509
+ CWD: ${process.cwd()}
510
+ Project root: ${projectRoot}
511
+ Hint: Try path relative to project root, or relative to CWD (${cwdPath})
512
+ Try smart_grep first to locate the correct file.`;
239
513
  } else {
240
514
  suggestion = "File does not exist. Try smart_grep to locate it.";
241
515
  }
242
- return 'Error: File not found: "' + filePath + '".\n' + suggestion;
516
+ return compact ? `ERR:not found "${filePath}"` : `Error: File not found: "${filePath}".
517
+ ${suggestion}`;
243
518
  }
244
519
  const stat = fs2.statSync(resolvedPath);
245
520
  if (stat.size > MAX_FILE_SIZE) {
246
- return "Error: File too large (" + (stat.size / 1024 / 1024).toFixed(1) + "MB). Max 1MB.\nUse smart_grep to find specific content instead.";
521
+ return compact ? `ERR:too large ${filePath} (${(stat.size / 1024 / 1024).toFixed(1)}MB)` : `Error: File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 1MB.
522
+ Use smart_grep to find specific content instead.`;
247
523
  }
248
524
  try {
249
525
  const content = fs2.readFileSync(resolvedPath, "utf-8");
@@ -254,6 +530,26 @@ async function handleSmartFilePicker(params) {
254
530
  chunkStrategy,
255
531
  totalLines
256
532
  });
533
+ if (compact || outputMode === "raw") {
534
+ if (startLine !== void 0 || endLine !== void 0) {
535
+ const start = startLine ?? 1;
536
+ const end = endLine ?? totalLines;
537
+ return lines.slice(start - 1, end).join("\n");
538
+ }
539
+ if (totalLines <= CHUNK_THRESHOLD || chunkStrategy === "full") {
540
+ return content;
541
+ }
542
+ const tailStart = Math.max(totalLines - 50, 0);
543
+ return lines.slice(0, 50).join("\n") + `
544
+ ... ${totalLines - 100} lines hidden ...
545
+ ` + lines.slice(tailStart).join("\n");
546
+ }
547
+ if (startLine !== void 0 && endLine === void 0) {
548
+ const ctxStart = Math.max(0, startLine - 1 - contextLines);
549
+ const ctxEnd = Math.min(totalLines, startLine - 1 + contextLines);
550
+ const selectedLines = lines.slice(ctxStart, ctxEnd);
551
+ return formatJumpOutput(filePath, selectedLines, ctxStart + 1, startLine, totalLines);
552
+ }
257
553
  if (startLine !== void 0 || endLine !== void 0) {
258
554
  const start = startLine ?? 1;
259
555
  const end = endLine ?? totalLines;
@@ -278,30 +574,43 @@ async function handleSmartFilePicker(params) {
278
574
  );
279
575
  }
280
576
  } catch (err) {
281
- return 'Error reading file "' + filePath + '": ' + (err instanceof Error ? err.message : String(err));
577
+ return compact ? `ERR:reading "${filePath}"` : `Error reading file "${filePath}": ${err instanceof Error ? err.message : String(err)}`;
282
578
  }
283
579
  }
580
+ function formatJumpOutput(filePath, lines, startLine, targetLine, totalLines) {
581
+ const header = [
582
+ `File: ${filePath}`,
583
+ `${totalLines} total lines`,
584
+ `Jumped to line ${targetLine} (\xB1context)`,
585
+ ""
586
+ ].join("\n");
587
+ const body = lines.map((line, i) => {
588
+ const lineNum = startLine + i;
589
+ const marker = lineNum === targetLine ? ">" : " ";
590
+ return `${marker} ${String(lineNum).padStart(4, " ")} | ${line}`;
591
+ }).join("\n");
592
+ return header + "\n" + body;
593
+ }
284
594
  function formatOutput(filePath, lines, startLine, totalLines, truncated) {
285
595
  const header = [
286
- "File: " + filePath,
287
- totalLines + " total lines",
288
- truncated ? "Showing " + lines.length + " lines (file >" + CHUNK_THRESHOLD + " lines). Use startLine/endLine for a specific range." : "",
596
+ `File: ${filePath}`,
597
+ `${totalLines} total lines`,
598
+ truncated ? `Showing ${lines.length} lines (file >${CHUNK_THRESHOLD} lines). Use startLine/endLine for a specific range.` : "",
289
599
  ""
290
600
  ].filter(Boolean).join("\n");
291
601
  const body = lines.map((line, i) => {
292
602
  const lineNum = startLine + i;
293
- return String(lineNum).padStart(4, " ") + " | " + line;
603
+ return `${String(lineNum).padStart(4, " ")} | ${line}`;
294
604
  }).join("\n");
295
- const full = header + "\n" + body;
296
- return truncateToTokenLimit(full, 2e3);
605
+ return truncateToTokenLimit(`${header}
606
+ ${body}`, 2e3);
297
607
  }
298
608
  async function handleOutlineStrategy(filePath, lines, totalLines) {
299
609
  const importLines = [];
300
610
  const exportLines = [];
301
611
  for (let i = 0; i < lines.length; i++) {
302
612
  const line = lines[i].trim();
303
- if (line.startsWith("//") || line.startsWith("#") || line.startsWith("/*") || line.startsWith("*"))
304
- continue;
613
+ if (line.startsWith("//") || line.startsWith("#") || line.startsWith("/*") || line.startsWith("*")) continue;
305
614
  if (line.startsWith("import ") || line.startsWith("from ") || line.startsWith("require(")) {
306
615
  importLines.push(line);
307
616
  continue;
@@ -311,15 +620,15 @@ async function handleOutlineStrategy(filePath, lines, totalLines) {
311
620
  }
312
621
  }
313
622
  const result = [
314
- "File: " + filePath,
315
- totalLines + " total lines (OUTLINE MODE - signatures and imports only)",
623
+ `File: ${filePath}`,
624
+ `${totalLines} total lines (OUTLINE MODE - signatures and imports only)`,
316
625
  "",
317
626
  "Imports:",
318
- ...importLines.slice(0, 30).map((l) => " " + l.substring(0, 150)),
319
- importLines.length > 30 ? " ...and " + (importLines.length - 30) + " more imports" : "",
627
+ ...importLines.slice(0, 30).map((l) => ` ${l.substring(0, 150)}`),
628
+ importLines.length > 30 ? ` ...and ${importLines.length - 30} more imports` : "",
320
629
  "",
321
630
  "Exports & Declarations:",
322
- ...exportLines.map((e) => " [L" + e.line + "] " + e.text),
631
+ ...exportLines.map((e) => ` [L${e.line}] ${e.text}`),
323
632
  "",
324
633
  "Pass startLine/endLine to read a specific range.",
325
634
  'Or use chunkStrategy: "full" to read the entire file.'
@@ -337,7 +646,7 @@ async function handleSmartStrategy(filePath, lines, totalLines) {
337
646
  smartLines.push({ line: -1, text: "" });
338
647
  smartLines.push({
339
648
  line: -1,
340
- text: " ... " + (tailStart - headerEnd) + " lines hidden ..."
649
+ text: ` ... ${tailStart - headerEnd} lines hidden ...`
341
650
  });
342
651
  smartLines.push({ line: -1, text: "" });
343
652
  }
@@ -354,18 +663,18 @@ async function handleSmartStrategy(filePath, lines, totalLines) {
354
663
  smartLines.push({ line: i + 1, text: lines[i] });
355
664
  }
356
665
  const header = [
357
- "File: " + filePath,
358
- totalLines + " total lines (SMART MODE - header + signatures + tail)",
666
+ `File: ${filePath}`,
667
+ `${totalLines} total lines (SMART MODE - header + signatures + tail)`,
359
668
  "Pass startLine/endLine to read a specific range.",
360
669
  'Or use chunkStrategy: "full" to read the entire file.',
361
670
  ""
362
671
  ].join("\n");
363
672
  const body = smartLines.map((s) => {
364
673
  if (s.line === -1) return s.text;
365
- return String(s.line).padStart(4, " ") + " | " + s.text;
674
+ return `${String(s.line).padStart(4, " ")} | ${s.text}`;
366
675
  }).join("\n");
367
- const full = header + "\n" + body;
368
- return truncateToTokenLimit(full, 3e3);
676
+ return truncateToTokenLimit(`${header}
677
+ ${body}`, 3e3);
369
678
  }
370
679
  function findHeaderEnd(lines) {
371
680
  let lastImportLine = 0;
@@ -386,9 +695,7 @@ function extractKeyDeclarations(lines) {
386
695
  for (let i = 0; i < lines.length; i++) {
387
696
  const line = lines[i].trim();
388
697
  if (!line || line.startsWith("//") || line.startsWith("#")) continue;
389
- if (/^(export\s+)?(async\s+)?(function|class|interface|type|enum|const|let|var|def)\s/.test(
390
- line
391
- )) {
698
+ if (/^(export\s+)?(async\s+)?(function|class|interface|type|enum|const|let|var|def)\s/.test(line)) {
392
699
  decls.push({ line: i, text: line.substring(0, 150) });
393
700
  }
394
701
  }
@@ -453,11 +760,48 @@ var CircuitBreakerStore = class {
453
760
  var circuitBreaker = new CircuitBreakerStore();
454
761
 
455
762
  // src/tools/preciseDiffEditor.ts
763
+ function fastApplyEdit(content, edit) {
764
+ const { searchBlock, replaceBlock, allowMultiple = false } = edit;
765
+ const count = countOccurrences(content, searchBlock);
766
+ if (count === 0) {
767
+ return {
768
+ success: false,
769
+ matched: 0,
770
+ replaced: 0,
771
+ error: `DIFF_MISMATCH: searchBlock not found (fast mode - exact match only)`,
772
+ details: content
773
+ };
774
+ }
775
+ const newContent = allowMultiple ? replaceAll(content, searchBlock, replaceBlock) : content.replace(searchBlock, replaceBlock);
776
+ return {
777
+ success: true,
778
+ matched: count,
779
+ replaced: allowMultiple ? count : 1,
780
+ details: newContent
781
+ };
782
+ }
783
+ function formatFastEditResult(results, filePath) {
784
+ const successCount = results.filter((r) => r.success).length;
785
+ const failCount = results.filter((r) => !r.success).length;
786
+ if (failCount === 0) {
787
+ return `\u26A1 Fast edit: ${filePath} \u2014 ${successCount} edits applied.`;
788
+ }
789
+ const lines = [`\u26A1 Fast edit: ${filePath}`];
790
+ for (let i = 0; i < results.length; i++) {
791
+ const r = results[i];
792
+ if (r.success) {
793
+ lines.push(` [${i + 1}] \u2705 ${r.matched}x matched, ${r.replaced}x replaced`);
794
+ } else {
795
+ lines.push(` [${i + 1}] \u274C ${r.error}`);
796
+ }
797
+ }
798
+ return lines.join("\n");
799
+ }
456
800
  function generateEditId() {
457
801
  return `edit-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
458
802
  }
459
803
  async function handlePreciseDiffEditor(params) {
460
- const { filePath, edits, dryRun = false, action, scope } = params;
804
+ const { filePath, edits, safe = true, dryRun = false, action, scope } = params;
461
805
  if (action === "rollback") {
462
806
  return handleRollbackEdit({ filePath, version: params.version, scope: scope || "file", editId: params.editId });
463
807
  }
@@ -469,11 +813,13 @@ async function handlePreciseDiffEditor(params) {
469
813
  return `Error: ${validation.error.message}`;
470
814
  }
471
815
  const resolvedPath = validation.resolvedPath;
472
- const cbResult = circuitBreaker.check("precise_diff_editor", { filePath });
473
- if (!cbResult.allowed) {
474
- return `\u26A0\uFE0F ${cbResult.reason}
816
+ if (safe) {
817
+ const cbResult = circuitBreaker.check("precise_diff_editor", { filePath });
818
+ if (!cbResult.allowed) {
819
+ return `\u26A0\uFE0F ${cbResult.reason}
475
820
 
476
821
  Try reading the file first with smart_file_picker to verify current content.`;
822
+ }
477
823
  }
478
824
  if (!fs3.existsSync(resolvedPath)) {
479
825
  return `Error: File not found: "${filePath}".
@@ -485,6 +831,22 @@ Use batch_file_writer to create a new file.`;
485
831
  const results = [];
486
832
  for (let i = 0; i < edits.length; i++) {
487
833
  const edit = edits[i];
834
+ if (!safe) {
835
+ const result2 = fastApplyEdit(currentContent, edit);
836
+ if (result2.success) {
837
+ fs3.writeFileSync(resolvedPath, result2.details, "utf-8");
838
+ currentContent = result2.details;
839
+ sessionMemory.recordToolCall("precise_diff_editor", {
840
+ filePath,
841
+ editIndex: i,
842
+ fast: true,
843
+ success: true
844
+ });
845
+ sessionMemory.addModifiedFile(filePath);
846
+ }
847
+ results.push(result2);
848
+ continue;
849
+ }
488
850
  const result = applyEdit(currentContent, edit, resolvedPath, i, dryRun);
489
851
  if (result.success) {
490
852
  if (!dryRun) {
@@ -510,6 +872,9 @@ Use batch_file_writer to create a new file.`;
510
872
  if (dryRun) {
511
873
  return formatDryRunResult(results, filePath, originalContent);
512
874
  }
875
+ if (!safe) {
876
+ return formatFastEditResult(results, filePath);
877
+ }
513
878
  return formatDiffResult(results, filePath);
514
879
  } catch (err) {
515
880
  return `Error editing file "${filePath}": ${err instanceof Error ? err.message : String(err)}`;
@@ -889,10 +1254,10 @@ File "${filePath}" restored from backup: "${relBackupPath}".`;
889
1254
  }
890
1255
  async function handleCommitRollback(filePath, version) {
891
1256
  try {
892
- const { execSync: execSync10 } = await import("child_process");
1257
+ const { execSync: execSync13 } = await import("child_process");
893
1258
  const root = getProjectRoot();
894
1259
  if (version === "list") {
895
- const log = execSync10("git log --oneline -20", { cwd: root, encoding: "utf-8" });
1260
+ const log = execSync13("git log --oneline -20", { cwd: root, encoding: "utf-8" });
896
1261
  const lines = log.trim().split("\n").map((l) => ` ${l}`).join("\n");
897
1262
  return [
898
1263
  `\u{1F4CB} **Recent Commits:**`,
@@ -902,7 +1267,7 @@ async function handleCommitRollback(filePath, version) {
902
1267
  `\u{1F4A1} Use { action: "rollback", scope: "commit", version: <N> } to restore a specific commit.`
903
1268
  ].join("\n");
904
1269
  }
905
- const status = execSync10("git status --porcelain", { cwd: root, encoding: "utf-8" }).trim();
1270
+ const status = execSync13("git status --porcelain", { cwd: root, encoding: "utf-8" }).trim();
906
1271
  if (status) {
907
1272
  return [
908
1273
  `\u26A0\uFE0F **Uncommitted changes detected.**`,
@@ -923,10 +1288,10 @@ async function handleCommitRollback(filePath, version) {
923
1288
  target = "HEAD~1";
924
1289
  }
925
1290
  if (filePath) {
926
- execSync10(`git checkout ${target} -- "${filePath}"`, { cwd: root, encoding: "utf-8" });
1291
+ execSync13(`git checkout ${target} -- "${filePath}"`, { cwd: root, encoding: "utf-8" });
927
1292
  return `\u2705 **Commit Rollback** \u2014 File "${filePath}" restored from ${target}.`;
928
1293
  } else {
929
- execSync10(`git checkout ${target}`, { cwd: root, encoding: "utf-8" });
1294
+ execSync13(`git checkout ${target}`, { cwd: root, encoding: "utf-8" });
930
1295
  return `\u2705 **Commit Rollback** \u2014 Project restored to ${target}.`;
931
1296
  }
932
1297
  } catch (err) {
@@ -1202,13 +1567,419 @@ function formatBatchResult(results, totalRequested) {
1202
1567
  return lines.join("\n");
1203
1568
  }
1204
1569
 
1205
- // src/tools/safeTerminalExec.ts
1206
- import fs6 from "fs";
1570
+ // src/tools/kumaFind.ts
1571
+ import fg2 from "fast-glob";
1572
+ import path5 from "path";
1573
+ import { execSync as execSync2 } from "child_process";
1574
+ var IGNORE_PATTERNS2 = [
1575
+ "**/node_modules/**",
1576
+ "**/.next/**",
1577
+ "**/dist/**",
1578
+ "**/build/**",
1579
+ "**/.git/**",
1580
+ "**/.cache/**",
1581
+ "**/.kuma/backups/**",
1582
+ "**/coverage/**",
1583
+ "**/.nyc_output/**"
1584
+ ];
1585
+ var CACHE_TTL_MS2 = 3e4;
1586
+ var findCache = /* @__PURE__ */ new Map();
1587
+ var rgAvailable2 = null;
1588
+ function isRipgrepAvailable2() {
1589
+ if (process.env.KUMA_DISABLE_RG === "1" || process.env.KUMA_DISABLE_RG === "true") {
1590
+ return false;
1591
+ }
1592
+ if (rgAvailable2 !== null) return rgAvailable2;
1593
+ try {
1594
+ execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
1595
+ rgAvailable2 = true;
1596
+ } catch {
1597
+ rgAvailable2 = false;
1598
+ }
1599
+ return rgAvailable2;
1600
+ }
1601
+ function quotePath2(p) {
1602
+ return `"${p.replace(/"/g, '\\"')}"`;
1603
+ }
1604
+ async function handleKumaFind(params) {
1605
+ const {
1606
+ name = "*",
1607
+ path: pathPattern,
1608
+ extensions,
1609
+ targetFolder,
1610
+ maxResults = 30,
1611
+ type = "file",
1612
+ outputMode = "rich",
1613
+ compact = false
1614
+ } = params;
1615
+ const recursiveName = !pathPattern && !targetFolder && !name.startsWith("**/") ? `**/${name}` : name;
1616
+ const cacheKey = `${recursiveName}:${pathPattern ?? ""}:${targetFolder ?? ""}:${type}:${maxResults}:${extensions ? extensions.join(",") : ""}`;
1617
+ const cached = findCache.get(cacheKey);
1618
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS2) {
1619
+ return cached.results;
1620
+ }
1621
+ const projectRoot = getProjectRoot();
1622
+ let entries;
1623
+ if (isRipgrepAvailable2() && type !== "dir") {
1624
+ entries = await tryRipgrepFind({
1625
+ name: recursiveName,
1626
+ pathPattern,
1627
+ targetFolder,
1628
+ extensions,
1629
+ maxResults,
1630
+ projectRoot
1631
+ });
1632
+ } else {
1633
+ entries = await fastGlobFind({
1634
+ name: recursiveName,
1635
+ pathPattern,
1636
+ targetFolder,
1637
+ extensions,
1638
+ maxResults,
1639
+ type,
1640
+ projectRoot
1641
+ });
1642
+ }
1643
+ if (entries.length === 0) {
1644
+ const noResult = compact ? `0:${name}` : `\u{1F50D} No files found matching "${name}"${targetFolder ? ` in ${targetFolder}` : ""}.`;
1645
+ findCache.set(cacheKey, { results: noResult, timestamp: Date.now() });
1646
+ return noResult;
1647
+ }
1648
+ sessionMemory.recordToolCall("kuma_find", {
1649
+ name,
1650
+ matchCount: entries.length,
1651
+ engine: rgAvailable2 ? "ripgrep" : "fast-glob"
1652
+ });
1653
+ let result;
1654
+ if (outputMode === "json") {
1655
+ result = JSON.stringify(entries);
1656
+ } else if (compact || outputMode === "raw") {
1657
+ result = entries.join("\n");
1658
+ } else {
1659
+ const lines = [
1660
+ `\u{1F50D} Find: "${name}"`,
1661
+ `\u{1F4C1} ${entries.length} files found${targetFolder ? ` in ${targetFolder}` : ""}`,
1662
+ "",
1663
+ ...entries.map((e, i) => `[${i + 1}] \u{1F4C4} ${e}`)
1664
+ ];
1665
+ result = lines.join("\n");
1666
+ }
1667
+ findCache.set(cacheKey, { results: result, timestamp: Date.now() });
1668
+ return result;
1669
+ }
1670
+ async function tryRipgrepFind(opts) {
1671
+ const { name, pathPattern, targetFolder, extensions, maxResults, projectRoot } = opts;
1672
+ const args = ["rg", "--files", "--no-ignore-vcs", "--color", "never"];
1673
+ for (const ignore of IGNORE_PATTERNS2) {
1674
+ const glob = ignore.replace(/^\*\*\//, "");
1675
+ args.push("-g", `!${glob}`);
1676
+ }
1677
+ args.push("-g", name);
1678
+ if (pathPattern) {
1679
+ args.push("-g", pathPattern);
1680
+ }
1681
+ if (extensions && extensions.length > 0) {
1682
+ for (const ext of extensions) {
1683
+ const clean = ext.startsWith(".") ? ext : `.${ext}`;
1684
+ args.push("-g", `*${clean}`);
1685
+ }
1686
+ }
1687
+ const searchDir = targetFolder ? path5.join(projectRoot, targetFolder) : projectRoot;
1688
+ args.push(quotePath2(searchDir));
1689
+ try {
1690
+ const output = execSync2(args.join(" "), {
1691
+ cwd: projectRoot,
1692
+ maxBuffer: 5 * 1024 * 1024,
1693
+ timeout: 1e4,
1694
+ encoding: "utf-8",
1695
+ stdio: ["ignore", "pipe", "pipe"]
1696
+ });
1697
+ if (!output || output.trim().length === 0) return [];
1698
+ let files = output.trim().split("\n").map((f) => f.trim()).filter(Boolean);
1699
+ if (targetFolder) {
1700
+ const prefix = targetFolder.replace(/\/+$/, "") + "/";
1701
+ files = files.map((f) => prefix + f);
1702
+ }
1703
+ return files.slice(0, maxResults);
1704
+ } catch (err) {
1705
+ return [];
1706
+ }
1707
+ }
1708
+ async function fastGlobFind(opts) {
1709
+ const { name, pathPattern, targetFolder, extensions, maxResults, type, projectRoot } = opts;
1710
+ const globParts = [];
1711
+ if (pathPattern) {
1712
+ globParts.push(pathPattern.replace(/^\/+/, "").replace(/\/+$/, ""));
1713
+ } else if (targetFolder) {
1714
+ globParts.push(targetFolder.replace(/^\/+/, "").replace(/\/+$/, ""));
1715
+ }
1716
+ globParts.push(name);
1717
+ const searchPattern = globParts.join("/");
1718
+ try {
1719
+ let entries = await fg2(searchPattern, {
1720
+ cwd: projectRoot,
1721
+ ignore: IGNORE_PATTERNS2,
1722
+ onlyFiles: type === "file",
1723
+ onlyDirectories: type === "dir",
1724
+ absolute: false,
1725
+ deep: 20,
1726
+ dot: false
1727
+ });
1728
+ if (extensions && extensions.length > 0 && type !== "dir") {
1729
+ const normalizedExts = extensions.map(
1730
+ (e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
1731
+ );
1732
+ entries = entries.filter((entry) => {
1733
+ const ext = path5.extname(entry).toLowerCase();
1734
+ return normalizedExts.includes(ext);
1735
+ });
1736
+ }
1737
+ return entries.slice(0, maxResults);
1738
+ } catch {
1739
+ return [];
1740
+ }
1741
+ }
1742
+
1743
+ // src/tools/kumaStats.ts
1744
+ import fs5 from "fs";
1207
1745
  import path6 from "path";
1746
+ import { execSync as execSync3 } from "child_process";
1747
+ import fg3 from "fast-glob";
1748
+ var LANG_MAP = {
1749
+ ts: "TypeScript",
1750
+ tsx: "TypeScript React",
1751
+ js: "JavaScript",
1752
+ jsx: "JavaScript React",
1753
+ py: "Python",
1754
+ go: "Go",
1755
+ rs: "Rust",
1756
+ java: "Java",
1757
+ kt: "Kotlin",
1758
+ rb: "Ruby",
1759
+ php: "PHP",
1760
+ cs: "C#",
1761
+ swift: "Swift",
1762
+ c: "C",
1763
+ cpp: "C++",
1764
+ h: "C/C++ Header",
1765
+ css: "CSS",
1766
+ scss: "SCSS",
1767
+ less: "Less",
1768
+ html: "HTML",
1769
+ json: "JSON",
1770
+ yaml: "YAML",
1771
+ yml: "YAML",
1772
+ md: "Markdown",
1773
+ sql: "SQL",
1774
+ graphql: "GraphQL",
1775
+ prisma: "Prisma",
1776
+ toml: "TOML",
1777
+ xml: "XML",
1778
+ svg: "SVG",
1779
+ sh: "Shell",
1780
+ bash: "Shell",
1781
+ dockerfile: "Dockerfile"
1782
+ };
1783
+ async function handleKumaStats(params) {
1784
+ const {
1785
+ filePath,
1786
+ target = "project",
1787
+ outputMode = "rich",
1788
+ compact = false,
1789
+ extensions
1790
+ } = params;
1791
+ if (target === "file" && filePath) {
1792
+ return await statsSingleFile(filePath, { outputMode, compact });
1793
+ }
1794
+ if (target === "dir" && filePath) {
1795
+ return await statsDirectory(filePath, { outputMode, compact, extensions });
1796
+ }
1797
+ return await statsProject({ outputMode, compact, extensions });
1798
+ }
1799
+ async function statsSingleFile(filePath, opts) {
1800
+ const { outputMode, compact } = opts;
1801
+ const validation = validateFilePath(filePath);
1802
+ if (!validation.valid) {
1803
+ return compact ? `ERR:${filePath}` : `Error: ${validation.error.message}`;
1804
+ }
1805
+ const resolvedPath = validation.resolvedPath;
1806
+ if (!fs5.existsSync(resolvedPath)) {
1807
+ return compact ? `ERR:not found` : `Error: File not found: ${filePath}`;
1808
+ }
1809
+ const stat = fs5.statSync(resolvedPath);
1810
+ const isDir = stat.isDirectory();
1811
+ if (isDir) {
1812
+ return statsDirectory(filePath, { outputMode, compact });
1813
+ }
1814
+ const content = fs5.readFileSync(resolvedPath, "utf-8");
1815
+ const lines = content.split("\n");
1816
+ const ext = path6.extname(resolvedPath).replace(".", "").toLowerCase();
1817
+ const lang = LANG_MAP[ext] || ext.toUpperCase() || "Unknown";
1818
+ const totalBytes = stat.size;
1819
+ const nonEmpty = lines.filter((l) => l.trim().length > 0).length;
1820
+ if (outputMode === "json") {
1821
+ return JSON.stringify({
1822
+ file: filePath,
1823
+ lines: lines.length,
1824
+ nonEmpty,
1825
+ size: totalBytes,
1826
+ sizeHuman: formatBytes(totalBytes),
1827
+ language: lang
1828
+ });
1829
+ }
1830
+ if (compact || outputMode === "raw") {
1831
+ return `${filePath} ${lines.length} ${formatBytes(totalBytes)} ${lang}`;
1832
+ }
1833
+ return [
1834
+ `\u{1F4CA} Stats: ${filePath}`,
1835
+ `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
1836
+ ` Language: ${lang}`,
1837
+ ` Lines: ${lines.length}`,
1838
+ ` Non-empty: ${nonEmpty}`,
1839
+ ` Size: ${formatBytes(totalBytes)}`
1840
+ ].join("\n");
1841
+ }
1842
+ async function statsDirectory(dirPath, opts) {
1843
+ const { outputMode, compact, extensions } = opts;
1844
+ const projectRoot = getProjectRoot();
1845
+ const resolved = path6.isAbsolute(dirPath) ? dirPath : path6.join(projectRoot, dirPath);
1846
+ if (!fs5.existsSync(resolved)) {
1847
+ return compact ? `ERR:not found ${dirPath}` : `Error: Directory not found: ${dirPath}`;
1848
+ }
1849
+ let totalFiles = 0;
1850
+ let totalLines = 0;
1851
+ let totalBytes = 0;
1852
+ const langCounts = {};
1853
+ const entries = await fg3("**/*", {
1854
+ cwd: resolved,
1855
+ onlyFiles: true,
1856
+ ignore: [
1857
+ "**/node_modules/**",
1858
+ "**/.git/**",
1859
+ "**/dist/**",
1860
+ "**/build/**",
1861
+ "**/.next/**",
1862
+ "**/coverage/**",
1863
+ "**/*.map"
1864
+ ],
1865
+ deep: 10,
1866
+ dot: false
1867
+ });
1868
+ for (const entry of entries) {
1869
+ const ext = path6.extname(entry).replace(".", "").toLowerCase();
1870
+ if (extensions && extensions.length > 0) {
1871
+ const normalizedExts = extensions.map(
1872
+ (e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
1873
+ );
1874
+ if (!normalizedExts.includes(`.${ext}`)) continue;
1875
+ }
1876
+ totalFiles++;
1877
+ const fullPath = path6.join(resolved, entry);
1878
+ try {
1879
+ const stat = fs5.statSync(fullPath);
1880
+ if (stat.size > 1e6) {
1881
+ totalBytes += stat.size;
1882
+ continue;
1883
+ }
1884
+ const content = fs5.readFileSync(fullPath, "utf-8");
1885
+ const lineCount = content.split("\n").length;
1886
+ totalLines += lineCount;
1887
+ totalBytes += stat.size;
1888
+ const lang = LANG_MAP[ext] || ext.toUpperCase() || "Other";
1889
+ if (!langCounts[lang]) {
1890
+ langCounts[lang] = { files: 0, lines: 0 };
1891
+ }
1892
+ langCounts[lang].files++;
1893
+ langCounts[lang].lines += lineCount;
1894
+ } catch {
1895
+ try {
1896
+ const fallbackStat = fs5.statSync(fullPath);
1897
+ totalBytes += fallbackStat.size;
1898
+ } catch {
1899
+ totalBytes += 0;
1900
+ }
1901
+ }
1902
+ }
1903
+ sessionMemory.recordToolCall("kuma_stats", {
1904
+ target: dirPath,
1905
+ totalFiles,
1906
+ totalLines
1907
+ });
1908
+ if (outputMode === "json") {
1909
+ return JSON.stringify({
1910
+ directory: dirPath,
1911
+ totalFiles,
1912
+ totalLines,
1913
+ totalBytes,
1914
+ sizeHuman: formatBytes(totalBytes),
1915
+ languages: langCounts
1916
+ });
1917
+ }
1918
+ if (compact || outputMode === "raw") {
1919
+ const langs = Object.entries(langCounts).sort((a, b) => b[1].lines - a[1].lines).map(([name, counts]) => `${name} ${counts.files} ${counts.lines}`).join("\n");
1920
+ return `${dirPath} ${totalFiles} ${totalLines} ${formatBytes(totalBytes)}
1921
+ ${langs}`;
1922
+ }
1923
+ const langSummary = Object.entries(langCounts).sort((a, b) => b[1].lines - a[1].lines).slice(0, 10).map(
1924
+ ([name, counts]) => ` ${name.padEnd(20)} ${String(counts.files).padStart(5)} files ${String(counts.lines).padStart(8)} lines`
1925
+ ).join("\n");
1926
+ return [
1927
+ `\u{1F4CA} Directory Stats: ${dirPath}`,
1928
+ `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
1929
+ ` Total files: ${totalFiles}`,
1930
+ ` Total lines: ${totalLines}`,
1931
+ ` Total size: ${formatBytes(totalBytes)}`,
1932
+ ``,
1933
+ `Top Languages:`,
1934
+ langSummary
1935
+ ].join("\n");
1936
+ }
1937
+ async function statsProject(opts) {
1938
+ const projectRoot = getProjectRoot();
1939
+ const projectName = path6.basename(projectRoot);
1940
+ const projectStats = await statsDirectory(projectRoot, opts);
1941
+ let branch = "unknown";
1942
+ let lastCommit = "unknown";
1943
+ try {
1944
+ branch = execSync3("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown", {
1945
+ cwd: projectRoot,
1946
+ encoding: "utf-8",
1947
+ timeout: 2e3
1948
+ }).trim();
1949
+ lastCommit = execSync3('git log -1 --format="%h %s" 2>/dev/null || echo unknown', {
1950
+ cwd: projectRoot,
1951
+ encoding: "utf-8",
1952
+ timeout: 2e3
1953
+ }).trim();
1954
+ } catch {
1955
+ }
1956
+ if (opts.outputMode === "raw" || opts.compact) {
1957
+ return `Project: ${projectName} Branch: ${branch} ${lastCommit}
1958
+ ${projectStats}`;
1959
+ }
1960
+ return [
1961
+ `\u{1F4CA} Project: ${projectName}`,
1962
+ `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
1963
+ ` Branch: ${branch}`,
1964
+ ` Last commit: ${lastCommit}`,
1965
+ ``,
1966
+ projectStats
1967
+ ].join("\n");
1968
+ }
1969
+ function formatBytes(bytes) {
1970
+ if (bytes === 0) return "0 B";
1971
+ const units = ["B", "KB", "MB", "GB"];
1972
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
1973
+ return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
1974
+ }
1975
+
1976
+ // src/tools/safeTerminalExec.ts
1977
+ import fs7 from "fs";
1978
+ import path8 from "path";
1208
1979
 
1209
1980
  // src/utils/conventionsDetector.ts
1210
- import fs5 from "fs";
1211
- import path5 from "path";
1981
+ import fs6 from "fs";
1982
+ import path7 from "path";
1212
1983
  var cachedConventions = null;
1213
1984
  async function detectConventions(forceRescan = false) {
1214
1985
  if (cachedConventions && !forceRescan) {
@@ -1239,7 +2010,7 @@ async function detectConventions(forceRescan = false) {
1239
2010
  return conventions;
1240
2011
  }
1241
2012
  function detectFramework(root) {
1242
- const pkg = readJsonSafe(path5.join(root, "package.json"));
2013
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
1243
2014
  if (pkg) {
1244
2015
  const pkgDeps = pkg.dependencies ?? {};
1245
2016
  const pkgDevDeps = pkg.devDependencies ?? {};
@@ -1284,11 +2055,11 @@ function detectFramework(root) {
1284
2055
  function scanSubProjectFrameworks(root) {
1285
2056
  const frameworks = [];
1286
2057
  try {
1287
- const entries = fs5.readdirSync(root, { withFileTypes: true });
2058
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
1288
2059
  for (const entry of entries) {
1289
2060
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
1290
- const subDir = path5.join(root, entry.name);
1291
- const subPkg = readJsonSafe(path5.join(subDir, "package.json"));
2061
+ const subDir = path7.join(root, entry.name);
2062
+ const subPkg = readJsonSafe(path7.join(subDir, "package.json"));
1292
2063
  if (!subPkg) continue;
1293
2064
  const subDeps = { ...subPkg.dependencies ?? {}, ...subPkg.devDependencies ?? {} };
1294
2065
  if (subDeps.next) frameworks.push("Next.js");
@@ -1307,7 +2078,7 @@ function scanSubProjectFrameworks(root) {
1307
2078
  return frameworks;
1308
2079
  }
1309
2080
  function detectProjectType(root) {
1310
- const pkg = readJsonSafe(path5.join(root, "package.json"));
2081
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
1311
2082
  if (pkg) {
1312
2083
  const pkgDeps = pkg.dependencies ?? {};
1313
2084
  const pkgDevDeps = pkg.devDependencies ?? {};
@@ -1331,8 +2102,8 @@ function detectProjectType(root) {
1331
2102
  }
1332
2103
  function detectWorkspaces(root) {
1333
2104
  const results = [];
1334
- const pkg = readJsonSafe(path5.join(root, "package.json"));
1335
- const pnpmWorkspace = readYamlLite(path5.join(root, "pnpm-workspace.yaml"));
2105
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
2106
+ const pnpmWorkspace = readYamlLite(path7.join(root, "pnpm-workspace.yaml"));
1336
2107
  const patterns = /* @__PURE__ */ new Set();
1337
2108
  const pkgWorkspaces = pkg?.workspaces;
1338
2109
  if (Array.isArray(pkgWorkspaces)) {
@@ -1352,22 +2123,22 @@ function detectWorkspaces(root) {
1352
2123
  for (const pattern of patterns) {
1353
2124
  const match = pattern.match(/^([^*]+)\/\*$/);
1354
2125
  if (!match) continue;
1355
- const dir = path5.join(root, match[1]);
1356
- if (!fs5.existsSync(dir)) continue;
2126
+ const dir = path7.join(root, match[1]);
2127
+ if (!fs6.existsSync(dir)) continue;
1357
2128
  let entries = [];
1358
2129
  try {
1359
- entries = fs5.readdirSync(dir, { withFileTypes: true });
2130
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1360
2131
  } catch {
1361
2132
  continue;
1362
2133
  }
1363
2134
  for (const entry of entries) {
1364
2135
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
1365
- const pkgPath = path5.join(dir, entry.name, "package.json");
2136
+ const pkgPath = path7.join(dir, entry.name, "package.json");
1366
2137
  const subPkg = readJsonSafe(pkgPath);
1367
2138
  if (!subPkg) continue;
1368
- const workspacePath = path5.join(dir, entry.name);
2139
+ const workspacePath = path7.join(dir, entry.name);
1369
2140
  results.push({
1370
- path: path5.relative(root, workspacePath),
2141
+ path: path7.relative(root, workspacePath),
1371
2142
  name: subPkg.name ?? entry.name,
1372
2143
  framework: detectFramework(workspacePath),
1373
2144
  packageManager: detectPackageManagerForDir(workspacePath)
@@ -1383,8 +2154,8 @@ function detectWorkspaces(root) {
1383
2154
  }
1384
2155
  function readYamlLite(filePath) {
1385
2156
  try {
1386
- if (!fs5.existsSync(filePath)) return null;
1387
- const text = fs5.readFileSync(filePath, "utf-8");
2157
+ if (!fs6.existsSync(filePath)) return null;
2158
+ const text = fs6.readFileSync(filePath, "utf-8");
1388
2159
  const packages = [];
1389
2160
  let inPackages = false;
1390
2161
  for (const rawLine of text.split("\n")) {
@@ -1405,7 +2176,7 @@ function readYamlLite(filePath) {
1405
2176
  }
1406
2177
  }
1407
2178
  function detectTestRunner(root) {
1408
- const pkg = readJsonSafe(path5.join(root, "package.json"));
2179
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
1409
2180
  if (!pkg) return "unknown";
1410
2181
  const pkgDeps2 = pkg.dependencies ?? {};
1411
2182
  const pkgDevDeps2 = pkg.devDependencies ?? {};
@@ -1426,7 +2197,7 @@ function detectTestRunner(root) {
1426
2197
  return "unknown";
1427
2198
  }
1428
2199
  function detectStyling(root) {
1429
- const pkg = readJsonSafe(path5.join(root, "package.json"));
2200
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
1430
2201
  if (!pkg) return "unknown";
1431
2202
  const pkgDeps3 = pkg.dependencies ?? {};
1432
2203
  const pkgDevDeps3 = pkg.devDependencies ?? {};
@@ -1439,22 +2210,22 @@ function detectStyling(root) {
1439
2210
  if (deps.less) return "Less";
1440
2211
  if (deps["@vanilla-extract/css"]) return "Vanilla Extract";
1441
2212
  if (deps.cssmodules || hasCSSModules(root)) return "CSS Modules";
1442
- const srcDir = path5.join(root, "src");
1443
- if (fs5.existsSync(srcDir)) {
2213
+ const srcDir = path7.join(root, "src");
2214
+ if (fs6.existsSync(srcDir)) {
1444
2215
  const cssFiles = findFiles(srcDir, [".css", ".scss", ".less"]);
1445
2216
  if (cssFiles.length > 0) return "Plain CSS/SCSS";
1446
2217
  }
1447
2218
  return "unknown";
1448
2219
  }
1449
2220
  function detectImportAlias(root) {
1450
- const tsconfig = readJsonSafe(path5.join(root, "tsconfig.json"));
2221
+ const tsconfig = readJsonSafe(path7.join(root, "tsconfig.json"));
1451
2222
  const tsconfigPaths = tsconfig?.compilerOptions;
1452
2223
  if (tsconfigPaths?.paths) {
1453
2224
  const paths = tsconfigPaths.paths;
1454
2225
  const alias = Object.keys(paths)[0];
1455
2226
  if (alias) return alias.replace("/*", "");
1456
2227
  }
1457
- const jsconfig = readJsonSafe(path5.join(root, "jsconfig.json"));
2228
+ const jsconfig = readJsonSafe(path7.join(root, "jsconfig.json"));
1458
2229
  const jsconfigPaths = jsconfig?.compilerOptions;
1459
2230
  if (jsconfigPaths?.paths) {
1460
2231
  const paths = jsconfigPaths.paths;
@@ -1465,15 +2236,15 @@ function detectImportAlias(root) {
1465
2236
  }
1466
2237
  function detectLintRules(root) {
1467
2238
  const rules = [];
1468
- if (fs5.existsSync(path5.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
1469
- if (fs5.existsSync(path5.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
1470
- if (fs5.existsSync(path5.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
1471
- if (fs5.existsSync(path5.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
1472
- if (fs5.existsSync(path5.join(root, ".prettierrc"))) rules.push("Prettier");
1473
- if (fs5.existsSync(path5.join(root, ".prettierrc.json"))) rules.push("Prettier");
1474
- if (fs5.existsSync(path5.join(root, ".prettierrc.js"))) rules.push("Prettier");
1475
- if (fs5.existsSync(path5.join(root, ".stylelintrc"))) rules.push("StyleLint");
1476
- if (fs5.existsSync(path5.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
2239
+ if (fs6.existsSync(path7.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
2240
+ if (fs6.existsSync(path7.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
2241
+ if (fs6.existsSync(path7.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
2242
+ if (fs6.existsSync(path7.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
2243
+ if (fs6.existsSync(path7.join(root, ".prettierrc"))) rules.push("Prettier");
2244
+ if (fs6.existsSync(path7.join(root, ".prettierrc.json"))) rules.push("Prettier");
2245
+ if (fs6.existsSync(path7.join(root, ".prettierrc.js"))) rules.push("Prettier");
2246
+ if (fs6.existsSync(path7.join(root, ".stylelintrc"))) rules.push("StyleLint");
2247
+ if (fs6.existsSync(path7.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
1477
2248
  return rules;
1478
2249
  }
1479
2250
  function detectPackageManager() {
@@ -1482,26 +2253,26 @@ function detectPackageManager() {
1482
2253
  }
1483
2254
  function detectPackageManagerForDir(dir) {
1484
2255
  const root = getProjectRoot();
1485
- let currentDir = path5.resolve(dir);
2256
+ let currentDir = path7.resolve(dir);
1486
2257
  while (currentDir.startsWith(root)) {
1487
- if (fs5.existsSync(path5.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
1488
- if (fs5.existsSync(path5.join(currentDir, "yarn.lock"))) return "yarn";
1489
- if (fs5.existsSync(path5.join(currentDir, "package-lock.json"))) return "npm";
1490
- if (fs5.existsSync(path5.join(currentDir, "bun.lockb"))) return "bun";
2258
+ if (fs6.existsSync(path7.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
2259
+ if (fs6.existsSync(path7.join(currentDir, "yarn.lock"))) return "yarn";
2260
+ if (fs6.existsSync(path7.join(currentDir, "package-lock.json"))) return "npm";
2261
+ if (fs6.existsSync(path7.join(currentDir, "bun.lockb"))) return "bun";
1491
2262
  if (currentDir === root) break;
1492
- currentDir = path5.dirname(currentDir);
2263
+ currentDir = path7.dirname(currentDir);
1493
2264
  }
1494
2265
  return "npm";
1495
2266
  }
1496
2267
  function detectModuleSystem(root) {
1497
- const pkg = readJsonSafe(path5.join(root, "package.json"));
2268
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
1498
2269
  if (pkg?.type === "module") return "esm";
1499
- const srcDir = path5.join(root, "src");
1500
- if (fs5.existsSync(srcDir)) {
2270
+ const srcDir = path7.join(root, "src");
2271
+ if (fs6.existsSync(srcDir)) {
1501
2272
  const tsFiles = findFiles(srcDir, [".ts", ".tsx", ".js", ".jsx", ".mjs"]);
1502
2273
  for (const file of tsFiles.slice(0, 20)) {
1503
2274
  try {
1504
- const content = fs5.readFileSync(file, "utf-8");
2275
+ const content = fs6.readFileSync(file, "utf-8");
1505
2276
  if (content.includes("import ") || content.includes("export ")) return "esm";
1506
2277
  } catch {
1507
2278
  continue;
@@ -1511,22 +2282,22 @@ function detectModuleSystem(root) {
1511
2282
  return "cjs";
1512
2283
  }
1513
2284
  function detectLanguage(root) {
1514
- if (fs5.existsSync(path5.join(root, "go.mod"))) return "go";
1515
- if (fs5.existsSync(path5.join(root, "Cargo.toml"))) return "rust";
1516
- if (fs5.existsSync(path5.join(root, "composer.json"))) return "php";
1517
- if (fs5.existsSync(path5.join(root, "pyproject.toml")) || fs5.existsSync(path5.join(root, "requirements.txt")) || fs5.existsSync(path5.join(root, "setup.py"))) return "python";
1518
- if (fs5.existsSync(path5.join(root, "Gemfile"))) return "ruby";
1519
- if (fs5.existsSync(path5.join(root, "pom.xml"))) return "java";
1520
- if (fs5.existsSync(path5.join(root, "build.gradle")) || fs5.existsSync(path5.join(root, "build.gradle.kts"))) return "kotlin";
1521
- if (fs5.existsSync(path5.join(root, "Package.swift"))) return "swift";
2285
+ if (fs6.existsSync(path7.join(root, "go.mod"))) return "go";
2286
+ if (fs6.existsSync(path7.join(root, "Cargo.toml"))) return "rust";
2287
+ if (fs6.existsSync(path7.join(root, "composer.json"))) return "php";
2288
+ if (fs6.existsSync(path7.join(root, "pyproject.toml")) || fs6.existsSync(path7.join(root, "requirements.txt")) || fs6.existsSync(path7.join(root, "setup.py"))) return "python";
2289
+ if (fs6.existsSync(path7.join(root, "Gemfile"))) return "ruby";
2290
+ if (fs6.existsSync(path7.join(root, "pom.xml"))) return "java";
2291
+ if (fs6.existsSync(path7.join(root, "build.gradle")) || fs6.existsSync(path7.join(root, "build.gradle.kts"))) return "kotlin";
2292
+ if (fs6.existsSync(path7.join(root, "Package.swift"))) return "swift";
1522
2293
  try {
1523
- const entries = fs5.readdirSync(root);
2294
+ const entries = fs6.readdirSync(root);
1524
2295
  if (entries.some((e) => e.endsWith(".csproj"))) return "csharp";
1525
2296
  } catch {
1526
2297
  }
1527
- if (fs5.existsSync(path5.join(root, "tsconfig.json"))) return "typescript";
1528
- const srcDir = path5.join(root, "src");
1529
- if (fs5.existsSync(srcDir)) {
2298
+ if (fs6.existsSync(path7.join(root, "tsconfig.json"))) return "typescript";
2299
+ const srcDir = path7.join(root, "src");
2300
+ if (fs6.existsSync(srcDir)) {
1530
2301
  const tsFiles = findFiles(srcDir, [".ts", ".tsx"]);
1531
2302
  if (tsFiles.length > 0) return "typescript";
1532
2303
  const jsFiles = findFiles(srcDir, [".js", ".jsx"]);
@@ -1545,7 +2316,7 @@ function detectMultiLanguageFramework(root, language) {
1545
2316
  switch (language) {
1546
2317
  case "go": {
1547
2318
  try {
1548
- const goMod = fs5.readFileSync(path5.join(root, "go.mod"), "utf-8");
2319
+ const goMod = fs6.readFileSync(path7.join(root, "go.mod"), "utf-8");
1549
2320
  if (goMod.includes("github.com/gin-gonic/gin")) return "Gin";
1550
2321
  if (goMod.includes("github.com/labstack/echo")) return "Echo";
1551
2322
  if (goMod.includes("github.com/gofiber/fiber")) return "Fiber";
@@ -1559,7 +2330,7 @@ function detectMultiLanguageFramework(root, language) {
1559
2330
  }
1560
2331
  case "php": {
1561
2332
  try {
1562
- const composer = JSON.parse(fs5.readFileSync(path5.join(root, "composer.json"), "utf-8"));
2333
+ const composer = JSON.parse(fs6.readFileSync(path7.join(root, "composer.json"), "utf-8"));
1563
2334
  const req = composer.require ?? {};
1564
2335
  const reqDev = composer["require-dev"] ?? {};
1565
2336
  const deps = { ...req, ...reqDev };
@@ -1576,14 +2347,14 @@ function detectMultiLanguageFramework(root, language) {
1576
2347
  }
1577
2348
  case "python": {
1578
2349
  try {
1579
- if (fs5.existsSync(path5.join(root, "pyproject.toml"))) {
1580
- const content = fs5.readFileSync(path5.join(root, "pyproject.toml"), "utf-8");
2350
+ if (fs6.existsSync(path7.join(root, "pyproject.toml"))) {
2351
+ const content = fs6.readFileSync(path7.join(root, "pyproject.toml"), "utf-8");
1581
2352
  if (content.includes("django")) return "Django";
1582
2353
  if (content.includes("flask")) return "Flask";
1583
2354
  if (content.includes("fastapi")) return "FastAPI";
1584
2355
  }
1585
- if (fs5.existsSync(path5.join(root, "requirements.txt"))) {
1586
- const content = fs5.readFileSync(path5.join(root, "requirements.txt"), "utf-8");
2356
+ if (fs6.existsSync(path7.join(root, "requirements.txt"))) {
2357
+ const content = fs6.readFileSync(path7.join(root, "requirements.txt"), "utf-8");
1587
2358
  if (content.includes("django")) return "Django";
1588
2359
  if (content.includes("flask")) return "Flask";
1589
2360
  if (content.includes("fastapi")) return "FastAPI";
@@ -1596,7 +2367,7 @@ function detectMultiLanguageFramework(root, language) {
1596
2367
  }
1597
2368
  case "rust": {
1598
2369
  try {
1599
- const cargo = fs5.readFileSync(path5.join(root, "Cargo.toml"), "utf-8");
2370
+ const cargo = fs6.readFileSync(path7.join(root, "Cargo.toml"), "utf-8");
1600
2371
  if (cargo.includes("axum")) return "Axum";
1601
2372
  if (cargo.includes("actix-web")) return "Actix Web";
1602
2373
  if (cargo.includes("rocket")) return "Rocket";
@@ -1610,7 +2381,7 @@ function detectMultiLanguageFramework(root, language) {
1610
2381
  }
1611
2382
  case "java": {
1612
2383
  try {
1613
- const content = fs5.readFileSync(path5.join(root, "pom.xml"), "utf-8");
2384
+ const content = fs6.readFileSync(path7.join(root, "pom.xml"), "utf-8");
1614
2385
  if (content.includes("spring-boot")) return "Spring Boot";
1615
2386
  if (content.includes("quarkus")) return "Quarkus";
1616
2387
  if (content.includes("micronaut")) return "Micronaut";
@@ -1618,7 +2389,7 @@ function detectMultiLanguageFramework(root, language) {
1618
2389
  if (content.includes("helidon")) return "Helidon";
1619
2390
  } catch {
1620
2391
  try {
1621
- const gradle = fs5.readFileSync(path5.join(root, "build.gradle"), "utf-8");
2392
+ const gradle = fs6.readFileSync(path7.join(root, "build.gradle"), "utf-8");
1622
2393
  if (gradle.includes("spring")) return "Spring Boot";
1623
2394
  if (gradle.includes("quarkus")) return "Quarkus";
1624
2395
  } catch {
@@ -1628,7 +2399,7 @@ function detectMultiLanguageFramework(root, language) {
1628
2399
  }
1629
2400
  case "kotlin": {
1630
2401
  try {
1631
- const gradle = fs5.readFileSync(path5.join(root, "build.gradle.kts"), "utf-8");
2402
+ const gradle = fs6.readFileSync(path7.join(root, "build.gradle.kts"), "utf-8");
1632
2403
  if (gradle.includes("spring")) return "Spring Boot (Kotlin)";
1633
2404
  if (gradle.includes("ktor")) return "Ktor";
1634
2405
  } catch {
@@ -1637,7 +2408,7 @@ function detectMultiLanguageFramework(root, language) {
1637
2408
  }
1638
2409
  case "ruby": {
1639
2410
  try {
1640
- const gemfile = fs5.readFileSync(path5.join(root, "Gemfile"), "utf-8");
2411
+ const gemfile = fs6.readFileSync(path7.join(root, "Gemfile"), "utf-8");
1641
2412
  if (gemfile.includes("rails")) return "Ruby on Rails";
1642
2413
  if (gemfile.includes("sinatra")) return "Sinatra";
1643
2414
  if (gemfile.includes("hanami")) return "Hanami";
@@ -1648,10 +2419,10 @@ function detectMultiLanguageFramework(root, language) {
1648
2419
  }
1649
2420
  case "csharp": {
1650
2421
  try {
1651
- const entries = fs5.readdirSync(root);
2422
+ const entries = fs6.readdirSync(root);
1652
2423
  const csproj = entries.find((e) => e.endsWith(".csproj"));
1653
2424
  if (csproj) {
1654
- const content = fs5.readFileSync(path5.join(root, csproj), "utf-8");
2425
+ const content = fs6.readFileSync(path7.join(root, csproj), "utf-8");
1655
2426
  if (content.includes("Microsoft.AspNetCore")) return "ASP.NET Core";
1656
2427
  if (content.includes("Microsoft.Maui")) return ".NET MAUI";
1657
2428
  if (content.includes("Serilog")) return "Serilog";
@@ -1662,7 +2433,7 @@ function detectMultiLanguageFramework(root, language) {
1662
2433
  }
1663
2434
  case "swift": {
1664
2435
  try {
1665
- const content = fs5.readFileSync(path5.join(root, "Package.swift"), "utf-8");
2436
+ const content = fs6.readFileSync(path7.join(root, "Package.swift"), "utf-8");
1666
2437
  if (content.includes("vapor")) return "Vapor";
1667
2438
  if (content.includes("swift-nio")) return "SwiftNIO";
1668
2439
  if (content.includes("perfect")) return "Perfect";
@@ -1677,7 +2448,7 @@ function detectMultiLanguageFramework(root, language) {
1677
2448
  }
1678
2449
  function detectFeatures(root) {
1679
2450
  const features = [];
1680
- const pkg = readJsonSafe(path5.join(root, "package.json"));
2451
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
1681
2452
  if (!pkg) return features;
1682
2453
  const pkgDeps4 = pkg.dependencies ?? {};
1683
2454
  const pkgDevDeps4 = pkg.devDependencies ?? {};
@@ -1701,25 +2472,25 @@ function detectFeatures(root) {
1701
2472
  }
1702
2473
  function readJsonSafe(filePath) {
1703
2474
  try {
1704
- if (fs5.existsSync(filePath)) {
1705
- return JSON.parse(fs5.readFileSync(filePath, "utf-8"));
2475
+ if (fs6.existsSync(filePath)) {
2476
+ return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
1706
2477
  }
1707
2478
  } catch {
1708
2479
  }
1709
2480
  return null;
1710
2481
  }
1711
2482
  function hasCSSModules(root) {
1712
- const srcDir = path5.join(root, "src");
1713
- if (!fs5.existsSync(srcDir)) return false;
2483
+ const srcDir = path7.join(root, "src");
2484
+ if (!fs6.existsSync(srcDir)) return false;
1714
2485
  const files = findFiles(srcDir, [".module.css", ".module.scss", ".module.less"]);
1715
2486
  return files.length > 0;
1716
2487
  }
1717
2488
  function findFiles(dir, extensions) {
1718
2489
  const results = [];
1719
2490
  try {
1720
- const entries = fs5.readdirSync(dir, { withFileTypes: true });
2491
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
1721
2492
  for (const entry of entries) {
1722
- const fullPath = path5.join(dir, entry.name);
2493
+ const fullPath = path7.join(dir, entry.name);
1723
2494
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
1724
2495
  results.push(...findFiles(fullPath, extensions));
1725
2496
  } else if (entry.isFile()) {
@@ -1910,7 +2681,7 @@ async function handleSafeTerminalExec(params) {
1910
2681
  const workspaces = conventions?.workspaces;
1911
2682
  const matched = workspaces?.find((w) => w.name === workspace || w.path === workspace);
1912
2683
  if (matched) {
1913
- workingDir = path6.resolve(projectRoot, matched.path);
2684
+ workingDir = path8.resolve(projectRoot, matched.path);
1914
2685
  resolvedFrom = `workspace "${matched.name}" \u2192 ${matched.path}`;
1915
2686
  } else {
1916
2687
  return `\u26A0\uFE0F Workspace "${workspace}" not found. Run project_conventions first to detect workspaces, or use 'cwd' parameter with a direct path.
@@ -1918,13 +2689,13 @@ async function handleSafeTerminalExec(params) {
1918
2689
  Available workspaces: ${workspaces?.map((w) => `"${w.name}" (${w.path})`).join(", ") || "none detected"}`;
1919
2690
  }
1920
2691
  } else if (inputCwd) {
1921
- const resolved = path6.resolve(projectRoot, inputCwd);
1922
- const normalizedResolved = path6.normalize(resolved).toLowerCase();
1923
- const normalizedRoot = path6.normalize(projectRoot).toLowerCase();
2692
+ const resolved = path8.resolve(projectRoot, inputCwd);
2693
+ const normalizedResolved = path8.normalize(resolved).toLowerCase();
2694
+ const normalizedRoot = path8.normalize(projectRoot).toLowerCase();
1924
2695
  if (!normalizedResolved.startsWith(normalizedRoot)) {
1925
2696
  return `\u{1F6AB} BLOCKED: Path "${inputCwd}" resolves outside project root "${projectRoot}".`;
1926
2697
  }
1927
- if (!fs6.existsSync(resolved)) {
2698
+ if (!fs7.existsSync(resolved)) {
1928
2699
  return `\u26A0\uFE0F Directory "${inputCwd}" does not exist at "${resolved}".`;
1929
2700
  }
1930
2701
  workingDir = resolved;
@@ -2010,9 +2781,9 @@ function formatTimeoutResult(command, timeout) {
2010
2781
  }
2011
2782
 
2012
2783
  // src/agents/codeReviewer.ts
2013
- import fs7 from "fs";
2014
- import path7 from "path";
2015
- import { execSync } from "child_process";
2784
+ import fs8 from "fs";
2785
+ import path9 from "path";
2786
+ import { execSync as execSync4 } from "child_process";
2016
2787
  var CODE_EXTENSIONS = [
2017
2788
  ".ts",
2018
2789
  ".tsx",
@@ -2042,7 +2813,7 @@ var CONFIG_EXTENSIONS = [
2042
2813
  function getGitChangedFiles() {
2043
2814
  try {
2044
2815
  const root = getProjectRoot();
2045
- const stdout = execSync("git status --porcelain", {
2816
+ const stdout = execSync4("git status --porcelain", {
2046
2817
  cwd: root,
2047
2818
  encoding: "utf-8"
2048
2819
  });
@@ -2055,7 +2826,7 @@ function getGitChangedFiles() {
2055
2826
  if (filePath.startsWith('"') && filePath.endsWith('"')) {
2056
2827
  filePath = filePath.substring(1, filePath.length - 1);
2057
2828
  }
2058
- const ext = path7.extname(filePath).toLowerCase();
2829
+ const ext = path9.extname(filePath).toLowerCase();
2059
2830
  if (CODE_EXTENSIONS.includes(ext)) files.push(filePath);
2060
2831
  }
2061
2832
  return files.slice(0, 10);
@@ -2092,7 +2863,7 @@ Pass an explicit 'files' array to review specific files.`;
2092
2863
  continue;
2093
2864
  }
2094
2865
  const resolvedPath = validation.resolvedPath;
2095
- if (!fs7.existsSync(resolvedPath)) {
2866
+ if (!fs8.existsSync(resolvedPath)) {
2096
2867
  allIssues.push({
2097
2868
  file: filePath,
2098
2869
  line: 0,
@@ -2104,7 +2875,7 @@ Pass an explicit 'files' array to review specific files.`;
2104
2875
  continue;
2105
2876
  }
2106
2877
  try {
2107
- const content = fs7.readFileSync(resolvedPath, "utf-8");
2878
+ const content = fs8.readFileSync(resolvedPath, "utf-8");
2108
2879
  filesReviewed++;
2109
2880
  checkGeneral(filePath, content, allIssues);
2110
2881
  switch (focus) {
@@ -2202,12 +2973,12 @@ function isTestFile(filePath) {
2202
2973
  return /\.(test|spec)\.[a-z]+$/i.test(filePath) || /__tests__/.test(filePath);
2203
2974
  }
2204
2975
  function isTsLike(filePath) {
2205
- const ext = path7.extname(filePath).toLowerCase();
2976
+ const ext = path9.extname(filePath).toLowerCase();
2206
2977
  return ext === ".ts" || ext === ".tsx";
2207
2978
  }
2208
2979
  function checkGeneral(filePath, content, issues) {
2209
2980
  const lines = content.split("\n");
2210
- const ext = path7.extname(filePath).toLowerCase();
2981
+ const ext = path9.extname(filePath).toLowerCase();
2211
2982
  if (content.length > 0 && !content.endsWith("\n")) {
2212
2983
  issues.push({
2213
2984
  file: filePath,
@@ -2409,7 +3180,7 @@ function stripJsonComments(text) {
2409
3180
  return cleaned.join("\n");
2410
3181
  }
2411
3182
  function checkConfigFile(filePath, content, issues) {
2412
- const ext = path7.extname(filePath).toLowerCase();
3183
+ const ext = path9.extname(filePath).toLowerCase();
2413
3184
  const lines = content.split("\n");
2414
3185
  if (ext === ".json" || ext === ".jsonc") {
2415
3186
  checkJson(filePath, content, lines, issues);
@@ -2420,7 +3191,7 @@ function checkConfigFile(filePath, content, issues) {
2420
3191
  }
2421
3192
  }
2422
3193
  function checkJson(filePath, content, lines, issues) {
2423
- const ext = path7.extname(filePath).toLowerCase();
3194
+ const ext = path9.extname(filePath).toLowerCase();
2424
3195
  const isJsonc = ext === ".jsonc";
2425
3196
  const contentToParse = isJsonc ? stripJsonComments(content) : content;
2426
3197
  try {
@@ -3146,10 +3917,10 @@ function formatConventionsOutput(conventions) {
3146
3917
  }
3147
3918
 
3148
3919
  // src/utils/gitUtils.ts
3149
- import { execSync as execSync2 } from "child_process";
3920
+ import { execSync as execSync5 } from "child_process";
3150
3921
  function runGitCommand(command) {
3151
3922
  const root = getProjectRoot();
3152
- return execSync2(command, {
3923
+ return execSync5(command, {
3153
3924
  cwd: root,
3154
3925
  encoding: "utf-8",
3155
3926
  maxBuffer: 2 * 1024 * 1024
@@ -3277,8 +4048,8 @@ async function handleGitDiff(params) {
3277
4048
  }
3278
4049
 
3279
4050
  // src/tools/projectStructure.ts
3280
- import fs8 from "fs";
3281
- import path8 from "path";
4051
+ import fs9 from "fs";
4052
+ import path10 from "path";
3282
4053
  var DEFAULT_IGNORE = [
3283
4054
  "node_modules",
3284
4055
  ".git",
@@ -3296,7 +4067,7 @@ var DEFAULT_IGNORE = [
3296
4067
  "*.log"
3297
4068
  ];
3298
4069
  var treeCache = /* @__PURE__ */ new Map();
3299
- var CACHE_TTL_MS2 = 6e4;
4070
+ var CACHE_TTL_MS3 = 6e4;
3300
4071
  async function handleProjectStructure(params) {
3301
4072
  const {
3302
4073
  depth = 3,
@@ -3308,14 +4079,14 @@ async function handleProjectStructure(params) {
3308
4079
  const clampedDepth = Math.max(1, Math.min(depth, 6));
3309
4080
  const cacheKey = `${root}:${clampedDepth}:${folderOnly}:${includePattern ?? ""}:${excludePattern ?? ""}`;
3310
4081
  const cached = treeCache.get(cacheKey);
3311
- if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS2) {
4082
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS3) {
3312
4083
  sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly, cached: true });
3313
4084
  return cached.result;
3314
4085
  }
3315
4086
  sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
3316
4087
  try {
3317
4088
  const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
3318
- const projectName = path8.basename(root);
4089
+ const projectName = path10.basename(root);
3319
4090
  const lines = [
3320
4091
  "[Project Structure] - " + projectName,
3321
4092
  "Depth: " + clampedDepth + " | " + (folderOnly ? "Folders only" : "Files and folders"),
@@ -3338,7 +4109,7 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3338
4109
  const lines = [];
3339
4110
  let entries = [];
3340
4111
  try {
3341
- entries = fs8.readdirSync(currentDir, { withFileTypes: true });
4112
+ entries = fs9.readdirSync(currentDir, { withFileTypes: true });
3342
4113
  } catch {
3343
4114
  return lines;
3344
4115
  }
@@ -3347,12 +4118,12 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3347
4118
  if (!a.isDirectory() && b.isDirectory()) return 1;
3348
4119
  return a.name.localeCompare(b.name);
3349
4120
  });
3350
- const relativeDir = path8.relative(root, currentDir) || ".";
4121
+ const relativeDir = path10.relative(root, currentDir) || ".";
3351
4122
  const prefix = getPrefix(currentDepth);
3352
4123
  for (const entry of entries) {
3353
4124
  if (shouldIgnore(entry.name, relativeDir, root)) continue;
3354
- const fullPath = path8.join(currentDir, entry.name);
3355
- const relativePath = path8.relative(root, fullPath);
4125
+ const fullPath = path10.join(currentDir, entry.name);
4126
+ const relativePath = path10.relative(root, fullPath);
3356
4127
  if (includePattern && !entry.name.includes(includePattern) && !relativePath.includes(includePattern)) {
3357
4128
  if (!entry.isDirectory()) continue;
3358
4129
  }
@@ -3363,11 +4134,11 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
3363
4134
  lines.push(prefix + "[D] " + entry.name + "/");
3364
4135
  let subEntries = [];
3365
4136
  try {
3366
- subEntries = fs8.readdirSync(fullPath, { withFileTypes: true });
4137
+ subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
3367
4138
  } catch {
3368
4139
  }
3369
4140
  const hasVisibleContent = subEntries.some(
3370
- (e) => !shouldIgnore(e.name, path8.relative(root, fullPath), root)
4141
+ (e) => !shouldIgnore(e.name, path10.relative(root, fullPath), root)
3371
4142
  );
3372
4143
  if (hasVisibleContent) {
3373
4144
  const subLines = buildTree(root, fullPath, maxDepth, currentDepth + 1, folderOnly, includePattern, excludePattern);
@@ -3398,7 +4169,7 @@ function getPrefix(depth) {
3398
4169
  }
3399
4170
  function getFileSize(fullPath) {
3400
4171
  try {
3401
- const stat = fs8.statSync(fullPath);
4172
+ const stat = fs9.statSync(fullPath);
3402
4173
  if (stat.size < 1024) return stat.size + "B";
3403
4174
  if (stat.size < 1024 * 1024) return (stat.size / 1024).toFixed(0) + "KB";
3404
4175
  const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
@@ -3409,8 +4180,8 @@ function getFileSize(fullPath) {
3409
4180
  }
3410
4181
 
3411
4182
  // src/tools/staticAnalysis.ts
3412
- import fs9 from "fs";
3413
- import path9 from "path";
4183
+ import fs10 from "fs";
4184
+ import path11 from "path";
3414
4185
  async function handleStaticAnalysis(params) {
3415
4186
  const { tool = "all", files, autoFix = false, timeout = 60 } = params;
3416
4187
  const root = getProjectRoot();
@@ -3503,11 +4274,11 @@ function detectAvailableTools(root) {
3503
4274
  "eslint.config.js",
3504
4275
  "eslint.config.mjs"
3505
4276
  ];
3506
- const hasEslintConfig = eslintConfigs.some((cfg) => fs9.existsSync(path9.join(root, cfg)));
4277
+ const hasEslintConfig = eslintConfigs.some((cfg) => fs10.existsSync(path11.join(root, cfg)));
3507
4278
  if (hasEslintConfig && depNames.has("eslint")) {
3508
4279
  tools.push("eslint");
3509
4280
  }
3510
- if (fs9.existsSync(path9.join(root, "tsconfig.json")) && depNames.has("typescript")) {
4281
+ if (fs10.existsSync(path11.join(root, "tsconfig.json")) && depNames.has("typescript")) {
3511
4282
  tools.push("tsc");
3512
4283
  }
3513
4284
  const prettierConfigs = [
@@ -3518,20 +4289,20 @@ function detectAvailableTools(root) {
3518
4289
  ".prettierrc.toml",
3519
4290
  "prettier.config.js"
3520
4291
  ];
3521
- const hasPrettierConfig = prettierConfigs.some((cfg) => fs9.existsSync(path9.join(root, cfg)));
4292
+ const hasPrettierConfig = prettierConfigs.some((cfg) => fs10.existsSync(path11.join(root, cfg)));
3522
4293
  if (hasPrettierConfig && depNames.has("prettier")) {
3523
4294
  tools.push("prettier");
3524
4295
  }
3525
- if (fs9.existsSync(path9.join(root, "ruff.toml")) || fs9.existsSync(path9.join(root, ".ruff.toml"))) {
4296
+ if (fs10.existsSync(path11.join(root, "ruff.toml")) || fs10.existsSync(path11.join(root, ".ruff.toml"))) {
3526
4297
  tools.push("ruff");
3527
4298
  }
3528
4299
  return tools;
3529
4300
  }
3530
4301
  function readPackageJson(root) {
3531
4302
  try {
3532
- const pkgPath = path9.join(root, "package.json");
3533
- if (fs9.existsSync(pkgPath)) {
3534
- return JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
4303
+ const pkgPath = path11.join(root, "package.json");
4304
+ if (fs10.existsSync(pkgPath)) {
4305
+ return JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
3535
4306
  }
3536
4307
  } catch {
3537
4308
  }
@@ -3587,18 +4358,18 @@ function buildToolCommand(tool, files, autoFix) {
3587
4358
  }
3588
4359
  function detectPackageManagerPrefix() {
3589
4360
  const root = getProjectRoot();
3590
- if (fs9.existsSync(path9.join(root, "pnpm-lock.yaml"))) return "pnpm ";
3591
- if (fs9.existsSync(path9.join(root, "yarn.lock"))) return "yarn ";
4361
+ if (fs10.existsSync(path11.join(root, "pnpm-lock.yaml"))) return "pnpm ";
4362
+ if (fs10.existsSync(path11.join(root, "yarn.lock"))) return "yarn ";
3592
4363
  return "npx --no-install ";
3593
4364
  }
3594
4365
  function findBinary(root, binName) {
3595
4366
  const candidates = [
3596
- path9.join(root, "node_modules", ".bin", binName),
3597
- path9.join(root, "..", "node_modules", ".bin", binName)
4367
+ path11.join(root, "node_modules", ".bin", binName),
4368
+ path11.join(root, "..", "node_modules", ".bin", binName)
3598
4369
  ];
3599
4370
  for (const candidate of candidates) {
3600
4371
  try {
3601
- if (fs9.existsSync(candidate)) {
4372
+ if (fs10.existsSync(candidate)) {
3602
4373
  return candidate;
3603
4374
  }
3604
4375
  } catch {
@@ -3622,7 +4393,7 @@ function parseToolOutput(tool, stdout, stderr, projectRoot) {
3622
4393
  }
3623
4394
  function resolveToolPath(filePath, projectRoot) {
3624
4395
  const trimmed = filePath.trim();
3625
- return path9.isAbsolute(trimmed) ? path9.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
4396
+ return path11.isAbsolute(trimmed) ? path11.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
3626
4397
  }
3627
4398
  function parseEslintOutput(output, projectRoot) {
3628
4399
  const issues = [];
@@ -3805,7 +4576,7 @@ function formatToolNotAvailable(requested, available) {
3805
4576
  }
3806
4577
 
3807
4578
  // src/utils/kumaShared.ts
3808
- import { execSync as execSync3 } from "child_process";
4579
+ import { execSync as execSync6 } from "child_process";
3809
4580
  function getSessionStats(inputGoal) {
3810
4581
  const summary = sessionMemory.getSummary();
3811
4582
  const goal = inputGoal || summary.currentGoal || "";
@@ -3827,7 +4598,7 @@ function getSessionStats(inputGoal) {
3827
4598
  function getGitDiffStat(timeout = 3e3) {
3828
4599
  try {
3829
4600
  const root = getProjectRoot();
3830
- return execSync3("git diff --stat", {
4601
+ return execSync6("git diff --stat", {
3831
4602
  cwd: root,
3832
4603
  encoding: "utf-8",
3833
4604
  timeout
@@ -4100,8 +4871,8 @@ function formatAnalytics(a) {
4100
4871
  }
4101
4872
 
4102
4873
  // src/engine/kumaHealthDashboard.ts
4103
- import fs10 from "fs";
4104
- import path10 from "path";
4874
+ import fs11 from "fs";
4875
+ import path12 from "path";
4105
4876
  function computeHealthDashboard() {
4106
4877
  const analytics = computeAnalytics();
4107
4878
  const root = getProjectRoot();
@@ -4109,27 +4880,27 @@ function computeHealthDashboard() {
4109
4880
  const gitChanges = gitStat ? gitStat.split("\n").filter((l) => l.includes("|")).length : 0;
4110
4881
  let backupCount = 0;
4111
4882
  try {
4112
- const bd = path10.join(root, ".kuma", "backups");
4113
- if (fs10.existsSync(bd)) backupCount = fs10.readdirSync(bd).filter((d) => /^\d+$/.test(d)).length;
4883
+ const bd = path12.join(root, ".kuma", "backups");
4884
+ if (fs11.existsSync(bd)) backupCount = fs11.readdirSync(bd).filter((d) => /^\d+$/.test(d)).length;
4114
4885
  } catch {
4115
4886
  }
4116
4887
  const dirFileCounts = /* @__PURE__ */ new Map();
4117
4888
  try {
4118
4889
  const walk = (dir, base) => {
4119
4890
  try {
4120
- for (const e of fs10.readdirSync(dir, { withFileTypes: true })) {
4891
+ for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
4121
4892
  if (e.name.startsWith(".") || e.name === "node_modules") continue;
4122
- const full = path10.join(dir, e.name);
4893
+ const full = path12.join(dir, e.name);
4123
4894
  if (e.isDirectory()) walk(full, base);
4124
4895
  else if (e.isFile() && /\.(ts|tsx|js|jsx)$/.test(e.name)) {
4125
- const rel = path10.dirname(path10.relative(base, full));
4896
+ const rel = path12.dirname(path12.relative(base, full));
4126
4897
  dirFileCounts.set(rel, (dirFileCounts.get(rel) || 0) + 1);
4127
4898
  }
4128
4899
  }
4129
4900
  } catch {
4130
4901
  }
4131
4902
  };
4132
- walk(path10.join(root, "src"), root);
4903
+ walk(path12.join(root, "src"), root);
4133
4904
  } catch {
4134
4905
  }
4135
4906
  const dirMap = /* @__PURE__ */ new Map();
@@ -4137,7 +4908,7 @@ function computeHealthDashboard() {
4137
4908
  if (call.toolName === "precise_diff_editor") {
4138
4909
  const fp = call.params?.filePath;
4139
4910
  if (fp) {
4140
- const dir = path10.dirname(fp) || ".";
4911
+ const dir = path12.dirname(fp) || ".";
4141
4912
  const e = dirMap.get(dir) || { edits: 0, failures: 0 };
4142
4913
  e.edits++;
4143
4914
  if (call.params?.success === false) e.failures++;
@@ -4217,9 +4988,9 @@ function formatHealthDashboard(r) {
4217
4988
  }
4218
4989
 
4219
4990
  // src/guards/antiPatternDetector.ts
4220
- import fs11 from "fs";
4221
- import path11 from "path";
4222
- import { execSync as execSync4 } from "child_process";
4991
+ import fs12 from "fs";
4992
+ import path13 from "path";
4993
+ import { execSync as execSync7 } from "child_process";
4223
4994
  var SCRIPT_PATCH_PATTERNS = [
4224
4995
  "writeFileSync",
4225
4996
  "writeFile",
@@ -4245,20 +5016,20 @@ function findPatchScripts(projectRoot) {
4245
5016
  const warnings = [];
4246
5017
  const recentFiles = scanRecentFiles(projectRoot);
4247
5018
  for (const file of recentFiles) {
4248
- const ext = path11.extname(file).toLowerCase();
5019
+ const ext = path13.extname(file).toLowerCase();
4249
5020
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
4250
- const relativePath = path11.relative(projectRoot, file);
5021
+ const relativePath = path13.relative(projectRoot, file);
4251
5022
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
4252
5023
  continue;
4253
5024
  }
4254
5025
  try {
4255
- const content = fs11.readFileSync(file, "utf-8").toLowerCase();
5026
+ const content = fs12.readFileSync(file, "utf-8").toLowerCase();
4256
5027
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
4257
5028
  if (matchedPattern) {
4258
5029
  warnings.push({
4259
5030
  severity: "high",
4260
5031
  pattern: "script-patching",
4261
- message: `Created script file that modifies other files: ${path11.basename(file)}`,
5032
+ message: `Created script file that modifies other files: ${path13.basename(file)}`,
4262
5033
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
4263
5034
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
4264
5035
  filePath: relativePath
@@ -4292,15 +5063,15 @@ function detectBashGrepUsage() {
4292
5063
  function scanRecentFiles(projectRoot) {
4293
5064
  const recent = [];
4294
5065
  try {
4295
- const entries = fs11.readdirSync(projectRoot, { withFileTypes: true });
5066
+ const entries = fs12.readdirSync(projectRoot, { withFileTypes: true });
4296
5067
  const now = Date.now();
4297
5068
  const recentThreshold = 30 * 60 * 1e3;
4298
5069
  for (const entry of entries) {
4299
5070
  if (!entry.isFile()) continue;
4300
5071
  try {
4301
- const stat = fs11.statSync(path11.join(projectRoot, entry.name));
5072
+ const stat = fs12.statSync(path13.join(projectRoot, entry.name));
4302
5073
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
4303
- recent.push(path11.join(projectRoot, entry.name));
5074
+ recent.push(path13.join(projectRoot, entry.name));
4304
5075
  }
4305
5076
  } catch {
4306
5077
  continue;
@@ -4313,7 +5084,7 @@ function scanRecentFiles(projectRoot) {
4313
5084
  function detectGitPatchScripts(projectRoot) {
4314
5085
  const warnings = [];
4315
5086
  try {
4316
- const statusStdout = execSync4("git status --porcelain", {
5087
+ const statusStdout = execSync7("git status --porcelain", {
4317
5088
  cwd: projectRoot,
4318
5089
  encoding: "utf-8",
4319
5090
  timeout: 3e3
@@ -4324,14 +5095,14 @@ function detectGitPatchScripts(projectRoot) {
4324
5095
  const prefix = line.substring(0, 2);
4325
5096
  if (prefix !== "??" && prefix !== "A ") continue;
4326
5097
  const file = line.substring(3).trim();
4327
- const ext = path11.extname(file).toLowerCase();
5098
+ const ext = path13.extname(file).toLowerCase();
4328
5099
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
4329
5100
  const isRootLevel = !file.includes("/");
4330
5101
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
4331
5102
  if (!isRootLevel && !isScriptsDir) continue;
4332
- const fullPath = path11.join(projectRoot, file);
4333
- if (fs11.existsSync(fullPath)) {
4334
- const content = fs11.readFileSync(fullPath, "utf-8").toLowerCase();
5103
+ const fullPath = path13.join(projectRoot, file);
5104
+ if (fs12.existsSync(fullPath)) {
5105
+ const content = fs12.readFileSync(fullPath, "utf-8").toLowerCase();
4335
5106
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
4336
5107
  if (matchedPattern) {
4337
5108
  warnings.push({
@@ -4346,7 +5117,7 @@ function detectGitPatchScripts(projectRoot) {
4346
5117
  }
4347
5118
  }
4348
5119
  }
4349
- const deletedStdout = execSync4("git diff --name-only --diff-filter=D HEAD", {
5120
+ const deletedStdout = execSync7("git diff --name-only --diff-filter=D HEAD", {
4350
5121
  cwd: projectRoot,
4351
5122
  encoding: "utf-8",
4352
5123
  timeout: 3e3
@@ -4354,7 +5125,7 @@ function detectGitPatchScripts(projectRoot) {
4354
5125
  if (deletedStdout) {
4355
5126
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
4356
5127
  for (const file of deletedFiles) {
4357
- const ext = path11.extname(file).toLowerCase();
5128
+ const ext = path13.extname(file).toLowerCase();
4358
5129
  if (SCRIPT_EXTENSIONS.includes(ext)) {
4359
5130
  warnings.push({
4360
5131
  severity: "high",
@@ -4414,21 +5185,21 @@ function detectAllAntiPatterns() {
4414
5185
  }
4415
5186
 
4416
5187
  // src/engine/contextSnapshot.ts
4417
- import fs12 from "fs";
4418
- import path12 from "path";
4419
- import { execSync as execSync5 } from "child_process";
5188
+ import fs13 from "fs";
5189
+ import path14 from "path";
5190
+ import { execSync as execSync8 } from "child_process";
4420
5191
  var SNAPSHOT_DIR = "context-snapshots";
4421
5192
  function snapshotDir() {
4422
- return path12.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
5193
+ return path14.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
4423
5194
  }
4424
5195
  function ensureSnapshotDir() {
4425
5196
  const dir = snapshotDir();
4426
- if (!fs12.existsSync(dir)) {
4427
- fs12.mkdirSync(dir, { recursive: true });
5197
+ if (!fs13.existsSync(dir)) {
5198
+ fs13.mkdirSync(dir, { recursive: true });
4428
5199
  }
4429
5200
  }
4430
5201
  function snapshotFilePath(timestamp) {
4431
- return path12.join(snapshotDir(), `${timestamp}.json`);
5202
+ return path14.join(snapshotDir(), `${timestamp}.json`);
4432
5203
  }
4433
5204
  function saveSnapshot(goal) {
4434
5205
  try {
@@ -4449,7 +5220,7 @@ function saveSnapshot(goal) {
4449
5220
  hasConventions: !!summary.hasConventions
4450
5221
  };
4451
5222
  try {
4452
- fs12.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
5223
+ fs13.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
4453
5224
  } catch {
4454
5225
  }
4455
5226
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -4457,12 +5228,12 @@ function saveSnapshot(goal) {
4457
5228
  }
4458
5229
  function listSnapshots() {
4459
5230
  const dir = snapshotDir();
4460
- if (!fs12.existsSync(dir)) return [];
5231
+ if (!fs13.existsSync(dir)) return [];
4461
5232
  try {
4462
- const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
5233
+ const files = fs13.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
4463
5234
  return files.map((f) => {
4464
5235
  try {
4465
- const content = fs12.readFileSync(path12.join(dir, f), "utf-8");
5236
+ const content = fs13.readFileSync(path14.join(dir, f), "utf-8");
4466
5237
  return JSON.parse(content);
4467
5238
  } catch {
4468
5239
  return null;
@@ -4505,7 +5276,7 @@ function formatSnapshot(snapshot) {
4505
5276
  function getGitDiffStat2() {
4506
5277
  try {
4507
5278
  const root = getProjectRoot();
4508
- const stdout = execSync5("git diff --stat", {
5279
+ const stdout = execSync8("git diff --stat", {
4509
5280
  cwd: root,
4510
5281
  encoding: "utf-8",
4511
5282
  timeout: 3e3
@@ -4650,25 +5421,25 @@ async function handleKumaContext(params) {
4650
5421
  }
4651
5422
 
4652
5423
  // src/tools/kumaInit.ts
4653
- import fs13 from "fs";
4654
- import path13 from "path";
5424
+ import fs14 from "fs";
5425
+ import path15 from "path";
4655
5426
  async function handleKumaInit(params) {
4656
5427
  const root = params.projectRoot ?? getProjectRoot();
4657
- const kumaDir = path13.join(root, ".kuma");
4658
- const memoriesDir = path13.join(kumaDir, "memories");
4659
- const initMdPath = path13.join(kumaDir, "init.md");
5428
+ const kumaDir = path15.join(root, ".kuma");
5429
+ const memoriesDir = path15.join(kumaDir, "memories");
5430
+ const initMdPath = path15.join(kumaDir, "init.md");
4660
5431
  sessionMemory.recordToolCall("kuma_init", { projectRoot: root });
4661
5432
  let rules = "";
4662
- if (fs13.existsSync(initMdPath)) {
4663
- rules = fs13.readFileSync(initMdPath, "utf-8");
5433
+ if (fs14.existsSync(initMdPath)) {
5434
+ rules = fs14.readFileSync(initMdPath, "utf-8");
4664
5435
  }
4665
5436
  const memories = [];
4666
- if (fs13.existsSync(memoriesDir)) {
5437
+ if (fs14.existsSync(memoriesDir)) {
4667
5438
  try {
4668
- const files = fs13.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
5439
+ const files = fs14.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
4669
5440
  for (const file of files) {
4670
5441
  try {
4671
- const content = fs13.readFileSync(path13.join(memoriesDir, file), "utf-8");
5442
+ const content = fs14.readFileSync(path15.join(memoriesDir, file), "utf-8");
4672
5443
  memories.push({ topic: file.replace(/\.md$/, ""), content });
4673
5444
  } catch {
4674
5445
  }
@@ -4712,9 +5483,9 @@ ${memoryList}`);
4712
5483
  }
4713
5484
 
4714
5485
  // src/tools/kumaRisk.ts
4715
- import path14 from "path";
4716
- import fg2 from "fast-glob";
4717
- import fs14 from "fs";
5486
+ import path16 from "path";
5487
+ import fg4 from "fast-glob";
5488
+ import fs15 from "fs";
4718
5489
  async function handleKumaRisk(params) {
4719
5490
  const { symbol, filePath, depth = 2 } = params;
4720
5491
  if (!symbol && !filePath) {
@@ -4748,7 +5519,7 @@ async function handleKumaRisk(params) {
4748
5519
  ];
4749
5520
  let entries = [];
4750
5521
  try {
4751
- entries = await fg2("**/*.{ts,tsx,js,jsx,mjs,cjs}", {
5522
+ entries = await fg4("**/*.{ts,tsx,js,jsx,mjs,cjs}", {
4752
5523
  cwd: root,
4753
5524
  ignore: ignorePatterns,
4754
5525
  onlyFiles: true,
@@ -4767,11 +5538,11 @@ async function handleKumaRisk(params) {
4767
5538
  for (const entry of entries) {
4768
5539
  if (results.length >= maxResults) break;
4769
5540
  try {
4770
- const fullPath = path14.join(root, entry);
4771
- const stat = fs14.statSync(fullPath);
5541
+ const fullPath = path16.join(root, entry);
5542
+ const stat = fs15.statSync(fullPath);
4772
5543
  if (stat.size > 5e5) continue;
4773
5544
  if (isBinaryFile(fullPath)) continue;
4774
- const content = fs14.readFileSync(fullPath, "utf-8");
5545
+ const content = fs15.readFileSync(fullPath, "utf-8");
4775
5546
  const lines = content.split("\n");
4776
5547
  for (let i = 0; i < lines.length; i++) {
4777
5548
  if (results.length >= maxResults) break;
@@ -4895,8 +5666,8 @@ function formatRiskReport(report, circularDepWarning) {
4895
5666
  }
4896
5667
 
4897
5668
  // src/tools/kumaDependencyGuard.ts
4898
- import fs15 from "fs";
4899
- import path15 from "path";
5669
+ import fs16 from "fs";
5670
+ import path17 from "path";
4900
5671
  var NATIVE_JS_ALTERNATIVES = {
4901
5672
  "lodash": [
4902
5673
  { method: "Array.prototype.map()", description: "Replace _.map()" },
@@ -4983,12 +5754,12 @@ var NATIVE_JS_ALTERNATIVES = {
4983
5754
  };
4984
5755
  function findExistingDependency(packageName) {
4985
5756
  const root = getProjectRoot();
4986
- const packageJsonPath = path15.join(root, "package.json");
4987
- if (!fs15.existsSync(packageJsonPath)) {
5757
+ const packageJsonPath = path17.join(root, "package.json");
5758
+ if (!fs16.existsSync(packageJsonPath)) {
4988
5759
  return { found: false };
4989
5760
  }
4990
5761
  try {
4991
- const pkg = JSON.parse(fs15.readFileSync(packageJsonPath, "utf-8"));
5762
+ const pkg = JSON.parse(fs16.readFileSync(packageJsonPath, "utf-8"));
4992
5763
  const deps = pkg.dependencies || {};
4993
5764
  if (deps[packageName]) {
4994
5765
  return { found: true, version: deps[packageName] };
@@ -5004,12 +5775,12 @@ function findExistingDependency(packageName) {
5004
5775
  }
5005
5776
  function findSimilarExisting(packageName) {
5006
5777
  const root = getProjectRoot();
5007
- const packageJsonPath = path15.join(root, "package.json");
5008
- if (!fs15.existsSync(packageJsonPath)) {
5778
+ const packageJsonPath = path17.join(root, "package.json");
5779
+ if (!fs16.existsSync(packageJsonPath)) {
5009
5780
  return [];
5010
5781
  }
5011
5782
  try {
5012
- const pkg = JSON.parse(fs15.readFileSync(packageJsonPath, "utf-8"));
5783
+ const pkg = JSON.parse(fs16.readFileSync(packageJsonPath, "utf-8"));
5013
5784
  const allDeps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
5014
5785
  const installedPackages = Object.keys(allDeps);
5015
5786
  const alternativeMap = {
@@ -5120,8 +5891,8 @@ function getEstimatedPackageSize(packageName) {
5120
5891
  }
5121
5892
 
5122
5893
  // src/tools/kumaPolicy.ts
5123
- import fs16 from "fs";
5124
- import path16 from "path";
5894
+ import fs17 from "fs";
5895
+ import path18 from "path";
5125
5896
  var DEFAULT_POLICY = {
5126
5897
  never_touch: [
5127
5898
  ".env",
@@ -5147,15 +5918,15 @@ var DEFAULT_POLICY = {
5147
5918
  };
5148
5919
  function loadPolicy() {
5149
5920
  const root = getProjectRoot();
5150
- const ymlPath = path16.join(root, ".kuma", "policy.yml");
5151
- const yamlPath = path16.join(root, ".kuma", "policy.yaml");
5921
+ const ymlPath = path18.join(root, ".kuma", "policy.yml");
5922
+ const yamlPath = path18.join(root, ".kuma", "policy.yaml");
5152
5923
  let policyContent = null;
5153
5924
  let policyPath = null;
5154
- if (fs16.existsSync(ymlPath)) {
5155
- policyContent = fs16.readFileSync(ymlPath, "utf-8");
5925
+ if (fs17.existsSync(ymlPath)) {
5926
+ policyContent = fs17.readFileSync(ymlPath, "utf-8");
5156
5927
  policyPath = ymlPath;
5157
- } else if (fs16.existsSync(yamlPath)) {
5158
- policyContent = fs16.readFileSync(yamlPath, "utf-8");
5928
+ } else if (fs17.existsSync(yamlPath)) {
5929
+ policyContent = fs17.readFileSync(yamlPath, "utf-8");
5159
5930
  policyPath = yamlPath;
5160
5931
  }
5161
5932
  if (!policyContent) {
@@ -5272,9 +6043,9 @@ function checkFilePathPolicy(filePath, policy) {
5272
6043
  if (policy.max_file_size && policy.max_file_size > 0) {
5273
6044
  try {
5274
6045
  const root = getProjectRoot();
5275
- const fullPath = path16.join(root, filePath);
5276
- if (fs16.existsSync(fullPath)) {
5277
- const sizeKB = fs16.statSync(fullPath).size / 1024;
6046
+ const fullPath = path18.join(root, filePath);
6047
+ if (fs17.existsSync(fullPath)) {
6048
+ const sizeKB = fs17.statSync(fullPath).size / 1024;
5278
6049
  if (sizeKB > policy.max_file_size) {
5279
6050
  violations.push({
5280
6051
  rule: "max_file_size",
@@ -5370,8 +6141,8 @@ function formatPolicyResult(type, value, result, policy) {
5370
6141
  }
5371
6142
 
5372
6143
  // src/engine/safetyScore.ts
5373
- import fs17 from "fs";
5374
- import path17 from "path";
6144
+ import fs18 from "fs";
6145
+ import path19 from "path";
5375
6146
  function computeSafetyScore(inputGoal) {
5376
6147
  const stats = getSessionStats(inputGoal);
5377
6148
  const checks = [];
@@ -5415,8 +6186,8 @@ function computeSafetyScore(inputGoal) {
5415
6186
  totalScore += 20;
5416
6187
  }
5417
6188
  const backupDir = getKumaBackupsDir();
5418
- if (fs17.existsSync(backupDir)) {
5419
- const backupCount = fs17.readdirSync(backupDir).filter((d) => /^\d+$/.test(d)).length;
6189
+ if (fs18.existsSync(backupDir)) {
6190
+ const backupCount = fs18.readdirSync(backupDir).filter((d) => /^\d+$/.test(d)).length;
5420
6191
  checks.push({
5421
6192
  label: "Backup Available",
5422
6193
  status: "pass",
@@ -5434,8 +6205,8 @@ function computeSafetyScore(inputGoal) {
5434
6205
  totalScore += 5;
5435
6206
  }
5436
6207
  try {
5437
- const lspPath = path17.join(getProjectRoot(), "node_modules", ".bin", "typescript-language-server");
5438
- if (fs17.existsSync(lspPath)) {
6208
+ const lspPath = path19.join(getProjectRoot(), "node_modules", ".bin", "typescript-language-server");
6209
+ if (fs18.existsSync(lspPath)) {
5439
6210
  checks.push({
5440
6211
  label: "LSP Available",
5441
6212
  status: "pass",
@@ -5657,8 +6428,8 @@ function formatSafetyScore(report) {
5657
6428
 
5658
6429
  // src/engine/kumaDb.ts
5659
6430
  import initSqlJs from "sql.js";
5660
- import fs18 from "fs";
5661
- import path18 from "path";
6431
+ import fs19 from "fs";
6432
+ import path20 from "path";
5662
6433
  var DB_FILENAME = "kuma.db";
5663
6434
  var dbInstance = null;
5664
6435
  var initPromise = null;
@@ -5671,13 +6442,13 @@ async function getDb() {
5671
6442
  async function initDb() {
5672
6443
  const SQL = await initSqlJs();
5673
6444
  const kumaDir = getKumaDir();
5674
- const dbPath = path18.join(kumaDir, DB_FILENAME);
5675
- if (!fs18.existsSync(kumaDir)) {
5676
- fs18.mkdirSync(kumaDir, { recursive: true });
6445
+ const dbPath = path20.join(kumaDir, DB_FILENAME);
6446
+ if (!fs19.existsSync(kumaDir)) {
6447
+ fs19.mkdirSync(kumaDir, { recursive: true });
5677
6448
  }
5678
6449
  let db;
5679
- if (fs18.existsSync(dbPath)) {
5680
- const buffer = fs18.readFileSync(dbPath);
6450
+ if (fs19.existsSync(dbPath)) {
6451
+ const buffer = fs19.readFileSync(dbPath);
5681
6452
  db = new SQL.Database(buffer);
5682
6453
  } else {
5683
6454
  db = new SQL.Database();
@@ -5694,10 +6465,10 @@ function saveDb(db) {
5694
6465
  if (!d) return;
5695
6466
  try {
5696
6467
  const kumaDir = getKumaDir();
5697
- const dbPath = path18.join(kumaDir, DB_FILENAME);
6468
+ const dbPath = path20.join(kumaDir, DB_FILENAME);
5698
6469
  const data = d.export();
5699
6470
  const buffer = Buffer.from(data);
5700
- fs18.writeFileSync(dbPath, buffer);
6471
+ fs19.writeFileSync(dbPath, buffer);
5701
6472
  } catch (err) {
5702
6473
  console.error(`[KumaDB] Failed to save database: ${err}`);
5703
6474
  }
@@ -5804,26 +6575,26 @@ function createSchema(db) {
5804
6575
  }
5805
6576
 
5806
6577
  // src/engine/kumaSelfHeal.ts
5807
- import { execSync as execSync6 } from "child_process";
5808
- import fs19 from "fs";
5809
- import path19 from "path";
6578
+ import { execSync as execSync9 } from "child_process";
6579
+ import fs20 from "fs";
6580
+ import path21 from "path";
5810
6581
  import crypto from "crypto";
5811
6582
  function contentFingerprint(filePath) {
5812
6583
  try {
5813
- const fullPath = path19.join(getProjectRoot(), filePath);
5814
- if (!fs19.existsSync(fullPath)) return null;
5815
- const stat = fs19.statSync(fullPath);
6584
+ const fullPath = path21.join(getProjectRoot(), filePath);
6585
+ if (!fs20.existsSync(fullPath)) return null;
6586
+ const stat = fs20.statSync(fullPath);
5816
6587
  if (!stat.isFile()) return null;
5817
- const fd = fs19.openSync(fullPath, "r");
6588
+ const fd = fs20.openSync(fullPath, "r");
5818
6589
  try {
5819
6590
  const size = stat.size;
5820
6591
  const readSize = Math.min(1024, size);
5821
6592
  const head = Buffer.alloc(readSize);
5822
- fs19.readSync(fd, head, 0, readSize, 0);
6593
+ fs20.readSync(fd, head, 0, readSize, 0);
5823
6594
  let tail = Buffer.alloc(0);
5824
6595
  if (size > 2048) {
5825
6596
  tail = Buffer.alloc(1024);
5826
- fs19.readSync(fd, tail, 0, 1024, size - 1024);
6597
+ fs20.readSync(fd, tail, 0, 1024, size - 1024);
5827
6598
  }
5828
6599
  const hash = crypto.createHash("md5");
5829
6600
  hash.update(head);
@@ -5831,7 +6602,7 @@ function contentFingerprint(filePath) {
5831
6602
  hash.update(String(size));
5832
6603
  return hash.digest("hex");
5833
6604
  } finally {
5834
- fs19.closeSync(fd);
6605
+ fs20.closeSync(fd);
5835
6606
  }
5836
6607
  } catch {
5837
6608
  return null;
@@ -5840,8 +6611,8 @@ function contentFingerprint(filePath) {
5840
6611
  function findByFingerprint(fingerprint, oldName) {
5841
6612
  try {
5842
6613
  const root = getProjectRoot();
5843
- const ext = path19.extname(oldName);
5844
- const result = execSync6(
6614
+ const ext = path21.extname(oldName);
6615
+ const result = execSync9(
5845
6616
  `find . -name "*${ext}" -type f -not -path "./node_modules/*" -not -path "./.git/*" -not -path "./dist/*" -not -path "./build/*" 2>/dev/null | head -200`,
5846
6617
  { cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 1e4 }
5847
6618
  ).trim();
@@ -5874,8 +6645,8 @@ async function detectStaleNodes() {
5874
6645
  const row = stmt.getAsObject();
5875
6646
  const filePath = row.file_path;
5876
6647
  if (filePath.startsWith("search::") || filePath.startsWith("api_route::")) continue;
5877
- const fullPath = path19.join(root, filePath);
5878
- if (!fs19.existsSync(fullPath)) {
6648
+ const fullPath = path21.join(root, filePath);
6649
+ if (!fs20.existsSync(fullPath)) {
5879
6650
  const newPath = findRenamedPath(filePath);
5880
6651
  if (newPath) {
5881
6652
  stale.push({
@@ -5888,7 +6659,7 @@ async function detectStaleNodes() {
5888
6659
  });
5889
6660
  } else {
5890
6661
  const oldFingerprint = contentFingerprint(filePath);
5891
- const contentMatch = oldFingerprint ? findByFingerprint(oldFingerprint, path19.basename(filePath)) : null;
6662
+ const contentMatch = oldFingerprint ? findByFingerprint(oldFingerprint, path21.basename(filePath)) : null;
5892
6663
  stale.push({
5893
6664
  nodeId: row.id,
5894
6665
  type: row.type,
@@ -5909,7 +6680,7 @@ async function detectStaleNodes() {
5909
6680
  function findRenamedPath(oldPath) {
5910
6681
  try {
5911
6682
  const root = getProjectRoot();
5912
- const output = execSync6(
6683
+ const output = execSync9(
5913
6684
  `git log --follow --diff-filter=R --name-only --format="" -1 -- "${oldPath}"`,
5914
6685
  { cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 5e3 }
5915
6686
  ).trim();
@@ -5990,8 +6761,8 @@ async function healOnQuery(filePaths) {
5990
6761
  try {
5991
6762
  const stalePaths = filePaths.filter((fp) => {
5992
6763
  if (!fp || fp.startsWith("search::") || fp.startsWith("api_route::")) return false;
5993
- const fullPath = path19.join(getProjectRoot(), fp);
5994
- return !fs19.existsSync(fullPath);
6764
+ const fullPath = path21.join(getProjectRoot(), fp);
6765
+ return !fs20.existsSync(fullPath);
5995
6766
  });
5996
6767
  if (stalePaths.length === 0) return { healed: 0 };
5997
6768
  let healed = 0;
@@ -6520,8 +7291,8 @@ async function pruneExperiences(keepPerTool = 50) {
6520
7291
 
6521
7292
  // src/engine/lspClient.ts
6522
7293
  import { spawn as spawn2 } from "child_process";
6523
- import path20 from "path";
6524
- import fs20 from "fs";
7294
+ import path22 from "path";
7295
+ import fs21 from "fs";
6525
7296
  var LSPClient = class {
6526
7297
  process = null;
6527
7298
  requestId = 0;
@@ -6559,8 +7330,8 @@ var LSPClient = class {
6559
7330
  console.error(`[LSP] typescript-language-server not found. LSP features will fallback to regex.`);
6560
7331
  return;
6561
7332
  }
6562
- const tsconfigPath = path20.join(root, "tsconfig.json");
6563
- if (!fs20.existsSync(tsconfigPath)) {
7333
+ const tsconfigPath = path22.join(root, "tsconfig.json");
7334
+ if (!fs21.existsSync(tsconfigPath)) {
6564
7335
  console.error(`[LSP] Warning: tsconfig.json not found at "${root}". Running in implicit project mode.`);
6565
7336
  }
6566
7337
  this.process = spawn2(lspBinary, ["--stdio", "--log-level=4"], {
@@ -6622,19 +7393,19 @@ var LSPClient = class {
6622
7393
  /** Resolve the typescript-language-server binary from local or global install */
6623
7394
  resolveLspBinary(projectRoot) {
6624
7395
  const candidates = [
6625
- path20.join(projectRoot, "node_modules", ".bin", "typescript-language-server"),
6626
- path20.join(projectRoot, "..", "node_modules", ".bin", "typescript-language-server")
7396
+ path22.join(projectRoot, "node_modules", ".bin", "typescript-language-server"),
7397
+ path22.join(projectRoot, "..", "node_modules", ".bin", "typescript-language-server")
6627
7398
  ];
6628
7399
  for (const candidate of candidates) {
6629
- if (fs20.existsSync(candidate)) {
7400
+ if (fs21.existsSync(candidate)) {
6630
7401
  return candidate;
6631
7402
  }
6632
7403
  }
6633
7404
  try {
6634
7405
  const envPath = process.env.PATH ?? "";
6635
- for (const dir of envPath.split(path20.delimiter)) {
6636
- const binPath = path20.join(dir, "typescript-language-server");
6637
- if (fs20.existsSync(binPath)) {
7406
+ for (const dir of envPath.split(path22.delimiter)) {
7407
+ const binPath = path22.join(dir, "typescript-language-server");
7408
+ if (fs21.existsSync(binPath)) {
6638
7409
  return binPath;
6639
7410
  }
6640
7411
  }
@@ -6656,7 +7427,7 @@ var LSPClient = class {
6656
7427
  this.openDocuments.add(filePath);
6657
7428
  let content;
6658
7429
  try {
6659
- content = fs20.readFileSync(filePath, "utf-8");
7430
+ content = fs21.readFileSync(filePath, "utf-8");
6660
7431
  } catch {
6661
7432
  content = "";
6662
7433
  }
@@ -6873,8 +7644,8 @@ var LSPClient = class {
6873
7644
  var lspClient = new LSPClient();
6874
7645
 
6875
7646
  // src/tools/lspTools.ts
6876
- import fs21 from "fs";
6877
- import path21 from "path";
7647
+ import fs22 from "fs";
7648
+ import path23 from "path";
6878
7649
  async function handleFindReferences(params) {
6879
7650
  const { filePath, line, character } = params;
6880
7651
  const validation = validateFilePath(filePath);
@@ -6882,7 +7653,7 @@ async function handleFindReferences(params) {
6882
7653
  return `Error: ${validation.error.message}`;
6883
7654
  }
6884
7655
  const resolvedPath = validation.resolvedPath;
6885
- if (!fs21.existsSync(resolvedPath)) {
7656
+ if (!fs22.existsSync(resolvedPath)) {
6886
7657
  return `Error: File not found: "${filePath}".`;
6887
7658
  }
6888
7659
  if (!lspClient.isAvailable()) {
@@ -6907,7 +7678,7 @@ No references found for symbol at this position.`;
6907
7678
  const enrichedRefs = references.map((ref) => {
6908
7679
  let lineContent = "";
6909
7680
  try {
6910
- const content = fs21.readFileSync(ref.filePath, "utf-8");
7681
+ const content = fs22.readFileSync(ref.filePath, "utf-8");
6911
7682
  const lines2 = content.split("\n");
6912
7683
  lineContent = lines2[ref.line]?.trim() ?? "";
6913
7684
  } catch {
@@ -6923,11 +7694,11 @@ No references found for symbol at this position.`;
6923
7694
  const projectRoot = getProjectRoot();
6924
7695
  const lines = [
6925
7696
  `**Find References** \u2014 ${enrichedRefs.length} references found`,
6926
- `\u{1F4CD} File: ${path21.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
7697
+ `\u{1F4CD} File: ${path23.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
6927
7698
  ""
6928
7699
  ];
6929
7700
  for (const [file, refs] of grouped) {
6930
- const relPath = path21.relative(projectRoot, file);
7701
+ const relPath = path23.relative(projectRoot, file);
6931
7702
  lines.push(`**\u{1F4C4} ${relPath}:**`);
6932
7703
  for (const ref of refs) {
6933
7704
  const loc = `L${ref.line + 1}:${ref.character + 1}`;
@@ -6948,7 +7719,7 @@ async function handleGoToDefinition(params) {
6948
7719
  return `Error: ${validation.error.message}`;
6949
7720
  }
6950
7721
  const resolvedPath = validation.resolvedPath;
6951
- if (!fs21.existsSync(resolvedPath)) {
7722
+ if (!fs22.existsSync(resolvedPath)) {
6952
7723
  return `Error: File not found: "${filePath}".`;
6953
7724
  }
6954
7725
  if (!lspClient.isAvailable()) {
@@ -6971,10 +7742,10 @@ Try selecting a smaller/cleaner position, or install typescript-language-server
6971
7742
  Cannot find definition for symbol at this position.`;
6972
7743
  }
6973
7744
  const projectRoot = getProjectRoot();
6974
- const relPath = path21.relative(projectRoot, definition.filePath);
7745
+ const relPath = path23.relative(projectRoot, definition.filePath);
6975
7746
  let lineContent = "";
6976
7747
  try {
6977
- const content = fs21.readFileSync(definition.filePath, "utf-8");
7748
+ const content = fs22.readFileSync(definition.filePath, "utf-8");
6978
7749
  const lines2 = content.split("\n");
6979
7750
  lineContent = lines2[definition.line]?.trim() ?? "";
6980
7751
  } catch {
@@ -7006,7 +7777,7 @@ async function handleRenameSymbol(params) {
7006
7777
  return `Error: ${validation.error.message}`;
7007
7778
  }
7008
7779
  const resolvedPath = validation.resolvedPath;
7009
- if (!fs21.existsSync(resolvedPath)) {
7780
+ if (!fs22.existsSync(resolvedPath)) {
7010
7781
  return `Error: File not found: "${filePath}".`;
7011
7782
  }
7012
7783
  if (!lspClient.isAvailable()) {
@@ -7037,7 +7808,7 @@ Make sure:
7037
7808
  const fileChanges = [];
7038
7809
  for (const change of result.changes) {
7039
7810
  try {
7040
- const content = fs21.readFileSync(change.filePath, "utf-8");
7811
+ const content = fs22.readFileSync(change.filePath, "utf-8");
7041
7812
  const lines2 = content.split("\n");
7042
7813
  const sortedEdits = [...change.edits].sort((a, b) => {
7043
7814
  if (b.line !== a.line) return b.line - a.line;
@@ -7051,10 +7822,10 @@ Make sure:
7051
7822
  lines2[edit.line] = before + edit.newText + after;
7052
7823
  }
7053
7824
  }
7054
- fs21.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
7825
+ fs22.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
7055
7826
  totalEdits += change.edits.length;
7056
7827
  fileChanges.push({
7057
- filePath: path21.relative(projectRoot, change.filePath),
7828
+ filePath: path23.relative(projectRoot, change.filePath),
7058
7829
  editCount: change.edits.length
7059
7830
  });
7060
7831
  } catch (err) {
@@ -7081,14 +7852,14 @@ async function handleGetTypeInfo(params) {
7081
7852
  return `Error: ${validation.error.message}`;
7082
7853
  }
7083
7854
  const resolvedPath = validation.resolvedPath;
7084
- if (!fs21.existsSync(resolvedPath)) {
7855
+ if (!fs22.existsSync(resolvedPath)) {
7085
7856
  return `Error: File not found: "${filePath}".`;
7086
7857
  }
7087
7858
  if (!lspClient.isAvailable()) {
7088
7859
  sessionMemory.recordToolCall("lsp_query", { action: "type", filePath, line, character, fallback: "line-context" });
7089
7860
  console.error(`[LSP] typescript-language-server not found. Using file-read fallback for type info.`);
7090
7861
  try {
7091
- const fileContent = fs21.readFileSync(resolvedPath, "utf-8");
7862
+ const fileContent = fs22.readFileSync(resolvedPath, "utf-8");
7092
7863
  const fileLines = fileContent.split("\n");
7093
7864
  const targetLine = fileLines[line];
7094
7865
  if (!targetLine) {
@@ -7096,7 +7867,7 @@ async function handleGetTypeInfo(params) {
7096
7867
  }
7097
7868
  const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
7098
7869
  const projectRoot = getProjectRoot();
7099
- const relPath = path21.relative(projectRoot, resolvedPath);
7870
+ const relPath = path23.relative(projectRoot, resolvedPath);
7100
7871
  const contextStart = Math.max(0, line - 3);
7101
7872
  const contextEnd = Math.min(fileLines.length, line + 4);
7102
7873
  const contextLines = [];
@@ -7131,7 +7902,7 @@ async function handleGetTypeInfo(params) {
7131
7902
  No type info for this position.`;
7132
7903
  }
7133
7904
  const projectRoot = getProjectRoot();
7134
- const relPath = path21.relative(projectRoot, resolvedPath);
7905
+ const relPath = path23.relative(projectRoot, resolvedPath);
7135
7906
  const lines = [
7136
7907
  `\u{1F4CB} **Type Info** \u2014 \`${relPath}:${line + 1}:${character + 1}\``,
7137
7908
  "",
@@ -7172,7 +7943,7 @@ async function handleLspQuery(params) {
7172
7943
  }
7173
7944
  function extractSymbolAtPosition(filePath, line, character) {
7174
7945
  try {
7175
- const content = fs21.readFileSync(filePath, "utf-8");
7946
+ const content = fs22.readFileSync(filePath, "utf-8");
7176
7947
  const lines = content.split("\n");
7177
7948
  const targetLine = lines[line];
7178
7949
  if (!targetLine) return null;
@@ -7190,9 +7961,9 @@ function extractSymbolAtPosition(filePath, line, character) {
7190
7961
  }
7191
7962
  async function fallbackGrepReferences(symbolName, _filePath, _line, _character) {
7192
7963
  try {
7193
- const { default: fg4 } = await import("fast-glob");
7964
+ const { default: fg6 } = await import("fast-glob");
7194
7965
  const root = getProjectRoot();
7195
- const tsFiles = await fg4(["**/*.{ts,tsx,js,jsx}"], {
7966
+ const tsFiles = await fg6(["**/*.{ts,tsx,js,jsx}"], {
7196
7967
  cwd: root,
7197
7968
  ignore: ["node_modules/**", "dist/**", ".git/**"],
7198
7969
  onlyFiles: true,
@@ -7203,7 +7974,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
7203
7974
  const regex = new RegExp(`\\b${escapedSymbol}\\b`, "g");
7204
7975
  for (const file of tsFiles.slice(0, 100)) {
7205
7976
  try {
7206
- const content = fs21.readFileSync(file, "utf-8");
7977
+ const content = fs22.readFileSync(file, "utf-8");
7207
7978
  const lines2 = content.split("\n");
7208
7979
  for (let i = 0; i < lines2.length; i++) {
7209
7980
  if (regex.test(lines2[i])) {
@@ -7232,7 +8003,7 @@ No references found. Symbol may not be used in other files.`;
7232
8003
  ""
7233
8004
  ];
7234
8005
  if (grouped.size > 1) {
7235
- const fileList = [...grouped.keys()].map((f) => path21.relative(projectRoot, f)).join(", ");
8006
+ const fileList = [...grouped.keys()].map((f) => path23.relative(projectRoot, f)).join(", ");
7236
8007
  lines.push(`**Ambiguity note:** Found matches across ${grouped.size} different files (${fileList}).`);
7237
8008
  lines.push(` Verify each match is the right scope \u2014 results may include same-named symbols.`);
7238
8009
  lines.push("");
@@ -7249,7 +8020,7 @@ No references found. Symbol may not be used in other files.`;
7249
8020
  }
7250
8021
  }
7251
8022
  for (const [file, refs] of grouped) {
7252
- const relPath = path21.relative(projectRoot, file);
8023
+ const relPath = path23.relative(projectRoot, file);
7253
8024
  lines.push(`**\u{1F4C4} ${relPath}:**`);
7254
8025
  for (const ref of refs) {
7255
8026
  lines.push(` \u2514 L${ref.line} \u2014 ${ref.content}`);
@@ -7264,9 +8035,9 @@ No references found. Symbol may not be used in other files.`;
7264
8035
  }
7265
8036
  async function fallbackGrepDefinition(symbolName) {
7266
8037
  try {
7267
- const { default: fg4 } = await import("fast-glob");
8038
+ const { default: fg6 } = await import("fast-glob");
7268
8039
  const root = getProjectRoot();
7269
- const tsFiles = await fg4(["**/*.{ts,tsx,js,jsx}"], {
8040
+ const tsFiles = await fg6(["**/*.{ts,tsx,js,jsx}"], {
7270
8041
  cwd: root,
7271
8042
  ignore: ["node_modules/**", "dist/**", ".git/**"],
7272
8043
  onlyFiles: true,
@@ -7283,14 +8054,14 @@ async function fallbackGrepDefinition(symbolName) {
7283
8054
  ];
7284
8055
  for (const file of tsFiles) {
7285
8056
  try {
7286
- const content = fs21.readFileSync(file, "utf-8");
8057
+ const content = fs22.readFileSync(file, "utf-8");
7287
8058
  const lines = content.split("\n");
7288
8059
  for (let i = 0; i < lines.length; i++) {
7289
8060
  const trimmed = lines[i].trim();
7290
8061
  for (const pattern of declPatterns) {
7291
8062
  if (pattern.test(trimmed)) {
7292
8063
  const projectRoot = getProjectRoot();
7293
- const relPath = path21.relative(projectRoot, file);
8064
+ const relPath = path23.relative(projectRoot, file);
7294
8065
  return [
7295
8066
  `\u{1F4CD} **Go to Definition**`,
7296
8067
  `\u{1F4C4} File: \`${relPath}\``,
@@ -7313,11 +8084,11 @@ Cannot find definition.`;
7313
8084
  }
7314
8085
 
7315
8086
  // src/engine/kumaTimeMachine.ts
7316
- import { execSync as execSync7 } from "child_process";
8087
+ import { execSync as execSync10 } from "child_process";
7317
8088
  function getBlameForFile(filePath) {
7318
8089
  const root = getProjectRoot();
7319
8090
  try {
7320
- const output = execSync7(
8091
+ const output = execSync10(
7321
8092
  `git blame --line-porcelain -- "${filePath}"`,
7322
8093
  { cwd: root, encoding: "utf-8", maxBuffer: 2 * 1024 * 1024, timeout: 1e4 }
7323
8094
  );
@@ -7363,7 +8134,7 @@ function getBlameForFile(filePath) {
7363
8134
  function getFileHistory(filePath, maxCount = 20) {
7364
8135
  const root = getProjectRoot();
7365
8136
  try {
7366
- const output = execSync7(
8137
+ const output = execSync10(
7367
8138
  `git log --follow --format="%H||%an||%ai||%s" --shortstat -- "${filePath}"`,
7368
8139
  { cwd: root, encoding: "utf-8", maxBuffer: 2 * 1024 * 1024, timeout: 1e4 }
7369
8140
  );
@@ -7398,7 +8169,7 @@ async function getSymbolTimeline(symbolName, filePath, symbolType = "function")
7398
8169
  const blame = getBlameForFile(filePath);
7399
8170
  const pattern = getSymbolPattern(symbolName, symbolType);
7400
8171
  const root = getProjectRoot();
7401
- const content = execSync7(`git show HEAD:"${filePath}"`, {
8172
+ const content = execSync10(`git show HEAD:"${filePath}"`, {
7402
8173
  cwd: root,
7403
8174
  encoding: "utf-8",
7404
8175
  maxBuffer: 2 * 1024 * 1024,
@@ -7425,7 +8196,7 @@ async function getSymbolTimeline(symbolName, filePath, symbolType = "function")
7425
8196
  const entries = [];
7426
8197
  for (const [hash, data] of commitMap) {
7427
8198
  try {
7428
- const msg = execSync7(
8199
+ const msg = execSync10(
7429
8200
  `git log -1 --format="%s" ${hash}`,
7430
8201
  { cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 3e3 }
7431
8202
  ).trim();
@@ -7610,10 +8381,10 @@ async function getIntentPatterns(limit = 10) {
7610
8381
  ORDER BY id ASC
7611
8382
  `);
7612
8383
  pathStmt.bind([session.id]);
7613
- const path29 = [];
8384
+ const path31 = [];
7614
8385
  while (pathStmt.step()) {
7615
8386
  const row = pathStmt.getAsObject();
7616
- path29.push(row.tool_name);
8387
+ path31.push(row.tool_name);
7617
8388
  }
7618
8389
  pathStmt.free();
7619
8390
  const existing = clusterMap.get(clusterKey) || {
@@ -7622,7 +8393,7 @@ async function getIntentPatterns(limit = 10) {
7622
8393
  totalDuration: 0,
7623
8394
  lastUsed: 0
7624
8395
  };
7625
- existing.paths.push(path29);
8396
+ existing.paths.push(path31);
7626
8397
  existing.successes.push(session.success);
7627
8398
  existing.lastUsed = Math.max(existing.lastUsed, session.startedAt);
7628
8399
  clusterMap.set(clusterKey, existing);
@@ -7730,9 +8501,9 @@ function formatIntentSuggestion(intent, suggestion) {
7730
8501
  }
7731
8502
 
7732
8503
  // src/engine/kumaArchGuard.ts
7733
- import fg3 from "fast-glob";
7734
- import fs22 from "fs";
7735
- import path22 from "path";
8504
+ import fg5 from "fast-glob";
8505
+ import fs23 from "fs";
8506
+ import path24 from "path";
7736
8507
  var ARCHITECTURES = [
7737
8508
  {
7738
8509
  name: "clean-architecture",
@@ -7823,8 +8594,8 @@ var ARCHITECTURES = [
7823
8594
  ];
7824
8595
  function detectArchitecture() {
7825
8596
  const root = getProjectRoot();
7826
- const srcDir = path22.join(root, "src");
7827
- if (!fs22.existsSync(srcDir)) {
8597
+ const srcDir = path24.join(root, "src");
8598
+ if (!fs23.existsSync(srcDir)) {
7828
8599
  return getArchitectureProfile("flat");
7829
8600
  }
7830
8601
  const scores = ARCHITECTURES.map((arch) => {
@@ -7835,7 +8606,7 @@ function detectArchitecture() {
7835
8606
  totalPatterns++;
7836
8607
  const globPattern = pattern.replace(/\/\*\*$/, "/**");
7837
8608
  try {
7838
- const matches = fg3.sync(globPattern, { cwd: root, onlyFiles: false, deep: 1 });
8609
+ const matches = fg5.sync(globPattern, { cwd: root, onlyFiles: false, deep: 1 });
7839
8610
  if (matches.length > 0) matchCount++;
7840
8611
  } catch {
7841
8612
  }
@@ -7846,10 +8617,10 @@ function detectArchitecture() {
7846
8617
  scores.sort((a, b) => b.score - a.score);
7847
8618
  const best = scores[0];
7848
8619
  if (!best || best.score < 0.2) {
7849
- if (fs22.existsSync(path22.join(srcDir, "domain")) || fs22.existsSync(path22.join(srcDir, "entities"))) {
8620
+ if (fs23.existsSync(path24.join(srcDir, "domain")) || fs23.existsSync(path24.join(srcDir, "entities"))) {
7850
8621
  return getArchitectureProfile("clean-architecture");
7851
8622
  }
7852
- if (fs22.existsSync(path22.join(srcDir, "data")) || fs22.existsSync(path22.join(srcDir, "business"))) {
8623
+ if (fs23.existsSync(path24.join(srcDir, "data")) || fs23.existsSync(path24.join(srcDir, "business"))) {
7853
8624
  return getArchitectureProfile("layered-architecture");
7854
8625
  }
7855
8626
  return getArchitectureProfile("flat");
@@ -7920,7 +8691,7 @@ async function scanFilesystemForViolations(profile) {
7920
8691
  const arch = profile || detectArchitecture();
7921
8692
  const root = getProjectRoot();
7922
8693
  try {
7923
- const files = await fg3(["src/**/*.{ts,tsx,js,jsx}"], {
8694
+ const files = await fg5(["src/**/*.{ts,tsx,js,jsx}"], {
7924
8695
  cwd: root,
7925
8696
  ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*.d.ts"],
7926
8697
  onlyFiles: true
@@ -7929,7 +8700,7 @@ async function scanFilesystemForViolations(profile) {
7929
8700
  const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
7930
8701
  for (const file of files.slice(0, 200)) {
7931
8702
  try {
7932
- const content = fs22.readFileSync(path22.join(root, file), "utf-8");
8703
+ const content = fs23.readFileSync(path24.join(root, file), "utf-8");
7933
8704
  const imports = [];
7934
8705
  let match;
7935
8706
  while ((match = importRegex.exec(content)) !== null) {
@@ -7942,16 +8713,16 @@ async function scanFilesystemForViolations(profile) {
7942
8713
  if (!imp.startsWith(".") && !imp.startsWith("/") && !imp.startsWith("src/")) continue;
7943
8714
  let targetFile = imp;
7944
8715
  if (imp.startsWith(".")) {
7945
- const resolved = path22.resolve(path22.dirname(file), imp);
7946
- targetFile = path22.relative(root, resolved);
8716
+ const resolved = path24.resolve(path24.dirname(file), imp);
8717
+ targetFile = path24.relative(root, resolved);
7947
8718
  } else if (imp.startsWith("/")) {
7948
8719
  targetFile = imp.substring(1);
7949
8720
  }
7950
- if (!path22.extname(targetFile)) {
8721
+ if (!path24.extname(targetFile)) {
7951
8722
  const extVariants = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
7952
8723
  let found = false;
7953
8724
  for (const ext of extVariants) {
7954
- if (fs22.existsSync(path22.join(root, targetFile + ext))) {
8725
+ if (fs23.existsSync(path24.join(root, targetFile + ext))) {
7955
8726
  targetFile += ext;
7956
8727
  found = true;
7957
8728
  break;
@@ -8035,20 +8806,20 @@ function formatArchitectureDetection(profile) {
8035
8806
  }
8036
8807
 
8037
8808
  // src/engine/kumaMemory.ts
8038
- import fs23 from "fs";
8039
- import path23 from "path";
8809
+ import fs24 from "fs";
8810
+ import path25 from "path";
8040
8811
  var MEMORY_DIR = ".kuma/memories";
8041
8812
  function scoreMemoryRelevance(context, limit = 5) {
8042
8813
  const results = [];
8043
- const kumaDir = path23.join(getProjectRoot(), MEMORY_DIR);
8044
- if (!fs23.existsSync(kumaDir)) return [];
8814
+ const kumaDir = path25.join(getProjectRoot(), MEMORY_DIR);
8815
+ if (!fs24.existsSync(kumaDir)) return [];
8045
8816
  const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
8046
8817
  if (terms.length === 0) return [];
8047
8818
  try {
8048
- const files = fs23.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
8819
+ const files = fs24.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
8049
8820
  for (const file of files) {
8050
8821
  try {
8051
- const content = fs23.readFileSync(path23.join(kumaDir, file), "utf-8");
8822
+ const content = fs24.readFileSync(path25.join(kumaDir, file), "utf-8");
8052
8823
  const lower = content.toLowerCase();
8053
8824
  let matchCount = 0;
8054
8825
  for (const term of terms) {
@@ -8128,7 +8899,7 @@ function formatDecisionTemplate() {
8128
8899
  }
8129
8900
 
8130
8901
  // src/engine/kumaContextEngine.ts
8131
- import { execSync as execSync8 } from "child_process";
8902
+ import { execSync as execSync11 } from "child_process";
8132
8903
  var STOP_WORDS = /* @__PURE__ */ new Set([
8133
8904
  "the",
8134
8905
  "this",
@@ -8245,7 +9016,7 @@ async function searchNodesFts(db, terms) {
8245
9016
  function getFileRecencySeconds(filePath) {
8246
9017
  try {
8247
9018
  const escapedPath = filePath.replace(/"/g, '\\"');
8248
- const result = execSync8(
9019
+ const result = execSync11(
8249
9020
  'git log -1 --format=%ct -- "' + escapedPath + '" 2>/dev/null || echo 0',
8250
9021
  { encoding: "utf-8", timeout: 2e3 }
8251
9022
  );
@@ -8962,9 +9733,9 @@ async function buildOwnershipMap(db, focus, limit) {
8962
9733
  const dirGroups = /* @__PURE__ */ new Map();
8963
9734
  while (stmt.step()) {
8964
9735
  const row = stmt.getAsObject();
8965
- const path29 = row.file_path || row.name;
8966
- const dir = topDir(path29);
8967
- if (focus && !path29.includes(focus)) continue;
9736
+ const path31 = row.file_path || row.name;
9737
+ const dir = topDir(path31);
9738
+ if (focus && !path31.includes(focus)) continue;
8968
9739
  if (!dirGroups.has(dir)) dirGroups.set(dir, []);
8969
9740
  const existing = dirGroups.get(dir);
8970
9741
  const emoji = row.type === "function" ? "\u{1F527}" : row.type === "file" ? "\u{1F4C4}" : "\u{1F4CC}";
@@ -9005,8 +9776,8 @@ function mermaidId(name) {
9005
9776
  }
9006
9777
 
9007
9778
  // src/engine/kumaHeatMap.ts
9008
- import fs24 from "fs";
9009
- import path24 from "path";
9779
+ import fs25 from "fs";
9780
+ import path26 from "path";
9010
9781
  async function computeHeatMap() {
9011
9782
  const history = sessionMemory.getToolCallHistory(100);
9012
9783
  const db = await getDb();
@@ -9015,7 +9786,7 @@ async function computeHeatMap() {
9015
9786
  const p = call.params;
9016
9787
  const fp = p.filePath || p.file || "";
9017
9788
  if (!fp) continue;
9018
- const dir = path24.dirname(fp).split("/")[0] || ".";
9789
+ const dir = path26.dirname(fp).split("/")[0] || ".";
9019
9790
  const entry = dirData.get(dir) || { edits: 0, reads: 0, failures: 0 };
9020
9791
  if (call.toolName === "precise_diff_editor" || call.toolName === "batch_file_writer") {
9021
9792
  entry.edits++;
@@ -9043,12 +9814,12 @@ async function computeHeatMap() {
9043
9814
  } catch {
9044
9815
  }
9045
9816
  const root = getProjectRoot();
9046
- const srcDir = path24.join(root, "src");
9047
- if (fs24.existsSync(srcDir)) {
9817
+ const srcDir = path26.join(root, "src");
9818
+ if (fs25.existsSync(srcDir)) {
9048
9819
  try {
9049
- for (const entry of fs24.readdirSync(srcDir, { withFileTypes: true })) {
9820
+ for (const entry of fs25.readdirSync(srcDir, { withFileTypes: true })) {
9050
9821
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
9051
- const count = countFiles(path24.join(srcDir, entry.name));
9822
+ const count = countFiles(path26.join(srcDir, entry.name));
9052
9823
  if (!dirFileCounts.has(entry.name) || dirFileCounts.get(entry.name) < count) {
9053
9824
  dirFileCounts.set(entry.name, count);
9054
9825
  }
@@ -9158,9 +9929,9 @@ function formatHeatMap(report) {
9158
9929
  function countFiles(dir) {
9159
9930
  let count = 0;
9160
9931
  try {
9161
- for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
9932
+ for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
9162
9933
  if (entry.name.startsWith(".")) continue;
9163
- const full = path24.join(dir, entry.name);
9934
+ const full = path26.join(dir, entry.name);
9164
9935
  if (entry.isDirectory()) {
9165
9936
  count += countFiles(full);
9166
9937
  } else if (/\.(ts|tsx|js|jsx|go|rs|py|java|kt)$/.test(entry.name)) {
@@ -9460,72 +10231,72 @@ function simpleHash(str) {
9460
10231
  }
9461
10232
 
9462
10233
  // src/engine/kumaLock.ts
9463
- import fs25 from "fs";
9464
- import path25 from "path";
10234
+ import fs26 from "fs";
10235
+ import path27 from "path";
9465
10236
  var LOCKS_DIR = ".kuma/locks";
9466
10237
  function locksDir() {
9467
- return path25.join(getProjectRoot(), LOCKS_DIR);
10238
+ return path27.join(getProjectRoot(), LOCKS_DIR);
9468
10239
  }
9469
10240
  function ensureLocksDir() {
9470
10241
  const dir = locksDir();
9471
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
10242
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
9472
10243
  }
9473
10244
  function lockPath(filePath) {
9474
10245
  const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
9475
- return path25.join(locksDir(), `${safeName}.lock.json`);
10246
+ return path27.join(locksDir(), `${safeName}.lock.json`);
9476
10247
  }
9477
10248
  function acquireLock(filePath, agentId) {
9478
10249
  ensureLocksDir();
9479
10250
  const lp = lockPath(filePath);
9480
10251
  const id = agentId || `agent-${process.pid}`;
9481
- if (fs25.existsSync(lp)) {
10252
+ if (fs26.existsSync(lp)) {
9482
10253
  try {
9483
- const existing = JSON.parse(fs25.readFileSync(lp, "utf-8"));
10254
+ const existing = JSON.parse(fs26.readFileSync(lp, "utf-8"));
9484
10255
  if (existing.agentId === id) {
9485
10256
  return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
9486
10257
  }
9487
10258
  const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
9488
10259
  if (elapsed > 300) {
9489
- fs25.unlinkSync(lp);
10260
+ fs26.unlinkSync(lp);
9490
10261
  } else {
9491
10262
  return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
9492
10263
  }
9493
10264
  } catch {
9494
- fs25.unlinkSync(lp);
10265
+ fs26.unlinkSync(lp);
9495
10266
  }
9496
10267
  }
9497
10268
  const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
9498
- fs25.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
10269
+ fs26.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
9499
10270
  return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
9500
10271
  }
9501
10272
  function releaseLock(filePath, agentId) {
9502
10273
  ensureLocksDir();
9503
10274
  const lp = lockPath(filePath);
9504
10275
  const id = agentId || `agent-${process.pid}`;
9505
- if (!fs25.existsSync(lp)) {
10276
+ if (!fs26.existsSync(lp)) {
9506
10277
  return `\u26A0\uFE0F No lock found for \`${filePath}\``;
9507
10278
  }
9508
10279
  try {
9509
- const existing = JSON.parse(fs25.readFileSync(lp, "utf-8"));
10280
+ const existing = JSON.parse(fs26.readFileSync(lp, "utf-8"));
9510
10281
  if (existing.agentId !== id) {
9511
10282
  return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
9512
10283
  }
9513
- fs25.unlinkSync(lp);
10284
+ fs26.unlinkSync(lp);
9514
10285
  return `\u{1F513} **Lock released** on \`${filePath}\``;
9515
10286
  } catch {
9516
- fs25.unlinkSync(lp);
10287
+ fs26.unlinkSync(lp);
9517
10288
  return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
9518
10289
  }
9519
10290
  }
9520
10291
  function listLocks() {
9521
10292
  ensureLocksDir();
9522
10293
  const dir = locksDir();
9523
- const files = fs25.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
10294
+ const files = fs26.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
9524
10295
  if (files.length === 0) return "\u{1F513} No active locks.";
9525
10296
  const lines = ["\u{1F512} **Active Locks:**", ""];
9526
10297
  for (const f of files) {
9527
10298
  try {
9528
- const entry = JSON.parse(fs25.readFileSync(path25.join(dir, f), "utf-8"));
10299
+ const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
9529
10300
  const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
9530
10301
  lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
9531
10302
  } catch {
@@ -9536,9 +10307,9 @@ function listLocks() {
9536
10307
  }
9537
10308
  function isLocked(filePath) {
9538
10309
  const lp = lockPath(filePath);
9539
- if (!fs25.existsSync(lp)) return { locked: false };
10310
+ if (!fs26.existsSync(lp)) return { locked: false };
9540
10311
  try {
9541
- const entry = JSON.parse(fs25.readFileSync(lp, "utf-8"));
10312
+ const entry = JSON.parse(fs26.readFileSync(lp, "utf-8"));
9542
10313
  return { locked: true, by: entry.agentId, since: entry.acquiredAt };
9543
10314
  } catch {
9544
10315
  return { locked: false };
@@ -9548,16 +10319,16 @@ function cleanStaleLocks() {
9548
10319
  ensureLocksDir();
9549
10320
  const dir = locksDir();
9550
10321
  let count = 0;
9551
- for (const f of fs25.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
10322
+ for (const f of fs26.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
9552
10323
  try {
9553
- const entry = JSON.parse(fs25.readFileSync(path25.join(dir, f), "utf-8"));
10324
+ const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
9554
10325
  if (Date.now() - entry.acquiredAt > 3e5) {
9555
- fs25.unlinkSync(path25.join(dir, f));
10326
+ fs26.unlinkSync(path27.join(dir, f));
9556
10327
  count++;
9557
10328
  }
9558
10329
  } catch {
9559
10330
  try {
9560
- fs25.unlinkSync(path25.join(dir, f));
10331
+ fs26.unlinkSync(path27.join(dir, f));
9561
10332
  count++;
9562
10333
  } catch {
9563
10334
  }
@@ -9905,13 +10676,13 @@ function formatConfidence(report, target) {
9905
10676
  }
9906
10677
 
9907
10678
  // src/engine/kumaDNA.ts
9908
- import path26 from "path";
10679
+ import path28 from "path";
9909
10680
  async function generateDNA() {
9910
10681
  try {
9911
10682
  const db = await getDb();
9912
10683
  const summary = sessionMemory.getSummary();
9913
10684
  const root = getProjectRoot();
9914
- const projectName = path26.basename(root);
10685
+ const projectName = path28.basename(root);
9915
10686
  let totalFiles = 0, totalFunctions = 0, totalTests = 0;
9916
10687
  try {
9917
10688
  const filesR = db.exec("SELECT COUNT(*) as c FROM nodes WHERE type = 'file'");
@@ -10760,14 +11531,14 @@ async function handleSafetyCheck(params) {
10760
11531
  }
10761
11532
 
10762
11533
  // src/engine/kumaCollective.ts
10763
- import fs26 from "fs";
10764
- import path27 from "path";
11534
+ import fs27 from "fs";
11535
+ import path29 from "path";
10765
11536
  var DEFAULT_ENDPOINT = process.env.KUMA_COLLECTIVE_URL || "";
10766
11537
  function getSyncConfig() {
10767
11538
  try {
10768
- const configPath = path27.join(getProjectRoot(), ".kuma", "config.json");
10769
- if (fs26.existsSync(configPath)) {
10770
- const raw = fs26.readFileSync(configPath, "utf-8");
11539
+ const configPath = path29.join(getProjectRoot(), ".kuma", "config.json");
11540
+ if (fs27.existsSync(configPath)) {
11541
+ const raw = fs27.readFileSync(configPath, "utf-8");
10771
11542
  const config = JSON.parse(raw);
10772
11543
  if (config.collective) {
10773
11544
  return {
@@ -10787,12 +11558,12 @@ function getSyncConfig() {
10787
11558
  }
10788
11559
  function getInstanceId() {
10789
11560
  try {
10790
- const idPath = path27.join(getProjectRoot(), ".kuma", ".instance-id");
10791
- if (fs26.existsSync(idPath)) {
10792
- return fs26.readFileSync(idPath, "utf-8").trim();
11561
+ const idPath = path29.join(getProjectRoot(), ".kuma", ".instance-id");
11562
+ if (fs27.existsSync(idPath)) {
11563
+ return fs27.readFileSync(idPath, "utf-8").trim();
10793
11564
  }
10794
11565
  const id = `anon-${Math.random().toString(36).slice(2, 10)}`;
10795
- fs26.writeFileSync(idPath, id, "utf-8");
11566
+ fs27.writeFileSync(idPath, id, "utf-8");
10796
11567
  return id;
10797
11568
  } catch {
10798
11569
  return "anon-unknown";
@@ -10899,13 +11670,13 @@ function exportAnonymizedPatterns() {
10899
11670
  function detectProjectLanguage() {
10900
11671
  try {
10901
11672
  const root = getProjectRoot();
10902
- if (fs26.existsSync(path27.join(root, "go.mod"))) return "go";
10903
- if (fs26.existsSync(path27.join(root, "Cargo.toml"))) return "rust";
10904
- if (fs26.existsSync(path27.join(root, "composer.json"))) return "php";
10905
- if (fs26.existsSync(path27.join(root, "pyproject.toml")) || fs26.existsSync(path27.join(root, "requirements.txt"))) return "python";
10906
- if (fs26.existsSync(path27.join(root, "Gemfile"))) return "ruby";
10907
- if (fs26.existsSync(path27.join(root, "pom.xml")) || fs26.existsSync(path27.join(root, "build.gradle"))) return "java";
10908
- if (fs26.existsSync(path27.join(root, "package.json"))) return "typescript";
11673
+ if (fs27.existsSync(path29.join(root, "go.mod"))) return "go";
11674
+ if (fs27.existsSync(path29.join(root, "Cargo.toml"))) return "rust";
11675
+ if (fs27.existsSync(path29.join(root, "composer.json"))) return "php";
11676
+ if (fs27.existsSync(path29.join(root, "pyproject.toml")) || fs27.existsSync(path29.join(root, "requirements.txt"))) return "python";
11677
+ if (fs27.existsSync(path29.join(root, "Gemfile"))) return "ruby";
11678
+ if (fs27.existsSync(path29.join(root, "pom.xml")) || fs27.existsSync(path29.join(root, "build.gradle"))) return "java";
11679
+ if (fs27.existsSync(path29.join(root, "package.json"))) return "typescript";
10909
11680
  return "unknown";
10910
11681
  } catch {
10911
11682
  return "unknown";
@@ -11061,9 +11832,9 @@ function formatCollectivePatterns(patterns) {
11061
11832
  }
11062
11833
 
11063
11834
  // src/engine/kumaMarketplace.ts
11064
- import { execSync as execSync9 } from "child_process";
11065
- import fs27 from "fs";
11066
- import path28 from "path";
11835
+ import { execSync as execSync12 } from "child_process";
11836
+ import fs28 from "fs";
11837
+ import path30 from "path";
11067
11838
  var BUILT_IN_TEMPLATES = [
11068
11839
  // ── Framework Web (JS/TS) ──
11069
11840
  {
@@ -11278,7 +12049,7 @@ async function listMarketplace() {
11278
12049
  `\u{1F4E6} ${templates.length} template(s) available`,
11279
12050
  ""
11280
12051
  ];
11281
- const LANG_MAP = [
12052
+ const LANG_MAP2 = [
11282
12053
  { lang: "typescript", keywords: ["typescript", "hono", "elysia", "tanstack", "zustand", "prisma", "drizzle", "shadcn"] },
11283
12054
  { lang: "javascript", keywords: ["javascript", "express"] },
11284
12055
  { lang: "go", keywords: ["go", "gin"] },
@@ -11298,7 +12069,7 @@ async function listMarketplace() {
11298
12069
  general: "\u{1F4E6}"
11299
12070
  };
11300
12071
  function detectLang(t) {
11301
- for (const entry of LANG_MAP) {
12072
+ for (const entry of LANG_MAP2) {
11302
12073
  if (entry.keywords.some((k) => t.tags.some((tag) => tag.includes(k)))) {
11303
12074
  return entry.lang;
11304
12075
  }
@@ -11343,17 +12114,17 @@ function scanNpmTemplates() {
11343
12114
  const found = [];
11344
12115
  try {
11345
12116
  const root = getProjectRoot();
11346
- const nodeModulesPath = path28.join(root, "node_modules", "@kuma-templates");
11347
- if (!fs27.existsSync(nodeModulesPath)) return found;
11348
- const packages = fs27.readdirSync(nodeModulesPath);
12117
+ const nodeModulesPath = path30.join(root, "node_modules", "@kuma-templates");
12118
+ if (!fs28.existsSync(nodeModulesPath)) return found;
12119
+ const packages = fs28.readdirSync(nodeModulesPath);
11349
12120
  for (const pkg of packages) {
11350
- const pkgPath = path28.join(nodeModulesPath, pkg, "package.json");
11351
- if (!fs27.existsSync(pkgPath)) continue;
11352
- const pkgJson = JSON.parse(fs27.readFileSync(pkgPath, "utf-8"));
11353
- const templatePath = path28.join(nodeModulesPath, pkg, "template.json");
12121
+ const pkgPath = path30.join(nodeModulesPath, pkg, "package.json");
12122
+ if (!fs28.existsSync(pkgPath)) continue;
12123
+ const pkgJson = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
12124
+ const templatePath = path30.join(nodeModulesPath, pkg, "template.json");
11354
12125
  let nodeCount = 0, edgeCount = 0;
11355
- if (fs27.existsSync(templatePath)) {
11356
- const tmpl = JSON.parse(fs27.readFileSync(templatePath, "utf-8"));
12126
+ if (fs28.existsSync(templatePath)) {
12127
+ const tmpl = JSON.parse(fs28.readFileSync(templatePath, "utf-8"));
11357
12128
  nodeCount = tmpl.nodes?.length || 0;
11358
12129
  edgeCount = tmpl.edges?.length || 0;
11359
12130
  }
@@ -11438,8 +12209,8 @@ async function tryInstallFromNpm(templateId) {
11438
12209
  const name = templateId.replace(/^graph:/, "");
11439
12210
  try {
11440
12211
  const root = getProjectRoot();
11441
- const pkgPath = path28.join(root, "node_modules", "@kuma-templates", name, "template.json");
11442
- if (fs27.existsSync(pkgPath)) {
12212
+ const pkgPath = path30.join(root, "node_modules", "@kuma-templates", name, "template.json");
12213
+ if (fs28.existsSync(pkgPath)) {
11443
12214
  return await loadNpmTemplate(pkgPath, name);
11444
12215
  }
11445
12216
  } catch {
@@ -11447,14 +12218,14 @@ async function tryInstallFromNpm(templateId) {
11447
12218
  try {
11448
12219
  const npmPackage = `@kuma-templates/${name}`;
11449
12220
  const root = getProjectRoot();
11450
- execSync9(`npm install --no-save ${npmPackage}`, {
12221
+ execSync12(`npm install --no-save ${npmPackage}`, {
11451
12222
  cwd: root,
11452
12223
  encoding: "utf-8",
11453
12224
  timeout: 3e4,
11454
12225
  stdio: "pipe"
11455
12226
  });
11456
- const templatePath = path28.join(root, "node_modules", "@kuma-templates", name, "template.json");
11457
- if (fs27.existsSync(templatePath)) {
12227
+ const templatePath = path30.join(root, "node_modules", "@kuma-templates", name, "template.json");
12228
+ if (fs28.existsSync(templatePath)) {
11458
12229
  return await loadNpmTemplate(templatePath, name);
11459
12230
  }
11460
12231
  return [
@@ -11472,7 +12243,7 @@ async function tryInstallFromNpm(templateId) {
11472
12243
  }
11473
12244
  }
11474
12245
  async function loadNpmTemplate(templatePath, name) {
11475
- const raw = fs27.readFileSync(templatePath, "utf-8");
12246
+ const raw = fs28.readFileSync(templatePath, "utf-8");
11476
12247
  const template = JSON.parse(raw);
11477
12248
  const db = await getDb();
11478
12249
  let nodeCount = 0;
@@ -11842,18 +12613,29 @@ async function handleInit(action, params) {
11842
12613
  }
11843
12614
  async function handleCore(action, params) {
11844
12615
  switch (action) {
11845
- case "grep":
11846
- return await handleSmartGrep(params);
11847
- case "read":
11848
- return await handleSmartFilePicker(params);
12616
+ case "grep": {
12617
+ const p = { ...params, outputMode: params.grepOutputMode || params.outputMode || "rich" };
12618
+ return await handleSmartGrep(p);
12619
+ }
12620
+ case "read": {
12621
+ const readParams = { ...params, outputMode: params.readOutputMode || params.outputMode || "rich" };
12622
+ if (params.filePaths) {
12623
+ readParams.files = params.filePaths;
12624
+ }
12625
+ return await handleSmartFilePicker(readParams);
12626
+ }
11849
12627
  case "edit":
11850
12628
  return await handlePreciseDiffEditor(params);
11851
12629
  case "batch":
11852
12630
  return await handleBatchFileWriter(params);
11853
12631
  case "lsp":
11854
12632
  return await handleLspQuery({ ...params, action: params.lspAction });
12633
+ case "find":
12634
+ return await handleKumaFind(params);
12635
+ case "stats":
12636
+ return await handleKumaStats(params);
11855
12637
  default:
11856
- return `\u26A0\uFE0F Unknown action "${action}" for kuma_core. Use: grep, read, edit, batch, lsp`;
12638
+ return `\u26A0\uFE0F Unknown action "${action}" for kuma_core. Use: grep, read, edit, batch, lsp, find, stats`;
11857
12639
  }
11858
12640
  }
11859
12641
  async function handleVerify(action, params) {
@@ -12237,26 +13019,34 @@ function registerAllTools(server) {
12237
13019
  });
12238
13020
  server.tool(
12239
13021
  "kuma_core",
12240
- "Core coding tools: grep (search), read (file), edit (search-and-replace), batch (create files), lsp (rename/reference).",
13022
+ "Core coding tools: grep (search code), read (open file), edit (search-and-replace), batch (create files), lsp (rename/reference), find (files by name/pattern), stats (file/dir/project statistics).",
12241
13023
  {
12242
- action: z.enum(["grep", "read", "edit", "batch", "lsp"]).describe("Action: grep=search code, read=open file, edit=edit with safety, batch=create files, lsp=semantic analysis"),
13024
+ action: z.enum(["grep", "read", "edit", "batch", "lsp", "find", "stats"]).describe("Action: grep=search code, read=open file, edit=edit with safety, batch=create files, lsp=semantic analysis, find=files by name, stats=file/dir/project stats"),
12243
13025
  // grep params
12244
13026
  query: z.string().optional().describe("Regex pattern for grep action"),
13027
+ queries: z.array(z.string()).optional().describe("Multiple regex patterns (OR'd together, single pass)"),
12245
13028
  targetFolder: z.string().optional().describe("Target folder for grep"),
12246
13029
  maxResults: z.number().min(1).max(100).optional().default(30).describe("Max results"),
12247
13030
  extensions: z.array(z.string()).optional().describe("File extensions filter"),
13031
+ contextLines: z.number().min(0).max(20).optional().default(1).describe("Context lines around match (grep) or jump-to-line (read)"),
13032
+ filenamesOnly: z.boolean().optional().default(false).describe("Only show matching filenames (like grep -l)"),
13033
+ countOnly: z.boolean().optional().default(false).describe("Only show match count per file (like grep -c)"),
13034
+ grepOutputMode: z.enum(["rich", "raw", "json"]).optional().default("rich").describe("Output format for grep: rich=emojis, raw=compact, json=parseable"),
12248
13035
  // read params
12249
13036
  filePath: z.string().optional().describe("File path for read/edit/batch actions"),
13037
+ filePaths: z.array(z.string()).optional().describe("Multiple file paths to read in one call"),
12250
13038
  startLine: z.number().min(1).optional().describe("Start line (1-indexed) for read"),
12251
13039
  endLine: z.number().min(1).optional().describe("End line (1-indexed) for read"),
12252
13040
  chunkStrategy: z.enum(["full", "smart", "outline"]).optional().default("smart").describe("Read strategy"),
12253
- // edit params
13041
+ readOutputMode: z.enum(["rich", "raw"]).optional().default("rich").describe("Output format for read: rich=line numbers, raw=content only"),
13042
+ // edit params — BATCH SUPPORTED: pass multiple edits[] in 1 call (faster than calling edit 5x)
12254
13043
  edits: z.array(z.object({
12255
13044
  searchBlock: z.string().min(1).describe("Code to replace"),
12256
13045
  replaceBlock: z.string().describe("Replacement code"),
12257
13046
  allowMultiple: z.boolean().optional().default(false).describe("Allow multiple replacements"),
12258
13047
  fuzzyThreshold: z.number().min(0).max(1).optional().default(0.85).describe("Fuzzy threshold")
12259
- })).min(1).max(10).optional().describe("Array of edits (for edit action)"),
13048
+ })).min(1).max(10).optional().describe("Array of edits (for edit action) \u2014 batch multiple edits in 1 call for speed"),
13049
+ safe: z.boolean().optional().default(true).describe("Safe mode: true=create backup + safety checks (default), false=skip backup for speed (40x faster, no rollback)"),
12260
13050
  dryRun: z.boolean().optional().default(false).describe("Preview without writing"),
12261
13051
  version: z.union([z.number().min(1), z.literal("list")]).optional().describe("Backup version for rollback"),
12262
13052
  scope: z.enum(["file", "dir", "edit-id", "commit"]).optional().describe("Rollback scope"),
@@ -12267,6 +13057,12 @@ function registerAllTools(server) {
12267
13057
  content: z.string().describe("File content"),
12268
13058
  instructions: z.string().min(1).describe("Reason for creating")
12269
13059
  })).min(1).max(15).optional().describe("Array of files (for batch action)"),
13060
+ // find params
13061
+ name: z.string().optional().describe("File name glob pattern for find action (e.g. '*seed*', '*.tsx')"),
13062
+ path: z.string().optional().describe("Path glob pattern for find action (e.g. '**/utils/**', '*/seed*')"),
13063
+ type: z.enum(["file", "dir", "both"]).optional().default("file").describe("Type filter for find: file=files only, dir=dirs only"),
13064
+ // stats params
13065
+ target: z.enum(["file", "dir", "project"]).optional().default("project").describe("Stats target: file=single file, dir=directory, project=entire project"),
12270
13066
  // lsp params
12271
13067
  line: z.number().min(0).optional().describe("Line number (0-indexed) for LSP"),
12272
13068
  character: z.number().min(0).optional().describe("Character position (0-indexed) for LSP"),
@@ -12602,23 +13398,23 @@ async function main() {
12602
13398
  });
12603
13399
  const output = formatInitResults(results);
12604
13400
  console.log(output);
12605
- const fs28 = await import("fs");
12606
- const path29 = await import("path");
12607
- const matchaSkills = path29.resolve(process.cwd(), "skills/matcha/SKILL.md");
12608
- const matchaAgents = path29.resolve(
13401
+ const fs29 = await import("fs");
13402
+ const path31 = await import("path");
13403
+ const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
13404
+ const matchaAgents = path31.resolve(
12609
13405
  process.cwd(),
12610
13406
  ".agents/skills/matcha/SKILL.md"
12611
13407
  );
12612
- const matchaRootSkills = path29.resolve(
13408
+ const matchaRootSkills = path31.resolve(
12613
13409
  process.cwd(),
12614
13410
  "skills/matcha/SKILL.md"
12615
13411
  );
12616
- const matchaAgentsMd = path29.resolve(process.cwd(), "AGENTS.md");
12617
- const matchaWindsurfRules = path29.resolve(
13412
+ const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
13413
+ const matchaWindsurfRules = path31.resolve(
12618
13414
  process.cwd(),
12619
13415
  ".windsurfrules"
12620
13416
  );
12621
- if (fs28.existsSync(matchaSkills) || fs28.existsSync(matchaAgents) || fs28.existsSync(matchaRootSkills) || fs28.existsSync(matchaAgentsMd) || fs28.existsSync(matchaWindsurfRules)) {
13417
+ if (fs29.existsSync(matchaSkills) || fs29.existsSync(matchaAgents) || fs29.existsSync(matchaRootSkills) || fs29.existsSync(matchaAgentsMd) || fs29.existsSync(matchaWindsurfRules)) {
12622
13418
  console.error(
12623
13419
  "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
12624
13420
  );
@@ -12632,13 +13428,13 @@ async function main() {
12632
13428
  (async () => {
12633
13429
  try {
12634
13430
  const { generateInitMdContent } = await import("./init-INY6XOQT.js");
12635
- const fs28 = await import("fs");
12636
- const path29 = await import("path");
12637
- const initMdPath = path29.resolve(process.cwd(), ".kuma/init.md");
12638
- if (!fs28.existsSync(initMdPath)) {
12639
- const kumaDir = path29.dirname(initMdPath);
12640
- if (!fs28.existsSync(kumaDir)) fs28.mkdirSync(kumaDir, { recursive: true });
12641
- fs28.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
13431
+ const fs29 = await import("fs");
13432
+ const path31 = await import("path");
13433
+ const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
13434
+ if (!fs29.existsSync(initMdPath)) {
13435
+ const kumaDir = path31.dirname(initMdPath);
13436
+ if (!fs29.existsSync(kumaDir)) fs29.mkdirSync(kumaDir, { recursive: true });
13437
+ fs29.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
12642
13438
  console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
12643
13439
  }
12644
13440
  } catch (err) {
@@ -12649,8 +13445,8 @@ async function main() {
12649
13445
  try {
12650
13446
  const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-HROEABDO.js");
12651
13447
  const { generateSkill, getSecondaryFiles } = await import("./skillGenerator-F6YO4H5D.js");
12652
- const fs28 = await import("fs");
12653
- const path29 = await import("path");
13448
+ const fs29 = await import("fs");
13449
+ const path31 = await import("path");
12654
13450
  const detection = detectAgent();
12655
13451
  if (!detection.primary) {
12656
13452
  console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
@@ -12658,22 +13454,22 @@ async function main() {
12658
13454
  }
12659
13455
  const agentType = detection.primary;
12660
13456
  const skillPath = getSkillPath(agentType);
12661
- const fullPath = path29.resolve(process.cwd(), skillPath);
12662
- if (fs28.existsSync(fullPath)) {
13457
+ const fullPath = path31.resolve(process.cwd(), skillPath);
13458
+ if (fs29.existsSync(fullPath)) {
12663
13459
  console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel(agentType)} \u2014 skipping`);
12664
13460
  return;
12665
13461
  }
12666
- const dir = path29.dirname(fullPath);
12667
- if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
12668
- fs28.writeFileSync(fullPath, generateSkill(agentType), "utf-8");
13462
+ const dir = path31.dirname(fullPath);
13463
+ if (!fs29.existsSync(dir)) fs29.mkdirSync(dir, { recursive: true });
13464
+ fs29.writeFileSync(fullPath, generateSkill(agentType), "utf-8");
12669
13465
  console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel(agentType)}`);
12670
13466
  const secondaryFiles = getSecondaryFiles(agentType);
12671
13467
  for (const sf of secondaryFiles) {
12672
- const sfPath = path29.resolve(process.cwd(), sf.path);
12673
- const sfDir = path29.dirname(sfPath);
12674
- if (!fs28.existsSync(sfPath)) {
12675
- if (!fs28.existsSync(sfDir)) fs28.mkdirSync(sfDir, { recursive: true });
12676
- fs28.writeFileSync(sfPath, sf.content, "utf-8");
13468
+ const sfPath = path31.resolve(process.cwd(), sf.path);
13469
+ const sfDir = path31.dirname(sfPath);
13470
+ if (!fs29.existsSync(sfPath)) {
13471
+ if (!fs29.existsSync(sfDir)) fs29.mkdirSync(sfDir, { recursive: true });
13472
+ fs29.writeFileSync(sfPath, sf.content, "utf-8");
12677
13473
  console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
12678
13474
  }
12679
13475
  }