@plumpslabs/kuma 2.3.30 → 2.3.32
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 +2083 -1078
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +278 -95
- package/packages/ide/studio/public/index.html +186 -9
|
@@ -252,6 +252,8 @@ header .status .dot.off { background: var(--red); }
|
|
|
252
252
|
<div class="tab active" data-tab="features"><i data-lucide="star" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Features</div>
|
|
253
253
|
<div class="tab" data-tab="gotchas"><i data-lucide="alert-triangle" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Gotchas</div>
|
|
254
254
|
<div class="tab" data-tab="health"><i data-lucide="heart-pulse" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Health</div>
|
|
255
|
+
<div class="tab" data-tab="efficiency"><i data-lucide="zap" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Efficiency</div>
|
|
256
|
+
<div class="tab" data-tab="staleness"><i data-lucide="activity" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Staleness</div>
|
|
255
257
|
</div>
|
|
256
258
|
<div class="tab-content" id="tab-content"></div>
|
|
257
259
|
</div>
|
|
@@ -273,7 +275,7 @@ async function resetKumaDb() {
|
|
|
273
275
|
var confirmation = prompt("⚠️ TYPE 'DELETE' TO CONFIRM RESETTING KUMA DB\nThis will permanently wipe all knowledge:");
|
|
274
276
|
if (confirmation !== "DELETE") { if (confirmation !== null) alert("Reset cancelled."); return; }
|
|
275
277
|
try {
|
|
276
|
-
var res = await fetch(API + "/reset-db", { method: "POST" }).then(function(r){return r.json()});
|
|
278
|
+
var res = await fetch(API + "/reset-db?confirm=1", { method: "POST" }).then(function(r){return r.json()});
|
|
277
279
|
if (res.ok) { alert("Kuma DB reset."); refetch(); }
|
|
278
280
|
} catch (e) { alert("Reset failed: " + e.message); }
|
|
279
281
|
}
|
|
@@ -281,7 +283,7 @@ async function resetKumaDb() {
|
|
|
281
283
|
async function stopKumaStudio() {
|
|
282
284
|
if (!confirm("Stop Kuma Studio?")) return;
|
|
283
285
|
try {
|
|
284
|
-
await fetch(API + "/stop-server", { method: "POST" });
|
|
286
|
+
await fetch(API + "/stop-server?confirm=1", { method: "POST" });
|
|
285
287
|
document.body.innerHTML = '<div style="display:flex;height:100vh;align-items:center;justify-content:center;color:#94a3b8;font-size:16px;">Server stopped. Close this tab.</div>';
|
|
286
288
|
} catch (e) { alert("Stop failed: " + e.message); }
|
|
287
289
|
}
|
|
@@ -336,6 +338,8 @@ async function init() {
|
|
|
336
338
|
renderFeatures(data);
|
|
337
339
|
renderGotchas(data);
|
|
338
340
|
renderHealth(data);
|
|
341
|
+
renderEfficiency(data);
|
|
342
|
+
renderStaleness(data);
|
|
339
343
|
setupSearch();
|
|
340
344
|
lucide.createIcons();
|
|
341
345
|
}
|
|
@@ -658,13 +662,49 @@ async function openNodeModal(nodeId) {
|
|
|
658
662
|
html += '<div class="modal-section">';
|
|
659
663
|
html += '<h3>Info</h3>';
|
|
660
664
|
html += '<div class="modal-kv"><span class="key">Type</span><span class="val"><span style="color:var(--accent)">'+escapeHtml(n.type)+'</span></span></div>';
|
|
665
|
+
html += '<div class="modal-kv"><span class="key">ID</span><span class="val" style="font-family:monospace;font-size:10px;color:var(--muted)">'+escapeHtml(n.id)+'</span></div>';
|
|
661
666
|
if (n.file_path) html += '<div class="modal-kv"><span class="key">Path</span><span class="val" style="color:var(--accent2)">'+escapeHtml(n.file_path)+'</span></div>';
|
|
662
|
-
if (
|
|
663
|
-
if (
|
|
664
|
-
if (
|
|
667
|
+
if (n.severity) html += '<div class="modal-kv"><span class="key">Severity</span><span class="val" style="color:'+(n.severity==='critical'?'var(--red)':n.severity==='high'?'var(--orange)':n.severity==='medium'?'var(--yellow)':'var(--green)')+'">'+escapeHtml(n.severity)+'</span></div>';
|
|
668
|
+
if (n.confidence !== undefined && n.confidence !== null) html += '<div class="modal-kv"><span class="key">Confidence</span><span class="val" style="color:var(--accent)">'+escapeHtml(String(n.confidence))+'</span></div>';
|
|
669
|
+
if (n.last_verified_at) html += '<div class="modal-kv"><span class="key">Last Verified</span><span class="val">'+new Date(n.last_verified_at*1000).toLocaleString()+'</span></div>';
|
|
670
|
+
if (n.created_at) html += '<div class="modal-kv"><span class="key">Created</span><span class="val">'+new Date(n.created_at*1000).toLocaleString()+'</span></div>';
|
|
665
671
|
if (n.updated_at) html += '<div class="modal-kv"><span class="key">Updated</span><span class="val">'+new Date(n.updated_at*1000).toLocaleString()+'</span></div>';
|
|
666
672
|
html += '</div>';
|
|
667
673
|
|
|
674
|
+
// ALL metadata fields — render every key dynamically
|
|
675
|
+
var metaKeys = Object.keys(meta);
|
|
676
|
+
if (metaKeys.length > 0) {
|
|
677
|
+
html += '<div class="modal-section">';
|
|
678
|
+
html += '<h3>Metadata ('+metaKeys.length+' fields)</h3>';
|
|
679
|
+
metaKeys.forEach(function(key) {
|
|
680
|
+
var val = meta[key];
|
|
681
|
+
if (val === null || val === undefined || val === '') return;
|
|
682
|
+
var display = '';
|
|
683
|
+
var color = 'var(--text)';
|
|
684
|
+
if (Array.isArray(val)) {
|
|
685
|
+
if (val.length === 0) return;
|
|
686
|
+
display = val.join(' → ');
|
|
687
|
+
color = 'var(--accent2)';
|
|
688
|
+
} else if (typeof val === 'object') {
|
|
689
|
+
display = JSON.stringify(val, null, 2);
|
|
690
|
+
color = 'var(--muted)';
|
|
691
|
+
} else if (typeof val === 'number') {
|
|
692
|
+
display = String(val);
|
|
693
|
+
color = 'var(--accent)';
|
|
694
|
+
} else if (key === 'severity' || key === 'risk') {
|
|
695
|
+
display = String(val);
|
|
696
|
+
color = val === 'critical' ? 'var(--red)' : val === 'high' ? 'var(--orange)' : val === 'medium' ? 'var(--yellow)' : 'var(--green)';
|
|
697
|
+
} else if (key === 'stale') {
|
|
698
|
+
display = val === 1 ? 'YES — needs heal' : 'No';
|
|
699
|
+
color = val === 1 ? 'var(--red)' : 'var(--green)';
|
|
700
|
+
} else {
|
|
701
|
+
display = String(val);
|
|
702
|
+
}
|
|
703
|
+
html += '<div class="modal-kv"><span class="key">'+escapeHtml(key)+'</span><span class="val" style="color:'+color+'">'+escapeHtml(display)+'</span></div>';
|
|
704
|
+
});
|
|
705
|
+
html += '</div>';
|
|
706
|
+
}
|
|
707
|
+
|
|
668
708
|
// Outgoing relations
|
|
669
709
|
if (outgoing.length) {
|
|
670
710
|
html += '<div class="modal-section"><h3>Outgoing ('+outgoing.length+')</h3>';
|
|
@@ -704,8 +744,12 @@ async function openNodeModal(nodeId) {
|
|
|
704
744
|
var got = typeof g === 'string' ? JSON.parse(g) : g;
|
|
705
745
|
html += '<div class="gotcha-item" style="border-color:var(--'+(got.severity==='critical'?'red':got.severity==='high'?'orange':got.severity==='medium'?'yellow':'green')+')">';
|
|
706
746
|
html += '<div class="desc">'+escapeHtml(got.description)+'</div>';
|
|
707
|
-
html += '<
|
|
708
|
-
html += '</
|
|
747
|
+
html += '<div style="display:flex;gap:8px;align-items:center;margin-top:4px">';
|
|
748
|
+
html += '<span class="sev-tag sev-'+got.severity+'">'+escapeHtml(got.severity)+'</span>';
|
|
749
|
+
if (got.workaround) html += '<span style="color:var(--accent2);font-size:10px">Workaround: '+escapeHtml(got.workaround)+'</span>';
|
|
750
|
+
if (got.added_by) html += '<span style="color:var(--muted);font-size:10px">by: '+escapeHtml(got.added_by)+'</span>';
|
|
751
|
+
if (got.created_at) html += '<span style="color:var(--muted);font-size:10px">'+new Date(got.created_at*1000).toLocaleDateString()+'</span>';
|
|
752
|
+
html += '</div></div>';
|
|
709
753
|
});
|
|
710
754
|
html += '</div>';
|
|
711
755
|
}
|
|
@@ -744,7 +788,7 @@ async function refetch() {
|
|
|
744
788
|
if (status.ok) { dot.className = 'dot on'; txt.textContent = status.project || 'connected'; } else { dot.className = 'dot off'; txt.textContent = 'no kuma.db found'; }
|
|
745
789
|
var data = await fetch(API+'/dashboard').then(function(r){return r.json()});
|
|
746
790
|
if (data.error) { showError(data.error); return; }
|
|
747
|
-
renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data);
|
|
791
|
+
renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data); renderEfficiency(data); renderStaleness(data);
|
|
748
792
|
lucide.createIcons();
|
|
749
793
|
} catch(e) { showError('Refetch failed: ' + e.message); }
|
|
750
794
|
btn.innerHTML = '<i data-lucide="refresh-cw" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Refetch';
|
|
@@ -762,7 +806,9 @@ function setupTabs(){
|
|
|
762
806
|
content.innerHTML =
|
|
763
807
|
'<div class="panel active" id="panel-features"><div class="empty">Loading...</div></div>' +
|
|
764
808
|
'<div class="panel" id="panel-gotchas"><div class="empty">Loading...</div></div>' +
|
|
765
|
-
'<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>'
|
|
809
|
+
'<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>' +
|
|
810
|
+
'<div class="panel" id="panel-efficiency"><div class="empty">Loading...</div></div>' +
|
|
811
|
+
'<div class="panel" id="panel-staleness"><div class="empty">Loading...</div></div>';
|
|
766
812
|
document.querySelectorAll('.tab').forEach(function(tab){
|
|
767
813
|
tab.onclick = function(){
|
|
768
814
|
document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active')});
|
|
@@ -827,6 +873,137 @@ function renderHealth(data){
|
|
|
827
873
|
}).join('');
|
|
828
874
|
}
|
|
829
875
|
|
|
876
|
+
// ============ EFFICIENCY PANEL (GAP 1) ============
|
|
877
|
+
// Proves the philosophy: "the more you use Kuma, the more efficient it gets."
|
|
878
|
+
// Every saved decision / record is an asset — here is the evidence.
|
|
879
|
+
|
|
880
|
+
function fmtDuration(sec) {
|
|
881
|
+
if (!sec || sec <= 0) return "0s";
|
|
882
|
+
if (sec >= 3600) return (sec/3600).toFixed(1)+"h";
|
|
883
|
+
if (sec >= 60) return Math.round(sec/60)+"m "+(sec%60)+"s";
|
|
884
|
+
return sec+"s";
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function renderEfficiency(data){
|
|
888
|
+
var el = document.getElementById("panel-efficiency");
|
|
889
|
+
if(!el) return;
|
|
890
|
+
var eff = data.efficiency || {};
|
|
891
|
+
var m = eff.metrics || {};
|
|
892
|
+
var r = eff.recordings || {};
|
|
893
|
+
var savedSec = m.researchTimeSaved ? Math.round(m.researchTimeSaved/1000) : 0;
|
|
894
|
+
var passRate = eff.verificationPassRate;
|
|
895
|
+
var passColor = passRate == null ? 'var(--muted)' : passRate >= 80 ? 'var(--green)' : passRate >= 50 ? 'var(--yellow)' : 'var(--red)';
|
|
896
|
+
|
|
897
|
+
var stat = function(num, lbl, color){
|
|
898
|
+
return '<div style="flex:1;min-width:110px;padding:10px 12px;background:var(--bg);border-radius:8px;border:1px solid var(--border)"><div style="font-size:20px;font-weight:700;color:'+(color||'var(--accent2)')+'">'+num+'</div><div style="font-size:11px;color:var(--muted);margin-top:2px">'+lbl+'</div></div>';
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
var html = '';
|
|
902
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">' +
|
|
903
|
+
stat(eff.sessions||0, 'sessions') +
|
|
904
|
+
stat(eff.toolCalls||0, 'tool calls') +
|
|
905
|
+
stat(eff.totalEdits||0, 'file edits') +
|
|
906
|
+
stat(eff.researchCacheScopes||0, 'research cached', 'var(--green)') +
|
|
907
|
+
stat(eff.decisions||0, 'decisions', 'var(--accent)') +
|
|
908
|
+
stat(eff.verifications||0, 'verifications', passRate==null?'var(--muted)':'var(--green)') +
|
|
909
|
+
'</div>';
|
|
910
|
+
|
|
911
|
+
// The headline: time saved from cache — the compounding asset
|
|
912
|
+
html += '<div class="health-item" style="border-left:3px solid var(--green);margin-bottom:12px">' +
|
|
913
|
+
'<div style="font-size:13px;color:var(--muted)">⏱ Estimated time saved from research cache</div>' +
|
|
914
|
+
'<div class="score good" style="font-size:28px">'+fmtDuration(savedSec)+'</div>' +
|
|
915
|
+
'<div class="meta">'+ (m.filesRead||0) +' files read · '+ (m.filesEdited||0) +' files edited (current session)</div>' +
|
|
916
|
+
'</div>';
|
|
917
|
+
|
|
918
|
+
// Recording assets — what we've banked
|
|
919
|
+
var recTotal = r.total||0;
|
|
920
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📦 Recorded Assets</div>';
|
|
921
|
+
html += '<div class="health-item" style="margin-bottom:12px">';
|
|
922
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:6px">';
|
|
923
|
+
html += '<span class="sev-tag" style="background:rgba(124,58,237,0.15);color:var(--accent)">arch_flow: '+(r.archFlows||0)+'</span>';
|
|
924
|
+
html += '<span class="sev-tag" style="background:#f9731620;color:var(--orange)">gotcha: '+(r.gotchas||0)+'</span>';
|
|
925
|
+
html += '<span class="sev-tag" style="background:#eab30820;color:var(--yellow)">decision: '+(r.decisions||0)+'</span>';
|
|
926
|
+
html += '<span class="sev-tag" style="background:#06b6d420;color:var(--accent2)">research_save: '+(r.researchSaves||0)+'</span>';
|
|
927
|
+
html += '</div>';
|
|
928
|
+
html += '<div class="meta" style="margin-top:6px">Total assets banked: <b style="color:var(--accent2)">'+recTotal+'</b> — each one saves re-research next session</div>';
|
|
929
|
+
html += '</div>';
|
|
930
|
+
|
|
931
|
+
// Verification pass rate
|
|
932
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🧪 Verification</div>';
|
|
933
|
+
html += '<div class="health-item" style="margin-bottom:12px">' +
|
|
934
|
+
'<div class="score" style="color:'+passColor+'">'+(passRate==null?'—':passRate+'%')+'</div>' +
|
|
935
|
+
'<div class="meta">'+(eff.verifications||0)+' runs · pass rate</div>' +
|
|
936
|
+
'</div>';
|
|
937
|
+
|
|
938
|
+
// Recent sessions breakdown
|
|
939
|
+
var sess = eff.recentSessions || [];
|
|
940
|
+
if (sess.length) {
|
|
941
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🕐 Recent Sessions</div>';
|
|
942
|
+
html += sess.map(function(s){
|
|
943
|
+
var d = s.startedAt ? new Date(s.startedAt*1000) : null;
|
|
944
|
+
var dateStr = d ? d.toLocaleDateString()+' '+d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}) : '-';
|
|
945
|
+
return '<div class="health-item">' +
|
|
946
|
+
'<div style="font-size:12px;color:var(--text)">'+escapeHtml(s.goal || 'untitled goal')+'</div>' +
|
|
947
|
+
'<div class="meta">'+dateStr+' · '+s.toolCalls+' calls · '+s.edits+' edits'+(s.failures?' · <span style="color:var(--red)">'+s.failures+' failures</span>':'')+'</div>' +
|
|
948
|
+
'</div>';
|
|
949
|
+
}).join('');
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
el.innerHTML = html;
|
|
953
|
+
lucide.createIcons();
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// ============ STALENESS PANEL (GAP 4) ============
|
|
957
|
+
// Assets can become liabilities when stale. Surface them before they bite.
|
|
958
|
+
|
|
959
|
+
function renderStaleness(data){
|
|
960
|
+
var el = document.getElementById("panel-staleness");
|
|
961
|
+
if(!el) return;
|
|
962
|
+
var st = data.staleness || { checked: 0, staleNodes: 0, missing: [] };
|
|
963
|
+
var html = '';
|
|
964
|
+
|
|
965
|
+
if (!st.staleNodes) {
|
|
966
|
+
html += '<div class="health-item" style="border-left:3px solid var(--green)">' +
|
|
967
|
+
'<div class="score good" style="font-size:22px">✅ All fresh</div>' +
|
|
968
|
+
'<div class="meta">'+st.checked+' file references checked — no stale assets found.</div>' +
|
|
969
|
+
'</div>';
|
|
970
|
+
} else {
|
|
971
|
+
html += '<div class="health-item" style="border-left:3px solid var(--red)">' +
|
|
972
|
+
'<div class="score bad" style="font-size:22px">'+st.staleNodes+' stale node(s)</div>' +
|
|
973
|
+
'<div class="meta">'+st.missing.length+' file(s) referenced but missing on disk ('+st.checked+' checked).</div>' +
|
|
974
|
+
'</div>';
|
|
975
|
+
html += '<div style="display:flex;gap:6px;margin:10px 0">' +
|
|
976
|
+
'<button onclick="copyHealCommand(this)" style="background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:6px;color:var(--accent);padding:6px 12px;font-size:11px;cursor:pointer">🛠 Copy heal command</button>' +
|
|
977
|
+
'</div>';
|
|
978
|
+
html += st.missing.map(function(m){
|
|
979
|
+
return '<div class="gotcha-item" style="border-color:var(--red)">' +
|
|
980
|
+
'<div class="file">'+escapeHtml(m.filePath)+'</div>' +
|
|
981
|
+
'<div class="desc">Referenced by <b style="color:var(--red)">'+m.nodeCount+'</b> node(s) but the file no longer exists.</div>' +
|
|
982
|
+
'</div>';
|
|
983
|
+
}).join('');
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
el.innerHTML = html;
|
|
987
|
+
lucide.createIcons();
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function copyHealCommand(btn) {
|
|
991
|
+
var cmd = "kuma_memory({ action: 'heal' })";
|
|
992
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
993
|
+
navigator.clipboard.writeText(cmd).then(function() {
|
|
994
|
+
if (btn) { btn.textContent = "✅ Copied!"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); }
|
|
995
|
+
}).catch(function() {
|
|
996
|
+
if (btn) { btn.textContent = "⚠️ Copy failed"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); }
|
|
997
|
+
});
|
|
998
|
+
} else {
|
|
999
|
+
// Fallback for non-secure contexts
|
|
1000
|
+
var ta = document.createElement("textarea");
|
|
1001
|
+
ta.value = cmd; document.body.appendChild(ta); ta.select();
|
|
1002
|
+
try { document.execCommand("copy"); if (btn) { btn.textContent = "✅ Copied!"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); } } catch(e) {}
|
|
1003
|
+
document.body.removeChild(ta);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
830
1007
|
function setupSearch() {
|
|
831
1008
|
var searchTimeout = null;
|
|
832
1009
|
document.getElementById("node-search").oninput = function() {
|