@plumpslabs/kuma 2.4.3 → 2.4.4

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