@cachly-dev/mcp-server 0.10.171 → 0.10.173

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
- import{computeEmbedding as w}from"../embeddings.js";const _=new Set(["remember_context","recall_context","list_remembered","forget_context"]);async function k(f,m,$,x){switch(f){case"remember_context":{const{instance_id:h,key:c,content:a,category:r="custom",ttl:t=86400}=m,e=await $(h),n=`cachly:ctx:${r}:${c}`,i=JSON.stringify({key:c,category:r,size:a.length,created:new Date().toISOString()});t&&t>0?(await e.set(n,a,"EX",t),await e.set(`${n}:meta`,i,"EX",t)):(await e.set(n,a),await e.set(`${n}:meta`,i));const o=await x(`/api/v1/instances/${h}`);if(o.vector_token)try{const s=await w(`${c}: ${a.slice(0,500)}`),l=`https://api.cachly.dev/v1/sem/${o.vector_token}`,d={id:`ctx:${r}:${c}`,prompt:c,namespace:"cachly:ctx",embedding:s};t&&t>0&&(d.expires_at=new Date(Date.now()+t*1e3).toISOString());const y=await fetch(`${l}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:AbortSignal.timeout(8e3)});y.ok||process.stderr.write(`[cachly] semantic index skipped (HTTP ${y.status}) \u2014 smart_recall on this context will be keyword-only
1
+ import{computeEmbedding as w}from"../embeddings.js";import{schwaerzeFelder as g}from"../geheimnis-filter.js";const S=new Set(["remember_context","recall_context","list_remembered","forget_context"]);async function b(f,m,$,x){switch(f){case"remember_context":{const{instance_id:h,key:c,content:a,category:r="custom",ttl:t=86400}=g(m).felder,e=await $(h),n=`cachly:ctx:${r}:${c}`,i=JSON.stringify({key:c,category:r,size:a.length,created:new Date().toISOString()});t&&t>0?(await e.set(n,a,"EX",t),await e.set(`${n}:meta`,i,"EX",t)):(await e.set(n,a),await e.set(`${n}:meta`,i));const o=await x(`/api/v1/instances/${h}`);if(o.vector_token)try{const s=await w(`${c}: ${a.slice(0,500)}`),l=`https://api.cachly.dev/v1/sem/${o.vector_token}`,d={id:`ctx:${r}:${c}`,prompt:c,namespace:"cachly:ctx",embedding:s};t&&t>0&&(d.expires_at=new Date(Date.now()+t*1e3).toISOString());const y=await fetch(`${l}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:AbortSignal.timeout(8e3)});y.ok||process.stderr.write(`[cachly] semantic index skipped (HTTP ${y.status}) \u2014 smart_recall on this context will be keyword-only
2
2
  `)}catch(s){process.stderr.write(`[cachly] semantic index failed: ${s instanceof Error?s.message:String(s)} \u2014 smart_recall on this context will be keyword-only
3
3
  `)}return["\u{1F9E0} **Context Saved**","",` Key: \`${c}\``,` Category: ${r}`,` Size: ${a.length} chars`,` TTL: ${t>0?`${t}s (${Math.round(t/3600)}h)`:"no expiry"}`,"",`\u{1F4A1} Use \`recall_context("${c}")\` to retrieve this later.`,` Or \`smart_recall("${c.split("_").join(" ")}")\` for semantic search.`].join(`
4
4
  `)}case"recall_context":{const{instance_id:h,key:c}=m,a=await $(h);if(c.includes("*")){const t=[],e=a.scanStream({match:`cachly:ctx:*:${c}`,count:100});if(await new Promise((i,o)=>{e.on("data",s=>{t.push(...s.filter(l=>!l.endsWith(":meta"))),t.length>=20&&(e.destroy(),i())}),e.on("end",i),e.on("error",o)}),t.length===0)return`\u26A0\uFE0F No cached context found matching pattern \`${c}\`.`;const n=[`\u{1F9E0} **Recalled ${t.length} context entries matching \`${c}\`:**
@@ -14,4 +14,4 @@ Use \`list_remembered\` to see available cached context.`}case"list_remembered":
14
14
  `];for(const o of e.slice(0,a)){const s=await r.ttl(o),l=await r.get(o),d=o.replace("cachly:ctx:","").split(":"),y=d[0],u=d.slice(1).join(":"),p=l?.slice(0,80).replace(/\n/g," ")??"";i.push(` \u2022 **${u}** (${y})`,` Size: ${l?.length??0} chars \xB7 TTL: ${s===-1?"\u221E":`${Math.round(s/60)}m`}`,` _"${p}${(l?.length??0)>80?"\u2026":""}"_`,"")}return i.join(`
15
15
  `)}case"forget_context":{const{instance_id:h,keys:c}=m,a=await $(h);let r=0;for(const t of c)if(t.includes("*")){const e=[],n=a.scanStream({match:`cachly:ctx:*:${t}*`,count:100});await new Promise((i,o)=>{n.on("data",s=>e.push(...s)),n.on("end",i),n.on("error",o)}),e.length>0&&(r+=await a.del(...e))}else{const e=["overview","architecture","file_summary","dependency","thinking","custom"];for(const n of e)r+=await a.del(`cachly:ctx:${n}:${t}`,`cachly:ctx:${n}:${t}:meta`)}return`\u{1F5D1}\uFE0F **Forgot ${r} context entries.**
16
16
 
17
- Keys: ${c.map(t=>`\`${t}\``).join(", ")}`}default:return null}}export{_ as CONTEXT_TOOL_NAMES,k as handleContextTool};
17
+ Keys: ${c.map(t=>`\`${t}\``).join(", ")}`}default:return null}}export{S as CONTEXT_TOOL_NAMES,b as handleContextTool};
@@ -1,8 +1,8 @@
1
- import{readFileSync as de}from"node:fs";import{bauabdruck as he,formatiereBauabdruck as pe}from"../bauabdruck.js";import{beurteileSpeicher as me,zahlAusInfo as oe}from"../speicherlage.js";import{beurteileDeckung as fe}from"../eingaenge.js";import{join as D}from"node:path";import{calculateConfidence as G,CONFIDENCE_STALE_VALUE as ee,CONFIDENCE_WARN_VALUE as ge,CONFIDENCE_WARN_DAYS as ae,CONFIDENCE_STALE_DAYS as re}from"../confidence.js";import{safeJsonParse as C}from"../utils.js";import{ersparteMinuten as ye,istStarterLektion as we,fmtStunden as ie}from"../wertbeitrag.js";import{cachlyUrl as _e}from"../cachly-url.js";import{leseVermerke as ve}from"../aussetzer.js";const Be=new Set(["sync_file_changes","team_learn","team_confirm","team_recall","team_synthesize","memory_crystalize","team_crystallize","brain_doctor","brain_hygiene","recall_at","trace_dependency","global_learn","global_recall","publish_lesson","import_public_brain","setup_ai_memory","team_assign_role","team_whoami","team_roster","team_grant_scope","team_scopes","team_audit"]),H={admin:4,reviewer:3,contributor:2,viewer:1},U={admin:"\u{1F451}",reviewer:"\u{1F6E1}\uFE0F",contributor:"\u270F\uFE0F",viewer:"\u{1F441}\uFE0F"},te={admin:"assign roles \xB7 delete lessons \xB7 all reviewer + contributor actions",reviewer:"senior-review lessons (\u{1F6E1}\uFE0F) \xB7 all contributor actions",contributor:"store lessons \xB7 peer-review lessons (\u2714\uFE0F) \xB7 recall",viewer:"recall only \u2014 cannot store or review lessons"},B=k=>`cachly:team:roles:${k}`;async function W(k,y,_){return await k.hget(B(y),_.toLowerCase()).catch(()=>null)}function q(k,y){return k?(H[k]??0)>=(H[y]??0):!1}function $e(k){return k==="admin"||k==="reviewer"?"senior":"peer"}async function be(k,y){const _=await k.hgetall(B(y)).catch(()=>({}));return Object.values(_).includes("admin")}async function se(k,y,_,N,i){if(!await be(k,y))return null;const e=_?await W(k,y,_):null;if(q(e,N))return null;const r=e?`${U[e]} ${e}`:"no role";return[`\u{1F512} **Permission denied** \u2014 to ${i} you need at least \`${N}\` ${U[N]} (you have: ${r}).`,"","Ask an admin to grant it:",`\`team_assign_role(instance_id="${y}", handle="${_||"<your-handle>"}", role="${N}", assigned_by="<admin>")\``,"_Governance is active on this brain. Run `team_whoami` to check your role._"].join(`
2
- `)}async function ce(k,y,_){const N=`cachly:team:audit:${y}`,i=JSON.stringify({..._,ts:new Date().toISOString()});await k.rpush(N,i).catch(()=>{}),await k.ltrim(N,-500,-1).catch(()=>{}),await k.expire(N,365*2*86400).catch(()=>{})}const ke="cachly:team:review_mode",Se="cachly:lesson:proposed:",xe=90*86400;async function Ae(k,y){const _=await k.get(ke).catch(()=>null);return _==="1"||_==="true"||_==="on"}async function Le(k,y,_){const N={..._,proposed_at:new Date().toISOString(),status:"pending"};await k.set(`${Se}${y}`,JSON.stringify(N),"EX",xe).catch(()=>{});const i=typeof _.author=="string"&&_.author?` by **${_.author}**`:"";return[`\u{1F4DD} **Lesson submitted for review** \u2014 \`${y}\`${i} is now a pending proposal.`,"","This team requires review before knowledge enters the Team Brain (org setting).","An org owner/admin can approve or reject it in the dashboard under **Team \u2192 Review**.","Until approved it will NOT surface in `smart_recall` / `team_recall`."].join(`
3
- `)}const Z=k=>`cachly:team:groups:${k}`,Y=(k,y)=>`cachly:team:group:${k}:${y.toLowerCase()}`;async function je(k,y,_){const N=await k.smembers(Z(y)).catch(()=>[]),i=new Set;for(const e of N)await k.sismember(Y(y,e),_.toLowerCase()).catch(()=>0)&&i.add(e.toLowerCase());return i}function Fe(k,y,_){return!k||_?!0:y.has(k.toLowerCase())}async function Ue(k,y,_,N){switch(k){case"sync_file_changes":{const{instance_id:i,changed_files:e,git_diff_stat:r,commit_msg:o}=y,a=await _(i),t={ts:new Date().toISOString(),files:e,commit_msg:o,diff_stat:r?.slice(0,500)};await a.lpush("cachly:session:file_changes",JSON.stringify(t)),await a.ltrim("cachly:session:file_changes",0,99),await a.expire("cachly:session:file_changes",30*86400);const c=[],l=a.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((p,g)=>{l.on("data",v=>c.push(...v)),l.on("end",p),l.on("error",g)});const h=[],s=c.length>0?await a.mget(...c):[];for(const p of s){if(!p)continue;const g=C(p,null);if(!g)continue;const v=g.topic.toLowerCase().split(/[:\-_]/);if(e.some(w=>{const b=w.split("/").pop()?.replace(/\.[^.]+$/,"").toLowerCase()??"";return v.some(m=>m.length>3&&b.includes(m))||(g.file_paths??[]).some(m=>w.includes(m)||m.includes(w))})){const w=g.outcome==="success"?"\u2705":"\u26A0\uFE0F";h.push(` ${w} \`${g.topic}\` \u2014 ${g.what_worked.slice(0,80)}`)}}const n=[`\u{1F4C1} **File sync recorded**: ${e.length} files`,o?`\u{1F4DD} Commit: "${o}"`:"","",`**Changed:** ${e.slice(0,8).map(p=>`\`${p}\``).join(", ")}${e.length>8?` +${e.length-8} more`:""}`,""];return h.length>0?n.push(`\u{1F9E0} **Relevant brain lessons (${h.length}):**`,...h):n.push("\u{1F4A1} No existing lessons match these files yet. Add them with `learn_from_attempts`."),n.filter(Boolean).join(`
4
- `)}case"team_learn":{const{instance_id:i,author:e,topic:r,outcome:o,what_worked:a,what_failed:t,severity:c,file_paths:l,commands:h,tags:s}=y;if(!e||!r||!o||!a)return"\u274C Required: author, topic, outcome, what_worked";const n=i;if(!n)return"\u274C instance_id required";const p=await _(n),g=await se(p,n,e,"contributor","store team lessons");if(g)return g;const v={topic:r,outcome:o,what_worked:a,what_failed:t??"",severity:c??"minor",author:e,file_paths:l??[],commands:h??[],tags:[...s??[],"team"],timestamp:new Date().toISOString(),recall_count:0,version:2};if(await Ae(p,n))return Le(p,r,v);const L=`cachly:lessons:${r}`;return await p.rpush(L,JSON.stringify(v)),o==="success"&&await p.set(`cachly:lesson:best:${r}`,JSON.stringify(v)),`\u2705 Team lesson stored by **${e}**: \`${r}\` (${o})
5
- \u{1F4A1} ${a.slice(0,120)}`}case"team_confirm":{const{instance_id:i,topic:e,reviewer:r,level:o,note:a=""}=y;if(!i)return"\u274C instance_id required";if(!e||!r)return"\u274C Required: topic, reviewer";const t=await _(i),c=await se(t,i,r,"reviewer","confirm team lessons");if(c)return c;const l=await W(t,i,r),h=$e(l),s=o==="peer"?"peer":o==="senior"&&q(l,"reviewer")?"senior":h,n=l?` (role: ${U[l]} ${l})`:" (no role assigned \u2014 peer weight applied)",p=`cachly:lesson:best:${e}`,g=await t.get(p),v=C(g,null);if(!v)return`\u{1F4ED} No best-solution lesson found for \`${e}\`.
1
+ import{readFileSync as de}from"node:fs";import{bauabdruck as he,formatiereBauabdruck as pe}from"../bauabdruck.js";import{beurteileSpeicher as me,zahlAusInfo as oe}from"../speicherlage.js";import{beurteileDeckung as fe}from"../eingaenge.js";import{join as D}from"node:path";import{calculateConfidence as G,CONFIDENCE_STALE_VALUE as ee,CONFIDENCE_WARN_VALUE as ge,CONFIDENCE_WARN_DAYS as ae,CONFIDENCE_STALE_DAYS as re}from"../confidence.js";import{safeJsonParse as C}from"../utils.js";import{ersparteMinuten as ye,istStarterLektion as we,fmtStunden as ie}from"../wertbeitrag.js";import{cachlyUrl as _e}from"../cachly-url.js";import{leseVermerke as ve}from"../aussetzer.js";import{schwaerzeFelder as $e}from"../geheimnis-filter.js";const Ue=new Set(["sync_file_changes","team_learn","team_confirm","team_recall","team_synthesize","memory_crystalize","team_crystallize","brain_doctor","brain_hygiene","recall_at","trace_dependency","global_learn","global_recall","publish_lesson","import_public_brain","setup_ai_memory","team_assign_role","team_whoami","team_roster","team_grant_scope","team_scopes","team_audit"]),H={admin:4,reviewer:3,contributor:2,viewer:1},U={admin:"\u{1F451}",reviewer:"\u{1F6E1}\uFE0F",contributor:"\u270F\uFE0F",viewer:"\u{1F441}\uFE0F"},te={admin:"assign roles \xB7 delete lessons \xB7 all reviewer + contributor actions",reviewer:"senior-review lessons (\u{1F6E1}\uFE0F) \xB7 all contributor actions",contributor:"store lessons \xB7 peer-review lessons (\u2714\uFE0F) \xB7 recall",viewer:"recall only \u2014 cannot store or review lessons"},B=k=>`cachly:team:roles:${k}`;async function W(k,y,_){return await k.hget(B(y),_.toLowerCase()).catch(()=>null)}function q(k,y){return k?(H[k]??0)>=(H[y]??0):!1}function be(k){return k==="admin"||k==="reviewer"?"senior":"peer"}async function ke(k,y){const _=await k.hgetall(B(y)).catch(()=>({}));return Object.values(_).includes("admin")}async function se(k,y,_,N,i){if(!await ke(k,y))return null;const e=_?await W(k,y,_):null;if(q(e,N))return null;const r=e?`${U[e]} ${e}`:"no role";return[`\u{1F512} **Permission denied** \u2014 to ${i} you need at least \`${N}\` ${U[N]} (you have: ${r}).`,"","Ask an admin to grant it:",`\`team_assign_role(instance_id="${y}", handle="${_||"<your-handle>"}", role="${N}", assigned_by="<admin>")\``,"_Governance is active on this brain. Run `team_whoami` to check your role._"].join(`
2
+ `)}async function ce(k,y,_){const N=`cachly:team:audit:${y}`,i=JSON.stringify({..._,ts:new Date().toISOString()});await k.rpush(N,i).catch(()=>{}),await k.ltrim(N,-500,-1).catch(()=>{}),await k.expire(N,365*2*86400).catch(()=>{})}const Se="cachly:team:review_mode",xe="cachly:lesson:proposed:",Ae=90*86400;async function Le(k,y){const _=await k.get(Se).catch(()=>null);return _==="1"||_==="true"||_==="on"}async function je(k,y,_){const N={..._,proposed_at:new Date().toISOString(),status:"pending"};await k.set(`${xe}${y}`,JSON.stringify(N),"EX",Ae).catch(()=>{});const i=typeof _.author=="string"&&_.author?` by **${_.author}**`:"";return[`\u{1F4DD} **Lesson submitted for review** \u2014 \`${y}\`${i} is now a pending proposal.`,"","This team requires review before knowledge enters the Team Brain (org setting).","An org owner/admin can approve or reject it in the dashboard under **Team \u2192 Review**.","Until approved it will NOT surface in `smart_recall` / `team_recall`."].join(`
3
+ `)}const Z=k=>`cachly:team:groups:${k}`,Y=(k,y)=>`cachly:team:group:${k}:${y.toLowerCase()}`;async function Oe(k,y,_){const N=await k.smembers(Z(y)).catch(()=>[]),i=new Set;for(const e of N)await k.sismember(Y(y,e),_.toLowerCase()).catch(()=>0)&&i.add(e.toLowerCase());return i}function Je(k,y,_){return!k||_?!0:y.has(k.toLowerCase())}async function Ke(k,y,_,N){switch(k){case"sync_file_changes":{const{instance_id:i,changed_files:e,git_diff_stat:r,commit_msg:o}=y,a=await _(i),t={ts:new Date().toISOString(),files:e,commit_msg:o,diff_stat:r?.slice(0,500)};await a.lpush("cachly:session:file_changes",JSON.stringify(t)),await a.ltrim("cachly:session:file_changes",0,99),await a.expire("cachly:session:file_changes",30*86400);const c=[],l=a.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((p,g)=>{l.on("data",v=>c.push(...v)),l.on("end",p),l.on("error",g)});const h=[],s=c.length>0?await a.mget(...c):[];for(const p of s){if(!p)continue;const g=C(p,null);if(!g)continue;const v=g.topic.toLowerCase().split(/[:\-_]/);if(e.some(w=>{const b=w.split("/").pop()?.replace(/\.[^.]+$/,"").toLowerCase()??"";return v.some(m=>m.length>3&&b.includes(m))||(g.file_paths??[]).some(m=>w.includes(m)||m.includes(w))})){const w=g.outcome==="success"?"\u2705":"\u26A0\uFE0F";h.push(` ${w} \`${g.topic}\` \u2014 ${g.what_worked.slice(0,80)}`)}}const n=[`\u{1F4C1} **File sync recorded**: ${e.length} files`,o?`\u{1F4DD} Commit: "${o}"`:"","",`**Changed:** ${e.slice(0,8).map(p=>`\`${p}\``).join(", ")}${e.length>8?` +${e.length-8} more`:""}`,""];return h.length>0?n.push(`\u{1F9E0} **Relevant brain lessons (${h.length}):**`,...h):n.push("\u{1F4A1} No existing lessons match these files yet. Add them with `learn_from_attempts`."),n.filter(Boolean).join(`
4
+ `)}case"team_learn":{const{instance_id:i,author:e,topic:r,outcome:o,what_worked:a,what_failed:t,severity:c,file_paths:l,commands:h,tags:s}=$e(y).felder;if(!e||!r||!o||!a)return"\u274C Required: author, topic, outcome, what_worked";const n=i;if(!n)return"\u274C instance_id required";const p=await _(n),g=await se(p,n,e,"contributor","store team lessons");if(g)return g;const v={topic:r,outcome:o,what_worked:a,what_failed:t??"",severity:c??"minor",author:e,file_paths:l??[],commands:h??[],tags:[...s??[],"team"],timestamp:new Date().toISOString(),recall_count:0,version:2};if(await Le(p,n))return je(p,r,v);const L=`cachly:lessons:${r}`;return await p.rpush(L,JSON.stringify(v)),o==="success"&&await p.set(`cachly:lesson:best:${r}`,JSON.stringify(v)),`\u2705 Team lesson stored by **${e}**: \`${r}\` (${o})
5
+ \u{1F4A1} ${a.slice(0,120)}`}case"team_confirm":{const{instance_id:i,topic:e,reviewer:r,level:o,note:a=""}=y;if(!i)return"\u274C instance_id required";if(!e||!r)return"\u274C Required: topic, reviewer";const t=await _(i),c=await se(t,i,r,"reviewer","confirm team lessons");if(c)return c;const l=await W(t,i,r),h=be(l),s=o==="peer"?"peer":o==="senior"&&q(l,"reviewer")?"senior":h,n=l?` (role: ${U[l]} ${l})`:" (no role assigned \u2014 peer weight applied)",p=`cachly:lesson:best:${e}`,g=await t.get(p),v=C(g,null);if(!v)return`\u{1F4ED} No best-solution lesson found for \`${e}\`.
6
6
 
7
7
  A lesson must exist (store one with \`team_learn\` / \`learn_from_attempts\`) before it can be confirmed.`;const L=`cachly:lesson:endorsers:${e}`,w=await t.sadd(L,r)===1;await t.expire(L,365*86400);const b=await t.scard(L),d=(v.review_level??"")==="senior"?"senior":s,u={...v,reviewed_by:r,review_level:d,endorsements:b,reviewed_at:new Date().toISOString()};await t.set(p,JSON.stringify(u));const A={reviewer:r,level:s,note:a.slice(0,280),ts:new Date().toISOString()},j=`cachly:lesson:reviews:${e}`;await t.rpush(j,JSON.stringify(A)),await t.ltrim(j,-50,-1),await t.expire(j,365*86400),await ce(t,i,{action:"team_confirm",actor:r,topic:e,level:d});const x=d==="senior"?"\u{1F6E1}\uFE0F senior-reviewed":"\u2714\uFE0F peer-reviewed",S=w?"":` (already endorsed by ${r} \u2014 no change to rank)`;return`${x} \u2014 \`${e}\` confirmed by **${r}**${n} (${b} distinct endorsement${b===1?"":"s"})${S}.
8
8
  \u{1F4C8} This lesson now ranks higher in \`smart_recall\` / \`team_recall\`.${a?`
@@ -181,6 +181,6 @@ Result: Your AI **never solves the same problem twice** and always picks up exac
181
181
  `)}case"team_audit":{const{instance_id:i,requester:e="",limit:r=50}=y;if(!i)return"\u274C Required: instance_id";const o=await _(i),a=await se(o,i,e,"admin","view the governance audit log");if(a)return a;const t=Math.min(Math.max(1,Number(r)||50),200),c=await o.lrange(`cachly:team:audit:${i}`,-t,-1).catch(()=>[]);if(c.length===0)return["## \u{1F4DC} Governance Audit Log","","No governance events recorded yet.","Events are logged automatically on `team_assign_role` and `team_confirm`."].join(`
182
182
  `);const l=c.map(s=>C(s,null)).filter(s=>s!==null).reverse(),h=s=>{const n=s.ts?new Date(s.ts).toISOString().replace("T"," ").slice(0,16):"?";return s.action==="team_assign_role"?`| ${n} | \u{1F451} role | **${s.actor}** set \`${s.handle}\`: ${s.from??"\u2014"} \u2192 **${s.to}** |`:s.action==="team_confirm"?`| ${n} | \u2705 confirm | **${s.actor}** confirmed \`${s.topic}\` (${s.level}) |`:`| ${n} | ${s.action??"?"} | ${s.actor??"?"} |`};return[`## \u{1F4DC} Governance Audit Log (${l.length} event${l.length!==1?"s":""})`,"","| When (UTC) | Type | Detail |","|---|---|---|",...l.map(h),"",`_Immutable trail \xB7 2-year retention \xB7 last ${t} events. Admin-only._`].join(`
183
183
  `)}case"team_grant_scope":{const{instance_id:i,handle:e,group:r,action:o="add",assigned_by:a=""}=y;if(!i||!e||!r)return"\u274C Required: instance_id, handle, group";const t=String(r).toLowerCase().trim();if(!t)return'\u274C `group` must be a non-empty name (e.g. "backend", "security")';const c=await _(i),l=await c.hgetall(B(i)).catch(()=>({}));if(Object.values(l).includes("admin")){const s=a?await W(c,i,a):null;if(!q(s,"admin"))return"\u274C **Permission denied** \u2014 only an `admin` can manage group scopes. Ask an admin to set `assigned_by`."}return o==="remove"?(await c.srem(Y(i,t),e.toLowerCase()),`\u{1F513} **${e}** removed from group \`${t}\`.`):(await c.sadd(Z(i),t),await c.sadd(Y(i,t),e.toLowerCase()),await c.expire(Z(i),365*2*86400),await c.expire(Y(i,t),365*2*86400),[`\u{1F510} **Scope granted** \u2014 **${e}** is now in group \`${t}\`.`,"",`Lessons stored with \`group="${t}"\` will surface in recall for ${e} (and other \`${t}\` members + admins) only.`,"",`Store a scoped lesson: \`learn_from_attempts(topic="...", group="${t}", ...)\``].join(`
184
- `))}case"team_scopes":{const{instance_id:i,handle:e}=y;if(!i)return"\u274C Required: instance_id";const r=await _(i);if(e){const t=await je(r,i,e);return t.size===0?`\u{1F441}\uFE0F **${e}** is in no groups \u2014 sees only team-wide + public lessons.`:`\u{1F510} **${e}** belongs to: ${[...t].map(c=>`\`${c}\``).join(", ")}`}const o=await r.smembers(Z(i)).catch(()=>[]);if(o.length===0)return["## \u{1F510} Team Scopes","","No groups defined yet. Create one by adding a member:",`\`team_grant_scope(instance_id="${i}", handle="alice", group="backend")\``].join(`
184
+ `))}case"team_scopes":{const{instance_id:i,handle:e}=y;if(!i)return"\u274C Required: instance_id";const r=await _(i);if(e){const t=await Oe(r,i,e);return t.size===0?`\u{1F441}\uFE0F **${e}** is in no groups \u2014 sees only team-wide + public lessons.`:`\u{1F510} **${e}** belongs to: ${[...t].map(c=>`\`${c}\``).join(", ")}`}const o=await r.smembers(Z(i)).catch(()=>[]);if(o.length===0)return["## \u{1F510} Team Scopes","","No groups defined yet. Create one by adding a member:",`\`team_grant_scope(instance_id="${i}", handle="alice", group="backend")\``].join(`
185
185
  `);const a=[`## \u{1F510} Team Scopes (${o.length} group${o.length!==1?"s":""})`,""];for(const t of o.sort()){const c=await r.smembers(Y(i,t)).catch(()=>[]);a.push(`- **\`${t}\`** (${c.length}): ${c.length?c.map(l=>`**${l}**`).join(", "):"_empty_"}`)}return a.push("",'_Scope a lesson to a group with `learn_from_attempts(group="...")`. Members + admins see it; others don\'t._'),a.join(`
186
- `)}default:return null}}export{Z as GROUPS_INDEX_KEY,Y as GROUP_KEY,Se as PROPOSED_LESSON_PREFIX,ke as REVIEW_MODE_KEY,B as ROLES_KEY,U as ROLE_BADGE,Be as TEAM_TOOL_NAMES,ce as auditLog,W as getRole,je as getScopes,Ue as handleTeamTool,q as hasPermission,Fe as lessonVisibleToScope,be as rbacActive,se as requireRole,Ae as reviewModeEnabled,$e as roleToReviewLevel,Le as storeLessonProposal};
186
+ `)}default:return null}}export{Z as GROUPS_INDEX_KEY,Y as GROUP_KEY,xe as PROPOSED_LESSON_PREFIX,Se as REVIEW_MODE_KEY,B as ROLES_KEY,U as ROLE_BADGE,Ue as TEAM_TOOL_NAMES,ce as auditLog,W as getRole,Oe as getScopes,Ke as handleTeamTool,q as hasPermission,Je as lessonVisibleToScope,ke as rbacActive,se as requireRole,Le as reviewModeEnabled,be as roleToReviewLevel,je as storeLessonProposal};