@plumpslabs/kuma 2.2.6 → 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +887 -412
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -760,11 +760,48 @@ var CircuitBreakerStore = class {
|
|
|
760
760
|
var circuitBreaker = new CircuitBreakerStore();
|
|
761
761
|
|
|
762
762
|
// src/tools/preciseDiffEditor.ts
|
|
763
|
+
function fastApplyEdit(content, edit) {
|
|
764
|
+
const { searchBlock, replaceBlock, allowMultiple = false } = edit;
|
|
765
|
+
const count = countOccurrences(content, searchBlock);
|
|
766
|
+
if (count === 0) {
|
|
767
|
+
return {
|
|
768
|
+
success: false,
|
|
769
|
+
matched: 0,
|
|
770
|
+
replaced: 0,
|
|
771
|
+
error: `DIFF_MISMATCH: searchBlock not found (fast mode - exact match only)`,
|
|
772
|
+
details: content
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
const newContent = allowMultiple ? replaceAll(content, searchBlock, replaceBlock) : content.replace(searchBlock, replaceBlock);
|
|
776
|
+
return {
|
|
777
|
+
success: true,
|
|
778
|
+
matched: count,
|
|
779
|
+
replaced: allowMultiple ? count : 1,
|
|
780
|
+
details: newContent
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function formatFastEditResult(results, filePath) {
|
|
784
|
+
const successCount = results.filter((r) => r.success).length;
|
|
785
|
+
const failCount = results.filter((r) => !r.success).length;
|
|
786
|
+
if (failCount === 0) {
|
|
787
|
+
return `\u26A1 Fast edit: ${filePath} \u2014 ${successCount} edits applied.`;
|
|
788
|
+
}
|
|
789
|
+
const lines = [`\u26A1 Fast edit: ${filePath}`];
|
|
790
|
+
for (let i = 0; i < results.length; i++) {
|
|
791
|
+
const r = results[i];
|
|
792
|
+
if (r.success) {
|
|
793
|
+
lines.push(` [${i + 1}] \u2705 ${r.matched}x matched, ${r.replaced}x replaced`);
|
|
794
|
+
} else {
|
|
795
|
+
lines.push(` [${i + 1}] \u274C ${r.error}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return lines.join("\n");
|
|
799
|
+
}
|
|
763
800
|
function generateEditId() {
|
|
764
801
|
return `edit-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
765
802
|
}
|
|
766
803
|
async function handlePreciseDiffEditor(params) {
|
|
767
|
-
const { filePath, edits, dryRun = false, action, scope } = params;
|
|
804
|
+
const { filePath, edits, safe = true, dryRun = false, action, scope } = params;
|
|
768
805
|
if (action === "rollback") {
|
|
769
806
|
return handleRollbackEdit({ filePath, version: params.version, scope: scope || "file", editId: params.editId });
|
|
770
807
|
}
|
|
@@ -776,11 +813,13 @@ async function handlePreciseDiffEditor(params) {
|
|
|
776
813
|
return `Error: ${validation.error.message}`;
|
|
777
814
|
}
|
|
778
815
|
const resolvedPath = validation.resolvedPath;
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
816
|
+
if (safe) {
|
|
817
|
+
const cbResult = circuitBreaker.check("precise_diff_editor", { filePath });
|
|
818
|
+
if (!cbResult.allowed) {
|
|
819
|
+
return `\u26A0\uFE0F ${cbResult.reason}
|
|
782
820
|
|
|
783
821
|
Try reading the file first with smart_file_picker to verify current content.`;
|
|
822
|
+
}
|
|
784
823
|
}
|
|
785
824
|
if (!fs3.existsSync(resolvedPath)) {
|
|
786
825
|
return `Error: File not found: "${filePath}".
|
|
@@ -792,6 +831,22 @@ Use batch_file_writer to create a new file.`;
|
|
|
792
831
|
const results = [];
|
|
793
832
|
for (let i = 0; i < edits.length; i++) {
|
|
794
833
|
const edit = edits[i];
|
|
834
|
+
if (!safe) {
|
|
835
|
+
const result2 = fastApplyEdit(currentContent, edit);
|
|
836
|
+
if (result2.success) {
|
|
837
|
+
fs3.writeFileSync(resolvedPath, result2.details, "utf-8");
|
|
838
|
+
currentContent = result2.details;
|
|
839
|
+
sessionMemory.recordToolCall("precise_diff_editor", {
|
|
840
|
+
filePath,
|
|
841
|
+
editIndex: i,
|
|
842
|
+
fast: true,
|
|
843
|
+
success: true
|
|
844
|
+
});
|
|
845
|
+
sessionMemory.addModifiedFile(filePath);
|
|
846
|
+
}
|
|
847
|
+
results.push(result2);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
795
850
|
const result = applyEdit(currentContent, edit, resolvedPath, i, dryRun);
|
|
796
851
|
if (result.success) {
|
|
797
852
|
if (!dryRun) {
|
|
@@ -817,6 +872,9 @@ Use batch_file_writer to create a new file.`;
|
|
|
817
872
|
if (dryRun) {
|
|
818
873
|
return formatDryRunResult(results, filePath, originalContent);
|
|
819
874
|
}
|
|
875
|
+
if (!safe) {
|
|
876
|
+
return formatFastEditResult(results, filePath);
|
|
877
|
+
}
|
|
820
878
|
return formatDiffResult(results, filePath);
|
|
821
879
|
} catch (err) {
|
|
822
880
|
return `Error editing file "${filePath}": ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -1196,10 +1254,10 @@ File "${filePath}" restored from backup: "${relBackupPath}".`;
|
|
|
1196
1254
|
}
|
|
1197
1255
|
async function handleCommitRollback(filePath, version) {
|
|
1198
1256
|
try {
|
|
1199
|
-
const { execSync:
|
|
1257
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
1200
1258
|
const root = getProjectRoot();
|
|
1201
1259
|
if (version === "list") {
|
|
1202
|
-
const log =
|
|
1260
|
+
const log = execSync13("git log --oneline -20", { cwd: root, encoding: "utf-8" });
|
|
1203
1261
|
const lines = log.trim().split("\n").map((l) => ` ${l}`).join("\n");
|
|
1204
1262
|
return [
|
|
1205
1263
|
`\u{1F4CB} **Recent Commits:**`,
|
|
@@ -1209,7 +1267,7 @@ async function handleCommitRollback(filePath, version) {
|
|
|
1209
1267
|
`\u{1F4A1} Use { action: "rollback", scope: "commit", version: <N> } to restore a specific commit.`
|
|
1210
1268
|
].join("\n");
|
|
1211
1269
|
}
|
|
1212
|
-
const status =
|
|
1270
|
+
const status = execSync13("git status --porcelain", { cwd: root, encoding: "utf-8" }).trim();
|
|
1213
1271
|
if (status) {
|
|
1214
1272
|
return [
|
|
1215
1273
|
`\u26A0\uFE0F **Uncommitted changes detected.**`,
|
|
@@ -1230,10 +1288,10 @@ async function handleCommitRollback(filePath, version) {
|
|
|
1230
1288
|
target = "HEAD~1";
|
|
1231
1289
|
}
|
|
1232
1290
|
if (filePath) {
|
|
1233
|
-
|
|
1291
|
+
execSync13(`git checkout ${target} -- "${filePath}"`, { cwd: root, encoding: "utf-8" });
|
|
1234
1292
|
return `\u2705 **Commit Rollback** \u2014 File "${filePath}" restored from ${target}.`;
|
|
1235
1293
|
} else {
|
|
1236
|
-
|
|
1294
|
+
execSync13(`git checkout ${target}`, { cwd: root, encoding: "utf-8" });
|
|
1237
1295
|
return `\u2705 **Commit Rollback** \u2014 Project restored to ${target}.`;
|
|
1238
1296
|
}
|
|
1239
1297
|
} catch (err) {
|
|
@@ -1509,13 +1567,419 @@ function formatBatchResult(results, totalRequested) {
|
|
|
1509
1567
|
return lines.join("\n");
|
|
1510
1568
|
}
|
|
1511
1569
|
|
|
1512
|
-
// src/tools/
|
|
1513
|
-
import
|
|
1570
|
+
// src/tools/kumaFind.ts
|
|
1571
|
+
import fg2 from "fast-glob";
|
|
1572
|
+
import path5 from "path";
|
|
1573
|
+
import { execSync as execSync2 } from "child_process";
|
|
1574
|
+
var IGNORE_PATTERNS2 = [
|
|
1575
|
+
"**/node_modules/**",
|
|
1576
|
+
"**/.next/**",
|
|
1577
|
+
"**/dist/**",
|
|
1578
|
+
"**/build/**",
|
|
1579
|
+
"**/.git/**",
|
|
1580
|
+
"**/.cache/**",
|
|
1581
|
+
"**/.kuma/backups/**",
|
|
1582
|
+
"**/coverage/**",
|
|
1583
|
+
"**/.nyc_output/**"
|
|
1584
|
+
];
|
|
1585
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
1586
|
+
var findCache = /* @__PURE__ */ new Map();
|
|
1587
|
+
var rgAvailable2 = null;
|
|
1588
|
+
function isRipgrepAvailable2() {
|
|
1589
|
+
if (process.env.KUMA_DISABLE_RG === "1" || process.env.KUMA_DISABLE_RG === "true") {
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
if (rgAvailable2 !== null) return rgAvailable2;
|
|
1593
|
+
try {
|
|
1594
|
+
execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
|
|
1595
|
+
rgAvailable2 = true;
|
|
1596
|
+
} catch {
|
|
1597
|
+
rgAvailable2 = false;
|
|
1598
|
+
}
|
|
1599
|
+
return rgAvailable2;
|
|
1600
|
+
}
|
|
1601
|
+
function quotePath2(p) {
|
|
1602
|
+
return `"${p.replace(/"/g, '\\"')}"`;
|
|
1603
|
+
}
|
|
1604
|
+
async function handleKumaFind(params) {
|
|
1605
|
+
const {
|
|
1606
|
+
name = "*",
|
|
1607
|
+
path: pathPattern,
|
|
1608
|
+
extensions,
|
|
1609
|
+
targetFolder,
|
|
1610
|
+
maxResults = 30,
|
|
1611
|
+
type = "file",
|
|
1612
|
+
outputMode = "rich",
|
|
1613
|
+
compact = false
|
|
1614
|
+
} = params;
|
|
1615
|
+
const recursiveName = !pathPattern && !targetFolder && !name.startsWith("**/") ? `**/${name}` : name;
|
|
1616
|
+
const cacheKey = `${recursiveName}:${pathPattern ?? ""}:${targetFolder ?? ""}:${type}:${maxResults}:${extensions ? extensions.join(",") : ""}`;
|
|
1617
|
+
const cached = findCache.get(cacheKey);
|
|
1618
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS2) {
|
|
1619
|
+
return cached.results;
|
|
1620
|
+
}
|
|
1621
|
+
const projectRoot = getProjectRoot();
|
|
1622
|
+
let entries;
|
|
1623
|
+
if (isRipgrepAvailable2() && type !== "dir") {
|
|
1624
|
+
entries = await tryRipgrepFind({
|
|
1625
|
+
name: recursiveName,
|
|
1626
|
+
pathPattern,
|
|
1627
|
+
targetFolder,
|
|
1628
|
+
extensions,
|
|
1629
|
+
maxResults,
|
|
1630
|
+
projectRoot
|
|
1631
|
+
});
|
|
1632
|
+
} else {
|
|
1633
|
+
entries = await fastGlobFind({
|
|
1634
|
+
name: recursiveName,
|
|
1635
|
+
pathPattern,
|
|
1636
|
+
targetFolder,
|
|
1637
|
+
extensions,
|
|
1638
|
+
maxResults,
|
|
1639
|
+
type,
|
|
1640
|
+
projectRoot
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
if (entries.length === 0) {
|
|
1644
|
+
const noResult = compact ? `0:${name}` : `\u{1F50D} No files found matching "${name}"${targetFolder ? ` in ${targetFolder}` : ""}.`;
|
|
1645
|
+
findCache.set(cacheKey, { results: noResult, timestamp: Date.now() });
|
|
1646
|
+
return noResult;
|
|
1647
|
+
}
|
|
1648
|
+
sessionMemory.recordToolCall("kuma_find", {
|
|
1649
|
+
name,
|
|
1650
|
+
matchCount: entries.length,
|
|
1651
|
+
engine: rgAvailable2 ? "ripgrep" : "fast-glob"
|
|
1652
|
+
});
|
|
1653
|
+
let result;
|
|
1654
|
+
if (outputMode === "json") {
|
|
1655
|
+
result = JSON.stringify(entries);
|
|
1656
|
+
} else if (compact || outputMode === "raw") {
|
|
1657
|
+
result = entries.join("\n");
|
|
1658
|
+
} else {
|
|
1659
|
+
const lines = [
|
|
1660
|
+
`\u{1F50D} Find: "${name}"`,
|
|
1661
|
+
`\u{1F4C1} ${entries.length} files found${targetFolder ? ` in ${targetFolder}` : ""}`,
|
|
1662
|
+
"",
|
|
1663
|
+
...entries.map((e, i) => `[${i + 1}] \u{1F4C4} ${e}`)
|
|
1664
|
+
];
|
|
1665
|
+
result = lines.join("\n");
|
|
1666
|
+
}
|
|
1667
|
+
findCache.set(cacheKey, { results: result, timestamp: Date.now() });
|
|
1668
|
+
return result;
|
|
1669
|
+
}
|
|
1670
|
+
async function tryRipgrepFind(opts) {
|
|
1671
|
+
const { name, pathPattern, targetFolder, extensions, maxResults, projectRoot } = opts;
|
|
1672
|
+
const args = ["rg", "--files", "--no-ignore-vcs", "--color", "never"];
|
|
1673
|
+
for (const ignore of IGNORE_PATTERNS2) {
|
|
1674
|
+
const glob = ignore.replace(/^\*\*\//, "");
|
|
1675
|
+
args.push("-g", `!${glob}`);
|
|
1676
|
+
}
|
|
1677
|
+
args.push("-g", name);
|
|
1678
|
+
if (pathPattern) {
|
|
1679
|
+
args.push("-g", pathPattern);
|
|
1680
|
+
}
|
|
1681
|
+
if (extensions && extensions.length > 0) {
|
|
1682
|
+
for (const ext of extensions) {
|
|
1683
|
+
const clean = ext.startsWith(".") ? ext : `.${ext}`;
|
|
1684
|
+
args.push("-g", `*${clean}`);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
const searchDir = targetFolder ? path5.join(projectRoot, targetFolder) : projectRoot;
|
|
1688
|
+
args.push(quotePath2(searchDir));
|
|
1689
|
+
try {
|
|
1690
|
+
const output = execSync2(args.join(" "), {
|
|
1691
|
+
cwd: projectRoot,
|
|
1692
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
1693
|
+
timeout: 1e4,
|
|
1694
|
+
encoding: "utf-8",
|
|
1695
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1696
|
+
});
|
|
1697
|
+
if (!output || output.trim().length === 0) return [];
|
|
1698
|
+
let files = output.trim().split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1699
|
+
if (targetFolder) {
|
|
1700
|
+
const prefix = targetFolder.replace(/\/+$/, "") + "/";
|
|
1701
|
+
files = files.map((f) => prefix + f);
|
|
1702
|
+
}
|
|
1703
|
+
return files.slice(0, maxResults);
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
async function fastGlobFind(opts) {
|
|
1709
|
+
const { name, pathPattern, targetFolder, extensions, maxResults, type, projectRoot } = opts;
|
|
1710
|
+
const globParts = [];
|
|
1711
|
+
if (pathPattern) {
|
|
1712
|
+
globParts.push(pathPattern.replace(/^\/+/, "").replace(/\/+$/, ""));
|
|
1713
|
+
} else if (targetFolder) {
|
|
1714
|
+
globParts.push(targetFolder.replace(/^\/+/, "").replace(/\/+$/, ""));
|
|
1715
|
+
}
|
|
1716
|
+
globParts.push(name);
|
|
1717
|
+
const searchPattern = globParts.join("/");
|
|
1718
|
+
try {
|
|
1719
|
+
let entries = await fg2(searchPattern, {
|
|
1720
|
+
cwd: projectRoot,
|
|
1721
|
+
ignore: IGNORE_PATTERNS2,
|
|
1722
|
+
onlyFiles: type === "file",
|
|
1723
|
+
onlyDirectories: type === "dir",
|
|
1724
|
+
absolute: false,
|
|
1725
|
+
deep: 20,
|
|
1726
|
+
dot: false
|
|
1727
|
+
});
|
|
1728
|
+
if (extensions && extensions.length > 0 && type !== "dir") {
|
|
1729
|
+
const normalizedExts = extensions.map(
|
|
1730
|
+
(e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
1731
|
+
);
|
|
1732
|
+
entries = entries.filter((entry) => {
|
|
1733
|
+
const ext = path5.extname(entry).toLowerCase();
|
|
1734
|
+
return normalizedExts.includes(ext);
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
return entries.slice(0, maxResults);
|
|
1738
|
+
} catch {
|
|
1739
|
+
return [];
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// src/tools/kumaStats.ts
|
|
1744
|
+
import fs5 from "fs";
|
|
1514
1745
|
import path6 from "path";
|
|
1746
|
+
import { execSync as execSync3 } from "child_process";
|
|
1747
|
+
import fg3 from "fast-glob";
|
|
1748
|
+
var LANG_MAP = {
|
|
1749
|
+
ts: "TypeScript",
|
|
1750
|
+
tsx: "TypeScript React",
|
|
1751
|
+
js: "JavaScript",
|
|
1752
|
+
jsx: "JavaScript React",
|
|
1753
|
+
py: "Python",
|
|
1754
|
+
go: "Go",
|
|
1755
|
+
rs: "Rust",
|
|
1756
|
+
java: "Java",
|
|
1757
|
+
kt: "Kotlin",
|
|
1758
|
+
rb: "Ruby",
|
|
1759
|
+
php: "PHP",
|
|
1760
|
+
cs: "C#",
|
|
1761
|
+
swift: "Swift",
|
|
1762
|
+
c: "C",
|
|
1763
|
+
cpp: "C++",
|
|
1764
|
+
h: "C/C++ Header",
|
|
1765
|
+
css: "CSS",
|
|
1766
|
+
scss: "SCSS",
|
|
1767
|
+
less: "Less",
|
|
1768
|
+
html: "HTML",
|
|
1769
|
+
json: "JSON",
|
|
1770
|
+
yaml: "YAML",
|
|
1771
|
+
yml: "YAML",
|
|
1772
|
+
md: "Markdown",
|
|
1773
|
+
sql: "SQL",
|
|
1774
|
+
graphql: "GraphQL",
|
|
1775
|
+
prisma: "Prisma",
|
|
1776
|
+
toml: "TOML",
|
|
1777
|
+
xml: "XML",
|
|
1778
|
+
svg: "SVG",
|
|
1779
|
+
sh: "Shell",
|
|
1780
|
+
bash: "Shell",
|
|
1781
|
+
dockerfile: "Dockerfile"
|
|
1782
|
+
};
|
|
1783
|
+
async function handleKumaStats(params) {
|
|
1784
|
+
const {
|
|
1785
|
+
filePath,
|
|
1786
|
+
target = "project",
|
|
1787
|
+
outputMode = "rich",
|
|
1788
|
+
compact = false,
|
|
1789
|
+
extensions
|
|
1790
|
+
} = params;
|
|
1791
|
+
if (target === "file" && filePath) {
|
|
1792
|
+
return await statsSingleFile(filePath, { outputMode, compact });
|
|
1793
|
+
}
|
|
1794
|
+
if (target === "dir" && filePath) {
|
|
1795
|
+
return await statsDirectory(filePath, { outputMode, compact, extensions });
|
|
1796
|
+
}
|
|
1797
|
+
return await statsProject({ outputMode, compact, extensions });
|
|
1798
|
+
}
|
|
1799
|
+
async function statsSingleFile(filePath, opts) {
|
|
1800
|
+
const { outputMode, compact } = opts;
|
|
1801
|
+
const validation = validateFilePath(filePath);
|
|
1802
|
+
if (!validation.valid) {
|
|
1803
|
+
return compact ? `ERR:${filePath}` : `Error: ${validation.error.message}`;
|
|
1804
|
+
}
|
|
1805
|
+
const resolvedPath = validation.resolvedPath;
|
|
1806
|
+
if (!fs5.existsSync(resolvedPath)) {
|
|
1807
|
+
return compact ? `ERR:not found` : `Error: File not found: ${filePath}`;
|
|
1808
|
+
}
|
|
1809
|
+
const stat = fs5.statSync(resolvedPath);
|
|
1810
|
+
const isDir = stat.isDirectory();
|
|
1811
|
+
if (isDir) {
|
|
1812
|
+
return statsDirectory(filePath, { outputMode, compact });
|
|
1813
|
+
}
|
|
1814
|
+
const content = fs5.readFileSync(resolvedPath, "utf-8");
|
|
1815
|
+
const lines = content.split("\n");
|
|
1816
|
+
const ext = path6.extname(resolvedPath).replace(".", "").toLowerCase();
|
|
1817
|
+
const lang = LANG_MAP[ext] || ext.toUpperCase() || "Unknown";
|
|
1818
|
+
const totalBytes = stat.size;
|
|
1819
|
+
const nonEmpty = lines.filter((l) => l.trim().length > 0).length;
|
|
1820
|
+
if (outputMode === "json") {
|
|
1821
|
+
return JSON.stringify({
|
|
1822
|
+
file: filePath,
|
|
1823
|
+
lines: lines.length,
|
|
1824
|
+
nonEmpty,
|
|
1825
|
+
size: totalBytes,
|
|
1826
|
+
sizeHuman: formatBytes(totalBytes),
|
|
1827
|
+
language: lang
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
if (compact || outputMode === "raw") {
|
|
1831
|
+
return `${filePath} ${lines.length} ${formatBytes(totalBytes)} ${lang}`;
|
|
1832
|
+
}
|
|
1833
|
+
return [
|
|
1834
|
+
`\u{1F4CA} Stats: ${filePath}`,
|
|
1835
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1836
|
+
` Language: ${lang}`,
|
|
1837
|
+
` Lines: ${lines.length}`,
|
|
1838
|
+
` Non-empty: ${nonEmpty}`,
|
|
1839
|
+
` Size: ${formatBytes(totalBytes)}`
|
|
1840
|
+
].join("\n");
|
|
1841
|
+
}
|
|
1842
|
+
async function statsDirectory(dirPath, opts) {
|
|
1843
|
+
const { outputMode, compact, extensions } = opts;
|
|
1844
|
+
const projectRoot = getProjectRoot();
|
|
1845
|
+
const resolved = path6.isAbsolute(dirPath) ? dirPath : path6.join(projectRoot, dirPath);
|
|
1846
|
+
if (!fs5.existsSync(resolved)) {
|
|
1847
|
+
return compact ? `ERR:not found ${dirPath}` : `Error: Directory not found: ${dirPath}`;
|
|
1848
|
+
}
|
|
1849
|
+
let totalFiles = 0;
|
|
1850
|
+
let totalLines = 0;
|
|
1851
|
+
let totalBytes = 0;
|
|
1852
|
+
const langCounts = {};
|
|
1853
|
+
const entries = await fg3("**/*", {
|
|
1854
|
+
cwd: resolved,
|
|
1855
|
+
onlyFiles: true,
|
|
1856
|
+
ignore: [
|
|
1857
|
+
"**/node_modules/**",
|
|
1858
|
+
"**/.git/**",
|
|
1859
|
+
"**/dist/**",
|
|
1860
|
+
"**/build/**",
|
|
1861
|
+
"**/.next/**",
|
|
1862
|
+
"**/coverage/**",
|
|
1863
|
+
"**/*.map"
|
|
1864
|
+
],
|
|
1865
|
+
deep: 10,
|
|
1866
|
+
dot: false
|
|
1867
|
+
});
|
|
1868
|
+
for (const entry of entries) {
|
|
1869
|
+
const ext = path6.extname(entry).replace(".", "").toLowerCase();
|
|
1870
|
+
if (extensions && extensions.length > 0) {
|
|
1871
|
+
const normalizedExts = extensions.map(
|
|
1872
|
+
(e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
1873
|
+
);
|
|
1874
|
+
if (!normalizedExts.includes(`.${ext}`)) continue;
|
|
1875
|
+
}
|
|
1876
|
+
totalFiles++;
|
|
1877
|
+
const fullPath = path6.join(resolved, entry);
|
|
1878
|
+
try {
|
|
1879
|
+
const stat = fs5.statSync(fullPath);
|
|
1880
|
+
if (stat.size > 1e6) {
|
|
1881
|
+
totalBytes += stat.size;
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
1885
|
+
const lineCount = content.split("\n").length;
|
|
1886
|
+
totalLines += lineCount;
|
|
1887
|
+
totalBytes += stat.size;
|
|
1888
|
+
const lang = LANG_MAP[ext] || ext.toUpperCase() || "Other";
|
|
1889
|
+
if (!langCounts[lang]) {
|
|
1890
|
+
langCounts[lang] = { files: 0, lines: 0 };
|
|
1891
|
+
}
|
|
1892
|
+
langCounts[lang].files++;
|
|
1893
|
+
langCounts[lang].lines += lineCount;
|
|
1894
|
+
} catch {
|
|
1895
|
+
try {
|
|
1896
|
+
const fallbackStat = fs5.statSync(fullPath);
|
|
1897
|
+
totalBytes += fallbackStat.size;
|
|
1898
|
+
} catch {
|
|
1899
|
+
totalBytes += 0;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
sessionMemory.recordToolCall("kuma_stats", {
|
|
1904
|
+
target: dirPath,
|
|
1905
|
+
totalFiles,
|
|
1906
|
+
totalLines
|
|
1907
|
+
});
|
|
1908
|
+
if (outputMode === "json") {
|
|
1909
|
+
return JSON.stringify({
|
|
1910
|
+
directory: dirPath,
|
|
1911
|
+
totalFiles,
|
|
1912
|
+
totalLines,
|
|
1913
|
+
totalBytes,
|
|
1914
|
+
sizeHuman: formatBytes(totalBytes),
|
|
1915
|
+
languages: langCounts
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
if (compact || outputMode === "raw") {
|
|
1919
|
+
const langs = Object.entries(langCounts).sort((a, b) => b[1].lines - a[1].lines).map(([name, counts]) => `${name} ${counts.files} ${counts.lines}`).join("\n");
|
|
1920
|
+
return `${dirPath} ${totalFiles} ${totalLines} ${formatBytes(totalBytes)}
|
|
1921
|
+
${langs}`;
|
|
1922
|
+
}
|
|
1923
|
+
const langSummary = Object.entries(langCounts).sort((a, b) => b[1].lines - a[1].lines).slice(0, 10).map(
|
|
1924
|
+
([name, counts]) => ` ${name.padEnd(20)} ${String(counts.files).padStart(5)} files ${String(counts.lines).padStart(8)} lines`
|
|
1925
|
+
).join("\n");
|
|
1926
|
+
return [
|
|
1927
|
+
`\u{1F4CA} Directory Stats: ${dirPath}`,
|
|
1928
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1929
|
+
` Total files: ${totalFiles}`,
|
|
1930
|
+
` Total lines: ${totalLines}`,
|
|
1931
|
+
` Total size: ${formatBytes(totalBytes)}`,
|
|
1932
|
+
``,
|
|
1933
|
+
`Top Languages:`,
|
|
1934
|
+
langSummary
|
|
1935
|
+
].join("\n");
|
|
1936
|
+
}
|
|
1937
|
+
async function statsProject(opts) {
|
|
1938
|
+
const projectRoot = getProjectRoot();
|
|
1939
|
+
const projectName = path6.basename(projectRoot);
|
|
1940
|
+
const projectStats = await statsDirectory(projectRoot, opts);
|
|
1941
|
+
let branch = "unknown";
|
|
1942
|
+
let lastCommit = "unknown";
|
|
1943
|
+
try {
|
|
1944
|
+
branch = execSync3("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown", {
|
|
1945
|
+
cwd: projectRoot,
|
|
1946
|
+
encoding: "utf-8",
|
|
1947
|
+
timeout: 2e3
|
|
1948
|
+
}).trim();
|
|
1949
|
+
lastCommit = execSync3('git log -1 --format="%h %s" 2>/dev/null || echo unknown', {
|
|
1950
|
+
cwd: projectRoot,
|
|
1951
|
+
encoding: "utf-8",
|
|
1952
|
+
timeout: 2e3
|
|
1953
|
+
}).trim();
|
|
1954
|
+
} catch {
|
|
1955
|
+
}
|
|
1956
|
+
if (opts.outputMode === "raw" || opts.compact) {
|
|
1957
|
+
return `Project: ${projectName} Branch: ${branch} ${lastCommit}
|
|
1958
|
+
${projectStats}`;
|
|
1959
|
+
}
|
|
1960
|
+
return [
|
|
1961
|
+
`\u{1F4CA} Project: ${projectName}`,
|
|
1962
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1963
|
+
` Branch: ${branch}`,
|
|
1964
|
+
` Last commit: ${lastCommit}`,
|
|
1965
|
+
``,
|
|
1966
|
+
projectStats
|
|
1967
|
+
].join("\n");
|
|
1968
|
+
}
|
|
1969
|
+
function formatBytes(bytes) {
|
|
1970
|
+
if (bytes === 0) return "0 B";
|
|
1971
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
1972
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
1973
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// src/tools/safeTerminalExec.ts
|
|
1977
|
+
import fs7 from "fs";
|
|
1978
|
+
import path8 from "path";
|
|
1515
1979
|
|
|
1516
1980
|
// src/utils/conventionsDetector.ts
|
|
1517
|
-
import
|
|
1518
|
-
import
|
|
1981
|
+
import fs6 from "fs";
|
|
1982
|
+
import path7 from "path";
|
|
1519
1983
|
var cachedConventions = null;
|
|
1520
1984
|
async function detectConventions(forceRescan = false) {
|
|
1521
1985
|
if (cachedConventions && !forceRescan) {
|
|
@@ -1546,7 +2010,7 @@ async function detectConventions(forceRescan = false) {
|
|
|
1546
2010
|
return conventions;
|
|
1547
2011
|
}
|
|
1548
2012
|
function detectFramework(root) {
|
|
1549
|
-
const pkg = readJsonSafe(
|
|
2013
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1550
2014
|
if (pkg) {
|
|
1551
2015
|
const pkgDeps = pkg.dependencies ?? {};
|
|
1552
2016
|
const pkgDevDeps = pkg.devDependencies ?? {};
|
|
@@ -1591,11 +2055,11 @@ function detectFramework(root) {
|
|
|
1591
2055
|
function scanSubProjectFrameworks(root) {
|
|
1592
2056
|
const frameworks = [];
|
|
1593
2057
|
try {
|
|
1594
|
-
const entries =
|
|
2058
|
+
const entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
1595
2059
|
for (const entry of entries) {
|
|
1596
2060
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1597
|
-
const subDir =
|
|
1598
|
-
const subPkg = readJsonSafe(
|
|
2061
|
+
const subDir = path7.join(root, entry.name);
|
|
2062
|
+
const subPkg = readJsonSafe(path7.join(subDir, "package.json"));
|
|
1599
2063
|
if (!subPkg) continue;
|
|
1600
2064
|
const subDeps = { ...subPkg.dependencies ?? {}, ...subPkg.devDependencies ?? {} };
|
|
1601
2065
|
if (subDeps.next) frameworks.push("Next.js");
|
|
@@ -1614,7 +2078,7 @@ function scanSubProjectFrameworks(root) {
|
|
|
1614
2078
|
return frameworks;
|
|
1615
2079
|
}
|
|
1616
2080
|
function detectProjectType(root) {
|
|
1617
|
-
const pkg = readJsonSafe(
|
|
2081
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1618
2082
|
if (pkg) {
|
|
1619
2083
|
const pkgDeps = pkg.dependencies ?? {};
|
|
1620
2084
|
const pkgDevDeps = pkg.devDependencies ?? {};
|
|
@@ -1638,8 +2102,8 @@ function detectProjectType(root) {
|
|
|
1638
2102
|
}
|
|
1639
2103
|
function detectWorkspaces(root) {
|
|
1640
2104
|
const results = [];
|
|
1641
|
-
const pkg = readJsonSafe(
|
|
1642
|
-
const pnpmWorkspace = readYamlLite(
|
|
2105
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
2106
|
+
const pnpmWorkspace = readYamlLite(path7.join(root, "pnpm-workspace.yaml"));
|
|
1643
2107
|
const patterns = /* @__PURE__ */ new Set();
|
|
1644
2108
|
const pkgWorkspaces = pkg?.workspaces;
|
|
1645
2109
|
if (Array.isArray(pkgWorkspaces)) {
|
|
@@ -1659,22 +2123,22 @@ function detectWorkspaces(root) {
|
|
|
1659
2123
|
for (const pattern of patterns) {
|
|
1660
2124
|
const match = pattern.match(/^([^*]+)\/\*$/);
|
|
1661
2125
|
if (!match) continue;
|
|
1662
|
-
const dir =
|
|
1663
|
-
if (!
|
|
2126
|
+
const dir = path7.join(root, match[1]);
|
|
2127
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1664
2128
|
let entries = [];
|
|
1665
2129
|
try {
|
|
1666
|
-
entries =
|
|
2130
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
1667
2131
|
} catch {
|
|
1668
2132
|
continue;
|
|
1669
2133
|
}
|
|
1670
2134
|
for (const entry of entries) {
|
|
1671
2135
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1672
|
-
const pkgPath =
|
|
2136
|
+
const pkgPath = path7.join(dir, entry.name, "package.json");
|
|
1673
2137
|
const subPkg = readJsonSafe(pkgPath);
|
|
1674
2138
|
if (!subPkg) continue;
|
|
1675
|
-
const workspacePath =
|
|
2139
|
+
const workspacePath = path7.join(dir, entry.name);
|
|
1676
2140
|
results.push({
|
|
1677
|
-
path:
|
|
2141
|
+
path: path7.relative(root, workspacePath),
|
|
1678
2142
|
name: subPkg.name ?? entry.name,
|
|
1679
2143
|
framework: detectFramework(workspacePath),
|
|
1680
2144
|
packageManager: detectPackageManagerForDir(workspacePath)
|
|
@@ -1690,8 +2154,8 @@ function detectWorkspaces(root) {
|
|
|
1690
2154
|
}
|
|
1691
2155
|
function readYamlLite(filePath) {
|
|
1692
2156
|
try {
|
|
1693
|
-
if (!
|
|
1694
|
-
const text =
|
|
2157
|
+
if (!fs6.existsSync(filePath)) return null;
|
|
2158
|
+
const text = fs6.readFileSync(filePath, "utf-8");
|
|
1695
2159
|
const packages = [];
|
|
1696
2160
|
let inPackages = false;
|
|
1697
2161
|
for (const rawLine of text.split("\n")) {
|
|
@@ -1712,7 +2176,7 @@ function readYamlLite(filePath) {
|
|
|
1712
2176
|
}
|
|
1713
2177
|
}
|
|
1714
2178
|
function detectTestRunner(root) {
|
|
1715
|
-
const pkg = readJsonSafe(
|
|
2179
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1716
2180
|
if (!pkg) return "unknown";
|
|
1717
2181
|
const pkgDeps2 = pkg.dependencies ?? {};
|
|
1718
2182
|
const pkgDevDeps2 = pkg.devDependencies ?? {};
|
|
@@ -1733,7 +2197,7 @@ function detectTestRunner(root) {
|
|
|
1733
2197
|
return "unknown";
|
|
1734
2198
|
}
|
|
1735
2199
|
function detectStyling(root) {
|
|
1736
|
-
const pkg = readJsonSafe(
|
|
2200
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1737
2201
|
if (!pkg) return "unknown";
|
|
1738
2202
|
const pkgDeps3 = pkg.dependencies ?? {};
|
|
1739
2203
|
const pkgDevDeps3 = pkg.devDependencies ?? {};
|
|
@@ -1746,22 +2210,22 @@ function detectStyling(root) {
|
|
|
1746
2210
|
if (deps.less) return "Less";
|
|
1747
2211
|
if (deps["@vanilla-extract/css"]) return "Vanilla Extract";
|
|
1748
2212
|
if (deps.cssmodules || hasCSSModules(root)) return "CSS Modules";
|
|
1749
|
-
const srcDir =
|
|
1750
|
-
if (
|
|
2213
|
+
const srcDir = path7.join(root, "src");
|
|
2214
|
+
if (fs6.existsSync(srcDir)) {
|
|
1751
2215
|
const cssFiles = findFiles(srcDir, [".css", ".scss", ".less"]);
|
|
1752
2216
|
if (cssFiles.length > 0) return "Plain CSS/SCSS";
|
|
1753
2217
|
}
|
|
1754
2218
|
return "unknown";
|
|
1755
2219
|
}
|
|
1756
2220
|
function detectImportAlias(root) {
|
|
1757
|
-
const tsconfig = readJsonSafe(
|
|
2221
|
+
const tsconfig = readJsonSafe(path7.join(root, "tsconfig.json"));
|
|
1758
2222
|
const tsconfigPaths = tsconfig?.compilerOptions;
|
|
1759
2223
|
if (tsconfigPaths?.paths) {
|
|
1760
2224
|
const paths = tsconfigPaths.paths;
|
|
1761
2225
|
const alias = Object.keys(paths)[0];
|
|
1762
2226
|
if (alias) return alias.replace("/*", "");
|
|
1763
2227
|
}
|
|
1764
|
-
const jsconfig = readJsonSafe(
|
|
2228
|
+
const jsconfig = readJsonSafe(path7.join(root, "jsconfig.json"));
|
|
1765
2229
|
const jsconfigPaths = jsconfig?.compilerOptions;
|
|
1766
2230
|
if (jsconfigPaths?.paths) {
|
|
1767
2231
|
const paths = jsconfigPaths.paths;
|
|
@@ -1772,15 +2236,15 @@ function detectImportAlias(root) {
|
|
|
1772
2236
|
}
|
|
1773
2237
|
function detectLintRules(root) {
|
|
1774
2238
|
const rules = [];
|
|
1775
|
-
if (
|
|
1776
|
-
if (
|
|
1777
|
-
if (
|
|
1778
|
-
if (
|
|
1779
|
-
if (
|
|
1780
|
-
if (
|
|
1781
|
-
if (
|
|
1782
|
-
if (
|
|
1783
|
-
if (
|
|
2239
|
+
if (fs6.existsSync(path7.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
|
|
2240
|
+
if (fs6.existsSync(path7.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
|
|
2241
|
+
if (fs6.existsSync(path7.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
|
|
2242
|
+
if (fs6.existsSync(path7.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
|
|
2243
|
+
if (fs6.existsSync(path7.join(root, ".prettierrc"))) rules.push("Prettier");
|
|
2244
|
+
if (fs6.existsSync(path7.join(root, ".prettierrc.json"))) rules.push("Prettier");
|
|
2245
|
+
if (fs6.existsSync(path7.join(root, ".prettierrc.js"))) rules.push("Prettier");
|
|
2246
|
+
if (fs6.existsSync(path7.join(root, ".stylelintrc"))) rules.push("StyleLint");
|
|
2247
|
+
if (fs6.existsSync(path7.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
|
|
1784
2248
|
return rules;
|
|
1785
2249
|
}
|
|
1786
2250
|
function detectPackageManager() {
|
|
@@ -1789,26 +2253,26 @@ function detectPackageManager() {
|
|
|
1789
2253
|
}
|
|
1790
2254
|
function detectPackageManagerForDir(dir) {
|
|
1791
2255
|
const root = getProjectRoot();
|
|
1792
|
-
let currentDir =
|
|
2256
|
+
let currentDir = path7.resolve(dir);
|
|
1793
2257
|
while (currentDir.startsWith(root)) {
|
|
1794
|
-
if (
|
|
1795
|
-
if (
|
|
1796
|
-
if (
|
|
1797
|
-
if (
|
|
2258
|
+
if (fs6.existsSync(path7.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2259
|
+
if (fs6.existsSync(path7.join(currentDir, "yarn.lock"))) return "yarn";
|
|
2260
|
+
if (fs6.existsSync(path7.join(currentDir, "package-lock.json"))) return "npm";
|
|
2261
|
+
if (fs6.existsSync(path7.join(currentDir, "bun.lockb"))) return "bun";
|
|
1798
2262
|
if (currentDir === root) break;
|
|
1799
|
-
currentDir =
|
|
2263
|
+
currentDir = path7.dirname(currentDir);
|
|
1800
2264
|
}
|
|
1801
2265
|
return "npm";
|
|
1802
2266
|
}
|
|
1803
2267
|
function detectModuleSystem(root) {
|
|
1804
|
-
const pkg = readJsonSafe(
|
|
2268
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1805
2269
|
if (pkg?.type === "module") return "esm";
|
|
1806
|
-
const srcDir =
|
|
1807
|
-
if (
|
|
2270
|
+
const srcDir = path7.join(root, "src");
|
|
2271
|
+
if (fs6.existsSync(srcDir)) {
|
|
1808
2272
|
const tsFiles = findFiles(srcDir, [".ts", ".tsx", ".js", ".jsx", ".mjs"]);
|
|
1809
2273
|
for (const file of tsFiles.slice(0, 20)) {
|
|
1810
2274
|
try {
|
|
1811
|
-
const content =
|
|
2275
|
+
const content = fs6.readFileSync(file, "utf-8");
|
|
1812
2276
|
if (content.includes("import ") || content.includes("export ")) return "esm";
|
|
1813
2277
|
} catch {
|
|
1814
2278
|
continue;
|
|
@@ -1818,22 +2282,22 @@ function detectModuleSystem(root) {
|
|
|
1818
2282
|
return "cjs";
|
|
1819
2283
|
}
|
|
1820
2284
|
function detectLanguage(root) {
|
|
1821
|
-
if (
|
|
1822
|
-
if (
|
|
1823
|
-
if (
|
|
1824
|
-
if (
|
|
1825
|
-
if (
|
|
1826
|
-
if (
|
|
1827
|
-
if (
|
|
1828
|
-
if (
|
|
2285
|
+
if (fs6.existsSync(path7.join(root, "go.mod"))) return "go";
|
|
2286
|
+
if (fs6.existsSync(path7.join(root, "Cargo.toml"))) return "rust";
|
|
2287
|
+
if (fs6.existsSync(path7.join(root, "composer.json"))) return "php";
|
|
2288
|
+
if (fs6.existsSync(path7.join(root, "pyproject.toml")) || fs6.existsSync(path7.join(root, "requirements.txt")) || fs6.existsSync(path7.join(root, "setup.py"))) return "python";
|
|
2289
|
+
if (fs6.existsSync(path7.join(root, "Gemfile"))) return "ruby";
|
|
2290
|
+
if (fs6.existsSync(path7.join(root, "pom.xml"))) return "java";
|
|
2291
|
+
if (fs6.existsSync(path7.join(root, "build.gradle")) || fs6.existsSync(path7.join(root, "build.gradle.kts"))) return "kotlin";
|
|
2292
|
+
if (fs6.existsSync(path7.join(root, "Package.swift"))) return "swift";
|
|
1829
2293
|
try {
|
|
1830
|
-
const entries =
|
|
2294
|
+
const entries = fs6.readdirSync(root);
|
|
1831
2295
|
if (entries.some((e) => e.endsWith(".csproj"))) return "csharp";
|
|
1832
2296
|
} catch {
|
|
1833
2297
|
}
|
|
1834
|
-
if (
|
|
1835
|
-
const srcDir =
|
|
1836
|
-
if (
|
|
2298
|
+
if (fs6.existsSync(path7.join(root, "tsconfig.json"))) return "typescript";
|
|
2299
|
+
const srcDir = path7.join(root, "src");
|
|
2300
|
+
if (fs6.existsSync(srcDir)) {
|
|
1837
2301
|
const tsFiles = findFiles(srcDir, [".ts", ".tsx"]);
|
|
1838
2302
|
if (tsFiles.length > 0) return "typescript";
|
|
1839
2303
|
const jsFiles = findFiles(srcDir, [".js", ".jsx"]);
|
|
@@ -1852,7 +2316,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1852
2316
|
switch (language) {
|
|
1853
2317
|
case "go": {
|
|
1854
2318
|
try {
|
|
1855
|
-
const goMod =
|
|
2319
|
+
const goMod = fs6.readFileSync(path7.join(root, "go.mod"), "utf-8");
|
|
1856
2320
|
if (goMod.includes("github.com/gin-gonic/gin")) return "Gin";
|
|
1857
2321
|
if (goMod.includes("github.com/labstack/echo")) return "Echo";
|
|
1858
2322
|
if (goMod.includes("github.com/gofiber/fiber")) return "Fiber";
|
|
@@ -1866,7 +2330,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1866
2330
|
}
|
|
1867
2331
|
case "php": {
|
|
1868
2332
|
try {
|
|
1869
|
-
const composer = JSON.parse(
|
|
2333
|
+
const composer = JSON.parse(fs6.readFileSync(path7.join(root, "composer.json"), "utf-8"));
|
|
1870
2334
|
const req = composer.require ?? {};
|
|
1871
2335
|
const reqDev = composer["require-dev"] ?? {};
|
|
1872
2336
|
const deps = { ...req, ...reqDev };
|
|
@@ -1883,14 +2347,14 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1883
2347
|
}
|
|
1884
2348
|
case "python": {
|
|
1885
2349
|
try {
|
|
1886
|
-
if (
|
|
1887
|
-
const content =
|
|
2350
|
+
if (fs6.existsSync(path7.join(root, "pyproject.toml"))) {
|
|
2351
|
+
const content = fs6.readFileSync(path7.join(root, "pyproject.toml"), "utf-8");
|
|
1888
2352
|
if (content.includes("django")) return "Django";
|
|
1889
2353
|
if (content.includes("flask")) return "Flask";
|
|
1890
2354
|
if (content.includes("fastapi")) return "FastAPI";
|
|
1891
2355
|
}
|
|
1892
|
-
if (
|
|
1893
|
-
const content =
|
|
2356
|
+
if (fs6.existsSync(path7.join(root, "requirements.txt"))) {
|
|
2357
|
+
const content = fs6.readFileSync(path7.join(root, "requirements.txt"), "utf-8");
|
|
1894
2358
|
if (content.includes("django")) return "Django";
|
|
1895
2359
|
if (content.includes("flask")) return "Flask";
|
|
1896
2360
|
if (content.includes("fastapi")) return "FastAPI";
|
|
@@ -1903,7 +2367,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1903
2367
|
}
|
|
1904
2368
|
case "rust": {
|
|
1905
2369
|
try {
|
|
1906
|
-
const cargo =
|
|
2370
|
+
const cargo = fs6.readFileSync(path7.join(root, "Cargo.toml"), "utf-8");
|
|
1907
2371
|
if (cargo.includes("axum")) return "Axum";
|
|
1908
2372
|
if (cargo.includes("actix-web")) return "Actix Web";
|
|
1909
2373
|
if (cargo.includes("rocket")) return "Rocket";
|
|
@@ -1917,7 +2381,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1917
2381
|
}
|
|
1918
2382
|
case "java": {
|
|
1919
2383
|
try {
|
|
1920
|
-
const content =
|
|
2384
|
+
const content = fs6.readFileSync(path7.join(root, "pom.xml"), "utf-8");
|
|
1921
2385
|
if (content.includes("spring-boot")) return "Spring Boot";
|
|
1922
2386
|
if (content.includes("quarkus")) return "Quarkus";
|
|
1923
2387
|
if (content.includes("micronaut")) return "Micronaut";
|
|
@@ -1925,7 +2389,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1925
2389
|
if (content.includes("helidon")) return "Helidon";
|
|
1926
2390
|
} catch {
|
|
1927
2391
|
try {
|
|
1928
|
-
const gradle =
|
|
2392
|
+
const gradle = fs6.readFileSync(path7.join(root, "build.gradle"), "utf-8");
|
|
1929
2393
|
if (gradle.includes("spring")) return "Spring Boot";
|
|
1930
2394
|
if (gradle.includes("quarkus")) return "Quarkus";
|
|
1931
2395
|
} catch {
|
|
@@ -1935,7 +2399,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1935
2399
|
}
|
|
1936
2400
|
case "kotlin": {
|
|
1937
2401
|
try {
|
|
1938
|
-
const gradle =
|
|
2402
|
+
const gradle = fs6.readFileSync(path7.join(root, "build.gradle.kts"), "utf-8");
|
|
1939
2403
|
if (gradle.includes("spring")) return "Spring Boot (Kotlin)";
|
|
1940
2404
|
if (gradle.includes("ktor")) return "Ktor";
|
|
1941
2405
|
} catch {
|
|
@@ -1944,7 +2408,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1944
2408
|
}
|
|
1945
2409
|
case "ruby": {
|
|
1946
2410
|
try {
|
|
1947
|
-
const gemfile =
|
|
2411
|
+
const gemfile = fs6.readFileSync(path7.join(root, "Gemfile"), "utf-8");
|
|
1948
2412
|
if (gemfile.includes("rails")) return "Ruby on Rails";
|
|
1949
2413
|
if (gemfile.includes("sinatra")) return "Sinatra";
|
|
1950
2414
|
if (gemfile.includes("hanami")) return "Hanami";
|
|
@@ -1955,10 +2419,10 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1955
2419
|
}
|
|
1956
2420
|
case "csharp": {
|
|
1957
2421
|
try {
|
|
1958
|
-
const entries =
|
|
2422
|
+
const entries = fs6.readdirSync(root);
|
|
1959
2423
|
const csproj = entries.find((e) => e.endsWith(".csproj"));
|
|
1960
2424
|
if (csproj) {
|
|
1961
|
-
const content =
|
|
2425
|
+
const content = fs6.readFileSync(path7.join(root, csproj), "utf-8");
|
|
1962
2426
|
if (content.includes("Microsoft.AspNetCore")) return "ASP.NET Core";
|
|
1963
2427
|
if (content.includes("Microsoft.Maui")) return ".NET MAUI";
|
|
1964
2428
|
if (content.includes("Serilog")) return "Serilog";
|
|
@@ -1969,7 +2433,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1969
2433
|
}
|
|
1970
2434
|
case "swift": {
|
|
1971
2435
|
try {
|
|
1972
|
-
const content =
|
|
2436
|
+
const content = fs6.readFileSync(path7.join(root, "Package.swift"), "utf-8");
|
|
1973
2437
|
if (content.includes("vapor")) return "Vapor";
|
|
1974
2438
|
if (content.includes("swift-nio")) return "SwiftNIO";
|
|
1975
2439
|
if (content.includes("perfect")) return "Perfect";
|
|
@@ -1984,7 +2448,7 @@ function detectMultiLanguageFramework(root, language) {
|
|
|
1984
2448
|
}
|
|
1985
2449
|
function detectFeatures(root) {
|
|
1986
2450
|
const features = [];
|
|
1987
|
-
const pkg = readJsonSafe(
|
|
2451
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
1988
2452
|
if (!pkg) return features;
|
|
1989
2453
|
const pkgDeps4 = pkg.dependencies ?? {};
|
|
1990
2454
|
const pkgDevDeps4 = pkg.devDependencies ?? {};
|
|
@@ -2008,25 +2472,25 @@ function detectFeatures(root) {
|
|
|
2008
2472
|
}
|
|
2009
2473
|
function readJsonSafe(filePath) {
|
|
2010
2474
|
try {
|
|
2011
|
-
if (
|
|
2012
|
-
return JSON.parse(
|
|
2475
|
+
if (fs6.existsSync(filePath)) {
|
|
2476
|
+
return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
2013
2477
|
}
|
|
2014
2478
|
} catch {
|
|
2015
2479
|
}
|
|
2016
2480
|
return null;
|
|
2017
2481
|
}
|
|
2018
2482
|
function hasCSSModules(root) {
|
|
2019
|
-
const srcDir =
|
|
2020
|
-
if (!
|
|
2483
|
+
const srcDir = path7.join(root, "src");
|
|
2484
|
+
if (!fs6.existsSync(srcDir)) return false;
|
|
2021
2485
|
const files = findFiles(srcDir, [".module.css", ".module.scss", ".module.less"]);
|
|
2022
2486
|
return files.length > 0;
|
|
2023
2487
|
}
|
|
2024
2488
|
function findFiles(dir, extensions) {
|
|
2025
2489
|
const results = [];
|
|
2026
2490
|
try {
|
|
2027
|
-
const entries =
|
|
2491
|
+
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2028
2492
|
for (const entry of entries) {
|
|
2029
|
-
const fullPath =
|
|
2493
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2030
2494
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
2031
2495
|
results.push(...findFiles(fullPath, extensions));
|
|
2032
2496
|
} else if (entry.isFile()) {
|
|
@@ -2217,7 +2681,7 @@ async function handleSafeTerminalExec(params) {
|
|
|
2217
2681
|
const workspaces = conventions?.workspaces;
|
|
2218
2682
|
const matched = workspaces?.find((w) => w.name === workspace || w.path === workspace);
|
|
2219
2683
|
if (matched) {
|
|
2220
|
-
workingDir =
|
|
2684
|
+
workingDir = path8.resolve(projectRoot, matched.path);
|
|
2221
2685
|
resolvedFrom = `workspace "${matched.name}" \u2192 ${matched.path}`;
|
|
2222
2686
|
} else {
|
|
2223
2687
|
return `\u26A0\uFE0F Workspace "${workspace}" not found. Run project_conventions first to detect workspaces, or use 'cwd' parameter with a direct path.
|
|
@@ -2225,13 +2689,13 @@ async function handleSafeTerminalExec(params) {
|
|
|
2225
2689
|
Available workspaces: ${workspaces?.map((w) => `"${w.name}" (${w.path})`).join(", ") || "none detected"}`;
|
|
2226
2690
|
}
|
|
2227
2691
|
} else if (inputCwd) {
|
|
2228
|
-
const resolved =
|
|
2229
|
-
const normalizedResolved =
|
|
2230
|
-
const normalizedRoot =
|
|
2692
|
+
const resolved = path8.resolve(projectRoot, inputCwd);
|
|
2693
|
+
const normalizedResolved = path8.normalize(resolved).toLowerCase();
|
|
2694
|
+
const normalizedRoot = path8.normalize(projectRoot).toLowerCase();
|
|
2231
2695
|
if (!normalizedResolved.startsWith(normalizedRoot)) {
|
|
2232
2696
|
return `\u{1F6AB} BLOCKED: Path "${inputCwd}" resolves outside project root "${projectRoot}".`;
|
|
2233
2697
|
}
|
|
2234
|
-
if (!
|
|
2698
|
+
if (!fs7.existsSync(resolved)) {
|
|
2235
2699
|
return `\u26A0\uFE0F Directory "${inputCwd}" does not exist at "${resolved}".`;
|
|
2236
2700
|
}
|
|
2237
2701
|
workingDir = resolved;
|
|
@@ -2317,9 +2781,9 @@ function formatTimeoutResult(command, timeout) {
|
|
|
2317
2781
|
}
|
|
2318
2782
|
|
|
2319
2783
|
// src/agents/codeReviewer.ts
|
|
2320
|
-
import
|
|
2321
|
-
import
|
|
2322
|
-
import { execSync as
|
|
2784
|
+
import fs8 from "fs";
|
|
2785
|
+
import path9 from "path";
|
|
2786
|
+
import { execSync as execSync4 } from "child_process";
|
|
2323
2787
|
var CODE_EXTENSIONS = [
|
|
2324
2788
|
".ts",
|
|
2325
2789
|
".tsx",
|
|
@@ -2349,7 +2813,7 @@ var CONFIG_EXTENSIONS = [
|
|
|
2349
2813
|
function getGitChangedFiles() {
|
|
2350
2814
|
try {
|
|
2351
2815
|
const root = getProjectRoot();
|
|
2352
|
-
const stdout =
|
|
2816
|
+
const stdout = execSync4("git status --porcelain", {
|
|
2353
2817
|
cwd: root,
|
|
2354
2818
|
encoding: "utf-8"
|
|
2355
2819
|
});
|
|
@@ -2362,7 +2826,7 @@ function getGitChangedFiles() {
|
|
|
2362
2826
|
if (filePath.startsWith('"') && filePath.endsWith('"')) {
|
|
2363
2827
|
filePath = filePath.substring(1, filePath.length - 1);
|
|
2364
2828
|
}
|
|
2365
|
-
const ext =
|
|
2829
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2366
2830
|
if (CODE_EXTENSIONS.includes(ext)) files.push(filePath);
|
|
2367
2831
|
}
|
|
2368
2832
|
return files.slice(0, 10);
|
|
@@ -2399,7 +2863,7 @@ Pass an explicit 'files' array to review specific files.`;
|
|
|
2399
2863
|
continue;
|
|
2400
2864
|
}
|
|
2401
2865
|
const resolvedPath = validation.resolvedPath;
|
|
2402
|
-
if (!
|
|
2866
|
+
if (!fs8.existsSync(resolvedPath)) {
|
|
2403
2867
|
allIssues.push({
|
|
2404
2868
|
file: filePath,
|
|
2405
2869
|
line: 0,
|
|
@@ -2411,7 +2875,7 @@ Pass an explicit 'files' array to review specific files.`;
|
|
|
2411
2875
|
continue;
|
|
2412
2876
|
}
|
|
2413
2877
|
try {
|
|
2414
|
-
const content =
|
|
2878
|
+
const content = fs8.readFileSync(resolvedPath, "utf-8");
|
|
2415
2879
|
filesReviewed++;
|
|
2416
2880
|
checkGeneral(filePath, content, allIssues);
|
|
2417
2881
|
switch (focus) {
|
|
@@ -2509,12 +2973,12 @@ function isTestFile(filePath) {
|
|
|
2509
2973
|
return /\.(test|spec)\.[a-z]+$/i.test(filePath) || /__tests__/.test(filePath);
|
|
2510
2974
|
}
|
|
2511
2975
|
function isTsLike(filePath) {
|
|
2512
|
-
const ext =
|
|
2976
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2513
2977
|
return ext === ".ts" || ext === ".tsx";
|
|
2514
2978
|
}
|
|
2515
2979
|
function checkGeneral(filePath, content, issues) {
|
|
2516
2980
|
const lines = content.split("\n");
|
|
2517
|
-
const ext =
|
|
2981
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2518
2982
|
if (content.length > 0 && !content.endsWith("\n")) {
|
|
2519
2983
|
issues.push({
|
|
2520
2984
|
file: filePath,
|
|
@@ -2716,7 +3180,7 @@ function stripJsonComments(text) {
|
|
|
2716
3180
|
return cleaned.join("\n");
|
|
2717
3181
|
}
|
|
2718
3182
|
function checkConfigFile(filePath, content, issues) {
|
|
2719
|
-
const ext =
|
|
3183
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2720
3184
|
const lines = content.split("\n");
|
|
2721
3185
|
if (ext === ".json" || ext === ".jsonc") {
|
|
2722
3186
|
checkJson(filePath, content, lines, issues);
|
|
@@ -2727,7 +3191,7 @@ function checkConfigFile(filePath, content, issues) {
|
|
|
2727
3191
|
}
|
|
2728
3192
|
}
|
|
2729
3193
|
function checkJson(filePath, content, lines, issues) {
|
|
2730
|
-
const ext =
|
|
3194
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2731
3195
|
const isJsonc = ext === ".jsonc";
|
|
2732
3196
|
const contentToParse = isJsonc ? stripJsonComments(content) : content;
|
|
2733
3197
|
try {
|
|
@@ -3453,10 +3917,10 @@ function formatConventionsOutput(conventions) {
|
|
|
3453
3917
|
}
|
|
3454
3918
|
|
|
3455
3919
|
// src/utils/gitUtils.ts
|
|
3456
|
-
import { execSync as
|
|
3920
|
+
import { execSync as execSync5 } from "child_process";
|
|
3457
3921
|
function runGitCommand(command) {
|
|
3458
3922
|
const root = getProjectRoot();
|
|
3459
|
-
return
|
|
3923
|
+
return execSync5(command, {
|
|
3460
3924
|
cwd: root,
|
|
3461
3925
|
encoding: "utf-8",
|
|
3462
3926
|
maxBuffer: 2 * 1024 * 1024
|
|
@@ -3584,8 +4048,8 @@ async function handleGitDiff(params) {
|
|
|
3584
4048
|
}
|
|
3585
4049
|
|
|
3586
4050
|
// src/tools/projectStructure.ts
|
|
3587
|
-
import
|
|
3588
|
-
import
|
|
4051
|
+
import fs9 from "fs";
|
|
4052
|
+
import path10 from "path";
|
|
3589
4053
|
var DEFAULT_IGNORE = [
|
|
3590
4054
|
"node_modules",
|
|
3591
4055
|
".git",
|
|
@@ -3603,7 +4067,7 @@ var DEFAULT_IGNORE = [
|
|
|
3603
4067
|
"*.log"
|
|
3604
4068
|
];
|
|
3605
4069
|
var treeCache = /* @__PURE__ */ new Map();
|
|
3606
|
-
var
|
|
4070
|
+
var CACHE_TTL_MS3 = 6e4;
|
|
3607
4071
|
async function handleProjectStructure(params) {
|
|
3608
4072
|
const {
|
|
3609
4073
|
depth = 3,
|
|
@@ -3615,14 +4079,14 @@ async function handleProjectStructure(params) {
|
|
|
3615
4079
|
const clampedDepth = Math.max(1, Math.min(depth, 6));
|
|
3616
4080
|
const cacheKey = `${root}:${clampedDepth}:${folderOnly}:${includePattern ?? ""}:${excludePattern ?? ""}`;
|
|
3617
4081
|
const cached = treeCache.get(cacheKey);
|
|
3618
|
-
if (cached && Date.now() - cached.timestamp <
|
|
4082
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS3) {
|
|
3619
4083
|
sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly, cached: true });
|
|
3620
4084
|
return cached.result;
|
|
3621
4085
|
}
|
|
3622
4086
|
sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
|
|
3623
4087
|
try {
|
|
3624
4088
|
const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
|
|
3625
|
-
const projectName =
|
|
4089
|
+
const projectName = path10.basename(root);
|
|
3626
4090
|
const lines = [
|
|
3627
4091
|
"[Project Structure] - " + projectName,
|
|
3628
4092
|
"Depth: " + clampedDepth + " | " + (folderOnly ? "Folders only" : "Files and folders"),
|
|
@@ -3645,7 +4109,7 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3645
4109
|
const lines = [];
|
|
3646
4110
|
let entries = [];
|
|
3647
4111
|
try {
|
|
3648
|
-
entries =
|
|
4112
|
+
entries = fs9.readdirSync(currentDir, { withFileTypes: true });
|
|
3649
4113
|
} catch {
|
|
3650
4114
|
return lines;
|
|
3651
4115
|
}
|
|
@@ -3654,12 +4118,12 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3654
4118
|
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
3655
4119
|
return a.name.localeCompare(b.name);
|
|
3656
4120
|
});
|
|
3657
|
-
const relativeDir =
|
|
4121
|
+
const relativeDir = path10.relative(root, currentDir) || ".";
|
|
3658
4122
|
const prefix = getPrefix(currentDepth);
|
|
3659
4123
|
for (const entry of entries) {
|
|
3660
4124
|
if (shouldIgnore(entry.name, relativeDir, root)) continue;
|
|
3661
|
-
const fullPath =
|
|
3662
|
-
const relativePath =
|
|
4125
|
+
const fullPath = path10.join(currentDir, entry.name);
|
|
4126
|
+
const relativePath = path10.relative(root, fullPath);
|
|
3663
4127
|
if (includePattern && !entry.name.includes(includePattern) && !relativePath.includes(includePattern)) {
|
|
3664
4128
|
if (!entry.isDirectory()) continue;
|
|
3665
4129
|
}
|
|
@@ -3670,11 +4134,11 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3670
4134
|
lines.push(prefix + "[D] " + entry.name + "/");
|
|
3671
4135
|
let subEntries = [];
|
|
3672
4136
|
try {
|
|
3673
|
-
subEntries =
|
|
4137
|
+
subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
|
|
3674
4138
|
} catch {
|
|
3675
4139
|
}
|
|
3676
4140
|
const hasVisibleContent = subEntries.some(
|
|
3677
|
-
(e) => !shouldIgnore(e.name,
|
|
4141
|
+
(e) => !shouldIgnore(e.name, path10.relative(root, fullPath), root)
|
|
3678
4142
|
);
|
|
3679
4143
|
if (hasVisibleContent) {
|
|
3680
4144
|
const subLines = buildTree(root, fullPath, maxDepth, currentDepth + 1, folderOnly, includePattern, excludePattern);
|
|
@@ -3705,7 +4169,7 @@ function getPrefix(depth) {
|
|
|
3705
4169
|
}
|
|
3706
4170
|
function getFileSize(fullPath) {
|
|
3707
4171
|
try {
|
|
3708
|
-
const stat =
|
|
4172
|
+
const stat = fs9.statSync(fullPath);
|
|
3709
4173
|
if (stat.size < 1024) return stat.size + "B";
|
|
3710
4174
|
if (stat.size < 1024 * 1024) return (stat.size / 1024).toFixed(0) + "KB";
|
|
3711
4175
|
const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
|
|
@@ -3716,8 +4180,8 @@ function getFileSize(fullPath) {
|
|
|
3716
4180
|
}
|
|
3717
4181
|
|
|
3718
4182
|
// src/tools/staticAnalysis.ts
|
|
3719
|
-
import
|
|
3720
|
-
import
|
|
4183
|
+
import fs10 from "fs";
|
|
4184
|
+
import path11 from "path";
|
|
3721
4185
|
async function handleStaticAnalysis(params) {
|
|
3722
4186
|
const { tool = "all", files, autoFix = false, timeout = 60 } = params;
|
|
3723
4187
|
const root = getProjectRoot();
|
|
@@ -3810,11 +4274,11 @@ function detectAvailableTools(root) {
|
|
|
3810
4274
|
"eslint.config.js",
|
|
3811
4275
|
"eslint.config.mjs"
|
|
3812
4276
|
];
|
|
3813
|
-
const hasEslintConfig = eslintConfigs.some((cfg) =>
|
|
4277
|
+
const hasEslintConfig = eslintConfigs.some((cfg) => fs10.existsSync(path11.join(root, cfg)));
|
|
3814
4278
|
if (hasEslintConfig && depNames.has("eslint")) {
|
|
3815
4279
|
tools.push("eslint");
|
|
3816
4280
|
}
|
|
3817
|
-
if (
|
|
4281
|
+
if (fs10.existsSync(path11.join(root, "tsconfig.json")) && depNames.has("typescript")) {
|
|
3818
4282
|
tools.push("tsc");
|
|
3819
4283
|
}
|
|
3820
4284
|
const prettierConfigs = [
|
|
@@ -3825,20 +4289,20 @@ function detectAvailableTools(root) {
|
|
|
3825
4289
|
".prettierrc.toml",
|
|
3826
4290
|
"prettier.config.js"
|
|
3827
4291
|
];
|
|
3828
|
-
const hasPrettierConfig = prettierConfigs.some((cfg) =>
|
|
4292
|
+
const hasPrettierConfig = prettierConfigs.some((cfg) => fs10.existsSync(path11.join(root, cfg)));
|
|
3829
4293
|
if (hasPrettierConfig && depNames.has("prettier")) {
|
|
3830
4294
|
tools.push("prettier");
|
|
3831
4295
|
}
|
|
3832
|
-
if (
|
|
4296
|
+
if (fs10.existsSync(path11.join(root, "ruff.toml")) || fs10.existsSync(path11.join(root, ".ruff.toml"))) {
|
|
3833
4297
|
tools.push("ruff");
|
|
3834
4298
|
}
|
|
3835
4299
|
return tools;
|
|
3836
4300
|
}
|
|
3837
4301
|
function readPackageJson(root) {
|
|
3838
4302
|
try {
|
|
3839
|
-
const pkgPath =
|
|
3840
|
-
if (
|
|
3841
|
-
return JSON.parse(
|
|
4303
|
+
const pkgPath = path11.join(root, "package.json");
|
|
4304
|
+
if (fs10.existsSync(pkgPath)) {
|
|
4305
|
+
return JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
|
|
3842
4306
|
}
|
|
3843
4307
|
} catch {
|
|
3844
4308
|
}
|
|
@@ -3894,18 +4358,18 @@ function buildToolCommand(tool, files, autoFix) {
|
|
|
3894
4358
|
}
|
|
3895
4359
|
function detectPackageManagerPrefix() {
|
|
3896
4360
|
const root = getProjectRoot();
|
|
3897
|
-
if (
|
|
3898
|
-
if (
|
|
4361
|
+
if (fs10.existsSync(path11.join(root, "pnpm-lock.yaml"))) return "pnpm ";
|
|
4362
|
+
if (fs10.existsSync(path11.join(root, "yarn.lock"))) return "yarn ";
|
|
3899
4363
|
return "npx --no-install ";
|
|
3900
4364
|
}
|
|
3901
4365
|
function findBinary(root, binName) {
|
|
3902
4366
|
const candidates = [
|
|
3903
|
-
|
|
3904
|
-
|
|
4367
|
+
path11.join(root, "node_modules", ".bin", binName),
|
|
4368
|
+
path11.join(root, "..", "node_modules", ".bin", binName)
|
|
3905
4369
|
];
|
|
3906
4370
|
for (const candidate of candidates) {
|
|
3907
4371
|
try {
|
|
3908
|
-
if (
|
|
4372
|
+
if (fs10.existsSync(candidate)) {
|
|
3909
4373
|
return candidate;
|
|
3910
4374
|
}
|
|
3911
4375
|
} catch {
|
|
@@ -3929,7 +4393,7 @@ function parseToolOutput(tool, stdout, stderr, projectRoot) {
|
|
|
3929
4393
|
}
|
|
3930
4394
|
function resolveToolPath(filePath, projectRoot) {
|
|
3931
4395
|
const trimmed = filePath.trim();
|
|
3932
|
-
return
|
|
4396
|
+
return path11.isAbsolute(trimmed) ? path11.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
|
|
3933
4397
|
}
|
|
3934
4398
|
function parseEslintOutput(output, projectRoot) {
|
|
3935
4399
|
const issues = [];
|
|
@@ -4112,7 +4576,7 @@ function formatToolNotAvailable(requested, available) {
|
|
|
4112
4576
|
}
|
|
4113
4577
|
|
|
4114
4578
|
// src/utils/kumaShared.ts
|
|
4115
|
-
import { execSync as
|
|
4579
|
+
import { execSync as execSync6 } from "child_process";
|
|
4116
4580
|
function getSessionStats(inputGoal) {
|
|
4117
4581
|
const summary = sessionMemory.getSummary();
|
|
4118
4582
|
const goal = inputGoal || summary.currentGoal || "";
|
|
@@ -4134,7 +4598,7 @@ function getSessionStats(inputGoal) {
|
|
|
4134
4598
|
function getGitDiffStat(timeout = 3e3) {
|
|
4135
4599
|
try {
|
|
4136
4600
|
const root = getProjectRoot();
|
|
4137
|
-
return
|
|
4601
|
+
return execSync6("git diff --stat", {
|
|
4138
4602
|
cwd: root,
|
|
4139
4603
|
encoding: "utf-8",
|
|
4140
4604
|
timeout
|
|
@@ -4407,8 +4871,8 @@ function formatAnalytics(a) {
|
|
|
4407
4871
|
}
|
|
4408
4872
|
|
|
4409
4873
|
// src/engine/kumaHealthDashboard.ts
|
|
4410
|
-
import
|
|
4411
|
-
import
|
|
4874
|
+
import fs11 from "fs";
|
|
4875
|
+
import path12 from "path";
|
|
4412
4876
|
function computeHealthDashboard() {
|
|
4413
4877
|
const analytics = computeAnalytics();
|
|
4414
4878
|
const root = getProjectRoot();
|
|
@@ -4416,27 +4880,27 @@ function computeHealthDashboard() {
|
|
|
4416
4880
|
const gitChanges = gitStat ? gitStat.split("\n").filter((l) => l.includes("|")).length : 0;
|
|
4417
4881
|
let backupCount = 0;
|
|
4418
4882
|
try {
|
|
4419
|
-
const bd =
|
|
4420
|
-
if (
|
|
4883
|
+
const bd = path12.join(root, ".kuma", "backups");
|
|
4884
|
+
if (fs11.existsSync(bd)) backupCount = fs11.readdirSync(bd).filter((d) => /^\d+$/.test(d)).length;
|
|
4421
4885
|
} catch {
|
|
4422
4886
|
}
|
|
4423
4887
|
const dirFileCounts = /* @__PURE__ */ new Map();
|
|
4424
4888
|
try {
|
|
4425
4889
|
const walk = (dir, base) => {
|
|
4426
4890
|
try {
|
|
4427
|
-
for (const e of
|
|
4891
|
+
for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
4428
4892
|
if (e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
4429
|
-
const full =
|
|
4893
|
+
const full = path12.join(dir, e.name);
|
|
4430
4894
|
if (e.isDirectory()) walk(full, base);
|
|
4431
4895
|
else if (e.isFile() && /\.(ts|tsx|js|jsx)$/.test(e.name)) {
|
|
4432
|
-
const rel =
|
|
4896
|
+
const rel = path12.dirname(path12.relative(base, full));
|
|
4433
4897
|
dirFileCounts.set(rel, (dirFileCounts.get(rel) || 0) + 1);
|
|
4434
4898
|
}
|
|
4435
4899
|
}
|
|
4436
4900
|
} catch {
|
|
4437
4901
|
}
|
|
4438
4902
|
};
|
|
4439
|
-
walk(
|
|
4903
|
+
walk(path12.join(root, "src"), root);
|
|
4440
4904
|
} catch {
|
|
4441
4905
|
}
|
|
4442
4906
|
const dirMap = /* @__PURE__ */ new Map();
|
|
@@ -4444,7 +4908,7 @@ function computeHealthDashboard() {
|
|
|
4444
4908
|
if (call.toolName === "precise_diff_editor") {
|
|
4445
4909
|
const fp = call.params?.filePath;
|
|
4446
4910
|
if (fp) {
|
|
4447
|
-
const dir =
|
|
4911
|
+
const dir = path12.dirname(fp) || ".";
|
|
4448
4912
|
const e = dirMap.get(dir) || { edits: 0, failures: 0 };
|
|
4449
4913
|
e.edits++;
|
|
4450
4914
|
if (call.params?.success === false) e.failures++;
|
|
@@ -4524,9 +4988,9 @@ function formatHealthDashboard(r) {
|
|
|
4524
4988
|
}
|
|
4525
4989
|
|
|
4526
4990
|
// src/guards/antiPatternDetector.ts
|
|
4527
|
-
import
|
|
4528
|
-
import
|
|
4529
|
-
import { execSync as
|
|
4991
|
+
import fs12 from "fs";
|
|
4992
|
+
import path13 from "path";
|
|
4993
|
+
import { execSync as execSync7 } from "child_process";
|
|
4530
4994
|
var SCRIPT_PATCH_PATTERNS = [
|
|
4531
4995
|
"writeFileSync",
|
|
4532
4996
|
"writeFile",
|
|
@@ -4552,20 +5016,20 @@ function findPatchScripts(projectRoot) {
|
|
|
4552
5016
|
const warnings = [];
|
|
4553
5017
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
4554
5018
|
for (const file of recentFiles) {
|
|
4555
|
-
const ext =
|
|
5019
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4556
5020
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
4557
|
-
const relativePath =
|
|
5021
|
+
const relativePath = path13.relative(projectRoot, file);
|
|
4558
5022
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
|
|
4559
5023
|
continue;
|
|
4560
5024
|
}
|
|
4561
5025
|
try {
|
|
4562
|
-
const content =
|
|
5026
|
+
const content = fs12.readFileSync(file, "utf-8").toLowerCase();
|
|
4563
5027
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
4564
5028
|
if (matchedPattern) {
|
|
4565
5029
|
warnings.push({
|
|
4566
5030
|
severity: "high",
|
|
4567
5031
|
pattern: "script-patching",
|
|
4568
|
-
message: `Created script file that modifies other files: ${
|
|
5032
|
+
message: `Created script file that modifies other files: ${path13.basename(file)}`,
|
|
4569
5033
|
suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
|
|
4570
5034
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
4571
5035
|
filePath: relativePath
|
|
@@ -4599,15 +5063,15 @@ function detectBashGrepUsage() {
|
|
|
4599
5063
|
function scanRecentFiles(projectRoot) {
|
|
4600
5064
|
const recent = [];
|
|
4601
5065
|
try {
|
|
4602
|
-
const entries =
|
|
5066
|
+
const entries = fs12.readdirSync(projectRoot, { withFileTypes: true });
|
|
4603
5067
|
const now = Date.now();
|
|
4604
5068
|
const recentThreshold = 30 * 60 * 1e3;
|
|
4605
5069
|
for (const entry of entries) {
|
|
4606
5070
|
if (!entry.isFile()) continue;
|
|
4607
5071
|
try {
|
|
4608
|
-
const stat =
|
|
5072
|
+
const stat = fs12.statSync(path13.join(projectRoot, entry.name));
|
|
4609
5073
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
4610
|
-
recent.push(
|
|
5074
|
+
recent.push(path13.join(projectRoot, entry.name));
|
|
4611
5075
|
}
|
|
4612
5076
|
} catch {
|
|
4613
5077
|
continue;
|
|
@@ -4620,7 +5084,7 @@ function scanRecentFiles(projectRoot) {
|
|
|
4620
5084
|
function detectGitPatchScripts(projectRoot) {
|
|
4621
5085
|
const warnings = [];
|
|
4622
5086
|
try {
|
|
4623
|
-
const statusStdout =
|
|
5087
|
+
const statusStdout = execSync7("git status --porcelain", {
|
|
4624
5088
|
cwd: projectRoot,
|
|
4625
5089
|
encoding: "utf-8",
|
|
4626
5090
|
timeout: 3e3
|
|
@@ -4631,14 +5095,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
4631
5095
|
const prefix = line.substring(0, 2);
|
|
4632
5096
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
4633
5097
|
const file = line.substring(3).trim();
|
|
4634
|
-
const ext =
|
|
5098
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4635
5099
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
4636
5100
|
const isRootLevel = !file.includes("/");
|
|
4637
5101
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
4638
5102
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
4639
|
-
const fullPath =
|
|
4640
|
-
if (
|
|
4641
|
-
const content =
|
|
5103
|
+
const fullPath = path13.join(projectRoot, file);
|
|
5104
|
+
if (fs12.existsSync(fullPath)) {
|
|
5105
|
+
const content = fs12.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
4642
5106
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
4643
5107
|
if (matchedPattern) {
|
|
4644
5108
|
warnings.push({
|
|
@@ -4653,7 +5117,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
4653
5117
|
}
|
|
4654
5118
|
}
|
|
4655
5119
|
}
|
|
4656
|
-
const deletedStdout =
|
|
5120
|
+
const deletedStdout = execSync7("git diff --name-only --diff-filter=D HEAD", {
|
|
4657
5121
|
cwd: projectRoot,
|
|
4658
5122
|
encoding: "utf-8",
|
|
4659
5123
|
timeout: 3e3
|
|
@@ -4661,7 +5125,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
4661
5125
|
if (deletedStdout) {
|
|
4662
5126
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
4663
5127
|
for (const file of deletedFiles) {
|
|
4664
|
-
const ext =
|
|
5128
|
+
const ext = path13.extname(file).toLowerCase();
|
|
4665
5129
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
4666
5130
|
warnings.push({
|
|
4667
5131
|
severity: "high",
|
|
@@ -4721,21 +5185,21 @@ function detectAllAntiPatterns() {
|
|
|
4721
5185
|
}
|
|
4722
5186
|
|
|
4723
5187
|
// src/engine/contextSnapshot.ts
|
|
4724
|
-
import
|
|
4725
|
-
import
|
|
4726
|
-
import { execSync as
|
|
5188
|
+
import fs13 from "fs";
|
|
5189
|
+
import path14 from "path";
|
|
5190
|
+
import { execSync as execSync8 } from "child_process";
|
|
4727
5191
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
4728
5192
|
function snapshotDir() {
|
|
4729
|
-
return
|
|
5193
|
+
return path14.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
4730
5194
|
}
|
|
4731
5195
|
function ensureSnapshotDir() {
|
|
4732
5196
|
const dir = snapshotDir();
|
|
4733
|
-
if (!
|
|
4734
|
-
|
|
5197
|
+
if (!fs13.existsSync(dir)) {
|
|
5198
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
4735
5199
|
}
|
|
4736
5200
|
}
|
|
4737
5201
|
function snapshotFilePath(timestamp) {
|
|
4738
|
-
return
|
|
5202
|
+
return path14.join(snapshotDir(), `${timestamp}.json`);
|
|
4739
5203
|
}
|
|
4740
5204
|
function saveSnapshot(goal) {
|
|
4741
5205
|
try {
|
|
@@ -4756,7 +5220,7 @@ function saveSnapshot(goal) {
|
|
|
4756
5220
|
hasConventions: !!summary.hasConventions
|
|
4757
5221
|
};
|
|
4758
5222
|
try {
|
|
4759
|
-
|
|
5223
|
+
fs13.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
4760
5224
|
} catch {
|
|
4761
5225
|
}
|
|
4762
5226
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -4764,12 +5228,12 @@ function saveSnapshot(goal) {
|
|
|
4764
5228
|
}
|
|
4765
5229
|
function listSnapshots() {
|
|
4766
5230
|
const dir = snapshotDir();
|
|
4767
|
-
if (!
|
|
5231
|
+
if (!fs13.existsSync(dir)) return [];
|
|
4768
5232
|
try {
|
|
4769
|
-
const files =
|
|
5233
|
+
const files = fs13.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
|
|
4770
5234
|
return files.map((f) => {
|
|
4771
5235
|
try {
|
|
4772
|
-
const content =
|
|
5236
|
+
const content = fs13.readFileSync(path14.join(dir, f), "utf-8");
|
|
4773
5237
|
return JSON.parse(content);
|
|
4774
5238
|
} catch {
|
|
4775
5239
|
return null;
|
|
@@ -4812,7 +5276,7 @@ function formatSnapshot(snapshot) {
|
|
|
4812
5276
|
function getGitDiffStat2() {
|
|
4813
5277
|
try {
|
|
4814
5278
|
const root = getProjectRoot();
|
|
4815
|
-
const stdout =
|
|
5279
|
+
const stdout = execSync8("git diff --stat", {
|
|
4816
5280
|
cwd: root,
|
|
4817
5281
|
encoding: "utf-8",
|
|
4818
5282
|
timeout: 3e3
|
|
@@ -4957,25 +5421,25 @@ async function handleKumaContext(params) {
|
|
|
4957
5421
|
}
|
|
4958
5422
|
|
|
4959
5423
|
// src/tools/kumaInit.ts
|
|
4960
|
-
import
|
|
4961
|
-
import
|
|
5424
|
+
import fs14 from "fs";
|
|
5425
|
+
import path15 from "path";
|
|
4962
5426
|
async function handleKumaInit(params) {
|
|
4963
5427
|
const root = params.projectRoot ?? getProjectRoot();
|
|
4964
|
-
const kumaDir =
|
|
4965
|
-
const memoriesDir =
|
|
4966
|
-
const initMdPath =
|
|
5428
|
+
const kumaDir = path15.join(root, ".kuma");
|
|
5429
|
+
const memoriesDir = path15.join(kumaDir, "memories");
|
|
5430
|
+
const initMdPath = path15.join(kumaDir, "init.md");
|
|
4967
5431
|
sessionMemory.recordToolCall("kuma_init", { projectRoot: root });
|
|
4968
5432
|
let rules = "";
|
|
4969
|
-
if (
|
|
4970
|
-
rules =
|
|
5433
|
+
if (fs14.existsSync(initMdPath)) {
|
|
5434
|
+
rules = fs14.readFileSync(initMdPath, "utf-8");
|
|
4971
5435
|
}
|
|
4972
5436
|
const memories = [];
|
|
4973
|
-
if (
|
|
5437
|
+
if (fs14.existsSync(memoriesDir)) {
|
|
4974
5438
|
try {
|
|
4975
|
-
const files =
|
|
5439
|
+
const files = fs14.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
|
|
4976
5440
|
for (const file of files) {
|
|
4977
5441
|
try {
|
|
4978
|
-
const content =
|
|
5442
|
+
const content = fs14.readFileSync(path15.join(memoriesDir, file), "utf-8");
|
|
4979
5443
|
memories.push({ topic: file.replace(/\.md$/, ""), content });
|
|
4980
5444
|
} catch {
|
|
4981
5445
|
}
|
|
@@ -5019,9 +5483,9 @@ ${memoryList}`);
|
|
|
5019
5483
|
}
|
|
5020
5484
|
|
|
5021
5485
|
// src/tools/kumaRisk.ts
|
|
5022
|
-
import
|
|
5023
|
-
import
|
|
5024
|
-
import
|
|
5486
|
+
import path16 from "path";
|
|
5487
|
+
import fg4 from "fast-glob";
|
|
5488
|
+
import fs15 from "fs";
|
|
5025
5489
|
async function handleKumaRisk(params) {
|
|
5026
5490
|
const { symbol, filePath, depth = 2 } = params;
|
|
5027
5491
|
if (!symbol && !filePath) {
|
|
@@ -5055,7 +5519,7 @@ async function handleKumaRisk(params) {
|
|
|
5055
5519
|
];
|
|
5056
5520
|
let entries = [];
|
|
5057
5521
|
try {
|
|
5058
|
-
entries = await
|
|
5522
|
+
entries = await fg4("**/*.{ts,tsx,js,jsx,mjs,cjs}", {
|
|
5059
5523
|
cwd: root,
|
|
5060
5524
|
ignore: ignorePatterns,
|
|
5061
5525
|
onlyFiles: true,
|
|
@@ -5074,11 +5538,11 @@ async function handleKumaRisk(params) {
|
|
|
5074
5538
|
for (const entry of entries) {
|
|
5075
5539
|
if (results.length >= maxResults) break;
|
|
5076
5540
|
try {
|
|
5077
|
-
const fullPath =
|
|
5078
|
-
const stat =
|
|
5541
|
+
const fullPath = path16.join(root, entry);
|
|
5542
|
+
const stat = fs15.statSync(fullPath);
|
|
5079
5543
|
if (stat.size > 5e5) continue;
|
|
5080
5544
|
if (isBinaryFile(fullPath)) continue;
|
|
5081
|
-
const content =
|
|
5545
|
+
const content = fs15.readFileSync(fullPath, "utf-8");
|
|
5082
5546
|
const lines = content.split("\n");
|
|
5083
5547
|
for (let i = 0; i < lines.length; i++) {
|
|
5084
5548
|
if (results.length >= maxResults) break;
|
|
@@ -5202,8 +5666,8 @@ function formatRiskReport(report, circularDepWarning) {
|
|
|
5202
5666
|
}
|
|
5203
5667
|
|
|
5204
5668
|
// src/tools/kumaDependencyGuard.ts
|
|
5205
|
-
import
|
|
5206
|
-
import
|
|
5669
|
+
import fs16 from "fs";
|
|
5670
|
+
import path17 from "path";
|
|
5207
5671
|
var NATIVE_JS_ALTERNATIVES = {
|
|
5208
5672
|
"lodash": [
|
|
5209
5673
|
{ method: "Array.prototype.map()", description: "Replace _.map()" },
|
|
@@ -5290,12 +5754,12 @@ var NATIVE_JS_ALTERNATIVES = {
|
|
|
5290
5754
|
};
|
|
5291
5755
|
function findExistingDependency(packageName) {
|
|
5292
5756
|
const root = getProjectRoot();
|
|
5293
|
-
const packageJsonPath =
|
|
5294
|
-
if (!
|
|
5757
|
+
const packageJsonPath = path17.join(root, "package.json");
|
|
5758
|
+
if (!fs16.existsSync(packageJsonPath)) {
|
|
5295
5759
|
return { found: false };
|
|
5296
5760
|
}
|
|
5297
5761
|
try {
|
|
5298
|
-
const pkg = JSON.parse(
|
|
5762
|
+
const pkg = JSON.parse(fs16.readFileSync(packageJsonPath, "utf-8"));
|
|
5299
5763
|
const deps = pkg.dependencies || {};
|
|
5300
5764
|
if (deps[packageName]) {
|
|
5301
5765
|
return { found: true, version: deps[packageName] };
|
|
@@ -5311,12 +5775,12 @@ function findExistingDependency(packageName) {
|
|
|
5311
5775
|
}
|
|
5312
5776
|
function findSimilarExisting(packageName) {
|
|
5313
5777
|
const root = getProjectRoot();
|
|
5314
|
-
const packageJsonPath =
|
|
5315
|
-
if (!
|
|
5778
|
+
const packageJsonPath = path17.join(root, "package.json");
|
|
5779
|
+
if (!fs16.existsSync(packageJsonPath)) {
|
|
5316
5780
|
return [];
|
|
5317
5781
|
}
|
|
5318
5782
|
try {
|
|
5319
|
-
const pkg = JSON.parse(
|
|
5783
|
+
const pkg = JSON.parse(fs16.readFileSync(packageJsonPath, "utf-8"));
|
|
5320
5784
|
const allDeps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
|
|
5321
5785
|
const installedPackages = Object.keys(allDeps);
|
|
5322
5786
|
const alternativeMap = {
|
|
@@ -5427,8 +5891,8 @@ function getEstimatedPackageSize(packageName) {
|
|
|
5427
5891
|
}
|
|
5428
5892
|
|
|
5429
5893
|
// src/tools/kumaPolicy.ts
|
|
5430
|
-
import
|
|
5431
|
-
import
|
|
5894
|
+
import fs17 from "fs";
|
|
5895
|
+
import path18 from "path";
|
|
5432
5896
|
var DEFAULT_POLICY = {
|
|
5433
5897
|
never_touch: [
|
|
5434
5898
|
".env",
|
|
@@ -5454,15 +5918,15 @@ var DEFAULT_POLICY = {
|
|
|
5454
5918
|
};
|
|
5455
5919
|
function loadPolicy() {
|
|
5456
5920
|
const root = getProjectRoot();
|
|
5457
|
-
const ymlPath =
|
|
5458
|
-
const yamlPath =
|
|
5921
|
+
const ymlPath = path18.join(root, ".kuma", "policy.yml");
|
|
5922
|
+
const yamlPath = path18.join(root, ".kuma", "policy.yaml");
|
|
5459
5923
|
let policyContent = null;
|
|
5460
5924
|
let policyPath = null;
|
|
5461
|
-
if (
|
|
5462
|
-
policyContent =
|
|
5925
|
+
if (fs17.existsSync(ymlPath)) {
|
|
5926
|
+
policyContent = fs17.readFileSync(ymlPath, "utf-8");
|
|
5463
5927
|
policyPath = ymlPath;
|
|
5464
|
-
} else if (
|
|
5465
|
-
policyContent =
|
|
5928
|
+
} else if (fs17.existsSync(yamlPath)) {
|
|
5929
|
+
policyContent = fs17.readFileSync(yamlPath, "utf-8");
|
|
5466
5930
|
policyPath = yamlPath;
|
|
5467
5931
|
}
|
|
5468
5932
|
if (!policyContent) {
|
|
@@ -5579,9 +6043,9 @@ function checkFilePathPolicy(filePath, policy) {
|
|
|
5579
6043
|
if (policy.max_file_size && policy.max_file_size > 0) {
|
|
5580
6044
|
try {
|
|
5581
6045
|
const root = getProjectRoot();
|
|
5582
|
-
const fullPath =
|
|
5583
|
-
if (
|
|
5584
|
-
const sizeKB =
|
|
6046
|
+
const fullPath = path18.join(root, filePath);
|
|
6047
|
+
if (fs17.existsSync(fullPath)) {
|
|
6048
|
+
const sizeKB = fs17.statSync(fullPath).size / 1024;
|
|
5585
6049
|
if (sizeKB > policy.max_file_size) {
|
|
5586
6050
|
violations.push({
|
|
5587
6051
|
rule: "max_file_size",
|
|
@@ -5677,8 +6141,8 @@ function formatPolicyResult(type, value, result, policy) {
|
|
|
5677
6141
|
}
|
|
5678
6142
|
|
|
5679
6143
|
// src/engine/safetyScore.ts
|
|
5680
|
-
import
|
|
5681
|
-
import
|
|
6144
|
+
import fs18 from "fs";
|
|
6145
|
+
import path19 from "path";
|
|
5682
6146
|
function computeSafetyScore(inputGoal) {
|
|
5683
6147
|
const stats = getSessionStats(inputGoal);
|
|
5684
6148
|
const checks = [];
|
|
@@ -5722,8 +6186,8 @@ function computeSafetyScore(inputGoal) {
|
|
|
5722
6186
|
totalScore += 20;
|
|
5723
6187
|
}
|
|
5724
6188
|
const backupDir = getKumaBackupsDir();
|
|
5725
|
-
if (
|
|
5726
|
-
const backupCount =
|
|
6189
|
+
if (fs18.existsSync(backupDir)) {
|
|
6190
|
+
const backupCount = fs18.readdirSync(backupDir).filter((d) => /^\d+$/.test(d)).length;
|
|
5727
6191
|
checks.push({
|
|
5728
6192
|
label: "Backup Available",
|
|
5729
6193
|
status: "pass",
|
|
@@ -5741,8 +6205,8 @@ function computeSafetyScore(inputGoal) {
|
|
|
5741
6205
|
totalScore += 5;
|
|
5742
6206
|
}
|
|
5743
6207
|
try {
|
|
5744
|
-
const lspPath =
|
|
5745
|
-
if (
|
|
6208
|
+
const lspPath = path19.join(getProjectRoot(), "node_modules", ".bin", "typescript-language-server");
|
|
6209
|
+
if (fs18.existsSync(lspPath)) {
|
|
5746
6210
|
checks.push({
|
|
5747
6211
|
label: "LSP Available",
|
|
5748
6212
|
status: "pass",
|
|
@@ -5964,8 +6428,8 @@ function formatSafetyScore(report) {
|
|
|
5964
6428
|
|
|
5965
6429
|
// src/engine/kumaDb.ts
|
|
5966
6430
|
import initSqlJs from "sql.js";
|
|
5967
|
-
import
|
|
5968
|
-
import
|
|
6431
|
+
import fs19 from "fs";
|
|
6432
|
+
import path20 from "path";
|
|
5969
6433
|
var DB_FILENAME = "kuma.db";
|
|
5970
6434
|
var dbInstance = null;
|
|
5971
6435
|
var initPromise = null;
|
|
@@ -5978,13 +6442,13 @@ async function getDb() {
|
|
|
5978
6442
|
async function initDb() {
|
|
5979
6443
|
const SQL = await initSqlJs();
|
|
5980
6444
|
const kumaDir = getKumaDir();
|
|
5981
|
-
const dbPath =
|
|
5982
|
-
if (!
|
|
5983
|
-
|
|
6445
|
+
const dbPath = path20.join(kumaDir, DB_FILENAME);
|
|
6446
|
+
if (!fs19.existsSync(kumaDir)) {
|
|
6447
|
+
fs19.mkdirSync(kumaDir, { recursive: true });
|
|
5984
6448
|
}
|
|
5985
6449
|
let db;
|
|
5986
|
-
if (
|
|
5987
|
-
const buffer =
|
|
6450
|
+
if (fs19.existsSync(dbPath)) {
|
|
6451
|
+
const buffer = fs19.readFileSync(dbPath);
|
|
5988
6452
|
db = new SQL.Database(buffer);
|
|
5989
6453
|
} else {
|
|
5990
6454
|
db = new SQL.Database();
|
|
@@ -6001,10 +6465,10 @@ function saveDb(db) {
|
|
|
6001
6465
|
if (!d) return;
|
|
6002
6466
|
try {
|
|
6003
6467
|
const kumaDir = getKumaDir();
|
|
6004
|
-
const dbPath =
|
|
6468
|
+
const dbPath = path20.join(kumaDir, DB_FILENAME);
|
|
6005
6469
|
const data = d.export();
|
|
6006
6470
|
const buffer = Buffer.from(data);
|
|
6007
|
-
|
|
6471
|
+
fs19.writeFileSync(dbPath, buffer);
|
|
6008
6472
|
} catch (err) {
|
|
6009
6473
|
console.error(`[KumaDB] Failed to save database: ${err}`);
|
|
6010
6474
|
}
|
|
@@ -6111,26 +6575,26 @@ function createSchema(db) {
|
|
|
6111
6575
|
}
|
|
6112
6576
|
|
|
6113
6577
|
// src/engine/kumaSelfHeal.ts
|
|
6114
|
-
import { execSync as
|
|
6115
|
-
import
|
|
6116
|
-
import
|
|
6578
|
+
import { execSync as execSync9 } from "child_process";
|
|
6579
|
+
import fs20 from "fs";
|
|
6580
|
+
import path21 from "path";
|
|
6117
6581
|
import crypto from "crypto";
|
|
6118
6582
|
function contentFingerprint(filePath) {
|
|
6119
6583
|
try {
|
|
6120
|
-
const fullPath =
|
|
6121
|
-
if (!
|
|
6122
|
-
const stat =
|
|
6584
|
+
const fullPath = path21.join(getProjectRoot(), filePath);
|
|
6585
|
+
if (!fs20.existsSync(fullPath)) return null;
|
|
6586
|
+
const stat = fs20.statSync(fullPath);
|
|
6123
6587
|
if (!stat.isFile()) return null;
|
|
6124
|
-
const fd =
|
|
6588
|
+
const fd = fs20.openSync(fullPath, "r");
|
|
6125
6589
|
try {
|
|
6126
6590
|
const size = stat.size;
|
|
6127
6591
|
const readSize = Math.min(1024, size);
|
|
6128
6592
|
const head = Buffer.alloc(readSize);
|
|
6129
|
-
|
|
6593
|
+
fs20.readSync(fd, head, 0, readSize, 0);
|
|
6130
6594
|
let tail = Buffer.alloc(0);
|
|
6131
6595
|
if (size > 2048) {
|
|
6132
6596
|
tail = Buffer.alloc(1024);
|
|
6133
|
-
|
|
6597
|
+
fs20.readSync(fd, tail, 0, 1024, size - 1024);
|
|
6134
6598
|
}
|
|
6135
6599
|
const hash = crypto.createHash("md5");
|
|
6136
6600
|
hash.update(head);
|
|
@@ -6138,7 +6602,7 @@ function contentFingerprint(filePath) {
|
|
|
6138
6602
|
hash.update(String(size));
|
|
6139
6603
|
return hash.digest("hex");
|
|
6140
6604
|
} finally {
|
|
6141
|
-
|
|
6605
|
+
fs20.closeSync(fd);
|
|
6142
6606
|
}
|
|
6143
6607
|
} catch {
|
|
6144
6608
|
return null;
|
|
@@ -6147,8 +6611,8 @@ function contentFingerprint(filePath) {
|
|
|
6147
6611
|
function findByFingerprint(fingerprint, oldName) {
|
|
6148
6612
|
try {
|
|
6149
6613
|
const root = getProjectRoot();
|
|
6150
|
-
const ext =
|
|
6151
|
-
const result =
|
|
6614
|
+
const ext = path21.extname(oldName);
|
|
6615
|
+
const result = execSync9(
|
|
6152
6616
|
`find . -name "*${ext}" -type f -not -path "./node_modules/*" -not -path "./.git/*" -not -path "./dist/*" -not -path "./build/*" 2>/dev/null | head -200`,
|
|
6153
6617
|
{ cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 1e4 }
|
|
6154
6618
|
).trim();
|
|
@@ -6181,8 +6645,8 @@ async function detectStaleNodes() {
|
|
|
6181
6645
|
const row = stmt.getAsObject();
|
|
6182
6646
|
const filePath = row.file_path;
|
|
6183
6647
|
if (filePath.startsWith("search::") || filePath.startsWith("api_route::")) continue;
|
|
6184
|
-
const fullPath =
|
|
6185
|
-
if (!
|
|
6648
|
+
const fullPath = path21.join(root, filePath);
|
|
6649
|
+
if (!fs20.existsSync(fullPath)) {
|
|
6186
6650
|
const newPath = findRenamedPath(filePath);
|
|
6187
6651
|
if (newPath) {
|
|
6188
6652
|
stale.push({
|
|
@@ -6195,7 +6659,7 @@ async function detectStaleNodes() {
|
|
|
6195
6659
|
});
|
|
6196
6660
|
} else {
|
|
6197
6661
|
const oldFingerprint = contentFingerprint(filePath);
|
|
6198
|
-
const contentMatch = oldFingerprint ? findByFingerprint(oldFingerprint,
|
|
6662
|
+
const contentMatch = oldFingerprint ? findByFingerprint(oldFingerprint, path21.basename(filePath)) : null;
|
|
6199
6663
|
stale.push({
|
|
6200
6664
|
nodeId: row.id,
|
|
6201
6665
|
type: row.type,
|
|
@@ -6216,7 +6680,7 @@ async function detectStaleNodes() {
|
|
|
6216
6680
|
function findRenamedPath(oldPath) {
|
|
6217
6681
|
try {
|
|
6218
6682
|
const root = getProjectRoot();
|
|
6219
|
-
const output =
|
|
6683
|
+
const output = execSync9(
|
|
6220
6684
|
`git log --follow --diff-filter=R --name-only --format="" -1 -- "${oldPath}"`,
|
|
6221
6685
|
{ cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 5e3 }
|
|
6222
6686
|
).trim();
|
|
@@ -6297,8 +6761,8 @@ async function healOnQuery(filePaths) {
|
|
|
6297
6761
|
try {
|
|
6298
6762
|
const stalePaths = filePaths.filter((fp) => {
|
|
6299
6763
|
if (!fp || fp.startsWith("search::") || fp.startsWith("api_route::")) return false;
|
|
6300
|
-
const fullPath =
|
|
6301
|
-
return !
|
|
6764
|
+
const fullPath = path21.join(getProjectRoot(), fp);
|
|
6765
|
+
return !fs20.existsSync(fullPath);
|
|
6302
6766
|
});
|
|
6303
6767
|
if (stalePaths.length === 0) return { healed: 0 };
|
|
6304
6768
|
let healed = 0;
|
|
@@ -6827,8 +7291,8 @@ async function pruneExperiences(keepPerTool = 50) {
|
|
|
6827
7291
|
|
|
6828
7292
|
// src/engine/lspClient.ts
|
|
6829
7293
|
import { spawn as spawn2 } from "child_process";
|
|
6830
|
-
import
|
|
6831
|
-
import
|
|
7294
|
+
import path22 from "path";
|
|
7295
|
+
import fs21 from "fs";
|
|
6832
7296
|
var LSPClient = class {
|
|
6833
7297
|
process = null;
|
|
6834
7298
|
requestId = 0;
|
|
@@ -6866,8 +7330,8 @@ var LSPClient = class {
|
|
|
6866
7330
|
console.error(`[LSP] typescript-language-server not found. LSP features will fallback to regex.`);
|
|
6867
7331
|
return;
|
|
6868
7332
|
}
|
|
6869
|
-
const tsconfigPath =
|
|
6870
|
-
if (!
|
|
7333
|
+
const tsconfigPath = path22.join(root, "tsconfig.json");
|
|
7334
|
+
if (!fs21.existsSync(tsconfigPath)) {
|
|
6871
7335
|
console.error(`[LSP] Warning: tsconfig.json not found at "${root}". Running in implicit project mode.`);
|
|
6872
7336
|
}
|
|
6873
7337
|
this.process = spawn2(lspBinary, ["--stdio", "--log-level=4"], {
|
|
@@ -6929,19 +7393,19 @@ var LSPClient = class {
|
|
|
6929
7393
|
/** Resolve the typescript-language-server binary from local or global install */
|
|
6930
7394
|
resolveLspBinary(projectRoot) {
|
|
6931
7395
|
const candidates = [
|
|
6932
|
-
|
|
6933
|
-
|
|
7396
|
+
path22.join(projectRoot, "node_modules", ".bin", "typescript-language-server"),
|
|
7397
|
+
path22.join(projectRoot, "..", "node_modules", ".bin", "typescript-language-server")
|
|
6934
7398
|
];
|
|
6935
7399
|
for (const candidate of candidates) {
|
|
6936
|
-
if (
|
|
7400
|
+
if (fs21.existsSync(candidate)) {
|
|
6937
7401
|
return candidate;
|
|
6938
7402
|
}
|
|
6939
7403
|
}
|
|
6940
7404
|
try {
|
|
6941
7405
|
const envPath = process.env.PATH ?? "";
|
|
6942
|
-
for (const dir of envPath.split(
|
|
6943
|
-
const binPath =
|
|
6944
|
-
if (
|
|
7406
|
+
for (const dir of envPath.split(path22.delimiter)) {
|
|
7407
|
+
const binPath = path22.join(dir, "typescript-language-server");
|
|
7408
|
+
if (fs21.existsSync(binPath)) {
|
|
6945
7409
|
return binPath;
|
|
6946
7410
|
}
|
|
6947
7411
|
}
|
|
@@ -6963,7 +7427,7 @@ var LSPClient = class {
|
|
|
6963
7427
|
this.openDocuments.add(filePath);
|
|
6964
7428
|
let content;
|
|
6965
7429
|
try {
|
|
6966
|
-
content =
|
|
7430
|
+
content = fs21.readFileSync(filePath, "utf-8");
|
|
6967
7431
|
} catch {
|
|
6968
7432
|
content = "";
|
|
6969
7433
|
}
|
|
@@ -7180,8 +7644,8 @@ var LSPClient = class {
|
|
|
7180
7644
|
var lspClient = new LSPClient();
|
|
7181
7645
|
|
|
7182
7646
|
// src/tools/lspTools.ts
|
|
7183
|
-
import
|
|
7184
|
-
import
|
|
7647
|
+
import fs22 from "fs";
|
|
7648
|
+
import path23 from "path";
|
|
7185
7649
|
async function handleFindReferences(params) {
|
|
7186
7650
|
const { filePath, line, character } = params;
|
|
7187
7651
|
const validation = validateFilePath(filePath);
|
|
@@ -7189,7 +7653,7 @@ async function handleFindReferences(params) {
|
|
|
7189
7653
|
return `Error: ${validation.error.message}`;
|
|
7190
7654
|
}
|
|
7191
7655
|
const resolvedPath = validation.resolvedPath;
|
|
7192
|
-
if (!
|
|
7656
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7193
7657
|
return `Error: File not found: "${filePath}".`;
|
|
7194
7658
|
}
|
|
7195
7659
|
if (!lspClient.isAvailable()) {
|
|
@@ -7214,7 +7678,7 @@ No references found for symbol at this position.`;
|
|
|
7214
7678
|
const enrichedRefs = references.map((ref) => {
|
|
7215
7679
|
let lineContent = "";
|
|
7216
7680
|
try {
|
|
7217
|
-
const content =
|
|
7681
|
+
const content = fs22.readFileSync(ref.filePath, "utf-8");
|
|
7218
7682
|
const lines2 = content.split("\n");
|
|
7219
7683
|
lineContent = lines2[ref.line]?.trim() ?? "";
|
|
7220
7684
|
} catch {
|
|
@@ -7230,11 +7694,11 @@ No references found for symbol at this position.`;
|
|
|
7230
7694
|
const projectRoot = getProjectRoot();
|
|
7231
7695
|
const lines = [
|
|
7232
7696
|
`**Find References** \u2014 ${enrichedRefs.length} references found`,
|
|
7233
|
-
`\u{1F4CD} File: ${
|
|
7697
|
+
`\u{1F4CD} File: ${path23.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
|
|
7234
7698
|
""
|
|
7235
7699
|
];
|
|
7236
7700
|
for (const [file, refs] of grouped) {
|
|
7237
|
-
const relPath =
|
|
7701
|
+
const relPath = path23.relative(projectRoot, file);
|
|
7238
7702
|
lines.push(`**\u{1F4C4} ${relPath}:**`);
|
|
7239
7703
|
for (const ref of refs) {
|
|
7240
7704
|
const loc = `L${ref.line + 1}:${ref.character + 1}`;
|
|
@@ -7255,7 +7719,7 @@ async function handleGoToDefinition(params) {
|
|
|
7255
7719
|
return `Error: ${validation.error.message}`;
|
|
7256
7720
|
}
|
|
7257
7721
|
const resolvedPath = validation.resolvedPath;
|
|
7258
|
-
if (!
|
|
7722
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7259
7723
|
return `Error: File not found: "${filePath}".`;
|
|
7260
7724
|
}
|
|
7261
7725
|
if (!lspClient.isAvailable()) {
|
|
@@ -7278,10 +7742,10 @@ Try selecting a smaller/cleaner position, or install typescript-language-server
|
|
|
7278
7742
|
Cannot find definition for symbol at this position.`;
|
|
7279
7743
|
}
|
|
7280
7744
|
const projectRoot = getProjectRoot();
|
|
7281
|
-
const relPath =
|
|
7745
|
+
const relPath = path23.relative(projectRoot, definition.filePath);
|
|
7282
7746
|
let lineContent = "";
|
|
7283
7747
|
try {
|
|
7284
|
-
const content =
|
|
7748
|
+
const content = fs22.readFileSync(definition.filePath, "utf-8");
|
|
7285
7749
|
const lines2 = content.split("\n");
|
|
7286
7750
|
lineContent = lines2[definition.line]?.trim() ?? "";
|
|
7287
7751
|
} catch {
|
|
@@ -7313,7 +7777,7 @@ async function handleRenameSymbol(params) {
|
|
|
7313
7777
|
return `Error: ${validation.error.message}`;
|
|
7314
7778
|
}
|
|
7315
7779
|
const resolvedPath = validation.resolvedPath;
|
|
7316
|
-
if (!
|
|
7780
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7317
7781
|
return `Error: File not found: "${filePath}".`;
|
|
7318
7782
|
}
|
|
7319
7783
|
if (!lspClient.isAvailable()) {
|
|
@@ -7344,7 +7808,7 @@ Make sure:
|
|
|
7344
7808
|
const fileChanges = [];
|
|
7345
7809
|
for (const change of result.changes) {
|
|
7346
7810
|
try {
|
|
7347
|
-
const content =
|
|
7811
|
+
const content = fs22.readFileSync(change.filePath, "utf-8");
|
|
7348
7812
|
const lines2 = content.split("\n");
|
|
7349
7813
|
const sortedEdits = [...change.edits].sort((a, b) => {
|
|
7350
7814
|
if (b.line !== a.line) return b.line - a.line;
|
|
@@ -7358,10 +7822,10 @@ Make sure:
|
|
|
7358
7822
|
lines2[edit.line] = before + edit.newText + after;
|
|
7359
7823
|
}
|
|
7360
7824
|
}
|
|
7361
|
-
|
|
7825
|
+
fs22.writeFileSync(change.filePath, lines2.join("\n"), "utf-8");
|
|
7362
7826
|
totalEdits += change.edits.length;
|
|
7363
7827
|
fileChanges.push({
|
|
7364
|
-
filePath:
|
|
7828
|
+
filePath: path23.relative(projectRoot, change.filePath),
|
|
7365
7829
|
editCount: change.edits.length
|
|
7366
7830
|
});
|
|
7367
7831
|
} catch (err) {
|
|
@@ -7388,14 +7852,14 @@ async function handleGetTypeInfo(params) {
|
|
|
7388
7852
|
return `Error: ${validation.error.message}`;
|
|
7389
7853
|
}
|
|
7390
7854
|
const resolvedPath = validation.resolvedPath;
|
|
7391
|
-
if (!
|
|
7855
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
7392
7856
|
return `Error: File not found: "${filePath}".`;
|
|
7393
7857
|
}
|
|
7394
7858
|
if (!lspClient.isAvailable()) {
|
|
7395
7859
|
sessionMemory.recordToolCall("lsp_query", { action: "type", filePath, line, character, fallback: "line-context" });
|
|
7396
7860
|
console.error(`[LSP] typescript-language-server not found. Using file-read fallback for type info.`);
|
|
7397
7861
|
try {
|
|
7398
|
-
const fileContent =
|
|
7862
|
+
const fileContent = fs22.readFileSync(resolvedPath, "utf-8");
|
|
7399
7863
|
const fileLines = fileContent.split("\n");
|
|
7400
7864
|
const targetLine = fileLines[line];
|
|
7401
7865
|
if (!targetLine) {
|
|
@@ -7403,7 +7867,7 @@ async function handleGetTypeInfo(params) {
|
|
|
7403
7867
|
}
|
|
7404
7868
|
const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
|
|
7405
7869
|
const projectRoot = getProjectRoot();
|
|
7406
|
-
const relPath =
|
|
7870
|
+
const relPath = path23.relative(projectRoot, resolvedPath);
|
|
7407
7871
|
const contextStart = Math.max(0, line - 3);
|
|
7408
7872
|
const contextEnd = Math.min(fileLines.length, line + 4);
|
|
7409
7873
|
const contextLines = [];
|
|
@@ -7438,7 +7902,7 @@ async function handleGetTypeInfo(params) {
|
|
|
7438
7902
|
No type info for this position.`;
|
|
7439
7903
|
}
|
|
7440
7904
|
const projectRoot = getProjectRoot();
|
|
7441
|
-
const relPath =
|
|
7905
|
+
const relPath = path23.relative(projectRoot, resolvedPath);
|
|
7442
7906
|
const lines = [
|
|
7443
7907
|
`\u{1F4CB} **Type Info** \u2014 \`${relPath}:${line + 1}:${character + 1}\``,
|
|
7444
7908
|
"",
|
|
@@ -7479,7 +7943,7 @@ async function handleLspQuery(params) {
|
|
|
7479
7943
|
}
|
|
7480
7944
|
function extractSymbolAtPosition(filePath, line, character) {
|
|
7481
7945
|
try {
|
|
7482
|
-
const content =
|
|
7946
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
7483
7947
|
const lines = content.split("\n");
|
|
7484
7948
|
const targetLine = lines[line];
|
|
7485
7949
|
if (!targetLine) return null;
|
|
@@ -7497,9 +7961,9 @@ function extractSymbolAtPosition(filePath, line, character) {
|
|
|
7497
7961
|
}
|
|
7498
7962
|
async function fallbackGrepReferences(symbolName, _filePath, _line, _character) {
|
|
7499
7963
|
try {
|
|
7500
|
-
const { default:
|
|
7964
|
+
const { default: fg6 } = await import("fast-glob");
|
|
7501
7965
|
const root = getProjectRoot();
|
|
7502
|
-
const tsFiles = await
|
|
7966
|
+
const tsFiles = await fg6(["**/*.{ts,tsx,js,jsx}"], {
|
|
7503
7967
|
cwd: root,
|
|
7504
7968
|
ignore: ["node_modules/**", "dist/**", ".git/**"],
|
|
7505
7969
|
onlyFiles: true,
|
|
@@ -7510,7 +7974,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
|
|
|
7510
7974
|
const regex = new RegExp(`\\b${escapedSymbol}\\b`, "g");
|
|
7511
7975
|
for (const file of tsFiles.slice(0, 100)) {
|
|
7512
7976
|
try {
|
|
7513
|
-
const content =
|
|
7977
|
+
const content = fs22.readFileSync(file, "utf-8");
|
|
7514
7978
|
const lines2 = content.split("\n");
|
|
7515
7979
|
for (let i = 0; i < lines2.length; i++) {
|
|
7516
7980
|
if (regex.test(lines2[i])) {
|
|
@@ -7539,7 +8003,7 @@ No references found. Symbol may not be used in other files.`;
|
|
|
7539
8003
|
""
|
|
7540
8004
|
];
|
|
7541
8005
|
if (grouped.size > 1) {
|
|
7542
|
-
const fileList = [...grouped.keys()].map((f) =>
|
|
8006
|
+
const fileList = [...grouped.keys()].map((f) => path23.relative(projectRoot, f)).join(", ");
|
|
7543
8007
|
lines.push(`**Ambiguity note:** Found matches across ${grouped.size} different files (${fileList}).`);
|
|
7544
8008
|
lines.push(` Verify each match is the right scope \u2014 results may include same-named symbols.`);
|
|
7545
8009
|
lines.push("");
|
|
@@ -7556,7 +8020,7 @@ No references found. Symbol may not be used in other files.`;
|
|
|
7556
8020
|
}
|
|
7557
8021
|
}
|
|
7558
8022
|
for (const [file, refs] of grouped) {
|
|
7559
|
-
const relPath =
|
|
8023
|
+
const relPath = path23.relative(projectRoot, file);
|
|
7560
8024
|
lines.push(`**\u{1F4C4} ${relPath}:**`);
|
|
7561
8025
|
for (const ref of refs) {
|
|
7562
8026
|
lines.push(` \u2514 L${ref.line} \u2014 ${ref.content}`);
|
|
@@ -7571,9 +8035,9 @@ No references found. Symbol may not be used in other files.`;
|
|
|
7571
8035
|
}
|
|
7572
8036
|
async function fallbackGrepDefinition(symbolName) {
|
|
7573
8037
|
try {
|
|
7574
|
-
const { default:
|
|
8038
|
+
const { default: fg6 } = await import("fast-glob");
|
|
7575
8039
|
const root = getProjectRoot();
|
|
7576
|
-
const tsFiles = await
|
|
8040
|
+
const tsFiles = await fg6(["**/*.{ts,tsx,js,jsx}"], {
|
|
7577
8041
|
cwd: root,
|
|
7578
8042
|
ignore: ["node_modules/**", "dist/**", ".git/**"],
|
|
7579
8043
|
onlyFiles: true,
|
|
@@ -7590,14 +8054,14 @@ async function fallbackGrepDefinition(symbolName) {
|
|
|
7590
8054
|
];
|
|
7591
8055
|
for (const file of tsFiles) {
|
|
7592
8056
|
try {
|
|
7593
|
-
const content =
|
|
8057
|
+
const content = fs22.readFileSync(file, "utf-8");
|
|
7594
8058
|
const lines = content.split("\n");
|
|
7595
8059
|
for (let i = 0; i < lines.length; i++) {
|
|
7596
8060
|
const trimmed = lines[i].trim();
|
|
7597
8061
|
for (const pattern of declPatterns) {
|
|
7598
8062
|
if (pattern.test(trimmed)) {
|
|
7599
8063
|
const projectRoot = getProjectRoot();
|
|
7600
|
-
const relPath =
|
|
8064
|
+
const relPath = path23.relative(projectRoot, file);
|
|
7601
8065
|
return [
|
|
7602
8066
|
`\u{1F4CD} **Go to Definition**`,
|
|
7603
8067
|
`\u{1F4C4} File: \`${relPath}\``,
|
|
@@ -7620,11 +8084,11 @@ Cannot find definition.`;
|
|
|
7620
8084
|
}
|
|
7621
8085
|
|
|
7622
8086
|
// src/engine/kumaTimeMachine.ts
|
|
7623
|
-
import { execSync as
|
|
8087
|
+
import { execSync as execSync10 } from "child_process";
|
|
7624
8088
|
function getBlameForFile(filePath) {
|
|
7625
8089
|
const root = getProjectRoot();
|
|
7626
8090
|
try {
|
|
7627
|
-
const output =
|
|
8091
|
+
const output = execSync10(
|
|
7628
8092
|
`git blame --line-porcelain -- "${filePath}"`,
|
|
7629
8093
|
{ cwd: root, encoding: "utf-8", maxBuffer: 2 * 1024 * 1024, timeout: 1e4 }
|
|
7630
8094
|
);
|
|
@@ -7670,7 +8134,7 @@ function getBlameForFile(filePath) {
|
|
|
7670
8134
|
function getFileHistory(filePath, maxCount = 20) {
|
|
7671
8135
|
const root = getProjectRoot();
|
|
7672
8136
|
try {
|
|
7673
|
-
const output =
|
|
8137
|
+
const output = execSync10(
|
|
7674
8138
|
`git log --follow --format="%H||%an||%ai||%s" --shortstat -- "${filePath}"`,
|
|
7675
8139
|
{ cwd: root, encoding: "utf-8", maxBuffer: 2 * 1024 * 1024, timeout: 1e4 }
|
|
7676
8140
|
);
|
|
@@ -7705,7 +8169,7 @@ async function getSymbolTimeline(symbolName, filePath, symbolType = "function")
|
|
|
7705
8169
|
const blame = getBlameForFile(filePath);
|
|
7706
8170
|
const pattern = getSymbolPattern(symbolName, symbolType);
|
|
7707
8171
|
const root = getProjectRoot();
|
|
7708
|
-
const content =
|
|
8172
|
+
const content = execSync10(`git show HEAD:"${filePath}"`, {
|
|
7709
8173
|
cwd: root,
|
|
7710
8174
|
encoding: "utf-8",
|
|
7711
8175
|
maxBuffer: 2 * 1024 * 1024,
|
|
@@ -7732,7 +8196,7 @@ async function getSymbolTimeline(symbolName, filePath, symbolType = "function")
|
|
|
7732
8196
|
const entries = [];
|
|
7733
8197
|
for (const [hash, data] of commitMap) {
|
|
7734
8198
|
try {
|
|
7735
|
-
const msg =
|
|
8199
|
+
const msg = execSync10(
|
|
7736
8200
|
`git log -1 --format="%s" ${hash}`,
|
|
7737
8201
|
{ cwd: root, encoding: "utf-8", maxBuffer: 1024 * 1024, timeout: 3e3 }
|
|
7738
8202
|
).trim();
|
|
@@ -7917,10 +8381,10 @@ async function getIntentPatterns(limit = 10) {
|
|
|
7917
8381
|
ORDER BY id ASC
|
|
7918
8382
|
`);
|
|
7919
8383
|
pathStmt.bind([session.id]);
|
|
7920
|
-
const
|
|
8384
|
+
const path31 = [];
|
|
7921
8385
|
while (pathStmt.step()) {
|
|
7922
8386
|
const row = pathStmt.getAsObject();
|
|
7923
|
-
|
|
8387
|
+
path31.push(row.tool_name);
|
|
7924
8388
|
}
|
|
7925
8389
|
pathStmt.free();
|
|
7926
8390
|
const existing = clusterMap.get(clusterKey) || {
|
|
@@ -7929,7 +8393,7 @@ async function getIntentPatterns(limit = 10) {
|
|
|
7929
8393
|
totalDuration: 0,
|
|
7930
8394
|
lastUsed: 0
|
|
7931
8395
|
};
|
|
7932
|
-
existing.paths.push(
|
|
8396
|
+
existing.paths.push(path31);
|
|
7933
8397
|
existing.successes.push(session.success);
|
|
7934
8398
|
existing.lastUsed = Math.max(existing.lastUsed, session.startedAt);
|
|
7935
8399
|
clusterMap.set(clusterKey, existing);
|
|
@@ -8037,9 +8501,9 @@ function formatIntentSuggestion(intent, suggestion) {
|
|
|
8037
8501
|
}
|
|
8038
8502
|
|
|
8039
8503
|
// src/engine/kumaArchGuard.ts
|
|
8040
|
-
import
|
|
8041
|
-
import
|
|
8042
|
-
import
|
|
8504
|
+
import fg5 from "fast-glob";
|
|
8505
|
+
import fs23 from "fs";
|
|
8506
|
+
import path24 from "path";
|
|
8043
8507
|
var ARCHITECTURES = [
|
|
8044
8508
|
{
|
|
8045
8509
|
name: "clean-architecture",
|
|
@@ -8130,8 +8594,8 @@ var ARCHITECTURES = [
|
|
|
8130
8594
|
];
|
|
8131
8595
|
function detectArchitecture() {
|
|
8132
8596
|
const root = getProjectRoot();
|
|
8133
|
-
const srcDir =
|
|
8134
|
-
if (!
|
|
8597
|
+
const srcDir = path24.join(root, "src");
|
|
8598
|
+
if (!fs23.existsSync(srcDir)) {
|
|
8135
8599
|
return getArchitectureProfile("flat");
|
|
8136
8600
|
}
|
|
8137
8601
|
const scores = ARCHITECTURES.map((arch) => {
|
|
@@ -8142,7 +8606,7 @@ function detectArchitecture() {
|
|
|
8142
8606
|
totalPatterns++;
|
|
8143
8607
|
const globPattern = pattern.replace(/\/\*\*$/, "/**");
|
|
8144
8608
|
try {
|
|
8145
|
-
const matches =
|
|
8609
|
+
const matches = fg5.sync(globPattern, { cwd: root, onlyFiles: false, deep: 1 });
|
|
8146
8610
|
if (matches.length > 0) matchCount++;
|
|
8147
8611
|
} catch {
|
|
8148
8612
|
}
|
|
@@ -8153,10 +8617,10 @@ function detectArchitecture() {
|
|
|
8153
8617
|
scores.sort((a, b) => b.score - a.score);
|
|
8154
8618
|
const best = scores[0];
|
|
8155
8619
|
if (!best || best.score < 0.2) {
|
|
8156
|
-
if (
|
|
8620
|
+
if (fs23.existsSync(path24.join(srcDir, "domain")) || fs23.existsSync(path24.join(srcDir, "entities"))) {
|
|
8157
8621
|
return getArchitectureProfile("clean-architecture");
|
|
8158
8622
|
}
|
|
8159
|
-
if (
|
|
8623
|
+
if (fs23.existsSync(path24.join(srcDir, "data")) || fs23.existsSync(path24.join(srcDir, "business"))) {
|
|
8160
8624
|
return getArchitectureProfile("layered-architecture");
|
|
8161
8625
|
}
|
|
8162
8626
|
return getArchitectureProfile("flat");
|
|
@@ -8227,7 +8691,7 @@ async function scanFilesystemForViolations(profile) {
|
|
|
8227
8691
|
const arch = profile || detectArchitecture();
|
|
8228
8692
|
const root = getProjectRoot();
|
|
8229
8693
|
try {
|
|
8230
|
-
const files = await
|
|
8694
|
+
const files = await fg5(["src/**/*.{ts,tsx,js,jsx}"], {
|
|
8231
8695
|
cwd: root,
|
|
8232
8696
|
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*.d.ts"],
|
|
8233
8697
|
onlyFiles: true
|
|
@@ -8236,7 +8700,7 @@ async function scanFilesystemForViolations(profile) {
|
|
|
8236
8700
|
const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
|
|
8237
8701
|
for (const file of files.slice(0, 200)) {
|
|
8238
8702
|
try {
|
|
8239
|
-
const content =
|
|
8703
|
+
const content = fs23.readFileSync(path24.join(root, file), "utf-8");
|
|
8240
8704
|
const imports = [];
|
|
8241
8705
|
let match;
|
|
8242
8706
|
while ((match = importRegex.exec(content)) !== null) {
|
|
@@ -8249,16 +8713,16 @@ async function scanFilesystemForViolations(profile) {
|
|
|
8249
8713
|
if (!imp.startsWith(".") && !imp.startsWith("/") && !imp.startsWith("src/")) continue;
|
|
8250
8714
|
let targetFile = imp;
|
|
8251
8715
|
if (imp.startsWith(".")) {
|
|
8252
|
-
const resolved =
|
|
8253
|
-
targetFile =
|
|
8716
|
+
const resolved = path24.resolve(path24.dirname(file), imp);
|
|
8717
|
+
targetFile = path24.relative(root, resolved);
|
|
8254
8718
|
} else if (imp.startsWith("/")) {
|
|
8255
8719
|
targetFile = imp.substring(1);
|
|
8256
8720
|
}
|
|
8257
|
-
if (!
|
|
8721
|
+
if (!path24.extname(targetFile)) {
|
|
8258
8722
|
const extVariants = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
8259
8723
|
let found = false;
|
|
8260
8724
|
for (const ext of extVariants) {
|
|
8261
|
-
if (
|
|
8725
|
+
if (fs23.existsSync(path24.join(root, targetFile + ext))) {
|
|
8262
8726
|
targetFile += ext;
|
|
8263
8727
|
found = true;
|
|
8264
8728
|
break;
|
|
@@ -8342,20 +8806,20 @@ function formatArchitectureDetection(profile) {
|
|
|
8342
8806
|
}
|
|
8343
8807
|
|
|
8344
8808
|
// src/engine/kumaMemory.ts
|
|
8345
|
-
import
|
|
8346
|
-
import
|
|
8809
|
+
import fs24 from "fs";
|
|
8810
|
+
import path25 from "path";
|
|
8347
8811
|
var MEMORY_DIR = ".kuma/memories";
|
|
8348
8812
|
function scoreMemoryRelevance(context, limit = 5) {
|
|
8349
8813
|
const results = [];
|
|
8350
|
-
const kumaDir =
|
|
8351
|
-
if (!
|
|
8814
|
+
const kumaDir = path25.join(getProjectRoot(), MEMORY_DIR);
|
|
8815
|
+
if (!fs24.existsSync(kumaDir)) return [];
|
|
8352
8816
|
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
8353
8817
|
if (terms.length === 0) return [];
|
|
8354
8818
|
try {
|
|
8355
|
-
const files =
|
|
8819
|
+
const files = fs24.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
8356
8820
|
for (const file of files) {
|
|
8357
8821
|
try {
|
|
8358
|
-
const content =
|
|
8822
|
+
const content = fs24.readFileSync(path25.join(kumaDir, file), "utf-8");
|
|
8359
8823
|
const lower = content.toLowerCase();
|
|
8360
8824
|
let matchCount = 0;
|
|
8361
8825
|
for (const term of terms) {
|
|
@@ -8435,7 +8899,7 @@ function formatDecisionTemplate() {
|
|
|
8435
8899
|
}
|
|
8436
8900
|
|
|
8437
8901
|
// src/engine/kumaContextEngine.ts
|
|
8438
|
-
import { execSync as
|
|
8902
|
+
import { execSync as execSync11 } from "child_process";
|
|
8439
8903
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
8440
8904
|
"the",
|
|
8441
8905
|
"this",
|
|
@@ -8552,7 +9016,7 @@ async function searchNodesFts(db, terms) {
|
|
|
8552
9016
|
function getFileRecencySeconds(filePath) {
|
|
8553
9017
|
try {
|
|
8554
9018
|
const escapedPath = filePath.replace(/"/g, '\\"');
|
|
8555
|
-
const result =
|
|
9019
|
+
const result = execSync11(
|
|
8556
9020
|
'git log -1 --format=%ct -- "' + escapedPath + '" 2>/dev/null || echo 0',
|
|
8557
9021
|
{ encoding: "utf-8", timeout: 2e3 }
|
|
8558
9022
|
);
|
|
@@ -9269,9 +9733,9 @@ async function buildOwnershipMap(db, focus, limit) {
|
|
|
9269
9733
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
9270
9734
|
while (stmt.step()) {
|
|
9271
9735
|
const row = stmt.getAsObject();
|
|
9272
|
-
const
|
|
9273
|
-
const dir = topDir(
|
|
9274
|
-
if (focus && !
|
|
9736
|
+
const path31 = row.file_path || row.name;
|
|
9737
|
+
const dir = topDir(path31);
|
|
9738
|
+
if (focus && !path31.includes(focus)) continue;
|
|
9275
9739
|
if (!dirGroups.has(dir)) dirGroups.set(dir, []);
|
|
9276
9740
|
const existing = dirGroups.get(dir);
|
|
9277
9741
|
const emoji = row.type === "function" ? "\u{1F527}" : row.type === "file" ? "\u{1F4C4}" : "\u{1F4CC}";
|
|
@@ -9312,8 +9776,8 @@ function mermaidId(name) {
|
|
|
9312
9776
|
}
|
|
9313
9777
|
|
|
9314
9778
|
// src/engine/kumaHeatMap.ts
|
|
9315
|
-
import
|
|
9316
|
-
import
|
|
9779
|
+
import fs25 from "fs";
|
|
9780
|
+
import path26 from "path";
|
|
9317
9781
|
async function computeHeatMap() {
|
|
9318
9782
|
const history = sessionMemory.getToolCallHistory(100);
|
|
9319
9783
|
const db = await getDb();
|
|
@@ -9322,7 +9786,7 @@ async function computeHeatMap() {
|
|
|
9322
9786
|
const p = call.params;
|
|
9323
9787
|
const fp = p.filePath || p.file || "";
|
|
9324
9788
|
if (!fp) continue;
|
|
9325
|
-
const dir =
|
|
9789
|
+
const dir = path26.dirname(fp).split("/")[0] || ".";
|
|
9326
9790
|
const entry = dirData.get(dir) || { edits: 0, reads: 0, failures: 0 };
|
|
9327
9791
|
if (call.toolName === "precise_diff_editor" || call.toolName === "batch_file_writer") {
|
|
9328
9792
|
entry.edits++;
|
|
@@ -9350,12 +9814,12 @@ async function computeHeatMap() {
|
|
|
9350
9814
|
} catch {
|
|
9351
9815
|
}
|
|
9352
9816
|
const root = getProjectRoot();
|
|
9353
|
-
const srcDir =
|
|
9354
|
-
if (
|
|
9817
|
+
const srcDir = path26.join(root, "src");
|
|
9818
|
+
if (fs25.existsSync(srcDir)) {
|
|
9355
9819
|
try {
|
|
9356
|
-
for (const entry of
|
|
9820
|
+
for (const entry of fs25.readdirSync(srcDir, { withFileTypes: true })) {
|
|
9357
9821
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
9358
|
-
const count = countFiles(
|
|
9822
|
+
const count = countFiles(path26.join(srcDir, entry.name));
|
|
9359
9823
|
if (!dirFileCounts.has(entry.name) || dirFileCounts.get(entry.name) < count) {
|
|
9360
9824
|
dirFileCounts.set(entry.name, count);
|
|
9361
9825
|
}
|
|
@@ -9465,9 +9929,9 @@ function formatHeatMap(report) {
|
|
|
9465
9929
|
function countFiles(dir) {
|
|
9466
9930
|
let count = 0;
|
|
9467
9931
|
try {
|
|
9468
|
-
for (const entry of
|
|
9932
|
+
for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
|
|
9469
9933
|
if (entry.name.startsWith(".")) continue;
|
|
9470
|
-
const full =
|
|
9934
|
+
const full = path26.join(dir, entry.name);
|
|
9471
9935
|
if (entry.isDirectory()) {
|
|
9472
9936
|
count += countFiles(full);
|
|
9473
9937
|
} else if (/\.(ts|tsx|js|jsx|go|rs|py|java|kt)$/.test(entry.name)) {
|
|
@@ -9767,72 +10231,72 @@ function simpleHash(str) {
|
|
|
9767
10231
|
}
|
|
9768
10232
|
|
|
9769
10233
|
// src/engine/kumaLock.ts
|
|
9770
|
-
import
|
|
9771
|
-
import
|
|
10234
|
+
import fs26 from "fs";
|
|
10235
|
+
import path27 from "path";
|
|
9772
10236
|
var LOCKS_DIR = ".kuma/locks";
|
|
9773
10237
|
function locksDir() {
|
|
9774
|
-
return
|
|
10238
|
+
return path27.join(getProjectRoot(), LOCKS_DIR);
|
|
9775
10239
|
}
|
|
9776
10240
|
function ensureLocksDir() {
|
|
9777
10241
|
const dir = locksDir();
|
|
9778
|
-
if (!
|
|
10242
|
+
if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
|
|
9779
10243
|
}
|
|
9780
10244
|
function lockPath(filePath) {
|
|
9781
10245
|
const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
|
|
9782
|
-
return
|
|
10246
|
+
return path27.join(locksDir(), `${safeName}.lock.json`);
|
|
9783
10247
|
}
|
|
9784
10248
|
function acquireLock(filePath, agentId) {
|
|
9785
10249
|
ensureLocksDir();
|
|
9786
10250
|
const lp = lockPath(filePath);
|
|
9787
10251
|
const id = agentId || `agent-${process.pid}`;
|
|
9788
|
-
if (
|
|
10252
|
+
if (fs26.existsSync(lp)) {
|
|
9789
10253
|
try {
|
|
9790
|
-
const existing = JSON.parse(
|
|
10254
|
+
const existing = JSON.parse(fs26.readFileSync(lp, "utf-8"));
|
|
9791
10255
|
if (existing.agentId === id) {
|
|
9792
10256
|
return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
|
|
9793
10257
|
}
|
|
9794
10258
|
const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
|
|
9795
10259
|
if (elapsed > 300) {
|
|
9796
|
-
|
|
10260
|
+
fs26.unlinkSync(lp);
|
|
9797
10261
|
} else {
|
|
9798
10262
|
return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
|
|
9799
10263
|
}
|
|
9800
10264
|
} catch {
|
|
9801
|
-
|
|
10265
|
+
fs26.unlinkSync(lp);
|
|
9802
10266
|
}
|
|
9803
10267
|
}
|
|
9804
10268
|
const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
|
|
9805
|
-
|
|
10269
|
+
fs26.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
|
|
9806
10270
|
return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
|
|
9807
10271
|
}
|
|
9808
10272
|
function releaseLock(filePath, agentId) {
|
|
9809
10273
|
ensureLocksDir();
|
|
9810
10274
|
const lp = lockPath(filePath);
|
|
9811
10275
|
const id = agentId || `agent-${process.pid}`;
|
|
9812
|
-
if (!
|
|
10276
|
+
if (!fs26.existsSync(lp)) {
|
|
9813
10277
|
return `\u26A0\uFE0F No lock found for \`${filePath}\``;
|
|
9814
10278
|
}
|
|
9815
10279
|
try {
|
|
9816
|
-
const existing = JSON.parse(
|
|
10280
|
+
const existing = JSON.parse(fs26.readFileSync(lp, "utf-8"));
|
|
9817
10281
|
if (existing.agentId !== id) {
|
|
9818
10282
|
return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
|
|
9819
10283
|
}
|
|
9820
|
-
|
|
10284
|
+
fs26.unlinkSync(lp);
|
|
9821
10285
|
return `\u{1F513} **Lock released** on \`${filePath}\``;
|
|
9822
10286
|
} catch {
|
|
9823
|
-
|
|
10287
|
+
fs26.unlinkSync(lp);
|
|
9824
10288
|
return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
|
|
9825
10289
|
}
|
|
9826
10290
|
}
|
|
9827
10291
|
function listLocks() {
|
|
9828
10292
|
ensureLocksDir();
|
|
9829
10293
|
const dir = locksDir();
|
|
9830
|
-
const files =
|
|
10294
|
+
const files = fs26.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
|
|
9831
10295
|
if (files.length === 0) return "\u{1F513} No active locks.";
|
|
9832
10296
|
const lines = ["\u{1F512} **Active Locks:**", ""];
|
|
9833
10297
|
for (const f of files) {
|
|
9834
10298
|
try {
|
|
9835
|
-
const entry = JSON.parse(
|
|
10299
|
+
const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
|
|
9836
10300
|
const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
|
|
9837
10301
|
lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
|
|
9838
10302
|
} catch {
|
|
@@ -9843,9 +10307,9 @@ function listLocks() {
|
|
|
9843
10307
|
}
|
|
9844
10308
|
function isLocked(filePath) {
|
|
9845
10309
|
const lp = lockPath(filePath);
|
|
9846
|
-
if (!
|
|
10310
|
+
if (!fs26.existsSync(lp)) return { locked: false };
|
|
9847
10311
|
try {
|
|
9848
|
-
const entry = JSON.parse(
|
|
10312
|
+
const entry = JSON.parse(fs26.readFileSync(lp, "utf-8"));
|
|
9849
10313
|
return { locked: true, by: entry.agentId, since: entry.acquiredAt };
|
|
9850
10314
|
} catch {
|
|
9851
10315
|
return { locked: false };
|
|
@@ -9855,16 +10319,16 @@ function cleanStaleLocks() {
|
|
|
9855
10319
|
ensureLocksDir();
|
|
9856
10320
|
const dir = locksDir();
|
|
9857
10321
|
let count = 0;
|
|
9858
|
-
for (const f of
|
|
10322
|
+
for (const f of fs26.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
|
|
9859
10323
|
try {
|
|
9860
|
-
const entry = JSON.parse(
|
|
10324
|
+
const entry = JSON.parse(fs26.readFileSync(path27.join(dir, f), "utf-8"));
|
|
9861
10325
|
if (Date.now() - entry.acquiredAt > 3e5) {
|
|
9862
|
-
|
|
10326
|
+
fs26.unlinkSync(path27.join(dir, f));
|
|
9863
10327
|
count++;
|
|
9864
10328
|
}
|
|
9865
10329
|
} catch {
|
|
9866
10330
|
try {
|
|
9867
|
-
|
|
10331
|
+
fs26.unlinkSync(path27.join(dir, f));
|
|
9868
10332
|
count++;
|
|
9869
10333
|
} catch {
|
|
9870
10334
|
}
|
|
@@ -10212,13 +10676,13 @@ function formatConfidence(report, target) {
|
|
|
10212
10676
|
}
|
|
10213
10677
|
|
|
10214
10678
|
// src/engine/kumaDNA.ts
|
|
10215
|
-
import
|
|
10679
|
+
import path28 from "path";
|
|
10216
10680
|
async function generateDNA() {
|
|
10217
10681
|
try {
|
|
10218
10682
|
const db = await getDb();
|
|
10219
10683
|
const summary = sessionMemory.getSummary();
|
|
10220
10684
|
const root = getProjectRoot();
|
|
10221
|
-
const projectName =
|
|
10685
|
+
const projectName = path28.basename(root);
|
|
10222
10686
|
let totalFiles = 0, totalFunctions = 0, totalTests = 0;
|
|
10223
10687
|
try {
|
|
10224
10688
|
const filesR = db.exec("SELECT COUNT(*) as c FROM nodes WHERE type = 'file'");
|
|
@@ -11067,14 +11531,14 @@ async function handleSafetyCheck(params) {
|
|
|
11067
11531
|
}
|
|
11068
11532
|
|
|
11069
11533
|
// src/engine/kumaCollective.ts
|
|
11070
|
-
import
|
|
11071
|
-
import
|
|
11534
|
+
import fs27 from "fs";
|
|
11535
|
+
import path29 from "path";
|
|
11072
11536
|
var DEFAULT_ENDPOINT = process.env.KUMA_COLLECTIVE_URL || "";
|
|
11073
11537
|
function getSyncConfig() {
|
|
11074
11538
|
try {
|
|
11075
|
-
const configPath =
|
|
11076
|
-
if (
|
|
11077
|
-
const raw =
|
|
11539
|
+
const configPath = path29.join(getProjectRoot(), ".kuma", "config.json");
|
|
11540
|
+
if (fs27.existsSync(configPath)) {
|
|
11541
|
+
const raw = fs27.readFileSync(configPath, "utf-8");
|
|
11078
11542
|
const config = JSON.parse(raw);
|
|
11079
11543
|
if (config.collective) {
|
|
11080
11544
|
return {
|
|
@@ -11094,12 +11558,12 @@ function getSyncConfig() {
|
|
|
11094
11558
|
}
|
|
11095
11559
|
function getInstanceId() {
|
|
11096
11560
|
try {
|
|
11097
|
-
const idPath =
|
|
11098
|
-
if (
|
|
11099
|
-
return
|
|
11561
|
+
const idPath = path29.join(getProjectRoot(), ".kuma", ".instance-id");
|
|
11562
|
+
if (fs27.existsSync(idPath)) {
|
|
11563
|
+
return fs27.readFileSync(idPath, "utf-8").trim();
|
|
11100
11564
|
}
|
|
11101
11565
|
const id = `anon-${Math.random().toString(36).slice(2, 10)}`;
|
|
11102
|
-
|
|
11566
|
+
fs27.writeFileSync(idPath, id, "utf-8");
|
|
11103
11567
|
return id;
|
|
11104
11568
|
} catch {
|
|
11105
11569
|
return "anon-unknown";
|
|
@@ -11206,13 +11670,13 @@ function exportAnonymizedPatterns() {
|
|
|
11206
11670
|
function detectProjectLanguage() {
|
|
11207
11671
|
try {
|
|
11208
11672
|
const root = getProjectRoot();
|
|
11209
|
-
if (
|
|
11210
|
-
if (
|
|
11211
|
-
if (
|
|
11212
|
-
if (
|
|
11213
|
-
if (
|
|
11214
|
-
if (
|
|
11215
|
-
if (
|
|
11673
|
+
if (fs27.existsSync(path29.join(root, "go.mod"))) return "go";
|
|
11674
|
+
if (fs27.existsSync(path29.join(root, "Cargo.toml"))) return "rust";
|
|
11675
|
+
if (fs27.existsSync(path29.join(root, "composer.json"))) return "php";
|
|
11676
|
+
if (fs27.existsSync(path29.join(root, "pyproject.toml")) || fs27.existsSync(path29.join(root, "requirements.txt"))) return "python";
|
|
11677
|
+
if (fs27.existsSync(path29.join(root, "Gemfile"))) return "ruby";
|
|
11678
|
+
if (fs27.existsSync(path29.join(root, "pom.xml")) || fs27.existsSync(path29.join(root, "build.gradle"))) return "java";
|
|
11679
|
+
if (fs27.existsSync(path29.join(root, "package.json"))) return "typescript";
|
|
11216
11680
|
return "unknown";
|
|
11217
11681
|
} catch {
|
|
11218
11682
|
return "unknown";
|
|
@@ -11368,9 +11832,9 @@ function formatCollectivePatterns(patterns) {
|
|
|
11368
11832
|
}
|
|
11369
11833
|
|
|
11370
11834
|
// src/engine/kumaMarketplace.ts
|
|
11371
|
-
import { execSync as
|
|
11372
|
-
import
|
|
11373
|
-
import
|
|
11835
|
+
import { execSync as execSync12 } from "child_process";
|
|
11836
|
+
import fs28 from "fs";
|
|
11837
|
+
import path30 from "path";
|
|
11374
11838
|
var BUILT_IN_TEMPLATES = [
|
|
11375
11839
|
// ── Framework Web (JS/TS) ──
|
|
11376
11840
|
{
|
|
@@ -11585,7 +12049,7 @@ async function listMarketplace() {
|
|
|
11585
12049
|
`\u{1F4E6} ${templates.length} template(s) available`,
|
|
11586
12050
|
""
|
|
11587
12051
|
];
|
|
11588
|
-
const
|
|
12052
|
+
const LANG_MAP2 = [
|
|
11589
12053
|
{ lang: "typescript", keywords: ["typescript", "hono", "elysia", "tanstack", "zustand", "prisma", "drizzle", "shadcn"] },
|
|
11590
12054
|
{ lang: "javascript", keywords: ["javascript", "express"] },
|
|
11591
12055
|
{ lang: "go", keywords: ["go", "gin"] },
|
|
@@ -11605,7 +12069,7 @@ async function listMarketplace() {
|
|
|
11605
12069
|
general: "\u{1F4E6}"
|
|
11606
12070
|
};
|
|
11607
12071
|
function detectLang(t) {
|
|
11608
|
-
for (const entry of
|
|
12072
|
+
for (const entry of LANG_MAP2) {
|
|
11609
12073
|
if (entry.keywords.some((k) => t.tags.some((tag) => tag.includes(k)))) {
|
|
11610
12074
|
return entry.lang;
|
|
11611
12075
|
}
|
|
@@ -11650,17 +12114,17 @@ function scanNpmTemplates() {
|
|
|
11650
12114
|
const found = [];
|
|
11651
12115
|
try {
|
|
11652
12116
|
const root = getProjectRoot();
|
|
11653
|
-
const nodeModulesPath =
|
|
11654
|
-
if (!
|
|
11655
|
-
const packages =
|
|
12117
|
+
const nodeModulesPath = path30.join(root, "node_modules", "@kuma-templates");
|
|
12118
|
+
if (!fs28.existsSync(nodeModulesPath)) return found;
|
|
12119
|
+
const packages = fs28.readdirSync(nodeModulesPath);
|
|
11656
12120
|
for (const pkg of packages) {
|
|
11657
|
-
const pkgPath =
|
|
11658
|
-
if (!
|
|
11659
|
-
const pkgJson = JSON.parse(
|
|
11660
|
-
const templatePath =
|
|
12121
|
+
const pkgPath = path30.join(nodeModulesPath, pkg, "package.json");
|
|
12122
|
+
if (!fs28.existsSync(pkgPath)) continue;
|
|
12123
|
+
const pkgJson = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
|
|
12124
|
+
const templatePath = path30.join(nodeModulesPath, pkg, "template.json");
|
|
11661
12125
|
let nodeCount = 0, edgeCount = 0;
|
|
11662
|
-
if (
|
|
11663
|
-
const tmpl = JSON.parse(
|
|
12126
|
+
if (fs28.existsSync(templatePath)) {
|
|
12127
|
+
const tmpl = JSON.parse(fs28.readFileSync(templatePath, "utf-8"));
|
|
11664
12128
|
nodeCount = tmpl.nodes?.length || 0;
|
|
11665
12129
|
edgeCount = tmpl.edges?.length || 0;
|
|
11666
12130
|
}
|
|
@@ -11745,8 +12209,8 @@ async function tryInstallFromNpm(templateId) {
|
|
|
11745
12209
|
const name = templateId.replace(/^graph:/, "");
|
|
11746
12210
|
try {
|
|
11747
12211
|
const root = getProjectRoot();
|
|
11748
|
-
const pkgPath =
|
|
11749
|
-
if (
|
|
12212
|
+
const pkgPath = path30.join(root, "node_modules", "@kuma-templates", name, "template.json");
|
|
12213
|
+
if (fs28.existsSync(pkgPath)) {
|
|
11750
12214
|
return await loadNpmTemplate(pkgPath, name);
|
|
11751
12215
|
}
|
|
11752
12216
|
} catch {
|
|
@@ -11754,14 +12218,14 @@ async function tryInstallFromNpm(templateId) {
|
|
|
11754
12218
|
try {
|
|
11755
12219
|
const npmPackage = `@kuma-templates/${name}`;
|
|
11756
12220
|
const root = getProjectRoot();
|
|
11757
|
-
|
|
12221
|
+
execSync12(`npm install --no-save ${npmPackage}`, {
|
|
11758
12222
|
cwd: root,
|
|
11759
12223
|
encoding: "utf-8",
|
|
11760
12224
|
timeout: 3e4,
|
|
11761
12225
|
stdio: "pipe"
|
|
11762
12226
|
});
|
|
11763
|
-
const templatePath =
|
|
11764
|
-
if (
|
|
12227
|
+
const templatePath = path30.join(root, "node_modules", "@kuma-templates", name, "template.json");
|
|
12228
|
+
if (fs28.existsSync(templatePath)) {
|
|
11765
12229
|
return await loadNpmTemplate(templatePath, name);
|
|
11766
12230
|
}
|
|
11767
12231
|
return [
|
|
@@ -11779,7 +12243,7 @@ async function tryInstallFromNpm(templateId) {
|
|
|
11779
12243
|
}
|
|
11780
12244
|
}
|
|
11781
12245
|
async function loadNpmTemplate(templatePath, name) {
|
|
11782
|
-
const raw =
|
|
12246
|
+
const raw = fs28.readFileSync(templatePath, "utf-8");
|
|
11783
12247
|
const template = JSON.parse(raw);
|
|
11784
12248
|
const db = await getDb();
|
|
11785
12249
|
let nodeCount = 0;
|
|
@@ -12166,8 +12630,12 @@ async function handleCore(action, params) {
|
|
|
12166
12630
|
return await handleBatchFileWriter(params);
|
|
12167
12631
|
case "lsp":
|
|
12168
12632
|
return await handleLspQuery({ ...params, action: params.lspAction });
|
|
12633
|
+
case "find":
|
|
12634
|
+
return await handleKumaFind(params);
|
|
12635
|
+
case "stats":
|
|
12636
|
+
return await handleKumaStats(params);
|
|
12169
12637
|
default:
|
|
12170
|
-
return `\u26A0\uFE0F Unknown action "${action}" for kuma_core. Use: grep, read, edit, batch, lsp`;
|
|
12638
|
+
return `\u26A0\uFE0F Unknown action "${action}" for kuma_core. Use: grep, read, edit, batch, lsp, find, stats`;
|
|
12171
12639
|
}
|
|
12172
12640
|
}
|
|
12173
12641
|
async function handleVerify(action, params) {
|
|
@@ -12551,9 +13019,9 @@ function registerAllTools(server) {
|
|
|
12551
13019
|
});
|
|
12552
13020
|
server.tool(
|
|
12553
13021
|
"kuma_core",
|
|
12554
|
-
"Core coding tools: grep (search), read (file), edit (search-and-replace), batch (create files), lsp (rename/reference).",
|
|
13022
|
+
"Core coding tools: grep (search code), read (open file), edit (search-and-replace), batch (create files), lsp (rename/reference), find (files by name/pattern), stats (file/dir/project statistics).",
|
|
12555
13023
|
{
|
|
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"),
|
|
13024
|
+
action: z.enum(["grep", "read", "edit", "batch", "lsp", "find", "stats"]).describe("Action: grep=search code, read=open file, edit=edit with safety, batch=create files, lsp=semantic analysis, find=files by name, stats=file/dir/project stats"),
|
|
12557
13025
|
// grep params
|
|
12558
13026
|
query: z.string().optional().describe("Regex pattern for grep action"),
|
|
12559
13027
|
queries: z.array(z.string()).optional().describe("Multiple regex patterns (OR'd together, single pass)"),
|
|
@@ -12571,13 +13039,14 @@ function registerAllTools(server) {
|
|
|
12571
13039
|
endLine: z.number().min(1).optional().describe("End line (1-indexed) for read"),
|
|
12572
13040
|
chunkStrategy: z.enum(["full", "smart", "outline"]).optional().default("smart").describe("Read strategy"),
|
|
12573
13041
|
readOutputMode: z.enum(["rich", "raw"]).optional().default("rich").describe("Output format for read: rich=line numbers, raw=content only"),
|
|
12574
|
-
// edit params
|
|
13042
|
+
// edit params — BATCH SUPPORTED: pass multiple edits[] in 1 call (faster than calling edit 5x)
|
|
12575
13043
|
edits: z.array(z.object({
|
|
12576
13044
|
searchBlock: z.string().min(1).describe("Code to replace"),
|
|
12577
13045
|
replaceBlock: z.string().describe("Replacement code"),
|
|
12578
13046
|
allowMultiple: z.boolean().optional().default(false).describe("Allow multiple replacements"),
|
|
12579
13047
|
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)"),
|
|
13048
|
+
})).min(1).max(10).optional().describe("Array of edits (for edit action) \u2014 batch multiple edits in 1 call for speed"),
|
|
13049
|
+
safe: z.boolean().optional().default(true).describe("Safe mode: true=create backup + safety checks (default), false=skip backup for speed (40x faster, no rollback)"),
|
|
12581
13050
|
dryRun: z.boolean().optional().default(false).describe("Preview without writing"),
|
|
12582
13051
|
version: z.union([z.number().min(1), z.literal("list")]).optional().describe("Backup version for rollback"),
|
|
12583
13052
|
scope: z.enum(["file", "dir", "edit-id", "commit"]).optional().describe("Rollback scope"),
|
|
@@ -12588,6 +13057,12 @@ function registerAllTools(server) {
|
|
|
12588
13057
|
content: z.string().describe("File content"),
|
|
12589
13058
|
instructions: z.string().min(1).describe("Reason for creating")
|
|
12590
13059
|
})).min(1).max(15).optional().describe("Array of files (for batch action)"),
|
|
13060
|
+
// find params
|
|
13061
|
+
name: z.string().optional().describe("File name glob pattern for find action (e.g. '*seed*', '*.tsx')"),
|
|
13062
|
+
path: z.string().optional().describe("Path glob pattern for find action (e.g. '**/utils/**', '*/seed*')"),
|
|
13063
|
+
type: z.enum(["file", "dir", "both"]).optional().default("file").describe("Type filter for find: file=files only, dir=dirs only"),
|
|
13064
|
+
// stats params
|
|
13065
|
+
target: z.enum(["file", "dir", "project"]).optional().default("project").describe("Stats target: file=single file, dir=directory, project=entire project"),
|
|
12591
13066
|
// lsp params
|
|
12592
13067
|
line: z.number().min(0).optional().describe("Line number (0-indexed) for LSP"),
|
|
12593
13068
|
character: z.number().min(0).optional().describe("Character position (0-indexed) for LSP"),
|
|
@@ -12923,23 +13398,23 @@ async function main() {
|
|
|
12923
13398
|
});
|
|
12924
13399
|
const output = formatInitResults(results);
|
|
12925
13400
|
console.log(output);
|
|
12926
|
-
const
|
|
12927
|
-
const
|
|
12928
|
-
const matchaSkills =
|
|
12929
|
-
const matchaAgents =
|
|
13401
|
+
const fs29 = await import("fs");
|
|
13402
|
+
const path31 = await import("path");
|
|
13403
|
+
const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
13404
|
+
const matchaAgents = path31.resolve(
|
|
12930
13405
|
process.cwd(),
|
|
12931
13406
|
".agents/skills/matcha/SKILL.md"
|
|
12932
13407
|
);
|
|
12933
|
-
const matchaRootSkills =
|
|
13408
|
+
const matchaRootSkills = path31.resolve(
|
|
12934
13409
|
process.cwd(),
|
|
12935
13410
|
"skills/matcha/SKILL.md"
|
|
12936
13411
|
);
|
|
12937
|
-
const matchaAgentsMd =
|
|
12938
|
-
const matchaWindsurfRules =
|
|
13412
|
+
const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
|
|
13413
|
+
const matchaWindsurfRules = path31.resolve(
|
|
12939
13414
|
process.cwd(),
|
|
12940
13415
|
".windsurfrules"
|
|
12941
13416
|
);
|
|
12942
|
-
if (
|
|
13417
|
+
if (fs29.existsSync(matchaSkills) || fs29.existsSync(matchaAgents) || fs29.existsSync(matchaRootSkills) || fs29.existsSync(matchaAgentsMd) || fs29.existsSync(matchaWindsurfRules)) {
|
|
12943
13418
|
console.error(
|
|
12944
13419
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
12945
13420
|
);
|
|
@@ -12953,13 +13428,13 @@ async function main() {
|
|
|
12953
13428
|
(async () => {
|
|
12954
13429
|
try {
|
|
12955
13430
|
const { generateInitMdContent } = await import("./init-INY6XOQT.js");
|
|
12956
|
-
const
|
|
12957
|
-
const
|
|
12958
|
-
const initMdPath =
|
|
12959
|
-
if (!
|
|
12960
|
-
const kumaDir =
|
|
12961
|
-
if (!
|
|
12962
|
-
|
|
13431
|
+
const fs29 = await import("fs");
|
|
13432
|
+
const path31 = await import("path");
|
|
13433
|
+
const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
|
|
13434
|
+
if (!fs29.existsSync(initMdPath)) {
|
|
13435
|
+
const kumaDir = path31.dirname(initMdPath);
|
|
13436
|
+
if (!fs29.existsSync(kumaDir)) fs29.mkdirSync(kumaDir, { recursive: true });
|
|
13437
|
+
fs29.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
12963
13438
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
12964
13439
|
}
|
|
12965
13440
|
} catch (err) {
|
|
@@ -12970,8 +13445,8 @@ async function main() {
|
|
|
12970
13445
|
try {
|
|
12971
13446
|
const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-HROEABDO.js");
|
|
12972
13447
|
const { generateSkill, getSecondaryFiles } = await import("./skillGenerator-F6YO4H5D.js");
|
|
12973
|
-
const
|
|
12974
|
-
const
|
|
13448
|
+
const fs29 = await import("fs");
|
|
13449
|
+
const path31 = await import("path");
|
|
12975
13450
|
const detection = detectAgent();
|
|
12976
13451
|
if (!detection.primary) {
|
|
12977
13452
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -12979,22 +13454,22 @@ async function main() {
|
|
|
12979
13454
|
}
|
|
12980
13455
|
const agentType = detection.primary;
|
|
12981
13456
|
const skillPath = getSkillPath(agentType);
|
|
12982
|
-
const fullPath =
|
|
12983
|
-
if (
|
|
13457
|
+
const fullPath = path31.resolve(process.cwd(), skillPath);
|
|
13458
|
+
if (fs29.existsSync(fullPath)) {
|
|
12984
13459
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel(agentType)} \u2014 skipping`);
|
|
12985
13460
|
return;
|
|
12986
13461
|
}
|
|
12987
|
-
const dir =
|
|
12988
|
-
if (!
|
|
12989
|
-
|
|
13462
|
+
const dir = path31.dirname(fullPath);
|
|
13463
|
+
if (!fs29.existsSync(dir)) fs29.mkdirSync(dir, { recursive: true });
|
|
13464
|
+
fs29.writeFileSync(fullPath, generateSkill(agentType), "utf-8");
|
|
12990
13465
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel(agentType)}`);
|
|
12991
13466
|
const secondaryFiles = getSecondaryFiles(agentType);
|
|
12992
13467
|
for (const sf of secondaryFiles) {
|
|
12993
|
-
const sfPath =
|
|
12994
|
-
const sfDir =
|
|
12995
|
-
if (!
|
|
12996
|
-
if (!
|
|
12997
|
-
|
|
13468
|
+
const sfPath = path31.resolve(process.cwd(), sf.path);
|
|
13469
|
+
const sfDir = path31.dirname(sfPath);
|
|
13470
|
+
if (!fs29.existsSync(sfPath)) {
|
|
13471
|
+
if (!fs29.existsSync(sfDir)) fs29.mkdirSync(sfDir, { recursive: true });
|
|
13472
|
+
fs29.writeFileSync(sfPath, sf.content, "utf-8");
|
|
12998
13473
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
12999
13474
|
}
|
|
13000
13475
|
}
|