wiki-plugin-similarity 0.13.0 → 0.13.1
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/client/similarity.js +1 -1
- package/package.json +1 -1
- package/server/galaxy-vectors.js +24 -17
- package/server/search-report.js +0 -0
package/client/similarity.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* wiki-plugin-similarity - 0.13.
|
|
1
|
+
/* wiki-plugin-similarity - 0.13.1 - Sun, 30 Aug 2026 12:37:30 GMT */
|
|
2
2
|
(()=>{var q={high:.78,medium:.68,low:.58},N=q.medium,J=10,_=t=>{let e=[],s=[],i=[],p=null,E=null,f="search",y=!1,L=!1,b=!1,k=null,C=null,S=(a,r)=>a===r||a.startsWith(r)&&/^[\s:]/.test(a.slice(r.length)),w=(a,r)=>a.slice(r.length).replace(/^\s*:?\s*/,"").trim();for(let a of t.split(`
|
|
3
3
|
`)){let r=a.trim();if(!r||r.startsWith("#"))continue;let g=r.toUpperCase();if(S(g,"LIVE")){y=!0;continue}if(S(g,"SUBJECT")){L=!0;continue}if(S(g,"AUTHOR")){!e.length&&f==="search"&&(f="author");continue}if(S(g,"REPORT")){f==="search"&&(f="report");continue}if(S(g,"KEYWORD")){f==="search"&&(f="keyword");continue}if(S(g,"SITES")){f==="search"&&(f="sites");continue}if(S(g,"BUILD")){f==="search"&&(f="build");continue}if(S(g,"FORCE")){b=!0;continue}if(S(g,"GHOST")){k=w(r,"GHOST"),f==="search"&&(f="ghost");continue}if(S(g,"BUTTON")){C=w(r,"BUTTON");continue}if(S(g,"ROSTER")){let v=w(r,"ROSTER");v&&s.push(v);continue}if(S(g,"FARM")){let v=w(r,"FARM");v&&i.push(v);continue}if(S(g,"LIST")){!e.length&&f==="search"&&(f="list");continue}if(S(g,"SIMILAR")){let v=w(g,"SIMILAR").toLowerCase();p=q[v]||N,!e.length&&f==="search"&&(f="similar");continue}if(S(g,"THRESHOLD")){let v=w(r,"THRESHOLD");p=q[v.toLowerCase()]??(parseFloat(v)||N);continue}if(S(g,"LIMIT")){E=parseInt(w(r,"LIMIT"))||J;continue}e.push(["PUBLIC","LOCAL","PRIVATE","GALAXY"].includes(g)?g:r)}return{mode:f,specs:e,rosterRefs:s,farms:i,threshold:p??N,limit:E??J,live:y,subject:L,force:b,ghostUrl:k,label:C,thresholdSet:p!==null}},Y=t=>t.includes("*")||t.includes("?"),W=t=>t==="PUBLIC"||t==="LOCAL"||t==="PRIVATE"||t==="GALAXY";var X=t=>t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),B=new Map,pt=async(t,e)=>{if(B.has(t))return B.get(t);let s=`${e}/system/indexed-domains.json?pattern=${encodeURIComponent(t)}`,i=await fetch(s);if(!i.ok)throw new Error(`indexed-domains failed: ${i.status}`);let p=await i.json();return B.set(t,p),p},O=async(t,e)=>{t.length||(t=[window.location.hostname]);let s=new Set,i=[];for(let p of t)if(p==="*"||Y(p)||W(p))for(let E of await pt(p,e))s.has(E.domain)||(s.add(E.domain),i.push(E));else s.has(p)||(s.add(p),i.push({domain:p,page_count:null}));return i},gt=/^([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+|localhost)(:\d+)?$/,ht=/^ROSTER ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,wt=/^REFERENCES ([A-Za-z0-9.\-:]+\/[a-z0-9-]+)$/,Z=async t=>{let e=t.indexOf("/"),s=await fetch(`//${t.slice(0,e)}/${t.slice(e+1)}.json`);if(!s.ok)throw new Error(`roster page ${t} failed: ${s.status}`);return s.json()},Q=async(t,e,s)=>{if(s.has(t))return;s.add(t);let i;try{i=await Z(t)}catch(p){console.warn(p.message);return}for(let p of i.story||[])if(p.type==="roster")for(let E of(p.text||"").split(/\r?\n/)){let f=E.trim();if(!f)continue;let y=f.match(gt);if(y){e.add(y[0]);continue}let L=f.match(ht);if(L){await Q(L[1],e,s);continue}let b=f.match(wt);if(b)try{let k=await Z(b[1]);for(let C of k.story||[])C.type==="reference"&&C.site&&e.add(C.site)}catch(k){console.warn(k.message)}}},yt=async t=>{let e=new Set,s=new Set;for(let i of t)await Q(i,e,s);return[...e]},tt=async(t,e)=>e.length?[...t,...await yt(e)]:t;var G=new Map,$t=t=>`${window.location.origin}/system/semantic-vectors.json?domain=${encodeURIComponent(t)}`,et=async t=>{if(G.has(t))return G.get(t);let e=await fetch($t(t));if(!e.ok)return[];let s=await e.json();return G.set(t,s),s},U=async(t,e)=>{let s=await fetch(`${e}/system/embed.json`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:t})});if(!s.ok)throw new Error(`embed failed: ${s.status}`);return(await s.json()).vector},st=async(t,e)=>{let i=(await et(e)).find(p=>p.slug===t);return i?i.vector:null},M=(t,e,{threshold:s,limit:i,excludeSlug:p,excludeDomain:E})=>{let f=[];for(let{domain:y,pages:L}of e)for(let{slug:b,title:k,vector:C}of L){if(b===p&&y===E)continue;let S=0;for(let w=0;w<t.length;w++)S+=t[w]*C[w];S>=s&&f.push({domain:y,slug:b,title:k,score:S})}return f.sort((y,L)=>L.score-y.score),f.slice(0,i)},z=async(t,e)=>{let s=await O(t,e);return(await Promise.all(s.map(async({domain:p})=>({domain:p,pages:await et(p)})))).filter(p=>p.pages.length>0)};var nt=t=>{let e=t.parents(".page:first"),s=e.prev(".page"),i=s.length?s:e,p=(i.attr("id")||"").split("_rev")[0],E=i.data("site")||window.location.hostname,f=i.data("data")||{},y=f.title||i.find(".title").text().trim()||p,L=[y,...(f.story||[]).filter(b=>b.type==="markdown"||b.type==="paragraph").map(b=>(b.text||"").trim()).filter(Boolean)].join(`
|
|
4
4
|
`).slice(0,2e3);return{slug:p,site:E,title:y,text:L,isSelf:!s.length}},T=t=>`Subject: ${t.title}${t.isSelf?" (this page)":` @ ${t.site}`}`;var it=t=>`sim-cache-${t}`,ot=t=>{try{let e=JSON.parse(localStorage.getItem(it(t.id))||"null");return e?.text===(t.text||"")?e:null}catch{return null}},P=(t,e)=>{try{localStorage.setItem(it(t.id),JSON.stringify({text:t.text||"",ts:Date.now(),...e}))}catch{}},at=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 D=`
|
package/package.json
CHANGED
package/server/galaxy-vectors.js
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
// at index time because off-farm pages have no local page JSON for
|
|
11
11
|
// search-report's enrich() to read.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// This module holds deserialized per-site entries in a byte-capped LRU keyed
|
|
14
|
+
// by file path + mtime, so a repeat query costs no JSON parsing at all. Since
|
|
15
|
+
// 0.13.1 the FARM vectors ride the same cache: "cheap enough to re-read per
|
|
16
|
+
// query" proved false at 400 sites / 150 MB — the synchronous re-parse
|
|
17
|
+
// blocked the event loop for tens of seconds and 502'd concurrent page saves
|
|
18
|
+
// (Semantic ReIndex Plan, Phase 1; the yellow-halo bug, 2026-08-30).
|
|
17
19
|
|
|
18
20
|
const fs = require('node:fs')
|
|
19
21
|
const path = require('node:path')
|
|
@@ -28,29 +30,28 @@ const galaxyRoot = () => {
|
|
|
28
30
|
return path.join(process.env.HOME || '/tmp', '.cache', 'wiki-similarity', 'galaxy-vectors')
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
//
|
|
33
|
+
// file path → {mtimeMs, bytes, pages} — keyed by path, not domain, so the
|
|
34
|
+
// same domain name in different farms (or farm vs galaxy) can never collide.
|
|
32
35
|
const cache = new Map()
|
|
33
36
|
let cachedBytes = 0
|
|
34
37
|
|
|
35
38
|
const evictUntil = budget => {
|
|
36
|
-
for (const [
|
|
39
|
+
for (const [file, entry] of cache) {
|
|
37
40
|
if (cachedBytes <= budget) break
|
|
38
|
-
cache.delete(
|
|
41
|
+
cache.delete(file)
|
|
39
42
|
cachedBytes -= entry.bytes
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
const loadGalaxyVectors = (farm, domain) => {
|
|
46
|
-
const file = path.join(farm, domain, 'status', 'semantic-vectors.json')
|
|
46
|
+
// The cache core: one vectors FILE → its entry array (or []), mtime-fresh.
|
|
47
|
+
const loadVectorsFile = file => {
|
|
47
48
|
let stat
|
|
48
49
|
try { stat = fs.statSync(file) } catch { return [] }
|
|
49
50
|
|
|
50
|
-
const hit = cache.get(
|
|
51
|
+
const hit = cache.get(file)
|
|
51
52
|
if (hit && hit.mtimeMs === stat.mtimeMs) {
|
|
52
|
-
cache.delete(
|
|
53
|
-
cache.set(
|
|
53
|
+
cache.delete(file) // refresh LRU position
|
|
54
|
+
cache.set(file, hit)
|
|
54
55
|
return hit.pages
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -58,17 +59,23 @@ const loadGalaxyVectors = (farm, domain) => {
|
|
|
58
59
|
try { pages = JSON.parse(fs.readFileSync(file, 'utf8')) } catch { return [] }
|
|
59
60
|
if (!Array.isArray(pages)) return []
|
|
60
61
|
|
|
61
|
-
if (hit) { cache.delete(
|
|
62
|
+
if (hit) { cache.delete(file); cachedBytes -= hit.bytes }
|
|
62
63
|
cachedBytes += stat.size
|
|
63
|
-
cache.set(
|
|
64
|
+
cache.set(file, { mtimeMs: stat.mtimeMs, bytes: stat.size, pages })
|
|
64
65
|
evictUntil(CACHE_BYTES)
|
|
65
66
|
return pages
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Same contract as the old loadVectors(farm, domain) — farm or galaxy tree.
|
|
70
|
+
const loadVectorsCached = (farm, domain) =>
|
|
71
|
+
loadVectorsFile(path.join(farm, domain, 'status', 'semantic-vectors.json'))
|
|
72
|
+
|
|
73
|
+
const loadGalaxyVectors = loadVectorsCached
|
|
74
|
+
|
|
68
75
|
const galaxyCacheStats = () => ({
|
|
69
76
|
sites: cache.size,
|
|
70
77
|
bytes: cachedBytes,
|
|
71
78
|
capBytes: CACHE_BYTES,
|
|
72
79
|
})
|
|
73
80
|
|
|
74
|
-
module.exports = { galaxyRoot, loadGalaxyVectors, galaxyCacheStats }
|
|
81
|
+
module.exports = { galaxyRoot, loadGalaxyVectors, loadVectorsCached, loadVectorsFile, galaxyCacheStats }
|
package/server/search-report.js
CHANGED
|
Binary file
|