wiki-plugin-similarity 0.21.1 → 0.21.2

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.
@@ -1,4 +1,4 @@
1
- /* wiki-plugin-similarity - 0.21.1 - Fri, 04 Sep 2026 19:48:21 GMT */
1
+ /* wiki-plugin-similarity - 0.21.2 - Fri, 04 Sep 2026 20:04:24 GMT */
2
2
  (()=>{var dt={high:.78,medium:.68,low:.58},ct=dt.medium,yt=10,ht=t=>{let e=[],n=[],s=[],r=null,l=null,u="search",g=!1,k=!1,x=!1,P=null,D=null,R=null,M=null,j=(o,m)=>o===m||o.startsWith(m)&&/^[\s:]/.test(o.slice(m.length)),L=(o,m)=>o.slice(m.length).replace(/^\s*:?\s*/,"").trim();for(let o of t.split(`
3
3
  `)){let m=o.trim();if(!m||m.startsWith("#"))continue;let w=m.toUpperCase();if(j(w,"LIVE")){g=!0;continue}if(j(w,"SUBJECT")){k=!0;continue}if(j(w,"AUTHOR")){!e.length&&u==="search"&&(u="author");continue}if(j(w,"REPORT")){u==="search"&&(u="report");continue}if(j(w,"KEYWORD")){u==="search"&&(u="keyword");continue}if(j(w,"SITES")){u==="search"&&(u="sites");continue}if(j(w,"BUILD")){u==="search"&&(u="build");continue}if(j(w,"FORCE")){x=!0;continue}if(j(w,"GHOST")){P=L(m,"GHOST"),u==="search"&&(u="ghost");continue}if(j(w,"BUTTON")){D=L(m,"BUTTON");continue}if(j(w,"BATCH")){let E=L(m,"BATCH").toLowerCase();R=E==="off"||E==="no"||E==="0"?0:parseInt(E)||50;continue}if(j(w,"ALGORITHM")){M=L(m,"ALGORITHM").replace(/^\[\[|\]\]$/g,"").trim()||"search-algorithm",u==="search"&&!e.length&&(u="algorithm");continue}if(j(w,"ROSTER")){let E=L(m,"ROSTER");E&&n.push(E);continue}if(j(w,"FARM")){let E=L(m,"FARM");E&&s.push(E);continue}if(j(w,"LIST")){!e.length&&u==="search"&&(u="list");continue}if(j(w,"STATUS")){u==="search"&&(u="status");continue}if(j(w,"SIMILAR")){let E=L(w,"SIMILAR").toLowerCase();r=dt[E]||ct,!e.length&&u==="search"&&(u="similar");continue}if(j(w,"THRESHOLD")){let E=L(m,"THRESHOLD");r=dt[E.toLowerCase()]??(parseFloat(E)||ct);continue}if(j(w,"LIMIT")){l=parseInt(L(m,"LIMIT"))||yt;continue}e.push(["PUBLIC","LOCAL","PRIVATE","GALAXY","SITE"].includes(w)?w:m)}return{mode:u,specs:e,rosterRefs:n,farms:s,threshold:r??ct,limit:l??yt,live:g,subject:k,force:x,ghostUrl:P,label:D,batch:R,algorithm:M,thresholdSet:r!==null}},X=t=>t.includes("*")||t.includes("?"),Z=t=>t==="PUBLIC"||t==="LOCAL"||t==="PRIVATE"||t==="GALAXY";var xt=t=>t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),ut=new Map,Qt=async(t,e)=>{if(ut.has(t))return ut.get(t);let n=`${e}/system/indexed-domains.json?pattern=${encodeURIComponent(t)}`,s=await fetch(n);if(!s.ok)throw new Error(`indexed-domains failed: ${s.status}`);let r=await s.json();return ut.set(t,r),r},K=async(t,e)=>{t.length||(t=[window.location.hostname]);let n=new Set,s=[];for(let r of t)if(r==="*"||X(r)||Z(r))for(let l of await Qt(r,e))n.has(l.domain)||(n.add(l.domain),s.push(l));else n.has(r)||(n.add(r),s.push({domain:r,page_count:null}));return s},te=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,ee=/^ROSTER ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,ne=/^REFERENCES ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,bt=async t=>{let e=t.indexOf("/"),n=await fetch(`//${t.slice(0,e)}/${t.slice(e+1)}.json`);if(!n.ok)throw new Error(`roster page ${t} failed: ${n.status}`);return n.json()},St=async(t,e,n)=>{if(n.has(t))return;n.add(t);let s;try{s=await bt(t)}catch(r){console.warn(r.message);return}for(let r of s.story||[])if(r.type==="roster")for(let l of(r.text||"").split(/\r?\n/)){let u=l.trim();if(!u)continue;let g=u.match(te);if(g){e.add(g[0]);continue}let k=u.match(ee);if(k){await St(k[1],e,n);continue}let x=u.match(ne);if(x)try{let P=await bt(x[1]);for(let D of P.story||[])D.type==="reference"&&D.site&&e.add(D.site)}catch(P){console.warn(P.message)}}},se=async t=>{let e=new Set,n=new Set;for(let s of t)await St(s,e,n);return[...e]},vt=async(t,e)=>{let n=typeof window<"u"&&window.location?window.location.hostname:null,s=t.map(r=>r.toUpperCase()==="SITE"&&n?n:r);return e.length?[...s,...await se(e)]:s};var mt=new Map,oe=t=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(t)}`,kt=async t=>{if(mt.has(t))return mt.get(t);let e=await fetch(oe(t));if(!e.ok)return[];let n=await e.json();return mt.set(t,n),n},Q=async(t,e)=>{let n=await fetch(`${e}/system/embed.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:t})});if(!n.ok)throw new Error(`embed failed: ${n.status}`);return(await n.json()).vector},Lt=async(t,e)=>{let s=(await kt(e)).find(r=>r.slug===t);return s?s.vector:null},tt=(t,e,{threshold:n,limit:s,excludeSlug:r,excludeDomain:l})=>{let u=[];for(let{domain:g,pages:k}of e)for(let{slug:x,title:P,vector:D}of k){if(x===r&&g===l)continue;let R=0;for(let M=0;M<t.length;M++)R+=t[M]*D[M];R>=n&&u.push({domain:g,slug:x,title:P,score:R})}return u.sort((g,k)=>k.score-g.score),u.slice(0,s)},et=async(t,e)=>{let n=await K(t,e);return(await Promise.all(n.map(async({domain:r})=>({domain:r,pages:await kt(r)})))).filter(r=>r.pages.length>0)};var Et=t=>{let e=t.parents(".page:first"),n=e.prev(".page"),s=n.length?n:e,r=(s.attr("id")||"").split("_rev")[0],l=s.data("site")||window.location.hostname,u=s.data("data")||{},g=u.title||s.find(".title").text().trim()||r,k=[g,...(u.story||[]).filter(x=>x.type==="markdown"||x.type==="paragraph").map(x=>(x.text||"").trim()).filter(Boolean)].join(`
4
4
  `).slice(0,2e3);return{slug:r,site:l,title:g,text:k,isSelf:!n.length}},G=t=>`Subject: ${t.title}${t.isSelf?" (this page)":` @ ${t.site}`}`;var Ct=t=>`sim-cache-${t}`,jt=t=>{try{let e=JSON.parse(localStorage.getItem(Ct(t.id))||"null");return e?.text===(t.text||"")?e:null}catch{return null}},W=(t,e)=>{try{localStorage.setItem(Ct(t.id),JSON.stringify({text:t.text||"",ts:Date.now(),...e}))}catch{}},Tt=t=>{let e=Math.floor((Date.now()-t)/1e3);return e<60?`${e}s ago`:e<3600?`${Math.floor(e/60)}m ago`:e<86400?`${Math.floor(e/3600)}h ago`:`${Math.floor(e/86400)}d ago`};var ie=()=>{let t=new Set;try{for(let e of Object.keys(window.wiki?.neighborhood||{}))t.add(e);document.querySelectorAll(".page").forEach(e=>{let n=window.$?window.$(e).data("site"):null;t.add(n||window.location.hostname)})}catch{}return[...t]},It=async(t,e)=>{let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!n.ok){let s=new Error(`${t.split("/").pop()} failed: ${n.status}`);throw s.status=n.status,s.body=await n.json().catch(()=>({})),s}return n.json()},ae=(t,e,n)=>{let s=t.slice(0,n).map(l=>`${l.site} ${l.slug}`).join("|"),r=e.slice(0,n).map(l=>`${l.site} ${l.slug}`).join("|");return s===r},At=async t=>{let{origin:e,query:n,seeded:s={},specs:r,limit:l,batch:u,algorithm:g,render:k}=t,x=a=>t.status&&t.status(a),P={roster:[...t.roster||[],...g&&g.liked||[]],neighborhood:ie(),followed:!0,...t.prefer||{}};x("Ranking sites\u2026");let D=await It(`${e}/system/site-rank.json`,{query:n,...s,domains:r,prefer:P,algorithm:g||{},batch:u||50}),R=D.batches||[],M=new Set(R.flat().map(a=>a.toLowerCase())),j=(r||[]).filter(a=>typeof a=="string"&&/\./.test(a)&&!/[*?]/.test(a)&&!["PUBLIC","LOCAL","PRIVATE","GALAXY"].includes(a.toUpperCase())&&!M.has(a.toLowerCase()));j.length&&R.push(j);let o={searched:0,total:(D.count||0)+j.length||R.reduce((a,p)=>a+p.length,0),batches:R.length,done:0,stopped:!1,converged:!1,running:!0,unindexed:D.unindexed||[],answered:{local:0},siteIndex:D.siteIndex||null,siteInfo:new Map((D.sites||[]).map(a=>[a.domain,a]))},m=new Map,w=[],E=0,J=()=>{o.stopped=!0};o.stop=J;let V=async a=>{let p={query:n,...s,domains:a,limit:Math.max(l*3,30),flat:!0};t.thresholdSet&&(p.threshold=t.threshold);let c=await It(`${e}/system/search-report.json`,p);for(let d of c.results||[]){let y=`${d.site} ${d.slug}`,v=m.get(y);(!v||v.score<d.score)&&m.set(y,d)}let f=0;for(let d of c.peers||[])o.answered[d.host]=(o.answered[d.host]||0)+(d.domains||0),f+=d.domains||0;o.answered.local+=Math.max(0,(c.stats&&c.stats.domains||0)-f);let h=[...m.values()].sort((d,y)=>y.score-d.score),i=ae(w,h,l);w=h,o.searched+=a.length,o.done+=1,E=i?E+1:0},lt=()=>R.slice(o.done),Y=async(a=!1)=>{for(o.running=!0,o.stopped=!1;o.done<R.length&&!o.stopped;)if(await V(R[o.done]),k(w,o),!a&&E>=3&&o.done<R.length){o.converged=!0;break}return o.running=!1,k(w,o),w};return o.continueAll=()=>(o.converged=!1,E=-1/0,Y(!0)),await Y(!1),{results:w,state:o,remaining:lt}};var st="similarity:learned",re=new Set(["liked","visited","neighbourhood","neighborhood","followed","centroid","fresh","reliable"]),nt=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,ft=t=>{let e={weights:{},always:[],never:[],batch:null,trust:null};for(let n of(t||"").split(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-similarity",
3
- "version": "0.21.1",
3
+ "version": "0.21.2",
4
4
  "description": "Federated Wiki plugin — semantic similarity search across wiki domains using prebuilt vector indexes",
5
5
  "keywords": [
6
6
  "wiki",
@@ -130,6 +130,11 @@
130
130
  "type": "markdown",
131
131
  "id": "ac44fae5937e27eb",
132
132
  "text": "**0.21.1** \u2014 three fixes from the first day of the second peer. A farm's copy of a peer's Site Index is checked **hourly** by conditional GET, and always once at startup, so a restart never serves a copy the peer has since rebuilt \u2014 it was six hours, and a 304 reset the clock, so a new indexer's sites could take most of a day to reach the Cafe. A domain named explicitly in a batched item is **searched whether or not the Site Index knows it**, as one more batch, since the cascade finds the peer that holds it. And a batched item's ready line reports how far the Site Index reaches rather than how many domains this host holds, which on a farm with no galaxy tree read as a misleading zero."
133
+ },
134
+ {
135
+ "type": "markdown",
136
+ "id": "99d1f2910f05b38d",
137
+ "text": "**0.21.2** \u2014 a site answered two hops away was counted twice in the *answered by* line, once under the peer that relayed it and once under the peer that held it. The relay's count now excludes what its own peers answered."
133
138
  }
134
139
  ],
135
140
  "plugin": "similarity",
@@ -490,6 +495,17 @@
490
495
  },
491
496
  "after": "e2cde2572a99224e",
492
497
  "date": 1788551290021
498
+ },
499
+ {
500
+ "type": "add",
501
+ "id": "99d1f2910f05b38d",
502
+ "item": {
503
+ "type": "markdown",
504
+ "id": "99d1f2910f05b38d",
505
+ "text": "**0.21.2** \u2014 a site answered two hops away was counted twice in the *answered by* line, once under the peer that relayed it and once under the peer that held it. The relay's count now excludes what its own peers answered."
506
+ },
507
+ "after": "ac44fae5937e27eb",
508
+ "date": 1788552261437
493
509
  }
