@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.
- package/dist/index.js +1315 -519
- 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 {
|
|
91
|
-
|
|
92
|
-
|
|
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 = `${
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 (
|
|
115
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return
|
|
205
|
+
return formatRipgrepOutput(output, patterns, opts);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
if (err.status === 1) {
|
|
208
|
+
return formatEmpty(patterns[0], opts);
|
|
127
209
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
287
|
-
totalLines
|
|
288
|
-
truncated ?
|
|
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, " ")
|
|
603
|
+
return `${String(lineNum).padStart(4, " ")} | ${line}`;
|
|
294
604
|
}).join("\n");
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
315
|
-
totalLines
|
|
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) =>
|
|
319
|
-
importLines.length > 30 ?
|
|
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) =>
|
|
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:
|
|
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
|
-
|
|
358
|
-
totalLines
|
|
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, " ")
|
|
674
|
+
return `${String(s.line).padStart(4, " ")} | ${s.text}`;
|
|
366
675
|
}).join("\n");
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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:
|
|
1257
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
893
1258
|
const root = getProjectRoot();
|
|
894
1259
|
if (version === "list") {
|
|
895
|
-
const log =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
1206
|
-
import
|
|
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
|
|
1211
|
-
import
|
|
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(
|
|
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 =
|
|
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 =
|
|
1291
|
-
const subPkg = readJsonSafe(
|
|
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(
|
|
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(
|
|
1335
|
-
const pnpmWorkspace = readYamlLite(
|
|
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 =
|
|
1356
|
-
if (!
|
|
2126
|
+
const dir = path7.join(root, match[1]);
|
|
2127
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1357
2128
|
let entries = [];
|
|
1358
2129
|
try {
|
|
1359
|
-
entries =
|
|
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 =
|
|
2136
|
+
const pkgPath = path7.join(dir, entry.name, "package.json");
|
|
1366
2137
|
const subPkg = readJsonSafe(pkgPath);
|
|
1367
2138
|
if (!subPkg) continue;
|
|
1368
|
-
const workspacePath =
|
|
2139
|
+
const workspacePath = path7.join(dir, entry.name);
|
|
1369
2140
|
results.push({
|
|
1370
|
-
path:
|
|
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 (!
|
|
1387
|
-
const text =
|
|
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(
|
|
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(
|
|
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 =
|
|
1443
|
-
if (
|
|
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(
|
|
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(
|
|
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 (
|
|
1469
|
-
if (
|
|
1470
|
-
if (
|
|
1471
|
-
if (
|
|
1472
|
-
if (
|
|
1473
|
-
if (
|
|
1474
|
-
if (
|
|
1475
|
-
if (
|
|
1476
|
-
if (
|
|
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 =
|
|
2256
|
+
let currentDir = path7.resolve(dir);
|
|
1486
2257
|
while (currentDir.startsWith(root)) {
|
|
1487
|
-
if (
|
|
1488
|
-
if (
|
|
1489
|
-
if (
|
|
1490
|
-
if (
|
|
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 =
|
|
2263
|
+
currentDir = path7.dirname(currentDir);
|
|
1493
2264
|
}
|
|
1494
2265
|
return "npm";
|
|
1495
2266
|
}
|
|
1496
2267
|
function detectModuleSystem(root) {
|
|
1497
|
-
const pkg = readJsonSafe(
|
|
2268
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1498
2269
|
if (pkg?.type === "module") return "esm";
|
|
1499
|
-
const srcDir =
|
|
1500
|
-
if (
|
|
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 =
|
|
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 (
|
|
1515
|
-
if (
|
|
1516
|
-
if (
|
|
1517
|
-
if (
|
|
1518
|
-
if (
|
|
1519
|
-
if (
|
|
1520
|
-
if (
|
|
1521
|
-
if (
|
|
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 =
|
|
2294
|
+
const entries = fs6.readdirSync(root);
|
|
1524
2295
|
if (entries.some((e) => e.endsWith(".csproj"))) return "csharp";
|
|
1525
2296
|
} catch {
|
|
1526
2297
|
}
|
|
1527
|
-
if (
|
|
1528
|
-
const srcDir =
|
|
1529
|
-
if (
|
|
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 =
|
|
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(
|
|
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 (
|
|
1580
|
-
const content =
|
|
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 (
|
|
1586
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2422
|
+
const entries = fs6.readdirSync(root);
|
|
1652
2423
|
const csproj = entries.find((e) => e.endsWith(".csproj"));
|
|
1653
2424
|
if (csproj) {
|
|
1654
|
-
const content =
|
|
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 =
|
|
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(
|
|
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 (
|
|
1705
|
-
return JSON.parse(
|
|
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 =
|
|
1713
|
-
if (!
|
|
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 =
|
|
2491
|
+
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
1721
2492
|
for (const entry of entries) {
|
|
1722
|
-
const fullPath =
|
|
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 =
|
|
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 =
|
|
1922
|
-
const normalizedResolved =
|
|
1923
|
-
const normalizedRoot =
|
|
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 (!
|
|
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
|
|
2014
|
-
import
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3920
|
+
import { execSync as execSync5 } from "child_process";
|
|
3150
3921
|
function runGitCommand(command) {
|
|
3151
3922
|
const root = getProjectRoot();
|
|
3152
|
-
return
|
|
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
|
|
3281
|
-
import
|
|
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
|
|
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 <
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3355
|
-
const relativePath =
|
|
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 =
|
|
4137
|
+
subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
|
|
3367
4138
|
} catch {
|
|
3368
4139
|
}
|
|
3369
4140
|
const hasVisibleContent = subEntries.some(
|
|
3370
|
-
(e) => !shouldIgnore(e.name,
|
|
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 =
|
|
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
|
|
3413
|
-
import
|
|
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) =>
|
|
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 (
|
|
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) =>
|
|
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 (
|
|
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 =
|
|
3533
|
-
if (
|
|
3534
|
-
return JSON.parse(
|
|
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 (
|
|
3591
|
-
if (
|
|
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
|
-
|
|
3597
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4104
|
-
import
|
|
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 =
|
|
4113
|
-
if (
|
|
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
|
|
4891
|
+
for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
4121
4892
|
if (e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
4122
|
-
const full =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
4221
|
-
import
|
|
4222
|
-
import { execSync as
|
|
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 =
|
|
5019
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4249
5020
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
4250
|
-
const relativePath =
|
|
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 =
|
|
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: ${
|
|
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 =
|
|
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 =
|
|
5072
|
+
const stat = fs12.statSync(path13.join(projectRoot, entry.name));
|
|
4302
5073
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
4303
|
-
recent.push(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4333
|
-
if (
|
|
4334
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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
|
|
4418
|
-
import
|
|
4419
|
-
import { execSync as
|
|
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
|
|
5193
|
+
return path14.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
4423
5194
|
}
|
|
4424
5195
|
function ensureSnapshotDir() {
|
|
4425
5196
|
const dir = snapshotDir();
|
|
4426
|
-
if (!
|
|
4427
|
-
|
|
5197
|
+
if (!fs13.existsSync(dir)) {
|
|
5198
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
4428
5199
|
}
|
|
4429
5200
|
}
|
|
4430
5201
|
function snapshotFilePath(timestamp) {
|
|
4431
|
-
return
|
|
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
|
-
|
|
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 (!
|
|
5231
|
+
if (!fs13.existsSync(dir)) return [];
|
|
4461
5232
|
try {
|
|
4462
|
-
const files =
|
|
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 =
|
|
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 =
|
|
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
|
|
4654
|
-
import
|
|
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 =
|
|
4658
|
-
const memoriesDir =
|
|
4659
|
-
const initMdPath =
|
|
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 (
|
|
4663
|
-
rules =
|
|
5433
|
+
if (fs14.existsSync(initMdPath)) {
|
|
5434
|
+
rules = fs14.readFileSync(initMdPath, "utf-8");
|
|
4664
5435
|
}
|
|
4665
5436
|
const memories = [];
|
|
4666
|
-
if (
|
|
5437
|
+
if (fs14.existsSync(memoriesDir)) {
|
|
4667
5438
|
try {
|
|
4668
|
-
const files =
|
|
5439
|
+
const files = fs14.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
|
|
4669
5440
|
for (const file of files) {
|
|
4670
5441
|
try {
|
|
4671
|
-
const content =
|
|
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
|
|
4716
|
-
import
|
|
4717
|
-
import
|
|
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
|
|
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 =
|
|
4771
|
-
const stat =
|
|
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 =
|
|
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
|
|
4899
|
-
import
|
|
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 =
|
|
4987
|
-
if (!
|
|
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(
|
|
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 =
|
|
5008
|
-
if (!
|
|
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(
|
|
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
|
|
5124
|
-
import
|
|
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 =
|
|
5151
|
-
const yamlPath =
|
|
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 (
|
|
5155
|
-
policyContent =
|
|
5925
|
+
if (fs17.existsSync(ymlPath)) {
|
|
5926
|
+
policyContent = fs17.readFileSync(ymlPath, "utf-8");
|
|
5156
5927
|
policyPath = ymlPath;
|
|
5157
|
-
} else if (
|
|
5158
|
-
policyContent =
|
|
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 =
|
|
5276
|
-
if (
|
|
5277
|
-
const sizeKB =
|
|
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
|
|
5374
|
-
import
|
|
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 (
|
|
5419
|
-
const backupCount =
|
|
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 =
|
|
5438
|
-
if (
|
|
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
|
|
5661
|
-
import
|
|
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 =
|
|
5675
|
-
if (!
|
|
5676
|
-
|
|
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 (
|
|
5680
|
-
const buffer =
|
|
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 =
|
|
6468
|
+
const dbPath = path20.join(kumaDir, DB_FILENAME);
|
|
5698
6469
|
const data = d.export();
|
|
5699
6470
|
const buffer = Buffer.from(data);
|
|
5700
|
-
|
|
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
|
|
5808
|
-
import
|
|
5809
|
-
import
|
|
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 =
|
|
5814
|
-
if (!
|
|
5815
|
-
const stat =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5844
|
-
const result =
|
|
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 =
|
|
5878
|
-
if (!
|
|
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,
|
|
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 =
|
|
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 =
|
|
5994
|
-
return !
|
|
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
|
|
6524
|
-
import
|
|
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 =
|
|
6563
|
-
if (!
|
|
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
|
-
|
|
6626
|
-
|
|
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 (
|
|
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(
|
|
6636
|
-
const binPath =
|
|
6637
|
-
if (
|
|
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 =
|
|
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
|
|
6877
|
-
import
|
|
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 (!
|
|
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 =
|
|
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: ${
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
7745
|
+
const relPath = path23.relative(projectRoot, definition.filePath);
|
|
6975
7746
|
let lineContent = "";
|
|
6976
7747
|
try {
|
|
6977
|
-
const content =
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
7825
|
+
fs22.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
|
|
7055
7826
|
totalEdits += change.edits.length;
|
|
7056
7827
|
fileChanges.push({
|
|
7057
|
-
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
7964
|
+
const { default: fg6 } = await import("fast-glob");
|
|
7194
7965
|
const root = getProjectRoot();
|
|
7195
|
-
const tsFiles = await
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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:
|
|
8038
|
+
const { default: fg6 } = await import("fast-glob");
|
|
7268
8039
|
const root = getProjectRoot();
|
|
7269
|
-
const tsFiles = await
|
|
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 =
|
|
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 =
|
|
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
|
|
8087
|
+
import { execSync as execSync10 } from "child_process";
|
|
7317
8088
|
function getBlameForFile(filePath) {
|
|
7318
8089
|
const root = getProjectRoot();
|
|
7319
8090
|
try {
|
|
7320
|
-
const output =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
8384
|
+
const path31 = [];
|
|
7614
8385
|
while (pathStmt.step()) {
|
|
7615
8386
|
const row = pathStmt.getAsObject();
|
|
7616
|
-
|
|
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(
|
|
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
|
|
7734
|
-
import
|
|
7735
|
-
import
|
|
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 =
|
|
7827
|
-
if (!
|
|
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 =
|
|
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 (
|
|
8620
|
+
if (fs23.existsSync(path24.join(srcDir, "domain")) || fs23.existsSync(path24.join(srcDir, "entities"))) {
|
|
7850
8621
|
return getArchitectureProfile("clean-architecture");
|
|
7851
8622
|
}
|
|
7852
|
-
if (
|
|
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
|
|
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 =
|
|
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 =
|
|
7946
|
-
targetFile =
|
|
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 (!
|
|
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 (
|
|
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
|
|
8039
|
-
import
|
|
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 =
|
|
8044
|
-
if (!
|
|
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 =
|
|
8819
|
+
const files = fs24.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
8049
8820
|
for (const file of files) {
|
|
8050
8821
|
try {
|
|
8051
|
-
const content =
|
|
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
|
|
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 =
|
|
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
|
|
8966
|
-
const dir = topDir(
|
|
8967
|
-
if (focus && !
|
|
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
|
|
9009
|
-
import
|
|
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 =
|
|
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 =
|
|
9047
|
-
if (
|
|
9817
|
+
const srcDir = path26.join(root, "src");
|
|
9818
|
+
if (fs25.existsSync(srcDir)) {
|
|
9048
9819
|
try {
|
|
9049
|
-
for (const entry of
|
|
9820
|
+
for (const entry of fs25.readdirSync(srcDir, { withFileTypes: true })) {
|
|
9050
9821
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
9051
|
-
const count = countFiles(
|
|
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
|
|
9932
|
+
for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
|
|
9162
9933
|
if (entry.name.startsWith(".")) continue;
|
|
9163
|
-
const full =
|
|
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
|
|
9464
|
-
import
|
|
10234
|
+
import fs26 from "fs";
|
|
10235
|
+
import path27 from "path";
|
|
9465
10236
|
var LOCKS_DIR = ".kuma/locks";
|
|
9466
10237
|
function locksDir() {
|
|
9467
|
-
return
|
|
10238
|
+
return path27.join(getProjectRoot(), LOCKS_DIR);
|
|
9468
10239
|
}
|
|
9469
10240
|
function ensureLocksDir() {
|
|
9470
10241
|
const dir = locksDir();
|
|
9471
|
-
if (!
|
|
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
|
|
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 (
|
|
10252
|
+
if (fs26.existsSync(lp)) {
|
|
9482
10253
|
try {
|
|
9483
|
-
const existing = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
10265
|
+
fs26.unlinkSync(lp);
|
|
9495
10266
|
}
|
|
9496
10267
|
}
|
|
9497
10268
|
const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
|
|
9498
|
-
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
10284
|
+
fs26.unlinkSync(lp);
|
|
9514
10285
|
return `\u{1F513} **Lock released** on \`${filePath}\``;
|
|
9515
10286
|
} catch {
|
|
9516
|
-
|
|
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 =
|
|
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(
|
|
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 (!
|
|
10310
|
+
if (!fs26.existsSync(lp)) return { locked: false };
|
|
9540
10311
|
try {
|
|
9541
|
-
const entry = JSON.parse(
|
|
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
|
|
10322
|
+
for (const f of fs26.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
|
|
9552
10323
|
try {
|
|
9553
|
-
const entry = JSON.parse(
|
|
10324
|
+
const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
|
|
9554
10325
|
if (Date.now() - entry.acquiredAt > 3e5) {
|
|
9555
|
-
|
|
10326
|
+
fs26.unlinkSync(path27.join(dir, f));
|
|
9556
10327
|
count++;
|
|
9557
10328
|
}
|
|
9558
10329
|
} catch {
|
|
9559
10330
|
try {
|
|
9560
|
-
|
|
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
|
|
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 =
|
|
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
|
|
10764
|
-
import
|
|
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 =
|
|
10769
|
-
if (
|
|
10770
|
-
const raw =
|
|
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 =
|
|
10791
|
-
if (
|
|
10792
|
-
return
|
|
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
|
-
|
|
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 (
|
|
10903
|
-
if (
|
|
10904
|
-
if (
|
|
10905
|
-
if (
|
|
10906
|
-
if (
|
|
10907
|
-
if (
|
|
10908
|
-
if (
|
|
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
|
|
11065
|
-
import
|
|
11066
|
-
import
|
|
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
|
|
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
|
|
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 =
|
|
11347
|
-
if (!
|
|
11348
|
-
const packages =
|
|
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 =
|
|
11351
|
-
if (!
|
|
11352
|
-
const pkgJson = JSON.parse(
|
|
11353
|
-
const templatePath =
|
|
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 (
|
|
11356
|
-
const tmpl = JSON.parse(
|
|
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 =
|
|
11442
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
11457
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
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
|
-
|
|
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
|
|
12606
|
-
const
|
|
12607
|
-
const matchaSkills =
|
|
12608
|
-
const matchaAgents =
|
|
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 =
|
|
13408
|
+
const matchaRootSkills = path31.resolve(
|
|
12613
13409
|
process.cwd(),
|
|
12614
13410
|
"skills/matcha/SKILL.md"
|
|
12615
13411
|
);
|
|
12616
|
-
const matchaAgentsMd =
|
|
12617
|
-
const matchaWindsurfRules =
|
|
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 (
|
|
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
|
|
12636
|
-
const
|
|
12637
|
-
const initMdPath =
|
|
12638
|
-
if (!
|
|
12639
|
-
const kumaDir =
|
|
12640
|
-
if (!
|
|
12641
|
-
|
|
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
|
|
12653
|
-
const
|
|
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 =
|
|
12662
|
-
if (
|
|
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 =
|
|
12667
|
-
if (!
|
|
12668
|
-
|
|
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 =
|
|
12673
|
-
const sfDir =
|
|
12674
|
-
if (!
|
|
12675
|
-
if (!
|
|
12676
|
-
|
|
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
|
}
|