@plumpslabs/kuma 2.4.3 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +10 -0
- package/INSTALL.md +265 -0
- package/README.md +32 -20
- package/agents/kuma-guardian.md +37 -0
- package/agents/kuma-researcher.md +36 -0
- package/commands/kuma:checkpoint.md +12 -0
- package/commands/kuma:guard.md +16 -0
- package/commands/kuma:impact.md +16 -0
- package/commands/kuma:init.md +16 -0
- package/commands/kuma:research.md +17 -0
- package/commands/kuma:rollback.md +12 -0
- package/commands/kuma:studio.md +17 -0
- package/commands/kuma:verify.md +15 -0
- package/dist/index.js +2510 -618
- package/hooks/kuma-agy-hooks.js +37 -0
- package/hooks.json +17 -0
- package/install.sh +129 -0
- package/mcp_config.json +11 -0
- package/package.json +13 -3
- package/packages/ide/studio/dist/index.js +25 -2
- package/packages/ide/studio/public/index.html +901 -1266
- package/plugin.json +60 -0
- package/skills/kuma/SKILL.md +28 -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, {
|
|
@@ -2514,7 +3297,8 @@ __export(kumaGraph_exports, {
|
|
|
2514
3297
|
});
|
|
2515
3298
|
function nodeId(type, name) {
|
|
2516
3299
|
if (type === "file") {
|
|
2517
|
-
|
|
3300
|
+
const normalized = normalizeScope(name) || name;
|
|
3301
|
+
return `file::${normalized}`;
|
|
2518
3302
|
}
|
|
2519
3303
|
return generateNodeId(type, name);
|
|
2520
3304
|
}
|
|
@@ -2575,7 +3359,9 @@ async function pruneGraphIfNeeded() {
|
|
|
2575
3359
|
async function upsertNode(node) {
|
|
2576
3360
|
try {
|
|
2577
3361
|
const db = await getDb();
|
|
2578
|
-
const
|
|
3362
|
+
const normName = node.type === "file" ? normalizeScope(node.name) || node.name : node.name;
|
|
3363
|
+
const normFilePath = node.filePath ? normalizeScope(node.filePath) || node.filePath : node.type === "file" ? normName : void 0;
|
|
3364
|
+
const id = node.id || nodeId(node.type, normName);
|
|
2579
3365
|
const metadata = JSON.stringify(node.metadata ?? {});
|
|
2580
3366
|
const severity = node.metadata?.severity || "medium";
|
|
2581
3367
|
const confidence = node.metadata?.confidence || 0.8;
|
|
@@ -2590,19 +3376,19 @@ async function upsertNode(node) {
|
|
|
2590
3376
|
severity = excluded.severity,
|
|
2591
3377
|
confidence = excluded.confidence,
|
|
2592
3378
|
updated_at = strftime('%s','now')
|
|
2593
|
-
`, [id, node.type,
|
|
3379
|
+
`, [id, node.type, normName, normFilePath || null, metadata, severity, confidence]);
|
|
2594
3380
|
try {
|
|
2595
|
-
db.run(`INSERT INTO nodes_fts (rowid, name, metadata) VALUES (last_insert_rowid(), ?, ?)`, [
|
|
3381
|
+
db.run(`INSERT INTO nodes_fts (rowid, name, metadata) VALUES (last_insert_rowid(), ?, ?)`, [normName, metadata]);
|
|
2596
3382
|
} catch {
|
|
2597
3383
|
}
|
|
2598
|
-
if (
|
|
2599
|
-
const fileNodeId = nodeId("file",
|
|
3384
|
+
if (normFilePath && node.type !== "file") {
|
|
3385
|
+
const fileNodeId = nodeId("file", normFilePath);
|
|
2600
3386
|
try {
|
|
2601
3387
|
db.run(`
|
|
2602
3388
|
INSERT INTO nodes (id, type, name, file_path, metadata, updated_at)
|
|
2603
3389
|
VALUES (?, 'file', ?, ?, '{}', strftime('%s','now'))
|
|
2604
3390
|
ON CONFLICT(id) DO NOTHING
|
|
2605
|
-
`, [fileNodeId,
|
|
3391
|
+
`, [fileNodeId, normFilePath, normFilePath]);
|
|
2606
3392
|
db.run(`
|
|
2607
3393
|
INSERT INTO edges (source_id, target_id, type, weight, metadata, created_at)
|
|
2608
3394
|
VALUES (?, ?, 'contains', 1.0, '{}', strftime('%s','now'))
|
|
@@ -3382,27 +4168,27 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
3382
4168
|
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
3383
4169
|
let entries = [];
|
|
3384
4170
|
try {
|
|
3385
|
-
entries =
|
|
4171
|
+
entries = fs27.readdirSync(dir);
|
|
3386
4172
|
} catch {
|
|
3387
4173
|
return;
|
|
3388
4174
|
}
|
|
3389
4175
|
for (const entry of entries) {
|
|
3390
4176
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
3391
|
-
const full =
|
|
4177
|
+
const full = path28.join(dir, entry);
|
|
3392
4178
|
try {
|
|
3393
|
-
const stat =
|
|
4179
|
+
const stat = fs27.statSync(full);
|
|
3394
4180
|
if (stat.isDirectory()) {
|
|
3395
4181
|
walk2(full, depth + 1);
|
|
3396
4182
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
3397
|
-
results.push(
|
|
4183
|
+
results.push(path28.relative(root, full));
|
|
3398
4184
|
}
|
|
3399
4185
|
} catch {
|
|
3400
4186
|
}
|
|
3401
4187
|
}
|
|
3402
4188
|
};
|
|
3403
4189
|
var walk = walk2;
|
|
3404
|
-
const
|
|
3405
|
-
const
|
|
4190
|
+
const fs27 = await import("fs");
|
|
4191
|
+
const path28 = await import("path");
|
|
3406
4192
|
const root = process.cwd();
|
|
3407
4193
|
const results = [];
|
|
3408
4194
|
const MAX_WALK_DEPTH = 8;
|
|
@@ -3473,71 +4259,43 @@ async function getGraphStats() {
|
|
|
3473
4259
|
}
|
|
3474
4260
|
async function analyzeImpact(target) {
|
|
3475
4261
|
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;
|
|
4262
|
+
const { calculateBlastRadius: calculateBlastRadius2 } = await Promise.resolve().then(() => (init_impactAnalysis(), impactAnalysis_exports));
|
|
4263
|
+
const blast = await calculateBlastRadius2(target);
|
|
4264
|
+
let graphEntryPoints = [];
|
|
3512
4265
|
try {
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3515
|
-
);
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
4266
|
+
const db = await getDb();
|
|
4267
|
+
const nodeStmt = db.prepare(`SELECT id FROM nodes WHERE name LIKE ? OR file_path = ? OR id = ? LIMIT 1`);
|
|
4268
|
+
nodeStmt.bind([`%${target}%`, target, target]);
|
|
4269
|
+
if (nodeStmt.step()) {
|
|
4270
|
+
const row = nodeStmt.getAsObject();
|
|
4271
|
+
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`);
|
|
4272
|
+
entryStmt.bind([row.id]);
|
|
4273
|
+
while (entryStmt.step()) {
|
|
4274
|
+
const erow = entryStmt.getAsObject();
|
|
4275
|
+
graphEntryPoints.push(`${erow.name} (${erow.cnt} refs)`);
|
|
4276
|
+
}
|
|
4277
|
+
entryStmt.free();
|
|
3519
4278
|
}
|
|
3520
|
-
|
|
4279
|
+
nodeStmt.free();
|
|
3521
4280
|
} catch {
|
|
3522
4281
|
}
|
|
3523
|
-
const
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
return { symbol: nodeName, references, files, testFiles, entryPoints, risk };
|
|
4282
|
+
const allEntryPoints = graphEntryPoints.length > 0 ? graphEntryPoints : blast.directDependents.slice(0, 5);
|
|
4283
|
+
return {
|
|
4284
|
+
symbol: target,
|
|
4285
|
+
references: blast.directDependents.length,
|
|
4286
|
+
files: blast.directDependents.length + (blast.downstreamPackages.length > 0 ? blast.downstreamPackages.length : 1),
|
|
4287
|
+
testFiles: blast.affectedTests.length,
|
|
4288
|
+
entryPoints: allEntryPoints,
|
|
4289
|
+
risk: blast.risk,
|
|
4290
|
+
packageName: blast.owningPackage?.name,
|
|
4291
|
+
downstreamPackages: blast.downstreamPackages,
|
|
4292
|
+
affectedTests: blast.affectedTests,
|
|
4293
|
+
riskFlags: blast.riskFlags,
|
|
4294
|
+
summary: blast.summary
|
|
4295
|
+
};
|
|
3538
4296
|
} catch (err) {
|
|
3539
4297
|
console.error(`[KumaGraph] Impact analysis failed: ${err}`);
|
|
3540
|
-
return
|
|
4298
|
+
return await codebaseImpactFallback(target);
|
|
3541
4299
|
}
|
|
3542
4300
|
}
|
|
3543
4301
|
async function codebaseImpactFallback(target) {
|
|
@@ -3569,19 +4327,22 @@ async function codebaseImpactFallback(target) {
|
|
|
3569
4327
|
}
|
|
3570
4328
|
}
|
|
3571
4329
|
function formatImpact(result) {
|
|
3572
|
-
|
|
4330
|
+
if (result.summary) return result.summary;
|
|
4331
|
+
const icon = result.risk === "critical" ? "\u{1F6A8}" : result.risk === "high" ? "\u{1F534}" : result.risk === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
3573
4332
|
const lines = [
|
|
3574
4333
|
`${icon} **Impact Analysis** \u2014 ${result.symbol}`,
|
|
3575
4334
|
`\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
4335
|
"",
|
|
3577
4336
|
`\u{1F4CA} ${result.references} reference(s) across ${result.files} file(s)`,
|
|
3578
4337
|
`\u{1F9EA} ${result.testFiles} test file(s)`,
|
|
4338
|
+
result.packageName ? `\u{1F4E6} Package: \`${result.packageName}\`` : "",
|
|
4339
|
+
result.downstreamPackages && result.downstreamPackages.length > 0 ? `\u{1F310} Downstream: ${result.downstreamPackages.join(", ")}` : "",
|
|
3579
4340
|
result.entryPoints.length > 0 ? `
|
|
3580
4341
|
\u{1F3AF} Entry Points:
|
|
3581
4342
|
${result.entryPoints.join("\n ")}` : "",
|
|
3582
4343
|
"",
|
|
3583
4344
|
`\u26A0\uFE0F Risk Level: **${result.risk.toUpperCase()}**`,
|
|
3584
|
-
result.risk === "high" ? "\u{1F4A1} High impact \u2014 review thoroughly before changing." : ""
|
|
4345
|
+
result.risk === "high" || result.risk === "critical" ? "\u{1F4A1} High impact \u2014 review thoroughly before changing." : ""
|
|
3585
4346
|
].filter(Boolean);
|
|
3586
4347
|
return lines.join("\n");
|
|
3587
4348
|
}
|
|
@@ -3653,6 +4414,7 @@ var init_kumaGraph = __esm({
|
|
|
3653
4414
|
init_kumaDb();
|
|
3654
4415
|
init_kumaSelfHeal();
|
|
3655
4416
|
init_languageSupport();
|
|
4417
|
+
init_pathValidator();
|
|
3656
4418
|
GRAPH_LIMITS = {
|
|
3657
4419
|
maxNodes: 5e3,
|
|
3658
4420
|
// Max nodes before pruning
|
|
@@ -3786,19 +4548,19 @@ __export(kumaMemory_exports, {
|
|
|
3786
4548
|
scoreMemoryRelevance: () => scoreMemoryRelevance,
|
|
3787
4549
|
shouldRecordDecision: () => shouldRecordDecision
|
|
3788
4550
|
});
|
|
3789
|
-
import
|
|
3790
|
-
import
|
|
4551
|
+
import fs8 from "fs";
|
|
4552
|
+
import path9 from "path";
|
|
3791
4553
|
function scoreMemoryRelevance(context, limit = 5) {
|
|
3792
4554
|
const results = [];
|
|
3793
|
-
const kumaDir =
|
|
3794
|
-
if (!
|
|
4555
|
+
const kumaDir = path9.join(getProjectRoot(), MEMORY_DIR);
|
|
4556
|
+
if (!fs8.existsSync(kumaDir)) return [];
|
|
3795
4557
|
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
3796
4558
|
if (terms.length === 0) return [];
|
|
3797
4559
|
try {
|
|
3798
|
-
const files =
|
|
4560
|
+
const files = fs8.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
3799
4561
|
for (const file of files) {
|
|
3800
4562
|
try {
|
|
3801
|
-
const content =
|
|
4563
|
+
const content = fs8.readFileSync(path9.join(kumaDir, file), "utf-8");
|
|
3802
4564
|
const lower = content.toLowerCase();
|
|
3803
4565
|
let matchCount = 0;
|
|
3804
4566
|
for (const term of terms) {
|
|
@@ -3961,18 +4723,18 @@ __export(domainRules_exports, {
|
|
|
3961
4723
|
readLayer: () => readLayer,
|
|
3962
4724
|
writeLayer: () => writeLayer
|
|
3963
4725
|
});
|
|
3964
|
-
import
|
|
3965
|
-
import
|
|
4726
|
+
import fs9 from "fs";
|
|
4727
|
+
import path10 from "path";
|
|
3966
4728
|
function layerFilePath(layer) {
|
|
3967
|
-
return
|
|
4729
|
+
return path10.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
|
|
3968
4730
|
}
|
|
3969
4731
|
function ensureLayerFile(layer) {
|
|
3970
4732
|
const fp = layerFilePath(layer);
|
|
3971
|
-
if (!
|
|
4733
|
+
if (!fs9.existsSync(fp)) {
|
|
3972
4734
|
const template = layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
|
|
3973
|
-
const dir =
|
|
3974
|
-
if (!
|
|
3975
|
-
|
|
4735
|
+
const dir = path10.dirname(fp);
|
|
4736
|
+
if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
|
|
4737
|
+
fs9.writeFileSync(fp, template, "utf-8");
|
|
3976
4738
|
console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
|
|
3977
4739
|
}
|
|
3978
4740
|
return fp;
|
|
@@ -3980,7 +4742,7 @@ function ensureLayerFile(layer) {
|
|
|
3980
4742
|
function readLayer(layer) {
|
|
3981
4743
|
try {
|
|
3982
4744
|
const fp = ensureLayerFile(layer);
|
|
3983
|
-
return
|
|
4745
|
+
return fs9.readFileSync(fp, "utf-8");
|
|
3984
4746
|
} catch (err) {
|
|
3985
4747
|
return `Error reading ${layer}: ${err}`;
|
|
3986
4748
|
}
|
|
@@ -3988,7 +4750,7 @@ function readLayer(layer) {
|
|
|
3988
4750
|
function writeLayer(layer, content) {
|
|
3989
4751
|
try {
|
|
3990
4752
|
const fp = ensureLayerFile(layer);
|
|
3991
|
-
|
|
4753
|
+
fs9.writeFileSync(fp, content, "utf-8");
|
|
3992
4754
|
const label = LAYER_FILES[layer].label;
|
|
3993
4755
|
sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
|
|
3994
4756
|
return `\u2705 **${label}** updated successfully.`;
|
|
@@ -3999,9 +4761,9 @@ function writeLayer(layer, content) {
|
|
|
3999
4761
|
function appendToLayer(layer, entry) {
|
|
4000
4762
|
try {
|
|
4001
4763
|
const fp = ensureLayerFile(layer);
|
|
4002
|
-
const existing =
|
|
4764
|
+
const existing = fs9.readFileSync(fp, "utf-8");
|
|
4003
4765
|
const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
|
|
4004
|
-
|
|
4766
|
+
fs9.writeFileSync(fp, newContent, "utf-8");
|
|
4005
4767
|
return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
|
|
4006
4768
|
} catch (err) {
|
|
4007
4769
|
return `\u274C Failed to append: ${err}`;
|
|
@@ -4011,29 +4773,33 @@ function getActiveGotchas() {
|
|
|
4011
4773
|
const results = [];
|
|
4012
4774
|
try {
|
|
4013
4775
|
const fp = ensureLayerFile("gotcha");
|
|
4014
|
-
const content =
|
|
4776
|
+
const content = fs9.readFileSync(fp, "utf-8");
|
|
4015
4777
|
const lines = content.split("\n");
|
|
4016
4778
|
let currentSection = "";
|
|
4017
4779
|
let currentFilePath = "";
|
|
4018
4780
|
let currentDesc = "";
|
|
4019
4781
|
let currentSeverity = "";
|
|
4782
|
+
let currentStatus = "active";
|
|
4020
4783
|
for (const line of lines) {
|
|
4021
4784
|
const fileMatch = line.match(/^###\s+(.+?)\s*[—–-]+\s*(.+)/);
|
|
4022
4785
|
if (fileMatch) {
|
|
4023
|
-
if (currentFilePath) {
|
|
4786
|
+
if (currentFilePath && currentStatus !== "resolved" && currentStatus !== "deprecated") {
|
|
4024
4787
|
results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
|
|
4025
4788
|
}
|
|
4026
4789
|
currentFilePath = fileMatch[1].trim();
|
|
4027
4790
|
currentDesc = fileMatch[2].trim();
|
|
4028
4791
|
currentSeverity = "medium";
|
|
4792
|
+
currentStatus = "active";
|
|
4029
4793
|
currentSection = line + "\n";
|
|
4030
4794
|
continue;
|
|
4031
4795
|
}
|
|
4032
4796
|
const sevMatch = line.match(/- \*\*Severity\*\*:\s*(\w+)/);
|
|
4033
4797
|
if (sevMatch) currentSeverity = sevMatch[1].toLowerCase();
|
|
4798
|
+
const statusMatch = line.match(/- \*\*Status\*\*:\s*(\w+)/);
|
|
4799
|
+
if (statusMatch) currentStatus = statusMatch[1].toLowerCase();
|
|
4034
4800
|
currentSection += line + "\n";
|
|
4035
4801
|
}
|
|
4036
|
-
if (currentFilePath) {
|
|
4802
|
+
if (currentFilePath && currentStatus !== "resolved" && currentStatus !== "deprecated") {
|
|
4037
4803
|
results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
|
|
4038
4804
|
}
|
|
4039
4805
|
} catch {
|
|
@@ -4143,8 +4909,8 @@ __export(kumaInject_exports, {
|
|
|
4143
4909
|
parseHookInput: () => parseHookInput,
|
|
4144
4910
|
trackFileEditLoop: () => trackFileEditLoop
|
|
4145
4911
|
});
|
|
4146
|
-
import
|
|
4147
|
-
import
|
|
4912
|
+
import fs10 from "fs";
|
|
4913
|
+
import path11 from "path";
|
|
4148
4914
|
function hashFileContent(filePath) {
|
|
4149
4915
|
return hashFile(filePath);
|
|
4150
4916
|
}
|
|
@@ -4156,14 +4922,14 @@ function isGotchaFresh(storedHash, currentHash) {
|
|
|
4156
4922
|
async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
4157
4923
|
const root = getProjectRoot();
|
|
4158
4924
|
try {
|
|
4159
|
-
const dbFile =
|
|
4160
|
-
if (
|
|
4925
|
+
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
4926
|
+
if (fs10.existsSync(dbFile)) {
|
|
4161
4927
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4162
4928
|
const db = await getDb2();
|
|
4163
4929
|
const stmt = db.prepare(`
|
|
4164
4930
|
SELECT id, file_path, description, severity, workaround, content_hash
|
|
4165
4931
|
FROM known_gotchas
|
|
4166
|
-
WHERE status
|
|
4932
|
+
WHERE status IN ('active', 'verified')
|
|
4167
4933
|
AND (file_path = ? OR ? LIKE ('%' || file_path || '%'))
|
|
4168
4934
|
ORDER BY
|
|
4169
4935
|
CASE WHEN file_path = ? THEN 0 ELSE 1 END, -- exact match first
|
|
@@ -4188,7 +4954,7 @@ async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
|
4188
4954
|
} catch {
|
|
4189
4955
|
}
|
|
4190
4956
|
try {
|
|
4191
|
-
if (!
|
|
4957
|
+
if (!fs10.existsSync(path11.join(root, ".kuma"))) return [];
|
|
4192
4958
|
const { checkFileGotchas: checkFileGotchas2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
4193
4959
|
const warnings = checkFileGotchas2(filePath);
|
|
4194
4960
|
if (warnings.length === 0) return [];
|
|
@@ -4206,8 +4972,8 @@ async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
|
4206
4972
|
async function getFileTrace(filePath, limit = 8) {
|
|
4207
4973
|
try {
|
|
4208
4974
|
const root = getProjectRoot();
|
|
4209
|
-
const dbFile =
|
|
4210
|
-
if (!
|
|
4975
|
+
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
4976
|
+
if (!fs10.existsSync(dbFile)) return [];
|
|
4211
4977
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4212
4978
|
const db = await getDb2();
|
|
4213
4979
|
const stmt = db.prepare(`
|
|
@@ -4217,7 +4983,7 @@ async function getFileTrace(filePath, limit = 8) {
|
|
|
4217
4983
|
ORDER BY cl.created_at DESC
|
|
4218
4984
|
LIMIT ?
|
|
4219
4985
|
`);
|
|
4220
|
-
stmt.bind([`%${
|
|
4986
|
+
stmt.bind([`%${path11.basename(filePath)}%`, limit]);
|
|
4221
4987
|
const rows = [];
|
|
4222
4988
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
4223
4989
|
stmt.free();
|
|
@@ -4235,7 +5001,7 @@ async function getFileTrace(filePath, limit = 8) {
|
|
|
4235
5001
|
function formatFileTrace(entries, filePath) {
|
|
4236
5002
|
if (entries.length === 0) return "";
|
|
4237
5003
|
const lines = [
|
|
4238
|
-
`\u{1F570}\uFE0F **Why is ${
|
|
5004
|
+
`\u{1F570}\uFE0F **Why is ${path11.basename(filePath)} written this way?** \u2014 ${entries.length} change(s) recorded:`
|
|
4239
5005
|
];
|
|
4240
5006
|
for (const e of entries) {
|
|
4241
5007
|
const date = new Date((e.createdAt || 0) * 1e3).toLocaleDateString("en-US", {
|
|
@@ -4251,7 +5017,7 @@ function formatFileTrace(entries, filePath) {
|
|
|
4251
5017
|
async function getDecisionsForFile(filePath, limit = 3) {
|
|
4252
5018
|
try {
|
|
4253
5019
|
const { scoreMemoryRelevance: scoreMemoryRelevance2 } = await Promise.resolve().then(() => (init_kumaMemory(), kumaMemory_exports));
|
|
4254
|
-
const base =
|
|
5020
|
+
const base = path11.basename(filePath);
|
|
4255
5021
|
const scored = scoreMemoryRelevance2(`${filePath} ${base}`, limit);
|
|
4256
5022
|
return scored.map((m) => `\u{1F4CC} ${m.topic} \u2014 ${m.score}% match (${m.reason})`);
|
|
4257
5023
|
} catch {
|
|
@@ -4279,13 +5045,13 @@ async function getRelevantContext(filePath, goal) {
|
|
|
4279
5045
|
if (traceStr) parts.push(traceStr);
|
|
4280
5046
|
try {
|
|
4281
5047
|
const root = getProjectRoot();
|
|
4282
|
-
if (
|
|
5048
|
+
if (fs10.existsSync(path11.join(root, ".kuma", "kuma.db"))) {
|
|
4283
5049
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4284
5050
|
const db = await getDb2();
|
|
4285
5051
|
const stmt = db.prepare(
|
|
4286
5052
|
`SELECT COUNT(*) FROM known_gotchas WHERE status = 'resolved' AND file_path LIKE ?`
|
|
4287
5053
|
);
|
|
4288
|
-
stmt.bind([`%${
|
|
5054
|
+
stmt.bind([`%${path11.basename(filePath)}%`]);
|
|
4289
5055
|
const found = stmt.step();
|
|
4290
5056
|
const count = found ? Number(stmt.getAsObject()["COUNT(*)"]) : 0;
|
|
4291
5057
|
stmt.free();
|
|
@@ -4296,7 +5062,7 @@ async function getRelevantContext(filePath, goal) {
|
|
|
4296
5062
|
} catch {
|
|
4297
5063
|
}
|
|
4298
5064
|
if (gotchas.length > 0) {
|
|
4299
|
-
const scopeHint =
|
|
5065
|
+
const scopeHint = path11.basename(path11.dirname(filePath)) || "current change";
|
|
4300
5066
|
parts.push(
|
|
4301
5067
|
`\u2705 AFTER editing, verify: kuma_safety({ action: "verify", scope: "${scopeHint}" })`
|
|
4302
5068
|
);
|
|
@@ -4312,11 +5078,11 @@ async function getRelevantContext(filePath, goal) {
|
|
|
4312
5078
|
async function getFileSummary2(filePath) {
|
|
4313
5079
|
try {
|
|
4314
5080
|
const root = getProjectRoot();
|
|
4315
|
-
const dbFile =
|
|
4316
|
-
if (!
|
|
5081
|
+
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
5082
|
+
if (!fs10.existsSync(dbFile)) return null;
|
|
4317
5083
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4318
5084
|
const db = await getDb2();
|
|
4319
|
-
const base =
|
|
5085
|
+
const base = path11.basename(filePath);
|
|
4320
5086
|
const stmt = db.prepare(`
|
|
4321
5087
|
SELECT
|
|
4322
5088
|
(SELECT COUNT(*) FROM known_gotchas WHERE status = 'active' AND (file_path = ? OR file_path LIKE ?)) as active_gotchas,
|
|
@@ -4394,9 +5160,9 @@ async function getCommandContext(command) {
|
|
|
4394
5160
|
}
|
|
4395
5161
|
function loadHookState(root) {
|
|
4396
5162
|
try {
|
|
4397
|
-
const fp =
|
|
4398
|
-
if (
|
|
4399
|
-
return JSON.parse(
|
|
5163
|
+
const fp = path11.join(root, HOOK_STATE_FILE);
|
|
5164
|
+
if (fs10.existsSync(fp)) {
|
|
5165
|
+
return JSON.parse(fs10.readFileSync(fp, "utf-8"));
|
|
4400
5166
|
}
|
|
4401
5167
|
} catch {
|
|
4402
5168
|
}
|
|
@@ -4404,10 +5170,10 @@ function loadHookState(root) {
|
|
|
4404
5170
|
}
|
|
4405
5171
|
function saveHookState(root, state) {
|
|
4406
5172
|
try {
|
|
4407
|
-
const fp =
|
|
4408
|
-
const dir =
|
|
4409
|
-
if (!
|
|
4410
|
-
|
|
5173
|
+
const fp = path11.join(root, HOOK_STATE_FILE);
|
|
5174
|
+
const dir = path11.dirname(fp);
|
|
5175
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
5176
|
+
fs10.writeFileSync(fp, JSON.stringify(state), "utf-8");
|
|
4411
5177
|
} catch {
|
|
4412
5178
|
}
|
|
4413
5179
|
}
|
|
@@ -4441,7 +5207,7 @@ async function trackFileEditLoop(filePath) {
|
|
|
4441
5207
|
saveHookState(root, state);
|
|
4442
5208
|
if (recorded) {
|
|
4443
5209
|
try {
|
|
4444
|
-
if (!
|
|
5210
|
+
if (!fs10.existsSync(path11.join(root, ".kuma", "kuma.db"))) return entry.count;
|
|
4445
5211
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
4446
5212
|
await addGotcha2({
|
|
4447
5213
|
filePath,
|
|
@@ -4478,16 +5244,20 @@ var kumaGotchas_exports = {};
|
|
|
4478
5244
|
__export(kumaGotchas_exports, {
|
|
4479
5245
|
addGotcha: () => addGotcha,
|
|
4480
5246
|
checkGotchasForFile: () => checkGotchasForFile,
|
|
5247
|
+
deprecateGotcha: () => deprecateGotcha,
|
|
4481
5248
|
getActiveGotchasForCommand: () => getActiveGotchasForCommand,
|
|
5249
|
+
getGotchasByStatus: () => getGotchasByStatus,
|
|
4482
5250
|
getInjectionStats: () => getInjectionStats,
|
|
4483
5251
|
listGotchas: () => listGotchas,
|
|
4484
5252
|
recordInjection: () => recordInjection,
|
|
5253
|
+
resolveGotcha: () => resolveGotcha,
|
|
4485
5254
|
resolveGotchasForScope: () => resolveGotchasForScope,
|
|
5255
|
+
setGotchaStatus: () => setGotchaStatus,
|
|
4486
5256
|
syncGotchasGraph: () => syncGotchasGraph,
|
|
4487
5257
|
syncGotchasToDb: () => syncGotchasToDb
|
|
4488
5258
|
});
|
|
4489
|
-
import
|
|
4490
|
-
import
|
|
5259
|
+
import fs11 from "fs";
|
|
5260
|
+
import path12 from "path";
|
|
4491
5261
|
async function ensureGotchasSchema() {
|
|
4492
5262
|
const db = await getDb();
|
|
4493
5263
|
db.exec(`
|
|
@@ -4504,8 +5274,10 @@ async function ensureGotchasSchema() {
|
|
|
4504
5274
|
content_hash TEXT,
|
|
4505
5275
|
trigger_command TEXT,
|
|
4506
5276
|
status TEXT NOT NULL DEFAULT 'active'
|
|
4507
|
-
CHECK(status IN ('active','resolved')),
|
|
4508
|
-
last_verified_at INTEGER
|
|
5277
|
+
CHECK(status IN ('candidate','active','verified','resolved','deprecated')),
|
|
5278
|
+
last_verified_at INTEGER,
|
|
5279
|
+
scope_package TEXT,
|
|
5280
|
+
verified_by TEXT
|
|
4509
5281
|
);
|
|
4510
5282
|
CREATE INDEX IF NOT EXISTS idx_gotchas_file ON known_gotchas(file_path);
|
|
4511
5283
|
CREATE INDEX IF NOT EXISTS idx_gotchas_severity ON known_gotchas(severity);
|
|
@@ -4525,6 +5297,37 @@ async function ensureGotchasSchema() {
|
|
|
4525
5297
|
if (!cols.includes("last_verified_at")) {
|
|
4526
5298
|
db.run(`ALTER TABLE known_gotchas ADD COLUMN last_verified_at INTEGER`);
|
|
4527
5299
|
}
|
|
5300
|
+
if (!cols.includes("scope_package")) {
|
|
5301
|
+
db.run(`ALTER TABLE known_gotchas ADD COLUMN scope_package TEXT`);
|
|
5302
|
+
}
|
|
5303
|
+
if (!cols.includes("verified_by")) {
|
|
5304
|
+
db.run(`ALTER TABLE known_gotchas ADD COLUMN verified_by TEXT`);
|
|
5305
|
+
}
|
|
5306
|
+
const schema = db.exec(`SELECT sql FROM sqlite_master WHERE type='table' AND name='known_gotchas'`);
|
|
5307
|
+
const gotchaSql = schema[0]?.values?.[0]?.[0] || "";
|
|
5308
|
+
if (gotchaSql && !gotchaSql.includes("candidate")) {
|
|
5309
|
+
db.run(`ALTER TABLE known_gotchas RENAME TO known_gotchas_old`);
|
|
5310
|
+
db.run(`CREATE TABLE IF NOT EXISTS known_gotchas (
|
|
5311
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5312
|
+
file_path TEXT NOT NULL,
|
|
5313
|
+
description TEXT NOT NULL,
|
|
5314
|
+
severity TEXT NOT NULL DEFAULT 'medium'
|
|
5315
|
+
CHECK(severity IN ('low','medium','high','critical')),
|
|
5316
|
+
workaround TEXT,
|
|
5317
|
+
added_by TEXT DEFAULT 'agent',
|
|
5318
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
5319
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
5320
|
+
content_hash TEXT,
|
|
5321
|
+
trigger_command TEXT,
|
|
5322
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
5323
|
+
CHECK(status IN ('candidate','active','verified','resolved','deprecated')),
|
|
5324
|
+
last_verified_at INTEGER,
|
|
5325
|
+
scope_package TEXT,
|
|
5326
|
+
verified_by TEXT
|
|
5327
|
+
)`);
|
|
5328
|
+
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`);
|
|
5329
|
+
db.run(`DROP TABLE known_gotchas_old`);
|
|
5330
|
+
}
|
|
4528
5331
|
} catch {
|
|
4529
5332
|
}
|
|
4530
5333
|
saveDb();
|
|
@@ -4534,6 +5337,7 @@ async function addGotcha(entry) {
|
|
|
4534
5337
|
await ensureGotchasSchema();
|
|
4535
5338
|
const db = await getDb();
|
|
4536
5339
|
const severity = entry.severity || "medium";
|
|
5340
|
+
const status = entry.status || "active";
|
|
4537
5341
|
const contentHash = hashFileContent(entry.filePath);
|
|
4538
5342
|
const formatted = [
|
|
4539
5343
|
`### ${entry.filePath} \u2014 ${entry.description}`,
|
|
@@ -4541,6 +5345,8 @@ async function addGotcha(entry) {
|
|
|
4541
5345
|
`- **Severity**: ${severity}`,
|
|
4542
5346
|
entry.workaround ? `- **Workaround**: ${entry.workaround}` : "",
|
|
4543
5347
|
entry.triggerCommand ? `- **Trigger**: when running \`${entry.triggerCommand}\`` : "",
|
|
5348
|
+
entry.status ? `- **Status**: ${entry.status}` : "",
|
|
5349
|
+
entry.scopePackage ? `- **Scope Package**: ${entry.scopePackage}` : "",
|
|
4544
5350
|
`- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
4545
5351
|
].filter(Boolean).join("\n");
|
|
4546
5352
|
const checkStmt = db.prepare(
|
|
@@ -4556,13 +5362,13 @@ async function addGotcha(entry) {
|
|
|
4556
5362
|
checkStmt.free();
|
|
4557
5363
|
if (existingId !== null) {
|
|
4558
5364
|
db.run(
|
|
4559
|
-
`UPDATE known_gotchas SET severity = ?, workaround = ?, content_hash = ?, trigger_command = ?, status =
|
|
4560
|
-
[severity, entry.workaround || null, contentHash, entry.triggerCommand || null, existingId]
|
|
5365
|
+
`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 = ?`,
|
|
5366
|
+
[severity, entry.workaround || null, contentHash, entry.triggerCommand || null, status, entry.scopePackage || null, entry.verifiedBy || null, existingId]
|
|
4561
5367
|
);
|
|
4562
5368
|
} else {
|
|
4563
5369
|
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]
|
|
5370
|
+
`INSERT INTO known_gotchas (file_path, description, severity, workaround, content_hash, trigger_command, status, scope_package, verified_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5371
|
+
[entry.filePath, entry.description, severity, entry.workaround || null, contentHash, entry.triggerCommand || null, status, entry.scopePackage || null, entry.verifiedBy || null]
|
|
4566
5372
|
);
|
|
4567
5373
|
}
|
|
4568
5374
|
const mdResult = appendToLayer("gotcha", formatted);
|
|
@@ -4614,7 +5420,12 @@ async function listGotchas(params) {
|
|
|
4614
5420
|
sql += " AND severity = ?";
|
|
4615
5421
|
bind.push(params.severity);
|
|
4616
5422
|
}
|
|
4617
|
-
|
|
5423
|
+
if (params.status && params.status !== "all") {
|
|
5424
|
+
sql += " AND status = ?";
|
|
5425
|
+
bind.push(params.status);
|
|
5426
|
+
} else if (!params.status) {
|
|
5427
|
+
sql += " AND status IN ('active', 'verified')";
|
|
5428
|
+
}
|
|
4618
5429
|
sql += " ORDER BY severity DESC, created_at DESC LIMIT 50";
|
|
4619
5430
|
const stmt = db.prepare(sql);
|
|
4620
5431
|
stmt.bind(bind);
|
|
@@ -4692,7 +5503,7 @@ async function syncGotchasGraph() {
|
|
|
4692
5503
|
const db = await getDb();
|
|
4693
5504
|
let created = 0;
|
|
4694
5505
|
const stmt = db.prepare(
|
|
4695
|
-
`SELECT file_path, description, severity, workaround FROM known_gotchas`
|
|
5506
|
+
`SELECT file_path, description, severity, workaround FROM known_gotchas WHERE status IN ('active', 'verified')`
|
|
4696
5507
|
);
|
|
4697
5508
|
const rows = [];
|
|
4698
5509
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
@@ -4752,7 +5563,7 @@ async function getActiveGotchasForCommand(command) {
|
|
|
4752
5563
|
const stmt = db.prepare(
|
|
4753
5564
|
`SELECT file_path, description, severity, workaround, trigger_command
|
|
4754
5565
|
FROM known_gotchas
|
|
4755
|
-
WHERE status
|
|
5566
|
+
WHERE status IN ('active', 'verified') AND trigger_command IS NOT NULL AND length(trigger_command) > 0
|
|
4756
5567
|
ORDER BY
|
|
4757
5568
|
CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END
|
|
4758
5569
|
LIMIT 30`
|
|
@@ -4777,8 +5588,8 @@ async function getActiveGotchasForCommand(command) {
|
|
|
4777
5588
|
}
|
|
4778
5589
|
async function recordInjection(params) {
|
|
4779
5590
|
try {
|
|
4780
|
-
const fp =
|
|
4781
|
-
if (!
|
|
5591
|
+
const fp = path12.join(getProjectRoot(), ".kuma", "injections.jsonl");
|
|
5592
|
+
if (!fs11.existsSync(path12.dirname(fp))) fs11.mkdirSync(path12.dirname(fp), { recursive: true });
|
|
4782
5593
|
const line = JSON.stringify({
|
|
4783
5594
|
filePath: params.filePath || null,
|
|
4784
5595
|
command: params.command || null,
|
|
@@ -4787,16 +5598,16 @@ async function recordInjection(params) {
|
|
|
4787
5598
|
// conservative estimate: avoided re-research / re-discovery
|
|
4788
5599
|
at: Date.now()
|
|
4789
5600
|
});
|
|
4790
|
-
|
|
5601
|
+
fs11.appendFileSync(fp, line + "\n", "utf-8");
|
|
4791
5602
|
} catch {
|
|
4792
5603
|
}
|
|
4793
5604
|
}
|
|
4794
5605
|
function getInjectionStats(hours = 24) {
|
|
4795
5606
|
try {
|
|
4796
|
-
const fp =
|
|
4797
|
-
if (!
|
|
5607
|
+
const fp = path12.join(getProjectRoot(), ".kuma", "injections.jsonl");
|
|
5608
|
+
if (!fs11.existsSync(fp)) return { count: 0, savedMs: 0 };
|
|
4798
5609
|
const cutoff = Date.now() - hours * 60 * 60 * 1e3;
|
|
4799
|
-
const lines =
|
|
5610
|
+
const lines = fs11.readFileSync(fp, "utf-8").split("\n");
|
|
4800
5611
|
let count = 0;
|
|
4801
5612
|
let savedMs = 0;
|
|
4802
5613
|
for (const line of lines) {
|
|
@@ -4815,6 +5626,104 @@ function getInjectionStats(hours = 24) {
|
|
|
4815
5626
|
return { count: 0, savedMs: 0 };
|
|
4816
5627
|
}
|
|
4817
5628
|
}
|
|
5629
|
+
async function setGotchaStatus(id, status, verifiedBy) {
|
|
5630
|
+
try {
|
|
5631
|
+
const db = await getDb();
|
|
5632
|
+
db.run(
|
|
5633
|
+
`UPDATE known_gotchas SET status = ?, verified_by = coalesce(?, verified_by), last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ?`,
|
|
5634
|
+
[status, verifiedBy || null, id]
|
|
5635
|
+
);
|
|
5636
|
+
saveDb();
|
|
5637
|
+
return true;
|
|
5638
|
+
} catch {
|
|
5639
|
+
return false;
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
async function getGotchasByStatus(status, limit = 20) {
|
|
5643
|
+
try {
|
|
5644
|
+
const db = await getDb();
|
|
5645
|
+
const stmt = db.prepare(
|
|
5646
|
+
`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 ?`
|
|
5647
|
+
);
|
|
5648
|
+
stmt.bind([status, limit]);
|
|
5649
|
+
const rows = [];
|
|
5650
|
+
while (stmt.step()) {
|
|
5651
|
+
rows.push(stmt.getAsObject());
|
|
5652
|
+
}
|
|
5653
|
+
stmt.free();
|
|
5654
|
+
return rows;
|
|
5655
|
+
} catch {
|
|
5656
|
+
return [];
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent") {
|
|
5660
|
+
try {
|
|
5661
|
+
await ensureGotchasSchema();
|
|
5662
|
+
const db = await getDb();
|
|
5663
|
+
let resolved = 0;
|
|
5664
|
+
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5665
|
+
if (isId) {
|
|
5666
|
+
const id = Number(idOrScope);
|
|
5667
|
+
db.run(
|
|
5668
|
+
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'resolved'`,
|
|
5669
|
+
[verifiedBy, id]
|
|
5670
|
+
);
|
|
5671
|
+
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5672
|
+
} else {
|
|
5673
|
+
const scope = String(idOrScope).trim();
|
|
5674
|
+
db.run(
|
|
5675
|
+
`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'`,
|
|
5676
|
+
[verifiedBy, scope, `%${scope}%`]
|
|
5677
|
+
);
|
|
5678
|
+
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5679
|
+
}
|
|
5680
|
+
if (resolved > 0) {
|
|
5681
|
+
saveDb();
|
|
5682
|
+
rebuildFtsIndex();
|
|
5683
|
+
return {
|
|
5684
|
+
resolved,
|
|
5685
|
+
message: `\u2705 Resolved ${resolved} gotcha(s) for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5686
|
+
};
|
|
5687
|
+
}
|
|
5688
|
+
return { resolved: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5689
|
+
} catch (err) {
|
|
5690
|
+
return { resolved: 0, message: `\u274C Failed to resolve gotcha: ${err}` };
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
async function deprecateGotcha(idOrScope, reason) {
|
|
5694
|
+
try {
|
|
5695
|
+
await ensureGotchasSchema();
|
|
5696
|
+
const db = await getDb();
|
|
5697
|
+
let deprecated = 0;
|
|
5698
|
+
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5699
|
+
if (isId) {
|
|
5700
|
+
const id = Number(idOrScope);
|
|
5701
|
+
db.run(
|
|
5702
|
+
`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'`,
|
|
5703
|
+
[id]
|
|
5704
|
+
);
|
|
5705
|
+
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5706
|
+
} else {
|
|
5707
|
+
const scope = String(idOrScope).trim();
|
|
5708
|
+
db.run(
|
|
5709
|
+
`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'`,
|
|
5710
|
+
[scope, `%${scope}%`]
|
|
5711
|
+
);
|
|
5712
|
+
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5713
|
+
}
|
|
5714
|
+
if (deprecated > 0) {
|
|
5715
|
+
saveDb();
|
|
5716
|
+
rebuildFtsIndex();
|
|
5717
|
+
return {
|
|
5718
|
+
deprecated,
|
|
5719
|
+
message: `\u{1F9F9} Deprecated ${deprecated} gotcha(s) for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5720
|
+
};
|
|
5721
|
+
}
|
|
5722
|
+
return { deprecated: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5723
|
+
} catch (err) {
|
|
5724
|
+
return { deprecated: 0, message: `\u274C Failed to deprecate gotcha: ${err}` };
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
4818
5727
|
var init_kumaGotchas = __esm({
|
|
4819
5728
|
"src/engine/kumaGotchas.ts"() {
|
|
4820
5729
|
"use strict";
|
|
@@ -4831,29 +5740,34 @@ var kumaMaintenance_exports = {};
|
|
|
4831
5740
|
__export(kumaMaintenance_exports, {
|
|
4832
5741
|
maybeRunAutoCleanup: () => maybeRunAutoCleanup
|
|
4833
5742
|
});
|
|
4834
|
-
import
|
|
4835
|
-
import
|
|
5743
|
+
import fs12 from "fs";
|
|
5744
|
+
import path13 from "path";
|
|
4836
5745
|
async function maybeRunAutoCleanup() {
|
|
4837
5746
|
try {
|
|
4838
5747
|
const root = getProjectRoot();
|
|
4839
|
-
const stateFile =
|
|
5748
|
+
const stateFile = path13.join(root, GC_STATE_FILE);
|
|
4840
5749
|
const now = Date.now();
|
|
4841
5750
|
let lastRun = 0;
|
|
4842
|
-
if (
|
|
5751
|
+
if (fs12.existsSync(stateFile)) {
|
|
4843
5752
|
try {
|
|
4844
|
-
lastRun = JSON.parse(
|
|
5753
|
+
lastRun = JSON.parse(fs12.readFileSync(stateFile, "utf-8")).lastRun || 0;
|
|
4845
5754
|
} catch {
|
|
4846
5755
|
lastRun = 0;
|
|
4847
5756
|
}
|
|
4848
5757
|
}
|
|
4849
5758
|
if (now - lastRun < GC_INTERVAL_MS) return "";
|
|
4850
|
-
|
|
4851
|
-
|
|
5759
|
+
fs12.mkdirSync(path13.dirname(stateFile), { recursive: true });
|
|
5760
|
+
fs12.writeFileSync(stateFile, JSON.stringify({ lastRun: now }), "utf-8");
|
|
4852
5761
|
const { syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
4853
5762
|
await syncGotchasToDb2();
|
|
4854
5763
|
const { runGarbageCollection: runGarbageCollection2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
4855
5764
|
const gcResult = await runGarbageCollection2();
|
|
4856
|
-
const
|
|
5765
|
+
const { autoDeprecateStaleGotchas: autoDeprecateStaleGotchas2 } = await Promise.resolve().then(() => (init_kumaSelfHeal(), kumaSelfHeal_exports));
|
|
5766
|
+
const deprecateRes = await autoDeprecateStaleGotchas2();
|
|
5767
|
+
let summary = gcResult.replace(/\n/g, " ").substring(0, 80);
|
|
5768
|
+
if (deprecateRes.deprecated > 0) {
|
|
5769
|
+
summary += ` (${deprecateRes.deprecated} gotcha(s) auto-deprecated)`;
|
|
5770
|
+
}
|
|
4857
5771
|
return `\u{1F9F9} ${summary}`;
|
|
4858
5772
|
} catch {
|
|
4859
5773
|
return "";
|
|
@@ -4879,22 +5793,22 @@ __export(kumaPathRules_exports, {
|
|
|
4879
5793
|
loadPathRules: () => loadPathRules,
|
|
4880
5794
|
matchRulePath: () => matchRulePath
|
|
4881
5795
|
});
|
|
4882
|
-
import
|
|
4883
|
-
import
|
|
5796
|
+
import fs13 from "fs";
|
|
5797
|
+
import path14 from "path";
|
|
4884
5798
|
function rulesDir() {
|
|
4885
|
-
return
|
|
5799
|
+
return path14.join(getProjectRoot(), RULES_DIR);
|
|
4886
5800
|
}
|
|
4887
5801
|
function ensureRulesDir() {
|
|
4888
5802
|
try {
|
|
4889
5803
|
const dir = rulesDir();
|
|
4890
|
-
if (!
|
|
5804
|
+
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
4891
5805
|
} catch {
|
|
4892
5806
|
}
|
|
4893
5807
|
}
|
|
4894
5808
|
function parseRuleFile(filePath) {
|
|
4895
5809
|
try {
|
|
4896
|
-
const raw =
|
|
4897
|
-
const id =
|
|
5810
|
+
const raw = fs13.readFileSync(filePath, "utf-8");
|
|
5811
|
+
const id = path14.basename(filePath, ".md");
|
|
4898
5812
|
const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
4899
5813
|
if (!fm) {
|
|
4900
5814
|
return { id, description: id, paths: ["*"], content: raw.trim(), filePath };
|
|
@@ -4919,7 +5833,7 @@ function loadPathRules() {
|
|
|
4919
5833
|
ensureRulesDir();
|
|
4920
5834
|
try {
|
|
4921
5835
|
const dir = rulesDir();
|
|
4922
|
-
return
|
|
5836
|
+
return fs13.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => parseRuleFile(path14.join(dir, f))).filter((r) => r !== null);
|
|
4923
5837
|
} catch {
|
|
4924
5838
|
return [];
|
|
4925
5839
|
}
|
|
@@ -5006,12 +5920,12 @@ __export(kumaCodeScanner_exports, {
|
|
|
5006
5920
|
scanAndFormat: () => scanAndFormat,
|
|
5007
5921
|
scanCodebase: () => scanCodebase
|
|
5008
5922
|
});
|
|
5009
|
-
import
|
|
5010
|
-
import
|
|
5011
|
-
import
|
|
5923
|
+
import fastGlob3 from "fast-glob";
|
|
5924
|
+
import fs14 from "fs";
|
|
5925
|
+
import path15 from "path";
|
|
5012
5926
|
function getRoot() {
|
|
5013
|
-
if (!
|
|
5014
|
-
return
|
|
5927
|
+
if (!_cachedRoot2) _cachedRoot2 = getProjectRoot();
|
|
5928
|
+
return _cachedRoot2;
|
|
5015
5929
|
}
|
|
5016
5930
|
function lineAt(content, index) {
|
|
5017
5931
|
return content.substring(0, index).split("\n").length;
|
|
@@ -5019,10 +5933,10 @@ function lineAt(content, index) {
|
|
|
5019
5933
|
function getDirectoryDirs(filePaths) {
|
|
5020
5934
|
const dirs = /* @__PURE__ */ new Set();
|
|
5021
5935
|
for (const fp of filePaths) {
|
|
5022
|
-
let dir =
|
|
5936
|
+
let dir = path15.dirname(fp);
|
|
5023
5937
|
while (dir && dir !== "." && dir !== "/") {
|
|
5024
5938
|
dirs.add(dir);
|
|
5025
|
-
dir =
|
|
5939
|
+
dir = path15.dirname(dir);
|
|
5026
5940
|
}
|
|
5027
5941
|
}
|
|
5028
5942
|
return [...dirs].sort();
|
|
@@ -5053,7 +5967,7 @@ async function scanCodebase(options = {}) {
|
|
|
5053
5967
|
];
|
|
5054
5968
|
let files;
|
|
5055
5969
|
try {
|
|
5056
|
-
files = await
|
|
5970
|
+
files = await fastGlob3(includePatterns, {
|
|
5057
5971
|
cwd: root,
|
|
5058
5972
|
ignore: ignorePatterns,
|
|
5059
5973
|
onlyFiles: true,
|
|
@@ -5072,16 +5986,16 @@ async function scanCodebase(options = {}) {
|
|
|
5072
5986
|
const scannedFilePaths = [];
|
|
5073
5987
|
for (let i = 0; i < scannedCount; i++) {
|
|
5074
5988
|
const filePath = files[i];
|
|
5075
|
-
const fullPath =
|
|
5076
|
-
if (!
|
|
5077
|
-
const stat =
|
|
5989
|
+
const fullPath = path15.join(root, filePath);
|
|
5990
|
+
if (!fs14.existsSync(fullPath)) continue;
|
|
5991
|
+
const stat = fs14.statSync(fullPath);
|
|
5078
5992
|
if (stat.size > maxFileSize) continue;
|
|
5079
5993
|
const mtime = stat.mtimeMs;
|
|
5080
5994
|
const cached = fileCache.get(filePath);
|
|
5081
5995
|
if (cached === mtime && !options.force) continue;
|
|
5082
5996
|
fileCache.set(filePath, mtime);
|
|
5083
5997
|
try {
|
|
5084
|
-
const content =
|
|
5998
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
5085
5999
|
const parsed = parseFile(filePath, content);
|
|
5086
6000
|
allParsed.push({ filePath, parsed, content });
|
|
5087
6001
|
scannedFilePaths.push(filePath);
|
|
@@ -5124,7 +6038,7 @@ async function scanCodebase(options = {}) {
|
|
|
5124
6038
|
});
|
|
5125
6039
|
result.nodeCount++;
|
|
5126
6040
|
for (const fp of scannedFilePaths) {
|
|
5127
|
-
if (
|
|
6041
|
+
if (path15.dirname(fp) === dir) {
|
|
5128
6042
|
const fileId = nodeId("file", fp);
|
|
5129
6043
|
try {
|
|
5130
6044
|
await addEdge({ sourceId: moduleId, targetId: fileId, type: "owns" });
|
|
@@ -5481,20 +6395,20 @@ async function recordParsedFile(filePath, parsed, content, result) {
|
|
|
5481
6395
|
}
|
|
5482
6396
|
function resolveImportPath(fromFile, importPath, root) {
|
|
5483
6397
|
if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
|
|
5484
|
-
const fromDir =
|
|
6398
|
+
const fromDir = path15.dirname(path15.join(root, fromFile));
|
|
5485
6399
|
const exts = [
|
|
5486
6400
|
...SOURCE_EXTENSIONS,
|
|
5487
6401
|
...SOURCE_EXTENSIONS.map((e) => `/index${e}`)
|
|
5488
6402
|
];
|
|
5489
6403
|
for (const ext of exts) {
|
|
5490
|
-
const resolved =
|
|
5491
|
-
if (
|
|
6404
|
+
const resolved = path15.resolve(fromDir, importPath + ext);
|
|
6405
|
+
if (fs14.existsSync(resolved)) return path15.relative(root, resolved);
|
|
5492
6406
|
}
|
|
5493
|
-
const dirPath =
|
|
5494
|
-
if (
|
|
6407
|
+
const dirPath = path15.resolve(fromDir, importPath);
|
|
6408
|
+
if (fs14.existsSync(dirPath) && fs14.statSync(dirPath).isDirectory()) {
|
|
5495
6409
|
for (const ext of SOURCE_EXTENSIONS) {
|
|
5496
|
-
const indexPath =
|
|
5497
|
-
if (
|
|
6410
|
+
const indexPath = path15.join(dirPath, `index${ext}`);
|
|
6411
|
+
if (fs14.existsSync(indexPath)) return path15.relative(root, indexPath);
|
|
5498
6412
|
}
|
|
5499
6413
|
}
|
|
5500
6414
|
return null;
|
|
@@ -5529,7 +6443,7 @@ async function scanAndFormat(options = {}) {
|
|
|
5529
6443
|
const result = await scanCodebase(options);
|
|
5530
6444
|
return formatScanResult(result);
|
|
5531
6445
|
}
|
|
5532
|
-
var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache,
|
|
6446
|
+
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
6447
|
var init_kumaCodeScanner = __esm({
|
|
5534
6448
|
"src/engine/kumaCodeScanner.ts"() {
|
|
5535
6449
|
"use strict";
|
|
@@ -5539,7 +6453,7 @@ var init_kumaCodeScanner = __esm({
|
|
|
5539
6453
|
DEFAULT_MAX_FILES = 200;
|
|
5540
6454
|
DEFAULT_MAX_FILE_SIZE = 100 * 1024;
|
|
5541
6455
|
fileCache = /* @__PURE__ */ new Map();
|
|
5542
|
-
|
|
6456
|
+
_cachedRoot2 = null;
|
|
5543
6457
|
FUNCTION_DECL_RE = /(?:export\s+)?(?:async\s+)?function\s*(?:\*\s*)?(\w+)\s*\(/g;
|
|
5544
6458
|
ARROW_FN_RE = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*[=:]\s*(?:async\s*)?(?:<[^>]+>\s*)?(?:\([\s\S]*?\)|\w+)\s*(?::\s*\w+(?:<[^>]*>)?)?\s*=>/g;
|
|
5545
6459
|
TYPED_ARROW_RE = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*:\s*(?:\w+(?:<[^>]*>)?)?\s*=\s*(?:async\s*)?(?:<[^>]+>\s*)?\(/g;
|
|
@@ -5565,8 +6479,8 @@ __export(kumaFlowCache_exports, {
|
|
|
5565
6479
|
getFlowFreshness: () => getFlowFreshness,
|
|
5566
6480
|
getFreshDomainFlow: () => getFreshDomainFlow
|
|
5567
6481
|
});
|
|
5568
|
-
import
|
|
5569
|
-
import
|
|
6482
|
+
import fs15 from "fs";
|
|
6483
|
+
import path16 from "path";
|
|
5570
6484
|
async function getFlowFreshness(domain) {
|
|
5571
6485
|
try {
|
|
5572
6486
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
@@ -5601,26 +6515,26 @@ async function getFlowFreshness(domain) {
|
|
|
5601
6515
|
}
|
|
5602
6516
|
function resolveImport(fromFile, spec) {
|
|
5603
6517
|
const root = getProjectRoot();
|
|
5604
|
-
const fromDir =
|
|
6518
|
+
const fromDir = path16.dirname(fromFile);
|
|
5605
6519
|
const candidates = [];
|
|
5606
6520
|
if (spec.startsWith(".")) {
|
|
5607
|
-
const abs =
|
|
6521
|
+
const abs = path16.resolve(fromDir, spec);
|
|
5608
6522
|
candidates.push(abs);
|
|
5609
6523
|
} else {
|
|
5610
|
-
candidates.push(
|
|
6524
|
+
candidates.push(path16.join(root, spec));
|
|
5611
6525
|
}
|
|
5612
6526
|
for (const c of candidates) {
|
|
5613
6527
|
for (const ext of SOURCE_EXTENSIONS) {
|
|
5614
6528
|
const withExt = `${c}${ext}`;
|
|
5615
|
-
if (
|
|
5616
|
-
if (
|
|
5617
|
-
const idx =
|
|
5618
|
-
if (
|
|
6529
|
+
if (fs15.existsSync(withExt)) return path16.relative(root, withExt);
|
|
6530
|
+
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) {
|
|
6531
|
+
const idx = path16.join(c, "index") + ext;
|
|
6532
|
+
if (fs15.existsSync(idx)) return path16.relative(root, idx);
|
|
5619
6533
|
}
|
|
5620
6534
|
}
|
|
5621
|
-
if (
|
|
6535
|
+
if (fs15.existsSync(c)) {
|
|
5622
6536
|
try {
|
|
5623
|
-
if (
|
|
6537
|
+
if (fs15.statSync(c).isFile()) return path16.relative(root, c);
|
|
5624
6538
|
} catch {
|
|
5625
6539
|
}
|
|
5626
6540
|
}
|
|
@@ -5629,13 +6543,13 @@ function resolveImport(fromFile, spec) {
|
|
|
5629
6543
|
}
|
|
5630
6544
|
function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
5631
6545
|
const root = getProjectRoot();
|
|
5632
|
-
const absEntry =
|
|
5633
|
-
if (!
|
|
6546
|
+
const absEntry = path16.isAbsolute(entryFile) ? entryFile : path16.resolve(root, entryFile);
|
|
6547
|
+
if (!fs15.existsSync(absEntry)) return [];
|
|
5634
6548
|
const hops = [];
|
|
5635
6549
|
const seen = /* @__PURE__ */ new Set([entryFile]);
|
|
5636
|
-
const fromName =
|
|
6550
|
+
const fromName = path16.basename(absEntry);
|
|
5637
6551
|
try {
|
|
5638
|
-
const lines =
|
|
6552
|
+
const lines = fs15.readFileSync(absEntry, "utf-8").split("\n");
|
|
5639
6553
|
for (const line of lines) {
|
|
5640
6554
|
if (hops.length >= maxHops) break;
|
|
5641
6555
|
const spec = matchImportPath(line);
|
|
@@ -5645,9 +6559,9 @@ function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
|
5645
6559
|
seen.add(resolved);
|
|
5646
6560
|
hops.push({
|
|
5647
6561
|
from: fromName,
|
|
5648
|
-
to:
|
|
6562
|
+
to: path16.basename(resolved),
|
|
5649
6563
|
relation: "imports",
|
|
5650
|
-
description: `${fromName} imports ${
|
|
6564
|
+
description: `${fromName} imports ${path16.basename(resolved)}`
|
|
5651
6565
|
});
|
|
5652
6566
|
}
|
|
5653
6567
|
} catch {
|
|
@@ -5679,7 +6593,7 @@ Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} |
|
|
|
5679
6593
|
hops,
|
|
5680
6594
|
filePaths: freshness.filePaths
|
|
5681
6595
|
});
|
|
5682
|
-
lines.push(`\u2705 Re-derived ${hops.length} hop(s) from ${
|
|
6596
|
+
lines.push(`\u2705 Re-derived ${hops.length} hop(s) from ${path16.basename(entry)} \u2014 cache refreshed.`);
|
|
5683
6597
|
} else {
|
|
5684
6598
|
lines.push("\u26A0\uFE0F No imports to follow \u2014 cache left as-is (record hops manually if needed).");
|
|
5685
6599
|
}
|
|
@@ -5714,8 +6628,8 @@ __export(kumaSearch_exports, {
|
|
|
5714
6628
|
formatHybridResults: () => formatHybridResults,
|
|
5715
6629
|
hybridSearch: () => hybridSearch
|
|
5716
6630
|
});
|
|
5717
|
-
import
|
|
5718
|
-
import
|
|
6631
|
+
import fs17 from "fs";
|
|
6632
|
+
import path18 from "path";
|
|
5719
6633
|
function expandQueryTerms(query) {
|
|
5720
6634
|
const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
|
|
5721
6635
|
const expanded = /* @__PURE__ */ new Map();
|
|
@@ -5778,13 +6692,13 @@ async function buildSearchVectors() {
|
|
|
5778
6692
|
}
|
|
5779
6693
|
try {
|
|
5780
6694
|
const root = getProjectRoot();
|
|
5781
|
-
const memDir =
|
|
5782
|
-
if (
|
|
5783
|
-
const files =
|
|
6695
|
+
const memDir = path18.join(root, ".kuma", "memories");
|
|
6696
|
+
if (fs17.existsSync(memDir)) {
|
|
6697
|
+
const files = fs17.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
|
|
5784
6698
|
for (const file of files) {
|
|
5785
6699
|
try {
|
|
5786
|
-
const fullPath =
|
|
5787
|
-
const content =
|
|
6700
|
+
const fullPath = path18.join(memDir, file);
|
|
6701
|
+
const content = fs17.readFileSync(fullPath, "utf-8");
|
|
5788
6702
|
const text = content.toLowerCase();
|
|
5789
6703
|
const rawTerms = extractTerms(text);
|
|
5790
6704
|
const termCounts = /* @__PURE__ */ new Map();
|
|
@@ -5795,7 +6709,7 @@ async function buildSearchVectors() {
|
|
|
5795
6709
|
terms: termCounts,
|
|
5796
6710
|
source: file.replace(/\.md$/, ""),
|
|
5797
6711
|
sourceType: "memory",
|
|
5798
|
-
recency: recencyScore(Math.floor(
|
|
6712
|
+
recency: recencyScore(Math.floor(fs17.statSync(fullPath).mtimeMs / 1e3))
|
|
5799
6713
|
});
|
|
5800
6714
|
} catch {
|
|
5801
6715
|
}
|
|
@@ -6105,6 +7019,137 @@ var init_kumaSearch = __esm({
|
|
|
6105
7019
|
}
|
|
6106
7020
|
});
|
|
6107
7021
|
|
|
7022
|
+
// src/guards/architectureGuard.ts
|
|
7023
|
+
var architectureGuard_exports = {};
|
|
7024
|
+
__export(architectureGuard_exports, {
|
|
7025
|
+
checkArchitectureBoundaries: () => checkArchitectureBoundaries,
|
|
7026
|
+
detectWorkspaceCircularDeps: () => detectWorkspaceCircularDeps,
|
|
7027
|
+
loadArchitectureRules: () => loadArchitectureRules
|
|
7028
|
+
});
|
|
7029
|
+
import fs21 from "fs";
|
|
7030
|
+
import path22 from "path";
|
|
7031
|
+
import fastGlob4 from "fast-glob";
|
|
7032
|
+
function loadArchitectureRules(root) {
|
|
7033
|
+
const rulesPath = path22.join(root, ".kuma", "architecture.json");
|
|
7034
|
+
if (fs21.existsSync(rulesPath)) {
|
|
7035
|
+
try {
|
|
7036
|
+
const data = JSON.parse(fs21.readFileSync(rulesPath, "utf-8"));
|
|
7037
|
+
if (Array.isArray(data.rules)) {
|
|
7038
|
+
return data.rules;
|
|
7039
|
+
}
|
|
7040
|
+
} catch {
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
return [];
|
|
7044
|
+
}
|
|
7045
|
+
function detectWorkspaceCircularDeps(wsInfo) {
|
|
7046
|
+
const circulars = [];
|
|
7047
|
+
const visited = /* @__PURE__ */ new Set();
|
|
7048
|
+
const recStack = /* @__PURE__ */ new Set();
|
|
7049
|
+
function dfs(current, pathTrace) {
|
|
7050
|
+
visited.add(current);
|
|
7051
|
+
recStack.add(current);
|
|
7052
|
+
pathTrace.push(current);
|
|
7053
|
+
const pkg = wsInfo.packageMap.get(current);
|
|
7054
|
+
if (pkg) {
|
|
7055
|
+
for (const dep of pkg.internalDependencies) {
|
|
7056
|
+
if (!visited.has(dep)) {
|
|
7057
|
+
dfs(dep, [...pathTrace]);
|
|
7058
|
+
} else if (recStack.has(dep)) {
|
|
7059
|
+
const cycleStart = pathTrace.indexOf(dep);
|
|
7060
|
+
circulars.push([...pathTrace.slice(cycleStart), dep]);
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
}
|
|
7064
|
+
recStack.delete(current);
|
|
7065
|
+
}
|
|
7066
|
+
for (const pkg of wsInfo.packages) {
|
|
7067
|
+
if (!visited.has(pkg.name)) {
|
|
7068
|
+
dfs(pkg.name, []);
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
return circulars;
|
|
7072
|
+
}
|
|
7073
|
+
async function checkArchitectureBoundaries(filesToCheck, customRoot) {
|
|
7074
|
+
const root = customRoot || getProjectRoot();
|
|
7075
|
+
const wsInfo = await getWorkspaceInfo(root);
|
|
7076
|
+
const violations = [];
|
|
7077
|
+
const circulars = detectWorkspaceCircularDeps(wsInfo);
|
|
7078
|
+
for (const cycle of circulars) {
|
|
7079
|
+
violations.push({
|
|
7080
|
+
sourceFile: cycle[0],
|
|
7081
|
+
targetImport: cycle[cycle.length - 1],
|
|
7082
|
+
rule: "circular-package-dependency",
|
|
7083
|
+
severity: "high",
|
|
7084
|
+
suggestion: `Circular dependency detected: ${cycle.join(" \u2192 ")}. Extract shared interfaces to a separate package.`
|
|
7085
|
+
});
|
|
7086
|
+
}
|
|
7087
|
+
let targetFiles = filesToCheck;
|
|
7088
|
+
if (!targetFiles || targetFiles.length === 0) {
|
|
7089
|
+
targetFiles = await fastGlob4(["**/*.{ts,tsx,js,jsx,mjs,cjs}"], {
|
|
7090
|
+
cwd: root,
|
|
7091
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.kuma/**"],
|
|
7092
|
+
onlyFiles: true
|
|
7093
|
+
});
|
|
7094
|
+
}
|
|
7095
|
+
const customRules = loadArchitectureRules(root);
|
|
7096
|
+
const importRegex = /(?:import|export\s+.*from|require\s*\()\s*['"]([^'"]+)['"]/g;
|
|
7097
|
+
for (const file of targetFiles) {
|
|
7098
|
+
const fullPath = path22.isAbsolute(file) ? file : path22.join(root, file);
|
|
7099
|
+
if (!fs21.existsSync(fullPath)) continue;
|
|
7100
|
+
let content = "";
|
|
7101
|
+
try {
|
|
7102
|
+
content = fs21.readFileSync(fullPath, "utf-8");
|
|
7103
|
+
} catch {
|
|
7104
|
+
continue;
|
|
7105
|
+
}
|
|
7106
|
+
const currentPkg = findPackageForFile(file, wsInfo);
|
|
7107
|
+
let match;
|
|
7108
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
7109
|
+
const importPath = match[1];
|
|
7110
|
+
if (wsInfo.isWorkspace && currentPkg && !currentPkg.isRoot) {
|
|
7111
|
+
if (importPath.startsWith("../") && (importPath.includes("/src/") || importPath.includes("/dist/"))) {
|
|
7112
|
+
const resolvedTarget = path22.normalize(path22.resolve(path22.dirname(fullPath), importPath));
|
|
7113
|
+
const targetPkg = findPackageForFile(resolvedTarget, wsInfo);
|
|
7114
|
+
if (targetPkg && targetPkg.name !== currentPkg.name) {
|
|
7115
|
+
violations.push({
|
|
7116
|
+
sourceFile: file,
|
|
7117
|
+
targetImport: importPath,
|
|
7118
|
+
rule: "no-deep-cross-package-import",
|
|
7119
|
+
severity: "high",
|
|
7120
|
+
suggestion: `Do not deep-import private files from '${targetPkg.name}'. Use the public package name \`${targetPkg.name}\` or its exported API.`
|
|
7121
|
+
});
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
for (const rule of customRules) {
|
|
7126
|
+
const matchesFrom = file.includes(rule.from) || currentPkg && currentPkg.name === rule.from;
|
|
7127
|
+
if (matchesFrom) {
|
|
7128
|
+
for (const disallowed of rule.disallow) {
|
|
7129
|
+
if (importPath.includes(disallowed) || currentPkg && disallowed === currentPkg.name) {
|
|
7130
|
+
violations.push({
|
|
7131
|
+
sourceFile: file,
|
|
7132
|
+
targetImport: importPath,
|
|
7133
|
+
rule: `disallowed-boundary: ${rule.from} \u2192 ${disallowed}`,
|
|
7134
|
+
severity: "high",
|
|
7135
|
+
suggestion: rule.message || `Import from '${disallowed}' is forbidden in '${rule.from}'.`
|
|
7136
|
+
});
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
}
|
|
7143
|
+
return violations;
|
|
7144
|
+
}
|
|
7145
|
+
var init_architectureGuard = __esm({
|
|
7146
|
+
"src/guards/architectureGuard.ts"() {
|
|
7147
|
+
"use strict";
|
|
7148
|
+
init_pathValidator();
|
|
7149
|
+
init_workspaceIntelligence();
|
|
7150
|
+
}
|
|
7151
|
+
});
|
|
7152
|
+
|
|
6108
7153
|
// src/engine/kumaCheckpoint.ts
|
|
6109
7154
|
var kumaCheckpoint_exports = {};
|
|
6110
7155
|
__export(kumaCheckpoint_exports, {
|
|
@@ -6113,16 +7158,16 @@ __export(kumaCheckpoint_exports, {
|
|
|
6113
7158
|
pruneCheckpoints: () => pruneCheckpoints,
|
|
6114
7159
|
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
6115
7160
|
});
|
|
6116
|
-
import
|
|
6117
|
-
import
|
|
7161
|
+
import fs22 from "fs";
|
|
7162
|
+
import path23 from "path";
|
|
6118
7163
|
async function createCheckpoint(label, description) {
|
|
6119
7164
|
try {
|
|
6120
7165
|
const root = getProjectRoot();
|
|
6121
|
-
const cpDir =
|
|
6122
|
-
if (
|
|
7166
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
7167
|
+
if (fs22.existsSync(cpDir)) {
|
|
6123
7168
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
6124
7169
|
}
|
|
6125
|
-
|
|
7170
|
+
fs22.mkdirSync(cpDir, { recursive: true });
|
|
6126
7171
|
const db = await getDb();
|
|
6127
7172
|
const filesStmt = db.prepare(
|
|
6128
7173
|
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
@@ -6131,19 +7176,19 @@ async function createCheckpoint(label, description) {
|
|
|
6131
7176
|
while (filesStmt.step()) {
|
|
6132
7177
|
const row = filesStmt.getAsObject();
|
|
6133
7178
|
const fp = row.file_path;
|
|
6134
|
-
const fullPath =
|
|
6135
|
-
if (
|
|
6136
|
-
const content =
|
|
7179
|
+
const fullPath = path23.resolve(root, fp);
|
|
7180
|
+
if (fs22.existsSync(fullPath)) {
|
|
7181
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
6137
7182
|
const hash = simpleHash2(content);
|
|
6138
|
-
const fileDir =
|
|
6139
|
-
if (!
|
|
6140
|
-
|
|
7183
|
+
const fileDir = path23.dirname(path23.join(cpDir, "files", fp));
|
|
7184
|
+
if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
|
|
7185
|
+
fs22.writeFileSync(path23.join(cpDir, "files", fp), content, "utf-8");
|
|
6141
7186
|
files.push({ path: fp, hash });
|
|
6142
7187
|
}
|
|
6143
7188
|
}
|
|
6144
7189
|
filesStmt.free();
|
|
6145
7190
|
const dbData = db.export();
|
|
6146
|
-
|
|
7191
|
+
fs22.writeFileSync(path23.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
6147
7192
|
const manifest = {
|
|
6148
7193
|
label,
|
|
6149
7194
|
timestamp: Date.now(),
|
|
@@ -6151,8 +7196,8 @@ async function createCheckpoint(label, description) {
|
|
|
6151
7196
|
dbSnapshot: true,
|
|
6152
7197
|
description
|
|
6153
7198
|
};
|
|
6154
|
-
|
|
6155
|
-
|
|
7199
|
+
fs22.writeFileSync(
|
|
7200
|
+
path23.join(cpDir, "manifest.json"),
|
|
6156
7201
|
JSON.stringify(manifest, null, 2),
|
|
6157
7202
|
"utf-8"
|
|
6158
7203
|
);
|
|
@@ -6178,39 +7223,39 @@ async function createCheckpoint(label, description) {
|
|
|
6178
7223
|
async function rollbackToCheckpoint(label) {
|
|
6179
7224
|
try {
|
|
6180
7225
|
const root = getProjectRoot();
|
|
6181
|
-
const cpDir =
|
|
6182
|
-
const manifestPath =
|
|
6183
|
-
if (!
|
|
7226
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
7227
|
+
const manifestPath = path23.join(cpDir, "manifest.json");
|
|
7228
|
+
if (!fs22.existsSync(manifestPath)) {
|
|
6184
7229
|
return `\u274C Checkpoint "${label}" not found. Available checkpoints:
|
|
6185
7230
|
|
|
6186
7231
|
${listCheckpoints()}`;
|
|
6187
7232
|
}
|
|
6188
7233
|
const manifest = JSON.parse(
|
|
6189
|
-
|
|
7234
|
+
fs22.readFileSync(manifestPath, "utf-8")
|
|
6190
7235
|
);
|
|
6191
7236
|
let restored = 0;
|
|
6192
7237
|
let failed = 0;
|
|
6193
7238
|
for (const f of manifest.files) {
|
|
6194
|
-
const snapshotPath =
|
|
6195
|
-
if (
|
|
7239
|
+
const snapshotPath = path23.join(cpDir, "files", f.path);
|
|
7240
|
+
if (fs22.existsSync(snapshotPath)) {
|
|
6196
7241
|
try {
|
|
6197
|
-
const content =
|
|
6198
|
-
const targetPath =
|
|
6199
|
-
const targetDir =
|
|
6200
|
-
if (!
|
|
6201
|
-
|
|
7242
|
+
const content = fs22.readFileSync(snapshotPath, "utf-8");
|
|
7243
|
+
const targetPath = path23.resolve(root, f.path);
|
|
7244
|
+
const targetDir = path23.dirname(targetPath);
|
|
7245
|
+
if (!fs22.existsSync(targetDir)) fs22.mkdirSync(targetDir, { recursive: true });
|
|
7246
|
+
fs22.writeFileSync(targetPath, content, "utf-8");
|
|
6202
7247
|
restored++;
|
|
6203
7248
|
} catch {
|
|
6204
7249
|
failed++;
|
|
6205
7250
|
}
|
|
6206
7251
|
}
|
|
6207
7252
|
}
|
|
6208
|
-
const dbSnapshotPath =
|
|
6209
|
-
if (
|
|
6210
|
-
const kumaDir =
|
|
6211
|
-
const dbPath =
|
|
6212
|
-
const snapshotData =
|
|
6213
|
-
|
|
7253
|
+
const dbSnapshotPath = path23.join(cpDir, "kuma.db");
|
|
7254
|
+
if (fs22.existsSync(dbSnapshotPath)) {
|
|
7255
|
+
const kumaDir = path23.join(root, ".kuma");
|
|
7256
|
+
const dbPath = path23.join(kumaDir, "kuma.db");
|
|
7257
|
+
const snapshotData = fs22.readFileSync(dbSnapshotPath);
|
|
7258
|
+
fs22.writeFileSync(dbPath, snapshotData);
|
|
6214
7259
|
resetDbInstance();
|
|
6215
7260
|
}
|
|
6216
7261
|
sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
|
|
@@ -6235,18 +7280,18 @@ ${listCheckpoints()}`;
|
|
|
6235
7280
|
function listCheckpoints() {
|
|
6236
7281
|
try {
|
|
6237
7282
|
const root = getProjectRoot();
|
|
6238
|
-
const cpDir =
|
|
6239
|
-
if (!
|
|
7283
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR);
|
|
7284
|
+
if (!fs22.existsSync(cpDir)) {
|
|
6240
7285
|
return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
|
|
6241
7286
|
}
|
|
6242
|
-
const entries =
|
|
7287
|
+
const entries = fs22.readdirSync(cpDir);
|
|
6243
7288
|
const checkpoints = [];
|
|
6244
7289
|
for (const entry of entries) {
|
|
6245
|
-
const manifestPath =
|
|
6246
|
-
if (
|
|
7290
|
+
const manifestPath = path23.join(cpDir, entry, "manifest.json");
|
|
7291
|
+
if (fs22.existsSync(manifestPath)) {
|
|
6247
7292
|
try {
|
|
6248
7293
|
const manifest = JSON.parse(
|
|
6249
|
-
|
|
7294
|
+
fs22.readFileSync(manifestPath, "utf-8")
|
|
6250
7295
|
);
|
|
6251
7296
|
checkpoints.push({ label: entry, manifest });
|
|
6252
7297
|
} catch {
|
|
@@ -6278,12 +7323,12 @@ function listCheckpoints() {
|
|
|
6278
7323
|
function pruneCheckpoints(keep = 5) {
|
|
6279
7324
|
try {
|
|
6280
7325
|
const root = getProjectRoot();
|
|
6281
|
-
const cpDir =
|
|
6282
|
-
if (!
|
|
6283
|
-
const entries =
|
|
7326
|
+
const cpDir = path23.join(root, CHECKPOINT_DIR);
|
|
7327
|
+
if (!fs22.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
|
|
7328
|
+
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
7329
|
let removed = 0;
|
|
6285
7330
|
for (let i = keep; i < entries.length; i++) {
|
|
6286
|
-
|
|
7331
|
+
fs22.rmSync(path23.join(cpDir, entries[i].name), { recursive: true, force: true });
|
|
6287
7332
|
removed++;
|
|
6288
7333
|
}
|
|
6289
7334
|
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 +7369,16 @@ __export(kumaAutoGotcha_exports, {
|
|
|
6324
7369
|
saveAutoGotchaState: () => saveAutoGotchaState,
|
|
6325
7370
|
trackVerificationResult: () => trackVerificationResult
|
|
6326
7371
|
});
|
|
6327
|
-
import
|
|
6328
|
-
import
|
|
7372
|
+
import fs23 from "fs";
|
|
7373
|
+
import path24 from "path";
|
|
6329
7374
|
function statePath() {
|
|
6330
|
-
return
|
|
7375
|
+
return path24.join(getProjectRoot(), STATE_FILE);
|
|
6331
7376
|
}
|
|
6332
7377
|
function loadAutoGotchaState() {
|
|
6333
7378
|
try {
|
|
6334
7379
|
const fp = statePath();
|
|
6335
|
-
if (
|
|
6336
|
-
const parsed = JSON.parse(
|
|
7380
|
+
if (fs23.existsSync(fp)) {
|
|
7381
|
+
const parsed = JSON.parse(fs23.readFileSync(fp, "utf-8"));
|
|
6337
7382
|
return {
|
|
6338
7383
|
failures: parsed?.failures ?? {},
|
|
6339
7384
|
lastRecorded: parsed?.lastRecorded ?? {}
|
|
@@ -6346,9 +7391,9 @@ function loadAutoGotchaState() {
|
|
|
6346
7391
|
function saveAutoGotchaState(state) {
|
|
6347
7392
|
try {
|
|
6348
7393
|
const fp = statePath();
|
|
6349
|
-
const dir =
|
|
6350
|
-
if (!
|
|
6351
|
-
|
|
7394
|
+
const dir = path24.dirname(fp);
|
|
7395
|
+
if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
|
|
7396
|
+
fs23.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
|
|
6352
7397
|
} catch {
|
|
6353
7398
|
}
|
|
6354
7399
|
}
|
|
@@ -6388,6 +7433,7 @@ async function trackVerificationResult(scope, passed) {
|
|
|
6388
7433
|
filePath: scope,
|
|
6389
7434
|
description: `Verification keeps failing for scope "${scope}"`,
|
|
6390
7435
|
severity,
|
|
7436
|
+
status: "candidate",
|
|
6391
7437
|
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
7438
|
});
|
|
6393
7439
|
state.lastRecorded[scope] = failureCount;
|
|
@@ -6431,37 +7477,37 @@ __export(kumaVerifier_exports, {
|
|
|
6431
7477
|
getRunningVerificationPid: () => getRunningVerificationPid,
|
|
6432
7478
|
runAutoVerification: () => runAutoVerification
|
|
6433
7479
|
});
|
|
6434
|
-
import
|
|
6435
|
-
import
|
|
7480
|
+
import fs24 from "fs";
|
|
7481
|
+
import path25 from "path";
|
|
6436
7482
|
import { exec } from "child_process";
|
|
6437
7483
|
function getLockDir(root = process.cwd()) {
|
|
6438
|
-
return
|
|
7484
|
+
return path25.resolve(root, LOCK_DIR_NAME);
|
|
6439
7485
|
}
|
|
6440
7486
|
function getPidFile(root = process.cwd()) {
|
|
6441
|
-
return
|
|
7487
|
+
return path25.join(getLockDir(root), "pid");
|
|
6442
7488
|
}
|
|
6443
7489
|
function acquireFileLock(root) {
|
|
6444
7490
|
const lockDir = getLockDir(root);
|
|
6445
7491
|
try {
|
|
6446
|
-
|
|
6447
|
-
|
|
7492
|
+
fs24.mkdirSync(lockDir, { recursive: false });
|
|
7493
|
+
fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
6448
7494
|
return true;
|
|
6449
7495
|
} catch {
|
|
6450
7496
|
try {
|
|
6451
|
-
if (
|
|
6452
|
-
const pid = parseInt(
|
|
7497
|
+
if (fs24.existsSync(getPidFile(root))) {
|
|
7498
|
+
const pid = parseInt(fs24.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
6453
7499
|
try {
|
|
6454
7500
|
process.kill(pid, 0);
|
|
6455
7501
|
return false;
|
|
6456
7502
|
} catch {
|
|
6457
|
-
|
|
7503
|
+
fs24.rmSync(lockDir, { recursive: true, force: true });
|
|
6458
7504
|
return acquireFileLock(root);
|
|
6459
7505
|
}
|
|
6460
7506
|
}
|
|
6461
7507
|
} catch {
|
|
6462
7508
|
}
|
|
6463
7509
|
try {
|
|
6464
|
-
|
|
7510
|
+
fs24.rmSync(lockDir, { recursive: true, force: true });
|
|
6465
7511
|
return acquireFileLock(root);
|
|
6466
7512
|
} catch {
|
|
6467
7513
|
return false;
|
|
@@ -6470,7 +7516,7 @@ function acquireFileLock(root) {
|
|
|
6470
7516
|
}
|
|
6471
7517
|
function releaseFileLock(root) {
|
|
6472
7518
|
try {
|
|
6473
|
-
|
|
7519
|
+
fs24.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
6474
7520
|
} catch {
|
|
6475
7521
|
}
|
|
6476
7522
|
}
|
|
@@ -6493,36 +7539,42 @@ function getRunningVerificationPid() {
|
|
|
6493
7539
|
return _currentProcess?.pid ?? null;
|
|
6494
7540
|
}
|
|
6495
7541
|
function detectTestRunner(root = process.cwd()) {
|
|
6496
|
-
const pkgPath =
|
|
6497
|
-
if (
|
|
7542
|
+
const pkgPath = path25.join(root, "package.json");
|
|
7543
|
+
if (fs24.existsSync(pkgPath)) {
|
|
6498
7544
|
try {
|
|
6499
|
-
const pkg = JSON.parse(
|
|
7545
|
+
const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
|
|
7546
|
+
const isJest = Boolean(
|
|
7547
|
+
pkg.devDependencies?.jest || pkg.dependencies?.jest || pkg.scripts?.test && pkg.scripts.test.includes("jest") || fs24.existsSync(path25.join(root, "jest.config.js")) || fs24.existsSync(path25.join(root, "jest.config.ts")) || fs24.existsSync(path25.join(root, "jest.config.mjs")) || fs24.existsSync(path25.join(root, "jest.config.json"))
|
|
7548
|
+
);
|
|
7549
|
+
const isVitest = Boolean(
|
|
7550
|
+
pkg.devDependencies?.vitest || pkg.dependencies?.vitest || pkg.scripts?.test && pkg.scripts.test.includes("vitest") || fs24.existsSync(path25.join(root, "vitest.config.ts")) || fs24.existsSync(path25.join(root, "vitest.config.js")) || fs24.existsSync(path25.join(root, "vitest.config.mjs"))
|
|
7551
|
+
);
|
|
6500
7552
|
if (pkg.scripts && pkg.scripts.test) {
|
|
6501
|
-
if (
|
|
6502
|
-
if (
|
|
6503
|
-
return { runner: "npm", baseCommand: "npm test" };
|
|
7553
|
+
if (fs24.existsSync(path25.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test", isJest, isVitest };
|
|
7554
|
+
if (fs24.existsSync(path25.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test", isJest, isVitest };
|
|
7555
|
+
return { runner: "npm", baseCommand: "npm test", isJest, isVitest };
|
|
6504
7556
|
}
|
|
6505
7557
|
} catch {
|
|
6506
7558
|
}
|
|
6507
7559
|
}
|
|
6508
|
-
if (
|
|
6509
|
-
if (
|
|
6510
|
-
if (
|
|
6511
|
-
if (
|
|
7560
|
+
if (fs24.existsSync(path25.join(root, "pytest.ini")) || fs24.existsSync(path25.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
7561
|
+
if (fs24.existsSync(path25.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
7562
|
+
if (fs24.existsSync(path25.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
7563
|
+
if (fs24.existsSync(path25.join(root, "Makefile"))) {
|
|
6512
7564
|
try {
|
|
6513
|
-
const makefileContent =
|
|
7565
|
+
const makefileContent = fs24.readFileSync(path25.join(root, "Makefile"), "utf-8");
|
|
6514
7566
|
const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
|
|
6515
7567
|
if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
|
|
6516
7568
|
} catch {
|
|
6517
7569
|
}
|
|
6518
7570
|
}
|
|
6519
|
-
if (
|
|
7571
|
+
if (fs24.existsSync(path25.join(root, "package.json"))) {
|
|
6520
7572
|
try {
|
|
6521
|
-
const pkg = JSON.parse(
|
|
7573
|
+
const pkg = JSON.parse(fs24.readFileSync(path25.join(root, "package.json"), "utf-8"));
|
|
6522
7574
|
if (pkg.dependencies || pkg.devDependencies) {
|
|
6523
|
-
if (
|
|
6524
|
-
const srcDir =
|
|
6525
|
-
if (
|
|
7575
|
+
if (fs24.existsSync(path25.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
7576
|
+
const srcDir = path25.join(root, "src");
|
|
7577
|
+
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
7578
|
}
|
|
6527
7579
|
} catch {
|
|
6528
7580
|
}
|
|
@@ -6580,7 +7632,7 @@ async function runAutoVerification(options = {}) {
|
|
|
6580
7632
|
}
|
|
6581
7633
|
}
|
|
6582
7634
|
_localRunning = true;
|
|
6583
|
-
const { runner, baseCommand } = detectTestRunner(root);
|
|
7635
|
+
const { runner, baseCommand, isJest, isVitest } = detectTestRunner(root);
|
|
6584
7636
|
if (runner === "unknown" || !baseCommand) {
|
|
6585
7637
|
_localRunning = false;
|
|
6586
7638
|
releaseFileLock(root);
|
|
@@ -6600,24 +7652,91 @@ async function runAutoVerification(options = {}) {
|
|
|
6600
7652
|
"\u{1F4A1} **Tip:** This is NOT a pass \u2014 it means nothing was verified. Run your tests manually or install a test framework."
|
|
6601
7653
|
].join("\n");
|
|
6602
7654
|
}
|
|
7655
|
+
let fullCommand = baseCommand;
|
|
6603
7656
|
let testFiles = [];
|
|
6604
|
-
|
|
6605
|
-
if (
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
testFiles = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
6612
|
-
} catch {
|
|
7657
|
+
let modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
7658
|
+
if (modified.length === 0) {
|
|
7659
|
+
try {
|
|
7660
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
7661
|
+
const gitDiff = execSync6("git diff --name-only HEAD", { encoding: "utf-8", timeout: 3e3, cwd: root }).trim();
|
|
7662
|
+
if (gitDiff) {
|
|
7663
|
+
modified = gitDiff.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
6613
7664
|
}
|
|
7665
|
+
} catch {
|
|
6614
7666
|
}
|
|
6615
|
-
} else {
|
|
6616
|
-
testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
6617
7667
|
}
|
|
6618
|
-
|
|
6619
|
-
if (
|
|
6620
|
-
|
|
7668
|
+
const targetScope = (options.scope || options.target || "").trim();
|
|
7669
|
+
if (options.command) {
|
|
7670
|
+
const cmd = options.command.trim();
|
|
7671
|
+
const isTestCmd = /^(npm|pnpm|yarn|bun|npx|pytest|cargo|go|make|jest|vitest)\s+/i.test(cmd);
|
|
7672
|
+
if (isTestCmd) {
|
|
7673
|
+
fullCommand = cmd;
|
|
7674
|
+
}
|
|
7675
|
+
} else {
|
|
7676
|
+
let workspaceScoped = false;
|
|
7677
|
+
try {
|
|
7678
|
+
const { getWorkspaceInfo: getWorkspaceInfo2, getAffectedPackages: getAffectedPackages2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
7679
|
+
const wsInfo = await getWorkspaceInfo2(root);
|
|
7680
|
+
if (wsInfo.isWorkspace && modified.length > 0 && !targetScope) {
|
|
7681
|
+
const affected = getAffectedPackages2(modified, wsInfo);
|
|
7682
|
+
if (affected.affectedTestCommands.length > 0) {
|
|
7683
|
+
fullCommand = affected.affectedTestCommands[0];
|
|
7684
|
+
workspaceScoped = true;
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
} catch {
|
|
7688
|
+
}
|
|
7689
|
+
if (!workspaceScoped) {
|
|
7690
|
+
if (targetScope && targetScope !== "session-impact") {
|
|
7691
|
+
const resolved = path25.resolve(root, targetScope);
|
|
7692
|
+
if (fs24.existsSync(resolved) && fs24.statSync(resolved).isFile()) {
|
|
7693
|
+
testFiles = [path25.relative(root, resolved)];
|
|
7694
|
+
} else {
|
|
7695
|
+
const term = targetScope.toLowerCase();
|
|
7696
|
+
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
7697
|
+
if (testFiles.length === 0) {
|
|
7698
|
+
try {
|
|
7699
|
+
const { default: glob } = await import("fast-glob");
|
|
7700
|
+
const baseName = path25.basename(targetScope, path25.extname(targetScope));
|
|
7701
|
+
testFiles = await glob([
|
|
7702
|
+
`**/*${baseName}*.test.*`,
|
|
7703
|
+
`**/*${baseName}*.spec.*`,
|
|
7704
|
+
`**/*${term}*test*.*`,
|
|
7705
|
+
`**/*test*/*${term}*.*`,
|
|
7706
|
+
`**/${targetScope}`
|
|
7707
|
+
], { cwd: root, ignore: ["node_modules/**", "dist/**", ".git/**"] });
|
|
7708
|
+
} catch {
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7711
|
+
}
|
|
7712
|
+
} else {
|
|
7713
|
+
const directTests = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
7714
|
+
if (directTests.length > 0) {
|
|
7715
|
+
testFiles = directTests;
|
|
7716
|
+
} else if (modified.length > 0) {
|
|
7717
|
+
try {
|
|
7718
|
+
const { default: glob } = await import("fast-glob");
|
|
7719
|
+
for (const f of modified.slice(0, 5)) {
|
|
7720
|
+
const base = path25.basename(f, path25.extname(f));
|
|
7721
|
+
const matches = await glob([`**/*${base}*.test.*`, `**/*${base}*.spec.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
7722
|
+
testFiles.push(...matches);
|
|
7723
|
+
}
|
|
7724
|
+
} catch {
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
if (testFiles.length > 0 && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
|
|
7729
|
+
fullCommand = `${baseCommand} -- ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
|
|
7730
|
+
} else if (testFiles.length === 0 && modified.length > 0 && isJest && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
|
|
7731
|
+
const relFiles = Array.from(new Set(modified.slice(0, 15))).map((f) => `"${f}"`).join(" ");
|
|
7732
|
+
fullCommand = `${baseCommand} -- --findRelatedTests ${relFiles}`;
|
|
7733
|
+
} else if (testFiles.length === 0 && modified.length > 0 && isVitest && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
|
|
7734
|
+
const relFiles = Array.from(new Set(modified.slice(0, 15))).map((f) => `"${f}"`).join(" ");
|
|
7735
|
+
fullCommand = `${baseCommand} -- related ${relFiles} --run`;
|
|
7736
|
+
} else if (testFiles.length > 0 && runner === "pytest") {
|
|
7737
|
+
fullCommand = `pytest ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
6621
7740
|
}
|
|
6622
7741
|
return await new Promise((resolve) => {
|
|
6623
7742
|
const killTimer = setTimeout(() => {
|
|
@@ -6641,7 +7760,7 @@ async function runAutoVerification(options = {}) {
|
|
|
6641
7760
|
const heartbeatTimer = setInterval(() => {
|
|
6642
7761
|
if (getLockDir(root)) {
|
|
6643
7762
|
try {
|
|
6644
|
-
|
|
7763
|
+
fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
6645
7764
|
} catch {
|
|
6646
7765
|
}
|
|
6647
7766
|
}
|
|
@@ -6691,9 +7810,11 @@ async function runAutoVerification(options = {}) {
|
|
|
6691
7810
|
_currentProcess = null;
|
|
6692
7811
|
releaseFileLock(root);
|
|
6693
7812
|
clearInterval(heartbeatTimer);
|
|
6694
|
-
const
|
|
7813
|
+
const isTimedOut = Boolean(error && error.killed);
|
|
7814
|
+
const statusSymbol = passed ? "\u2705" : isTimedOut ? "\u23F1\uFE0F" : "\u{1F534}";
|
|
7815
|
+
const statusTitle = passed ? "PASSED" : isTimedOut ? "TIMED_OUT" : "FAILED";
|
|
6695
7816
|
const lines = [
|
|
6696
|
-
`${statusSymbol} **Verification ${
|
|
7817
|
+
`${statusSymbol} **Verification ${statusTitle}**`,
|
|
6697
7818
|
`\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`,
|
|
6698
7819
|
"",
|
|
6699
7820
|
`\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
|
|
@@ -6706,12 +7827,14 @@ async function runAutoVerification(options = {}) {
|
|
|
6706
7827
|
if (isNoTests) {
|
|
6707
7828
|
lines[0] = `\u26AA **Verification: NO TESTS**`;
|
|
6708
7829
|
lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
|
|
7830
|
+
} else if (isTimedOut) {
|
|
7831
|
+
lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout (TIMED_OUT)**`);
|
|
7832
|
+
lines.push("\u2139\uFE0F Tests did not fail; the test execution exceeded the allotted timeout threshold.");
|
|
7833
|
+
lines.push(`\u{1F4A1} *Tip*: Run a specific test file via \`scope: 'tests/my-test.test.ts'\`, specify a custom command via \`command: '...' \`, or increase timeout via \`timeoutMs: ${Math.max(timeoutMs * 2, 12e4)}\`.`);
|
|
7834
|
+
if (rawOutput) lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
6709
7835
|
} else {
|
|
6710
7836
|
lines.push("\u26A0\uFE0F **Verification failed!** Please fix test failures before shipping.");
|
|
6711
|
-
if (
|
|
6712
|
-
lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout**`);
|
|
6713
|
-
}
|
|
6714
|
-
lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
7837
|
+
if (rawOutput) lines.push("```text", rawOutput.substring(0, 1500), "```");
|
|
6715
7838
|
}
|
|
6716
7839
|
} else {
|
|
6717
7840
|
lines.push("\u{1F389} All scoped tests passed!");
|
|
@@ -6745,7 +7868,7 @@ var init_kumaVerifier = __esm({
|
|
|
6745
7868
|
_localRunning = false;
|
|
6746
7869
|
_currentProcess = null;
|
|
6747
7870
|
STALE_RESULT_MS = 3e5;
|
|
6748
|
-
DEFAULT_TIMEOUT_MS =
|
|
7871
|
+
DEFAULT_TIMEOUT_MS = 6e4;
|
|
6749
7872
|
RUNAWAY_WINDOW_MS = 3e5;
|
|
6750
7873
|
RUNAWAY_MAX_CALLS = 3;
|
|
6751
7874
|
_verifyCallTimestamps = [];
|
|
@@ -6761,8 +7884,8 @@ __export(init_exports, {
|
|
|
6761
7884
|
generateInitMdContent: () => generateInitMdContent,
|
|
6762
7885
|
runInit: () => runInit
|
|
6763
7886
|
});
|
|
6764
|
-
import
|
|
6765
|
-
import
|
|
7887
|
+
import fs25 from "fs";
|
|
7888
|
+
import path26 from "path";
|
|
6766
7889
|
function configFilePath(type) {
|
|
6767
7890
|
switch (type) {
|
|
6768
7891
|
case "claude":
|
|
@@ -6860,27 +7983,31 @@ function aiderTemplate() {
|
|
|
6860
7983
|
}
|
|
6861
7984
|
function opencodeAgentsMdTemplate() {
|
|
6862
7985
|
const opencodeLines = [
|
|
6863
|
-
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7986
|
+
"Kuma MCP tools: kuma_kuma_context (or kuma_context), kuma_kuma_memory (or kuma_memory), kuma_kuma_safety (or kuma_safety).",
|
|
6864
7987
|
"Record what matters, skip what doesn't.",
|
|
6865
7988
|
"",
|
|
6866
7989
|
"<kuma_obedience>",
|
|
6867
7990
|
"## \u{1F512} Obedience (non-negotiable)",
|
|
6868
|
-
'- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
|
|
7991
|
+
'- MUST call `kuma_kuma_context({ action: "init" })` (or `kuma_context({ action: "init" })`) at session start.',
|
|
6869
7992
|
"- 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
|
|
7993
|
+
'- MUST call `kuma_kuma_safety({ action: "verify" })` (or `kuma_safety({ action: "verify" })`) after edits.',
|
|
7994
|
+
"- MUST NOT call actions outside the core actions.",
|
|
6872
7995
|
"</kuma_obedience>",
|
|
6873
7996
|
"",
|
|
6874
7997
|
"<kuma_workflow>",
|
|
6875
7998
|
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
6876
|
-
'\u{
|
|
7999
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_kuma_context({ action: "map" })`',
|
|
8000
|
+
'\u{1F52C} **Blast radius / impact:** `kuma_kuma_context({ action: "impact", target: "<file>" })` or `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
6877
8001
|
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
6878
8002
|
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
6879
8003
|
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
6880
8004
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
6881
8005
|
"</kuma_workflow>",
|
|
6882
8006
|
"",
|
|
6883
|
-
"\u26A0\uFE0F **OpenCode note:**
|
|
8007
|
+
"\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.",
|
|
8008
|
+
"",
|
|
8009
|
+
"### \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
8010
|
+
"If Matcha is active: Matcha provides Intent Discovery (`.agents/plan/current.md`); Kuma provides blast radius, gotchas memory, and test verification.",
|
|
6884
8011
|
""
|
|
6885
8012
|
].join("\n");
|
|
6886
8013
|
return [
|
|
@@ -6888,7 +8015,8 @@ function opencodeAgentsMdTemplate() {
|
|
|
6888
8015
|
"",
|
|
6889
8016
|
opencodeLines,
|
|
6890
8017
|
"\u{1F4D6} Rules: `.kuma/init.md`",
|
|
6891
|
-
"\u{1F9E0}
|
|
8018
|
+
"\u{1F9E0} Skills: `.agents/skills/kuma/SKILL.md` \xB7 `.opencode/skills/kuma/SKILL.md`",
|
|
8019
|
+
"\u{1F50C} Plugin: `.opencode/plugins/kuma.js`"
|
|
6892
8020
|
].join("\n");
|
|
6893
8021
|
}
|
|
6894
8022
|
function codexTemplate() {
|
|
@@ -6996,58 +8124,203 @@ function antigravityMcpConfigTemplate() {
|
|
|
6996
8124
|
};
|
|
6997
8125
|
return JSON.stringify(config, null, 2) + "\n";
|
|
6998
8126
|
}
|
|
6999
|
-
function
|
|
8127
|
+
function antigravityRuleTemplate() {
|
|
7000
8128
|
return [
|
|
7001
|
-
"# Kuma
|
|
7002
|
-
"",
|
|
7003
|
-
"_(Auto-generated by `kuma init` \u2014 edit this file to customize)_",
|
|
8129
|
+
"# \u{1F43B} Kuma MCP \u2014 Antigravity Rules",
|
|
7004
8130
|
"",
|
|
7005
|
-
"
|
|
8131
|
+
"This project uses **Kuma MCP** for runtime safety, blast radius analysis, and codebase memory.",
|
|
7006
8132
|
"",
|
|
7007
|
-
|
|
7008
|
-
'> \u2022 **OpenCode:** Use `kuma_kuma_*` prefix (e.g. `kuma_kuma_context({ action: "init" })`)',
|
|
7009
|
-
'> \u2022 **Other platforms:** Use `kuma_*` directly (e.g. `kuma_context({ action: "init" })`)',
|
|
8133
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
7010
8134
|
"",
|
|
7011
|
-
"
|
|
8135
|
+
"### \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
8136
|
+
"If Matcha is active in this workspace:",
|
|
8137
|
+
"- **Matcha** governs engineering philosophy, intent discovery, reuse checks, and review gates.",
|
|
8138
|
+
"- **Kuma** governs blast radius calculation, monorepo workspace dependencies, known gotchas memory, architecture boundary guards, and scoped test verification.",
|
|
8139
|
+
"- 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' })`).",
|
|
7012
8140
|
"",
|
|
7013
|
-
"
|
|
7014
|
-
"
|
|
8141
|
+
"\u{1F4D6} Rules: `.kuma/init.md`",
|
|
8142
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
8143
|
+
].join("\n");
|
|
8144
|
+
}
|
|
8145
|
+
function geminiTemplate() {
|
|
8146
|
+
return [
|
|
8147
|
+
"# \u{1F43B} Kuma MCP \u2014 Workspace Instructions",
|
|
7015
8148
|
"",
|
|
7016
|
-
|
|
7017
|
-
'2. **MUST record gotchas IMMEDIATELY** when a bug/quirk is found (`kuma_memory({ action: "gotcha" })`).',
|
|
7018
|
-
'3. **MUST call `kuma_safety({ action: "verify" })`** after code edits.',
|
|
7019
|
-
"4. **MUST NOT call actions outside the 13 core actions** in this file \u2014 anything else is rejected by the MCP schema.",
|
|
7020
|
-
"5. If a Kuma call fails or returns an error, **re-read this file and retry** \u2014 do not silently continue.",
|
|
8149
|
+
"This workspace uses **Kuma MCP** for runtime safety, monorepo intelligence, blast radius calculation, and memory.",
|
|
7021
8150
|
"",
|
|
7022
|
-
">
|
|
8151
|
+
"<kuma_obedience>",
|
|
8152
|
+
"## \u{1F512} Kuma MCP Safety & Memory (non-negotiable)",
|
|
8153
|
+
'- **Session Start:** Call `kuma_context({ action: "init" })` before reading or editing code.',
|
|
8154
|
+
'- **Monorepo / Blast Radius:** Call `kuma_context({ action: "impact", target: "<file>" })` or `kuma_context({ action: "research", scope: "<area>" })` before modifying unfamiliar code.',
|
|
8155
|
+
'- **Gotchas:** Call `kuma_memory({ action: "gotcha", scope: "<file>", content: "<description>" })` IMMEDIATELY when any quirk/bug is discovered.',
|
|
8156
|
+
'- **Decisions:** Call `kuma_memory({ action: "decision", title: "...", rationale: "..." })` when choosing between options.',
|
|
8157
|
+
'- **Post-Edits:** Call `kuma_safety({ action: "verify" })` after modifying code to run affected package/unit tests.',
|
|
7023
8158
|
"</kuma_obedience>",
|
|
7024
8159
|
"",
|
|
7025
|
-
"
|
|
8160
|
+
"### \u{1F375} Matcha & Kuma Harmony",
|
|
8161
|
+
"If Matcha is active: Matcha provides Intent Discovery & review; Kuma provides blast radius, gotchas memory, architecture guards, and test verification.",
|
|
7026
8162
|
"",
|
|
7027
|
-
"
|
|
7028
|
-
|
|
8163
|
+
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
8164
|
+
].join("\n");
|
|
8165
|
+
}
|
|
8166
|
+
function opencodePluginTemplate() {
|
|
8167
|
+
return [
|
|
8168
|
+
"/**",
|
|
8169
|
+
" * \u{1F43B} Kuma MCP \u2014 OpenCode Plugin",
|
|
8170
|
+
" * Auto-injects gotcha warnings and safety guidance before file edits and commands in OpenCode.",
|
|
8171
|
+
" * OpenCode loads all plugins from .opencode/plugins/*.js automatically.",
|
|
8172
|
+
" */",
|
|
8173
|
+
'import fs from "node:fs";',
|
|
8174
|
+
'import path from "node:path";',
|
|
7029
8175
|
"",
|
|
7030
|
-
"
|
|
8176
|
+
"function getProjectRoot() {",
|
|
8177
|
+
" let curr = process.cwd();",
|
|
8178
|
+
" while (curr !== path.dirname(curr)) {",
|
|
8179
|
+
' if (fs.existsSync(path.join(curr, ".kuma")) || fs.existsSync(path.join(curr, ".git"))) {',
|
|
8180
|
+
" return curr;",
|
|
8181
|
+
" }",
|
|
8182
|
+
" curr = path.dirname(curr);",
|
|
8183
|
+
" }",
|
|
8184
|
+
" return process.cwd();",
|
|
8185
|
+
"}",
|
|
7031
8186
|
"",
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
'
|
|
7036
|
-
|
|
7037
|
-
'
|
|
7038
|
-
|
|
7039
|
-
'
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
"
|
|
8187
|
+
"function getActiveGotchasForFile(filePath) {",
|
|
8188
|
+
" try {",
|
|
8189
|
+
" const root = getProjectRoot();",
|
|
8190
|
+
' const gotchasPath = path.join(root, ".kuma", "KNOWN_GOTCHAS.md");',
|
|
8191
|
+
" if (!fs.existsSync(gotchasPath)) return [];",
|
|
8192
|
+
' const content = fs.readFileSync(gotchasPath, "utf-8");',
|
|
8193
|
+
" const gotchas = [];",
|
|
8194
|
+
' const normalizedTarget = filePath.replace(/^[./]+/, "");',
|
|
8195
|
+
" const sections = content.split(/###\\s+/);",
|
|
8196
|
+
" for (const section of sections) {",
|
|
8197
|
+
" if (!section.trim()) continue;",
|
|
8198
|
+
' const lines = section.split("\\n");',
|
|
8199
|
+
' const title = lines[0] || "";',
|
|
8200
|
+
" if (title.includes(normalizedTarget) || section.includes(normalizedTarget)) {",
|
|
8201
|
+
" gotchas.push(title.trim());",
|
|
8202
|
+
" }",
|
|
8203
|
+
" }",
|
|
8204
|
+
" return gotchas;",
|
|
8205
|
+
" } catch {",
|
|
8206
|
+
" return [];",
|
|
8207
|
+
" }",
|
|
8208
|
+
"}",
|
|
7043
8209
|
"",
|
|
7044
|
-
"
|
|
8210
|
+
"export const KumaPlugin = async () => {",
|
|
8211
|
+
" return {",
|
|
8212
|
+
' "tool.execute.before": async (input, output) => {',
|
|
8213
|
+
' const tool = (input.tool || "").toLowerCase();',
|
|
8214
|
+
" const args = output.args || {};",
|
|
8215
|
+
' const targetPath = args.filePath || args.targetFile || args.path || args.file || "";',
|
|
8216
|
+
' if (targetPath && (tool.includes("edit") || tool.includes("write") || tool.includes("patch"))) {',
|
|
8217
|
+
" const gotchas = getActiveGotchasForFile(String(targetPath));",
|
|
8218
|
+
" if (gotchas.length > 0) {",
|
|
8219
|
+
" console.warn(",
|
|
8220
|
+
" `\\n\u{1F43B} [Kuma Alert] Active gotchas found for ${targetPath}:\\n` +",
|
|
8221
|
+
' gotchas.map((g) => ` \u26A0\uFE0F ${g}`).join("\\n") +',
|
|
8222
|
+
' `\\n\u{1F449} Remember to record any new gotchas via kuma_memory({ action: "gotcha" })\\n`',
|
|
8223
|
+
" );",
|
|
8224
|
+
" }",
|
|
8225
|
+
" }",
|
|
8226
|
+
" },",
|
|
8227
|
+
" };",
|
|
8228
|
+
"};",
|
|
8229
|
+
""
|
|
8230
|
+
].join("\n");
|
|
8231
|
+
}
|
|
8232
|
+
function rooRulesTemplate() {
|
|
8233
|
+
return [
|
|
8234
|
+
"---",
|
|
8235
|
+
"description: Kuma MCP \u2014 .kuma/ is the single source of truth",
|
|
8236
|
+
"alwaysApply: true",
|
|
8237
|
+
"---",
|
|
7045
8238
|
"",
|
|
7046
|
-
|
|
7047
|
-
"
|
|
7048
|
-
"
|
|
7049
|
-
|
|
7050
|
-
|
|
8239
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8240
|
+
"",
|
|
8241
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8242
|
+
].join("\n");
|
|
8243
|
+
}
|
|
8244
|
+
function traeRulesTemplate() {
|
|
8245
|
+
return [
|
|
8246
|
+
"# Kuma MCP \u2014 Trae Rules",
|
|
8247
|
+
"",
|
|
8248
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8249
|
+
"",
|
|
8250
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8251
|
+
].join("\n");
|
|
8252
|
+
}
|
|
8253
|
+
function qoderRulesTemplate() {
|
|
8254
|
+
return [
|
|
8255
|
+
"# Kuma MCP \u2014 Qoder Rules",
|
|
8256
|
+
"",
|
|
8257
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8258
|
+
"",
|
|
8259
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8260
|
+
].join("\n");
|
|
8261
|
+
}
|
|
8262
|
+
function windsurfrulesTemplate() {
|
|
8263
|
+
return [
|
|
8264
|
+
"# Kuma MCP \u2014 Windsurf Rules",
|
|
8265
|
+
"",
|
|
8266
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
8267
|
+
"",
|
|
8268
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8269
|
+
].join("\n");
|
|
8270
|
+
}
|
|
8271
|
+
function generateInitMdContent() {
|
|
8272
|
+
return [
|
|
8273
|
+
"# Kuma Init \u2014 Workflow & Rules",
|
|
8274
|
+
"",
|
|
8275
|
+
"_(Auto-generated by `kuma init` \u2014 edit this file to customize)_",
|
|
8276
|
+
"",
|
|
8277
|
+
"---",
|
|
8278
|
+
"",
|
|
8279
|
+
"> **Platform Tool Names:**",
|
|
8280
|
+
'> \u2022 **OpenCode:** Use `kuma_kuma_*` prefix (e.g. `kuma_kuma_context({ action: "init" })`)',
|
|
8281
|
+
'> \u2022 **Other platforms:** Use `kuma_*` directly (e.g. `kuma_context({ action: "init" })`)',
|
|
8282
|
+
"",
|
|
8283
|
+
"---",
|
|
8284
|
+
"",
|
|
8285
|
+
"<kuma_obedience>",
|
|
8286
|
+
"## \u{1F512} OBEDIENCE (non-negotiable)",
|
|
8287
|
+
"",
|
|
8288
|
+
'1. **MUST call `kuma_context({ action: "init" })`** at session start \u2014 before any code edit.',
|
|
8289
|
+
'2. **MUST record gotchas IMMEDIATELY** when a bug/quirk is found (`kuma_memory({ action: "gotcha" })`).',
|
|
8290
|
+
'3. **MUST call `kuma_safety({ action: "verify" })`** after code edits.',
|
|
8291
|
+
"4. **MUST NOT call actions outside the core actions** in this file \u2014 anything else is rejected by the MCP schema.",
|
|
8292
|
+
"5. If a Kuma call fails or returns an error, **re-read this file and retry** \u2014 do not silently continue.",
|
|
8293
|
+
"",
|
|
8294
|
+
"> These rules are enforced: the MCP schema rejects unknown actions, and Claude Code hooks inject gotchas before every edit.",
|
|
8295
|
+
"</kuma_obedience>",
|
|
8296
|
+
"",
|
|
8297
|
+
"---",
|
|
8298
|
+
"",
|
|
8299
|
+
"<kuma_workflow>",
|
|
8300
|
+
"## SESSION WORKFLOW",
|
|
8301
|
+
"",
|
|
8302
|
+
"### Core Actions (15)",
|
|
8303
|
+
"",
|
|
8304
|
+
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
8305
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_context({ action: "map" })`',
|
|
8306
|
+
'\u{1F52C} **Blast radius / impact:** `kuma_context({ action: "impact", target: "<file>" })` or `kuma_context({ action: "research", scope: "<area>" })`',
|
|
8307
|
+
'\u{1F570}\uFE0F **Why this file?** `kuma_context({ action: "history", target: "<file>" })`',
|
|
8308
|
+
'\u{1F5C2}\uFE0F **Read a flow:** `kuma_context({ action: "flow", target: "<domain>" })`',
|
|
8309
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8310
|
+
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
8311
|
+
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
8312
|
+
'\u{1F4BE} **Saved findings:** `kuma_memory({ action: "research_save" })` \xB7 \u{1F50D} **Quick lookup:** `kuma_memory({ action: "search" })`',
|
|
8313
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
|
|
8314
|
+
'\u{1F4F8} **Rollback (one mechanism):** `kuma_safety({ action: "checkpoint", label: "pre-x" })` \u2192 `rollback_label` to restore',
|
|
8315
|
+
"</kuma_workflow>",
|
|
8316
|
+
"",
|
|
8317
|
+
"### Recording Rules",
|
|
8318
|
+
"",
|
|
8319
|
+
"**RECORD (high value):**",
|
|
8320
|
+
"- `gotcha` \u2014 IMMEDIATELY when bug/quirk found (saves future agents from same issue)",
|
|
8321
|
+
"- `arch_flow` \u2014 AFTER tracing COMPLETE flow (max 5 core files, skip UI/controllers/schemas)",
|
|
8322
|
+
"- `decision` \u2014 IMMEDIATELY when choosing between options (preserves rationale)",
|
|
8323
|
+
"- `research_save` \u2014 After exploring a new area (creates search cache)",
|
|
7051
8324
|
"",
|
|
7052
8325
|
"**SKIP (low value):** function/class/component nodes \u2014 grep/glob is faster",
|
|
7053
8326
|
"",
|
|
@@ -7075,6 +8348,19 @@ function generateInitMdContent() {
|
|
|
7075
8348
|
"",
|
|
7076
8349
|
"---",
|
|
7077
8350
|
"",
|
|
8351
|
+
"## \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
8352
|
+
"",
|
|
8353
|
+
"If **Matcha** is active in this workspace:",
|
|
8354
|
+
"- **Matcha** governs engineering philosophy, intent discovery, reuse checks, and review gates.",
|
|
8355
|
+
"- **Kuma** governs blast radius calculation, monorepo workspace dependencies, known gotchas memory, architecture boundary guards, and scoped test verification.",
|
|
8356
|
+
"- **Workflow:**",
|
|
8357
|
+
" 1. Use Matcha's Intent Discovery (`.agents/plan/current.md`) to define Problem / Goals / Success Criteria.",
|
|
8358
|
+
' 2. Use Kuma (`kuma_context({ action: "map" })` and `kuma_context({ action: "impact", target: "..." })`) to analyze blast radius and package dependents before modifying code.',
|
|
8359
|
+
' 3. When encountering quirks or architectural boundaries, record immediately with `kuma_memory({ action: "gotcha" | "decision" })`.',
|
|
8360
|
+
' 4. After changes, verify with `kuma_safety({ action: "verify" })` to run scoped tests.',
|
|
8361
|
+
"",
|
|
8362
|
+
"---",
|
|
8363
|
+
"",
|
|
7078
8364
|
"## \u26A0\uFE0F HONEST LIMITATIONS",
|
|
7079
8365
|
"",
|
|
7080
8366
|
"- **Node IDs are text-based** \u2014 same file recorded with slightly different paths can create duplicates.",
|
|
@@ -7090,7 +8376,9 @@ function generateInitMdContent() {
|
|
|
7090
8376
|
"",
|
|
7091
8377
|
"| Action | Description |",
|
|
7092
8378
|
"|--------|-------------|",
|
|
7093
|
-
"| `init` | Lean project brief, restore session |",
|
|
8379
|
+
"| `init` | Lean project brief, restore session, monorepo brief |",
|
|
8380
|
+
"| `map` | Repository workspace map & package dependency graph |",
|
|
8381
|
+
"| `impact` | Direct blast radius analysis (dependents, consumers, tests) |",
|
|
7094
8382
|
"| `research` | 5-step pipeline before editing unfamiliar code |",
|
|
7095
8383
|
"| `history` | Why is this file written this way (cross-session trace) |",
|
|
7096
8384
|
"| `flow` | Read a recorded architecture flow |",
|
|
@@ -7099,7 +8387,7 @@ function generateInitMdContent() {
|
|
|
7099
8387
|
"",
|
|
7100
8388
|
"| Action | Description |",
|
|
7101
8389
|
"|--------|-------------|",
|
|
7102
|
-
"| `gotcha` | Record bug/quirk (
|
|
8390
|
+
"| `gotcha` | Record bug/quirk (candidate -> active -> verified) |",
|
|
7103
8391
|
"| `arch_flow` | Record architecture flow (max 5 core files) |",
|
|
7104
8392
|
"| `decision` | Record ADR / decision rationale |",
|
|
7105
8393
|
"| `research_save` | Cache research findings |",
|
|
@@ -7109,8 +8397,8 @@ function generateInitMdContent() {
|
|
|
7109
8397
|
"",
|
|
7110
8398
|
"| Action | Description |",
|
|
7111
8399
|
"|--------|-------------|",
|
|
7112
|
-
"| `guard` | Anti-pattern detection
|
|
7113
|
-
"| `verify` | Auto-run scoped tests
|
|
8400
|
+
"| `guard` | Anti-pattern detection & architecture boundary check |",
|
|
8401
|
+
"| `verify` | Auto-run scoped tests for affected packages/files |",
|
|
7114
8402
|
"| `checkpoint` | Labeled snapshot before risky work |",
|
|
7115
8403
|
"| `rollback_label` | Restore a labeled snapshot |",
|
|
7116
8404
|
"",
|
|
@@ -7170,79 +8458,132 @@ function generateInitMdContent() {
|
|
|
7170
8458
|
].join("\n");
|
|
7171
8459
|
}
|
|
7172
8460
|
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
|
-
|
|
8461
|
+
const opencodeContent = [
|
|
8462
|
+
"---",
|
|
8463
|
+
"name: kuma-mcp",
|
|
8464
|
+
"description: Kuma MCP \u2014 safety toolkit for AI coding agents. Research, memory, and safety guard.",
|
|
8465
|
+
"---",
|
|
8466
|
+
"",
|
|
8467
|
+
"Kuma MCP tools: kuma_kuma_context (or kuma_context), kuma_kuma_memory (or kuma_memory), kuma_kuma_safety (or kuma_safety).",
|
|
8468
|
+
"Record what matters, skip what doesn't.",
|
|
8469
|
+
"",
|
|
8470
|
+
"<kuma_obedience>",
|
|
8471
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
8472
|
+
'- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
|
|
8473
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
8474
|
+
'- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
|
|
8475
|
+
"- MUST NOT call actions outside the core actions.",
|
|
8476
|
+
"</kuma_obedience>",
|
|
8477
|
+
"",
|
|
8478
|
+
"<kuma_workflow>",
|
|
8479
|
+
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
8480
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_kuma_context({ action: "map" })`',
|
|
8481
|
+
'\u{1F52C} **Blast radius / impact:** `kuma_kuma_context({ action: "impact", target: "<file>" })` or `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
8482
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8483
|
+
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
8484
|
+
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
8485
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
8486
|
+
"</kuma_workflow>",
|
|
8487
|
+
"",
|
|
8488
|
+
"\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` or `kuma_*` prefix depending on server registration.",
|
|
8489
|
+
"",
|
|
8490
|
+
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
8491
|
+
].join("\n");
|
|
8492
|
+
const skillPath = path26.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
|
|
8493
|
+
if (!results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) {
|
|
8494
|
+
try {
|
|
8495
|
+
const dir = path26.dirname(skillPath);
|
|
8496
|
+
if (fs25.existsSync(skillPath)) {
|
|
8497
|
+
const existing = fs25.readFileSync(skillPath, "utf-8");
|
|
8498
|
+
if (existing.includes("kuma-mcp")) {
|
|
8499
|
+
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
|
|
8500
|
+
} else {
|
|
8501
|
+
fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n---\n\n" + opencodeContent, "utf-8");
|
|
8502
|
+
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
|
|
8503
|
+
}
|
|
8504
|
+
} else {
|
|
8505
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8506
|
+
fs25.writeFileSync(skillPath, opencodeContent, "utf-8");
|
|
8507
|
+
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
|
|
7212
8508
|
}
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
8509
|
+
} catch (err) {
|
|
8510
|
+
results.push({
|
|
8511
|
+
type: "opencode",
|
|
8512
|
+
filePath: ".agents/skills/kuma/SKILL.md",
|
|
8513
|
+
action: "error",
|
|
8514
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8515
|
+
});
|
|
8516
|
+
}
|
|
8517
|
+
}
|
|
8518
|
+
const opencodeSkillPath = path26.resolve(root, ".opencode", "skills", "kuma", "SKILL.md");
|
|
8519
|
+
if (!results.some((r) => r.filePath === ".opencode/skills/kuma/SKILL.md")) {
|
|
8520
|
+
try {
|
|
8521
|
+
const dir = path26.dirname(opencodeSkillPath);
|
|
8522
|
+
if (fs25.existsSync(opencodeSkillPath)) {
|
|
8523
|
+
const existing = fs25.readFileSync(opencodeSkillPath, "utf-8");
|
|
8524
|
+
if (existing.includes("kuma-mcp")) {
|
|
8525
|
+
results.push({ type: "opencode", filePath: ".opencode/skills/kuma/SKILL.md", action: "skipped" });
|
|
8526
|
+
} else {
|
|
8527
|
+
fs25.writeFileSync(opencodeSkillPath, existing.trimEnd() + "\n\n---\n\n" + opencodeContent, "utf-8");
|
|
8528
|
+
results.push({ type: "opencode", filePath: ".opencode/skills/kuma/SKILL.md", action: "appended" });
|
|
8529
|
+
}
|
|
8530
|
+
} else {
|
|
8531
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8532
|
+
fs25.writeFileSync(opencodeSkillPath, opencodeContent, "utf-8");
|
|
8533
|
+
results.push({ type: "opencode", filePath: ".opencode/skills/kuma/SKILL.md", action: "created" });
|
|
8534
|
+
}
|
|
8535
|
+
} catch (err) {
|
|
8536
|
+
results.push({
|
|
8537
|
+
type: "opencode",
|
|
8538
|
+
filePath: ".opencode/skills/kuma/SKILL.md",
|
|
8539
|
+
action: "error",
|
|
8540
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8541
|
+
});
|
|
8542
|
+
}
|
|
8543
|
+
}
|
|
8544
|
+
const pluginPath = path26.resolve(root, ".opencode", "plugins", "kuma.js");
|
|
8545
|
+
if (!results.some((r) => r.filePath === ".opencode/plugins/kuma.js")) {
|
|
8546
|
+
try {
|
|
8547
|
+
const dir = path26.dirname(pluginPath);
|
|
8548
|
+
if (fs25.existsSync(pluginPath)) {
|
|
8549
|
+
const existing = fs25.readFileSync(pluginPath, "utf-8");
|
|
8550
|
+
if (existing.includes("KumaPlugin") || existing.includes("kuma")) {
|
|
8551
|
+
results.push({ type: "opencode", filePath: ".opencode/plugins/kuma.js", action: "skipped" });
|
|
8552
|
+
} else {
|
|
8553
|
+
fs25.writeFileSync(pluginPath, existing.trimEnd() + "\n\n" + opencodePluginTemplate(), "utf-8");
|
|
8554
|
+
results.push({ type: "opencode", filePath: ".opencode/plugins/kuma.js", action: "appended" });
|
|
8555
|
+
}
|
|
8556
|
+
} else {
|
|
8557
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8558
|
+
fs25.writeFileSync(pluginPath, opencodePluginTemplate(), "utf-8");
|
|
8559
|
+
results.push({ type: "opencode", filePath: ".opencode/plugins/kuma.js", action: "created" });
|
|
8560
|
+
}
|
|
8561
|
+
} catch (err) {
|
|
8562
|
+
results.push({
|
|
8563
|
+
type: "opencode",
|
|
8564
|
+
filePath: ".opencode/plugins/kuma.js",
|
|
8565
|
+
action: "error",
|
|
8566
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8567
|
+
});
|
|
7220
8568
|
}
|
|
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
8569
|
}
|
|
7229
8570
|
}
|
|
7230
8571
|
function handleCodexSecondary(root, results) {
|
|
7231
|
-
const tomlPath =
|
|
8572
|
+
const tomlPath = path26.resolve(root, ".codex/config.toml");
|
|
7232
8573
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
7233
8574
|
try {
|
|
7234
|
-
const dir =
|
|
7235
|
-
if (!
|
|
7236
|
-
if (
|
|
7237
|
-
const existingContent =
|
|
8575
|
+
const dir = path26.dirname(tomlPath);
|
|
8576
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8577
|
+
if (fs25.existsSync(tomlPath)) {
|
|
8578
|
+
const existingContent = fs25.readFileSync(tomlPath, "utf-8");
|
|
7238
8579
|
if (existingContent.includes("kuma")) {
|
|
7239
8580
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
7240
8581
|
return;
|
|
7241
8582
|
}
|
|
7242
|
-
|
|
8583
|
+
fs25.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
7243
8584
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
7244
8585
|
} else {
|
|
7245
|
-
|
|
8586
|
+
fs25.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
7246
8587
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
7247
8588
|
}
|
|
7248
8589
|
} catch (err) {
|
|
@@ -7255,11 +8596,11 @@ function handleCodexSecondary(root, results) {
|
|
|
7255
8596
|
}
|
|
7256
8597
|
}
|
|
7257
8598
|
function handleQwenSecondary(root, results) {
|
|
7258
|
-
const settingsPath =
|
|
8599
|
+
const settingsPath = path26.resolve(root, "settings.json");
|
|
7259
8600
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
7260
8601
|
try {
|
|
7261
|
-
if (
|
|
7262
|
-
const existingContent =
|
|
8602
|
+
if (fs25.existsSync(settingsPath)) {
|
|
8603
|
+
const existingContent = fs25.readFileSync(settingsPath, "utf-8");
|
|
7263
8604
|
if (existingContent.includes("kuma")) {
|
|
7264
8605
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
7265
8606
|
try {
|
|
@@ -7267,7 +8608,7 @@ function handleQwenSecondary(root, results) {
|
|
|
7267
8608
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7268
8609
|
if (!parsed.mcpServers.kuma) {
|
|
7269
8610
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7270
|
-
|
|
8611
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7271
8612
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
7272
8613
|
return;
|
|
7273
8614
|
}
|
|
@@ -7282,13 +8623,13 @@ function handleQwenSecondary(root, results) {
|
|
|
7282
8623
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7283
8624
|
if (!parsed.mcpServers.kuma) {
|
|
7284
8625
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7285
|
-
|
|
8626
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7286
8627
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
7287
8628
|
}
|
|
7288
8629
|
} catch {
|
|
7289
8630
|
}
|
|
7290
8631
|
} else {
|
|
7291
|
-
|
|
8632
|
+
fs25.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
7292
8633
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
7293
8634
|
}
|
|
7294
8635
|
} catch (err) {
|
|
@@ -7391,12 +8732,12 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
7391
8732
|
return lines.join("\n");
|
|
7392
8733
|
}
|
|
7393
8734
|
function handleAntigravityMcpConfig(root, results) {
|
|
7394
|
-
const mcpPath =
|
|
8735
|
+
const mcpPath = path26.resolve(root, ".agents/mcp_config.json");
|
|
7395
8736
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
7396
8737
|
try {
|
|
7397
|
-
const mcpDir =
|
|
7398
|
-
if (
|
|
7399
|
-
const existingContent =
|
|
8738
|
+
const mcpDir = path26.dirname(mcpPath);
|
|
8739
|
+
if (fs25.existsSync(mcpPath)) {
|
|
8740
|
+
const existingContent = fs25.readFileSync(mcpPath, "utf-8");
|
|
7400
8741
|
if (existingContent.includes("kuma")) {
|
|
7401
8742
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "skipped" });
|
|
7402
8743
|
return;
|
|
@@ -7405,14 +8746,14 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
7405
8746
|
const parsed = JSON.parse(existingContent);
|
|
7406
8747
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7407
8748
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7408
|
-
|
|
8749
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7409
8750
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
7410
8751
|
} catch {
|
|
7411
8752
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "error", error: "Invalid JSON in existing mcp_config.json" });
|
|
7412
8753
|
}
|
|
7413
8754
|
} else {
|
|
7414
|
-
if (!
|
|
7415
|
-
|
|
8755
|
+
if (!fs25.existsSync(mcpDir)) fs25.mkdirSync(mcpDir, { recursive: true });
|
|
8756
|
+
fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
7416
8757
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
7417
8758
|
}
|
|
7418
8759
|
} catch (err) {
|
|
@@ -7424,19 +8765,181 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
7424
8765
|
});
|
|
7425
8766
|
}
|
|
7426
8767
|
}
|
|
8768
|
+
function handleAntigravityRules(root, results) {
|
|
8769
|
+
const rulePath = path26.resolve(root, ".agents", "rules", "kuma.md");
|
|
8770
|
+
if (results.some((r) => r.filePath === ".agents/rules/kuma.md")) return;
|
|
8771
|
+
try {
|
|
8772
|
+
const dir = path26.dirname(rulePath);
|
|
8773
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8774
|
+
if (fs25.existsSync(rulePath)) {
|
|
8775
|
+
const existing = fs25.readFileSync(rulePath, "utf-8");
|
|
8776
|
+
if (existing.includes("Kuma MCP") || existing.includes("kuma-mcp")) {
|
|
8777
|
+
results.push({ type: "antigravity", filePath: ".agents/rules/kuma.md", action: "skipped" });
|
|
8778
|
+
return;
|
|
8779
|
+
}
|
|
8780
|
+
fs25.writeFileSync(rulePath, existing.trimEnd() + "\n\n---\n\n" + antigravityRuleTemplate(), "utf-8");
|
|
8781
|
+
results.push({ type: "antigravity", filePath: ".agents/rules/kuma.md", action: "appended" });
|
|
8782
|
+
} else {
|
|
8783
|
+
fs25.writeFileSync(rulePath, antigravityRuleTemplate(), "utf-8");
|
|
8784
|
+
results.push({ type: "antigravity", filePath: ".agents/rules/kuma.md", action: "created" });
|
|
8785
|
+
}
|
|
8786
|
+
} catch (err) {
|
|
8787
|
+
results.push({
|
|
8788
|
+
type: "antigravity",
|
|
8789
|
+
filePath: ".agents/rules/kuma.md",
|
|
8790
|
+
action: "error",
|
|
8791
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8792
|
+
});
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
function handleGeminiMd(root, results) {
|
|
8796
|
+
const geminiPath = path26.resolve(root, "GEMINI.md");
|
|
8797
|
+
if (results.some((r) => r.filePath === "GEMINI.md")) return;
|
|
8798
|
+
try {
|
|
8799
|
+
if (fs25.existsSync(geminiPath)) {
|
|
8800
|
+
const existing = fs25.readFileSync(geminiPath, "utf-8");
|
|
8801
|
+
if (existing.includes("Kuma MCP") || existing.includes("kuma_context")) {
|
|
8802
|
+
results.push({ type: "antigravity", filePath: "GEMINI.md", action: "skipped" });
|
|
8803
|
+
return;
|
|
8804
|
+
}
|
|
8805
|
+
fs25.writeFileSync(geminiPath, existing.trimEnd() + "\n\n---\n\n" + geminiTemplate(), "utf-8");
|
|
8806
|
+
results.push({ type: "antigravity", filePath: "GEMINI.md", action: "appended" });
|
|
8807
|
+
} else {
|
|
8808
|
+
fs25.writeFileSync(geminiPath, geminiTemplate(), "utf-8");
|
|
8809
|
+
results.push({ type: "antigravity", filePath: "GEMINI.md", action: "created" });
|
|
8810
|
+
}
|
|
8811
|
+
} catch (err) {
|
|
8812
|
+
results.push({
|
|
8813
|
+
type: "antigravity",
|
|
8814
|
+
filePath: "GEMINI.md",
|
|
8815
|
+
action: "error",
|
|
8816
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8817
|
+
});
|
|
8818
|
+
}
|
|
8819
|
+
}
|
|
8820
|
+
function handleAntigravitySecondary(root, results) {
|
|
8821
|
+
handleAntigravityMcpConfig(root, results);
|
|
8822
|
+
handleAntigravityRules(root, results);
|
|
8823
|
+
handleGeminiMd(root, results);
|
|
8824
|
+
}
|
|
8825
|
+
function handleWindsurfSecondary(root, results) {
|
|
8826
|
+
const rulesPath = path26.resolve(root, ".windsurfrules");
|
|
8827
|
+
if (results.some((r) => r.filePath === ".windsurfrules")) return;
|
|
8828
|
+
try {
|
|
8829
|
+
if (fs25.existsSync(rulesPath)) {
|
|
8830
|
+
const existing = fs25.readFileSync(rulesPath, "utf-8");
|
|
8831
|
+
if (existing.includes("Kuma MCP") || existing.includes("kuma_context")) {
|
|
8832
|
+
results.push({ type: "windsurf", filePath: ".windsurfrules", action: "skipped" });
|
|
8833
|
+
return;
|
|
8834
|
+
}
|
|
8835
|
+
fs25.writeFileSync(rulesPath, existing.trimEnd() + "\n\n---\n\n" + windsurfrulesTemplate(), "utf-8");
|
|
8836
|
+
results.push({ type: "windsurf", filePath: ".windsurfrules", action: "appended" });
|
|
8837
|
+
} else {
|
|
8838
|
+
fs25.writeFileSync(rulesPath, windsurfrulesTemplate(), "utf-8");
|
|
8839
|
+
results.push({ type: "windsurf", filePath: ".windsurfrules", action: "created" });
|
|
8840
|
+
}
|
|
8841
|
+
} catch (err) {
|
|
8842
|
+
results.push({
|
|
8843
|
+
type: "windsurf",
|
|
8844
|
+
filePath: ".windsurfrules",
|
|
8845
|
+
action: "error",
|
|
8846
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8847
|
+
});
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
function handleClineSecondary(root, results) {
|
|
8851
|
+
const rooPath = path26.resolve(root, ".roo", "rules", "kuma.md");
|
|
8852
|
+
if (!results.some((r) => r.filePath === ".roo/rules/kuma.md")) {
|
|
8853
|
+
try {
|
|
8854
|
+
const dir = path26.dirname(rooPath);
|
|
8855
|
+
if (fs25.existsSync(rooPath)) {
|
|
8856
|
+
const existing = fs25.readFileSync(rooPath, "utf-8");
|
|
8857
|
+
if (existing.includes("kuma-mcp") || existing.includes("Kuma MCP")) {
|
|
8858
|
+
results.push({ type: "cline", filePath: ".roo/rules/kuma.md", action: "skipped" });
|
|
8859
|
+
} else {
|
|
8860
|
+
fs25.writeFileSync(rooPath, existing.trimEnd() + "\n\n---\n\n" + rooRulesTemplate(), "utf-8");
|
|
8861
|
+
results.push({ type: "cline", filePath: ".roo/rules/kuma.md", action: "appended" });
|
|
8862
|
+
}
|
|
8863
|
+
} else {
|
|
8864
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8865
|
+
fs25.writeFileSync(rooPath, rooRulesTemplate(), "utf-8");
|
|
8866
|
+
results.push({ type: "cline", filePath: ".roo/rules/kuma.md", action: "created" });
|
|
8867
|
+
}
|
|
8868
|
+
} catch (err) {
|
|
8869
|
+
results.push({
|
|
8870
|
+
type: "cline",
|
|
8871
|
+
filePath: ".roo/rules/kuma.md",
|
|
8872
|
+
action: "error",
|
|
8873
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8874
|
+
});
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
const traePath = path26.resolve(root, ".trae", "rules", "kuma.md");
|
|
8878
|
+
if (!results.some((r) => r.filePath === ".trae/rules/kuma.md")) {
|
|
8879
|
+
try {
|
|
8880
|
+
const dir = path26.dirname(traePath);
|
|
8881
|
+
if (fs25.existsSync(traePath)) {
|
|
8882
|
+
const existing = fs25.readFileSync(traePath, "utf-8");
|
|
8883
|
+
if (existing.includes("Kuma MCP")) {
|
|
8884
|
+
results.push({ type: "cline", filePath: ".trae/rules/kuma.md", action: "skipped" });
|
|
8885
|
+
} else {
|
|
8886
|
+
fs25.writeFileSync(traePath, existing.trimEnd() + "\n\n---\n\n" + traeRulesTemplate(), "utf-8");
|
|
8887
|
+
results.push({ type: "cline", filePath: ".trae/rules/kuma.md", action: "appended" });
|
|
8888
|
+
}
|
|
8889
|
+
} else {
|
|
8890
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8891
|
+
fs25.writeFileSync(traePath, traeRulesTemplate(), "utf-8");
|
|
8892
|
+
results.push({ type: "cline", filePath: ".trae/rules/kuma.md", action: "created" });
|
|
8893
|
+
}
|
|
8894
|
+
} catch (err) {
|
|
8895
|
+
results.push({
|
|
8896
|
+
type: "cline",
|
|
8897
|
+
filePath: ".trae/rules/kuma.md",
|
|
8898
|
+
action: "error",
|
|
8899
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8900
|
+
});
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
const qoderPath = path26.resolve(root, ".qoder", "rules", "kuma.md");
|
|
8904
|
+
if (!results.some((r) => r.filePath === ".qoder/rules/kuma.md")) {
|
|
8905
|
+
try {
|
|
8906
|
+
const dir = path26.dirname(qoderPath);
|
|
8907
|
+
if (fs25.existsSync(qoderPath)) {
|
|
8908
|
+
const existing = fs25.readFileSync(qoderPath, "utf-8");
|
|
8909
|
+
if (existing.includes("Kuma MCP")) {
|
|
8910
|
+
results.push({ type: "cline", filePath: ".qoder/rules/kuma.md", action: "skipped" });
|
|
8911
|
+
} else {
|
|
8912
|
+
fs25.writeFileSync(qoderPath, existing.trimEnd() + "\n\n---\n\n" + qoderRulesTemplate(), "utf-8");
|
|
8913
|
+
results.push({ type: "cline", filePath: ".qoder/rules/kuma.md", action: "appended" });
|
|
8914
|
+
}
|
|
8915
|
+
} else {
|
|
8916
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8917
|
+
fs25.writeFileSync(qoderPath, qoderRulesTemplate(), "utf-8");
|
|
8918
|
+
results.push({ type: "cline", filePath: ".qoder/rules/kuma.md", action: "created" });
|
|
8919
|
+
}
|
|
8920
|
+
} catch (err) {
|
|
8921
|
+
results.push({
|
|
8922
|
+
type: "cline",
|
|
8923
|
+
filePath: ".qoder/rules/kuma.md",
|
|
8924
|
+
action: "error",
|
|
8925
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8926
|
+
});
|
|
8927
|
+
}
|
|
8928
|
+
}
|
|
8929
|
+
}
|
|
7427
8930
|
function handleAiderSecondary(root, results) {
|
|
7428
|
-
const ymlPath =
|
|
8931
|
+
const ymlPath = path26.resolve(root, ".aider.conf.yml");
|
|
7429
8932
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
7430
8933
|
try {
|
|
7431
8934
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
7432
|
-
if (
|
|
7433
|
-
const existingContent =
|
|
8935
|
+
if (fs25.existsSync(ymlPath)) {
|
|
8936
|
+
const existingContent = fs25.readFileSync(ymlPath, "utf-8");
|
|
7434
8937
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
7435
8938
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
7436
8939
|
return;
|
|
7437
8940
|
}
|
|
7438
8941
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
7439
|
-
|
|
8942
|
+
fs25.writeFileSync(ymlPath, newContent, "utf-8");
|
|
7440
8943
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
7441
8944
|
} else {
|
|
7442
8945
|
const content = [
|
|
@@ -7446,7 +8949,7 @@ function handleAiderSecondary(root, results) {
|
|
|
7446
8949
|
conventionsRef,
|
|
7447
8950
|
""
|
|
7448
8951
|
].join("\n");
|
|
7449
|
-
|
|
8952
|
+
fs25.writeFileSync(ymlPath, content, "utf-8");
|
|
7450
8953
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
7451
8954
|
}
|
|
7452
8955
|
} catch (err) {
|
|
@@ -7459,10 +8962,10 @@ function handleAiderSecondary(root, results) {
|
|
|
7459
8962
|
}
|
|
7460
8963
|
}
|
|
7461
8964
|
function handleCopilotSecondary(root, results) {
|
|
7462
|
-
const skillPath =
|
|
8965
|
+
const skillPath = path26.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
7463
8966
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
7464
8967
|
try {
|
|
7465
|
-
const dir =
|
|
8968
|
+
const dir = path26.dirname(skillPath);
|
|
7466
8969
|
const content = [
|
|
7467
8970
|
"---",
|
|
7468
8971
|
"name: kuma-mcp",
|
|
@@ -7473,17 +8976,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
7473
8976
|
"",
|
|
7474
8977
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
7475
8978
|
].join("\n");
|
|
7476
|
-
if (
|
|
7477
|
-
const existingContent =
|
|
8979
|
+
if (fs25.existsSync(skillPath)) {
|
|
8980
|
+
const existingContent = fs25.readFileSync(skillPath, "utf-8");
|
|
7478
8981
|
if (existingContent.includes("kuma")) {
|
|
7479
8982
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
7480
8983
|
return;
|
|
7481
8984
|
}
|
|
7482
|
-
|
|
8985
|
+
fs25.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
7483
8986
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
7484
8987
|
} else {
|
|
7485
|
-
if (!
|
|
7486
|
-
|
|
8988
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8989
|
+
fs25.writeFileSync(skillPath, content, "utf-8");
|
|
7487
8990
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
7488
8991
|
}
|
|
7489
8992
|
} catch (err) {
|
|
@@ -7496,21 +8999,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
7496
8999
|
}
|
|
7497
9000
|
}
|
|
7498
9001
|
function handleOpenclawSecondary(root, results) {
|
|
7499
|
-
const mcpPath =
|
|
9002
|
+
const mcpPath = path26.resolve(root, ".agents/mcp_config.json");
|
|
7500
9003
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
7501
9004
|
try {
|
|
7502
|
-
const dir =
|
|
7503
|
-
if (
|
|
7504
|
-
const existingContent =
|
|
9005
|
+
const dir = path26.dirname(mcpPath);
|
|
9006
|
+
if (fs25.existsSync(mcpPath)) {
|
|
9007
|
+
const existingContent = fs25.readFileSync(mcpPath, "utf-8");
|
|
7505
9008
|
if (existingContent.includes("kuma")) return;
|
|
7506
9009
|
const parsed = JSON.parse(existingContent);
|
|
7507
9010
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7508
9011
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7509
|
-
|
|
9012
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7510
9013
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
7511
9014
|
} else {
|
|
7512
|
-
if (!
|
|
7513
|
-
|
|
9015
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9016
|
+
fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
7514
9017
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
7515
9018
|
}
|
|
7516
9019
|
} catch (err) {
|
|
@@ -7523,20 +9026,20 @@ function handleOpenclawSecondary(root, results) {
|
|
|
7523
9026
|
}
|
|
7524
9027
|
}
|
|
7525
9028
|
function handleInitMdGeneration(root, results) {
|
|
7526
|
-
const initMdPath =
|
|
9029
|
+
const initMdPath = path26.resolve(root, ".kuma/init.md");
|
|
7527
9030
|
try {
|
|
7528
|
-
const kumaDir =
|
|
7529
|
-
if (!
|
|
7530
|
-
if (
|
|
7531
|
-
const existing =
|
|
9031
|
+
const kumaDir = path26.dirname(initMdPath);
|
|
9032
|
+
if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
|
|
9033
|
+
if (fs25.existsSync(initMdPath)) {
|
|
9034
|
+
const existing = fs25.readFileSync(initMdPath, "utf-8");
|
|
7532
9035
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
7533
9036
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
7534
9037
|
return;
|
|
7535
9038
|
}
|
|
7536
|
-
|
|
9039
|
+
fs25.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
7537
9040
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
7538
9041
|
} else {
|
|
7539
|
-
|
|
9042
|
+
fs25.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
7540
9043
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
7541
9044
|
}
|
|
7542
9045
|
} catch (err) {
|
|
@@ -7549,20 +9052,20 @@ function handleInitMdGeneration(root, results) {
|
|
|
7549
9052
|
}
|
|
7550
9053
|
}
|
|
7551
9054
|
function handleCodewhaleSecondary(root, results) {
|
|
7552
|
-
const mcpPath =
|
|
9055
|
+
const mcpPath = path26.resolve(root, ".codewhale/mcp.json");
|
|
7553
9056
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
7554
9057
|
try {
|
|
7555
|
-
const dir =
|
|
7556
|
-
if (
|
|
7557
|
-
const existingContent =
|
|
9058
|
+
const dir = path26.dirname(mcpPath);
|
|
9059
|
+
if (fs25.existsSync(mcpPath)) {
|
|
9060
|
+
const existingContent = fs25.readFileSync(mcpPath, "utf-8");
|
|
7558
9061
|
if (existingContent.includes("kuma")) return;
|
|
7559
9062
|
const parsed = JSON.parse(existingContent);
|
|
7560
9063
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
7561
9064
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7562
|
-
|
|
9065
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7563
9066
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
7564
9067
|
} else {
|
|
7565
|
-
if (!
|
|
9068
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
7566
9069
|
const config = {
|
|
7567
9070
|
mcpServers: {
|
|
7568
9071
|
kuma: {
|
|
@@ -7572,7 +9075,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
7572
9075
|
}
|
|
7573
9076
|
}
|
|
7574
9077
|
};
|
|
7575
|
-
|
|
9078
|
+
fs25.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7576
9079
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
7577
9080
|
}
|
|
7578
9081
|
} catch (err) {
|
|
@@ -7585,7 +9088,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
7585
9088
|
}
|
|
7586
9089
|
}
|
|
7587
9090
|
function handleClaudeSecondary(root, results) {
|
|
7588
|
-
const settingsPath =
|
|
9091
|
+
const settingsPath = path26.resolve(root, ".claude", "settings.json");
|
|
7589
9092
|
const hookBlock = {
|
|
7590
9093
|
hooks: {
|
|
7591
9094
|
PreToolUse: [
|
|
@@ -7601,8 +9104,8 @@ function handleClaudeSecondary(root, results) {
|
|
|
7601
9104
|
}
|
|
7602
9105
|
};
|
|
7603
9106
|
try {
|
|
7604
|
-
if (
|
|
7605
|
-
const existing = JSON.parse(
|
|
9107
|
+
if (fs25.existsSync(settingsPath)) {
|
|
9108
|
+
const existing = JSON.parse(fs25.readFileSync(settingsPath, "utf-8"));
|
|
7606
9109
|
const merged = {
|
|
7607
9110
|
...existing,
|
|
7608
9111
|
hooks: {
|
|
@@ -7617,12 +9120,12 @@ function handleClaudeSecondary(root, results) {
|
|
|
7617
9120
|
matchers.add(entry.matcher);
|
|
7618
9121
|
}
|
|
7619
9122
|
}
|
|
7620
|
-
|
|
9123
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
7621
9124
|
results.push({ type: "claude", filePath: ".claude/settings.json", action: "appended" });
|
|
7622
9125
|
} else {
|
|
7623
|
-
const dir =
|
|
7624
|
-
if (!
|
|
7625
|
-
|
|
9126
|
+
const dir = path26.dirname(settingsPath);
|
|
9127
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9128
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(hookBlock, null, 2) + "\n", "utf-8");
|
|
7626
9129
|
results.push({ type: "claude", filePath: ".claude/settings.json", action: "created" });
|
|
7627
9130
|
}
|
|
7628
9131
|
} catch (err) {
|
|
@@ -7633,20 +9136,57 @@ function handleClaudeSecondary(root, results) {
|
|
|
7633
9136
|
error: err instanceof Error ? err.message : String(err)
|
|
7634
9137
|
});
|
|
7635
9138
|
}
|
|
9139
|
+
const skillPath = path26.resolve(root, ".claude", "skills", "kuma", "SKILL.md");
|
|
9140
|
+
if (!results.some((r) => r.filePath === ".claude/skills/kuma/SKILL.md")) {
|
|
9141
|
+
try {
|
|
9142
|
+
const dir = path26.dirname(skillPath);
|
|
9143
|
+
const content = [
|
|
9144
|
+
"---",
|
|
9145
|
+
"name: kuma-mcp",
|
|
9146
|
+
"description: Kuma MCP \u2014 safety toolkit for AI coding agents",
|
|
9147
|
+
"---",
|
|
9148
|
+
"",
|
|
9149
|
+
KUMA_CORE_INSTRUCTIONS,
|
|
9150
|
+
"",
|
|
9151
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
|
|
9152
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
9153
|
+
].join("\n");
|
|
9154
|
+
if (fs25.existsSync(skillPath)) {
|
|
9155
|
+
const existing = fs25.readFileSync(skillPath, "utf-8");
|
|
9156
|
+
if (existing.includes("kuma-mcp")) {
|
|
9157
|
+
results.push({ type: "claude", filePath: ".claude/skills/kuma/SKILL.md", action: "skipped" });
|
|
9158
|
+
} else {
|
|
9159
|
+
fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n---\n\n" + content, "utf-8");
|
|
9160
|
+
results.push({ type: "claude", filePath: ".claude/skills/kuma/SKILL.md", action: "appended" });
|
|
9161
|
+
}
|
|
9162
|
+
} else {
|
|
9163
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9164
|
+
fs25.writeFileSync(skillPath, content, "utf-8");
|
|
9165
|
+
results.push({ type: "claude", filePath: ".claude/skills/kuma/SKILL.md", action: "created" });
|
|
9166
|
+
}
|
|
9167
|
+
} catch (err) {
|
|
9168
|
+
results.push({
|
|
9169
|
+
type: "claude",
|
|
9170
|
+
filePath: ".claude/skills/kuma/SKILL.md",
|
|
9171
|
+
action: "error",
|
|
9172
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9173
|
+
});
|
|
9174
|
+
}
|
|
9175
|
+
}
|
|
7636
9176
|
}
|
|
7637
9177
|
function handleCursorGotchaRules(root, results) {
|
|
7638
|
-
const rulesDir2 =
|
|
9178
|
+
const rulesDir2 = path26.resolve(root, ".cursor", "rules", "kuma-gotchas");
|
|
7639
9179
|
try {
|
|
7640
9180
|
let gotchas = [];
|
|
7641
9181
|
try {
|
|
7642
9182
|
gotchas = getActiveGotchas().filter((g) => g.severity === "high" || g.severity === "critical");
|
|
7643
9183
|
} catch {
|
|
7644
9184
|
}
|
|
7645
|
-
if (
|
|
7646
|
-
for (const f of
|
|
9185
|
+
if (fs25.existsSync(rulesDir2)) {
|
|
9186
|
+
for (const f of fs25.readdirSync(rulesDir2)) {
|
|
7647
9187
|
if (f.endsWith(".mdc")) {
|
|
7648
9188
|
try {
|
|
7649
|
-
|
|
9189
|
+
fs25.rmSync(path26.join(rulesDir2, f), { force: true });
|
|
7650
9190
|
} catch {
|
|
7651
9191
|
}
|
|
7652
9192
|
}
|
|
@@ -7656,7 +9196,7 @@ function handleCursorGotchaRules(root, results) {
|
|
|
7656
9196
|
results.push({ type: "cursor", filePath: ".cursor/rules/kuma-gotchas/", action: "skipped" });
|
|
7657
9197
|
return;
|
|
7658
9198
|
}
|
|
7659
|
-
|
|
9199
|
+
fs25.mkdirSync(rulesDir2, { recursive: true });
|
|
7660
9200
|
const seen = /* @__PURE__ */ new Set();
|
|
7661
9201
|
let created = 0;
|
|
7662
9202
|
for (const g of gotchas) {
|
|
@@ -7677,7 +9217,7 @@ function handleCursorGotchaRules(root, results) {
|
|
|
7677
9217
|
"`kuma_context({ action: 'history', target: '<file>' })`",
|
|
7678
9218
|
""
|
|
7679
9219
|
].join("\n");
|
|
7680
|
-
|
|
9220
|
+
fs25.writeFileSync(path26.join(rulesDir2, `${slug}.mdc`), md, "utf-8");
|
|
7681
9221
|
created++;
|
|
7682
9222
|
}
|
|
7683
9223
|
results.push({
|
|
@@ -7704,28 +9244,28 @@ function runInit(options) {
|
|
|
7704
9244
|
let agentsMdHandled = false;
|
|
7705
9245
|
for (const type of selected) {
|
|
7706
9246
|
const relativePath = configFilePath(type);
|
|
7707
|
-
const fullPath =
|
|
9247
|
+
const fullPath = path26.resolve(root, relativePath);
|
|
7708
9248
|
const getTemplate = TEMPLATES[type];
|
|
7709
9249
|
try {
|
|
7710
9250
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
7711
9251
|
agentsMdHandled = true;
|
|
7712
9252
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
7713
|
-
if (
|
|
9253
|
+
if (fs25.existsSync(fullPath)) {
|
|
7714
9254
|
if (options.skipExisting) {
|
|
7715
9255
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7716
9256
|
} else {
|
|
7717
|
-
const existingContent =
|
|
9257
|
+
const existingContent = fs25.readFileSync(fullPath, "utf-8");
|
|
7718
9258
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
7719
9259
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7720
9260
|
} else {
|
|
7721
|
-
|
|
9261
|
+
fs25.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
7722
9262
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
7723
9263
|
}
|
|
7724
9264
|
}
|
|
7725
9265
|
} else {
|
|
7726
|
-
const dir =
|
|
7727
|
-
if (!
|
|
7728
|
-
|
|
9266
|
+
const dir = path26.dirname(fullPath);
|
|
9267
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
9268
|
+
fs25.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
7729
9269
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
7730
9270
|
}
|
|
7731
9271
|
if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
|
|
@@ -7737,42 +9277,50 @@ function runInit(options) {
|
|
|
7737
9277
|
continue;
|
|
7738
9278
|
} else {
|
|
7739
9279
|
const template = getTemplate();
|
|
7740
|
-
if (
|
|
9280
|
+
if (fs25.existsSync(fullPath)) {
|
|
7741
9281
|
if (options.skipExisting) {
|
|
7742
9282
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7743
9283
|
continue;
|
|
7744
9284
|
}
|
|
7745
|
-
const existingContent =
|
|
9285
|
+
const existingContent = fs25.readFileSync(fullPath, "utf-8");
|
|
7746
9286
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
7747
9287
|
if (type === "antigravity") {
|
|
7748
|
-
|
|
9288
|
+
handleAntigravitySecondary(root, results);
|
|
7749
9289
|
} else if (type === "openclaw") {
|
|
7750
9290
|
handleOpenclawSecondary(root, results);
|
|
7751
9291
|
} else if (type === "codewhale") {
|
|
7752
9292
|
handleCodewhaleSecondary(root, results);
|
|
9293
|
+
} else if (type === "windsurf") {
|
|
9294
|
+
handleWindsurfSecondary(root, results);
|
|
9295
|
+
} else if (type === "cline") {
|
|
9296
|
+
handleClineSecondary(root, results);
|
|
7753
9297
|
}
|
|
7754
9298
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
7755
9299
|
continue;
|
|
7756
9300
|
}
|
|
7757
9301
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
7758
|
-
|
|
9302
|
+
fs25.writeFileSync(fullPath, newContent, "utf-8");
|
|
7759
9303
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
7760
9304
|
} else {
|
|
7761
|
-
const dir =
|
|
7762
|
-
if (!
|
|
7763
|
-
|
|
9305
|
+
const dir = path26.dirname(fullPath);
|
|
9306
|
+
if (!fs25.existsSync(dir)) {
|
|
9307
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
7764
9308
|
}
|
|
7765
|
-
|
|
9309
|
+
fs25.writeFileSync(fullPath, template, "utf-8");
|
|
7766
9310
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
7767
9311
|
}
|
|
7768
9312
|
if (type === "antigravity") {
|
|
7769
|
-
|
|
9313
|
+
handleAntigravitySecondary(root, results);
|
|
7770
9314
|
} else if (type === "openclaw") {
|
|
7771
9315
|
handleOpenclawSecondary(root, results);
|
|
7772
9316
|
} else if (type === "codewhale") {
|
|
7773
9317
|
handleCodewhaleSecondary(root, results);
|
|
7774
9318
|
} else if (type === "aider") {
|
|
7775
9319
|
handleAiderSecondary(root, results);
|
|
9320
|
+
} else if (type === "windsurf") {
|
|
9321
|
+
handleWindsurfSecondary(root, results);
|
|
9322
|
+
} else if (type === "cline") {
|
|
9323
|
+
handleClineSecondary(root, results);
|
|
7776
9324
|
}
|
|
7777
9325
|
}
|
|
7778
9326
|
} catch (err) {
|
|
@@ -7877,12 +9425,13 @@ var init_init = __esm({
|
|
|
7877
9425
|
'- MUST call `kuma_context({ action: "init" })` at session start.',
|
|
7878
9426
|
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
7879
9427
|
'- MUST call `kuma_safety({ action: "verify" })` after edits.',
|
|
7880
|
-
"- MUST NOT call actions outside the
|
|
9428
|
+
"- MUST NOT call actions outside the core actions.",
|
|
7881
9429
|
"</kuma_obedience>",
|
|
7882
9430
|
"",
|
|
7883
9431
|
"<kuma_workflow>",
|
|
7884
9432
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
7885
|
-
'\u{
|
|
9433
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_context({ action: "map" })`',
|
|
9434
|
+
'\u{1F52C} **Unfamiliar code / blast radius:** `kuma_context({ action: "research", scope: "<area>" })` or `kuma_context({ action: "impact", target: "<file>" })`',
|
|
7886
9435
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7887
9436
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
7888
9437
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
@@ -7923,19 +9472,19 @@ __export(agentDetector_exports, {
|
|
|
7923
9472
|
getSkillPath: () => getSkillPath,
|
|
7924
9473
|
skillExists: () => skillExists
|
|
7925
9474
|
});
|
|
7926
|
-
import
|
|
7927
|
-
import
|
|
9475
|
+
import fs26 from "fs";
|
|
9476
|
+
import path27 from "path";
|
|
7928
9477
|
function checkFile(root, filePath) {
|
|
7929
9478
|
try {
|
|
7930
|
-
return
|
|
9479
|
+
return fs26.existsSync(path27.join(root, filePath));
|
|
7931
9480
|
} catch {
|
|
7932
9481
|
return false;
|
|
7933
9482
|
}
|
|
7934
9483
|
}
|
|
7935
9484
|
function checkDir(root, dirPath) {
|
|
7936
9485
|
try {
|
|
7937
|
-
const fullPath =
|
|
7938
|
-
return
|
|
9486
|
+
const fullPath = path27.join(root, dirPath);
|
|
9487
|
+
return fs26.existsSync(fullPath) && fs26.statSync(fullPath).isDirectory();
|
|
7939
9488
|
} catch {
|
|
7940
9489
|
return false;
|
|
7941
9490
|
}
|
|
@@ -8003,7 +9552,7 @@ function getAgentLabel(type) {
|
|
|
8003
9552
|
function skillExists(type, projectRoot) {
|
|
8004
9553
|
const root = projectRoot ?? getProjectRoot();
|
|
8005
9554
|
const skillPath = getSkillPath(type);
|
|
8006
|
-
return
|
|
9555
|
+
return fs26.existsSync(path27.join(root, skillPath));
|
|
8007
9556
|
}
|
|
8008
9557
|
var AGENT_DETECTORS;
|
|
8009
9558
|
var init_agentDetector = __esm({
|
|
@@ -8034,25 +9583,25 @@ var init_agentDetector = __esm({
|
|
|
8034
9583
|
},
|
|
8035
9584
|
{
|
|
8036
9585
|
type: "antigravity",
|
|
8037
|
-
checkFiles: [],
|
|
9586
|
+
checkFiles: ["GEMINI.md", ".agents/rules/kuma.md"],
|
|
8038
9587
|
checkDirs: [".agents"],
|
|
8039
9588
|
priority: 75,
|
|
8040
9589
|
label: "Antigravity CLI (.agents/)"
|
|
8041
9590
|
},
|
|
9591
|
+
{
|
|
9592
|
+
type: "opencode",
|
|
9593
|
+
checkFiles: ["opencode.json", ".opencode/opencode.json", ".opencode/plugins/kuma.js"],
|
|
9594
|
+
checkDirs: [".opencode"],
|
|
9595
|
+
priority: 78,
|
|
9596
|
+
label: "OpenCode (.opencode/)"
|
|
9597
|
+
},
|
|
8042
9598
|
{
|
|
8043
9599
|
type: "windsurf",
|
|
8044
|
-
checkFiles: [],
|
|
9600
|
+
checkFiles: [".windsurfrules"],
|
|
8045
9601
|
checkDirs: [".windsurf"],
|
|
8046
9602
|
priority: 70,
|
|
8047
9603
|
label: "Windsurf (.windsurf/)"
|
|
8048
9604
|
},
|
|
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
9605
|
{
|
|
8057
9606
|
type: "kiro",
|
|
8058
9607
|
checkFiles: [],
|
|
@@ -8145,7 +9694,30 @@ function generateSkill(type) {
|
|
|
8145
9694
|
function getSecondaryFiles(type) {
|
|
8146
9695
|
switch (type) {
|
|
8147
9696
|
case "antigravity":
|
|
8148
|
-
return [
|
|
9697
|
+
return [
|
|
9698
|
+
{ path: ".agents/mcp_config.json", content: generateAntigravityMcpConfig() },
|
|
9699
|
+
{ path: ".agents/rules/kuma.md", content: generateAntigravityRule() },
|
|
9700
|
+
{ path: "GEMINI.md", content: generateGeminiMd() }
|
|
9701
|
+
];
|
|
9702
|
+
case "opencode":
|
|
9703
|
+
return [
|
|
9704
|
+
{ path: ".opencode/plugins/kuma.js", content: generateOpencodePlugin() },
|
|
9705
|
+
{ path: ".opencode/skills/kuma/SKILL.md", content: generateOpencodeSkill() },
|
|
9706
|
+
{ path: ".agents/skills/kuma/SKILL.md", content: generateOpencodeSkill() }
|
|
9707
|
+
];
|
|
9708
|
+
case "claude":
|
|
9709
|
+
return [
|
|
9710
|
+
{ path: ".claude/skills/kuma/SKILL.md", content: generateClaudeSkill() }
|
|
9711
|
+
];
|
|
9712
|
+
case "windsurf":
|
|
9713
|
+
return [
|
|
9714
|
+
{ path: ".windsurfrules", content: generateWindsurfrules() }
|
|
9715
|
+
];
|
|
9716
|
+
case "cline":
|
|
9717
|
+
return [
|
|
9718
|
+
{ path: ".roo/rules/kuma.md", content: generateRooRule() },
|
|
9719
|
+
{ path: ".trae/rules/kuma.md", content: generateTraeRule() }
|
|
9720
|
+
];
|
|
8149
9721
|
case "codex":
|
|
8150
9722
|
return [{ path: ".codex/config.toml", content: generateCodexConfigToml() }];
|
|
8151
9723
|
case "qwen":
|
|
@@ -8158,6 +9730,132 @@ function getSecondaryFiles(type) {
|
|
|
8158
9730
|
return [];
|
|
8159
9731
|
}
|
|
8160
9732
|
}
|
|
9733
|
+
function generateAntigravityRule() {
|
|
9734
|
+
return [
|
|
9735
|
+
"# \u{1F43B} Kuma MCP \u2014 Antigravity Rules",
|
|
9736
|
+
"",
|
|
9737
|
+
"This project uses **Kuma MCP** for runtime safety, blast radius analysis, and codebase memory.",
|
|
9738
|
+
"",
|
|
9739
|
+
BOOTSTRAP,
|
|
9740
|
+
"",
|
|
9741
|
+
"### \u{1F375} Companion: Matcha & Kuma Harmony",
|
|
9742
|
+
"If Matcha is active in this workspace:",
|
|
9743
|
+
"- **Matcha** governs engineering philosophy, intent discovery, reuse checks, and review gates.",
|
|
9744
|
+
"- **Kuma** governs blast radius calculation, monorepo workspace dependencies, known gotchas memory, architecture boundary guards, and scoped test verification.",
|
|
9745
|
+
"- 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' })`).",
|
|
9746
|
+
"",
|
|
9747
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9748
|
+
].join("\n");
|
|
9749
|
+
}
|
|
9750
|
+
function generateGeminiMd() {
|
|
9751
|
+
return [
|
|
9752
|
+
"# \u{1F43B} Kuma MCP \u2014 Workspace Instructions",
|
|
9753
|
+
"",
|
|
9754
|
+
"This workspace uses **Kuma MCP** for runtime safety, monorepo intelligence, blast radius calculation, and memory.",
|
|
9755
|
+
"",
|
|
9756
|
+
BOOTSTRAP,
|
|
9757
|
+
"",
|
|
9758
|
+
"### \u{1F375} Matcha & Kuma Harmony",
|
|
9759
|
+
"If Matcha is active: Matcha provides Intent Discovery & review; Kuma provides blast radius, gotchas memory, architecture guards, and test verification.",
|
|
9760
|
+
"",
|
|
9761
|
+
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
9762
|
+
].join("\n");
|
|
9763
|
+
}
|
|
9764
|
+
function generateOpencodePlugin() {
|
|
9765
|
+
return [
|
|
9766
|
+
"/**",
|
|
9767
|
+
" * \u{1F43B} Kuma MCP \u2014 OpenCode Plugin",
|
|
9768
|
+
" * Auto-injects gotcha warnings and safety checks before file edits and commands in OpenCode.",
|
|
9769
|
+
" */",
|
|
9770
|
+
'import fs from "node:fs";',
|
|
9771
|
+
'import path from "node:path";',
|
|
9772
|
+
"",
|
|
9773
|
+
"function getProjectRoot() {",
|
|
9774
|
+
" let curr = process.cwd();",
|
|
9775
|
+
" while (curr !== path.dirname(curr)) {",
|
|
9776
|
+
' if (fs.existsSync(path.join(curr, ".kuma")) || fs.existsSync(path.join(curr, ".git"))) {',
|
|
9777
|
+
" return curr;",
|
|
9778
|
+
" }",
|
|
9779
|
+
" curr = path.dirname(curr);",
|
|
9780
|
+
" }",
|
|
9781
|
+
" return process.cwd();",
|
|
9782
|
+
"}",
|
|
9783
|
+
"",
|
|
9784
|
+
"function getActiveGotchasForFile(filePath) {",
|
|
9785
|
+
" try {",
|
|
9786
|
+
" const root = getProjectRoot();",
|
|
9787
|
+
' const gotchasPath = path.join(root, ".kuma", "KNOWN_GOTCHAS.md");',
|
|
9788
|
+
" if (!fs.existsSync(gotchasPath)) return [];",
|
|
9789
|
+
' const content = fs.readFileSync(gotchasPath, "utf-8");',
|
|
9790
|
+
" const gotchas = [];",
|
|
9791
|
+
' const normalizedTarget = filePath.replace(/^[./]+/, "");',
|
|
9792
|
+
" const sections = content.split(/###\\s+/);",
|
|
9793
|
+
" for (const section of sections) {",
|
|
9794
|
+
" if (!section.trim()) continue;",
|
|
9795
|
+
' const lines = section.split("\\n");',
|
|
9796
|
+
' const title = lines[0] || "";',
|
|
9797
|
+
" if (title.includes(normalizedTarget) || section.includes(normalizedTarget)) {",
|
|
9798
|
+
" gotchas.push(title.trim());",
|
|
9799
|
+
" }",
|
|
9800
|
+
" }",
|
|
9801
|
+
" return gotchas;",
|
|
9802
|
+
" } catch {",
|
|
9803
|
+
" return [];",
|
|
9804
|
+
" }",
|
|
9805
|
+
"}",
|
|
9806
|
+
"",
|
|
9807
|
+
"export const KumaPlugin = async () => {",
|
|
9808
|
+
" return {",
|
|
9809
|
+
' "tool.execute.before": async (input, output) => {',
|
|
9810
|
+
' const tool = (input.tool || "").toLowerCase();',
|
|
9811
|
+
" const args = output.args || {};",
|
|
9812
|
+
' const targetPath = args.filePath || args.targetFile || args.path || args.file || "";',
|
|
9813
|
+
' if (targetPath && (tool.includes("edit") || tool.includes("write") || tool.includes("patch"))) {',
|
|
9814
|
+
" const gotchas = getActiveGotchasForFile(String(targetPath));",
|
|
9815
|
+
" if (gotchas.length > 0) {",
|
|
9816
|
+
" console.warn(",
|
|
9817
|
+
" `\\n\u{1F43B} [Kuma Alert] Active gotchas found for ${targetPath}:\\n` +",
|
|
9818
|
+
' gotchas.map((g) => ` \u26A0\uFE0F ${g}`).join("\\n") +',
|
|
9819
|
+
' `\\n\u{1F449} Remember to record any new gotchas via kuma_memory({ action: "gotcha" })\\n`',
|
|
9820
|
+
" );",
|
|
9821
|
+
" }",
|
|
9822
|
+
" }",
|
|
9823
|
+
" },",
|
|
9824
|
+
" };",
|
|
9825
|
+
"};",
|
|
9826
|
+
""
|
|
9827
|
+
].join("\n");
|
|
9828
|
+
}
|
|
9829
|
+
function generateWindsurfrules() {
|
|
9830
|
+
return [
|
|
9831
|
+
"# Kuma MCP \u2014 Windsurf Rules",
|
|
9832
|
+
"",
|
|
9833
|
+
BOOTSTRAP,
|
|
9834
|
+
"",
|
|
9835
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9836
|
+
].join("\n");
|
|
9837
|
+
}
|
|
9838
|
+
function generateRooRule() {
|
|
9839
|
+
return [
|
|
9840
|
+
"---",
|
|
9841
|
+
"description: Kuma MCP \u2014 .kuma/ is the single source of truth",
|
|
9842
|
+
"alwaysApply: true",
|
|
9843
|
+
"---",
|
|
9844
|
+
"",
|
|
9845
|
+
BOOTSTRAP,
|
|
9846
|
+
"",
|
|
9847
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9848
|
+
].join("\n");
|
|
9849
|
+
}
|
|
9850
|
+
function generateTraeRule() {
|
|
9851
|
+
return [
|
|
9852
|
+
"# Kuma MCP \u2014 Trae Rules",
|
|
9853
|
+
"",
|
|
9854
|
+
BOOTSTRAP,
|
|
9855
|
+
"",
|
|
9856
|
+
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9857
|
+
].join("\n");
|
|
9858
|
+
}
|
|
8161
9859
|
function generateClaudeSkill() {
|
|
8162
9860
|
return [
|
|
8163
9861
|
"---",
|
|
@@ -8399,12 +10097,13 @@ var init_skillGenerator = __esm({
|
|
|
8399
10097
|
'- MUST call `kuma_context({ action: "init" })` at session start.',
|
|
8400
10098
|
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
8401
10099
|
'- MUST call `kuma_safety({ action: "verify" })` after edits.',
|
|
8402
|
-
"- MUST NOT call actions outside the
|
|
10100
|
+
"- MUST NOT call actions outside the core actions.",
|
|
8403
10101
|
"</kuma_obedience>",
|
|
8404
10102
|
"",
|
|
8405
10103
|
"<kuma_workflow>",
|
|
8406
10104
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
8407
|
-
'\u{
|
|
10105
|
+
'\u{1F5FA}\uFE0F **Monorepo map:** `kuma_context({ action: "map" })`',
|
|
10106
|
+
'\u{1F52C} **Unfamiliar code / blast radius:** `kuma_context({ action: "research", scope: "<area>" })` or `kuma_context({ action: "impact", target: "<file>" })`',
|
|
8408
10107
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8409
10108
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
8410
10109
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
@@ -8446,8 +10145,8 @@ init_kumaDb();
|
|
|
8446
10145
|
init_kumaGraph();
|
|
8447
10146
|
init_kumaMemory();
|
|
8448
10147
|
init_pathValidator();
|
|
8449
|
-
import
|
|
8450
|
-
import
|
|
10148
|
+
import fs16 from "fs";
|
|
10149
|
+
import path17 from "path";
|
|
8451
10150
|
import crypto3 from "crypto";
|
|
8452
10151
|
var CONTEXT_ALIASES = {
|
|
8453
10152
|
"research": "research",
|
|
@@ -8466,7 +10165,16 @@ var CONTEXT_ALIASES = {
|
|
|
8466
10165
|
"history": "history",
|
|
8467
10166
|
"why": "history",
|
|
8468
10167
|
"touched": "history",
|
|
8469
|
-
"provenance": "history"
|
|
10168
|
+
"provenance": "history",
|
|
10169
|
+
"map": "map",
|
|
10170
|
+
"repo-map": "map",
|
|
10171
|
+
"repo_map": "map",
|
|
10172
|
+
"workspace": "map",
|
|
10173
|
+
"topology": "map",
|
|
10174
|
+
"impact": "impact",
|
|
10175
|
+
"blast-radius": "impact",
|
|
10176
|
+
"blast_radius": "impact",
|
|
10177
|
+
"blast": "impact"
|
|
8470
10178
|
};
|
|
8471
10179
|
async function handleContext(params) {
|
|
8472
10180
|
const rawAction = params.action || "init";
|
|
@@ -8481,20 +10189,41 @@ async function handleContext(params) {
|
|
|
8481
10189
|
return handleHistory(params);
|
|
8482
10190
|
case "flow":
|
|
8483
10191
|
return handleFlow(params);
|
|
10192
|
+
case "map":
|
|
10193
|
+
return handleMap(params);
|
|
10194
|
+
case "impact":
|
|
10195
|
+
return handleImpact(params);
|
|
8484
10196
|
default:
|
|
8485
|
-
return `Unknown action "${action}". Use: init, research, history, flow`;
|
|
10197
|
+
return `Unknown action "${action}". Use: init, research, history, flow, map, impact`;
|
|
8486
10198
|
}
|
|
8487
10199
|
}
|
|
8488
10200
|
async function handleInit(_params) {
|
|
8489
10201
|
sessionMemory.setGoal(_params.goal || "Exploring project");
|
|
8490
10202
|
sessionMemory.recordToolCall("kuma_context_init", {});
|
|
10203
|
+
const branch = sessionMemory.getCurrentBranch();
|
|
10204
|
+
const branchTag = branch ? ` [git: \`${branch}\`]` : "";
|
|
8491
10205
|
const lines = [
|
|
8492
10206
|
"\u{1F9E0} **Kuma \u2014 Project Brief (lean)**",
|
|
8493
10207
|
`\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
10208
|
"",
|
|
8495
|
-
`\u{1F4C1} Project: ${getProjectRoot().split("/").pop() || "unknown"}`,
|
|
10209
|
+
`\u{1F4C1} Project: ${getProjectRoot().split("/").pop() || "unknown"}${branchTag}`,
|
|
8496
10210
|
""
|
|
8497
10211
|
];
|
|
10212
|
+
const branchTransition = sessionMemory.getBranchTransition();
|
|
10213
|
+
if (branchTransition?.switched) {
|
|
10214
|
+
lines.push(`\u{1F500} **Branch switched**: from \`${branchTransition.from}\` to \`${branchTransition.to}\`. Session context updated.`);
|
|
10215
|
+
lines.push("");
|
|
10216
|
+
}
|
|
10217
|
+
try {
|
|
10218
|
+
const { getWorkspaceInfo: getWorkspaceInfo2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
10219
|
+
const wsInfo = await getWorkspaceInfo2();
|
|
10220
|
+
if (wsInfo.isWorkspace) {
|
|
10221
|
+
lines.push(`\u{1F4E6} **Workspace**: ${wsInfo.packages.length} package(s) detected (${wsInfo.type.toUpperCase()})`);
|
|
10222
|
+
lines.push(` ${wsInfo.packages.slice(0, 5).map((p) => `\`${p.name}\``).join(", ")}${wsInfo.packages.length > 5 ? ` +${wsInfo.packages.length - 5} more` : ""}`);
|
|
10223
|
+
lines.push("");
|
|
10224
|
+
}
|
|
10225
|
+
} catch {
|
|
10226
|
+
}
|
|
8498
10227
|
const goal = _params.goal || sessionMemory.getSummary().currentGoal || "";
|
|
8499
10228
|
try {
|
|
8500
10229
|
const { maybeRunAutoCleanup: maybeRunAutoCleanup2 } = await Promise.resolve().then(() => (init_kumaMaintenance(), kumaMaintenance_exports));
|
|
@@ -8519,21 +10248,74 @@ async function handleInit(_params) {
|
|
|
8519
10248
|
}
|
|
8520
10249
|
try {
|
|
8521
10250
|
const { getFreshGotchasForFile: getFreshGotchasForFile2 } = await Promise.resolve().then(() => (init_kumaInject(), kumaInject_exports));
|
|
10251
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
8522
10252
|
const modified = summary.modifiedFiles || [];
|
|
8523
10253
|
const seen = /* @__PURE__ */ new Set();
|
|
8524
|
-
|
|
10254
|
+
const collectedGotchas = [];
|
|
8525
10255
|
for (const f of modified.slice(0, 4)) {
|
|
8526
10256
|
if (!f.filePath || seen.has(f.filePath)) continue;
|
|
8527
10257
|
seen.add(f.filePath);
|
|
8528
10258
|
const gotchas = await getFreshGotchasForFile2(f.filePath, 3);
|
|
8529
10259
|
for (const g of gotchas) {
|
|
8530
|
-
|
|
8531
|
-
|
|
10260
|
+
const key = `${g.filePath}::${g.description.substring(0, 30)}`;
|
|
10261
|
+
if (!seen.has(key)) {
|
|
10262
|
+
seen.add(key);
|
|
10263
|
+
collectedGotchas.push({
|
|
10264
|
+
filePath: g.filePath,
|
|
10265
|
+
description: g.description,
|
|
10266
|
+
severity: g.severity
|
|
10267
|
+
});
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
}
|
|
10271
|
+
let totalActiveCount = 0;
|
|
10272
|
+
try {
|
|
10273
|
+
const db = await getDb2();
|
|
10274
|
+
const countStmt = db.prepare(`SELECT COUNT(*) as cnt FROM known_gotchas WHERE status IN ('active', 'verified')`);
|
|
10275
|
+
if (countStmt.step()) totalActiveCount = countStmt.getAsObject().cnt || 0;
|
|
10276
|
+
countStmt.free();
|
|
10277
|
+
if (collectedGotchas.length < 5) {
|
|
10278
|
+
const stmt = db.prepare(`
|
|
10279
|
+
SELECT file_path, description, severity
|
|
10280
|
+
FROM known_gotchas
|
|
10281
|
+
WHERE status IN ('active', 'verified')
|
|
10282
|
+
ORDER BY
|
|
10283
|
+
CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
|
|
10284
|
+
updated_at DESC
|
|
10285
|
+
LIMIT 10
|
|
10286
|
+
`);
|
|
10287
|
+
while (stmt.step()) {
|
|
10288
|
+
const row = stmt.getAsObject();
|
|
10289
|
+
const key = `${row.file_path}::${row.description.substring(0, 30)}`;
|
|
10290
|
+
if (!seen.has(key)) {
|
|
10291
|
+
seen.add(key);
|
|
10292
|
+
collectedGotchas.push({
|
|
10293
|
+
filePath: row.file_path,
|
|
10294
|
+
description: row.description,
|
|
10295
|
+
severity: row.severity
|
|
10296
|
+
});
|
|
10297
|
+
}
|
|
10298
|
+
if (collectedGotchas.length >= 5) break;
|
|
10299
|
+
}
|
|
10300
|
+
stmt.free();
|
|
10301
|
+
}
|
|
10302
|
+
} catch {
|
|
10303
|
+
}
|
|
10304
|
+
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
10305
|
+
collectedGotchas.sort((a, b) => (severityRank[a.severity] ?? 2) - (severityRank[b.severity] ?? 2));
|
|
10306
|
+
const budgetedGotchas = collectedGotchas.slice(0, 5);
|
|
10307
|
+
if (budgetedGotchas.length > 0) {
|
|
10308
|
+
lines.push("**Active Gotchas (fresh & budgeted)**");
|
|
10309
|
+
for (const g of budgetedGotchas) {
|
|
8532
10310
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
8533
|
-
|
|
10311
|
+
const desc = g.description.length <= 180 ? g.description : g.description.substring(0, 177).replace(/\s+\S*$/, "") + "...";
|
|
10312
|
+
lines.push(` ${icon} [${g.severity}] ${g.filePath} \u2014 ${desc}`);
|
|
8534
10313
|
}
|
|
10314
|
+
if (totalActiveCount > budgetedGotchas.length) {
|
|
10315
|
+
lines.push(` \u2139\uFE0F +${totalActiveCount - budgetedGotchas.length} more active gotchas in DB (query: kuma_memory({ action: 'gotcha' }))`);
|
|
10316
|
+
}
|
|
10317
|
+
lines.push("");
|
|
8535
10318
|
}
|
|
8536
|
-
if (gotchasPushed > 0) lines.push("");
|
|
8537
10319
|
} catch {
|
|
8538
10320
|
}
|
|
8539
10321
|
try {
|
|
@@ -8552,7 +10334,7 @@ async function handleInit(_params) {
|
|
|
8552
10334
|
}
|
|
8553
10335
|
} catch {
|
|
8554
10336
|
}
|
|
8555
|
-
lines.push("\u{1F4A1} Deeper: kuma_context({ action: 'history', target: '<file>' }) \xB7 Flow: kuma_context({ action: 'flow', target: '<domain>' }) \xB7
|
|
10337
|
+
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
10338
|
return lines.join("\n");
|
|
8557
10339
|
}
|
|
8558
10340
|
function detectSessionFocus(goal) {
|
|
@@ -8645,13 +10427,20 @@ async function handleResearch(params) {
|
|
|
8645
10427
|
} catch {
|
|
8646
10428
|
}
|
|
8647
10429
|
lines.push("");
|
|
8648
|
-
lines.push("**Step 4/5: Impact Analysis**");
|
|
10430
|
+
lines.push("**Step 4/5: Impact Analysis (Blast Radius)**");
|
|
8649
10431
|
try {
|
|
8650
10432
|
const impact = await analyzeImpact(scope);
|
|
8651
10433
|
lines.push(` \u{1F4CA} ${impact.references} reference(s) across ${impact.files} file(s)`);
|
|
10434
|
+
if (impact.packageName) lines.push(` \u{1F4E6} Package: \`${impact.packageName}\``);
|
|
10435
|
+
if (impact.downstreamPackages && impact.downstreamPackages.length > 0) {
|
|
10436
|
+
lines.push(` \u{1F310} Downstream consumers (${impact.downstreamPackages.length}): ${impact.downstreamPackages.slice(0, 4).join(", ")}`);
|
|
10437
|
+
}
|
|
8652
10438
|
lines.push(` \u{1F9EA} ${impact.testFiles} test file(s) related`);
|
|
8653
10439
|
lines.push(` \u26A0\uFE0F Risk: ${impact.risk.toUpperCase()}`);
|
|
8654
|
-
if (impact.
|
|
10440
|
+
if (impact.riskFlags && impact.riskFlags.length > 0) {
|
|
10441
|
+
for (const rf of impact.riskFlags) lines.push(` \u2022 ${rf}`);
|
|
10442
|
+
}
|
|
10443
|
+
if (impact.entryPoints && impact.entryPoints.length > 0) lines.push(` \u{1F3AF} Entry: ${impact.entryPoints.slice(0, 3).join(", ")}`);
|
|
8655
10444
|
} catch {
|
|
8656
10445
|
lines.push(" \u26A0\uFE0F Impact analysis unavailable");
|
|
8657
10446
|
}
|
|
@@ -8698,7 +10487,10 @@ async function handleHistory(params) {
|
|
|
8698
10487
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
8699
10488
|
lines.push(` ${icon} [${g.severity}] ${g.description}`);
|
|
8700
10489
|
if (g.triggerCommand) lines.push(` \u2328\uFE0F when running: \`${g.triggerCommand}\``);
|
|
8701
|
-
if (g.workaround)
|
|
10490
|
+
if (g.workaround) {
|
|
10491
|
+
const work = g.workaround.length <= 180 ? g.workaround : g.workaround.substring(0, 177).replace(/\s+\S*$/, "") + "...";
|
|
10492
|
+
lines.push(` \u{1F4A1} ${work}`);
|
|
10493
|
+
}
|
|
8702
10494
|
}
|
|
8703
10495
|
lines.push("");
|
|
8704
10496
|
}
|
|
@@ -8706,13 +10498,16 @@ async function handleHistory(params) {
|
|
|
8706
10498
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
8707
10499
|
const db = await getDb2();
|
|
8708
10500
|
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([`%${
|
|
10501
|
+
stmt.bind([`%${path17.basename(target)}%`]);
|
|
8710
10502
|
const resolved = [];
|
|
8711
10503
|
while (stmt.step()) resolved.push(stmt.getAsObject());
|
|
8712
10504
|
stmt.free();
|
|
8713
10505
|
if (resolved.length > 0) {
|
|
8714
10506
|
lines.push("\u2705 **Resolved gotchas** (fixed):");
|
|
8715
|
-
for (const r of resolved)
|
|
10507
|
+
for (const r of resolved) {
|
|
10508
|
+
const rdesc = r.description.length <= 140 ? r.description : r.description.substring(0, 137).replace(/\s+\S*$/, "") + "...";
|
|
10509
|
+
lines.push(` \u2705 ${rdesc}`);
|
|
10510
|
+
}
|
|
8716
10511
|
lines.push("");
|
|
8717
10512
|
}
|
|
8718
10513
|
} catch {
|
|
@@ -8735,15 +10530,28 @@ async function handleFlow(params) {
|
|
|
8735
10530
|
const { getFreshDomainFlow: getFreshDomainFlow2 } = await Promise.resolve().then(() => (init_kumaFlowCache(), kumaFlowCache_exports));
|
|
8736
10531
|
return await getFreshDomainFlow2(domain);
|
|
8737
10532
|
}
|
|
10533
|
+
async function handleMap(_params) {
|
|
10534
|
+
sessionMemory.recordToolCall("kuma_context_map", {});
|
|
10535
|
+
const { getWorkspaceInfo: getWorkspaceInfo2, formatWorkspaceMap: formatWorkspaceMap2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
|
|
10536
|
+
const wsInfo = await getWorkspaceInfo2();
|
|
10537
|
+
return formatWorkspaceMap2(wsInfo);
|
|
10538
|
+
}
|
|
10539
|
+
async function handleImpact(params) {
|
|
10540
|
+
const target = params.target || params.scope || "project";
|
|
10541
|
+
sessionMemory.recordToolCall("kuma_context_impact", { target });
|
|
10542
|
+
const { analyzeImpact: analyzeImpact2, formatImpact: formatImpact2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10543
|
+
const impact = await analyzeImpact2(target);
|
|
10544
|
+
return formatImpact2(impact);
|
|
10545
|
+
}
|
|
8738
10546
|
function computeProjectHash(scope) {
|
|
8739
10547
|
try {
|
|
8740
10548
|
const root = getProjectRoot();
|
|
8741
|
-
const files =
|
|
10549
|
+
const files = fs16.readdirSync(root).slice(0, 20);
|
|
8742
10550
|
const hash = crypto3.createHash("md5");
|
|
8743
10551
|
hash.update(scope);
|
|
8744
10552
|
for (const f of files) {
|
|
8745
10553
|
try {
|
|
8746
|
-
const stat =
|
|
10554
|
+
const stat = fs16.statSync(path17.join(root, f));
|
|
8747
10555
|
hash.update(`${f}:${stat.mtimeMs}`);
|
|
8748
10556
|
} catch {
|
|
8749
10557
|
}
|
|
@@ -8760,8 +10568,8 @@ init_kumaDb();
|
|
|
8760
10568
|
init_kumaMemory();
|
|
8761
10569
|
init_pathValidator();
|
|
8762
10570
|
init_pathValidator();
|
|
8763
|
-
import
|
|
8764
|
-
import
|
|
10571
|
+
import fs18 from "fs";
|
|
10572
|
+
import path19 from "path";
|
|
8765
10573
|
var MEMORY_ALIASES = {
|
|
8766
10574
|
"decision": "decision",
|
|
8767
10575
|
"adr": "decision",
|
|
@@ -8777,7 +10585,13 @@ var MEMORY_ALIASES = {
|
|
|
8777
10585
|
"architecture": "arch_flow",
|
|
8778
10586
|
"gotcha": "gotcha",
|
|
8779
10587
|
"gotchas": "gotcha",
|
|
8780
|
-
"quirk": "gotcha"
|
|
10588
|
+
"quirk": "gotcha",
|
|
10589
|
+
"resolve_gotcha": "gotcha",
|
|
10590
|
+
"resolve-gotcha": "gotcha",
|
|
10591
|
+
"resolve": "gotcha",
|
|
10592
|
+
"deprecate_gotcha": "gotcha",
|
|
10593
|
+
"deprecate-gotcha": "gotcha",
|
|
10594
|
+
"deprecate": "gotcha"
|
|
8781
10595
|
};
|
|
8782
10596
|
async function handleMemory(params) {
|
|
8783
10597
|
const rawAction = params.action || "search";
|
|
@@ -8800,13 +10614,15 @@ async function handleMemory(params) {
|
|
|
8800
10614
|
}
|
|
8801
10615
|
}
|
|
8802
10616
|
async function handleDecision(params) {
|
|
8803
|
-
|
|
8804
|
-
|
|
10617
|
+
const title = params.title || params.content || (params.target ? `Decision regarding ${params.target}` : params.scope ? `Decision for ${params.scope}` : "");
|
|
10618
|
+
const rationale = params.rationale || params.description || (params.content && params.content !== title ? params.content : "");
|
|
10619
|
+
if (!title) return "\u26A0\uFE0F `title` is required (or provide `content`/`target`). Use: kuma_memory({ action: 'decision', title: '...', rationale: '...' })";
|
|
10620
|
+
if (!rationale) return "\u26A0\uFE0F `rationale` is required (or provide `content`/`description`). Why did you choose this option?";
|
|
8805
10621
|
const result = await recordDecision({
|
|
8806
|
-
title
|
|
8807
|
-
context: params.context || "",
|
|
10622
|
+
title,
|
|
10623
|
+
context: params.context || (params.target ? `Target: ${params.target}` : params.scope ? `Scope: ${params.scope}` : ""),
|
|
8808
10624
|
options: [],
|
|
8809
|
-
rationale
|
|
10625
|
+
rationale,
|
|
8810
10626
|
outcome: params.outcome || "implemented",
|
|
8811
10627
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8812
10628
|
});
|
|
@@ -8844,9 +10660,9 @@ async function handleResearchSave(params) {
|
|
|
8844
10660
|
} catch {
|
|
8845
10661
|
}
|
|
8846
10662
|
try {
|
|
8847
|
-
const researchDir =
|
|
8848
|
-
if (!
|
|
8849
|
-
|
|
10663
|
+
const researchDir = path19.join(getProjectRoot(), ".kuma", "research");
|
|
10664
|
+
if (!fs18.existsSync(researchDir)) fs18.mkdirSync(researchDir, { recursive: true });
|
|
10665
|
+
fs18.writeFileSync(path19.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
8850
10666
|
} catch {
|
|
8851
10667
|
}
|
|
8852
10668
|
sessionMemory.recordMemoryAction("research_save");
|
|
@@ -8966,28 +10782,70 @@ async function handleArchFlow(params) {
|
|
|
8966
10782
|
return readLayer2("arch_flow");
|
|
8967
10783
|
}
|
|
8968
10784
|
async function handleGotchaAction(params) {
|
|
10785
|
+
const rawStatus = (params.status || "").toLowerCase().trim();
|
|
10786
|
+
const rawAction = (params.action || "").toLowerCase().trim();
|
|
10787
|
+
const isResolution = rawStatus === "resolved" || rawStatus === "resolve" || rawAction === "resolve_gotcha" || rawAction === "resolve";
|
|
10788
|
+
const isDeprecation = rawStatus === "deprecated" || rawStatus === "deprecate" || rawAction === "deprecate_gotcha" || rawAction === "deprecate";
|
|
10789
|
+
const target = params.target || params.id || params.scope;
|
|
10790
|
+
if (isResolution && target) {
|
|
10791
|
+
const { resolveGotcha: resolveGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10792
|
+
const res = await resolveGotcha2(
|
|
10793
|
+
target,
|
|
10794
|
+
params.content || params.rationale || params.description,
|
|
10795
|
+
"agent"
|
|
10796
|
+
);
|
|
10797
|
+
sessionMemory.recordMemoryAction("gotcha");
|
|
10798
|
+
return res.message;
|
|
10799
|
+
}
|
|
10800
|
+
if (isDeprecation && target) {
|
|
10801
|
+
const { deprecateGotcha: deprecateGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10802
|
+
const res = await deprecateGotcha2(
|
|
10803
|
+
target,
|
|
10804
|
+
params.content || params.rationale || params.description
|
|
10805
|
+
);
|
|
10806
|
+
sessionMemory.recordMemoryAction("gotcha");
|
|
10807
|
+
return res.message;
|
|
10808
|
+
}
|
|
8969
10809
|
const content = (params.content ?? params.description ?? "").trim();
|
|
8970
10810
|
const scope = (params.scope ?? "").trim();
|
|
10811
|
+
const validSeverities = ["low", "medium", "high", "critical"];
|
|
10812
|
+
const validStatuses = ["candidate", "active", "verified", "resolved", "deprecated"];
|
|
10813
|
+
let severity = "medium";
|
|
10814
|
+
if (params.severity && validSeverities.includes(params.severity.toLowerCase())) {
|
|
10815
|
+
severity = params.severity.toLowerCase();
|
|
10816
|
+
} else if (rawStatus && validSeverities.includes(rawStatus)) {
|
|
10817
|
+
severity = rawStatus;
|
|
10818
|
+
}
|
|
10819
|
+
let status = "active";
|
|
10820
|
+
if (rawStatus && validStatuses.includes(rawStatus)) {
|
|
10821
|
+
status = rawStatus;
|
|
10822
|
+
}
|
|
8971
10823
|
if (content && scope) {
|
|
8972
10824
|
const normalizedScope = normalizeScope(scope) || scope;
|
|
8973
10825
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
8974
10826
|
const result = await addGotcha2({
|
|
8975
10827
|
filePath: normalizedScope,
|
|
8976
10828
|
description: content,
|
|
8977
|
-
severity
|
|
10829
|
+
severity,
|
|
10830
|
+
status,
|
|
8978
10831
|
// `description` doubles as workaround only when the canonical `content` field was used.
|
|
8979
10832
|
workaround: params.content ? params.description : void 0,
|
|
8980
|
-
triggerCommand: params.trigger_command
|
|
10833
|
+
triggerCommand: params.trigger_command,
|
|
10834
|
+
scopePackage: params.scopePackage
|
|
8981
10835
|
});
|
|
8982
10836
|
sessionMemory.recordMemoryAction("gotcha");
|
|
8983
10837
|
return result;
|
|
8984
10838
|
}
|
|
8985
10839
|
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';
|
|
10840
|
+
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
10841
|
}
|
|
8988
10842
|
const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
8989
10843
|
await syncGotchasToDb2();
|
|
8990
|
-
return await listGotchas2({
|
|
10844
|
+
return await listGotchas2({
|
|
10845
|
+
filePath: params.scope,
|
|
10846
|
+
severity: validSeverities.includes(rawStatus) ? rawStatus : void 0,
|
|
10847
|
+
status: validStatuses.includes(rawStatus) ? rawStatus : void 0
|
|
10848
|
+
});
|
|
8991
10849
|
}
|
|
8992
10850
|
|
|
8993
10851
|
// src/tools/kumaSafetyTool.ts
|
|
@@ -9000,8 +10858,8 @@ init_sessionMemory();
|
|
|
9000
10858
|
init_pathValidator();
|
|
9001
10859
|
init_sessionMemory();
|
|
9002
10860
|
init_kumaShared();
|
|
9003
|
-
import
|
|
9004
|
-
import
|
|
10861
|
+
import fs19 from "fs";
|
|
10862
|
+
import path20 from "path";
|
|
9005
10863
|
import { execSync as execSync4 } from "child_process";
|
|
9006
10864
|
var SCRIPT_PATCH_PATTERNS = [
|
|
9007
10865
|
"writeFileSync",
|
|
@@ -9028,20 +10886,20 @@ function findPatchScripts(projectRoot) {
|
|
|
9028
10886
|
const warnings = [];
|
|
9029
10887
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
9030
10888
|
for (const file of recentFiles) {
|
|
9031
|
-
const ext =
|
|
10889
|
+
const ext = path20.extname(file).toLowerCase();
|
|
9032
10890
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
9033
|
-
const relativePath =
|
|
10891
|
+
const relativePath = path20.relative(projectRoot, file);
|
|
9034
10892
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
9035
10893
|
continue;
|
|
9036
10894
|
}
|
|
9037
10895
|
try {
|
|
9038
|
-
const content =
|
|
10896
|
+
const content = fs19.readFileSync(file, "utf-8").toLowerCase();
|
|
9039
10897
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
9040
10898
|
if (matchedPattern) {
|
|
9041
10899
|
warnings.push({
|
|
9042
10900
|
severity: "high",
|
|
9043
10901
|
pattern: "script-patching",
|
|
9044
|
-
message: `Created script file that modifies other files: ${
|
|
10902
|
+
message: `Created script file that modifies other files: ${path20.basename(file)}`,
|
|
9045
10903
|
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
9046
10904
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
9047
10905
|
filePath: relativePath
|
|
@@ -9075,15 +10933,15 @@ function detectBashGrepUsage() {
|
|
|
9075
10933
|
function scanRecentFiles(projectRoot) {
|
|
9076
10934
|
const recent = [];
|
|
9077
10935
|
try {
|
|
9078
|
-
const entries =
|
|
10936
|
+
const entries = fs19.readdirSync(projectRoot, { withFileTypes: true });
|
|
9079
10937
|
const now = Date.now();
|
|
9080
10938
|
const recentThreshold = 30 * 60 * 1e3;
|
|
9081
10939
|
for (const entry of entries) {
|
|
9082
10940
|
if (!entry.isFile()) continue;
|
|
9083
10941
|
try {
|
|
9084
|
-
const stat =
|
|
10942
|
+
const stat = fs19.statSync(path20.join(projectRoot, entry.name));
|
|
9085
10943
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
9086
|
-
recent.push(
|
|
10944
|
+
recent.push(path20.join(projectRoot, entry.name));
|
|
9087
10945
|
}
|
|
9088
10946
|
} catch {
|
|
9089
10947
|
continue;
|
|
@@ -9107,14 +10965,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
9107
10965
|
const prefix = line.substring(0, 2);
|
|
9108
10966
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
9109
10967
|
const file = line.substring(3).trim();
|
|
9110
|
-
const ext =
|
|
10968
|
+
const ext = path20.extname(file).toLowerCase();
|
|
9111
10969
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
9112
10970
|
const isRootLevel = !file.includes("/");
|
|
9113
10971
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
9114
10972
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
9115
|
-
const fullPath =
|
|
9116
|
-
if (
|
|
9117
|
-
const content =
|
|
10973
|
+
const fullPath = path20.join(projectRoot, file);
|
|
10974
|
+
if (fs19.existsSync(fullPath)) {
|
|
10975
|
+
const content = fs19.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
9118
10976
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
9119
10977
|
if (matchedPattern) {
|
|
9120
10978
|
warnings.push({
|
|
@@ -9137,7 +10995,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
9137
10995
|
if (deletedStdout) {
|
|
9138
10996
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
9139
10997
|
for (const file of deletedFiles) {
|
|
9140
|
-
const ext =
|
|
10998
|
+
const ext = path20.extname(file).toLowerCase();
|
|
9141
10999
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
9142
11000
|
warnings.push({
|
|
9143
11001
|
severity: "high",
|
|
@@ -9199,21 +11057,21 @@ function detectAllAntiPatterns() {
|
|
|
9199
11057
|
// src/engine/contextSnapshot.ts
|
|
9200
11058
|
init_sessionMemory();
|
|
9201
11059
|
init_pathValidator();
|
|
9202
|
-
import
|
|
9203
|
-
import
|
|
11060
|
+
import fs20 from "fs";
|
|
11061
|
+
import path21 from "path";
|
|
9204
11062
|
import { execSync as execSync5 } from "child_process";
|
|
9205
11063
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
9206
11064
|
function snapshotDir() {
|
|
9207
|
-
return
|
|
11065
|
+
return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
9208
11066
|
}
|
|
9209
11067
|
function ensureSnapshotDir() {
|
|
9210
11068
|
const dir = snapshotDir();
|
|
9211
|
-
if (!
|
|
9212
|
-
|
|
11069
|
+
if (!fs20.existsSync(dir)) {
|
|
11070
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
9213
11071
|
}
|
|
9214
11072
|
}
|
|
9215
11073
|
function snapshotFilePath(timestamp) {
|
|
9216
|
-
return
|
|
11074
|
+
return path21.join(snapshotDir(), `${timestamp}.json`);
|
|
9217
11075
|
}
|
|
9218
11076
|
function saveSnapshot(goal) {
|
|
9219
11077
|
try {
|
|
@@ -9234,7 +11092,7 @@ function saveSnapshot(goal) {
|
|
|
9234
11092
|
hasConventions: !!summary.hasConventions
|
|
9235
11093
|
};
|
|
9236
11094
|
try {
|
|
9237
|
-
|
|
11095
|
+
fs20.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
9238
11096
|
} catch {
|
|
9239
11097
|
}
|
|
9240
11098
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -9294,6 +11152,22 @@ async function handleKumaGuard(params) {
|
|
|
9294
11152
|
if (check === "all" || check === "anti-pattern") {
|
|
9295
11153
|
warnings.push(...detectAllAntiPatterns());
|
|
9296
11154
|
}
|
|
11155
|
+
if (check === "all" || check === "architecture") {
|
|
11156
|
+
try {
|
|
11157
|
+
const { checkArchitectureBoundaries: checkArchitectureBoundaries2 } = await Promise.resolve().then(() => (init_architectureGuard(), architectureGuard_exports));
|
|
11158
|
+
const modifiedPaths = stats.modifiedFiles.map((f) => f.filePath);
|
|
11159
|
+
const violations = await checkArchitectureBoundaries2(modifiedPaths.length > 0 ? modifiedPaths : void 0);
|
|
11160
|
+
for (const v of violations) {
|
|
11161
|
+
warnings.push({
|
|
11162
|
+
severity: v.severity,
|
|
11163
|
+
pattern: `arch-boundary:${v.rule}`,
|
|
11164
|
+
message: `Architecture violation in ${v.sourceFile}: ${v.targetImport}`,
|
|
11165
|
+
suggestion: v.suggestion
|
|
11166
|
+
});
|
|
11167
|
+
}
|
|
11168
|
+
} catch {
|
|
11169
|
+
}
|
|
11170
|
+
}
|
|
9297
11171
|
const loop = check === "all" || check === "loop" ? sessionMemory.detectLoop() : { isLooping: false };
|
|
9298
11172
|
if (loop.isLooping) {
|
|
9299
11173
|
warnings.push({
|
|
@@ -9475,13 +11349,13 @@ async function handleRollbackLabel(params) {
|
|
|
9475
11349
|
const { rollbackToCheckpoint: rollbackToCheckpoint2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
|
|
9476
11350
|
return await rollbackToCheckpoint2(params.label);
|
|
9477
11351
|
}
|
|
9478
|
-
var _lastVerifyCall = 0;
|
|
9479
11352
|
var VERIFY_COOLDOWN_MS = 3e4;
|
|
11353
|
+
var _lastVerifyCall = 0;
|
|
9480
11354
|
async function handleVerify(params) {
|
|
9481
11355
|
const now = Date.now();
|
|
9482
|
-
if (
|
|
11356
|
+
if (now - _lastVerifyCall < VERIFY_COOLDOWN_MS && !params.force) {
|
|
9483
11357
|
const remaining = Math.ceil((VERIFY_COOLDOWN_MS - (now - _lastVerifyCall)) / 1e3);
|
|
9484
|
-
return `\u23F3
|
|
11358
|
+
return `\u23F3 Verification cooldown active. Please wait ${remaining}s before calling verify again, or use force: true.`;
|
|
9485
11359
|
}
|
|
9486
11360
|
_lastVerifyCall = now;
|
|
9487
11361
|
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
@@ -9500,12 +11374,16 @@ async function handleVerify(params) {
|
|
|
9500
11374
|
|
|
9501
11375
|
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
|
|
9502
11376
|
}
|
|
9503
|
-
|
|
11377
|
+
const effectiveScope = params.scope || params.target;
|
|
11378
|
+
const timeoutMs = typeof params.timeoutMs === "number" && params.timeoutMs > 0 ? params.timeoutMs : typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 : process.env.KUMA_VERIFY_TIMEOUT_MS ? parseInt(process.env.KUMA_VERIFY_TIMEOUT_MS, 10) : 6e4;
|
|
11379
|
+
sessionMemory.recordToolCall("kuma_safety_verify", { scope: effectiveScope, timeoutMs });
|
|
9504
11380
|
const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
|
|
9505
11381
|
const verifyResult = await runAutoVerification2({
|
|
9506
|
-
scope:
|
|
11382
|
+
scope: effectiveScope,
|
|
11383
|
+
target: params.target,
|
|
11384
|
+
command: params.command,
|
|
9507
11385
|
force: params.force,
|
|
9508
|
-
timeoutMs
|
|
11386
|
+
timeoutMs
|
|
9509
11387
|
});
|
|
9510
11388
|
return verifyResult + recordingWarning;
|
|
9511
11389
|
}
|
|
@@ -9573,13 +11451,13 @@ var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
|
|
|
9573
11451
|
function registerAllTools(server) {
|
|
9574
11452
|
server.tool(
|
|
9575
11453
|
"kuma_context",
|
|
9576
|
-
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow).",
|
|
11454
|
+
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow), `map` (repository topology), `impact` (blast radius analysis).",
|
|
9577
11455
|
{
|
|
9578
|
-
action: z.enum(["init", "research", "history", "flow"]).describe(
|
|
9579
|
-
"init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow"
|
|
11456
|
+
action: z.enum(["init", "research", "history", "flow", "map", "impact"]).describe(
|
|
11457
|
+
"init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow, map=repo topology, impact=blast radius"
|
|
9580
11458
|
),
|
|
9581
|
-
scope: z.string().optional().describe("Research scope for
|
|
9582
|
-
target: z.string().optional().describe("Target symbol/file for history/flow"),
|
|
11459
|
+
scope: z.string().optional().describe("Research scope or target for impact"),
|
|
11460
|
+
target: z.string().optional().describe("Target symbol/file for history/flow/impact"),
|
|
9583
11461
|
goal: z.string().optional().describe("Current goal (for init)")
|
|
9584
11462
|
},
|
|
9585
11463
|
async (params) => {
|
|
@@ -9605,17 +11483,20 @@ function registerAllTools(server) {
|
|
|
9605
11483
|
"gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup"
|
|
9606
11484
|
),
|
|
9607
11485
|
scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
|
|
11486
|
+
target: z.string().optional().describe("Target file, gotcha ID, or component"),
|
|
9608
11487
|
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
|
|
9609
11488
|
query: z.string().optional().describe("Search query for search action"),
|
|
9610
11489
|
content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
|
|
9611
11490
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
9612
11491
|
confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
|
|
9613
|
-
title: z.string().optional().describe("Decision title (
|
|
11492
|
+
title: z.string().optional().describe("Decision title (optional if content/target provided)"),
|
|
9614
11493
|
context: z.string().optional().describe("Decision context"),
|
|
9615
|
-
rationale: z.string().optional().describe("Decision rationale (
|
|
11494
|
+
rationale: z.string().optional().describe("Decision rationale (optional if content provided)"),
|
|
9616
11495
|
outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
|
|
9617
|
-
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
9618
|
-
|
|
11496
|
+
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical) or status (resolved|deprecated)"),
|
|
11497
|
+
severity: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11498
|
+
description: z.string().optional().describe("Gotcha workaround / rationale"),
|
|
11499
|
+
id: z.union([z.number(), z.string()]).optional().describe("Gotcha ID for resolution or deprecation"),
|
|
9619
11500
|
limit: z.number().min(1).max(100).optional().describe("Result limit for search")
|
|
9620
11501
|
},
|
|
9621
11502
|
async (params) => {
|
|
@@ -9624,6 +11505,7 @@ function registerAllTools(server) {
|
|
|
9624
11505
|
const text = await handleMemory({
|
|
9625
11506
|
action: params.action,
|
|
9626
11507
|
scope: params.scope,
|
|
11508
|
+
target: params.target,
|
|
9627
11509
|
query: params.query,
|
|
9628
11510
|
content: params.content,
|
|
9629
11511
|
record: params.record,
|
|
@@ -9634,8 +11516,10 @@ function registerAllTools(server) {
|
|
|
9634
11516
|
outcome: params.outcome,
|
|
9635
11517
|
limit: params.limit,
|
|
9636
11518
|
status: params.status,
|
|
11519
|
+
severity: params.severity,
|
|
9637
11520
|
description: params.description,
|
|
9638
|
-
trigger_command: params.trigger_command
|
|
11521
|
+
trigger_command: params.trigger_command,
|
|
11522
|
+
id: params.id
|
|
9639
11523
|
});
|
|
9640
11524
|
return { content: [{ type: "text", text }] };
|
|
9641
11525
|
} catch (err) {
|
|
@@ -9651,7 +11535,11 @@ function registerAllTools(server) {
|
|
|
9651
11535
|
"guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
|
|
9652
11536
|
),
|
|
9653
11537
|
scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
|
|
9654
|
-
|
|
11538
|
+
target: z.string().optional().describe("Target test file or scope for verify"),
|
|
11539
|
+
command: z.string().optional().describe("Custom test command to run during verify"),
|
|
11540
|
+
timeoutMs: z.number().optional().describe("Custom timeout in milliseconds for verify (default: 60000)"),
|
|
11541
|
+
timeout: z.number().optional().describe("Custom timeout in seconds for verify"),
|
|
11542
|
+
force: z.boolean().optional().describe("Force re-run even if cache is fresh or rate limited (verify)"),
|
|
9655
11543
|
guardGoal: z.string().optional().describe("Goal for guard check"),
|
|
9656
11544
|
guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
|
|
9657
11545
|
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
|
|
@@ -9665,6 +11553,10 @@ function registerAllTools(server) {
|
|
|
9665
11553
|
guardGoal: params.guardGoal,
|
|
9666
11554
|
guardCheck: params.guardCheck,
|
|
9667
11555
|
scope: params.scope,
|
|
11556
|
+
target: params.target,
|
|
11557
|
+
command: params.command,
|
|
11558
|
+
timeoutMs: params.timeoutMs,
|
|
11559
|
+
timeout: params.timeout,
|
|
9668
11560
|
force: params.force,
|
|
9669
11561
|
label: params.label,
|
|
9670
11562
|
description: params.description
|
|
@@ -9754,14 +11646,14 @@ async function main() {
|
|
|
9754
11646
|
} catch {
|
|
9755
11647
|
}
|
|
9756
11648
|
try {
|
|
9757
|
-
const
|
|
9758
|
-
const
|
|
9759
|
-
const lockDir =
|
|
9760
|
-
if (
|
|
9761
|
-
const pidFile =
|
|
9762
|
-
if (
|
|
11649
|
+
const path28 = await import("path");
|
|
11650
|
+
const fs27 = await import("fs");
|
|
11651
|
+
const lockDir = path28.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
11652
|
+
if (fs27.existsSync(lockDir)) {
|
|
11653
|
+
const pidFile = path28.join(lockDir, "pid");
|
|
11654
|
+
if (fs27.existsSync(pidFile)) {
|
|
9763
11655
|
try {
|
|
9764
|
-
const pid = parseInt(
|
|
11656
|
+
const pid = parseInt(fs27.readFileSync(pidFile, "utf-8"), 10);
|
|
9765
11657
|
if (pid && pid !== process.pid) {
|
|
9766
11658
|
try {
|
|
9767
11659
|
process.kill(-pid, "SIGKILL");
|
|
@@ -9777,7 +11669,7 @@ async function main() {
|
|
|
9777
11669
|
} catch {
|
|
9778
11670
|
}
|
|
9779
11671
|
}
|
|
9780
|
-
|
|
11672
|
+
fs27.rmSync(lockDir, { recursive: true, force: true });
|
|
9781
11673
|
}
|
|
9782
11674
|
if (killedCount === 0) {
|
|
9783
11675
|
console.error("\u2705 No running verifications found to kill.");
|
|
@@ -9976,23 +11868,23 @@ async function main() {
|
|
|
9976
11868
|
});
|
|
9977
11869
|
const output = formatInitResults(results);
|
|
9978
11870
|
console.log(output);
|
|
9979
|
-
const
|
|
9980
|
-
const
|
|
9981
|
-
const matchaSkills =
|
|
9982
|
-
const matchaAgents =
|
|
11871
|
+
const fs27 = await import("fs");
|
|
11872
|
+
const path28 = await import("path");
|
|
11873
|
+
const matchaSkills = path28.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
11874
|
+
const matchaAgents = path28.resolve(
|
|
9983
11875
|
process.cwd(),
|
|
9984
11876
|
".agents/skills/matcha/SKILL.md"
|
|
9985
11877
|
);
|
|
9986
|
-
const matchaRootSkills =
|
|
11878
|
+
const matchaRootSkills = path28.resolve(
|
|
9987
11879
|
process.cwd(),
|
|
9988
11880
|
"skills/matcha/SKILL.md"
|
|
9989
11881
|
);
|
|
9990
|
-
const matchaAgentsMd =
|
|
9991
|
-
const matchaWindsurfRules =
|
|
11882
|
+
const matchaAgentsMd = path28.resolve(process.cwd(), "AGENTS.md");
|
|
11883
|
+
const matchaWindsurfRules = path28.resolve(
|
|
9992
11884
|
process.cwd(),
|
|
9993
11885
|
".windsurf"
|
|
9994
11886
|
);
|
|
9995
|
-
if (
|
|
11887
|
+
if (fs27.existsSync(matchaSkills) || fs27.existsSync(matchaAgents) || fs27.existsSync(matchaRootSkills) || fs27.existsSync(matchaAgentsMd) || fs27.existsSync(matchaWindsurfRules)) {
|
|
9996
11888
|
console.error(
|
|
9997
11889
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
9998
11890
|
);
|
|
@@ -10006,13 +11898,13 @@ async function main() {
|
|
|
10006
11898
|
(async () => {
|
|
10007
11899
|
try {
|
|
10008
11900
|
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
|
-
|
|
11901
|
+
const fs27 = await import("fs");
|
|
11902
|
+
const path28 = await import("path");
|
|
11903
|
+
const initMdPath = path28.resolve(process.cwd(), ".kuma/init.md");
|
|
11904
|
+
if (!fs27.existsSync(initMdPath)) {
|
|
11905
|
+
const kumaDir = path28.dirname(initMdPath);
|
|
11906
|
+
if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
|
|
11907
|
+
fs27.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
|
|
10016
11908
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
10017
11909
|
}
|
|
10018
11910
|
} catch (err) {
|
|
@@ -10023,8 +11915,8 @@ async function main() {
|
|
|
10023
11915
|
try {
|
|
10024
11916
|
const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
|
|
10025
11917
|
const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
|
|
10026
|
-
const
|
|
10027
|
-
const
|
|
11918
|
+
const fs27 = await import("fs");
|
|
11919
|
+
const path28 = await import("path");
|
|
10028
11920
|
const detection = detectAgent2();
|
|
10029
11921
|
if (!detection.primary) {
|
|
10030
11922
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -10032,22 +11924,22 @@ async function main() {
|
|
|
10032
11924
|
}
|
|
10033
11925
|
const agentType = detection.primary;
|
|
10034
11926
|
const skillPath = getSkillPath2(agentType);
|
|
10035
|
-
const fullPath =
|
|
10036
|
-
if (
|
|
11927
|
+
const fullPath = path28.resolve(process.cwd(), skillPath);
|
|
11928
|
+
if (fs27.existsSync(fullPath)) {
|
|
10037
11929
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
|
|
10038
11930
|
return;
|
|
10039
11931
|
}
|
|
10040
|
-
const dir =
|
|
10041
|
-
if (!
|
|
10042
|
-
|
|
11932
|
+
const dir = path28.dirname(fullPath);
|
|
11933
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
11934
|
+
fs27.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
|
|
10043
11935
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
|
|
10044
11936
|
const secondaryFiles = getSecondaryFiles2(agentType);
|
|
10045
11937
|
for (const sf of secondaryFiles) {
|
|
10046
|
-
const sfPath =
|
|
10047
|
-
const sfDir =
|
|
10048
|
-
if (!
|
|
10049
|
-
if (!
|
|
10050
|
-
|
|
11938
|
+
const sfPath = path28.resolve(process.cwd(), sf.path);
|
|
11939
|
+
const sfDir = path28.dirname(sfPath);
|
|
11940
|
+
if (!fs27.existsSync(sfPath)) {
|
|
11941
|
+
if (!fs27.existsSync(sfDir)) fs27.mkdirSync(sfDir, { recursive: true });
|
|
11942
|
+
fs27.writeFileSync(sfPath, sf.content, "utf-8");
|
|
10051
11943
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
10052
11944
|
}
|
|
10053
11945
|
}
|