@plumpslabs/kuma 2.4.8 → 2.4.10

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
@@ -5579,7 +5579,7 @@ async function addGotcha(entry) {
5579
5579
  try {
5580
5580
  const { addEdge: addEdge2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
5581
5581
  const stmt = db.prepare(`
5582
- SELECT id FROM nodes WHERE type = 'arch_flow'
5582
+ SELECT id FROM nodes WHERE type IN ('arch_flow', 'feature_domain')
5583
5583
  AND (metadata LIKE ? OR file_path = ?)
5584
5584
  LIMIT 5
5585
5585
  `);
@@ -6758,7 +6758,7 @@ async function getFlowFreshness(domain) {
6758
6758
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
6759
6759
  const db = await getDb2();
6760
6760
  const stmt = db.prepare(
6761
- `SELECT metadata FROM nodes WHERE type = 'feature_domain' AND name = ? LIMIT 1`
6761
+ `SELECT metadata FROM nodes WHERE type IN ('feature_domain', 'arch_flow') AND name = ? LIMIT 1`
6762
6762
  );
6763
6763
  stmt.bind([domain]);
6764
6764
  const found = stmt.step();
@@ -6848,7 +6848,7 @@ async function getHopsForDomain(domain) {
6848
6848
  try {
6849
6849
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
6850
6850
  const db = await getDb2();
6851
- const stmt = db.prepare(`SELECT metadata FROM nodes WHERE type = 'feature_domain' AND name = ? LIMIT 1`);
6851
+ const stmt = db.prepare(`SELECT metadata FROM nodes WHERE type IN ('feature_domain', 'arch_flow') AND name = ? LIMIT 1`);
6852
6852
  stmt.bind([domain]);
6853
6853
  if (stmt.step()) {
6854
6854
  const meta = JSON.parse(stmt.getAsObject().metadata || "{}");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.4.8",
3
+ "version": "2.4.10",
4
4
  "description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -166,7 +166,7 @@ async function getDashboardData() {
166
166
  `SELECT json_group_array(json_object('id',id,'file_path',file_path,'description',REPLACE(REPLACE(description,char(10),' '),char(13),''),'severity',severity,'workaround',REPLACE(REPLACE(COALESCE(workaround,''),char(10),' '),char(13),''),'added_by',COALESCE(added_by,'agent'),'status',COALESCE(status,'active'),'scope_package',COALESCE(scope_package,''),'verified_by',COALESCE(verified_by,''),'created_at',created_at,'updated_at',COALESCE(updated_at,created_at))) FROM (SELECT * FROM known_gotchas ORDER BY created_at DESC)`
167
167
  );
168
168
  const flows = jsonRows(
169
- `SELECT json_group_array(json_object('id',id,'name',name,'file_path',COALESCE(file_path,''),'metadata',COALESCE(metadata,'{}'),'updated_at',updated_at)) FROM (SELECT * FROM nodes WHERE type = 'arch_flow' ORDER BY updated_at DESC)`
169
+ `SELECT json_group_array(json_object('id',id,'name',name,'file_path',COALESCE(file_path,''),'metadata',COALESCE(metadata,'{}'),'updated_at',updated_at)) FROM (SELECT * FROM nodes WHERE type IN ('arch_flow', 'feature_domain') ORDER BY updated_at DESC)`
170
170
  );
171
171
  const decisions = jsonRows(
172
172
  `SELECT json_group_array(json_object('id',id,'name',name,'metadata',COALESCE(metadata,'{}'),'created_at',created_at)) FROM (SELECT * FROM nodes WHERE type = 'decision' ORDER BY created_at DESC)`
@@ -182,11 +182,31 @@ async function getDashboardData() {
182
182
  const pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
183
183
  const pnpmWs = path.join(root, "pnpm-workspace.yaml");
184
184
  const isWs = Boolean(pkg.workspaces || fs.existsSync(pnpmWs));
185
+ const packages = [];
186
+ if (isWs) {
187
+ for (const dirName of ["packages", "apps", "libs", "services", "crates"]) {
188
+ const dirPath = path.join(root, dirName);
189
+ if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
190
+ for (const sub of fs.readdirSync(dirPath)) {
191
+ const subPkg = path.join(dirPath, sub, "package.json");
192
+ if (fs.existsSync(subPkg)) {
193
+ try {
194
+ const sp = JSON.parse(fs.readFileSync(subPkg, "utf-8"));
195
+ packages.push({ name: sp.name || sub, path: `${dirName}/${sub}` });
196
+ } catch {
197
+ packages.push({ name: sub, path: `${dirName}/${sub}` });
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
185
204
  workspace = {
186
205
  isWorkspace: isWs,
187
206
  name: pkg.name || path.basename(root),
188
207
  version: pkg.version || "1.0.0",
189
- type: isWs ? fs.existsSync(pnpmWs) ? "pnpm" : "npm/yarn" : "single-package"
208
+ type: isWs ? fs.existsSync(pnpmWs) ? "pnpm" : "npm/yarn" : "single-package",
209
+ packages
190
210
  };
191
211
  }
192
212
  } catch {
@@ -440,10 +460,13 @@ try {
440
460
  } catch {
441
461
  }
442
462
  app.get("/", (c) => {
443
- if (!htmlCache) {
463
+ try {
464
+ const html = readFileSync(htmlPath, "utf-8");
465
+ return c.html(html);
466
+ } catch {
467
+ if (htmlCache) return c.html(htmlCache);
444
468
  return c.text("index.html not found. Run from packages/ide/studio/", 500);
445
469
  }
446
- return c.html(htmlCache);
447
470
  });
448
471
  serve({ fetch: app.fetch, port: PORT }, (info) => {
449
472
  const dbPath = findKumaDb();
@@ -322,10 +322,47 @@ header {
322
322
  color: var(--muted);
323
323
  border-radius: 4px;
324
324
  font-size: 10px;
325
- padding: 2px 6px;
325
+ padding: 3px 8px;
326
326
  cursor: pointer;
327
+ display: inline-flex;
328
+ align-items: center;
329
+ gap: 4px;
330
+ transition: all 0.2s ease;
327
331
  }
328
332
  .btn-copy-workaround:hover { color: #fff; border-color: var(--amber); }
333
+ .btn-copy-workaround.copied {
334
+ background: rgba(16, 185, 129, 0.15);
335
+ border-color: var(--emerald);
336
+ color: #34d399;
337
+ }
338
+
339
+ #kuma-toast {
340
+ position: fixed;
341
+ bottom: 24px;
342
+ right: 24px;
343
+ background: #1e293b;
344
+ color: #f8fafc;
345
+ border: 1px solid #334155;
346
+ border-left: 4px solid var(--emerald);
347
+ padding: 10px 16px;
348
+ border-radius: 6px;
349
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 8px;
353
+ font-size: 12px;
354
+ font-weight: 500;
355
+ z-index: 9999;
356
+ opacity: 0;
357
+ transform: translateY(12px);
358
+ transition: opacity 0.25s ease, transform 0.25s ease;
359
+ pointer-events: none;
360
+ }
361
+ #kuma-toast.show {
362
+ opacity: 1;
363
+ transform: translateY(0);
364
+ pointer-events: auto;
365
+ }
329
366
 
330
367
  .gotcha-footer {
331
368
  font-size: 11px;
@@ -699,11 +736,14 @@ async function fetchDashboard() {
699
736
  }
700
737
 
701
738
  function renderAll(data) {
702
- renderGotchas(data.gotchas || []);
703
- renderFlows(data.flows || []);
704
- renderWorkspace(data.workspace, data.nodes || []);
705
- renderHealth(data);
706
- if (window.lucide && window.lucide.createIcons) window.lucide.createIcons();
739
+ if (!data) return;
740
+ try { renderGotchas(data.gotchas || []); } catch (e) { console.error("renderGotchas error:", e); }
741
+ try { renderFlows(data.flows || []); } catch (e) { console.error("renderFlows error:", e); }
742
+ try { renderWorkspace(data.workspace, data.nodes || []); } catch (e) { console.error("renderWorkspace error:", e); }
743
+ try { renderHealth(data); } catch (e) { console.error("renderHealth error:", e); }
744
+ if (window.lucide && window.lucide.createIcons) {
745
+ try { window.lucide.createIcons(); } catch {}
746
+ }
707
747
  }
708
748
 
709
749
  function setStatusFilter(status) {
@@ -756,8 +796,10 @@ function renderGotchaCards(list) {
756
796
  ${g.workaround ? `
757
797
  <div class="gotcha-workaround">
758
798
  <strong>Workaround:</strong>
759
- ${escapeHtml(g.workaround)}
760
- <button class="btn-copy-workaround" onclick="copyText('${escapeAttr(g.workaround)}')">Copy</button>
799
+ <div class="workaround-text" style="margin-top:4px">${escapeHtml(g.workaround)}</div>
800
+ <button class="btn-copy-workaround" onclick="copyWorkaround(this)" title="Copy workaround to clipboard">
801
+ <i data-lucide="copy" style="width:11px;height:11px"></i> Copy
802
+ </button>
761
803
  </div>
762
804
  ` : ''}
763
805
  <div class="gotcha-footer">
@@ -771,29 +813,51 @@ function renderGotchaCards(list) {
771
813
  }
772
814
 
773
815
  function renderFlows(flows) {
774
- document.getElementById("badge-flow-count").textContent = flows.length;
816
+ var countBadge = document.getElementById("badge-flow-count");
817
+ if (countBadge) countBadge.textContent = flows ? flows.length : 0;
775
818
  var container = document.getElementById("flow-cards-container");
776
- if (!flows.length) {
819
+ if (!container) return;
820
+ if (!flows || !flows.length) {
777
821
  container.innerHTML = '<div class="empty-placeholder"><i data-lucide="git-branch"></i><div>No domain sequence flows recorded yet.</div></div>';
778
822
  if (window.lucide && window.lucide.createIcons) window.lucide.createIcons();
779
823
  return;
780
824
  }
781
825
  container.innerHTML = flows.map(function(f) {
782
826
  var meta = {};
783
- try { meta = typeof f.metadata === "string" ? JSON.parse(f.metadata) : f.metadata; } catch {}
784
- var hops = meta.hops || [];
827
+ try { meta = typeof f.metadata === "string" ? JSON.parse(f.metadata) : (f.metadata || {}); } catch {}
828
+ var hops = Array.isArray(meta.hops) ? meta.hops : [];
829
+ var hopsCount = hops.length || (typeof meta.hops === "number" ? meta.hops : (meta.hopsCount || 0));
830
+ var gotchas = Array.isArray(meta.gotchas) ? meta.gotchas : [];
831
+
832
+ var hopsChainHtml = '';
833
+ if (hops.length > 0) {
834
+ hopsChainHtml = `
835
+ <div class="flow-hops-chain">
836
+ ${hops.map(function(h, idx) {
837
+ var toLabel = typeof h === "object" ? (h.to || "") : String(h);
838
+ var fromLabel = (typeof h === "object" && h.from) ? h.from : "";
839
+ var rel = (typeof h === "object" && h.relation) ? ` <span style="opacity:0.6;font-size:10px">(${escapeHtml(h.relation)})</span>` : '';
840
+ var firstHopPrefix = (idx === 0 && fromLabel && fromLabel !== toLabel)
841
+ ? `<span class="hop-node">${escapeHtml(fromLabel)}</span><span class="hop-arrow">→</span>`
842
+ : '';
843
+ return `${firstHopPrefix}<span class="hop-node">${escapeHtml(toLabel)}${rel}</span>${idx < hops.length - 1 ? '<span class="hop-arrow">→</span>' : ''}`;
844
+ }).join("")}
845
+ </div>
846
+ `;
847
+ } else if (hopsCount > 0) {
848
+ hopsChainHtml = `<div style="font-size:12px;color:var(--muted);padding:4px 0">${hopsCount} sequence hop(s) recorded.</div>`;
849
+ } else {
850
+ hopsChainHtml = `<div style="font-size:12px;color:var(--muted);padding:4px 0">No hops recorded for this flow.</div>`;
851
+ }
852
+
785
853
  return `
786
854
  <div class="flow-card">
787
855
  <div class="flow-card-header">
788
856
  <div class="flow-anchor"><i data-lucide="anchor" style="width:16px;height:16px"></i> ${escapeHtml(f.name || "Flow")}</div>
789
- <span style="font-size:11px;color:var(--muted)">${hops.length} hop(s)</span>
857
+ <span style="font-size:11px;color:var(--muted)">${hopsCount} hop(s)</span>
790
858
  </div>
791
- <div class="flow-hops-chain">
792
- ${hops.map(function(h, idx) {
793
- return `<span class="hop-node">${escapeHtml(h.to || h)}</span>${idx < hops.length - 1 ? '<span class="hop-arrow">→</span>' : ''}`;
794
- }).join("")}
795
- </div>
796
- ${meta.gotchas && meta.gotchas.length ? `<div style="font-size:11px;color:var(--amber)">⚠️ Linked Gotchas: ${meta.gotchas.map(escapeHtml).join(", ")}</div>` : ''}
859
+ ${hopsChainHtml}
860
+ ${gotchas.length ? `<div style="font-size:11px;color:var(--amber);margin-top:6px">⚠️ Linked Gotchas: ${gotchas.map(escapeHtml).join(", ")}</div>` : ''}
797
861
  </div>
798
862
  `;
799
863
  }).join("");
@@ -802,14 +866,29 @@ function renderFlows(flows) {
802
866
 
803
867
  function renderWorkspace(ws, nodes) {
804
868
  var container = document.getElementById("ws-topology-info");
869
+ if (!container) return;
805
870
  if (!ws) {
806
871
  container.innerHTML = "<div>Single package workspace.</div>";
807
872
  return;
808
873
  }
874
+ var nodesLen = (nodes && nodes.length) ? nodes.length : 0;
875
+ var pkgHtml = '';
876
+ if (ws.packages && Array.isArray(ws.packages) && ws.packages.length > 0) {
877
+ pkgHtml = `
878
+ <div style="margin-top:10px"><strong>Monorepo Packages (${ws.packages.length}):</strong></div>
879
+ <div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px">
880
+ ${ws.packages.map(function(p) {
881
+ var name = typeof p === 'string' ? p : (p.name || p.path || '');
882
+ return `<span class="badge-pkg" style="background:#1e293b;border:1px solid #334155;padding:2px 8px;border-radius:4px;font-size:11px">📦 ${escapeHtml(name)}</span>`;
883
+ }).join("")}
884
+ </div>
885
+ `;
886
+ }
809
887
  container.innerHTML = `
810
888
  <div style="margin-bottom:8px"><strong>Workspace Type:</strong> <span class="badge-project">${escapeHtml(ws.type || "standard")}</span></div>
811
889
  <div style="margin-bottom:8px"><strong>Project Root:</strong> <code>${escapeHtml(ws.name || "root")}</code> (v${escapeHtml(ws.version || "1.0.0")})</div>
812
- <div style="margin-top:12px;color:var(--muted)">Total indexed knowledge entities: <strong>${nodes.length}</strong></div>
890
+ ${pkgHtml}
891
+ <div style="margin-top:12px;color:var(--muted)">Total indexed knowledge entities: <strong>${nodesLen}</strong></div>
813
892
  `;
814
893
  }
815
894
 
@@ -859,13 +938,16 @@ function renderHealth(data) {
859
938
 
860
939
  var tbody = document.querySelector("#sessions-table tbody");
861
940
  var sessions = eff.recentSessions || [];
941
+ if (!tbody) return;
862
942
  if (!sessions.length) {
863
943
  tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--muted)">No session records logged yet.</td></tr>';
864
944
  } else {
865
945
  tbody.innerHTML = sessions.map(function(s) {
946
+ var dateObj = s.startedAt ? new Date(s.startedAt < 1e11 ? s.startedAt * 1000 : s.startedAt) : null;
947
+ var timeStr = dateObj && !isNaN(dateObj.getTime()) ? dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : "Recent";
866
948
  return `
867
949
  <tr>
868
- <td>${s.startedAt ? new Date(s.startedAt).toLocaleTimeString() : "Recent"}</td>
950
+ <td>${timeStr}</td>
869
951
  <td>${escapeHtml(s.goal || "General development")}</td>
870
952
  <td>${s.toolCalls}</td>
871
953
  <td>${s.edits}</td>
@@ -877,6 +959,7 @@ function renderHealth(data) {
877
959
  }
878
960
 
879
961
  var staleBox = document.getElementById("staleness-container");
962
+ if (!staleBox) return;
880
963
  if (data.staleness && data.staleness.missing && data.staleness.missing.length) {
881
964
  staleBox.innerHTML = `
882
965
  <div style="color:var(--rose);margin-bottom:8px">⚠️ Detected ${data.staleness.missing.length} reference(s) to deleted files:</div>
@@ -896,7 +979,8 @@ function initOrRefreshGraph() {
896
979
  var color = "#64748b";
897
980
  if (n.type === "file") color = "#06b6d4";
898
981
  else if (n.type === "gotcha") color = "#f59e0b";
899
- else if (n.type === "arch_flow") color = "#6366f1";
982
+ else if (n.type === "arch_flow" || n.type === "feature_domain") color = "#6366f1";
983
+ else if (n.type === "cross_service_link" || n.type === "workflow") color = "#818cf8";
900
984
  else if (n.type === "decision") color = "#10b981";
901
985
  return {
902
986
  id: n.id,
@@ -954,10 +1038,76 @@ async function resetDb() {
954
1038
  }
955
1039
  }
956
1040
 
957
- function copyText(str) {
958
- if (navigator.clipboard) {
959
- navigator.clipboard.writeText(str);
1041
+ function showToast(msg) {
1042
+ var toast = document.getElementById("kuma-toast");
1043
+ if (!toast) {
1044
+ toast = document.createElement("div");
1045
+ toast.id = "kuma-toast";
1046
+ document.body.appendChild(toast);
1047
+ }
1048
+ toast.innerHTML = `<span style="color:var(--emerald);font-size:14px">✓</span> <span>${escapeHtml(msg || "Copied to clipboard!")}</span>`;
1049
+ toast.classList.add("show");
1050
+ clearTimeout(window.__kumaToastTimer);
1051
+ window.__kumaToastTimer = setTimeout(function() {
1052
+ toast.classList.remove("show");
1053
+ }, 2500);
1054
+ }
1055
+
1056
+ function fallbackCopy(text) {
1057
+ var textarea = document.createElement("textarea");
1058
+ textarea.value = text;
1059
+ textarea.style.position = "fixed";
1060
+ textarea.style.top = "-9999px";
1061
+ textarea.style.left = "-9999px";
1062
+ document.body.appendChild(textarea);
1063
+ textarea.focus();
1064
+ textarea.select();
1065
+ try {
1066
+ document.execCommand("copy");
1067
+ } catch (err) {
1068
+ console.error("execCommand copy error:", err);
1069
+ }
1070
+ document.body.removeChild(textarea);
1071
+ }
1072
+
1073
+ async function copyText(str, btn, toastMsg) {
1074
+ if (!str) return;
1075
+ var success = false;
1076
+ if (navigator.clipboard && window.isSecureContext) {
1077
+ try {
1078
+ await navigator.clipboard.writeText(str);
1079
+ success = true;
1080
+ } catch {
1081
+ fallbackCopy(str);
1082
+ success = true;
1083
+ }
1084
+ } else {
1085
+ fallbackCopy(str);
1086
+ success = true;
960
1087
  }
1088
+
1089
+ if (success) {
1090
+ showToast(toastMsg || "Copied to clipboard!");
1091
+ if (btn) {
1092
+ var origHtml = btn.innerHTML;
1093
+ btn.classList.add("copied");
1094
+ btn.innerHTML = '<span>Copied! ✓</span>';
1095
+ setTimeout(function() {
1096
+ btn.classList.remove("copied");
1097
+ btn.innerHTML = origHtml;
1098
+ }, 2000);
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ function copyWorkaround(btn) {
1104
+ if (!btn) return;
1105
+ var parent = btn.closest(".gotcha-workaround");
1106
+ if (!parent) return;
1107
+ var textEl = parent.querySelector(".workaround-text");
1108
+ var text = textEl ? textEl.innerText.trim() : "";
1109
+ if (!text) return;
1110
+ copyText(text, btn, "Workaround copied to clipboard!");
961
1111
  }
962
1112
 
963
1113
  function escapeHtml(str) {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "kuma",
4
- "version": "2.4.8",
4
+ "version": "2.4.10",
5
5
  "description": "Safety-first context & orchestration engine for AI coding agents. Shadow memory, gotcha lifecycle, blast radius impact analysis, and test verification.",
6
6
  "author": {
7
7
  "name": "plumpslabs",