@plumpslabs/kuma 2.4.3 → 2.4.4
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/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +10 -0
- package/INSTALL.md +265 -0
- package/README.md +32 -20
- package/dist/index.js +2354 -542
- package/install.sh +129 -0
- package/mcp_config.json +11 -0
- package/package.json +8 -3
- package/packages/ide/studio/dist/index.js +25 -2
- package/packages/ide/studio/public/index.html +901 -1266
- package/plugin.json +18 -0
package/dist/index.js
CHANGED
|
@@ -302,7 +302,20 @@ function flushDb(db) {
|
|
|
302
302
|
}
|
|
303
303
|
const data = d.export();
|
|
304
304
|
const buffer = Buffer.from(data);
|
|
305
|
-
|
|
305
|
+
const tmpPath = path2.join(
|
|
306
|
+
kumaDir,
|
|
307
|
+
`${DB_FILENAME}.tmp.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString("hex")}`
|
|
308
|
+
);
|
|
309
|
+
fs2.writeFileSync(tmpPath, buffer);
|
|
310
|
+
try {
|
|
311
|
+
fs2.renameSync(tmpPath, dbPath);
|
|
312
|
+
} catch {
|
|
313
|
+
fs2.writeFileSync(dbPath, buffer);
|
|
314
|
+
try {
|
|
315
|
+
if (fs2.existsSync(tmpPath)) fs2.unlinkSync(tmpPath);
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
306
319
|
} catch (err) {
|
|
307
320
|
console.error(`[KumaDB] Failed to flush database: ${err}`);
|
|
308
321
|
}
|
|
@@ -347,7 +360,7 @@ function createSchema(db) {
|
|
|
347
360
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
348
361
|
source_id TEXT NOT NULL REFERENCES nodes(id),
|
|
349
362
|
target_id TEXT NOT NULL REFERENCES nodes(id),
|
|
350
|
-
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with','affects')),
|
|
363
|
+
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with','affects','explains')),
|
|
351
364
|
weight REAL DEFAULT 1.0,
|
|
352
365
|
metadata TEXT DEFAULT '{}',
|
|
353
366
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
@@ -356,13 +369,13 @@ function createSchema(db) {
|
|
|
356
369
|
try {
|
|
357
370
|
const schema = db.exec(`SELECT sql FROM sqlite_master WHERE type='table' AND name='edges'`);
|
|
358
371
|
const edgeSql = schema[0]?.values?.[0]?.[0] || "";
|
|
359
|
-
if (edgeSql.includes("
|
|
372
|
+
if (edgeSql.includes("explains") === false) {
|
|
360
373
|
db.run(`ALTER TABLE edges RENAME TO edges_old`);
|
|
361
374
|
db.run(`CREATE TABLE IF NOT EXISTS edges (
|
|
362
375
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
363
376
|
source_id TEXT NOT NULL REFERENCES nodes(id),
|
|
364
377
|
target_id TEXT NOT NULL REFERENCES nodes(id),
|
|
365
|
-
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with','affects')),
|
|
378
|
+
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with','affects','explains')),
|
|
366
379
|
weight REAL DEFAULT 1.0,
|
|
367
380
|
metadata TEXT DEFAULT '{}',
|
|
368
381
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
@@ -586,8 +599,10 @@ function createSchema(db) {
|
|
|
586
599
|
content_hash TEXT,
|
|
587
600
|
trigger_command TEXT,
|
|
588
601
|
status TEXT NOT NULL DEFAULT 'active'
|
|
589
|
-
CHECK(status IN ('active','resolved')),
|
|
590
|
-
last_verified_at INTEGER
|
|
602
|
+
CHECK(status IN ('candidate','active','verified','resolved','deprecated')),
|
|
603
|
+
last_verified_at INTEGER,
|
|
604
|
+
scope_package TEXT,
|
|
605
|
+
verified_by TEXT
|
|
591
606
|
);
|
|
592
607
|
`);
|
|
593
608
|
try {
|
|
@@ -605,6 +620,37 @@ function createSchema(db) {
|
|
|
605
620
|
if (!cols.includes("last_verified_at")) {
|
|
606
621
|
db.run(`ALTER TABLE known_gotchas ADD COLUMN last_verified_at INTEGER`);
|
|
607
622
|
}
|
|
623
|
+
if (!cols.includes("scope_package")) {
|
|
624
|
+
db.run(`ALTER TABLE known_gotchas ADD COLUMN scope_package TEXT`);
|
|
625
|
+
}
|
|
626
|
+
if (!cols.includes("verified_by")) {
|
|
627
|
+
db.run(`ALTER TABLE known_gotchas ADD COLUMN verified_by TEXT`);
|
|
628
|
+
}
|
|
629
|
+
const schema = db.exec(`SELECT sql FROM sqlite_master WHERE type='table' AND name='known_gotchas'`);
|
|
630
|
+
const gotchaSql = schema[0]?.values?.[0]?.[0] || "";
|
|
631
|
+
if (gotchaSql && !gotchaSql.includes("candidate")) {
|
|
632
|
+
db.run(`ALTER TABLE known_gotchas RENAME TO known_gotchas_old`);
|
|
633
|
+
db.run(`CREATE TABLE IF NOT EXISTS known_gotchas (
|
|
634
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
635
|
+
file_path TEXT NOT NULL,
|
|
636
|
+
description TEXT NOT NULL,
|
|
637
|
+
severity TEXT NOT NULL DEFAULT 'medium'
|
|
638
|
+
CHECK(severity IN ('low','medium','high','critical')),
|
|
639
|
+
workaround TEXT,
|
|
640
|
+
added_by TEXT DEFAULT 'agent',
|
|
641
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
642
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
643
|
+
content_hash TEXT,
|
|
644
|
+
trigger_command TEXT,
|
|
645
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
646
|
+
CHECK(status IN ('candidate','active','verified','resolved','deprecated')),
|
|
647
|
+
last_verified_at INTEGER,
|
|
648
|
+
scope_package TEXT,
|
|
649
|
+
verified_by TEXT
|
|
650
|
+
)`);
|
|
651
|
+
db.run(`INSERT OR IGNORE INTO known_gotchas (id, file_path, description, severity, workaround, added_by, created_at, updated_at, content_hash, trigger_command, status, last_verified_at) SELECT id, file_path, description, severity, workaround, added_by, created_at, updated_at, content_hash, trigger_command, status, last_verified_at FROM known_gotchas_old`);
|
|
652
|
+
db.run(`DROP TABLE known_gotchas_old`);
|
|
653
|
+
}
|
|
608
654
|
} catch {
|
|
609
655
|
}
|
|
610
656
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_gotchas_file ON known_gotchas(file_path)`);
|
|
@@ -1042,7 +1088,7 @@ async function runGarbageCollection() {
|
|
|
1042
1088
|
if (benchCount > 100) {
|
|
1043
1089
|
db.exec(`DELETE FROM benchmarks WHERE id IN (SELECT id FROM benchmarks ORDER BY created_at ASC LIMIT ${benchCount - 100})`);
|
|
1044
1090
|
}
|
|
1045
|
-
db.exec(`DELETE FROM known_gotchas WHERE
|
|
1091
|
+
db.exec(`DELETE FROM known_gotchas WHERE status = 'deprecated' AND updated_at < strftime('%s','now', '-30 days')`);
|
|
1046
1092
|
const costCount = getCount("SELECT COUNT(*) FROM cost_tracking");
|
|
1047
1093
|
if (costCount > 500) {
|
|
1048
1094
|
db.exec(`DELETE FROM cost_tracking WHERE id IN (SELECT id FROM cost_tracking ORDER BY created_at ASC LIMIT ${costCount - 500})`);
|
|
@@ -1260,6 +1306,7 @@ var init_sessionMemory = __esm({
|
|
|
1260
1306
|
init(config) {
|
|
1261
1307
|
const kumaDir = path3.join(config.projectRoot, ".kuma");
|
|
1262
1308
|
const sessionFile = path3.join(kumaDir, "memory.json");
|
|
1309
|
+
const currentGitBranch = this.detectCurrentBranch(config.projectRoot);
|
|
1263
1310
|
const oldFile = path3.join(kumaDir, ".kuma-memory.json");
|
|
1264
1311
|
if (fs3.existsSync(oldFile) && !fs3.existsSync(sessionFile)) {
|
|
1265
1312
|
try {
|
|
@@ -1280,10 +1327,15 @@ var init_sessionMemory = __esm({
|
|
|
1280
1327
|
try {
|
|
1281
1328
|
const raw = fs3.readFileSync(sessionFile, "utf-8");
|
|
1282
1329
|
const parsed = JSON.parse(raw);
|
|
1330
|
+
const storedBranch = parsed.currentBranch;
|
|
1331
|
+
const branchSwitched = Boolean(storedBranch && currentGitBranch && storedBranch !== currentGitBranch);
|
|
1283
1332
|
this.state = {
|
|
1284
1333
|
projectRoot: parsed.projectRoot || config.projectRoot,
|
|
1285
1334
|
startTime: parsed.startTime || config.startTime,
|
|
1286
1335
|
currentGoal: parsed.currentGoal || "",
|
|
1336
|
+
currentBranch: currentGitBranch || storedBranch || void 0,
|
|
1337
|
+
previousBranch: branchSwitched ? storedBranch : parsed.previousBranch,
|
|
1338
|
+
branchSwitched,
|
|
1287
1339
|
completedSteps: parsed.completedSteps || [],
|
|
1288
1340
|
modifiedFiles: new Map(parsed.modifiedFiles || []),
|
|
1289
1341
|
failedFiles: new Map(parsed.failedFiles || []),
|
|
@@ -1305,6 +1357,9 @@ var init_sessionMemory = __esm({
|
|
|
1305
1357
|
projectRoot: config.projectRoot,
|
|
1306
1358
|
startTime: config.startTime,
|
|
1307
1359
|
currentGoal: "",
|
|
1360
|
+
currentBranch: currentGitBranch || void 0,
|
|
1361
|
+
previousBranch: void 0,
|
|
1362
|
+
branchSwitched: false,
|
|
1308
1363
|
completedSteps: [],
|
|
1309
1364
|
modifiedFiles: /* @__PURE__ */ new Map(),
|
|
1310
1365
|
failedFiles: /* @__PURE__ */ new Map(),
|
|
@@ -1454,6 +1509,9 @@ No unresolved issues.`;
|
|
|
1454
1509
|
projectRoot: this.state.projectRoot,
|
|
1455
1510
|
startTime: this.state.startTime,
|
|
1456
1511
|
currentGoal: this.state.currentGoal,
|
|
1512
|
+
currentBranch: this.state.currentBranch,
|
|
1513
|
+
previousBranch: this.state.previousBranch,
|
|
1514
|
+
branchSwitched: this.state.branchSwitched,
|
|
1457
1515
|
completedSteps: this.state.completedSteps,
|
|
1458
1516
|
modifiedFiles: Array.from(this.state.modifiedFiles.entries()),
|
|
1459
1517
|
failedFiles: Array.from(this.state.failedFiles.entries()),
|
|
@@ -1464,7 +1522,18 @@ No unresolved issues.`;
|
|
|
1464
1522
|
recordings: this.state.recordings,
|
|
1465
1523
|
metrics: this.state.metrics
|
|
1466
1524
|
};
|
|
1467
|
-
|
|
1525
|
+
const finalPath = path3.join(kumaDir, "memory.json");
|
|
1526
|
+
const tmpPath = path3.join(kumaDir, `memory.json.tmp.${process.pid}.${Date.now()}`);
|
|
1527
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(serialized, null, 2), "utf-8");
|
|
1528
|
+
try {
|
|
1529
|
+
fs3.renameSync(tmpPath, finalPath);
|
|
1530
|
+
} catch {
|
|
1531
|
+
fs3.writeFileSync(finalPath, JSON.stringify(serialized, null, 2), "utf-8");
|
|
1532
|
+
try {
|
|
1533
|
+
if (fs3.existsSync(tmpPath)) fs3.unlinkSync(tmpPath);
|
|
1534
|
+
} catch {
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1468
1537
|
} catch (err) {
|
|
1469
1538
|
console.error(`[SessionMemory] Failed to save session: ${err}`);
|
|
1470
1539
|
}
|
|
@@ -2008,6 +2077,42 @@ No unresolved issues.`;
|
|
|
2008
2077
|
return null;
|
|
2009
2078
|
}
|
|
2010
2079
|
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Detect current git branch.
|
|
2082
|
+
*/
|
|
2083
|
+
detectCurrentBranch(cwd = process.cwd()) {
|
|
2084
|
+
try {
|
|
2085
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
2086
|
+
cwd,
|
|
2087
|
+
encoding: "utf-8",
|
|
2088
|
+
timeout: 3e3,
|
|
2089
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2090
|
+
}).trim() || null;
|
|
2091
|
+
} catch {
|
|
2092
|
+
return null;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Get the active git branch for the current session.
|
|
2097
|
+
*/
|
|
2098
|
+
getCurrentBranch() {
|
|
2099
|
+
this.ensureInit();
|
|
2100
|
+
return this.state.currentBranch || this.detectCurrentBranch(this.state.projectRoot);
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Check if git branch transitioned since previous session.
|
|
2104
|
+
*/
|
|
2105
|
+
getBranchTransition() {
|
|
2106
|
+
this.ensureInit();
|
|
2107
|
+
if (this.state.branchSwitched && this.state.previousBranch && this.state.currentBranch) {
|
|
2108
|
+
return {
|
|
2109
|
+
switched: true,
|
|
2110
|
+
from: this.state.previousBranch,
|
|
2111
|
+
to: this.state.currentBranch
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
return null;
|
|
2115
|
+
}
|
|
2011
2116
|
/**
|
|
2012
2117
|
* Get changes for selective undo, grouped by session.
|
|
2013
2118
|
*/
|
|
@@ -2033,6 +2138,13 @@ No unresolved issues.`;
|
|
|
2033
2138
|
});
|
|
2034
2139
|
|
|
2035
2140
|
// src/engine/kumaSelfHeal.ts
|
|
2141
|
+
var kumaSelfHeal_exports = {};
|
|
2142
|
+
__export(kumaSelfHeal_exports, {
|
|
2143
|
+
autoDeprecateStaleGotchas: () => autoDeprecateStaleGotchas,
|
|
2144
|
+
formatGotchaStalenessReport: () => formatGotchaStalenessReport,
|
|
2145
|
+
healOnQuery: () => healOnQuery,
|
|
2146
|
+
verifyGotchaStaleness: () => verifyGotchaStaleness
|
|
2147
|
+
});
|
|
2036
2148
|
import { execSync as execSync2 } from "child_process";
|
|
2037
2149
|
import fs4 from "fs";
|
|
2038
2150
|
import path4 from "path";
|
|
@@ -2093,6 +2205,119 @@ async function healOnQuery(filePaths) {
|
|
|
2093
2205
|
return { healed: 0 };
|
|
2094
2206
|
}
|
|
2095
2207
|
}
|
|
2208
|
+
async function verifyGotchaStaleness() {
|
|
2209
|
+
const db = await getDb();
|
|
2210
|
+
const stale = [];
|
|
2211
|
+
try {
|
|
2212
|
+
const rows = db.exec(`
|
|
2213
|
+
SELECT id, metadata FROM nodes WHERE type = 'gotcha'
|
|
2214
|
+
`);
|
|
2215
|
+
if (rows.length === 0) return stale;
|
|
2216
|
+
const columns = rows[0].columns;
|
|
2217
|
+
const filePathIdx = columns.indexOf("metadata");
|
|
2218
|
+
for (const row of rows[0].values) {
|
|
2219
|
+
const nodeId2 = row[0];
|
|
2220
|
+
let metadata = {};
|
|
2221
|
+
try {
|
|
2222
|
+
metadata = JSON.parse(row[filePathIdx]);
|
|
2223
|
+
} catch {
|
|
2224
|
+
}
|
|
2225
|
+
const filePath = metadata.file_path;
|
|
2226
|
+
if (!filePath || filePath.startsWith("search::") || filePath.startsWith("api_route::")) continue;
|
|
2227
|
+
const fullPath = path4.join(getProjectRoot(), filePath);
|
|
2228
|
+
if (!fs4.existsSync(fullPath)) {
|
|
2229
|
+
stale.push({ gotchaId: nodeId2, file_path: filePath, issue: "file_missing" });
|
|
2230
|
+
} else {
|
|
2231
|
+
const desc = metadata.description || "";
|
|
2232
|
+
const symbolMatch = desc.match(/\b(function|class|method|const|let|var|export)\s+(\w+)/i);
|
|
2233
|
+
if (symbolMatch) {
|
|
2234
|
+
const symbolName = symbolMatch[2];
|
|
2235
|
+
try {
|
|
2236
|
+
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
2237
|
+
if (!content.includes(symbolName)) {
|
|
2238
|
+
stale.push({ gotchaId: nodeId2, file_path: filePath, issue: "symbol_missing" });
|
|
2239
|
+
}
|
|
2240
|
+
} catch {
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
return stale;
|
|
2248
|
+
}
|
|
2249
|
+
function formatGotchaStalenessReport(stale) {
|
|
2250
|
+
if (stale.length === 0) {
|
|
2251
|
+
return "\u2705 **Gotcha Staleness Check** \u2014 All gotcha references are valid.";
|
|
2252
|
+
}
|
|
2253
|
+
const fileMissing = stale.filter((s) => s.issue === "file_missing");
|
|
2254
|
+
const symbolMissing = stale.filter((s) => s.issue === "symbol_missing");
|
|
2255
|
+
const lines = [
|
|
2256
|
+
`\u26A0\uFE0F **Gotcha Staleness Report**`,
|
|
2257
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
2258
|
+
"",
|
|
2259
|
+
`\u{1F4CA} ${stale.length} stale gotcha${stale.length > 1 ? "s" : ""} found`,
|
|
2260
|
+
fileMissing.length > 0 ? ` \u{1F5D1}\uFE0F ${fileMissing.length} reference${fileMissing.length > 1 ? "s" : ""} deleted file` : "",
|
|
2261
|
+
symbolMissing.length > 0 ? ` \u{1F50D} ${symbolMissing.length} reference${symbolMissing.length > 1 ? "s" : ""} missing symbol` : "",
|
|
2262
|
+
""
|
|
2263
|
+
];
|
|
2264
|
+
for (const s of fileMissing.slice(0, 5)) {
|
|
2265
|
+
lines.push(` \u{1F5D1}\uFE0F \`${s.gotchaId}\` \u2192 file missing: \`${s.file_path}\``);
|
|
2266
|
+
}
|
|
2267
|
+
for (const s of symbolMissing.slice(0, 5)) {
|
|
2268
|
+
lines.push(` \u{1F50D} \`${s.gotchaId}\` \u2192 symbol missing in \`${s.file_path}\``);
|
|
2269
|
+
}
|
|
2270
|
+
if (stale.length > 5) lines.push(` ... and ${stale.length - 5} more`);
|
|
2271
|
+
lines.push("");
|
|
2272
|
+
lines.push("\u{1F4A1} Use `kuma_memory({ action: 'delete_node', target: '<gotchaId>' })` to remove obsolete gotchas.");
|
|
2273
|
+
return lines.join("\n");
|
|
2274
|
+
}
|
|
2275
|
+
async function autoDeprecateStaleGotchas() {
|
|
2276
|
+
const root = getProjectRoot();
|
|
2277
|
+
const details = [];
|
|
2278
|
+
let deprecated = 0;
|
|
2279
|
+
let resolved = 0;
|
|
2280
|
+
try {
|
|
2281
|
+
const db = await getDb();
|
|
2282
|
+
const stmt = db.prepare(`
|
|
2283
|
+
SELECT id, file_path, description, status, content_hash
|
|
2284
|
+
FROM known_gotchas
|
|
2285
|
+
WHERE status IN ('candidate', 'active', 'verified')
|
|
2286
|
+
`);
|
|
2287
|
+
const rows = [];
|
|
2288
|
+
while (stmt.step()) {
|
|
2289
|
+
rows.push(stmt.getAsObject());
|
|
2290
|
+
}
|
|
2291
|
+
stmt.free();
|
|
2292
|
+
for (const g of rows) {
|
|
2293
|
+
if (!g.file_path || g.file_path.startsWith("search::") || g.file_path.startsWith("api_route::")) continue;
|
|
2294
|
+
const fullPath = path4.join(root, g.file_path);
|
|
2295
|
+
if (!fs4.existsSync(fullPath)) {
|
|
2296
|
+
const renamed = findRenamedPath(g.file_path);
|
|
2297
|
+
if (renamed) {
|
|
2298
|
+
db.run(
|
|
2299
|
+
`UPDATE known_gotchas SET file_path = ?, updated_at = strftime('%s','now') WHERE id = ?`,
|
|
2300
|
+
[renamed, g.id]
|
|
2301
|
+
);
|
|
2302
|
+
details.push(`\u{1F504} Migrated gotcha #${g.id} to renamed file: ${renamed}`);
|
|
2303
|
+
} else {
|
|
2304
|
+
db.run(
|
|
2305
|
+
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'self_heal:file_deleted', updated_at = strftime('%s','now') WHERE id = ?`,
|
|
2306
|
+
[g.id]
|
|
2307
|
+
);
|
|
2308
|
+
deprecated++;
|
|
2309
|
+
details.push(`\u{1F5D1}\uFE0F Deprecated gotcha #${g.id} (${g.file_path}) \u2014 file deleted`);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
if (deprecated > 0 || details.length > 0) {
|
|
2314
|
+
saveDb();
|
|
2315
|
+
}
|
|
2316
|
+
} catch (err) {
|
|
2317
|
+
details.push(`\u26A0\uFE0F Self-heal gotcha check encountered an issue: ${err}`);
|
|
2318
|
+
}
|
|
2319
|
+
return { deprecated, resolved, details };
|
|
2320
|
+
}
|
|
2096
2321
|
var init_kumaSelfHeal = __esm({
|
|
2097
2322
|
"src/engine/kumaSelfHeal.ts"() {
|
|
2098
2323
|
"use strict";
|
|
@@ -2486,6 +2711,564 @@ var init_kumaDriftDetector = __esm({
|
|
|
2486
2711
|
}
|
|
2487
2712
|
});
|
|
2488
2713
|
|
|
2714
|
+
// src/engine/workspaceIntelligence.ts
|
|
2715
|
+
var workspaceIntelligence_exports = {};
|
|
2716
|
+
__export(workspaceIntelligence_exports, {
|
|
2717
|
+
clearWorkspaceCache: () => clearWorkspaceCache,
|
|
2718
|
+
findPackageForFile: () => findPackageForFile,
|
|
2719
|
+
formatWorkspaceMap: () => formatWorkspaceMap,
|
|
2720
|
+
getAffectedPackages: () => getAffectedPackages,
|
|
2721
|
+
getReverseDependencies: () => getReverseDependencies,
|
|
2722
|
+
getWorkspaceInfo: () => getWorkspaceInfo
|
|
2723
|
+
});
|
|
2724
|
+
import fs6 from "fs";
|
|
2725
|
+
import path7 from "path";
|
|
2726
|
+
import fastGlob from "fast-glob";
|
|
2727
|
+
function clearWorkspaceCache() {
|
|
2728
|
+
_cachedWorkspaceInfo = null;
|
|
2729
|
+
_cachedRoot = null;
|
|
2730
|
+
}
|
|
2731
|
+
function parsePnpmWorkspaceGlobs(content) {
|
|
2732
|
+
const globs = [];
|
|
2733
|
+
const lines = content.split("\n");
|
|
2734
|
+
let inPackages = false;
|
|
2735
|
+
for (const line of lines) {
|
|
2736
|
+
const trimmed = line.trim();
|
|
2737
|
+
if (trimmed.startsWith("packages:")) {
|
|
2738
|
+
inPackages = true;
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
if (inPackages) {
|
|
2742
|
+
if (trimmed.startsWith("-")) {
|
|
2743
|
+
const cleaned = trimmed.replace(/^-\s*['"]?/, "").replace(/['"]?\s*$/, "").trim();
|
|
2744
|
+
if (cleaned && !cleaned.startsWith("#")) {
|
|
2745
|
+
globs.push(cleaned);
|
|
2746
|
+
}
|
|
2747
|
+
} else if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("-")) {
|
|
2748
|
+
break;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
return globs.length > 0 ? globs : ["packages/*"];
|
|
2753
|
+
}
|
|
2754
|
+
function detectWorkspaceType(root) {
|
|
2755
|
+
const pnpmWs = path7.join(root, "pnpm-workspace.yaml");
|
|
2756
|
+
if (fs6.existsSync(pnpmWs)) {
|
|
2757
|
+
try {
|
|
2758
|
+
const content = fs6.readFileSync(pnpmWs, "utf-8");
|
|
2759
|
+
return { type: "pnpm", globs: parsePnpmWorkspaceGlobs(content) };
|
|
2760
|
+
} catch {
|
|
2761
|
+
return { type: "pnpm", globs: ["packages/*"] };
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
const rootPkgPath = path7.join(root, "package.json");
|
|
2765
|
+
if (fs6.existsSync(rootPkgPath)) {
|
|
2766
|
+
try {
|
|
2767
|
+
const pkg = JSON.parse(fs6.readFileSync(rootPkgPath, "utf-8"));
|
|
2768
|
+
if (pkg.workspaces) {
|
|
2769
|
+
let globs = [];
|
|
2770
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
2771
|
+
globs = pkg.workspaces;
|
|
2772
|
+
} else if (Array.isArray(pkg.workspaces.packages)) {
|
|
2773
|
+
globs = pkg.workspaces.packages;
|
|
2774
|
+
}
|
|
2775
|
+
const hasYarnLock = fs6.existsSync(path7.join(root, "yarn.lock"));
|
|
2776
|
+
return { type: hasYarnLock ? "yarn" : "npm", globs };
|
|
2777
|
+
}
|
|
2778
|
+
} catch {
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
const cargoToml = path7.join(root, "Cargo.toml");
|
|
2782
|
+
if (fs6.existsSync(cargoToml)) {
|
|
2783
|
+
try {
|
|
2784
|
+
const content = fs6.readFileSync(cargoToml, "utf-8");
|
|
2785
|
+
if (content.includes("[workspace]")) {
|
|
2786
|
+
const memberMatch = content.match(/members\s*=\s*\[([^\]]+)\]/);
|
|
2787
|
+
if (memberMatch) {
|
|
2788
|
+
const members = memberMatch[1].split(",").map((s) => s.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
2789
|
+
return { type: "cargo", globs: members };
|
|
2790
|
+
}
|
|
2791
|
+
return { type: "cargo", globs: ["crates/*", "packages/*"] };
|
|
2792
|
+
}
|
|
2793
|
+
} catch {
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
const goWork = path7.join(root, "go.work");
|
|
2797
|
+
if (fs6.existsSync(goWork)) {
|
|
2798
|
+
try {
|
|
2799
|
+
const content = fs6.readFileSync(goWork, "utf-8");
|
|
2800
|
+
const uses = Array.from(content.matchAll(/\buse\s+(?:\(\s*([\s\S]*?)\s*\)|([^\s\n]+))/g));
|
|
2801
|
+
const dirs = [];
|
|
2802
|
+
for (const m of uses) {
|
|
2803
|
+
if (m[1]) {
|
|
2804
|
+
dirs.push(...m[1].split(/\s+/).filter(Boolean));
|
|
2805
|
+
} else if (m[2]) {
|
|
2806
|
+
dirs.push(m[2].trim());
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
return { type: "go", globs: dirs.length > 0 ? dirs : ["./*"] };
|
|
2810
|
+
} catch {
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
return { type: "standalone", globs: [] };
|
|
2814
|
+
}
|
|
2815
|
+
function loadNodePackage(pkgJsonPath, root, isRoot = false) {
|
|
2816
|
+
try {
|
|
2817
|
+
const content = fs6.readFileSync(pkgJsonPath, "utf-8");
|
|
2818
|
+
const pkg = JSON.parse(content);
|
|
2819
|
+
const absPath = path7.dirname(pkgJsonPath);
|
|
2820
|
+
const relPath = path7.relative(root, absPath) || ".";
|
|
2821
|
+
const name = pkg.name || (isRoot ? path7.basename(root) : path7.basename(relPath));
|
|
2822
|
+
const dependencies = Object.keys(pkg.dependencies || {});
|
|
2823
|
+
const devDependencies = Object.keys(pkg.devDependencies || {});
|
|
2824
|
+
const scripts = pkg.scripts || {};
|
|
2825
|
+
let testCommand = void 0;
|
|
2826
|
+
if (scripts.test) {
|
|
2827
|
+
testCommand = scripts.test;
|
|
2828
|
+
}
|
|
2829
|
+
const entryPoints = [];
|
|
2830
|
+
if (pkg.main) entryPoints.push(path7.normalize(pkg.main));
|
|
2831
|
+
if (pkg.module) entryPoints.push(path7.normalize(pkg.module));
|
|
2832
|
+
if (pkg.exports) {
|
|
2833
|
+
if (typeof pkg.exports === "string") {
|
|
2834
|
+
entryPoints.push(path7.normalize(pkg.exports));
|
|
2835
|
+
} else if (typeof pkg.exports === "object") {
|
|
2836
|
+
for (const val of Object.values(pkg.exports)) {
|
|
2837
|
+
if (typeof val === "string") entryPoints.push(path7.normalize(val));
|
|
2838
|
+
else if (val && typeof val === "object") {
|
|
2839
|
+
const def = val.default || val.import;
|
|
2840
|
+
if (typeof def === "string") entryPoints.push(path7.normalize(def));
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
if (entryPoints.length === 0) {
|
|
2846
|
+
for (const candidate of ["src/index.ts", "src/index.js", "src/main.ts", "src/main.js", "index.ts", "index.js"]) {
|
|
2847
|
+
if (fs6.existsSync(path7.join(absPath, candidate))) {
|
|
2848
|
+
entryPoints.push(candidate);
|
|
2849
|
+
break;
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
return {
|
|
2854
|
+
name,
|
|
2855
|
+
path: relPath,
|
|
2856
|
+
absPath,
|
|
2857
|
+
version: pkg.version,
|
|
2858
|
+
description: pkg.description,
|
|
2859
|
+
scripts,
|
|
2860
|
+
dependencies,
|
|
2861
|
+
devDependencies,
|
|
2862
|
+
internalDependencies: [],
|
|
2863
|
+
// populated later
|
|
2864
|
+
testCommand,
|
|
2865
|
+
entryPoints,
|
|
2866
|
+
isRoot
|
|
2867
|
+
};
|
|
2868
|
+
} catch {
|
|
2869
|
+
return null;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
async function getWorkspaceInfo(root, forceRefresh = false) {
|
|
2873
|
+
const projectRoot = root || getProjectRoot();
|
|
2874
|
+
if (_cachedWorkspaceInfo && _cachedRoot === projectRoot && !forceRefresh) {
|
|
2875
|
+
return _cachedWorkspaceInfo;
|
|
2876
|
+
}
|
|
2877
|
+
const { type, globs } = detectWorkspaceType(projectRoot);
|
|
2878
|
+
const packages = [];
|
|
2879
|
+
const packageMap = /* @__PURE__ */ new Map();
|
|
2880
|
+
const rootPkgPath = path7.join(projectRoot, "package.json");
|
|
2881
|
+
if (fs6.existsSync(rootPkgPath)) {
|
|
2882
|
+
const rootPkg = loadNodePackage(rootPkgPath, projectRoot, true);
|
|
2883
|
+
if (rootPkg) {
|
|
2884
|
+
packages.push(rootPkg);
|
|
2885
|
+
packageMap.set(rootPkg.name, rootPkg);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
if (globs.length > 0 && type !== "standalone") {
|
|
2889
|
+
const patterns = globs.map((g) => {
|
|
2890
|
+
if (g.endsWith("package.json")) return g;
|
|
2891
|
+
const clean = g.replace(/\/+$/, "");
|
|
2892
|
+
return `${clean}/package.json`;
|
|
2893
|
+
});
|
|
2894
|
+
try {
|
|
2895
|
+
const matches = await fastGlob(patterns, {
|
|
2896
|
+
cwd: projectRoot,
|
|
2897
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.kuma/**"],
|
|
2898
|
+
onlyFiles: true
|
|
2899
|
+
});
|
|
2900
|
+
for (const match of matches) {
|
|
2901
|
+
const fullPath = path7.join(projectRoot, match);
|
|
2902
|
+
if (path7.normalize(fullPath) === path7.normalize(rootPkgPath)) continue;
|
|
2903
|
+
const pkg = loadNodePackage(fullPath, projectRoot, false);
|
|
2904
|
+
if (pkg) {
|
|
2905
|
+
packages.push(pkg);
|
|
2906
|
+
packageMap.set(pkg.name, pkg);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
} catch {
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
const allNames = new Set(packages.map((p) => p.name));
|
|
2913
|
+
for (const pkg of packages) {
|
|
2914
|
+
const combined = /* @__PURE__ */ new Set([...pkg.dependencies, ...pkg.devDependencies]);
|
|
2915
|
+
pkg.internalDependencies = Array.from(combined).filter((dep) => allNames.has(dep) && dep !== pkg.name);
|
|
2916
|
+
}
|
|
2917
|
+
const isWorkspace = packages.length > 1 || type !== "standalone";
|
|
2918
|
+
_cachedWorkspaceInfo = {
|
|
2919
|
+
isWorkspace,
|
|
2920
|
+
type,
|
|
2921
|
+
root: projectRoot,
|
|
2922
|
+
packages,
|
|
2923
|
+
packageMap
|
|
2924
|
+
};
|
|
2925
|
+
_cachedRoot = projectRoot;
|
|
2926
|
+
return _cachedWorkspaceInfo;
|
|
2927
|
+
}
|
|
2928
|
+
function findPackageForFile(filePath, wsInfo) {
|
|
2929
|
+
const normFile = path7.normalize(filePath);
|
|
2930
|
+
const relFile = path7.isAbsolute(normFile) ? path7.relative(wsInfo.root, normFile) : normFile;
|
|
2931
|
+
let bestMatch = null;
|
|
2932
|
+
let maxLen = -1;
|
|
2933
|
+
for (const pkg of wsInfo.packages) {
|
|
2934
|
+
if (pkg.isRoot) continue;
|
|
2935
|
+
const pkgRel = path7.normalize(pkg.path);
|
|
2936
|
+
if (relFile === pkgRel || relFile.startsWith(pkgRel + path7.sep)) {
|
|
2937
|
+
if (pkgRel.length > maxLen) {
|
|
2938
|
+
maxLen = pkgRel.length;
|
|
2939
|
+
bestMatch = pkg;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
if (bestMatch) return bestMatch;
|
|
2944
|
+
const rootPkg = wsInfo.packages.find((p) => p.isRoot);
|
|
2945
|
+
return rootPkg || null;
|
|
2946
|
+
}
|
|
2947
|
+
function getReverseDependencies(pkgName, wsInfo) {
|
|
2948
|
+
const consumers = [];
|
|
2949
|
+
for (const pkg of wsInfo.packages) {
|
|
2950
|
+
if (pkg.name === pkgName) continue;
|
|
2951
|
+
if (pkg.internalDependencies.includes(pkgName)) {
|
|
2952
|
+
consumers.push(pkg);
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
return consumers;
|
|
2956
|
+
}
|
|
2957
|
+
function getAffectedPackages(modifiedFiles, wsInfo) {
|
|
2958
|
+
const directlyModifiedMap = /* @__PURE__ */ new Map();
|
|
2959
|
+
for (const file of modifiedFiles) {
|
|
2960
|
+
const pkg = findPackageForFile(file, wsInfo);
|
|
2961
|
+
if (pkg) {
|
|
2962
|
+
directlyModifiedMap.set(pkg.name, pkg);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
const directlyModified = Array.from(directlyModifiedMap.values());
|
|
2966
|
+
const downstreamMap = /* @__PURE__ */ new Map();
|
|
2967
|
+
const queue = [...directlyModified];
|
|
2968
|
+
const visited = new Set(queue.map((p) => p.name));
|
|
2969
|
+
while (queue.length > 0) {
|
|
2970
|
+
const current = queue.shift();
|
|
2971
|
+
const revDeps = getReverseDependencies(current.name, wsInfo);
|
|
2972
|
+
for (const dep of revDeps) {
|
|
2973
|
+
if (!visited.has(dep.name)) {
|
|
2974
|
+
visited.add(dep.name);
|
|
2975
|
+
downstreamMap.set(dep.name, dep);
|
|
2976
|
+
queue.push(dep);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
const downstream = Array.from(downstreamMap.values());
|
|
2981
|
+
const allAffected = [...directlyModified, ...downstream];
|
|
2982
|
+
const testCommands = [];
|
|
2983
|
+
if (allAffected.length > 0) {
|
|
2984
|
+
const nonRootPkgs = allAffected.filter((p) => !p.isRoot);
|
|
2985
|
+
const targetPkgs = nonRootPkgs.length > 0 ? nonRootPkgs : allAffected;
|
|
2986
|
+
if (wsInfo.type === "pnpm") {
|
|
2987
|
+
const filterArgs = targetPkgs.map((p) => `--filter "${p.name}"`).join(" ");
|
|
2988
|
+
testCommands.push(`pnpm ${filterArgs} test`);
|
|
2989
|
+
} else if (wsInfo.type === "npm") {
|
|
2990
|
+
const wsArgs = targetPkgs.map((p) => `--workspace="${p.name}"`).join(" ");
|
|
2991
|
+
testCommands.push(`npm test ${wsArgs}`);
|
|
2992
|
+
} else if (wsInfo.type === "yarn") {
|
|
2993
|
+
for (const p of targetPkgs) {
|
|
2994
|
+
testCommands.push(`yarn workspace ${p.name} test`);
|
|
2995
|
+
}
|
|
2996
|
+
} else {
|
|
2997
|
+
for (const p of targetPkgs) {
|
|
2998
|
+
if (p.testCommand) {
|
|
2999
|
+
testCommands.push(`cd ${p.path} && npm test`);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
return {
|
|
3005
|
+
directlyModifiedPackages: directlyModified,
|
|
3006
|
+
downstreamPackages: downstream,
|
|
3007
|
+
allAffectedPackages: allAffected,
|
|
3008
|
+
affectedTestCommands: testCommands
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
function formatWorkspaceMap(wsInfo) {
|
|
3012
|
+
if (!wsInfo.isWorkspace || wsInfo.packages.length <= 1) {
|
|
3013
|
+
const rootPkg = wsInfo.packages[0];
|
|
3014
|
+
return [
|
|
3015
|
+
`\u{1F4E6} **Repository Map (Standalone)**`,
|
|
3016
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
3017
|
+
`\u2022 Root: \`${path7.basename(wsInfo.root)}\``,
|
|
3018
|
+
rootPkg?.description ? `\u2022 Description: ${rootPkg.description}` : "",
|
|
3019
|
+
rootPkg?.entryPoints?.length ? `\u2022 Entrypoints: ${rootPkg.entryPoints.map((e) => `\`${e}\``).join(", ")}` : "",
|
|
3020
|
+
rootPkg?.scripts?.test ? `\u2022 Test command: \`${rootPkg.scripts.test}\`` : ""
|
|
3021
|
+
].filter(Boolean).join("\n");
|
|
3022
|
+
}
|
|
3023
|
+
const lines = [
|
|
3024
|
+
`\u{1F4E6} **Workspace Repository Map (${wsInfo.type.toUpperCase()})**`,
|
|
3025
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
3026
|
+
`Total packages: ${wsInfo.packages.length}`,
|
|
3027
|
+
""
|
|
3028
|
+
];
|
|
3029
|
+
for (const pkg of wsInfo.packages) {
|
|
3030
|
+
lines.push(`\u{1F4E6} **${pkg.name}** \`${pkg.path}\``);
|
|
3031
|
+
if (pkg.description) {
|
|
3032
|
+
lines.push(` \u{1F4DD} ${pkg.description}`);
|
|
3033
|
+
}
|
|
3034
|
+
if (pkg.entryPoints && pkg.entryPoints.length > 0) {
|
|
3035
|
+
lines.push(` \u{1F3AF} Entry: ${pkg.entryPoints.slice(0, 3).map((e) => `\`${e}\``).join(", ")}`);
|
|
3036
|
+
}
|
|
3037
|
+
if (pkg.internalDependencies && pkg.internalDependencies.length > 0) {
|
|
3038
|
+
lines.push(` \u{1F517} Internal deps: ${pkg.internalDependencies.map((d) => `\`${d}\``).join(", ")}`);
|
|
3039
|
+
}
|
|
3040
|
+
const consumers = getReverseDependencies(pkg.name, wsInfo);
|
|
3041
|
+
if (consumers.length > 0) {
|
|
3042
|
+
lines.push(` \u{1F465} Used by: ${consumers.map((c) => `\`${c.name}\``).join(", ")}`);
|
|
3043
|
+
}
|
|
3044
|
+
if (pkg.scripts && pkg.scripts.test) {
|
|
3045
|
+
lines.push(` \u{1F9EA} Test: \`${pkg.scripts.test}\``);
|
|
3046
|
+
}
|
|
3047
|
+
lines.push("");
|
|
3048
|
+
}
|
|
3049
|
+
return lines.join("\n").trim();
|
|
3050
|
+
}
|
|
3051
|
+
var _cachedWorkspaceInfo, _cachedRoot;
|
|
3052
|
+
var init_workspaceIntelligence = __esm({
|
|
3053
|
+
"src/engine/workspaceIntelligence.ts"() {
|
|
3054
|
+
"use strict";
|
|
3055
|
+
init_pathValidator();
|
|
3056
|
+
_cachedWorkspaceInfo = null;
|
|
3057
|
+
_cachedRoot = null;
|
|
3058
|
+
}
|
|
3059
|
+
});
|
|
3060
|
+
|
|
3061
|
+
// src/engine/impactAnalysis.ts
|
|
3062
|
+
var impactAnalysis_exports = {};
|
|
3063
|
+
__export(impactAnalysis_exports, {
|
|
3064
|
+
calculateBlastRadius: () => calculateBlastRadius
|
|
3065
|
+
});
|
|
3066
|
+
import fs7 from "fs";
|
|
3067
|
+
import path8 from "path";
|
|
3068
|
+
import fastGlob2 from "fast-glob";
|
|
3069
|
+
async function findImportingFiles(targetFile, root) {
|
|
3070
|
+
const normTarget = path8.normalize(targetFile);
|
|
3071
|
+
const baseName = path8.basename(normTarget, path8.extname(normTarget));
|
|
3072
|
+
const relTarget = path8.isAbsolute(normTarget) ? path8.relative(root, normTarget) : normTarget;
|
|
3073
|
+
try {
|
|
3074
|
+
const files = await fastGlob2(
|
|
3075
|
+
["**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs}"],
|
|
3076
|
+
{
|
|
3077
|
+
cwd: root,
|
|
3078
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.kuma/**"],
|
|
3079
|
+
onlyFiles: true
|
|
3080
|
+
}
|
|
3081
|
+
);
|
|
3082
|
+
const dependents = [];
|
|
3083
|
+
const importRegex = new RegExp(
|
|
3084
|
+
`(?:import|from|require\\s*\\(|export\\s+.*from)\\s*['"][^'"]*\\b${baseName}(?:\\.[a-zA-Z0-9]+)?['"]`,
|
|
3085
|
+
"i"
|
|
3086
|
+
);
|
|
3087
|
+
for (const f of files) {
|
|
3088
|
+
if (path8.normalize(f) === path8.normalize(relTarget)) continue;
|
|
3089
|
+
const fullPath = path8.join(root, f);
|
|
3090
|
+
try {
|
|
3091
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
3092
|
+
if (importRegex.test(content)) {
|
|
3093
|
+
dependents.push(f);
|
|
3094
|
+
}
|
|
3095
|
+
} catch {
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return dependents;
|
|
3099
|
+
} catch {
|
|
3100
|
+
return [];
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
async function findRelatedTests(targetFile, root, owningPackage) {
|
|
3104
|
+
const norm = path8.normalize(targetFile);
|
|
3105
|
+
const baseName = path8.basename(norm, path8.extname(norm));
|
|
3106
|
+
const testFiles = [];
|
|
3107
|
+
const testPatterns = [
|
|
3108
|
+
`**/*${baseName}*.test.*`,
|
|
3109
|
+
`**/*${baseName}*.spec.*`,
|
|
3110
|
+
`**/*${baseName}*_test.*`,
|
|
3111
|
+
`**/tests/**/*${baseName}*.*`,
|
|
3112
|
+
`**/test/**/*${baseName}*.*`,
|
|
3113
|
+
`**/__tests__/**/*${baseName}*.*`
|
|
3114
|
+
];
|
|
3115
|
+
try {
|
|
3116
|
+
const matches = await fastGlob2(testPatterns, {
|
|
3117
|
+
cwd: root,
|
|
3118
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.kuma/**"],
|
|
3119
|
+
onlyFiles: true
|
|
3120
|
+
});
|
|
3121
|
+
testFiles.push(...matches);
|
|
3122
|
+
} catch {
|
|
3123
|
+
}
|
|
3124
|
+
if (owningPackage && owningPackage.path !== ".") {
|
|
3125
|
+
try {
|
|
3126
|
+
const pkgTests = await fastGlob2(["**/*.{test,spec}.*"], {
|
|
3127
|
+
cwd: path8.join(root, owningPackage.path),
|
|
3128
|
+
ignore: ["**/node_modules/**", "**/dist/**"],
|
|
3129
|
+
onlyFiles: true
|
|
3130
|
+
});
|
|
3131
|
+
for (const pt of pkgTests) {
|
|
3132
|
+
const fullRel = path8.join(owningPackage.path, pt);
|
|
3133
|
+
if (!testFiles.includes(fullRel)) {
|
|
3134
|
+
testFiles.push(fullRel);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
} catch {
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
return Array.from(new Set(testFiles));
|
|
3141
|
+
}
|
|
3142
|
+
function assessRisk(target, targetType, directDependents, downstreamPackages, affectedTests, owningPackage) {
|
|
3143
|
+
const riskFlags = [];
|
|
3144
|
+
if (/(database|schema|migration|auth|security|safety|guard|policy|payment|secret|billing)/i.test(target)) {
|
|
3145
|
+
riskFlags.push("Security/Critical domain file");
|
|
3146
|
+
}
|
|
3147
|
+
if (owningPackage?.entryPoints.some((e) => target.endsWith(e) || e.includes(target))) {
|
|
3148
|
+
riskFlags.push("Public package entrypoint/export");
|
|
3149
|
+
}
|
|
3150
|
+
if (downstreamPackages.length > 0) {
|
|
3151
|
+
riskFlags.push(`Cross-package impact: ${downstreamPackages.length} consumer package(s) affected`);
|
|
3152
|
+
}
|
|
3153
|
+
if (directDependents.length > 8) {
|
|
3154
|
+
riskFlags.push(`High fan-in: ${directDependents.length} files depend directly on this`);
|
|
3155
|
+
}
|
|
3156
|
+
if (affectedTests.length === 0) {
|
|
3157
|
+
riskFlags.push("No direct test suite found for this target");
|
|
3158
|
+
}
|
|
3159
|
+
if (targetType === "package") {
|
|
3160
|
+
riskFlags.push("Package-level modification scope");
|
|
3161
|
+
}
|
|
3162
|
+
let risk = "low";
|
|
3163
|
+
if (riskFlags.includes("Security/Critical domain file") || downstreamPackages.length > 2 && riskFlags.includes("Public package entrypoint/export")) {
|
|
3164
|
+
risk = "critical";
|
|
3165
|
+
} else if (targetType === "package" || downstreamPackages.length > 0 || directDependents.length > 5 || riskFlags.includes("Public package entrypoint/export")) {
|
|
3166
|
+
risk = "high";
|
|
3167
|
+
} else if (directDependents.length > 0 || riskFlags.includes("No direct test suite found for this target")) {
|
|
3168
|
+
risk = "medium";
|
|
3169
|
+
}
|
|
3170
|
+
return { risk, riskFlags };
|
|
3171
|
+
}
|
|
3172
|
+
async function calculateBlastRadius(target, options = {}) {
|
|
3173
|
+
const root = options.root || getProjectRoot();
|
|
3174
|
+
const wsInfo = await getWorkspaceInfo(root);
|
|
3175
|
+
let targetType = "file";
|
|
3176
|
+
const isPkg = wsInfo.packages.some((p) => p.name === target);
|
|
3177
|
+
if (isPkg) {
|
|
3178
|
+
targetType = "package";
|
|
3179
|
+
} else if (target.includes("/") || target.includes(".")) {
|
|
3180
|
+
targetType = "file";
|
|
3181
|
+
} else {
|
|
3182
|
+
targetType = "symbol";
|
|
3183
|
+
}
|
|
3184
|
+
let owningPackage = null;
|
|
3185
|
+
if (targetType === "package") {
|
|
3186
|
+
owningPackage = wsInfo.packageMap.get(target) || null;
|
|
3187
|
+
} else {
|
|
3188
|
+
owningPackage = findPackageForFile(target, wsInfo);
|
|
3189
|
+
}
|
|
3190
|
+
const downstreamPackages = [];
|
|
3191
|
+
if (owningPackage && !owningPackage.isRoot) {
|
|
3192
|
+
const rev = getReverseDependencies(owningPackage.name, wsInfo);
|
|
3193
|
+
downstreamPackages.push(...rev.map((p) => p.name));
|
|
3194
|
+
}
|
|
3195
|
+
let directDependents = [];
|
|
3196
|
+
if (targetType === "file" || targetType === "symbol") {
|
|
3197
|
+
directDependents = await findImportingFiles(target, root);
|
|
3198
|
+
}
|
|
3199
|
+
const affectedTests = await findRelatedTests(target, root, owningPackage);
|
|
3200
|
+
const { risk, riskFlags } = assessRisk(
|
|
3201
|
+
target,
|
|
3202
|
+
targetType,
|
|
3203
|
+
directDependents,
|
|
3204
|
+
downstreamPackages,
|
|
3205
|
+
affectedTests,
|
|
3206
|
+
owningPackage
|
|
3207
|
+
);
|
|
3208
|
+
const summaryLines = [
|
|
3209
|
+
`\u{1F3AF} **Impact / Blast Radius: \`${target}\`**`,
|
|
3210
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
3211
|
+
`\u26A0\uFE0F **Risk Level: ${risk.toUpperCase()}**`,
|
|
3212
|
+
owningPackage ? `\u{1F4E6} Owning package: \`${owningPackage.name}\` (${owningPackage.path})` : ""
|
|
3213
|
+
];
|
|
3214
|
+
if (riskFlags.length > 0) {
|
|
3215
|
+
summaryLines.push("");
|
|
3216
|
+
summaryLines.push(`\u{1F6A8} **Risk Flags:**`);
|
|
3217
|
+
for (const flag of riskFlags) {
|
|
3218
|
+
summaryLines.push(` \u2022 ${flag}`);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
if (downstreamPackages.length > 0) {
|
|
3222
|
+
summaryLines.push("");
|
|
3223
|
+
summaryLines.push(`\u{1F310} **Downstream Consumer Packages (${downstreamPackages.length}):**`);
|
|
3224
|
+
for (const dp of downstreamPackages) {
|
|
3225
|
+
summaryLines.push(` \u2022 \`${dp}\``);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
if (directDependents.length > 0) {
|
|
3229
|
+
summaryLines.push("");
|
|
3230
|
+
summaryLines.push(`\u{1F4C4} **Direct Dependents (${directDependents.length} file(s)):**`);
|
|
3231
|
+
for (const dep of directDependents.slice(0, 6)) {
|
|
3232
|
+
summaryLines.push(` \u2022 \`${dep}\``);
|
|
3233
|
+
}
|
|
3234
|
+
if (directDependents.length > 6) {
|
|
3235
|
+
summaryLines.push(` ... and ${directDependents.length - 6} more`);
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
if (affectedTests.length > 0) {
|
|
3239
|
+
summaryLines.push("");
|
|
3240
|
+
summaryLines.push(`\u{1F9EA} **Affected Test Suites (${affectedTests.length}):**`);
|
|
3241
|
+
for (const t of affectedTests.slice(0, 5)) {
|
|
3242
|
+
summaryLines.push(` \u2022 \`${t}\``);
|
|
3243
|
+
}
|
|
3244
|
+
if (affectedTests.length > 5) {
|
|
3245
|
+
summaryLines.push(` ... and ${affectedTests.length - 5} more`);
|
|
3246
|
+
}
|
|
3247
|
+
} else {
|
|
3248
|
+
summaryLines.push("");
|
|
3249
|
+
summaryLines.push(`\u26A0\uFE0F **No test suites detected for this scope.** Consider adding tests before editing.`);
|
|
3250
|
+
}
|
|
3251
|
+
return {
|
|
3252
|
+
target,
|
|
3253
|
+
targetType,
|
|
3254
|
+
owningPackage,
|
|
3255
|
+
directDependents,
|
|
3256
|
+
downstreamPackages,
|
|
3257
|
+
affectedTests,
|
|
3258
|
+
risk,
|
|
3259
|
+
riskFlags,
|
|
3260
|
+
summary: summaryLines.filter(Boolean).join("\n"),
|
|
3261
|
+
confidence: 0.85
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
var init_impactAnalysis = __esm({
|
|
3265
|
+
"src/engine/impactAnalysis.ts"() {
|
|
3266
|
+
"use strict";
|
|
3267
|
+
init_pathValidator();
|
|
3268
|
+
init_workspaceIntelligence();
|
|
3269
|
+
}
|
|
3270
|
+
});
|
|
3271
|
+
|
|
2489
3272
|
// src/engine/kumaGraph.ts
|
|
2490
3273
|
var kumaGraph_exports = {};
|
|
2491
3274
|
__export(kumaGraph_exports, {
|
|
@@ -3382,27 +4165,27 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
3382
4165
|
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
3383
4166
|
let entries = [];
|
|
3384
4167
|
try {
|
|
3385
|
-
entries =
|
|
4168
|
+
entries = fs27.readdirSync(dir);
|
|
3386
4169
|
} catch {
|
|
3387
4170
|
return;
|
|
3388
4171
|
}
|
|
3389
4172
|
for (const entry of entries) {
|
|
3390
4173
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
3391
|
-
const full =
|
|
4174
|
+
const full = path28.join(dir, entry);
|
|
3392
4175
|
try {
|
|
3393
|
-
const stat =
|
|
4176
|
+
const stat = fs27.statSync(full);
|
|
3394
4177
|
if (stat.isDirectory()) {
|
|
3395
4178
|
walk2(full, depth + 1);
|
|
3396
4179
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
3397
|
-
results.push(
|
|
4180
|
+
results.push(path28.relative(root, full));
|
|
3398
4181
|
}
|
|
3399
4182
|
} catch {
|
|
3400
4183
|
}
|
|
3401
4184
|
}
|
|
3402
4185
|
};
|
|
3403
4186
|
var walk = walk2;
|
|
3404
|
-
const
|
|
3405
|
-
const
|
|
4187
|
+
const fs27 = await import("fs");
|
|
4188
|
+
const path28 = await import("path");
|
|
3406
4189
|
const root = process.cwd();
|
|
3407
4190
|
const results = [];
|
|
3408
4191
|
const MAX_WALK_DEPTH = 8;
|
|
@@ -3473,71 +4256,43 @@ async function getGraphStats() {
|
|
|
3473
4256
|
}
|
|
3474
4257
|
async function analyzeImpact(target) {
|
|
3475
4258
|
try {
|
|
3476
|
-
const
|
|
3477
|
-
const
|
|
3478
|
-
|
|
3479
|
-
return await codebaseImpactFallback(target);
|
|
3480
|
-
}
|
|
3481
|
-
const nodeStmt = db.prepare(`
|
|
3482
|
-
SELECT id, name, type, file_path FROM nodes
|
|
3483
|
-
WHERE name LIKE ? OR file_path = ? OR id = ?
|
|
3484
|
-
LIMIT 1
|
|
3485
|
-
`);
|
|
3486
|
-
nodeStmt.bind([`%${target}%`, target, target]);
|
|
3487
|
-
let nodeId2 = "";
|
|
3488
|
-
let nodeName = "";
|
|
3489
|
-
if (nodeStmt.step()) {
|
|
3490
|
-
const row = nodeStmt.getAsObject();
|
|
3491
|
-
nodeId2 = row.id;
|
|
3492
|
-
nodeName = row.name;
|
|
3493
|
-
}
|
|
3494
|
-
nodeStmt.free();
|
|
3495
|
-
if (!nodeId2) {
|
|
3496
|
-
return await codebaseImpactFallback(target);
|
|
3497
|
-
}
|
|
3498
|
-
const refStmt = db.prepare(`
|
|
3499
|
-
SELECT COUNT(*) as cnt, COUNT(DISTINCT CASE WHEN e.source_id != ? THEN e.source_id ELSE e.target_id END) as file_count
|
|
3500
|
-
FROM edges e WHERE e.source_id = ? OR e.target_id = ?
|
|
3501
|
-
`);
|
|
3502
|
-
refStmt.bind([nodeId2, nodeId2, nodeId2]);
|
|
3503
|
-
let references = 0;
|
|
3504
|
-
let files = 0;
|
|
3505
|
-
if (refStmt.step()) {
|
|
3506
|
-
const row = refStmt.getAsObject();
|
|
3507
|
-
references = row.cnt || 0;
|
|
3508
|
-
files = row.file_count || 0;
|
|
3509
|
-
}
|
|
3510
|
-
refStmt.free();
|
|
3511
|
-
let testFiles = 0;
|
|
4259
|
+
const { calculateBlastRadius: calculateBlastRadius2 } = await Promise.resolve().then(() => (init_impactAnalysis(), impactAnalysis_exports));
|
|
4260
|
+
const blast = await calculateBlastRadius2(target);
|
|
4261
|
+
let graphEntryPoints = [];
|
|
3512
4262
|
try {
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3515
|
-
);
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
4263
|
+
const db = await getDb();
|
|
4264
|
+
const nodeStmt = db.prepare(`SELECT id FROM nodes WHERE name LIKE ? OR file_path = ? OR id = ? LIMIT 1`);
|
|
4265
|
+
nodeStmt.bind([`%${target}%`, target, target]);
|
|
4266
|
+
if (nodeStmt.step()) {
|
|
4267
|
+
const row = nodeStmt.getAsObject();
|
|
4268
|
+
const entryStmt = db.prepare(`SELECT n.name, COUNT(*) as cnt FROM edges e JOIN nodes n ON n.id = e.target_id WHERE e.target_id = ? GROUP BY n.name ORDER BY cnt DESC LIMIT 5`);
|
|
4269
|
+
entryStmt.bind([row.id]);
|
|
4270
|
+
while (entryStmt.step()) {
|
|
4271
|
+
const erow = entryStmt.getAsObject();
|
|
4272
|
+
graphEntryPoints.push(`${erow.name} (${erow.cnt} refs)`);
|
|
4273
|
+
}
|
|
4274
|
+
entryStmt.free();
|
|
3519
4275
|
}
|
|
3520
|
-
|
|
4276
|
+
nodeStmt.free();
|
|
3521
4277
|
} catch {
|
|
3522
4278
|
}
|
|
3523
|
-
const
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
return { symbol: nodeName, references, files, testFiles, entryPoints, risk };
|
|
4279
|
+
const allEntryPoints = graphEntryPoints.length > 0 ? graphEntryPoints : blast.directDependents.slice(0, 5);
|
|
4280
|
+
return {
|
|
4281
|
+
symbol: target,
|
|
4282
|
+
references: blast.directDependents.length,
|
|
4283
|
+
files: blast.directDependents.length + (blast.downstreamPackages.length > 0 ? blast.downstreamPackages.length : 1),
|
|
4284
|
+
testFiles: blast.affectedTests.length,
|
|
4285
|
+
entryPoints: allEntryPoints,
|
|
4286
|
+
risk: blast.risk,
|
|
4287
|
+
packageName: blast.owningPackage?.name,
|
|
4288
|
+
downstreamPackages: blast.downstreamPackages,
|
|
4289
|
+
affectedTests: blast.affectedTests,
|
|
4290
|
+
riskFlags: blast.riskFlags,
|
|
4291
|
+
summary: blast.summary
|
|
4292
|
+
};
|
|
3538
4293
|
} catch (err) {
|
|
3539
4294
|
console.error(`[KumaGraph] Impact analysis failed: ${err}`);
|
|
3540
|
-
return
|
|
4295
|
+
return await codebaseImpactFallback(target);
|
|
3541
4296
|
}
|
|
3542
4297
|
}
|
|
3543
4298
|
async function codebaseImpactFallback(target) {
|
|
@@ -3569,19 +4324,22 @@ async function codebaseImpactFallback(target) {
|
|
|
3569
4324
|
}
|
|
3570
4325
|
}
|
|
3571
4326
|
function formatImpact(result) {
|
|
3572
|
-
|
|
4327
|
+
if (result.summary) return result.summary;
|
|
4328
|
+
const icon = result.risk === "critical" ? "\u{1F6A8}" : result.risk === "high" ? "\u{1F534}" : result.risk === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
3573
4329
|
const lines = [
|
|
3574
4330
|
`${icon} **Impact Analysis** \u2014 ${result.symbol}`,
|
|
3575
4331
|
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
3576
4332
|
"",
|
|
3577
4333
|
`\u{1F4CA} ${result.references} reference(s) across ${result.files} file(s)`,
|
|
3578
4334
|
`\u{1F9EA} ${result.testFiles} test file(s)`,
|
|
4335
|
+
result.packageName ? `\u{1F4E6} Package: \`${result.packageName}\`` : "",
|
|
4336
|
+
result.downstreamPackages && result.downstreamPackages.length > 0 ? `\u{1F310} Downstream: ${result.downstreamPackages.join(", ")}` : "",
|
|
3579
4337
|
result.entryPoints.length > 0 ? `
|
|
3580
4338
|
\u{1F3AF} Entry Points:
|
|
3581
4339
|
${result.entryPoints.join("\n ")}` : "",
|
|
3582
4340
|
"",
|
|
3583
4341
|
`\u26A0\uFE0F Risk Level: **${result.risk.toUpperCase()}**`,
|
|
3584
|
-
result.risk === "high" ? "\u{1F4A1} High impact \u2014 review thoroughly before changing." : ""
|
|
4342
|
+
result.risk === "high" || result.risk === "critical" ? "\u{1F4A1} High impact \u2014 review thoroughly before changing." : ""
|
|
3585
4343
|
].filter(Boolean);
|
|
3586
4344
|
return lines.join("\n");
|
|
3587
4345
|
}
|
|
@@ -3786,19 +4544,19 @@ __export(kumaMemory_exports, {
|
|
|
3786
4544
|
scoreMemoryRelevance: () => scoreMemoryRelevance,
|
|
3787
4545
|
shouldRecordDecision: () => shouldRecordDecision
|
|
3788
4546
|
});
|
|
3789
|
-
import
|
|
3790
|
-
import
|
|
4547
|
+
import fs8 from "fs";
|
|
4548
|
+
import path9 from "path";
|
|
3791
4549
|
function scoreMemoryRelevance(context, limit = 5) {
|
|
3792
4550
|
const results = [];
|
|
3793
|
-
const kumaDir =
|
|
3794
|
-
if (!
|
|
4551
|
+
const kumaDir = path9.join(getProjectRoot(), MEMORY_DIR);
|
|
4552
|
+
if (!fs8.existsSync(kumaDir)) return [];
|
|
3795
4553
|
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
3796
4554
|
if (terms.length === 0) return [];
|
|
3797
4555
|
try {
|
|
3798
|
-
const files =
|
|
4556
|
+
const files = fs8.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
3799
4557
|
for (const file of files) {
|
|
3800
4558
|
try {
|
|
3801
|
-
const content =
|
|
4559
|
+
const content = fs8.readFileSync(path9.join(kumaDir, file), "utf-8");
|
|
3802
4560
|
const lower = content.toLowerCase();
|
|
3803
4561
|
let matchCount = 0;
|
|
3804
4562
|
for (const term of terms) {
|
|
@@ -3961,18 +4719,18 @@ __export(domainRules_exports, {
|
|
|
3961
4719
|
readLayer: () => readLayer,
|
|
3962
4720
|
writeLayer: () => writeLayer
|
|
3963
4721
|
});
|
|
3964
|
-
import
|
|
3965
|
-
import
|
|
4722
|
+
import fs9 from "fs";
|
|
4723
|
+
import path10 from "path";
|
|
3966
4724
|
function layerFilePath(layer) {
|
|
3967
|
-
return
|
|
4725
|
+
return path10.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
|
|
3968
4726
|
}
|
|
3969
4727
|
function ensureLayerFile(layer) {
|
|
3970
4728
|
const fp = layerFilePath(layer);
|
|
3971
|
-
if (!
|
|
4729
|
+
if (!fs9.existsSync(fp)) {
|
|
3972
4730
|
const template = layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
|
|
3973
|
-
const dir =
|
|
3974
|
-
if (!
|
|
3975
|
-
|
|
4731
|
+
const dir = path10.dirname(fp);
|
|
4732
|
+
if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
|
|
4733
|
+
fs9.writeFileSync(fp, template, "utf-8");
|
|
3976
4734
|
console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
|
|
3977
4735
|
}
|
|
3978
4736
|
return fp;
|
|
@@ -3980,7 +4738,7 @@ function ensureLayerFile(layer) {
|
|
|
3980
4738
|
function readLayer(layer) {
|
|
3981
4739
|
try {
|
|
3982
4740
|
const fp = ensureLayerFile(layer);
|
|
3983
|
-
return
|
|
4741
|
+
return fs9.readFileSync(fp, "utf-8");
|
|
3984
4742
|
} catch (err) {
|
|
3985
4743
|
return `Error reading ${layer}: ${err}`;
|
|
3986
4744
|
}
|
|
@@ -3988,7 +4746,7 @@ function readLayer(layer) {
|
|
|
3988
4746
|
function writeLayer(layer, content) {
|
|
3989
4747
|
try {
|
|
3990
4748
|
const fp = ensureLayerFile(layer);
|
|
3991
|
-
|
|
4749
|
+
fs9.writeFileSync(fp, content, "utf-8");
|
|
3992
4750
|
const label = LAYER_FILES[layer].label;
|
|
3993
4751
|
sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
|
|
3994
4752
|
return `\u2705 **${label}** updated successfully.`;
|
|
@@ -3999,9 +4757,9 @@ function writeLayer(layer, content) {
|
|
|
3999
4757
|
function appendToLayer(layer, entry) {
|
|
4000
4758
|
try {
|
|
4001
4759
|
const fp = ensureLayerFile(layer);
|
|
4002
|
-
const existing =
|
|
4760
|
+
const existing = fs9.readFileSync(fp, "utf-8");
|
|
4003
4761
|
const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
|
|
4004
|
-
|
|
4762
|
+
fs9.writeFileSync(fp, newContent, "utf-8");
|
|
4005
4763
|
return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
|
|
4006
4764
|
} catch (err) {
|
|
4007
4765
|
return `\u274C Failed to append: ${err}`;
|
|
@@ -4011,29 +4769,33 @@ function getActiveGotchas() {
|
|
|
4011
4769
|
const results = [];
|
|
4012
4770
|
try {
|
|
4013
4771
|
const fp = ensureLayerFile("gotcha");
|
|
4014
|
-
const content =
|
|
4772
|
+
const content = fs9.readFileSync(fp, "utf-8");
|
|
4015
4773
|
const lines = content.split("\n");
|
|
4016
4774
|
let currentSection = "";
|
|
4017
4775
|
let currentFilePath = "";
|
|
4018
4776
|
let currentDesc = "";
|
|
4019
4777
|
let currentSeverity = "";
|
|
4778
|
+
let currentStatus = "active";
|
|
4020
4779
|
for (const line of lines) {
|
|
4021
4780
|
const fileMatch = line.match(/^###\s+(.+?)\s*[—–-]+\s*(.+)/);
|
|
4022
4781
|
if (fileMatch) {
|
|
4023
|
-
if (currentFilePath) {
|
|
4782
|
+
if (currentFilePath && currentStatus !== "resolved" && currentStatus !== "deprecated") {
|
|
4024
4783
|
results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
|
|
4025
4784
|
}
|
|
4026
4785
|
currentFilePath = fileMatch[1].trim();
|
|
4027
4786
|
currentDesc = fileMatch[2].trim();
|
|
4028
4787
|
currentSeverity = "medium";
|
|
4788
|
+
currentStatus = "active";
|
|
4029
4789
|
currentSection = line + "\n";
|
|
4030
4790
|
continue;
|
|
4031
4791
|
}
|
|
4032
4792
|
const sevMatch = line.match(/- \*\*Severity\*\*:\s*(\w+)/);
|
|
4033
4793
|
if (sevMatch) currentSeverity = sevMatch[1].toLowerCase();
|
|
4794
|
+
const statusMatch = line.match(/- \*\*Status\*\*:\s*(\w+)/);
|
|
4795
|
+
if (statusMatch) currentStatus = statusMatch[1].toLowerCase();
|
|
4034
4796
|
currentSection += line + "\n";
|
|
4035
4797
|
}
|
|
4036
|
-
if (currentFilePath) {
|
|
4798
|
+
if (currentFilePath && currentStatus !== "resolved" && currentStatus !== "deprecated") {
|
|
4037
4799
|
results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
|
|
4038
4800
|
}
|
|
4039
4801
|
} catch {
|
|
@@ -4143,8 +4905,8 @@ __export(kumaInject_exports, {
|
|
|
4143
4905
|
parseHookInput: () => parseHookInput,
|
|
4144
4906
|
trackFileEditLoop: () => trackFileEditLoop
|
|
4145
4907
|
});
|
|
4146
|
-
import
|
|
4147
|
-
import
|
|
4908
|
+
import fs10 from "fs";
|
|
4909
|
+
import path11 from "path";
|
|
4148
4910
|
function hashFileContent(filePath) {
|
|
4149
4911
|
return hashFile(filePath);
|
|
4150
4912
|
}
|
|
@@ -4156,14 +4918,14 @@ function isGotchaFresh(storedHash, currentHash) {
|
|
|
4156
4918
|
async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
4157
4919
|
const root = getProjectRoot();
|
|
4158
4920
|
try {
|
|
4159
|
-
const dbFile =
|
|
4160
|
-
if (
|
|
4921
|
+
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
4922
|
+
if (fs10.existsSync(dbFile)) {
|
|
4161
4923
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4162
4924
|
const db = await getDb2();
|
|
4163
4925
|
const stmt = db.prepare(`
|
|
4164
4926
|
SELECT id, file_path, description, severity, workaround, content_hash
|
|
4165
4927
|
FROM known_gotchas
|
|
4166
|
-
WHERE status
|
|
4928
|
+
WHERE status IN ('active', 'verified')
|
|
4167
4929
|
AND (file_path = ? OR ? LIKE ('%' || file_path || '%'))
|
|
4168
4930
|
ORDER BY
|
|
4169
4931
|
CASE WHEN file_path = ? THEN 0 ELSE 1 END, -- exact match first
|
|
@@ -4188,7 +4950,7 @@ async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
|
4188
4950
|
} catch {
|
|
4189
4951
|
}
|
|
4190
4952
|
try {
|
|
4191
|
-
if (!
|
|
4953
|
+
if (!fs10.existsSync(path11.join(root, ".kuma"))) return [];
|
|
4192
4954
|
const { checkFileGotchas: checkFileGotchas2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
4193
4955
|
const warnings = checkFileGotchas2(filePath);
|
|
4194
4956
|
if (warnings.length === 0) return [];
|
|
@@ -4206,8 +4968,8 @@ async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
|
4206
4968
|
async function getFileTrace(filePath, limit = 8) {
|
|
4207
4969
|
try {
|
|
4208
4970
|
const root = getProjectRoot();
|
|
4209
|
-
const dbFile =
|
|
4210
|
-
if (!
|
|
4971
|
+
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
4972
|
+
if (!fs10.existsSync(dbFile)) return [];
|
|
4211
4973
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4212
4974
|
const db = await getDb2();
|
|
4213
4975
|
const stmt = db.prepare(`
|
|
@@ -4217,7 +4979,7 @@ async function getFileTrace(filePath, limit = 8) {
|
|
|
4217
4979
|
ORDER BY cl.created_at DESC
|
|
4218
4980
|
LIMIT ?
|
|
4219
4981
|
`);
|
|
4220
|
-
stmt.bind([`%${
|
|
4982
|
+
stmt.bind([`%${path11.basename(filePath)}%`, limit]);
|
|
4221
4983
|
const rows = [];
|
|
4222
4984
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
4223
4985
|
stmt.free();
|
|
@@ -4235,7 +4997,7 @@ async function getFileTrace(filePath, limit = 8) {
|
|
|
4235
4997
|
function formatFileTrace(entries, filePath) {
|
|
4236
4998
|
if (entries.length === 0) return "";
|
|
4237
4999
|
const lines = [
|
|
4238
|
-
`\u{1F570}\uFE0F **Why is ${
|
|
5000
|
+
`\u{1F570}\uFE0F **Why is ${path11.basename(filePath)} written this way?** \u2014 ${entries.length} change(s) recorded:`
|
|
4239
5001
|
];
|
|
4240
5002
|
for (const e of entries) {
|
|
4241
5003
|
const date = new Date((e.createdAt || 0) * 1e3).toLocaleDateString("en-US", {
|
|
@@ -4251,7 +5013,7 @@ function formatFileTrace(entries, filePath) {
|
|
|
4251
5013
|
async function getDecisionsForFile(filePath, limit = 3) {
|
|
4252
5014
|
try {
|
|
4253
5015
|
const { scoreMemoryRelevance: scoreMemoryRelevance2 } = await Promise.resolve().then(() => (init_kumaMemory(), kumaMemory_exports));
|
|
4254
|
-
const base =
|
|
5016
|
+
const base = path11.basename(filePath);
|
|
4255
5017
|
const scored = scoreMemoryRelevance2(`${filePath} ${base}`, limit);
|
|
4256
5018
|
return scored.map((m) => `\u{1F4CC} ${m.topic} \u2014 ${m.score}% match (${m.reason})`);
|
|
4257
5019
|
} catch {
|
|
@@ -4279,13 +5041,13 @@ async function getRelevantContext(filePath, goal) {
|
|
|
4279
5041
|
if (traceStr) parts.push(traceStr);
|
|
4280
5042
|
try {
|
|
4281
5043
|
const root = getProjectRoot();
|
|
4282
|
-
if (
|
|
5044
|
+
if (fs10.existsSync(path11.join(root, ".kuma", "kuma.db"))) {
|
|
4283
5045
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4284
5046
|
const db = await getDb2();
|
|
4285
5047
|
const stmt = db.prepare(
|
|
4286
5048
|
`SELECT COUNT(*) FROM known_gotchas WHERE status = 'resolved' AND file_path LIKE ?`
|
|
4287
5049
|
);
|
|
4288
|
-
stmt.bind([`%${
|
|
5050
|
+
stmt.bind([`%${path11.basename(filePath)}%`]);
|
|
4289
5051
|
const found = stmt.step();
|
|
4290
5052
|
const count = found ? Number(stmt.getAsObject()["COUNT(*)"]) : 0;
|
|
4291
5053
|
stmt.free();
|
|
@@ -4296,7 +5058,7 @@ async function getRelevantContext(filePath, goal) {
|
|
|
4296
5058
|
} catch {
|
|
4297
5059
|
}
|
|
4298
5060
|
if (gotchas.length > 0) {
|
|
4299
|
-
const scopeHint =
|
|
5061
|
+
const scopeHint = path11.basename(path11.dirname(filePath)) || "current change";
|
|
4300
5062
|
parts.push(
|
|
4301
5063
|
`\u2705 AFTER editing, verify: kuma_safety({ action: "verify", scope: "${scopeHint}" })`
|
|
4302
5064
|
);
|
|
@@ -4312,11 +5074,11 @@ async function getRelevantContext(filePath, goal) {
|
|
|
4312
5074
|
async function getFileSummary2(filePath) {
|
|
4313
5075
|
try {
|
|
4314
5076
|
const root = getProjectRoot();
|
|
4315
|
-
const dbFile =
|
|
4316
|
-
if (!
|
|
5077
|
+
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
5078
|
+
if (!fs10.existsSync(dbFile)) return null;
|
|
4317
5079
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4318
5080
|
const db = await getDb2();
|
|
4319
|
-
const base =
|
|
5081
|
+
const base = path11.basename(filePath);
|
|
4320
5082
|
const stmt = db.prepare(`
|
|
4321
5083
|
SELECT
|
|
4322
5084
|
(SELECT COUNT(*) FROM known_gotchas WHERE status = 'active' AND (file_path = ? OR file_path LIKE ?)) as active_gotchas,
|
|
@@ -4394,9 +5156,9 @@ async function getCommandContext(command) {
|
|
|
4394
5156
|
}
|
|
4395
5157
|
function loadHookState(root) {
|
|
4396
5158
|
try {
|
|
4397
|
-
const fp =
|
|
4398
|
-
if (
|
|
4399
|
-
return JSON.parse(
|
|
5159
|
+
const fp = path11.join(root, HOOK_STATE_FILE);
|
|
5160
|
+
if (fs10.existsSync(fp)) {
|
|
5161
|
+
return JSON.parse(fs10.readFileSync(fp, "utf-8"));
|
|
4400
5162
|
}
|
|
4401
5163
|
} catch {
|
|
4402
5164
|
}
|
|
@@ -4404,10 +5166,10 @@ function loadHookState(root) {
|
|
|
4404
5166
|
}
|
|
4405
5167
|
function saveHookState(root, state) {
|
|
4406
5168
|
try {
|
|
4407
|
-
const fp =
|
|
4408
|
-
const dir =
|
|
4409
|
-
if (!
|
|
4410
|
-
|
|
5169
|
+
const fp = path11.join(root, HOOK_STATE_FILE);
|
|
5170
|
+
const dir = path11.dirname(fp);
|
|
5171
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
5172
|
+
fs10.writeFileSync(fp, JSON.stringify(state), "utf-8");
|
|
4411
5173
|
} catch {
|
|
4412
5174
|
}
|
|
4413
5175
|
}
|
|
@@ -4441,7 +5203,7 @@ async function trackFileEditLoop(filePath) {
|
|
|
4441
5203
|
saveHookState(root, state);
|
|
4442
5204
|
if (recorded) {
|
|
4443
5205
|
try {
|
|
4444
|
-
if (!
|
|
5206
|
+
if (!fs10.existsSync(path11.join(root, ".kuma", "kuma.db"))) return entry.count;
|
|
4445
5207
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
4446
5208
|
await addGotcha2({
|
|
4447
5209
|
filePath,
|
|
@@ -4478,16 +5240,20 @@ var kumaGotchas_exports = {};
|
|
|
4478
5240
|
__export(kumaGotchas_exports, {
|
|
4479
5241
|
addGotcha: () => addGotcha,
|
|
4480
5242
|
checkGotchasForFile: () => checkGotchasForFile,
|
|
5243
|
+
deprecateGotcha: () => deprecateGotcha,
|
|
4481
5244
|
getActiveGotchasForCommand: () => getActiveGotchasForCommand,
|
|
5245
|
+
getGotchasByStatus: () => getGotchasByStatus,
|
|
4482
5246
|
getInjectionStats: () => getInjectionStats,
|
|
4483
5247
|
listGotchas: () => listGotchas,
|
|
4484
5248
|
recordInjection: () => recordInjection,
|
|
5249
|
+
resolveGotcha: () => resolveGotcha,
|
|
4485
5250
|
resolveGotchasForScope: () => resolveGotchasForScope,
|
|
5251
|
+
setGotchaStatus: () => setGotchaStatus,
|
|
4486
5252
|
syncGotchasGraph: () => syncGotchasGraph,
|
|
4487
5253
|
syncGotchasToDb: () => syncGotchasToDb
|
|
4488
5254
|
});
|
|
4489
|
-
import
|
|
4490
|
-
import
|
|
5255
|
+
import fs11 from "fs";
|
|
5256
|
+
import path12 from "path";
|
|
4491
5257
|
async function ensureGotchasSchema() {
|
|
4492
5258
|
const db = await getDb();
|
|
4493
5259
|
db.exec(`
|
|
@@ -4504,8 +5270,10 @@ async function ensureGotchasSchema() {
|
|
|
4504
5270
|
content_hash TEXT,
|
|
4505
5271
|
trigger_command TEXT,
|
|
4506
5272
|
status TEXT NOT NULL DEFAULT 'active'
|
|
4507
|
-
CHECK(status IN ('active','resolved')),
|
|
4508
|
-
last_verified_at INTEGER
|
|
5273
|
+
CHECK(status IN ('candidate','active','verified','resolved','deprecated')),
|
|
5274
|
+
last_verified_at INTEGER,
|
|
5275
|
+
scope_package TEXT,
|
|
5276
|
+
verified_by TEXT
|
|
4509
5277
|
);
|
|
4510
5278
|
CREATE INDEX IF NOT EXISTS idx_gotchas_file ON known_gotchas(file_path);
|
|
4511
5279
|
CREATE INDEX IF NOT EXISTS idx_gotchas_severity ON known_gotchas(severity);
|
|
@@ -4525,6 +5293,37 @@ async function ensureGotchasSchema() {
|
|
|
4525
5293
|
if (!cols.includes("last_verified_at")) {
|
|
4526
5294
|
db.run(`ALTER TABLE known_gotchas ADD COLUMN last_verified_at INTEGER`);
|
|
4527
5295
|
}
|
|
5296
|
+
if (!cols.includes("scope_package")) {
|
|
5297
|
+
db.run(`ALTER TABLE known_gotchas ADD COLUMN scope_package TEXT`);
|
|
5298
|
+
}
|
|
5299
|
+
if (!cols.includes("verified_by")) {
|
|
5300
|
+
db.run(`ALTER TABLE known_gotchas ADD COLUMN verified_by TEXT`);
|
|
5301
|
+
}
|
|
5302
|
+
const schema = db.exec(`SELECT sql FROM sqlite_master WHERE type='table' AND name='known_gotchas'`);
|
|
5303
|
+
const gotchaSql = schema[0]?.values?.[0]?.[0] || "";
|
|
5304
|
+
if (gotchaSql && !gotchaSql.includes("candidate")) {
|
|
5305
|
+
db.run(`ALTER TABLE known_gotchas RENAME TO known_gotchas_old`);
|
|
5306
|
+
db.run(`CREATE TABLE IF NOT EXISTS known_gotchas (
|
|
5307
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5308
|
+
file_path TEXT NOT NULL,
|
|
5309
|
+
description TEXT NOT NULL,
|
|
5310
|
+
severity TEXT NOT NULL DEFAULT 'medium'
|
|
5311
|
+
CHECK(severity IN ('low','medium','high','critical')),
|
|
5312
|
+
workaround TEXT,
|
|
5313
|
+
added_by TEXT DEFAULT 'agent',
|
|
5314
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
5315
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
5316
|
+
content_hash TEXT,
|
|
5317
|
+
trigger_command TEXT,
|
|
5318
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
5319
|
+
CHECK(status IN ('candidate','active','verified','resolved','deprecated')),
|
|
5320
|
+
last_verified_at INTEGER,
|
|
5321
|
+
scope_package TEXT,
|
|
5322
|
+
verified_by TEXT
|
|
5323
|
+
)`);
|
|
5324
|
+
db.run(`INSERT OR IGNORE INTO known_gotchas (id, file_path, description, severity, workaround, added_by, created_at, updated_at, content_hash, trigger_command, status, last_verified_at) SELECT id, file_path, description, severity, workaround, added_by, created_at, updated_at, content_hash, trigger_command, status, last_verified_at FROM known_gotchas_old`);
|
|
5325
|
+
db.run(`DROP TABLE known_gotchas_old`);
|
|
5326
|
+
}
|
|
4528
5327
|
} catch {
|
|
4529
5328
|
}
|
|
4530
5329
|
saveDb();
|
|
@@ -4534,6 +5333,7 @@ async function addGotcha(entry) {
|
|
|
4534
5333
|
await ensureGotchasSchema();
|
|
4535
5334
|
const db = await getDb();
|
|
4536
5335
|
const severity = entry.severity || "medium";
|
|
5336
|
+
const status = entry.status || "active";
|
|
4537
5337
|
const contentHash = hashFileContent(entry.filePath);
|
|
4538
5338
|
const formatted = [
|
|
4539
5339
|
`### ${entry.filePath} \u2014 ${entry.description}`,
|
|
@@ -4541,6 +5341,8 @@ async function addGotcha(entry) {
|
|
|
4541
5341
|
`- **Severity**: ${severity}`,
|
|
4542
5342
|
entry.workaround ? `- **Workaround**: ${entry.workaround}` : "",
|
|
4543
5343
|
entry.triggerCommand ? `- **Trigger**: when running \`${entry.triggerCommand}\`` : "",
|
|
5344
|
+
entry.status ? `- **Status**: ${entry.status}` : "",
|
|
5345
|
+
entry.scopePackage ? `- **Scope Package**: ${entry.scopePackage}` : "",
|
|
4544
5346
|
`- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
4545
5347
|
].filter(Boolean).join("\n");
|
|
4546
5348
|
const checkStmt = db.prepare(
|
|
@@ -4556,13 +5358,13 @@ async function addGotcha(entry) {
|
|
|
4556
5358
|
checkStmt.free();
|
|
4557
5359
|
if (existingId !== null) {
|
|
4558
5360
|
db.run(
|
|
4559
|
-
`UPDATE known_gotchas SET severity = ?, workaround = ?, content_hash = ?, trigger_command = ?, status =
|
|
4560
|
-
[severity, entry.workaround || null, contentHash, entry.triggerCommand || null, existingId]
|
|
5361
|
+
`UPDATE known_gotchas SET severity = ?, workaround = ?, content_hash = ?, trigger_command = ?, status = ?, scope_package = coalesce(?, scope_package), verified_by = coalesce(?, verified_by), updated_at = strftime('%s','now') WHERE id = ?`,
|
|
5362
|
+
[severity, entry.workaround || null, contentHash, entry.triggerCommand || null, status, entry.scopePackage || null, entry.verifiedBy || null, existingId]
|
|
4561
5363
|
);
|
|
4562
5364
|
} else {
|
|
4563
5365
|
db.run(
|
|
4564
|
-
`INSERT INTO known_gotchas (file_path, description, severity, workaround, content_hash, trigger_command) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
4565
|
-
[entry.filePath, entry.description, severity, entry.workaround || null, contentHash, entry.triggerCommand || null]
|
|
5366
|
+
`INSERT INTO known_gotchas (file_path, description, severity, workaround, content_hash, trigger_command, status, scope_package, verified_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5367
|
+
[entry.filePath, entry.description, severity, entry.workaround || null, contentHash, entry.triggerCommand || null, status, entry.scopePackage || null, entry.verifiedBy || null]
|
|
4566
5368
|
);
|
|
4567
5369
|
}
|
|
4568
5370
|
const mdResult = appendToLayer("gotcha", formatted);
|
|
@@ -4614,7 +5416,12 @@ async function listGotchas(params) {
|
|
|
4614
5416
|
sql += " AND severity = ?";
|
|
4615
5417
|
bind.push(params.severity);
|
|
4616
5418
|
}
|
|
4617
|
-
|
|
5419
|
+
if (params.status && params.status !== "all") {
|
|
5420
|
+
sql += " AND status = ?";
|
|
5421
|
+
bind.push(params.status);
|
|
5422
|
+
} else if (!params.status) {
|
|
5423
|
+
sql += " AND status IN ('active', 'verified')";
|
|
5424
|
+
}
|
|
4618
5425
|
sql += " ORDER BY severity DESC, created_at DESC LIMIT 50";
|
|
4619
5426
|
const stmt = db.prepare(sql);
|
|
4620
5427
|
stmt.bind(bind);
|
|
@@ -4692,7 +5499,7 @@ async function syncGotchasGraph() {
|
|
|
4692
5499
|
const db = await getDb();
|
|
4693
5500
|
let created = 0;
|
|
4694
5501
|
const stmt = db.prepare(
|
|
4695
|
-
`SELECT file_path, description, severity, workaround FROM known_gotchas`
|
|
5502
|
+
`SELECT file_path, description, severity, workaround FROM known_gotchas WHERE status IN ('active', 'verified')`
|
|
4696
5503
|
);
|
|
4697
5504
|
const rows = [];
|
|
4698
5505
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
@@ -4752,7 +5559,7 @@ async function getActiveGotchasForCommand(command) {
|
|
|
4752
5559
|
const stmt = db.prepare(
|
|
4753
5560
|
`SELECT file_path, description, severity, workaround, trigger_command
|
|
4754
5561
|
FROM known_gotchas
|
|
4755
|
-
WHERE status
|
|
5562
|
+
WHERE status IN ('active', 'verified') AND trigger_command IS NOT NULL AND length(trigger_command) > 0
|
|
4756
5563
|
ORDER BY
|
|
4757
5564
|
CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END
|
|
4758
5565
|
LIMIT 30`
|
|
@@ -4777,8 +5584,8 @@ async function getActiveGotchasForCommand(command) {
|
|
|
4777
5584
|
}
|
|
4778
5585
|
async function recordInjection(params) {
|
|
4779
5586
|
try {
|
|
4780
|
-
const fp =
|
|
4781
|
-
if (!
|
|
5587
|
+
const fp = path12.join(getProjectRoot(), ".kuma", "injections.jsonl");
|
|
5588
|
+
if (!fs11.existsSync(path12.dirname(fp))) fs11.mkdirSync(path12.dirname(fp), { recursive: true });
|
|
4782
5589
|
const line = JSON.stringify({
|
|
4783
5590
|
filePath: params.filePath || null,
|
|
4784
5591
|
command: params.command || null,
|
|
@@ -4787,16 +5594,16 @@ async function recordInjection(params) {
|
|
|
4787
5594
|
// conservative estimate: avoided re-research / re-discovery
|
|
4788
5595
|
at: Date.now()
|
|
4789
5596
|
});
|
|
4790
|
-
|
|
5597
|
+
fs11.appendFileSync(fp, line + "\n", "utf-8");
|
|
4791
5598
|
} catch {
|
|
4792
5599
|
}
|
|
4793
5600
|
}
|
|
4794
5601
|
function getInjectionStats(hours = 24) {
|
|
4795
5602
|
try {
|
|
4796
|
-
const fp =
|
|
4797
|
-
if (!
|
|
5603
|
+
const fp = path12.join(getProjectRoot(), ".kuma", "injections.jsonl");
|
|
5604
|
+
if (!fs11.existsSync(fp)) return { count: 0, savedMs: 0 };
|
|
4798
5605
|
const cutoff = Date.now() - hours * 60 * 60 * 1e3;
|
|
4799
|
-
const lines =
|
|
5606
|
+
const lines = fs11.readFileSync(fp, "utf-8").split("\n");
|
|
4800
5607
|
let count = 0;
|
|
4801
5608
|
let savedMs = 0;
|
|
4802
5609
|
for (const line of lines) {
|
|
@@ -4815,6 +5622,104 @@ function getInjectionStats(hours = 24) {
|
|
|
4815
5622
|
return { count: 0, savedMs: 0 };
|
|
4816
5623
|
}
|
|
4817
5624
|
}
|
|
5625
|
+
async function setGotchaStatus(id, status, verifiedBy) {
|
|
5626
|
+
try {
|
|
5627
|
+
const db = await getDb();
|
|
5628
|
+
db.run(
|
|
5629
|
+
`UPDATE known_gotchas SET status = ?, verified_by = coalesce(?, verified_by), last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ?`,
|
|
5630
|
+
[status, verifiedBy || null, id]
|
|
5631
|
+
);
|
|
5632
|
+
saveDb();
|
|
5633
|
+
return true;
|
|
5634
|
+
} catch {
|
|
5635
|
+
return false;
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
async function getGotchasByStatus(status, limit = 20) {
|
|
5639
|
+
try {
|
|
5640
|
+
const db = await getDb();
|
|
5641
|
+
const stmt = db.prepare(
|
|
5642
|
+
`SELECT id, file_path, description, severity, workaround, status, scope_package, verified_by, updated_at FROM known_gotchas WHERE status = ? ORDER BY updated_at DESC LIMIT ?`
|
|
5643
|
+
);
|
|
5644
|
+
stmt.bind([status, limit]);
|
|
5645
|
+
const rows = [];
|
|
5646
|
+
while (stmt.step()) {
|
|
5647
|
+
rows.push(stmt.getAsObject());
|
|
5648
|
+
}
|
|
5649
|
+
stmt.free();
|
|
5650
|
+
return rows;
|
|
5651
|
+
} catch {
|
|
5652
|
+
return [];
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent") {
|
|
5656
|
+
try {
|
|
5657
|
+
await ensureGotchasSchema();
|
|
5658
|
+
const db = await getDb();
|
|
5659
|
+
let resolved = 0;
|
|
5660
|
+
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5661
|
+
if (isId) {
|
|
5662
|
+
const id = Number(idOrScope);
|
|
5663
|
+
db.run(
|
|
5664
|
+
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'resolved'`,
|
|
5665
|
+
[verifiedBy, id]
|
|
5666
|
+
);
|
|
5667
|
+
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5668
|
+
} else {
|
|
5669
|
+
const scope = String(idOrScope).trim();
|
|
5670
|
+
db.run(
|
|
5671
|
+
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE (file_path = ? OR file_path LIKE ?) AND status != 'resolved'`,
|
|
5672
|
+
[verifiedBy, scope, `%${scope}%`]
|
|
5673
|
+
);
|
|
5674
|
+
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5675
|
+
}
|
|
5676
|
+
if (resolved > 0) {
|
|
5677
|
+
saveDb();
|
|
5678
|
+
rebuildFtsIndex();
|
|
5679
|
+
return {
|
|
5680
|
+
resolved,
|
|
5681
|
+
message: `\u2705 Resolved ${resolved} gotcha(s) for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5682
|
+
};
|
|
5683
|
+
}
|
|
5684
|
+
return { resolved: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5685
|
+
} catch (err) {
|
|
5686
|
+
return { resolved: 0, message: `\u274C Failed to resolve gotcha: ${err}` };
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
async function deprecateGotcha(idOrScope, reason) {
|
|
5690
|
+
try {
|
|
5691
|
+
await ensureGotchasSchema();
|
|
5692
|
+
const db = await getDb();
|
|
5693
|
+
let deprecated = 0;
|
|
5694
|
+
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5695
|
+
if (isId) {
|
|
5696
|
+
const id = Number(idOrScope);
|
|
5697
|
+
db.run(
|
|
5698
|
+
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'deprecation', last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'deprecated'`,
|
|
5699
|
+
[id]
|
|
5700
|
+
);
|
|
5701
|
+
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5702
|
+
} else {
|
|
5703
|
+
const scope = String(idOrScope).trim();
|
|
5704
|
+
db.run(
|
|
5705
|
+
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'deprecation', last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE (file_path = ? OR file_path LIKE ?) AND status != 'deprecated'`,
|
|
5706
|
+
[scope, `%${scope}%`]
|
|
5707
|
+
);
|
|
5708
|
+
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5709
|
+
}
|
|
5710
|
+
if (deprecated > 0) {
|
|
5711
|
+
saveDb();
|
|
5712
|
+
rebuildFtsIndex();
|
|
5713
|
+
return {
|
|
5714
|
+
deprecated,
|
|
5715
|
+
message: `\u{1F9F9} Deprecated ${deprecated} gotcha(s) for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5716
|
+
};
|
|
5717
|
+
}
|
|
5718
|
+
return { deprecated: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5719
|
+
} catch (err) {
|
|
5720
|
+
return { deprecated: 0, message: `\u274C Failed to deprecate gotcha: ${err}` };
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
4818
5723
|
var init_kumaGotchas = __esm({
|
|
4819
5724
|
"src/engine/kumaGotchas.ts"() {
|
|
4820
5725
|
"use strict";
|
|
@@ -4831,29 +5736,34 @@ var kumaMaintenance_exports = {};
|
|
|
4831
5736
|
__export(kumaMaintenance_exports, {
|
|
4832
5737
|
maybeRunAutoCleanup: () => maybeRunAutoCleanup
|
|
4833
5738
|
});
|
|
4834
|
-
import
|
|
4835
|
-
import
|
|
5739
|
+
import fs12 from "fs";
|
|
5740
|
+
import path13 from "path";
|
|
4836
5741
|
async function maybeRunAutoCleanup() {
|
|
4837
5742
|
try {
|
|
4838
5743
|
const root = getProjectRoot();
|
|
4839
|
-
const stateFile =
|
|
5744
|
+
const stateFile = path13.join(root, GC_STATE_FILE);
|
|
4840
5745
|
const now = Date.now();
|
|
4841
5746
|
let lastRun = 0;
|
|
4842
|
-
if (
|
|
5747
|
+
if (fs12.existsSync(stateFile)) {
|
|
4843
5748
|
try {
|
|
4844
|
-
lastRun = JSON.parse(
|
|
5749
|
+
lastRun = JSON.parse(fs12.readFileSync(stateFile, "utf-8")).lastRun || 0;
|
|
4845
5750
|
} catch {
|
|
4846
5751
|
lastRun = 0;
|
|
4847
5752
|
}
|
|
4848
5753
|
}
|
|
4849
5754
|
if (now - lastRun < GC_INTERVAL_MS) return "";
|
|
4850
|
-
|
|
4851
|
-
|
|
5755
|
+
fs12.mkdirSync(path13.dirname(stateFile), { recursive: true });
|
|
5756
|
+
fs12.writeFileSync(stateFile, JSON.stringify({ lastRun: now }), "utf-8");
|
|
4852
5757
|
const { syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
4853
5758
|
await syncGotchasToDb2();
|
|
4854
5759
|
const { runGarbageCollection: runGarbageCollection2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4855
5760
|
const gcResult = await runGarbageCollection2();
|
|
4856
|
-
const
|
|
5761
|
+
const { autoDeprecateStaleGotchas: autoDeprecateStaleGotchas2 } = await Promise.resolve().then(() => (init_kumaSelfHeal(), kumaSelfHeal_exports));
|
|
5762
|
+
const deprecateRes = await autoDeprecateStaleGotchas2();
|
|
5763
|
+
let summary = gcResult.replace(/\n/g, " ").substring(0, 80);
|
|
5764
|
+
if (deprecateRes.deprecated > 0) {
|
|
5765
|
+
summary += ` (${deprecateRes.deprecated} gotcha(s) auto-deprecated)`;
|
|
5766
|
+
}
|
|
4857
5767
|
return `\u{1F9F9} ${summary}`;
|
|
4858
5768
|
} catch {
|
|
4859
5769
|
return "";
|
|
@@ -4879,22 +5789,22 @@ __export(kumaPathRules_exports, {
|
|
|
4879
5789
|
loadPathRules: () => loadPathRules,
|
|
4880
5790
|
matchRulePath: () => matchRulePath
|
|
4881
5791
|
});
|
|
4882
|
-
import
|
|
4883
|
-
import
|
|
5792
|
+
import fs13 from "fs";
|
|
5793
|
+
import path14 from "path";
|
|
4884
5794
|
function rulesDir() {
|
|
4885
|
-
return
|
|
5795
|
+
return path14.join(getProjectRoot(), RULES_DIR);
|
|
4886
5796
|
}
|
|
4887
5797
|
function ensureRulesDir() {
|
|
4888
5798
|
try {
|
|
4889
5799
|
const dir = rulesDir();
|
|
4890
|
-
if (!
|
|
5800
|
+
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
4891
5801
|
} catch {
|
|
4892
5802
|
}
|
|
4893
5803
|
}
|
|
4894
5804
|
function parseRuleFile(filePath) {
|
|
4895
5805
|
try {
|
|
4896
|
-
const raw =
|
|
4897
|
-
const id =
|
|
5806
|
+
const raw = fs13.readFileSync(filePath, "utf-8");
|
|
5807
|
+
const id = path14.basename(filePath, ".md");
|
|
4898
5808
|
const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
4899
5809
|
if (!fm) {
|
|
4900
5810
|
return { id, description: id, paths: ["*"], content: raw.trim(), filePath };
|
|
@@ -4919,7 +5829,7 @@ function loadPathRules() {
|
|
|
4919
5829
|
ensureRulesDir();
|
|
4920
5830
|
try {
|
|
4921
5831
|
const dir = rulesDir();
|
|
4922
|
-
return
|
|
5832
|
+
return fs13.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => parseRuleFile(path14.join(dir, f))).filter((r) => r !== null);
|
|
4923
5833
|
} catch {
|
|
4924
5834
|
return [];
|
|
4925
5835
|
}
|
|
@@ -5006,12 +5916,12 @@ __export(kumaCodeScanner_exports, {
|
|
|
5006
5916
|
scanAndFormat: () => scanAndFormat,
|
|
5007
5917
|
scanCodebase: () => scanCodebase
|
|
5008
5918
|
});
|
|
5009
|
-
import
|
|
5010
|
-
import
|
|
5011
|
-
import
|
|
5919
|
+
import fastGlob3 from "fast-glob";
|
|
5920
|
+
import fs14 from "fs";
|
|
5921
|
+
import path15 from "path";
|
|
5012
5922
|
function getRoot() {
|
|
5013
|
-
if (!
|
|
5014
|
-
return
|
|
5923
|
+
if (!_cachedRoot2) _cachedRoot2 = getProjectRoot();
|
|
5924
|
+
return _cachedRoot2;
|
|
5015
5925
|
}
|
|
5016
5926
|
function lineAt(content, index) {
|
|
5017
5927
|
return content.substring(0, index).split("\n").length;
|
|
@@ -5019,10 +5929,10 @@ function lineAt(content, index) {
|
|
|
5019
5929
|
function getDirectoryDirs(filePaths) {
|
|
5020
5930
|
const dirs = /* @__PURE__ */ new Set();
|
|
5021
5931
|
for (const fp of filePaths) {
|
|
5022
|
-
let dir =
|
|
5932
|
+
let dir = path15.dirname(fp);
|
|
5023
5933
|
while (dir && dir !== "." && dir !== "/") {
|
|
5024
5934
|
dirs.add(dir);
|
|
5025
|
-
dir =
|
|
5935
|
+
dir = path15.dirname(dir);
|
|
5026
5936
|
}
|
|
5027
5937
|
}
|
|
5028
5938
|
return [...dirs].sort();
|
|
@@ -5053,7 +5963,7 @@ async function scanCodebase(options = {}) {
|
|
|
5053
5963
|
];
|
|
5054
5964
|
let files;
|
|
5055
5965
|
try {
|
|
5056
|
-
files = await
|
|
5966
|
+
files = await fastGlob3(includePatterns, {
|
|
5057
5967
|
cwd: root,
|
|
5058
5968
|
ignore: ignorePatterns,
|
|
5059
5969
|
onlyFiles: true,
|
|
@@ -5072,16 +5982,16 @@ async function scanCodebase(options = {}) {
|
|
|
5072
5982
|
const scannedFilePaths = [];
|
|
5073
5983
|
for (let i = 0; i < scannedCount; i++) {
|
|
5074
5984
|
const filePath = files[i];
|
|
5075
|
-
const fullPath =
|
|
5076
|
-
if (!
|
|
5077
|
-
const stat =
|
|
5985
|
+
const fullPath = path15.join(root, filePath);
|
|
5986
|
+
if (!fs14.existsSync(fullPath)) continue;
|
|
5987
|
+
const stat = fs14.statSync(fullPath);
|
|
5078
5988
|
if (stat.size > maxFileSize) continue;
|
|
5079
5989
|
const mtime = stat.mtimeMs;
|
|
5080
5990
|
const cached = fileCache.get(filePath);
|
|
5081
5991
|
if (cached === mtime && !options.force) continue;
|
|
5082
5992
|
fileCache.set(filePath, mtime);
|
|
5083
5993
|
try {
|
|
5084
|
-
const content =
|
|
5994
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
5085
5995
|
const parsed = parseFile(filePath, content);
|
|
5086
5996
|
allParsed.push({ filePath, parsed, content });
|
|
5087
5997
|
scannedFilePaths.push(filePath);
|
|
@@ -5124,7 +6034,7 @@ async function scanCodebase(options = {}) {
|
|
|
5124
6034
|
});
|
|
5125
6035
|
result.nodeCount++;
|
|
5126
6036
|
for (const fp of scannedFilePaths) {
|
|
5127
|
-
if (
|
|
6037
|
+
if (path15.dirname(fp) === dir) {
|
|
5128
6038
|
const fileId = nodeId("file", fp);
|
|
5129
6039
|
try {
|
|
5130
6040
|
await addEdge({ sourceId: moduleId, targetId: fileId, type: "owns" });
|
|
@@ -5481,20 +6391,20 @@ async function recordParsedFile(filePath, parsed, content, result) {
|
|
|
5481
6391
|
}
|
|
5482
6392
|
function resolveImportPath(fromFile, importPath, root) {
|
|
5483
6393
|
if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
|
|
5484
|
-
const fromDir =
|
|
6394
|
+
const fromDir = path15.dirname(path15.join(root, fromFile));
|
|
5485
6395
|
const exts = [
|
|
5486
6396
|
...SOURCE_EXTENSIONS,
|
|
5487
6397
|
...SOURCE_EXTENSIONS.map((e) => `/index${e}`)
|
|
5488
6398
|
];
|
|
5489
6399
|
for (const ext of exts) {
|
|
5490
|
-
const resolved =
|
|
5491
|
-
if (
|
|
6400
|
+
const resolved = path15.resolve(fromDir, importPath + ext);
|
|
6401
|
+
if (fs14.existsSync(resolved)) return path15.relative(root, resolved);
|
|
5492
6402
|
}
|
|
5493
|
-
const dirPath =
|
|
5494
|
-
if (
|
|
6403
|
+
const dirPath = path15.resolve(fromDir, importPath);
|
|
6404
|
+
if (fs14.existsSync(dirPath) && fs14.statSync(dirPath).isDirectory()) {
|
|
5495
6405
|
for (const ext of SOURCE_EXTENSIONS) {
|
|
5496
|
-
const indexPath =
|
|
5497
|
-
if (
|
|
6406
|
+
const indexPath = path15.join(dirPath, `index${ext}`);
|
|
6407
|
+
if (fs14.existsSync(indexPath)) return path15.relative(root, indexPath);
|
|
5498
6408
|
}
|
|
5499
6409
|
}
|
|
5500
6410
|
return null;
|
|
@@ -5529,7 +6439,7 @@ async function scanAndFormat(options = {}) {
|
|
|
5529
6439
|
const result = await scanCodebase(options);
|
|
5530
6440
|
return formatScanResult(result);
|
|
5531
6441
|
}
|
|
5532
|
-
var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache,
|
|
6442
|
+
var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache, _cachedRoot2, FUNCTION_DECL_RE, ARROW_FN_RE, TYPED_ARROW_RE, CLASS_RE, IMPORT_RE, JSX_RETURN_RE, JSX_ELEMENT_RE, JSX_IMPLICIT_RE, EXPRESS_ROUTE_RE, HONO_ROUTE_RE, EXPORT_RE, CALL_RE, knownComponents, knownFunctions, symbolLocations;
|
|
5533
6443
|
var init_kumaCodeScanner = __esm({
|
|
5534
6444
|
"src/engine/kumaCodeScanner.ts"() {
|
|
5535
6445
|
"use strict";
|
|
@@ -5539,7 +6449,7 @@ var init_kumaCodeScanner = __esm({
|
|
|
5539
6449
|
DEFAULT_MAX_FILES = 200;
|
|
5540
6450
|
DEFAULT_MAX_FILE_SIZE = 100 * 1024;
|
|
5541
6451
|
fileCache = /* @__PURE__ */ new Map();
|
|
5542
|
-
|
|
6452
|
+
_cachedRoot2 = null;
|
|
5543
6453
|
FUNCTION_DECL_RE = /(?:export\s+)?(?:async\s+)?function\s*(?:\*\s*)?(\w+)\s*\(/g;
|
|
5544
6454
|
ARROW_FN_RE = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*[=:]\s*(?:async\s*)?(?:<[^>]+>\s*)?(?:\([\s\S]*?\)|\w+)\s*(?::\s*\w+(?:<[^>]*>)?)?\s*=>/g;
|
|
5545
6455
|
TYPED_ARROW_RE = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*:\s*(?:\w+(?:<[^>]*>)?)?\s*=\s*(?:async\s*)?(?:<[^>]+>\s*)?\(/g;
|
|
@@ -5565,8 +6475,8 @@ __export(kumaFlowCache_exports, {
|
|
|
5565
6475
|
getFlowFreshness: () => getFlowFreshness,
|
|
5566
6476
|
getFreshDomainFlow: () => getFreshDomainFlow
|
|
5567
6477
|
});
|
|
5568
|
-
import
|
|
5569
|
-
import
|
|
6478
|
+
import fs15 from "fs";
|
|
6479
|
+
import path16 from "path";
|
|
5570
6480
|
async function getFlowFreshness(domain) {
|
|
5571
6481
|
try {
|
|
5572
6482
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
@@ -5601,26 +6511,26 @@ async function getFlowFreshness(domain) {
|
|
|
5601
6511
|
}
|
|
5602
6512
|
function resolveImport(fromFile, spec) {
|
|
5603
6513
|
const root = getProjectRoot();
|
|
5604
|
-
const fromDir =
|
|
6514
|
+
const fromDir = path16.dirname(fromFile);
|
|
5605
6515
|
const candidates = [];
|
|
5606
6516
|
if (spec.startsWith(".")) {
|
|
5607
|
-
const abs =
|
|
6517
|
+
const abs = path16.resolve(fromDir, spec);
|
|
5608
6518
|
candidates.push(abs);
|
|
5609
6519
|
} else {
|
|
5610
|
-
candidates.push(
|
|
6520
|
+
candidates.push(path16.join(root, spec));
|
|
5611
6521
|
}
|
|
5612
6522
|
for (const c of candidates) {
|
|
5613
6523
|
for (const ext of SOURCE_EXTENSIONS) {
|
|
5614
6524
|
const withExt = `${c}${ext}`;
|
|
5615
|
-
if (
|
|
5616
|
-
if (
|
|
5617
|
-
const idx =
|
|
5618
|
-
if (
|
|
6525
|
+
if (fs15.existsSync(withExt)) return path16.relative(root, withExt);
|
|
6526
|
+
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) {
|
|
6527
|
+
const idx = path16.join(c, "index") + ext;
|
|
6528
|
+
if (fs15.existsSync(idx)) return path16.relative(root, idx);
|
|
5619
6529
|
}
|
|
5620
6530
|
}
|
|
5621
|
-
if (
|
|
6531
|
+
if (fs15.existsSync(c)) {
|
|
5622
6532
|
try {
|
|
5623
|
-
if (
|
|
6533
|
+
if (fs15.statSync(c).isFile()) return path16.relative(root, c);
|
|
5624
6534
|
} catch {
|
|
5625
6535
|
}
|
|
5626
6536
|
}
|
|
@@ -5629,13 +6539,13 @@ function resolveImport(fromFile, spec) {
|
|
|
5629
6539
|
}
|
|
5630
6540
|
function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
5631
6541
|
const root = getProjectRoot();
|
|
5632
|
-
const absEntry =
|
|
5633
|
-
if (!
|
|
6542
|
+
const absEntry = path16.isAbsolute(entryFile) ? entryFile : path16.resolve(root, entryFile);
|
|
6543
|
+
if (!fs15.existsSync(absEntry)) return [];
|
|
5634
6544
|
const hops = [];
|
|
5635
6545
|
const seen = /* @__PURE__ */ new Set([entryFile]);
|
|
5636
|
-
const fromName =
|
|
6546
|
+
const fromName = path16.basename(absEntry);
|
|
5637
6547
|
try {
|
|
5638
|
-
const lines =
|
|
6548
|
+
const lines = fs15.readFileSync(absEntry, "utf-8").split("\n");
|
|
5639
6549
|
for (const line of lines) {
|
|
5640
6550
|
if (hops.length >= maxHops) break;
|
|
5641
6551
|
const spec = matchImportPath(line);
|
|
@@ -5645,9 +6555,9 @@ function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
|
5645
6555
|
seen.add(resolved);
|
|
5646
6556
|
hops.push({
|
|
5647
6557
|
from: fromName,
|
|
5648
|
-
to:
|
|
6558
|
+
to: path16.basename(resolved),
|
|
5649
6559
|
relation: "imports",
|
|
5650
|
-
description: `${fromName} imports ${
|
|
6560
|
+
description: `${fromName} imports ${path16.basename(resolved)}`
|
|
5651
6561
|
});
|
|
5652
6562
|
}
|
|
5653
6563
|
} catch {
|
|
@@ -5679,7 +6589,7 @@ Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} |
|
|
|
5679
6589
|
hops,
|
|
5680
6590
|
filePaths: freshness.filePaths
|
|
5681
6591
|
});
|
|
5682
|
-
lines.push(`\u2705 Re-derived ${hops.length} hop(s) from ${
|
|
6592
|
+
lines.push(`\u2705 Re-derived ${hops.length} hop(s) from ${path16.basename(entry)} \u2014 cache refreshed.`);
|
|
5683
6593
|
} else {
|
|
5684
6594
|
lines.push("\u26A0\uFE0F No imports to follow \u2014 cache left as-is (record hops manually if needed).");
|
|
5685
6595
|
}
|
|
@@ -5714,8 +6624,8 @@ __export(kumaSearch_exports, {
|
|
|
5714
6624
|
formatHybridResults: () => formatHybridResults,
|
|
5715
6625
|
hybridSearch: () => hybridSearch
|
|
5716
6626
|
});
|
|
5717
|
-
import
|
|
5718
|
-
import
|
|
6627
|
+
import fs17 from "fs";
|
|
6628
|
+
import path18 from "path";
|
|
5719
6629
|
function expandQueryTerms(query) {
|
|
5720
6630
|
const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
|
|
5721
6631
|
const expanded = /* @__PURE__ */ new Map();
|
|
@@ -5778,13 +6688,13 @@ async function buildSearchVectors() {
|
|
|
5778
6688
|
}
|
|
5779
6689
|
try {
|
|
5780
6690
|
const root = getProjectRoot();
|
|
5781
|
-
const memDir =
|
|
5782
|
-
if (
|
|
5783
|
-
const files =
|
|
6691
|
+
const memDir = path18.join(root, ".kuma", "memories");
|
|
6692
|
+
if (fs17.existsSync(memDir)) {
|
|
6693
|
+
const files = fs17.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
|
|
5784
6694
|
for (const file of files) {
|
|
5785
6695
|
try {
|
|
5786
|
-
const fullPath =
|
|
5787
|
-
const content =
|
|
6696
|
+
const fullPath = path18.join(memDir, file);
|
|
6697
|
+
const content = fs17.readFileSync(fullPath, "utf-8");
|
|
5788
6698
|
const text = content.toLowerCase();
|
|
5789
6699
|
const rawTerms = extractTerms(text);
|
|
5790
6700
|
const termCounts = /* @__PURE__ */ new Map();
|
|
@@ -5795,7 +6705,7 @@ async function buildSearchVectors() {
|
|
|
5795
6705
|
terms: termCounts,
|
|
5796
6706
|
source: file.replace(/\.md$/, ""),
|
|
5797
6707
|
sourceType: "memory",
|
|
5798
|
-
recency: recencyScore(Math.floor(
|
|
6708
|
+
recency: recencyScore(Math.floor(fs17.statSync(fullPath).mtimeMs / 1e3))
|
|
5799
6709
|
});
|
|
5800
6710
|
} catch {
|
|
5801
6711
|
}
|
|
@@ -6105,6 +7015,137 @@ var init_kumaSearch = __esm({
|
|
|
6105
7015
|
}
|
|
6106
7016
|
});
|
|
6107
7017
|
|
|
7018
|
+
// src/guards/architectureGuard.ts
|
|
7019
|
+
var architectureGuard_exports = {};
|
|
7020
|
+
__export(architectureGuard_exports, {
|
|
7021
|
+
checkArchitectureBoundaries: () => checkArchitectureBoundaries,
|
|
7022
|
+
detectWorkspaceCircularDeps: () => detectWorkspaceCircularDeps,
|
|
7023
|
+
loadArchitectureRules: () => loadArchitectureRules
|
|
7024
|
+
});
|
|
7025
|
+
import fs21 from "fs";
|
|
7026
|
+
import path22 from "path";
|
|
7027
|
+
import fastGlob4 from "fast-glob";
|
|
7028
|
+
function loadArchitectureRules(root) {
|
|
7029
|
+
const rulesPath = path22.join(root, ".kuma", "architecture.json");
|
|
7030
|
+
if (fs21.existsSync(rulesPath)) {
|
|
7031
|
+
try {
|
|
7032
|
+
const data = JSON.parse(fs21.readFileSync(rulesPath, "utf-8"));
|
|
7033
|
+
if (Array.isArray(data.rules)) {
|
|
7034
|
+
return data.rules;
|
|
7035
|
+
}
|
|
7036
|
+
} catch {
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
return [];
|
|
7040
|
+
}
|
|
7041
|
+
function detectWorkspaceCircularDeps(wsInfo) {
|
|
7042
|
+
const circulars = [];
|
|
7043
|
+
const visited = /* @__PURE__ */ new Set();
|
|
7044
|
+
const recStack = /* @__PURE__ */ new Set();
|
|
7045
|
+
function dfs(current, pathTrace) {
|
|
7046
|
+
visited.add(current);
|
|
7047
|
+
recStack.add(current);
|
|
7048
|
+
pathTrace.push(current);
|
|
7049
|
+
const pkg = wsInfo.packageMap.get(current);
|
|
7050
|
+
if (pkg) {
|
|
7051
|
+
for (const dep of pkg.internalDependencies) {
|
|
7052
|
+
if (!visited.has(dep)) {
|
|
7053
|
+
dfs(dep, [...pathTrace]);
|
|
7054
|
+
} else if (recStack.has(dep)) {
|
|
7055
|
+
const cycleStart = pathTrace.indexOf(dep);
|
|
7056
|
+
circulars.push([...pathTrace.slice(cycleStart), dep]);
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
recStack.delete(current);
|
|
7061
|
+
}
|
|
7062
|
+
for (const pkg of wsInfo.packages) {
|
|
7063
|
+
if (!visited.has(pkg.name)) {
|
|
7064
|
+
dfs(pkg.name, []);
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
7067
|
+
return circulars;
|
|
7068
|
+
}
|
|
7069
|
+
async function checkArchitectureBoundaries(filesToCheck, customRoot) {
|
|
7070
|
+
const root = customRoot || getProjectRoot();
|
|
7071
|
+
const wsInfo = await getWorkspaceInfo(root);
|
|
7072
|
+
const violations = [];
|
|
7073
|
+
const circulars = detectWorkspaceCircularDeps(wsInfo);
|
|
7074
|
+
for (const cycle of circulars) {
|
|
7075
|
+
violations.push({
|
|
7076
|
+
sourceFile: cycle[0],
|
|
7077
|
+
targetImport: cycle[cycle.length - 1],
|
|
7078
|
+
rule: "circular-package-dependency",
|
|
7079
|
+
severity: "high",
|
|
7080
|
+
suggestion: `Circular dependency detected: ${cycle.join(" \u2192 ")}. Extract shared interfaces to a separate package.`
|
|
7081
|
+
});
|
|
7082
|
+
}
|
|
7083
|
+
let targetFiles = filesToCheck;
|
|
7084
|
+
if (!targetFiles || targetFiles.length === 0) {
|
|
7085
|
+
targetFiles = await fastGlob4(["**/*.{ts,tsx,js,jsx,mjs,cjs}"], {
|
|
7086
|
+
cwd: root,
|
|
7087
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.kuma/**"],
|
|
7088
|
+
onlyFiles: true
|
|
7089
|
+
});
|
|
7090
|
+
}
|
|
7091
|
+
const customRules = loadArchitectureRules(root);
|
|
7092
|
+
const importRegex = /(?:import|export\s+.*from|require\s*\()\s*['"]([^'"]+)['"]/g;
|
|
7093
|
+
for (const file of targetFiles) {
|
|
7094
|
+
const fullPath = path22.isAbsolute(file) ? file : path22.join(root, file);
|
|
7095
|
+
if (!fs21.existsSync(fullPath)) continue;
|
|
7096
|
+
let content = "";
|
|
7097
|
+
try {
|
|
7098
|
+
content = fs21.readFileSync(fullPath, "utf-8");
|
|
7099
|
+
} catch {
|
|
7100
|
+
continue;
|
|
7101
|
+
}
|
|
7102
|
+
const currentPkg = findPackageForFile(file, wsInfo);
|
|
7103
|
+
let match;
|
|
7104
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
7105
|
+
const importPath = match[1];
|
|
7106
|
+
if (wsInfo.isWorkspace && currentPkg && !currentPkg.isRoot) {
|
|
7107
|
+
if (importPath.startsWith("../") && (importPath.includes("/src/") || importPath.includes("/dist/"))) {
|
|
7108
|
+
const resolvedTarget = path22.normalize(path22.resolve(path22.dirname(fullPath), importPath));
|
|
7109
|
+
const targetPkg = findPackageForFile(resolvedTarget, wsInfo);
|
|
7110
|
+
if (targetPkg && targetPkg.name !== currentPkg.name) {
|
|
7111
|
+
violations.push({
|
|
7112
|
+
sourceFile: file,
|
|
7113
|
+
targetImport: importPath,
|
|
7114
|
+
rule: "no-deep-cross-package-import",
|
|
7115
|
+
severity: "high",
|
|
7116
|
+
suggestion: `Do not deep-import private files from '${targetPkg.name}'. Use the public package name \`${targetPkg.name}\` or its exported API.`
|
|
7117
|
+
});
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
}
|
|
7121
|
+
for (const rule of customRules) {
|
|
7122
|
+
const matchesFrom = file.includes(rule.from) || currentPkg && currentPkg.name === rule.from;
|
|
7123
|
+
if (matchesFrom) {
|
|
7124
|
+
for (const disallowed of rule.disallow) {
|
|
7125
|
+
if (importPath.includes(disallowed) || currentPkg && disallowed === currentPkg.name) {
|
|
7126
|
+
violations.push({
|
|
7127
|
+
sourceFile: file,
|
|
7128
|
+
targetImport: importPath,
|
|
7129
|
+
rule: `disallowed-boundary: ${rule.from} \u2192 ${disallowed}`,
|
|
7130
|
+
severity: "high",
|
|
7131
|
+
suggestion: rule.message || `Import from '${disallowed}' is forbidden in '${rule.from}'.`
|
|
7132
|
+
});
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
return violations;
|
|
7140
|
+
}
|
|
7141
|
+
var init_architectureGuard = __esm({
|
|
7142
|
+
"src/guards/architectureGuard.ts"() {
|
|
7143
|
+
"use strict";
|
|
7144
|
+
init_pathValidator();
|
|
7145
|
+
init_workspaceIntelligence();
|
|
7146
|
+
}
|
|
7147
|
+
});
|
|
7148
|
+
|
|
6108
7149
|
// src/engine/kumaCheckpoint.ts
|
|
6109
7150
|
var kumaCheckpoint_exports = {};
|
|
6110
7151
|
__export(kumaCheckpoint_exports, {
|
|
@@ -6113,16 +7154,16 @@ __export(kumaCheckpoint_exports, {
|
|
|
6113
7154
|
pruneCheckpoints: () => pruneCheckpoints,
|
|
6114
7155
|
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
6115
7156
|
});
|
|
6116
|
-
import
|
|
6117
|
-
import
|
|
7157
|
+
import fs22 from "fs";
|
|
7158
|
+
import path23 from "path";
|
|
6118
7159
|
async function createCheckpoint(label, description) {
|
|
6119
7160
|
try {
|
|
6120
7161
|
const root = getProjectRoot();
|
|
6121
|
-
const cpDir =
|
|
6122
|
-
if (
|
|
7162
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
7163
|
+
if (fs22.existsSync(cpDir)) {
|
|
6123
7164
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
6124
7165
|
}
|
|
6125
|
-
|
|
7166
|
+
fs22.mkdirSync(cpDir, { recursive: true });
|
|
6126
7167
|
const db = await getDb();
|
|
6127
7168
|
const filesStmt = db.prepare(
|
|
6128
7169
|
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
@@ -6131,19 +7172,19 @@ async function createCheckpoint(label, description) {
|
|
|
6131
7172
|
while (filesStmt.step()) {
|
|
6132
7173
|
const row = filesStmt.getAsObject();
|
|
6133
7174
|
const fp = row.file_path;
|
|
6134
|
-
const fullPath =
|
|
6135
|
-
if (
|
|
6136
|
-
const content =
|
|
7175
|
+
const fullPath = path23.resolve(root, fp);
|
|
7176
|
+
if (fs22.existsSync(fullPath)) {
|
|
7177
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
6137
7178
|
const hash = simpleHash2(content);
|
|
6138
|
-
const fileDir =
|
|
6139
|
-
if (!
|
|
6140
|
-
|
|
7179
|
+
const fileDir = path23.dirname(path23.join(cpDir, "files", fp));
|
|
7180
|
+
if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
|
|
7181
|
+
fs22.writeFileSync(path23.join(cpDir, "files", fp), content, "utf-8");
|
|
6141
7182
|
files.push({ path: fp, hash });
|
|
6142
7183
|
}
|
|
6143
7184
|
}
|
|
6144
7185
|
filesStmt.free();
|
|
6145
7186
|
const dbData = db.export();
|
|
6146
|
-
|
|
7187
|
+
fs22.writeFileSync(path23.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
6147
7188
|
const manifest = {
|
|
6148
7189
|
label,
|
|
6149
7190
|
timestamp: Date.now(),
|
|
@@ -6151,8 +7192,8 @@ async function createCheckpoint(label, description) {
|
|
|
6151
7192
|
dbSnapshot: true,
|
|
6152
7193
|
description
|
|
6153
7194
|
};
|
|
6154
|
-
|
|
6155
|
-
|
|
7195
|
+
fs22.writeFileSync(
|
|
7196
|
+
path23.join(cpDir, "manifest.json"),
|
|
6156
7197
|
JSON.stringify(manifest, null, 2),
|
|
6157
7198
|
"utf-8"
|
|
6158
7199
|
);
|
|
@@ -6178,39 +7219,39 @@ async function createCheckpoint(label, description) {
|
|
|
6178
7219
|
async function rollbackToCheckpoint(label) {
|
|
6179
7220
|
try {
|
|
6180
7221
|
const root = getProjectRoot();
|
|
6181
|
-
const cpDir =
|
|
6182
|
-
const manifestPath =
|
|
6183
|
-
if (!
|
|
7222
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
7223
|
+
const manifestPath = path23.join(cpDir, "manifest.json");
|
|
7224
|
+
if (!fs22.existsSync(manifestPath)) {
|
|
6184
7225
|
return `\u274C Checkpoint "${label}" not found. Available checkpoints:
|
|
6185
7226
|
|
|
6186
7227
|
${listCheckpoints()}`;
|
|
6187
7228
|
}
|
|
6188
7229
|
const manifest = JSON.parse(
|
|
6189
|
-
|
|
7230
|
+
fs22.readFileSync(manifestPath, "utf-8")
|
|
6190
7231
|
);
|
|
6191
7232
|
let restored = 0;
|
|
6192
7233
|
let failed = 0;
|
|
6193
7234
|
for (const f of manifest.files) {
|
|
6194
|
-
const snapshotPath =
|
|
6195
|
-
if (
|
|
7235
|
+
const snapshotPath = path23.join(cpDir, "files", f.path);
|
|
7236
|
+
if (fs22.existsSync(snapshotPath)) {
|
|
6196
7237
|
try {
|
|
6197
|
-
const content =
|
|
6198
|
-
const targetPath =
|
|
6199
|
-
const targetDir =
|
|
6200
|
-
if (!
|
|
6201
|
-
|
|
7238
|
+
const content = fs22.readFileSync(snapshotPath, "utf-8");
|
|
7239
|
+
const targetPath = path23.resolve(root, f.path);
|
|
7240
|
+
const targetDir = path23.dirname(targetPath);
|
|
7241
|
+
if (!fs22.existsSync(targetDir)) fs22.mkdirSync(targetDir, { recursive: true });
|
|
7242
|
+
fs22.writeFileSync(targetPath, content, "utf-8");
|
|
6202
7243
|
restored++;
|
|
6203
7244
|
} catch {
|
|
6204
7245
|
failed++;
|
|
6205
7246
|
}
|
|
6206
7247
|
}
|
|
6207
7248
|
}
|
|
6208
|
-
const dbSnapshotPath =
|
|
6209
|
-
if (
|
|
6210
|
-
const kumaDir =
|
|
6211
|
-
const dbPath =
|
|
6212
|
-
const snapshotData =
|
|
6213
|
-
|
|
7249
|
+
const dbSnapshotPath = path23.join(cpDir, "kuma.db");
|
|
7250
|
+
if (fs22.existsSync(dbSnapshotPath)) {
|
|
7251
|
+
const kumaDir = path23.join(root, ".kuma");
|
|
7252
|
+
const dbPath = path23.join(kumaDir, "kuma.db");
|
|
7253
|
+
const snapshotData = fs22.readFileSync(dbSnapshotPath);
|
|
7254
|
+
fs22.writeFileSync(dbPath, snapshotData);
|
|
6214
7255
|
resetDbInstance();
|
|
6215
7256
|
}
|
|
6216
7257
|
sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
|
|
@@ -6235,18 +7276,18 @@ ${listCheckpoints()}`;
|
|
|
6235
7276
|
function listCheckpoints() {
|
|
6236
7277
|
try {
|
|
6237
7278
|
const root = getProjectRoot();
|
|
6238
|
-
const cpDir =
|
|
6239
|
-
if (!
|
|
7279
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR);
|
|
7280
|
+
if (!fs22.existsSync(cpDir)) {
|
|
6240
7281
|
return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
|
|
6241
7282
|
}
|
|
6242
|
-
const entries =
|
|
7283
|
+
const entries = fs22.readdirSync(cpDir);
|
|
6243
7284
|
const checkpoints = [];
|
|
6244
7285
|
for (const entry of entries) {
|
|
6245
|
-
const manifestPath =
|
|
6246
|
-
if (
|
|
7286
|
+
const manifestPath = path23.join(cpDir, entry, "manifest.json");
|
|
7287
|
+
if (fs22.existsSync(manifestPath)) {
|
|
6247
7288
|
try {
|
|
6248
7289
|
const manifest = JSON.parse(
|
|
6249
|
-
|
|
7290
|
+
fs22.readFileSync(manifestPath, "utf-8")
|
|
6250
7291
|
);
|
|
6251
7292
|
checkpoints.push({ label: entry, manifest });
|
|
6252
7293
|
} catch {
|
|
@@ -6278,12 +7319,12 @@ function listCheckpoints() {
|
|
|
6278
7319
|
function pruneCheckpoints(keep = 5) {
|
|
6279
7320
|
try {
|
|
6280
7321
|
const root = getProjectRoot();
|
|
6281
|
-
const cpDir =
|
|
6282
|
-
if (!
|
|
6283
|
-
const entries =
|
|
7322
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR);
|
|
7323
|
+
if (!fs22.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
|
|
7324
|
+
const entries = fs22.readdirSync(cpDir).map((e) => ({ name: e, time: fs22.statSync(path23.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
6284
7325
|
let removed = 0;
|
|
6285
7326
|
for (let i = keep; i < entries.length; i++) {
|
|
6286
|
-
|
|
7327
|
+
fs22.rmSync(path23.join(cpDir, entries[i].name), { recursive: true, force: true });
|
|
6287
7328
|
removed++;
|
|
6288
7329
|
}
|
|
6289
7330
|
return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
|
|
@@ -6324,16 +7365,16 @@ __export(kumaAutoGotcha_exports, {
|
|
|
6324
7365
|
saveAutoGotchaState: () => saveAutoGotchaState,
|
|
6325
7366
|
trackVerificationResult: () => trackVerificationResult
|
|
6326
7367
|
});
|
|
6327
|
-
import
|
|
6328
|
-
import
|
|
7368
|
+
import fs23 from "fs";
|
|
7369
|
+
import path24 from "path";
|
|
6329
7370
|
function statePath() {
|
|
6330
|
-
return
|
|
7371
|
+
return path24.join(getProjectRoot(), STATE_FILE);
|
|
6331
7372
|
}
|
|
6332
7373
|
function loadAutoGotchaState() {
|
|
6333
7374
|
try {
|
|
6334
7375
|
const fp = statePath();
|
|
6335
|
-
if (
|
|
6336
|
-
const parsed = JSON.parse(
|
|
7376
|
+
if (fs23.existsSync(fp)) {
|
|
7377
|
+
const parsed = JSON.parse(fs23.readFileSync(fp, "utf-8"));
|
|
6337
7378
|
return {
|
|
6338
7379
|
failures: parsed?.failures ?? {},
|
|
6339
7380
|
lastRecorded: parsed?.lastRecorded ?? {}
|
|
@@ -6346,9 +7387,9 @@ function loadAutoGotchaState() {
|
|
|
6346
7387
|
function saveAutoGotchaState(state) {
|
|
6347
7388
|
try {
|
|
6348
7389
|
const fp = statePath();
|
|
6349
|
-
const dir =
|
|
6350
|
-
if (!
|
|
6351
|
-
|
|
7390
|
+
const dir = path24.dirname(fp);
|
|
7391
|
+
if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
|
|
7392
|
+
fs23.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
|
|
6352
7393
|
} catch {
|
|
6353
7394
|
}
|
|
6354
7395
|
}
|
|
@@ -6388,6 +7429,7 @@ async function trackVerificationResult(scope, passed) {
|
|
|
6388
7429
|
filePath: scope,
|
|
6389
7430
|
description: `Verification keeps failing for scope "${scope}"`,
|
|
6390
7431
|
severity,
|
|
7432
|
+
status: "candidate",
|
|
6391
7433
|
workaround: `Run tests locally before touching this area. Failing scope: "${scope}" \u2014 ${failureCount} consecutive failures. Verify with kuma_safety({ action: "verify", scope: "${scope}" }) after fixes.`
|
|
6392
7434
|
});
|
|
6393
7435
|
state.lastRecorded[scope] = failureCount;
|
|
@@ -6431,37 +7473,37 @@ __export(kumaVerifier_exports, {
|
|
|
6431
7473
|
getRunningVerificationPid: () => getRunningVerificationPid,
|
|
6432
7474
|
runAutoVerification: () => runAutoVerification
|
|
6433
7475
|
});
|
|
6434
|
-
import
|
|
6435
|
-
import
|
|
7476
|
+
import fs24 from "fs";
|
|
7477
|
+
import path25 from "path";
|
|
6436
7478
|
import { exec } from "child_process";
|
|
6437
7479
|
function getLockDir(root = process.cwd()) {
|
|
6438
|
-
return
|
|
7480
|
+
return path25.resolve(root, LOCK_DIR_NAME);
|
|
6439
7481
|
}
|
|
6440
7482
|
function getPidFile(root = process.cwd()) {
|
|
6441
|
-
return
|
|
7483
|
+
return path25.join(getLockDir(root), "pid");
|
|
6442
7484
|
}
|
|
6443
7485
|
function acquireFileLock(root) {
|
|
6444
7486
|
const lockDir = getLockDir(root);
|
|
6445
7487
|
try {
|
|
6446
|
-
|
|
6447
|
-
|
|
7488
|
+
fs24.mkdirSync(lockDir, { recursive: false });
|
|
7489
|
+
fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
6448
7490
|
return true;
|
|
6449
7491
|
} catch {
|
|
6450
7492
|
try {
|
|
6451
|
-
if (
|
|
6452
|
-
const pid = parseInt(
|
|
7493
|
+
if (fs24.existsSync(getPidFile(root))) {
|
|
7494
|
+
const pid = parseInt(fs24.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
6453
7495
|
try {
|
|
6454
7496
|
process.kill(pid, 0);
|
|
6455
7497
|
return false;
|
|
6456
7498
|
} catch {
|
|
6457
|
-
|
|
7499
|
+
fs24.rmSync(lockDir, { recursive: true, force: true });
|
|
6458
7500
|
return acquireFileLock(root);
|
|
6459
7501
|
}
|
|
6460
7502
|
}
|
|
6461
7503
|
} catch {
|
|
6462
7504
|
}
|
|
6463
7505
|
try {
|
|
6464
|
-
|
|
7506
|
+
fs24.rmSync(lockDir, { recursive: true, force: true });
|
|
6465
7507
|
return acquireFileLock(root);
|
|
6466
7508
|
} catch {
|
|
6467
7509
|
return false;
|
|
@@ -6470,7 +7512,7 @@ function acquireFileLock(root) {
|
|
|
6470
7512
|
}
|
|
6471
7513
|
function releaseFileLock(root) {
|
|
6472
7514
|
try {
|
|
6473
|
-
|
|
7515
|
+
fs24.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
6474
7516
|
} catch {
|
|
6475
7517
|
}
|
|
6476
7518
|
}
|
|
@@ -6493,36 +7535,36 @@ function getRunningVerificationPid() {
|
|
|
6493
7535
|
return _currentProcess?.pid ?? null;
|
|
6494
7536
|
}
|
|
6495
7537
|
function detectTestRunner(root = process.cwd()) {
|
|
6496
|
-
const pkgPath =
|
|
6497
|
-
if (
|
|
7538
|
+
const pkgPath = path25.join(root, "package.json");
|
|
7539
|
+
if (fs24.existsSync(pkgPath)) {
|
|
6498
7540
|
try {
|
|
6499
|
-
const pkg = JSON.parse(
|
|
7541
|
+
const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
|
|
6500
7542
|
if (pkg.scripts && pkg.scripts.test) {
|
|
6501
|
-
if (
|
|
6502
|
-
if (
|
|
7543
|
+
if (fs24.existsSync(path25.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
7544
|
+
if (fs24.existsSync(path25.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
6503
7545
|
return { runner: "npm", baseCommand: "npm test" };
|
|
6504
7546
|
}
|
|
6505
7547
|
} catch {
|
|
6506
7548
|
}
|
|
6507
7549
|
}
|
|
6508
|
-
if (
|
|
6509
|
-
if (
|
|
6510
|
-
if (
|
|
6511
|
-
if (
|
|
7550
|
+
if (fs24.existsSync(path25.join(root, "pytest.ini")) || fs24.existsSync(path25.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
7551
|
+
if (fs24.existsSync(path25.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
7552
|
+
if (fs24.existsSync(path25.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
7553
|
+
if (fs24.existsSync(path25.join(root, "Makefile"))) {
|
|
6512
7554
|
try {
|
|
6513
|
-
const makefileContent =
|
|
7555
|
+
const makefileContent = fs24.readFileSync(path25.join(root, "Makefile"), "utf-8");
|
|
6514
7556
|
const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
|
|
6515
7557
|
if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
|
|
6516
7558
|
} catch {
|
|
6517
7559
|
}
|
|
6518
7560
|
}
|
|
6519
|
-
if (
|
|
7561
|
+
if (fs24.existsSync(path25.join(root, "package.json"))) {
|
|
6520
7562
|
try {
|
|
6521
|
-
const pkg = JSON.parse(
|
|
7563
|
+
const pkg = JSON.parse(fs24.readFileSync(path25.join(root, "package.json"), "utf-8"));
|
|
6522
7564
|
if (pkg.dependencies || pkg.devDependencies) {
|
|
6523
|
-
if (
|
|
6524
|
-
const srcDir =
|
|
6525
|
-
if (
|
|
7565
|
+
if (fs24.existsSync(path25.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
7566
|
+
const srcDir = path25.join(root, "src");
|
|
7567
|
+
if (fs24.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
6526
7568
|
}
|
|
6527
7569
|
} catch {
|
|
6528
7570
|
}
|
|
@@ -6600,24 +7642,52 @@ async function runAutoVerification(options = {}) {
|
|
|
6600
7642
|
"\u{1F4A1} **Tip:** This is NOT a pass \u2014 it means nothing was verified. Run your tests manually or install a test framework."
|
|
6601
7643
|
].join("\n");
|
|
6602
7644
|
}
|
|
7645
|
+
let fullCommand = baseCommand;
|
|
6603
7646
|
let testFiles = [];
|
|
6604
7647
|
const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
7648
|
+
let workspaceScoped = false;
|
|
7649
|
+
try {
|
|
7650
|
+
const { getWorkspaceInfo: getWorkspaceInfo2, getAffectedPackages: getAffectedPackages2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
7651
|
+
const wsInfo = await getWorkspaceInfo2(root);
|
|
7652
|
+
if (wsInfo.isWorkspace && modified.length > 0 && !options.scope) {
|
|
7653
|
+
const affected = getAffectedPackages2(modified, wsInfo);
|
|
7654
|
+
if (affected.affectedTestCommands.length > 0) {
|
|
7655
|
+
fullCommand = affected.affectedTestCommands[0];
|
|
7656
|
+
workspaceScoped = true;
|
|
6613
7657
|
}
|
|
6614
7658
|
}
|
|
6615
|
-
}
|
|
6616
|
-
testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
7659
|
+
} catch {
|
|
6617
7660
|
}
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
7661
|
+
if (!workspaceScoped) {
|
|
7662
|
+
if (options.scope) {
|
|
7663
|
+
const term = options.scope.toLowerCase();
|
|
7664
|
+
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
7665
|
+
if (testFiles.length === 0) {
|
|
7666
|
+
try {
|
|
7667
|
+
const { default: glob } = await import("fast-glob");
|
|
7668
|
+
testFiles = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
7669
|
+
} catch {
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
} else {
|
|
7673
|
+
const directTests = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
7674
|
+
if (directTests.length > 0) {
|
|
7675
|
+
testFiles = directTests;
|
|
7676
|
+
} else if (modified.length > 0) {
|
|
7677
|
+
try {
|
|
7678
|
+
const { default: glob } = await import("fast-glob");
|
|
7679
|
+
for (const f of modified.slice(0, 5)) {
|
|
7680
|
+
const base = path25.basename(f, path25.extname(f));
|
|
7681
|
+
const matches = await glob([`**/*${base}*.test.*`, `**/*${base}*.spec.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
7682
|
+
testFiles.push(...matches);
|
|
7683
|
+
}
|
|
7684
|
+
} catch {
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
if (testFiles.length > 0 && (runner === "npm" || runner === "pnpm" || runner === "yarn")) {
|
|
7689
|
+
fullCommand = `${baseCommand} -- ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
|
|
7690
|
+
}
|
|
6621
7691
|
}
|
|
6622
7692
|
return await new Promise((resolve) => {
|
|
6623
7693
|
const killTimer = setTimeout(() => {
|
|
@@ -6641,7 +7711,7 @@ async function runAutoVerification(options = {}) {
|
|
|
6641
7711
|
const heartbeatTimer = setInterval(() => {
|
|
6642
7712
|
if (getLockDir(root)) {
|
|
6643
7713
|
try {
|
|
6644
|
-
|
|
7714
|
+
fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
6645
7715
|
} catch {
|
|
6646
7716
|
}
|
|
6647
7717
|
}
|
|
@@ -6761,8 +7831,8 @@ __export(init_exports, {
|
|
|
6761
7831
|
generateInitMdContent: () => generateInitMdContent,
|
|
6762
7832
|
runInit: () => runInit
|
|
6763
7833
|
});
|
|
6764
|
-
import
|
|
6765
|
-
import
|
|
7834
|
+
import fs25 from "fs";
|
|
7835
|
+
import path26 from "path";
|
|
6766
7836
|
function configFilePath(type) {
|
|
6767
7837
|
switch (type) {
|
|
6768
7838
|
case "claude":
|
|
@@ -6860,27 +7930,31 @@ function aiderTemplate() {
|
|
|
6860
7930
|
}
|
|
6861
7931
|
function opencodeAgentsMdTemplate() {
|
|
6862
7932
|
const opencodeLines = [
|
|
6863
|
-
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7933
|
+
"Kuma MCP tools: kuma_kuma_context (or kuma_context), kuma_kuma_memory (or kuma_memory), kuma_kuma_safety (or kuma_safety).",
|
|
6864
7934
|
"Record what matters, skip what doesn't.",
|
|
6865
7935
|
"",
|
|
6866
7936
|
"<kuma_obedience>",
|
|
6867
7937
|
"## \u{1F512} Obedience (non-negotiable)",
|
|
6868
|
-
'- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
|
|
7938
|
+
'- MUST call `kuma_kuma_context({ action: "init" })` (or `kuma_context({ action: "init" })`) at session start.',
|
|
6869
7939
|
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
6870
|
-
'- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
|
|
6871
|
-
"- MUST NOT call actions outside the
|
|
7940
|
+
'- MUST call `kuma_kuma_safety({ action: "verify" })` (or `kuma_safety({ action: "verify" })`) after edits.',
|
|
7941
|
+
"- MUST NOT call actions outside the core actions.",
|
|
6872
7942
|
"</kuma_obedience>",
|
|
6873
7943
|
"",
|
|
6874
7944
|
"<kuma_workflow>",
|
|
6875
7945
|
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
6876
|
-
'\u{
|
|
7946
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_kuma_context({ action: "map" })`',
|
|
7947
|
+
'\u{1F52C} **Blast radius / impact:** `kuma_kuma_context({ action: "impact", target: "<file>" })` or `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
6877
7948
|
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
6878
7949
|
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
6879
7950
|
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
6880
7951
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
6881
7952
|
"</kuma_workflow>",
|
|
6882
7953
|
"",
|
|
6883
|
-
"\u26A0\uFE0F **OpenCode note:**
|
|
7954
|
+
"\u26A0\uFE0F **OpenCode note:** Depending on OpenCode MCP registration, tool names may be prefixed with server name (`kuma_kuma_*`) or bare (`kuma_*`). Both are valid.",
|
|
7955
|
+
"",
|
|
7956
|
+
"### \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
7957
|
+
"If Matcha is active: Matcha provides Intent Discovery (`.agents/plan/current.md`); Kuma provides blast radius, gotchas memory, and test verification.",
|
|
6884
7958
|
""
|
|
6885
7959
|
].join("\n");
|
|
6886
7960
|
return [
|
|
@@ -6888,7 +7962,8 @@ function opencodeAgentsMdTemplate() {
|
|
|
6888
7962
|
"",
|
|
6889
7963
|
opencodeLines,
|
|
6890
7964
|
"\u{1F4D6} Rules: `.kuma/init.md`",
|
|
6891
|
-
"\u{1F9E0}
|
|
7965
|
+
"\u{1F9E0} Skills: `.agents/skills/kuma/SKILL.md` \xB7 `.opencode/skills/kuma/SKILL.md`",
|
|
7966
|
+
"\u{1F50C} Plugin: `.opencode/plugins/kuma.js`"
|
|
6892
7967
|
].join("\n");
|
|
6893
7968
|
}
|
|
6894
7969
|
function codexTemplate() {
|
|
@@ -6996,6 +8071,150 @@ function antigravityMcpConfigTemplate() {
|
|
|
6996
8071
|
};
|
|
6997
8072
|
return JSON.stringify(config, null, 2) + "\n";
|
|
6998
8073
|
}
|
|
8074
|
+
function antigravityRuleTemplate() {
|
|
8075
|
+
return [
|
|
8076
|
+
"# \u{1F43B} Kuma MCP \u2014 Antigravity Rules",
|
|
8077
|
+
"",
|
|
8078
|
+
"This project uses **Kuma MCP** for runtime safety, blast radius analysis, and codebase memory.",
|
|
8079
|
+
"",
|
|
8080
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8081
|
+
"",
|
|
8082
|
+
"### \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
8083
|
+
"If Matcha is active in this workspace:",
|
|
8084
|
+
"- **Matcha** governs engineering philosophy, intent discovery, reuse checks, and review gates.",
|
|
8085
|
+
"- **Kuma** governs blast radius calculation, monorepo workspace dependencies, known gotchas memory, architecture boundary guards, and scoped test verification.",
|
|
8086
|
+
"- Workflow: Use Matcha to plan (Intent Discovery) \u2192 Use Kuma to inspect dependencies & blast radius (`kuma_context({ action: 'impact' })`) \u2192 Use Kuma to verify after edits (`kuma_safety({ action: 'verify' })`).",
|
|
8087
|
+
"",
|
|
8088
|
+
"\u{1F4D6} Rules: `.kuma/init.md`",
|
|
8089
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
8090
|
+
].join("\n");
|
|
8091
|
+
}
|
|
8092
|
+
function geminiTemplate() {
|
|
8093
|
+
return [
|
|
8094
|
+
"# \u{1F43B} Kuma MCP \u2014 Workspace Instructions",
|
|
8095
|
+
"",
|
|
8096
|
+
"This workspace uses **Kuma MCP** for runtime safety, monorepo intelligence, blast radius calculation, and memory.",
|
|
8097
|
+
"",
|
|
8098
|
+
"<kuma_obedience>",
|
|
8099
|
+
"## \u{1F512} Kuma MCP Safety & Memory (non-negotiable)",
|
|
8100
|
+
'- **Session Start:** Call `kuma_context({ action: "init" })` before reading or editing code.',
|
|
8101
|
+
'- **Monorepo / Blast Radius:** Call `kuma_context({ action: "impact", target: "<file>" })` or `kuma_context({ action: "research", scope: "<area>" })` before modifying unfamiliar code.',
|
|
8102
|
+
'- **Gotchas:** Call `kuma_memory({ action: "gotcha", scope: "<file>", content: "<description>" })` IMMEDIATELY when any quirk/bug is discovered.',
|
|
8103
|
+
'- **Decisions:** Call `kuma_memory({ action: "decision", title: "...", rationale: "..." })` when choosing between options.',
|
|
8104
|
+
'- **Post-Edits:** Call `kuma_safety({ action: "verify" })` after modifying code to run affected package/unit tests.',
|
|
8105
|
+
"</kuma_obedience>",
|
|
8106
|
+
"",
|
|
8107
|
+
"### \u{1F375} Matcha & Kuma Harmony",
|
|
8108
|
+
"If Matcha is active: Matcha provides Intent Discovery & review; Kuma provides blast radius, gotchas memory, architecture guards, and test verification.",
|
|
8109
|
+
"",
|
|
8110
|
+
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
8111
|
+
].join("\n");
|
|
8112
|
+
}
|
|
8113
|
+
function opencodePluginTemplate() {
|
|
8114
|
+
return [
|
|
8115
|
+
"/**",
|
|
8116
|
+
" * \u{1F43B} Kuma MCP \u2014 OpenCode Plugin",
|
|
8117
|
+
" * Auto-injects gotcha warnings and safety guidance before file edits and commands in OpenCode.",
|
|
8118
|
+
" * OpenCode loads all plugins from .opencode/plugins/*.js automatically.",
|
|
8119
|
+
" */",
|
|
8120
|
+
'import fs from "node:fs";',
|
|
8121
|
+
'import path from "node:path";',
|
|
8122
|
+
"",
|
|
8123
|
+
"function getProjectRoot() {",
|
|
8124
|
+
" let curr = process.cwd();",
|
|
8125
|
+
" while (curr !== path.dirname(curr)) {",
|
|
8126
|
+
' if (fs.existsSync(path.join(curr, ".kuma")) || fs.existsSync(path.join(curr, ".git"))) {',
|
|
8127
|
+
" return curr;",
|
|
8128
|
+
" }",
|
|
8129
|
+
" curr = path.dirname(curr);",
|
|
8130
|
+
" }",
|
|
8131
|
+
" return process.cwd();",
|
|
8132
|
+
"}",
|
|
8133
|
+
"",
|
|
8134
|
+
"function getActiveGotchasForFile(filePath) {",
|
|
8135
|
+
" try {",
|
|
8136
|
+
" const root = getProjectRoot();",
|
|
8137
|
+
' const gotchasPath = path.join(root, ".kuma", "KNOWN_GOTCHAS.md");',
|
|
8138
|
+
" if (!fs.existsSync(gotchasPath)) return [];",
|
|
8139
|
+
' const content = fs.readFileSync(gotchasPath, "utf-8");',
|
|
8140
|
+
" const gotchas = [];",
|
|
8141
|
+
' const normalizedTarget = filePath.replace(/^[./]+/, "");',
|
|
8142
|
+
" const sections = content.split(/###\\s+/);",
|
|
8143
|
+
" for (const section of sections) {",
|
|
8144
|
+
" if (!section.trim()) continue;",
|
|
8145
|
+
' const lines = section.split("\\n");',
|
|
8146
|
+
' const title = lines[0] || "";',
|
|
8147
|
+
" if (title.includes(normalizedTarget) || section.includes(normalizedTarget)) {",
|
|
8148
|
+
" gotchas.push(title.trim());",
|
|
8149
|
+
" }",
|
|
8150
|
+
" }",
|
|
8151
|
+
" return gotchas;",
|
|
8152
|
+
" } catch {",
|
|
8153
|
+
" return [];",
|
|
8154
|
+
" }",
|
|
8155
|
+
"}",
|
|
8156
|
+
"",
|
|
8157
|
+
"export const KumaPlugin = async () => {",
|
|
8158
|
+
" return {",
|
|
8159
|
+
' "tool.execute.before": async (input, output) => {',
|
|
8160
|
+
' const tool = (input.tool || "").toLowerCase();',
|
|
8161
|
+
" const args = output.args || {};",
|
|
8162
|
+
' const targetPath = args.filePath || args.targetFile || args.path || args.file || "";',
|
|
8163
|
+
' if (targetPath && (tool.includes("edit") || tool.includes("write") || tool.includes("patch"))) {',
|
|
8164
|
+
" const gotchas = getActiveGotchasForFile(String(targetPath));",
|
|
8165
|
+
" if (gotchas.length > 0) {",
|
|
8166
|
+
" console.warn(",
|
|
8167
|
+
" `\\n\u{1F43B} [Kuma Alert] Active gotchas found for ${targetPath}:\\n` +",
|
|
8168
|
+
' gotchas.map((g) => ` \u26A0\uFE0F ${g}`).join("\\n") +',
|
|
8169
|
+
' `\\n\u{1F449} Remember to record any new gotchas via kuma_memory({ action: "gotcha" })\\n`',
|
|
8170
|
+
" );",
|
|
8171
|
+
" }",
|
|
8172
|
+
" }",
|
|
8173
|
+
" },",
|
|
8174
|
+
" };",
|
|
8175
|
+
"};",
|
|
8176
|
+
""
|
|
8177
|
+
].join("\n");
|
|
8178
|
+
}
|
|
8179
|
+
function rooRulesTemplate() {
|
|
8180
|
+
return [
|
|
8181
|
+
"---",
|
|
8182
|
+
"description: Kuma MCP \u2014 .kuma/ is the single source of truth",
|
|
8183
|
+
"alwaysApply: true",
|
|
8184
|
+
"---",
|
|
8185
|
+
"",
|
|
8186
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8187
|
+
"",
|
|
8188
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8189
|
+
].join("\n");
|
|
8190
|
+
}
|
|
8191
|
+
function traeRulesTemplate() {
|
|
8192
|
+
return [
|
|
8193
|
+
"# Kuma MCP \u2014 Trae Rules",
|
|
8194
|
+
"",
|
|
8195
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8196
|
+
"",
|
|
8197
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8198
|
+
].join("\n");
|
|
8199
|
+
}
|
|
8200
|
+
function qoderRulesTemplate() {
|
|
8201
|
+
return [
|
|
8202
|
+
"# Kuma MCP \u2014 Qoder Rules",
|
|
8203
|
+
"",
|
|
8204
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8205
|
+
"",
|
|
8206
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8207
|
+
].join("\n");
|
|
8208
|
+
}
|
|
8209
|
+
function windsurfrulesTemplate() {
|
|
8210
|
+
return [
|
|
8211
|
+
"# Kuma MCP \u2014 Windsurf Rules",
|
|
8212
|
+
"",
|
|
8213
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8214
|
+
"",
|
|
8215
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8216
|
+
].join("\n");
|
|
8217
|
+
}
|
|
6999
8218
|
function generateInitMdContent() {
|
|
7000
8219
|
return [
|
|
7001
8220
|
"# Kuma Init \u2014 Workflow & Rules",
|
|
@@ -7016,7 +8235,7 @@ function generateInitMdContent() {
|
|
|
7016
8235
|
'1. **MUST call `kuma_context({ action: "init" })`** at session start \u2014 before any code edit.',
|
|
7017
8236
|
'2. **MUST record gotchas IMMEDIATELY** when a bug/quirk is found (`kuma_memory({ action: "gotcha" })`).',
|
|
7018
8237
|
'3. **MUST call `kuma_safety({ action: "verify" })`** after code edits.',
|
|
7019
|
-
"4. **MUST NOT call actions outside the
|
|
8238
|
+
"4. **MUST NOT call actions outside the core actions** in this file \u2014 anything else is rejected by the MCP schema.",
|
|
7020
8239
|
"5. If a Kuma call fails or returns an error, **re-read this file and retry** \u2014 do not silently continue.",
|
|
7021
8240
|
"",
|
|
7022
8241
|
"> These rules are enforced: the MCP schema rejects unknown actions, and Claude Code hooks inject gotchas before every edit.",
|
|
@@ -7027,10 +8246,11 @@ function generateInitMdContent() {
|
|
|
7027
8246
|
"<kuma_workflow>",
|
|
7028
8247
|
"## SESSION WORKFLOW",
|
|
7029
8248
|
"",
|
|
7030
|
-
"### Core Actions (
|
|
8249
|
+
"### Core Actions (15)",
|
|
7031
8250
|
"",
|
|
7032
8251
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
7033
|
-
'\u{
|
|
8252
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_context({ action: "map" })`',
|
|
8253
|
+
'\u{1F52C} **Blast radius / impact:** `kuma_context({ action: "impact", target: "<file>" })` or `kuma_context({ action: "research", scope: "<area>" })`',
|
|
7034
8254
|
'\u{1F570}\uFE0F **Why this file?** `kuma_context({ action: "history", target: "<file>" })`',
|
|
7035
8255
|
'\u{1F5C2}\uFE0F **Read a flow:** `kuma_context({ action: "flow", target: "<domain>" })`',
|
|
7036
8256
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
@@ -7075,6 +8295,19 @@ function generateInitMdContent() {
|
|
|
7075
8295
|
"",
|
|
7076
8296
|
"---",
|
|
7077
8297
|
"",
|
|
8298
|
+
"## \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
8299
|
+
"",
|
|
8300
|
+
"If **Matcha** is active in this workspace:",
|
|
8301
|
+
"- **Matcha** governs engineering philosophy, intent discovery, reuse checks, and review gates.",
|
|
8302
|
+
"- **Kuma** governs blast radius calculation, monorepo workspace dependencies, known gotchas memory, architecture boundary guards, and scoped test verification.",
|
|
8303
|
+
"- **Workflow:**",
|
|
8304
|
+
" 1. Use Matcha's Intent Discovery (`.agents/plan/current.md`) to define Problem / Goals / Success Criteria.",
|
|
8305
|
+
' 2. Use Kuma (`kuma_context({ action: "map" })` and `kuma_context({ action: "impact", target: "..." })`) to analyze blast radius and package dependents before modifying code.',
|
|
8306
|
+
' 3. When encountering quirks or architectural boundaries, record immediately with `kuma_memory({ action: "gotcha" | "decision" })`.',
|
|
8307
|
+
' 4. After changes, verify with `kuma_safety({ action: "verify" })` to run scoped tests.',
|
|
8308
|
+
"",
|
|
8309
|
+
"---",
|
|
8310
|
+
"",
|
|
7078
8311
|
"## \u26A0\uFE0F HONEST LIMITATIONS",
|
|
7079
8312
|
"",
|
|
7080
8313
|
"- **Node IDs are text-based** \u2014 same file recorded with slightly different paths can create duplicates.",
|
|
@@ -7090,7 +8323,9 @@ function generateInitMdContent() {
|
|
|
7090
8323
|
"",
|
|
7091
8324
|
"| Action | Description |",
|
|
7092
8325
|
"|--------|-------------|",
|
|
7093
|
-
"| `init` | Lean project brief, restore session |",
|
|
8326
|
+
"| `init` | Lean project brief, restore session, monorepo brief |",
|
|
8327
|
+
"| `map` | Repository workspace map & package dependency graph |",
|
|
8328
|
+
"| `impact` | Direct blast radius analysis (dependents, consumers, tests) |",
|
|
7094
8329
|
"| `research` | 5-step pipeline before editing unfamiliar code |",
|
|
7095
8330
|
"| `history` | Why is this file written this way (cross-session trace) |",
|
|
7096
8331
|
"| `flow` | Read a recorded architecture flow |",
|
|
@@ -7099,7 +8334,7 @@ function generateInitMdContent() {
|
|
|
7099
8334
|
"",
|
|
7100
8335
|
"| Action | Description |",
|
|
7101
8336
|
"|--------|-------------|",
|
|
7102
|
-
"| `gotcha` | Record bug/quirk (
|
|
8337
|
+
"| `gotcha` | Record bug/quirk (candidate -> active -> verified) |",
|
|
7103
8338
|
"| `arch_flow` | Record architecture flow (max 5 core files) |",
|
|
7104
8339
|
"| `decision` | Record ADR / decision rationale |",
|
|
7105
8340
|
"| `research_save` | Cache research findings |",
|
|
@@ -7109,8 +8344,8 @@ function generateInitMdContent() {
|
|
|
7109
8344
|
"",
|
|
7110
8345
|
"| Action | Description |",
|
|
7111
8346
|
"|--------|-------------|",
|
|
7112
|
-
"| `guard` | Anti-pattern detection
|
|
7113
|
-
"| `verify` | Auto-run scoped tests
|
|
8347
|
+
"| `guard` | Anti-pattern detection & architecture boundary check |",
|
|
8348
|
+
"| `verify` | Auto-run scoped tests for affected packages/files |",
|
|
7114
8349
|
"| `checkpoint` | Labeled snapshot before risky work |",
|
|
7115
8350
|
"| `rollback_label` | Restore a labeled snapshot |",
|
|
7116
8351
|
"",
|
|
@@ -7170,79 +8405,132 @@ function generateInitMdContent() {
|
|
|
7170
8405
|
].join("\n");
|
|
7171
8406
|
}
|
|
7172
8407
|
function handleOpencodeSecondary(root, results) {
|
|
7173
|
-
const
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
8408
|
+
const opencodeContent = [
|
|
8409
|
+
"---",
|
|
8410
|
+
"name: kuma-mcp",
|
|
8411
|
+
"description: Kuma MCP \u2014 safety toolkit for AI coding agents. Research, memory, and safety guard.",
|
|
8412
|
+
"---",
|
|
8413
|
+
"",
|
|
8414
|
+
"Kuma MCP tools: kuma_kuma_context (or kuma_context), kuma_kuma_memory (or kuma_memory), kuma_kuma_safety (or kuma_safety).",
|
|
8415
|
+
"Record what matters, skip what doesn't.",
|
|
8416
|
+
"",
|
|
8417
|
+
"<kuma_obedience>",
|
|
8418
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
8419
|
+
'- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
|
|
8420
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
8421
|
+
'- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
|
|
8422
|
+
"- MUST NOT call actions outside the core actions.",
|
|
8423
|
+
"</kuma_obedience>",
|
|
8424
|
+
"",
|
|
8425
|
+
"<kuma_workflow>",
|
|
8426
|
+
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
8427
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_kuma_context({ action: "map" })`',
|
|
8428
|
+
'\u{1F52C} **Blast radius / impact:** `kuma_kuma_context({ action: "impact", target: "<file>" })` or `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
8429
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8430
|
+
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
8431
|
+
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
8432
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
8433
|
+
"</kuma_workflow>",
|
|
8434
|
+
"",
|
|
8435
|
+
"\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` or `kuma_*` prefix depending on server registration.",
|
|
8436
|
+
"",
|
|
8437
|
+
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
8438
|
+
].join("\n");
|
|
8439
|
+
const skillPath = path26.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
|
|
8440
|
+
if (!results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) {
|
|
8441
|
+
try {
|
|
8442
|
+
const dir = path26.dirname(skillPath);
|
|
8443
|
+
if (fs25.existsSync(skillPath)) {
|
|
8444
|
+
const existing = fs25.readFileSync(skillPath, "utf-8");
|
|
8445
|
+
if (existing.includes("kuma-mcp")) {
|
|
8446
|
+
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
|
|
8447
|
+
} else {
|
|
8448
|
+
fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n---\n\n" + opencodeContent, "utf-8");
|
|
8449
|
+
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
|
|
8450
|
+
}
|
|
8451
|
+
} else {
|
|
8452
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8453
|
+
fs25.writeFileSync(skillPath, opencodeContent, "utf-8");
|
|
8454
|
+
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
|
|
7212
8455
|
}
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
8456
|
+
} catch (err) {
|
|
8457
|
+
results.push({
|
|
8458
|
+
type: "opencode",
|
|
8459
|
+
filePath: ".agents/skills/kuma/SKILL.md",
|
|
8460
|
+
action: "error",
|
|
8461
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8462
|
+
});
|
|
8463
|
+
}
|
|
8464
|
+
}
|
|
8465
|
+
const opencodeSkillPath = path26.resolve(root, ".opencode", "skills", "kuma", "SKILL.md");
|
|
8466
|
+
if (!results.some((r) => r.filePath === ".opencode/skills/kuma/SKILL.md")) {
|
|
8467
|
+
try {
|
|
8468
|
+
const dir = path26.dirname(opencodeSkillPath);
|
|
8469
|
+
if (fs25.existsSync(opencodeSkillPath)) {
|
|
8470
|
+
const existing = fs25.readFileSync(opencodeSkillPath, "utf-8");
|
|
8471
|
+
if (existing.includes("kuma-mcp")) {
|
|
8472
|
+
results.push({ type: "opencode", filePath: ".opencode/skills/kuma/SKILL.md", action: "skipped" });
|
|
8473
|
+
} else {
|
|
8474
|
+
fs25.writeFileSync(opencodeSkillPath, existing.trimEnd() + "\n\n---\n\n" + opencodeContent, "utf-8");
|
|
8475
|
+
results.push({ type: "opencode", filePath: ".opencode/skills/kuma/SKILL.md", action: "appended" });
|
|
8476
|
+
}
|
|
8477
|
+
} else {
|
|
8478
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8479
|
+
fs25.writeFileSync(opencodeSkillPath, opencodeContent, "utf-8");
|
|
8480
|
+
results.push({ type: "opencode", filePath: ".opencode/skills/kuma/SKILL.md", action: "created" });
|
|
8481
|
+
}
|
|
8482
|
+
} catch (err) {
|
|
8483
|
+
results.push({
|
|
8484
|
+
type: "opencode",
|
|
8485
|
+
filePath: ".opencode/skills/kuma/SKILL.md",
|
|
8486
|
+
action: "error",
|
|
8487
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8488
|
+
});
|
|
8489
|
+
}
|
|
8490
|
+
}
|
|
8491
|
+
const pluginPath = path26.resolve(root, ".opencode", "plugins", "kuma.js");
|
|
8492
|
+
if (!results.some((r) => r.filePath === ".opencode/plugins/kuma.js")) {
|
|
8493
|
+
try {
|
|
8494
|
+
const dir = path26.dirname(pluginPath);
|
|
8495
|
+
if (fs25.existsSync(pluginPath)) {
|
|
8496
|
+
const existing = fs25.readFileSync(pluginPath, "utf-8");
|
|
8497
|
+
if (existing.includes("KumaPlugin") || existing.includes("kuma")) {
|
|
8498
|
+
results.push({ type: "opencode", filePath: ".opencode/plugins/kuma.js", action: "skipped" });
|
|
8499
|
+
} else {
|
|
8500
|
+
fs25.writeFileSync(pluginPath, existing.trimEnd() + "\n\n" + opencodePluginTemplate(), "utf-8");
|
|
8501
|
+
results.push({ type: "opencode", filePath: ".opencode/plugins/kuma.js", action: "appended" });
|
|
8502
|
+
}
|
|
8503
|
+
} else {
|
|
8504
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8505
|
+
fs25.writeFileSync(pluginPath, opencodePluginTemplate(), "utf-8");
|
|
8506
|
+
results.push({ type: "opencode", filePath: ".opencode/plugins/kuma.js", action: "created" });
|
|
8507
|
+
}
|
|
8508
|
+
} catch (err) {
|
|
8509
|
+
results.push({
|
|
8510
|
+
type: "opencode",
|
|
8511
|
+
filePath: ".opencode/plugins/kuma.js",
|
|
8512
|
+
action: "error",
|
|
8513
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8514
|
+
});
|
|
7220
8515
|
}
|
|
7221
|
-
} catch (err) {
|
|
7222
|
-
results.push({
|
|
7223
|
-
type: "opencode",
|
|
7224
|
-
filePath: ".agents/skills/kuma/SKILL.md",
|
|
7225
|
-
action: "error",
|
|
7226
|
-
error: err instanceof Error ? err.message : String(err)
|
|
7227
|
-
});
|
|
7228
8516
|
}
|
|
7229
8517
|
}
|
|
7230
8518
|
function handleCodexSecondary(root, results) {
|
|
7231
|
-
const tomlPath =
|
|
8519
|
+
const tomlPath = path26.resolve(root, ".codex/config.toml");
|
|
7232
8520
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
7233
8521
|
try {
|
|
7234
|
-
const dir =
|
|
7235
|
-
if (!
|
|
7236
|
-
if (
|
|
7237
|
-
const existingContent =
|
|
8522
|
+
const dir = path26.dirname(tomlPath);
|
|
8523
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8524
|
+
if (fs25.existsSync(tomlPath)) {
|
|
8525
|
+
const existingContent = fs25.readFileSync(tomlPath, "utf-8");
|
|
7238
8526
|
if (existingContent.includes("kuma")) {
|
|
7239
8527
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
7240
8528
|
return;
|
|
7241
8529
|
}
|
|
7242
|
-
|
|
8530
|
+
fs25.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
7243
8531
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
7244
8532
|
} else {
|
|
7245
|
-
|
|
8533
|
+
fs25.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
7246
8534
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
7247
8535
|
}
|
|
7248
8536
|
} catch (err) {
|
|
@@ -7255,11 +8543,11 @@ function handleCodexSecondary(root, results) {
|
|
|
7255
8543
|
}
|
|
7256
8544
|
}
|
|
7257
8545
|
function handleQwenSecondary(root, results) {
|
|
7258
|
-
const settingsPath =
|
|
8546
|
+
const settingsPath = path26.resolve(root, "settings.json");
|
|
7259
8547
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
7260
8548
|
try {
|
|
7261
|
-
if (
|
|
7262
|
-
const existingContent =
|
|
8549
|
+
if (fs25.existsSync(settingsPath)) {
|
|
8550
|
+
const existingContent = fs25.readFileSync(settingsPath, "utf-8");
|
|
7263
8551
|
if (existingContent.includes("kuma")) {
|
|
7264
8552
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
7265
8553
|
try {
|
|
@@ -7267,7 +8555,7 @@ function handleQwenSecondary(root, results) {
|
|
|
7267
8555
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7268
8556
|
if (!parsed.mcpServers.kuma) {
|
|
7269
8557
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7270
|
-
|
|
8558
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7271
8559
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
7272
8560
|
return;
|
|
7273
8561
|
}
|
|
@@ -7282,13 +8570,13 @@ function handleQwenSecondary(root, results) {
|
|
|
7282
8570
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7283
8571
|
if (!parsed.mcpServers.kuma) {
|
|
7284
8572
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7285
|
-
|
|
8573
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7286
8574
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
7287
8575
|
}
|
|
7288
8576
|
} catch {
|
|
7289
8577
|
}
|
|
7290
8578
|
} else {
|
|
7291
|
-
|
|
8579
|
+
fs25.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
7292
8580
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
7293
8581
|
}
|
|
7294
8582
|
} catch (err) {
|
|
@@ -7391,12 +8679,12 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
7391
8679
|
return lines.join("\n");
|
|
7392
8680
|
}
|
|
7393
8681
|
function handleAntigravityMcpConfig(root, results) {
|
|
7394
|
-
const mcpPath =
|
|
8682
|
+
const mcpPath = path26.resolve(root, ".agents/mcp_config.json");
|
|
7395
8683
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
7396
8684
|
try {
|
|
7397
|
-
const mcpDir =
|
|
7398
|
-
if (
|
|
7399
|
-
const existingContent =
|
|
8685
|
+
const mcpDir = path26.dirname(mcpPath);
|
|
8686
|
+
if (fs25.existsSync(mcpPath)) {
|
|
8687
|
+
const existingContent = fs25.readFileSync(mcpPath, "utf-8");
|
|
7400
8688
|
if (existingContent.includes("kuma")) {
|
|
7401
8689
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "skipped" });
|
|
7402
8690
|
return;
|
|
@@ -7405,14 +8693,14 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
7405
8693
|
const parsed = JSON.parse(existingContent);
|
|
7406
8694
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7407
8695
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7408
|
-
|
|
8696
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7409
8697
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
7410
8698
|
} catch {
|
|
7411
8699
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "error", error: "Invalid JSON in existing mcp_config.json" });
|
|
7412
8700
|
}
|
|
7413
8701
|
} else {
|
|
7414
|
-
if (!
|
|
7415
|
-
|
|
8702
|
+
if (!fs25.existsSync(mcpDir)) fs25.mkdirSync(mcpDir, { recursive: true });
|
|
8703
|
+
fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
7416
8704
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
7417
8705
|
}
|
|
7418
8706
|
} catch (err) {
|
|
@@ -7424,19 +8712,181 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
7424
8712
|
});
|
|
7425
8713
|
}
|
|
7426
8714
|
}
|
|
8715
|
+
function handleAntigravityRules(root, results) {
|
|
8716
|
+
const rulePath = path26.resolve(root, ".agents", "rules", "kuma.md");
|
|
8717
|
+
if (results.some((r) => r.filePath === ".agents/rules/kuma.md")) return;
|
|
8718
|
+
try {
|
|
8719
|
+
const dir = path26.dirname(rulePath);
|
|
8720
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8721
|
+
if (fs25.existsSync(rulePath)) {
|
|
8722
|
+
const existing = fs25.readFileSync(rulePath, "utf-8");
|
|
8723
|
+
if (existing.includes("Kuma MCP") || existing.includes("kuma-mcp")) {
|
|
8724
|
+
results.push({ type: "antigravity", filePath: ".agents/rules/kuma.md", action: "skipped" });
|
|
8725
|
+
return;
|
|
8726
|
+
}
|
|
8727
|
+
fs25.writeFileSync(rulePath, existing.trimEnd() + "\n\n---\n\n" + antigravityRuleTemplate(), "utf-8");
|
|
8728
|
+
results.push({ type: "antigravity", filePath: ".agents/rules/kuma.md", action: "appended" });
|
|
8729
|
+
} else {
|
|
8730
|
+
fs25.writeFileSync(rulePath, antigravityRuleTemplate(), "utf-8");
|
|
8731
|
+
results.push({ type: "antigravity", filePath: ".agents/rules/kuma.md", action: "created" });
|
|
8732
|
+
}
|
|
8733
|
+
} catch (err) {
|
|
8734
|
+
results.push({
|
|
8735
|
+
type: "antigravity",
|
|
8736
|
+
filePath: ".agents/rules/kuma.md",
|
|
8737
|
+
action: "error",
|
|
8738
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8739
|
+
});
|
|
8740
|
+
}
|
|
8741
|
+
}
|
|
8742
|
+
function handleGeminiMd(root, results) {
|
|
8743
|
+
const geminiPath = path26.resolve(root, "GEMINI.md");
|
|
8744
|
+
if (results.some((r) => r.filePath === "GEMINI.md")) return;
|
|
8745
|
+
try {
|
|
8746
|
+
if (fs25.existsSync(geminiPath)) {
|
|
8747
|
+
const existing = fs25.readFileSync(geminiPath, "utf-8");
|
|
8748
|
+
if (existing.includes("Kuma MCP") || existing.includes("kuma_context")) {
|
|
8749
|
+
results.push({ type: "antigravity", filePath: "GEMINI.md", action: "skipped" });
|
|
8750
|
+
return;
|
|
8751
|
+
}
|
|
8752
|
+
fs25.writeFileSync(geminiPath, existing.trimEnd() + "\n\n---\n\n" + geminiTemplate(), "utf-8");
|
|
8753
|
+
results.push({ type: "antigravity", filePath: "GEMINI.md", action: "appended" });
|
|
8754
|
+
} else {
|
|
8755
|
+
fs25.writeFileSync(geminiPath, geminiTemplate(), "utf-8");
|
|
8756
|
+
results.push({ type: "antigravity", filePath: "GEMINI.md", action: "created" });
|
|
8757
|
+
}
|
|
8758
|
+
} catch (err) {
|
|
8759
|
+
results.push({
|
|
8760
|
+
type: "antigravity",
|
|
8761
|
+
filePath: "GEMINI.md",
|
|
8762
|
+
action: "error",
|
|
8763
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8764
|
+
});
|
|
8765
|
+
}
|
|
8766
|
+
}
|
|
8767
|
+
function handleAntigravitySecondary(root, results) {
|
|
8768
|
+
handleAntigravityMcpConfig(root, results);
|
|
8769
|
+
handleAntigravityRules(root, results);
|
|
8770
|
+
handleGeminiMd(root, results);
|
|
8771
|
+
}
|
|
8772
|
+
function handleWindsurfSecondary(root, results) {
|
|
8773
|
+
const rulesPath = path26.resolve(root, ".windsurfrules");
|
|
8774
|
+
if (results.some((r) => r.filePath === ".windsurfrules")) return;
|
|
8775
|
+
try {
|
|
8776
|
+
if (fs25.existsSync(rulesPath)) {
|
|
8777
|
+
const existing = fs25.readFileSync(rulesPath, "utf-8");
|
|
8778
|
+
if (existing.includes("Kuma MCP") || existing.includes("kuma_context")) {
|
|
8779
|
+
results.push({ type: "windsurf", filePath: ".windsurfrules", action: "skipped" });
|
|
8780
|
+
return;
|
|
8781
|
+
}
|
|
8782
|
+
fs25.writeFileSync(rulesPath, existing.trimEnd() + "\n\n---\n\n" + windsurfrulesTemplate(), "utf-8");
|
|
8783
|
+
results.push({ type: "windsurf", filePath: ".windsurfrules", action: "appended" });
|
|
8784
|
+
} else {
|
|
8785
|
+
fs25.writeFileSync(rulesPath, windsurfrulesTemplate(), "utf-8");
|
|
8786
|
+
results.push({ type: "windsurf", filePath: ".windsurfrules", action: "created" });
|
|
8787
|
+
}
|
|
8788
|
+
} catch (err) {
|
|
8789
|
+
results.push({
|
|
8790
|
+
type: "windsurf",
|
|
8791
|
+
filePath: ".windsurfrules",
|
|
8792
|
+
action: "error",
|
|
8793
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8794
|
+
});
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
function handleClineSecondary(root, results) {
|
|
8798
|
+
const rooPath = path26.resolve(root, ".roo", "rules", "kuma.md");
|
|
8799
|
+
if (!results.some((r) => r.filePath === ".roo/rules/kuma.md")) {
|
|
8800
|
+
try {
|
|
8801
|
+
const dir = path26.dirname(rooPath);
|
|
8802
|
+
if (fs25.existsSync(rooPath)) {
|
|
8803
|
+
const existing = fs25.readFileSync(rooPath, "utf-8");
|
|
8804
|
+
if (existing.includes("kuma-mcp") || existing.includes("Kuma MCP")) {
|
|
8805
|
+
results.push({ type: "cline", filePath: ".roo/rules/kuma.md", action: "skipped" });
|
|
8806
|
+
} else {
|
|
8807
|
+
fs25.writeFileSync(rooPath, existing.trimEnd() + "\n\n---\n\n" + rooRulesTemplate(), "utf-8");
|
|
8808
|
+
results.push({ type: "cline", filePath: ".roo/rules/kuma.md", action: "appended" });
|
|
8809
|
+
}
|
|
8810
|
+
} else {
|
|
8811
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8812
|
+
fs25.writeFileSync(rooPath, rooRulesTemplate(), "utf-8");
|
|
8813
|
+
results.push({ type: "cline", filePath: ".roo/rules/kuma.md", action: "created" });
|
|
8814
|
+
}
|
|
8815
|
+
} catch (err) {
|
|
8816
|
+
results.push({
|
|
8817
|
+
type: "cline",
|
|
8818
|
+
filePath: ".roo/rules/kuma.md",
|
|
8819
|
+
action: "error",
|
|
8820
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8821
|
+
});
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
const traePath = path26.resolve(root, ".trae", "rules", "kuma.md");
|
|
8825
|
+
if (!results.some((r) => r.filePath === ".trae/rules/kuma.md")) {
|
|
8826
|
+
try {
|
|
8827
|
+
const dir = path26.dirname(traePath);
|
|
8828
|
+
if (fs25.existsSync(traePath)) {
|
|
8829
|
+
const existing = fs25.readFileSync(traePath, "utf-8");
|
|
8830
|
+
if (existing.includes("Kuma MCP")) {
|
|
8831
|
+
results.push({ type: "cline", filePath: ".trae/rules/kuma.md", action: "skipped" });
|
|
8832
|
+
} else {
|
|
8833
|
+
fs25.writeFileSync(traePath, existing.trimEnd() + "\n\n---\n\n" + traeRulesTemplate(), "utf-8");
|
|
8834
|
+
results.push({ type: "cline", filePath: ".trae/rules/kuma.md", action: "appended" });
|
|
8835
|
+
}
|
|
8836
|
+
} else {
|
|
8837
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8838
|
+
fs25.writeFileSync(traePath, traeRulesTemplate(), "utf-8");
|
|
8839
|
+
results.push({ type: "cline", filePath: ".trae/rules/kuma.md", action: "created" });
|
|
8840
|
+
}
|
|
8841
|
+
} catch (err) {
|
|
8842
|
+
results.push({
|
|
8843
|
+
type: "cline",
|
|
8844
|
+
filePath: ".trae/rules/kuma.md",
|
|
8845
|
+
action: "error",
|
|
8846
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8847
|
+
});
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
const qoderPath = path26.resolve(root, ".qoder", "rules", "kuma.md");
|
|
8851
|
+
if (!results.some((r) => r.filePath === ".qoder/rules/kuma.md")) {
|
|
8852
|
+
try {
|
|
8853
|
+
const dir = path26.dirname(qoderPath);
|
|
8854
|
+
if (fs25.existsSync(qoderPath)) {
|
|
8855
|
+
const existing = fs25.readFileSync(qoderPath, "utf-8");
|
|
8856
|
+
if (existing.includes("Kuma MCP")) {
|
|
8857
|
+
results.push({ type: "cline", filePath: ".qoder/rules/kuma.md", action: "skipped" });
|
|
8858
|
+
} else {
|
|
8859
|
+
fs25.writeFileSync(qoderPath, existing.trimEnd() + "\n\n---\n\n" + qoderRulesTemplate(), "utf-8");
|
|
8860
|
+
results.push({ type: "cline", filePath: ".qoder/rules/kuma.md", action: "appended" });
|
|
8861
|
+
}
|
|
8862
|
+
} else {
|
|
8863
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8864
|
+
fs25.writeFileSync(qoderPath, qoderRulesTemplate(), "utf-8");
|
|
8865
|
+
results.push({ type: "cline", filePath: ".qoder/rules/kuma.md", action: "created" });
|
|
8866
|
+
}
|
|
8867
|
+
} catch (err) {
|
|
8868
|
+
results.push({
|
|
8869
|
+
type: "cline",
|
|
8870
|
+
filePath: ".qoder/rules/kuma.md",
|
|
8871
|
+
action: "error",
|
|
8872
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8873
|
+
});
|
|
8874
|
+
}
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
7427
8877
|
function handleAiderSecondary(root, results) {
|
|
7428
|
-
const ymlPath =
|
|
8878
|
+
const ymlPath = path26.resolve(root, ".aider.conf.yml");
|
|
7429
8879
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
7430
8880
|
try {
|
|
7431
8881
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
7432
|
-
if (
|
|
7433
|
-
const existingContent =
|
|
8882
|
+
if (fs25.existsSync(ymlPath)) {
|
|
8883
|
+
const existingContent = fs25.readFileSync(ymlPath, "utf-8");
|
|
7434
8884
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
7435
8885
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
7436
8886
|
return;
|
|
7437
8887
|
}
|
|
7438
8888
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
7439
|
-
|
|
8889
|
+
fs25.writeFileSync(ymlPath, newContent, "utf-8");
|
|
7440
8890
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
7441
8891
|
} else {
|
|
7442
8892
|
const content = [
|
|
@@ -7446,7 +8896,7 @@ function handleAiderSecondary(root, results) {
|
|
|
7446
8896
|
conventionsRef,
|
|
7447
8897
|
""
|
|
7448
8898
|
].join("\n");
|
|
7449
|
-
|
|
8899
|
+
fs25.writeFileSync(ymlPath, content, "utf-8");
|
|
7450
8900
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
7451
8901
|
}
|
|
7452
8902
|
} catch (err) {
|
|
@@ -7459,10 +8909,10 @@ function handleAiderSecondary(root, results) {
|
|
|
7459
8909
|
}
|
|
7460
8910
|
}
|
|
7461
8911
|
function handleCopilotSecondary(root, results) {
|
|
7462
|
-
const skillPath =
|
|
8912
|
+
const skillPath = path26.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
7463
8913
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
7464
8914
|
try {
|
|
7465
|
-
const dir =
|
|
8915
|
+
const dir = path26.dirname(skillPath);
|
|
7466
8916
|
const content = [
|
|
7467
8917
|
"---",
|
|
7468
8918
|
"name: kuma-mcp",
|
|
@@ -7473,17 +8923,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
7473
8923
|
"",
|
|
7474
8924
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
7475
8925
|
].join("\n");
|
|
7476
|
-
if (
|
|
7477
|
-
const existingContent =
|
|
8926
|
+
if (fs25.existsSync(skillPath)) {
|
|
8927
|
+
const existingContent = fs25.readFileSync(skillPath, "utf-8");
|
|
7478
8928
|
if (existingContent.includes("kuma")) {
|
|
7479
8929
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
7480
8930
|
return;
|
|
7481
8931
|
}
|
|
7482
|
-
|
|
8932
|
+
fs25.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
7483
8933
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
7484
8934
|
} else {
|
|
7485
|
-
if (!
|
|
7486
|
-
|
|
8935
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8936
|
+
fs25.writeFileSync(skillPath, content, "utf-8");
|
|
7487
8937
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
7488
8938
|
}
|
|
7489
8939
|
} catch (err) {
|
|
@@ -7496,21 +8946,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
7496
8946
|
}
|
|
7497
8947
|
}
|
|
7498
8948
|
function handleOpenclawSecondary(root, results) {
|
|
7499
|
-
const mcpPath =
|
|
8949
|
+
const mcpPath = path26.resolve(root, ".agents/mcp_config.json");
|
|
7500
8950
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
7501
8951
|
try {
|
|
7502
|
-
const dir =
|
|
7503
|
-
if (
|
|
7504
|
-
const existingContent =
|
|
8952
|
+
const dir = path26.dirname(mcpPath);
|
|
8953
|
+
if (fs25.existsSync(mcpPath)) {
|
|
8954
|
+
const existingContent = fs25.readFileSync(mcpPath, "utf-8");
|
|
7505
8955
|
if (existingContent.includes("kuma")) return;
|
|
7506
8956
|
const parsed = JSON.parse(existingContent);
|
|
7507
8957
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7508
8958
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7509
|
-
|
|
8959
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7510
8960
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
7511
8961
|
} else {
|
|
7512
|
-
if (!
|
|
7513
|
-
|
|
8962
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8963
|
+
fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
7514
8964
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
7515
8965
|
}
|
|
7516
8966
|
} catch (err) {
|
|
@@ -7523,20 +8973,20 @@ function handleOpenclawSecondary(root, results) {
|
|
|
7523
8973
|
}
|
|
7524
8974
|
}
|
|
7525
8975
|
function handleInitMdGeneration(root, results) {
|
|
7526
|
-
const initMdPath =
|
|
8976
|
+
const initMdPath = path26.resolve(root, ".kuma/init.md");
|
|
7527
8977
|
try {
|
|
7528
|
-
const kumaDir =
|
|
7529
|
-
if (!
|
|
7530
|
-
if (
|
|
7531
|
-
const existing =
|
|
8978
|
+
const kumaDir = path26.dirname(initMdPath);
|
|
8979
|
+
if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
|
|
8980
|
+
if (fs25.existsSync(initMdPath)) {
|
|
8981
|
+
const existing = fs25.readFileSync(initMdPath, "utf-8");
|
|
7532
8982
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
7533
8983
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
7534
8984
|
return;
|
|
7535
8985
|
}
|
|
7536
|
-
|
|
8986
|
+
fs25.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
7537
8987
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
7538
8988
|
} else {
|
|
7539
|
-
|
|
8989
|
+
fs25.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
7540
8990
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
7541
8991
|
}
|
|
7542
8992
|
} catch (err) {
|
|
@@ -7549,20 +8999,20 @@ function handleInitMdGeneration(root, results) {
|
|
|
7549
8999
|
}
|
|
7550
9000
|
}
|
|
7551
9001
|
function handleCodewhaleSecondary(root, results) {
|
|
7552
|
-
const mcpPath =
|
|
9002
|
+
const mcpPath = path26.resolve(root, ".codewhale/mcp.json");
|
|
7553
9003
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
7554
9004
|
try {
|
|
7555
|
-
const dir =
|
|
7556
|
-
if (
|
|
7557
|
-
const existingContent =
|
|
9005
|
+
const dir = path26.dirname(mcpPath);
|
|
9006
|
+
if (fs25.existsSync(mcpPath)) {
|
|
9007
|
+
const existingContent = fs25.readFileSync(mcpPath, "utf-8");
|
|
7558
9008
|
if (existingContent.includes("kuma")) return;
|
|
7559
9009
|
const parsed = JSON.parse(existingContent);
|
|
7560
9010
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7561
9011
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7562
|
-
|
|
9012
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7563
9013
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
7564
9014
|
} else {
|
|
7565
|
-
if (!
|
|
9015
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
7566
9016
|
const config = {
|
|
7567
9017
|
mcpServers: {
|
|
7568
9018
|
kuma: {
|
|
@@ -7572,7 +9022,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
7572
9022
|
}
|
|
7573
9023
|
}
|
|
7574
9024
|
};
|
|
7575
|
-
|
|
9025
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7576
9026
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
7577
9027
|
}
|
|
7578
9028
|
} catch (err) {
|
|
@@ -7585,7 +9035,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
7585
9035
|
}
|
|
7586
9036
|
}
|
|
7587
9037
|
function handleClaudeSecondary(root, results) {
|
|
7588
|
-
const settingsPath =
|
|
9038
|
+
const settingsPath = path26.resolve(root, ".claude", "settings.json");
|
|
7589
9039
|
const hookBlock = {
|
|
7590
9040
|
hooks: {
|
|
7591
9041
|
PreToolUse: [
|
|
@@ -7601,8 +9051,8 @@ function handleClaudeSecondary(root, results) {
|
|
|
7601
9051
|
}
|
|
7602
9052
|
};
|
|
7603
9053
|
try {
|
|
7604
|
-
if (
|
|
7605
|
-
const existing = JSON.parse(
|
|
9054
|
+
if (fs25.existsSync(settingsPath)) {
|
|
9055
|
+
const existing = JSON.parse(fs25.readFileSync(settingsPath, "utf-8"));
|
|
7606
9056
|
const merged = {
|
|
7607
9057
|
...existing,
|
|
7608
9058
|
hooks: {
|
|
@@ -7617,12 +9067,12 @@ function handleClaudeSecondary(root, results) {
|
|
|
7617
9067
|
matchers.add(entry.matcher);
|
|
7618
9068
|
}
|
|
7619
9069
|
}
|
|
7620
|
-
|
|
9070
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
7621
9071
|
results.push({ type: "claude", filePath: ".claude/settings.json", action: "appended" });
|
|
7622
9072
|
} else {
|
|
7623
|
-
const dir =
|
|
7624
|
-
if (!
|
|
7625
|
-
|
|
9073
|
+
const dir = path26.dirname(settingsPath);
|
|
9074
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9075
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(hookBlock, null, 2) + "\n", "utf-8");
|
|
7626
9076
|
results.push({ type: "claude", filePath: ".claude/settings.json", action: "created" });
|
|
7627
9077
|
}
|
|
7628
9078
|
} catch (err) {
|
|
@@ -7633,20 +9083,57 @@ function handleClaudeSecondary(root, results) {
|
|
|
7633
9083
|
error: err instanceof Error ? err.message : String(err)
|
|
7634
9084
|
});
|
|
7635
9085
|
}
|
|
9086
|
+
const skillPath = path26.resolve(root, ".claude", "skills", "kuma", "SKILL.md");
|
|
9087
|
+
if (!results.some((r) => r.filePath === ".claude/skills/kuma/SKILL.md")) {
|
|
9088
|
+
try {
|
|
9089
|
+
const dir = path26.dirname(skillPath);
|
|
9090
|
+
const content = [
|
|
9091
|
+
"---",
|
|
9092
|
+
"name: kuma-mcp",
|
|
9093
|
+
"description: Kuma MCP \u2014 safety toolkit for AI coding agents",
|
|
9094
|
+
"---",
|
|
9095
|
+
"",
|
|
9096
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
9097
|
+
"",
|
|
9098
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
|
|
9099
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
9100
|
+
].join("\n");
|
|
9101
|
+
if (fs25.existsSync(skillPath)) {
|
|
9102
|
+
const existing = fs25.readFileSync(skillPath, "utf-8");
|
|
9103
|
+
if (existing.includes("kuma-mcp")) {
|
|
9104
|
+
results.push({ type: "claude", filePath: ".claude/skills/kuma/SKILL.md", action: "skipped" });
|
|
9105
|
+
} else {
|
|
9106
|
+
fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n---\n\n" + content, "utf-8");
|
|
9107
|
+
results.push({ type: "claude", filePath: ".claude/skills/kuma/SKILL.md", action: "appended" });
|
|
9108
|
+
}
|
|
9109
|
+
} else {
|
|
9110
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9111
|
+
fs25.writeFileSync(skillPath, content, "utf-8");
|
|
9112
|
+
results.push({ type: "claude", filePath: ".claude/skills/kuma/SKILL.md", action: "created" });
|
|
9113
|
+
}
|
|
9114
|
+
} catch (err) {
|
|
9115
|
+
results.push({
|
|
9116
|
+
type: "claude",
|
|
9117
|
+
filePath: ".claude/skills/kuma/SKILL.md",
|
|
9118
|
+
action: "error",
|
|
9119
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9120
|
+
});
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
7636
9123
|
}
|
|
7637
9124
|
function handleCursorGotchaRules(root, results) {
|
|
7638
|
-
const rulesDir2 =
|
|
9125
|
+
const rulesDir2 = path26.resolve(root, ".cursor", "rules", "kuma-gotchas");
|
|
7639
9126
|
try {
|
|
7640
9127
|
let gotchas = [];
|
|
7641
9128
|
try {
|
|
7642
9129
|
gotchas = getActiveGotchas().filter((g) => g.severity === "high" || g.severity === "critical");
|
|
7643
9130
|
} catch {
|
|
7644
9131
|
}
|
|
7645
|
-
if (
|
|
7646
|
-
for (const f of
|
|
9132
|
+
if (fs25.existsSync(rulesDir2)) {
|
|
9133
|
+
for (const f of fs25.readdirSync(rulesDir2)) {
|
|
7647
9134
|
if (f.endsWith(".mdc")) {
|
|
7648
9135
|
try {
|
|
7649
|
-
|
|
9136
|
+
fs25.rmSync(path26.join(rulesDir2, f), { force: true });
|
|
7650
9137
|
} catch {
|
|
7651
9138
|
}
|
|
7652
9139
|
}
|
|
@@ -7656,7 +9143,7 @@ function handleCursorGotchaRules(root, results) {
|
|
|
7656
9143
|
results.push({ type: "cursor", filePath: ".cursor/rules/kuma-gotchas/", action: "skipped" });
|
|
7657
9144
|
return;
|
|
7658
9145
|
}
|
|
7659
|
-
|
|
9146
|
+
fs25.mkdirSync(rulesDir2, { recursive: true });
|
|
7660
9147
|
const seen = /* @__PURE__ */ new Set();
|
|
7661
9148
|
let created = 0;
|
|
7662
9149
|
for (const g of gotchas) {
|
|
@@ -7677,7 +9164,7 @@ function handleCursorGotchaRules(root, results) {
|
|
|
7677
9164
|
"`kuma_context({ action: 'history', target: '<file>' })`",
|
|
7678
9165
|
""
|
|
7679
9166
|
].join("\n");
|
|
7680
|
-
|
|
9167
|
+
fs25.writeFileSync(path26.join(rulesDir2, `${slug}.mdc`), md, "utf-8");
|
|
7681
9168
|
created++;
|
|
7682
9169
|
}
|
|
7683
9170
|
results.push({
|
|
@@ -7704,28 +9191,28 @@ function runInit(options) {
|
|
|
7704
9191
|
let agentsMdHandled = false;
|
|
7705
9192
|
for (const type of selected) {
|
|
7706
9193
|
const relativePath = configFilePath(type);
|
|
7707
|
-
const fullPath =
|
|
9194
|
+
const fullPath = path26.resolve(root, relativePath);
|
|
7708
9195
|
const getTemplate = TEMPLATES[type];
|
|
7709
9196
|
try {
|
|
7710
9197
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
7711
9198
|
agentsMdHandled = true;
|
|
7712
9199
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
7713
|
-
if (
|
|
9200
|
+
if (fs25.existsSync(fullPath)) {
|
|
7714
9201
|
if (options.skipExisting) {
|
|
7715
9202
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7716
9203
|
} else {
|
|
7717
|
-
const existingContent =
|
|
9204
|
+
const existingContent = fs25.readFileSync(fullPath, "utf-8");
|
|
7718
9205
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
7719
9206
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7720
9207
|
} else {
|
|
7721
|
-
|
|
9208
|
+
fs25.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
7722
9209
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
7723
9210
|
}
|
|
7724
9211
|
}
|
|
7725
9212
|
} else {
|
|
7726
|
-
const dir =
|
|
7727
|
-
if (!
|
|
7728
|
-
|
|
9213
|
+
const dir = path26.dirname(fullPath);
|
|
9214
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9215
|
+
fs25.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
7729
9216
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
7730
9217
|
}
|
|
7731
9218
|
if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
|
|
@@ -7737,42 +9224,50 @@ function runInit(options) {
|
|
|
7737
9224
|
continue;
|
|
7738
9225
|
} else {
|
|
7739
9226
|
const template = getTemplate();
|
|
7740
|
-
if (
|
|
9227
|
+
if (fs25.existsSync(fullPath)) {
|
|
7741
9228
|
if (options.skipExisting) {
|
|
7742
9229
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7743
9230
|
continue;
|
|
7744
9231
|
}
|
|
7745
|
-
const existingContent =
|
|
9232
|
+
const existingContent = fs25.readFileSync(fullPath, "utf-8");
|
|
7746
9233
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
7747
9234
|
if (type === "antigravity") {
|
|
7748
|
-
|
|
9235
|
+
handleAntigravitySecondary(root, results);
|
|
7749
9236
|
} else if (type === "openclaw") {
|
|
7750
9237
|
handleOpenclawSecondary(root, results);
|
|
7751
9238
|
} else if (type === "codewhale") {
|
|
7752
9239
|
handleCodewhaleSecondary(root, results);
|
|
9240
|
+
} else if (type === "windsurf") {
|
|
9241
|
+
handleWindsurfSecondary(root, results);
|
|
9242
|
+
} else if (type === "cline") {
|
|
9243
|
+
handleClineSecondary(root, results);
|
|
7753
9244
|
}
|
|
7754
9245
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7755
9246
|
continue;
|
|
7756
9247
|
}
|
|
7757
9248
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
7758
|
-
|
|
9249
|
+
fs25.writeFileSync(fullPath, newContent, "utf-8");
|
|
7759
9250
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
7760
9251
|
} else {
|
|
7761
|
-
const dir =
|
|
7762
|
-
if (!
|
|
7763
|
-
|
|
9252
|
+
const dir = path26.dirname(fullPath);
|
|
9253
|
+
if (!fs25.existsSync(dir)) {
|
|
9254
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
7764
9255
|
}
|
|
7765
|
-
|
|
9256
|
+
fs25.writeFileSync(fullPath, template, "utf-8");
|
|
7766
9257
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
7767
9258
|
}
|
|
7768
9259
|
if (type === "antigravity") {
|
|
7769
|
-
|
|
9260
|
+
handleAntigravitySecondary(root, results);
|
|
7770
9261
|
} else if (type === "openclaw") {
|
|
7771
9262
|
handleOpenclawSecondary(root, results);
|
|
7772
9263
|
} else if (type === "codewhale") {
|
|
7773
9264
|
handleCodewhaleSecondary(root, results);
|
|
7774
9265
|
} else if (type === "aider") {
|
|
7775
9266
|
handleAiderSecondary(root, results);
|
|
9267
|
+
} else if (type === "windsurf") {
|
|
9268
|
+
handleWindsurfSecondary(root, results);
|
|
9269
|
+
} else if (type === "cline") {
|
|
9270
|
+
handleClineSecondary(root, results);
|
|
7776
9271
|
}
|
|
7777
9272
|
}
|
|
7778
9273
|
} catch (err) {
|
|
@@ -7877,12 +9372,13 @@ var init_init = __esm({
|
|
|
7877
9372
|
'- MUST call `kuma_context({ action: "init" })` at session start.',
|
|
7878
9373
|
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
7879
9374
|
'- MUST call `kuma_safety({ action: "verify" })` after edits.',
|
|
7880
|
-
"- MUST NOT call actions outside the
|
|
9375
|
+
"- MUST NOT call actions outside the core actions.",
|
|
7881
9376
|
"</kuma_obedience>",
|
|
7882
9377
|
"",
|
|
7883
9378
|
"<kuma_workflow>",
|
|
7884
9379
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
7885
|
-
'\u{
|
|
9380
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_context({ action: "map" })`',
|
|
9381
|
+
'\u{1F52C} **Unfamiliar code / blast radius:** `kuma_context({ action: "research", scope: "<area>" })` or `kuma_context({ action: "impact", target: "<file>" })`',
|
|
7886
9382
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7887
9383
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
7888
9384
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
@@ -7923,19 +9419,19 @@ __export(agentDetector_exports, {
|
|
|
7923
9419
|
getSkillPath: () => getSkillPath,
|
|
7924
9420
|
skillExists: () => skillExists
|
|
7925
9421
|
});
|
|
7926
|
-
import
|
|
7927
|
-
import
|
|
9422
|
+
import fs26 from "fs";
|
|
9423
|
+
import path27 from "path";
|
|
7928
9424
|
function checkFile(root, filePath) {
|
|
7929
9425
|
try {
|
|
7930
|
-
return
|
|
9426
|
+
return fs26.existsSync(path27.join(root, filePath));
|
|
7931
9427
|
} catch {
|
|
7932
9428
|
return false;
|
|
7933
9429
|
}
|
|
7934
9430
|
}
|
|
7935
9431
|
function checkDir(root, dirPath) {
|
|
7936
9432
|
try {
|
|
7937
|
-
const fullPath =
|
|
7938
|
-
return
|
|
9433
|
+
const fullPath = path27.join(root, dirPath);
|
|
9434
|
+
return fs26.existsSync(fullPath) && fs26.statSync(fullPath).isDirectory();
|
|
7939
9435
|
} catch {
|
|
7940
9436
|
return false;
|
|
7941
9437
|
}
|
|
@@ -8003,7 +9499,7 @@ function getAgentLabel(type) {
|
|
|
8003
9499
|
function skillExists(type, projectRoot) {
|
|
8004
9500
|
const root = projectRoot ?? getProjectRoot();
|
|
8005
9501
|
const skillPath = getSkillPath(type);
|
|
8006
|
-
return
|
|
9502
|
+
return fs26.existsSync(path27.join(root, skillPath));
|
|
8007
9503
|
}
|
|
8008
9504
|
var AGENT_DETECTORS;
|
|
8009
9505
|
var init_agentDetector = __esm({
|
|
@@ -8034,25 +9530,25 @@ var init_agentDetector = __esm({
|
|
|
8034
9530
|
},
|
|
8035
9531
|
{
|
|
8036
9532
|
type: "antigravity",
|
|
8037
|
-
checkFiles: [],
|
|
9533
|
+
checkFiles: ["GEMINI.md", ".agents/rules/kuma.md"],
|
|
8038
9534
|
checkDirs: [".agents"],
|
|
8039
9535
|
priority: 75,
|
|
8040
9536
|
label: "Antigravity CLI (.agents/)"
|
|
8041
9537
|
},
|
|
9538
|
+
{
|
|
9539
|
+
type: "opencode",
|
|
9540
|
+
checkFiles: ["opencode.json", ".opencode/opencode.json", ".opencode/plugins/kuma.js"],
|
|
9541
|
+
checkDirs: [".opencode"],
|
|
9542
|
+
priority: 78,
|
|
9543
|
+
label: "OpenCode (.opencode/)"
|
|
9544
|
+
},
|
|
8042
9545
|
{
|
|
8043
9546
|
type: "windsurf",
|
|
8044
|
-
checkFiles: [],
|
|
9547
|
+
checkFiles: [".windsurfrules"],
|
|
8045
9548
|
checkDirs: [".windsurf"],
|
|
8046
9549
|
priority: 70,
|
|
8047
9550
|
label: "Windsurf (.windsurf/)"
|
|
8048
9551
|
},
|
|
8049
|
-
{
|
|
8050
|
-
type: "opencode",
|
|
8051
|
-
checkFiles: [".agents/skills/kuma/SKILL.md"],
|
|
8052
|
-
checkDirs: [".agents"],
|
|
8053
|
-
priority: 65,
|
|
8054
|
-
label: "OpenCode (.agents/skills/kuma/SKILL.md)"
|
|
8055
|
-
},
|
|
8056
9552
|
{
|
|
8057
9553
|
type: "kiro",
|
|
8058
9554
|
checkFiles: [],
|
|
@@ -8145,7 +9641,30 @@ function generateSkill(type) {
|
|
|
8145
9641
|
function getSecondaryFiles(type) {
|
|
8146
9642
|
switch (type) {
|
|
8147
9643
|
case "antigravity":
|
|
8148
|
-
return [
|
|
9644
|
+
return [
|
|
9645
|
+
{ path: ".agents/mcp_config.json", content: generateAntigravityMcpConfig() },
|
|
9646
|
+
{ path: ".agents/rules/kuma.md", content: generateAntigravityRule() },
|
|
9647
|
+
{ path: "GEMINI.md", content: generateGeminiMd() }
|
|
9648
|
+
];
|
|
9649
|
+
case "opencode":
|
|
9650
|
+
return [
|
|
9651
|
+
{ path: ".opencode/plugins/kuma.js", content: generateOpencodePlugin() },
|
|
9652
|
+
{ path: ".opencode/skills/kuma/SKILL.md", content: generateOpencodeSkill() },
|
|
9653
|
+
{ path: ".agents/skills/kuma/SKILL.md", content: generateOpencodeSkill() }
|
|
9654
|
+
];
|
|
9655
|
+
case "claude":
|
|
9656
|
+
return [
|
|
9657
|
+
{ path: ".claude/skills/kuma/SKILL.md", content: generateClaudeSkill() }
|
|
9658
|
+
];
|
|
9659
|
+
case "windsurf":
|
|
9660
|
+
return [
|
|
9661
|
+
{ path: ".windsurfrules", content: generateWindsurfrules() }
|
|
9662
|
+
];
|
|
9663
|
+
case "cline":
|
|
9664
|
+
return [
|
|
9665
|
+
{ path: ".roo/rules/kuma.md", content: generateRooRule() },
|
|
9666
|
+
{ path: ".trae/rules/kuma.md", content: generateTraeRule() }
|
|
9667
|
+
];
|
|
8149
9668
|
case "codex":
|
|
8150
9669
|
return [{ path: ".codex/config.toml", content: generateCodexConfigToml() }];
|
|
8151
9670
|
case "qwen":
|
|
@@ -8158,6 +9677,132 @@ function getSecondaryFiles(type) {
|
|
|
8158
9677
|
return [];
|
|
8159
9678
|
}
|
|
8160
9679
|
}
|
|
9680
|
+
function generateAntigravityRule() {
|
|
9681
|
+
return [
|
|
9682
|
+
"# \u{1F43B} Kuma MCP \u2014 Antigravity Rules",
|
|
9683
|
+
"",
|
|
9684
|
+
"This project uses **Kuma MCP** for runtime safety, blast radius analysis, and codebase memory.",
|
|
9685
|
+
"",
|
|
9686
|
+
BOOTSTRAP,
|
|
9687
|
+
"",
|
|
9688
|
+
"### \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
9689
|
+
"If Matcha is active in this workspace:",
|
|
9690
|
+
"- **Matcha** governs engineering philosophy, intent discovery, reuse checks, and review gates.",
|
|
9691
|
+
"- **Kuma** governs blast radius calculation, monorepo workspace dependencies, known gotchas memory, architecture boundary guards, and scoped test verification.",
|
|
9692
|
+
"- Workflow: Use Matcha to plan (Intent Discovery) \u2192 Use Kuma to inspect dependencies & blast radius (`kuma_context({ action: 'impact' })`) \u2192 Use Kuma to verify after edits (`kuma_safety({ action: 'verify' })`).",
|
|
9693
|
+
"",
|
|
9694
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9695
|
+
].join("\n");
|
|
9696
|
+
}
|
|
9697
|
+
function generateGeminiMd() {
|
|
9698
|
+
return [
|
|
9699
|
+
"# \u{1F43B} Kuma MCP \u2014 Workspace Instructions",
|
|
9700
|
+
"",
|
|
9701
|
+
"This workspace uses **Kuma MCP** for runtime safety, monorepo intelligence, blast radius calculation, and memory.",
|
|
9702
|
+
"",
|
|
9703
|
+
BOOTSTRAP,
|
|
9704
|
+
"",
|
|
9705
|
+
"### \u{1F375} Matcha & Kuma Harmony",
|
|
9706
|
+
"If Matcha is active: Matcha provides Intent Discovery & review; Kuma provides blast radius, gotchas memory, architecture guards, and test verification.",
|
|
9707
|
+
"",
|
|
9708
|
+
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
9709
|
+
].join("\n");
|
|
9710
|
+
}
|
|
9711
|
+
function generateOpencodePlugin() {
|
|
9712
|
+
return [
|
|
9713
|
+
"/**",
|
|
9714
|
+
" * \u{1F43B} Kuma MCP \u2014 OpenCode Plugin",
|
|
9715
|
+
" * Auto-injects gotcha warnings and safety checks before file edits and commands in OpenCode.",
|
|
9716
|
+
" */",
|
|
9717
|
+
'import fs from "node:fs";',
|
|
9718
|
+
'import path from "node:path";',
|
|
9719
|
+
"",
|
|
9720
|
+
"function getProjectRoot() {",
|
|
9721
|
+
" let curr = process.cwd();",
|
|
9722
|
+
" while (curr !== path.dirname(curr)) {",
|
|
9723
|
+
' if (fs.existsSync(path.join(curr, ".kuma")) || fs.existsSync(path.join(curr, ".git"))) {',
|
|
9724
|
+
" return curr;",
|
|
9725
|
+
" }",
|
|
9726
|
+
" curr = path.dirname(curr);",
|
|
9727
|
+
" }",
|
|
9728
|
+
" return process.cwd();",
|
|
9729
|
+
"}",
|
|
9730
|
+
"",
|
|
9731
|
+
"function getActiveGotchasForFile(filePath) {",
|
|
9732
|
+
" try {",
|
|
9733
|
+
" const root = getProjectRoot();",
|
|
9734
|
+
' const gotchasPath = path.join(root, ".kuma", "KNOWN_GOTCHAS.md");',
|
|
9735
|
+
" if (!fs.existsSync(gotchasPath)) return [];",
|
|
9736
|
+
' const content = fs.readFileSync(gotchasPath, "utf-8");',
|
|
9737
|
+
" const gotchas = [];",
|
|
9738
|
+
' const normalizedTarget = filePath.replace(/^[./]+/, "");',
|
|
9739
|
+
" const sections = content.split(/###\\s+/);",
|
|
9740
|
+
" for (const section of sections) {",
|
|
9741
|
+
" if (!section.trim()) continue;",
|
|
9742
|
+
' const lines = section.split("\\n");',
|
|
9743
|
+
' const title = lines[0] || "";',
|
|
9744
|
+
" if (title.includes(normalizedTarget) || section.includes(normalizedTarget)) {",
|
|
9745
|
+
" gotchas.push(title.trim());",
|
|
9746
|
+
" }",
|
|
9747
|
+
" }",
|
|
9748
|
+
" return gotchas;",
|
|
9749
|
+
" } catch {",
|
|
9750
|
+
" return [];",
|
|
9751
|
+
" }",
|
|
9752
|
+
"}",
|
|
9753
|
+
"",
|
|
9754
|
+
"export const KumaPlugin = async () => {",
|
|
9755
|
+
" return {",
|
|
9756
|
+
' "tool.execute.before": async (input, output) => {',
|
|
9757
|
+
' const tool = (input.tool || "").toLowerCase();',
|
|
9758
|
+
" const args = output.args || {};",
|
|
9759
|
+
' const targetPath = args.filePath || args.targetFile || args.path || args.file || "";',
|
|
9760
|
+
' if (targetPath && (tool.includes("edit") || tool.includes("write") || tool.includes("patch"))) {',
|
|
9761
|
+
" const gotchas = getActiveGotchasForFile(String(targetPath));",
|
|
9762
|
+
" if (gotchas.length > 0) {",
|
|
9763
|
+
" console.warn(",
|
|
9764
|
+
" `\\n\u{1F43B} [Kuma Alert] Active gotchas found for ${targetPath}:\\n` +",
|
|
9765
|
+
' gotchas.map((g) => ` \u26A0\uFE0F ${g}`).join("\\n") +',
|
|
9766
|
+
' `\\n\u{1F449} Remember to record any new gotchas via kuma_memory({ action: "gotcha" })\\n`',
|
|
9767
|
+
" );",
|
|
9768
|
+
" }",
|
|
9769
|
+
" }",
|
|
9770
|
+
" },",
|
|
9771
|
+
" };",
|
|
9772
|
+
"};",
|
|
9773
|
+
""
|
|
9774
|
+
].join("\n");
|
|
9775
|
+
}
|
|
9776
|
+
function generateWindsurfrules() {
|
|
9777
|
+
return [
|
|
9778
|
+
"# Kuma MCP \u2014 Windsurf Rules",
|
|
9779
|
+
"",
|
|
9780
|
+
BOOTSTRAP,
|
|
9781
|
+
"",
|
|
9782
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9783
|
+
].join("\n");
|
|
9784
|
+
}
|
|
9785
|
+
function generateRooRule() {
|
|
9786
|
+
return [
|
|
9787
|
+
"---",
|
|
9788
|
+
"description: Kuma MCP \u2014 .kuma/ is the single source of truth",
|
|
9789
|
+
"alwaysApply: true",
|
|
9790
|
+
"---",
|
|
9791
|
+
"",
|
|
9792
|
+
BOOTSTRAP,
|
|
9793
|
+
"",
|
|
9794
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9795
|
+
].join("\n");
|
|
9796
|
+
}
|
|
9797
|
+
function generateTraeRule() {
|
|
9798
|
+
return [
|
|
9799
|
+
"# Kuma MCP \u2014 Trae Rules",
|
|
9800
|
+
"",
|
|
9801
|
+
BOOTSTRAP,
|
|
9802
|
+
"",
|
|
9803
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9804
|
+
].join("\n");
|
|
9805
|
+
}
|
|
8161
9806
|
function generateClaudeSkill() {
|
|
8162
9807
|
return [
|
|
8163
9808
|
"---",
|
|
@@ -8399,12 +10044,13 @@ var init_skillGenerator = __esm({
|
|
|
8399
10044
|
'- MUST call `kuma_context({ action: "init" })` at session start.',
|
|
8400
10045
|
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
8401
10046
|
'- MUST call `kuma_safety({ action: "verify" })` after edits.',
|
|
8402
|
-
"- MUST NOT call actions outside the
|
|
10047
|
+
"- MUST NOT call actions outside the core actions.",
|
|
8403
10048
|
"</kuma_obedience>",
|
|
8404
10049
|
"",
|
|
8405
10050
|
"<kuma_workflow>",
|
|
8406
10051
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
8407
|
-
'\u{
|
|
10052
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_context({ action: "map" })`',
|
|
10053
|
+
'\u{1F52C} **Unfamiliar code / blast radius:** `kuma_context({ action: "research", scope: "<area>" })` or `kuma_context({ action: "impact", target: "<file>" })`',
|
|
8408
10054
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8409
10055
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
8410
10056
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
@@ -8446,8 +10092,8 @@ init_kumaDb();
|
|
|
8446
10092
|
init_kumaGraph();
|
|
8447
10093
|
init_kumaMemory();
|
|
8448
10094
|
init_pathValidator();
|
|
8449
|
-
import
|
|
8450
|
-
import
|
|
10095
|
+
import fs16 from "fs";
|
|
10096
|
+
import path17 from "path";
|
|
8451
10097
|
import crypto3 from "crypto";
|
|
8452
10098
|
var CONTEXT_ALIASES = {
|
|
8453
10099
|
"research": "research",
|
|
@@ -8466,7 +10112,16 @@ var CONTEXT_ALIASES = {
|
|
|
8466
10112
|
"history": "history",
|
|
8467
10113
|
"why": "history",
|
|
8468
10114
|
"touched": "history",
|
|
8469
|
-
"provenance": "history"
|
|
10115
|
+
"provenance": "history",
|
|
10116
|
+
"map": "map",
|
|
10117
|
+
"repo-map": "map",
|
|
10118
|
+
"repo_map": "map",
|
|
10119
|
+
"workspace": "map",
|
|
10120
|
+
"topology": "map",
|
|
10121
|
+
"impact": "impact",
|
|
10122
|
+
"blast-radius": "impact",
|
|
10123
|
+
"blast_radius": "impact",
|
|
10124
|
+
"blast": "impact"
|
|
8470
10125
|
};
|
|
8471
10126
|
async function handleContext(params) {
|
|
8472
10127
|
const rawAction = params.action || "init";
|
|
@@ -8481,20 +10136,41 @@ async function handleContext(params) {
|
|
|
8481
10136
|
return handleHistory(params);
|
|
8482
10137
|
case "flow":
|
|
8483
10138
|
return handleFlow(params);
|
|
10139
|
+
case "map":
|
|
10140
|
+
return handleMap(params);
|
|
10141
|
+
case "impact":
|
|
10142
|
+
return handleImpact(params);
|
|
8484
10143
|
default:
|
|
8485
|
-
return `Unknown action "${action}". Use: init, research, history, flow`;
|
|
10144
|
+
return `Unknown action "${action}". Use: init, research, history, flow, map, impact`;
|
|
8486
10145
|
}
|
|
8487
10146
|
}
|
|
8488
10147
|
async function handleInit(_params) {
|
|
8489
10148
|
sessionMemory.setGoal(_params.goal || "Exploring project");
|
|
8490
10149
|
sessionMemory.recordToolCall("kuma_context_init", {});
|
|
10150
|
+
const branch = sessionMemory.getCurrentBranch();
|
|
10151
|
+
const branchTag = branch ? ` [git: \`${branch}\`]` : "";
|
|
8491
10152
|
const lines = [
|
|
8492
10153
|
"\u{1F9E0} **Kuma \u2014 Project Brief (lean)**",
|
|
8493
10154
|
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
8494
10155
|
"",
|
|
8495
|
-
`\u{1F4C1} Project: ${getProjectRoot().split("/").pop() || "unknown"}`,
|
|
10156
|
+
`\u{1F4C1} Project: ${getProjectRoot().split("/").pop() || "unknown"}${branchTag}`,
|
|
8496
10157
|
""
|
|
8497
10158
|
];
|
|
10159
|
+
const branchTransition = sessionMemory.getBranchTransition();
|
|
10160
|
+
if (branchTransition?.switched) {
|
|
10161
|
+
lines.push(`\u{1F500} **Branch switched**: from \`${branchTransition.from}\` to \`${branchTransition.to}\`. Session context updated.`);
|
|
10162
|
+
lines.push("");
|
|
10163
|
+
}
|
|
10164
|
+
try {
|
|
10165
|
+
const { getWorkspaceInfo: getWorkspaceInfo2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
10166
|
+
const wsInfo = await getWorkspaceInfo2();
|
|
10167
|
+
if (wsInfo.isWorkspace) {
|
|
10168
|
+
lines.push(`\u{1F4E6} **Workspace**: ${wsInfo.packages.length} package(s) detected (${wsInfo.type.toUpperCase()})`);
|
|
10169
|
+
lines.push(` ${wsInfo.packages.slice(0, 5).map((p) => `\`${p.name}\``).join(", ")}${wsInfo.packages.length > 5 ? ` +${wsInfo.packages.length - 5} more` : ""}`);
|
|
10170
|
+
lines.push("");
|
|
10171
|
+
}
|
|
10172
|
+
} catch {
|
|
10173
|
+
}
|
|
8498
10174
|
const goal = _params.goal || sessionMemory.getSummary().currentGoal || "";
|
|
8499
10175
|
try {
|
|
8500
10176
|
const { maybeRunAutoCleanup: maybeRunAutoCleanup2 } = await Promise.resolve().then(() => (init_kumaMaintenance(), kumaMaintenance_exports));
|
|
@@ -8519,21 +10195,73 @@ async function handleInit(_params) {
|
|
|
8519
10195
|
}
|
|
8520
10196
|
try {
|
|
8521
10197
|
const { getFreshGotchasForFile: getFreshGotchasForFile2 } = await Promise.resolve().then(() => (init_kumaInject(), kumaInject_exports));
|
|
10198
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
8522
10199
|
const modified = summary.modifiedFiles || [];
|
|
8523
10200
|
const seen = /* @__PURE__ */ new Set();
|
|
8524
|
-
|
|
10201
|
+
const collectedGotchas = [];
|
|
8525
10202
|
for (const f of modified.slice(0, 4)) {
|
|
8526
10203
|
if (!f.filePath || seen.has(f.filePath)) continue;
|
|
8527
10204
|
seen.add(f.filePath);
|
|
8528
10205
|
const gotchas = await getFreshGotchasForFile2(f.filePath, 3);
|
|
8529
10206
|
for (const g of gotchas) {
|
|
8530
|
-
|
|
8531
|
-
|
|
10207
|
+
const key = `${g.filePath}::${g.description.substring(0, 30)}`;
|
|
10208
|
+
if (!seen.has(key)) {
|
|
10209
|
+
seen.add(key);
|
|
10210
|
+
collectedGotchas.push({
|
|
10211
|
+
filePath: g.filePath,
|
|
10212
|
+
description: g.description,
|
|
10213
|
+
severity: g.severity
|
|
10214
|
+
});
|
|
10215
|
+
}
|
|
10216
|
+
}
|
|
10217
|
+
}
|
|
10218
|
+
let totalActiveCount = 0;
|
|
10219
|
+
try {
|
|
10220
|
+
const db = await getDb2();
|
|
10221
|
+
const countStmt = db.prepare(`SELECT COUNT(*) as cnt FROM known_gotchas WHERE status IN ('active', 'verified')`);
|
|
10222
|
+
if (countStmt.step()) totalActiveCount = countStmt.getAsObject().cnt || 0;
|
|
10223
|
+
countStmt.free();
|
|
10224
|
+
if (collectedGotchas.length < 5) {
|
|
10225
|
+
const stmt = db.prepare(`
|
|
10226
|
+
SELECT file_path, description, severity
|
|
10227
|
+
FROM known_gotchas
|
|
10228
|
+
WHERE status IN ('active', 'verified')
|
|
10229
|
+
ORDER BY
|
|
10230
|
+
CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
|
|
10231
|
+
updated_at DESC
|
|
10232
|
+
LIMIT 10
|
|
10233
|
+
`);
|
|
10234
|
+
while (stmt.step()) {
|
|
10235
|
+
const row = stmt.getAsObject();
|
|
10236
|
+
const key = `${row.file_path}::${row.description.substring(0, 30)}`;
|
|
10237
|
+
if (!seen.has(key)) {
|
|
10238
|
+
seen.add(key);
|
|
10239
|
+
collectedGotchas.push({
|
|
10240
|
+
filePath: row.file_path,
|
|
10241
|
+
description: row.description,
|
|
10242
|
+
severity: row.severity
|
|
10243
|
+
});
|
|
10244
|
+
}
|
|
10245
|
+
if (collectedGotchas.length >= 5) break;
|
|
10246
|
+
}
|
|
10247
|
+
stmt.free();
|
|
10248
|
+
}
|
|
10249
|
+
} catch {
|
|
10250
|
+
}
|
|
10251
|
+
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
10252
|
+
collectedGotchas.sort((a, b) => (severityRank[a.severity] ?? 2) - (severityRank[b.severity] ?? 2));
|
|
10253
|
+
const budgetedGotchas = collectedGotchas.slice(0, 5);
|
|
10254
|
+
if (budgetedGotchas.length > 0) {
|
|
10255
|
+
lines.push("**Active Gotchas (fresh & budgeted)**");
|
|
10256
|
+
for (const g of budgetedGotchas) {
|
|
8532
10257
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
8533
|
-
lines.push(` ${icon} ${g.filePath} \u2014 ${g.description.substring(0, 90)}`);
|
|
10258
|
+
lines.push(` ${icon} [${g.severity}] ${g.filePath} \u2014 ${g.description.substring(0, 90)}`);
|
|
8534
10259
|
}
|
|
10260
|
+
if (totalActiveCount > budgetedGotchas.length) {
|
|
10261
|
+
lines.push(` \u2139\uFE0F +${totalActiveCount - budgetedGotchas.length} more active gotchas in DB (query: kuma_memory({ action: 'gotcha' }))`);
|
|
10262
|
+
}
|
|
10263
|
+
lines.push("");
|
|
8535
10264
|
}
|
|
8536
|
-
if (gotchasPushed > 0) lines.push("");
|
|
8537
10265
|
} catch {
|
|
8538
10266
|
}
|
|
8539
10267
|
try {
|
|
@@ -8552,7 +10280,7 @@ async function handleInit(_params) {
|
|
|
8552
10280
|
}
|
|
8553
10281
|
} catch {
|
|
8554
10282
|
}
|
|
8555
|
-
lines.push("\u{1F4A1} Deeper: kuma_context({ action: 'history', target: '<file>' }) \xB7 Flow: kuma_context({ action: 'flow', target: '<domain>' }) \xB7
|
|
10283
|
+
lines.push("\u{1F4A1} Deeper: kuma_context({ action: 'history', target: '<file>' }) \xB7 Flow: kuma_context({ action: 'flow', target: '<domain>' }) \xB7 Map: kuma_context({ action: 'map' }) \xB7 Impact: kuma_context({ action: 'impact', target: '<file>' })");
|
|
8556
10284
|
return lines.join("\n");
|
|
8557
10285
|
}
|
|
8558
10286
|
function detectSessionFocus(goal) {
|
|
@@ -8645,13 +10373,20 @@ async function handleResearch(params) {
|
|
|
8645
10373
|
} catch {
|
|
8646
10374
|
}
|
|
8647
10375
|
lines.push("");
|
|
8648
|
-
lines.push("**Step 4/5: Impact Analysis**");
|
|
10376
|
+
lines.push("**Step 4/5: Impact Analysis (Blast Radius)**");
|
|
8649
10377
|
try {
|
|
8650
10378
|
const impact = await analyzeImpact(scope);
|
|
8651
10379
|
lines.push(` \u{1F4CA} ${impact.references} reference(s) across ${impact.files} file(s)`);
|
|
10380
|
+
if (impact.packageName) lines.push(` \u{1F4E6} Package: \`${impact.packageName}\``);
|
|
10381
|
+
if (impact.downstreamPackages && impact.downstreamPackages.length > 0) {
|
|
10382
|
+
lines.push(` \u{1F310} Downstream consumers (${impact.downstreamPackages.length}): ${impact.downstreamPackages.slice(0, 4).join(", ")}`);
|
|
10383
|
+
}
|
|
8652
10384
|
lines.push(` \u{1F9EA} ${impact.testFiles} test file(s) related`);
|
|
8653
10385
|
lines.push(` \u26A0\uFE0F Risk: ${impact.risk.toUpperCase()}`);
|
|
8654
|
-
if (impact.
|
|
10386
|
+
if (impact.riskFlags && impact.riskFlags.length > 0) {
|
|
10387
|
+
for (const rf of impact.riskFlags) lines.push(` \u2022 ${rf}`);
|
|
10388
|
+
}
|
|
10389
|
+
if (impact.entryPoints && impact.entryPoints.length > 0) lines.push(` \u{1F3AF} Entry: ${impact.entryPoints.slice(0, 3).join(", ")}`);
|
|
8655
10390
|
} catch {
|
|
8656
10391
|
lines.push(" \u26A0\uFE0F Impact analysis unavailable");
|
|
8657
10392
|
}
|
|
@@ -8706,7 +10441,7 @@ async function handleHistory(params) {
|
|
|
8706
10441
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
8707
10442
|
const db = await getDb2();
|
|
8708
10443
|
const stmt = db.prepare(`SELECT description, last_verified_at FROM known_gotchas WHERE status = 'resolved' AND file_path LIKE ? ORDER BY last_verified_at DESC LIMIT 3`);
|
|
8709
|
-
stmt.bind([`%${
|
|
10444
|
+
stmt.bind([`%${path17.basename(target)}%`]);
|
|
8710
10445
|
const resolved = [];
|
|
8711
10446
|
while (stmt.step()) resolved.push(stmt.getAsObject());
|
|
8712
10447
|
stmt.free();
|
|
@@ -8735,15 +10470,28 @@ async function handleFlow(params) {
|
|
|
8735
10470
|
const { getFreshDomainFlow: getFreshDomainFlow2 } = await Promise.resolve().then(() => (init_kumaFlowCache(), kumaFlowCache_exports));
|
|
8736
10471
|
return await getFreshDomainFlow2(domain);
|
|
8737
10472
|
}
|
|
10473
|
+
async function handleMap(_params) {
|
|
10474
|
+
sessionMemory.recordToolCall("kuma_context_map", {});
|
|
10475
|
+
const { getWorkspaceInfo: getWorkspaceInfo2, formatWorkspaceMap: formatWorkspaceMap2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
10476
|
+
const wsInfo = await getWorkspaceInfo2();
|
|
10477
|
+
return formatWorkspaceMap2(wsInfo);
|
|
10478
|
+
}
|
|
10479
|
+
async function handleImpact(params) {
|
|
10480
|
+
const target = params.target || params.scope || "project";
|
|
10481
|
+
sessionMemory.recordToolCall("kuma_context_impact", { target });
|
|
10482
|
+
const { analyzeImpact: analyzeImpact2, formatImpact: formatImpact2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10483
|
+
const impact = await analyzeImpact2(target);
|
|
10484
|
+
return formatImpact2(impact);
|
|
10485
|
+
}
|
|
8738
10486
|
function computeProjectHash(scope) {
|
|
8739
10487
|
try {
|
|
8740
10488
|
const root = getProjectRoot();
|
|
8741
|
-
const files =
|
|
10489
|
+
const files = fs16.readdirSync(root).slice(0, 20);
|
|
8742
10490
|
const hash = crypto3.createHash("md5");
|
|
8743
10491
|
hash.update(scope);
|
|
8744
10492
|
for (const f of files) {
|
|
8745
10493
|
try {
|
|
8746
|
-
const stat =
|
|
10494
|
+
const stat = fs16.statSync(path17.join(root, f));
|
|
8747
10495
|
hash.update(`${f}:${stat.mtimeMs}`);
|
|
8748
10496
|
} catch {
|
|
8749
10497
|
}
|
|
@@ -8760,8 +10508,8 @@ init_kumaDb();
|
|
|
8760
10508
|
init_kumaMemory();
|
|
8761
10509
|
init_pathValidator();
|
|
8762
10510
|
init_pathValidator();
|
|
8763
|
-
import
|
|
8764
|
-
import
|
|
10511
|
+
import fs18 from "fs";
|
|
10512
|
+
import path19 from "path";
|
|
8765
10513
|
var MEMORY_ALIASES = {
|
|
8766
10514
|
"decision": "decision",
|
|
8767
10515
|
"adr": "decision",
|
|
@@ -8777,7 +10525,13 @@ var MEMORY_ALIASES = {
|
|
|
8777
10525
|
"architecture": "arch_flow",
|
|
8778
10526
|
"gotcha": "gotcha",
|
|
8779
10527
|
"gotchas": "gotcha",
|
|
8780
|
-
"quirk": "gotcha"
|
|
10528
|
+
"quirk": "gotcha",
|
|
10529
|
+
"resolve_gotcha": "gotcha",
|
|
10530
|
+
"resolve-gotcha": "gotcha",
|
|
10531
|
+
"resolve": "gotcha",
|
|
10532
|
+
"deprecate_gotcha": "gotcha",
|
|
10533
|
+
"deprecate-gotcha": "gotcha",
|
|
10534
|
+
"deprecate": "gotcha"
|
|
8781
10535
|
};
|
|
8782
10536
|
async function handleMemory(params) {
|
|
8783
10537
|
const rawAction = params.action || "search";
|
|
@@ -8844,9 +10598,9 @@ async function handleResearchSave(params) {
|
|
|
8844
10598
|
} catch {
|
|
8845
10599
|
}
|
|
8846
10600
|
try {
|
|
8847
|
-
const researchDir =
|
|
8848
|
-
if (!
|
|
8849
|
-
|
|
10601
|
+
const researchDir = path19.join(getProjectRoot(), ".kuma", "research");
|
|
10602
|
+
if (!fs18.existsSync(researchDir)) fs18.mkdirSync(researchDir, { recursive: true });
|
|
10603
|
+
fs18.writeFileSync(path19.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
8850
10604
|
} catch {
|
|
8851
10605
|
}
|
|
8852
10606
|
sessionMemory.recordMemoryAction("research_save");
|
|
@@ -8966,28 +10720,70 @@ async function handleArchFlow(params) {
|
|
|
8966
10720
|
return readLayer2("arch_flow");
|
|
8967
10721
|
}
|
|
8968
10722
|
async function handleGotchaAction(params) {
|
|
10723
|
+
const rawStatus = (params.status || "").toLowerCase().trim();
|
|
10724
|
+
const rawAction = (params.action || "").toLowerCase().trim();
|
|
10725
|
+
const isResolution = rawStatus === "resolved" || rawStatus === "resolve" || rawAction === "resolve_gotcha" || rawAction === "resolve";
|
|
10726
|
+
const isDeprecation = rawStatus === "deprecated" || rawStatus === "deprecate" || rawAction === "deprecate_gotcha" || rawAction === "deprecate";
|
|
10727
|
+
const target = params.target || params.id || params.scope;
|
|
10728
|
+
if (isResolution && target) {
|
|
10729
|
+
const { resolveGotcha: resolveGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10730
|
+
const res = await resolveGotcha2(
|
|
10731
|
+
target,
|
|
10732
|
+
params.content || params.rationale || params.description,
|
|
10733
|
+
"agent"
|
|
10734
|
+
);
|
|
10735
|
+
sessionMemory.recordMemoryAction("gotcha");
|
|
10736
|
+
return res.message;
|
|
10737
|
+
}
|
|
10738
|
+
if (isDeprecation && target) {
|
|
10739
|
+
const { deprecateGotcha: deprecateGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10740
|
+
const res = await deprecateGotcha2(
|
|
10741
|
+
target,
|
|
10742
|
+
params.content || params.rationale || params.description
|
|
10743
|
+
);
|
|
10744
|
+
sessionMemory.recordMemoryAction("gotcha");
|
|
10745
|
+
return res.message;
|
|
10746
|
+
}
|
|
8969
10747
|
const content = (params.content ?? params.description ?? "").trim();
|
|
8970
10748
|
const scope = (params.scope ?? "").trim();
|
|
10749
|
+
const validSeverities = ["low", "medium", "high", "critical"];
|
|
10750
|
+
const validStatuses = ["candidate", "active", "verified", "resolved", "deprecated"];
|
|
10751
|
+
let severity = "medium";
|
|
10752
|
+
if (params.severity && validSeverities.includes(params.severity.toLowerCase())) {
|
|
10753
|
+
severity = params.severity.toLowerCase();
|
|
10754
|
+
} else if (rawStatus && validSeverities.includes(rawStatus)) {
|
|
10755
|
+
severity = rawStatus;
|
|
10756
|
+
}
|
|
10757
|
+
let status = "active";
|
|
10758
|
+
if (rawStatus && validStatuses.includes(rawStatus)) {
|
|
10759
|
+
status = rawStatus;
|
|
10760
|
+
}
|
|
8971
10761
|
if (content && scope) {
|
|
8972
10762
|
const normalizedScope = normalizeScope(scope) || scope;
|
|
8973
10763
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
8974
10764
|
const result = await addGotcha2({
|
|
8975
10765
|
filePath: normalizedScope,
|
|
8976
10766
|
description: content,
|
|
8977
|
-
severity
|
|
10767
|
+
severity,
|
|
10768
|
+
status,
|
|
8978
10769
|
// `description` doubles as workaround only when the canonical `content` field was used.
|
|
8979
10770
|
workaround: params.content ? params.description : void 0,
|
|
8980
|
-
triggerCommand: params.trigger_command
|
|
10771
|
+
triggerCommand: params.trigger_command,
|
|
10772
|
+
scopePackage: params.scopePackage
|
|
8981
10773
|
});
|
|
8982
10774
|
sessionMemory.recordMemoryAction("gotcha");
|
|
8983
10775
|
return result;
|
|
8984
10776
|
}
|
|
8985
10777
|
if (scope || params.title || params.status || params.trigger_command || params.description) {
|
|
8986
|
-
return '\u274C **Gotcha NOT saved** \u2014 missing `content` (bug description).\n\u2705 Use: kuma_memory({ action: "gotcha", scope: "<file_path>", content: "<what went wrong>", status: "high" })\n- `scope` \u2014 file path where the bug was found\n- `content` \u2014 bug description (required to save)\n- `status` \u2014 low | medium | high | critical';
|
|
10778
|
+
return '\u274C **Gotcha NOT saved** \u2014 missing `content` (bug description).\n\u2705 Use: kuma_memory({ action: "gotcha", scope: "<file_path>", content: "<what went wrong>", status: "high" })\n- `scope` \u2014 file path where the bug was found\n- `content` \u2014 bug description (required to save)\n- `status` \u2014 low | medium | high | critical (or \'resolved\' to mark fixed)';
|
|
8987
10779
|
}
|
|
8988
10780
|
const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
8989
10781
|
await syncGotchasToDb2();
|
|
8990
|
-
return await listGotchas2({
|
|
10782
|
+
return await listGotchas2({
|
|
10783
|
+
filePath: params.scope,
|
|
10784
|
+
severity: validSeverities.includes(rawStatus) ? rawStatus : void 0,
|
|
10785
|
+
status: validStatuses.includes(rawStatus) ? rawStatus : void 0
|
|
10786
|
+
});
|
|
8991
10787
|
}
|
|
8992
10788
|
|
|
8993
10789
|
// src/tools/kumaSafetyTool.ts
|
|
@@ -9000,8 +10796,8 @@ init_sessionMemory();
|
|
|
9000
10796
|
init_pathValidator();
|
|
9001
10797
|
init_sessionMemory();
|
|
9002
10798
|
init_kumaShared();
|
|
9003
|
-
import
|
|
9004
|
-
import
|
|
10799
|
+
import fs19 from "fs";
|
|
10800
|
+
import path20 from "path";
|
|
9005
10801
|
import { execSync as execSync4 } from "child_process";
|
|
9006
10802
|
var SCRIPT_PATCH_PATTERNS = [
|
|
9007
10803
|
"writeFileSync",
|
|
@@ -9028,20 +10824,20 @@ function findPatchScripts(projectRoot) {
|
|
|
9028
10824
|
const warnings = [];
|
|
9029
10825
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
9030
10826
|
for (const file of recentFiles) {
|
|
9031
|
-
const ext =
|
|
10827
|
+
const ext = path20.extname(file).toLowerCase();
|
|
9032
10828
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
9033
|
-
const relativePath =
|
|
10829
|
+
const relativePath = path20.relative(projectRoot, file);
|
|
9034
10830
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
9035
10831
|
continue;
|
|
9036
10832
|
}
|
|
9037
10833
|
try {
|
|
9038
|
-
const content =
|
|
10834
|
+
const content = fs19.readFileSync(file, "utf-8").toLowerCase();
|
|
9039
10835
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
9040
10836
|
if (matchedPattern) {
|
|
9041
10837
|
warnings.push({
|
|
9042
10838
|
severity: "high",
|
|
9043
10839
|
pattern: "script-patching",
|
|
9044
|
-
message: `Created script file that modifies other files: ${
|
|
10840
|
+
message: `Created script file that modifies other files: ${path20.basename(file)}`,
|
|
9045
10841
|
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
9046
10842
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
9047
10843
|
filePath: relativePath
|
|
@@ -9075,15 +10871,15 @@ function detectBashGrepUsage() {
|
|
|
9075
10871
|
function scanRecentFiles(projectRoot) {
|
|
9076
10872
|
const recent = [];
|
|
9077
10873
|
try {
|
|
9078
|
-
const entries =
|
|
10874
|
+
const entries = fs19.readdirSync(projectRoot, { withFileTypes: true });
|
|
9079
10875
|
const now = Date.now();
|
|
9080
10876
|
const recentThreshold = 30 * 60 * 1e3;
|
|
9081
10877
|
for (const entry of entries) {
|
|
9082
10878
|
if (!entry.isFile()) continue;
|
|
9083
10879
|
try {
|
|
9084
|
-
const stat =
|
|
10880
|
+
const stat = fs19.statSync(path20.join(projectRoot, entry.name));
|
|
9085
10881
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
9086
|
-
recent.push(
|
|
10882
|
+
recent.push(path20.join(projectRoot, entry.name));
|
|
9087
10883
|
}
|
|
9088
10884
|
} catch {
|
|
9089
10885
|
continue;
|
|
@@ -9107,14 +10903,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
9107
10903
|
const prefix = line.substring(0, 2);
|
|
9108
10904
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
9109
10905
|
const file = line.substring(3).trim();
|
|
9110
|
-
const ext =
|
|
10906
|
+
const ext = path20.extname(file).toLowerCase();
|
|
9111
10907
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
9112
10908
|
const isRootLevel = !file.includes("/");
|
|
9113
10909
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
9114
10910
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
9115
|
-
const fullPath =
|
|
9116
|
-
if (
|
|
9117
|
-
const content =
|
|
10911
|
+
const fullPath = path20.join(projectRoot, file);
|
|
10912
|
+
if (fs19.existsSync(fullPath)) {
|
|
10913
|
+
const content = fs19.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
9118
10914
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
9119
10915
|
if (matchedPattern) {
|
|
9120
10916
|
warnings.push({
|
|
@@ -9137,7 +10933,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
9137
10933
|
if (deletedStdout) {
|
|
9138
10934
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
9139
10935
|
for (const file of deletedFiles) {
|
|
9140
|
-
const ext =
|
|
10936
|
+
const ext = path20.extname(file).toLowerCase();
|
|
9141
10937
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
9142
10938
|
warnings.push({
|
|
9143
10939
|
severity: "high",
|
|
@@ -9199,21 +10995,21 @@ function detectAllAntiPatterns() {
|
|
|
9199
10995
|
// src/engine/contextSnapshot.ts
|
|
9200
10996
|
init_sessionMemory();
|
|
9201
10997
|
init_pathValidator();
|
|
9202
|
-
import
|
|
9203
|
-
import
|
|
10998
|
+
import fs20 from "fs";
|
|
10999
|
+
import path21 from "path";
|
|
9204
11000
|
import { execSync as execSync5 } from "child_process";
|
|
9205
11001
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
9206
11002
|
function snapshotDir() {
|
|
9207
|
-
return
|
|
11003
|
+
return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
9208
11004
|
}
|
|
9209
11005
|
function ensureSnapshotDir() {
|
|
9210
11006
|
const dir = snapshotDir();
|
|
9211
|
-
if (!
|
|
9212
|
-
|
|
11007
|
+
if (!fs20.existsSync(dir)) {
|
|
11008
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
9213
11009
|
}
|
|
9214
11010
|
}
|
|
9215
11011
|
function snapshotFilePath(timestamp) {
|
|
9216
|
-
return
|
|
11012
|
+
return path21.join(snapshotDir(), `${timestamp}.json`);
|
|
9217
11013
|
}
|
|
9218
11014
|
function saveSnapshot(goal) {
|
|
9219
11015
|
try {
|
|
@@ -9234,7 +11030,7 @@ function saveSnapshot(goal) {
|
|
|
9234
11030
|
hasConventions: !!summary.hasConventions
|
|
9235
11031
|
};
|
|
9236
11032
|
try {
|
|
9237
|
-
|
|
11033
|
+
fs20.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
9238
11034
|
} catch {
|
|
9239
11035
|
}
|
|
9240
11036
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -9294,6 +11090,22 @@ async function handleKumaGuard(params) {
|
|
|
9294
11090
|
if (check === "all" || check === "anti-pattern") {
|
|
9295
11091
|
warnings.push(...detectAllAntiPatterns());
|
|
9296
11092
|
}
|
|
11093
|
+
if (check === "all" || check === "architecture") {
|
|
11094
|
+
try {
|
|
11095
|
+
const { checkArchitectureBoundaries: checkArchitectureBoundaries2 } = await Promise.resolve().then(() => (init_architectureGuard(), architectureGuard_exports));
|
|
11096
|
+
const modifiedPaths = stats.modifiedFiles.map((f) => f.filePath);
|
|
11097
|
+
const violations = await checkArchitectureBoundaries2(modifiedPaths.length > 0 ? modifiedPaths : void 0);
|
|
11098
|
+
for (const v of violations) {
|
|
11099
|
+
warnings.push({
|
|
11100
|
+
severity: v.severity,
|
|
11101
|
+
pattern: `arch-boundary:${v.rule}`,
|
|
11102
|
+
message: `Architecture violation in ${v.sourceFile}: ${v.targetImport}`,
|
|
11103
|
+
suggestion: v.suggestion
|
|
11104
|
+
});
|
|
11105
|
+
}
|
|
11106
|
+
} catch {
|
|
11107
|
+
}
|
|
11108
|
+
}
|
|
9297
11109
|
const loop = check === "all" || check === "loop" ? sessionMemory.detectLoop() : { isLooping: false };
|
|
9298
11110
|
if (loop.isLooping) {
|
|
9299
11111
|
warnings.push({
|
|
@@ -9754,14 +11566,14 @@ async function main() {
|
|
|
9754
11566
|
} catch {
|
|
9755
11567
|
}
|
|
9756
11568
|
try {
|
|
9757
|
-
const
|
|
9758
|
-
const
|
|
9759
|
-
const lockDir =
|
|
9760
|
-
if (
|
|
9761
|
-
const pidFile =
|
|
9762
|
-
if (
|
|
11569
|
+
const path28 = await import("path");
|
|
11570
|
+
const fs27 = await import("fs");
|
|
11571
|
+
const lockDir = path28.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
11572
|
+
if (fs27.existsSync(lockDir)) {
|
|
11573
|
+
const pidFile = path28.join(lockDir, "pid");
|
|
11574
|
+
if (fs27.existsSync(pidFile)) {
|
|
9763
11575
|
try {
|
|
9764
|
-
const pid = parseInt(
|
|
11576
|
+
const pid = parseInt(fs27.readFileSync(pidFile, "utf-8"), 10);
|
|
9765
11577
|
if (pid && pid !== process.pid) {
|
|
9766
11578
|
try {
|
|
9767
11579
|
process.kill(-pid, "SIGKILL");
|
|
@@ -9777,7 +11589,7 @@ async function main() {
|
|
|
9777
11589
|
} catch {
|
|
9778
11590
|
}
|
|
9779
11591
|
}
|
|
9780
|
-
|
|
11592
|
+
fs27.rmSync(lockDir, { recursive: true, force: true });
|
|
9781
11593
|
}
|
|
9782
11594
|
if (killedCount === 0) {
|
|
9783
11595
|
console.error("\u2705 No running verifications found to kill.");
|
|
@@ -9976,23 +11788,23 @@ async function main() {
|
|
|
9976
11788
|
});
|
|
9977
11789
|
const output = formatInitResults(results);
|
|
9978
11790
|
console.log(output);
|
|
9979
|
-
const
|
|
9980
|
-
const
|
|
9981
|
-
const matchaSkills =
|
|
9982
|
-
const matchaAgents =
|
|
11791
|
+
const fs27 = await import("fs");
|
|
11792
|
+
const path28 = await import("path");
|
|
11793
|
+
const matchaSkills = path28.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
11794
|
+
const matchaAgents = path28.resolve(
|
|
9983
11795
|
process.cwd(),
|
|
9984
11796
|
".agents/skills/matcha/SKILL.md"
|
|
9985
11797
|
);
|
|
9986
|
-
const matchaRootSkills =
|
|
11798
|
+
const matchaRootSkills = path28.resolve(
|
|
9987
11799
|
process.cwd(),
|
|
9988
11800
|
"skills/matcha/SKILL.md"
|
|
9989
11801
|
);
|
|
9990
|
-
const matchaAgentsMd =
|
|
9991
|
-
const matchaWindsurfRules =
|
|
11802
|
+
const matchaAgentsMd = path28.resolve(process.cwd(), "AGENTS.md");
|
|
11803
|
+
const matchaWindsurfRules = path28.resolve(
|
|
9992
11804
|
process.cwd(),
|
|
9993
11805
|
".windsurf"
|
|
9994
11806
|
);
|
|
9995
|
-
if (
|
|
11807
|
+
if (fs27.existsSync(matchaSkills) || fs27.existsSync(matchaAgents) || fs27.existsSync(matchaRootSkills) || fs27.existsSync(matchaAgentsMd) || fs27.existsSync(matchaWindsurfRules)) {
|
|
9996
11808
|
console.error(
|
|
9997
11809
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
9998
11810
|
);
|
|
@@ -10006,13 +11818,13 @@ async function main() {
|
|
|
10006
11818
|
(async () => {
|
|
10007
11819
|
try {
|
|
10008
11820
|
const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
10009
|
-
const
|
|
10010
|
-
const
|
|
10011
|
-
const initMdPath =
|
|
10012
|
-
if (!
|
|
10013
|
-
const kumaDir =
|
|
10014
|
-
if (!
|
|
10015
|
-
|
|
11821
|
+
const fs27 = await import("fs");
|
|
11822
|
+
const path28 = await import("path");
|
|
11823
|
+
const initMdPath = path28.resolve(process.cwd(), ".kuma/init.md");
|
|
11824
|
+
if (!fs27.existsSync(initMdPath)) {
|
|
11825
|
+
const kumaDir = path28.dirname(initMdPath);
|
|
11826
|
+
if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
|
|
11827
|
+
fs27.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
|
|
10016
11828
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
10017
11829
|
}
|
|
10018
11830
|
} catch (err) {
|
|
@@ -10023,8 +11835,8 @@ async function main() {
|
|
|
10023
11835
|
try {
|
|
10024
11836
|
const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
|
|
10025
11837
|
const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
|
|
10026
|
-
const
|
|
10027
|
-
const
|
|
11838
|
+
const fs27 = await import("fs");
|
|
11839
|
+
const path28 = await import("path");
|
|
10028
11840
|
const detection = detectAgent2();
|
|
10029
11841
|
if (!detection.primary) {
|
|
10030
11842
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -10032,22 +11844,22 @@ async function main() {
|
|
|
10032
11844
|
}
|
|
10033
11845
|
const agentType = detection.primary;
|
|
10034
11846
|
const skillPath = getSkillPath2(agentType);
|
|
10035
|
-
const fullPath =
|
|
10036
|
-
if (
|
|
11847
|
+
const fullPath = path28.resolve(process.cwd(), skillPath);
|
|
11848
|
+
if (fs27.existsSync(fullPath)) {
|
|
10037
11849
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
|
|
10038
11850
|
return;
|
|
10039
11851
|
}
|
|
10040
|
-
const dir =
|
|
10041
|
-
if (!
|
|
10042
|
-
|
|
11852
|
+
const dir = path28.dirname(fullPath);
|
|
11853
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
11854
|
+
fs27.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
|
|
10043
11855
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
|
|
10044
11856
|
const secondaryFiles = getSecondaryFiles2(agentType);
|
|
10045
11857
|
for (const sf of secondaryFiles) {
|
|
10046
|
-
const sfPath =
|
|
10047
|
-
const sfDir =
|
|
10048
|
-
if (!
|
|
10049
|
-
if (!
|
|
10050
|
-
|
|
11858
|
+
const sfPath = path28.resolve(process.cwd(), sf.path);
|
|
11859
|
+
const sfDir = path28.dirname(sfPath);
|
|
11860
|
+
if (!fs27.existsSync(sfPath)) {
|
|
11861
|
+
if (!fs27.existsSync(sfDir)) fs27.mkdirSync(sfDir, { recursive: true });
|
|
11862
|
+
fs27.writeFileSync(sfPath, sf.content, "utf-8");
|
|
10051
11863
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
10052
11864
|
}
|
|
10053
11865
|
}
|