@plumpslabs/kuma 2.3.22 → 2.3.23

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
@@ -311,7 +311,7 @@ function createSchema(db) {
311
311
  id INTEGER PRIMARY KEY AUTOINCREMENT,
312
312
  source_id TEXT NOT NULL REFERENCES nodes(id),
313
313
  target_id TEXT NOT NULL REFERENCES nodes(id),
314
- 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')),
314
+ 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')),
315
315
  weight REAL DEFAULT 1.0,
316
316
  metadata TEXT DEFAULT '{}',
317
317
  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
@@ -326,7 +326,7 @@ function createSchema(db) {
326
326
  id INTEGER PRIMARY KEY AUTOINCREMENT,
327
327
  source_id TEXT NOT NULL REFERENCES nodes(id),
328
328
  target_id TEXT NOT NULL REFERENCES nodes(id),
329
- 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')),
329
+ 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')),
330
330
  weight REAL DEFAULT 1.0,
331
331
  metadata TEXT DEFAULT '{}',
332
332
  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
@@ -717,22 +717,20 @@ async function listResearchCache() {
717
717
  async function recordChange(entry) {
718
718
  try {
719
719
  const db = await getDb();
720
- const root = process.cwd();
721
- const fullPath = path2.resolve(root, entry.filePath);
722
- let previousContent = null;
723
- if (entry.changeType === "modified" && fs2.existsSync(fullPath)) {
724
- try {
725
- previousContent = fs2.readFileSync(fullPath, "utf-8");
726
- } catch {
727
- }
728
- } else if (entry.changeType === "created") {
729
- previousContent = "";
730
- }
731
720
  db.run(
732
721
  `INSERT INTO change_log (session_id, file_path, change_type, symbol, diff_summary, git_commit_hash, previous_content) VALUES (?, ?, ?, ?, ?, ?, ?)`,
733
- [entry.sessionId ?? null, entry.filePath, entry.changeType, entry.symbol ?? null, entry.diffSummary ?? null, entry.gitCommitHash ?? null, previousContent]
722
+ [entry.sessionId ?? null, entry.filePath, entry.changeType, entry.symbol ?? null, entry.diffSummary ?? null, entry.gitCommitHash ?? null, null]
734
723
  );
735
724
  saveDb();
725
+ try {
726
+ const countResult = db.exec("SELECT COUNT(*) FROM change_log");
727
+ const count = countResult[0]?.values?.[0] || 0;
728
+ if (count > 600) {
729
+ db.exec("DELETE FROM change_log WHERE id IN (SELECT id FROM change_log ORDER BY created_at ASC LIMIT 200)");
730
+ saveDb();
731
+ }
732
+ } catch {
733
+ }
736
734
  } catch (err) {
737
735
  console.error(`[KumaDB] Failed to record change: ${err}`);
738
736
  }
@@ -1077,16 +1075,69 @@ async function runGarbageCollection() {
1077
1075
  try {
1078
1076
  const db = await getDb();
1079
1077
  let removed = 0;
1078
+ const getCount = (sql) => {
1079
+ try {
1080
+ const result = db.exec(sql);
1081
+ return result[0]?.values?.[0] || 0;
1082
+ } catch {
1083
+ return 0;
1084
+ }
1085
+ };
1080
1086
  const orphanResult = db.exec(`DELETE FROM nodes WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges) AND file_path IS NULL AND updated_at < strftime('%s','now','-30 days')`);
1081
1087
  removed += orphanResult[0]?.values?.length || 0;
1088
+ const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND updated_at < strftime('%s','now','-30 days')`);
1089
+ removed += staleEdges[0]?.values?.length || 0;
1082
1090
  db.exec(`DELETE FROM tool_calls WHERE created_at < strftime('%s','now','-90 days')`);
1083
1091
  db.exec(`DELETE FROM experiences WHERE created_at < strftime('%s','now','-90 days')`);
1092
+ const changeCount = getCount("SELECT COUNT(*) FROM change_log");
1093
+ if (changeCount > 500) {
1094
+ db.exec(`DELETE FROM change_log WHERE id IN (SELECT id FROM change_log ORDER BY created_at ASC LIMIT ${changeCount - 500})`);
1095
+ }
1096
+ const verifCount = getCount("SELECT COUNT(*) FROM verifications");
1097
+ if (verifCount > 100) {
1098
+ db.exec(`DELETE FROM verifications WHERE id IN (SELECT id FROM verifications ORDER BY created_at ASC LIMIT ${verifCount - 100})`);
1099
+ }
1100
+ const healthCount = getCount("SELECT COUNT(*) FROM health_snapshots");
1101
+ if (healthCount > 50) {
1102
+ db.exec(`DELETE FROM health_snapshots WHERE id IN (SELECT id FROM health_snapshots ORDER BY created_at ASC LIMIT ${healthCount - 50})`);
1103
+ }
1104
+ const auditCount = getCount("SELECT COUNT(*) FROM safety_audit");
1105
+ if (auditCount > 200) {
1106
+ db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY created_at ASC LIMIT ${auditCount - 200})`);
1107
+ }
1108
+ db.exec(`DELETE FROM research_cache WHERE updated_at < strftime('%s','now','-60 days')`);
1109
+ const secCount = getCount("SELECT COUNT(*) FROM security_findings");
1110
+ if (secCount > 100) {
1111
+ db.exec(`DELETE FROM security_findings WHERE id IN (SELECT id FROM security_findings ORDER BY created_at ASC LIMIT ${secCount - 100})`);
1112
+ }
1113
+ const ctxCount = getCount("SELECT COUNT(*) FROM context_notes");
1114
+ if (ctxCount > 200) {
1115
+ db.exec(`DELETE FROM context_notes WHERE id IN (SELECT id FROM context_notes ORDER BY created_at ASC LIMIT ${ctxCount - 200})`);
1116
+ }
1117
+ const benchCount = getCount("SELECT COUNT(*) FROM benchmarks");
1118
+ if (benchCount > 100) {
1119
+ db.exec(`DELETE FROM benchmarks WHERE id IN (SELECT id FROM benchmarks ORDER BY created_at ASC LIMIT ${benchCount - 100})`);
1120
+ }
1121
+ const decCount = getCount("SELECT COUNT(*) FROM decision_log");
1122
+ if (decCount > 200) {
1123
+ db.exec(`DELETE FROM decision_log WHERE id IN (SELECT id FROM decision_log ORDER BY created_at ASC LIMIT ${decCount - 200})`);
1124
+ }
1125
+ 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 '%::%'`);
1126
+ db.exec(`DELETE FROM scratch_entries WHERE created_at < strftime('%s','now','-7 days')`);
1127
+ const costCount = getCount("SELECT COUNT(*) FROM cost_tracking");
1128
+ if (costCount > 500) {
1129
+ db.exec(`DELETE FROM cost_tracking WHERE id IN (SELECT id FROM cost_tracking ORDER BY created_at ASC LIMIT ${costCount - 500})`);
1130
+ }
1131
+ const sessCount = getCount("SELECT COUNT(*) FROM sessions");
1132
+ if (sessCount > 50) {
1133
+ db.exec(`DELETE FROM sessions WHERE id IN (SELECT id FROM sessions ORDER BY started_at ASC LIMIT ${sessCount - 50})`);
1134
+ }
1084
1135
  try {
1085
1136
  db.exec("VACUUM");
1086
1137
  } catch {
1087
1138
  }
1088
1139
  saveDb();
1089
- return `\u{1F9F9} GC complete. Removed orphaned data. Database vacuumed.`;
1140
+ return `\u{1F9F9} GC complete \u2014 cleaned 17 tables. Database vacuumed.`;
1090
1141
  } catch (err) {
1091
1142
  return `\u274C GC failed: ${err}`;
1092
1143
  }
@@ -1324,7 +1375,9 @@ var init_sessionMemory = __esm({
1324
1375
  searchResults: new Map(parsed.searchResults || []),
1325
1376
  dependencyGraph: new Map(parsed.dependencyGraph || []),
1326
1377
  toolCalls: parsed.toolCalls || [],
1327
- conventions: parsed.conventions
1378
+ conventions: parsed.conventions,
1379
+ recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
1380
+ metrics: parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
1328
1381
  };
1329
1382
  this.initialized = true;
1330
1383
  console.error(`[SessionMemory] Loaded persistent session memory.json`);
@@ -1342,7 +1395,9 @@ var init_sessionMemory = __esm({
1342
1395
  failedFiles: /* @__PURE__ */ new Map(),
1343
1396
  searchResults: /* @__PURE__ */ new Map(),
1344
1397
  dependencyGraph: /* @__PURE__ */ new Map(),
1345
- toolCalls: []
1398
+ toolCalls: [],
1399
+ recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
1400
+ metrics: { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
1346
1401
  };
1347
1402
  this.initialized = true;
1348
1403
  this.save();
@@ -1490,7 +1545,9 @@ No unresolved issues.`;
1490
1545
  searchResults: Array.from(this.state.searchResults.entries()),
1491
1546
  dependencyGraph: Array.from(this.state.dependencyGraph.entries()),
1492
1547
  toolCalls: this.state.toolCalls,
1493
- conventions: this.state.conventions
1548
+ conventions: this.state.conventions,
1549
+ recordings: this.state.recordings,
1550
+ metrics: this.state.metrics
1494
1551
  };
1495
1552
  fs3.writeFileSync(path3.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
1496
1553
  } catch (err) {
@@ -1516,6 +1573,10 @@ No unresolved issues.`;
1516
1573
  existing.modifiedAt = Date.now();
1517
1574
  existing.status = "modified";
1518
1575
  } else {
1576
+ if (this.state.modifiedFiles.size >= 200) {
1577
+ const oldest = this.state.modifiedFiles.keys().next().value;
1578
+ if (oldest) this.state.modifiedFiles.delete(oldest);
1579
+ }
1519
1580
  this.state.modifiedFiles.set(filePath, {
1520
1581
  filePath,
1521
1582
  modifiedAt: Date.now(),
@@ -1547,7 +1608,14 @@ No unresolved issues.`;
1547
1608
  timestamp: Date.now(),
1548
1609
  resolved: false
1549
1610
  });
1611
+ if (failures.length > 10) {
1612
+ failures.splice(0, failures.length - 10);
1613
+ }
1550
1614
  this.state.failedFiles.set(task, failures);
1615
+ if (this.state.failedFiles.size > 100) {
1616
+ const oldest = this.state.failedFiles.keys().next().value;
1617
+ if (oldest) this.state.failedFiles.delete(oldest);
1618
+ }
1551
1619
  this.save();
1552
1620
  this.ensureMemoriesDir();
1553
1621
  this.writeMemoryFile("known-issues", this.generateKnownIssuesMd());
@@ -1572,6 +1640,10 @@ No unresolved issues.`;
1572
1640
  }
1573
1641
  addSearchResult(query, files) {
1574
1642
  this.ensureInit();
1643
+ if (this.state.searchResults.size >= 50) {
1644
+ const oldest = this.state.searchResults.keys().next().value;
1645
+ if (oldest) this.state.searchResults.delete(oldest);
1646
+ }
1575
1647
  this.state.searchResults.set(query, files);
1576
1648
  this.save();
1577
1649
  }
@@ -1580,6 +1652,9 @@ No unresolved issues.`;
1580
1652
  const deps = this.state.dependencyGraph.get(file) ?? [];
1581
1653
  if (!deps.includes(dependsOn)) {
1582
1654
  deps.push(dependsOn);
1655
+ if (deps.length > 20) {
1656
+ deps.splice(0, deps.length - 20);
1657
+ }
1583
1658
  this.state.dependencyGraph.set(file, deps);
1584
1659
  this.save();
1585
1660
  }
@@ -1879,6 +1954,95 @@ No unresolved issues.`;
1879
1954
  return { isLooping: false };
1880
1955
  }
1881
1956
  // ============================================================
1957
+ // RECORDING TRACKING — Enforcement & Feedback
1958
+ // ============================================================
1959
+ /**
1960
+ * Increment recording counter for a specific type.
1961
+ * Called by kumaMemoryTool after successful record operations.
1962
+ */
1963
+ recordMemoryAction(type) {
1964
+ this.ensureInit();
1965
+ switch (type) {
1966
+ case "arch_flow":
1967
+ this.state.recordings.archFlows++;
1968
+ break;
1969
+ case "gotcha":
1970
+ this.state.recordings.gotchas++;
1971
+ break;
1972
+ case "decision":
1973
+ this.state.recordings.decisions++;
1974
+ break;
1975
+ case "research_save":
1976
+ this.state.recordings.researchSaves++;
1977
+ break;
1978
+ }
1979
+ this.state.recordings.total++;
1980
+ this.save();
1981
+ }
1982
+ /**
1983
+ * Get recording summary for this session.
1984
+ * Used by guard to detect if agent hasn't recorded anything.
1985
+ */
1986
+ getRecordingSummary() {
1987
+ this.ensureInit();
1988
+ const r = this.state.recordings;
1989
+ const missing = [];
1990
+ if (r.archFlows === 0) missing.push("arch_flow");
1991
+ if (r.gotchas === 0) missing.push("gotcha");
1992
+ if (r.decisions === 0) missing.push("decision");
1993
+ if (r.researchSaves === 0) missing.push("research_save");
1994
+ return {
1995
+ ...r,
1996
+ hasAnyRecordings: r.total > 0,
1997
+ missingRecordings: missing
1998
+ };
1999
+ }
2000
+ // ============================================================
2001
+ // SESSION METRICS — Time saved tracking
2002
+ // ============================================================
2003
+ /**
2004
+ * Track a file read operation.
2005
+ * Estimates time saved if research_cache exists for this file.
2006
+ */
2007
+ trackFileRead(filePath) {
2008
+ this.ensureInit();
2009
+ this.state.metrics.filesRead++;
2010
+ try {
2011
+ const researchDir = path3.join(this.state.projectRoot, ".kuma", "research");
2012
+ const cacheFile = path3.join(researchDir, `${filePath}.json`);
2013
+ if (fs3.existsSync(cacheFile)) {
2014
+ this.state.metrics.researchTimeSaved += 4e3;
2015
+ }
2016
+ } catch {
2017
+ }
2018
+ this.save();
2019
+ }
2020
+ /**
2021
+ * Track a file edit operation.
2022
+ */
2023
+ trackFileEdit(_filePath) {
2024
+ this.ensureInit();
2025
+ this.state.metrics.filesEdited++;
2026
+ this.save();
2027
+ }
2028
+ /**
2029
+ * Get metrics summary for session.
2030
+ */
2031
+ getMetricsSummary() {
2032
+ this.ensureInit();
2033
+ const m = this.state.metrics;
2034
+ const durationMs = Date.now() - this.state.startTime;
2035
+ const durationMin = Math.round(durationMs / 6e4);
2036
+ const savedSec = Math.round(m.researchTimeSaved / 1e3);
2037
+ return {
2038
+ filesRead: m.filesRead,
2039
+ filesEdited: m.filesEdited,
2040
+ researchTimeSaved: m.researchTimeSaved,
2041
+ researchTimeSavedFormatted: savedSec > 60 ? `${Math.round(savedSec / 60)}m ${savedSec % 60}s` : `${savedSec}s`,
2042
+ sessionDuration: durationMin > 60 ? `${Math.round(durationMin / 60)}h ${durationMin % 60}m` : `${durationMin}m`
2043
+ };
2044
+ }
2045
+ // ============================================================
1882
2046
  // V3: Change Tracking (Selective Undo)
1883
2047
  // ============================================================
1884
2048
  /**
@@ -1997,7 +2161,7 @@ async function detectStaleNodes() {
1997
2161
  SELECT id, type, name, file_path FROM nodes
1998
2162
  WHERE file_path IS NOT NULL
1999
2163
  AND length(file_path) > 0
2000
- ORDER BY updated_at DESC LIMIT 500
2164
+ ORDER BY updated_at DESC LIMIT 2000
2001
2165
  `);
2002
2166
  while (stmt.step()) {
2003
2167
  const row = stmt.getAsObject();
@@ -2182,16 +2346,18 @@ __export(kumaGraph_exports, {
2182
2346
  formatFlow: () => formatFlow,
2183
2347
  formatImpact: () => formatImpact,
2184
2348
  getGraphStats: () => getGraphStats,
2185
- listFederatedReferences: () => listFederatedReferences,
2186
2349
  nodeId: () => nodeId,
2350
+ propagateImpact: () => propagateImpact,
2187
2351
  queryGraph: () => queryGraph,
2352
+ recordAffects: () => recordAffects,
2188
2353
  recordApiRoute: () => recordApiRoute,
2189
2354
  recordDomainFlow: () => recordDomainFlow,
2355
+ recordFeature: () => recordFeature,
2190
2356
  recordFileDefinition: () => recordFileDefinition,
2191
2357
  recordFunctionCall: () => recordFunctionCall,
2192
2358
  recordImport: () => recordImport,
2193
2359
  recordTestRelation: () => recordTestRelation,
2194
- resolveFederatedNode: () => resolveFederatedNode,
2360
+ retrieveForTask: () => retrieveForTask,
2195
2361
  searchGraph: () => searchGraph,
2196
2362
  traceFlow: () => traceFlow,
2197
2363
  upsertNode: () => upsertNode
@@ -2199,6 +2365,60 @@ __export(kumaGraph_exports, {
2199
2365
  function nodeId(type, name) {
2200
2366
  return `${type}::${name}`;
2201
2367
  }
2368
+ async function pruneGraphIfNeeded() {
2369
+ try {
2370
+ const db = await getDb();
2371
+ const getCount = (sql) => {
2372
+ try {
2373
+ const result = db.exec(sql);
2374
+ return result[0]?.values?.[0] || 0;
2375
+ } catch {
2376
+ return 0;
2377
+ }
2378
+ };
2379
+ const nodeCount = getCount("SELECT COUNT(*) FROM nodes");
2380
+ const edgeCount = getCount("SELECT COUNT(*) FROM edges");
2381
+ if (nodeCount <= GRAPH_LIMITS.maxNodes && edgeCount <= GRAPH_LIMITS.maxEdges) {
2382
+ return { pruned: false, removedNodes: 0, removedEdges: 0 };
2383
+ }
2384
+ let removedNodes = 0;
2385
+ let removedEdges = 0;
2386
+ if (edgeCount > GRAPH_LIMITS.maxEdges) {
2387
+ const result = db.exec(`DELETE FROM edges WHERE weight < ${GRAPH_LIMITS.minWeight} AND metadata NOT LIKE '%stale:0%'`);
2388
+ removedEdges = result[0]?.values?.length || 0;
2389
+ }
2390
+ const currentNodes = getCount("SELECT COUNT(*) FROM nodes");
2391
+ if (currentNodes > GRAPH_LIMITS.maxNodes) {
2392
+ const result = db.exec(`
2393
+ DELETE FROM nodes WHERE id IN (
2394
+ SELECT id FROM nodes
2395
+ WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges)
2396
+ ORDER BY updated_at ASC
2397
+ LIMIT ${GRAPH_LIMITS.pruneBatch}
2398
+ )
2399
+ `);
2400
+ removedNodes = result[0]?.values?.length || 0;
2401
+ }
2402
+ const finalNodes = getCount("SELECT COUNT(*) FROM nodes");
2403
+ if (finalNodes > GRAPH_LIMITS.maxNodes) {
2404
+ const excess = finalNodes - GRAPH_LIMITS.maxNodes + 100;
2405
+ const result = db.exec(`
2406
+ DELETE FROM nodes WHERE id IN (
2407
+ SELECT id FROM nodes ORDER BY updated_at ASC LIMIT ${excess}
2408
+ )
2409
+ `);
2410
+ removedNodes += result[0]?.values?.length || 0;
2411
+ }
2412
+ if (removedNodes > 0 || removedEdges > 0) {
2413
+ saveDb(db);
2414
+ return { pruned: true, removedNodes, removedEdges };
2415
+ }
2416
+ return { pruned: false, removedNodes: 0, removedEdges: 0 };
2417
+ } catch (err) {
2418
+ console.error(`[KumaGraph] Prune failed: ${err}`);
2419
+ return { pruned: false, removedNodes: 0, removedEdges: 0 };
2420
+ }
2421
+ }
2202
2422
  async function upsertNode(node) {
2203
2423
  try {
2204
2424
  const db = await getDb();
@@ -2219,6 +2439,14 @@ async function upsertNode(node) {
2219
2439
  } catch {
2220
2440
  }
2221
2441
  saveDb(db);
2442
+ try {
2443
+ const countResult = db.exec("SELECT COUNT(*) FROM nodes");
2444
+ const nodeCount = countResult[0]?.values?.[0] || 0;
2445
+ if (nodeCount % 50 === 0 && nodeCount > GRAPH_LIMITS.maxNodes * 0.8) {
2446
+ await pruneGraphIfNeeded();
2447
+ }
2448
+ } catch {
2449
+ }
2222
2450
  } catch (err) {
2223
2451
  console.error(`[KumaGraph] Failed to upsert node: ${err}`);
2224
2452
  }
@@ -2327,6 +2555,14 @@ async function recordDomainFlow(params) {
2327
2555
  metadata: { relation: hop.relation, description: hop.description }
2328
2556
  });
2329
2557
  edgeCount++;
2558
+ await addEdge({
2559
+ sourceId: fromId,
2560
+ targetId: toId,
2561
+ type: "affects",
2562
+ weight: 0.8,
2563
+ metadata: { reason: "arch_flow", domain: params.domain }
2564
+ });
2565
+ edgeCount++;
2330
2566
  await addEdge({
2331
2567
  sourceId: domainId,
2332
2568
  targetId: fromId,
@@ -2403,6 +2639,14 @@ async function recordDomainFlow(params) {
2403
2639
  metadata: { domain: params.domain }
2404
2640
  });
2405
2641
  edgeCount++;
2642
+ await addEdge({
2643
+ sourceId: domainId,
2644
+ targetId: fileId,
2645
+ type: "affects",
2646
+ weight: 0.7,
2647
+ metadata: { reason: "arch_flow" }
2648
+ });
2649
+ edgeCount++;
2406
2650
  } catch {
2407
2651
  }
2408
2652
  }
@@ -2430,6 +2674,214 @@ async function clearGraph() {
2430
2674
  return -1;
2431
2675
  }
2432
2676
  }
2677
+ async function recordFeature(params) {
2678
+ let nodeCount = 0;
2679
+ let edgeCount = 0;
2680
+ try {
2681
+ const featureId = nodeId("feature", params.name);
2682
+ await upsertNode({
2683
+ id: featureId,
2684
+ type: "feature",
2685
+ name: params.name,
2686
+ metadata: {
2687
+ description: params.description || "",
2688
+ tags: params.tags || [],
2689
+ risk: params.risk || "medium"
2690
+ }
2691
+ });
2692
+ nodeCount++;
2693
+ if (params.files) {
2694
+ for (const fp of params.files) {
2695
+ const fileId = nodeId("file", fp);
2696
+ await upsertNode({ id: fileId, type: "file", name: fp });
2697
+ await addEdge({
2698
+ sourceId: featureId,
2699
+ targetId: fileId,
2700
+ type: "owns",
2701
+ weight: 1,
2702
+ metadata: { feature: params.name }
2703
+ });
2704
+ nodeCount++;
2705
+ edgeCount++;
2706
+ }
2707
+ }
2708
+ flushDb();
2709
+ return { nodeCount, edgeCount };
2710
+ } catch (err) {
2711
+ console.error(`[KumaGraph] Failed to record feature: ${err}`);
2712
+ return { nodeCount, edgeCount };
2713
+ }
2714
+ }
2715
+ async function recordAffects(sourceId, targetId, weight = 0.8, reason) {
2716
+ await addEdge({
2717
+ sourceId,
2718
+ targetId,
2719
+ type: "affects",
2720
+ weight,
2721
+ metadata: { reason: reason || "manual" }
2722
+ });
2723
+ }
2724
+ async function propagateImpact(startNodeId, maxDepth = 5, minWeight = 0.1) {
2725
+ try {
2726
+ const db = await getDb();
2727
+ const visited = /* @__PURE__ */ new Set();
2728
+ const results = [];
2729
+ const queue = [[startNodeId, 0, 1]];
2730
+ while (queue.length > 0) {
2731
+ const [currentId, depth, accWeight] = queue.shift();
2732
+ if (visited.has(currentId) || depth > maxDepth) continue;
2733
+ visited.add(currentId);
2734
+ const stmt = db.prepare(`
2735
+ SELECT e.target_id, e.weight, n.type, n.name, n.file_path
2736
+ FROM edges e
2737
+ LEFT JOIN nodes n ON n.id = e.target_id
2738
+ WHERE e.source_id = ? AND e.type = 'affects' AND e.weight >= ?
2739
+ `);
2740
+ stmt.bind([currentId, minWeight]);
2741
+ while (stmt.step()) {
2742
+ const row = stmt.getAsObject();
2743
+ const targetId = row.target_id;
2744
+ const edgeWeight = row.weight || 0.8;
2745
+ const propagatedWeight = accWeight * edgeWeight;
2746
+ if (!visited.has(targetId) && propagatedWeight >= minWeight) {
2747
+ results.push({
2748
+ id: targetId,
2749
+ type: row.type,
2750
+ name: row.name,
2751
+ depth: depth + 1,
2752
+ weight: Math.round(propagatedWeight * 100) / 100,
2753
+ filePath: row.file_path || void 0
2754
+ });
2755
+ queue.push([targetId, depth + 1, propagatedWeight]);
2756
+ }
2757
+ }
2758
+ stmt.free();
2759
+ }
2760
+ return results.sort((a, b) => b.weight - a.weight);
2761
+ } catch (err) {
2762
+ console.error(`[KumaGraph] Failed to propagate impact: ${err}`);
2763
+ return [];
2764
+ }
2765
+ }
2766
+ async function retrieveForTask(taskDescription, maxNodes = 10) {
2767
+ try {
2768
+ const db = await getDb();
2769
+ const keywords = taskDescription.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2);
2770
+ if (keywords.length === 0) {
2771
+ return `\u{1F50D} **Task Retrieval** \u2014 No meaningful keywords in "${taskDescription}".`;
2772
+ }
2773
+ const allNodes = [];
2774
+ try {
2775
+ const ftsQuery = keywords.join(" OR ");
2776
+ const stmt = db.prepare(`
2777
+ SELECT n.id, n.type, n.name, n.file_path
2778
+ FROM nodes_fts f
2779
+ JOIN nodes n ON n.rowid = f.rowid
2780
+ WHERE nodes_fts MATCH ?
2781
+ LIMIT ?
2782
+ `);
2783
+ stmt.bind([ftsQuery, maxNodes * 2]);
2784
+ while (stmt.step()) {
2785
+ const row = stmt.getAsObject();
2786
+ allNodes.push({
2787
+ id: row.id,
2788
+ type: row.type,
2789
+ name: row.name,
2790
+ filePath: row.file_path || void 0,
2791
+ relevance: 0.8
2792
+ });
2793
+ }
2794
+ stmt.free();
2795
+ } catch {
2796
+ }
2797
+ if (allNodes.length === 0) {
2798
+ for (const kw of keywords) {
2799
+ const stmt = db.prepare(`
2800
+ SELECT id, type, name, file_path FROM nodes
2801
+ WHERE name LIKE ? OR file_path LIKE ?
2802
+ LIMIT ?
2803
+ `);
2804
+ stmt.bind([`%${kw}%`, `%${kw}%`, maxNodes]);
2805
+ while (stmt.step()) {
2806
+ const row = stmt.getAsObject();
2807
+ allNodes.push({
2808
+ id: row.id,
2809
+ type: row.type,
2810
+ name: row.name,
2811
+ filePath: row.file_path || void 0,
2812
+ relevance: 0.6
2813
+ });
2814
+ }
2815
+ stmt.free();
2816
+ }
2817
+ }
2818
+ const seen = /* @__PURE__ */ new Set();
2819
+ const prioritized = [];
2820
+ const typePriority = {
2821
+ feature: 10,
2822
+ feature_domain: 9,
2823
+ class: 8,
2824
+ api_route: 7,
2825
+ db_table: 6,
2826
+ file: 5,
2827
+ function: 4,
2828
+ component: 3,
2829
+ gotcha: 2,
2830
+ decision: 1
2831
+ };
2832
+ for (const node of allNodes) {
2833
+ if (!seen.has(node.id)) {
2834
+ seen.add(node.id);
2835
+ node.relevance += typePriority[node.type] || 0;
2836
+ prioritized.push(node);
2837
+ }
2838
+ }
2839
+ prioritized.sort((a, b) => b.relevance - a.relevance);
2840
+ const topNodes = prioritized.slice(0, maxNodes);
2841
+ if (topNodes.length === 0) {
2842
+ return `\u{1F50D} **Task Retrieval** \u2014 No relevant nodes found for "${taskDescription}".
2843
+
2844
+ \u{1F4A1} Try recording features, arch_flows, or gotchas first.`;
2845
+ }
2846
+ const lines = [
2847
+ `\u{1F50D} **Task Context** \u2014 "${taskDescription}"`,
2848
+ `\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`,
2849
+ `\u{1F4CA} ${topNodes.length} relevant node(s) found
2850
+ `
2851
+ ];
2852
+ for (const node of topNodes) {
2853
+ const emoji = node.type === "feature" ? "\u2B50" : node.type === "feature_domain" ? "\u{1F3DB}\uFE0F" : node.type === "class" ? "\u{1F3D7}\uFE0F" : node.type === "api_route" ? "\u{1F310}" : node.type === "db_table" ? "\u{1F5C4}\uFE0F" : node.type === "file" ? "\u{1F4C4}" : node.type === "function" ? "\u{1F527}" : node.type === "component" ? "\u25C7" : node.type === "gotcha" ? "\u26A0\uFE0F" : "\u{1F4CC}";
2854
+ lines.push(`${emoji} **${node.name}** (${node.type})`);
2855
+ if (node.filePath) lines.push(` \u{1F4CD} ${node.filePath}`);
2856
+ try {
2857
+ const stmt = db.prepare(`
2858
+ SELECT e.type AS edgeType, n.type AS nodeType, n.name AS nodeName
2859
+ FROM edges e
2860
+ LEFT JOIN nodes n ON n.id = (CASE WHEN e.source_id = ? THEN e.target_id ELSE e.source_id END)
2861
+ WHERE (e.source_id = ? OR e.target_id = ?) AND n.id IS NOT NULL
2862
+ ORDER BY e.weight DESC
2863
+ LIMIT 3
2864
+ `);
2865
+ stmt.bind([node.id, node.id, node.id]);
2866
+ const connections = [];
2867
+ while (stmt.step()) {
2868
+ const row = stmt.getAsObject();
2869
+ connections.push(`${row.edgeType}\u2192${row.nodeName}`);
2870
+ }
2871
+ stmt.free();
2872
+ if (connections.length > 0) {
2873
+ lines.push(` \u{1F517} ${connections.join(", ")}`);
2874
+ }
2875
+ } catch {
2876
+ }
2877
+ lines.push("");
2878
+ }
2879
+ lines.push("\u{1F4A1} Use this context instead of scanning the full codebase.");
2880
+ return lines.join("\n");
2881
+ } catch (err) {
2882
+ return `Error retrieving task context: ${err}`;
2883
+ }
2884
+ }
2433
2885
  async function queryGraph(params) {
2434
2886
  try {
2435
2887
  const db = await getDb();
@@ -2701,27 +3153,27 @@ async function codebaseSearchFallback(query, limit = 20) {
2701
3153
  if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
2702
3154
  let entries = [];
2703
3155
  try {
2704
- entries = fs27.readdirSync(dir);
3156
+ entries = fs26.readdirSync(dir);
2705
3157
  } catch {
2706
3158
  return;
2707
3159
  }
2708
3160
  for (const entry of entries) {
2709
3161
  if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
2710
- const full = path27.join(dir, entry);
3162
+ const full = path26.join(dir, entry);
2711
3163
  try {
2712
- const stat = fs27.statSync(full);
3164
+ const stat = fs26.statSync(full);
2713
3165
  if (stat.isDirectory()) {
2714
3166
  walk2(full, depth + 1);
2715
3167
  } else if (entry.toLowerCase().includes(query.toLowerCase())) {
2716
- results.push(path27.relative(root, full));
3168
+ results.push(path26.relative(root, full));
2717
3169
  }
2718
3170
  } catch {
2719
3171
  }
2720
3172
  }
2721
3173
  };
2722
3174
  var walk = walk2;
2723
- const fs27 = await import("fs");
2724
- const path27 = await import("path");
3175
+ const fs26 = await import("fs");
3176
+ const path26 = await import("path");
2725
3177
  const root = process.cwd();
2726
3178
  const results = [];
2727
3179
  const MAX_WALK_DEPTH = 8;
@@ -2741,114 +3193,19 @@ async function codebaseSearchFallback(query, limit = 20) {
2741
3193
  }
2742
3194
  }
2743
3195
  }
2744
- async function resolveFederatedNode(uri) {
2745
- try {
2746
- if (!uri.startsWith("kuma://")) {
2747
- return `\u26A0\uFE0F Invalid URI scheme: "${uri.substring(0, 20)}...". Use kuma://project/node-id`;
2748
- }
2749
- const pathPart = uri.replace("kuma://", "");
2750
- const firstSlash = pathPart.indexOf("/");
2751
- if (firstSlash === -1) {
2752
- return `\u26A0\uFE0F Invalid federated URI: "${uri}". Format: kuma://<project>/<node-id>`;
2753
- }
2754
- const projectName = pathPart.substring(0, firstSlash);
2755
- const nodeRef = pathPart.substring(firstSlash + 1);
2756
- const db = await getDb();
2757
- const stmt = db.prepare(`
2758
- SELECT id, type, name, file_path, metadata FROM nodes
2759
- WHERE id = ? OR name LIKE ?
2760
- LIMIT 5
2761
- `);
2762
- stmt.bind([nodeRef, `%${nodeRef}%`]);
2763
- const results = [];
2764
- while (stmt.step()) results.push(stmt.getAsObject());
2765
- stmt.free();
2766
- if (results.length > 0) {
2767
- const lines = [
2768
- `\u{1F517} **Federated Node Resolved** (local)`,
2769
- `\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`,
2770
- `\u{1F4C1} Project: ${projectName}`,
2771
- `\u{1F517} URI: ${uri}`,
2772
- ""
2773
- ];
2774
- for (const r of results) {
2775
- lines.push(` \u2022 **${r.name}** (${r.type}) \u2014 ${r.file_path || "no path"}`);
2776
- }
2777
- lines.push("", "\u{1F4A1} Federated cross-repo resolution is active. Remote resolution via HTTP coming soon.");
2778
- return lines.join("\n");
2779
- }
2780
- const federatedNodes = await registerFederatedReference(uri, projectName, nodeRef);
2781
- return [
2782
- `\u{1F517} **Federated Reference**`,
2783
- `\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`,
2784
- `\u{1F4C1} Remote project: ${projectName}`,
2785
- `\u{1F517} URI: ${uri}`,
2786
- `\u{1F4CC} Node ref: ${nodeRef}`,
2787
- "",
2788
- `\u{1F4DD} Registered as federated reference for future resolution.`,
2789
- `\u{1F4A1} Remote graph resolution will be available when the target project is reachable.`,
2790
- federatedNodes > 0 ? `\u2705 Created ${federatedNodes} federated node(s) in local graph.` : ""
2791
- ].filter(Boolean).join("\n");
2792
- } catch (err) {
2793
- return `\u274C Federated resolution failed: ${err}`;
2794
- }
2795
- }
2796
- async function registerFederatedReference(uri, projectName, nodeRef) {
2797
- try {
2798
- await upsertNode({
2799
- id: `federated::${uri}`,
2800
- type: "module",
2801
- name: `[${projectName}] ${nodeRef}`,
2802
- metadata: {
2803
- federated: true,
2804
- uri,
2805
- project: projectName,
2806
- ref: nodeRef,
2807
- resolved: false
2808
- }
2809
- });
2810
- return 1;
2811
- } catch {
2812
- return 0;
2813
- }
2814
- }
2815
- async function listFederatedReferences() {
2816
- try {
2817
- const db = await getDb();
2818
- const stmt = db.prepare(`
2819
- SELECT id, name, metadata FROM nodes
2820
- WHERE metadata LIKE '%"federated":true%'
2821
- ORDER BY updated_at DESC
2822
- LIMIT 20
2823
- `);
2824
- const results = [];
2825
- while (stmt.step()) results.push(stmt.getAsObject());
2826
- stmt.free();
2827
- if (results.length === 0) {
2828
- return "\u{1F517} **No federated references.** Use kuma_memory({ action: 'federated', uri: 'kuma://project/node-id' }) to add one.";
2829
- }
2830
- const lines = [
2831
- "\u{1F517} **Federated References**",
2832
- "\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",
2833
- ""
2834
- ];
2835
- for (const r of results) {
2836
- const meta = JSON.parse(r.metadata || "{}");
2837
- lines.push(` \u{1F517} **${r.name}**`);
2838
- lines.push(` URI: ${meta.uri || "unknown"}`);
2839
- lines.push(` Resolved: ${meta.resolved ? "\u2705" : "\u23F3"}`);
2840
- lines.push("");
2841
- }
2842
- return lines.join("\n");
2843
- } catch (err) {
2844
- return `Error: ${err}`;
2845
- }
2846
- }
2847
3196
  async function getGraphStats() {
2848
3197
  try {
2849
3198
  const db = await getDb();
2850
- const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
2851
- const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
3199
+ const getCount = (sql) => {
3200
+ try {
3201
+ const result = db.exec(sql);
3202
+ return result[0]?.values?.[0] || 0;
3203
+ } catch {
3204
+ return 0;
3205
+ }
3206
+ };
3207
+ const nodeCount = getCount("SELECT COUNT(*) as c FROM nodes");
3208
+ const edgeCount = getCount("SELECT COUNT(*) as c FROM edges");
2852
3209
  const typeCounts = [];
2853
3210
  try {
2854
3211
  const stmt = db.prepare("SELECT type, COUNT(*) as cnt FROM nodes GROUP BY type ORDER BY cnt DESC");
@@ -2863,6 +3220,7 @@ async function getGraphStats() {
2863
3220
  `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
2864
3221
  "",
2865
3222
  `\u{1F4CA} **${nodeCount} nodes** | **${edgeCount} edges**`,
3223
+ `\u{1F4CF} **Limits:** ${nodeCount}/${GRAPH_LIMITS.maxNodes} nodes (${Math.round(nodeCount / GRAPH_LIMITS.maxNodes * 100)}%) | ${edgeCount}/${GRAPH_LIMITS.maxEdges} edges (${Math.round(edgeCount / GRAPH_LIMITS.maxEdges * 100)}%)`,
2866
3224
  ""
2867
3225
  ];
2868
3226
  if (typeCounts.length > 0) {
@@ -3059,11 +3417,22 @@ function formatFlow(entryPoint, steps) {
3059
3417
  lines.push("", `\u{1F4A1} ${steps.length} step(s) traversed.`);
3060
3418
  return lines.join("\n");
3061
3419
  }
3420
+ var GRAPH_LIMITS;
3062
3421
  var init_kumaGraph = __esm({
3063
3422
  "src/engine/kumaGraph.ts"() {
3064
3423
  "use strict";
3065
3424
  init_kumaDb();
3066
3425
  init_kumaSelfHeal();
3426
+ GRAPH_LIMITS = {
3427
+ maxNodes: 5e3,
3428
+ // Max nodes before pruning
3429
+ maxEdges: 1e4,
3430
+ // Max edges before pruning
3431
+ pruneBatch: 500,
3432
+ // Remove this many old nodes when limit hit
3433
+ minWeight: 0.1
3434
+ // Minimum edge weight before deletion
3435
+ };
3067
3436
  }
3068
3437
  });
3069
3438
 
@@ -5931,495 +6300,57 @@ async function addGotcha(entry) {
5931
6300
  `- **Issue**: ${entry.description}`,
5932
6301
  `- **Severity**: ${severity}`,
5933
6302
  entry.workaround ? `- **Workaround**: ${entry.workaround}` : "",
5934
- `- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
5935
- ].filter(Boolean).join("\n");
5936
- db.run(
5937
- `INSERT INTO known_gotchas (file_path, description, severity, workaround) VALUES (?, ?, ?, ?)`,
5938
- [entry.filePath, entry.description, severity, entry.workaround || null]
5939
- );
5940
- const mdResult = appendToLayer("gotcha", formatted);
5941
- saveDb();
5942
- sessionMemory.recordToolCall("kuma_gotcha_add", {
5943
- filePath: entry.filePath,
5944
- severity
5945
- });
5946
- return `\u2705 **Gotcha recorded**: ${entry.filePath} \u2014 ${entry.description}
5947
- ${mdResult}`;
5948
- } catch (err) {
5949
- return `\u274C Failed to add gotcha: ${err}`;
5950
- }
5951
- }
5952
- async function listGotchas(params) {
5953
- try {
5954
- await ensureGotchasSchema();
5955
- const db = await getDb();
5956
- let sql = "SELECT * FROM known_gotchas WHERE 1=1";
5957
- const bind = [];
5958
- if (params.filePath) {
5959
- sql += " AND file_path LIKE ?";
5960
- bind.push(`%${params.filePath}%`);
5961
- }
5962
- if (params.severity) {
5963
- sql += " AND severity = ?";
5964
- bind.push(params.severity);
5965
- }
5966
- sql += " ORDER BY severity DESC, created_at DESC LIMIT 50";
5967
- const stmt = db.prepare(sql);
5968
- stmt.bind(bind);
5969
- const results = [];
5970
- while (stmt.step()) results.push(stmt.getAsObject());
5971
- stmt.free();
5972
- if (results.length === 0) {
5973
- return "\u2705 **No gotchas recorded** \u2014 legacy codebase looks clean.";
5974
- }
5975
- const lines = [
5976
- `\u26A0\uFE0F **Known Gotchas** \u2014 ${results.length} recorded`,
5977
- "\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",
5978
- ""
5979
- ];
5980
- for (const g of results) {
5981
- const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
5982
- lines.push(`${icon} [${g.severity}] ${g.file_path}`);
5983
- lines.push(` \u{1F4DD} ${g.description?.toString().substring(0, 100)}`);
5984
- if (g.workaround) lines.push(` \u{1F4A1} ${g.workaround.substring(0, 100)}`);
5985
- lines.push("");
5986
- }
5987
- return lines.join("\n");
5988
- } catch (err) {
5989
- return `Error: ${err}`;
5990
- }
5991
- }
5992
- function checkGotchasForFile(filePath) {
5993
- const warnings = [];
5994
- const markdownWarnings = checkFileGotchas(filePath);
5995
- try {
5996
- const gotchas = getActiveGotchas();
5997
- for (const g of gotchas) {
5998
- if (filePath.includes(g.filePath) || g.filePath.includes(filePath)) {
5999
- if (!markdownWarnings.some((w) => w.includes(g.description))) {
6000
- const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
6001
- warnings.push(`${icon} **Known Gotcha**: ${g.description} (${g.severity})`);
6002
- }
6003
- }
6004
- }
6005
- } catch {
6006
- }
6007
- return [.../* @__PURE__ */ new Set([...markdownWarnings, ...warnings])];
6008
- }
6009
- async function syncGotchasToDb() {
6010
- try {
6011
- await ensureGotchasSchema();
6012
- const db = await getDb();
6013
- const gotchas = getActiveGotchas();
6014
- let synced = 0;
6015
- for (const g of gotchas) {
6016
- const checkStmt = db.prepare(
6017
- `SELECT id FROM known_gotchas WHERE file_path = ? AND description = ?`
6018
- );
6019
- checkStmt.bind([g.filePath, g.description]);
6020
- const exists = checkStmt.step();
6021
- checkStmt.free();
6022
- if (exists) continue;
6023
- db.run(
6024
- `INSERT INTO known_gotchas (file_path, description, severity) VALUES (?, ?, ?)`,
6025
- [g.filePath, g.description, g.severity]
6026
- );
6027
- synced++;
6028
- }
6029
- saveDb();
6030
- return { synced };
6031
- } catch {
6032
- return { synced: 0 };
6033
- }
6034
- }
6035
- var init_kumaGotchas = __esm({
6036
- "src/engine/kumaGotchas.ts"() {
6037
- "use strict";
6038
- init_sessionMemory();
6039
- init_kumaDb();
6040
- init_domainRules();
6041
- }
6042
- });
6043
-
6044
- // src/engine/kumaTrajectory.ts
6045
- var kumaTrajectory_exports = {};
6046
- __export(kumaTrajectory_exports, {
6047
- findSimilarTrajectories: () => findSimilarTrajectories,
6048
- generateTestFromTrajectory: () => generateTestFromTrajectory,
6049
- generateTestFromTrajectoryId: () => generateTestFromTrajectoryId,
6050
- listDistilledSkills: () => listDistilledSkills,
6051
- listGeneratedTests: () => listGeneratedTests,
6052
- listTrajectories: () => listTrajectories,
6053
- recordTrajectory: () => recordTrajectory
6054
- });
6055
- import fs14 from "fs";
6056
- import path14 from "path";
6057
- async function ensureTrajectorySchema() {
6058
- const db = await getDb();
6059
- db.exec(`
6060
- CREATE TABLE IF NOT EXISTS trajectories (
6061
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6062
- goal TEXT NOT NULL,
6063
- steps TEXT NOT NULL DEFAULT '[]',
6064
- total_duration_ms INTEGER DEFAULT 0,
6065
- success_rate REAL DEFAULT 0.0,
6066
- complexity INTEGER DEFAULT 0,
6067
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
6068
- );
6069
- CREATE INDEX IF NOT EXISTS idx_traj_goal ON trajectories(goal);
6070
- CREATE INDEX IF NOT EXISTS idx_traj_complexity ON trajectories(complexity);
6071
- CREATE INDEX IF NOT EXISTS idx_traj_created ON trajectories(created_at);
6072
- `);
6073
- db.exec(`
6074
- CREATE TABLE IF NOT EXISTS distilled_skills (
6075
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6076
- name TEXT NOT NULL UNIQUE,
6077
- description TEXT NOT NULL,
6078
- pattern TEXT NOT NULL, -- JSON: tool sequence pattern
6079
- parameters TEXT DEFAULT '[]', -- JSON: parameterized inputs
6080
- success_count INTEGER DEFAULT 1,
6081
- avg_duration_ms INTEGER DEFAULT 0,
6082
- source_trajectory_id INTEGER,
6083
- last_used_at INTEGER,
6084
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
6085
- );
6086
- CREATE INDEX IF NOT EXISTS idx_skills_name ON distilled_skills(name);
6087
- CREATE INDEX IF NOT EXISTS idx_skills_used ON distilled_skills(last_used_at);
6088
- `);
6089
- db.exec(`
6090
- CREATE TABLE IF NOT EXISTS generated_tests (
6091
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6092
- trajectory_id INTEGER,
6093
- file_path TEXT NOT NULL,
6094
- test_framework TEXT NOT NULL,
6095
- description TEXT,
6096
- test_code TEXT NOT NULL,
6097
- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
6098
- );
6099
- CREATE INDEX IF NOT EXISTS idx_gen_tests_traj ON generated_tests(trajectory_id);
6100
- `);
6101
- saveDb();
6102
- }
6103
- async function recordTrajectory(goal) {
6104
- try {
6105
- await ensureTrajectorySchema();
6106
- const calls = sessionMemory.getToolCallHistory(100);
6107
- const steps = calls.map((c) => ({
6108
- toolName: c.toolName,
6109
- params: c.params,
6110
- success: true,
6111
- durationMs: 0,
6112
- timestamp: c.timestamp
6113
- }));
6114
- const totalDurationMs = steps.length > 0 ? steps[steps.length - 1].timestamp - steps[0].timestamp : 0;
6115
- const errorCalls = steps.filter((s) => {
6116
- const action = s.params.action;
6117
- return action === "verify" && s.params.success === false;
6118
- }).length;
6119
- const successRate = steps.length > 0 ? (steps.length - errorCalls) / steps.length : 1;
6120
- const complexity = Math.min(
6121
- 100,
6122
- Math.round(
6123
- steps.length * 5 + // 5 pts per step
6124
- errorCalls * 15 + // 15 pts per error
6125
- Math.min(totalDurationMs / 6e4 * 10, 30)
6126
- // 10 pts per minute (max 30)
6127
- )
6128
- );
6129
- const db2 = await getDb();
6130
- db2.run(
6131
- `INSERT INTO trajectories (goal, steps, total_duration_ms, success_rate, complexity)
6132
- VALUES (?, ?, ?, ?, ?)`,
6133
- [
6134
- goal.substring(0, 200),
6135
- JSON.stringify(steps),
6136
- totalDurationMs,
6137
- successRate,
6138
- complexity
6139
- ]
6140
- );
6141
- saveDb();
6142
- const result = db2.exec("SELECT last_insert_rowid() as id");
6143
- const id = result[0]?.values[0]?.[0];
6144
- if (id !== null && complexity > 40 && successRate > 0.8) {
6145
- await distillSkill(id, goal, steps).catch(() => {
6146
- });
6147
- if (goal.toLowerCase().includes("fix") || goal.toLowerCase().includes("bug") || goal.toLowerCase().includes("error")) {
6148
- await generateTestFromTrajectory(id, goal, steps).catch(() => {
6149
- });
6150
- }
6151
- }
6152
- return id ?? null;
6153
- } catch (err) {
6154
- console.error(`[Trajectory] Record error: ${err}`);
6155
- return null;
6156
- }
6157
- }
6158
- async function distillSkill(trajectoryId, goal, steps) {
6159
- try {
6160
- const db = await getDb();
6161
- const toolSequence = steps.map((s) => s.toolName);
6162
- const patternKey = toolSequence.join(" \u2192 ");
6163
- const uniqueTools = [...new Set(toolSequence)];
6164
- const skillName = `trajectory:${uniqueTools.slice(0, 3).join("-")}`;
6165
- const checkStmt = db.prepare(
6166
- "SELECT id, success_count, avg_duration_ms FROM distilled_skills WHERE name = ?"
6167
- );
6168
- checkStmt.bind([skillName]);
6169
- const exists = checkStmt.step();
6170
- let existingRow = null;
6171
- if (exists) {
6172
- existingRow = checkStmt.getAsObject();
6173
- }
6174
- checkStmt.free();
6175
- const totalDuration = steps.reduce((sum, s) => sum + s.durationMs, 0);
6176
- if (existingRow) {
6177
- const existingId = existingRow.id;
6178
- const existingCount = existingRow.success_count;
6179
- const existingAvg = existingRow.avg_duration_ms;
6180
- db.run(
6181
- `UPDATE distilled_skills
6182
- SET success_count = ?, avg_duration_ms = ?, last_used_at = strftime('%s','now')
6183
- WHERE id = ?`,
6184
- [
6185
- existingCount + 1,
6186
- Math.round((existingAvg * existingCount + totalDuration) / (existingCount + 1)),
6187
- existingId
6188
- ]
6189
- );
6190
- } else {
6191
- const description = [
6192
- `Distilled trajectory pattern for "${goal.substring(0, 60)}"`,
6193
- `Tool sequence: ${patternKey}`,
6194
- `${steps.length} steps, ${uniqueTools.length} unique tools`
6195
- ].join(". ");
6196
- db.run(
6197
- `INSERT INTO distilled_skills (name, description, pattern, parameters, success_count, avg_duration_ms, source_trajectory_id, last_used_at)
6198
- VALUES (?, ?, ?, ?, 1, ?, ?, strftime('%s','now'))`,
6199
- [
6200
- skillName,
6201
- description,
6202
- JSON.stringify(toolSequence),
6203
- JSON.stringify(extractParameters(steps)),
6204
- totalDuration,
6205
- trajectoryId
6206
- ]
6207
- );
6208
- }
6209
- saveDb();
6210
- console.error(`[Trajectory] Distilled skill "${skillName}" from trajectory #${trajectoryId}`);
6211
- } catch (err) {
6212
- console.error(`[Trajectory] Distill error: ${err}`);
6213
- }
6214
- }
6215
- function extractParameters(steps) {
6216
- const params = /* @__PURE__ */ new Set();
6217
- for (const step of steps) {
6218
- for (const [key] of Object.entries(step.params)) {
6219
- if (["scope", "target", "query", "filePath", "action"].includes(key)) {
6220
- params.add(key);
6221
- }
6222
- }
6223
- }
6224
- return Array.from(params);
6225
- }
6226
- async function generateTestFromTrajectory(trajectoryId, goal, steps) {
6227
- try {
6228
- await ensureTrajectorySchema();
6229
- const modifiedFiles = steps.filter((s) => s.params.filePath || s.params.target).map((s) => s.params.filePath || s.params.target).filter(Boolean);
6230
- if (modifiedFiles.length === 0) return null;
6231
- const framework = detectTestFramework();
6232
- const primaryFile = modifiedFiles[0];
6233
- const testCode = synthesizeTestCode(goal, primaryFile, modifiedFiles, framework);
6234
- const testFilePath = determineTestPath(primaryFile, framework);
6235
- const genTest = {
6236
- filePath: testFilePath,
6237
- framework,
6238
- code: testCode,
6239
- description: `Auto-generated from trajectory #${trajectoryId}: ${goal.substring(0, 80)}`
6240
- };
6241
- const db = await getDb();
6242
- db.run(
6243
- `INSERT INTO generated_tests (trajectory_id, file_path, test_framework, description, test_code)
6244
- VALUES (?, ?, ?, ?, ?)`,
6245
- [trajectoryId, testFilePath, framework, genTest.description, testCode]
6246
- );
6247
- saveDb();
6248
- const root = getProjectRoot();
6249
- const fullPath = path14.resolve(root, testFilePath);
6250
- const dir = path14.dirname(fullPath);
6251
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
6252
- fs14.writeFileSync(fullPath, testCode, "utf-8");
6253
- console.error(`[Trajectory] Generated test: ${testFilePath} from trajectory #${trajectoryId}`);
6254
- return genTest;
6255
- } catch (err) {
6256
- console.error(`[Trajectory] Test generation error: ${err}`);
6257
- return null;
6258
- }
6259
- }
6260
- async function generateTestFromTrajectoryId(trajectoryId) {
6261
- try {
6262
- await ensureTrajectorySchema();
6263
- const db = await getDb();
6264
- const stmt = db.prepare("SELECT id, goal, steps FROM trajectories WHERE id = ?");
6265
- stmt.bind([trajectoryId]);
6266
- if (!stmt.step()) {
6267
- stmt.free();
6268
- return `\u274C Trajectory #${trajectoryId} not found.`;
6269
- }
6270
- const row = stmt.getAsObject();
6271
- stmt.free();
6272
- const goal = row.goal;
6273
- const steps = JSON.parse(row.steps);
6274
- const result = await generateTestFromTrajectory(trajectoryId, goal, steps);
6275
- if (!result) {
6276
- return "\u26A0\uFE0F Could not generate test \u2014 no modified files found in trajectory.";
6277
- }
6278
- return [
6279
- `\u{1F9EA} **Test Generated** from trajectory #${trajectoryId}`,
6280
- `\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`,
6281
- ``,
6282
- `\u{1F4C1} **File**: ${result.filePath}`,
6283
- `\u26A1 **Framework**: ${result.framework}`,
6284
- `\u{1F4DD} **Description**: ${result.description}`,
6285
- ``,
6286
- `\`\`\`${result.framework === "jest" ? "typescript" : "javascript"}`,
6287
- result.code.substring(0, 800),
6288
- result.code.length > 800 ? "..." : "",
6289
- `\`\`\``,
6290
- ``,
6291
- `\u{1F4A1} Test file has been written to disk. Run your test suite to verify.`
6303
+ `- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
6292
6304
  ].filter(Boolean).join("\n");
6305
+ db.run(
6306
+ `INSERT INTO known_gotchas (file_path, description, severity, workaround) VALUES (?, ?, ?, ?)`,
6307
+ [entry.filePath, entry.description, severity, entry.workaround || null]
6308
+ );
6309
+ const mdResult = appendToLayer("gotcha", formatted);
6310
+ saveDb();
6311
+ sessionMemory.recordToolCall("kuma_gotcha_add", {
6312
+ filePath: entry.filePath,
6313
+ severity
6314
+ });
6315
+ return `\u2705 **Gotcha recorded**: ${entry.filePath} \u2014 ${entry.description}
6316
+ ${mdResult}`;
6293
6317
  } catch (err) {
6294
- return `\u274C Test generation failed: ${err}`;
6318
+ return `\u274C Failed to add gotcha: ${err}`;
6295
6319
  }
6296
6320
  }
6297
- async function listGeneratedTests() {
6321
+ async function listGotchas(params) {
6298
6322
  try {
6299
- await ensureTrajectorySchema();
6323
+ await ensureGotchasSchema();
6300
6324
  const db = await getDb();
6301
- const stmt = db.prepare(`
6302
- SELECT gt.*, t.goal FROM generated_tests gt
6303
- LEFT JOIN trajectories t ON t.id = gt.trajectory_id
6304
- ORDER BY gt.created_at DESC LIMIT 20
6305
- `);
6306
- const results = [];
6307
- while (stmt.step()) results.push(stmt.getAsObject());
6308
- stmt.free();
6309
- if (results.length === 0) {
6310
- return "\u{1F9EA} **No generated tests yet.** Tests are auto-generated from successful fix trajectories.";
6325
+ let sql = "SELECT * FROM known_gotchas WHERE 1=1";
6326
+ const bind = [];
6327
+ if (params.filePath) {
6328
+ sql += " AND file_path LIKE ?";
6329
+ bind.push(`%${params.filePath}%`);
6311
6330
  }
6312
- const lines = [
6313
- "\u{1F9EA} **Generated Tests**",
6314
- "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
6315
- ""
6316
- ];
6317
- for (const r of results) {
6318
- const time = new Date(r.created_at * 1e3).toLocaleString();
6319
- lines.push(` \u{1F4C4} **${r.file_path}**`);
6320
- lines.push(` \u26A1 ${r.test_framework} | \u{1F550} ${time}`);
6321
- if (r.goal) lines.push(` \u{1F3AF} ${r.goal.substring(0, 60)}`);
6322
- lines.push("");
6331
+ if (params.severity) {
6332
+ sql += " AND severity = ?";
6333
+ bind.push(params.severity);
6323
6334
  }
6324
- return lines.join("\n");
6325
- } catch (err) {
6326
- return `Error: ${err}`;
6327
- }
6328
- }
6329
- function detectTestFramework() {
6330
- const root = getProjectRoot();
6331
- try {
6332
- const pkg = JSON.parse(fs14.readFileSync(path14.join(root, "package.json"), "utf-8"));
6333
- if (pkg.devDependencies?.vitest) return "vitest";
6334
- if (pkg.devDependencies?.jest || pkg.devDependencies?.["@jest/globals"]) return "jest";
6335
- } catch {
6336
- }
6337
- if (fs14.existsSync(path14.join(root, "vitest.config.ts")) || fs14.existsSync(path14.join(root, "vitest.config.js"))) return "vitest";
6338
- if (fs14.existsSync(path14.join(root, "jest.config.ts")) || fs14.existsSync(path14.join(root, "jest.config.js"))) return "jest";
6339
- return "node";
6340
- }
6341
- function determineTestPath(filePath, _framework) {
6342
- const dir = path14.dirname(filePath);
6343
- const basename = path14.basename(filePath, path14.extname(filePath));
6344
- return path14.join(dir, `__tests__`, `${basename}.fix.test.ts`);
6345
- }
6346
- function synthesizeTestCode(goal, primaryFile, modifiedFiles, framework) {
6347
- const importPath = primaryFile.replace(/\.ts$/, "").replace(/\.js$/, "");
6348
- const describeBlock = `describe('Fix: ${goal.substring(0, 60)}', () => {`;
6349
- const testBody = `
6350
- it('should not regress the fix applied in ${path14.basename(primaryFile)}', async () => {
6351
- // Auto-generated from Kuma trajectory \u2014 regression test
6352
- // Source file: ${primaryFile}
6353
- // Goal: ${goal}
6354
- // Modified files: ${modifiedFiles.slice(0, 3).join(", ")}
6355
-
6356
- // TODO: Replace with actual test logic
6357
- // const { yourFunction } = await import('${importPath}');
6358
- // const result = yourFunction();
6359
- // expect(result).toBeDefined();
6360
-
6361
- expect(true).toBe(true);
6362
- });
6363
- `;
6364
- const closeBlock = `});`;
6365
- switch (framework) {
6366
- case "vitest":
6367
- case "jest":
6368
- return [
6369
- `// ============================================================`,
6370
- `// AUTO-GENERATED REGRESSION TEST`,
6371
- `// Generated by Kuma Trajectory-to-Test Generator (Issue #28)`,
6372
- `// ${(/* @__PURE__ */ new Date()).toISOString()}`,
6373
- `// Goal: ${goal}`,
6374
- `// Files: ${modifiedFiles.join(", ")}`,
6375
- `// ============================================================`,
6376
- ``,
6377
- `import { describe, it, expect } from '@jest/globals';`,
6378
- ``,
6379
- describeBlock,
6380
- testBody,
6381
- closeBlock,
6382
- ``
6383
- ].join("\n");
6384
- default:
6385
- return [
6386
- `// AUTO-GENERATED REGRESSION TEST`,
6387
- `// Goal: ${goal}`,
6388
- `// Files: ${modifiedFiles.join(", ")}`,
6389
- ``,
6390
- `const assert = require('assert');`,
6391
- ``,
6392
- describeBlock,
6393
- testBody.replace("import", "// ").replace("expect", "// expect"),
6394
- closeBlock,
6395
- ``
6396
- ].join("\n");
6397
- }
6398
- }
6399
- async function listDistilledSkills() {
6400
- try {
6401
- await ensureTrajectorySchema();
6402
- const db = await getDb();
6403
- const stmt = db.prepare(`
6404
- SELECT * FROM distilled_skills ORDER BY success_count DESC, last_used_at DESC LIMIT 20
6405
- `);
6335
+ sql += " ORDER BY severity DESC, created_at DESC LIMIT 50";
6336
+ const stmt = db.prepare(sql);
6337
+ stmt.bind(bind);
6406
6338
  const results = [];
6407
6339
  while (stmt.step()) results.push(stmt.getAsObject());
6408
6340
  stmt.free();
6409
6341
  if (results.length === 0) {
6410
- return "\u{1F9E0} **No distilled skills yet** \u2014 skills are auto-generated from successful trajectories.";
6342
+ return "\u2705 **No gotchas recorded** \u2014 legacy codebase looks clean.";
6411
6343
  }
6412
6344
  const lines = [
6413
- "\u{1F9E0} **Distilled Skills** \u2014 auto-generated from trajectories",
6414
- "\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\u2501",
6345
+ `\u26A0\uFE0F **Known Gotchas** \u2014 ${results.length} recorded`,
6346
+ "\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",
6415
6347
  ""
6416
6348
  ];
6417
- for (const s of results) {
6418
- const pattern = JSON.parse(s.pattern || "[]");
6419
- const patternStr = pattern.slice(0, 5).join(" \u2192 ");
6420
- lines.push(` \u26A1 **${s.name}**`);
6421
- lines.push(` Pattern: ${patternStr}${pattern.length > 5 ? ` (+${pattern.length - 5} more)` : ""}`);
6422
- lines.push(` Used ${s.success_count}x | Avg ${s.avg_duration_ms}ms`);
6349
+ for (const g of results) {
6350
+ const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
6351
+ lines.push(`${icon} [${g.severity}] ${g.file_path}`);
6352
+ lines.push(` \u{1F4DD} ${g.description?.toString().substring(0, 100)}`);
6353
+ if (g.workaround) lines.push(` \u{1F4A1} ${g.workaround.substring(0, 100)}`);
6423
6354
  lines.push("");
6424
6355
  }
6425
6356
  return lines.join("\n");
@@ -6427,76 +6358,55 @@ async function listDistilledSkills() {
6427
6358
  return `Error: ${err}`;
6428
6359
  }
6429
6360
  }
6430
- async function findSimilarTrajectories(errorPattern, limit = 5) {
6361
+ function checkGotchasForFile(filePath) {
6362
+ const warnings = [];
6363
+ const markdownWarnings = checkFileGotchas(filePath);
6431
6364
  try {
6432
- await ensureTrajectorySchema();
6433
- const db = await getDb();
6434
- const stmt = db.prepare(`
6435
- SELECT id, goal, steps, total_duration_ms, success_rate, complexity, created_at
6436
- FROM trajectories
6437
- WHERE steps LIKE ?
6438
- ORDER BY complexity DESC, created_at DESC
6439
- LIMIT ?
6440
- `);
6441
- stmt.bind([`%${errorPattern}%`, limit]);
6442
- const results = [];
6443
- while (stmt.step()) results.push(stmt.getAsObject());
6444
- stmt.free();
6445
- if (results.length === 0) return "";
6446
- const lines = [
6447
- "\u{1F504} **Similar Past Trajectories Found**",
6448
- "\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",
6449
- ""
6450
- ];
6451
- for (const r of results) {
6452
- const successIcon = r.success_rate > 0.8 ? "\u2705" : "\u26A0\uFE0F";
6453
- lines.push(`${successIcon} #${r.id} \u2014 ${r.goal.substring(0, 60)}`);
6454
- lines.push(` Complexity: ${r.complexity} | Duration: ${Math.round(r.total_duration_ms / 1e3)}s | Rate: ${Math.round(r.success_rate * 100)}%`);
6455
- lines.push("");
6365
+ const gotchas = getActiveGotchas();
6366
+ for (const g of gotchas) {
6367
+ if (filePath.includes(g.filePath) || g.filePath.includes(filePath)) {
6368
+ if (!markdownWarnings.some((w) => w.includes(g.description))) {
6369
+ const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
6370
+ warnings.push(`${icon} **Known Gotcha**: ${g.description} (${g.severity})`);
6371
+ }
6372
+ }
6456
6373
  }
6457
- return lines.join("\n");
6458
6374
  } catch {
6459
- return "";
6460
6375
  }
6376
+ return [.../* @__PURE__ */ new Set([...markdownWarnings, ...warnings])];
6461
6377
  }
6462
- async function listTrajectories(limit = 10) {
6378
+ async function syncGotchasToDb() {
6463
6379
  try {
6464
- await ensureTrajectorySchema();
6380
+ await ensureGotchasSchema();
6465
6381
  const db = await getDb();
6466
- const stmt = db.prepare(`
6467
- SELECT id, goal, total_duration_ms, success_rate, complexity, created_at
6468
- FROM trajectories ORDER BY created_at DESC LIMIT ?
6469
- `);
6470
- stmt.bind([limit]);
6471
- const results = [];
6472
- while (stmt.step()) results.push(stmt.getAsObject());
6473
- stmt.free();
6474
- if (results.length === 0) {
6475
- return "\u{1F4C8} **No trajectories recorded yet.** Trajectories are logged automatically as agents work.";
6476
- }
6477
- const lines = [
6478
- "\u{1F4C8} **Agent Trajectories**",
6479
- "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
6480
- ""
6481
- ];
6482
- for (const r of results) {
6483
- const time = new Date(r.created_at * 1e3).toLocaleString();
6484
- const successIcon = r.success_rate > 0.8 ? "\u2705" : "\u26A0\uFE0F";
6485
- lines.push(` ${successIcon} #${r.id} \u2014 ${r.goal.substring(0, 50)} @ ${time}`);
6486
- lines.push(` ${Math.round(r.total_duration_ms / 1e3)}s | ${Math.round(r.success_rate * 100)}% success | complexity: ${r.complexity}`);
6487
- lines.push("");
6382
+ const gotchas = getActiveGotchas();
6383
+ let synced = 0;
6384
+ for (const g of gotchas) {
6385
+ const checkStmt = db.prepare(
6386
+ `SELECT id FROM known_gotchas WHERE file_path = ? AND description = ?`
6387
+ );
6388
+ checkStmt.bind([g.filePath, g.description]);
6389
+ const exists = checkStmt.step();
6390
+ checkStmt.free();
6391
+ if (exists) continue;
6392
+ db.run(
6393
+ `INSERT INTO known_gotchas (file_path, description, severity) VALUES (?, ?, ?)`,
6394
+ [g.filePath, g.description, g.severity]
6395
+ );
6396
+ synced++;
6488
6397
  }
6489
- return lines.join("\n");
6490
- } catch (err) {
6491
- return `Error: ${err}`;
6398
+ saveDb();
6399
+ return { synced };
6400
+ } catch {
6401
+ return { synced: 0 };
6492
6402
  }
6493
6403
  }
6494
- var init_kumaTrajectory = __esm({
6495
- "src/engine/kumaTrajectory.ts"() {
6404
+ var init_kumaGotchas = __esm({
6405
+ "src/engine/kumaGotchas.ts"() {
6496
6406
  "use strict";
6497
- init_kumaDb();
6498
6407
  init_sessionMemory();
6499
- init_pathValidator();
6408
+ init_kumaDb();
6409
+ init_domainRules();
6500
6410
  }
6501
6411
  });
6502
6412
 
@@ -6669,19 +6579,19 @@ __export(kumaPolicyEngine_exports, {
6669
6579
  processOverride: () => processOverride,
6670
6580
  savePolicyConfig: () => savePolicyConfig
6671
6581
  });
6672
- import fs20 from "fs";
6673
- import path20 from "path";
6582
+ import fs19 from "fs";
6583
+ import path19 from "path";
6674
6584
  function loadPolicyConfig() {
6675
6585
  try {
6676
6586
  const root = getProjectRoot();
6677
- const policyPath = path20.join(root, ".kuma", "POLICY.json");
6678
- if (fs20.existsSync(policyPath)) {
6679
- const content = fs20.readFileSync(policyPath, "utf-8");
6587
+ const policyPath = path19.join(root, ".kuma", "POLICY.json");
6588
+ if (fs19.existsSync(policyPath)) {
6589
+ const content = fs19.readFileSync(policyPath, "utf-8");
6680
6590
  const config = JSON.parse(content);
6681
6591
  return { ...DEFAULT_POLICY_CONFIG, ...config };
6682
6592
  }
6683
- const ymlPath = path20.join(root, ".kuma", "policy.yml");
6684
- if (fs20.existsSync(ymlPath)) {
6593
+ const ymlPath = path19.join(root, ".kuma", "policy.yml");
6594
+ if (fs19.existsSync(ymlPath)) {
6685
6595
  console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
6686
6596
  }
6687
6597
  } catch (err) {
@@ -6692,10 +6602,10 @@ function loadPolicyConfig() {
6692
6602
  function savePolicyConfig(config) {
6693
6603
  try {
6694
6604
  const root = getProjectRoot();
6695
- const policyPath = path20.join(root, ".kuma", "POLICY.json");
6696
- const dir = path20.dirname(policyPath);
6697
- if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
6698
- fs20.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
6605
+ const policyPath = path19.join(root, ".kuma", "POLICY.json");
6606
+ const dir = path19.dirname(policyPath);
6607
+ if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
6608
+ fs19.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
6699
6609
  return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
6700
6610
  } catch (err) {
6701
6611
  return `\u274C Failed to save policy: ${err}`;
@@ -6980,14 +6890,14 @@ __export(kumaAstValidator_exports, {
6980
6890
  validateDiff: () => validateDiff,
6981
6891
  validateFile: () => validateFile
6982
6892
  });
6983
- import fs21 from "fs";
6984
- import path21 from "path";
6893
+ import fs20 from "fs";
6894
+ import path20 from "path";
6985
6895
  function loadImportWhitelist() {
6986
6896
  try {
6987
6897
  const root = getProjectRoot();
6988
- const whitelistPath = path21.join(root, ".kuma", "import-whitelist.json");
6989
- if (fs21.existsSync(whitelistPath)) {
6990
- return JSON.parse(fs21.readFileSync(whitelistPath, "utf-8"));
6898
+ const whitelistPath = path20.join(root, ".kuma", "import-whitelist.json");
6899
+ if (fs20.existsSync(whitelistPath)) {
6900
+ return JSON.parse(fs20.readFileSync(whitelistPath, "utf-8"));
6991
6901
  }
6992
6902
  } catch {
6993
6903
  }
@@ -7061,9 +6971,9 @@ function validateCodeContent(code, filePath) {
7061
6971
  function validateFile(filePath) {
7062
6972
  try {
7063
6973
  const root = getProjectRoot();
7064
- const fullPath = path21.resolve(root, filePath);
7065
- if (!fs21.existsSync(fullPath)) return [];
7066
- const content = fs21.readFileSync(fullPath, "utf-8");
6974
+ const fullPath = path20.resolve(root, filePath);
6975
+ if (!fs20.existsSync(fullPath)) return [];
6976
+ const content = fs20.readFileSync(fullPath, "utf-8");
7067
6977
  return validateCodeContent(content, filePath);
7068
6978
  } catch {
7069
6979
  return [];
@@ -7134,16 +7044,16 @@ __export(kumaCheckpoint_exports, {
7134
7044
  pruneCheckpoints: () => pruneCheckpoints,
7135
7045
  rollbackToCheckpoint: () => rollbackToCheckpoint
7136
7046
  });
7137
- import fs22 from "fs";
7138
- import path22 from "path";
7047
+ import fs21 from "fs";
7048
+ import path21 from "path";
7139
7049
  async function createCheckpoint(label, description) {
7140
7050
  try {
7141
7051
  const root = getProjectRoot();
7142
- const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7143
- if (fs22.existsSync(cpDir)) {
7052
+ const cpDir = path21.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7053
+ if (fs21.existsSync(cpDir)) {
7144
7054
  return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
7145
7055
  }
7146
- fs22.mkdirSync(cpDir, { recursive: true });
7056
+ fs21.mkdirSync(cpDir, { recursive: true });
7147
7057
  const db = await getDb();
7148
7058
  const filesStmt = db.prepare(
7149
7059
  "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
@@ -7152,19 +7062,19 @@ async function createCheckpoint(label, description) {
7152
7062
  while (filesStmt.step()) {
7153
7063
  const row = filesStmt.getAsObject();
7154
7064
  const fp = row.file_path;
7155
- const fullPath = path22.resolve(root, fp);
7156
- if (fs22.existsSync(fullPath)) {
7157
- const content = fs22.readFileSync(fullPath, "utf-8");
7065
+ const fullPath = path21.resolve(root, fp);
7066
+ if (fs21.existsSync(fullPath)) {
7067
+ const content = fs21.readFileSync(fullPath, "utf-8");
7158
7068
  const hash = simpleHash2(content);
7159
- const fileDir = path22.dirname(path22.join(cpDir, "files", fp));
7160
- if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
7161
- fs22.writeFileSync(path22.join(cpDir, "files", fp), content, "utf-8");
7069
+ const fileDir = path21.dirname(path21.join(cpDir, "files", fp));
7070
+ if (!fs21.existsSync(fileDir)) fs21.mkdirSync(fileDir, { recursive: true });
7071
+ fs21.writeFileSync(path21.join(cpDir, "files", fp), content, "utf-8");
7162
7072
  files.push({ path: fp, hash });
7163
7073
  }
7164
7074
  }
7165
7075
  filesStmt.free();
7166
7076
  const dbData = db.export();
7167
- fs22.writeFileSync(path22.join(cpDir, "kuma.db"), Buffer.from(dbData));
7077
+ fs21.writeFileSync(path21.join(cpDir, "kuma.db"), Buffer.from(dbData));
7168
7078
  const manifest = {
7169
7079
  label,
7170
7080
  timestamp: Date.now(),
@@ -7172,8 +7082,8 @@ async function createCheckpoint(label, description) {
7172
7082
  dbSnapshot: true,
7173
7083
  description
7174
7084
  };
7175
- fs22.writeFileSync(
7176
- path22.join(cpDir, "manifest.json"),
7085
+ fs21.writeFileSync(
7086
+ path21.join(cpDir, "manifest.json"),
7177
7087
  JSON.stringify(manifest, null, 2),
7178
7088
  "utf-8"
7179
7089
  );
@@ -7199,37 +7109,37 @@ async function createCheckpoint(label, description) {
7199
7109
  async function rollbackToCheckpoint(label) {
7200
7110
  try {
7201
7111
  const root = getProjectRoot();
7202
- const cpDir = path22.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7203
- const manifestPath = path22.join(cpDir, "manifest.json");
7204
- if (!fs22.existsSync(manifestPath)) {
7112
+ const cpDir = path21.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
7113
+ const manifestPath = path21.join(cpDir, "manifest.json");
7114
+ if (!fs21.existsSync(manifestPath)) {
7205
7115
  return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
7206
7116
  }
7207
7117
  const manifest = JSON.parse(
7208
- fs22.readFileSync(manifestPath, "utf-8")
7118
+ fs21.readFileSync(manifestPath, "utf-8")
7209
7119
  );
7210
7120
  let restored = 0;
7211
7121
  let failed = 0;
7212
7122
  for (const f of manifest.files) {
7213
- const snapshotPath = path22.join(cpDir, "files", f.path);
7214
- if (fs22.existsSync(snapshotPath)) {
7123
+ const snapshotPath = path21.join(cpDir, "files", f.path);
7124
+ if (fs21.existsSync(snapshotPath)) {
7215
7125
  try {
7216
- const content = fs22.readFileSync(snapshotPath, "utf-8");
7217
- const targetPath = path22.resolve(root, f.path);
7218
- const targetDir = path22.dirname(targetPath);
7219
- if (!fs22.existsSync(targetDir)) fs22.mkdirSync(targetDir, { recursive: true });
7220
- fs22.writeFileSync(targetPath, content, "utf-8");
7126
+ const content = fs21.readFileSync(snapshotPath, "utf-8");
7127
+ const targetPath = path21.resolve(root, f.path);
7128
+ const targetDir = path21.dirname(targetPath);
7129
+ if (!fs21.existsSync(targetDir)) fs21.mkdirSync(targetDir, { recursive: true });
7130
+ fs21.writeFileSync(targetPath, content, "utf-8");
7221
7131
  restored++;
7222
7132
  } catch {
7223
7133
  failed++;
7224
7134
  }
7225
7135
  }
7226
7136
  }
7227
- const dbSnapshotPath = path22.join(cpDir, "kuma.db");
7228
- if (fs22.existsSync(dbSnapshotPath)) {
7229
- const kumaDir = path22.join(root, ".kuma");
7230
- const dbPath = path22.join(kumaDir, "kuma.db");
7231
- const snapshotData = fs22.readFileSync(dbSnapshotPath);
7232
- fs22.writeFileSync(dbPath, snapshotData);
7137
+ const dbSnapshotPath = path21.join(cpDir, "kuma.db");
7138
+ if (fs21.existsSync(dbSnapshotPath)) {
7139
+ const kumaDir = path21.join(root, ".kuma");
7140
+ const dbPath = path21.join(kumaDir, "kuma.db");
7141
+ const snapshotData = fs21.readFileSync(dbSnapshotPath);
7142
+ fs21.writeFileSync(dbPath, snapshotData);
7233
7143
  resetDbInstance();
7234
7144
  }
7235
7145
  sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
@@ -7254,18 +7164,18 @@ async function rollbackToCheckpoint(label) {
7254
7164
  function listCheckpoints() {
7255
7165
  try {
7256
7166
  const root = getProjectRoot();
7257
- const cpDir = path22.join(root, CHECKPOINT_DIR);
7258
- if (!fs22.existsSync(cpDir)) {
7167
+ const cpDir = path21.join(root, CHECKPOINT_DIR);
7168
+ if (!fs21.existsSync(cpDir)) {
7259
7169
  return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
7260
7170
  }
7261
- const entries = fs22.readdirSync(cpDir);
7171
+ const entries = fs21.readdirSync(cpDir);
7262
7172
  const checkpoints = [];
7263
7173
  for (const entry of entries) {
7264
- const manifestPath = path22.join(cpDir, entry, "manifest.json");
7265
- if (fs22.existsSync(manifestPath)) {
7174
+ const manifestPath = path21.join(cpDir, entry, "manifest.json");
7175
+ if (fs21.existsSync(manifestPath)) {
7266
7176
  try {
7267
7177
  const manifest = JSON.parse(
7268
- fs22.readFileSync(manifestPath, "utf-8")
7178
+ fs21.readFileSync(manifestPath, "utf-8")
7269
7179
  );
7270
7180
  checkpoints.push({ label: entry, manifest });
7271
7181
  } catch {
@@ -7297,12 +7207,12 @@ function listCheckpoints() {
7297
7207
  function pruneCheckpoints(keep = 5) {
7298
7208
  try {
7299
7209
  const root = getProjectRoot();
7300
- const cpDir = path22.join(root, CHECKPOINT_DIR);
7301
- if (!fs22.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
7302
- const entries = fs22.readdirSync(cpDir).map((e) => ({ name: e, time: fs22.statSync(path22.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
7210
+ const cpDir = path21.join(root, CHECKPOINT_DIR);
7211
+ if (!fs21.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
7212
+ const entries = fs21.readdirSync(cpDir).map((e) => ({ name: e, time: fs21.statSync(path21.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
7303
7213
  let removed = 0;
7304
7214
  for (let i = keep; i < entries.length; i++) {
7305
- fs22.rmSync(path22.join(cpDir, entries[i].name), { recursive: true, force: true });
7215
+ fs21.rmSync(path21.join(cpDir, entries[i].name), { recursive: true, force: true });
7306
7216
  removed++;
7307
7217
  }
7308
7218
  return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
@@ -7342,18 +7252,18 @@ __export(kumaContractEngine_exports, {
7342
7252
  loadContracts: () => loadContracts,
7343
7253
  runContractChecks: () => runContractChecks
7344
7254
  });
7345
- import fs23 from "fs";
7346
- import path23 from "path";
7255
+ import fs22 from "fs";
7256
+ import path22 from "path";
7347
7257
  function contractsDir() {
7348
- return path23.join(getProjectRoot(), ".kuma", "contracts");
7258
+ return path22.join(getProjectRoot(), ".kuma", "contracts");
7349
7259
  }
7350
7260
  function ensureContractsDir() {
7351
7261
  const dir = contractsDir();
7352
- if (!fs23.existsSync(dir)) {
7353
- fs23.mkdirSync(dir, { recursive: true });
7354
- const defaultPath = path23.join(dir, "default.json");
7355
- if (!fs23.existsSync(defaultPath)) {
7356
- fs23.writeFileSync(
7262
+ if (!fs22.existsSync(dir)) {
7263
+ fs22.mkdirSync(dir, { recursive: true });
7264
+ const defaultPath = path22.join(dir, "default.json");
7265
+ if (!fs22.existsSync(defaultPath)) {
7266
+ fs22.writeFileSync(
7357
7267
  defaultPath,
7358
7268
  JSON.stringify(DEFAULT_CONTRACTS, null, 2),
7359
7269
  "utf-8"
@@ -7366,10 +7276,10 @@ function loadContracts() {
7366
7276
  const dir = contractsDir();
7367
7277
  const contracts = [];
7368
7278
  try {
7369
- const files = fs23.readdirSync(dir).filter((f) => f.endsWith(".json"));
7279
+ const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".json"));
7370
7280
  for (const file of files) {
7371
7281
  try {
7372
- const content = fs23.readFileSync(path23.join(dir, file), "utf-8");
7282
+ const content = fs22.readFileSync(path22.join(dir, file), "utf-8");
7373
7283
  const config = JSON.parse(content);
7374
7284
  contracts.push(config);
7375
7285
  } catch (err) {
@@ -7442,7 +7352,7 @@ function evaluateCondition(condition, contextPath) {
7442
7352
  switch (condition.type) {
7443
7353
  case "file_exists": {
7444
7354
  const targetPath = resolveTarget(condition.target, contextPath, root);
7445
- const exists = targetPath.some((p) => fs23.existsSync(p));
7355
+ const exists = targetPath.some((p) => fs22.existsSync(p));
7446
7356
  return {
7447
7357
  passed: exists,
7448
7358
  message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
@@ -7450,7 +7360,7 @@ function evaluateCondition(condition, contextPath) {
7450
7360
  }
7451
7361
  case "file_not_exists": {
7452
7362
  const targetPath = resolveTarget(condition.target, contextPath, root);
7453
- const exists = targetPath.some((p) => fs23.existsSync(p));
7363
+ const exists = targetPath.some((p) => fs22.existsSync(p));
7454
7364
  return {
7455
7365
  passed: !exists,
7456
7366
  message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
@@ -7458,9 +7368,9 @@ function evaluateCondition(condition, contextPath) {
7458
7368
  }
7459
7369
  case "contains": {
7460
7370
  if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
7461
- const fullPath = path23.resolve(root, contextPath);
7462
- if (!fs23.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
7463
- const content = fs23.readFileSync(fullPath, "utf-8");
7371
+ const fullPath = path22.resolve(root, contextPath);
7372
+ if (!fs22.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
7373
+ const content = fs22.readFileSync(fullPath, "utf-8");
7464
7374
  const hasContent = condition.value ? content.includes(condition.value) : false;
7465
7375
  return {
7466
7376
  passed: hasContent,
@@ -7472,12 +7382,12 @@ function evaluateCondition(condition, contextPath) {
7472
7382
  const root2 = getProjectRoot();
7473
7383
  const targets = resolveTarget(condition.target, contextPath, root2);
7474
7384
  for (const t of targets) {
7475
- if (fs23.existsSync(t)) {
7476
- const content = fs23.readFileSync(t, "utf-8");
7385
+ if (fs22.existsSync(t)) {
7386
+ const content = fs22.readFileSync(t, "utf-8");
7477
7387
  if (condition.value && content.includes(condition.value)) {
7478
7388
  return {
7479
7389
  passed: false,
7480
- message: `\u274C Found forbidden pattern "${condition.value}" in ${path23.relative(root2, t)} (${condition.description})`
7390
+ message: `\u274C Found forbidden pattern "${condition.value}" in ${path22.relative(root2, t)} (${condition.description})`
7481
7391
  };
7482
7392
  }
7483
7393
  }
@@ -7486,15 +7396,15 @@ function evaluateCondition(condition, contextPath) {
7486
7396
  }
7487
7397
  case "has_test_file": {
7488
7398
  if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
7489
- const basename = path23.basename(contextPath, path23.extname(contextPath));
7490
- const dir = path23.dirname(contextPath);
7399
+ const basename = path22.basename(contextPath, path22.extname(contextPath));
7400
+ const dir = path22.dirname(contextPath);
7491
7401
  const possibleTests = [
7492
- path23.join(dir, `${basename}.test.ts`),
7493
- path23.join(dir, `${basename}.spec.ts`),
7494
- path23.join(dir, `__tests__/${basename}.test.ts`),
7495
- path23.join(dir, `__tests__/${basename}.spec.ts`)
7402
+ path22.join(dir, `${basename}.test.ts`),
7403
+ path22.join(dir, `${basename}.spec.ts`),
7404
+ path22.join(dir, `__tests__/${basename}.test.ts`),
7405
+ path22.join(dir, `__tests__/${basename}.spec.ts`)
7496
7406
  ];
7497
- const hasTest = possibleTests.some((t) => fs23.existsSync(t));
7407
+ const hasTest = possibleTests.some((t) => fs22.existsSync(t));
7498
7408
  return {
7499
7409
  passed: hasTest,
7500
7410
  message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
@@ -7518,18 +7428,18 @@ function resolveTarget(pattern, contextPath, root) {
7518
7428
  const r = root || getProjectRoot();
7519
7429
  if (pattern.startsWith("*")) {
7520
7430
  if (contextPath) {
7521
- const dir = path23.dirname(path23.resolve(r, contextPath));
7431
+ const dir = path22.dirname(path22.resolve(r, contextPath));
7522
7432
  const suffix = pattern.replace("*", "");
7523
7433
  try {
7524
- const files = fs23.readdirSync(dir);
7525
- return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path23.join(dir, f));
7434
+ const files = fs22.readdirSync(dir);
7435
+ return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path22.join(dir, f));
7526
7436
  } catch {
7527
- return [path23.join(dir, pattern.replace("*", ""))];
7437
+ return [path22.join(dir, pattern.replace("*", ""))];
7528
7438
  }
7529
7439
  }
7530
7440
  return [];
7531
7441
  }
7532
- return [path23.resolve(r, pattern)];
7442
+ return [path22.resolve(r, pattern)];
7533
7443
  }
7534
7444
  async function runContractChecks(toolName, filePath, phase = "pre") {
7535
7445
  const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
@@ -7650,37 +7560,37 @@ __export(kumaVerifier_exports, {
7650
7560
  getRunningVerificationPid: () => getRunningVerificationPid,
7651
7561
  runAutoVerification: () => runAutoVerification
7652
7562
  });
7653
- import fs24 from "fs";
7654
- import path24 from "path";
7563
+ import fs23 from "fs";
7564
+ import path23 from "path";
7655
7565
  import { exec } from "child_process";
7656
7566
  function getLockDir(root = process.cwd()) {
7657
- return path24.resolve(root, LOCK_DIR_NAME);
7567
+ return path23.resolve(root, LOCK_DIR_NAME);
7658
7568
  }
7659
7569
  function getPidFile(root = process.cwd()) {
7660
- return path24.join(getLockDir(root), "pid");
7570
+ return path23.join(getLockDir(root), "pid");
7661
7571
  }
7662
7572
  function acquireFileLock(root) {
7663
7573
  const lockDir = getLockDir(root);
7664
7574
  try {
7665
- fs24.mkdirSync(lockDir, { recursive: false });
7666
- fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7575
+ fs23.mkdirSync(lockDir, { recursive: false });
7576
+ fs23.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7667
7577
  return true;
7668
7578
  } catch {
7669
7579
  try {
7670
- if (fs24.existsSync(getPidFile(root))) {
7671
- const pid = parseInt(fs24.readFileSync(getPidFile(root), "utf-8"), 10);
7580
+ if (fs23.existsSync(getPidFile(root))) {
7581
+ const pid = parseInt(fs23.readFileSync(getPidFile(root), "utf-8"), 10);
7672
7582
  try {
7673
7583
  process.kill(pid, 0);
7674
7584
  return false;
7675
7585
  } catch {
7676
- fs24.rmSync(lockDir, { recursive: true, force: true });
7586
+ fs23.rmSync(lockDir, { recursive: true, force: true });
7677
7587
  return acquireFileLock(root);
7678
7588
  }
7679
7589
  }
7680
7590
  } catch {
7681
7591
  }
7682
7592
  try {
7683
- fs24.rmSync(lockDir, { recursive: true, force: true });
7593
+ fs23.rmSync(lockDir, { recursive: true, force: true });
7684
7594
  return acquireFileLock(root);
7685
7595
  } catch {
7686
7596
  return false;
@@ -7689,7 +7599,7 @@ function acquireFileLock(root) {
7689
7599
  }
7690
7600
  function releaseFileLock(root) {
7691
7601
  try {
7692
- fs24.rmSync(getLockDir(root), { recursive: true, force: true });
7602
+ fs23.rmSync(getLockDir(root), { recursive: true, force: true });
7693
7603
  } catch {
7694
7604
  }
7695
7605
  }
@@ -7712,36 +7622,36 @@ function getRunningVerificationPid() {
7712
7622
  return _currentProcess?.pid ?? null;
7713
7623
  }
7714
7624
  function detectTestRunner(root = process.cwd()) {
7715
- const pkgPath = path24.join(root, "package.json");
7716
- if (fs24.existsSync(pkgPath)) {
7625
+ const pkgPath = path23.join(root, "package.json");
7626
+ if (fs23.existsSync(pkgPath)) {
7717
7627
  try {
7718
- const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
7628
+ const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf-8"));
7719
7629
  if (pkg.scripts && pkg.scripts.test) {
7720
- if (fs24.existsSync(path24.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
7721
- if (fs24.existsSync(path24.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
7630
+ if (fs23.existsSync(path23.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
7631
+ if (fs23.existsSync(path23.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
7722
7632
  return { runner: "npm", baseCommand: "npm test" };
7723
7633
  }
7724
7634
  } catch {
7725
7635
  }
7726
7636
  }
7727
- if (fs24.existsSync(path24.join(root, "pytest.ini")) || fs24.existsSync(path24.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
7728
- if (fs24.existsSync(path24.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
7729
- if (fs24.existsSync(path24.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
7730
- if (fs24.existsSync(path24.join(root, "Makefile"))) {
7637
+ if (fs23.existsSync(path23.join(root, "pytest.ini")) || fs23.existsSync(path23.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
7638
+ if (fs23.existsSync(path23.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
7639
+ if (fs23.existsSync(path23.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
7640
+ if (fs23.existsSync(path23.join(root, "Makefile"))) {
7731
7641
  try {
7732
- const makefileContent = fs24.readFileSync(path24.join(root, "Makefile"), "utf-8");
7642
+ const makefileContent = fs23.readFileSync(path23.join(root, "Makefile"), "utf-8");
7733
7643
  const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
7734
7644
  if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
7735
7645
  } catch {
7736
7646
  }
7737
7647
  }
7738
- if (fs24.existsSync(path24.join(root, "package.json"))) {
7648
+ if (fs23.existsSync(path23.join(root, "package.json"))) {
7739
7649
  try {
7740
- const pkg = JSON.parse(fs24.readFileSync(path24.join(root, "package.json"), "utf-8"));
7650
+ const pkg = JSON.parse(fs23.readFileSync(path23.join(root, "package.json"), "utf-8"));
7741
7651
  if (pkg.dependencies || pkg.devDependencies) {
7742
- if (fs24.existsSync(path24.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
7743
- const srcDir = path24.join(root, "src");
7744
- 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)` };
7652
+ if (fs23.existsSync(path23.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
7653
+ const srcDir = path23.join(root, "src");
7654
+ if (fs23.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
7745
7655
  }
7746
7656
  } catch {
7747
7657
  }
@@ -7841,7 +7751,7 @@ async function runAutoVerification(options = {}) {
7841
7751
  const heartbeatTimer = setInterval(() => {
7842
7752
  if (getLockDir(root)) {
7843
7753
  try {
7844
- fs24.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7754
+ fs23.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
7845
7755
  } catch {
7846
7756
  }
7847
7757
  }
@@ -7918,8 +7828,8 @@ __export(init_exports, {
7918
7828
  generateInitMdContent: () => generateInitMdContent,
7919
7829
  runInit: () => runInit
7920
7830
  });
7921
- import fs25 from "fs";
7922
- import path25 from "path";
7831
+ import fs24 from "fs";
7832
+ import path24 from "path";
7923
7833
  function configFilePath(type) {
7924
7834
  switch (type) {
7925
7835
  case "claude":
@@ -8366,14 +8276,11 @@ function generateInitMdContent() {
8366
8276
  "| `todo` | Persistent todo CRUD | When managing tasks |",
8367
8277
  "| `context` | Inject context notes | Additional notes when needed |",
8368
8278
  "| `domain_rules` | Layer 1: business rules | Record business rules when discovered |",
8369
- "| `federated` | Resolve kuma:// URIs | Cross-project references |",
8370
- "| `gen_test` | Generate test from trajectory | After trajectory analysis |",
8371
- "| `trajectory` | List trajectories | Review past agent trajectories |",
8372
- "| `skills` | List distilled skills | See auto-learned patterns |",
8279
+ "| `feature` | Record high-level feature with owns edges | Define features like 'Authentication', 'Billing' |",
8373
8280
  "| `add_node` | Create structural nodes (low impact \u2014 grep faster) | Rarely; only for visualize debugging |",
8374
8281
  "",
8375
8282
  "| `delete_node` | Delete specific nodes, gotchas, todos, or decisions by ID | When you saved wrong data or need cleanup |",
8376
- "| `clear` | Wipe ALL graph nodes, edges, gotchas, trajectories | Emergency reset \u2014 irreversible |",
8283
+ "| `clear` | Wipe ALL graph nodes, edges, gotchas | Emergency reset \u2014 irreversible |",
8377
8284
  "| `layers` | Show all 3 layers summary | Overview memory layers |",
8378
8285
  "",
8379
8286
  "### \u{1F6E1}\uFE0F kuma_safety \u2014 Safety & Policy",
@@ -8445,6 +8352,7 @@ function generateInitMdContent() {
8445
8352
  "| \u{1F539} `small_square` | variable | Variables/constants | AUTO by scanner |",
8446
8353
  "| \u{1F504} `circle` | workflow | Workflow definitions | AUTO by scanner |",
8447
8354
  "| \u{1F4C4} `note` | context | Manual context notes | MANUAL \u2014 kuma_memory context |",
8355
+ "| \u2B50 `star` | feature | High-level feature (Authentication, Billing) | MANUAL \u2014 kuma_memory feature |",
8448
8356
  "| \u{1F3DB}\uFE0F `pillar` | feature_domain | High-level feature domain anchor | MANUAL \u2014 kuma_memory arch_flow (domain:) |",
8449
8357
  "| \u{1F517} `link` | cross_service_link | Cross-service/inter-file flow step | MANUAL \u2014 kuma_memory arch_flow (hops:) |",
8450
8358
  "| \u{1F3F7}\uFE0F `tag` | domain_rule | Business rules (Layer 1) | MANUAL \u2014 kuma_memory domain_rules |",
@@ -8461,7 +8369,8 @@ function generateInitMdContent() {
8461
8369
  "- `extends` \u2014 class extends class (AUTO)",
8462
8370
  "- `contains` \u2014 file contains function (AUTO)",
8463
8371
  "- `composes` \u2014 component contains sub-component (AUTO)",
8464
- "- `owns` \u2014 domain owns file (MANUAL \u2014 arch_flow)",
8372
+ "- `owns` \u2014 domain/file owns file (MANUAL \u2014 arch_flow, feature)",
8373
+ "- `affects` \u2014 impact propagation chain (MANUAL \u2014 arch_flow, feature)",
8465
8374
  "- `flows_through` \u2014 flow step to next step (MANUAL \u2014 arch_flow)",
8466
8375
  "- `triggers` \u2014 flow triggers action (MANUAL)",
8467
8376
  "- `syncs_with` \u2014 cross-service sync (MANUAL)",
@@ -8490,11 +8399,17 @@ function generateInitMdContent() {
8490
8399
  "\u{1F9E0} Graph is persistent \u2014 nodes/edges accumulate across sessions.",
8491
8400
  "",
8492
8401
  "ON DISCOVERY (When needed):",
8402
+ " kuma_memory \u2192 feature \u2014 Record high-level feature (Authentication, Billing)",
8493
8403
  " kuma_memory \u2192 domain_rules \u2014 Business rules (Layer 1)",
8494
8404
  " kuma_memory \u2192 context \u2014 Additional notes",
8495
8405
  " kuma_memory \u2192 todo \u2014 Task management",
8496
8406
  " kuma_memory \u2192 mine \u2014 Historical context",
8497
8407
  " kuma_memory \u2192 add_node \u2014 Manual function/class/component node (for visualize)",
8408
+ "",
8409
+ "GRAPH-AS-RETRIEVAL (search now includes):",
8410
+ " \u2022 Task-focused retrieval: top N relevant nodes for a task description",
8411
+ " \u2022 Impact analysis: which nodes are affected by a change",
8412
+ " \u2022 arch_flow now auto-creates affects edges for impact propagation",
8498
8413
  "```",
8499
8414
  "",
8500
8415
  "## \u{1F9E0} How the Scanner Works",
@@ -8589,10 +8504,10 @@ function generateInitMdContent() {
8589
8504
  ].join("\n");
8590
8505
  }
8591
8506
  function handleOpencodeSecondary(root, results) {
8592
- const skillPath = path25.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
8507
+ const skillPath = path24.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
8593
8508
  if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
8594
8509
  try {
8595
- const dir = path25.dirname(skillPath);
8510
+ const dir = path24.dirname(skillPath);
8596
8511
  const content = [
8597
8512
  "---",
8598
8513
  "name: kuma-mcp",
@@ -8625,18 +8540,18 @@ function handleOpencodeSecondary(root, results) {
8625
8540
  "",
8626
8541
  "\u{1F4D6} Full rules: `.kuma/init.md`"
8627
8542
  ].join("\n");
8628
- if (fs25.existsSync(skillPath)) {
8629
- const existing = fs25.readFileSync(skillPath, "utf-8");
8543
+ if (fs24.existsSync(skillPath)) {
8544
+ const existing = fs24.readFileSync(skillPath, "utf-8");
8630
8545
  if (existing.includes("kuma-mcp")) {
8631
8546
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
8632
8547
  return;
8633
8548
  }
8634
8549
  const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
8635
- fs25.writeFileSync(skillPath, newContent, "utf-8");
8550
+ fs24.writeFileSync(skillPath, newContent, "utf-8");
8636
8551
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
8637
8552
  } else {
8638
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8639
- fs25.writeFileSync(skillPath, content, "utf-8");
8553
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8554
+ fs24.writeFileSync(skillPath, content, "utf-8");
8640
8555
  results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
8641
8556
  }
8642
8557
  } catch (err) {
@@ -8649,21 +8564,21 @@ function handleOpencodeSecondary(root, results) {
8649
8564
  }
8650
8565
  }
8651
8566
  function handleCodexSecondary(root, results) {
8652
- const tomlPath = path25.resolve(root, ".codex/config.toml");
8567
+ const tomlPath = path24.resolve(root, ".codex/config.toml");
8653
8568
  if (results.some((r) => r.filePath === ".codex/config.toml")) return;
8654
8569
  try {
8655
- const dir = path25.dirname(tomlPath);
8656
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8657
- if (fs25.existsSync(tomlPath)) {
8658
- const existingContent = fs25.readFileSync(tomlPath, "utf-8");
8570
+ const dir = path24.dirname(tomlPath);
8571
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8572
+ if (fs24.existsSync(tomlPath)) {
8573
+ const existingContent = fs24.readFileSync(tomlPath, "utf-8");
8659
8574
  if (existingContent.includes("kuma")) {
8660
8575
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
8661
8576
  return;
8662
8577
  }
8663
- fs25.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
8578
+ fs24.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
8664
8579
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
8665
8580
  } else {
8666
- fs25.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
8581
+ fs24.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
8667
8582
  results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
8668
8583
  }
8669
8584
  } catch (err) {
@@ -8676,11 +8591,11 @@ function handleCodexSecondary(root, results) {
8676
8591
  }
8677
8592
  }
8678
8593
  function handleQwenSecondary(root, results) {
8679
- const settingsPath = path25.resolve(root, "settings.json");
8594
+ const settingsPath = path24.resolve(root, "settings.json");
8680
8595
  if (results.some((r) => r.filePath === "settings.json")) return;
8681
8596
  try {
8682
- if (fs25.existsSync(settingsPath)) {
8683
- const existingContent = fs25.readFileSync(settingsPath, "utf-8");
8597
+ if (fs24.existsSync(settingsPath)) {
8598
+ const existingContent = fs24.readFileSync(settingsPath, "utf-8");
8684
8599
  if (existingContent.includes("kuma")) {
8685
8600
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
8686
8601
  try {
@@ -8688,7 +8603,7 @@ function handleQwenSecondary(root, results) {
8688
8603
  parsed.mcpServers = parsed.mcpServers || {};
8689
8604
  if (!parsed.mcpServers.kuma) {
8690
8605
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8691
- fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8606
+ fs24.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8692
8607
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
8693
8608
  return;
8694
8609
  }
@@ -8703,13 +8618,13 @@ function handleQwenSecondary(root, results) {
8703
8618
  parsed.mcpServers = parsed.mcpServers || {};
8704
8619
  if (!parsed.mcpServers.kuma) {
8705
8620
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8706
- fs25.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8621
+ fs24.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8707
8622
  results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
8708
8623
  }
8709
8624
  } catch {
8710
8625
  }
8711
8626
  } else {
8712
- fs25.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
8627
+ fs24.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
8713
8628
  results.push({ type: "qwen", filePath: "settings.json", action: "created" });
8714
8629
  }
8715
8630
  } catch (err) {
@@ -8812,18 +8727,18 @@ function getCombinedAgentsMd(selectedTypes) {
8812
8727
  return lines.join("\n");
8813
8728
  }
8814
8729
  function handleAntigravityMcpConfig(root, results) {
8815
- const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
8730
+ const mcpPath = path24.resolve(root, ".agents/mcp_config.json");
8816
8731
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
8817
8732
  try {
8818
- const mcpDir = path25.dirname(mcpPath);
8819
- if (fs25.existsSync(mcpPath)) {
8820
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
8733
+ const mcpDir = path24.dirname(mcpPath);
8734
+ if (fs24.existsSync(mcpPath)) {
8735
+ const existingContent = fs24.readFileSync(mcpPath, "utf-8");
8821
8736
  if (existingContent.includes("kuma")) {
8822
8737
  if (!existingContent.includes("_Generated by Kuma MCP_")) {
8823
8738
  const trimmed = existingContent.trimEnd();
8824
8739
  if (trimmed.endsWith("}")) {
8825
8740
  const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
8826
- fs25.writeFileSync(mcpPath, updated, "utf-8");
8741
+ fs24.writeFileSync(mcpPath, updated, "utf-8");
8827
8742
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
8828
8743
  }
8829
8744
  }
@@ -8832,11 +8747,11 @@ function handleAntigravityMcpConfig(root, results) {
8832
8747
  const parsed = JSON.parse(existingContent);
8833
8748
  parsed.mcpServers = parsed.mcpServers || {};
8834
8749
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8835
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8750
+ fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8836
8751
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
8837
8752
  } else {
8838
- if (!fs25.existsSync(mcpDir)) fs25.mkdirSync(mcpDir, { recursive: true });
8839
- fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8753
+ if (!fs24.existsSync(mcpDir)) fs24.mkdirSync(mcpDir, { recursive: true });
8754
+ fs24.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8840
8755
  results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
8841
8756
  }
8842
8757
  } catch (err) {
@@ -8849,18 +8764,18 @@ function handleAntigravityMcpConfig(root, results) {
8849
8764
  }
8850
8765
  }
8851
8766
  function handleAiderSecondary(root, results) {
8852
- const ymlPath = path25.resolve(root, ".aider.conf.yml");
8767
+ const ymlPath = path24.resolve(root, ".aider.conf.yml");
8853
8768
  if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
8854
8769
  try {
8855
8770
  const conventionsRef = "read: CONVENTIONS.md";
8856
- if (fs25.existsSync(ymlPath)) {
8857
- const existingContent = fs25.readFileSync(ymlPath, "utf-8");
8771
+ if (fs24.existsSync(ymlPath)) {
8772
+ const existingContent = fs24.readFileSync(ymlPath, "utf-8");
8858
8773
  if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
8859
8774
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
8860
8775
  return;
8861
8776
  }
8862
8777
  const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
8863
- fs25.writeFileSync(ymlPath, newContent, "utf-8");
8778
+ fs24.writeFileSync(ymlPath, newContent, "utf-8");
8864
8779
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
8865
8780
  } else {
8866
8781
  const content = [
@@ -8870,7 +8785,7 @@ function handleAiderSecondary(root, results) {
8870
8785
  conventionsRef,
8871
8786
  ""
8872
8787
  ].join("\n");
8873
- fs25.writeFileSync(ymlPath, content, "utf-8");
8788
+ fs24.writeFileSync(ymlPath, content, "utf-8");
8874
8789
  results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
8875
8790
  }
8876
8791
  } catch (err) {
@@ -8883,10 +8798,10 @@ function handleAiderSecondary(root, results) {
8883
8798
  }
8884
8799
  }
8885
8800
  function handleCopilotSecondary(root, results) {
8886
- const skillPath = path25.resolve(root, ".github/skills/kuma/SKILL.md");
8801
+ const skillPath = path24.resolve(root, ".github/skills/kuma/SKILL.md");
8887
8802
  if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
8888
8803
  try {
8889
- const dir = path25.dirname(skillPath);
8804
+ const dir = path24.dirname(skillPath);
8890
8805
  const content = [
8891
8806
  "---",
8892
8807
  "name: kuma-mcp",
@@ -8897,17 +8812,17 @@ function handleCopilotSecondary(root, results) {
8897
8812
  "",
8898
8813
  "\u{1F4D6} Read `.kuma/init.md` for detailed rules."
8899
8814
  ].join("\n");
8900
- if (fs25.existsSync(skillPath)) {
8901
- const existingContent = fs25.readFileSync(skillPath, "utf-8");
8815
+ if (fs24.existsSync(skillPath)) {
8816
+ const existingContent = fs24.readFileSync(skillPath, "utf-8");
8902
8817
  if (existingContent.includes("kuma")) {
8903
8818
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
8904
8819
  return;
8905
8820
  }
8906
- fs25.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
8821
+ fs24.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
8907
8822
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
8908
8823
  } else {
8909
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8910
- fs25.writeFileSync(skillPath, content, "utf-8");
8824
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8825
+ fs24.writeFileSync(skillPath, content, "utf-8");
8911
8826
  results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
8912
8827
  }
8913
8828
  } catch (err) {
@@ -8920,21 +8835,21 @@ function handleCopilotSecondary(root, results) {
8920
8835
  }
8921
8836
  }
8922
8837
  function handleOpenclawSecondary(root, results) {
8923
- const mcpPath = path25.resolve(root, ".agents/mcp_config.json");
8838
+ const mcpPath = path24.resolve(root, ".agents/mcp_config.json");
8924
8839
  if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
8925
8840
  try {
8926
- const dir = path25.dirname(mcpPath);
8927
- if (fs25.existsSync(mcpPath)) {
8928
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
8841
+ const dir = path24.dirname(mcpPath);
8842
+ if (fs24.existsSync(mcpPath)) {
8843
+ const existingContent = fs24.readFileSync(mcpPath, "utf-8");
8929
8844
  if (existingContent.includes("kuma")) return;
8930
8845
  const parsed = JSON.parse(existingContent);
8931
8846
  parsed.mcpServers = parsed.mcpServers || {};
8932
8847
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
8933
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8848
+ fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8934
8849
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
8935
8850
  } else {
8936
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8937
- fs25.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8851
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
8852
+ fs24.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
8938
8853
  results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
8939
8854
  }
8940
8855
  } catch (err) {
@@ -8947,7 +8862,7 @@ function handleOpenclawSecondary(root, results) {
8947
8862
  }
8948
8863
  }
8949
8864
  function handleQuickrefGeneration(root, results) {
8950
- const quickrefPath = path25.resolve(root, ".kuma/quickref.md");
8865
+ const quickrefPath = path24.resolve(root, ".kuma/quickref.md");
8951
8866
  const content = [
8952
8867
  "# Kuma Quick Reference",
8953
8868
  "",
@@ -8981,17 +8896,17 @@ function handleQuickrefGeneration(root, results) {
8981
8896
  "_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
8982
8897
  ].join("\n");
8983
8898
  try {
8984
- const kumaDir = path25.dirname(quickrefPath);
8985
- if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
8986
- if (fs25.existsSync(quickrefPath)) {
8987
- const existing = fs25.readFileSync(quickrefPath, "utf-8");
8899
+ const kumaDir = path24.dirname(quickrefPath);
8900
+ if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
8901
+ if (fs24.existsSync(quickrefPath)) {
8902
+ const existing = fs24.readFileSync(quickrefPath, "utf-8");
8988
8903
  if (existing.includes("_Generated by Kuma MCP_")) {
8989
8904
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
8990
8905
  return;
8991
8906
  }
8992
8907
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
8993
8908
  } else {
8994
- fs25.writeFileSync(quickrefPath, content, "utf-8");
8909
+ fs24.writeFileSync(quickrefPath, content, "utf-8");
8995
8910
  results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
8996
8911
  }
8997
8912
  } catch (err) {
@@ -9004,20 +8919,20 @@ function handleQuickrefGeneration(root, results) {
9004
8919
  }
9005
8920
  }
9006
8921
  function handleInitMdGeneration(root, results) {
9007
- const initMdPath = path25.resolve(root, ".kuma/init.md");
8922
+ const initMdPath = path24.resolve(root, ".kuma/init.md");
9008
8923
  try {
9009
- const kumaDir = path25.dirname(initMdPath);
9010
- if (!fs25.existsSync(kumaDir)) fs25.mkdirSync(kumaDir, { recursive: true });
9011
- if (fs25.existsSync(initMdPath)) {
9012
- const existing = fs25.readFileSync(initMdPath, "utf-8");
8924
+ const kumaDir = path24.dirname(initMdPath);
8925
+ if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
8926
+ if (fs24.existsSync(initMdPath)) {
8927
+ const existing = fs24.readFileSync(initMdPath, "utf-8");
9013
8928
  if (existing.includes("_Generated by Kuma MCP_")) {
9014
8929
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
9015
8930
  return;
9016
8931
  }
9017
- fs25.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
8932
+ fs24.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
9018
8933
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
9019
8934
  } else {
9020
- fs25.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
8935
+ fs24.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
9021
8936
  results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
9022
8937
  }
9023
8938
  } catch (err) {
@@ -9031,20 +8946,20 @@ function handleInitMdGeneration(root, results) {
9031
8946
  handleQuickrefGeneration(root, results);
9032
8947
  }
9033
8948
  function handleCodewhaleSecondary(root, results) {
9034
- const mcpPath = path25.resolve(root, ".codewhale/mcp.json");
8949
+ const mcpPath = path24.resolve(root, ".codewhale/mcp.json");
9035
8950
  if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
9036
8951
  try {
9037
- const dir = path25.dirname(mcpPath);
9038
- if (fs25.existsSync(mcpPath)) {
9039
- const existingContent = fs25.readFileSync(mcpPath, "utf-8");
8952
+ const dir = path24.dirname(mcpPath);
8953
+ if (fs24.existsSync(mcpPath)) {
8954
+ const existingContent = fs24.readFileSync(mcpPath, "utf-8");
9040
8955
  if (existingContent.includes("kuma")) return;
9041
8956
  const parsed = JSON.parse(existingContent);
9042
8957
  parsed.mcpServers = parsed.mcpServers || {};
9043
8958
  parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
9044
- fs25.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
8959
+ fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
9045
8960
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
9046
8961
  } else {
9047
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
8962
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
9048
8963
  const config = {
9049
8964
  mcpServers: {
9050
8965
  kuma: {
@@ -9054,7 +8969,7 @@ function handleCodewhaleSecondary(root, results) {
9054
8969
  }
9055
8970
  }
9056
8971
  };
9057
- fs25.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
8972
+ fs24.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9058
8973
  results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
9059
8974
  }
9060
8975
  } catch (err) {
@@ -9076,28 +8991,28 @@ function runInit(options) {
9076
8991
  let agentsMdHandled = false;
9077
8992
  for (const type of selected) {
9078
8993
  const relativePath = configFilePath(type);
9079
- const fullPath = path25.resolve(root, relativePath);
8994
+ const fullPath = path24.resolve(root, relativePath);
9080
8995
  const getTemplate = TEMPLATES[type];
9081
8996
  try {
9082
8997
  if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
9083
8998
  agentsMdHandled = true;
9084
8999
  const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
9085
- if (fs25.existsSync(fullPath)) {
9000
+ if (fs24.existsSync(fullPath)) {
9086
9001
  if (options.skipExisting) {
9087
9002
  results.push({ type, filePath: relativePath, action: "skipped" });
9088
9003
  } else {
9089
- const existingContent = fs25.readFileSync(fullPath, "utf-8");
9004
+ const existingContent = fs24.readFileSync(fullPath, "utf-8");
9090
9005
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9091
9006
  results.push({ type, filePath: relativePath, action: "skipped" });
9092
9007
  } else {
9093
- fs25.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
9008
+ fs24.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
9094
9009
  results.push({ type, filePath: relativePath, action: "appended" });
9095
9010
  }
9096
9011
  }
9097
9012
  } else {
9098
- const dir = path25.dirname(fullPath);
9099
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
9100
- fs25.writeFileSync(fullPath, combinedContent, "utf-8");
9013
+ const dir = path24.dirname(fullPath);
9014
+ if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
9015
+ fs24.writeFileSync(fullPath, combinedContent, "utf-8");
9101
9016
  results.push({ type, filePath: relativePath, action: "created" });
9102
9017
  }
9103
9018
  if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
@@ -9109,12 +9024,12 @@ function runInit(options) {
9109
9024
  continue;
9110
9025
  } else {
9111
9026
  const template = getTemplate();
9112
- if (fs25.existsSync(fullPath)) {
9027
+ if (fs24.existsSync(fullPath)) {
9113
9028
  if (options.skipExisting) {
9114
9029
  results.push({ type, filePath: relativePath, action: "skipped" });
9115
9030
  continue;
9116
9031
  }
9117
- const existingContent = fs25.readFileSync(fullPath, "utf-8");
9032
+ const existingContent = fs24.readFileSync(fullPath, "utf-8");
9118
9033
  if (existingContent.includes("_Generated by Kuma MCP_")) {
9119
9034
  if (type === "antigravity") {
9120
9035
  handleAntigravityMcpConfig(root, results);
@@ -9127,14 +9042,14 @@ function runInit(options) {
9127
9042
  continue;
9128
9043
  }
9129
9044
  const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
9130
- fs25.writeFileSync(fullPath, newContent, "utf-8");
9045
+ fs24.writeFileSync(fullPath, newContent, "utf-8");
9131
9046
  results.push({ type, filePath: relativePath, action: "appended" });
9132
9047
  } else {
9133
- const dir = path25.dirname(fullPath);
9134
- if (!fs25.existsSync(dir)) {
9135
- fs25.mkdirSync(dir, { recursive: true });
9048
+ const dir = path24.dirname(fullPath);
9049
+ if (!fs24.existsSync(dir)) {
9050
+ fs24.mkdirSync(dir, { recursive: true });
9136
9051
  }
9137
- fs25.writeFileSync(fullPath, template, "utf-8");
9052
+ fs24.writeFileSync(fullPath, template, "utf-8");
9138
9053
  results.push({ type, filePath: relativePath, action: "created" });
9139
9054
  }
9140
9055
  if (type === "antigravity") {
@@ -9290,19 +9205,19 @@ __export(agentDetector_exports, {
9290
9205
  getSkillPath: () => getSkillPath,
9291
9206
  skillExists: () => skillExists
9292
9207
  });
9293
- import fs26 from "fs";
9294
- import path26 from "path";
9208
+ import fs25 from "fs";
9209
+ import path25 from "path";
9295
9210
  function checkFile(root, filePath) {
9296
9211
  try {
9297
- return fs26.existsSync(path26.join(root, filePath));
9212
+ return fs25.existsSync(path25.join(root, filePath));
9298
9213
  } catch {
9299
9214
  return false;
9300
9215
  }
9301
9216
  }
9302
9217
  function checkDir(root, dirPath) {
9303
9218
  try {
9304
- const fullPath = path26.join(root, dirPath);
9305
- return fs26.existsSync(fullPath) && fs26.statSync(fullPath).isDirectory();
9219
+ const fullPath = path25.join(root, dirPath);
9220
+ return fs25.existsSync(fullPath) && fs25.statSync(fullPath).isDirectory();
9306
9221
  } catch {
9307
9222
  return false;
9308
9223
  }
@@ -9370,7 +9285,7 @@ function getAgentLabel(type) {
9370
9285
  function skillExists(type, projectRoot) {
9371
9286
  const root = projectRoot ?? getProjectRoot();
9372
9287
  const skillPath = getSkillPath(type);
9373
- return fs26.existsSync(path26.join(root, skillPath));
9288
+ return fs25.existsSync(path25.join(root, skillPath));
9374
9289
  }
9375
9290
  var AGENT_DETECTORS;
9376
9291
  var init_agentDetector = __esm({
@@ -10339,8 +10254,8 @@ init_kumaDb();
10339
10254
  init_kumaSelfHeal();
10340
10255
  init_kumaMemory();
10341
10256
  init_pathValidator();
10342
- import fs15 from "fs";
10343
- import path15 from "path";
10257
+ import fs14 from "fs";
10258
+ import path14 from "path";
10344
10259
  var MEMORY_ALIASES = {
10345
10260
  // Session synonyms
10346
10261
  "session": "session",
@@ -10421,29 +10336,14 @@ var MEMORY_ALIASES = {
10421
10336
  "layers": "layers",
10422
10337
  "3-layer": "layers",
10423
10338
  "memory-layers": "layers",
10424
- // Federated synonyms (Issue #27)
10425
- "federated": "federated",
10426
- "federated-graph": "federated",
10427
- "kuma-uri": "federated",
10428
- "remote-graph": "federated",
10429
- // Test generation synonyms (Issue #28)
10430
- "gen_test": "gen_test",
10431
- "gen-test": "gen_test",
10432
- "generate-test": "gen_test",
10433
- "gentest": "gen_test",
10434
- // Trajectory synonyms
10435
- "trajectory": "trajectory",
10436
- "trajectories": "trajectory",
10437
- "traj": "trajectory",
10438
- // Skills synonyms
10439
- "skills": "skills",
10440
- "distilled-skills": "skills",
10441
- "skill-list": "skills",
10442
10339
  // Add node synonyms
10443
- "add_node": "add_node",
10444
10340
  "add-node": "add_node",
10445
- "node": "add_node",
10446
10341
  "create-node": "add_node",
10342
+ // Feature synonyms
10343
+ "feature": "feature",
10344
+ "features": "feature",
10345
+ "record-feature": "feature",
10346
+ "define-feature": "feature",
10447
10347
  "record-node": "add_node",
10448
10348
  // Delete node synonyms
10449
10349
  "delete_node": "delete_node",
@@ -10495,22 +10395,16 @@ async function handleMemory(params) {
10495
10395
  return handleGotchaAction(params);
10496
10396
  case "layers":
10497
10397
  return handleLayersSummary(params);
10498
- case "federated":
10499
- return handleFederated(params);
10500
- case "gen_test":
10501
- return handleGenTest(params);
10502
- case "trajectory":
10503
- return handleTrajectoryList(params);
10504
- case "skills":
10505
- return handleSkillsList(params);
10506
10398
  case "add_node":
10507
10399
  return handleAddNode(params);
10400
+ case "feature":
10401
+ return handleFeature(params);
10508
10402
  case "delete_node":
10509
10403
  return handleDeleteNode(params);
10510
10404
  case "clear": {
10511
10405
  const { clearGraph: clearGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10512
10406
  await clearGraph2();
10513
- return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges, gotchas, and trajectories have been wiped from disk and memory.";
10407
+ return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges, and gotchas have been wiped from disk and memory.";
10514
10408
  }
10515
10409
  default:
10516
10410
  return `Unknown action "${action}".`;
@@ -10528,8 +10422,13 @@ async function handleDecision(params) {
10528
10422
  Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
10529
10423
  }
10530
10424
  case "record":
10531
- if (!params.title || !params.rationale) return "\u26A0\uFE0F title and rationale are required.";
10532
- return recordDecision({
10425
+ if (!params.title) return `\u274C **decision format error:** title is required.
10426
+
10427
+ \u2705 Correct: kuma_memory({ action: "decision", decisionAction: "record", title: "Use bcrypt vs argon2", rationale: "bcrypt is battle-tested, argon2 is newer but less adoption", context: "Login system security", outcome: "Chose bcrypt" })`;
10428
+ if (!params.rationale) return `\u274C **decision format error:** rationale is required.
10429
+
10430
+ \u2705 Correct: kuma_memory({ action: "decision", decisionAction: "record", title: "${params.title}", rationale: "WHY you chose this option", context: "optional context", outcome: "optional outcome" })`;
10431
+ const decisionResult = await recordDecision({
10533
10432
  title: params.title,
10534
10433
  context: params.context || "",
10535
10434
  options: [],
@@ -10537,6 +10436,8 @@ Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '
10537
10436
  outcome: params.outcome || "implemented",
10538
10437
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
10539
10438
  });
10439
+ sessionMemory.recordMemoryAction("decision");
10440
+ return decisionResult;
10540
10441
  default:
10541
10442
  return formatDecisionTemplate();
10542
10443
  }
@@ -10562,11 +10463,12 @@ async function handleResearchSave(params) {
10562
10463
  } catch {
10563
10464
  }
10564
10465
  try {
10565
- const researchDir = path15.join(getProjectRoot(), ".kuma", "research");
10566
- if (!fs15.existsSync(researchDir)) fs15.mkdirSync(researchDir, { recursive: true });
10567
- fs15.writeFileSync(path15.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
10466
+ const researchDir = path14.join(getProjectRoot(), ".kuma", "research");
10467
+ if (!fs14.existsSync(researchDir)) fs14.mkdirSync(researchDir, { recursive: true });
10468
+ fs14.writeFileSync(path14.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
10568
10469
  } catch {
10569
10470
  }
10471
+ sessionMemory.recordMemoryAction("research_save");
10570
10472
  return `\u2705 Research "${scope}" saved.`;
10571
10473
  }
10572
10474
  async function handleSession(params) {
@@ -10575,13 +10477,23 @@ async function handleSession(params) {
10575
10477
  if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
10576
10478
  const modifiedFiles = summary.modifiedFiles || [];
10577
10479
  const failures = summary.unresolvedFailures || [];
10480
+ const recordingSummary = sessionMemory.getRecordingSummary();
10481
+ const metricsSummary = sessionMemory.getMetricsSummary();
10578
10482
  const lines = [
10579
10483
  "\u{1F4CB} **Session Summary**\n\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\n",
10580
10484
  `\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
10581
10485
  `\u{1F550} Duration: ${summary.sessionDuration}`,
10582
10486
  `\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
10583
- `
10487
+ `,
10488
+ `\u{1F9E0} **Recordings:** ${recordingSummary.total} total \u2014 ${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save`,
10489
+ `\u{1F4CA} **Metrics:** ${metricsSummary.filesRead} files read, ${metricsSummary.filesEdited} files edited`,
10490
+ `\u23F1\uFE0F **Time saved:** ~${metricsSummary.researchTimeSavedFormatted} (from research cache)`
10584
10491
  ];
10492
+ if (recordingSummary.missingRecordings.length > 0 && recordingSummary.total === 0) {
10493
+ lines.push(`
10494
+ \u26A0\uFE0F **No recordings yet!** Missing: ${recordingSummary.missingRecordings.join(", ")}`);
10495
+ lines.push(`\u{1F4A1} Tip: Record findings after reading files. arch_flow + gotcha are exponential value.`);
10496
+ }
10585
10497
  if (modifiedFiles.length > 0) {
10586
10498
  lines.push(`**Modified Files** (${modifiedFiles.length}):`);
10587
10499
  for (const f of modifiedFiles.slice(0, 10)) {
@@ -10613,6 +10525,33 @@ async function handleSearch(params) {
10613
10525
  const memResults = sessionMemory.searchMemory(query, limit);
10614
10526
  const { searchGraph: searchGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10615
10527
  const graphResults = await searchGraph2(query, Math.min(limit, 10));
10528
+ let taskContext = "";
10529
+ try {
10530
+ const { retrieveForTask: retrieveForTask2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10531
+ taskContext = await retrieveForTask2(query, 10);
10532
+ } catch {
10533
+ }
10534
+ let impactAnalysis = "";
10535
+ try {
10536
+ const { propagateImpact: propagateImpact2, searchGraph: sg } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10537
+ const searchRes = await sg(query, 1);
10538
+ const nodeIdMatch = searchRes.match(/\*\*([^*]+)\*\* \(([^)]+)\)/);
10539
+ if (nodeIdMatch) {
10540
+ const nodeType = nodeIdMatch[2];
10541
+ const nodeName = nodeIdMatch[1];
10542
+ const nid = `${nodeType}::${nodeName}`;
10543
+ const impacts = await propagateImpact2(nid, 3, 0.2);
10544
+ if (impacts.length > 0) {
10545
+ const impactLines = impacts.slice(0, 5).map(
10546
+ (i) => ` \u2022 ${i.name} (${i.type}) \u2014 depth ${i.depth}, weight ${i.weight}`
10547
+ );
10548
+ impactAnalysis = `
10549
+ \u{1F4A5} **Impact Analysis** \u2014 ${impacts.length} node(s) affected by "${nodeName}":
10550
+ ${impactLines.join("\n")}`;
10551
+ }
10552
+ }
10553
+ } catch {
10554
+ }
10616
10555
  let hybridResults = "";
10617
10556
  try {
10618
10557
  const { hybridSearch: hybridSearch2, formatHybridResults: formatHybridResults2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
@@ -10630,7 +10569,14 @@ async function handleSearch(params) {
10630
10569
  for (const r of memResults.slice(0, 5)) lines.push(` \u2022 ${r.content.substring(0, 120)}`);
10631
10570
  lines.push("");
10632
10571
  }
10572
+ if (taskContext) {
10573
+ lines.push(taskContext);
10574
+ lines.push("");
10575
+ }
10633
10576
  lines.push("**Knowledge Graph:\n" + graphResults);
10577
+ if (impactAnalysis) {
10578
+ lines.push(impactAnalysis);
10579
+ }
10634
10580
  if (hybridResults) {
10635
10581
  lines.push("");
10636
10582
  lines.push(hybridResults);
@@ -10722,12 +10668,24 @@ async function handleLayerAction(layer, params) {
10722
10668
  const gotchasStr = gotchasMatch ? gotchasMatch[1].trim() : "";
10723
10669
  const decisionsStr = decisionsMatch ? decisionsMatch[1].trim() : "";
10724
10670
  const filesStr = filesMatch ? filesMatch[1].trim() : "";
10671
+ if (hopsStr && !content.includes("\u2192")) {
10672
+ return `\u274C **arch_flow format error:** hops must use \u2192 separator.
10673
+
10674
+ \u274C Wrong: hops: file1.js, file2.js
10675
+ \u274C Wrong: hops: file1.js\\nfile2.js
10676
+ \u2705 Correct: domain: <name> | hops: file1.js \u2192 file2.js \u2192 file3.js`;
10677
+ }
10725
10678
  const hops = hopsStr ? hopsStr.split("\u2192").map((h) => h.trim()).filter(Boolean).map((h, i, arr) => ({
10726
10679
  from: i === 0 ? domain : arr[i - 1],
10727
10680
  to: h,
10728
10681
  relation: "flows",
10729
10682
  description: h
10730
10683
  })) : [];
10684
+ if (hops.length === 0) {
10685
+ return `\u274C **arch_flow format error:** no hops found after \u2192 separator.
10686
+
10687
+ \u2705 Correct: domain: <name> | hops: file1.js \u2192 file2.js`;
10688
+ }
10731
10689
  const gotchas = gotchasStr ? gotchasStr.split(",").map((g) => g.trim()).filter(Boolean) : [];
10732
10690
  const decisions = decisionsStr ? decisionsStr.split(",").map((d) => d.trim()).filter(Boolean) : [];
10733
10691
  const filePaths = filesStr ? filesStr.split(",").map((f) => f.trim()).filter(Boolean) : [];
@@ -10735,6 +10693,7 @@ async function handleLayerAction(layer, params) {
10735
10693
  const { recordDomainFlow: recordDomainFlow2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10736
10694
  const flow = await recordDomainFlow2({ domain, hops, gotchas, decisions, filePaths });
10737
10695
  await writeLayer2("arch_flow", content);
10696
+ sessionMemory.recordMemoryAction("arch_flow");
10738
10697
  return `\u2705 Domain flow "${domain}" recorded \u2014 ${flow.nodeCount} nodes, ${flow.edgeCount} edges created.
10739
10698
  \u{1F3DB}\uFE0F **Domain Anchor:** ${domain}
10740
10699
  \u{1F504} **Hops:** ${hops.length}
@@ -10745,7 +10704,15 @@ async function handleLayerAction(layer, params) {
10745
10704
  return `\u2705 Architecture flow saved (text only). Graph recording failed: ${err}`;
10746
10705
  }
10747
10706
  }
10748
- return writeLayer2("arch_flow", content);
10707
+ return `\u274C **arch_flow format not recognized.** Use this format:
10708
+
10709
+ domain: <DomainName> | hops: <file1.tsx> \u2192 <file2.js> \u2192 <file3.ts>
10710
+
10711
+ Fields:
10712
+ - domain: short name (e.g. InviteUserFlow)
10713
+ - hops: file names separated by \u2192 (NOT commas, NOT newlines)
10714
+ - gotchas: optional, comma-separated
10715
+ - decisions: optional, comma-separated`;
10749
10716
  }
10750
10717
  if (params.content) {
10751
10718
  return writeLayer2(layer, params.content);
@@ -10754,13 +10721,25 @@ async function handleLayerAction(layer, params) {
10754
10721
  }
10755
10722
  async function handleGotchaAction(params) {
10756
10723
  if (params.content && params.scope) {
10724
+ if (!params.scope.trim()) {
10725
+ return `\u274C **gotcha format error:** scope (file path) is required.
10726
+
10727
+ \u2705 Correct: kuma_memory({ action: "gotcha", scope: "path/to/file.ts", content: "bug description", status: "high" })`;
10728
+ }
10729
+ if (!params.content.trim()) {
10730
+ return `\u274C **gotcha format error:** content (description) is required.
10731
+
10732
+ \u2705 Correct: kuma_memory({ action: "gotcha", scope: "path/to/file.ts", content: "bug description", status: "high" })`;
10733
+ }
10757
10734
  const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
10758
- return await addGotcha2({
10735
+ const result = await addGotcha2({
10759
10736
  filePath: params.scope,
10760
10737
  description: params.content,
10761
10738
  severity: params.status || "medium",
10762
10739
  workaround: params.description
10763
10740
  });
10741
+ sessionMemory.recordMemoryAction("gotcha");
10742
+ return result;
10764
10743
  }
10765
10744
  const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
10766
10745
  await syncGotchasToDb2();
@@ -10796,19 +10775,13 @@ async function handleDeleteNode(params) {
10796
10775
  saveDb2(db);
10797
10776
  return `\u{1F5D1}\uFE0F **Decision #${id} deleted.**`;
10798
10777
  }
10799
- case "trajectory":
10800
- case "trajectories": {
10801
- db.run("DELETE FROM trajectories WHERE id = ?", [id]);
10802
- saveDb2(db);
10803
- return `\u{1F5D1}\uFE0F **Trajectory #${id} deleted.**`;
10804
- }
10805
10778
  case "checkpoint": {
10806
10779
  db.run("DELETE FROM health_snapshots WHERE id = ?", [id]);
10807
10780
  saveDb2(db);
10808
10781
  return `\u{1F5D1}\uFE0F **Checkpoint #${id} deleted.**`;
10809
10782
  }
10810
10783
  default:
10811
- return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha, todo, decision, trajectory, checkpoint`;
10784
+ return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha, todo, decision, checkpoint`;
10812
10785
  }
10813
10786
  }
10814
10787
  if (params.target) {
@@ -10841,38 +10814,7 @@ async function handleDeleteNode(params) {
10841
10814
  return `\u{1F5D1}\uFE0F **Node #${id} deleted.**`;
10842
10815
  }
10843
10816
  }
10844
- return "\u26A0\uFE0F Provide `target` (node ID) to delete, or `scope` + `target` (numeric ID) for gotcha/todo/decision/trajectory/checkpoint.\n\nExamples:\n- `delete_node`, target: 'function::sendMessage'\n- `delete_node`, scope: 'gotcha', target: '42'\n- `delete_node`, scope: 'todo', target: '7'\n- `delete_node`, scope: 'decision', target: '3'\n- `delete_node`, scope: 'trajectory', target: '5'\n- `delete_node`, scope: 'checkpoint', target: '1'";
10845
- }
10846
- async function handleFederated(params) {
10847
- sessionMemory.recordToolCall("kuma_memory_federated", { scope: params.scope, uri: params.uri });
10848
- if (params.uri) {
10849
- const { resolveFederatedNode: resolveFederatedNode2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10850
- return await resolveFederatedNode2(params.uri);
10851
- }
10852
- const { listFederatedReferences: listFederatedReferences2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10853
- return await listFederatedReferences2();
10854
- }
10855
- async function handleGenTest(params) {
10856
- sessionMemory.recordToolCall("kuma_memory_gen_test", { target: params.target });
10857
- if (params.target) {
10858
- const id = parseInt(params.target, 10);
10859
- if (!isNaN(id)) {
10860
- const { generateTestFromTrajectoryId: generateTestFromTrajectoryId2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
10861
- return await generateTestFromTrajectoryId2(id);
10862
- }
10863
- }
10864
- const { listGeneratedTests: listGeneratedTests2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
10865
- return await listGeneratedTests2();
10866
- }
10867
- async function handleTrajectoryList(params) {
10868
- sessionMemory.recordToolCall("kuma_memory_trajectory", {});
10869
- const { listTrajectories: listTrajectories2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
10870
- return await listTrajectories2(params.limit || 10);
10871
- }
10872
- async function handleSkillsList(_params) {
10873
- sessionMemory.recordToolCall("kuma_memory_skills", {});
10874
- const { listDistilledSkills: listDistilledSkills2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
10875
- return await listDistilledSkills2();
10817
+ return "\u26A0\uFE0F Provide `target` (node ID) to delete, or `scope` + `target` (numeric ID) for gotcha/todo/decision/checkpoint.\n\nExamples:\n- `delete_node`, target: 'function::sendMessage'\n- `delete_node`, scope: 'gotcha', target: '42'\n- `delete_node`, scope: 'todo', target: '7'\n- `delete_node`, scope: 'decision', target: '3'\n- `delete_node`, scope: 'checkpoint', target: '1'";
10876
10818
  }
10877
10819
  async function handleAddNode(params) {
10878
10820
  const type = params.title;
@@ -10907,6 +10849,29 @@ async function handleAddNode(params) {
10907
10849
  return `\u274C Failed to create node: ${err}`;
10908
10850
  }
10909
10851
  }
10852
+ async function handleFeature(params) {
10853
+ sessionMemory.recordToolCall("kuma_memory_feature", { title: params.title, scope: params.scope });
10854
+ if (!params.title) {
10855
+ return "\u26A0\uFE0F `title` (feature name) required.\n\n\u2705 Correct: kuma_memory({ action: 'feature', title: 'Authentication', content: 'description', scope: 'src/auth/login.ts,src/auth/session.ts' })\n\n\u2022 title = feature name (e.g. 'Authentication', 'Billing')\n\u2022 content = description (optional)\n\u2022 scope = comma-separated file paths (optional)\n\u2022 tags = comma-separated tags (optional)\n\u2022 status = risk level: low/medium/high/critical (optional)";
10856
+ }
10857
+ const { recordFeature: recordFeature2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
10858
+ const files = params.scope ? params.scope.split(",").map((f) => f.trim()).filter(Boolean) : [];
10859
+ const tags = params.tags ? params.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
10860
+ const risk = params.status || "medium";
10861
+ const result = await recordFeature2({
10862
+ name: params.title,
10863
+ description: params.content,
10864
+ files,
10865
+ tags,
10866
+ risk
10867
+ });
10868
+ sessionMemory.recordMemoryAction("research_save");
10869
+ return `\u2705 **Feature "${params.title}" recorded** \u2014 ${result.nodeCount} node(s), ${result.edgeCount} edge(s)
10870
+ \u2B50 Level: feature
10871
+ \u{1F4C1} Files: ${files.length}
10872
+ \u{1F3F7}\uFE0F Tags: ${tags.length}
10873
+ \u26A0\uFE0F Risk: ${risk}`;
10874
+ }
10910
10875
  async function handleLayersSummary(_params) {
10911
10876
  const { getLayersSummary: getLayersSummary2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
10912
10877
  return getLayersSummary2();
@@ -10917,72 +10882,72 @@ init_sessionMemory();
10917
10882
 
10918
10883
  // src/engine/kumaLock.ts
10919
10884
  init_pathValidator();
10920
- import fs16 from "fs";
10921
- import path16 from "path";
10885
+ import fs15 from "fs";
10886
+ import path15 from "path";
10922
10887
  var LOCKS_DIR = ".kuma/locks";
10923
10888
  function locksDir() {
10924
- return path16.join(getProjectRoot(), LOCKS_DIR);
10889
+ return path15.join(getProjectRoot(), LOCKS_DIR);
10925
10890
  }
10926
10891
  function ensureLocksDir() {
10927
10892
  const dir = locksDir();
10928
- if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
10893
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
10929
10894
  }
10930
10895
  function lockPath(filePath) {
10931
10896
  const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
10932
- return path16.join(locksDir(), `${safeName}.lock.json`);
10897
+ return path15.join(locksDir(), `${safeName}.lock.json`);
10933
10898
  }
10934
10899
  function acquireLock(filePath, agentId) {
10935
10900
  ensureLocksDir();
10936
10901
  const lp = lockPath(filePath);
10937
10902
  const id = agentId || `agent-${process.pid}`;
10938
- if (fs16.existsSync(lp)) {
10903
+ if (fs15.existsSync(lp)) {
10939
10904
  try {
10940
- const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
10905
+ const existing = JSON.parse(fs15.readFileSync(lp, "utf-8"));
10941
10906
  if (existing.agentId === id) {
10942
10907
  return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
10943
10908
  }
10944
10909
  const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
10945
10910
  if (elapsed > 300) {
10946
- fs16.unlinkSync(lp);
10911
+ fs15.unlinkSync(lp);
10947
10912
  } else {
10948
10913
  return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
10949
10914
  }
10950
10915
  } catch {
10951
- fs16.unlinkSync(lp);
10916
+ fs15.unlinkSync(lp);
10952
10917
  }
10953
10918
  }
10954
10919
  const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
10955
- fs16.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
10920
+ fs15.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
10956
10921
  return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
10957
10922
  }
10958
10923
  function releaseLock(filePath, agentId) {
10959
10924
  ensureLocksDir();
10960
10925
  const lp = lockPath(filePath);
10961
10926
  const id = agentId || `agent-${process.pid}`;
10962
- if (!fs16.existsSync(lp)) {
10927
+ if (!fs15.existsSync(lp)) {
10963
10928
  return `\u26A0\uFE0F No lock found for \`${filePath}\``;
10964
10929
  }
10965
10930
  try {
10966
- const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
10931
+ const existing = JSON.parse(fs15.readFileSync(lp, "utf-8"));
10967
10932
  if (existing.agentId !== id) {
10968
10933
  return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
10969
10934
  }
10970
- fs16.unlinkSync(lp);
10935
+ fs15.unlinkSync(lp);
10971
10936
  return `\u{1F513} **Lock released** on \`${filePath}\``;
10972
10937
  } catch {
10973
- fs16.unlinkSync(lp);
10938
+ fs15.unlinkSync(lp);
10974
10939
  return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
10975
10940
  }
10976
10941
  }
10977
10942
  function listLocks() {
10978
10943
  ensureLocksDir();
10979
10944
  const dir = locksDir();
10980
- const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
10945
+ const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
10981
10946
  if (files.length === 0) return "\u{1F513} No active locks.";
10982
10947
  const lines = ["\u{1F512} **Active Locks:**", ""];
10983
10948
  for (const f of files) {
10984
10949
  try {
10985
- const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
10950
+ const entry = JSON.parse(fs15.readFileSync(path15.join(dir, f), "utf-8"));
10986
10951
  const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
10987
10952
  lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
10988
10953
  } catch {
@@ -10993,9 +10958,9 @@ function listLocks() {
10993
10958
  }
10994
10959
  function isLocked(filePath) {
10995
10960
  const lp = lockPath(filePath);
10996
- if (!fs16.existsSync(lp)) return { locked: false };
10961
+ if (!fs15.existsSync(lp)) return { locked: false };
10997
10962
  try {
10998
- const entry = JSON.parse(fs16.readFileSync(lp, "utf-8"));
10963
+ const entry = JSON.parse(fs15.readFileSync(lp, "utf-8"));
10999
10964
  return { locked: true, by: entry.agentId, since: entry.acquiredAt };
11000
10965
  } catch {
11001
10966
  return { locked: false };
@@ -11005,16 +10970,16 @@ function cleanStaleLocks() {
11005
10970
  ensureLocksDir();
11006
10971
  const dir = locksDir();
11007
10972
  let count = 0;
11008
- for (const f of fs16.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
10973
+ for (const f of fs15.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
11009
10974
  try {
11010
- const entry = JSON.parse(fs16.readFileSync(path16.join(dir, f), "utf-8"));
10975
+ const entry = JSON.parse(fs15.readFileSync(path15.join(dir, f), "utf-8"));
11011
10976
  if (Date.now() - entry.acquiredAt > 3e5) {
11012
- fs16.unlinkSync(path16.join(dir, f));
10977
+ fs15.unlinkSync(path15.join(dir, f));
11013
10978
  count++;
11014
10979
  }
11015
10980
  } catch {
11016
10981
  try {
11017
- fs16.unlinkSync(path16.join(dir, f));
10982
+ fs15.unlinkSync(path15.join(dir, f));
11018
10983
  count++;
11019
10984
  } catch {
11020
10985
  }
@@ -11030,8 +10995,8 @@ init_safetyAudit();
11030
10995
 
11031
10996
  // src/tools/kumaPolicy.ts
11032
10997
  init_pathValidator();
11033
- import fs17 from "fs";
11034
- import path17 from "path";
10998
+ import fs16 from "fs";
10999
+ import path16 from "path";
11035
11000
  var DEFAULT_POLICY = {
11036
11001
  never_touch: [
11037
11002
  ".env",
@@ -11057,15 +11022,15 @@ var DEFAULT_POLICY = {
11057
11022
  };
11058
11023
  function loadPolicy() {
11059
11024
  const root = getProjectRoot();
11060
- const ymlPath = path17.join(root, ".kuma", "policy.yml");
11061
- const yamlPath = path17.join(root, ".kuma", "policy.yaml");
11025
+ const ymlPath = path16.join(root, ".kuma", "policy.yml");
11026
+ const yamlPath = path16.join(root, ".kuma", "policy.yaml");
11062
11027
  let policyContent = null;
11063
11028
  let policyPath = null;
11064
- if (fs17.existsSync(ymlPath)) {
11065
- policyContent = fs17.readFileSync(ymlPath, "utf-8");
11029
+ if (fs16.existsSync(ymlPath)) {
11030
+ policyContent = fs16.readFileSync(ymlPath, "utf-8");
11066
11031
  policyPath = ymlPath;
11067
- } else if (fs17.existsSync(yamlPath)) {
11068
- policyContent = fs17.readFileSync(yamlPath, "utf-8");
11032
+ } else if (fs16.existsSync(yamlPath)) {
11033
+ policyContent = fs16.readFileSync(yamlPath, "utf-8");
11069
11034
  policyPath = yamlPath;
11070
11035
  }
11071
11036
  if (!policyContent) {
@@ -11175,9 +11140,9 @@ function checkFilePathPolicy(filePath, policy) {
11175
11140
  if (policy.max_file_size && policy.max_file_size > 0) {
11176
11141
  try {
11177
11142
  const root = getProjectRoot();
11178
- const fullPath = path17.join(root, filePath);
11179
- if (fs17.existsSync(fullPath)) {
11180
- const sizeKB = fs17.statSync(fullPath).size / 1024;
11143
+ const fullPath = path16.join(root, filePath);
11144
+ if (fs16.existsSync(fullPath)) {
11145
+ const sizeKB = fs16.statSync(fullPath).size / 1024;
11181
11146
  if (sizeKB > policy.max_file_size) {
11182
11147
  violations.push({
11183
11148
  rule: "max_file_size",
@@ -11344,12 +11309,12 @@ async function safetyCheck(action, filePath, command) {
11344
11309
  }
11345
11310
  if (filePath) {
11346
11311
  try {
11347
- const fs27 = await import("fs");
11348
- const path27 = await import("path");
11312
+ const fs26 = await import("fs");
11313
+ const path26 = await import("path");
11349
11314
  const root = process.cwd();
11350
- const fullPath = path27.resolve(root, filePath);
11351
- if (fs27.existsSync(fullPath)) {
11352
- const stat = fs27.statSync(fullPath);
11315
+ const fullPath = path26.resolve(root, filePath);
11316
+ if (fs26.existsSync(fullPath)) {
11317
+ const stat = fs26.statSync(fullPath);
11353
11318
  const sizeKB = Math.round(stat.size / 1024);
11354
11319
  if (sizeKB > 500) {
11355
11320
  checks.push({
@@ -11359,12 +11324,12 @@ async function safetyCheck(action, filePath, command) {
11359
11324
  });
11360
11325
  }
11361
11326
  if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
11362
- const baseName = path27.basename(filePath, path27.extname(filePath));
11327
+ const baseName = path26.basename(filePath, path26.extname(filePath));
11363
11328
  const testPatterns = [
11364
11329
  filePath.replace(/\.(ts|js)$/, ".test.$1"),
11365
11330
  filePath.replace(/\.(ts|js)$/, ".spec.$1"),
11366
11331
  filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
11367
- `**/__tests__/**/${baseName}.test.${path27.extname(filePath).slice(1)}`
11332
+ `**/__tests__/**/${baseName}.test.${path26.extname(filePath).slice(1)}`
11368
11333
  ];
11369
11334
  let hasTests = false;
11370
11335
  const fg = await import("fast-glob");
@@ -11380,7 +11345,7 @@ async function safetyCheck(action, filePath, command) {
11380
11345
  name: "Tests Check",
11381
11346
  passed: true,
11382
11347
  // Not a blocker, just informative
11383
- detail: `\u{1F9EA} No test file found for "${path27.basename(filePath)}" \u2014 consider adding tests`
11348
+ detail: `\u{1F9EA} No test file found for "${path26.basename(filePath)}" \u2014 consider adding tests`
11384
11349
  });
11385
11350
  } else {
11386
11351
  checks.push({
@@ -11469,8 +11434,8 @@ init_sessionMemory();
11469
11434
  // src/guards/antiPatternDetector.ts
11470
11435
  init_pathValidator();
11471
11436
  init_sessionMemory();
11472
- import fs18 from "fs";
11473
- import path18 from "path";
11437
+ import fs17 from "fs";
11438
+ import path17 from "path";
11474
11439
  import { execSync as execSync5 } from "child_process";
11475
11440
  var SCRIPT_PATCH_PATTERNS = [
11476
11441
  "writeFileSync",
@@ -11497,20 +11462,20 @@ function findPatchScripts(projectRoot) {
11497
11462
  const warnings = [];
11498
11463
  const recentFiles = scanRecentFiles(projectRoot);
11499
11464
  for (const file of recentFiles) {
11500
- const ext = path18.extname(file).toLowerCase();
11465
+ const ext = path17.extname(file).toLowerCase();
11501
11466
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
11502
- const relativePath = path18.relative(projectRoot, file);
11467
+ const relativePath = path17.relative(projectRoot, file);
11503
11468
  if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
11504
11469
  continue;
11505
11470
  }
11506
11471
  try {
11507
- const content = fs18.readFileSync(file, "utf-8").toLowerCase();
11472
+ const content = fs17.readFileSync(file, "utf-8").toLowerCase();
11508
11473
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
11509
11474
  if (matchedPattern) {
11510
11475
  warnings.push({
11511
11476
  severity: "high",
11512
11477
  pattern: "script-patching",
11513
- message: `Created script file that modifies other files: ${path18.basename(file)}`,
11478
+ message: `Created script file that modifies other files: ${path17.basename(file)}`,
11514
11479
  suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
11515
11480
  evidence: `File: ${relativePath} contains '${matchedPattern}'`,
11516
11481
  filePath: relativePath
@@ -11544,15 +11509,15 @@ function detectBashGrepUsage() {
11544
11509
  function scanRecentFiles(projectRoot) {
11545
11510
  const recent = [];
11546
11511
  try {
11547
- const entries = fs18.readdirSync(projectRoot, { withFileTypes: true });
11512
+ const entries = fs17.readdirSync(projectRoot, { withFileTypes: true });
11548
11513
  const now = Date.now();
11549
11514
  const recentThreshold = 30 * 60 * 1e3;
11550
11515
  for (const entry of entries) {
11551
11516
  if (!entry.isFile()) continue;
11552
11517
  try {
11553
- const stat = fs18.statSync(path18.join(projectRoot, entry.name));
11518
+ const stat = fs17.statSync(path17.join(projectRoot, entry.name));
11554
11519
  if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
11555
- recent.push(path18.join(projectRoot, entry.name));
11520
+ recent.push(path17.join(projectRoot, entry.name));
11556
11521
  }
11557
11522
  } catch {
11558
11523
  continue;
@@ -11576,14 +11541,14 @@ function detectGitPatchScripts(projectRoot) {
11576
11541
  const prefix = line.substring(0, 2);
11577
11542
  if (prefix !== "??" && prefix !== "A ") continue;
11578
11543
  const file = line.substring(3).trim();
11579
- const ext = path18.extname(file).toLowerCase();
11544
+ const ext = path17.extname(file).toLowerCase();
11580
11545
  if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
11581
11546
  const isRootLevel = !file.includes("/");
11582
11547
  const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
11583
11548
  if (!isRootLevel && !isScriptsDir) continue;
11584
- const fullPath = path18.join(projectRoot, file);
11585
- if (fs18.existsSync(fullPath)) {
11586
- const content = fs18.readFileSync(fullPath, "utf-8").toLowerCase();
11549
+ const fullPath = path17.join(projectRoot, file);
11550
+ if (fs17.existsSync(fullPath)) {
11551
+ const content = fs17.readFileSync(fullPath, "utf-8").toLowerCase();
11587
11552
  const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
11588
11553
  if (matchedPattern) {
11589
11554
  warnings.push({
@@ -11606,7 +11571,7 @@ function detectGitPatchScripts(projectRoot) {
11606
11571
  if (deletedStdout) {
11607
11572
  const deletedFiles = deletedStdout.split("\n").filter(Boolean);
11608
11573
  for (const file of deletedFiles) {
11609
- const ext = path18.extname(file).toLowerCase();
11574
+ const ext = path17.extname(file).toLowerCase();
11610
11575
  if (SCRIPT_EXTENSIONS.includes(ext)) {
11611
11576
  warnings.push({
11612
11577
  severity: "high",
@@ -11668,21 +11633,21 @@ function detectAllAntiPatterns() {
11668
11633
  // src/engine/contextSnapshot.ts
11669
11634
  init_sessionMemory();
11670
11635
  init_pathValidator();
11671
- import fs19 from "fs";
11672
- import path19 from "path";
11636
+ import fs18 from "fs";
11637
+ import path18 from "path";
11673
11638
  import { execSync as execSync6 } from "child_process";
11674
11639
  var SNAPSHOT_DIR = "context-snapshots";
11675
11640
  function snapshotDir() {
11676
- return path19.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
11641
+ return path18.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
11677
11642
  }
11678
11643
  function ensureSnapshotDir() {
11679
11644
  const dir = snapshotDir();
11680
- if (!fs19.existsSync(dir)) {
11681
- fs19.mkdirSync(dir, { recursive: true });
11645
+ if (!fs18.existsSync(dir)) {
11646
+ fs18.mkdirSync(dir, { recursive: true });
11682
11647
  }
11683
11648
  }
11684
11649
  function snapshotFilePath(timestamp) {
11685
- return path19.join(snapshotDir(), `${timestamp}.json`);
11650
+ return path18.join(snapshotDir(), `${timestamp}.json`);
11686
11651
  }
11687
11652
  function saveSnapshot(goal) {
11688
11653
  try {
@@ -11703,7 +11668,7 @@ function saveSnapshot(goal) {
11703
11668
  hasConventions: !!summary.hasConventions
11704
11669
  };
11705
11670
  try {
11706
- fs19.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
11671
+ fs18.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
11707
11672
  } catch {
11708
11673
  }
11709
11674
  sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
@@ -11802,6 +11767,55 @@ async function handleKumaGuard(params) {
11802
11767
  });
11803
11768
  }
11804
11769
  }
11770
+ const recordingSummary = sessionMemory.getRecordingSummary();
11771
+ if (check === "all" || check === "drift") {
11772
+ const readCalls = stats.toolCalls.filter(
11773
+ (c) => c.toolName === "read" || c.toolName === "grep" || c.toolName === "glob" || c.toolName === "smart_file_picker"
11774
+ ).length;
11775
+ if (stats.toolCallCount >= 10 && !recordingSummary.hasAnyRecordings) {
11776
+ warnings.push({
11777
+ severity: "high",
11778
+ pattern: "no-recordings-critical",
11779
+ message: `\u{1F6AB} BLOCKING: ${stats.toolCallCount} tool calls with 0 knowledge recordings. You are wasting future sessions by not recording.`,
11780
+ suggestion: "STOP. Record now:\n- kuma_memory({ action: 'research_save', scope: '<file>' }) after reading files\n- kuma_memory({ action: 'gotcha', scope: '<file>', content: '<bug>' }) when finding bugs\n- kuma_memory({ action: 'arch_flow', content: 'domain: <X> | hops: <file1> \u2192 <file2>' }) when tracing flows"
11781
+ });
11782
+ } else if (stats.toolCallCount >= 5 && !recordingSummary.hasAnyRecordings) {
11783
+ warnings.push({
11784
+ severity: "medium",
11785
+ pattern: "no-recordings",
11786
+ message: `${stats.toolCallCount} tool calls made but 0 knowledge recordings. Agent is not building persistent knowledge.`,
11787
+ suggestion: "Record findings after reading files. arch_flow + gotcha are exponential value."
11788
+ });
11789
+ } else if (readCalls >= 3 && recordingSummary.researchSaves === 0) {
11790
+ warnings.push({
11791
+ severity: "low",
11792
+ pattern: "read-without-record",
11793
+ message: `${readCalls} file reads but 0 research_save calls. Reading without recording = wasted context.`,
11794
+ suggestion: "After reading unfamiliar files: kuma_memory({ action: 'research_save', scope: '<file>' })"
11795
+ });
11796
+ }
11797
+ if (recordingSummary.total > 0) {
11798
+ }
11799
+ const errorCalls = stats.toolCalls.filter(
11800
+ (c) => c.toolName === "execute_safe_command" && JSON.stringify(c.params).includes("error")
11801
+ ).length;
11802
+ if (errorCalls >= 2 && recordingSummary.gotchas === 0) {
11803
+ warnings.push({
11804
+ severity: "medium",
11805
+ pattern: "error-without-gotcha",
11806
+ message: `${errorCalls} error encounters but 0 gotchas recorded. Errors = future gotchas.`,
11807
+ suggestion: "Record gotchas for bugs you encounter:\nkuma_memory({ action: 'gotcha', scope: '<file>', content: '<what went wrong>', status: 'high' })"
11808
+ });
11809
+ }
11810
+ if (readCalls >= 4 && recordingSummary.archFlows === 0) {
11811
+ warnings.push({
11812
+ severity: "low",
11813
+ pattern: "trace-without-arch-flow",
11814
+ message: `${readCalls} file reads (possible flow tracing) but 0 arch_flows recorded. Flow knowledge dissipates fast.`,
11815
+ suggestion: "After tracing a flow, record it:\nkuma_memory({ action: 'arch_flow', content: 'domain: <Name> | hops: <file1> \u2192 <file2> \u2192 <file3>' })"
11816
+ });
11817
+ }
11818
+ }
11805
11819
  if (check === "context") {
11806
11820
  const snapshot = saveSnapshot(stats.goal);
11807
11821
  if (!snapshot) {
@@ -11843,7 +11857,24 @@ async function handleKumaGuard(params) {
11843
11857
  hasRunTests: stats.hasRunTests
11844
11858
  }
11845
11859
  };
11846
- return JSON.stringify(report, null, 2);
11860
+ const metricsSummary = sessionMemory.getMetricsSummary();
11861
+ const reportWithRecordings = {
11862
+ ...report,
11863
+ recordings: {
11864
+ archFlows: recordingSummary.archFlows,
11865
+ gotchas: recordingSummary.gotchas,
11866
+ decisions: recordingSummary.decisions,
11867
+ researchSaves: recordingSummary.researchSaves,
11868
+ total: recordingSummary.total
11869
+ },
11870
+ metrics: {
11871
+ filesRead: metricsSummary.filesRead,
11872
+ filesEdited: metricsSummary.filesEdited,
11873
+ researchTimeSaved: metricsSummary.researchTimeSavedFormatted,
11874
+ sessionDuration: metricsSummary.sessionDuration
11875
+ }
11876
+ };
11877
+ return JSON.stringify(reportWithRecordings, null, 2);
11847
11878
  }
11848
11879
 
11849
11880
  // src/tools/kumaSafetyTool.ts
@@ -11948,12 +11979,12 @@ async function handleSecurity(params) {
11948
11979
  sessionMemory.recordToolCall("kuma_safety_security", {});
11949
11980
  if (params.filePath) {
11950
11981
  try {
11951
- const fs27 = await import("fs");
11952
- const path27 = await import("path");
11982
+ const fs26 = await import("fs");
11983
+ const path26 = await import("path");
11953
11984
  const root = process.cwd();
11954
- const fullPath = path27.resolve(root, params.filePath);
11955
- if (fs27.existsSync(fullPath) && fs27.statSync(fullPath).isFile()) {
11956
- const content = fs27.readFileSync(fullPath, "utf-8");
11985
+ const fullPath = path26.resolve(root, params.filePath);
11986
+ if (fs26.existsSync(fullPath) && fs26.statSync(fullPath).isFile()) {
11987
+ const content = fs26.readFileSync(fullPath, "utf-8");
11957
11988
  const lines = content.split("\n");
11958
11989
  const findings = [];
11959
11990
  for (let i = 0; i < lines.length; i++) {
@@ -12007,23 +12038,23 @@ async function handleGitignore(_params) {
12007
12038
  }
12008
12039
  async function handleClean(_params) {
12009
12040
  sessionMemory.recordToolCall("kuma_safety_clean", {});
12010
- const fs27 = await import("fs");
12011
- const path27 = await import("path");
12041
+ const fs26 = await import("fs");
12042
+ const path26 = await import("path");
12012
12043
  const root = process.cwd();
12013
- const scratchDir = path27.resolve(root, ".kuma", "scratch");
12044
+ const scratchDir = path26.resolve(root, ".kuma", "scratch");
12014
12045
  let removed = 0;
12015
- if (fs27.existsSync(scratchDir)) {
12046
+ if (fs26.existsSync(scratchDir)) {
12016
12047
  try {
12017
- const entries = fs27.readdirSync(scratchDir);
12048
+ const entries = fs26.readdirSync(scratchDir);
12018
12049
  for (const entry of entries) {
12019
- const fullPath = path27.join(scratchDir, entry);
12050
+ const fullPath = path26.join(scratchDir, entry);
12020
12051
  try {
12021
- const stat = fs27.statSync(fullPath);
12052
+ const stat = fs26.statSync(fullPath);
12022
12053
  if (stat.isFile()) {
12023
- fs27.unlinkSync(fullPath);
12054
+ fs26.unlinkSync(fullPath);
12024
12055
  removed++;
12025
12056
  } else if (stat.isDirectory()) {
12026
- fs27.rmSync(fullPath, { recursive: true, force: true });
12057
+ fs26.rmSync(fullPath, { recursive: true, force: true });
12027
12058
  removed++;
12028
12059
  }
12029
12060
  } catch {
@@ -12125,13 +12156,30 @@ async function handleVerify(params) {
12125
12156
  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.`;
12126
12157
  }
12127
12158
  _lastVerifyCall = now;
12159
+ const recordingSummary = sessionMemory.getRecordingSummary();
12160
+ const stats = sessionMemory.getSummary();
12161
+ const toolCallCount = stats.toolCallCount || 0;
12162
+ let recordingWarning = "";
12163
+ if (toolCallCount >= 5 && !recordingSummary.hasAnyRecordings) {
12164
+ recordingWarning = `
12165
+
12166
+ \u26A0\uFE0F **RECORDING MISSING:** You made ${toolCallCount} tool calls with 0 recordings. Before switching tasks, record what you learned:
12167
+ - kuma_memory({ action: 'research_save', scope: '<file>' })
12168
+ - kuma_memory({ action: 'gotcha', ... }) if you found bugs
12169
+ - kuma_memory({ action: 'arch_flow', ... }) if you traced a flow`;
12170
+ } else if (recordingSummary.total > 0) {
12171
+ recordingWarning = `
12172
+
12173
+ \u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
12174
+ }
12128
12175
  sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
12129
12176
  const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
12130
- return await runAutoVerification2({
12177
+ const verifyResult = await runAutoVerification2({
12131
12178
  scope: params.scope || params.filePath,
12132
12179
  force: params.force,
12133
12180
  timeoutMs: 3e4
12134
12181
  });
12182
+ return verifyResult + recordingWarning;
12135
12183
  }
12136
12184
 
12137
12185
  // src/manifest.ts
@@ -12169,8 +12217,8 @@ function registerAllTools(server) {
12169
12217
  "kuma_memory",
12170
12218
  "**Call after research/editing** (including research-only). Record what matters, skip what doesn't.\n\n\u{1F534} MUST RECORD \u2014 High Impact (saves agent time next session):\n\u2022 STEP 5: `research_save` \u2014 after exploring area (creates search cache).\n\u2022 STEP 6: `gotcha` \u2014 IMMEDIATELY when you discover bugs/quirks. No re-research.\n\u2022 STEP 7: `arch_flow` \u2014 IMMEDIATELY after EACH flow hop. Saves reading 5-10 files.\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\n\u{1F7E2} SKIP using MCP (agent native tools are faster):\n\u2022 Function/class nodes \u2192 grep\n\u2022 Component/route nodes \u2192 glob or check directly\n\u2022 Import edges \u2192 read imports directly\n\u2022 Visual graph \u2192 for humans, not AI agents\n\nOther actions: session, heal, search, changes, benchmark, layers.",
12171
12219
  {
12172
- action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "federated", "gen_test", "trajectory", "skills", "add_node", "delete_node", "clear"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search, changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules (Issue #17), arch_flow=Layer 2 architecture flow (Issue #17), gotcha=Layer 3 known gotchas (Issue #17/#21), layers=all 3 layers summary, federated=resolve federated kuma:// URI (Issue #27), gen_test=generate test from trajectory (Issue #28), trajectory=list trajectories, skills=list distilled skills, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas/trajectories"),
12173
- scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine/federated"),
12220
+ action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "add_node", "delete_node", "clear", "feature"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search (now with task retrieval + impact analysis), changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules, arch_flow=Layer 2 architecture flow (now auto-creates affects edges), gotcha=Layer 3 known gotchas, layers=all 3 layers summary, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas, feature=record high-level feature with owns edges to files"),
12221
+ scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
12174
12222
  query: z.string().optional().describe("Search query for search action"),
12175
12223
  content: z.string().optional().describe("Content/notes for research_save / context"),
12176
12224
  record: z.string().optional().describe("JSON record string for research_save"),
@@ -12186,7 +12234,7 @@ function registerAllTools(server) {
12186
12234
  healAction: z.enum(["check", "heal"]).optional().describe("Heal sub-action"),
12187
12235
  topic: z.string().optional().describe("Memory topic for session"),
12188
12236
  limit: z.number().min(1).max(100).optional().describe("Result limit"),
12189
- target: z.string().optional().describe("File path for changes / decision_log ID / trajectory ID"),
12237
+ target: z.string().optional().describe("File path for changes / decision_log ID"),
12190
12238
  since: z.number().optional().describe("Timestamp filter for changes"),
12191
12239
  compact: z.boolean().optional().default(false).describe("Compact output mode"),
12192
12240
  // Todo params
@@ -12223,8 +12271,7 @@ function registerAllTools(server) {
12223
12271
  topic: params.topic,
12224
12272
  limit: params.limit,
12225
12273
  target: params.target,
12226
- since: params.since,
12227
- uri: params.uri
12274
+ since: params.since
12228
12275
  });
12229
12276
  return { content: [{ type: "text", text }] };
12230
12277
  } catch (err) {
@@ -12358,14 +12405,14 @@ async function main() {
12358
12405
  } catch {
12359
12406
  }
12360
12407
  try {
12361
- const path27 = await import("path");
12362
- const fs27 = await import("fs");
12363
- const lockDir = path27.resolve(process.cwd(), ".kuma/verifier.lock");
12364
- if (fs27.existsSync(lockDir)) {
12365
- const pidFile = path27.join(lockDir, "pid");
12366
- if (fs27.existsSync(pidFile)) {
12408
+ const path26 = await import("path");
12409
+ const fs26 = await import("fs");
12410
+ const lockDir = path26.resolve(process.cwd(), ".kuma/verifier.lock");
12411
+ if (fs26.existsSync(lockDir)) {
12412
+ const pidFile = path26.join(lockDir, "pid");
12413
+ if (fs26.existsSync(pidFile)) {
12367
12414
  try {
12368
- const pid = parseInt(fs27.readFileSync(pidFile, "utf-8"), 10);
12415
+ const pid = parseInt(fs26.readFileSync(pidFile, "utf-8"), 10);
12369
12416
  if (pid && pid !== process.pid) {
12370
12417
  try {
12371
12418
  process.kill(-pid, "SIGKILL");
@@ -12381,7 +12428,7 @@ async function main() {
12381
12428
  } catch {
12382
12429
  }
12383
12430
  }
12384
- fs27.rmSync(lockDir, { recursive: true, force: true });
12431
+ fs26.rmSync(lockDir, { recursive: true, force: true });
12385
12432
  }
12386
12433
  if (killedCount === 0) {
12387
12434
  console.error("\u2705 No running verifications found to kill.");
@@ -12521,23 +12568,23 @@ async function main() {
12521
12568
  });
12522
12569
  const output = formatInitResults(results);
12523
12570
  console.log(output);
12524
- const fs27 = await import("fs");
12525
- const path27 = await import("path");
12526
- const matchaSkills = path27.resolve(process.cwd(), "skills/matcha/SKILL.md");
12527
- const matchaAgents = path27.resolve(
12571
+ const fs26 = await import("fs");
12572
+ const path26 = await import("path");
12573
+ const matchaSkills = path26.resolve(process.cwd(), "skills/matcha/SKILL.md");
12574
+ const matchaAgents = path26.resolve(
12528
12575
  process.cwd(),
12529
12576
  ".agents/skills/matcha/SKILL.md"
12530
12577
  );
12531
- const matchaRootSkills = path27.resolve(
12578
+ const matchaRootSkills = path26.resolve(
12532
12579
  process.cwd(),
12533
12580
  "skills/matcha/SKILL.md"
12534
12581
  );
12535
- const matchaAgentsMd = path27.resolve(process.cwd(), "AGENTS.md");
12536
- const matchaWindsurfRules = path27.resolve(
12582
+ const matchaAgentsMd = path26.resolve(process.cwd(), "AGENTS.md");
12583
+ const matchaWindsurfRules = path26.resolve(
12537
12584
  process.cwd(),
12538
12585
  ".windsurf"
12539
12586
  );
12540
- if (fs27.existsSync(matchaSkills) || fs27.existsSync(matchaAgents) || fs27.existsSync(matchaRootSkills) || fs27.existsSync(matchaAgentsMd) || fs27.existsSync(matchaWindsurfRules)) {
12587
+ if (fs26.existsSync(matchaSkills) || fs26.existsSync(matchaAgents) || fs26.existsSync(matchaRootSkills) || fs26.existsSync(matchaAgentsMd) || fs26.existsSync(matchaWindsurfRules)) {
12541
12588
  console.error(
12542
12589
  "\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
12543
12590
  );
@@ -12551,13 +12598,13 @@ async function main() {
12551
12598
  (async () => {
12552
12599
  try {
12553
12600
  const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
12554
- const fs27 = await import("fs");
12555
- const path27 = await import("path");
12556
- const initMdPath = path27.resolve(process.cwd(), ".kuma/init.md");
12557
- if (!fs27.existsSync(initMdPath)) {
12558
- const kumaDir = path27.dirname(initMdPath);
12559
- if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
12560
- fs27.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
12601
+ const fs26 = await import("fs");
12602
+ const path26 = await import("path");
12603
+ const initMdPath = path26.resolve(process.cwd(), ".kuma/init.md");
12604
+ if (!fs26.existsSync(initMdPath)) {
12605
+ const kumaDir = path26.dirname(initMdPath);
12606
+ if (!fs26.existsSync(kumaDir)) fs26.mkdirSync(kumaDir, { recursive: true });
12607
+ fs26.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
12561
12608
  console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
12562
12609
  }
12563
12610
  } catch (err) {
@@ -12568,8 +12615,8 @@ async function main() {
12568
12615
  try {
12569
12616
  const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
12570
12617
  const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
12571
- const fs27 = await import("fs");
12572
- const path27 = await import("path");
12618
+ const fs26 = await import("fs");
12619
+ const path26 = await import("path");
12573
12620
  const detection = detectAgent2();
12574
12621
  if (!detection.primary) {
12575
12622
  console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
@@ -12577,22 +12624,22 @@ async function main() {
12577
12624
  }
12578
12625
  const agentType = detection.primary;
12579
12626
  const skillPath = getSkillPath2(agentType);
12580
- const fullPath = path27.resolve(process.cwd(), skillPath);
12581
- if (fs27.existsSync(fullPath)) {
12627
+ const fullPath = path26.resolve(process.cwd(), skillPath);
12628
+ if (fs26.existsSync(fullPath)) {
12582
12629
  console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
12583
12630
  return;
12584
12631
  }
12585
- const dir = path27.dirname(fullPath);
12586
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
12587
- fs27.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
12632
+ const dir = path26.dirname(fullPath);
12633
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
12634
+ fs26.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
12588
12635
  console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
12589
12636
  const secondaryFiles = getSecondaryFiles2(agentType);
12590
12637
  for (const sf of secondaryFiles) {
12591
- const sfPath = path27.resolve(process.cwd(), sf.path);
12592
- const sfDir = path27.dirname(sfPath);
12593
- if (!fs27.existsSync(sfPath)) {
12594
- if (!fs27.existsSync(sfDir)) fs27.mkdirSync(sfDir, { recursive: true });
12595
- fs27.writeFileSync(sfPath, sf.content, "utf-8");
12638
+ const sfPath = path26.resolve(process.cwd(), sf.path);
12639
+ const sfDir = path26.dirname(sfPath);
12640
+ if (!fs26.existsSync(sfPath)) {
12641
+ if (!fs26.existsSync(sfDir)) fs26.mkdirSync(sfDir, { recursive: true });
12642
+ fs26.writeFileSync(sfPath, sf.content, "utf-8");
12596
12643
  console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
12597
12644
  }
12598
12645
  }
@@ -12617,11 +12664,11 @@ async function main() {
12617
12664
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
12618
12665
  }
12619
12666
  try {
12620
- const fs27 = await import("fs");
12621
- const path27 = await import("path");
12622
- const scratchDir = path27.resolve(process.cwd(), ".kuma", "scratch");
12623
- if (!fs27.existsSync(scratchDir)) {
12624
- fs27.mkdirSync(scratchDir, { recursive: true });
12667
+ const fs26 = await import("fs");
12668
+ const path26 = await import("path");
12669
+ const scratchDir = path26.resolve(process.cwd(), ".kuma", "scratch");
12670
+ if (!fs26.existsSync(scratchDir)) {
12671
+ fs26.mkdirSync(scratchDir, { recursive: true });
12625
12672
  console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
12626
12673
  }
12627
12674
  } catch (err) {
@@ -12639,11 +12686,11 @@ async function main() {
12639
12686
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
12640
12687
  }
12641
12688
  try {
12642
- const fs27 = await import("fs");
12643
- const path27 = await import("path");
12644
- const hooksDir = path27.resolve(process.cwd(), ".kuma", "hooks");
12645
- if (!fs27.existsSync(hooksDir)) {
12646
- fs27.mkdirSync(hooksDir, { recursive: true });
12689
+ const fs26 = await import("fs");
12690
+ const path26 = await import("path");
12691
+ const hooksDir = path26.resolve(process.cwd(), ".kuma", "hooks");
12692
+ if (!fs26.existsSync(hooksDir)) {
12693
+ fs26.mkdirSync(hooksDir, { recursive: true });
12647
12694
  const hookContent = `#!/bin/bash
12648
12695
  # Kuma auto-capture hook (post-commit)
12649
12696
  # Auto-updates knowledge graph after git commits
@@ -12651,13 +12698,13 @@ if command -v npx &> /dev/null; then
12651
12698
  npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
12652
12699
  fi
12653
12700
  `;
12654
- const gitHooksDir = path27.resolve(process.cwd(), ".git", "hooks");
12655
- if (fs27.existsSync(gitHooksDir)) {
12656
- const postCommitPath = path27.join(gitHooksDir, "post-commit");
12657
- if (!fs27.existsSync(postCommitPath)) {
12658
- fs27.writeFileSync(postCommitPath, hookContent, "utf-8");
12701
+ const gitHooksDir = path26.resolve(process.cwd(), ".git", "hooks");
12702
+ if (fs26.existsSync(gitHooksDir)) {
12703
+ const postCommitPath = path26.join(gitHooksDir, "post-commit");
12704
+ if (!fs26.existsSync(postCommitPath)) {
12705
+ fs26.writeFileSync(postCommitPath, hookContent, "utf-8");
12659
12706
  try {
12660
- fs27.chmodSync(postCommitPath, 493);
12707
+ fs26.chmodSync(postCommitPath, 493);
12661
12708
  } catch {
12662
12709
  }
12663
12710
  console.error(`[${SERVER_NAME}] \u2705 Created .git/hooks/post-commit for auto-capture`);