@plumpslabs/kuma 2.4.3 → 2.4.5

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