494
510
  ]
495
511
  }
package/server/cascade.js CHANGED
@@ -42,9 +42,15 @@ const askCascade = async ({ peers, missing, body, hops = 0, via = [], post, time
42
42
  continue
43
43
  }
44
44
  for (const r of ans.body.results) out.results.push({ ...r, via: r.via || host })
45
+ // A peer's stats include what its own peers answered; count those
46
+ // under the host that really held them, not twice.
45
47
  const s = ans.body.stats || {}
46
- out.peers.push({ host, domains: s.domains || 0, pages: s.pages || 0 })
47
- for (const p of ans.body.peers || []) out.peers.push(p) // what it asked in turn
48
+ const nested = ans.body.peers || []
49
+ const nestedDomains = nested.reduce((n, p) => n + (p.domains || 0), 0)
50
+ const nestedPages = nested.reduce((n, p) => n + (p.pages || 0), 0)
51
+ out.peers.push({ host, domains: Math.max(0, (s.domains || 0) - nestedDomains),
52
+ pages: Math.max(0, (s.pages || 0) - nestedPages) })
53
+ for (const p of nested) out.peers.push(p) // what it asked in turn
48
54
  // A peer that says what it held lets the walk go on with the rest; an
49
55
  // older peer that does not is taken to have answered for everything.
50
56
  const held = Array.isArray(ans.body.held) ? new Set(ans.body.held) : null