@plumpslabs/kuma 2.4.9 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.4.9",
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",
@@ -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,23 +813,25 @@ 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 || [];
785
- return `
786
- <div class="flow-card">
787
- <div class="flow-card-header">
788
- <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>
790
- </div>
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 = `
791
835
  <div class="flow-hops-chain">
792
836
  ${hops.map(function(h, idx) {
793
837
  var toLabel = typeof h === "object" ? (h.to || "") : String(h);
@@ -799,7 +843,21 @@ function renderFlows(flows) {
799
843
  return `${firstHopPrefix}<span class="hop-node">${escapeHtml(toLabel)}${rel}</span>${idx < hops.length - 1 ? '<span class="hop-arrow">→</span>' : ''}`;
800
844
  }).join("")}
801
845
  </div>
802
- ${meta.gotchas && meta.gotchas.length ? `<div style="font-size:11px;color:var(--amber)">⚠️ Linked Gotchas: ${meta.gotchas.map(escapeHtml).join(", ")}</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
+
853
+ return `
854
+ <div class="flow-card">
855
+ <div class="flow-card-header">
856
+ <div class="flow-anchor"><i data-lucide="anchor" style="width:16px;height:16px"></i> ${escapeHtml(f.name || "Flow")}</div>
857
+ <span style="font-size:11px;color:var(--muted)">${hopsCount} hop(s)</span>
858
+ </div>
859
+ ${hopsChainHtml}
860
+ ${gotchas.length ? `<div style="font-size:11px;color:var(--amber);margin-top:6px">⚠️ Linked Gotchas: ${gotchas.map(escapeHtml).join(", ")}</div>` : ''}
803
861
  </div>
804
862
  `;
805
863
  }).join("");
@@ -808,14 +866,29 @@ function renderFlows(flows) {
808
866
 
809
867
  function renderWorkspace(ws, nodes) {
810
868
  var container = document.getElementById("ws-topology-info");
869
+ if (!container) return;
811
870
  if (!ws) {
812
871
  container.innerHTML = "<div>Single package workspace.</div>";
813
872
  return;
814
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
+ }
815
887
  container.innerHTML = `
816
888
  <div style="margin-bottom:8px"><strong>Workspace Type:</strong> <span class="badge-project">${escapeHtml(ws.type || "standard")}</span></div>
817
889
  <div style="margin-bottom:8px"><strong>Project Root:</strong> <code>${escapeHtml(ws.name || "root")}</code> (v${escapeHtml(ws.version || "1.0.0")})</div>
818
- <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>
819
892
  `;
820
893
  }
821
894
 
@@ -865,13 +938,16 @@ function renderHealth(data) {
865
938
 
866
939
  var tbody = document.querySelector("#sessions-table tbody");
867
940
  var sessions = eff.recentSessions || [];
941
+ if (!tbody) return;
868
942
  if (!sessions.length) {
869
943
  tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--muted)">No session records logged yet.</td></tr>';
870
944
  } else {
871
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";
872
948
  return `
873
949
  <tr>
874
- <td>${s.startedAt ? new Date(s.startedAt).toLocaleTimeString() : "Recent"}</td>
950
+ <td>${timeStr}</td>
875
951
  <td>${escapeHtml(s.goal || "General development")}</td>
876
952
  <td>${s.toolCalls}</td>
877
953
  <td>${s.edits}</td>
@@ -883,6 +959,7 @@ function renderHealth(data) {
883
959
  }
884
960
 
885
961
  var staleBox = document.getElementById("staleness-container");
962
+ if (!staleBox) return;
886
963
  if (data.staleness && data.staleness.missing && data.staleness.missing.length) {
887
964
  staleBox.innerHTML = `
888
965
  <div style="color:var(--rose);margin-bottom:8px">⚠️ Detected ${data.staleness.missing.length} reference(s) to deleted files:</div>
@@ -961,10 +1038,76 @@ async function resetDb() {
961
1038
  }
962
1039
  }
963
1040
 
964
- function copyText(str) {
965
- if (navigator.clipboard) {
966
- 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);
967
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;
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!");
968
1111
  }
969
1112
 
970
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.9",
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",