@plumpslabs/kuma 2.1.3 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2BFTJMBP.js +907 -0
- package/dist/index.js +865 -1255
- package/dist/init-AMRMKI4X.js +14 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALL_CONFIG_TYPES,
|
|
4
|
+
ensureBackupDir,
|
|
5
|
+
formatInitResults,
|
|
6
|
+
getBackupPath,
|
|
7
|
+
getProjectRoot,
|
|
8
|
+
runInit,
|
|
9
|
+
validateFileExtension,
|
|
10
|
+
validateFilePath
|
|
11
|
+
} from "./chunk-2BFTJMBP.js";
|
|
2
12
|
|
|
3
13
|
// src/index.ts
|
|
4
14
|
import { readFileSync } from "fs";
|
|
@@ -11,157 +21,8 @@ import { z } from "zod";
|
|
|
11
21
|
|
|
12
22
|
// src/tools/smartGrep.ts
|
|
13
23
|
import fg from "fast-glob";
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
// src/utils/pathValidator.ts
|
|
18
|
-
import path from "path";
|
|
19
|
-
import fs from "fs";
|
|
20
|
-
function validateFilePath(filePath, projectRoot) {
|
|
21
|
-
try {
|
|
22
|
-
const root = projectRoot ?? getProjectRoot();
|
|
23
|
-
const resolvedRoot = path.resolve(root);
|
|
24
|
-
const normalizedRoot = path.normalize(resolvedRoot).toLowerCase();
|
|
25
|
-
let resolvedPath;
|
|
26
|
-
if (!path.isAbsolute(filePath)) {
|
|
27
|
-
const cwdPath = path.resolve(process.cwd(), filePath);
|
|
28
|
-
const normalizedCwdPath = path.normalize(cwdPath).toLowerCase();
|
|
29
|
-
if (fs.existsSync(cwdPath) && normalizedCwdPath.startsWith(normalizedRoot)) {
|
|
30
|
-
resolvedPath = cwdPath;
|
|
31
|
-
} else {
|
|
32
|
-
resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
36
|
-
}
|
|
37
|
-
const normalizedPath = path.normalize(resolvedPath).toLowerCase();
|
|
38
|
-
if (!normalizedPath.startsWith(normalizedRoot)) {
|
|
39
|
-
return {
|
|
40
|
-
valid: false,
|
|
41
|
-
error: new Error(
|
|
42
|
-
`PATH_TRAVERSAL: Access denied. Path "${filePath}" resolves outside project root "${resolvedRoot}".`
|
|
43
|
-
)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (normalizedPath.includes("..")) {
|
|
47
|
-
return {
|
|
48
|
-
valid: false,
|
|
49
|
-
error: new Error(
|
|
50
|
-
`PATH_TRAVERSAL: Path traversal detected in "${filePath}". Relative paths with ".." are not allowed.`
|
|
51
|
-
)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const blockedPatterns = [
|
|
55
|
-
"/etc/",
|
|
56
|
-
"/sys/",
|
|
57
|
-
"/proc/",
|
|
58
|
-
"/dev/",
|
|
59
|
-
"/boot/",
|
|
60
|
-
"/usr/",
|
|
61
|
-
"C:\\Windows",
|
|
62
|
-
"C:\\Program Files",
|
|
63
|
-
"C:\\Program Files (x86)",
|
|
64
|
-
"C:\\System32",
|
|
65
|
-
"~/.ssh",
|
|
66
|
-
"/root/"
|
|
67
|
-
];
|
|
68
|
-
for (const pattern of blockedPatterns) {
|
|
69
|
-
if (normalizedPath.includes(pattern.toLowerCase())) {
|
|
70
|
-
return {
|
|
71
|
-
valid: false,
|
|
72
|
-
error: new Error(
|
|
73
|
-
`PATH_TRAVERSAL: Access to system directory "${pattern}" is blocked.`
|
|
74
|
-
)
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
if (fs.existsSync(resolvedPath)) {
|
|
80
|
-
const realPath = fs.realpathSync(resolvedPath);
|
|
81
|
-
const normalizedRealPath = path.normalize(realPath).toLowerCase();
|
|
82
|
-
if (!normalizedRealPath.startsWith(normalizedRoot)) {
|
|
83
|
-
return {
|
|
84
|
-
valid: false,
|
|
85
|
-
error: new Error(
|
|
86
|
-
`PATH_TRAVERSAL: Symlink escape detected. Path "${filePath}" resolves to "${realPath}" which is outside project root "${resolvedRoot}".`
|
|
87
|
-
)
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
} catch {
|
|
92
|
-
}
|
|
93
|
-
if (normalizedPath.includes("node_modules")) {
|
|
94
|
-
return {
|
|
95
|
-
valid: true,
|
|
96
|
-
resolvedPath
|
|
97
|
-
// Warning will be handled by caller
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
return { valid: true, resolvedPath };
|
|
101
|
-
} catch (err) {
|
|
102
|
-
return {
|
|
103
|
-
valid: false,
|
|
104
|
-
error: err instanceof Error ? err : new Error(`Path validation error: ${String(err)}`)
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function validateFileExtension(filePath) {
|
|
109
|
-
const allowedExtensions = [
|
|
110
|
-
".ts",
|
|
111
|
-
".tsx",
|
|
112
|
-
".js",
|
|
113
|
-
".jsx",
|
|
114
|
-
".mjs",
|
|
115
|
-
".cjs",
|
|
116
|
-
".json",
|
|
117
|
-
".md",
|
|
118
|
-
".css",
|
|
119
|
-
".html",
|
|
120
|
-
".htm",
|
|
121
|
-
".env",
|
|
122
|
-
".env.example",
|
|
123
|
-
".env.local",
|
|
124
|
-
".yml",
|
|
125
|
-
".yaml",
|
|
126
|
-
".toml",
|
|
127
|
-
".svg",
|
|
128
|
-
".png",
|
|
129
|
-
".jpg",
|
|
130
|
-
".gif",
|
|
131
|
-
".sh",
|
|
132
|
-
".bat",
|
|
133
|
-
".ps1",
|
|
134
|
-
".sql",
|
|
135
|
-
".graphql",
|
|
136
|
-
".gql",
|
|
137
|
-
".vue",
|
|
138
|
-
".svelte",
|
|
139
|
-
".astro",
|
|
140
|
-
".prisma",
|
|
141
|
-
".proto",
|
|
142
|
-
".txt",
|
|
143
|
-
".log"
|
|
144
|
-
];
|
|
145
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
146
|
-
return allowedExtensions.includes(ext);
|
|
147
|
-
}
|
|
148
|
-
function getProjectRoot() {
|
|
149
|
-
return process.env.AGENT_PROJECT_ROOT ?? process.cwd();
|
|
150
|
-
}
|
|
151
|
-
function getBackupPath(filePath, timestamp) {
|
|
152
|
-
const ts = timestamp ?? Date.now();
|
|
153
|
-
const root = getProjectRoot();
|
|
154
|
-
const relativePath = path.relative(root, filePath);
|
|
155
|
-
return path.join(root, ".agent-backups", String(ts), relativePath);
|
|
156
|
-
}
|
|
157
|
-
function ensureBackupDir() {
|
|
158
|
-
const root = getProjectRoot();
|
|
159
|
-
const backupDir = path.join(root, ".agent-backups");
|
|
160
|
-
if (!fs.existsSync(backupDir)) {
|
|
161
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
162
|
-
}
|
|
163
|
-
return backupDir;
|
|
164
|
-
}
|
|
24
|
+
import fs2 from "fs";
|
|
25
|
+
import path2 from "path";
|
|
165
26
|
|
|
166
27
|
// src/utils/tokenCounter.ts
|
|
167
28
|
function estimateTokens(text) {
|
|
@@ -186,33 +47,33 @@ function limitLines(text, maxLines) {
|
|
|
186
47
|
}
|
|
187
48
|
|
|
188
49
|
// src/engine/sessionMemory.ts
|
|
189
|
-
import
|
|
190
|
-
import
|
|
50
|
+
import fs from "fs";
|
|
51
|
+
import path from "path";
|
|
191
52
|
var SessionMemory = class {
|
|
192
53
|
state;
|
|
193
54
|
initialized = false;
|
|
194
55
|
init(config) {
|
|
195
|
-
const kumaDir =
|
|
196
|
-
const sessionFile =
|
|
197
|
-
const oldFile =
|
|
198
|
-
if (
|
|
56
|
+
const kumaDir = path.join(config.projectRoot, ".kuma");
|
|
57
|
+
const sessionFile = path.join(kumaDir, "memory.json");
|
|
58
|
+
const oldFile = path.join(kumaDir, ".kuma-memory.json");
|
|
59
|
+
if (fs.existsSync(oldFile) && !fs.existsSync(sessionFile)) {
|
|
199
60
|
try {
|
|
200
|
-
|
|
61
|
+
fs.renameSync(oldFile, sessionFile);
|
|
201
62
|
console.error("[SessionMemory] Migrated .kuma-memory.json \u2192 memory.json");
|
|
202
63
|
} catch {
|
|
203
64
|
}
|
|
204
65
|
}
|
|
205
|
-
const oldSessionFile =
|
|
206
|
-
if (
|
|
66
|
+
const oldSessionFile = path.join(kumaDir, "session.json");
|
|
67
|
+
if (fs.existsSync(oldSessionFile) && !fs.existsSync(sessionFile)) {
|
|
207
68
|
try {
|
|
208
|
-
|
|
69
|
+
fs.renameSync(oldSessionFile, sessionFile);
|
|
209
70
|
console.error("[SessionMemory] Migrated session.json \u2192 memory.json");
|
|
210
71
|
} catch {
|
|
211
72
|
}
|
|
212
73
|
}
|
|
213
|
-
if (
|
|
74
|
+
if (fs.existsSync(sessionFile)) {
|
|
214
75
|
try {
|
|
215
|
-
const raw =
|
|
76
|
+
const raw = fs.readFileSync(sessionFile, "utf-8");
|
|
216
77
|
const parsed = JSON.parse(raw);
|
|
217
78
|
this.state = {
|
|
218
79
|
projectRoot: parsed.projectRoot || config.projectRoot,
|
|
@@ -250,23 +111,23 @@ var SessionMemory = class {
|
|
|
250
111
|
console.error(`[SessionMemory] Initialized at ${new Date(config.startTime).toISOString()}`);
|
|
251
112
|
}
|
|
252
113
|
memoriesDir() {
|
|
253
|
-
return
|
|
114
|
+
return path.join(this.state.projectRoot, ".kuma", "memories");
|
|
254
115
|
}
|
|
255
116
|
ensureMemoriesDir() {
|
|
256
117
|
const dir = this.memoriesDir();
|
|
257
|
-
if (!
|
|
258
|
-
|
|
118
|
+
if (!fs.existsSync(dir)) {
|
|
119
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
259
120
|
}
|
|
260
121
|
}
|
|
261
122
|
memoryFilePath(topic) {
|
|
262
|
-
return
|
|
123
|
+
return path.join(this.memoriesDir(), `${topic}.md`);
|
|
263
124
|
}
|
|
264
125
|
getMemoryContent(topic) {
|
|
265
126
|
this.ensureInit();
|
|
266
127
|
this.ensureMemoriesDir();
|
|
267
128
|
const filePath = this.memoryFilePath(topic);
|
|
268
|
-
if (
|
|
269
|
-
return
|
|
129
|
+
if (fs.existsSync(filePath)) {
|
|
130
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
270
131
|
}
|
|
271
132
|
return this.generateMemory(topic);
|
|
272
133
|
}
|
|
@@ -277,7 +138,7 @@ var SessionMemory = class {
|
|
|
277
138
|
}
|
|
278
139
|
writeMemoryFile(topic, content) {
|
|
279
140
|
const filePath = this.memoryFilePath(topic);
|
|
280
|
-
|
|
141
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
281
142
|
}
|
|
282
143
|
generateMemory(topic) {
|
|
283
144
|
switch (topic) {
|
|
@@ -376,9 +237,9 @@ No unresolved issues.`;
|
|
|
376
237
|
save() {
|
|
377
238
|
if (!this.initialized || !this.state) return;
|
|
378
239
|
try {
|
|
379
|
-
const kumaDir =
|
|
380
|
-
if (!
|
|
381
|
-
|
|
240
|
+
const kumaDir = path.join(this.state.projectRoot, ".kuma");
|
|
241
|
+
if (!fs.existsSync(kumaDir)) {
|
|
242
|
+
fs.mkdirSync(kumaDir, { recursive: true });
|
|
382
243
|
}
|
|
383
244
|
const serialized = {
|
|
384
245
|
projectRoot: this.state.projectRoot,
|
|
@@ -392,7 +253,7 @@ No unresolved issues.`;
|
|
|
392
253
|
toolCalls: this.state.toolCalls,
|
|
393
254
|
conventions: this.state.conventions
|
|
394
255
|
};
|
|
395
|
-
|
|
256
|
+
fs.writeFileSync(path.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
|
|
396
257
|
} catch (err) {
|
|
397
258
|
console.error(`[SessionMemory] Failed to save session: ${err}`);
|
|
398
259
|
}
|
|
@@ -507,6 +368,68 @@ No unresolved issues.`;
|
|
|
507
368
|
// ============================================================
|
|
508
369
|
// KEYWORD SEARCH — Search session memory content
|
|
509
370
|
// ============================================================
|
|
371
|
+
/**
|
|
372
|
+
* Load previous session data into current session.
|
|
373
|
+
* Called by kuma_init() to hydrate conventions, failures, and modified files
|
|
374
|
+
* from a prior session stored in memory.json.
|
|
375
|
+
*/
|
|
376
|
+
loadSession() {
|
|
377
|
+
this.ensureInit();
|
|
378
|
+
const kumaDir = path.join(this.state.projectRoot, ".kuma");
|
|
379
|
+
const sessionFile = path.join(kumaDir, "memory.json");
|
|
380
|
+
if (!fs.existsSync(sessionFile)) {
|
|
381
|
+
return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const raw = fs.readFileSync(sessionFile, "utf-8");
|
|
385
|
+
const parsed = JSON.parse(raw);
|
|
386
|
+
if (!this.state.conventions && parsed.conventions) {
|
|
387
|
+
this.state.conventions = parsed.conventions;
|
|
388
|
+
}
|
|
389
|
+
const prevFailed = parsed.failedFiles;
|
|
390
|
+
if (prevFailed) {
|
|
391
|
+
for (const [task, failures] of prevFailed) {
|
|
392
|
+
for (const f of failures) {
|
|
393
|
+
if (!f.resolved) {
|
|
394
|
+
const existing = this.state.failedFiles.get(task);
|
|
395
|
+
if (!existing?.some((ef) => ef.error === f.error)) {
|
|
396
|
+
this.state.failedFiles.set(task, [...existing || [], f]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const prevModified = parsed.modifiedFiles;
|
|
403
|
+
if (prevModified) {
|
|
404
|
+
for (const [filePath, mod] of prevModified) {
|
|
405
|
+
if (!this.state.modifiedFiles.has(filePath)) {
|
|
406
|
+
this.state.modifiedFiles.set(filePath, {
|
|
407
|
+
filePath,
|
|
408
|
+
modifiedAt: mod.modifiedAt,
|
|
409
|
+
status: mod.status
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const prevSearch = parsed.searchResults;
|
|
415
|
+
if (prevSearch) {
|
|
416
|
+
for (const [query, files] of prevSearch) {
|
|
417
|
+
if (!this.state.searchResults.has(query)) {
|
|
418
|
+
this.state.searchResults.set(query, files);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const toolCallCount = parsed.toolCalls?.length ?? 0;
|
|
423
|
+
this.save();
|
|
424
|
+
return {
|
|
425
|
+
hasPrevSession: true,
|
|
426
|
+
toolCallCount,
|
|
427
|
+
hasConventions: !!this.state.conventions
|
|
428
|
+
};
|
|
429
|
+
} catch {
|
|
430
|
+
return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
510
433
|
/**
|
|
511
434
|
* Search through tool call history, memory files, search results,
|
|
512
435
|
* errors, and file modifications for a keyword.
|
|
@@ -562,14 +485,14 @@ No unresolved issues.`;
|
|
|
562
485
|
}
|
|
563
486
|
}
|
|
564
487
|
const memoriesDir = this.memoriesDir();
|
|
565
|
-
if (
|
|
488
|
+
if (fs.existsSync(memoriesDir)) {
|
|
566
489
|
try {
|
|
567
|
-
const files =
|
|
490
|
+
const files = fs.readdirSync(memoriesDir);
|
|
568
491
|
for (const file of files) {
|
|
569
492
|
if (!file.endsWith(".md")) continue;
|
|
570
|
-
const filePath =
|
|
493
|
+
const filePath = path.join(memoriesDir, file);
|
|
571
494
|
try {
|
|
572
|
-
const content =
|
|
495
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
573
496
|
const lines = content.split("\n");
|
|
574
497
|
for (let i = 0; i < lines.length; i++) {
|
|
575
498
|
if (lines[i].toLowerCase().includes(q)) {
|
|
@@ -625,11 +548,11 @@ No unresolved issues.`;
|
|
|
625
548
|
unresolvedFailures.push({ task: f.task, error });
|
|
626
549
|
}
|
|
627
550
|
}
|
|
628
|
-
const hasMemories =
|
|
551
|
+
const hasMemories = fs.existsSync(this.memoriesDir());
|
|
629
552
|
let availableMemories = [];
|
|
630
553
|
if (hasMemories) {
|
|
631
554
|
try {
|
|
632
|
-
availableMemories =
|
|
555
|
+
availableMemories = fs.readdirSync(this.memoriesDir()).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
|
|
633
556
|
} catch {
|
|
634
557
|
}
|
|
635
558
|
}
|
|
@@ -771,7 +694,7 @@ async function handleSmartGrep(params) {
|
|
|
771
694
|
}
|
|
772
695
|
const projectRoot = getProjectRoot();
|
|
773
696
|
try {
|
|
774
|
-
const searchPattern = targetFolder ?
|
|
697
|
+
const searchPattern = targetFolder ? path2.join(targetFolder, "**/*").replace(/\\/g, "/") : "**/*";
|
|
775
698
|
let entries = await fg(searchPattern, {
|
|
776
699
|
cwd: projectRoot,
|
|
777
700
|
ignore: IGNORE_PATTERNS,
|
|
@@ -787,7 +710,7 @@ async function handleSmartGrep(params) {
|
|
|
787
710
|
(e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`
|
|
788
711
|
);
|
|
789
712
|
entries = entries.filter((entry) => {
|
|
790
|
-
const ext =
|
|
713
|
+
const ext = path2.extname(entry).toLowerCase();
|
|
791
714
|
return normalizedExts.includes(ext);
|
|
792
715
|
});
|
|
793
716
|
}
|
|
@@ -803,11 +726,11 @@ async function handleSmartGrep(params) {
|
|
|
803
726
|
if (results.length >= maxResults) break;
|
|
804
727
|
filesScanned++;
|
|
805
728
|
try {
|
|
806
|
-
const fullPath =
|
|
807
|
-
const stat =
|
|
729
|
+
const fullPath = path2.join(projectRoot, entry);
|
|
730
|
+
const stat = fs2.statSync(fullPath);
|
|
808
731
|
if (stat.size > 5e5) continue;
|
|
809
732
|
if (isBinaryFile(fullPath)) continue;
|
|
810
|
-
const content =
|
|
733
|
+
const content = fs2.readFileSync(fullPath, "utf-8");
|
|
811
734
|
const lines = content.split("\n");
|
|
812
735
|
for (let i = 0; i < lines.length; i++) {
|
|
813
736
|
if (results.length >= maxResults) break;
|
|
@@ -876,9 +799,9 @@ ${r.content.split("\n").map((l) => ` ${l}`).join("\n")}`;
|
|
|
876
799
|
function isBinaryFile(filePath) {
|
|
877
800
|
try {
|
|
878
801
|
const buffer = Buffer.alloc(512);
|
|
879
|
-
const fd =
|
|
880
|
-
const bytesRead =
|
|
881
|
-
|
|
802
|
+
const fd = fs2.openSync(filePath, "r");
|
|
803
|
+
const bytesRead = fs2.readSync(fd, buffer, 0, 512, 0);
|
|
804
|
+
fs2.closeSync(fd);
|
|
882
805
|
for (let i = 0; i < bytesRead; i++) {
|
|
883
806
|
if (buffer[i] === 0) return true;
|
|
884
807
|
}
|
|
@@ -889,8 +812,8 @@ function isBinaryFile(filePath) {
|
|
|
889
812
|
}
|
|
890
813
|
|
|
891
814
|
// src/tools/smartFilePicker.ts
|
|
892
|
-
import
|
|
893
|
-
import
|
|
815
|
+
import fs3 from "fs";
|
|
816
|
+
import path3 from "path";
|
|
894
817
|
var MAX_FILE_SIZE = 1e6;
|
|
895
818
|
var CHUNK_THRESHOLD = 300;
|
|
896
819
|
async function handleSmartFilePicker(params) {
|
|
@@ -900,23 +823,23 @@ async function handleSmartFilePicker(params) {
|
|
|
900
823
|
return "Error: " + validation.error.message;
|
|
901
824
|
}
|
|
902
825
|
const resolvedPath = validation.resolvedPath;
|
|
903
|
-
if (!
|
|
826
|
+
if (!fs3.existsSync(resolvedPath)) {
|
|
904
827
|
const projectRoot = getProjectRoot();
|
|
905
828
|
let suggestion;
|
|
906
|
-
if (!
|
|
907
|
-
const cwdPath =
|
|
829
|
+
if (!path3.isAbsolute(filePath)) {
|
|
830
|
+
const cwdPath = path3.resolve(process.cwd(), filePath);
|
|
908
831
|
suggestion = "Path resolved to: " + resolvedPath + "\nCWD: " + process.cwd() + "\nProject root: " + projectRoot + "\nHint: Try path relative to project root, or relative to CWD (" + cwdPath + ")\nTry smart_grep first to locate the correct file.";
|
|
909
832
|
} else {
|
|
910
833
|
suggestion = "File does not exist. Try smart_grep to locate it.";
|
|
911
834
|
}
|
|
912
835
|
return 'Error: File not found: "' + filePath + '".\n' + suggestion;
|
|
913
836
|
}
|
|
914
|
-
const stat =
|
|
837
|
+
const stat = fs3.statSync(resolvedPath);
|
|
915
838
|
if (stat.size > MAX_FILE_SIZE) {
|
|
916
839
|
return "Error: File too large (" + (stat.size / 1024 / 1024).toFixed(1) + "MB). Max 1MB.\nUse smart_grep to find specific content instead.";
|
|
917
840
|
}
|
|
918
841
|
try {
|
|
919
|
-
const content =
|
|
842
|
+
const content = fs3.readFileSync(resolvedPath, "utf-8");
|
|
920
843
|
const lines = content.split("\n");
|
|
921
844
|
const totalLines = lines.length;
|
|
922
845
|
sessionMemory.recordToolCall("smart_file_picker", {
|
|
@@ -1066,8 +989,8 @@ function extractKeyDeclarations(lines) {
|
|
|
1066
989
|
}
|
|
1067
990
|
|
|
1068
991
|
// src/tools/preciseDiffEditor.ts
|
|
1069
|
-
import
|
|
1070
|
-
import
|
|
992
|
+
import fs4 from "fs";
|
|
993
|
+
import path4 from "path";
|
|
1071
994
|
|
|
1072
995
|
// src/utils/errorHandler.ts
|
|
1073
996
|
var CircuitBreakerStore = class {
|
|
@@ -1142,12 +1065,12 @@ async function handlePreciseDiffEditor(params) {
|
|
|
1142
1065
|
|
|
1143
1066
|
Try reading the file first with smart_file_picker to verify current content.`;
|
|
1144
1067
|
}
|
|
1145
|
-
if (!
|
|
1068
|
+
if (!fs4.existsSync(resolvedPath)) {
|
|
1146
1069
|
return `Error: File not found: "${filePath}".
|
|
1147
1070
|
Use batch_file_writer to create a new file.`;
|
|
1148
1071
|
}
|
|
1149
1072
|
try {
|
|
1150
|
-
const originalContent =
|
|
1073
|
+
const originalContent = fs4.readFileSync(resolvedPath, "utf-8");
|
|
1151
1074
|
let currentContent = originalContent;
|
|
1152
1075
|
const results = [];
|
|
1153
1076
|
for (let i = 0; i < edits.length; i++) {
|
|
@@ -1155,7 +1078,7 @@ Use batch_file_writer to create a new file.`;
|
|
|
1155
1078
|
const result = applyEdit(currentContent, edit, resolvedPath, i, dryRun);
|
|
1156
1079
|
if (result.success) {
|
|
1157
1080
|
if (!dryRun) {
|
|
1158
|
-
|
|
1081
|
+
fs4.writeFileSync(resolvedPath, result.details, "utf-8");
|
|
1159
1082
|
sessionMemory.recordToolCall("precise_diff_editor", {
|
|
1160
1083
|
filePath,
|
|
1161
1084
|
editIndex: i,
|
|
@@ -1345,9 +1268,9 @@ function findNearestLine(content, search) {
|
|
|
1345
1268
|
function createBackup(filePath) {
|
|
1346
1269
|
ensureBackupDir();
|
|
1347
1270
|
const backupPath = getBackupPath(filePath);
|
|
1348
|
-
const backupDir =
|
|
1349
|
-
|
|
1350
|
-
|
|
1271
|
+
const backupDir = path4.dirname(backupPath);
|
|
1272
|
+
fs4.mkdirSync(backupDir, { recursive: true });
|
|
1273
|
+
fs4.copyFileSync(filePath, backupPath);
|
|
1351
1274
|
return backupPath;
|
|
1352
1275
|
}
|
|
1353
1276
|
function formatDryRunResult(results, filePath, originalContent) {
|
|
@@ -1433,7 +1356,7 @@ function formatDiffResult(results, filePath) {
|
|
|
1433
1356
|
if (r.success) {
|
|
1434
1357
|
lines.push(`[${i + 1}] \u2705 Matched: ${r.matched}x, Replaced: ${r.replaced}x`);
|
|
1435
1358
|
if (r.backupPath) {
|
|
1436
|
-
const relativePath =
|
|
1359
|
+
const relativePath = path4.relative(getProjectRoot(), r.backupPath);
|
|
1437
1360
|
lines.push(` Backup: ${relativePath}`);
|
|
1438
1361
|
}
|
|
1439
1362
|
} else {
|
|
@@ -1468,15 +1391,15 @@ async function handleRollbackEdit(params) {
|
|
|
1468
1391
|
}
|
|
1469
1392
|
const resolvedPath = validation.resolvedPath;
|
|
1470
1393
|
const root = getProjectRoot();
|
|
1471
|
-
const relativePath =
|
|
1472
|
-
const backupRoot =
|
|
1473
|
-
if (!
|
|
1394
|
+
const relativePath = path4.relative(root, resolvedPath);
|
|
1395
|
+
const backupRoot = path4.join(root, ".agent-backups");
|
|
1396
|
+
if (!fs4.existsSync(backupRoot)) {
|
|
1474
1397
|
return `Error: No backup folder (.agent-backups) found in project root.`;
|
|
1475
1398
|
}
|
|
1476
1399
|
try {
|
|
1477
|
-
const dirs =
|
|
1478
|
-
const fullPath =
|
|
1479
|
-
return
|
|
1400
|
+
const dirs = fs4.readdirSync(backupRoot).filter((name) => {
|
|
1401
|
+
const fullPath = path4.join(backupRoot, name);
|
|
1402
|
+
return fs4.statSync(fullPath).isDirectory() && /^\d+$/.test(name);
|
|
1480
1403
|
});
|
|
1481
1404
|
if (dirs.length === 0) {
|
|
1482
1405
|
return `Error: No valid backup found in .agent-backups folder.`;
|
|
@@ -1484,8 +1407,8 @@ async function handleRollbackEdit(params) {
|
|
|
1484
1407
|
dirs.sort((a, b) => Number(b) - Number(a));
|
|
1485
1408
|
const backupVersions = [];
|
|
1486
1409
|
for (const dir of dirs) {
|
|
1487
|
-
const potentialBackupPath =
|
|
1488
|
-
if (
|
|
1410
|
+
const potentialBackupPath = path4.join(backupRoot, dir, relativePath);
|
|
1411
|
+
if (fs4.existsSync(potentialBackupPath)) {
|
|
1489
1412
|
backupVersions.push({ dir, backupPath: potentialBackupPath });
|
|
1490
1413
|
}
|
|
1491
1414
|
}
|
|
@@ -1512,14 +1435,14 @@ async function handleRollbackEdit(params) {
|
|
|
1512
1435
|
selectedIndex = version - 1;
|
|
1513
1436
|
}
|
|
1514
1437
|
const selected = backupVersions[selectedIndex];
|
|
1515
|
-
|
|
1438
|
+
fs4.copyFileSync(selected.backupPath, resolvedPath);
|
|
1516
1439
|
sessionMemory.recordToolCall("rollback_last_edit", {
|
|
1517
1440
|
filePath,
|
|
1518
1441
|
backupTimestamp: selected.dir,
|
|
1519
1442
|
version: version ?? 1,
|
|
1520
1443
|
success: true
|
|
1521
1444
|
});
|
|
1522
|
-
const relBackupPath =
|
|
1445
|
+
const relBackupPath = path4.relative(root, selected.backupPath);
|
|
1523
1446
|
return `\u2705 Rollback Successful!
|
|
1524
1447
|
File "${filePath}" restored from backup: "${relBackupPath}".`;
|
|
1525
1448
|
} catch (err) {
|
|
@@ -1528,8 +1451,8 @@ File "${filePath}" restored from backup: "${relBackupPath}".`;
|
|
|
1528
1451
|
}
|
|
1529
1452
|
|
|
1530
1453
|
// src/tools/batchFileWriter.ts
|
|
1531
|
-
import
|
|
1532
|
-
import
|
|
1454
|
+
import fs5 from "fs";
|
|
1455
|
+
import path5 from "path";
|
|
1533
1456
|
async function handleBatchFileWriter(params) {
|
|
1534
1457
|
const { files } = params;
|
|
1535
1458
|
if (files.length > 15) {
|
|
@@ -1556,22 +1479,22 @@ async function handleBatchFileWriter(params) {
|
|
|
1556
1479
|
results.push({
|
|
1557
1480
|
filePath: file.filePath,
|
|
1558
1481
|
success: false,
|
|
1559
|
-
error: `File extension not allowed: "${
|
|
1482
|
+
error: `File extension not allowed: "${path5.extname(file.filePath)}". Allowed extensions: .ts, .js, .tsx, .jsx, .json, .md, .css, .html, .yml, .yaml, .toml, .sh, .env`
|
|
1560
1483
|
});
|
|
1561
1484
|
continue;
|
|
1562
1485
|
}
|
|
1563
|
-
if (
|
|
1486
|
+
if (fs5.existsSync(resolvedPath)) {
|
|
1564
1487
|
ensureBackupDir();
|
|
1565
1488
|
const backupPath = getBackupPath(file.filePath);
|
|
1566
|
-
const backupDir =
|
|
1567
|
-
|
|
1568
|
-
|
|
1489
|
+
const backupDir = path5.dirname(backupPath);
|
|
1490
|
+
fs5.mkdirSync(backupDir, { recursive: true });
|
|
1491
|
+
fs5.copyFileSync(resolvedPath, backupPath);
|
|
1569
1492
|
}
|
|
1570
|
-
const dir =
|
|
1571
|
-
if (!
|
|
1572
|
-
|
|
1493
|
+
const dir = path5.dirname(resolvedPath);
|
|
1494
|
+
if (!fs5.existsSync(dir)) {
|
|
1495
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1573
1496
|
}
|
|
1574
|
-
|
|
1497
|
+
fs5.writeFileSync(resolvedPath, file.content, "utf-8");
|
|
1575
1498
|
sessionMemory.recordToolCall("batch_file_writer", {
|
|
1576
1499
|
filePath: file.filePath,
|
|
1577
1500
|
instructions: file.instructions,
|
|
@@ -1614,12 +1537,12 @@ function formatBatchResult(results, totalRequested) {
|
|
|
1614
1537
|
}
|
|
1615
1538
|
|
|
1616
1539
|
// src/tools/safeTerminalExec.ts
|
|
1617
|
-
import fs8 from "fs";
|
|
1618
|
-
import path8 from "path";
|
|
1619
|
-
|
|
1620
|
-
// src/utils/conventionsDetector.ts
|
|
1621
1540
|
import fs7 from "fs";
|
|
1622
1541
|
import path7 from "path";
|
|
1542
|
+
|
|
1543
|
+
// src/utils/conventionsDetector.ts
|
|
1544
|
+
import fs6 from "fs";
|
|
1545
|
+
import path6 from "path";
|
|
1623
1546
|
var cachedConventions = null;
|
|
1624
1547
|
async function detectConventions(forceRescan = false) {
|
|
1625
1548
|
if (cachedConventions && !forceRescan) {
|
|
@@ -1645,7 +1568,7 @@ async function detectConventions(forceRescan = false) {
|
|
|
1645
1568
|
return conventions;
|
|
1646
1569
|
}
|
|
1647
1570
|
function detectFramework(root) {
|
|
1648
|
-
const pkg = readJsonSafe(
|
|
1571
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1649
1572
|
if (pkg) {
|
|
1650
1573
|
const pkgDeps = pkg.dependencies ?? {};
|
|
1651
1574
|
const pkgDevDeps = pkg.devDependencies ?? {};
|
|
@@ -1690,11 +1613,11 @@ function detectFramework(root) {
|
|
|
1690
1613
|
function scanSubProjectFrameworks(root) {
|
|
1691
1614
|
const frameworks = [];
|
|
1692
1615
|
try {
|
|
1693
|
-
const entries =
|
|
1616
|
+
const entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
1694
1617
|
for (const entry of entries) {
|
|
1695
1618
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1696
|
-
const subDir =
|
|
1697
|
-
const subPkg = readJsonSafe(
|
|
1619
|
+
const subDir = path6.join(root, entry.name);
|
|
1620
|
+
const subPkg = readJsonSafe(path6.join(subDir, "package.json"));
|
|
1698
1621
|
if (!subPkg) continue;
|
|
1699
1622
|
const subDeps = { ...subPkg.dependencies ?? {}, ...subPkg.devDependencies ?? {} };
|
|
1700
1623
|
if (subDeps.next) frameworks.push("Next.js");
|
|
@@ -1713,7 +1636,7 @@ function scanSubProjectFrameworks(root) {
|
|
|
1713
1636
|
return frameworks;
|
|
1714
1637
|
}
|
|
1715
1638
|
function detectProjectType(root) {
|
|
1716
|
-
const pkg = readJsonSafe(
|
|
1639
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1717
1640
|
if (pkg) {
|
|
1718
1641
|
const pkgDeps = pkg.dependencies ?? {};
|
|
1719
1642
|
const pkgDevDeps = pkg.devDependencies ?? {};
|
|
@@ -1737,8 +1660,8 @@ function detectProjectType(root) {
|
|
|
1737
1660
|
}
|
|
1738
1661
|
function detectWorkspaces(root) {
|
|
1739
1662
|
const results = [];
|
|
1740
|
-
const pkg = readJsonSafe(
|
|
1741
|
-
const pnpmWorkspace = readYamlLite(
|
|
1663
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1664
|
+
const pnpmWorkspace = readYamlLite(path6.join(root, "pnpm-workspace.yaml"));
|
|
1742
1665
|
const patterns = /* @__PURE__ */ new Set();
|
|
1743
1666
|
const pkgWorkspaces = pkg?.workspaces;
|
|
1744
1667
|
if (Array.isArray(pkgWorkspaces)) {
|
|
@@ -1758,22 +1681,22 @@ function detectWorkspaces(root) {
|
|
|
1758
1681
|
for (const pattern of patterns) {
|
|
1759
1682
|
const match = pattern.match(/^([^*]+)\/\*$/);
|
|
1760
1683
|
if (!match) continue;
|
|
1761
|
-
const dir =
|
|
1762
|
-
if (!
|
|
1684
|
+
const dir = path6.join(root, match[1]);
|
|
1685
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1763
1686
|
let entries = [];
|
|
1764
1687
|
try {
|
|
1765
|
-
entries =
|
|
1688
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
1766
1689
|
} catch {
|
|
1767
1690
|
continue;
|
|
1768
1691
|
}
|
|
1769
1692
|
for (const entry of entries) {
|
|
1770
1693
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1771
|
-
const pkgPath =
|
|
1694
|
+
const pkgPath = path6.join(dir, entry.name, "package.json");
|
|
1772
1695
|
const subPkg = readJsonSafe(pkgPath);
|
|
1773
1696
|
if (!subPkg) continue;
|
|
1774
|
-
const workspacePath =
|
|
1697
|
+
const workspacePath = path6.join(dir, entry.name);
|
|
1775
1698
|
results.push({
|
|
1776
|
-
path:
|
|
1699
|
+
path: path6.relative(root, workspacePath),
|
|
1777
1700
|
name: subPkg.name ?? entry.name,
|
|
1778
1701
|
framework: detectFramework(workspacePath),
|
|
1779
1702
|
packageManager: detectPackageManagerForDir(workspacePath)
|
|
@@ -1789,8 +1712,8 @@ function detectWorkspaces(root) {
|
|
|
1789
1712
|
}
|
|
1790
1713
|
function readYamlLite(filePath) {
|
|
1791
1714
|
try {
|
|
1792
|
-
if (!
|
|
1793
|
-
const text =
|
|
1715
|
+
if (!fs6.existsSync(filePath)) return null;
|
|
1716
|
+
const text = fs6.readFileSync(filePath, "utf-8");
|
|
1794
1717
|
const packages = [];
|
|
1795
1718
|
let inPackages = false;
|
|
1796
1719
|
for (const rawLine of text.split("\n")) {
|
|
@@ -1811,7 +1734,7 @@ function readYamlLite(filePath) {
|
|
|
1811
1734
|
}
|
|
1812
1735
|
}
|
|
1813
1736
|
function detectTestRunner(root) {
|
|
1814
|
-
const pkg = readJsonSafe(
|
|
1737
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1815
1738
|
if (!pkg) return "unknown";
|
|
1816
1739
|
const pkgDeps2 = pkg.dependencies ?? {};
|
|
1817
1740
|
const pkgDevDeps2 = pkg.devDependencies ?? {};
|
|
@@ -1832,7 +1755,7 @@ function detectTestRunner(root) {
|
|
|
1832
1755
|
return "unknown";
|
|
1833
1756
|
}
|
|
1834
1757
|
function detectStyling(root) {
|
|
1835
|
-
const pkg = readJsonSafe(
|
|
1758
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1836
1759
|
if (!pkg) return "unknown";
|
|
1837
1760
|
const pkgDeps3 = pkg.dependencies ?? {};
|
|
1838
1761
|
const pkgDevDeps3 = pkg.devDependencies ?? {};
|
|
@@ -1845,22 +1768,22 @@ function detectStyling(root) {
|
|
|
1845
1768
|
if (deps.less) return "Less";
|
|
1846
1769
|
if (deps["@vanilla-extract/css"]) return "Vanilla Extract";
|
|
1847
1770
|
if (deps.cssmodules || hasCSSModules(root)) return "CSS Modules";
|
|
1848
|
-
const srcDir =
|
|
1849
|
-
if (
|
|
1771
|
+
const srcDir = path6.join(root, "src");
|
|
1772
|
+
if (fs6.existsSync(srcDir)) {
|
|
1850
1773
|
const cssFiles = findFiles(srcDir, [".css", ".scss", ".less"]);
|
|
1851
1774
|
if (cssFiles.length > 0) return "Plain CSS/SCSS";
|
|
1852
1775
|
}
|
|
1853
1776
|
return "unknown";
|
|
1854
1777
|
}
|
|
1855
1778
|
function detectImportAlias(root) {
|
|
1856
|
-
const tsconfig = readJsonSafe(
|
|
1779
|
+
const tsconfig = readJsonSafe(path6.join(root, "tsconfig.json"));
|
|
1857
1780
|
const tsconfigPaths = tsconfig?.compilerOptions;
|
|
1858
1781
|
if (tsconfigPaths?.paths) {
|
|
1859
1782
|
const paths = tsconfigPaths.paths;
|
|
1860
1783
|
const alias = Object.keys(paths)[0];
|
|
1861
1784
|
if (alias) return alias.replace("/*", "");
|
|
1862
1785
|
}
|
|
1863
|
-
const jsconfig = readJsonSafe(
|
|
1786
|
+
const jsconfig = readJsonSafe(path6.join(root, "jsconfig.json"));
|
|
1864
1787
|
const jsconfigPaths = jsconfig?.compilerOptions;
|
|
1865
1788
|
if (jsconfigPaths?.paths) {
|
|
1866
1789
|
const paths = jsconfigPaths.paths;
|
|
@@ -1871,15 +1794,15 @@ function detectImportAlias(root) {
|
|
|
1871
1794
|
}
|
|
1872
1795
|
function detectLintRules(root) {
|
|
1873
1796
|
const rules = [];
|
|
1874
|
-
if (
|
|
1875
|
-
if (
|
|
1876
|
-
if (
|
|
1877
|
-
if (
|
|
1878
|
-
if (
|
|
1879
|
-
if (
|
|
1880
|
-
if (
|
|
1881
|
-
if (
|
|
1882
|
-
if (
|
|
1797
|
+
if (fs6.existsSync(path6.join(root, ".eslintrc"))) rules.push("ESLint (.eslintrc)");
|
|
1798
|
+
if (fs6.existsSync(path6.join(root, ".eslintrc.js"))) rules.push("ESLint (.eslintrc.js)");
|
|
1799
|
+
if (fs6.existsSync(path6.join(root, ".eslintrc.json"))) rules.push("ESLint (.eslintrc.json)");
|
|
1800
|
+
if (fs6.existsSync(path6.join(root, "eslint.config.js"))) rules.push("ESLint (flat config)");
|
|
1801
|
+
if (fs6.existsSync(path6.join(root, ".prettierrc"))) rules.push("Prettier");
|
|
1802
|
+
if (fs6.existsSync(path6.join(root, ".prettierrc.json"))) rules.push("Prettier");
|
|
1803
|
+
if (fs6.existsSync(path6.join(root, ".prettierrc.js"))) rules.push("Prettier");
|
|
1804
|
+
if (fs6.existsSync(path6.join(root, ".stylelintrc"))) rules.push("StyleLint");
|
|
1805
|
+
if (fs6.existsSync(path6.join(root, ".stylelintrc.json"))) rules.push("StyleLint");
|
|
1883
1806
|
return rules;
|
|
1884
1807
|
}
|
|
1885
1808
|
function detectPackageManager() {
|
|
@@ -1888,26 +1811,26 @@ function detectPackageManager() {
|
|
|
1888
1811
|
}
|
|
1889
1812
|
function detectPackageManagerForDir(dir) {
|
|
1890
1813
|
const root = getProjectRoot();
|
|
1891
|
-
let currentDir =
|
|
1814
|
+
let currentDir = path6.resolve(dir);
|
|
1892
1815
|
while (currentDir.startsWith(root)) {
|
|
1893
|
-
if (
|
|
1894
|
-
if (
|
|
1895
|
-
if (
|
|
1896
|
-
if (
|
|
1816
|
+
if (fs6.existsSync(path6.join(currentDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1817
|
+
if (fs6.existsSync(path6.join(currentDir, "yarn.lock"))) return "yarn";
|
|
1818
|
+
if (fs6.existsSync(path6.join(currentDir, "package-lock.json"))) return "npm";
|
|
1819
|
+
if (fs6.existsSync(path6.join(currentDir, "bun.lockb"))) return "bun";
|
|
1897
1820
|
if (currentDir === root) break;
|
|
1898
|
-
currentDir =
|
|
1821
|
+
currentDir = path6.dirname(currentDir);
|
|
1899
1822
|
}
|
|
1900
1823
|
return "npm";
|
|
1901
1824
|
}
|
|
1902
1825
|
function detectModuleSystem(root) {
|
|
1903
|
-
const pkg = readJsonSafe(
|
|
1826
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1904
1827
|
if (pkg?.type === "module") return "esm";
|
|
1905
|
-
const srcDir =
|
|
1906
|
-
if (
|
|
1828
|
+
const srcDir = path6.join(root, "src");
|
|
1829
|
+
if (fs6.existsSync(srcDir)) {
|
|
1907
1830
|
const tsFiles = findFiles(srcDir, [".ts", ".tsx", ".js", ".jsx", ".mjs"]);
|
|
1908
1831
|
for (const file of tsFiles.slice(0, 20)) {
|
|
1909
1832
|
try {
|
|
1910
|
-
const content =
|
|
1833
|
+
const content = fs6.readFileSync(file, "utf-8");
|
|
1911
1834
|
if (content.includes("import ") || content.includes("export ")) return "esm";
|
|
1912
1835
|
} catch {
|
|
1913
1836
|
continue;
|
|
@@ -1917,9 +1840,9 @@ function detectModuleSystem(root) {
|
|
|
1917
1840
|
return "cjs";
|
|
1918
1841
|
}
|
|
1919
1842
|
function detectLanguage(root) {
|
|
1920
|
-
if (
|
|
1921
|
-
const srcDir =
|
|
1922
|
-
if (
|
|
1843
|
+
if (fs6.existsSync(path6.join(root, "tsconfig.json"))) return "typescript";
|
|
1844
|
+
const srcDir = path6.join(root, "src");
|
|
1845
|
+
if (fs6.existsSync(srcDir)) {
|
|
1923
1846
|
const tsFiles = findFiles(srcDir, [".ts", ".tsx"]);
|
|
1924
1847
|
if (tsFiles.length > 0) return "typescript";
|
|
1925
1848
|
}
|
|
@@ -1927,7 +1850,7 @@ function detectLanguage(root) {
|
|
|
1927
1850
|
}
|
|
1928
1851
|
function detectFeatures(root) {
|
|
1929
1852
|
const features = [];
|
|
1930
|
-
const pkg = readJsonSafe(
|
|
1853
|
+
const pkg = readJsonSafe(path6.join(root, "package.json"));
|
|
1931
1854
|
if (!pkg) return features;
|
|
1932
1855
|
const pkgDeps4 = pkg.dependencies ?? {};
|
|
1933
1856
|
const pkgDevDeps4 = pkg.devDependencies ?? {};
|
|
@@ -1951,25 +1874,25 @@ function detectFeatures(root) {
|
|
|
1951
1874
|
}
|
|
1952
1875
|
function readJsonSafe(filePath) {
|
|
1953
1876
|
try {
|
|
1954
|
-
if (
|
|
1955
|
-
return JSON.parse(
|
|
1877
|
+
if (fs6.existsSync(filePath)) {
|
|
1878
|
+
return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
1956
1879
|
}
|
|
1957
1880
|
} catch {
|
|
1958
1881
|
}
|
|
1959
1882
|
return null;
|
|
1960
1883
|
}
|
|
1961
1884
|
function hasCSSModules(root) {
|
|
1962
|
-
const srcDir =
|
|
1963
|
-
if (!
|
|
1885
|
+
const srcDir = path6.join(root, "src");
|
|
1886
|
+
if (!fs6.existsSync(srcDir)) return false;
|
|
1964
1887
|
const files = findFiles(srcDir, [".module.css", ".module.scss", ".module.less"]);
|
|
1965
1888
|
return files.length > 0;
|
|
1966
1889
|
}
|
|
1967
1890
|
function findFiles(dir, extensions) {
|
|
1968
1891
|
const results = [];
|
|
1969
1892
|
try {
|
|
1970
|
-
const entries =
|
|
1893
|
+
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
1971
1894
|
for (const entry of entries) {
|
|
1972
|
-
const fullPath =
|
|
1895
|
+
const fullPath = path6.join(dir, entry.name);
|
|
1973
1896
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
1974
1897
|
results.push(...findFiles(fullPath, extensions));
|
|
1975
1898
|
} else if (entry.isFile()) {
|
|
@@ -2160,7 +2083,7 @@ async function handleSafeTerminalExec(params) {
|
|
|
2160
2083
|
const workspaces = conventions?.workspaces;
|
|
2161
2084
|
const matched = workspaces?.find((w) => w.name === workspace || w.path === workspace);
|
|
2162
2085
|
if (matched) {
|
|
2163
|
-
workingDir =
|
|
2086
|
+
workingDir = path7.resolve(projectRoot, matched.path);
|
|
2164
2087
|
resolvedFrom = `workspace "${matched.name}" \u2192 ${matched.path}`;
|
|
2165
2088
|
} else {
|
|
2166
2089
|
return `\u26A0\uFE0F Workspace "${workspace}" not found. Run project_conventions first to detect workspaces, or use 'cwd' parameter with a direct path.
|
|
@@ -2168,13 +2091,13 @@ async function handleSafeTerminalExec(params) {
|
|
|
2168
2091
|
Available workspaces: ${workspaces?.map((w) => `"${w.name}" (${w.path})`).join(", ") || "none detected"}`;
|
|
2169
2092
|
}
|
|
2170
2093
|
} else if (inputCwd) {
|
|
2171
|
-
const resolved =
|
|
2172
|
-
const normalizedResolved =
|
|
2173
|
-
const normalizedRoot =
|
|
2094
|
+
const resolved = path7.resolve(projectRoot, inputCwd);
|
|
2095
|
+
const normalizedResolved = path7.normalize(resolved).toLowerCase();
|
|
2096
|
+
const normalizedRoot = path7.normalize(projectRoot).toLowerCase();
|
|
2174
2097
|
if (!normalizedResolved.startsWith(normalizedRoot)) {
|
|
2175
2098
|
return `\u{1F6AB} BLOCKED: Path "${inputCwd}" resolves outside project root "${projectRoot}".`;
|
|
2176
2099
|
}
|
|
2177
|
-
if (!
|
|
2100
|
+
if (!fs7.existsSync(resolved)) {
|
|
2178
2101
|
return `\u26A0\uFE0F Directory "${inputCwd}" does not exist at "${resolved}".`;
|
|
2179
2102
|
}
|
|
2180
2103
|
workingDir = resolved;
|
|
@@ -2260,8 +2183,8 @@ function formatTimeoutResult(command, timeout) {
|
|
|
2260
2183
|
}
|
|
2261
2184
|
|
|
2262
2185
|
// src/agents/codeReviewer.ts
|
|
2263
|
-
import
|
|
2264
|
-
import
|
|
2186
|
+
import fs8 from "fs";
|
|
2187
|
+
import path8 from "path";
|
|
2265
2188
|
import { execSync } from "child_process";
|
|
2266
2189
|
var CODE_EXTENSIONS = [
|
|
2267
2190
|
".ts",
|
|
@@ -2275,7 +2198,19 @@ var CODE_EXTENSIONS = [
|
|
|
2275
2198
|
".rs",
|
|
2276
2199
|
".php",
|
|
2277
2200
|
".cs",
|
|
2278
|
-
".java"
|
|
2201
|
+
".java",
|
|
2202
|
+
".json",
|
|
2203
|
+
".jsonc",
|
|
2204
|
+
".yaml",
|
|
2205
|
+
".yml",
|
|
2206
|
+
".toml"
|
|
2207
|
+
];
|
|
2208
|
+
var CONFIG_EXTENSIONS = [
|
|
2209
|
+
".json",
|
|
2210
|
+
".jsonc",
|
|
2211
|
+
".yaml",
|
|
2212
|
+
".yml",
|
|
2213
|
+
".toml"
|
|
2279
2214
|
];
|
|
2280
2215
|
function getGitChangedFiles() {
|
|
2281
2216
|
try {
|
|
@@ -2293,7 +2228,7 @@ function getGitChangedFiles() {
|
|
|
2293
2228
|
if (filePath.startsWith('"') && filePath.endsWith('"')) {
|
|
2294
2229
|
filePath = filePath.substring(1, filePath.length - 1);
|
|
2295
2230
|
}
|
|
2296
|
-
const ext =
|
|
2231
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2297
2232
|
if (CODE_EXTENSIONS.includes(ext)) files.push(filePath);
|
|
2298
2233
|
}
|
|
2299
2234
|
return files.slice(0, 10);
|
|
@@ -2330,7 +2265,7 @@ Pass an explicit 'files' array to review specific files.`;
|
|
|
2330
2265
|
continue;
|
|
2331
2266
|
}
|
|
2332
2267
|
const resolvedPath = validation.resolvedPath;
|
|
2333
|
-
if (!
|
|
2268
|
+
if (!fs8.existsSync(resolvedPath)) {
|
|
2334
2269
|
allIssues.push({
|
|
2335
2270
|
file: filePath,
|
|
2336
2271
|
line: 0,
|
|
@@ -2342,7 +2277,7 @@ Pass an explicit 'files' array to review specific files.`;
|
|
|
2342
2277
|
continue;
|
|
2343
2278
|
}
|
|
2344
2279
|
try {
|
|
2345
|
-
const content =
|
|
2280
|
+
const content = fs8.readFileSync(resolvedPath, "utf-8");
|
|
2346
2281
|
filesReviewed++;
|
|
2347
2282
|
checkGeneral(filePath, content, allIssues);
|
|
2348
2283
|
switch (focus) {
|
|
@@ -2440,11 +2375,12 @@ function isTestFile(filePath) {
|
|
|
2440
2375
|
return /\.(test|spec)\.[a-z]+$/i.test(filePath) || /__tests__/.test(filePath);
|
|
2441
2376
|
}
|
|
2442
2377
|
function isTsLike(filePath) {
|
|
2443
|
-
const ext =
|
|
2378
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2444
2379
|
return ext === ".ts" || ext === ".tsx";
|
|
2445
2380
|
}
|
|
2446
2381
|
function checkGeneral(filePath, content, issues) {
|
|
2447
2382
|
const lines = content.split("\n");
|
|
2383
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2448
2384
|
if (content.length > 0 && !content.endsWith("\n")) {
|
|
2449
2385
|
issues.push({
|
|
2450
2386
|
file: filePath,
|
|
@@ -2465,6 +2401,9 @@ function checkGeneral(filePath, content, issues) {
|
|
|
2465
2401
|
suggestion: "Extract cohesive sections into separate modules"
|
|
2466
2402
|
});
|
|
2467
2403
|
}
|
|
2404
|
+
if (CONFIG_EXTENSIONS.includes(ext)) {
|
|
2405
|
+
checkConfigFile(filePath, content, issues);
|
|
2406
|
+
}
|
|
2468
2407
|
}
|
|
2469
2408
|
function checkCorrectness(filePath, content, issues) {
|
|
2470
2409
|
const lines = content.split("\n");
|
|
@@ -2627,6 +2566,282 @@ function checkCorrectness(filePath, content, issues) {
|
|
|
2627
2566
|
});
|
|
2628
2567
|
}
|
|
2629
2568
|
}
|
|
2569
|
+
function stripJsonComments(text) {
|
|
2570
|
+
let result = text.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
2571
|
+
const lines = result.split("\n");
|
|
2572
|
+
const cleaned = lines.map((line) => {
|
|
2573
|
+
const commentIdx = line.indexOf("//");
|
|
2574
|
+
if (commentIdx === -1) return line;
|
|
2575
|
+
const before = line.substring(0, commentIdx);
|
|
2576
|
+
const quoteCount = (before.match(/"/g) || []).length;
|
|
2577
|
+
if (quoteCount % 2 === 0) {
|
|
2578
|
+
return before;
|
|
2579
|
+
}
|
|
2580
|
+
return line;
|
|
2581
|
+
});
|
|
2582
|
+
return cleaned.join("\n");
|
|
2583
|
+
}
|
|
2584
|
+
function checkConfigFile(filePath, content, issues) {
|
|
2585
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2586
|
+
const lines = content.split("\n");
|
|
2587
|
+
if (ext === ".json" || ext === ".jsonc") {
|
|
2588
|
+
checkJson(filePath, content, lines, issues);
|
|
2589
|
+
} else if (ext === ".yaml" || ext === ".yml") {
|
|
2590
|
+
checkYaml(filePath, content, lines, issues);
|
|
2591
|
+
} else if (ext === ".toml") {
|
|
2592
|
+
checkToml(filePath, content, lines, issues);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
function checkJson(filePath, content, lines, issues) {
|
|
2596
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2597
|
+
const isJsonc = ext === ".jsonc";
|
|
2598
|
+
const contentToParse = isJsonc ? stripJsonComments(content) : content;
|
|
2599
|
+
try {
|
|
2600
|
+
JSON.parse(contentToParse);
|
|
2601
|
+
} catch (err) {
|
|
2602
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2603
|
+
if (isJsonc && /\/\/|\/\*/.test(content)) {
|
|
2604
|
+
issues.push({
|
|
2605
|
+
file: filePath,
|
|
2606
|
+
line: 1,
|
|
2607
|
+
severity: "info",
|
|
2608
|
+
rule: "jsonc/comments-stripped",
|
|
2609
|
+
message: "JSON with Comments \u2014 comments were stripped for validation. Check the remaining syntax.",
|
|
2610
|
+
suggestion: "If issues remain, check quotes, commas, braces after comment removal"
|
|
2611
|
+
});
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
const posMatch = msg.match(/position\s+(\d+)/i);
|
|
2615
|
+
const lineMatch = msg.match(/line\s+(\d+)/i);
|
|
2616
|
+
let errorLine = 1;
|
|
2617
|
+
if (lineMatch) {
|
|
2618
|
+
errorLine = parseInt(lineMatch[1], 10);
|
|
2619
|
+
} else if (posMatch) {
|
|
2620
|
+
const pos = parseInt(posMatch[1], 10);
|
|
2621
|
+
let charCount = 0;
|
|
2622
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2623
|
+
charCount += lines[i].length + 1;
|
|
2624
|
+
if (charCount >= pos) {
|
|
2625
|
+
errorLine = i + 1;
|
|
2626
|
+
break;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
issues.push({
|
|
2631
|
+
file: filePath,
|
|
2632
|
+
line: errorLine,
|
|
2633
|
+
severity: "error",
|
|
2634
|
+
rule: "json/parse-error",
|
|
2635
|
+
message: `Invalid JSON: ${msg.substring(0, 120)}`,
|
|
2636
|
+
suggestion: "Fix the syntax error \u2014 check quotes, commas, braces, and brackets"
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2640
|
+
const trimmed = lines[i].trim();
|
|
2641
|
+
if (!/,\s*$/.test(trimmed)) continue;
|
|
2642
|
+
let nextNonEmpty = "";
|
|
2643
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
2644
|
+
const t = lines[j].trim();
|
|
2645
|
+
if (t && !t.startsWith("//") && !t.startsWith("*") && !t.startsWith("#")) {
|
|
2646
|
+
nextNonEmpty = t;
|
|
2647
|
+
break;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
if (nextNonEmpty === "]" || nextNonEmpty === "}" || nextNonEmpty === "]," || nextNonEmpty === "},") {
|
|
2651
|
+
issues.push({
|
|
2652
|
+
file: filePath,
|
|
2653
|
+
line: i + 1,
|
|
2654
|
+
severity: "error",
|
|
2655
|
+
rule: "json/trailing-comma",
|
|
2656
|
+
message: "Trailing comma before closing bracket/brace",
|
|
2657
|
+
suggestion: "Remove the trailing comma after the last element"
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
try {
|
|
2662
|
+
const parsed = JSON.parse(content);
|
|
2663
|
+
findDuplicateKeys(parsed, "", filePath, issues, lines);
|
|
2664
|
+
} catch {
|
|
2665
|
+
}
|
|
2666
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2667
|
+
const line = lines[i];
|
|
2668
|
+
if ((/\s+'[a-zA-Z_][a-zA-Z0-9_]*'\s*:/.test(line) || /:\s*'[^']*'\s*[,}\]$]/.test(line)) && !/\/\//.test(line)) {
|
|
2669
|
+
issues.push({
|
|
2670
|
+
file: filePath,
|
|
2671
|
+
line: i + 1,
|
|
2672
|
+
severity: "warning",
|
|
2673
|
+
rule: "json/single-quote",
|
|
2674
|
+
message: "Single quotes used \u2014 JSON requires double quotes",
|
|
2675
|
+
suggestion: `Replace ' with "`
|
|
2676
|
+
});
|
|
2677
|
+
break;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
function findDuplicateKeys(obj, nestedPath, filePath, issues, lines) {
|
|
2682
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return;
|
|
2683
|
+
const keys = Object.keys(obj);
|
|
2684
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2685
|
+
for (const key of keys) {
|
|
2686
|
+
if (seen.has(key)) {
|
|
2687
|
+
const searchStr = `"${key}"`;
|
|
2688
|
+
let lineNum = 1;
|
|
2689
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2690
|
+
if (lines[i].includes(searchStr)) {
|
|
2691
|
+
lineNum = i + 1;
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
issues.push({
|
|
2696
|
+
file: filePath,
|
|
2697
|
+
line: lineNum,
|
|
2698
|
+
severity: "warning",
|
|
2699
|
+
rule: "json/duplicate-key",
|
|
2700
|
+
message: `Duplicate key "${key}" in ${nestedPath || "root"}`,
|
|
2701
|
+
suggestion: "Remove or rename the duplicate \u2014 last value wins in JSON"
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
seen.set(key, 1);
|
|
2705
|
+
}
|
|
2706
|
+
for (const key of keys) {
|
|
2707
|
+
const deeperPath = nestedPath ? `${nestedPath}.${key}` : key;
|
|
2708
|
+
findDuplicateKeys(obj[key], deeperPath, filePath, issues, lines);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function checkYaml(filePath, _content, lines, issues) {
|
|
2712
|
+
let hasTabs = false;
|
|
2713
|
+
let hasSpaces = false;
|
|
2714
|
+
let inconsistentIndent = false;
|
|
2715
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2716
|
+
const rawLine = lines[i];
|
|
2717
|
+
const trimmed = rawLine.trim();
|
|
2718
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2719
|
+
if (rawLine.startsWith(" ")) hasTabs = true;
|
|
2720
|
+
const spaceMatch = rawLine.match(/^( +)/);
|
|
2721
|
+
if (spaceMatch) {
|
|
2722
|
+
hasSpaces = true;
|
|
2723
|
+
const indent = spaceMatch[1].length;
|
|
2724
|
+
if (indent % 2 !== 0 && indent !== 0) {
|
|
2725
|
+
inconsistentIndent = true;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
if (hasTabs) {
|
|
2730
|
+
issues.push({
|
|
2731
|
+
file: filePath,
|
|
2732
|
+
line: 1,
|
|
2733
|
+
severity: "error",
|
|
2734
|
+
rule: "yaml/tab-indent",
|
|
2735
|
+
message: "YAML uses spaces for indentation \u2014 tabs are not allowed",
|
|
2736
|
+
suggestion: "Replace tabs with spaces (2 spaces per indent level)"
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
if (inconsistentIndent) {
|
|
2740
|
+
issues.push({
|
|
2741
|
+
file: filePath,
|
|
2742
|
+
line: 1,
|
|
2743
|
+
severity: "warning",
|
|
2744
|
+
rule: "yaml/inconsistent-indent",
|
|
2745
|
+
message: "Indentation depth is not a multiple of 2 \u2014 may cause parsing issues",
|
|
2746
|
+
suggestion: "Use consistent 2-space indentation throughout the file"
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2750
|
+
const trimmed = lines[i].trim();
|
|
2751
|
+
if (/^https?:\/\//.test(trimmed) || /:\s*https?:\/\//.test(trimmed)) {
|
|
2752
|
+
issues.push({
|
|
2753
|
+
file: filePath,
|
|
2754
|
+
line: i + 1,
|
|
2755
|
+
severity: "info",
|
|
2756
|
+
rule: "yaml/unquoted-url",
|
|
2757
|
+
message: "URL should be quoted to avoid colon parsing ambiguity",
|
|
2758
|
+
suggestion: 'Wrap URL in quotes: "https://..."'
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
if (/^\s*\w+:\s*(yes|no|on|off)\s*$/.test(lines[i])) {
|
|
2762
|
+
issues.push({
|
|
2763
|
+
file: filePath,
|
|
2764
|
+
line: i + 1,
|
|
2765
|
+
severity: "info",
|
|
2766
|
+
rule: "yaml/ambiguous-bool",
|
|
2767
|
+
message: "Ambiguous boolean value \u2014 YAML interprets yes/no/on/off as booleans",
|
|
2768
|
+
suggestion: "Use true/false instead for clarity, or quote the value"
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
if (hasTabs && hasSpaces) {
|
|
2773
|
+
issues.push({
|
|
2774
|
+
file: filePath,
|
|
2775
|
+
line: 1,
|
|
2776
|
+
severity: "error",
|
|
2777
|
+
rule: "yaml/mixed-indent",
|
|
2778
|
+
message: "Mixed tabs and spaces in indentation",
|
|
2779
|
+
suggestion: "Use spaces only (2 spaces per level)"
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
function checkToml(filePath, _content, lines, issues) {
|
|
2784
|
+
const seenKeys = /* @__PURE__ */ new Map();
|
|
2785
|
+
const arrayTables = /* @__PURE__ */ new Set();
|
|
2786
|
+
let currentTable = "";
|
|
2787
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2788
|
+
const trimmed = lines[i].trim();
|
|
2789
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2790
|
+
const isArrayTable = trimmed.startsWith("[[");
|
|
2791
|
+
const tableMatch = trimmed.match(/^\[\[?(.+?)\]?\]$/);
|
|
2792
|
+
if (tableMatch) {
|
|
2793
|
+
currentTable = tableMatch[1];
|
|
2794
|
+
if (isArrayTable) {
|
|
2795
|
+
arrayTables.add(currentTable);
|
|
2796
|
+
}
|
|
2797
|
+
continue;
|
|
2798
|
+
}
|
|
2799
|
+
const kvMatch = trimmed.match(/^(\w+)\s*=/);
|
|
2800
|
+
if (kvMatch) {
|
|
2801
|
+
const fullKey = currentTable ? `${currentTable}.${kvMatch[1]}` : kvMatch[1];
|
|
2802
|
+
const parentTable = currentTable.split(".").slice(0, -1).join(".");
|
|
2803
|
+
const isInArrayTable = arrayTables.has(currentTable) || arrayTables.has(parentTable);
|
|
2804
|
+
if (seenKeys.has(fullKey)) {
|
|
2805
|
+
if (!isInArrayTable) {
|
|
2806
|
+
const prevLine = seenKeys.get(fullKey);
|
|
2807
|
+
issues.push({
|
|
2808
|
+
file: filePath,
|
|
2809
|
+
line: i + 1,
|
|
2810
|
+
severity: "warning",
|
|
2811
|
+
rule: "toml/duplicate-key",
|
|
2812
|
+
message: `Duplicate key "${fullKey}" (first defined at line ${prevLine})`,
|
|
2813
|
+
suggestion: "Remove or rename the duplicate \u2014 only last value is kept"
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
} else {
|
|
2817
|
+
seenKeys.set(fullKey, i + 1);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (/\{[^}]*\}/.test(trimmed)) {
|
|
2821
|
+
const inlineContent = trimmed.match(/\{([^}]*)\}/);
|
|
2822
|
+
if (inlineContent) {
|
|
2823
|
+
const inlineKeys = inlineContent[1].match(/(\w+)\s*=/g);
|
|
2824
|
+
if (inlineKeys) {
|
|
2825
|
+
const inlineSeen = /* @__PURE__ */ new Set();
|
|
2826
|
+
for (const k of inlineKeys) {
|
|
2827
|
+
const keyName = k.replace(/\s*=\s*$/, "").trim();
|
|
2828
|
+
if (inlineSeen.has(keyName)) {
|
|
2829
|
+
issues.push({
|
|
2830
|
+
file: filePath,
|
|
2831
|
+
line: i + 1,
|
|
2832
|
+
severity: "warning",
|
|
2833
|
+
rule: "toml/duplicate-inline-key",
|
|
2834
|
+
message: `Duplicate key "${keyName}" in inline table`,
|
|
2835
|
+
suggestion: "Remove the duplicate key"
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
inlineSeen.add(keyName);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2630
2845
|
function checkConventions(filePath, content, issues) {
|
|
2631
2846
|
const lines = content.split("\n");
|
|
2632
2847
|
let hasTabs = false;
|
|
@@ -3235,8 +3450,8 @@ async function handleGitDiff(params) {
|
|
|
3235
3450
|
}
|
|
3236
3451
|
|
|
3237
3452
|
// src/tools/projectStructure.ts
|
|
3238
|
-
import
|
|
3239
|
-
import
|
|
3453
|
+
import fs9 from "fs";
|
|
3454
|
+
import path9 from "path";
|
|
3240
3455
|
var DEFAULT_IGNORE = [
|
|
3241
3456
|
"node_modules",
|
|
3242
3457
|
".git",
|
|
@@ -3273,7 +3488,7 @@ async function handleProjectStructure(params) {
|
|
|
3273
3488
|
sessionMemory.recordToolCall("project_structure", { depth: clampedDepth, folderOnly });
|
|
3274
3489
|
try {
|
|
3275
3490
|
const tree = buildTree(root, root, clampedDepth, 0, folderOnly, includePattern, excludePattern);
|
|
3276
|
-
const projectName =
|
|
3491
|
+
const projectName = path9.basename(root);
|
|
3277
3492
|
const lines = [
|
|
3278
3493
|
"[Project Structure] - " + projectName,
|
|
3279
3494
|
"Depth: " + clampedDepth + " | " + (folderOnly ? "Folders only" : "Files and folders"),
|
|
@@ -3296,7 +3511,7 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3296
3511
|
const lines = [];
|
|
3297
3512
|
let entries = [];
|
|
3298
3513
|
try {
|
|
3299
|
-
entries =
|
|
3514
|
+
entries = fs9.readdirSync(currentDir, { withFileTypes: true });
|
|
3300
3515
|
} catch {
|
|
3301
3516
|
return lines;
|
|
3302
3517
|
}
|
|
@@ -3305,12 +3520,12 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3305
3520
|
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
3306
3521
|
return a.name.localeCompare(b.name);
|
|
3307
3522
|
});
|
|
3308
|
-
const relativeDir =
|
|
3523
|
+
const relativeDir = path9.relative(root, currentDir) || ".";
|
|
3309
3524
|
const prefix = getPrefix(currentDepth);
|
|
3310
3525
|
for (const entry of entries) {
|
|
3311
3526
|
if (shouldIgnore(entry.name, relativeDir, root)) continue;
|
|
3312
|
-
const fullPath =
|
|
3313
|
-
const relativePath =
|
|
3527
|
+
const fullPath = path9.join(currentDir, entry.name);
|
|
3528
|
+
const relativePath = path9.relative(root, fullPath);
|
|
3314
3529
|
if (includePattern && !entry.name.includes(includePattern) && !relativePath.includes(includePattern)) {
|
|
3315
3530
|
if (!entry.isDirectory()) continue;
|
|
3316
3531
|
}
|
|
@@ -3321,11 +3536,11 @@ function buildTree(root, currentDir, maxDepth, currentDepth, folderOnly, include
|
|
|
3321
3536
|
lines.push(prefix + "[D] " + entry.name + "/");
|
|
3322
3537
|
let subEntries = [];
|
|
3323
3538
|
try {
|
|
3324
|
-
subEntries =
|
|
3539
|
+
subEntries = fs9.readdirSync(fullPath, { withFileTypes: true });
|
|
3325
3540
|
} catch {
|
|
3326
3541
|
}
|
|
3327
3542
|
const hasVisibleContent = subEntries.some(
|
|
3328
|
-
(e) => !shouldIgnore(e.name,
|
|
3543
|
+
(e) => !shouldIgnore(e.name, path9.relative(root, fullPath), root)
|
|
3329
3544
|
);
|
|
3330
3545
|
if (hasVisibleContent) {
|
|
3331
3546
|
const subLines = buildTree(root, fullPath, maxDepth, currentDepth + 1, folderOnly, includePattern, excludePattern);
|
|
@@ -3356,7 +3571,7 @@ function getPrefix(depth) {
|
|
|
3356
3571
|
}
|
|
3357
3572
|
function getFileSize(fullPath) {
|
|
3358
3573
|
try {
|
|
3359
|
-
const stat =
|
|
3574
|
+
const stat = fs9.statSync(fullPath);
|
|
3360
3575
|
if (stat.size < 1024) return stat.size + "B";
|
|
3361
3576
|
if (stat.size < 1024 * 1024) return (stat.size / 1024).toFixed(0) + "KB";
|
|
3362
3577
|
const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
|
|
@@ -3367,8 +3582,8 @@ function getFileSize(fullPath) {
|
|
|
3367
3582
|
}
|
|
3368
3583
|
|
|
3369
3584
|
// src/tools/staticAnalysis.ts
|
|
3370
|
-
import
|
|
3371
|
-
import
|
|
3585
|
+
import fs10 from "fs";
|
|
3586
|
+
import path10 from "path";
|
|
3372
3587
|
async function handleStaticAnalysis(params) {
|
|
3373
3588
|
const { tool = "all", files, autoFix = false, timeout = 60 } = params;
|
|
3374
3589
|
const root = getProjectRoot();
|
|
@@ -3386,7 +3601,26 @@ async function handleStaticAnalysis(params) {
|
|
|
3386
3601
|
for (const t of toolsToRun) {
|
|
3387
3602
|
const output = await runTool(t, root, files, autoFix, timeout);
|
|
3388
3603
|
if (output === null) {
|
|
3389
|
-
|
|
3604
|
+
const cmd = buildToolCommand(t, files, autoFix);
|
|
3605
|
+
const reason = cmd === null ? `Could not build command for tool "${t}" (unknown tool type)` : `Command "${cmd}" returned no output (tool may not be installed)`;
|
|
3606
|
+
console.error(`[StaticAnalysis] ${reason}`);
|
|
3607
|
+
allIssues.push({
|
|
3608
|
+
file: `(${t} error)`,
|
|
3609
|
+
line: 0,
|
|
3610
|
+
column: 0,
|
|
3611
|
+
severity: "error",
|
|
3612
|
+
message: reason,
|
|
3613
|
+
rule: t,
|
|
3614
|
+
source: t
|
|
3615
|
+
});
|
|
3616
|
+
const result2 = {
|
|
3617
|
+
tool: t,
|
|
3618
|
+
exitCode: -1,
|
|
3619
|
+
issues: [{ file: `(${t} error)`, line: 0, column: 0, severity: "error", message: reason, rule: t, source: t }],
|
|
3620
|
+
summary: { errors: 1, warnings: 0, info: 0 }
|
|
3621
|
+
};
|
|
3622
|
+
results.push(result2);
|
|
3623
|
+
sessionMemory.addFailedFile("static_analysis:" + t, reason);
|
|
3390
3624
|
continue;
|
|
3391
3625
|
}
|
|
3392
3626
|
const issues = parseToolOutput(t, output.stdout, output.stderr, root);
|
|
@@ -3402,6 +3636,22 @@ async function handleStaticAnalysis(params) {
|
|
|
3402
3636
|
};
|
|
3403
3637
|
results.push(result);
|
|
3404
3638
|
allIssues.push(...issues);
|
|
3639
|
+
if (result.exitCode !== 0 && issues.length === 0 && output.stderr.trim()) {
|
|
3640
|
+
const stderrLines = output.stderr.trim().split("\n").slice(0, 8);
|
|
3641
|
+
console.error(`[StaticAnalysis] ${t} exited with code ${result.exitCode} (no parseable output):`);
|
|
3642
|
+
for (const errLine of stderrLines) {
|
|
3643
|
+
console.error(`[StaticAnalysis] ${errLine}`);
|
|
3644
|
+
}
|
|
3645
|
+
allIssues.push({
|
|
3646
|
+
file: `(${t} error)`,
|
|
3647
|
+
line: 0,
|
|
3648
|
+
column: 0,
|
|
3649
|
+
severity: "error",
|
|
3650
|
+
message: `Tool "${t}" exited with code ${result.exitCode}. Check stderr for details.`,
|
|
3651
|
+
rule: t,
|
|
3652
|
+
source: t
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3405
3655
|
if (result.summary.errors > 0 || result.summary.warnings > 0) {
|
|
3406
3656
|
const errorMsg = result.summary.errors > 0 ? result.summary.errors + " error(s)" : result.summary.warnings + " warning(s)";
|
|
3407
3657
|
sessionMemory.addFailedFile("static_analysis:" + t, errorMsg + " found");
|
|
@@ -3423,11 +3673,11 @@ function detectAvailableTools(root) {
|
|
|
3423
3673
|
"eslint.config.js",
|
|
3424
3674
|
"eslint.config.mjs"
|
|
3425
3675
|
];
|
|
3426
|
-
const hasEslintConfig = eslintConfigs.some((cfg) =>
|
|
3676
|
+
const hasEslintConfig = eslintConfigs.some((cfg) => fs10.existsSync(path10.join(root, cfg)));
|
|
3427
3677
|
if (hasEslintConfig && depNames.has("eslint")) {
|
|
3428
3678
|
tools.push("eslint");
|
|
3429
3679
|
}
|
|
3430
|
-
if (
|
|
3680
|
+
if (fs10.existsSync(path10.join(root, "tsconfig.json")) && depNames.has("typescript")) {
|
|
3431
3681
|
tools.push("tsc");
|
|
3432
3682
|
}
|
|
3433
3683
|
const prettierConfigs = [
|
|
@@ -3438,20 +3688,20 @@ function detectAvailableTools(root) {
|
|
|
3438
3688
|
".prettierrc.toml",
|
|
3439
3689
|
"prettier.config.js"
|
|
3440
3690
|
];
|
|
3441
|
-
const hasPrettierConfig = prettierConfigs.some((cfg) =>
|
|
3691
|
+
const hasPrettierConfig = prettierConfigs.some((cfg) => fs10.existsSync(path10.join(root, cfg)));
|
|
3442
3692
|
if (hasPrettierConfig && depNames.has("prettier")) {
|
|
3443
3693
|
tools.push("prettier");
|
|
3444
3694
|
}
|
|
3445
|
-
if (
|
|
3695
|
+
if (fs10.existsSync(path10.join(root, "ruff.toml")) || fs10.existsSync(path10.join(root, ".ruff.toml"))) {
|
|
3446
3696
|
tools.push("ruff");
|
|
3447
3697
|
}
|
|
3448
3698
|
return tools;
|
|
3449
3699
|
}
|
|
3450
3700
|
function readPackageJson(root) {
|
|
3451
3701
|
try {
|
|
3452
|
-
const pkgPath =
|
|
3453
|
-
if (
|
|
3454
|
-
return JSON.parse(
|
|
3702
|
+
const pkgPath = path10.join(root, "package.json");
|
|
3703
|
+
if (fs10.existsSync(pkgPath)) {
|
|
3704
|
+
return JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
|
|
3455
3705
|
}
|
|
3456
3706
|
} catch {
|
|
3457
3707
|
}
|
|
@@ -3471,10 +3721,16 @@ async function runTool(tool, cwd, files, autoFix, timeoutSeconds) {
|
|
|
3471
3721
|
}
|
|
3472
3722
|
function buildToolCommand(tool, files, autoFix) {
|
|
3473
3723
|
const pm = detectPackageManagerPrefix();
|
|
3724
|
+
const root = getProjectRoot();
|
|
3474
3725
|
switch (tool) {
|
|
3475
3726
|
case "eslint": {
|
|
3476
3727
|
const fixFlag = autoFix ? " --fix" : "";
|
|
3477
3728
|
const target = files ? files.join(" ") : ".";
|
|
3729
|
+
const eslintBin = findBinary(root, "eslint");
|
|
3730
|
+
if (!eslintBin) {
|
|
3731
|
+
console.error(`[StaticAnalysis] eslint not found locally. Trying npx (auto-install).`);
|
|
3732
|
+
return "npx eslint" + fixFlag + " " + target + " --format unix";
|
|
3733
|
+
}
|
|
3478
3734
|
return pm + "eslint" + fixFlag + " " + target + " --format unix";
|
|
3479
3735
|
}
|
|
3480
3736
|
case "tsc": {
|
|
@@ -3483,6 +3739,11 @@ function buildToolCommand(tool, files, autoFix) {
|
|
|
3483
3739
|
case "prettier": {
|
|
3484
3740
|
const fixFlag = autoFix ? " --write" : " --check";
|
|
3485
3741
|
const target = files ? files.join(" ") : ".";
|
|
3742
|
+
const prettierBin = findBinary(root, "prettier");
|
|
3743
|
+
if (!prettierBin) {
|
|
3744
|
+
console.error(`[StaticAnalysis] prettier not found locally. Trying npx (auto-install).`);
|
|
3745
|
+
return "npx prettier" + fixFlag + " " + target;
|
|
3746
|
+
}
|
|
3486
3747
|
return pm + "prettier" + fixFlag + " " + target;
|
|
3487
3748
|
}
|
|
3488
3749
|
case "ruff": {
|
|
@@ -3496,10 +3757,25 @@ function buildToolCommand(tool, files, autoFix) {
|
|
|
3496
3757
|
}
|
|
3497
3758
|
function detectPackageManagerPrefix() {
|
|
3498
3759
|
const root = getProjectRoot();
|
|
3499
|
-
if (
|
|
3500
|
-
if (
|
|
3760
|
+
if (fs10.existsSync(path10.join(root, "pnpm-lock.yaml"))) return "pnpm ";
|
|
3761
|
+
if (fs10.existsSync(path10.join(root, "yarn.lock"))) return "yarn ";
|
|
3501
3762
|
return "npx --no-install ";
|
|
3502
3763
|
}
|
|
3764
|
+
function findBinary(root, binName) {
|
|
3765
|
+
const candidates = [
|
|
3766
|
+
path10.join(root, "node_modules", ".bin", binName),
|
|
3767
|
+
path10.join(root, "..", "node_modules", ".bin", binName)
|
|
3768
|
+
];
|
|
3769
|
+
for (const candidate of candidates) {
|
|
3770
|
+
try {
|
|
3771
|
+
if (fs10.existsSync(candidate)) {
|
|
3772
|
+
return candidate;
|
|
3773
|
+
}
|
|
3774
|
+
} catch {
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
return null;
|
|
3778
|
+
}
|
|
3503
3779
|
function parseToolOutput(tool, stdout, stderr, projectRoot) {
|
|
3504
3780
|
switch (tool) {
|
|
3505
3781
|
case "eslint":
|
|
@@ -3516,7 +3792,7 @@ function parseToolOutput(tool, stdout, stderr, projectRoot) {
|
|
|
3516
3792
|
}
|
|
3517
3793
|
function resolveToolPath(filePath, projectRoot) {
|
|
3518
3794
|
const trimmed = filePath.trim();
|
|
3519
|
-
return
|
|
3795
|
+
return path10.isAbsolute(trimmed) ? path10.relative(projectRoot, trimmed) : trimmed.replace(/^\.\//, "");
|
|
3520
3796
|
}
|
|
3521
3797
|
function parseEslintOutput(output, projectRoot) {
|
|
3522
3798
|
const issues = [];
|
|
@@ -3629,10 +3905,13 @@ function formatResults2(results, allIssues, toolsRun) {
|
|
|
3629
3905
|
if (!hasIssues) {
|
|
3630
3906
|
const failedTools = results.filter((r) => r.exitCode !== 0);
|
|
3631
3907
|
if (failedTools.length > 0) {
|
|
3632
|
-
lines.push("Tool(s) executed but
|
|
3633
|
-
|
|
3908
|
+
lines.push("Tool(s) executed but failed:");
|
|
3909
|
+
for (const ft of failedTools) {
|
|
3910
|
+
lines.push(` - ${ft.tool} (exit code: ${ft.exitCode})`);
|
|
3911
|
+
}
|
|
3634
3912
|
lines.push("");
|
|
3635
|
-
lines.push("The
|
|
3913
|
+
lines.push("The tools may not be installed correctly or encountered an error.");
|
|
3914
|
+
lines.push("Run the tool manually to see the full output.");
|
|
3636
3915
|
} else {
|
|
3637
3916
|
lines.push("All checks passed \u2014 no issues found.");
|
|
3638
3917
|
}
|
|
@@ -3674,13 +3953,13 @@ function formatNoToolsDetected() {
|
|
|
3674
3953
|
"No linters or checkers detected for this project.",
|
|
3675
3954
|
"",
|
|
3676
3955
|
"Detected tools include:",
|
|
3677
|
-
" - ESLint
|
|
3678
|
-
" -
|
|
3679
|
-
" - Prettier
|
|
3680
|
-
" - Ruff
|
|
3956
|
+
" - ESLint \u2014 check .eslintrc or eslint.config.*",
|
|
3957
|
+
" - tsc \u2014 check tsconfig.json",
|
|
3958
|
+
" - Prettier \u2014 check .prettierrc",
|
|
3959
|
+
" - Ruff \u2014 check ruff.toml",
|
|
3681
3960
|
"",
|
|
3682
|
-
"
|
|
3683
|
-
"
|
|
3961
|
+
"Each needs both a config file AND the npm package installed.",
|
|
3962
|
+
"Install a linter, then run project_conventions to refresh."
|
|
3684
3963
|
].join("\n");
|
|
3685
3964
|
}
|
|
3686
3965
|
function formatToolNotAvailable(requested, available) {
|
|
@@ -3793,8 +4072,8 @@ async function handleReflect(params) {
|
|
|
3793
4072
|
import { execSync as execSync6 } from "child_process";
|
|
3794
4073
|
|
|
3795
4074
|
// src/guards/antiPatternDetector.ts
|
|
3796
|
-
import
|
|
3797
|
-
import
|
|
4075
|
+
import fs11 from "fs";
|
|
4076
|
+
import path11 from "path";
|
|
3798
4077
|
import { execSync as execSync4 } from "child_process";
|
|
3799
4078
|
var SCRIPT_PATCH_PATTERNS = [
|
|
3800
4079
|
"writeFileSync",
|
|
@@ -3821,20 +4100,20 @@ function findPatchScripts(projectRoot) {
|
|
|
3821
4100
|
const warnings = [];
|
|
3822
4101
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
3823
4102
|
for (const file of recentFiles) {
|
|
3824
|
-
const ext =
|
|
4103
|
+
const ext = path11.extname(file).toLowerCase();
|
|
3825
4104
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
3826
|
-
const relativePath =
|
|
4105
|
+
const relativePath = path11.relative(projectRoot, file);
|
|
3827
4106
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
|
|
3828
4107
|
continue;
|
|
3829
4108
|
}
|
|
3830
4109
|
try {
|
|
3831
|
-
const content =
|
|
4110
|
+
const content = fs11.readFileSync(file, "utf-8").toLowerCase();
|
|
3832
4111
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
3833
4112
|
if (matchedPattern) {
|
|
3834
4113
|
warnings.push({
|
|
3835
4114
|
severity: "high",
|
|
3836
4115
|
pattern: "script-patching",
|
|
3837
|
-
message: `Created script file that modifies other files: ${
|
|
4116
|
+
message: `Created script file that modifies other files: ${path11.basename(file)}`,
|
|
3838
4117
|
suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
|
|
3839
4118
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
3840
4119
|
filePath: relativePath
|
|
@@ -3868,15 +4147,15 @@ function detectBashGrepUsage() {
|
|
|
3868
4147
|
function scanRecentFiles(projectRoot) {
|
|
3869
4148
|
const recent = [];
|
|
3870
4149
|
try {
|
|
3871
|
-
const entries =
|
|
4150
|
+
const entries = fs11.readdirSync(projectRoot, { withFileTypes: true });
|
|
3872
4151
|
const now = Date.now();
|
|
3873
4152
|
const recentThreshold = 30 * 60 * 1e3;
|
|
3874
4153
|
for (const entry of entries) {
|
|
3875
4154
|
if (!entry.isFile()) continue;
|
|
3876
4155
|
try {
|
|
3877
|
-
const stat =
|
|
4156
|
+
const stat = fs11.statSync(path11.join(projectRoot, entry.name));
|
|
3878
4157
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
3879
|
-
recent.push(
|
|
4158
|
+
recent.push(path11.join(projectRoot, entry.name));
|
|
3880
4159
|
}
|
|
3881
4160
|
} catch {
|
|
3882
4161
|
continue;
|
|
@@ -3900,14 +4179,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
3900
4179
|
const prefix = line.substring(0, 2);
|
|
3901
4180
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
3902
4181
|
const file = line.substring(3).trim();
|
|
3903
|
-
const ext =
|
|
4182
|
+
const ext = path11.extname(file).toLowerCase();
|
|
3904
4183
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
3905
4184
|
const isRootLevel = !file.includes("/");
|
|
3906
4185
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
3907
4186
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
3908
|
-
const fullPath =
|
|
3909
|
-
if (
|
|
3910
|
-
const content =
|
|
4187
|
+
const fullPath = path11.join(projectRoot, file);
|
|
4188
|
+
if (fs11.existsSync(fullPath)) {
|
|
4189
|
+
const content = fs11.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
3911
4190
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
3912
4191
|
if (matchedPattern) {
|
|
3913
4192
|
warnings.push({
|
|
@@ -3930,7 +4209,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
3930
4209
|
if (deletedStdout) {
|
|
3931
4210
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
3932
4211
|
for (const file of deletedFiles) {
|
|
3933
|
-
const ext =
|
|
4212
|
+
const ext = path11.extname(file).toLowerCase();
|
|
3934
4213
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
3935
4214
|
warnings.push({
|
|
3936
4215
|
severity: "high",
|
|
@@ -3990,21 +4269,21 @@ function detectAllAntiPatterns() {
|
|
|
3990
4269
|
}
|
|
3991
4270
|
|
|
3992
4271
|
// src/engine/contextSnapshot.ts
|
|
3993
|
-
import
|
|
3994
|
-
import
|
|
4272
|
+
import fs12 from "fs";
|
|
4273
|
+
import path12 from "path";
|
|
3995
4274
|
import { execSync as execSync5 } from "child_process";
|
|
3996
4275
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
3997
4276
|
function snapshotDir() {
|
|
3998
|
-
return
|
|
4277
|
+
return path12.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
3999
4278
|
}
|
|
4000
4279
|
function ensureSnapshotDir() {
|
|
4001
4280
|
const dir = snapshotDir();
|
|
4002
|
-
if (!
|
|
4003
|
-
|
|
4281
|
+
if (!fs12.existsSync(dir)) {
|
|
4282
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
4004
4283
|
}
|
|
4005
4284
|
}
|
|
4006
4285
|
function snapshotFilePath(timestamp) {
|
|
4007
|
-
return
|
|
4286
|
+
return path12.join(snapshotDir(), `${timestamp}.json`);
|
|
4008
4287
|
}
|
|
4009
4288
|
function saveSnapshot(goal) {
|
|
4010
4289
|
try {
|
|
@@ -4025,7 +4304,7 @@ function saveSnapshot(goal) {
|
|
|
4025
4304
|
hasConventions: !!summary.hasConventions
|
|
4026
4305
|
};
|
|
4027
4306
|
try {
|
|
4028
|
-
|
|
4307
|
+
fs12.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
4029
4308
|
} catch {
|
|
4030
4309
|
}
|
|
4031
4310
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -4033,12 +4312,12 @@ function saveSnapshot(goal) {
|
|
|
4033
4312
|
}
|
|
4034
4313
|
function listSnapshots() {
|
|
4035
4314
|
const dir = snapshotDir();
|
|
4036
|
-
if (!
|
|
4315
|
+
if (!fs12.existsSync(dir)) return [];
|
|
4037
4316
|
try {
|
|
4038
|
-
const files =
|
|
4317
|
+
const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".json")).sort((a, b) => b.localeCompare(a));
|
|
4039
4318
|
return files.map((f) => {
|
|
4040
4319
|
try {
|
|
4041
|
-
const content =
|
|
4320
|
+
const content = fs12.readFileSync(path12.join(dir, f), "utf-8");
|
|
4042
4321
|
return JSON.parse(content);
|
|
4043
4322
|
} catch {
|
|
4044
4323
|
return null;
|
|
@@ -4244,11 +4523,73 @@ async function handleKumaContext(params) {
|
|
|
4244
4523
|
}
|
|
4245
4524
|
}
|
|
4246
4525
|
|
|
4247
|
-
// src/
|
|
4248
|
-
import
|
|
4249
|
-
import
|
|
4250
|
-
|
|
4251
|
-
|
|
4526
|
+
// src/tools/kumaInit.ts
|
|
4527
|
+
import fs13 from "fs";
|
|
4528
|
+
import path13 from "path";
|
|
4529
|
+
async function handleKumaInit(params) {
|
|
4530
|
+
const root = params.projectRoot ?? getProjectRoot();
|
|
4531
|
+
const kumaDir = path13.join(root, ".kuma");
|
|
4532
|
+
const memoriesDir = path13.join(kumaDir, "memories");
|
|
4533
|
+
const initMdPath = path13.join(kumaDir, "init.md");
|
|
4534
|
+
sessionMemory.recordToolCall("kuma_init", { projectRoot: root });
|
|
4535
|
+
let rules = "";
|
|
4536
|
+
if (fs13.existsSync(initMdPath)) {
|
|
4537
|
+
rules = fs13.readFileSync(initMdPath, "utf-8");
|
|
4538
|
+
}
|
|
4539
|
+
const memories = [];
|
|
4540
|
+
if (fs13.existsSync(memoriesDir)) {
|
|
4541
|
+
try {
|
|
4542
|
+
const files = fs13.readdirSync(memoriesDir).filter((f) => f.endsWith(".md"));
|
|
4543
|
+
for (const file of files) {
|
|
4544
|
+
try {
|
|
4545
|
+
const content = fs13.readFileSync(path13.join(memoriesDir, file), "utf-8");
|
|
4546
|
+
memories.push({ topic: file.replace(/\.md$/, ""), content });
|
|
4547
|
+
} catch {
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
} catch {
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
const sessionLoad = sessionMemory.loadSession();
|
|
4554
|
+
const sections = [
|
|
4555
|
+
"\u{1F9E0} **Kuma Context Loaded**",
|
|
4556
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
|
|
4557
|
+
];
|
|
4558
|
+
const rulesLineCount = rules ? rules.split("\n").filter((l) => l.trim()).length : 0;
|
|
4559
|
+
sections.push(`\u{1F4CB} **Rules:** ${rulesLineCount} lines from .kuma/init.md`);
|
|
4560
|
+
if (memories.length > 0) {
|
|
4561
|
+
const memoryList = memories.map((m) => ` \u2022 **${m.topic}** \u2014 ${m.content.split("\n").filter((l) => l.trim()).length} lines`).join("\n");
|
|
4562
|
+
sections.push(`\u{1F4C1} **Memories (${memories.length}):**
|
|
4563
|
+
${memoryList}`);
|
|
4564
|
+
} else {
|
|
4565
|
+
sections.push("\u{1F4C1} **Memories:** none yet \u2014 run project_conventions() to generate");
|
|
4566
|
+
}
|
|
4567
|
+
if (sessionLoad.hasPrevSession) {
|
|
4568
|
+
sections.push(
|
|
4569
|
+
"\u{1F4CA} **Previous Session:**",
|
|
4570
|
+
` \u2022 \u{1F6E0}\uFE0F ${sessionLoad.toolCallCount} tool calls`,
|
|
4571
|
+
` \u2022 ${sessionLoad.hasConventions ? "\u2705 conventions detected" : "\u274C no conventions"}`
|
|
4572
|
+
);
|
|
4573
|
+
} else {
|
|
4574
|
+
sections.push("\u{1F4CA} **Previous Session:** none \u2014 first session for this project");
|
|
4575
|
+
}
|
|
4576
|
+
sections.push(
|
|
4577
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
4578
|
+
"",
|
|
4579
|
+
"\u{1F4A1} **Next Steps:**",
|
|
4580
|
+
" \u2022 Previous session context has been loaded into session memory",
|
|
4581
|
+
' \u2022 Call `get_session_memory({topic: "..."})` for detailed memory',
|
|
4582
|
+
" \u2022 If project hasn't been scanned, run `project_conventions()`",
|
|
4583
|
+
" \u2022 Or continue working \u2014 session state is ready."
|
|
4584
|
+
);
|
|
4585
|
+
return sections.join("\n");
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
// src/engine/lspClient.ts
|
|
4589
|
+
import { spawn as spawn2 } from "child_process";
|
|
4590
|
+
import path14 from "path";
|
|
4591
|
+
import fs14 from "fs";
|
|
4592
|
+
var LSPClient = class {
|
|
4252
4593
|
process = null;
|
|
4253
4594
|
requestId = 0;
|
|
4254
4595
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -4366,7 +4707,8 @@ var LSPClient = class {
|
|
|
4366
4707
|
}
|
|
4367
4708
|
} catch {
|
|
4368
4709
|
}
|
|
4369
|
-
|
|
4710
|
+
console.error(`[LSP] typescript-language-server not found locally. Trying npx fallback.`);
|
|
4711
|
+
return "npx --yes typescript-language-server";
|
|
4370
4712
|
}
|
|
4371
4713
|
toUri(filePath) {
|
|
4372
4714
|
return `file://${filePath}`;
|
|
@@ -4612,19 +4954,22 @@ async function handleFindReferences(params) {
|
|
|
4612
4954
|
}
|
|
4613
4955
|
if (!lspClient.isAvailable()) {
|
|
4614
4956
|
sessionMemory.recordToolCall("lsp_query", { action: "refs", filePath, line, character, fallback: "regex" });
|
|
4957
|
+
console.error(`[LSP] typescript-language-server not found. Using regex fallback for find_references.`);
|
|
4615
4958
|
const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
|
|
4616
4959
|
if (!symbolName) {
|
|
4617
|
-
return
|
|
4960
|
+
return `**Find References** \u2014 "${filePath}:${line + 1}:${character + 1}"
|
|
4961
|
+
Could not read symbol at this position.
|
|
4618
4962
|
|
|
4619
|
-
|
|
4963
|
+
Try selecting a smaller/cleaner position, or install typescript-language-server for better results:
|
|
4964
|
+
npm install typescript-language-server --save-dev`;
|
|
4620
4965
|
}
|
|
4621
4966
|
return fallbackGrepReferences(symbolName, resolvedPath, line, character);
|
|
4622
4967
|
}
|
|
4623
4968
|
try {
|
|
4624
4969
|
const references = await lspClient.findReferences(resolvedPath, line, character);
|
|
4625
4970
|
if (references.length === 0) {
|
|
4626
|
-
return
|
|
4627
|
-
|
|
4971
|
+
return `**Find References** \u2014 "${filePath}:${line + 1}:${character + 1}"
|
|
4972
|
+
No references found for symbol at this position.`;
|
|
4628
4973
|
}
|
|
4629
4974
|
const enrichedRefs = references.map((ref) => {
|
|
4630
4975
|
let lineContent = "";
|
|
@@ -4644,7 +4989,7 @@ async function handleFindReferences(params) {
|
|
|
4644
4989
|
}
|
|
4645
4990
|
const projectRoot = getProjectRoot();
|
|
4646
4991
|
const lines = [
|
|
4647
|
-
|
|
4992
|
+
`**Find References** \u2014 ${enrichedRefs.length} references found`,
|
|
4648
4993
|
`\u{1F4CD} File: ${path15.relative(projectRoot, resolvedPath)}:${line + 1}:${character + 1}`,
|
|
4649
4994
|
""
|
|
4650
4995
|
];
|
|
@@ -4660,7 +5005,7 @@ async function handleFindReferences(params) {
|
|
|
4660
5005
|
lines.push("\u{1F4A1} Use smart_file_picker to read specific files.");
|
|
4661
5006
|
return lines.join("\n");
|
|
4662
5007
|
} catch (err) {
|
|
4663
|
-
return
|
|
5008
|
+
return `**Find References** \u2014 error during lookup: ${err instanceof Error ? err.message : String(err)}`;
|
|
4664
5009
|
}
|
|
4665
5010
|
}
|
|
4666
5011
|
async function handleGoToDefinition(params) {
|
|
@@ -4675,19 +5020,22 @@ async function handleGoToDefinition(params) {
|
|
|
4675
5020
|
}
|
|
4676
5021
|
if (!lspClient.isAvailable()) {
|
|
4677
5022
|
sessionMemory.recordToolCall("lsp_query", { action: "def", filePath, line, character, fallback: "regex" });
|
|
5023
|
+
console.error(`[LSP] typescript-language-server not found. Using regex fallback for go_to_definition.`);
|
|
4678
5024
|
const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
|
|
4679
5025
|
if (!symbolName) {
|
|
4680
|
-
return
|
|
5026
|
+
return `**Go to Definition** \u2014 "${filePath}:${line + 1}:${character + 1}"
|
|
5027
|
+
Could not read symbol at this position.
|
|
4681
5028
|
|
|
4682
|
-
|
|
5029
|
+
Try selecting a smaller/cleaner position, or install typescript-language-server for better results:
|
|
5030
|
+
npm install typescript-language-server --save-dev`;
|
|
4683
5031
|
}
|
|
4684
5032
|
return fallbackGrepDefinition(symbolName);
|
|
4685
5033
|
}
|
|
4686
5034
|
try {
|
|
4687
5035
|
const definition = await lspClient.goToDefinition(resolvedPath, line, character);
|
|
4688
5036
|
if (!definition) {
|
|
4689
|
-
return
|
|
4690
|
-
|
|
5037
|
+
return `**Go to Definition** \u2014 "${filePath}:${line + 1}:${character + 1}"
|
|
5038
|
+
Cannot find definition for symbol at this position.`;
|
|
4691
5039
|
}
|
|
4692
5040
|
const projectRoot = getProjectRoot();
|
|
4693
5041
|
const relPath = path15.relative(projectRoot, definition.filePath);
|
|
@@ -4712,7 +5060,7 @@ async function handleGoToDefinition(params) {
|
|
|
4712
5060
|
];
|
|
4713
5061
|
return lines.join("\n");
|
|
4714
5062
|
} catch (err) {
|
|
4715
|
-
return
|
|
5063
|
+
return `**Go to Definition** \u2014 error during lookup: ${err instanceof Error ? err.message : String(err)}`;
|
|
4716
5064
|
}
|
|
4717
5065
|
}
|
|
4718
5066
|
async function handleRenameSymbol(params) {
|
|
@@ -4730,11 +5078,13 @@ async function handleRenameSymbol(params) {
|
|
|
4730
5078
|
}
|
|
4731
5079
|
if (!lspClient.isAvailable()) {
|
|
4732
5080
|
sessionMemory.recordToolCall("lsp_query", { action: "rename", filePath, line, character, newName, fallback: "none" });
|
|
4733
|
-
|
|
4734
|
-
Rename
|
|
4735
|
-
|
|
5081
|
+
console.error(`[LSP] typescript-language-server not found. Rename requires LSP server.`);
|
|
5082
|
+
return `**Rename Symbol** \u2014 cannot rename without language server.
|
|
5083
|
+
Rename needs typescript-language-server to track references across all files.
|
|
5084
|
+
|
|
5085
|
+
Install: npm install typescript-language-server --save-dev
|
|
4736
5086
|
|
|
4737
|
-
Meanwhile, use smart_grep to find all references, then precise_diff_editor
|
|
5087
|
+
Meanwhile, use smart_grep to find all references, then precise_diff_editor to rename manually.`;
|
|
4738
5088
|
}
|
|
4739
5089
|
try {
|
|
4740
5090
|
const result = await lspClient.renameSymbol(resolvedPath, line, character, newName);
|
|
@@ -4747,7 +5097,7 @@ Make sure:
|
|
|
4747
5097
|
\`\`\``;
|
|
4748
5098
|
}
|
|
4749
5099
|
if (result.changes.length === 0) {
|
|
4750
|
-
return "
|
|
5100
|
+
return "No changes needed.";
|
|
4751
5101
|
}
|
|
4752
5102
|
const projectRoot = getProjectRoot();
|
|
4753
5103
|
let totalEdits = 0;
|
|
@@ -4803,12 +5153,13 @@ async function handleGetTypeInfo(params) {
|
|
|
4803
5153
|
}
|
|
4804
5154
|
if (!lspClient.isAvailable()) {
|
|
4805
5155
|
sessionMemory.recordToolCall("lsp_query", { action: "type", filePath, line, character, fallback: "line-context" });
|
|
5156
|
+
console.error(`[LSP] typescript-language-server not found. Using file-read fallback for type info.`);
|
|
4806
5157
|
try {
|
|
4807
5158
|
const fileContent = fs15.readFileSync(resolvedPath, "utf-8");
|
|
4808
5159
|
const fileLines = fileContent.split("\n");
|
|
4809
5160
|
const targetLine = fileLines[line];
|
|
4810
5161
|
if (!targetLine) {
|
|
4811
|
-
return
|
|
5162
|
+
return `**Type Info** \u2014 Line ${line + 1} does not exist in "${filePath}".`;
|
|
4812
5163
|
}
|
|
4813
5164
|
const symbolName = extractSymbolAtPosition(resolvedPath, line, character);
|
|
4814
5165
|
const projectRoot = getProjectRoot();
|
|
@@ -4825,29 +5176,26 @@ async function handleGetTypeInfo(params) {
|
|
|
4825
5176
|
}
|
|
4826
5177
|
}
|
|
4827
5178
|
const resultLines = [
|
|
4828
|
-
`\u{1F4CB} **Type Info**
|
|
4829
|
-
`\u{
|
|
4830
|
-
symbolName ? `\u{1F524} **Symbol:** \`${symbolName}\`` : `\u26A0\uFE0F Could not extract symbol at this position.`,
|
|
5179
|
+
`\u{1F4CB} **Type Info** \u2014 \`${relPath}:${line + 1}:${character + 1}\``,
|
|
5180
|
+
symbolName ? `\u{1F524} **Symbol:** \`${symbolName}\`` : `Could not extract symbol at this position.`,
|
|
4831
5181
|
"",
|
|
4832
5182
|
"\u{1F4CE} Context:",
|
|
4833
5183
|
"```",
|
|
4834
5184
|
...contextLines,
|
|
4835
5185
|
"```",
|
|
4836
5186
|
"",
|
|
4837
|
-
|
|
4838
|
-
`\u{1F4A1} Install: npm install typescript-language-server --save-dev`,
|
|
4839
|
-
`\u{1F4A1} Use smart_grep or smart_file_picker to read the file for more context.`
|
|
5187
|
+
"Use smart_grep or smart_file_picker to read more context."
|
|
4840
5188
|
];
|
|
4841
5189
|
return resultLines.join("\n");
|
|
4842
5190
|
} catch (err) {
|
|
4843
|
-
return
|
|
5191
|
+
return `**Type Info** \u2014 could not read file for context: ${err instanceof Error ? err.message : String(err)}`;
|
|
4844
5192
|
}
|
|
4845
5193
|
}
|
|
4846
5194
|
try {
|
|
4847
5195
|
const hoverInfo = await lspClient.getTypeInfo(resolvedPath, line, character);
|
|
4848
5196
|
if (!hoverInfo || !hoverInfo.contents) {
|
|
4849
|
-
return
|
|
4850
|
-
|
|
5197
|
+
return `**Type Info** \u2014 "${filePath}:${line + 1}:${character + 1}"
|
|
5198
|
+
No type info for this position.`;
|
|
4851
5199
|
}
|
|
4852
5200
|
const projectRoot = getProjectRoot();
|
|
4853
5201
|
const relPath = path15.relative(projectRoot, resolvedPath);
|
|
@@ -4935,8 +5283,8 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
|
|
|
4935
5283
|
}
|
|
4936
5284
|
}
|
|
4937
5285
|
if (results.length === 0) {
|
|
4938
|
-
return
|
|
4939
|
-
|
|
5286
|
+
return `**Find References** \u2014 "${symbolName}"
|
|
5287
|
+
No references found. Symbol may not be used in other files.`;
|
|
4940
5288
|
}
|
|
4941
5289
|
const grouped = /* @__PURE__ */ new Map();
|
|
4942
5290
|
for (const r of results) {
|
|
@@ -4946,17 +5294,14 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
|
|
|
4946
5294
|
}
|
|
4947
5295
|
const projectRoot = getProjectRoot();
|
|
4948
5296
|
const lines = [
|
|
4949
|
-
|
|
5297
|
+
`**Find References** \u2014 ${results.length} references found`,
|
|
4950
5298
|
`\u{1F4CD} Symbol: "${symbolName}"`,
|
|
4951
|
-
`\u26A0\uFE0F Regex results may be less accurate than LSP (includes comments/strings).`,
|
|
4952
5299
|
""
|
|
4953
5300
|
];
|
|
4954
5301
|
if (grouped.size > 1) {
|
|
4955
5302
|
const fileList = [...grouped.keys()].map((f) => path15.relative(projectRoot, f)).join(", ");
|
|
4956
|
-
lines.push(
|
|
4957
|
-
lines.push(`
|
|
4958
|
-
lines.push(` If you intended only one scope, clarify which file or location you mean.`);
|
|
4959
|
-
lines.push(` \u{1F4A1} Install typescript-language-server for scope-aware disambiguation.`);
|
|
5303
|
+
lines.push(`**Ambiguity note:** Found matches across ${grouped.size} different files (${fileList}).`);
|
|
5304
|
+
lines.push(` Verify each match is the right scope \u2014 results may include same-named symbols.`);
|
|
4960
5305
|
lines.push("");
|
|
4961
5306
|
} else if (results.length > 1) {
|
|
4962
5307
|
const filePath = [...grouped.keys()][0];
|
|
@@ -4965,9 +5310,8 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
|
|
|
4965
5310
|
const minLine = Math.min(...matchLines);
|
|
4966
5311
|
const maxLine = Math.max(...matchLines);
|
|
4967
5312
|
if (maxLine - minLine > 15) {
|
|
4968
|
-
lines.push(
|
|
4969
|
-
lines.push(`
|
|
4970
|
-
lines.push(` \u{1F4A1} Verify each match is the intended symbol before editing.`);
|
|
5313
|
+
lines.push(`**Ambiguity note:** Found ${results.length} matches for "${symbolName}" spanning lines ${minLine}-${maxLine} in the same file.`);
|
|
5314
|
+
lines.push(` Verify each match is the intended symbol.`);
|
|
4971
5315
|
lines.push("");
|
|
4972
5316
|
}
|
|
4973
5317
|
}
|
|
@@ -4979,7 +5323,7 @@ async function fallbackGrepReferences(symbolName, _filePath, _line, _character)
|
|
|
4979
5323
|
}
|
|
4980
5324
|
lines.push("");
|
|
4981
5325
|
}
|
|
4982
|
-
|
|
5326
|
+
console.error(`[LSP] Regex fallback: found ${results.length} references for "${symbolName}".`);
|
|
4983
5327
|
return lines.join("\n");
|
|
4984
5328
|
} catch (err) {
|
|
4985
5329
|
return `Error in fallback grep references: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -5015,12 +5359,10 @@ async function fallbackGrepDefinition(symbolName) {
|
|
|
5015
5359
|
const projectRoot = getProjectRoot();
|
|
5016
5360
|
const relPath = path15.relative(projectRoot, file);
|
|
5017
5361
|
return [
|
|
5018
|
-
`\u{1F4CD} **Go to Definition
|
|
5362
|
+
`\u{1F4CD} **Go to Definition**`,
|
|
5019
5363
|
`\u{1F4C4} File: \`${relPath}\``,
|
|
5020
5364
|
`\u{1F4CF} Line: ${i + 1}`,
|
|
5021
|
-
`\u2514 ${trimmed.substring(0, 120)}
|
|
5022
|
-
"",
|
|
5023
|
-
`\u{1F4A1} Install typescript-language-server for more accurate results.`
|
|
5365
|
+
`\u2514 ${trimmed.substring(0, 120)}`
|
|
5024
5366
|
].join("\n");
|
|
5025
5367
|
}
|
|
5026
5368
|
}
|
|
@@ -5029,9 +5371,9 @@ async function fallbackGrepDefinition(symbolName) {
|
|
|
5029
5371
|
continue;
|
|
5030
5372
|
}
|
|
5031
5373
|
}
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5374
|
+
console.error(`[LSP] Regex fallback: no definition found for "${symbolName}".`);
|
|
5375
|
+
return `**Go to Definition** \u2014 "${symbolName}"
|
|
5376
|
+
Cannot find definition.`;
|
|
5035
5377
|
} catch (err) {
|
|
5036
5378
|
return `Error in fallback grep definition: ${err instanceof Error ? err.message : String(err)}`;
|
|
5037
5379
|
}
|
|
@@ -5361,819 +5703,22 @@ function registerAllTools(server) {
|
|
|
5361
5703
|
}
|
|
5362
5704
|
}
|
|
5363
5705
|
);
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
import path16 from "path";
|
|
5370
|
-
var ALL_CONFIG_TYPES = [
|
|
5371
|
-
"claude",
|
|
5372
|
-
"cursor",
|
|
5373
|
-
"windsurf",
|
|
5374
|
-
"copilot",
|
|
5375
|
-
"cline",
|
|
5376
|
-
"aider",
|
|
5377
|
-
"antigravity",
|
|
5378
|
-
"opencode",
|
|
5379
|
-
"codex",
|
|
5380
|
-
"qwen",
|
|
5381
|
-
"kiro",
|
|
5382
|
-
"openclaw",
|
|
5383
|
-
"codewhale"
|
|
5384
|
-
];
|
|
5385
|
-
var CONFIG_LABELS = {
|
|
5386
|
-
claude: "Claude Code (CLAUDE.md / plugin)",
|
|
5387
|
-
cursor: "Cursor (.cursor/rules/*.mdc)",
|
|
5388
|
-
windsurf: "Windsurf (.windsurfrules)",
|
|
5389
|
-
copilot: "GitHub Copilot Editor (AGENTS.md + Skill)",
|
|
5390
|
-
cline: "Cline (.clinerules/*.md)",
|
|
5391
|
-
aider: "Aider (CONVENTIONS.md via .aider.conf.yml)",
|
|
5392
|
-
antigravity: "Antigravity CLI (.agents/skills/)",
|
|
5393
|
-
opencode: "OpenCode (opencode.json)",
|
|
5394
|
-
codex: "Codex CLI (AGENTS.md + .codex/config.toml)",
|
|
5395
|
-
qwen: "Qwen Code (AGENTS.md + settings.json)",
|
|
5396
|
-
kiro: "Kiro (.kiro/steering/*.md)",
|
|
5397
|
-
openclaw: "OpenClaw (skills/)",
|
|
5398
|
-
codewhale: "CodeWhale (skills/ + .codewhale/mcp.json)"
|
|
5399
|
-
};
|
|
5400
|
-
function configFilePath(type) {
|
|
5401
|
-
switch (type) {
|
|
5402
|
-
case "claude":
|
|
5403
|
-
return "CLAUDE.md";
|
|
5404
|
-
case "cursor":
|
|
5405
|
-
return ".cursor/rules/kuma.mdc";
|
|
5406
|
-
case "windsurf":
|
|
5407
|
-
return ".windsurfrules";
|
|
5408
|
-
case "copilot":
|
|
5409
|
-
return "AGENTS.md";
|
|
5410
|
-
case "cline":
|
|
5411
|
-
return ".clinerules/kuma.md";
|
|
5412
|
-
case "aider":
|
|
5413
|
-
return "CONVENTIONS.md";
|
|
5414
|
-
case "antigravity":
|
|
5415
|
-
return ".agents/skills/kuma/SKILL.md";
|
|
5416
|
-
case "opencode":
|
|
5417
|
-
return "opencode.json";
|
|
5418
|
-
case "codex":
|
|
5419
|
-
return "AGENTS.md";
|
|
5420
|
-
case "qwen":
|
|
5421
|
-
return "AGENTS.md";
|
|
5422
|
-
case "kiro":
|
|
5423
|
-
return ".kiro/steering/kuma.md";
|
|
5424
|
-
case "openclaw":
|
|
5425
|
-
return "skills/kuma/SKILL.md";
|
|
5426
|
-
case "codewhale":
|
|
5427
|
-
return "skills/kuma/SKILL.md";
|
|
5428
|
-
}
|
|
5429
|
-
}
|
|
5430
|
-
var CORE_RULES = [
|
|
5431
|
-
"## AI Agent Usage Guidelines",
|
|
5432
|
-
"",
|
|
5433
|
-
"Kuma MCP tools are available. Use them correctly:",
|
|
5434
|
-
"",
|
|
5435
|
-
"### Code Search",
|
|
5436
|
-
"- Use the **smart_grep** tool to search code - NOT bash grep/ripgrep manually",
|
|
5437
|
-
"- smart_grep returns line numbers + context, caches results, respects .gitignore",
|
|
5438
|
-
`- **Example:** smart_grep({ query: "function handleAuth", extensions: ['ts'] })`,
|
|
5439
|
-
"",
|
|
5440
|
-
"### Reading Code",
|
|
5441
|
-
"- Use the **smart_file_picker** tool to read files with smart chunking",
|
|
5442
|
-
"- For large files, use startLine/endLine to read specific ranges",
|
|
5443
|
-
'- **Example:** smart_file_picker({ filePath: "src/index.ts", chunkStrategy: "outline" })',
|
|
5444
|
-
'- **Example:** smart_file_picker({ filePath: "src/index.ts", startLine: 10, endLine: 30 })',
|
|
5445
|
-
"",
|
|
5446
|
-
"### Editing Code",
|
|
5447
|
-
"- Use the **precise_diff_editor** tool to edit files (fuzzy matching + auto-backup)",
|
|
5448
|
-
"- DO NOT create Python/Node scripts to patch files; use precise_diff_editor directly",
|
|
5449
|
-
"- DO NOT use bash sed/cat/awk to modify source files",
|
|
5450
|
-
'- **Example:** precise_diff_editor({ filePath: "src/app.ts", edits: [{ searchBlock: "old code", replaceBlock: "new code" }] })',
|
|
5451
|
-
'- **Example:** precise_diff_editor({ filePath: "src/app.ts", dryRun: true, edits: [...] })',
|
|
5452
|
-
'- **Example:** precise_diff_editor({ filePath: "src/app.ts", action: "rollback" })',
|
|
5453
|
-
"",
|
|
5454
|
-
"### Creating Files",
|
|
5455
|
-
"- Use the **batch_file_writer** tool to create new files (up to 15 at once)",
|
|
5456
|
-
'- **Example:** batch_file_writer({ files: [{ filePath: "src/util.ts", content: "// code", instructions: "reason for creating" }] })',
|
|
5457
|
-
"",
|
|
5458
|
-
"### Running Tasks",
|
|
5459
|
-
"- Use the **execute_safe_test** tool for test/build/lint/typecheck",
|
|
5460
|
-
"- Always run typecheck after editing TypeScript files",
|
|
5461
|
-
'- **Example:** execute_safe_test({ task: "typecheck" })',
|
|
5462
|
-
'- **Example:** execute_safe_test({ task: "custom", customCommand: "npm run lint" })',
|
|
5463
|
-
"",
|
|
5464
|
-
"### Code Review",
|
|
5465
|
-
"- Use the **code_reviewer** tool after changes",
|
|
5466
|
-
"- Supports focus: correctness, security, performance, over-engineering",
|
|
5467
|
-
'- **Example:** code_reviewer({ focus: "security" })',
|
|
5468
|
-
'- **Example:** code_reviewer({ files: ["src/auth.ts"], format: "json" })',
|
|
5469
|
-
"",
|
|
5470
|
-
"### Git Operations",
|
|
5471
|
-
"- Use the **git_diff** tool for structured diff output",
|
|
5472
|
-
"- Use the **git_log** tool for commit history",
|
|
5473
|
-
"- **Example:** git_log({ maxCount: 5 })",
|
|
5474
|
-
"- **Example:** git_diff({ staged: true })",
|
|
5475
|
-
"",
|
|
5476
|
-
"### Session Awareness",
|
|
5477
|
-
"- Use the **kuma_reflect** tool to check on-track/drift/loops",
|
|
5478
|
-
"- Use the **kuma_guard** tool for deeper safety checks (anti-patterns, auto-detection)",
|
|
5479
|
-
"- Use the **get_session_memory** tool to recall session state",
|
|
5480
|
-
'- **Example:** kuma_reflect({ goal: "refactor auth" })',
|
|
5481
|
-
'- **Example:** kuma_guard({ check: "all", goal: "refactor auth" })',
|
|
5482
|
-
"",
|
|
5483
|
-
"### LSP / Code Intelligence",
|
|
5484
|
-
"- Use the **lsp_query** tool for go-to-definition, find references, type info",
|
|
5485
|
-
'- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "def" })',
|
|
5486
|
-
'- **Example:** lsp_query({ filePath: "src/index.ts", line: 5, character: 10, action: "refs" })',
|
|
5487
|
-
"",
|
|
5488
|
-
"### Static Analysis",
|
|
5489
|
-
"- Use the **static_analysis** tool to run ESLint/TSC/Prettier/Ruff",
|
|
5490
|
-
'- **Example:** static_analysis({ tool: "eslint", autoFix: true })',
|
|
5491
|
-
"",
|
|
5492
|
-
"### Project Structure",
|
|
5493
|
-
"- Use the **project_structure** tool to see project layout",
|
|
5494
|
-
"- **Example:** project_structure({ depth: 2, folderOnly: true })",
|
|
5495
|
-
"",
|
|
5496
|
-
"### Write Memory",
|
|
5497
|
-
"- Use the **write_memory** tool to persist decisions and glossary",
|
|
5498
|
-
'- **Example:** write_memory({ topic: "decisions", content: "## Reason for using X" })',
|
|
5499
|
-
"",
|
|
5500
|
-
"### General Rules",
|
|
5501
|
-
"- When you error, READ the error carefully before acting",
|
|
5502
|
-
"- After 3+ edits without running tests, stop and verify",
|
|
5503
|
-
"- If a tool fails, check the message - don't retry blindly",
|
|
5504
|
-
"- Detect conventions first with the **project_conventions** tool",
|
|
5505
|
-
"- **Example:** project_conventions({ forceRescan: true })"
|
|
5506
|
-
].join("\n");
|
|
5507
|
-
var KUMA_CORE_INSTRUCTIONS = CORE_RULES;
|
|
5508
|
-
function claudeTemplate() {
|
|
5509
|
-
return [
|
|
5510
|
-
"# Kuma AI Agent Guidelines",
|
|
5511
|
-
"",
|
|
5512
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5513
|
-
"",
|
|
5514
|
-
"## Workflow Pipeline",
|
|
5515
|
-
"",
|
|
5516
|
-
"For best results:",
|
|
5517
|
-
"1. **project_conventions** - detect stack",
|
|
5518
|
-
"2. **smart_grep** / **smart_file_picker** - understand code",
|
|
5519
|
-
"3. **precise_diff_editor** / **batch_file_writer** - make changes",
|
|
5520
|
-
"4. **execute_safe_test** - verify (typecheck + test)",
|
|
5521
|
-
"5. **code_reviewer** - review changes"
|
|
5522
|
-
].join("\n");
|
|
5523
|
-
}
|
|
5524
|
-
function cursorRulesTemplate() {
|
|
5525
|
-
return [
|
|
5526
|
-
"---",
|
|
5527
|
-
"description: Kuma MCP tool usage rules for AI coding agents",
|
|
5528
|
-
"alwaysApply: true",
|
|
5529
|
-
"---",
|
|
5530
|
-
"",
|
|
5531
|
-
"You are an expert engineer. Kuma MCP tools are available.",
|
|
5532
|
-
"",
|
|
5533
|
-
"## Critical Rules",
|
|
5534
|
-
"",
|
|
5535
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5536
|
-
"",
|
|
5537
|
-
"## NEVER",
|
|
5538
|
-
"- Never create Python/Node scripts to patch code",
|
|
5539
|
-
"- Never use bash sed/cat/awk to edit source files",
|
|
5540
|
-
"- Never run git push/git commit through bash"
|
|
5541
|
-
].join("\n");
|
|
5542
|
-
}
|
|
5543
|
-
function windsurfRulesTemplate() {
|
|
5544
|
-
return [
|
|
5545
|
-
"# Windsurf Cascade Rules with Kuma",
|
|
5546
|
-
"",
|
|
5547
|
-
KUMA_CORE_INSTRUCTIONS
|
|
5548
|
-
].join("\n");
|
|
5549
|
-
}
|
|
5550
|
-
function copilotTemplate() {
|
|
5551
|
-
return [
|
|
5552
|
-
"## GitHub Copilot Editor",
|
|
5553
|
-
"",
|
|
5554
|
-
"Kuma MCP tools are available. Use them correctly:",
|
|
5555
|
-
"",
|
|
5556
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5557
|
-
"",
|
|
5558
|
-
"### Copilot Editor-Specific",
|
|
5559
|
-
"- Copilot Editor reads AGENTS.md at project root for persistent instructions",
|
|
5560
|
-
"- Configure MCP servers via VS Code settings (cmd+shift+P \u2192 Developer: Reload Window after adding Kuma)",
|
|
5561
|
-
"- Use kuma_guard periodically to check for anti-patterns"
|
|
5562
|
-
].join("\n");
|
|
5563
|
-
}
|
|
5564
|
-
function clineRulesTemplate() {
|
|
5565
|
-
return [
|
|
5566
|
-
"---",
|
|
5567
|
-
"description: Kuma MCP tool usage rules for AI coding agents",
|
|
5568
|
-
"paths:",
|
|
5569
|
-
' - "*"',
|
|
5570
|
-
"---",
|
|
5571
|
-
"",
|
|
5572
|
-
KUMA_CORE_INSTRUCTIONS
|
|
5573
|
-
].join("\n");
|
|
5574
|
-
}
|
|
5575
|
-
function aiderTemplate() {
|
|
5576
|
-
return [
|
|
5577
|
-
"# Kuma MCP - Aider Coding Conventions",
|
|
5578
|
-
"",
|
|
5579
|
-
"These conventions are loaded by Aider via the `read:` field in .aider.conf.yml",
|
|
5580
|
-
"",
|
|
5581
|
-
KUMA_CORE_INSTRUCTIONS
|
|
5582
|
-
].join("\n");
|
|
5583
|
-
}
|
|
5584
|
-
function opencodeTemplate() {
|
|
5585
|
-
const config = {
|
|
5586
|
-
mcp: {
|
|
5587
|
-
kuma: {
|
|
5588
|
-
type: "local",
|
|
5589
|
-
command: ["npx", "-y", "@plumpslabs/kuma"],
|
|
5590
|
-
enabled: true
|
|
5591
|
-
}
|
|
5706
|
+
server.tool(
|
|
5707
|
+
"kuma_init",
|
|
5708
|
+
"**Call this FIRST** in every new session. Loads project context: rules from .kuma/init.md, memories from .kuma/memories/, and previous session state. After this, you can work without re-detecting conventions.",
|
|
5709
|
+
{
|
|
5710
|
+
projectRoot: z.string().optional().describe("Project root path (auto-detected if omitted)")
|
|
5592
5711
|
},
|
|
5593
|
-
|
|
5594
|
-
};
|
|
5595
|
-
const header = [
|
|
5596
|
-
"// Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
|
|
5597
|
-
"// OpenCode config with Kuma MCP tools. Edit opencode.json to customize.",
|
|
5598
|
-
""
|
|
5599
|
-
].join("\n");
|
|
5600
|
-
return header + JSON.stringify(config, null, 2) + "\n";
|
|
5601
|
-
}
|
|
5602
|
-
function codexTemplate() {
|
|
5603
|
-
return [
|
|
5604
|
-
"## Codex CLI (OpenAI)",
|
|
5605
|
-
"",
|
|
5606
|
-
"Kuma MCP tools are available. Use them correctly:",
|
|
5607
|
-
"",
|
|
5608
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5609
|
-
"",
|
|
5610
|
-
"### Codex-Specific",
|
|
5611
|
-
"- Codex uses cascading AGENTS.md files (global ~/.codex/AGENTS.md -> project AGENTS.md)",
|
|
5612
|
-
"- MCP config is in .codex/config.toml (auto-generated by kuma init)",
|
|
5613
|
-
"- Use kuma_guard periodically to check for anti-patterns"
|
|
5614
|
-
].join("\n");
|
|
5615
|
-
}
|
|
5616
|
-
function codexConfigTomlTemplate() {
|
|
5617
|
-
return [
|
|
5618
|
-
"# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
|
|
5619
|
-
"# Kuma MCP server config for Codex CLI",
|
|
5620
|
-
"",
|
|
5621
|
-
"[mcp_servers.kuma]",
|
|
5622
|
-
'command = "npx"',
|
|
5623
|
-
'args = ["-y", "@plumpslabs/kuma"]',
|
|
5624
|
-
""
|
|
5625
|
-
].join("\n");
|
|
5626
|
-
}
|
|
5627
|
-
function qwenTemplate() {
|
|
5628
|
-
return [
|
|
5629
|
-
"## Qwen Code",
|
|
5630
|
-
"",
|
|
5631
|
-
"Kuma MCP tools are available. Use them correctly:",
|
|
5632
|
-
"",
|
|
5633
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5634
|
-
"",
|
|
5635
|
-
"### Qwen-Specific",
|
|
5636
|
-
"- Qwen reads AGENTS.md at project root for persistent instructions",
|
|
5637
|
-
"- MCP config is in settings.json (auto-generated by kuma init)",
|
|
5638
|
-
"- Use kuma_guard periodically to check for anti-patterns"
|
|
5639
|
-
].join("\n");
|
|
5640
|
-
}
|
|
5641
|
-
function qwenSettingsTemplate() {
|
|
5642
|
-
const config = {
|
|
5643
|
-
mcpServers: {
|
|
5644
|
-
kuma: {
|
|
5645
|
-
command: "npx",
|
|
5646
|
-
args: ["-y", "@plumpslabs/kuma"],
|
|
5647
|
-
env: {}
|
|
5648
|
-
}
|
|
5649
|
-
}
|
|
5650
|
-
};
|
|
5651
|
-
return JSON.stringify(config, null, 2) + "\n";
|
|
5652
|
-
}
|
|
5653
|
-
function kiroRulesTemplate() {
|
|
5654
|
-
return [
|
|
5655
|
-
"---",
|
|
5656
|
-
"name: kuma-mcp",
|
|
5657
|
-
"description: Kuma safety toolkit - use smart_grep for search, precise_diff_editor for edits",
|
|
5658
|
-
"inclusion: always",
|
|
5659
|
-
"---",
|
|
5660
|
-
"",
|
|
5661
|
-
"# Kuma MCP - Kiro Steering",
|
|
5662
|
-
"",
|
|
5663
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5664
|
-
"",
|
|
5665
|
-
"## Kiro-Specific",
|
|
5666
|
-
"- Kiro reads steering files from .kiro/steering/ for project instructions",
|
|
5667
|
-
"- Configure MCP servers via IDE settings or global Kiro config",
|
|
5668
|
-
"- Use kuma_guard periodically to check for anti-patterns"
|
|
5669
|
-
].join("\n");
|
|
5670
|
-
}
|
|
5671
|
-
function openclawSkillTemplate() {
|
|
5672
|
-
return [
|
|
5673
|
-
"---",
|
|
5674
|
-
"name: kuma-mcp",
|
|
5675
|
-
"description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
|
|
5676
|
-
"---",
|
|
5677
|
-
"",
|
|
5678
|
-
"# Kuma MCP - OpenClaw Skill",
|
|
5679
|
-
"",
|
|
5680
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5681
|
-
"",
|
|
5682
|
-
"## OpenClaw-Specific",
|
|
5683
|
-
"- OpenClaw loads skills/ from workspace root or ~/.openclaw/skills for global",
|
|
5684
|
-
"- Configure Kuma MCP server via ~/.openclaw/openclaw.json or agents standard",
|
|
5685
|
-
"- Use kuma_guard periodically to check for anti-patterns",
|
|
5686
|
-
"",
|
|
5687
|
-
"## Verification",
|
|
5688
|
-
"- After edits, run execute_safe_test to verify no breakage",
|
|
5689
|
-
"- Use code_reviewer for correctness/security review",
|
|
5690
|
-
"- Check kuma_reflect to confirm on-track"
|
|
5691
|
-
].join("\n");
|
|
5692
|
-
}
|
|
5693
|
-
function codewhaleTemplate() {
|
|
5694
|
-
return [
|
|
5695
|
-
"---",
|
|
5696
|
-
"name: kuma-mcp",
|
|
5697
|
-
"description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
|
|
5698
|
-
"---",
|
|
5699
|
-
"",
|
|
5700
|
-
"# Kuma MCP - CodeWhale Skill",
|
|
5701
|
-
"",
|
|
5702
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5703
|
-
"",
|
|
5704
|
-
"## CodeWhale-Specific",
|
|
5705
|
-
"- CodeWhale loads SKILL.md from skills/ (workspace-local), .agents/skills/, or ~/.codewhale/skills/",
|
|
5706
|
-
"- MCP config is in ~/.codewhale/mcp.json or ~/.deepseek/mcp.json",
|
|
5707
|
-
"- Use kuma_guard periodically to check for anti-patterns",
|
|
5708
|
-
"",
|
|
5709
|
-
"## Verification",
|
|
5710
|
-
"- After edits, run execute_safe_test to verify no breakage",
|
|
5711
|
-
"- Use code_reviewer for correctness/security review",
|
|
5712
|
-
"- Check kuma_reflect to confirm on-track"
|
|
5713
|
-
].join("\n");
|
|
5714
|
-
}
|
|
5715
|
-
function antigravitySkillTemplate() {
|
|
5716
|
-
return [
|
|
5717
|
-
"---",
|
|
5718
|
-
"name: kuma-mcp",
|
|
5719
|
-
"description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
|
|
5720
|
-
"---",
|
|
5721
|
-
"",
|
|
5722
|
-
"# Kuma MCP - Antigravity Skill",
|
|
5723
|
-
"",
|
|
5724
|
-
KUMA_CORE_INSTRUCTIONS,
|
|
5725
|
-
"",
|
|
5726
|
-
"## Verification",
|
|
5727
|
-
"- After edits, run execute_safe_test to verify no breakage",
|
|
5728
|
-
"- Use code_reviewer for correctness/security review",
|
|
5729
|
-
"- Check kuma_reflect to confirm on-track"
|
|
5730
|
-
].join("\n");
|
|
5731
|
-
}
|
|
5732
|
-
function antigravityMcpConfigTemplate() {
|
|
5733
|
-
const config = {
|
|
5734
|
-
mcpServers: {
|
|
5735
|
-
kuma: {
|
|
5736
|
-
command: "npx",
|
|
5737
|
-
args: ["-y", "@plumpslabs/kuma"],
|
|
5738
|
-
env: {}
|
|
5739
|
-
}
|
|
5740
|
-
}
|
|
5741
|
-
};
|
|
5742
|
-
return JSON.stringify(config, null, 2) + "\n";
|
|
5743
|
-
}
|
|
5744
|
-
var TEMPLATES = {
|
|
5745
|
-
claude: claudeTemplate,
|
|
5746
|
-
cursor: cursorRulesTemplate,
|
|
5747
|
-
windsurf: windsurfRulesTemplate,
|
|
5748
|
-
copilot: copilotTemplate,
|
|
5749
|
-
cline: clineRulesTemplate,
|
|
5750
|
-
aider: aiderTemplate,
|
|
5751
|
-
antigravity: antigravitySkillTemplate,
|
|
5752
|
-
opencode: opencodeTemplate,
|
|
5753
|
-
codex: codexTemplate,
|
|
5754
|
-
qwen: qwenTemplate,
|
|
5755
|
-
kiro: kiroRulesTemplate,
|
|
5756
|
-
openclaw: openclawSkillTemplate,
|
|
5757
|
-
codewhale: codewhaleTemplate
|
|
5758
|
-
};
|
|
5759
|
-
var APPEND_SEPARATOR = "\n\n---\n_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_\n\n";
|
|
5760
|
-
function handleOpencodeSecondary(root, results) {
|
|
5761
|
-
const claudePath = path16.resolve(root, "CLAUDE.md");
|
|
5762
|
-
if (!fs16.existsSync(claudePath)) {
|
|
5763
|
-
try {
|
|
5764
|
-
fs16.writeFileSync(claudePath, [
|
|
5765
|
-
"# Kuma MCP - OpenCode Instructions",
|
|
5766
|
-
"",
|
|
5767
|
-
KUMA_CORE_INSTRUCTIONS
|
|
5768
|
-
].join("\n"), "utf-8");
|
|
5769
|
-
results.push({ type: "opencode", filePath: "CLAUDE.md", action: "created" });
|
|
5770
|
-
} catch (err) {
|
|
5771
|
-
results.push({
|
|
5772
|
-
type: "opencode",
|
|
5773
|
-
filePath: "CLAUDE.md",
|
|
5774
|
-
action: "error",
|
|
5775
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5776
|
-
});
|
|
5777
|
-
}
|
|
5778
|
-
}
|
|
5779
|
-
}
|
|
5780
|
-
function handleCodexSecondary(root, results) {
|
|
5781
|
-
const tomlPath = path16.resolve(root, ".codex/config.toml");
|
|
5782
|
-
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
5783
|
-
try {
|
|
5784
|
-
const dir = path16.dirname(tomlPath);
|
|
5785
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
5786
|
-
if (fs16.existsSync(tomlPath)) {
|
|
5787
|
-
const existingContent = fs16.readFileSync(tomlPath, "utf-8");
|
|
5788
|
-
if (existingContent.includes("kuma")) {
|
|
5789
|
-
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
5790
|
-
return;
|
|
5791
|
-
}
|
|
5792
|
-
fs16.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
5793
|
-
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
5794
|
-
} else {
|
|
5795
|
-
fs16.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
5796
|
-
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
5797
|
-
}
|
|
5798
|
-
} catch (err) {
|
|
5799
|
-
results.push({
|
|
5800
|
-
type: "codex",
|
|
5801
|
-
filePath: ".codex/config.toml",
|
|
5802
|
-
action: "error",
|
|
5803
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5804
|
-
});
|
|
5805
|
-
}
|
|
5806
|
-
}
|
|
5807
|
-
function handleQwenSecondary(root, results) {
|
|
5808
|
-
const settingsPath = path16.resolve(root, "settings.json");
|
|
5809
|
-
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
5810
|
-
try {
|
|
5811
|
-
if (fs16.existsSync(settingsPath)) {
|
|
5812
|
-
const existingContent = fs16.readFileSync(settingsPath, "utf-8");
|
|
5813
|
-
if (existingContent.includes("kuma")) {
|
|
5814
|
-
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
5815
|
-
try {
|
|
5816
|
-
const parsed = JSON.parse(existingContent);
|
|
5817
|
-
parsed.mcpServers = parsed.mcpServers || {};
|
|
5818
|
-
if (!parsed.mcpServers.kuma) {
|
|
5819
|
-
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
5820
|
-
fs16.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
5821
|
-
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
5822
|
-
return;
|
|
5823
|
-
}
|
|
5824
|
-
} catch {
|
|
5825
|
-
}
|
|
5826
|
-
}
|
|
5827
|
-
results.push({ type: "qwen", filePath: "settings.json", action: "skipped" });
|
|
5828
|
-
return;
|
|
5829
|
-
}
|
|
5712
|
+
async (params) => {
|
|
5830
5713
|
try {
|
|
5831
|
-
const
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
fs16.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
5836
|
-
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
5837
|
-
}
|
|
5838
|
-
} catch {
|
|
5839
|
-
}
|
|
5840
|
-
} else {
|
|
5841
|
-
fs16.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
5842
|
-
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
5843
|
-
}
|
|
5844
|
-
} catch (err) {
|
|
5845
|
-
results.push({
|
|
5846
|
-
type: "qwen",
|
|
5847
|
-
filePath: "settings.json",
|
|
5848
|
-
action: "error",
|
|
5849
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5850
|
-
});
|
|
5851
|
-
}
|
|
5852
|
-
}
|
|
5853
|
-
var AGENTS_MD_TYPES = ["codex", "qwen", "copilot"];
|
|
5854
|
-
function getAgentsMdHeader() {
|
|
5855
|
-
return [
|
|
5856
|
-
"# Kuma MCP - Combined Agent Instructions",
|
|
5857
|
-
"",
|
|
5858
|
-
"This file contains instructions for AI coding agents that read AGENTS.md.",
|
|
5859
|
-
"Each section applies to a specific agent. Unused sections can be safely removed.",
|
|
5860
|
-
"",
|
|
5861
|
-
"---",
|
|
5862
|
-
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_",
|
|
5863
|
-
""
|
|
5864
|
-
].join("\n");
|
|
5865
|
-
}
|
|
5866
|
-
function getCombinedAgentsMd(selectedTypes) {
|
|
5867
|
-
const sections = [getAgentsMdHeader()];
|
|
5868
|
-
const agentOrder = ["codex", "qwen", "copilot"];
|
|
5869
|
-
for (const t of agentOrder) {
|
|
5870
|
-
if (selectedTypes.has(t)) {
|
|
5871
|
-
sections.push(TEMPLATES[t]());
|
|
5872
|
-
}
|
|
5873
|
-
}
|
|
5874
|
-
return sections.join("\n\n---\n\n");
|
|
5875
|
-
}
|
|
5876
|
-
function handleAntigravityMcpConfig(root, results) {
|
|
5877
|
-
const mcpPath = path16.resolve(root, ".agents/mcp_config.json");
|
|
5878
|
-
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
5879
|
-
try {
|
|
5880
|
-
const mcpDir = path16.dirname(mcpPath);
|
|
5881
|
-
if (fs16.existsSync(mcpPath)) {
|
|
5882
|
-
const existingContent = fs16.readFileSync(mcpPath, "utf-8");
|
|
5883
|
-
if (existingContent.includes("kuma")) {
|
|
5884
|
-
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
5885
|
-
const trimmed = existingContent.trimEnd();
|
|
5886
|
-
if (trimmed.endsWith("}")) {
|
|
5887
|
-
const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
|
|
5888
|
-
fs16.writeFileSync(mcpPath, updated, "utf-8");
|
|
5889
|
-
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
5890
|
-
}
|
|
5891
|
-
}
|
|
5892
|
-
return;
|
|
5893
|
-
}
|
|
5894
|
-
const parsed = JSON.parse(existingContent);
|
|
5895
|
-
parsed.mcpServers = parsed.mcpServers || {};
|
|
5896
|
-
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
5897
|
-
fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
5898
|
-
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
5899
|
-
} else {
|
|
5900
|
-
if (!fs16.existsSync(mcpDir)) fs16.mkdirSync(mcpDir, { recursive: true });
|
|
5901
|
-
fs16.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
5902
|
-
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
5903
|
-
}
|
|
5904
|
-
} catch (err) {
|
|
5905
|
-
results.push({
|
|
5906
|
-
type: "antigravity",
|
|
5907
|
-
filePath: ".agents/mcp_config.json",
|
|
5908
|
-
action: "error",
|
|
5909
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5910
|
-
});
|
|
5911
|
-
}
|
|
5912
|
-
}
|
|
5913
|
-
function handleAiderSecondary(root, results) {
|
|
5914
|
-
const ymlPath = path16.resolve(root, ".aider.conf.yml");
|
|
5915
|
-
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
5916
|
-
try {
|
|
5917
|
-
const conventionsRef = "read: CONVENTIONS.md";
|
|
5918
|
-
if (fs16.existsSync(ymlPath)) {
|
|
5919
|
-
const existingContent = fs16.readFileSync(ymlPath, "utf-8");
|
|
5920
|
-
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
5921
|
-
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
5922
|
-
return;
|
|
5923
|
-
}
|
|
5924
|
-
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
5925
|
-
fs16.writeFileSync(ymlPath, newContent, "utf-8");
|
|
5926
|
-
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
5927
|
-
} else {
|
|
5928
|
-
const content = [
|
|
5929
|
-
"# Generated by Kuma MCP - https://github.com/plumpslabs/kuma",
|
|
5930
|
-
"# Aider will read CONVENTIONS.md for coding conventions",
|
|
5931
|
-
"",
|
|
5932
|
-
conventionsRef,
|
|
5933
|
-
""
|
|
5934
|
-
].join("\n");
|
|
5935
|
-
fs16.writeFileSync(ymlPath, content, "utf-8");
|
|
5936
|
-
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
5937
|
-
}
|
|
5938
|
-
} catch (err) {
|
|
5939
|
-
results.push({
|
|
5940
|
-
type: "aider",
|
|
5941
|
-
filePath: ".aider.conf.yml",
|
|
5942
|
-
action: "error",
|
|
5943
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5944
|
-
});
|
|
5945
|
-
}
|
|
5946
|
-
}
|
|
5947
|
-
function handleCopilotSecondary(root, results) {
|
|
5948
|
-
const skillPath = path16.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
5949
|
-
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
5950
|
-
try {
|
|
5951
|
-
const dir = path16.dirname(skillPath);
|
|
5952
|
-
const content = [
|
|
5953
|
-
"---",
|
|
5954
|
-
"name: kuma-mcp",
|
|
5955
|
-
"description: Kuma safety toolkit for AI coding agents. Use smart_grep for search, precise_diff_editor for edits, execute_safe_test for verification.",
|
|
5956
|
-
"---",
|
|
5957
|
-
"",
|
|
5958
|
-
"# Kuma MCP - Copilot Editor Skill",
|
|
5959
|
-
"",
|
|
5960
|
-
KUMA_CORE_INSTRUCTIONS
|
|
5961
|
-
].join("\n");
|
|
5962
|
-
if (fs16.existsSync(skillPath)) {
|
|
5963
|
-
const existingContent = fs16.readFileSync(skillPath, "utf-8");
|
|
5964
|
-
if (existingContent.includes("kuma")) {
|
|
5965
|
-
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
5966
|
-
return;
|
|
5967
|
-
}
|
|
5968
|
-
fs16.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
5969
|
-
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
5970
|
-
} else {
|
|
5971
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
5972
|
-
fs16.writeFileSync(skillPath, content, "utf-8");
|
|
5973
|
-
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
5974
|
-
}
|
|
5975
|
-
} catch (err) {
|
|
5976
|
-
results.push({
|
|
5977
|
-
type: "copilot",
|
|
5978
|
-
filePath: ".github/skills/kuma/SKILL.md",
|
|
5979
|
-
action: "error",
|
|
5980
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5981
|
-
});
|
|
5982
|
-
}
|
|
5983
|
-
}
|
|
5984
|
-
function handleOpenclawSecondary(root, results) {
|
|
5985
|
-
const mcpPath = path16.resolve(root, ".agents/mcp_config.json");
|
|
5986
|
-
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
5987
|
-
try {
|
|
5988
|
-
const dir = path16.dirname(mcpPath);
|
|
5989
|
-
if (fs16.existsSync(mcpPath)) {
|
|
5990
|
-
const existingContent = fs16.readFileSync(mcpPath, "utf-8");
|
|
5991
|
-
if (existingContent.includes("kuma")) return;
|
|
5992
|
-
const parsed = JSON.parse(existingContent);
|
|
5993
|
-
parsed.mcpServers = parsed.mcpServers || {};
|
|
5994
|
-
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
5995
|
-
fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
5996
|
-
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
5997
|
-
} else {
|
|
5998
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
5999
|
-
fs16.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
6000
|
-
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
6001
|
-
}
|
|
6002
|
-
} catch (err) {
|
|
6003
|
-
results.push({
|
|
6004
|
-
type: "openclaw",
|
|
6005
|
-
filePath: ".agents/mcp_config.json",
|
|
6006
|
-
action: "error",
|
|
6007
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6008
|
-
});
|
|
6009
|
-
}
|
|
6010
|
-
}
|
|
6011
|
-
function handleCodewhaleSecondary(root, results) {
|
|
6012
|
-
const mcpPath = path16.resolve(root, ".codewhale/mcp.json");
|
|
6013
|
-
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
6014
|
-
try {
|
|
6015
|
-
const dir = path16.dirname(mcpPath);
|
|
6016
|
-
if (fs16.existsSync(mcpPath)) {
|
|
6017
|
-
const existingContent = fs16.readFileSync(mcpPath, "utf-8");
|
|
6018
|
-
if (existingContent.includes("kuma")) return;
|
|
6019
|
-
const parsed = JSON.parse(existingContent);
|
|
6020
|
-
parsed.mcpServers = parsed.mcpServers || {};
|
|
6021
|
-
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
6022
|
-
fs16.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
6023
|
-
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
6024
|
-
} else {
|
|
6025
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
6026
|
-
const config = {
|
|
6027
|
-
mcpServers: {
|
|
6028
|
-
kuma: {
|
|
6029
|
-
command: "npx",
|
|
6030
|
-
args: ["-y", "@plumpslabs/kuma"],
|
|
6031
|
-
env: {}
|
|
6032
|
-
}
|
|
6033
|
-
}
|
|
6034
|
-
};
|
|
6035
|
-
fs16.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6036
|
-
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
6037
|
-
}
|
|
6038
|
-
} catch (err) {
|
|
6039
|
-
results.push({
|
|
6040
|
-
type: "codewhale",
|
|
6041
|
-
filePath: ".codewhale/mcp.json",
|
|
6042
|
-
action: "error",
|
|
6043
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6044
|
-
});
|
|
6045
|
-
}
|
|
6046
|
-
}
|
|
6047
|
-
function runInit(options) {
|
|
6048
|
-
const root = options.projectRoot ?? getProjectRoot();
|
|
6049
|
-
const selected = options.types.length > 0 ? options.types : ALL_CONFIG_TYPES;
|
|
6050
|
-
const results = [];
|
|
6051
|
-
const selectedSet = new Set(selected);
|
|
6052
|
-
const agentsMdSelected = AGENTS_MD_TYPES.filter((t) => selectedSet.has(t));
|
|
6053
|
-
let agentsMdHandled = false;
|
|
6054
|
-
for (const type of selected) {
|
|
6055
|
-
const relativePath = configFilePath(type);
|
|
6056
|
-
const fullPath = path16.resolve(root, relativePath);
|
|
6057
|
-
const getTemplate = TEMPLATES[type];
|
|
6058
|
-
try {
|
|
6059
|
-
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
6060
|
-
agentsMdHandled = true;
|
|
6061
|
-
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
6062
|
-
if (fs16.existsSync(fullPath)) {
|
|
6063
|
-
if (options.skipExisting) {
|
|
6064
|
-
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
6065
|
-
} else {
|
|
6066
|
-
const existingContent = fs16.readFileSync(fullPath, "utf-8");
|
|
6067
|
-
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
6068
|
-
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
6069
|
-
} else {
|
|
6070
|
-
fs16.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
6071
|
-
results.push({ type, filePath: relativePath, action: "appended" });
|
|
6072
|
-
}
|
|
6073
|
-
}
|
|
6074
|
-
} else {
|
|
6075
|
-
const dir = path16.dirname(fullPath);
|
|
6076
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
6077
|
-
fs16.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
6078
|
-
results.push({ type, filePath: relativePath, action: "created" });
|
|
6079
|
-
}
|
|
6080
|
-
if (selectedSet.has("codex")) handleCodexSecondary(root, results);
|
|
6081
|
-
if (selectedSet.has("qwen")) handleQwenSecondary(root, results);
|
|
6082
|
-
if (selectedSet.has("copilot")) handleCopilotSecondary(root, results);
|
|
6083
|
-
} else if (AGENTS_MD_TYPES.includes(type) && agentsMdHandled) {
|
|
6084
|
-
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
6085
|
-
continue;
|
|
6086
|
-
} else {
|
|
6087
|
-
const template = getTemplate();
|
|
6088
|
-
if (fs16.existsSync(fullPath)) {
|
|
6089
|
-
if (options.skipExisting) {
|
|
6090
|
-
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
6091
|
-
continue;
|
|
6092
|
-
}
|
|
6093
|
-
const existingContent = fs16.readFileSync(fullPath, "utf-8");
|
|
6094
|
-
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
6095
|
-
if (type === "antigravity") {
|
|
6096
|
-
handleAntigravityMcpConfig(root, results);
|
|
6097
|
-
} else if (type === "openclaw") {
|
|
6098
|
-
handleOpenclawSecondary(root, results);
|
|
6099
|
-
} else if (type === "codewhale") {
|
|
6100
|
-
handleCodewhaleSecondary(root, results);
|
|
6101
|
-
}
|
|
6102
|
-
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
6103
|
-
continue;
|
|
6104
|
-
}
|
|
6105
|
-
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
6106
|
-
fs16.writeFileSync(fullPath, newContent, "utf-8");
|
|
6107
|
-
results.push({ type, filePath: relativePath, action: "appended" });
|
|
6108
|
-
} else {
|
|
6109
|
-
const dir = path16.dirname(fullPath);
|
|
6110
|
-
if (!fs16.existsSync(dir)) {
|
|
6111
|
-
fs16.mkdirSync(dir, { recursive: true });
|
|
6112
|
-
}
|
|
6113
|
-
fs16.writeFileSync(fullPath, template, "utf-8");
|
|
6114
|
-
results.push({ type, filePath: relativePath, action: "created" });
|
|
6115
|
-
}
|
|
6116
|
-
if (type === "antigravity") {
|
|
6117
|
-
handleAntigravityMcpConfig(root, results);
|
|
6118
|
-
} else if (type === "openclaw") {
|
|
6119
|
-
handleOpenclawSecondary(root, results);
|
|
6120
|
-
} else if (type === "codewhale") {
|
|
6121
|
-
handleCodewhaleSecondary(root, results);
|
|
6122
|
-
} else if (type === "aider") {
|
|
6123
|
-
handleAiderSecondary(root, results);
|
|
6124
|
-
} else if (type === "opencode") {
|
|
6125
|
-
handleOpencodeSecondary(root, results);
|
|
6126
|
-
}
|
|
5714
|
+
const result = await handleKumaInit(params);
|
|
5715
|
+
return { content: [{ type: "text", text: result }] };
|
|
5716
|
+
} catch (err) {
|
|
5717
|
+
return { content: [{ type: "text", text: `Error in kuma_init: ${err}` }], isError: true };
|
|
6127
5718
|
}
|
|
6128
|
-
} catch (err) {
|
|
6129
|
-
results.push({
|
|
6130
|
-
type,
|
|
6131
|
-
filePath: relativePath,
|
|
6132
|
-
action: "error",
|
|
6133
|
-
error: err instanceof Error ? err.message : String(err)
|
|
6134
|
-
});
|
|
6135
|
-
}
|
|
6136
|
-
}
|
|
6137
|
-
return results;
|
|
6138
|
-
}
|
|
6139
|
-
function formatInitResults(results) {
|
|
6140
|
-
const lines = [
|
|
6141
|
-
"\u{1F43B} **Kuma Init - AI Agent Config Generator**",
|
|
6142
|
-
""
|
|
6143
|
-
];
|
|
6144
|
-
for (const r of results) {
|
|
6145
|
-
const label = CONFIG_LABELS[r.type];
|
|
6146
|
-
switch (r.action) {
|
|
6147
|
-
case "created":
|
|
6148
|
-
lines.push(" \u2705 " + label);
|
|
6149
|
-
lines.push(" \u2192 Created: " + r.filePath);
|
|
6150
|
-
break;
|
|
6151
|
-
case "appended":
|
|
6152
|
-
lines.push(" \u2795 " + label);
|
|
6153
|
-
lines.push(" \u2192 Appended to: " + r.filePath);
|
|
6154
|
-
break;
|
|
6155
|
-
case "skipped":
|
|
6156
|
-
lines.push(" \u23ED " + label);
|
|
6157
|
-
lines.push(" \u2192 Skipped (already has Kuma): " + r.filePath);
|
|
6158
|
-
break;
|
|
6159
|
-
case "error":
|
|
6160
|
-
lines.push(" \u274C " + label);
|
|
6161
|
-
lines.push(" \u2192 Error: " + (r.error ?? "unknown"));
|
|
6162
|
-
break;
|
|
6163
5719
|
}
|
|
6164
|
-
}
|
|
6165
|
-
const created = results.filter((r) => r.action === "created").length;
|
|
6166
|
-
const appended = results.filter((r) => r.action === "appended").length;
|
|
6167
|
-
const skipped = results.filter((r) => r.action === "skipped").length;
|
|
6168
|
-
const errors = results.filter((r) => r.action === "error").length;
|
|
6169
|
-
lines.push(
|
|
6170
|
-
"",
|
|
6171
|
-
"\u{1F4CA} Summary: " + created + " created, " + appended + " appended, " + skipped + " skipped, " + errors + " errors",
|
|
6172
|
-
"",
|
|
6173
|
-
"\u{1F4A1} Config files teach your AI how to use Kuma tools.",
|
|
6174
|
-
"\u{1F4A1} Run again to generate additional config files anytime."
|
|
6175
5720
|
);
|
|
6176
|
-
|
|
5721
|
+
console.error("[Manifest] Registered 18 tools.");
|
|
6177
5722
|
}
|
|
6178
5723
|
|
|
6179
5724
|
// src/index.ts
|
|
@@ -6259,24 +5804,40 @@ async function main() {
|
|
|
6259
5804
|
}
|
|
6260
5805
|
}
|
|
6261
5806
|
if (selectedTypes.length === 0) {
|
|
6262
|
-
console.error(
|
|
5807
|
+
console.error(
|
|
5808
|
+
"\u26A0\uFE0F No valid flags provided. Use --help to see options."
|
|
5809
|
+
);
|
|
6263
5810
|
process.exit(1);
|
|
6264
5811
|
}
|
|
6265
5812
|
}
|
|
6266
5813
|
}
|
|
6267
5814
|
const skipExisting = requestedFlags.includes("--skip-existing");
|
|
6268
|
-
const
|
|
6269
|
-
|
|
5815
|
+
const results = runInit({
|
|
5816
|
+
types: selectedTypes,
|
|
5817
|
+
projectRoot: process.cwd(),
|
|
5818
|
+
skipExisting
|
|
5819
|
+
});
|
|
6270
5820
|
const output = formatInitResults(results);
|
|
6271
5821
|
console.log(output);
|
|
6272
|
-
const
|
|
6273
|
-
const
|
|
6274
|
-
const matchaSkills =
|
|
6275
|
-
const matchaAgents =
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
5822
|
+
const fs16 = await import("fs");
|
|
5823
|
+
const path16 = await import("path");
|
|
5824
|
+
const matchaSkills = path16.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
5825
|
+
const matchaAgents = path16.resolve(
|
|
5826
|
+
process.cwd(),
|
|
5827
|
+
".agents/skills/matcha/SKILL.md"
|
|
5828
|
+
);
|
|
5829
|
+
const matchaCursor = path16.resolve(
|
|
5830
|
+
process.cwd(),
|
|
5831
|
+
".cursor/rules/matcha.mdc"
|
|
5832
|
+
);
|
|
5833
|
+
const matchaWindsurf = path16.resolve(
|
|
5834
|
+
process.cwd(),
|
|
5835
|
+
".windsurf/rules/matcha.md"
|
|
5836
|
+
);
|
|
5837
|
+
if (fs16.existsSync(matchaSkills) || fs16.existsSync(matchaAgents) || fs16.existsSync(matchaCursor) || fs16.existsSync(matchaWindsurf)) {
|
|
5838
|
+
console.error(
|
|
5839
|
+
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
5840
|
+
);
|
|
6280
5841
|
}
|
|
6281
5842
|
process.exit(0);
|
|
6282
5843
|
}
|
|
@@ -6284,6 +5845,22 @@ async function main() {
|
|
|
6284
5845
|
projectRoot: process.cwd(),
|
|
6285
5846
|
startTime: Date.now()
|
|
6286
5847
|
});
|
|
5848
|
+
(async () => {
|
|
5849
|
+
try {
|
|
5850
|
+
const { generateInitMdContent } = await import("./init-AMRMKI4X.js");
|
|
5851
|
+
const fs16 = await import("fs");
|
|
5852
|
+
const path16 = await import("path");
|
|
5853
|
+
const initMdPath = path16.resolve(process.cwd(), ".kuma/init.md");
|
|
5854
|
+
if (!fs16.existsSync(initMdPath)) {
|
|
5855
|
+
const kumaDir = path16.dirname(initMdPath);
|
|
5856
|
+
if (!fs16.existsSync(kumaDir)) fs16.mkdirSync(kumaDir, { recursive: true });
|
|
5857
|
+
fs16.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
5858
|
+
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
5859
|
+
}
|
|
5860
|
+
} catch (err) {
|
|
5861
|
+
console.error(`[${SERVER_NAME}] Failed to auto-generate .kuma/init.md: ${err}`);
|
|
5862
|
+
}
|
|
5863
|
+
})();
|
|
6287
5864
|
const server = new McpServer(
|
|
6288
5865
|
{
|
|
6289
5866
|
name: SERVER_NAME,
|
|
@@ -6303,6 +5880,12 @@ async function main() {
|
|
|
6303
5880
|
console.error(
|
|
6304
5881
|
`[${SERVER_NAME}] Session started: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
6305
5882
|
);
|
|
5883
|
+
console.error(
|
|
5884
|
+
`[${SERVER_NAME}] Kuma Core v3 \u2014 .kuma/init.md is the single source of truth`
|
|
5885
|
+
);
|
|
5886
|
+
console.error(
|
|
5887
|
+
`[${SERVER_NAME}] \u{1F9E0} Call kuma_init() at session start to load project context`
|
|
5888
|
+
);
|
|
6306
5889
|
await server.connect(transport);
|
|
6307
5890
|
console.error(
|
|
6308
5891
|
`[${SERVER_NAME}] Server connected via stdio. Waiting for requests...`
|
|
@@ -6311,18 +5894,42 @@ async function main() {
|
|
|
6311
5894
|
function interactiveSelect() {
|
|
6312
5895
|
const labels = [
|
|
6313
5896
|
{ type: "claude", label: "1) Claude Code (CLAUDE.md)" },
|
|
6314
|
-
{
|
|
5897
|
+
{
|
|
5898
|
+
type: "cursor",
|
|
5899
|
+
label: "2) Cursor (.cursor/rules/kuma.mdc)"
|
|
5900
|
+
},
|
|
6315
5901
|
{ type: "windsurf", label: "3) Windsurf (.windsurfrules)" },
|
|
6316
|
-
{
|
|
5902
|
+
{
|
|
5903
|
+
type: "copilot",
|
|
5904
|
+
label: "4) GitHub Copilot Editor (AGENTS.md + Skill)"
|
|
5905
|
+
},
|
|
6317
5906
|
{ type: "cline", label: "5) Cline (.clinerules/kuma.md)" },
|
|
6318
|
-
{
|
|
6319
|
-
|
|
5907
|
+
{
|
|
5908
|
+
type: "aider",
|
|
5909
|
+
label: "6) Aider (CONVENTIONS.md via .aider.conf.yml)"
|
|
5910
|
+
},
|
|
5911
|
+
{
|
|
5912
|
+
type: "antigravity",
|
|
5913
|
+
label: "7) Antigravity CLI (.agents/skills/)"
|
|
5914
|
+
},
|
|
6320
5915
|
{ type: "opencode", label: "8) OpenCode (opencode.json)" },
|
|
6321
|
-
{
|
|
6322
|
-
|
|
5916
|
+
{
|
|
5917
|
+
type: "codex",
|
|
5918
|
+
label: "9) Codex CLI - OpenAI (AGENTS.md + .codex/config.toml)"
|
|
5919
|
+
},
|
|
5920
|
+
{
|
|
5921
|
+
type: "qwen",
|
|
5922
|
+
label: "10) Qwen Code (AGENTS.md + settings.json)"
|
|
5923
|
+
},
|
|
6323
5924
|
{ type: "kiro", label: "11) Kiro (.kiro/steering/kuma.md)" },
|
|
6324
|
-
{
|
|
6325
|
-
|
|
5925
|
+
{
|
|
5926
|
+
type: "openclaw",
|
|
5927
|
+
label: "12) OpenClaw (skills/kuma/SKILL.md)"
|
|
5928
|
+
},
|
|
5929
|
+
{
|
|
5930
|
+
type: "codewhale",
|
|
5931
|
+
label: "13) CodeWhale (skills/kuma/SKILL.md + .codewhale/mcp.json)"
|
|
5932
|
+
}
|
|
6326
5933
|
];
|
|
6327
5934
|
const rl = readline.createInterface({
|
|
6328
5935
|
input: process.stdin,
|
|
@@ -6334,38 +5941,41 @@ function interactiveSelect() {
|
|
|
6334
5941
|
console.error(l.label);
|
|
6335
5942
|
}
|
|
6336
5943
|
console.error("");
|
|
6337
|
-
rl.question(
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
const typeMap = {
|
|
6346
|
-
1: "claude",
|
|
6347
|
-
2: "cursor",
|
|
6348
|
-
3: "windsurf",
|
|
6349
|
-
4: "copilot",
|
|
6350
|
-
5: "cline",
|
|
6351
|
-
6: "aider",
|
|
6352
|
-
7: "antigravity",
|
|
6353
|
-
8: "opencode",
|
|
6354
|
-
9: "codex",
|
|
6355
|
-
10: "qwen",
|
|
6356
|
-
11: "kiro",
|
|
6357
|
-
12: "openclaw",
|
|
6358
|
-
13: "codewhale"
|
|
6359
|
-
};
|
|
6360
|
-
const selected = [];
|
|
6361
|
-
for (const n of nums) {
|
|
6362
|
-
const t = typeMap[n];
|
|
6363
|
-
if (t && !selected.includes(t)) {
|
|
6364
|
-
selected.push(t);
|
|
5944
|
+
rl.question(
|
|
5945
|
+
"Enter numbers separated by space (e.g. '1 3 5'), or 'all': ",
|
|
5946
|
+
(answer) => {
|
|
5947
|
+
rl.close();
|
|
5948
|
+
const input = answer.trim().toLowerCase();
|
|
5949
|
+
if (input === "all") {
|
|
5950
|
+
resolve(ALL_CONFIG_TYPES);
|
|
5951
|
+
return;
|
|
6365
5952
|
}
|
|
5953
|
+
const nums = input.split(/\s+/).map(Number).filter((n) => n >= 1 && n <= 13);
|
|
5954
|
+
const typeMap = {
|
|
5955
|
+
1: "claude",
|
|
5956
|
+
2: "cursor",
|
|
5957
|
+
3: "windsurf",
|
|
5958
|
+
4: "copilot",
|
|
5959
|
+
5: "cline",
|
|
5960
|
+
6: "aider",
|
|
5961
|
+
7: "antigravity",
|
|
5962
|
+
8: "opencode",
|
|
5963
|
+
9: "codex",
|
|
5964
|
+
10: "qwen",
|
|
5965
|
+
11: "kiro",
|
|
5966
|
+
12: "openclaw",
|
|
5967
|
+
13: "codewhale"
|
|
5968
|
+
};
|
|
5969
|
+
const selected = [];
|
|
5970
|
+
for (const n of nums) {
|
|
5971
|
+
const t = typeMap[n];
|
|
5972
|
+
if (t && !selected.includes(t)) {
|
|
5973
|
+
selected.push(t);
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
resolve(selected);
|
|
6366
5977
|
}
|
|
6367
|
-
|
|
6368
|
-
});
|
|
5978
|
+
);
|
|
6369
5979
|
});
|
|
6370
5980
|
}
|
|
6371
5981
|
main().catch((err) => {
|