peon-mem 1.0.3 → 1.0.4
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/README.md +10 -1
- package/dist/monitor.js +126 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🧠 Peon — a memory brain for your AI coding agents
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/peon-mem) [](LICENSE) [](test/)
|
|
3
|
+
[](https://www.npmjs.com/package/peon-mem) [](https://www.npmjs.com/package/peon-mem) [](LICENSE) [](test/) [](https://registry.modelcontextprotocol.io) [](https://github.com/VineetV2/peon-mem)
|
|
4
4
|
|
|
5
5
|
**Local-first, hierarchical, self-improving memory for Claude Code, Codex, and any MCP client.**
|
|
6
6
|
|
|
@@ -433,3 +433,12 @@ or send a PR directly.
|
|
|
433
433
|
Rules of the house: every retrieval/quality change ships with a test and an eval-ledger run
|
|
434
434
|
(`npm run eval`); negative results get documented, not deleted; nothing may hard-delete user
|
|
435
435
|
memory. `npm test` must stay green.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
<div align="center">
|
|
440
|
+
|
|
441
|
+
**If Peon is useful to you, [★ star it on GitHub](https://github.com/VineetV2/peon-mem)** — it is
|
|
442
|
+
how other people building with coding agents find it.
|
|
443
|
+
|
|
444
|
+
</div>
|
package/dist/monitor.js
CHANGED
|
@@ -58,6 +58,8 @@ const CLIENT_SCRIPT = String.raw `
|
|
|
58
58
|
|
|
59
59
|
function esc(v){ return String(v==null?"":v).replace(/[&<>"']/g,function(c){return {"&":"&","<":"<",">":">",'"':""","'":"'"}[c];}); }
|
|
60
60
|
function clip(v,n){ var s=String(v==null?"":v).trim(); return s.length>n?s.slice(0,n)+"…":s; }
|
|
61
|
+
// "summary" → "summaries", "fact" → "facts". Belief type names are user-visible in search results.
|
|
62
|
+
function plural(w,n){ if(n===1) return w; return /(s|x|z|ch|sh)$/.test(w)?w+"es":/[^aeiou]y$/.test(w)?w.slice(0,-1)+"ies":w+"s"; }
|
|
61
63
|
function fmt(n){ return (Number(n)||0).toLocaleString("en-US"); }
|
|
62
64
|
function tm(iso){ try{ return new Date(iso).toLocaleTimeString([], {hour:"2-digit",minute:"2-digit",second:"2-digit"}); }catch(e){ return ""; } }
|
|
63
65
|
function name(p){ return String(p||"").split("/").filter(Boolean).pop()||"project"; }
|
|
@@ -316,6 +318,96 @@ const CLIENT_SCRIPT = String.raw `
|
|
|
316
318
|
UNI.hits=set;
|
|
317
319
|
if(hitsEl) hitsEl.textContent=m?(m+" MATCH"+(m>1?"ES":"")):"NO MATCH";
|
|
318
320
|
if(m){ UNI.tx.x=sx/m; UNI.tx.y=sy/m; UNI.tx.z=Math.max(UNI.cam.z, m<20?1.1:0.55); }
|
|
321
|
+
uniResults(q, toks);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Readable answer for a query: an EXTRACTIVE summary (no model call — works with AI off) plus
|
|
325
|
+
the ranked hits. Flaring stars tell you a match exists; this tells you what it says. */
|
|
326
|
+
function uniResults(q, toks){
|
|
327
|
+
var el=EL("uni-results"); if(!el) return;
|
|
328
|
+
if(!q||!UNI.hits||!UNI.hits.size){ el.hidden=true; el.innerHTML=""; return; }
|
|
329
|
+
// Dedupe by record id: the same belief can be drawn as more than one star (a global belief
|
|
330
|
+
// also appears under a project galaxy), and counting/listing it twice would misreport memory.
|
|
331
|
+
var seen={}, hits=[];
|
|
332
|
+
UNI.nodes.forEach(function(n){
|
|
333
|
+
if(!UNI.hits.has(n.id)||seen[n.id]) return;
|
|
334
|
+
seen[n.id]=1; hits.push(n);
|
|
335
|
+
});
|
|
336
|
+
// Rank: query-term density, then belief strength/importance, then recency.
|
|
337
|
+
hits.forEach(function(n){
|
|
338
|
+
var hay=(n.rec.content+" "+(n.rec.entities||[]).join(" ")).toLowerCase(), d=0;
|
|
339
|
+
toks.forEach(function(t){ var i=0; while((i=hay.indexOf(t,i))>=0){ d++; i+=t.length; } });
|
|
340
|
+
var imp=(n.rec.score&&n.rec.score.importance)||0;
|
|
341
|
+
n._r=d*2 + imp*3 + (n.rec.strength||0) + (n.rec.recallCount||0)*0.2;
|
|
342
|
+
// Current truth outranks history: a superseded/archived phrasing must never sit above the
|
|
343
|
+
// active belief that replaced it (same reasoning as stale-shadow demotion in retrieval).
|
|
344
|
+
if(n.rec.status&&n.rec.status!=="active") n._r-=6;
|
|
345
|
+
if(n.rec.pinned) n._r+=3;
|
|
346
|
+
// Prefer beliefs that ANSWER the question over pointers to where an answer lives: a bare
|
|
347
|
+
// file path matching on its filename is not a summary. Prose types lead; paths sink.
|
|
348
|
+
var c=n.rec.content||"";
|
|
349
|
+
var pathish=/^[~/.]|^[A-Za-z]:\\/.test(c.trim()) && c.trim().split(/\s+/).length<=3;
|
|
350
|
+
if(pathish) n._r-=4;
|
|
351
|
+
if(n.rec.type==="decision"||n.rec.type==="fact"||n.rec.type==="preference") n._r+=1.5;
|
|
352
|
+
});
|
|
353
|
+
hits.sort(function(a,b){ return b._r-a._r; });
|
|
354
|
+
|
|
355
|
+
// Facets: what KIND of memory answered, and where it lives.
|
|
356
|
+
var byType={}, byProj={}, ents={}, oldest=null, newest=null;
|
|
357
|
+
hits.forEach(function(n){
|
|
358
|
+
var r=n.rec;
|
|
359
|
+
byType[r.type]=(byType[r.type]||0)+1;
|
|
360
|
+
byProj[n.cl.name]=(byProj[n.cl.name]||0)+1;
|
|
361
|
+
(r.entities||[]).forEach(function(e){ ents[e]=(ents[e]||0)+1; });
|
|
362
|
+
var t=r.updatedAt||r.createdAt;
|
|
363
|
+
if(t){ if(!oldest||t<oldest) oldest=t; if(!newest||t>newest) newest=t; }
|
|
364
|
+
});
|
|
365
|
+
var topEnts=Object.keys(ents).sort(function(a,b){ return ents[b]-ents[a]; }).slice(0,6);
|
|
366
|
+
var typeStr=Object.keys(byType).sort(function(a,b){ return byType[b]-byType[a]; })
|
|
367
|
+
.map(function(t){ return byType[t]+" "+plural(t.replace(/_/g," "),byType[t]); }).join(" · ");
|
|
368
|
+
var projStr=Object.keys(byProj).sort(function(a,b){ return byProj[b]-byProj[a]; }).slice(0,3).join(", ");
|
|
369
|
+
|
|
370
|
+
// The summary line: the single strongest belief, verbatim, plus the shape of the rest.
|
|
371
|
+
// Beliefs recorded against a file often read "<absolute path>: <the actual point>" — the path
|
|
372
|
+
// is provenance (kept in the hit list), so lead with the point instead of the filename.
|
|
373
|
+
var lead=String(hits[0].rec.content||"").replace(/^\s*[~/][^\s:]{12,}:\s*/,"");
|
|
374
|
+
var summary='<b>'+esc(clip(lead,260))+'</b>';
|
|
375
|
+
if(hits.length>1) summary+='<br><span style="color:var(--muted)">+ '+(hits.length-1)+' more across '+esc(projStr)+
|
|
376
|
+
(newest?', last updated '+esc(ago(newest)):'')+'.</span>';
|
|
377
|
+
|
|
378
|
+
var facets='<span class="ur-facet">'+esc(typeStr)+'</span>'+
|
|
379
|
+
topEnts.map(function(e){ return '<span class="ur-facet">'+esc(e)+'</span>'; }).join("");
|
|
380
|
+
|
|
381
|
+
var SHOW=12;
|
|
382
|
+
var list=hits.slice(0,SHOW).map(function(n,i){
|
|
383
|
+
var r=n.rec, meta=[];
|
|
384
|
+
if(r.status&&r.status!=="active") meta.push(r.status);
|
|
385
|
+
meta.push(n.cl.name);
|
|
386
|
+
if(r.recallCount) meta.push("recalled "+r.recallCount+"×");
|
|
387
|
+
if(r.updatedAt) meta.push(ago(r.updatedAt));
|
|
388
|
+
return '<button class="ur-hit" data-i="'+i+'">'+
|
|
389
|
+
'<div class="h-t" style="color:'+n.c+'">'+esc(r.type)+'</div>'+
|
|
390
|
+
esc(clip(r.content,180))+
|
|
391
|
+
'<div class="h-m">'+esc(meta.join(" · "))+'</div></button>';
|
|
392
|
+
}).join("");
|
|
393
|
+
|
|
394
|
+
el.hidden=false;
|
|
395
|
+
el.innerHTML='<button class="ui-x" id="ur-x">✕</button>'+
|
|
396
|
+
'<div class="ur-head">'+hits.length+' belief'+(hits.length>1?"s":"")+' answer "'+esc(clip(q,40))+'"</div>'+
|
|
397
|
+
'<div class="ur-sum">'+summary+'</div>'+
|
|
398
|
+
'<div class="ur-facets">'+facets+'</div>'+
|
|
399
|
+
'<div class="ur-list">'+list+'</div>'+
|
|
400
|
+
(hits.length>SHOW?'<div class="ur-more">showing top '+SHOW+' of '+hits.length+'</div>':"");
|
|
401
|
+
|
|
402
|
+
var x=EL("ur-x"); if(x) x.addEventListener("click",function(){ el.hidden=true; });
|
|
403
|
+
Array.prototype.forEach.call(el.querySelectorAll(".ur-hit"),function(b){
|
|
404
|
+
b.addEventListener("click",function(){
|
|
405
|
+
var n=hits[Number(b.getAttribute("data-i"))];
|
|
406
|
+
if(!n) return;
|
|
407
|
+
uniInspect(n); // full detail + provenance
|
|
408
|
+
UNI.tx.x=n.x; UNI.tx.y=n.y; UNI.tx.z=Math.max(1.6,UNI.cam.z); // fly to the star
|
|
409
|
+
});
|
|
410
|
+
});
|
|
319
411
|
}
|
|
320
412
|
function uniInspect(n){
|
|
321
413
|
var el=EL("uni-inspect"); if(!el) return;
|
|
@@ -329,7 +421,19 @@ const CLIENT_SCRIPT = String.raw `
|
|
|
329
421
|
'<span class="g">IMP <b>'+pct(r.score&&r.score.importance)+'</b><i class="bar"><i style="width:'+pct(r.score&&r.score.importance)+'%"></i></i></span>'+
|
|
330
422
|
'<span class="g">CONF <b>'+pct(r.score&&r.score.confidence)+'</b><i class="bar"><i class="b2" style="width:'+pct(r.score&&r.score.confidence)+'%"></i></i></span></div>'+
|
|
331
423
|
((r.entities&&r.entities.length)?'<div class="ui-ents">'+r.entities.slice(0,8).map(function(e){return '<span class="ent mono">'+esc(e)+'</span>';}).join("")+'</div>':"")+
|
|
332
|
-
|
|
424
|
+
// Provenance: where this belief came from and how it has been used. Answers "why does my
|
|
425
|
+
// agent believe this, and is it still current?" without opening the JSONL by hand.
|
|
426
|
+
'<div class="ui-proj mono">'+(function(){
|
|
427
|
+
var p=[];
|
|
428
|
+
if(r.createdAt) p.push("learned "+ago(r.createdAt));
|
|
429
|
+
if(r.updatedAt&&r.updatedAt!==r.createdAt) p.push("updated "+ago(r.updatedAt));
|
|
430
|
+
if(r.recallCount) p.push("recalled "+r.recallCount+"×"+(r.lastRecalledAt?" (last "+ago(r.lastRecalledAt)+")":""));
|
|
431
|
+
if(r.pinned) p.push("pinned");
|
|
432
|
+
if(r.summarizedBy) p.push("folded into a summary");
|
|
433
|
+
if(r.source&&r.source.reason) p.push("via "+clip(r.source.reason,48));
|
|
434
|
+
if(r.provenance&&r.provenance.ref) p.push("source: "+clip(r.provenance.ref,44));
|
|
435
|
+
return esc(n.cl.name)+(p.length?' · '+esc(p.join(" · ")):'');
|
|
436
|
+
})()+'</div>'+
|
|
333
437
|
(n.cl.path?'<button class="btn sm" id="ui-open">OPEN IN MEMORY BANKS →</button>':"");
|
|
334
438
|
var x=EL("ui-x"); if(x) x.addEventListener("click",function(){ uniInspect(null); });
|
|
335
439
|
var op=EL("ui-open"); if(op) op.addEventListener("click",function(){
|
|
@@ -873,6 +977,26 @@ const DOCUMENT = String.raw `<!doctype html>
|
|
|
873
977
|
.ui-meta{display:flex; gap:14px; margin-bottom:9px;}
|
|
874
978
|
.ui-ents{display:flex; flex-wrap:wrap; gap:5px; margin-bottom:10px;}
|
|
875
979
|
.ui-proj{color:var(--faint); font-size:10px; margin-bottom:11px;}
|
|
980
|
+
/* Search RESULTS panel (right side, mirrors .uni-inspect on the left): the readable answer to
|
|
981
|
+
a query — an extractive summary of what memory says, then the ranked hits you can click. */
|
|
982
|
+
.uni-results{position:absolute; top:60px; right:14px; z-index:8; width:360px; max-height:calc(100% - 130px); overflow:auto;
|
|
983
|
+
background:linear-gradient(165deg, rgba(8,26,42,.96), rgba(4,14,26,.96)); border:1px solid var(--cyan); clip-path:var(--cham);
|
|
984
|
+
padding:16px; box-shadow:0 0 34px -8px rgba(89,227,255,.5);}
|
|
985
|
+
.ur-head{font-family:var(--mono); font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:var(--cyan);
|
|
986
|
+
margin-bottom:10px; text-shadow:0 0 10px rgba(89,227,255,.6);}
|
|
987
|
+
.ur-sum{font-size:12px; line-height:1.6; color:var(--ink); border-left:2px solid var(--cyan);
|
|
988
|
+
padding:2px 0 2px 10px; margin-bottom:12px;}
|
|
989
|
+
.ur-sum b{color:var(--cyan-ink);}
|
|
990
|
+
.ur-facets{display:flex; flex-wrap:wrap; gap:5px; margin-bottom:12px;}
|
|
991
|
+
.ur-facet{font-family:var(--mono); font-size:9.5px; letter-spacing:.08em; text-transform:uppercase;
|
|
992
|
+
border:1px solid rgba(89,227,255,.35); color:var(--muted); padding:2px 6px; clip-path:var(--cham);}
|
|
993
|
+
.ur-list{display:flex; flex-direction:column; gap:7px;}
|
|
994
|
+
.ur-hit{text-align:left; width:100%; background:rgba(6,20,34,.7); border:1px solid rgba(89,227,255,.18);
|
|
995
|
+
padding:8px 10px; clip-path:var(--cham); cursor:pointer; color:var(--ink); font-size:11.5px; line-height:1.5;}
|
|
996
|
+
.ur-hit:hover{border-color:var(--cyan); background:rgba(10,30,48,.9);}
|
|
997
|
+
.ur-hit .h-t{font-family:var(--mono); font-size:9px; letter-spacing:.12em; text-transform:uppercase; color:var(--cyan); margin-bottom:3px;}
|
|
998
|
+
.ur-hit .h-m{font-family:var(--mono); font-size:9px; color:var(--faint); margin-top:4px;}
|
|
999
|
+
.ur-more{font-family:var(--mono); font-size:9.5px; color:var(--faint); margin-top:9px; text-align:center;}
|
|
876
1000
|
.uni-ticker{position:absolute; left:0; right:0; bottom:0; z-index:6; padding:8px 16px 10px;
|
|
877
1001
|
background:linear-gradient(180deg, transparent, rgba(2,8,16,.9) 40%); font-family:var(--mono); font-size:10px; color:var(--muted);}
|
|
878
1002
|
.utk{padding:2px 0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
|
|
@@ -918,6 +1042,7 @@ const DOCUMENT = String.raw `<!doctype html>
|
|
|
918
1042
|
<div class="uni-legend" id="uni-legend"></div>
|
|
919
1043
|
<div class="uni-tip mono" id="uni-tip" hidden></div>
|
|
920
1044
|
<div class="uni-inspect" id="uni-inspect" hidden></div>
|
|
1045
|
+
<div class="uni-results" id="uni-results" hidden></div>
|
|
921
1046
|
<div class="uni-ticker" id="uni-ticker"></div>
|
|
922
1047
|
</div>
|
|
923
1048
|
<div id="bh-body"></div>
|