@plumpslabs/kuma 2.2.6 → 2.2.8
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 +975 -414
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -104,12 +104,89 @@ function isRipgrepAvailable() {
|
|
|
104
104
|
function quotePath(p) {
|
|
105
105
|
return `"${p.replace(/"/g, '\\"')}"`;
|
|
106
106
|
}
|
|
107
|
+
async function searchInSingleFile(filePath, patterns, opts) {
|
|
108
|
+
const { maxResults, contextLines, filenamesOnly, countOnly, outputMode, compact } = opts;
|
|
109
|
+
const validation = validateFilePath(filePath);
|
|
110
|
+
if (!validation.valid) {
|
|
111
|
+
return compact ? `ERR:${filePath} - invalid path` : `Error: ${validation.error.message}`;
|
|
112
|
+
}
|
|
113
|
+
const resolvedPath = validation.resolvedPath;
|
|
114
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
115
|
+
return compact ? `ERR:not found ${filePath}` : `Error: File not found: ${filePath}`;
|
|
116
|
+
}
|
|
117
|
+
const stat = fs.statSync(resolvedPath);
|
|
118
|
+
if (stat.size > 5e5) {
|
|
119
|
+
return compact ? `ERR:too large ${filePath}` : `Error: File too large (${(stat.size / 1024).toFixed(0)}KB). Use a more specific query.`;
|
|
120
|
+
}
|
|
121
|
+
if (isBinaryFile(resolvedPath)) {
|
|
122
|
+
return compact ? `ERR:binary ${filePath}` : `Error: Cannot search binary file: ${filePath}`;
|
|
123
|
+
}
|
|
124
|
+
const projectRoot = getProjectRoot();
|
|
125
|
+
const relativePath = resolvedPath.startsWith(projectRoot + "/") ? resolvedPath.slice(projectRoot.length + 1) : resolvedPath;
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
128
|
+
const lines = content.split("\n");
|
|
129
|
+
const regex = createCombinedRegex(patterns);
|
|
130
|
+
const results = [];
|
|
131
|
+
const maxFileResults = Math.min(maxResults, 100);
|
|
132
|
+
let matchCount = 0;
|
|
133
|
+
for (let i = 0; i < lines.length; i++) {
|
|
134
|
+
if (results.length >= maxFileResults) break;
|
|
135
|
+
if (regex.test(lines[i])) {
|
|
136
|
+
matchCount++;
|
|
137
|
+
if (filenamesOnly) {
|
|
138
|
+
return relativePath;
|
|
139
|
+
}
|
|
140
|
+
if (countOnly) continue;
|
|
141
|
+
const ctxLines = [];
|
|
142
|
+
const startCtx = Math.max(0, i - contextLines);
|
|
143
|
+
const endCtx = Math.min(lines.length - 1, i + contextLines);
|
|
144
|
+
for (let ci = startCtx; ci <= endCtx; ci++) {
|
|
145
|
+
const prefix = ci === i ? ">" : " ";
|
|
146
|
+
ctxLines.push(`${prefix}${ci + 1}: ${lines[ci].substring(0, 200)}`);
|
|
147
|
+
}
|
|
148
|
+
results.push({
|
|
149
|
+
file: relativePath,
|
|
150
|
+
line: i + 1,
|
|
151
|
+
content: ctxLines.join("\n")
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (countOnly) {
|
|
156
|
+
const counts = `${filePath}:${matchCount}`;
|
|
157
|
+
if (outputMode === "json") return JSON.stringify({ [filePath]: matchCount });
|
|
158
|
+
if (compact) return counts;
|
|
159
|
+
return `\u{1F4CA} Count results for "${patterns[0]}":
|
|
160
|
+
${counts}`;
|
|
161
|
+
}
|
|
162
|
+
if (matchCount === 0) {
|
|
163
|
+
if (outputMode === "json") return JSON.stringify({ query: patterns[0], matches: 0 });
|
|
164
|
+
if (compact) return `0:${patterns[0]}`;
|
|
165
|
+
return `\u{1F50D} No matches for "${patterns[0]}" in ${filePath}.`;
|
|
166
|
+
}
|
|
167
|
+
if (compact || outputMode === "raw") {
|
|
168
|
+
return results.map((r) => `${r.file}:${r.line}:${r.content.split("\n")[0].replace(/^[>\s]+\d+:\s*/, "")}`).join("\n");
|
|
169
|
+
}
|
|
170
|
+
if (outputMode === "json") {
|
|
171
|
+
return JSON.stringify(results);
|
|
172
|
+
}
|
|
173
|
+
const header = `\u{1F50D} Smart Grep: "${patterns[0]}" \u2014 ${filePath}
|
|
174
|
+
\u{1F4C1} ${results.length} matches
|
|
175
|
+
`;
|
|
176
|
+
const body = results.map((r, i) => `[${i + 1}] \u{1F4C4} ${r.file}:${r.line}
|
|
177
|
+
${r.content}`).join("\n");
|
|
178
|
+
return limitLines(`${header}${body}`, 150);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
return compact ? `ERR:reading ${filePath}` : `Error reading file "${filePath}": ${err instanceof Error ? err.message : String(err)}`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
107
183
|
async function handleSmartGrep(params) {
|
|
108
184
|
const {
|
|
109
185
|
query,
|
|
110
186
|
queries,
|
|
111
187
|
targetFolder,
|
|
112
188
|
maxResults = 30,
|
|
189
|
+
filePath,
|
|
113
190
|
extensions,
|
|
114
191
|
contextLines = 1,
|
|
115
192
|
filenamesOnly = false,
|
|
@@ -121,7 +198,7 @@ async function handleSmartGrep(params) {
|
|
|
121
198
|
if (patterns.length === 0 || patterns[0].length < 1) {
|
|
122
199
|
return compact ? "ERR:query required" : "Error: 'query' or 'queries' parameter is required.";
|
|
123
200
|
}
|
|
124
|
-
const cacheKey = `${patterns.join("||")}:${targetFolder ?? "root"}:${maxResults}:${contextLines}:${filenamesOnly}:${countOnly}:${outputMode}:${extensions ? extensions.join(",") : ""}`;
|
|
201
|
+
const cacheKey = `${patterns.join("||")}:${filePath ?? ""}:${targetFolder ?? "root"}:${maxResults}:${contextLines}:${filenamesOnly}:${countOnly}:${outputMode}:${extensions ? extensions.join(",") : ""}`;
|
|
125
202
|
const cached = grepCache.get(cacheKey);
|
|
126
203
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
127
204
|
sessionMemory.recordToolCall("smart_grep", { query: patterns[0], cached: true });
|
|
@@ -129,7 +206,16 @@ async function handleSmartGrep(params) {
|
|
|
129
206
|
}
|
|
130
207
|
const projectRoot = getProjectRoot();
|
|
131
208
|
let result;
|
|
132
|
-
if (
|
|
209
|
+
if (filePath) {
|
|
210
|
+
result = await searchInSingleFile(filePath, patterns, {
|
|
211
|
+
maxResults,
|
|
212
|
+
contextLines,
|
|
213
|
+
filenamesOnly,
|
|
214
|
+
countOnly,
|
|
215
|
+
outputMode,
|
|
216
|
+
compact
|
|
217
|
+
});
|
|
218
|
+
} else if (isRipgrepAvailable()) {
|
|
133
219
|
result = await tryRipgrep(patterns, {
|
|
134
220
|
projectRoot,
|
|
135
221
|
targetFolder,
|
|
@@ -760,11 +846,48 @@ var CircuitBreakerStore = class {
|
|
|
760
846
|
var circuitBreaker = new CircuitBreakerStore();
|
|
761
847
|
|
|
762
848
|
// src/tools/preciseDiffEditor.ts
|
|
849
|
+
function fastApplyEdit(content, edit) {
|
|
850
|
+
const { searchBlock, replaceBlock, allowMultiple = false } = edit;
|
|
851
|
+
const count = countOccurrences(content, searchBlock);
|
|
852
|
+
if (count === 0) {
|
|
853
|
+
return {
|
|
854
|
+
success: false,
|
|
855
|
+
matched: 0,
|
|
856
|
+
replaced: 0,
|
|
857
|
+
error: `DIFF_MISMATCH: searchBlock not found (fast mode - exact match only)`,
|
|
858
|
+
details: content
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const newContent = allowMultiple ? replaceAll(content, searchBlock, replaceBlock) : content.replace(searchBlock, replaceBlock);
|
|
862
|
+
return {
|
|
863
|
+
success: true,
|
|
864
|
+
matched: count,
|
|
865
|
+
replaced: allowMultiple ? count : 1,
|
|
866
|
+
details: newContent
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function formatFastEditResult(results, filePath) {
|
|
870
|
+
const successCount = results.filter((r) => r.success).length;
|
|
871
|
+
const failCount = results.filter((r) => !r.success).length;
|
|
872
|
+
if (failCount === 0) {
|
|
873
|
+
return `\u26A1 Fast edit: ${filePath} \u2014 ${successCount} edits applied.`;
|
|
874
|
+
}
|
|
875
|
+
const lines = [`\u26A1 Fast edit: ${filePath}`];
|
|
876
|
+
for (let i = 0; i < results.length; i++) {
|
|
877
|
+
const r = results[i];
|
|
878
|
+
if (r.success) {
|
|
879
|
+
lines.push(` [${i + 1}] \u2705 ${r.matched}x matched, ${r.replaced}x replaced`);
|
|
880
|
+
} else {
|
|
881
|
+
lines.push(` [${i + 1}] \u274C ${r.error}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return lines.join("\n");
|
|
885
|
+
}
|
|
763
886
|
function generateEditId() {
|
|
764
887
|
return `edit-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
765
888
|
}
|
|
766
889
|
async function handlePreciseDiffEditor(params) {
|
|
767
|
-
const { filePath, edits, dryRun = false, action, scope } = params;
|
|
890
|
+
const { filePath, edits, safe = true, dryRun = false, action, scope } = params;
|
|
768
891
|
if (action === "rollback") {
|
|
769
892
|
return handleRollbackEdit({ filePath, version: params.version, scope: scope || "file", editId: params.editId });
|
|
770
893
|
}
|
|
@@ -776,11 +899,13 @@ async function handlePreciseDiffEditor(params) {
|
|
|
776
899
|
return `Error: ${validation.error.message}`;
|
|
777
900
|
}
|
|
778
901
|
const resolvedPath = validation.resolvedPath;
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
902
|
+
if (safe) {
|
|
903
|
+
const cbResult = circuitBreaker.check("precise_diff_editor", { filePath });
|
|
904
|
+
if (!cbResult.allowed) {
|
|
905
|
+
return `\u26A0\uFE0F ${cbResult.reason}
|
|
782
906
|
|
|
783
907
|
Try reading the file first with smart_file_picker to verify current content.`;
|
|
908
|
+
}
|
|
784
909
|
}
|
|
785
910
|
if (!fs3.existsSync(resolvedPath)) {
|
|
786
911
|
return `Error: File not found: "${filePath}".
|
|
@@ -792,6 +917,22 @@ Use batch_file_writer to create a new file.`;
|
|
|
792
917
|
const results = [];
|
|
793
918
|
for (let i = 0; i < edits.length; i++) {
|
|
794
919
|
const edit = edits[i];
|
|
920
|
+
if (!safe) {
|
|
921
|
+
const result2 = fastApplyEdit(currentContent, edit);
|
|
922
|
+
if (result2.success) {
|
|
923
|
+
fs3.writeFileSync(resolvedPath, result2.details, "utf-8");
|
|
924
|
+
currentContent = result2.details;
|
|
925
|
+
sessionMemory.recordToolCall("precise_diff_editor", {
|
|
926
|
+
filePath,
|
|
927
|
+
editIndex: i,
|
|
928
|
+
fast: true,
|
|
929
|
+
success: true
|
|
930
|
+
});
|
|
931
|
+
sessionMemory.addModifiedFile(filePath);
|
|
932
|
+
}
|
|
933
|
+
results.push(result2);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
795
936
|
const result = applyEdit(currentContent, edit, resolvedPath, i, dryRun);
|
|
796
937
|
if (result.success) {
|
|
797
938
|
if (!dryRun) {
|
|
@@ -817,6 +958,9 @@ Use batch_file_writer to create a new file.`;
|
|
|
817
958
|
if (dryRun) {
|
|
818
959
|
return formatDryRunResult(results, filePath, originalContent);
|
|
819
960
|
}
|
|
961
|
+
if (!safe) {
|
|
962
|
+
return formatFastEditResult(results, filePath);
|
|
963
|
+
}
|
|
820
964
|
return formatDiffResult(results, filePath);
|
|
821
965
|
} catch (err) {
|
|
822
966
|
return `Error editing file "${filePath}": ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -1196,10 +1340,10 @@ File "${filePath}" restored from backup: "${relBackupPath}".`;
|
|
|
1196
1340
|
}
|
|
1197
1341
|
async function handleCommitRollback(filePath, version) {
|
|
1198
1342
|
try {
|
|
1199
|
-
const { execSync:
|
|
1343
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
1200
1344
|
const root = getProjectRoot();
|
|
1201
1345
|
if (version === "list") {
|
|
1202
|
-
const log =
|
|
1346
|
+
const log = execSync13("git log --oneline -20", { cwd: root, encoding: "utf-8" });
|
|
1203
1347
|
const lines = log.trim().split("\n").map((l) => ` ${l}`).join("\n");
|
|
1204
1348
|
return [
|
|
1205
1349
|
`\u{1F4CB} **Recent Commits:**`,
|
|
@@ -1209,7 +1353,7 @@ async function handleCommitRollback(filePath, version) {
|
|
|
1209
1353
|
`\u{1F4A1} Use { action: "rollback", scope: "commit", version: <N> } to restore a specific commit.`
|
|
1210
1354
|
].join("\n");
|
|
1211
1355
|
}
|
|
1212
|
-
const status =
|
|
1356
|
+
const status = execSync13("git status --porcelain", { cwd: root, encoding: "utf-8" }).trim();
|
|
1213
1357
|
if (status) {
|
|
1214
1358
|
return [
|
|
1215
1359
|
`\u26A0\uFE0F **Uncommitted changes detected.**`,
|
|
@@ -1230,10 +1374,10 @@ async function handleCommitRollback(filePath, version) {
|
|
|
1230
1374
|
target = "HEAD~1";
|
|
1231
1375
|
}
|
|
1232
1376
|
if (filePath) {
|
|
1233
|
-
|
|
1377
|
+
execSync13(`git checkout ${target} -- "${filePath}"`, { cwd: root, encoding: "utf-8" });
|
|
1234
1378
|
return `\u2705 **Commit Rollback** \u2014 File "${filePath}" restored from ${target}.`;
|
|
1235
1379
|
} else {
|
|
1236
|
-
|
|
1380
|
+
execSync13(`git checkout ${target}`, { cwd: root, encoding: "utf-8" });
|
|
1237
1381
|
return `\u2705 **Commit Rollback** \u2014 Project restored to ${target}.`;
|
|
1238
1382
|
}
|
|
1239
1383
|
} catch (err) {
|
|
@@ -1509,13 +1653,419 @@ function formatBatchResult(results, totalRequested) {
|
|
|
1509
1653
|
return lines.join("\n");
|
|
1510
1654
|
}
|
|
1511
1655
|
|
|
1512
|
-
// src/tools/
|
|
1513
|
-
import
|
|
1656
|
+
// src/tools/kumaFind.ts
|
|
1657
|
+
import fg2 from "fast-glob";
|
|
1658
|
+
import path5 from "path";
|
|
1659
|
+
import { execSync as execSync2 } from "child_process";
|
|
1660
|
+
var IGNORE_PATTERNS2 = [
|
|
1661
|
+
"**/node_modules/**",
|
|
1662
|
+
"**/.next/**",
|
|
1663
|
+
"**/dist/**",
|
|
1664
|
+
"**/build/**",
|
|
1665
|
+
"**/.git/**",
|
|
1666
|
+
"**/.cache/**",
|
|
1667
|
+
"**/.kuma/backups/**",
|
|
1668
|
+
"**/coverage/**",
|
|
1669
|
+
"**/.nyc_output/**"
|
|
1670
|
+
];
|
|
1671
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
1672
|
+
var findCache = /* @__PURE__ */ new Map();
|
|
1673
|
+
var rgAvailable2 = null;
|
|
1674
|
+
function isRipgrepAvailable2() {
|
|
1675
|
+
if (process.env.KUMA_DISABLE_RG === "1" || process.env.KUMA_DISABLE_RG === "true") {
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
if (rgAvailable2 !== null) return rgAvailable2;
|
|
1679
|
+
try {
|
|
1680
|
+
execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
|
|
1681
|
+
rgAvailable2 = true;
|
|
1682
|
+
} catch {
|
|
1683
|
+
rgAvailable2 = false;
|
|
1684
|
+
}
|
|
1685
|
+
return rgAvailable2;
|
|
1686
|
+
}
|
|
1687
|
+
function quotePath2(p) {
|
|
1688
|
+
return `"${p.replace(/"/g, '\\"')}"`;
|
|
1689
|
+
}
|
|
1690
|
+
async function handleKumaFind(params) {
|
|
1691
|
+
const {
|
|
1692
|
+
name = "*",
|
|
1693
|
+
path: pathPattern,
|
|
1694
|
+
extensions,
|
|
1695
|
+
targetFolder,
|
|
1696
|
+
maxResults = 30,
|
|
1697
|
+
type = "file",
|
|
1698
|
+
outputMode = "rich",
|
|
1699
|
+
compact = false
|
|
1700
|
+
} = params;
|
|
1701
|
+
const recursiveName = !pathPattern && !targetFolder && !name.startsWith("**/") ? `**/${name}` : name;
|
|
1702
|
+
const cacheKey = `${recursiveName}:${pathPattern ?? ""}:${targetFolder ?? ""}:${type}:${maxResults}:${extensions ? extensions.join(",") : ""}`;
|
|
1703
|
+
const cached = findCache.get(cacheKey);
|
|
1704
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS2) {
|
|
1705
|
+
return cached.results;
|
|
1706
|
+
}
|
|
1707
|
+
const projectRoot = getProjectRoot();
|
|
1708
|
+
let entries;
|
|
1709
|
+
if (isRipgrepAvailable2() && type !== "dir") {
|
|
1710
|
+
entries = await tryRipgrepFind({
|
|
1711
|
+
name: recursiveName,
|
|
1712
|
+
pathPattern,
|
|
1713
|
+
targetFolder,
|
|
1714
|
+
extensions,
|
|
1715
|
+
maxResults,
|
|
1716
|
+
projectRoot
|
|
1717
|
+
});
|
|
1718
|
+
} else {
|
|
1719
|
+
entries = await fastGlobFind({
|
|
1720
|
+
name: recursiveName,
|
|
1721
|
+
pathPattern,
|
|
1722
|
+
targetFolder,
|
|
1723
|
+
extensions,
|
|
1724
|
+
maxResults,
|
|
1725
|
+
type,
|
|
1726
|
+
projectRoot
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
if (entries.length === 0) {
|
|
1730
|
+
const noResult = compact ? `0:${name}` : `\u{1F50D} No files found matching "${name}"${targetFolder ? ` in ${targetFolder}` : ""}.`;
|
|
1731
|
+
findCache.set(cacheKey, { results: noResult, timestamp: Date.now() });
|
|
1732
|
+
return noResult;
|
|
1733
|
+
}
|
|
1734
|
+
sessionMemory.recordToolCall("kuma_find", {
|
|
1735
|
+
name,
|
|
1736
|
+
matchCount: entries.length,
|
|
1737
|
+
engine: rgAvailable2 ? "ripgrep" : "fast-glob"
|
|
1738
|
+
});
|
|
1739
|
+
let result;
|
|
1740
|
+
if (outputMode === "json") {
|
|
1741
|
+
result = JSON.stringify(entries);
|
|
1742
|
+
} else if (compact || outputMode === "raw") {
|
|
1743
|
+
result = entries.join("\n");
|
|
1744
|
+
} else {
|
|
1745
|
+
const lines = [
|
|
1746
|
+
`\u{1F50D} Find: "${name}"`,
|
|
1747
|
+
`\u{1F4C1} ${entries.length} files found${targetFolder ? ` in ${targetFolder}` : ""}`,
|
|
1748
|
+
"",
|
|
1749
|
+
...entries.map((e, i) => `[${i + 1}] \u{1F4C4} ${e}`)
|
|
1750
|
+
];
|
|
1751
|
+
result = lines.join("\n");
|
|
1752
|
+
}
|
|
1753
|
+
findCache.set(cacheKey, { results: result, timestamp: Date.now() });
|
|
1754
|
+
return result;
|
|
1755
|
+
}
|
|
1756
|
+
async function tryRipgrepFind(opts) {
|
|
1757
|
+
const { name, pathPattern, targetFolder, extensions, maxResults, projectRoot } = opts;
|
|
1758
|
+
const args = ["rg", "--files", "--no-ignore-vcs", "--color", "never"];
|
|
1759
|
+
for (const ignore of IGNORE_PATTERNS2) {
|
|
1760
|
+
const glob = ignore.replace(/^\*\*\//, "");
|
|
1761
|
+
args.push("-g", `!${glob}`);
|
|
1762
|
+
}
|
|
1763
|
+
args.push("-g", name);
|
|
1764
|
+
if (pathPattern) {
|
|
1765
|
+
args.push("-g", pathPattern);
|
|
1766
|
+
}
|
|
1767
|
+
if (extensions && extensions.length > 0) {
|
|
1768
|
+
for (const ext of extensions) {
|
|
1769
|
+
const clean = ext.startsWith(".") ? ext : `.${ext}`;
|
|
1770
|
+
args.push("-g", `*${clean}`);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
const searchDir = targetFolder ? path5.join(projectRoot, targetFolder) : projectRoot;
|
|
1774
|
+
args.push(quotePath2(searchDir));
|
|
1775
|
+
try {
|
|
1776
|
+
const output = execSync2(args.join(" "), {
|
|
1777
|
+
cwd: projectRoot,
|
|
1778
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
1779
|
+
timeout: 1e4,
|
|
1780
|
+
encoding: "utf-8",
|
|
1781
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1782
|
+
});
|
|
1783
|
+
if (!output || output.trim().length === 0) return [];
|
|
1784
|
+
let files = output.trim().split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1785
|
+
if (targetFolder) {
|
|
1786
|
+
const prefix = targetFolder.replace(/\/+$/, "") + "/";
|
|
1787
|
+
files = files.map((f) => prefix + f);
|
|
1788
|
+
}
|
|
1789
|
+
return files.slice(0, maxResults);
|
|
1790
|
+
} catch (err) {
|
|
1791
|
+
return [];
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
async function fastGlobFind(opts) {
|
|
1795
|
+
const { name, pathPattern, targetFolder, extensions, maxResults, type, projectRoot } = opts;
|
|
1796
|
+
const globParts = [];
|
|
1797
|
+
if (pathPattern) {
|
|
1798
|
+
globParts.push(pathPattern.replace(/^\/+/, "").replace(/\/+$/, ""));
|
|
1799
|
+
} else if (targetFolder) {
|
|
1800
|
+
globParts.push(targetFolder.replace(/^\/+/, "").replace(/\/+$/, ""));
|
|
1801
|
+
}
|
|
1802
|
+
globParts.push(name);
|
|
1803
|
+
const searchPattern = globParts.join("/");
|
|
1804
|
+
try {
|
|
1805
|
+
let entries = await fg2(searchPattern, {
|
|
1806
|
+
cwd: projectRoot,
|
|
1807
|
+
ignore: IGNORE_PATTERNS2,
|
|
1808
|
+
onlyFiles: type === "file",
|
|
1809
|
+
onlyDirectories: type === "dir",
|
|
1810
|
+
absolute: false,
|
|
1811
|
+
deep: 20,
|
|
1812
|
+
dot: false
|
|
1813
|
+
});
|
|
1814
|
+
if (extensions && extensions.length > 0 && type !== "dir") {
|
|
1815
|
+
const normalizedExts = extensions.map(
|
|
1816
|
+
(e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
1817
|
+
);
|
|
1818
|
+
entries = entries.filter((entry) => {
|
|
1819
|
+
const ext = path5.extname(entry).toLowerCase();
|
|
1820
|
+
return normalizedExts.includes(ext);
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
return entries.slice(0, maxResults);
|
|
1824
|
+
} catch {
|
|
1825
|
+
return [];
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// src/tools/kumaStats.ts
|
|
1830
|
+
import fs5 from "fs";
|
|
1514
1831
|
import path6 from "path";
|
|
1832
|
+
import { execSync as execSync3 } from "child_process";
|
|
1833
|
+
import fg3 from "fast-glob";
|
|
1834
|
+
var LANG_MAP = {
|
|
1835
|
+
ts: "TypeScript",
|
|
1836
|
+
tsx: "TypeScript React",
|
|
1837
|
+
js: "JavaScript",
|
|
1838
|
+
jsx: "JavaScript React",
|
|
1839
|
+
py: "Python",
|
|
1840
|
+
go: "Go",
|
|
1841
|
+
rs: "Rust",
|
|
1842
|
+
java: "Java",
|
|
1843
|
+
kt: "Kotlin",
|
|
1844
|
+
rb: "Ruby",
|
|
1845
|
+
php: "PHP",
|
|
1846
|
+
cs: "C#",
|
|
1847
|
+
swift: "Swift",
|
|
1848
|
+
c: "C",
|
|
1849
|
+
cpp: "C++",
|
|
1850
|
+
h: "C/C++ Header",
|
|
1851
|
+
css: "CSS",
|
|
1852
|
+
scss: "SCSS",
|
|
1853
|
+
less: "Less",
|
|
1854
|
+
html: "HTML",
|
|
1855
|
+
json: "JSON",
|
|
1856
|
+
yaml: "YAML",
|
|
1857
|
+
yml: "YAML",
|
|
1858
|
+
md: "Markdown",
|
|
1859
|
+
sql: "SQL",
|
|
1860
|
+
graphql: "GraphQL",
|
|
1861
|
+
prisma: "Prisma",
|
|
1862
|
+
toml: "TOML",
|
|
1863
|
+
xml: "XML",
|
|
1864
|
+
svg: "SVG",
|
|
1865
|
+
sh: "Shell",
|
|
1866
|
+
bash: "Shell",
|
|
1867
|
+
dockerfile: "Dockerfile"
|
|
1868
|
+
};
|
|
1869
|
+
async function handleKumaStats(params) {
|
|
1870
|
+
const {
|
|
1871
|
+
filePath,
|
|
1872
|
+
target = "project",
|
|
1873
|
+
outputMode = "rich",
|
|
1874
|
+
compact = false,
|
|
1875
|
+
extensions
|
|
1876
|
+
} = params;
|
|
1877
|
+
if (target === "file" && filePath) {
|
|
1878
|
+
return await statsSingleFile(filePath, { outputMode, compact });
|
|
1879
|
+
}
|
|
1880
|
+
if (target === "dir" && filePath) {
|
|
1881
|
+
return await statsDirectory(filePath, { outputMode, compact, extensions });
|
|
1882
|
+
}
|
|
1883
|
+
return await statsProject({ outputMode, compact, extensions });
|
|
1884
|
+
}
|
|
1885
|
+
async function statsSingleFile(filePath, opts) {
|
|
1886
|
+
const { outputMode, compact } = opts;
|
|
1887
|
+
const validation = validateFilePath(filePath);
|
|
1888
|
+
if (!validation.valid) {
|
|
1889
|
+
return compact ? `ERR:${filePath}` : `Error: ${validation.error.message}`;
|
|
1890
|
+
}
|
|
1891
|
+
const resolvedPath = validation.resolvedPath;
|
|
1892
|
+
if (!fs5.existsSync(resolvedPath)) {
|
|
1893
|
+
return compact ? `ERR:not found` : `Error: File not found: ${filePath}`;
|
|
1894
|
+
}
|
|
1895
|
+
const stat = fs5.statSync(resolvedPath);
|
|
1896
|
+
const isDir = stat.isDirectory();
|
|
1897
|
+
if (isDir) {
|
|
1898
|
+
return statsDirectory(filePath, { outputMode, compact });
|
|
1899
|
+
}
|
|
1900
|
+
const content = fs5.readFileSync(resolvedPath, "utf-8");
|
|
1901
|
+
const lines = content.split("\n");
|
|
1902
|
+
const ext = path6.extname(resolvedPath).replace(".", "").toLowerCase();
|
|
1903
|
+
const lang = LANG_MAP[ext] || ext.toUpperCase() || "Unknown";
|
|
1904
|
+
const totalBytes = stat.size;
|
|
1905
|
+
const nonEmpty = lines.filter((l) => l.trim().length > 0).length;
|
|
1906
|
+
if (outputMode === "json") {
|
|
1907
|
+
return JSON.stringify({
|
|
1908
|
+
file: filePath,
|
|
1909
|
+
lines: lines.length,
|
|
1910
|
+
nonEmpty,
|
|
1911
|
+
size: totalBytes,
|
|
1912
|
+
sizeHuman: formatBytes(totalBytes),
|
|
1913
|
+
language: lang
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
if (compact || outputMode === "raw") {
|
|
1917
|
+
return `${filePath} ${lines.length} ${formatBytes(totalBytes)} ${lang}`;
|
|
1918
|
+
}
|
|
1919
|
+
return [
|
|
1920
|
+
`\u{1F4CA} Stats: ${filePath}`,
|
|
1921
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1922
|
+
` Language: ${lang}`,
|
|
1923
|
+
` Lines: ${lines.length}`,
|
|
1924
|
+
` Non-empty: ${nonEmpty}`,
|
|
1925
|
+
` Size: ${formatBytes(totalBytes)}`
|
|
1926
|
+
].join("\n");
|
|
1927
|
+
}
|
|
1928
|
+
async function statsDirectory(dirPath, opts) {
|
|
1929
|
+
const { outputMode, compact, extensions } = opts;
|
|
1930
|
+
const projectRoot = getProjectRoot();
|
|
1931
|
+
const resolved = path6.isAbsolute(dirPath) ? dirPath : path6.join(projectRoot, dirPath);
|
|
1932
|
+
if (!fs5.existsSync(resolved)) {
|
|
1933
|
+
return compact ? `ERR:not found ${dirPath}` : `Error: Directory not found: ${dirPath}`;
|
|
1934
|
+
}
|
|
1935
|
+
let totalFiles = 0;
|
|
1936
|
+
let totalLines = 0;
|
|
1937
|
+
let totalBytes = 0;
|
|
1938
|
+
const langCounts = {};
|
|
1939
|
+
const entries = await fg3("**/*", {
|
|
1940
|
+
cwd: resolved,
|
|
1941
|
+
onlyFiles: true,
|
|
1942
|
+
ignore: [
|
|
1943
|
+
"**/node_modules/**",
|
|
1944
|
+
"**/.git/**",
|
|
1945
|
+
"**/dist/**",
|
|
1946
|
+
"**/build/**",
|
|
1947
|
+
"**/.next/**",
|
|
1948
|
+
"**/coverage/**",
|
|
1949
|
+
"**/*.map"
|
|
1950
|
+
],
|
|
1951
|
+
deep: 10,
|
|
1952
|
+
dot: false
|
|
1953
|
+
});
|
|
1954
|
+
for (const entry of entries) {
|
|
1955
|
+
const ext = path6.extname(entry).replace(".", "").toLowerCase();
|
|
1956
|
+
if (extensions && extensions.length > 0) {
|
|
1957
|
+
const normalizedExts = extensions.map(
|
|
1958
|
+
(e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
1959
|
+
);
|
|
1960
|
+
if (!normalizedExts.includes(`.${ext}`)) continue;
|
|
1961
|
+
}
|
|
1962
|
+
totalFiles++;
|
|
1963
|
+
const fullPath = path6.join(resolved, entry);
|
|
1964
|
+
try {
|
|
1965
|
+
const stat = fs5.statSync(fullPath);
|
|
1966
|
+
if (stat.size > 1e6) {
|
|
1967
|
+
totalBytes += stat.size;
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
1971
|
+
const lineCount = content.split("\n").length;
|
|
1972
|
+
totalLines += lineCount;
|
|
1973
|
+
totalBytes += stat.size;
|
|
1974
|
+
const lang = LANG_MAP[ext] || ext.toUpperCase() || "Other";
|
|
1975
|
+
if (!langCounts[lang]) {
|
|
1976
|
+
langCounts[lang] = { files: 0, lines: 0 };
|
|
1977
|
+
}
|
|
1978
|
+
langCounts[lang].files++;
|
|
1979
|
+
langCounts[lang].lines += lineCount;
|
|
1980
|
+
} catch {
|
|
1981
|
+
try {
|
|
1982
|
+
const fallbackStat = fs5.statSync(fullPath);
|
|
1983
|
+
totalBytes += fallbackStat.size;
|
|
1984
|
+
} catch {
|
|
1985
|
+
totalBytes += 0;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
sessionMemory.recordToolCall("kuma_stats", {
|
|
1990
|
+
target: dirPath,
|
|
1991
|
+
totalFiles,
|
|
1992
|
+
totalLines
|
|
1993
|
+
});
|
|
1994
|
+
if (outputMode === "json") {
|
|
1995
|
+
return JSON.stringify({
|
|
1996
|
+
directory: dirPath,
|
|
1997
|
+
totalFiles,
|
|
1998
|
+
totalLines,
|
|
1999
|
+
totalBytes,
|
|
2000
|
+
sizeHuman: formatBytes(totalBytes),
|
|
2001
|
+
languages: langCounts
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
if (compact || outputMode === "raw") {
|
|
2005
|
+
const langs = Object.entries(langCounts).sort((a, b) => b[1].lines - a[1].lines).map(([name, counts]) => `${name} ${counts.files} ${counts.lines}`).join("\n");
|
|
2006
|
+
return `${dirPath} ${totalFiles} ${totalLines} ${formatBytes(totalBytes)}
|
|
2007
|
+
${langs}`;
|
|
2008
|
+
}
|
|
2009
|
+
const langSummary = Object.entries(langCounts).sort((a, b) => b[1].lines - a[1].lines).slice(0, 10).map(
|
|
2010
|
+
([name, counts]) => ` ${name.padEnd(20)} ${String(counts.files).padStart(5)} files ${String(counts.lines).padStart(8)} lines`
|
|
2011
|
+
).join("\n");
|
|
2012
|
+
return [
|
|
2013
|
+
`\u{1F4CA} Directory Stats: ${dirPath}`,
|
|
2014
|
+
`\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`,
|
|
2015
|
+
` Total files: ${totalFiles}`,
|
|
2016
|
+
` Total lines: ${totalLines}`,
|
|
2017
|
+
` Total size: ${formatBytes(totalBytes)}`,
|
|
2018
|
+
``,
|
|
2019
|
+
`Top Languages:`,
|
|
2020
|
+
langSummary
|
|
2021
|
+
].join("\n");
|
|
2022
|
+
}
|
|
2023
|
+
async function statsProject(opts) {
|
|
2024
|
+
const projectRoot = getProjectRoot();
|
|
2025
|
+
const projectName = path6.basename(projectRoot);
|
|
2026
|
+
const projectStats = await statsDirectory(projectRoot, opts);
|
|
2027
|
+
let branch = "unknown";
|
|
2028
|
+
let lastCommit = "unknown";
|
|
2029
|
+
try {
|
|
2030
|
+
branch = execSync3("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown", {
|
|
2031
|
+
cwd: projectRoot,
|
|
2032
|
+
encoding: "utf-8",
|
|
2033
|
+
timeout: 2e3
|
|
2034
|
+
}).trim();
|
|
2035
|
+
lastCommit = execSync3('git log -1 --format="%h %s" 2>/dev/null || echo unknown', {
|
|
2036
|
+
cwd: projectRoot,
|
|
2037
|
+
encoding: "utf-8",
|
|
2038
|
+
timeout: 2e3
|
|
2039
|
+
}).trim();
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
if (opts.outputMode === "raw" || opts.compact) {
|
|
2043
|
+
return `Project: ${projectName} Branch: ${branch} ${lastCommit}
|
|
2044
|
+
${projectStats}`;
|
|
2045
|
+
}
|
|
2046
|
+
return [
|
|
2047
|
+
`\u{1F4CA} Project: ${projectName}`,
|
|
2048
|
+
`\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`,
|
|
2049
|
+
` Branch: ${branch}`,
|
|
2050
|
+
` Last commit: ${lastCommit}`,
|
|
2051
|
+
``,
|
|
2052
|
+
projectStats
|
|
2053
|
+
].join("\n");
|
|
2054
|
+
}
|
|
2055
|
+
function formatBytes(bytes) {
|
|
2056
|
+
if (bytes === 0) return "0 B";
|
|
2057
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
2058
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
2059
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// src/tools/safeTerminalExec.ts
|
|
2063
|
+
import fs7 from "fs";
|
|
2064
|
+
import path8 from "path";
|
|
1515
2065
|
|
|
1516
2066
|
// src/utils/conventionsDetector.ts
|
|
1517
|
-
import
|
|
1518
|
-
import
|
|
2067
|
+
import fs6 from "fs";
|
|
2068
|
+
import path7 from "path";
|
|
1519
2069
|
var cachedConventions = null;
|
|
1520
2070
|
async function detectConventions(forceRescan = false) {
|
|
1521
2071
|
if (cachedConventions && !forceRescan) {
|
|
@@ -1546,7 +2096,7 @@ async function detectConventions(forceRescan = false) {
|
|
|
1546
2096
|
return conventions;
|
|
1547
2097
|
}
|
|
1548
2098
|
function detectFramework(root) {
|
|
1549
|
-
const pkg = readJsonSafe(
|
|
2099
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1550
2100
|
if (pkg) {
|
|
1551
2101
|
const pkgDeps = pkg.dependencies ?? {};
|
|
1552
2102
|
const pkgDevDeps = pkg.devDependencies ?? {};
|
|
@@ -1591,11 +2141,11 @@ function detectFramework(root) {
|
|
|
1591
2141
|
function scanSubProjectFrameworks(root) {
|
|
1592
2142
|
const frameworks = [];
|
|
1593
2143
|
try {
|
|
1594
|
-
const entries =
|
|
2144
|
+
const entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
1595
2145
|
for (const entry of entries) {
|
|
1596
2146
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1597
|
-
const subDir =
|
|
1598
|
-
const subPkg = readJsonSafe(
|
|
2147
|
+
const subDir = path7.join(root, entry.name);
|
|
2148
|
+
const subPkg = readJsonSafe(path7.join(subDir, "package.json"));
|
|
1599
2149
|
if (!subPkg) continue;
|
|
1600
2150
|
const subDeps = { ...subPkg.dependencies ?? {}, ...subPkg.devDependencies ?? {} };
|
|
1601
2151
|
if (subDeps.next) frameworks.push("Next.js");
|
|
@@ -1614,7 +2164,7 @@ function scanSubProjectFrameworks(root) {
|
|
|
1614
2164
|
return frameworks;
|
|
1615
2165
|
}
|
|
1616
2166
|
function detectProjectType(root) {
|
|
1617
|
-
const pkg = readJsonSafe(
|
|
2167
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1618
2168
|
if (pkg) {
|
|
1619
2169
|
const pkgDeps = pkg.dependencies ?? {};
|
|
1620
2170
|
const pkgDevDeps = pkg.devDependencies ?? {};
|
|
@@ -1638,8 +2188,8 @@ function detectProjectType(root) {
|
|
|
1638
2188
|
}
|
|
1639
2189
|
function detectWorkspaces(root) {
|
|
1640
2190
|
const results = [];
|
|
1641
|
-
const pkg = readJsonSafe(
|
|
1642
|
-
const pnpmWorkspace = readYamlLite(
|
|
2191
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
2192
|
+
const pnpmWorkspace = readYamlLite(path7.join(root, "pnpm-workspace.yaml"));
|
|
1643
2193
|
const patterns = /* @__PURE__ */ new Set();
|
|
1644
2194
|
const pkgWorkspaces = pkg?.workspaces;
|
|
1645
2195
|
if (Array.isArray(pkgWorkspaces)) {
|
|
@@ -1659,22 +2209,22 @@ function detectWorkspaces(root) {
|
|
|
1659
2209
|
for (const pattern of patterns) {
|
|
1660
2210
|
const match = pattern.match(/^([^*]+)\/\*$/);
|
|
1661
2211
|
if (!match) continue;
|
|
1662
|
-
const dir =
|
|
1663
|
-
if (!
|
|
2212
|
+
const dir = path7.join(root, match[1]);
|
|
2213
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1664
2214
|
let entries = [];
|
|
1665
2215
|
try {
|
|
1666
|
-
entries =
|
|
2216
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
1667
2217
|
} catch {
|
|
1668
2218
|
continue;
|
|
1669
2219
|
}
|
|
1670
2220
|
for (const entry of entries) {
|
|
1671
2221
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1672
|
-
const pkgPath =
|
|
2222
|
+
const pkgPath = path7.join(dir, entry.name, "package.json");
|
|
1673
2223
|
const subPkg = readJsonSafe(pkgPath);
|
|
1674
2224
|
if (!subPkg) continue;
|
|
1675
|
-
const workspacePath =
|
|
2225
|
+
const workspacePath = path7.join(dir, entry.name);
|
|
1676
2226
|
results.push({
|
|
1677
|
-
path:
|
|
2227
|
+
path: path7.relative(root, workspacePath),
|
|
1678
2228
|
name: subPkg.name ?? entry.name,
|
|
1679
2229
|
framework: detectFramework(workspacePath),
|
|
1680
2230
|
packageManager: detectPackageManagerForDir(workspacePath)
|
|
@@ -1690,8 +2240,8 @@ function detectWorkspaces(root) {
|
|
|
1690
2240
|
}
|
|
1691
2241
|
function readYamlLite(filePath) {
|
|
1692
2242
|
try {
|
|
1693
|
-
if (!
|
|
1694
|
-
const text =
|
|
2243
|
+
if (!fs6.existsSync(filePath)) return null;
|
|
2244
|
+
const text = fs6.readFileSync(filePath, "utf-8");
|
|
1695
2245
|
const packages = [];
|
|
1696
2246
|
let inPackages = false;
|
|
1697
2247
|
for (const rawLine of text.split("\n")) {
|
|
@@ -1712,7 +2262,7 @@ function readYamlLite(filePath) {
|
|
|
1712
2262
|
}
|
|
1713
2263
|
}
|
|
1714
2264
|
function detectTestRunner(root) {
|
|
1715
|
-
const pkg = readJsonSafe(
|
|
2265
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1716
2266
|
if (!pkg) return "unknown";
|
|
1717
2267
|
const pkgDeps2 = pkg.dependencies ?? {};
|
|
1718
2268
|
const pkgDevDeps2 = pkg.devDependencies ?? {};
|
|
@@ -1733,7 +2283,7 @@ function detectTestRunner(root) {
|
|
|
1733
2283
|
return "unknown";
|
|
1734
2284
|
}
|
|
1735
2285
|
function detectStyling(root) {
|
|
1736
|
-
const pkg = readJsonSafe(
|
|
2286
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1737
2287
|
if (!pkg) return "unknown";
|
|
1738
2288
|
const pkgDeps3 = pkg.dependencies ?? {};
|
|
1739
2289
|
const pkgDevDeps3 = pkg.devDependencies ?? {};
|
|
@@ -1746,22 +2296,22 @@ function detectStyling(root) {
|
|
|
1746
2296
|
if (deps.less) return "Less";
|
|
1747
2297
|
if (deps["@vanilla-extract/css"]) return "Vanilla Extract";
|
|
1748
2298
|
if (deps.cssmodules || hasCSSModules(root)) return "CSS Modules";
|
|
1749
|
-
const srcDir =
|
|
1750
|
-
if (
|
|
2299
|
+
const srcDir = path7.join(root, "src");
|
|
2300
|
+
if (fs6.existsSync(srcDir)) {
|
|
1751
2301
|
const cssFiles = findFiles(srcDir, [".css", ".scss", ".less"]);
|
|
1752
2302
|
if (cssFiles.length > 0) return "Plain CSS/SCSS";
|
|
1753
2303
|
}
|
|
1754
2304
|
return "unknown";
|
|
1755
2305
|
}
|
|
1756
2306
|
function detectImportAlias(root) {
|
|
1757
|
-
const tsconfig = readJsonSafe(
|
|
2307
|
+
const tsconfig = readJsonSafe(path7.join(root, "tsconfig.json"));
|
|
1758
2308
|
const tsconfigPaths = tsconfig?.compilerOptions;
|
|
1759
2309
|
if (tsconfigPaths?.paths) {
|
|
1760
2310
|
const paths = tsconfigPaths.paths;
|
|
1761
2311
|
const alias = Object.keys(paths)[0];
|
|
1762
2312
|
if (alias) return alias.replace("/*", "");
|
|
1763
2313
|
}
|
|
1764
|
-
const jsconfig = readJsonSafe(
|
|
2314
|
+
const jsconfig = readJsonSafe(path7.join(root, "jsconfig.json"));
|
|
1765
2315
|
const jsconfigPaths = jsconfig?.compilerOptions;
|
|
1766
2316
|
if (jsconfigPaths?.paths) {
|
|
1767
2317
|
const paths = jsconfigPaths.paths;
|
|
@@ -1772,15 +2322,15 @@ function detectImportAlias(root) {
|
|
|
1772
2322
|
}
|
|
1773
2323
|
function detectLintRules(root) {
|
|
1774
2324
|
const rules = [];
|
|
1775
|
-
if (
|
|
1776
|
-
if (
|
|
1777
|
-
if (
|
|
1778
|
-
if (
|
|
1779
|
-
if (
|
|
1780
|
-
if (
|
|
1781
|
-
if (
|
|
1782
|
-
if (
|
|
1783
|
-
if (
|
|
2325
|
+
if (fs6.existsSync(path7.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
|
|
2326
|
+
if (fs6.existsSync(path7.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
|
|
2327
|
+
if (fs6.existsSync(path7.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
|
|
2328
|
+
if (fs6.existsSync(path7.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
|
|
2329
|
+
if (fs6.existsSync(path7.join(root, ".prettierrc"))) rules.push("Prettier");
|
|
2330
|
+
if (fs6.existsSync(path7.join(root, ".prettierrc.json"))) rules.push("Prettier");
|
|
2331
|
+
if (fs6.existsSync(path7.join(root, ".prettierrc.js"))) rules.push("Prettier");
|
|
2332
|
+
if (fs6.existsSync(path7.join(root, ".stylelintrc"))) rules.push("StyleLint");
|
|
2333
|
+
if (fs6.existsSync(path7.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
|
|
1784
2334
|
return rules;
|
|
1785
2335
|
}
|
|
1786
2336
|
function detectPackageManager() {
|
|
@@ -1789,26 +2339,26 @@ function detectPackageManager() {
|
|
|
1789
2339
|
}
|
|
1790
2340
|
function detectPackageManagerForDir(dir) {
|
|
1791
2341
|
const root = getProjectRoot();
|
|
1792
|
-
let currentDir =
|
|
2342
|
+
let currentDir = path7.resolve(dir);
|
|
1793
2343
|
while (currentDir.startsWith(root)) {
|
|
1794
|
-
if (
|
|
1795
|
-
if (
|
|
1796
|
-
if (
|
|
1797
|
-
if (
|
|
2344
|
+
if (fs6.existsSync(path7.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2345
|
+
if (fs6.existsSync(path7.join(currentDir, "yarn.lock"))) return "yarn";
|
|
2346
|
+
if (fs6.existsSync(path7.join(currentDir, "package-lock.json"))) return "npm";
|
|
2347
|
+
if (fs6.existsSync(path7.join(currentDir, "bun.lockb"))) return "bun";
|
|
1798
2348
|
if (currentDir === root) break;
|
|
1799
|
-
currentDir =
|
|
2349
|
+
currentDir = path7.dirname(currentDir);
|
|
1800
2350
|
}
|
|
1801
2351
|
return "npm";
|
|
1802
2352
|
}
|
|
1803
2353
|
function detectModuleSystem(root) {
|
|
1804
|
-
const pkg = readJsonSafe(
|
|
2354
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1805
2355
|
if (pkg?.type === "module") return "esm";
|
|
1806
|
-
const srcDir =
|
|
1807
|
-
if (
|
|
2356
|
+
const srcDir = path7.join(root, "src");
|
|
2357
|
+
if (fs6.existsSync(srcDir)) {
|
|
1808
2358
|
const tsFiles = findFiles(srcDir, [".ts", ".tsx", ".js", ".jsx", ".mjs"]);
|
|
1809
2359
|
for (const file of tsFiles.slice(0, 20)) {
|
|
1810
2360
|
try {
|
|
1811
|
-
const content =
|
|
2361
|
+
const content = fs6.readFileSync(file, "utf-8");
|
|
1812
2362
|
if (content.includes("import ") || content.includes("export ")) return "esm";
|
|
1813
2363
|
} catch {
|
|
1814
2364
|
continue;
|
|
@@ -1818,22 +2368,22 @@ function detectModuleSystem(root) {
|
|
|
1818
2368
|
return "cjs";
|
|
1819
2369
|
}
|
|
1820
2370
|
function detectLanguage(root) {
|
|
1821
|
-
if (
|
|
1822
|
-
if (
|
|
1823
|
-
if (
|
|
1824
|
-
if (
|
|
1825
|
-
if (
|
|
1826
|
-
if (
|
|
1827
|
-
if (
|
|
1828
|
-
if (
|
|
2371
|
+
if (fs6.existsSync(path7.join(root, "go.mod"))) return "go";
|
|
2372
|
+
if (fs6.existsSync(path7.join(root, "Cargo.toml"))) return "rust";
|
|
2373
|
+
if (fs6.existsSync(path7.join(root, "composer.json"))) return "php";
|
|
2374
|
+
if (fs6.existsSync(path7.join(root, "pyproject.toml")) || fs6.existsSync(path7.join(root, "requirements.txt")) || fs6.existsSync(path7.join(root, "setup.py"))) return "python";
|
|
2375
|
+
if (fs6.existsSync(path7.join(root, "Gemfile"))) return "ruby";
|
|
2376
|
+
if (fs6.existsSync(path7.join(root, "pom.xml"))) return "java";
|
|
2377
|
+
if (fs6.existsSync(path7.join(root, "build.gradle")) || fs6.existsSync(path7.join(root, "build.gradle.kts"))) return "kotlin";
|
|
2378
|
+
if (fs6.existsSync(path7.join(root, "Package.swift"))) return "swift";
|
|
1829
2379
|
try {
|
|
1830
|
-
const entries =
|
|
2380
|
+
const entries = fs6.readdirSync(root);
|
|
1831
2381
|
if (entries.some((e) => e.endsWith(".csproj"))) return "csharp";
|
|
1832
2382
|
} catch {
|
|
1833
2383
|
}
|
|
1834
|
-
if (
|
|
1835
|
-
const srcDir =
|
|
1836
|
-
if (
|
|
2384
|
+
if (fs6.existsSync(path7.join(root, "tsconfig.json"))) return "typescript";
|
|
2385
|
+
const srcDir = path7.join(root, "src");
|
|
2386
|
+
if (fs6.existsSync(srcDir)) {
|
|
1837
2387
|
const tsFiles = findFiles(srcDir, [".ts", ".tsx"]);
|
|
1838
2388
|
if (tsFiles.length > 0) return "typescript";
|
|
1839
2389
|
const jsFiles = findFiles(srcDir, [".js", ".jsx"]);
|
|
@@ -1852,7 +2402,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1852
2402
|
switch (language) {
|
|
1853
2403
|
case "go": {
|
|
1854
2404
|
try {
|
|
1855
|
-
const goMod =
|
|
2405
|
+
const goMod = fs6.readFileSync(path7.join(root, "go.mod"), "utf-8");
|
|
1856
2406
|
if (goMod.includes("github.com/gin-gonic/gin")) return "Gin";
|
|
1857
2407
|
if (goMod.includes("github.com/labstack/echo")) return "Echo";
|
|
1858
2408
|
if (goMod.includes("github.com/gofiber/fiber")) return "Fiber";
|
|
@@ -1866,7 +2416,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1866
2416
|
}
|
|
1867
2417
|
case "php": {
|
|
1868
2418
|
try {
|
|
1869
|
-
const composer = JSON.parse(
|
|
2419
|
+
const composer = JSON.parse(fs6.readFileSync(path7.join(root, "composer.json"), "utf-8"));
|
|
1870
2420
|
const req = composer.require ?? {};
|
|
1871
2421
|
const reqDev = composer["require-dev"] ?? {};
|
|
1872
2422
|
const deps = { ...req, ...reqDev };
|
|
@@ -1883,14 +2433,14 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1883
2433
|
}
|
|
1884
2434
|
case "python": {
|
|
1885
2435
|
try {
|
|
1886
|
-
if (
|
|
1887
|
-
const content =
|
|
2436
|
+
if (fs6.existsSync(path7.join(root, "pyproject.toml"))) {
|
|
2437
|
+
const content = fs6.readFileSync(path7.join(root, "pyproject.toml"), "utf-8");
|
|
1888
2438
|
if (content.includes("django")) return "Django";
|
|
1889
2439
|
if (content.includes("flask")) return "Flask";
|
|
1890
2440
|
if (content.includes("fastapi")) return "FastAPI";
|
|
1891
2441
|
}
|
|
1892
|
-
if (
|
|
1893
|
-
const content =
|
|
2442
|
+
if (fs6.existsSync(path7.join(root, "requirements.txt"))) {
|
|
2443
|
+
const content = fs6.readFileSync(path7.join(root, "requirements.txt"), "utf-8");
|
|
1894
2444
|
if (content.includes("django")) return "Django";
|
|
1895
2445
|
if (content.includes("flask")) return "Flask";
|
|
1896
2446
|
if (content.includes("fastapi")) return "FastAPI";
|
|
@@ -1903,7 +2453,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1903
2453
|
}
|
|
1904
2454
|
case "rust": {
|
|
1905
2455
|
try {
|
|
1906
|
-
const cargo =
|
|
2456
|
+
const cargo = fs6.readFileSync(path7.join(root, "Cargo.toml"), "utf-8");
|
|
1907
2457
|
if (cargo.includes("axum")) return "Axum";
|
|
1908
2458
|
if (cargo.includes("actix-web")) return "Actix Web";
|
|
1909
2459
|
if (cargo.includes("rocket")) return "Rocket";
|
|
@@ -1917,7 +2467,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1917
2467
|
}
|
|
1918
2468
|
case "java": {
|
|
1919
2469
|
try {
|
|
1920
|
-
const content =
|
|
2470
|
+
const content = fs6.readFileSync(path7.join(root, "pom.xml"), "utf-8");
|
|
1921
2471
|
if (content.includes("spring-boot")) return "Spring Boot";
|
|
1922
2472
|
if (content.includes("quarkus")) return "Quarkus";
|
|
1923
2473
|
if (content.includes("micronaut")) return "Micronaut";
|
|
@@ -1925,7 +2475,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1925
2475
|
if (content.includes("helidon")) return "Helidon";
|
|
1926
2476
|
} catch {
|
|
1927
2477
|
try {
|
|
1928
|
-
const gradle =
|
|
2478
|
+
const gradle = fs6.readFileSync(path7.join(root, "build.gradle"), "utf-8");
|
|
1929
2479
|
if (gradle.includes("spring")) return "Spring Boot";
|
|
1930
2480
|
if (gradle.includes("quarkus")) return "Quarkus";
|
|
1931
2481
|
} catch {
|
|
@@ -1935,7 +2485,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1935
2485
|
}
|
|
1936
2486
|
case "kotlin": {
|
|
1937
2487
|
try {
|
|
1938
|
-
const gradle =
|
|
2488
|
+
const gradle = fs6.readFileSync(path7.join(root, "build.gradle.kts"), "utf-8");
|
|
1939
2489
|
if (gradle.includes("spring")) return "Spring Boot (Kotlin)";
|
|
1940
2490
|
if (gradle.includes("ktor")) return "Ktor";
|
|
1941
2491
|
} catch {
|
|
@@ -1944,7 +2494,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1944
2494
|
}
|
|
1945
2495
|
case "ruby": {
|
|
1946
2496
|
try {
|
|
1947
|
-
const gemfile =
|
|
2497
|
+
const gemfile = fs6.readFileSync(path7.join(root, "Gemfile"), "utf-8");
|
|
1948
2498
|
if (gemfile.includes("rails")) return "Ruby on Rails";
|
|
1949
2499
|
if (gemfile.includes("sinatra")) return "Sinatra";
|
|
1950
2500
|
if (gemfile.includes("hanami")) return "Hanami";
|
|
@@ -1955,10 +2505,10 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1955
2505
|
}
|
|
1956
2506
|
case "csharp": {
|
|
1957
2507
|
try {
|
|
1958
|
-
const entries =
|
|
2508
|
+
const entries = fs6.readdirSync(root);
|
|
1959
2509
|
const csproj = entries.find((e) => e.endsWith(".csproj"));
|
|
1960
2510
|
if (csproj) {
|
|
1961
|
-
const content =
|
|
2511
|
+
const content = fs6.readFileSync(path7.join(root, csproj), "utf-8");
|
|
1962
2512
|
if (content.includes("Microsoft.AspNetCore")) return "ASP.NET Core";
|
|
1963
2513
|
if (content.includes("Microsoft.Maui")) return ".NET MAUI";
|
|
1964
2514
|
if (content.includes("Serilog")) return "Serilog";
|
|
@@ -1969,7 +2519,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1969
2519
|
}
|
|
1970
2520
|
case "swift": {
|
|
1971
2521
|
try {
|
|
1972
|
-
const content =
|
|
2522
|
+
const content = fs6.readFileSync(path7.join(root, "Package.swift"), "utf-8");
|
|
1973
2523
|
if (content.includes("vapor")) return "Vapor";
|
|
1974
2524
|
if (content.includes("swift-nio")) return "SwiftNIO";
|
|
1975
2525
|
if (content.includes("perfect")) return "Perfect";
|
|
@@ -1984,7 +2534,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1984
2534
|
}
|
|
1985
2535
|
function detectFeatures(root) {
|
|
1986
2536
|
const features = [];
|
|
1987
|
-
const pkg = readJsonSafe(
|
|
2537
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1988
2538
|
if (!pkg) return features;
|
|
1989
2539
|
const pkgDeps4 = pkg.dependencies ?? {};
|
|
1990
2540
|
const pkgDevDeps4 = pkg.devDependencies ?? {};
|
|
@@ -2008,25 +2558,25 @@ function detectFeatures(root) {
|
|
|
2008
2558
|
}
|
|
2009
2559
|
function readJsonSafe(filePath) {
|
|
2010
2560
|
try {
|
|
2011
|
-
if (
|
|
2012
|
-
return JSON.parse(
|
|
2561
|
+
if (fs6.existsSync(filePath)) {
|
|
2562
|
+
return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
2013
2563
|
}
|
|
2014
2564
|
} catch {
|
|
2015
2565
|
}
|
|
2016
2566
|
return null;
|
|
2017
2567
|
}
|
|
2018
2568
|
function hasCSSModules(root) {
|
|
2019
|
-
const srcDir =
|
|
2020
|
-
if (!
|
|
2569
|
+
const srcDir = path7.join(root, "src");
|
|
2570
|
+
if (!fs6.existsSync(srcDir)) return false;
|
|
2021
2571
|
const files = findFiles(srcDir, [".module.css", ".module.scss", ".module.less"]);
|
|
2022
2572
|
return files.length > 0;
|
|
2023
2573
|
}
|
|
2024
2574
|
function findFiles(dir, extensions) {
|
|
2025
2575
|
const results = [];
|
|
2026
2576
|
try {
|
|
2027
|
-
const entries =
|
|
2577
|
+
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2028
2578
|
for (const entry of entries) {
|
|
2029
|
-
const fullPath =
|
|
2579
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2030
2580
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
2031
2581
|
results.push(...findFiles(fullPath, extensions));
|
|
2032
2582
|
} else if (entry.isFile()) {
|
|
@@ -2217,7 +2767,7 @@ async function handleSafeTerminalExec(params) {
|
|
|
2217
2767
|
const workspaces = conventions?.workspaces;
|
|
2218
2768
|
const matched = workspaces?.find((w) => w.name === workspace || w.path === workspace);
|
|
2219
2769
|
if (matched) {
|
|
2220
|
-
workingDir =
|
|
2770
|
+
workingDir = path8.resolve(projectRoot, matched.path);
|
|
2221
2771
|
resolvedFrom = `workspace "${matched.name}" \u2192 ${matched.path}`;
|
|
2222
2772
|
} else {
|
|
2223
2773
|
return `\u26A0\uFE0F Workspace "${workspace}" not found. Run project_conventions first to detect workspaces, or use 'cwd' parameter with a direct path.
|
|
@@ -2225,13 +2775,13 @@ async function handleSafeTerminalExec(params) {
|
|
|
2225
2775
|
Available workspaces: ${workspaces?.map((w) => `"${w.name}" (${w.path})`).join(", ") || "none detected"}`;
|
|
2226
2776
|
}
|
|
2227
2777
|
} else if (inputCwd) {
|
|
2228
|
-
const resolved =
|
|
2229
|
-
const normalizedResolved =
|
|
2230
|
-
const normalizedRoot =
|
|
2778
|
+
const resolved = path8.resolve(projectRoot, inputCwd);
|
|
2779
|
+
const normalizedResolved = path8.normalize(resolved).toLowerCase();
|
|
2780
|
+
const normalizedRoot = path8.normalize(projectRoot).toLowerCase();
|
|
2231
2781
|
if (!normalizedResolved.startsWith(normalizedRoot)) {
|
|
2232
2782
|
return `\u{1F6AB} BLOCKED: Path "${inputCwd}" resolves outside project root "${projectRoot}".`;
|
|
2233
2783
|
}
|
|
2234
|
-
if (!
|
|
2784
|
+
if (!fs7.existsSync(resolved)) {
|
|
2235
2785
|
return `\u26A0\uFE0F Directory "${inputCwd}" does not exist at "${resolved}".`;
|
|
2236
2786
|
}
|
|
2237
2787
|
workingDir = resolved;
|
|
@@ -2317,9 +2867,9 @@ function formatTimeoutResult(command, timeout) {
|
|
|
2317
2867
|
}
|
|
2318
2868
|
|
|
2319
2869
|
// src/agents/codeReviewer.ts
|
|
2320
|
-
import
|
|
2321
|
-
import
|
|
2322
|
-
import { execSync as
|
|
2870
|
+
import fs8 from "fs";
|
|
2871
|
+
import path9 from "path";
|
|
2872
|
+
import { execSync as execSync4 } from "child_process";
|
|
2323
2873
|
var CODE_EXTENSIONS = [
|
|
2324
2874
|
".ts",
|
|
2325
2875
|
".tsx",
|
|
@@ -2349,7 +2899,7 @@ var CONFIG_EXTENSIONS = [
|
|
|
2349
2899
|
function getGitChangedFiles() {
|
|
2350
2900
|
try {
|
|
2351
2901
|
const root = getProjectRoot();
|
|
2352
|
-
const stdout =
|
|
2902
|
+
const stdout = execSync4("git status --porcelain", {
|
|
2353
2903
|
cwd: root,
|
|
2354
2904
|
encoding: "utf-8"
|
|
2355
2905
|
});
|
|
@@ -2362,7 +2912,7 @@ function getGitChangedFiles() {
|
|
|
2362
2912
|
if (filePath.startsWith('"') && filePath.endsWith('"')) {
|
|
2363
2913
|
filePath = filePath.substring(1, filePath.length - 1);
|
|
2364
2914
|
}
|
|
2365
|
-
const ext =
|
|
2915
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2366
2916
|
if (CODE_EXTENSIONS.includes(ext)) files.push(filePath);
|
|
2367
2917
|
}
|
|
2368
2918
|
return files.slice(0, 10);
|
|
@@ -2399,7 +2949,7 @@ Pass an explicit 'files' array to review specific files.`;
|
|
|
2399
2949
|
continue;
|
|
2400
2950
|
}
|
|
2401
2951
|
const resolvedPath = validation.resolvedPath;
|
|
2402
|
-
if (!
|
|
2952
|
+
if (!fs8.existsSync(resolvedPath)) {
|
|
2403
2953
|
allIssues.push({
|
|
2404
2954
|
file: filePath,
|
|
2405
2955
|
line: 0,
|
|
@@ -2411,7 +2961,7 @@ Pass an explicit 'files' array to review specific files.`;
|
|
|
2411
2961
|
continue;
|
|
2412
2962
|
}
|
|
2413
2963
|
try {
|
|
2414
|
-
const content =
|
|
2964
|
+
const content = fs8.readFileSync(resolvedPath, "utf-8");
|
|
2415
2965
|
filesReviewed++;
|
|
2416
2966
|
checkGeneral(filePath, content, allIssues);
|
|
2417
2967
|
switch (focus) {
|
|
@@ -2509,12 +3059,12 @@ function isTestFile(filePath) {
|
|
|
2509
3059
|
return /\.(test|spec)\.[a-z]+$/i.test(filePath) || /__tests__/.test(filePath);
|
|
2510
3060
|
}
|
|
2511
3061
|
function isTsLike(filePath) {
|
|
2512
|
-
const ext =
|
|
3062
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2513
3063
|
return ext === ".ts" || ext === ".tsx";
|
|
2514
3064
|
}
|
|
2515
3065
|
function checkGeneral(filePath, content, issues) {
|
|
2516
3066
|
const lines = content.split("\n");
|
|
2517
|
-
const ext =
|
|
3067
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2518
3068
|
if (content.length > 0 && !content.endsWith("\n")) {
|
|
2519
3069
|
issues.push({
|
|
2520
3070
|
file: filePath,
|
|
@@ -2716,7 +3266,7 @@ function stripJsonComments(text) {
|
|
|
2716
3266
|
return cleaned.join("\n");
|
|
2717
3267
|
}
|
|
2718
3268
|
function checkConfigFile(filePath, content, issues) {
|
|
2719
|
-
const ext =
|
|
3269
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2720
3270
|
const lines = content.split("\n");
|
|
2721
3271
|
if (ext === ".json" || ext === ".jsonc") {
|
|
2722
3272
|
checkJson(filePath, content, lines, issues);
|
|
@@ -2727,7 +3277,7 @@ function checkConfigFile(filePath, content, issues) {
|
|
|
2727
3277
|
}
|
|
2728
3278
|
}
|
|
2729
3279
|
function checkJson(filePath, content, lines, issues) {
|
|
2730
|
-
const ext =
|
|
3280
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2731
3281
|
const isJsonc = ext === ".jsonc";
|
|
2732
3282
|
const contentToParse = isJsonc ? stripJsonComments(content) : content;
|
|
2733
3283
|
try {
|
|
@@ -3453,10 +4003,10 @@ function formatConventionsOutput(conventions) {
|
|
|
3453
4003
|
}
|
|
3454
4004
|
|
|
3455
4005
|
// src/utils/gitUtils.ts
|
|
3456
|
-
import { execSync as
|
|
4006
|
+
import { execSync as execSync5 } from "child_process";
|
|
3457
4007
|
function runGitCommand(command) {
|
|
3458
4008
|
const root = getProjectRoot();
|
|
3459
|
-
return
|
|
4009
|
+
return execSync5(command, {
|
|
3460
4010
|
cwd: root,
|
|
3461
4011
|
encoding: "utf-8",
|
|
3462
4012
|
maxBuffer: 2 * 1024 * 1024
|
|
@@ -3584,8 +4134,8 @@ async function handleGitDiff(params) {
|
|
|
3584
4134
|
}
|
|
3585
4135
|
|
|
3586
4136
|
// src/tools/projectStructure.ts
|
|
3587
|
-
import
|
|
3588
|
-
import
|
|
4137
|
+
import fs9 from "fs";
|
|
4138
|
+
import path10 from "path";
|
|
3589
4139
|
var DEFAULT_IGNORE = [
|
|
3590
4140
|
"node_modules",
|
|
3591
4141
|
".git",
|
|
@@ -3603,7 +4153,7 @@ var DEFAULT_IGNORE = [
|
|
|
3603
4153
|
"*.log"
|
|
3604
4154
|
];
|
|
3605
4155
|
var treeCache = /* @__PURE__ */ new Map();
|
|
3606
|
-
var
|
|
4156
|
+
var CACHE_TTL_MS3 = 6e4;
|
|
3607
4157
|
async function handleProjectStructure(params) {
|
|
3608
4158
|
const {
|
|
3609
4159
|
depth = 3,
|
|
@@ -3615,14 +4165,14 @@ async function handleProjectStructure(params) {
|
|
|
3615
4165
|
const clampedDepth = Math.max(1, Math.min(depth, 6));
|
|
3616
4166
|
const cacheKey = `${root}:${clampedDepth}:${folderOnly}:${includePattern ?? ""}:${excludePattern ?? ""}`;
|
|
3617
4167
|
const cached = treeCache.get(cacheKey);
|
|
3618
|
-
if (cached && Date.now() - cached.timestamp <
|
|
4168
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS3) {
|
|
3619
4169
|
sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly, cached: true });
|
|
3620
4170
|
return cached.result;
|
|
3621
4171
|
}
|
|
3622
4172
|
sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
|
|
3623
4173
|
try {
|
|
3624
4174
|
const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
|
|
3625
|
-
const projectName =
|
|
4175
|
+
const projectName = path10.basename(root);
|
|
3626
4176
|
const lines = [
|
|
3627
4177
|
"[Project Structure] - " + projectName,
|
|
3628
4178
|
"Depth: " + clampedDepth + " | " + (folderOnly ? "Folders only" : "Files and folders"),
|
|
@@ -3645,7 +4195,7 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3645
4195
|
const lines = [];
|
|
3646
4196
|
let entries = [];
|
|
3647
4197
|
try {
|
|
3648
|
-
entries =
|
|
4198
|
+
entries = fs9.readdirSync(currentDir, { withFileTypes: true });
|
|
3649
4199
|
} catch {
|
|
3650
4200
|
return lines;
|
|
3651
4201
|
}
|
|
@@ -3654,12 +4204,12 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3654
4204
|
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
3655
4205
|
return a.name.localeCompare(b.name);
|
|
3656
4206
|
});
|
|
3657
|
-
const relativeDir =
|
|
4207
|
+
const relativeDir = path10.relative(root, currentDir) || ".";
|
|
3658
4208
|
const prefix = getPrefix(currentDepth);
|
|
3659
4209
|
for (const entry of entries) {
|
|
3660
4210
|
if (shouldIgnore(entry.name, relativeDir, root)) continue;
|
|
3661
|
-
const fullPath =
|
|
3662
|
-
const relativePath =
|
|
4211
|
+
const fullPath = path10.join(currentDir, entry.name);
|
|
4212
|
+
const relativePath = path10.relative(root, fullPath);
|
|
3663
4213
|
if (includePattern && !entry.name.includes(includePattern) && !relativePath.includes(includePattern)) {
|
|
3664
4214
|
if (!entry.isDirectory()) continue;
|
|
3665
4215
|
}
|
|
@@ -3670,11 +4220,11 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3670
4220
|
lines.push(prefix + "[D] " + entry.name + "/");
|
|
3671
4221
|
let subEntries = [];
|
|
3672
4222
|
try {
|
|
3673
|
-
subEntries =
|
|
4223
|
+
subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
|
|
3674
4224
|
} catch {
|
|
3675
4225
|
}
|
|
3676
4226
|
const hasVisibleContent = subEntries.some(
|
|
3677
|
-
(e) => !shouldIgnore(e.name,
|
|
4227
|
+
(e) => !shouldIgnore(e.name, path10.relative(root, fullPath), root)
|
|
3678
4228
|
);
|
|
3679
4229
|
if (hasVisibleContent) {
|
|
3680
4230
|
const subLines = buildTree(root, fullPath, maxDepth, currentDepth + 1, folderOnly, includePattern, excludePattern);
|
|
@@ -3705,7 +4255,7 @@ function getPrefix(depth) {
|
|
|
3705
4255
|
}
|
|
3706
4256
|
function getFileSize(fullPath) {
|
|
3707
4257
|
try {
|
|
3708
|
-
const stat =
|
|
4258
|
+
const stat = fs9.statSync(fullPath);
|
|
3709
4259
|
if (stat.size < 1024) return stat.size + "B";
|
|
3710
4260
|
if (stat.size < 1024 * 1024) return (stat.size / 1024).toFixed(0) + "KB";
|
|
3711
4261
|
const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
|
|
@@ -3716,8 +4266,8 @@ function getFileSize(fullPath) {
|
|
|
3716
4266
|
}
|
|
3717
4267
|
|
|
3718
4268
|
// src/tools/staticAnalysis.ts
|
|
3719
|
-
import
|
|
3720
|
-
import
|
|
4269
|
+
import fs10 from "fs";
|
|
4270
|
+
import path11 from "path";
|
|
3721
4271
|
async function handleStaticAnalysis(params) {
|
|
3722
4272
|
const { tool = "all", files, autoFix = false, timeout = 60 } = params;
|
|
3723
4273
|
const root = getProjectRoot();
|
|
@@ -3810,11 +4360,11 @@ function detectAvailableTools(root) {
|
|
|
3810
4360
|
"eslint.config.js",
|
|
3811
4361
|
"eslint.config.mjs"
|
|
3812
4362
|
];
|
|
3813
|
-
const hasEslintConfig = eslintConfigs.some((cfg) =>
|
|
4363
|
+
const hasEslintConfig = eslintConfigs.some((cfg) => fs10.existsSync(path11.join(root, cfg)));
|
|
3814
4364
|
if (hasEslintConfig && depNames.has("eslint")) {
|
|
3815
4365
|
tools.push("eslint");
|
|
3816
4366
|
}
|
|
3817
|
-
if (
|
|
4367
|
+
if (fs10.existsSync(path11.join(root, "tsconfig.json")) && depNames.has("typescript")) {
|
|
3818
4368
|
tools.push("tsc");
|
|
3819
4369
|
}
|
|
3820
4370
|
const prettierConfigs = [
|
|
@@ -3825,20 +4375,20 @@ function detectAvailableTools(root) {
|
|
|
3825
4375
|
".prettierrc.toml",
|
|
3826
4376
|
"prettier.config.js"
|
|
3827
4377
|
];
|
|
3828
|
-
const hasPrettierConfig = prettierConfigs.some((cfg) =>
|
|
4378
|
+
const hasPrettierConfig = prettierConfigs.some((cfg) => fs10.existsSync(path11.join(root, cfg)));
|
|
3829
4379
|
if (hasPrettierConfig && depNames.has("prettier")) {
|
|
3830
4380
|
tools.push("prettier");
|
|
3831
4381
|
}
|
|
3832
|
-
if (
|
|
4382
|
+
if (fs10.existsSync(path11.join(root, "ruff.toml")) || fs10.existsSync(path11.join(root, ".ruff.toml"))) {
|
|
3833
4383
|
tools.push("ruff");
|
|
3834
4384
|
}
|
|
3835
4385
|
return tools;
|
|
3836
4386
|
}
|
|
3837
4387
|
function readPackageJson(root) {
|
|
3838
4388
|
try {
|
|
3839
|
-
const pkgPath =
|
|
3840
|
-
if (
|
|
3841
|
-
return JSON.parse(
|
|
4389
|
+
const pkgPath = path11.join(root, "package.json");
|
|
4390
|
+
if (fs10.existsSync(pkgPath)) {
|
|
4391
|
+
return JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
|
|
3842
4392
|
}
|
|
3843
4393
|
} catch {
|
|
3844
4394
|
}
|
|
@@ -3894,18 +4444,18 @@ function buildToolCommand(tool, files, autoFix) {
|
|
|
3894
4444
|
}
|
|
3895
4445
|
function detectPackageManagerPrefix() {
|
|
3896
4446
|
const root = getProjectRoot();
|
|
3897
|
-
if (
|
|
3898
|
-
if (
|
|
4447
|
+
if (fs10.existsSync(path11.join(root, "pnpm-lock.yaml"))) return "pnpm ";
|
|
4448
|
+
if (fs10.existsSync(path11.join(root, "yarn.lock"))) return "yarn ";
|
|
3899
4449
|
return "npx --no-install ";
|
|
3900
4450
|
}
|
|
3901
4451
|
function findBinary(root, binName) {
|
|
3902
4452
|
const candidates = [
|
|
3903
|
-
|
|
3904
|
-
|
|
4453
|
+
path11.join(root, "node_modules", ".bin", binName),
|
|
4454
|
+
path11.join(root, "..", "node_modules", ".bin", binName)
|
|
3905
4455
|
];
|
|
3906
4456
|
for (const candidate of candidates) {
|
|
3907
4457
|
try {
|
|
3908
|
-
if (
|
|
4458
|
+
if (fs10.existsSync(candidate)) {
|
|
3909
4459
|
return candidate;
|
|
3910
4460
|
}
|
|
3911
4461
|
} catch {
|
|
@@ -3929,7 +4479,7 @@ function parseToolOutput(tool, stdout, stderr, projectRoot) {
|
|
|
3929
4479
|
}
|
|
3930
4480
|
function resolveToolPath(filePath, projectRoot) {
|
|
3931
4481
|
const trimmed = filePath.trim();
|
|
3932
|
-
return
|
|
4482
|
+
return path11.isAbsolute(trimmed) ? path11.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
|
|
3933
4483
|
}
|
|
3934
4484
|
function parseEslintOutput(output, projectRoot) {
|
|
3935
4485
|
const issues = [];
|
|
@@ -4112,7 +4662,7 @@ function formatToolNotAvailable(requested, available) {
|
|
|
4112
4662
|
}
|
|
4113
4663
|
|
|
4114
4664
|
// src/utils/kumaShared.ts
|
|
4115
|
-
import { execSync as
|
|
4665
|
+
import { execSync as execSync6 } from "child_process";
|
|
4116
4666
|
function getSessionStats(inputGoal) {
|
|
4117
4667
|
const summary = sessionMemory.getSummary();
|
|
4118
4668
|
const goal = inputGoal || summary.currentGoal || "";
|
|
@@ -4134,7 +4684,7 @@ function getSessionStats(inputGoal) {
|
|
|
4134
4684
|
function getGitDiffStat(timeout = 3e3) {
|
|
4135
4685
|
try {
|
|
4136
4686
|
const root = getProjectRoot();
|
|
4137
|
-
return
|
|
4687
|
+
return execSync6("git diff --stat", {
|
|
4138
4688
|
cwd: root,
|
|
4139
4689
|
encoding: "utf-8",
|
|
4140
4690
|
timeout
|
|
@@ -4407,8 +4957,8 @@ function formatAnalytics(a) {
|
|
|
4407
4957
|
}
|
|
4408
4958
|
|
|
4409
4959
|
// src/engine/kumaHealthDashboard.ts
|
|
4410
|
-
import
|
|
4411
|
-
import
|
|
4960
|
+
import fs11 from "fs";
|
|
4961
|
+
import path12 from "path";
|
|
4412
4962
|
function computeHealthDashboard() {
|
|
4413
4963
|
const analytics = computeAnalytics();
|
|
4414
4964
|
const root = getProjectRoot();
|
|
@@ -4416,27 +4966,27 @@ function computeHealthDashboard() {
|
|
|
4416
4966
|
const gitChanges = gitStat ? gitStat.split("\n").filter((l) => l.includes("|")).length : 0;
|
|
4417
4967
|
let backupCount = 0;
|
|
4418
4968
|
try {
|
|
4419
|
-
const bd =
|
|
4420
|
-
if (
|
|
4969
|
+
const bd = path12.join(root, ".kuma", "backups");
|
|
4970
|
+
if (fs11.existsSync(bd)) backupCount = fs11.readdirSync(bd).filter((d) => /^\d+$/.test(d)).length;
|
|
4421
4971
|
} catch {
|
|
4422
4972
|
}
|
|
4423
4973
|
const dirFileCounts = /* @__PURE__ */ new Map();
|
|
4424
4974
|
try {
|
|
4425
4975
|
const walk = (dir, base) => {
|
|
4426
4976
|
try {
|
|
4427
|
-
for (const e of
|
|
4977
|
+
for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
4428
4978
|
if (e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
4429
|
-
const full =
|
|
4979
|
+
const full = path12.join(dir, e.name);
|
|
4430
4980
|
if (e.isDirectory()) walk(full, base);
|
|
4431
4981
|
else if (e.isFile() && /\.(ts|tsx|js|jsx)$/.test(e.name)) {
|
|
4432
|
-
const rel =
|
|
4982
|
+
const rel = path12.dirname(path12.relative(base, full));
|
|
4433
4983
|
dirFileCounts.set(rel, (dirFileCounts.get(rel) || 0) + 1);
|
|
4434
4984
|
}
|
|
4435
4985
|
}
|
|
4436
4986
|
} catch {
|
|
4437
4987
|
}
|
|
4438
4988
|
};
|
|
4439
|
-
walk(
|
|
4989
|
+
walk(path12.join(root, "src"), root);
|
|
4440
4990
|
} catch {
|
|
4441
4991
|
}
|
|
4442
4992
|
const dirMap = /* @__PURE__ */ new Map();
|
|
@@ -4444,7 +4994,7 @@ function computeHealthDashboard() {
|
|
|
4444
4994
|
if (call.toolName === "precise_diff_editor") {
|
|
4445
4995
|
const fp = call.params?.filePath;
|
|
4446
4996
|
if (fp) {
|
|
4447
|
-
const dir =
|
|
4997
|
+
const dir = path12.dirname(fp) || ".";
|
|
4448
4998
|
const e = dirMap.get(dir) || { edits: 0, failures: 0 };
|
|
4449
4999
|
e.edits++;
|
|
4450
5000
|
if (call.params?.success === false) e.failures++;
|
|
@@ -4524,9 +5074,9 @@ function formatHealthDashboard(r) {
|
|
|
4524
5074
|
}
|
|
4525
5075
|
|
|
4526
5076
|
// src/guards/antiPatternDetector.ts
|
|
4527
|
-
import
|
|
4528
|
-
import
|
|
4529
|
-
import { execSync as
|
|
5077
|
+
import fs12 from "fs";
|
|
5078
|
+
import path13 from "path";
|
|
5079
|
+
import { execSync as execSync7 } from "child_process";
|
|
4530
5080
|
var SCRIPT_PATCH_PATTERNS = [
|
|
4531
5081
|
"writeFileSync",
|
|
4532
5082
|
"writeFile",
|
|
@@ -4552,20 +5102,20 @@ function findPatchScripts(projectRoot) {
|
|
|
4552
5102
|
const warnings = [];
|
|
4553
5103
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
4554
5104
|
for (const file of recentFiles) {
|
|
4555
|
-
const ext =
|
|
5105
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4556
5106
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
4557
|
-
const relativePath =
|
|
5107
|
+
const relativePath = path13.relative(projectRoot, file);
|
|
4558
5108
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
|
|
4559
5109
|
continue;
|
|
4560
5110
|
}
|
|
4561
5111
|
try {
|
|
4562
|
-
const content =
|
|
5112
|
+
const content = fs12.readFileSync(file, "utf-8").toLowerCase();
|
|
4563
5113
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
4564
5114
|
if (matchedPattern) {
|
|
4565
5115
|
warnings.push({
|
|
4566
5116
|
severity: "high",
|
|
4567
5117
|
pattern: "script-patching",
|
|
4568
|
-
message: `Created script file that modifies other files: ${
|
|
5118
|
+
message: `Created script file that modifies other files: ${path13.basename(file)}`,
|
|
4569
5119
|
suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
|
|
4570
5120
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
4571
5121
|
filePath: relativePath
|
|
@@ -4599,15 +5149,15 @@ function detectBashGrepUsage() {
|
|
|
4599
5149
|
function scanRecentFiles(projectRoot) {
|
|
4600
5150
|
const recent = [];
|
|
4601
5151
|
try {
|
|
4602
|
-
const entries =
|
|
5152
|
+
const entries = fs12.readdirSync(projectRoot, { withFileTypes: true });
|
|
4603
5153
|
const now = Date.now();
|
|
4604
5154
|
const recentThreshold = 30 * 60 * 1e3;
|
|
4605
5155
|
for (const entry of entries) {
|
|
4606
5156
|
if (!entry.isFile()) continue;
|
|
4607
5157
|
try {
|
|
4608
|
-
const stat =
|
|
5158
|
+
const stat = fs12.statSync(path13.join(projectRoot, entry.name));
|
|
4609
5159
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
4610
|
-
recent.push(
|
|
5160
|
+
recent.push(path13.join(projectRoot, entry.name));
|
|
4611
5161
|
}
|
|
4612
5162
|
} catch {
|
|
4613
5163
|
continue;
|
|
@@ -4620,7 +5170,7 @@ function scanRecentFiles(projectRoot) {
|
|
|
4620
5170
|
function detectGitPatchScripts(projectRoot) {
|
|
4621
5171
|
const warnings = [];
|
|
4622
5172
|
try {
|
|
4623
|
-
const statusStdout =
|
|
5173
|
+
const statusStdout = execSync7("git status --porcelain", {
|
|
4624
5174
|
cwd: projectRoot,
|
|
4625
5175
|
encoding: "utf-8",
|
|
4626
5176
|
timeout: 3e3
|
|
@@ -4631,14 +5181,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
4631
5181
|
const prefix = line.substring(0, 2);
|
|
4632
5182
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
4633
5183
|
const file = line.substring(3).trim();
|
|
4634
|
-
const ext =
|
|
5184
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4635
5185
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
4636
5186
|
const isRootLevel = !file.includes("/");
|
|
4637
5187
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
4638
5188
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
4639
|
-
const fullPath =
|
|
4640
|
-
if (
|
|
4641
|
-
const content =
|
|
5189
|
+
const fullPath = path13.join(projectRoot, file);
|
|
5190
|
+
if (fs12.existsSync(fullPath)) {
|
|
5191
|
+
const content = fs12.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
4642
5192
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
4643
5193
|
if (matchedPattern) {
|
|
4644
5194
|
warnings.push({
|
|
@@ -4653,7 +5203,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
4653
5203
|
}
|
|
4654
5204
|
}
|
|
4655
5205
|
}
|
|
4656
|
-
const deletedStdout =
|
|
5206
|
+
const deletedStdout = execSync7("git diff --name-only --diff-filter=D HEAD", {
|
|
4657
5207
|
cwd: projectRoot,
|
|
4658
5208
|
encoding: "utf-8",
|
|
4659
5209
|
timeout: 3e3
|
|
@@ -4661,7 +5211,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
4661
5211
|
if (deletedStdout) {
|
|
4662
5212
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
4663
5213
|
for (const file of deletedFiles) {
|
|
4664
|
-
const ext =
|
|
5214
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4665
5215
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
4666
5216
|
warnings.push({
|
|
4667
5217
|
severity: "high",
|
|
@@ -4721,21 +5271,21 @@ function detectAllAntiPatterns() {
|
|
|
4721
5271
|
}
|
|
4722
5272
|
|
|
4723
5273
|
// src/engine/contextSnapshot.ts
|
|
4724
|
-
import
|
|
4725
|
-
import
|
|
4726
|
-
import { execSync as
|
|
5274
|
+
import fs13 from "fs";
|
|
5275
|
+
import path14 from "path";
|
|
5276
|
+
import { execSync as execSync8 } from "child_process";
|
|
4727
5277
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
4728
5278
|
function snapshotDir() {
|
|
4729
|
-
return
|
|
5279
|
+
return path14.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
4730
5280
|
}
|
|
4731
5281
|
function ensureSnapshotDir() {
|
|
4732
5282
|
const dir = snapshotDir();
|
|
4733
|
-
if (!
|
|
4734
|
-
|
|
5283
|
+
if (!fs13.existsSync(dir)) {
|
|
5284
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
4735
5285
|
}
|
|
4736
5286
|
}
|
|
4737
5287
|
function snapshotFilePath(timestamp) {
|
|
4738
|
-
return
|
|
5288
|
+
return path14.join(snapshotDir(), `${timestamp}.json`);
|
|
4739
5289
|
}
|
|
4740
5290
|
function saveSnapshot(goal) {
|
|
4741
5291
|
try {
|
|
@@ -4756,7 +5306,7 @@ function saveSnapshot(goal) {
|
|
|
4756
5306
|
hasConventions: !!summary.hasConventions
|
|
4757
5307
|
};
|
|
4758
5308
|
try {
|
|
4759
|
-
|
|
5309
|
+
fs13.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
4760
5310
|
} catch {
|
|
4761
5311
|
}
|
|
4762
5312
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -4764,12 +5314,12 @@ function saveSnapshot(goal) {
|
|
|
4764
5314
|
}
|
|
4765
5315
|
function listSnapshots() {
|
|
4766
5316
|
const dir = snapshotDir();
|
|
4767
|
-
if (!
|
|
5317
|
+
if (!fs13.existsSync(dir)) return [];
|
|
4768
5318
|
try {
|
|
4769
|
-
const files =
|
|
5319
|
+
const files = fs13.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
|
|
4770
5320
|
return files.map((f) => {
|
|
4771
5321
|
try {
|
|
4772
|
-
const content =
|
|
5322
|
+
const content = fs13.readFileSync(path14.join(dir, f), "utf-8");
|
|
4773
5323
|
return JSON.parse(content);
|
|
4774
5324
|
} catch {
|
|
4775
5325
|
return null;
|
|
@@ -4812,7 +5362,7 @@ function formatSnapshot(snapshot) {
|
|
|
4812
5362
|
function getGitDiffStat2() {
|
|
4813
5363
|
try {
|
|
4814
5364
|
const root = getProjectRoot();
|
|
4815
|
-
const stdout =
|
|
5365
|
+
const stdout = execSync8("git diff --stat", {
|
|
4816
5366
|
cwd: root,
|
|
4817
5367
|
encoding: "utf-8",
|
|
4818
5368
|
timeout: 3e3
|
|
@@ -4957,25 +5507,25 @@ async function handleKumaContext(params) {
|
|
|
4957
5507
|
}
|
|
4958
5508
|
|
|
4959
5509
|
// src/tools/kumaInit.ts
|
|
4960
|
-
import
|
|
4961
|
-
import
|
|
5510
|
+
import fs14 from "fs";
|
|
5511
|
+
import path15 from "path";
|
|
4962
5512
|
async function handleKumaInit(params) {
|
|
4963
5513
|
const root = params.projectRoot ?? getProjectRoot();
|
|
4964
|
-
const kumaDir =
|
|
4965
|
-
const memoriesDir =
|
|
4966
|
-
const initMdPath =
|
|
5514
|
+
const kumaDir = path15.join(root, ".kuma");
|
|
5515
|
+
const memoriesDir = path15.join(kumaDir, "memories");
|
|
5516
|
+
const initMdPath = path15.join(kumaDir, "init.md");
|
|
4967
5517
|
sessionMemory.recordToolCall("kuma_init", { projectRoot: root });
|
|
4968
5518
|
let rules = "";
|
|
4969
|
-
if (
|
|
4970
|
-
rules =
|
|
5519
|
+
if (fs14.existsSync(initMdPath)) {
|
|
5520
|
+
rules = fs14.readFileSync(initMdPath, "utf-8");
|
|
4971
5521
|
}
|
|
4972
5522
|
const memories = [];
|
|
4973
|
-
if (
|
|
5523
|
+
if (fs14.existsSync(memoriesDir)) {
|
|
4974
5524
|
try {
|
|
4975
|
-
const files =
|
|
5525
|
+
const files = fs14.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
|
|
4976
5526
|
for (const file of files) {
|
|
4977
5527
|
try {
|
|
4978
|
-
const content =
|
|
5528
|
+
const content = fs14.readFileSync(path15.join(memoriesDir, file), "utf-8");
|
|
4979
5529
|
memories.push({ topic: file.replace(/\.md$/, ""), content });
|
|
4980
5530
|
} catch {
|
|
4981
5531
|
}
|
|
@@ -5019,9 +5569,9 @@ ${memoryList}`);
|
|
|
5019
5569
|
}
|
|
5020
5570
|
|
|
5021
5571
|
// src/tools/kumaRisk.ts
|
|
5022
|
-
import
|
|
5023
|
-
import
|
|
5024
|
-
import
|
|
5572
|
+
import path16 from "path";
|
|
5573
|
+
import fg4 from "fast-glob";
|
|
5574
|
+
import fs15 from "fs";
|
|
5025
5575
|
async function handleKumaRisk(params) {
|
|
5026
5576
|
const { symbol, filePath, depth = 2 } = params;
|
|
5027
5577
|
if (!symbol && !filePath) {
|
|
@@ -5055,7 +5605,7 @@ async function handleKumaRisk(params) {
|
|
|
5055
5605
|
];
|
|
5056
5606
|
let entries = [];
|
|
5057
5607
|
try {
|
|
5058
|
-
entries = await
|
|
5608
|
+
entries = await fg4("**/*.{ts,tsx,js,jsx,mjs,cjs}", {
|
|
5059
5609
|
cwd: root,
|
|
5060
5610
|
ignore: ignorePatterns,
|
|
5061
5611
|
onlyFiles: true,
|
|
@@ -5074,11 +5624,11 @@ async function handleKumaRisk(params) {
|
|
|
5074
5624
|
for (const entry of entries) {
|
|
5075
5625
|
if (results.length >= maxResults) break;
|
|
5076
5626
|
try {
|
|
5077
|
-
const fullPath =
|
|
5078
|
-
const stat =
|
|
5627
|
+
const fullPath = path16.join(root, entry);
|
|
5628
|
+
const stat = fs15.statSync(fullPath);
|
|
5079
5629
|
if (stat.size > 5e5) continue;
|
|
5080
5630
|
if (isBinaryFile(fullPath)) continue;
|
|
5081
|
-
const content =
|
|
5631
|
+
const content = fs15.readFileSync(fullPath, "utf-8");
|
|
5082
5632
|
const lines = content.split("\n");
|
|
5083
5633
|
for (let i = 0; i < lines.length; i++) {
|
|
5084
5634
|
if (results.length >= maxResults) break;
|
|
@@ -5202,8 +5752,8 @@ function formatRiskReport(report, circularDepWarning) {
|
|
|
5202
5752
|
}
|
|
5203
5753
|
|
|
5204
5754
|
// src/tools/kumaDependencyGuard.ts
|
|
5205
|
-
import
|
|
5206
|
-
import
|
|
5755
|
+
import fs16 from "fs";
|
|
5756
|
+
import path17 from "path";
|
|
5207
5757
|
var NATIVE_JS_ALTERNATIVES = {
|
|
5208
5758
|
"lodash": [
|
|
5209
5759
|
{ method: "Array.prototype.map()", description: "Replace _.map()" },
|
|
@@ -5290,12 +5840,12 @@ var NATIVE_JS_ALTERNATIVES = {
|
|
|
5290
5840
|
};
|
|
5291
5841
|
function findExistingDependency(packageName) {
|
|
5292
5842
|
const root = getProjectRoot();
|
|
5293
|
-
const packageJsonPath =
|
|
5294
|
-
if (!
|
|
5843
|
+
const packageJsonPath = path17.join(root, "package.json");
|
|
5844
|
+
if (!fs16.existsSync(packageJsonPath)) {
|
|
5295
5845
|
return { found: false };
|
|
5296
5846
|
}
|
|
5297
5847
|
try {
|
|
5298
|
-
const pkg = JSON.parse(
|
|
5848
|
+
const pkg = JSON.parse(fs16.readFileSync(packageJsonPath, "utf-8"));
|
|
5299
5849
|
const deps = pkg.dependencies || {};
|
|
5300
5850
|
if (deps[packageName]) {
|
|
5301
5851
|
return { found: true, version: deps[packageName] };
|
|
@@ -5311,12 +5861,12 @@ function findExistingDependency(packageName) {
|
|
|
5311
5861
|
}
|
|
5312
5862
|
function findSimilarExisting(packageName) {
|
|
5313
5863
|
const root = getProjectRoot();
|
|
5314
|
-
const packageJsonPath =
|
|
5315
|
-
if (!
|
|
5864
|
+
const packageJsonPath = path17.join(root, "package.json");
|
|
5865
|
+
if (!fs16.existsSync(packageJsonPath)) {
|
|
5316
5866
|
return [];
|
|
5317
5867
|
}
|
|
5318
5868
|
try {
|
|
5319
|
-
const pkg = JSON.parse(
|
|
5869
|
+
const pkg = JSON.parse(fs16.readFileSync(packageJsonPath, "utf-8"));
|
|
5320
5870
|
const allDeps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
|
|
5321
5871
|
const installedPackages = Object.keys(allDeps);
|
|
5322
5872
|
const alternativeMap = {
|
|
@@ -5427,8 +5977,8 @@ function getEstimatedPackageSize(packageName) {
|
|
|
5427
5977
|
}
|
|
5428
5978
|
|
|
5429
5979
|
// src/tools/kumaPolicy.ts
|
|
5430
|
-
import
|
|
5431
|
-
import
|
|
5980
|
+
import fs17 from "fs";
|
|
5981
|
+
import path18 from "path";
|
|
5432
5982
|
var DEFAULT_POLICY = {
|
|
5433
5983
|
never_touch: [
|
|
5434
5984
|
".env",
|
|
@@ -5454,15 +6004,15 @@ var DEFAULT_POLICY = {
|
|
|
5454
6004
|
};
|
|
5455
6005
|
function loadPolicy() {
|
|
5456
6006
|
const root = getProjectRoot();
|
|
5457
|
-
const ymlPath =
|
|
5458
|
-
const yamlPath =
|
|
6007
|
+
const ymlPath = path18.join(root, ".kuma", "policy.yml");
|
|
6008
|
+
const yamlPath = path18.join(root, ".kuma", "policy.yaml");
|
|
5459
6009
|
let policyContent = null;
|
|
5460
6010
|
let policyPath = null;
|
|
5461
|
-
if (
|
|
5462
|
-
policyContent =
|
|
6011
|
+
if (fs17.existsSync(ymlPath)) {
|
|
6012
|
+
policyContent = fs17.readFileSync(ymlPath, "utf-8");
|
|
5463
6013
|
policyPath = ymlPath;
|
|
5464
|
-
} else if (
|
|
5465
|
-
policyContent =
|
|
6014
|
+
} else if (fs17.existsSync(yamlPath)) {
|
|
6015
|
+
policyContent = fs17.readFileSync(yamlPath, "utf-8");
|
|
5466
6016
|
policyPath = yamlPath;
|
|
5467
6017
|
}
|
|
5468
6018
|
if (!policyContent) {
|
|
@@ -5579,9 +6129,9 @@ function checkFilePathPolicy(filePath, policy) {
|
|
|
5579
6129
|
if (policy.max_file_size && policy.max_file_size > 0) {
|
|
5580
6130
|
try {
|
|
5581
6131
|
const root = getProjectRoot();
|
|
5582
|
-
const fullPath =
|
|
5583
|
-
if (
|
|
5584
|
-
const sizeKB =
|
|
6132
|
+
const fullPath = path18.join(root, filePath);
|
|
6133
|
+
if (fs17.existsSync(fullPath)) {
|
|
6134
|
+
const sizeKB = fs17.statSync(fullPath).size / 1024;
|
|
5585
6135
|
if (sizeKB > policy.max_file_size) {
|
|
5586
6136
|
violations.push({
|
|
5587
6137
|
rule: "max_file_size",
|
|
@@ -5677,8 +6227,8 @@ function formatPolicyResult(type, value, result, policy) {
|
|
|
5677
6227
|
}
|
|
5678
6228
|
|
|
5679
6229
|
// src/engine/safetyScore.ts
|
|
5680
|
-
import
|
|
5681
|
-
import
|
|
6230
|
+
import fs18 from "fs";
|
|
6231
|
+
import path19 from "path";
|
|
5682
6232
|
function computeSafetyScore(inputGoal) {
|
|
5683
6233
|
const stats = getSessionStats(inputGoal);
|
|
5684
6234
|
const checks = [];
|
|
@@ -5722,8 +6272,8 @@ function computeSafetyScore(inputGoal) {
|
|
|
5722
6272
|
totalScore += 20;
|
|
5723
6273
|
}
|
|
5724
6274
|
const backupDir = getKumaBackupsDir();
|
|
5725
|
-
if (
|
|
5726
|
-
const backupCount =
|
|
6275
|
+
if (fs18.existsSync(backupDir)) {
|
|
6276
|
+
const backupCount = fs18.readdirSync(backupDir).filter((d) => /^\d+$/.test(d)).length;
|
|
5727
6277
|
checks.push({
|
|
5728
6278
|
label: "Backup Available",
|
|
5729
6279
|
status: "pass",
|
|
@@ -5741,8 +6291,8 @@ function computeSafetyScore(inputGoal) {
|
|
|
5741
6291
|
totalScore += 5;
|
|
5742
6292
|
}
|
|
5743
6293
|
try {
|
|
5744
|
-
const lspPath =
|
|
5745
|
-
if (
|
|
6294
|
+
const lspPath = path19.join(getProjectRoot(), "node_modules", ".bin", "typescript-language-server");
|
|
6295
|
+
if (fs18.existsSync(lspPath)) {
|
|
5746
6296
|
checks.push({
|
|
5747
6297
|
label: "LSP Available",
|
|
5748
6298
|
status: "pass",
|
|
@@ -5964,8 +6514,8 @@ function formatSafetyScore(report) {
|
|
|
5964
6514
|
|
|
5965
6515
|
// src/engine/kumaDb.ts
|
|
5966
6516
|
import initSqlJs from "sql.js";
|
|
5967
|
-
import
|
|
5968
|
-
import
|
|
6517
|
+
import fs19 from "fs";
|
|
6518
|
+
import path20 from "path";
|
|
5969
6519
|
var DB_FILENAME = "kuma.db";
|
|
5970
6520
|
var dbInstance = null;
|
|
5971
6521
|
var initPromise = null;
|
|
@@ -5978,13 +6528,13 @@ async function getDb() {
|
|
|
5978
6528
|
async function initDb() {
|
|
5979
6529
|
const SQL = await initSqlJs();
|
|
5980
6530
|
const kumaDir = getKumaDir();
|
|
5981
|
-
const dbPath =
|
|
5982
|
-
if (!
|
|
5983
|
-
|
|
6531
|
+
const dbPath = path20.join(kumaDir, DB_FILENAME);
|
|
6532
|
+
if (!fs19.existsSync(kumaDir)) {
|
|
6533
|
+
fs19.mkdirSync(kumaDir, { recursive: true });
|
|
5984
6534
|
}
|
|
5985
6535
|
let db;
|
|
5986
|
-
if (
|
|
5987
|
-
const buffer =
|
|
6536
|
+
if (fs19.existsSync(dbPath)) {
|
|
6537
|
+
const buffer = fs19.readFileSync(dbPath);
|
|
5988
6538
|
db = new SQL.Database(buffer);
|
|
5989
6539
|
} else {
|
|
5990
6540
|
db = new SQL.Database();
|
|
@@ -6001,10 +6551,10 @@ function saveDb(db) {
|
|
|
6001
6551
|
if (!d) return;
|
|
6002
6552
|
try {
|
|
6003
6553
|
const kumaDir = getKumaDir();
|
|
6004
|
-
const dbPath =
|
|
6554
|
+
const dbPath = path20.join(kumaDir, DB_FILENAME);
|
|
6005
6555
|
const data = d.export();
|
|
6006
6556
|
const buffer = Buffer.from(data);
|
|
6007
|
-
|
|
6557
|
+
fs19.writeFileSync(dbPath, buffer);
|
|
6008
6558
|
} catch (err) {
|
|
6009
6559
|
console.error(`[KumaDB] Failed to save database: ${err}`);
|
|
6010
6560
|
}
|
|
@@ -6111,26 +6661,26 @@ function createSchema(db) {
|
|
|
6111
6661
|
}
|
|
6112
6662
|
|
|
6113
6663
|
// src/engine/kumaSelfHeal.ts
|
|
6114
|
-
import { execSync as
|
|
6115
|
-
import
|
|
6116
|
-
import
|
|
6664
|
+
import { execSync as execSync9 } from "child_process";
|
|
6665
|
+
import fs20 from "fs";
|
|
6666
|
+
import path21 from "path";
|
|
6117
6667
|
import crypto from "crypto";
|
|
6118
6668
|
function contentFingerprint(filePath) {
|
|
6119
6669
|
try {
|
|
6120
|
-
const fullPath =
|
|
6121
|
-
if (!
|
|
6122
|
-
const stat =
|
|
6670
|
+
const fullPath = path21.join(getProjectRoot(), filePath);
|
|
6671
|
+
if (!fs20.existsSync(fullPath)) return null;
|
|
6672
|
+
const stat = fs20.statSync(fullPath);
|
|
6123
6673
|
if (!stat.isFile()) return null;
|
|
6124
|
-
const fd =
|
|
6674
|
+
const fd = fs20.openSync(fullPath, "r");
|
|
6125
6675
|
try {
|
|
6126
6676
|
const size = stat.size;
|
|
6127
6677
|
const readSize = Math.min(1024, size);
|
|
6128
6678
|
const head = Buffer.alloc(readSize);
|
|
6129
|
-
|
|
6679
|
+
fs20.readSync(fd, head, 0, readSize, 0);
|
|
6130
6680
|
let tail = Buffer.alloc(0);
|
|
6131
6681
|
if (size > 2048) {
|
|
6132
6682
|
tail = Buffer.alloc(1024);
|
|
6133
|
-
|
|
6683
|
+
fs20.readSync(fd, tail, 0, 1024, size - 1024);
|
|
6134
6684
|
}
|
|
6135
6685
|
const hash = crypto.createHash("md5");
|
|
6136
6686
|
hash.update(head);
|
|
@@ -6138,7 +6688,7 @@ function contentFingerprint(filePath) {
|
|
|
6138
6688
|
hash.update(String(size));
|
|
6139
6689
|
return hash.digest("hex");
|
|
6140
6690
|
} finally {
|
|
6141
|
-
|
|
6691
|
+
fs20.closeSync(fd);
|
|
6142
6692
|
}
|
|
6143
6693
|
} catch {
|
|
6144
6694
|
return null;
|
|
@@ -6147,8 +6697,8 @@ function contentFingerprint(filePath) {
|
|
|
6147
6697
|
function findByFingerprint(fingerprint, oldName) {
|
|
6148
6698
|
try {
|
|
6149
6699
|
const root = getProjectRoot();
|
|
6150
|
-
const ext =
|
|
6151
|
-
const result =
|
|
6700
|
+
const ext = path21.extname(oldName);
|
|
6701
|
+
const result = execSync9(
|
|
6152
6702
|
`find . -name "*${ext}" -type f -not -path "./node_modules/*" -not -path "./.git/*" -not -path "./dist/*" -not -path "./build/*" 2>/dev/null | head -200`,
|
|
6153
6703
|
{ cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 1e4 }
|
|
6154
6704
|
).trim();
|
|
@@ -6181,8 +6731,8 @@ async function detectStaleNodes() {
|
|
|
6181
6731
|
const row = stmt.getAsObject();
|
|
6182
6732
|
const filePath = row.file_path;
|
|
6183
6733
|
if (filePath.startsWith("search::") || filePath.startsWith("api_route::")) continue;
|
|
6184
|
-
const fullPath =
|
|
6185
|
-
if (!
|
|
6734
|
+
const fullPath = path21.join(root, filePath);
|
|
6735
|
+
if (!fs20.existsSync(fullPath)) {
|
|
6186
6736
|
const newPath = findRenamedPath(filePath);
|
|
6187
6737
|
if (newPath) {
|
|
6188
6738
|
stale.push({
|
|
@@ -6195,7 +6745,7 @@ async function detectStaleNodes() {
|
|
|
6195
6745
|
});
|
|
6196
6746
|
} else {
|
|
6197
6747
|
const oldFingerprint = contentFingerprint(filePath);
|
|
6198
|
-
const contentMatch = oldFingerprint ? findByFingerprint(oldFingerprint,
|
|
6748
|
+
const contentMatch = oldFingerprint ? findByFingerprint(oldFingerprint, path21.basename(filePath)) : null;
|
|
6199
6749
|
stale.push({
|
|
6200
6750
|
nodeId: row.id,
|
|
6201
6751
|
type: row.type,
|
|
@@ -6216,7 +6766,7 @@ async function detectStaleNodes() {
|
|
|
6216
6766
|
function findRenamedPath(oldPath) {
|
|
6217
6767
|
try {
|
|
6218
6768
|
const root = getProjectRoot();
|
|
6219
|
-
const output =
|
|
6769
|
+
const output = execSync9(
|
|
6220
6770
|
`git log --follow --diff-filter=R --name-only --format="" -1 -- "${oldPath}"`,
|
|
6221
6771
|
{ cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 5e3 }
|
|
6222
6772
|
).trim();
|
|
@@ -6297,8 +6847,8 @@ async function healOnQuery(filePaths) {
|
|
|
6297
6847
|
try {
|
|
6298
6848
|
const stalePaths = filePaths.filter((fp) => {
|
|
6299
6849
|
if (!fp || fp.startsWith("search::") || fp.startsWith("api_route::")) return false;
|
|
6300
|
-
const fullPath =
|
|
6301
|
-
return !
|
|
6850
|
+
const fullPath = path21.join(getProjectRoot(), fp);
|
|
6851
|
+
return !fs20.existsSync(fullPath);
|
|
6302
6852
|
});
|
|
6303
6853
|
if (stalePaths.length === 0) return { healed: 0 };
|
|
6304
6854
|
let healed = 0;
|
|
@@ -6827,8 +7377,8 @@ async function pruneExperiences(keepPerTool = 50) {
|
|
|
6827
7377
|
|
|
6828
7378
|
// src/engine/lspClient.ts
|
|
6829
7379
|
import { spawn as spawn2 } from "child_process";
|
|
6830
|
-
import
|
|
6831
|
-
import
|
|
7380
|
+
import path22 from "path";
|
|
7381
|
+
import fs21 from "fs";
|
|
6832
7382
|
var LSPClient = class {
|
|
6833
7383
|
process = null;
|
|
6834
7384
|
requestId = 0;
|
|
@@ -6866,8 +7416,8 @@ var LSPClient = class {
|
|
|
6866
7416
|
console.error(`[LSP] typescript-language-server not found. LSP features will fallback to regex.`);
|
|
6867
7417
|
return;
|
|
6868
7418
|
}
|
|
6869
|
-
const tsconfigPath =
|
|
6870
|
-
if (!
|
|
7419
|
+
const tsconfigPath = path22.join(root, "tsconfig.json");
|
|
7420
|
+
if (!fs21.existsSync(tsconfigPath)) {
|
|
6871
7421
|
console.error(`[LSP] Warning: tsconfig.json not found at "${root}". Running in implicit project mode.`);
|
|
6872
7422
|
}
|
|
6873
7423
|
this.process = spawn2(lspBinary, ["--stdio", "--log-level=4"], {
|
|
@@ -6929,19 +7479,19 @@ var LSPClient = class {
|
|
|
6929
7479
|
/** Resolve the typescript-language-server binary from local or global install */
|
|
6930
7480
|
resolveLspBinary(projectRoot) {
|
|
6931
7481
|
const candidates = [
|
|
6932
|
-
|
|
6933
|
-
|
|
7482
|
+
path22.join(projectRoot, "node_modules", ".bin", "typescript-language-server"),
|
|
7483
|
+
path22.join(projectRoot, "..", "node_modules", ".bin", "typescript-language-server")
|
|
6934
7484
|
];
|
|
6935
7485
|
for (const candidate of candidates) {
|
|
6936
|
-
if (
|
|
7486
|
+
if (fs21.existsSync(candidate)) {
|
|
6937
7487
|
return candidate;
|
|
6938
7488
|
}
|
|
6939
7489
|
}
|
|
6940
7490
|
try {
|
|
6941
7491
|
const envPath = process.env.PATH ?? "";
|
|
6942
|
-
for (const dir of envPath.split(
|
|
6943
|
-
const binPath =
|
|
6944
|
-
if (
|
|
7492
|
+
for (const dir of envPath.split(path22.delimiter)) {
|
|
7493
|
+
const binPath = path22.join(dir, "typescript-language-server");
|
|
7494
|
+
if (fs21.existsSync(binPath)) {
|
|
6945
7495
|
return binPath;
|
|
6946
7496
|
}
|
|
6947
7497
|
}
|
|
@@ -6963,7 +7513,7 @@ var LSPClient = class {
|
|
|
6963
7513
|
this.openDocuments.add(filePath);
|
|
6964
7514
|
let content;
|
|
6965
7515
|
try {
|
|
6966
|
-
content =
|
|
7516
|
+
content = fs21.readFileSync(filePath, "utf-8");
|
|
6967
7517
|
} catch {
|
|
6968
7518
|
content = "";
|
|
6969
7519
|
}
|
|
@@ -7180,8 +7730,8 @@ var LSPClient = class {
|
|
|
7180
7730
|
var lspClient = new LSPClient();
|
|
7181
7731
|
|
|
7182
7732
|
// src/tools/lspTools.ts
|
|
7183
|
-
import
|
|
7184
|
-
import
|
|
7733
|
+
import fs22 from "fs";
|
|
7734
|
+
import path23 from "path";
|
|
7185
7735
|
async function handleFindReferences(params) {
|
|
7186
7736
|
const { filePath, line, character } = params;
|
|
7187
7737
|
const validation = validateFilePath(filePath);
|
|
@@ -7189,7 +7739,7 @@ async function handleFindReferences(params) {
|
|
|
7189
7739
|
return `Error: ${validation.error.message}`;
|
|
7190
7740
|
}
|
|
7191
7741
|
const resolvedPath = validation.resolvedPath;
|
|
7192
|
-
if (!
|
|
7742
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7193
7743
|
return `Error: File not found: "${filePath}".`;
|
|
7194
7744
|
}
|
|
7195
7745
|
if (!lspClient.isAvailable()) {
|
|
@@ -7214,7 +7764,7 @@ No references found for symbol at this position.`;
|
|
|
7214
7764
|
const enrichedRefs = references.map((ref) => {
|
|
7215
7765
|
let lineContent = "";
|
|
7216
7766
|
try {
|
|
7217
|
-
const content =
|
|
7767
|
+
const content = fs22.readFileSync(ref.filePath, "utf-8");
|
|
7218
7768
|
const lines2 = content.split("\n");
|
|
7219
7769
|
lineContent = lines2[ref.line]?.trim() ?? "";
|
|
7220
7770
|
} catch {
|
|
@@ -7230,11 +7780,11 @@ No references found for symbol at this position.`;
|
|
|
7230
7780
|
const projectRoot = getProjectRoot();
|
|
7231
7781
|
const lines = [
|
|
7232
7782
|
`**Find References** \u2014 ${enrichedRefs.length} references found`,
|
|
7233
|
-
`\u{1F4CD} File: ${
|
|
7783
|
+
`\u{1F4CD} File: ${path23.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
|
|
7234
7784
|
""
|
|
7235
7785
|
];
|
|
7236
7786
|
for (const [file, refs] of grouped) {
|
|
7237
|
-
const relPath =
|
|
7787
|
+
const relPath = path23.relative(projectRoot, file);
|
|
7238
7788
|
lines.push(`**\u{1F4C4} ${relPath}:**`);
|
|
7239
7789
|
for (const ref of refs) {
|
|
7240
7790
|
const loc = `L${ref.line + 1}:${ref.character + 1}`;
|
|
@@ -7255,7 +7805,7 @@ async function handleGoToDefinition(params) {
|
|
|
7255
7805
|
return `Error: ${validation.error.message}`;
|
|
7256
7806
|
}
|
|
7257
7807
|
const resolvedPath = validation.resolvedPath;
|
|
7258
|
-
if (!
|
|
7808
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7259
7809
|
return `Error: File not found: "${filePath}".`;
|
|
7260
7810
|
}
|
|
7261
7811
|
if (!lspClient.isAvailable()) {
|
|
@@ -7278,10 +7828,10 @@ Try selecting a smaller/cleaner position, or install typescript-language-server
|
|
|
7278
7828
|
Cannot find definition for symbol at this position.`;
|
|
7279
7829
|
}
|
|
7280
7830
|
const projectRoot = getProjectRoot();
|
|
7281
|
-
const relPath =
|
|
7831
|
+
const relPath = path23.relative(projectRoot, definition.filePath);
|
|
7282
7832
|
let lineContent = "";
|
|
7283
7833
|
try {
|
|
7284
|
-
const content =
|
|
7834
|
+
const content = fs22.readFileSync(definition.filePath, "utf-8");
|
|
7285
7835
|
const lines2 = content.split("\n");
|
|
7286
7836
|
lineContent = lines2[definition.line]?.trim() ?? "";
|
|
7287
7837
|
} catch {
|
|
@@ -7313,7 +7863,7 @@ async function handleRenameSymbol(params) {
|
|
|
7313
7863
|
return `Error: ${validation.error.message}`;
|
|
7314
7864
|
}
|
|
7315
7865
|
const resolvedPath = validation.resolvedPath;
|
|
7316
|
-
if (!
|
|
7866
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7317
7867
|
return `Error: File not found: "${filePath}".`;
|
|
7318
7868
|
}
|
|
7319
7869
|
if (!lspClient.isAvailable()) {
|
|
@@ -7344,7 +7894,7 @@ Make sure:
|
|
|
7344
7894
|
const fileChanges = [];
|
|
7345
7895
|
for (const change of result.changes) {
|
|
7346
7896
|
try {
|
|
7347
|
-
const content =
|
|
7897
|
+
const content = fs22.readFileSync(change.filePath, "utf-8");
|
|
7348
7898
|
const lines2 = content.split("\n");
|
|
7349
7899
|
const sortedEdits = [...change.edits].sort((a, b) => {
|
|
7350
7900
|
if (b.line !== a.line) return b.line - a.line;
|
|
@@ -7358,10 +7908,10 @@ Make sure:
|
|
|
7358
7908
|
lines2[edit.line] = before + edit.newText + after;
|
|
7359
7909
|
}
|
|
7360
7910
|
}
|
|
7361
|
-
|
|
7911
|
+
fs22.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
|
|
7362
7912
|
totalEdits += change.edits.length;
|
|
7363
7913
|
fileChanges.push({
|
|
7364
|
-
filePath:
|
|
7914
|
+
filePath: path23.relative(projectRoot, change.filePath),
|
|
7365
7915
|
editCount: change.edits.length
|
|
7366
7916
|
});
|
|
7367
7917
|
} catch (err) {
|
|
@@ -7388,14 +7938,14 @@ async function handleGetTypeInfo(params) {
|
|
|
7388
7938
|
return `Error: ${validation.error.message}`;
|
|
7389
7939
|
}
|
|
7390
7940
|
const resolvedPath = validation.resolvedPath;
|
|
7391
|
-
if (!
|
|
7941
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7392
7942
|
return `Error: File not found: "${filePath}".`;
|
|
7393
7943
|
}
|
|
7394
7944
|
if (!lspClient.isAvailable()) {
|
|
7395
7945
|
sessionMemory.recordToolCall("lsp_query", { action: "type", filePath, line, character, fallback: "line-context" });
|
|
7396
7946
|
console.error(`[LSP] typescript-language-server not found. Using file-read fallback for type info.`);
|
|
7397
7947
|
try {
|
|
7398
|
-
const fileContent =
|
|
7948
|
+
const fileContent = fs22.readFileSync(resolvedPath, "utf-8");
|
|
7399
7949
|
const fileLines = fileContent.split("\n");
|
|
7400
7950
|
const targetLine = fileLines[line];
|
|
7401
7951
|
if (!targetLine) {
|
|
@@ -7403,7 +7953,7 @@ async function handleGetTypeInfo(params) {
|
|
|
7403
7953
|
}
|
|
7404
7954
|
const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
|
|
7405
7955
|
const projectRoot = getProjectRoot();
|
|
7406
|
-
const relPath =
|
|
7956
|
+
const relPath = path23.relative(projectRoot, resolvedPath);
|
|
7407
7957
|
const contextStart = Math.max(0, line - 3);
|
|
7408
7958
|
const contextEnd = Math.min(fileLines.length, line + 4);
|
|
7409
7959
|
const contextLines = [];
|
|
@@ -7438,7 +7988,7 @@ async function handleGetTypeInfo(params) {
|
|
|
7438
7988
|
No type info for this position.`;
|
|
7439
7989
|
}
|
|
7440
7990
|
const projectRoot = getProjectRoot();
|
|
7441
|
-
const relPath =
|
|
7991
|
+
const relPath = path23.relative(projectRoot, resolvedPath);
|
|
7442
7992
|
const lines = [
|
|
7443
7993
|
`\u{1F4CB} **Type Info** \u2014 \`${relPath}:${line + 1}:${character + 1}\``,
|
|
7444
7994
|
"",
|
|
@@ -7479,7 +8029,7 @@ async function handleLspQuery(params) {
|
|
|
7479
8029
|
}
|
|
7480
8030
|
function extractSymbolAtPosition(filePath, line, character) {
|
|
7481
8031
|
try {
|
|
7482
|
-
const content =
|
|
8032
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
7483
8033
|
const lines = content.split("\n");
|
|
7484
8034
|
const targetLine = lines[line];
|
|
7485
8035
|
if (!targetLine) return null;
|
|
@@ -7497,9 +8047,9 @@ function extractSymbolAtPosition(filePath, line, character) {
|
|
|
7497
8047
|
}
|
|
7498
8048
|
async function fallbackGrepReferences(symbolName, _filePath, _line, _character) {
|
|
7499
8049
|
try {
|
|
7500
|
-
const { default:
|
|
8050
|
+
const { default: fg6 } = await import("fast-glob");
|
|
7501
8051
|
const root = getProjectRoot();
|
|
7502
|
-
const tsFiles = await
|
|
8052
|
+
const tsFiles = await fg6(["**/*.{ts,tsx,js,jsx}"], {
|
|
7503
8053
|
cwd: root,
|
|
7504
8054
|
ignore: ["node_modules/**", "dist/**", ".git/**"],
|
|
7505
8055
|
onlyFiles: true,
|
|
@@ -7510,7 +8060,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
|
|
|
7510
8060
|
const regex = new RegExp(`\\b${escapedSymbol}\\b`, "g");
|
|
7511
8061
|
for (const file of tsFiles.slice(0, 100)) {
|
|
7512
8062
|
try {
|
|
7513
|
-
const content =
|
|
8063
|
+
const content = fs22.readFileSync(file, "utf-8");
|
|
7514
8064
|
const lines2 = content.split("\n");
|
|
7515
8065
|
for (let i = 0; i < lines2.length; i++) {
|
|
7516
8066
|
if (regex.test(lines2[i])) {
|
|
@@ -7539,7 +8089,7 @@ No references found. Symbol may not be used in other files.`;
|
|
|
7539
8089
|
""
|
|
7540
8090
|
];
|
|
7541
8091
|
if (grouped.size > 1) {
|
|
7542
|
-
const fileList = [...grouped.keys()].map((f) =>
|
|
8092
|
+
const fileList = [...grouped.keys()].map((f) => path23.relative(projectRoot, f)).join(", ");
|
|
7543
8093
|
lines.push(`**Ambiguity note:** Found matches across ${grouped.size} different files (${fileList}).`);
|
|
7544
8094
|
lines.push(` Verify each match is the right scope \u2014 results may include same-named symbols.`);
|
|
7545
8095
|
lines.push("");
|
|
@@ -7556,7 +8106,7 @@ No references found. Symbol may not be used in other files.`;
|
|
|
7556
8106
|
}
|
|
7557
8107
|
}
|
|
7558
8108
|
for (const [file, refs] of grouped) {
|
|
7559
|
-
const relPath =
|
|
8109
|
+
const relPath = path23.relative(projectRoot, file);
|
|
7560
8110
|
lines.push(`**\u{1F4C4} ${relPath}:**`);
|
|
7561
8111
|
for (const ref of refs) {
|
|
7562
8112
|
lines.push(` \u2514 L${ref.line} \u2014 ${ref.content}`);
|
|
@@ -7571,9 +8121,9 @@ No references found. Symbol may not be used in other files.`;
|
|
|
7571
8121
|
}
|
|
7572
8122
|
async function fallbackGrepDefinition(symbolName) {
|
|
7573
8123
|
try {
|
|
7574
|
-
const { default:
|
|
8124
|
+
const { default: fg6 } = await import("fast-glob");
|
|
7575
8125
|
const root = getProjectRoot();
|
|
7576
|
-
const tsFiles = await
|
|
8126
|
+
const tsFiles = await fg6(["**/*.{ts,tsx,js,jsx}"], {
|
|
7577
8127
|
cwd: root,
|
|
7578
8128
|
ignore: ["node_modules/**", "dist/**", ".git/**"],
|
|
7579
8129
|
onlyFiles: true,
|
|
@@ -7590,14 +8140,14 @@ async function fallbackGrepDefinition(symbolName) {
|
|
|
7590
8140
|
];
|
|
7591
8141
|
for (const file of tsFiles) {
|
|
7592
8142
|
try {
|
|
7593
|
-
const content =
|
|
8143
|
+
const content = fs22.readFileSync(file, "utf-8");
|
|
7594
8144
|
const lines = content.split("\n");
|
|
7595
8145
|
for (let i = 0; i < lines.length; i++) {
|
|
7596
8146
|
const trimmed = lines[i].trim();
|
|
7597
8147
|
for (const pattern of declPatterns) {
|
|
7598
8148
|
if (pattern.test(trimmed)) {
|
|
7599
8149
|
const projectRoot = getProjectRoot();
|
|
7600
|
-
const relPath =
|
|
8150
|
+
const relPath = path23.relative(projectRoot, file);
|
|
7601
8151
|
return [
|
|
7602
8152
|
`\u{1F4CD} **Go to Definition**`,
|
|
7603
8153
|
`\u{1F4C4} File: \`${relPath}\``,
|
|
@@ -7620,11 +8170,11 @@ Cannot find definition.`;
|
|
|
7620
8170
|
}
|
|
7621
8171
|
|
|
7622
8172
|
// src/engine/kumaTimeMachine.ts
|
|
7623
|
-
import { execSync as
|
|
8173
|
+
import { execSync as execSync10 } from "child_process";
|
|
7624
8174
|
function getBlameForFile(filePath) {
|
|
7625
8175
|
const root = getProjectRoot();
|
|
7626
8176
|
try {
|
|
7627
|
-
const output =
|
|
8177
|
+
const output = execSync10(
|
|
7628
8178
|
`git blame --line-porcelain -- "${filePath}"`,
|
|
7629
8179
|
{ cwd: root, encoding: "utf-8", maxBuffer: 2 * 1024 * 1024, timeout: 1e4 }
|
|
7630
8180
|
);
|
|
@@ -7670,7 +8220,7 @@ function getBlameForFile(filePath) {
|
|
|
7670
8220
|
function getFileHistory(filePath, maxCount = 20) {
|
|
7671
8221
|
const root = getProjectRoot();
|
|
7672
8222
|
try {
|
|
7673
|
-
const output =
|
|
8223
|
+
const output = execSync10(
|
|
7674
8224
|
`git log --follow --format="%H||%an||%ai||%s" --shortstat -- "${filePath}"`,
|
|
7675
8225
|
{ cwd: root, encoding: "utf-8", maxBuffer: 2 * 1024 * 1024, timeout: 1e4 }
|
|
7676
8226
|
);
|
|
@@ -7705,7 +8255,7 @@ async function getSymbolTimeline(symbolName, filePath, symbolType = "function")
|
|
|
7705
8255
|
const blame = getBlameForFile(filePath);
|
|
7706
8256
|
const pattern = getSymbolPattern(symbolName, symbolType);
|
|
7707
8257
|
const root = getProjectRoot();
|
|
7708
|
-
const content =
|
|
8258
|
+
const content = execSync10(`git show HEAD:"${filePath}"`, {
|
|
7709
8259
|
cwd: root,
|
|
7710
8260
|
encoding: "utf-8",
|
|
7711
8261
|
maxBuffer: 2 * 1024 * 1024,
|
|
@@ -7732,7 +8282,7 @@ async function getSymbolTimeline(symbolName, filePath, symbolType = "function")
|
|
|
7732
8282
|
const entries = [];
|
|
7733
8283
|
for (const [hash, data] of commitMap) {
|
|
7734
8284
|
try {
|
|
7735
|
-
const msg =
|
|
8285
|
+
const msg = execSync10(
|
|
7736
8286
|
`git log -1 --format="%s" ${hash}`,
|
|
7737
8287
|
{ cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 3e3 }
|
|
7738
8288
|
).trim();
|
|
@@ -7917,10 +8467,10 @@ async function getIntentPatterns(limit = 10) {
|
|
|
7917
8467
|
ORDER BY id ASC
|
|
7918
8468
|
`);
|
|
7919
8469
|
pathStmt.bind([session.id]);
|
|
7920
|
-
const
|
|
8470
|
+
const path31 = [];
|
|
7921
8471
|
while (pathStmt.step()) {
|
|
7922
8472
|
const row = pathStmt.getAsObject();
|
|
7923
|
-
|
|
8473
|
+
path31.push(row.tool_name);
|
|
7924
8474
|
}
|
|
7925
8475
|
pathStmt.free();
|
|
7926
8476
|
const existing = clusterMap.get(clusterKey) || {
|
|
@@ -7929,7 +8479,7 @@ async function getIntentPatterns(limit = 10) {
|
|
|
7929
8479
|
totalDuration: 0,
|
|
7930
8480
|
lastUsed: 0
|
|
7931
8481
|
};
|
|
7932
|
-
existing.paths.push(
|
|
8482
|
+
existing.paths.push(path31);
|
|
7933
8483
|
existing.successes.push(session.success);
|
|
7934
8484
|
existing.lastUsed = Math.max(existing.lastUsed, session.startedAt);
|
|
7935
8485
|
clusterMap.set(clusterKey, existing);
|
|
@@ -8037,9 +8587,9 @@ function formatIntentSuggestion(intent, suggestion) {
|
|
|
8037
8587
|
}
|
|
8038
8588
|
|
|
8039
8589
|
// src/engine/kumaArchGuard.ts
|
|
8040
|
-
import
|
|
8041
|
-
import
|
|
8042
|
-
import
|
|
8590
|
+
import fg5 from "fast-glob";
|
|
8591
|
+
import fs23 from "fs";
|
|
8592
|
+
import path24 from "path";
|
|
8043
8593
|
var ARCHITECTURES = [
|
|
8044
8594
|
{
|
|
8045
8595
|
name: "clean-architecture",
|
|
@@ -8130,8 +8680,8 @@ var ARCHITECTURES = [
|
|
|
8130
8680
|
];
|
|
8131
8681
|
function detectArchitecture() {
|
|
8132
8682
|
const root = getProjectRoot();
|
|
8133
|
-
const srcDir =
|
|
8134
|
-
if (!
|
|
8683
|
+
const srcDir = path24.join(root, "src");
|
|
8684
|
+
if (!fs23.existsSync(srcDir)) {
|
|
8135
8685
|
return getArchitectureProfile("flat");
|
|
8136
8686
|
}
|
|
8137
8687
|
const scores = ARCHITECTURES.map((arch) => {
|
|
@@ -8142,7 +8692,7 @@ function detectArchitecture() {
|
|
|
8142
8692
|
totalPatterns++;
|
|
8143
8693
|
const globPattern = pattern.replace(/\/\*\*$/, "/**");
|
|
8144
8694
|
try {
|
|
8145
|
-
const matches =
|
|
8695
|
+
const matches = fg5.sync(globPattern, { cwd: root, onlyFiles: false, deep: 1 });
|
|
8146
8696
|
if (matches.length > 0) matchCount++;
|
|
8147
8697
|
} catch {
|
|
8148
8698
|
}
|
|
@@ -8153,10 +8703,10 @@ function detectArchitecture() {
|
|
|
8153
8703
|
scores.sort((a, b) => b.score - a.score);
|
|
8154
8704
|
const best = scores[0];
|
|
8155
8705
|
if (!best || best.score < 0.2) {
|
|
8156
|
-
if (
|
|
8706
|
+
if (fs23.existsSync(path24.join(srcDir, "domain")) || fs23.existsSync(path24.join(srcDir, "entities"))) {
|
|
8157
8707
|
return getArchitectureProfile("clean-architecture");
|
|
8158
8708
|
}
|
|
8159
|
-
if (
|
|
8709
|
+
if (fs23.existsSync(path24.join(srcDir, "data")) || fs23.existsSync(path24.join(srcDir, "business"))) {
|
|
8160
8710
|
return getArchitectureProfile("layered-architecture");
|
|
8161
8711
|
}
|
|
8162
8712
|
return getArchitectureProfile("flat");
|
|
@@ -8227,7 +8777,7 @@ async function scanFilesystemForViolations(profile) {
|
|
|
8227
8777
|
const arch = profile || detectArchitecture();
|
|
8228
8778
|
const root = getProjectRoot();
|
|
8229
8779
|
try {
|
|
8230
|
-
const files = await
|
|
8780
|
+
const files = await fg5(["src/**/*.{ts,tsx,js,jsx}"], {
|
|
8231
8781
|
cwd: root,
|
|
8232
8782
|
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*.d.ts"],
|
|
8233
8783
|
onlyFiles: true
|
|
@@ -8236,7 +8786,7 @@ async function scanFilesystemForViolations(profile) {
|
|
|
8236
8786
|
const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
|
|
8237
8787
|
for (const file of files.slice(0, 200)) {
|
|
8238
8788
|
try {
|
|
8239
|
-
const content =
|
|
8789
|
+
const content = fs23.readFileSync(path24.join(root, file), "utf-8");
|
|
8240
8790
|
const imports = [];
|
|
8241
8791
|
let match;
|
|
8242
8792
|
while ((match = importRegex.exec(content)) !== null) {
|
|
@@ -8249,16 +8799,16 @@ async function scanFilesystemForViolations(profile) {
|
|
|
8249
8799
|
if (!imp.startsWith(".") && !imp.startsWith("/") && !imp.startsWith("src/")) continue;
|
|
8250
8800
|
let targetFile = imp;
|
|
8251
8801
|
if (imp.startsWith(".")) {
|
|
8252
|
-
const resolved =
|
|
8253
|
-
targetFile =
|
|
8802
|
+
const resolved = path24.resolve(path24.dirname(file), imp);
|
|
8803
|
+
targetFile = path24.relative(root, resolved);
|
|
8254
8804
|
} else if (imp.startsWith("/")) {
|
|
8255
8805
|
targetFile = imp.substring(1);
|
|
8256
8806
|
}
|
|
8257
|
-
if (!
|
|
8807
|
+
if (!path24.extname(targetFile)) {
|
|
8258
8808
|
const extVariants = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
8259
8809
|
let found = false;
|
|
8260
8810
|
for (const ext of extVariants) {
|
|
8261
|
-
if (
|
|
8811
|
+
if (fs23.existsSync(path24.join(root, targetFile + ext))) {
|
|
8262
8812
|
targetFile += ext;
|
|
8263
8813
|
found = true;
|
|
8264
8814
|
break;
|
|
@@ -8342,20 +8892,20 @@ function formatArchitectureDetection(profile) {
|
|
|
8342
8892
|
}
|
|
8343
8893
|
|
|
8344
8894
|
// src/engine/kumaMemory.ts
|
|
8345
|
-
import
|
|
8346
|
-
import
|
|
8895
|
+
import fs24 from "fs";
|
|
8896
|
+
import path25 from "path";
|
|
8347
8897
|
var MEMORY_DIR = ".kuma/memories";
|
|
8348
8898
|
function scoreMemoryRelevance(context, limit = 5) {
|
|
8349
8899
|
const results = [];
|
|
8350
|
-
const kumaDir =
|
|
8351
|
-
if (!
|
|
8900
|
+
const kumaDir = path25.join(getProjectRoot(), MEMORY_DIR);
|
|
8901
|
+
if (!fs24.existsSync(kumaDir)) return [];
|
|
8352
8902
|
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
8353
8903
|
if (terms.length === 0) return [];
|
|
8354
8904
|
try {
|
|
8355
|
-
const files =
|
|
8905
|
+
const files = fs24.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
8356
8906
|
for (const file of files) {
|
|
8357
8907
|
try {
|
|
8358
|
-
const content =
|
|
8908
|
+
const content = fs24.readFileSync(path25.join(kumaDir, file), "utf-8");
|
|
8359
8909
|
const lower = content.toLowerCase();
|
|
8360
8910
|
let matchCount = 0;
|
|
8361
8911
|
for (const term of terms) {
|
|
@@ -8435,7 +8985,7 @@ function formatDecisionTemplate() {
|
|
|
8435
8985
|
}
|
|
8436
8986
|
|
|
8437
8987
|
// src/engine/kumaContextEngine.ts
|
|
8438
|
-
import { execSync as
|
|
8988
|
+
import { execSync as execSync11 } from "child_process";
|
|
8439
8989
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
8440
8990
|
"the",
|
|
8441
8991
|
"this",
|
|
@@ -8552,7 +9102,7 @@ async function searchNodesFts(db, terms) {
|
|
|
8552
9102
|
function getFileRecencySeconds(filePath) {
|
|
8553
9103
|
try {
|
|
8554
9104
|
const escapedPath = filePath.replace(/"/g, '\\"');
|
|
8555
|
-
const result =
|
|
9105
|
+
const result = execSync11(
|
|
8556
9106
|
'git log -1 --format=%ct -- "' + escapedPath + '" 2>/dev/null || echo 0',
|
|
8557
9107
|
{ encoding: "utf-8", timeout: 2e3 }
|
|
8558
9108
|
);
|
|
@@ -9269,9 +9819,9 @@ async function buildOwnershipMap(db, focus, limit) {
|
|
|
9269
9819
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
9270
9820
|
while (stmt.step()) {
|
|
9271
9821
|
const row = stmt.getAsObject();
|
|
9272
|
-
const
|
|
9273
|
-
const dir = topDir(
|
|
9274
|
-
if (focus && !
|
|
9822
|
+
const path31 = row.file_path || row.name;
|
|
9823
|
+
const dir = topDir(path31);
|
|
9824
|
+
if (focus && !path31.includes(focus)) continue;
|
|
9275
9825
|
if (!dirGroups.has(dir)) dirGroups.set(dir, []);
|
|
9276
9826
|
const existing = dirGroups.get(dir);
|
|
9277
9827
|
const emoji = row.type === "function" ? "\u{1F527}" : row.type === "file" ? "\u{1F4C4}" : "\u{1F4CC}";
|
|
@@ -9312,8 +9862,8 @@ function mermaidId(name) {
|
|
|
9312
9862
|
}
|
|
9313
9863
|
|
|
9314
9864
|
// src/engine/kumaHeatMap.ts
|
|
9315
|
-
import
|
|
9316
|
-
import
|
|
9865
|
+
import fs25 from "fs";
|
|
9866
|
+
import path26 from "path";
|
|
9317
9867
|
async function computeHeatMap() {
|
|
9318
9868
|
const history = sessionMemory.getToolCallHistory(100);
|
|
9319
9869
|
const db = await getDb();
|
|
@@ -9322,7 +9872,7 @@ async function computeHeatMap() {
|
|
|
9322
9872
|
const p = call.params;
|
|
9323
9873
|
const fp = p.filePath || p.file || "";
|
|
9324
9874
|
if (!fp) continue;
|
|
9325
|
-
const dir =
|
|
9875
|
+
const dir = path26.dirname(fp).split("/")[0] || ".";
|
|
9326
9876
|
const entry = dirData.get(dir) || { edits: 0, reads: 0, failures: 0 };
|
|
9327
9877
|
if (call.toolName === "precise_diff_editor" || call.toolName === "batch_file_writer") {
|
|
9328
9878
|
entry.edits++;
|
|
@@ -9350,12 +9900,12 @@ async function computeHeatMap() {
|
|
|
9350
9900
|
} catch {
|
|
9351
9901
|
}
|
|
9352
9902
|
const root = getProjectRoot();
|
|
9353
|
-
const srcDir =
|
|
9354
|
-
if (
|
|
9903
|
+
const srcDir = path26.join(root, "src");
|
|
9904
|
+
if (fs25.existsSync(srcDir)) {
|
|
9355
9905
|
try {
|
|
9356
|
-
for (const entry of
|
|
9906
|
+
for (const entry of fs25.readdirSync(srcDir, { withFileTypes: true })) {
|
|
9357
9907
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
9358
|
-
const count = countFiles(
|
|
9908
|
+
const count = countFiles(path26.join(srcDir, entry.name));
|
|
9359
9909
|
if (!dirFileCounts.has(entry.name) || dirFileCounts.get(entry.name) < count) {
|
|
9360
9910
|
dirFileCounts.set(entry.name, count);
|
|
9361
9911
|
}
|
|
@@ -9465,9 +10015,9 @@ function formatHeatMap(report) {
|
|
|
9465
10015
|
function countFiles(dir) {
|
|
9466
10016
|
let count = 0;
|
|
9467
10017
|
try {
|
|
9468
|
-
for (const entry of
|
|
10018
|
+
for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
|
|
9469
10019
|
if (entry.name.startsWith(".")) continue;
|
|
9470
|
-
const full =
|
|
10020
|
+
const full = path26.join(dir, entry.name);
|
|
9471
10021
|
if (entry.isDirectory()) {
|
|
9472
10022
|
count += countFiles(full);
|
|
9473
10023
|
} else if (/\.(ts|tsx|js|jsx|go|rs|py|java|kt)$/.test(entry.name)) {
|
|
@@ -9767,72 +10317,72 @@ function simpleHash(str) {
|
|
|
9767
10317
|
}
|
|
9768
10318
|
|
|
9769
10319
|
// src/engine/kumaLock.ts
|
|
9770
|
-
import
|
|
9771
|
-
import
|
|
10320
|
+
import fs26 from "fs";
|
|
10321
|
+
import path27 from "path";
|
|
9772
10322
|
var LOCKS_DIR = ".kuma/locks";
|
|
9773
10323
|
function locksDir() {
|
|
9774
|
-
return
|
|
10324
|
+
return path27.join(getProjectRoot(), LOCKS_DIR);
|
|
9775
10325
|
}
|
|
9776
10326
|
function ensureLocksDir() {
|
|
9777
10327
|
const dir = locksDir();
|
|
9778
|
-
if (!
|
|
10328
|
+
if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
|
|
9779
10329
|
}
|
|
9780
10330
|
function lockPath(filePath) {
|
|
9781
10331
|
const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
|
|
9782
|
-
return
|
|
10332
|
+
return path27.join(locksDir(), `${safeName}.lock.json`);
|
|
9783
10333
|
}
|
|
9784
10334
|
function acquireLock(filePath, agentId) {
|
|
9785
10335
|
ensureLocksDir();
|
|
9786
10336
|
const lp = lockPath(filePath);
|
|
9787
10337
|
const id = agentId || `agent-${process.pid}`;
|
|
9788
|
-
if (
|
|
10338
|
+
if (fs26.existsSync(lp)) {
|
|
9789
10339
|
try {
|
|
9790
|
-
const existing = JSON.parse(
|
|
10340
|
+
const existing = JSON.parse(fs26.readFileSync(lp, "utf-8"));
|
|
9791
10341
|
if (existing.agentId === id) {
|
|
9792
10342
|
return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
|
|
9793
10343
|
}
|
|
9794
10344
|
const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
|
|
9795
10345
|
if (elapsed > 300) {
|
|
9796
|
-
|
|
10346
|
+
fs26.unlinkSync(lp);
|
|
9797
10347
|
} else {
|
|
9798
10348
|
return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
|
|
9799
10349
|
}
|
|
9800
10350
|
} catch {
|
|
9801
|
-
|
|
10351
|
+
fs26.unlinkSync(lp);
|
|
9802
10352
|
}
|
|
9803
10353
|
}
|
|
9804
10354
|
const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
|
|
9805
|
-
|
|
10355
|
+
fs26.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
|
|
9806
10356
|
return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
|
|
9807
10357
|
}
|
|
9808
10358
|
function releaseLock(filePath, agentId) {
|
|
9809
10359
|
ensureLocksDir();
|
|
9810
10360
|
const lp = lockPath(filePath);
|
|
9811
10361
|
const id = agentId || `agent-${process.pid}`;
|
|
9812
|
-
if (!
|
|
10362
|
+
if (!fs26.existsSync(lp)) {
|
|
9813
10363
|
return `\u26A0\uFE0F No lock found for \`${filePath}\``;
|
|
9814
10364
|
}
|
|
9815
10365
|
try {
|
|
9816
|
-
const existing = JSON.parse(
|
|
10366
|
+
const existing = JSON.parse(fs26.readFileSync(lp, "utf-8"));
|
|
9817
10367
|
if (existing.agentId !== id) {
|
|
9818
10368
|
return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
|
|
9819
10369
|
}
|
|
9820
|
-
|
|
10370
|
+
fs26.unlinkSync(lp);
|
|
9821
10371
|
return `\u{1F513} **Lock released** on \`${filePath}\``;
|
|
9822
10372
|
} catch {
|
|
9823
|
-
|
|
10373
|
+
fs26.unlinkSync(lp);
|
|
9824
10374
|
return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
|
|
9825
10375
|
}
|
|
9826
10376
|
}
|
|
9827
10377
|
function listLocks() {
|
|
9828
10378
|
ensureLocksDir();
|
|
9829
10379
|
const dir = locksDir();
|
|
9830
|
-
const files =
|
|
10380
|
+
const files = fs26.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
|
|
9831
10381
|
if (files.length === 0) return "\u{1F513} No active locks.";
|
|
9832
10382
|
const lines = ["\u{1F512} **Active Locks:**", ""];
|
|
9833
10383
|
for (const f of files) {
|
|
9834
10384
|
try {
|
|
9835
|
-
const entry = JSON.parse(
|
|
10385
|
+
const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
|
|
9836
10386
|
const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
|
|
9837
10387
|
lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
|
|
9838
10388
|
} catch {
|
|
@@ -9843,9 +10393,9 @@ function listLocks() {
|
|
|
9843
10393
|
}
|
|
9844
10394
|
function isLocked(filePath) {
|
|
9845
10395
|
const lp = lockPath(filePath);
|
|
9846
|
-
if (!
|
|
10396
|
+
if (!fs26.existsSync(lp)) return { locked: false };
|
|
9847
10397
|
try {
|
|
9848
|
-
const entry = JSON.parse(
|
|
10398
|
+
const entry = JSON.parse(fs26.readFileSync(lp, "utf-8"));
|
|
9849
10399
|
return { locked: true, by: entry.agentId, since: entry.acquiredAt };
|
|
9850
10400
|
} catch {
|
|
9851
10401
|
return { locked: false };
|
|
@@ -9855,16 +10405,16 @@ function cleanStaleLocks() {
|
|
|
9855
10405
|
ensureLocksDir();
|
|
9856
10406
|
const dir = locksDir();
|
|
9857
10407
|
let count = 0;
|
|
9858
|
-
for (const f of
|
|
10408
|
+
for (const f of fs26.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
|
|
9859
10409
|
try {
|
|
9860
|
-
const entry = JSON.parse(
|
|
10410
|
+
const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
|
|
9861
10411
|
if (Date.now() - entry.acquiredAt > 3e5) {
|
|
9862
|
-
|
|
10412
|
+
fs26.unlinkSync(path27.join(dir, f));
|
|
9863
10413
|
count++;
|
|
9864
10414
|
}
|
|
9865
10415
|
} catch {
|
|
9866
10416
|
try {
|
|
9867
|
-
|
|
10417
|
+
fs26.unlinkSync(path27.join(dir, f));
|
|
9868
10418
|
count++;
|
|
9869
10419
|
} catch {
|
|
9870
10420
|
}
|
|
@@ -10212,13 +10762,13 @@ function formatConfidence(report, target) {
|
|
|
10212
10762
|
}
|
|
10213
10763
|
|
|
10214
10764
|
// src/engine/kumaDNA.ts
|
|
10215
|
-
import
|
|
10765
|
+
import path28 from "path";
|
|
10216
10766
|
async function generateDNA() {
|
|
10217
10767
|
try {
|
|
10218
10768
|
const db = await getDb();
|
|
10219
10769
|
const summary = sessionMemory.getSummary();
|
|
10220
10770
|
const root = getProjectRoot();
|
|
10221
|
-
const projectName =
|
|
10771
|
+
const projectName = path28.basename(root);
|
|
10222
10772
|
let totalFiles = 0, totalFunctions = 0, totalTests = 0;
|
|
10223
10773
|
try {
|
|
10224
10774
|
const filesR = db.exec("SELECT COUNT(*) as c FROM nodes WHERE type = 'file'");
|
|
@@ -11067,14 +11617,14 @@ async function handleSafetyCheck(params) {
|
|
|
11067
11617
|
}
|
|
11068
11618
|
|
|
11069
11619
|
// src/engine/kumaCollective.ts
|
|
11070
|
-
import
|
|
11071
|
-
import
|
|
11620
|
+
import fs27 from "fs";
|
|
11621
|
+
import path29 from "path";
|
|
11072
11622
|
var DEFAULT_ENDPOINT = process.env.KUMA_COLLECTIVE_URL || "";
|
|
11073
11623
|
function getSyncConfig() {
|
|
11074
11624
|
try {
|
|
11075
|
-
const configPath =
|
|
11076
|
-
if (
|
|
11077
|
-
const raw =
|
|
11625
|
+
const configPath = path29.join(getProjectRoot(), ".kuma", "config.json");
|
|
11626
|
+
if (fs27.existsSync(configPath)) {
|
|
11627
|
+
const raw = fs27.readFileSync(configPath, "utf-8");
|
|
11078
11628
|
const config = JSON.parse(raw);
|
|
11079
11629
|
if (config.collective) {
|
|
11080
11630
|
return {
|
|
@@ -11094,12 +11644,12 @@ function getSyncConfig() {
|
|
|
11094
11644
|
}
|
|
11095
11645
|
function getInstanceId() {
|
|
11096
11646
|
try {
|
|
11097
|
-
const idPath =
|
|
11098
|
-
if (
|
|
11099
|
-
return
|
|
11647
|
+
const idPath = path29.join(getProjectRoot(), ".kuma", ".instance-id");
|
|
11648
|
+
if (fs27.existsSync(idPath)) {
|
|
11649
|
+
return fs27.readFileSync(idPath, "utf-8").trim();
|
|
11100
11650
|
}
|
|
11101
11651
|
const id = `anon-${Math.random().toString(36).slice(2, 10)}`;
|
|
11102
|
-
|
|
11652
|
+
fs27.writeFileSync(idPath, id, "utf-8");
|
|
11103
11653
|
return id;
|
|
11104
11654
|
} catch {
|
|
11105
11655
|
return "anon-unknown";
|
|
@@ -11206,13 +11756,13 @@ function exportAnonymizedPatterns() {
|
|
|
11206
11756
|
function detectProjectLanguage() {
|
|
11207
11757
|
try {
|
|
11208
11758
|
const root = getProjectRoot();
|
|
11209
|
-
if (
|
|
11210
|
-
if (
|
|
11211
|
-
if (
|
|
11212
|
-
if (
|
|
11213
|
-
if (
|
|
11214
|
-
if (
|
|
11215
|
-
if (
|
|
11759
|
+
if (fs27.existsSync(path29.join(root, "go.mod"))) return "go";
|
|
11760
|
+
if (fs27.existsSync(path29.join(root, "Cargo.toml"))) return "rust";
|
|
11761
|
+
if (fs27.existsSync(path29.join(root, "composer.json"))) return "php";
|
|
11762
|
+
if (fs27.existsSync(path29.join(root, "pyproject.toml")) || fs27.existsSync(path29.join(root, "requirements.txt"))) return "python";
|
|
11763
|
+
if (fs27.existsSync(path29.join(root, "Gemfile"))) return "ruby";
|
|
11764
|
+
if (fs27.existsSync(path29.join(root, "pom.xml")) || fs27.existsSync(path29.join(root, "build.gradle"))) return "java";
|
|
11765
|
+
if (fs27.existsSync(path29.join(root, "package.json"))) return "typescript";
|
|
11216
11766
|
return "unknown";
|
|
11217
11767
|
} catch {
|
|
11218
11768
|
return "unknown";
|
|
@@ -11368,9 +11918,9 @@ function formatCollectivePatterns(patterns) {
|
|
|
11368
11918
|
}
|
|
11369
11919
|
|
|
11370
11920
|
// src/engine/kumaMarketplace.ts
|
|
11371
|
-
import { execSync as
|
|
11372
|
-
import
|
|
11373
|
-
import
|
|
11921
|
+
import { execSync as execSync12 } from "child_process";
|
|
11922
|
+
import fs28 from "fs";
|
|
11923
|
+
import path30 from "path";
|
|
11374
11924
|
var BUILT_IN_TEMPLATES = [
|
|
11375
11925
|
// ── Framework Web (JS/TS) ──
|
|
11376
11926
|
{
|
|
@@ -11585,7 +12135,7 @@ async function listMarketplace() {
|
|
|
11585
12135
|
`\u{1F4E6} ${templates.length} template(s) available`,
|
|
11586
12136
|
""
|
|
11587
12137
|
];
|
|
11588
|
-
const
|
|
12138
|
+
const LANG_MAP2 = [
|
|
11589
12139
|
{ lang: "typescript", keywords: ["typescript", "hono", "elysia", "tanstack", "zustand", "prisma", "drizzle", "shadcn"] },
|
|
11590
12140
|
{ lang: "javascript", keywords: ["javascript", "express"] },
|
|
11591
12141
|
{ lang: "go", keywords: ["go", "gin"] },
|
|
@@ -11605,7 +12155,7 @@ async function listMarketplace() {
|
|
|
11605
12155
|
general: "\u{1F4E6}"
|
|
11606
12156
|
};
|
|
11607
12157
|
function detectLang(t) {
|
|
11608
|
-
for (const entry of
|
|
12158
|
+
for (const entry of LANG_MAP2) {
|
|
11609
12159
|
if (entry.keywords.some((k) => t.tags.some((tag) => tag.includes(k)))) {
|
|
11610
12160
|
return entry.lang;
|
|
11611
12161
|
}
|
|
@@ -11650,17 +12200,17 @@ function scanNpmTemplates() {
|
|
|
11650
12200
|
const found = [];
|
|
11651
12201
|
try {
|
|
11652
12202
|
const root = getProjectRoot();
|
|
11653
|
-
const nodeModulesPath =
|
|
11654
|
-
if (!
|
|
11655
|
-
const packages =
|
|
12203
|
+
const nodeModulesPath = path30.join(root, "node_modules", "@kuma-templates");
|
|
12204
|
+
if (!fs28.existsSync(nodeModulesPath)) return found;
|
|
12205
|
+
const packages = fs28.readdirSync(nodeModulesPath);
|
|
11656
12206
|
for (const pkg of packages) {
|
|
11657
|
-
const pkgPath =
|
|
11658
|
-
if (!
|
|
11659
|
-
const pkgJson = JSON.parse(
|
|
11660
|
-
const templatePath =
|
|
12207
|
+
const pkgPath = path30.join(nodeModulesPath, pkg, "package.json");
|
|
12208
|
+
if (!fs28.existsSync(pkgPath)) continue;
|
|
12209
|
+
const pkgJson = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
|
|
12210
|
+
const templatePath = path30.join(nodeModulesPath, pkg, "template.json");
|
|
11661
12211
|
let nodeCount = 0, edgeCount = 0;
|
|
11662
|
-
if (
|
|
11663
|
-
const tmpl = JSON.parse(
|
|
12212
|
+
if (fs28.existsSync(templatePath)) {
|
|
12213
|
+
const tmpl = JSON.parse(fs28.readFileSync(templatePath, "utf-8"));
|
|
11664
12214
|
nodeCount = tmpl.nodes?.length || 0;
|
|
11665
12215
|
edgeCount = tmpl.edges?.length || 0;
|
|
11666
12216
|
}
|
|
@@ -11745,8 +12295,8 @@ async function tryInstallFromNpm(templateId) {
|
|
|
11745
12295
|
const name = templateId.replace(/^graph:/, "");
|
|
11746
12296
|
try {
|
|
11747
12297
|
const root = getProjectRoot();
|
|
11748
|
-
const pkgPath =
|
|
11749
|
-
if (
|
|
12298
|
+
const pkgPath = path30.join(root, "node_modules", "@kuma-templates", name, "template.json");
|
|
12299
|
+
if (fs28.existsSync(pkgPath)) {
|
|
11750
12300
|
return await loadNpmTemplate(pkgPath, name);
|
|
11751
12301
|
}
|
|
11752
12302
|
} catch {
|
|
@@ -11754,14 +12304,14 @@ async function tryInstallFromNpm(templateId) {
|
|
|
11754
12304
|
try {
|
|
11755
12305
|
const npmPackage = `@kuma-templates/${name}`;
|
|
11756
12306
|
const root = getProjectRoot();
|
|
11757
|
-
|
|
12307
|
+
execSync12(`npm install --no-save ${npmPackage}`, {
|
|
11758
12308
|
cwd: root,
|
|
11759
12309
|
encoding: "utf-8",
|
|
11760
12310
|
timeout: 3e4,
|
|
11761
12311
|
stdio: "pipe"
|
|
11762
12312
|
});
|
|
11763
|
-
const templatePath =
|
|
11764
|
-
if (
|
|
12313
|
+
const templatePath = path30.join(root, "node_modules", "@kuma-templates", name, "template.json");
|
|
12314
|
+
if (fs28.existsSync(templatePath)) {
|
|
11765
12315
|
return await loadNpmTemplate(templatePath, name);
|
|
11766
12316
|
}
|
|
11767
12317
|
return [
|
|
@@ -11779,7 +12329,7 @@ async function tryInstallFromNpm(templateId) {
|
|
|
11779
12329
|
}
|
|
11780
12330
|
}
|
|
11781
12331
|
async function loadNpmTemplate(templatePath, name) {
|
|
11782
|
-
const raw =
|
|
12332
|
+
const raw = fs28.readFileSync(templatePath, "utf-8");
|
|
11783
12333
|
const template = JSON.parse(raw);
|
|
11784
12334
|
const db = await getDb();
|
|
11785
12335
|
let nodeCount = 0;
|
|
@@ -12166,8 +12716,12 @@ async function handleCore(action, params) {
|
|
|
12166
12716
|
return await handleBatchFileWriter(params);
|
|
12167
12717
|
case "lsp":
|
|
12168
12718
|
return await handleLspQuery({ ...params, action: params.lspAction });
|
|
12719
|
+
case "find":
|
|
12720
|
+
return await handleKumaFind(params);
|
|
12721
|
+
case "stats":
|
|
12722
|
+
return await handleKumaStats(params);
|
|
12169
12723
|
default:
|
|
12170
|
-
return `\u26A0\uFE0F Unknown action "${action}" for kuma_core. Use: grep, read, edit, batch, lsp`;
|
|
12724
|
+
return `\u26A0\uFE0F Unknown action "${action}" for kuma_core. Use: grep, read, edit, batch, lsp, find, stats`;
|
|
12171
12725
|
}
|
|
12172
12726
|
}
|
|
12173
12727
|
async function handleVerify(action, params) {
|
|
@@ -12551,9 +13105,9 @@ function registerAllTools(server) {
|
|
|
12551
13105
|
});
|
|
12552
13106
|
server.tool(
|
|
12553
13107
|
"kuma_core",
|
|
12554
|
-
"Core coding tools: grep (search), read (file), edit (search-and-replace), batch (create files), lsp (rename/reference).",
|
|
13108
|
+
"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).",
|
|
12555
13109
|
{
|
|
12556
|
-
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"),
|
|
13110
|
+
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"),
|
|
12557
13111
|
// grep params
|
|
12558
13112
|
query: z.string().optional().describe("Regex pattern for grep action"),
|
|
12559
13113
|
queries: z.array(z.string()).optional().describe("Multiple regex patterns (OR'd together, single pass)"),
|
|
@@ -12571,13 +13125,14 @@ function registerAllTools(server) {
|
|
|
12571
13125
|
endLine: z.number().min(1).optional().describe("End line (1-indexed) for read"),
|
|
12572
13126
|
chunkStrategy: z.enum(["full", "smart", "outline"]).optional().default("smart").describe("Read strategy"),
|
|
12573
13127
|
readOutputMode: z.enum(["rich", "raw"]).optional().default("rich").describe("Output format for read: rich=line numbers, raw=content only"),
|
|
12574
|
-
// edit params
|
|
13128
|
+
// edit params — BATCH SUPPORTED: pass multiple edits[] in 1 call (faster than calling edit 5x)
|
|
12575
13129
|
edits: z.array(z.object({
|
|
12576
13130
|
searchBlock: z.string().min(1).describe("Code to replace"),
|
|
12577
13131
|
replaceBlock: z.string().describe("Replacement code"),
|
|
12578
13132
|
allowMultiple: z.boolean().optional().default(false).describe("Allow multiple replacements"),
|
|
12579
13133
|
fuzzyThreshold: z.number().min(0).max(1).optional().default(0.85).describe("Fuzzy threshold")
|
|
12580
|
-
})).min(1).max(10).optional().describe("Array of edits (for edit action)"),
|
|
13134
|
+
})).min(1).max(10).optional().describe("Array of edits (for edit action) \u2014 batch multiple edits in 1 call for speed"),
|
|
13135
|
+
safe: z.boolean().optional().default(true).describe("Safe mode: true=create backup + safety checks (default), false=skip backup for speed (40x faster, no rollback)"),
|
|
12581
13136
|
dryRun: z.boolean().optional().default(false).describe("Preview without writing"),
|
|
12582
13137
|
version: z.union([z.number().min(1), z.literal("list")]).optional().describe("Backup version for rollback"),
|
|
12583
13138
|
scope: z.enum(["file", "dir", "edit-id", "commit"]).optional().describe("Rollback scope"),
|
|
@@ -12588,6 +13143,12 @@ function registerAllTools(server) {
|
|
|
12588
13143
|
content: z.string().describe("File content"),
|
|
12589
13144
|
instructions: z.string().min(1).describe("Reason for creating")
|
|
12590
13145
|
})).min(1).max(15).optional().describe("Array of files (for batch action)"),
|
|
13146
|
+
// find params
|
|
13147
|
+
name: z.string().optional().describe("File name glob pattern for find action (e.g. '*seed*', '*.tsx')"),
|
|
13148
|
+
path: z.string().optional().describe("Path glob pattern for find action (e.g. '**/utils/**', '*/seed*')"),
|
|
13149
|
+
type: z.enum(["file", "dir", "both"]).optional().default("file").describe("Type filter for find: file=files only, dir=dirs only"),
|
|
13150
|
+
// stats params
|
|
13151
|
+
target: z.enum(["file", "dir", "project"]).optional().default("project").describe("Stats target: file=single file, dir=directory, project=entire project"),
|
|
12591
13152
|
// lsp params
|
|
12592
13153
|
line: z.number().min(0).optional().describe("Line number (0-indexed) for LSP"),
|
|
12593
13154
|
character: z.number().min(0).optional().describe("Character position (0-indexed) for LSP"),
|
|
@@ -12923,23 +13484,23 @@ async function main() {
|
|
|
12923
13484
|
});
|
|
12924
13485
|
const output = formatInitResults(results);
|
|
12925
13486
|
console.log(output);
|
|
12926
|
-
const
|
|
12927
|
-
const
|
|
12928
|
-
const matchaSkills =
|
|
12929
|
-
const matchaAgents =
|
|
13487
|
+
const fs29 = await import("fs");
|
|
13488
|
+
const path31 = await import("path");
|
|
13489
|
+
const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
13490
|
+
const matchaAgents = path31.resolve(
|
|
12930
13491
|
process.cwd(),
|
|
12931
13492
|
".agents/skills/matcha/SKILL.md"
|
|
12932
13493
|
);
|
|
12933
|
-
const matchaRootSkills =
|
|
13494
|
+
const matchaRootSkills = path31.resolve(
|
|
12934
13495
|
process.cwd(),
|
|
12935
13496
|
"skills/matcha/SKILL.md"
|
|
12936
13497
|
);
|
|
12937
|
-
const matchaAgentsMd =
|
|
12938
|
-
const matchaWindsurfRules =
|
|
13498
|
+
const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
|
|
13499
|
+
const matchaWindsurfRules = path31.resolve(
|
|
12939
13500
|
process.cwd(),
|
|
12940
13501
|
".windsurfrules"
|
|
12941
13502
|
);
|
|
12942
|
-
if (
|
|
13503
|
+
if (fs29.existsSync(matchaSkills) || fs29.existsSync(matchaAgents) || fs29.existsSync(matchaRootSkills) || fs29.existsSync(matchaAgentsMd) || fs29.existsSync(matchaWindsurfRules)) {
|
|
12943
13504
|
console.error(
|
|
12944
13505
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
12945
13506
|
);
|
|
@@ -12953,13 +13514,13 @@ async function main() {
|
|
|
12953
13514
|
(async () => {
|
|
12954
13515
|
try {
|
|
12955
13516
|
const { generateInitMdContent } = await import("./init-INY6XOQT.js");
|
|
12956
|
-
const
|
|
12957
|
-
const
|
|
12958
|
-
const initMdPath =
|
|
12959
|
-
if (!
|
|
12960
|
-
const kumaDir =
|
|
12961
|
-
if (!
|
|
12962
|
-
|
|
13517
|
+
const fs29 = await import("fs");
|
|
13518
|
+
const path31 = await import("path");
|
|
13519
|
+
const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
|
|
13520
|
+
if (!fs29.existsSync(initMdPath)) {
|
|
13521
|
+
const kumaDir = path31.dirname(initMdPath);
|
|
13522
|
+
if (!fs29.existsSync(kumaDir)) fs29.mkdirSync(kumaDir, { recursive: true });
|
|
13523
|
+
fs29.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
12963
13524
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
12964
13525
|
}
|
|
12965
13526
|
} catch (err) {
|
|
@@ -12970,8 +13531,8 @@ async function main() {
|
|
|
12970
13531
|
try {
|
|
12971
13532
|
const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-HROEABDO.js");
|
|
12972
13533
|
const { generateSkill, getSecondaryFiles } = await import("./skillGenerator-F6YO4H5D.js");
|
|
12973
|
-
const
|
|
12974
|
-
const
|
|
13534
|
+
const fs29 = await import("fs");
|
|
13535
|
+
const path31 = await import("path");
|
|
12975
13536
|
const detection = detectAgent();
|
|
12976
13537
|
if (!detection.primary) {
|
|
12977
13538
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -12979,22 +13540,22 @@ async function main() {
|
|
|
12979
13540
|
}
|
|
12980
13541
|
const agentType = detection.primary;
|
|
12981
13542
|
const skillPath = getSkillPath(agentType);
|
|
12982
|
-
const fullPath =
|
|
12983
|
-
if (
|
|
13543
|
+
const fullPath = path31.resolve(process.cwd(), skillPath);
|
|
13544
|
+
if (fs29.existsSync(fullPath)) {
|
|
12984
13545
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel(agentType)} \u2014 skipping`);
|
|
12985
13546
|
return;
|
|
12986
13547
|
}
|
|
12987
|
-
const dir =
|
|
12988
|
-
if (!
|
|
12989
|
-
|
|
13548
|
+
const dir = path31.dirname(fullPath);
|
|
13549
|
+
if (!fs29.existsSync(dir)) fs29.mkdirSync(dir, { recursive: true });
|
|
13550
|
+
fs29.writeFileSync(fullPath, generateSkill(agentType), "utf-8");
|
|
12990
13551
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel(agentType)}`);
|
|
12991
13552
|
const secondaryFiles = getSecondaryFiles(agentType);
|
|
12992
13553
|
for (const sf of secondaryFiles) {
|
|
12993
|
-
const sfPath =
|
|
12994
|
-
const sfDir =
|
|
12995
|
-
if (!
|
|
12996
|
-
if (!
|
|
12997
|
-
|
|
13554
|
+
const sfPath = path31.resolve(process.cwd(), sf.path);
|
|
13555
|
+
const sfDir = path31.dirname(sfPath);
|
|
13556
|
+
if (!fs29.existsSync(sfPath)) {
|
|
13557
|
+
if (!fs29.existsSync(sfDir)) fs29.mkdirSync(sfDir, { recursive: true });
|
|
13558
|
+
fs29.writeFileSync(sfPath, sf.content, "utf-8");
|
|
12998
13559
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
12999
13560
|
}
|
|
13000
13561
|
}
|