@cachly-dev/mcp-server 0.10.225 → 0.10.227

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,77 +1,72 @@
1
- import{gitLesen as Pe,istCommitHash as hs,pruefeOrdner as et}from"../git-ohne-shell.js";import{createRequire as bt}from"node:module";import{calculateConfidence as $t,confidenceBadge as us,STRUCTURED_TEMPLATES as ds,CONFIDENCE_WARN_VALUE as fs}from"../confidence.js";import{kurzfassenHinweis as ps}from"../antwort-hinweis.js";import{pruefeMeldung as ms,wendeAn as gs}from"../pruefung-melden.js";import{ckgSlug as _e,extractProblemConcept as tt,ckgUpsertNode as je,ckgUpdateEdge as ve,ckgUpsertPersonNode as ws,ckgUpsertFileNode as kt,ckgRecordCollaboration as ys,ckgUpsertServiceNode as _s}from"../ckg.js";import{safeJsonParse as O,scanKeys as Ee,leseOderNull as Le,darfHeraus as We}from"../utils.js";import{ersparteMinuten as vt,istStarterLektion as bs,zaehltAlsErsparnis as St}from"../wertbeitrag.js";import{lessonPreviewLines as Ye,trimTo as $s}from"../lesson-preview.js";import{normalisiereZeit as st,zaehleZeitangaben as nt}from"../zeit-normalisieren.js";import{abstentionSatz as ks,alterssatzFuerBelegte as vs,beurteileTreffer as Ss,istBelegt as Cs}from"../abstention.js";import{meldeEinmal as ot,meldeUndVermerke as Ze,fehlerText as at,OHNE_VEKTOREN as xs,OHNE_DIENST as Ts,OHNE_FRAGEVEKTOR as Ns,SINNPFAD_ABBRUCH as Es,OHNE_SCHREIBVEKTOR as it}from"../aussetzer.js";import{versuchStart as As,neueKennung as Is,wendeZuteilungAn as Ms,schliesseVersuchAb as Rs}from"../versuch.js";function rt(D,M,j,ne=j){if(D.includes("lesson:best:")){const t=O(M,{});if(t&&typeof t=="object"&&(t.what_worked||t.what_failed||t.commands?.length))return Ye(t,{maxChars:j,indent:" "})}const d=$s(M,ne);return d?[d]:[]}const Ct=2e3,xt=400;function Tt(){return process.env.CACHLY_RECALL_NUR_LESEN==="1"}import{getRole as ct,ROLE_BADGE as Bs,getScopes as Nt,lessonVisibleToScope as Et,reviewModeEnabled as Ls,storeLessonProposal as Os}from"./team.js";import{keywordSearch as Xe,treffeUeberDateipfad as Ds,wortindexEntwerten as At,tokenize as lt,splitMultiQuery as zs,levenshtein as Ps,indexVocab as It,bestandHolen as js}from"../search.js";import{startetNochLeer as Mt,leeresBrainAntwort as Rt,startLageMitCommits as ht,sofortAntwortNeuesBrain as Ws,neuesBrainBriefing as Fs}from"../leeres-brain.js";import{rerankByQuality as Gs,qualityMultiplier as Hs,extractLessonQuality as Ks}from"../rerank.js";import{computeEmbedding as xe,hasEmbedProvider as Oe,EMBED_PROVIDER as Te}from"../embeddings.js";import{leserAktiv as Js,leserPunkte as Vs,mischeMitLeser as qs}from"../leser.js";import{repariereFelder as Us}from"../feldreparatur.js";import{VEKTOR_PRAEFIX as Bt,NAME_VEKTOR_PRAEFIX as ut,ZWEIT_VEKTOR_PRAEFIX as Fe,packe as De,textFuerVektor as Ge,textFuerNamensVektor as Lt,Vektorbestand as dt,VEKTOR_NACHTRAG as Ys,NAME_VEKTOR_NACHTRAG as ft,ZWEIT_VEKTOR_NACHTRAG as Qe}from"../bedeutung.js";import{schreibeEingaenge as Zs,Eingangsbestand as Xs}from"../eingaenge.js";import{ordneNachHausordnung as Qs,rangWerte as en}from"../ausgabe-ordnung.js";import{Seltenheitsbestand as tn}from"../seltenheitsbestand.js";import{spurLegen as sn}from"../spuren.js";import{schlageErsetzungVor as nn}from"../ersetzung-vorschlag.js";import{loeseGiltFuerAuf as Ot,stufeSucheNachProjektversion as on,versionsNotizen as an,versionsBanner as rn}from"../gueltigkeit-version.js";const He=Ys,cn=365;import{etikettenFuer as ln,lieferJournalSchluessel as Dt,JOURNAL_DECKEL as hn}from"../etiketten.js";import{klassifiziereFehlschlag as un,suchProtokollSchluessel as pt,trichterSchluessel as zt,SUCH_DECKEL as Pt}from"../recall-trichter.js";import{leiteEntscheidungAb as dn,entscheidungsZeile as jt}from"../entscheidung-ableiten.js";import{bewerteTopf as fn,spreizeImTopf as Wt,reichereAn as pn,inhaltsWoerter as Ft,grobStamm as mn,GEWICHTE as gn,knapperSiegSatz as wn}from"../rangfolge.js";import{pruefeWirkung as yn,werteAus as _n,lehrwert as bn,leiteAb as $n,belegeJeThema as kn}from"../wirkungsspur.js";import{merkeSuchlauf as mt}from"../suchstatistik.js";import{schreibeTopfNotiz as vn,topfProtokollAktiv as Sn}from"../topf-protokoll.js";import{SINN_TOPF as Cn,EINGANG_SCHWELLE as xn,EINGANG_SORTIER_GEWICHT as Tn,ZWEIT_MODELL as Ve,ZWEIT_GEWICHT as Nn,ZWEIT_MINDESTDECKUNG as Gt,NAHDUPLIKAT_SCHWELLE as En,LESER_TIEFE as Ht}from"../rangfolge-stellschrauben.js";import{ermittleQuelle as Kt,quelleZeile as An}from"../herkunft.js";import{doppelHinweis as In,leseDoppelKandidaten as Mn,mitFrist as Rn,pruefeDoppelung as Bn}from"../doppel-kandidat.js";import{merkeRecall as Ln,sitzungsKosten as On,vergissSitzungsKosten as Dn,kostenZeilen as zn}from"../sitzungs-kosten.js";const Jt=new WeakMap;function gt(D){let M=Jt.get(D);return M||(M={vektor:new dt,namen:new dt(6e4,ut),zweit:new dt(6e4,Fe),eingang:new Xs,seltenheit:new tn},Jt.set(D,M)),M}const Vt=Cn,Pn=xn,jn=Tn;import{upgradeNudge as Wn}from"../upgrade-nudge.js";import{recallTiefe as qt,verlangteTiefe as Fn,TIEFE_VOLL as Gn,TIEFE_VOLL_MEHRTHEMIG as Hn}from"../recall-tiefe.js";import{nutzungInWorten as Kn,nutzungsSchluessel as Jn,verdichte as Vn}from"../werkzeug-nutzung.js";import{inWorten as qn,lieferBild as Un,lieferSchluessel as Yn,trefferSchluessel as Zn}from"../geliefert.js";import{COAUTHOR_PREF_KEY as Xn,autorHinweis as Qn,coautorAus as eo,echterGitLeser as to,ermittleAutor as so,mitCoautor as no,nurMensch as Ut}from"../autor.js";import{TOOLS as oo}from"../tools.js";import{vorspannHinweis as ao}from"../vorspann.js";import{autorAbzeichen as io,fremdanteil as ro}from"../autor-abzeichen.js";import{cachlyUrl as co}from"../cachly-url.js";import{belegFuer as lo}from"../schreibbeleg.js";import{schwaerzeFelder as ho}from"../geheimnis-filter.js";import{VERDACHT_TAG as wt,mitVerdachtTag as uo,pruefeFelder as fo,verdachtsWarnung as po}from"../tuersteher.js";import{fuerEigenenSpeicher as ze,hinweise as Ke,istMarkiert as Yt,markiert as mo,markierungsWarnung as Zt,markierungsWarnungAusRohwert as Xt,zaehlHinweise as Qt}from"../schreibtor.js";import{istBlossesThema as es}from"../blosses-thema.js";import{zeitpunkt as ke}from"../zeitmessung.js";const qe=(()=>{try{const D=bt(import.meta.url);for(const M of["../../../package.json","../../package.json"])try{const j=D(M);if(j.name==="@cachly-dev/mcp-server"&&j.version)return j.version}catch{}}catch{}return"0.10.100"})(),go=()=>{try{return bt(import.meta.url)("../../../CACHLY_CAPABILITIES.json").mcp?.total_tools??null}catch{}return null},wo={"0.10.119":["\u{1F50D} **Explainable briefings \u2014 provenance in every warning**"," \u{1F4CE} `/briefing` warnings now carry `outcome`, `author`, `learned_at` and `matched_on` (the context tokens that triggered the warning) \u2014 editors can show WHY a fix hint fired, not just what it claims.",' \u{1F9F0} VS Code 0.12.0 uses this: severity + confidence in the toast, full lesson card with provenance, "Not helpful" per-file suppression, reviewable CLS auto-learns.'," \u{1F9F9} CI docs now describe exactly what ships: scan mode uses the REST `/scan` endpoint, the GitLab template covers learn/scan/confirm."],"0.10.115":["\u{1F527} **`brain_get_pref` + `brain_set_pref` \u2014 persistent Brain preferences**"," \u{1F4D6} `brain_get_pref` reads back any stored preference (omit key to list all)."," \u270F\uFE0F `brain_set_pref` saves a preference to Redis \u2014 survives restarts, scoped per Brain instance.",' \u{1F4A1} Known key: `auto_changelog` \u2014 set to "false" to silence the new-lessons digest at session_start.'," \u{1F4CA} 140 MCP tools"],"0.10.114":["\u{1F4CB} **`brain_changelog` \u2014 weekly knowledge digest in one call**"," \u{1F4C5} Generates a grouped Markdown changelog of lessons learned in the last N days."," \u{1F465} Grouped by topic category, annotated with author, recall count, and confidence \u2014 paste directly into standup or Slack."," \u2699\uFE0F Params: `days` (default 7), `max_lessons` (default 30), `include_failures` (default true)."," \u{1F4CA} 138 MCP tools"],"0.10.113":["\u{1F4C8} **Week-over-week savings trend in `cache_stats`**"," \u{1F4C5} `cache_stats` now shows **7-day hits** + **WoW trend %** (e.g. `+23.0% vs prev week`) so ROI momentum is visible, not just the cumulative total."," \u2B07\uFE0F Goes red (\u{1F4C9}) on drops, green (\u{1F4C8}) on growth \u2014 first week always shows +100% (new)."," \u{1F527} Backed by new `CountHitsInWindow` repo method + `hits_last_7d`, `hits_prev_7d`, `week_over_week_pct` fields in `SavingsEstimate`."," \u{1F4CA} 137 MCP tools"],"0.10.112":["\u2699\uFE0F **Configurable cost_per_call_usd \u2014 accurate ROI for any LLM**"," \u{1F4A1} New `set_cost_per_call` tool: set your real per-call cost (claude-opus-4.8 \u2192 $0.02, gpt-5.5 \u2192 $0.015, claude-sonnet-4.6 \u2192 $0.009, claude-haiku-4.5 \u2192 $0.001) so savings estimates in `cache_stats` reflect your actual bill."," \u{1F527} New API: `PUT /api/v1/instances/:id/cost-per-call` \u2014 persisted per instance, used by `cache_stats`, org savings, and SSE analytics stream."," \u{1F4CA} 137 MCP tools"],"0.10.111":["\u{1F3E2} **Org-wide ROI + GitLab CI \u2014 the CFO number, aggregated**"," \u{1F4B0} `cache_org_stats` now shows **org-wide Tokenmaxxing ROI**: total savings summed across every instance in your org, monthly projection, top-5 instances by savings. One number for the whole team."," \u{1F50C} Backed by new API endpoint `GET /api/v1/orgs/:id/savings` (org members only)"," \u{1F98A} GitLab CI integration: `brain-from-ci-gitlab.yml` \u2014 two `.post` jobs (on_success/on_failure), same helper script, pipeline never fails because of Brain push"," \u{1F6E0}\uFE0F GitHub Action template fixed: `secrets.*` in job-level `if` is invalid \u2014 guard removed (helper script skips cleanly on missing secrets)"," \u{1F4CA} 136 MCP tools"],"0.10.110":["\u{1F3E2} **Zero friction: ROI-Projektor, Org-Shared-Cache, CI ready-to-paste**"," \u{1F4C8} `cache_stats` \u2014 zero-hit projection calculator: shows expected monthly savings for 5/10/20-dev teams before a single cache hit lands. ROI visible from day 1."," \u{1F3E2} New `cache_org_stats` \u2014 shared org-level cache stats across multiple instances. Convention-based (`org:{id}:sem`), zero API changes needed."," \u{1F517} `cache_set`/`cache_get` now accept optional `org_id` \u2014 write to personal + org namespace, fall back to org on miss. Fully backwards-compatible."," \u{1F4CB} Copy-paste CI integration: `brain-from-ci-action.yml` \u2192 Brain self-calibrates confidence on every GitHub Actions run. No config beyond two repo secrets."," \u{1F4CA} 136 MCP tools"],"0.10.109":["\u{1F4B0} **Tokenmaxxing ROI + Closed-loop CI learning**"," \u{1F4CA} `cache_stats` now shows a **Tokenmaxxing ROI** section \u2014 total tokens saved, monthly cost projection, and top 3 most-cached prompts. Finally a number you can show the CFO."," \u{1F504} New `brain_from_ci` \u2014 bulk-ingest CI history into the Brain (red\u2192green = learned fix, green\u2192red = CKG causal edge). The brain_from_git equivalent for CI. Bootstrap the Brain from your CI logs in one call."," \u{1F4CA} 134 MCP tools"],"0.10.108":["\u{1F9E0} **Focused briefing + trustworthy autopilot**"," \u{1F3AF} New `session_start_summary` \u2014 top-N focused briefing by topic. Scores lessons by relevance \xD7 confidence + recall_count + severity + recency. Use it for a quick pre-brief without loading the full Brain.",' \u{1F91D} Autopilot template now explains the data flow upfront ("your own Brain, EU-hosted, no third parties") \u2014 no more friction with cautious AI assistants during onboarding'," \u{1F4CA} 132 MCP tools"],"0.10.107":["\u{1F3AF} **Recall@3 gap closed \u2014 the moat proof got sharper (the head-to-head that matters)**"," \u{1F50D} External-corpus Recall@3 **82.1% \u2192 98.2%** \xB7 Precision@1 **80.4%** \xB7 MRR **89.0%** \u2014 now ahead of flat-file memory on every metric"," \u{1F9EC} Root-cause fix: documents are no longer indexed with cross-lingual synonyms (the symmetric-expansion bug inflated any doc containing a common word ~28\xD7, burying topic-specific lessons). Queries still expand \u2014 a \u65E5\u672C\u8A9E query still finds English lessons."," \u2696\uFE0F Reranker now compresses BM25 with score^0.3 before applying lesson quality, so a proven success can overtake a text-similar failed attempt even when the distractor scores 5\u20136\xD7 higher in raw BM25"," \u{1F6E1}\uFE0F CI bench gate widened to both home + external corpora with committed floors (`npm run bench:gate`) \u2014 recall quality can never silently regress again"," \u{1F4CA} 121 MCP tools"],"0.10.98":['\u{1F310} **Model-Neutrality as Feature (W9) \u2014 "Bring your own model, keep your brain."**'," \u{1F50C} New `brain_portability()` \u2014 shows your Brain ID, all compatible MCP clients, and ready-to-paste config snippets for each; proves the same Brain works in Claude, Cursor, Copilot, Windsurf, Cline, Zed and more"," \u{1F9E0} Your lessons, crystals, and predictions are model-agnostic \u2014 no vendor lock-in at the memory layer",' \u{1F4D6} New docs: cachly.dev/docs/model-neutral \u2014 "Same Brain, Any Model" guide with live config examples'," \u{1F4CA} 121 MCP tools (new: `brain_portability`)"],"0.10.97":['\u{1F4A0} **Team Crystallize (W8) \u2014 the team-wide, causal counter to per-user "Dreaming"**'," \u{1F9EC} New `team_crystallize()` \u2014 surfaces fixes that 2+ teammates *independently converged on*, the one thing a single-user memory structurally can't build",' \u{1F9E9} Cross-person clustering: groups structurally similar problems across people & namespaces (e.g. "pool exhaustion" solved the same way in payments, auth & db)',' \u{1F465} Each pattern names who converged \u2014 instant "ask @alice and @bob, they both hit this"'," \u{1F48E} Shows up in `crystal_view` alongside your per-brain crystal"," \u{1F4CA} 120 MCP tools (new: `team_crystallize`)"],"0.10.96":["\u{1F510} **Roles & Team-Scopes now ENFORCED (W6) \u2014 the enterprise governance layer**"," \u{1F6E1}\uFE0F `team_confirm` is now reviewer-gated, `team_learn` blocks viewers (recall-only) \u2014 roles stop being cosmetic the moment an admin is assigned"," \u{1F4DC} New `team_audit` tool \u2014 immutable, admin-only trail of every role change & lesson confirmation, for compliance & security reviews"," \u{1F513} Zero impact on open teams: governance activates only once you bootstrap an admin; before that everything works exactly as before"," \u{1F4CA} 119 MCP tools (new: `team_audit`)"],"0.10.95":["\u{1F9E0} **Domain Brain Marketplace (W10) \u2014 bootstrap your Brain with curated community knowledge**"," \u{1F6D2} `brain_marketplace()` \u2014 browse installable packs of high-trust lessons by domain: \u2638\uFE0F Kubernetes, \u{1F510} Auth, \u{1F5C4}\uFE0F Database, \u269B\uFE0F React, \u{1F4B3} Payments & more",' \u{1F4E6} `brain_install(slug="k8s")` \u2014 merges a domain\'s verified lessons into your Brain; live in `smart_recall` instantly, even offline',' \u{1F6E1}\uFE0F Idempotent + non-destructive: installed lessons are tagged `source:"marketplace:<slug>"` and NEVER override your own'," \u{1F50C} New gRPC `Subscribe` RPC \u2014 agents get new lessons streamed live the moment they're learned (completes the M2M loop with `Learn`)"," \u{1F4CA} 118 MCP tools (new: `brain_marketplace`, `brain_install`)"],"0.10.94":['\u{1F91D} **Person\u2194Person Collaboration Graph (W5) \u2014 "Frag X und Y, die haben das zusammen gel\xF6st"**'," \u{1F578}\uFE0F New `brain_collab_pairs()` tool: shows every contributor pair who've touched the same files or recalled each other's lessons \u2014 ideal for onboarding and bus-factor analysis"," \u{1F4E1} `smart_recall` now wires CKG `collaborates` edges live when cross-author reuse fires \u2014 collaboration graph grows with every team recall"," \u26A0\uFE0F Bus-factor section: flags solo contributors whose knowledge no teammate has yet recalled"," \u{1F4CA} 116 MCP tools (new: `brain_collab_pairs`)"],"0.10.93":['\u{1F5FA}\uFE0F **`brain_plan` \u2014 your Brain goes from "what did we learn?" to "what should I do?"**',` \u{1F9ED} Give it a task you're about to start ("upgrade Postgres 14\u219216") and it returns an ordered, grounded action plan: ranked failure modes to avoid, proven fix steps (dependency-aware, with commands), and a pre-flight checklist \u2014 all from your own lessons`,' \u{1F52C} Generative layer on the CKG: where `brain_predict` answers "what might fail?", `brain_plan` answers "what, in what order?"'," \u{1F4CA} 115 MCP tools (new: `brain_plan`)"],"0.10.92":["\u{1F44B} **Welcome-back digest \u2014 re-entry after a week away now leads with what changed**"," \u{1F331} `session_start` detects a gap \u2265 7 days and surfaces how many lessons your Brain gained while you were gone (git, CI & teammates kept it learning), with the freshest topics"," \u{1F9E0} Zero extra latency \u2014 the digest is computed from data session_start already loads"," \u{1F50C} New gRPC `Learn` RPC (M2M write path) \u2014 agents & CI can now teach the Brain directly, byte-compatible with the MCP lesson format"," \u{1F4CA} 115 MCP tools"],"0.10.91":["\u{1F550} **Lesson-anchored time attribution \u2014 every recall shows when it was learned and how much it saves**",` \u{1F4A1} "Brain saved you" banner now includes: when the lesson was learned (e.g. "12 May"), estimated original debug cost by severity (critical=2h, major=1h, minor=20min), and team attribution when a teammate's lesson fires`,' \u{1F4C5} Origin date formatted as "12 May" (same year) or "12 May 2024" (different year) \u2014 no clutter',' \u{1F465} Cross-author recalls now show "@author" on the same banner line \u2014 the "team knowledge reuse" value is front-and-center'," \u{1F4CA} 115 MCP tools"],"0.10.90":["\u{1F4CA} **External benchmark corpus v2 \u2014 the proof is now statistically solid**"," \u{1F9EA} Expanded from 8 queries \u2192 61 lessons / 56 queries across 10 real-world engineering domains (k8s, DB, auth, CI, frontend, API, payments, observability, network, Node.js, security, infra)"," \u2694\uFE0F 10 adversarial distractors added \u2014 failure lessons that share the exact same vocabulary as the query, forcing the quality reranker to earn its lift"," \u{1F4C8} Results on the larger corpus: cachly +2.9% Precision@1 vs flat-file, +12.5% vs raw BM25 baseline \u2014 on a harder dataset, not an easy one"," \u{1F4CA} 115 MCP tools"],"0.10.89":["\u26A1 **One-command onboarding \u2014 `autopilot` is now the single canonical setup**"," \u{1F680} Every surface (README, docs, emails, dashboard, blog) now leads with `npx @cachly-dev/mcp-server@latest autopilot` \u2014 one command signs you in, configures every editor, and bootstraps from git",' \u{1F9F9} Retired the split-brain instructions (legacy `@cachly-dev/init` wrapper + the old "three steps") so there\'s exactly one thing to copy-paste'," \u{1F527} Fixed a build-breaking duplicate `export` on buildClaudeMdBlock that failed CI on v0.10.88"," \u{1F522} Version hygiene \u2014 package.json, server.json, lockfile and MCP_VERSION resynced (server.json/lockfile were stuck at 0.10.85)"," \u{1F4CA} 115 MCP tools"],"0.10.88":["\u2705 **Honest setup + tested config writer (v0.10.88)**",' \u{1F512} setup/autopilot now report the truth: if no editor config could be written it exits non-zero with the exact failures instead of falsely claiming "Brain is ready"'," \u{1F4CA} New setup_config_write_failed telemetry \u2014 permission/path failures that block activation are now visible in reporting"," \u{1F9EA} 24 new unit tests for the config writer (buildServerEnv, buildMcpConfig, mergeMcpConfig, buildClaudeMdBlock) \u2014 the merge logic that must never clobber your other MCP servers is now regression-guarded"," \u{1F4CA} 115 MCP tools"],"0.10.87":["\u{1F310} **Multi-editor auth persistence + full activation telemetry (v0.10.87)**"," \u2705 JWT + instance_id now persisted to Cursor (`~/.cursor/mcp.json`) and Windsurf configs on auth \u2014 no more infinite re-auth for non-Claude-Code users",' \u{1F4E1} `device_flow_failed(reason="timeout")` now fires when sign-in window expires \u2014 blind spot in the funnel is gone'," \u{1F4E1} `auto_provision_failed` now fires on network errors too (was only HTTP non-2xx)"," \u{1F4E1} `brain_from_git_failed` fires with error reason when auto-bootstrap throws \u2014 no more empty brain with no explanation",' \u2728 Git bootstrap shows lesson count in session_start: "Brain bootstrapped \u2014 23 lessons loaded" \u2014 the WOW moment'," \u{1F527} API: McpEvent stores brain quality metrics (fixes/features/refactors/total) \u2014 git learning quality now measurable"," \u{1F4CA} 115 MCP tools"],"0.10.86":["\u{1F6E1}\uFE0F **Zero-friction activation \u2014 your Brain self-heals into existence**"," \u{1F527} Auto-provision now runs on *any* tool call, not just at sign-in \u2014 if instance creation ever fails once, the next call recovers it automatically"," \u{1F91D} Parallel tool calls on startup are coalesced into one resolve (no more duplicate-instance races)",' \u{1F4AC} Clearer "instance still starting" message \u2014 leads with "just retry", no manual config needed'," \u{1F4C8} New activation-funnel telemetry: install \u2192 sign-in \u2192 provision, so silent churn is visible"," \u{1F4CA} 115 MCP tools"],"0.10.85":["\u{1F680} **One-command onboarding \u2014 `npx @cachly-dev/mcp-server@latest autopilot`**"," \u26A1 Single command does it all: auth \u2192 instance \u2192 every editor config \u2192 CLAUDE.md \u2192 Brain bootstrap \u2192 health"," \u{1F331} Seeds the 16-lesson starter corpus when git history is empty \u2014 your first smart_recall hits instantly"," \u{1F916} Fully automatic (zero prompts); `setup` remains for interactive, pick-your-editors onboarding"],"0.10.84":["\u{1F916} **M2M & agent-ecosystem reach \u2014 cachly for every caller, human or machine**"," \u{1F511} OAuth2 `client_credentials` grant \u2014 set CACHLY_CLIENT_ID + CACHLY_CLIENT_SECRET for fully headless auth (CI, agents, AI-to-AI)"," \u{1F5C2}\uFE0F `npx ... tool-specs --format=openai|anthropic|langchain` \u2014 export all 121 tools in any framework's dialect"," \u{1F310} `npx ... openapi` \u2014 OpenAPI 3.1 doc (1 POST path per tool) for Assistants / codegen / Postman"," \u{1F9EA} 14 new tests: client_credentials helpers, all four spec dialects, OpenAPI required-body inference"],"0.10.83":["\u{1F9E0} **Brain Viz \u2014 3D graph export**"," \u{1F310} `brain_graph(instance_id)` \u2014 exports the Causal Knowledge Graph as a render-ready {nodes, links} payload"," \u{1F3A8} Schema `cachly.brain_graph/v1`: node kinds (concept/person/file/service) with color groups + size, edges with confidence"," \u{1F5A5}\uFE0F Consumed verbatim by the 3D frontend (react-force-graph-3d / three.js) \u2014 the visual brain map",' \u{1F50E} Filters: `domain`, `min_confidence`, `max_nodes` (with truncation flag), `format="summary"`'," \u{1F9EA} 10 tests: kind detection, dangling-edge pruning, confidence/domain filters, schema contract"," \u{1F4CA} 115 MCP tools"],"0.10.82":["\u{1F4E1} **Telemetry API contracts \u2014 dashboard ingest pipeline prepared**"," \u{1F5C2}\uFE0F `src/telemetry-types.ts` \u2014 canonical event types + resolved event format (TelemetryEventRaw / TelemetryEventResolved)"," \u{1F510} `user_fingerprint` \u2014 non-reversible JWT sub hash, PII-free user clustering for dashboard"," \u{1F4CA} `metrics` block on key events: smart_recall (hit/topic), brain_from_git (fixes/features/total), brain_seed_starter (seeded_count)"," \u{1F6A6} Internal API contract documented: GET /internal/telemetry/stream \u2192 TelemetryEventResolved[]"],"0.10.81":["\u23F1\uFE0F **Onboarding-Bench \u2014 time-to-first-recall, now measured**"," \u{1F4D0} `npm run bench:onboarding` \u2014 cold (empty) vs seeded (starter) first-query hit rate via the real search engine"," \u{1F4CA} Result: first-query hit@1 **0% \u2192 87.5%**, hit@3 **0% \u2192 100%**, MRR **0% \u2192 93.8%**"," \u{1F6E1}\uFE0F CI-defended in `onboarding-bench.test.ts` (cold must answer 0%, seeded thresholds enforced)"," \u{1F4CB} `PLAN-DASHBOARD.md` \u2014 full build spec for the external Team-Knowledge-Reuse dashboard (separate repo)"],"0.10.80":["\u{1F331} **Starter corpus \u2014 first recall in seconds, not sessions**"," \u{1F4DA} `brain_seed_starter(instance_id)` \u2014 seed 16 curated universal lessons (Docker cache, JWT skew, K8s OOM, N+1, cache stampede\u2026)"," \u{1F680} Auto-seeds on first session when git history is empty/shallow \u2014 your first `smart_recall` returns a real hit",' \u{1F6E1}\uFE0F Tagged `source:"starter"`, idempotent, never overrides your own lessons'," \u23F1\uFE0F Directly attacks time-to-first-recall (<2 min goal); stamps `born_at` on seed"," \u{1F9EA} 18 new tests: seeding, idempotency, topic_filter, force, user-lesson protection"," \u{1F4CA} 113 MCP tools"],"0.10.79":["\u{1F310} **Brain Marketplace \u2014 full shareable Brain lifecycle**"," \u{1F4CB} `brain_share_list(instance_id)` \u2014 see all your public shares with lesson counts + share IDs"," \u{1F5D1}\uFE0F `brain_unshare(share_id)` \u2014 revoke a share; link goes dead immediately"," \u{1F50D} `brain_discover(query, topic)` \u2014 search public Brains in the cachly marketplace"," \u{1F4E4} `publish` CLI \u2014 fancy card + share URL; `--public` for discoverable; `--title` custom name"," \u{1F9EA} stress tests: team_expertise_map with 50 contributors, chain import/unshare, discover fallback"," \u{1F4CA} 112 MCP tools"],"0.10.78":["\u{1F310} **Phase 3: Shareable / Public Brains**",' \u{1F4E4} `brain_share(instance_id="...")` \u2014 export a Brain snapshot as a shareable link (public or unlisted)',' \u{1F4E5} `brain_import(instance_id="...", share_id="...")` \u2014 import any public Brain into yours (1-line)'," \u{1F50D} Optional topic_filter, min_confidence, topic_prefix \u2014 precise control over what gets shared/imported"," \u{1F512} dry_run mode on both tools \u2014 preview before committing"," \u{1F9EA} Regression suite added: share handler, buildServerEnv, stdout cleanness"," \u{1F4CA} 109 MCP tools"],"0.10.77":["\u{1FA79} **Frictionless onboarding + first-class self-hosting/BYOK**"," \u{1F680} Editor-launched server now serves all tools even before sign-in \u2014 zero-credential device flow finally triggers on first tool call"," \u{1F512} No more stdout pollution: stdio JSON-RPC stream stays clean when no JWT is set"," \u{1F3E0} `setup --api-url` / `init --api-url` point at a self-hosted backend; URL baked into config only when non-default",' \u{1F9EC} `health` now shows your BYOK embedding provider + accepts cky_ long-lived keys (was a false "invalid token" failure)'],"0.10.76":["\u{1F4CA} **Agent-trace benchmark + editor matrix + setup timer**"," \u{1F4C2} New `agent-traces-corpus.json` \u2014 22 lessons, 15 queries, realistic AI-agent debug patterns"," \u{1F3C6} Benchmark result: **P@1 +66.7%, MRR +9.5% vs flat-file** (adversarial failure-distractors per category)"," \u{1F4CB} README: first-class editor support matrix (Claude Code, Cursor, Windsurf, VSCode, Cline, Continue.dev, Zed)",' \u23F1\uFE0F setup now shows elapsed time + "Brain is ready" banner at the end'],"0.10.75":["\u{1FA79} **Setup hardened \u2014 works from the VSCode plugin & any non-TTY terminal**"," \u{1F50C} `setup` no longer hangs on a non-interactive stdin (VSCode tasks, CI) \u2014 auto-detects + runs in automatic mode"," \u{1F510} Device-flow now uses the API proxy first (the old direct-Keycloak call returned 403) \u2014 sign-in actually completes"," \u{1F310} When auto sign-in is unavailable, the browser opens with a step-by-step API-key guide instead of a silent paste prompt"," \u{1F4CA} Funnel fix: `device_flow_completed` / `device_flow_failed(reason)` now emitted from setup too"],"0.10.73":["\u{1F510} **Team visibility scopes + one-command init + external bench**",' \u{1F195} `team_grant_scope` / `team_scopes` \u2014 scope lessons to a sub-team (group="security"); only members + admins recall them',' \u{1F3F7}\uFE0F `learn_from_attempts(group="...")` \u2014 orthogonal to private; smart_recall enforces it per requester'," \u26A1 `init` is now zero-arg + idempotent (reuses saved creds, only writes what changed, <60s)"," \u{1F4E6} `npm run bench:external` \u2014 prove recall-lift on a third-party-labeled corpus (portable JSON format)"," \u{1F4CA} 107 MCP tools"],"0.10.72":["\u{1F451} **Role model \u2014 admin \xB7 reviewer \xB7 contributor \xB7 viewer (Phase 3)**"," \u{1F195} `team_assign_role` \u2014 establish governance; first call bootstraps, admins manage the rest"," \u{1F195} `team_whoami` \u2014 see your own role and capabilities"," \u{1F195} `team_roster` \u2014 full team role table (\u{1F451}\u{1F6E1}\uFE0F\u270F\uFE0F\u{1F441}\uFE0F)"," \u{1F6E1}\uFE0F `team_confirm` is now role-aware \u2014 admin/reviewer auto-get senior weight; contributor gets peer; no self-promotion"," \u{1F451} Setup CLI now prompts for governance bootstrap (idempotent). 105 MCP tools"],"0.10.71":['\u{1F6E1}\uFE0F **Self-healing auth** \u2014 no more silent "0 recalls because the token quietly died"'," \u267B\uFE0F Near-expiry tokens auto-refresh into a long-lived key while still valid (zero interaction)"," \u{1F501} A rejected (401) call self-heals once and retries before surfacing an error"," \u{1F4E3} If the credential is truly dead, session_start + get_api_status say so plainly + how to fix"],"0.10.70":["\u{1F6F0}\uFE0F **Service/System nodes in the graph (Phase 3)** \u2014 the brain now models running systems",' \u{1F195} `brain_service_map(service="prometheus")` \u2014 who owns it + every failure/fix known',' \u{1F3F7}\uFE0F Tag lessons with `service="..."` (`service_kind="system"` for infra) \u2014 person\u2192operates, file\u2192runs_in edges',` \u{1F6A8} Incident triage: a restarting pod \u2192 instant "who knows this and what's broken before"`," \u{1F4CA} 102 MCP tools"],"0.10.69":["\u{1F4C1} **Personalized context-aware recall (Phase 3)** \u2014 pass `context_files` to `smart_recall`"," \u{1F3AF} Lessons learned on your current files bubble up \u2014 even when the query doesn't name the file"," \u{1F3F7}\uFE0F `\u{1F4C1} context match` badge on every file-boosted result so you know WHY it ranked up"," \u2705 Works alongside keyword, semantic, CKG, and governance signals \u2014 a 6th ranking dimension"],"0.10.68":["\u{1F91D} **Collaboration graph (Phase 3)** \u2014 person\u2194person edges from shared files"," \u{1F465} `brain_who_knows` now shows who the top expert frequently works with",' \u{1F68C} Bus-factor insight: "ask X and Y together \u2014 they solved this on shared files"'," \u{1F6E1}\uFE0F Stability: every network call in the agent hot path is now timeout-bounded"],"0.10.64":["\u{1F4C8} **The three decisive metrics, now measurable**"," \u23F1\uFE0F `brain_metrics()` \u2014 time-to-first-recall, recall-lift, team-knowledge-reuse in one view"," \u{1F465} Cross-author reuse tracked: recall a teammate's lesson \u2192 counted + surfaced inline"," \u{1F6E1}\uFE0F Stability: scans now capped + timed out (a huge keyspace can't hang the agent)"],"0.10.61":["\u{1F3AF} **Phase 3C: 100 MCP tools milestone \u2014 zero-setup knowledge graph**"," \u{1F504} `brain_from_git` now auto-builds Person+File nodes from git history (zero setup!)"," \u{1F50D} `skill_gaps()` \u2014 find domains with unresolved failures, missing attribution"," \u{1F4CA} `brain_coverage()` \u2014 scored 0-100 health report: lessons \xB7 attribution \xB7 file coverage"],"0.10.60":["\u{1F195} **Phase 3B: File map + team overview + visibility**"," \u{1F4C1} `brain_file_map(file_paths=[...])` \u2014 experts + lessons per file before you touch it"," \u{1F5FA}\uFE0F `team_expertise_map()` \u2014 full team skills matrix in one table",' \u{1F512} `visibility: "private"` on `learn_from_attempts` \u2014 private notes never leak to smart_recall'," \u{1F4CA} 98 MCP tools"],"0.10.59":["\u{1F195} **Phase 3A: Org-wide knowledge graph**",' \u{1F465} `brain_who_knows(topic="...")` \u2014 find your team\'s experts on any topic instantly',' \u{1F578}\uFE0F Person + File nodes auto-built from `learn_from_attempts(author="...", file_paths=[...])`'," \u{1F464} Author attribution now shown inline in `smart_recall` results"," \u{1F4CA} 96 MCP tools \xB7 386 tests green"],"0.10.58":["\u{1F9F9} Zero lint warnings \u2014 all unused imports cleaned across every handler","\u2705 Build: 0 errors, 0 warnings \xB7 379 tests green"],"0.10.57":[`\u{1F195} **What's new in v${qe}:**`," \u{1F527} **Critical fix** \u2014 corrected the npm bin/main entry path (`dist/src/index.js`); 0.10.50\u20130.10.52 shipped a broken entry point"," \u{1F527} Version hygiene \u2014 synced package.json, server.json, lockfile, MCP_VERSION; `latest` now points forward again"," \u2705 Accurate tool count (95) across README, server.json, CLI banners"," \u2705 Includes all Phase 2/3 work: quality reranking, Cachly-Bench, hybrid + CKG recall, team_confirm governance"],"0.10.52":[" \u2705 `smart_recall` \u2014 CKG traversal as Layer 3: finds lessons that FIXED causal-graph-similar problems (\u{1F578}\uFE0F causal graph badge)"," \u2705 Bench corpus expanded: governance adversarial pair proves review-boost end-to-end (+22% P@1 headline)"," \u{1F527} Import cleanup: unused symbols removed from brain.ts (no functional change)"," \u{1F4A1} `smart_recall` now shows keyword / semantic / CKG / hybrid match type for every result"],"0.10.51":[" \u2705 `team_confirm` \u2014 human review raises lesson recall ranking (\u{1F6E1}\uFE0F senior / \u2714\uFE0F peer)"," \u2705 Governance-aware recall \u2014 confirmed lessons outrank unreviewed auto-learned entries"],"0.10.50":[" \u2705 `smart_recall` \u2014 unified keyword + semantic hybrid list (one ranked result, not two sections)",' \u2705 `smart_recall` \u2014 "Brain saved you here" banner surfaces time saved inline per proven lesson'," \u2705 Quality reranking \u2014 proven successes outrank symptom-dense failures (+11% Precision@1)"," \u2705 Cachly-Bench \u2014 IR benchmark with CI regression guard (see BENCH.md for the proof)"," \u{1F527} Contradiction resolution now persisted to Redis (90-day TTL)"," \u{1F4A1} Run `smart_recall` to see your Brain's recall quality in action"],"0.10.49":[" \u2705 `brain_from_git` \u2014 auto-seed your Brain from git history in seconds"," \u2705 `brain_predict_failures` \u2014 predict CI/build failures before they happen"," \u2705 VS Code extension \u2014 Brain status bar, ambient learning, CodeLens hints"," \u2705 Team Brain \u2014 share lessons across your whole engineering team"," \u{1F527} Stability \u2014 auto-expiring memory keys, longer provisioning wait, robust recall"]},va=new Set(["learn_from_attempts","recall_best_solution","smart_recall","recall_feedback","lesson_verified","session_start","session_start_summary","session_end","session_ping","session_handoff","auto_learn_session","brain_who_knows","brain_file_map","team_expertise_map","skill_gaps","brain_coverage","brain_metrics","brain_service_map","brain_collab_pairs","brain_portability","brain_changelog","brain_set_pref","brain_get_pref"]),yo=co("/billing","upgrade"),ts=new Map,ss=new Set,_o=1680*60*60;function bo(D=new Date){return`cachly:quota:recalls:${D.toISOString().slice(0,7)}`}async function yt(D){try{const M=bo();await D.incr(M)===1&&await D.expire(M,_o)}catch{}}function ns(D){return`cachly:stats:drei-ausgaenge:${D}`}async function _t(D,M,j){try{await D.hincrby(ns(M),j,1)}catch{}}function os(D,M,j,ne){yt(D),mt(D,j,ne);const d={ts:new Date().toISOString(),frage:j.slice(0,200),geliefert:[]};(async()=>{const t=pt(M);await D.lpush(t,JSON.stringify(d)),await D.ltrim(t,0,Pt-1)})().catch(()=>{}),_t(D,M,"schweigen")}async function as(D){const M={tage:0,rekord:0};try{const j=await D.get("cachly:streak:current"),ne=O(j,null),d=new Date().toISOString().slice(0,10);if(ne){const t=ne.last_date,h=new Date(Date.now()-864e5).toISOString().slice(0,10);t===d?(M.tage=ne.days,M.rekord=ne.record):t===h?(M.tage=ne.days+1,M.rekord=Math.max(M.tage,ne.record),await D.set("cachly:streak:current",JSON.stringify({days:M.tage,last_date:d,record:M.rekord}))):(M.tage=1,M.rekord=ne.record,await D.set("cachly:streak:current",JSON.stringify({days:1,last_date:d,record:M.rekord})))}else M.tage=1,M.rekord=1,await D.set("cachly:streak:current",JSON.stringify({days:1,last_date:d,record:1}))}catch{}return M}async function is(D,M,j){await D.set("cachly:session:current",JSON.stringify({started:new Date().toISOString(),focus:M,provider:j}),"EX",86400)}async function rs(D,M){const j=ts.get(D);if(j&&j.expiresAt>Date.now())return j.gate;try{const ne=await M(`/api/v1/instances/${D}/memory`),d=ne?.recall_limit??-1,t=ne?.total_recall_count??0,h={reached:d>0&&t>=d,limit:d,used:t,goodwillMessage:ne?.goodwill_message,notice:ne?.notice};return ts.set(D,{gate:h,expiresAt:Date.now()+6e4}),h}catch{return{reached:!1,limit:-1,used:0}}}const $o={smart_recall:"query",recall_best_solution:"topic",causal_trace:"problem"};async function Sa(D,M,j,ne){const d=await ko(D,M,j,ne),t=$o[D];if(t&&typeof d=="string")try{Ln(String(M.instance_id??""),String(M[t]??""),d.length)}catch{}return d}async function ko(D,M,j,ne){switch(D){case"learn_from_attempts":{const d=ho(M),t=fo(d.felder),h=t.length?{...d.felder,tags:uo(d.felder.tags)}:d.felder,{instance_id:b,topic:o,outcome:n,what_worked:E,what_failed:$="",context:i="",severity:l="major",file_paths:a=[],commands:s=[],tags:C=[],depends_on:r=[],author:c="",visibility:v="team",service:m="",service_kind:y="service",group:A="",grund:I="",ersetzt:e="",gilt_ab:H="",gilt_fuer:ae}=h,{what_worked:ce,what_failed:de="",context:le=""}=Us({what_worked:E,what_failed:$,context:i}),p=await j(b),W=new Date,Z=W.toISOString();let q=Ot(ae,void 0);const Q=H&&!Number.isNaN(Date.parse(String(H)))?new Date(String(H)):W,K=st(ce,Q),te=st(de,Q),u=st(le,Q),k=nt(ce,Q)+nt(de,Q)+nt(le,Q),x=so({uebergeben:c,umgebung:process.env.CACHLY_AUTHOR,git:to(process.env.CACHLY_WORKSPACE??process.cwd())});let T="";try{T=eo(await p.get(`cachly:prefs:${Xn}:${b}`))}catch{}const _=no(x.autor,T),S=Qn(x);if(v!=="private"&&await Ls(p,b))return Os(p,o,{topic:o,outcome:n,what_worked:K,what_failed:te,context:u,severity:l,file_paths:a,commands:s,tags:C,depends_on:r,...q.angaben.length?{gilt_fuer:q.angaben}:{},..._?{author:_}:{},source:Kt(),...m?{service:m}:{},...A?{group:String(A).toLowerCase().trim()}:{},visibility:v,recall_count:0,ts:Z,confidence:1,version:3});const N=o.split(":")[0],L=ds[N],J=[];if(L)for(const f of L.required)f==="commands"&&s.length===0&&J.push(`\u{1F4CB} ${L.hint}`);let ie=!1,ge=0,se,ye=[];const $e=await p.get(`cachly:lesson:best:${o}`),Ne=String(I??"").trim();if($e&&!Ne)return[`\u270B **Update rejected:** \`${o}\` already exists \u2014 an update needs the field \`grund\` (the reason).`,"","One line is enough: WHY was the previous version wrong or incomplete?",'Example: grund="The IP changed \u2014 the worker moved to 10.8.0.7"',"","The diff says WHAT changes. The grund line says why the old version was wrong \u2014","and that is the line someone reads two months from now (audit_trail, recall_best_solution)."].filter(Boolean).join(`
2
- `);if($e)try{const f=JSON.parse($e);q=Ot(ae,f.gilt_fuer),ge=f.recall_count??0,se=f.last_recalled_at,ye=f.audit_trail??[],ye.push({ts:Z,action:"updated",prev_outcome:f.outcome,grund:Ne}),ye.length>20&&(ye=ye.slice(-20)),ie=!0;const z=[];if(f.outcome==="success"&&n==="failure"?z.push("\u26A0\uFE0F **Contradiction detected!** Existing lesson has outcome: `success`, but you're storing `failure`.",'The existing "success" lesson will be preserved. Only the audit trail is updated.',"If you meant to mark this as failed permanently, store a new lesson with a distinct topic slug."):f.outcome==="failure"&&n==="success"&&z.push("\u2705 **Conflict resolved!** Previous lesson was `failure` \u2014 now overwriting with `success`."),z.length>0){ye[ye.length-1].action="contradiction-resolved";try{const re=_e(o),G=_e(`resolution:${o}`);await ve(p,re,"contradicts",G,!1)}catch{}try{const re=`cachly:contradictions:${o}`,G=f.outcome==="success"&&n==="failure"?"kept-success":"overwrote-failure";await p.rpush(re,JSON.stringify({ts:Z,prev_outcome:f.outcome,new_outcome:n,direction:G})),await p.ltrim(re,-50,-1),await p.expire(re,180*86400)}catch{}z.push(`\u{1F5F3}\uFE0F Run \`madc_deliberate(topic="${o}")\` to resolve via expert agent voting.`)}}catch{}else ye=[{ts:Z,action:"created"}];const Ae=[];if(n==="failure"){const f=[],z=p.scanStream({match:"cachly:lesson:best:*",count:100});await new Promise((G,P)=>{z.on("data",V=>f.push(...V)),z.on("end",G),z.on("error",P)});const re=o.split(/[:\-_]/).filter(G=>G.length>2);for(const G of f.slice(0,50)){const P=await p.get(G);if(P)try{const V=JSON.parse(P);if(V.outcome!=="success")continue;const pe=(V.topic??"").split(/[:\-_]/).filter(F=>F.length>2);if(re.filter(F=>pe.includes(F)).length>=1&&V.topic!==o){Ae.push(`\u26A0\uFE0F **"I Was Wrong"**: lesson \`${V.topic}\` (success, ${V.severity??"major"}) might have prevented this failure.`,` \u2192 Use \`recall_best_solution(topic="${V.topic}")\` before next attempt.`,` \u2192 To mark it critical: \`learn_from_attempts(topic="${V.topic}", ..., severity="critical")\``);break}}catch{}}}for(const f of r){const z=`cachly:dep:${f}`,re=await p.get(z),G=O(re,[]);G.includes(o)||G.push(o),await p.set(z,JSON.stringify(G),"EX",90*86400)}const we={topic:o,outcome:n,what_worked:K,what_failed:te,context:u,severity:l,file_paths:a,commands:s,tags:C,depends_on:r,..._?{author:_}:{},source:Kt(),...m?{service:m}:{},...A?{group:String(A).toLowerCase().trim()}:{},visibility:v,recall_count:ge,...se?{last_recalled_at:se}:{},...H&&!Number.isNaN(Date.parse(String(H)))?{gilt_ab:new Date(String(H)).toISOString()}:{},...q.angaben.length?{gilt_fuer:q.angaben}:{},ts:Z,verified_at:n==="success"||n==="partial"?Z:void 0,confidence:1,audit_trail:ye,version:3},fe=String(e??"").trim();let Se="",Y=null;fe===o&&fe?Se="\u26A0\uFE0F `ersetzt` points to the same topic \u2014 for a correction within one topic, an update with `grund` is enough. The link was ignored.":fe&&(Y=await p.get(`cachly:lesson:best:${fe}`),Y?O(Y,{}).ersetzt===o?(Y=null,Se=`\u26A0\uFE0F \`${fe}\` already replaces \`${o}\` \u2014 two lessons cannot replace each other. The link was NOT stored. If this version is the newer one: remove the link in \`${fe}\` first, then set it here.`):we.ersetzt=fe:Se=`\u26A0\uFE0F The lesson to replace, \`${fe}\`, does not exist \u2014 the link was NOT stored. \`smart_recall\` shows topic names.`);let Ie="";if(!fe)try{const f=V=>{const pe=O(V.content,{});return{topic:pe.topic??V.key.slice(19),what_worked:pe.what_worked}},[z,re]=await Promise.all([Xe(p,["cachly:lesson:best:*"],`${o} ${K}`.slice(0,300),5),Ds(p,["cachly:lesson:best:*"],a??[])]),G=new Set,P=[...z,...re].filter(V=>V.key.startsWith("cachly:lesson:best:")).filter(V=>!G.has(V.key)&&G.add(V.key)).map(f);Ie=nn(o,K,P)??""}catch{}if(n==="failure")try{const z=(await p.lrange(pt(b),0,199)).map(P=>O(P,{ts:"",frage:"",geliefert:[]})).filter(P=>P.ts!==""),re=(await Xe(p,["cachly:lesson:best:*"],`${o} ${te||K}`.slice(0,300),5)).filter(P=>P.key.startsWith("cachly:lesson:best:")).map(P=>{const V=O(P.content,{});return{topic:V.topic??P.key.slice(19),what_worked:V.what_worked}}),G=un(o,String(te||""),z,re);await p.hincrby(zt(b),G,1)}catch{}const Me=dn([K,te,i].filter(Boolean).join(" ").slice(0,2e3));Me&&(we.entscheidung=Me);const Be=lo(o,Z,K);we.beleg=Be;const Re=JSON.stringify(we);p.set(`cachly:stats:born_at:${b}`,Z,"EX",365*86400,"NX").catch(()=>{});const g=`cachly:lessons:${o}`;await p.rpush(g,Re),await p.ltrim(g,-100,-1),await p.expire(g,cn*86400);let w=!1;if(n==="success"||n==="partial"?(await p.set(`cachly:lesson:best:${o}`,Re),w=!0):$e||(await p.set(`cachly:lesson:best:${o}`,Re),w=!0),Y&&w)try{const f=JSON.parse(Y);f.ersetzt_durch=o,f.gilt_bis=Z,f.ersetzt_am=Z,Ne&&(f.ersetzt_grund=Ne.slice(0,200)),await p.set(`cachly:lesson:best:${fe}`,JSON.stringify(f))}catch{}w&&At();let B=()=>{};const R=new Promise(f=>{B=f});w&&Oe()||B(null),w&&Oe()&&(await p.sadd(He,o).catch(()=>{}),await p.sadd(ft,o).catch(()=>{}),(Te==="cachly"||Te==="ollama")&&await p.sadd(Qe,o).catch(()=>{}),(async()=>{const f=O(Re,{});try{const z=await xe(Ge(f),{geduld:"lang"});if(z?.length?(await p.set(`${Bt}${o}`,De(z)),await p.srem(He,o).catch(()=>{}),B(await Bn(p,{...f,topic:o},z,gt(p).vektor))):B(null),Te==="cachly"||Te==="ollama")try{const re=await xe(Ge(f),{geduld:"lang",modell:Ve});re?.length&&(await p.set(`${Fe}${o}`,De(re)),await p.srem(Qe,o).catch(()=>{}))}catch{}}catch(z){B(null),await p.sadd(He,o).catch(()=>{}),await Ze(p,it,`Volltext-Vektor nicht geschrieben (${Te}): ${at(z)} \u2014 die Lektion ist gespeichert, der Bedeutungsabgleich uebergeht sie.`)}try{const z=await xe(Lt(o),{geduld:"lang"});z?.length&&(await p.set(`${ut}${o}`,De(z)),await p.srem(ft,o).catch(()=>{}))}catch(z){await Ze(p,it,`Namens-Vektor nicht geschrieben (${Te}): ${at(z)} \u2014 siehe oben, die Lektion selbst ist gespeichert.`)}try{await Zs(p,o,f,z=>xe(z,{geduld:"lang"}))}catch(z){await Ze(p,it,`Eingaenge nicht geschrieben (${Te}): ${at(z)} \u2014 siehe oben, die Lektion selbst ist gespeichert.`)}await cs(p,o,3),B(null)})());try{const f="cachly:session:decision-log",z=JSON.stringify({ts:Z,topic:o,outcome:n,what_worked:K.slice(0,120)});await p.rpush(f,z),await p.ltrim(f,-50,-1)}catch{}let ee=null;try{const f=_e(o),z=o.split(":")[0]??"unknown";await je(p,f,z,z);for(const G of C){const P=_e(`tag:${G}`);await je(p,P,"tag","tag"),await ve(p,f,"co-occurs",P,n==="success",n==="partial")}for(const G of r){const P=_e(G);await ve(p,f,"requires",P,!0)}if((z==="fix"||z==="debug")&&(n==="success"||n==="partial")){const G=te||u||"",P=G?tt(G):null;if(P){const V=_e(`problem:${P}`);await je(p,V,"problem","problem"),await ve(p,f,"fixes",V,n==="success",n==="partial")}}if(n==="failure"&&(te||K)){const P=tt(te||K);if(P){const V=_e(`cause:${P}`);await je(p,V,"cause","cause"),await ve(p,f,"causes",V,!1)}}if(n==="failure"){const G=await p.smembers(`cachly:ckg:idx:from:${f}`);for(const P of G){const V=await p.get(P);if(!V)continue;const pe=O(V,null);if(pe&&pe.edgeType==="fixes"&&pe.confidence>.7&&pe.trials>=3){ee=`\u26A0\uFE0F **belief_conflict** on \`${o}\`: previously confirmed fix (confidence ${pe.confidence.toFixed(2)}, n=${pe.trials}) now reports failure. Both beliefs retained as \`contested\`. Use \`brain_resolve_conflict(instance_id="...", topic="${o}", winner="success"|"failure")\` to arbitrate, or \`ckg_inspect(concept="${f}")\` to review.`;const be=`cachly:ckg:conflict:${f}`;await p.set(be,JSON.stringify({topic:o,concept_id:f,detected_at:new Date().toISOString(),fix_confidence:pe.confidence,fix_trials:pe.trials,failure_outcome:n,reported_by:_||"unknown",what_failed:te||"",resolved:!1}),"EX",3600*24*90)}}}if(n==="success"&&(z==="fix"||z==="debug")){const G=await p.smembers(`cachly:ckg:idx:from:${f}`);for(const P of G.slice(0,10)){const V=await p.get(P);if(!V)continue;const pe=O(V,null);if(!pe||pe.edgeType!=="fixes")continue;const be=await p.smembers(`cachly:ckg:idx:from:${pe.to}`);for(const F of be.slice(0,5)){const me=await p.get(F);if(!me)continue;const ue=O(me,null);ue&&ue.edgeType==="co-occurs"&&(ue.successes=(ue.successes||0)+.1,ue.trials=(ue.trials||0)+.1,ue.confidence=(ue.successes+1)/(ue.trials+2),ue.last_updated=new Date().toISOString(),await p.set(F,JSON.stringify(ue)))}}}if(Math.random()<.01){const G=Date.now()-7776e6,P=await p.smembers(`cachly:ckg:idx:from:${f}`);for(const V of P){const pe=await p.get(V);if(!pe)continue;const be=O(pe,null);be&&be.trials<3&&be.last_updated&&new Date(be.last_updated).getTime()<G&&(be.confidence=be.confidence*.9,be.last_updated=new Date().toISOString(),await p.set(V,JSON.stringify(be)))}}}catch{}if(_){p.set(`cachly:agents:active:${_}`,JSON.stringify({author:_,last_topic:o,last_outcome:n,ts:new Date().toISOString()}),"EX",3600).catch(()=>{});try{const f=o.split(":")[0]??"unknown",z=await ws(p,Ut(_),f),re=_e(o);await ve(p,z,"authored",re,n==="success",n==="partial");for(const G of a.slice(0,8)){const P=await kt(p,G);await ve(p,z,"touched",P,!0),await ys(p,P,z)}}catch{}}if(m&&typeof m=="string"&&m.trim())try{const f=o.split(":")[0]??"unknown",z=y==="system"?"system":"service",re=await _s(p,m.trim(),f,z),G=_e(o);if(await ve(p,G,"affects",re,n==="success",n==="partial"),_){const P=`person:${_e(Ut(_))}`;await ve(p,P,"operates",re,!0)}for(const P of a.slice(0,8)){const V=`file:${_e(P)}`;await ve(p,V,"runs_in",re,!0)}}catch{}if(_&&(n==="success"||n==="partial")){const f={topic:o,outcome:n,what_worked:K,what_failed:te??"",severity:l,author:_,file_paths:a,commands:s,tags:[...new Set([...C,"team"])],timestamp:new Date().toISOString(),recall_count:0,version:2,auto_propagated:!0},z=`cachly:lessons:${o}`;p.rpush(z,JSON.stringify(f)).catch(()=>{})}const U=n==="success"?"\u2705":n==="partial"?"\u26A0\uFE0F":"\u274C",oe=l==="critical"?"\u{1F534}":l==="major"?"\u{1F7E1}":"\u{1F7E2}",X=ie?"updated":"stored",he=await Rn(R);return[`${U} **Lesson ${X}:** \`${o}\` (${n}) ${oe} ${l} \xB7 receipt: \`${Be}\``,d.funde.length?`\u{1F512} **${d.funde.length} Geheimnis(se) geschwaerzt** vor dem Speichern (${d.funde.join("; ")}). Der Wert ist nicht im Brain \u2014 verweise kuenftig auf den Ort (z. B. Infisical-Pfad oder Variablenname).`:"",po(t),ee??"","",`**What worked:** ${K}`,te?`**What failed:** ${te}`:"",u?`**Context:** ${u}`:"",a.length>0?`**Files:** ${a.map(f=>`\`${f}\``).join(", ")}`:"",s.length>0?`**Commands:** ${s.map(f=>`\`${f}\``).join(", ")}`:"",C.length>0?`**Tags:** ${C.map(f=>`#${f}`).join(" ")}`:"","",ie?`\u267B\uFE0F Updated (recall count: ${ge} \xB7 audit entries: ${ye.length})`:`\u{1F4A1} Recall later with \`recall_best_solution(topic="${o}")\``,_&&(n==="success"||n==="partial")?`\u{1F465} Auto-shared with team (by _${_}_${x.quelle!=="uebergeben"?` \xB7 from ${x.quelle}`:""}) \u2014 visible in \`team_recall\``:"",S??"",r.length>0?`\u{1F517} Depends on: ${r.map(f=>`\`${f}\``).join(", ")} \u2192 trace with \`trace_dependency\``:"",Ie,In(he,Ie),q.angaben.length?`\u{1F4E6} Applies to ${q.angaben.map(f=>`\`${f}\``).join(", ")}${q.uebernommen?" (carried over from the previous version)":""} \u2014 if the project does not match, the lesson ranks last in recall and is marked.`:"",...q.fehler.map(f=>`\u26A0\uFE0F gilt_fuer not stored, ${f}. Format: package@range, for example next@<15 or python:fastapi@>=0.100 <0.110.`),Y&&w?Ne?`\u21B7 Replaces \`${fe}\` \u2014 recall hides the old version and names its successor with the reason.`:`\u21B7 Replaces \`${fe}\` \u2014 recall hides the old version and names its successor. Without \`grund\` it says 'No reason recorded' \u2014 one line (why the old one was wrong) makes the link checkable.`:Se,...J,...Ae,k>0?`\u{1F5D3}\uFE0F ${k} relative time reference${k===1?"":"s"} dated (e.g. "yesterday" \u2192 "yesterday (${new Date(W.getTime()-864e5).toISOString().slice(0,10)})") \u2014 the original text stays unchanged, the date stands next to it.`:"",ao(K)??""].filter(f=>f!=="").join(`
3
- `)}case"lesson_verified":{const{instance_id:d}=M,t=await j(d),h=new Date().toISOString(),b=M,o=ze({geprueft_mit:b.geprueft_mit,befund:b.befund}),n=ms({...b,...o.felder},h);if(!n.ok)return[`\u26A0\uFE0F ${n.grund}`,"","Beispiel:","```",'lesson_verified(topic="deploy:bau-hero-node1", haelt=true,',' geprueft_mit="curl -s http://127.0.0.1:3220/bereit")',"```","Bei haelt=false gehoert `befund` dazu: was hast du stattdessen gesehen?"].join(`
4
- `);const E=n.eintrag,$=`cachly:lesson:best:${E.topic}`,i=await t.get($);if(!i)return`\u{1F4ED} Keine Lektion \`${E.topic}\` \u2014 nichts zu pruefen. Tippfehler im Themennamen?`;const l=O(i,{}),a=gs(l,E);return await t.set($,JSON.stringify(o.verdacht.length?{...a,tags:mo(a.tags,o.verdacht)}:a)),At(),E.haelt?[`\u2705 \`${E.topic}\` ist wieder frisch \u2014 geprueft am ${h.slice(0,10)}.`,E.geprueft_mit?` geprueft mit: \`${E.geprueft_mit}\``:"","","Das ist die einzige Handlung, die `verified_at` setzt. Ein Abruf tut es nicht.",...Ke(o)].filter(Boolean).join(`
5
- `):[`\u26A0\uFE0F \`${E.topic}\` ist als fraglich markiert.`,` Befund: ${E.befund}`,"","Geloescht wurde nichts \u2014 eine Pruefung kann aus Gruenden fehlschlagen,","die mit der Lektion nichts zu tun haben. Wenn du weisst, was jetzt gilt:",`\`learn_from_attempts(topic="${E.topic}", \u2026, grund="\u2026")\`.`,...Ke(o)].join(`
6
- `)}case"recall_feedback":{const{instance_id:d}=M,t=await j(d),h=yn(M,new Date().toISOString());if(!h.ok)return[`\u26A0\uFE0F ${h.grund}`,"","Beispiel:","```",'recall_feedback(instance_id="\u2026", query="warum bricht der Deploy ab",',' topic="ci:zeitgrenze-meldet-cancelled", helped=true, rank=0)',"```","rank=0 heisst: die Lektion stand GAR NICHT in der Antwort.","Das ist die wertvollste Rueckmeldung von allen."].join(`
7
- `);const b=h.eintrag,o=`cachly:wirkung:${d}`;await t.rpush(o,JSON.stringify(b));const E=(await t.lrange(o,0,-1).catch(()=>[])).map(s=>O(s,null)).filter(Boolean),$=_n(E),i=bn(b),l={"fehlender-treffer":"Sie half, stand aber gar nicht in der Antwort \u2014 der Topf hat sie nicht aufgenommen. Das ist der wertvollste Fall.","zu-weit-hinten":`Sie half, stand aber auf Platz ${b.platz} \u2014 die Sortierung war zu schwach.`,"falsch-und-sicher":`Sie stand auf Platz ${b.platz} und half nicht \u2014 die Sortierung war sich sicher und lag falsch.`,bestaetigung:"Sie stand vorn und half. Bestaetigung.",unauffaellig:"Half nicht und stand hinten. Sagt wenig."},a=$.trefferquote===null?"noch keine hilfreiche Rueckmeldung":`${($.trefferquote*100).toFixed(0)} % der hilfreichen Lektionen standen unter den ersten dreien`;return[`\u2705 Notiert: **${b.thema}** \u2014 ${b.geholfen?"hat geholfen":"hat nicht geholfen"}, Platz ${b.platz===0?"\u2014 (stand nicht drin)":b.platz}`,"",l[i],"",`Spur: ${$.gesamt} Rueckmeldungen, ${a}.`,`Davon: ${$.nachLehrwert["fehlender-treffer"]} nicht gefunden \xB7 ${$.nachLehrwert["zu-weit-hinten"]} zu weit hinten \xB7 ${$.nachLehrwert["falsch-und-sicher"]} falsch und sicher.`].join(`
8
- `)}case"recall_best_solution":{const d=Date.now(),{instance_id:t,topic:h,author:b}=M,o=await Ws(t,ne,`\u{1F4ED} No lessons found for \`${h}\`.`);if(o)return o;const n=await j(t);try{const s=await n.get(`cachly:letzter-recall:${t}`),C=s?O(s,null):null,r=$n(C,h,new Date().toISOString());r&&(await n.rpush(`cachly:wirkung:${t}`,JSON.stringify(r)),await n.del(`cachly:letzter-recall:${t}`))}catch{}const E=await n.get(`cachly:lesson:best:${h}`);if(E){const s=O(E,null);if(!s)return`\u26A0\uFE0F Lesson data for \`${h}\` is corrupted. Re-store it with \`learn_from_attempts\`.`;const C=s.group;if(C){const se=b?await Nt(n,t,b).catch(()=>new Set):new Set,ye=b?await ct(n,t,b).catch(()=>null)==="admin":!1;if(!Et(C,se,ye))return`\u{1F4ED} No lessons found for \`${h}\`. Use \`learn_from_attempts\` after solving it.`}const r=$t(s),c=s.verified_at??s.ts,v=(Date.now()-new Date(c).getTime())/864e5,m=s.commands,y=us(r,v,Array.isArray(m)&&typeof m[0]=="string"?m[0]:void 0,s.pruefung_gefallen_am?{am:s.pruefung_gefallen_am,befund:s.pruefung_befund}:void 0),A=new Date().toISOString(),I={...s,recall_count:(s.recall_count??0)+1,last_recalled_at:A};await n.set(`cachly:lesson:best:${h}`,JSON.stringify(I)),yt(n),mt(n,String(h??""),Date.now()-d);const e=St(s)?vt(s):0;e>0&&n.incrbyfloat(`cachly:stats:time_saved_mins:${t}`,e).catch(()=>{});const H=s.severity==="critical"?"\u{1F534}":s.severity==="major"?"\u{1F7E1}":s.severity?"\u{1F7E2}":"",ae=s.audit_trail??[],ce=ae.filter(se=>se&&typeof se.grund=="string"&&se.grund.trim().length>0).slice(-3).map(se=>` \xB7 ${se.ts?new Date(se.ts).toLocaleDateString("de-DE"):"?"}: ${se.grund.trim()}`),de=ae.length>1?[`_Audit: ${ae.length} changes \xB7 stored ${new Date(s.ts).toLocaleDateString("de-DE")}_`,...ce.length>0?["**Warum zuletzt geaendert:**",...ce]:[]].join(`
9
- `):"",le=(Date.now()-new Date(s.ts).getTime())/864e5,p=le>60?`\u{1F4AD} _Remember when you solved this ${Math.round(le/30)} months ago? Still works._`:"",W=I.recall_count===3&&!s.pinned?"\u{1F4CC} **This lesson came up in 3 answers so far.** If you need it often, pin it: add `pinned: true` via `learn_from_attempts` to always surface it first.":"",Z=s.authors??(s.author?[s.author]:[]),q=[...new Set(Z.filter(Boolean))];let Q="";q.length>=2&&(Q=`\u2705 **Team-verified** \u2014 confirmed by ${q.length} developers.`);const K=s.ersetzt_durch,te=s.ersetzt_am?` (on ${s.ersetzt_am.slice(0,10)})`:"",u=K?await n.get(`cachly:lesson:best:${K}`):null,k=String(s.ersetzt_grund??"").trim(),x=k?` Reason: ${k}`:" No reason recorded.",T=u?!We(Le(u)):!1,_=K?u?T?`\u{1F6AB} **THIS VERSION IS REPLACED**${te} \u2014 the successor lesson is private, not visible to the team. The text below stays readable as history; its author knows the current version.`:`\u{1F6AB} **THIS VERSION IS REPLACED**${te} \u2014 the current answer is in \`recall_best_solution(topic="${K}")\`.${x} The text below stays readable as history.`:`\u26A0\uFE0F **REPLACEMENT POINTS NOWHERE**${te} \u2014 the successor \`${K}\` has been deleted. This version applies again as the best EXISTING answer; confirm it when you can (learn_from_attempts with grund).`:"",S=s.gilt_ab,N=s.gilt_bis,L=S?`\u{1F4C5} Valid since ${String(S).slice(0,10)}.`:"",J=s.entscheidung,ie=J?.gegen?jt({gegen:J.gegen,grund:J.grund??null}):"",ge=N&&Date.parse(String(N))<Date.now()?`\u23F3 **THIS FACT WAS VALID UNTIL ${String(N).slice(0,10)}** \u2014 it is history, not current information.`:"";return[Zt(s),_,ge,rn(s.gilt_fuer),L,ie,p,Q,`${y} **Best solution for \`${h}\`** ${H}${s.severity?` (${s.severity})`:""} \xB7 in ${I.recall_count} answer${I.recall_count===1?"":"s"} so far`,An(s.author,s.source),"",s.what_worked?`**What worked:** ${s.what_worked}`:"",s.what_failed?`**What failed (avoid this):** ${s.what_failed}`:"",s.context?`**Context:** ${s.context}`:"",(s.file_paths??[]).length>0?`**Files:** ${(s.file_paths??[]).map(se=>`\`${se}\``).join(", ")}`:"",(s.commands??[]).length>0?`**Commands:** ${(s.commands??[]).map(se=>`\`${se}\``).join(", ")}`:"",(s.tags??[]).length>0?`**Tags:** ${(s.tags??[]).map(se=>`#${se}`).join(" ")}`:"",de,W].filter(se=>se!=="").join(`
10
- `)}const $=[],i=n.scanStream({match:"cachly:lesson:best:*",count:100});await new Promise((s,C)=>{i.on("data",r=>$.push(...r)),i.on("end",s),i.on("error",C)});const l=$.filter(s=>s.toLowerCase().includes(h.toLowerCase()));if(l.length===0){const s=`cachly:lessons:${h}`,C=await n.lrange(s,-3,-1);if(C.length>0){const c=C.map(v=>O(v,null)).filter(Boolean).map(v=>`- ${v.outcome==="success"?"\u2705":"\u274C"} ${v.what_worked.slice(0,120)} (${new Date(v.ts).toLocaleDateString("de-DE")})`);return`\u26A0\uFE0F No successful solution for \`${h}\` yet. Last attempts:
1
+ import{gitLesen as Pe,istCommitHash as hs,pruefeOrdner as et}from"../git-ohne-shell.js";import{createRequire as bt}from"node:module";import{calculateConfidence as $t,confidenceBadge as us,STRUCTURED_TEMPLATES as ds,CONFIDENCE_WARN_VALUE as fs}from"../confidence.js";import{kurzfassenHinweis as ps}from"../antwort-hinweis.js";import{pruefeMeldung as ms,wendeAn as gs}from"../pruefung-melden.js";import{ckgSlug as we,extractProblemConcept as tt,ckgUpsertNode as je,ckgUpdateEdge as $e,ckgUpsertPersonNode as ws,ckgUpsertFileNode as kt,ckgRecordCollaboration as ys,ckgUpsertServiceNode as _s}from"../ckg.js";import{safeJsonParse as O,scanKeys as Ne,leseOderNull as Be,darfHeraus as We}from"../utils.js";import{ersparteMinuten as vt,istStarterLektion as bs,zaehltAlsErsparnis as St}from"../wertbeitrag.js";import{lessonPreviewLines as Ye,trimTo as $s}from"../lesson-preview.js";import{normalisiereZeit as st,zaehleZeitangaben as nt}from"../zeit-normalisieren.js";import{abstentionSatz as ks,alterssatzFuerBelegte as vs,beurteileTreffer as Ss,istBelegt as Cs}from"../abstention.js";import{meldeEinmal as ot,meldeUndVermerke as Ze,fehlerText as at,OHNE_VEKTOREN as xs,OHNE_DIENST as Ts,OHNE_FRAGEVEKTOR as Ns,SINNPFAD_ABBRUCH as Es,OHNE_SCHREIBVEKTOR as it}from"../aussetzer.js";import{versuchStart as As,neueKennung as Is,wendeZuteilungAn as Ms,schliesseVersuchAb as Rs}from"../versuch.js";function rt(D,M,j,ae=j){if(D.includes("lesson:best:")){const t=O(M,{});if(t&&typeof t=="object"&&(t.what_worked||t.what_failed||t.commands?.length))return Ye(t,{maxChars:j,indent:" "})}const f=$s(M,ae);return f?[f]:[]}const Ct=2e3,xt=400;function Tt(){return process.env.CACHLY_RECALL_NUR_LESEN==="1"}import{getRole as ct,ROLE_BADGE as Ls,getScopes as Nt,lessonVisibleToScope as Et,reviewModeEnabled as Bs,storeLessonProposal as Os}from"./team.js";import{keywordSearch as Xe,treffeUeberDateipfad as Ds,wortindexEntwerten as At,tokenize as lt,splitMultiQuery as zs,levenshtein as Ps,indexVocab as It,bestandHolen as js}from"../search.js";import{startetNochLeer as Mt,leeresBrainAntwort as Rt,startLageMitCommits as ht,sofortAntwortNeuesBrain as Ws,neuesBrainBriefing as Fs}from"../leeres-brain.js";import{rerankByQuality as Gs,qualityMultiplier as Ks,extractLessonQuality as Hs}from"../rerank.js";import{computeEmbedding as xe,hasEmbedProvider as Oe,EMBED_PROVIDER as Te}from"../embeddings.js";import{leserAktiv as Js,leserPunkte as qs,mischeMitLeser as Vs}from"../leser.js";import{repariereFelder as Us}from"../feldreparatur.js";import{VEKTOR_PRAEFIX as Lt,NAME_VEKTOR_PRAEFIX as ut,ZWEIT_VEKTOR_PRAEFIX as Fe,packe as De,textFuerVektor as Ge,textFuerNamensVektor as Bt,Vektorbestand as dt,VEKTOR_NACHTRAG as Ys,NAME_VEKTOR_NACHTRAG as ft,ZWEIT_VEKTOR_NACHTRAG as Qe}from"../bedeutung.js";import{schreibeEingaenge as Zs,Eingangsbestand as Xs}from"../eingaenge.js";import{ordneNachHausordnung as Qs,rangWerte as en}from"../ausgabe-ordnung.js";import{Seltenheitsbestand as tn}from"../seltenheitsbestand.js";import{spurLegen as sn}from"../spuren.js";import{schlageErsetzungVor as nn}from"../ersetzung-vorschlag.js";import{loeseGiltFuerAuf as Ot,stufeSucheNachProjektversion as on,versionsNotizen as an,versionsBanner as rn}from"../gueltigkeit-version.js";const Ke=Ys,cn=365;import{etikettenFuer as ln,lieferJournalSchluessel as Dt,JOURNAL_DECKEL as hn}from"../etiketten.js";import{klassifiziereFehlschlag as un,suchProtokollSchluessel as pt,trichterSchluessel as zt,SUCH_DECKEL as Pt}from"../recall-trichter.js";import{leiteEntscheidungAb as dn,entscheidungsZeile as jt}from"../entscheidung-ableiten.js";import{bewerteTopf as fn,spreizeImTopf as Wt,reichereAn as pn,inhaltsWoerter as Ft,grobStamm as mn,GEWICHTE as gn,knapperSiegSatz as wn}from"../rangfolge.js";import{fehlendeLernfelder as yn,hatFrage as _n}from"../pflichtfelder.js";import{pruefeWirkung as bn,wirkungsEingabeAus as $n,werteAus as kn,lehrwert as vn,leiteAb as Sn,belegeJeThema as Cn}from"../wirkungsspur.js";import{merkeSuchlauf as mt}from"../suchstatistik.js";import{schreibeTopfNotiz as xn,topfProtokollAktiv as Tn}from"../topf-protokoll.js";import{SINN_TOPF as Nn,EINGANG_SCHWELLE as En,EINGANG_SORTIER_GEWICHT as An,ZWEIT_MODELL as qe,ZWEIT_GEWICHT as In,ZWEIT_MINDESTDECKUNG as Gt,NAHDUPLIKAT_SCHWELLE as Mn,LESER_TIEFE as Kt}from"../rangfolge-stellschrauben.js";import{ermittleQuelle as Ht,quelleZeile as Rn}from"../herkunft.js";import{doppelHinweis as Ln,leseDoppelKandidaten as Bn,mitFrist as On,pruefeDoppelung as Dn}from"../doppel-kandidat.js";import{merkeRecall as zn,sitzungsKosten as Pn,vergissSitzungsKosten as jn,kostenZeilen as Wn}from"../sitzungs-kosten.js";const Jt=new WeakMap;function gt(D){let M=Jt.get(D);return M||(M={vektor:new dt,namen:new dt(6e4,ut),zweit:new dt(6e4,Fe),eingang:new Xs,seltenheit:new tn},Jt.set(D,M)),M}const qt=Nn,Fn=En,Gn=An;import{upgradeNudge as Kn}from"../upgrade-nudge.js";import{recallTiefe as Vt,verlangteTiefe as Hn,TIEFE_VOLL as Jn,TIEFE_VOLL_MEHRTHEMIG as qn}from"../recall-tiefe.js";import{nutzungInWorten as Vn,nutzungsSchluessel as Un,verdichte as Yn}from"../werkzeug-nutzung.js";import{inWorten as Zn,lieferBild as Xn,lieferSchluessel as Qn,trefferSchluessel as eo}from"../geliefert.js";import{COAUTHOR_PREF_KEY as to,autorHinweis as so,coautorAus as no,echterGitLeser as oo,ermittleAutor as ao,mitCoautor as io,nurMensch as Ut}from"../autor.js";import{TOOLS as ro}from"../tools.js";import{vorspannHinweis as co}from"../vorspann.js";import{autorAbzeichen as lo,fremdanteil as ho}from"../autor-abzeichen.js";import{cachlyUrl as uo}from"../cachly-url.js";import{belegFuer as fo}from"../schreibbeleg.js";import{schwaerzeFelder as po}from"../geheimnis-filter.js";import{VERDACHT_TAG as wt,mitVerdachtTag as mo,pruefeFelder as go,verdachtsWarnung as wo}from"../tuersteher.js";import{fuerEigenenSpeicher as ze,hinweise as He,istMarkiert as Yt,markiert as yo,markierungsWarnung as Zt,markierungsWarnungAusRohwert as Xt,zaehlHinweise as Qt}from"../schreibtor.js";import{istBlossesThema as es}from"../blosses-thema.js";import{zeitpunkt as be}from"../zeitmessung.js";const Ve=(()=>{try{const D=bt(import.meta.url);for(const M of["../../../package.json","../../package.json"])try{const j=D(M);if(j.name==="@cachly-dev/mcp-server"&&j.version)return j.version}catch{}}catch{}return"0.10.100"})(),_o=()=>{try{return bt(import.meta.url)("../../../CACHLY_CAPABILITIES.json").mcp?.total_tools??null}catch{}return null},bo={"0.10.119":["\u{1F50D} **Explainable briefings \u2014 provenance in every warning**"," \u{1F4CE} `/briefing` warnings now carry `outcome`, `author`, `learned_at` and `matched_on` (the context tokens that triggered the warning) \u2014 editors can show WHY a fix hint fired, not just what it claims.",' \u{1F9F0} VS Code 0.12.0 uses this: severity + confidence in the toast, full lesson card with provenance, "Not helpful" per-file suppression, reviewable CLS auto-learns.'," \u{1F9F9} CI docs now describe exactly what ships: scan mode uses the REST `/scan` endpoint, the GitLab template covers learn/scan/confirm."],"0.10.115":["\u{1F527} **`brain_get_pref` + `brain_set_pref` \u2014 persistent Brain preferences**"," \u{1F4D6} `brain_get_pref` reads back any stored preference (omit key to list all)."," \u270F\uFE0F `brain_set_pref` saves a preference to Redis \u2014 survives restarts, scoped per Brain instance.",' \u{1F4A1} Known key: `auto_changelog` \u2014 set to "false" to silence the new-lessons digest at session_start.'," \u{1F4CA} 140 MCP tools"],"0.10.114":["\u{1F4CB} **`brain_changelog` \u2014 weekly knowledge digest in one call**"," \u{1F4C5} Generates a grouped Markdown changelog of lessons learned in the last N days."," \u{1F465} Grouped by topic category, annotated with author, recall count, and confidence \u2014 paste directly into standup or Slack."," \u2699\uFE0F Params: `days` (default 7), `max_lessons` (default 30), `include_failures` (default true)."," \u{1F4CA} 138 MCP tools"],"0.10.113":["\u{1F4C8} **Week-over-week savings trend in `cache_stats`**"," \u{1F4C5} `cache_stats` now shows **7-day hits** + **WoW trend %** (e.g. `+23.0% vs prev week`) so ROI momentum is visible, not just the cumulative total."," \u2B07\uFE0F Goes red (\u{1F4C9}) on drops, green (\u{1F4C8}) on growth \u2014 first week always shows +100% (new)."," \u{1F527} Backed by new `CountHitsInWindow` repo method + `hits_last_7d`, `hits_prev_7d`, `week_over_week_pct` fields in `SavingsEstimate`."," \u{1F4CA} 137 MCP tools"],"0.10.112":["\u2699\uFE0F **Configurable cost_per_call_usd \u2014 accurate ROI for any LLM**"," \u{1F4A1} New `set_cost_per_call` tool: set your real per-call cost (claude-opus-4.8 \u2192 $0.02, gpt-5.5 \u2192 $0.015, claude-sonnet-4.6 \u2192 $0.009, claude-haiku-4.5 \u2192 $0.001) so savings estimates in `cache_stats` reflect your actual bill."," \u{1F527} New API: `PUT /api/v1/instances/:id/cost-per-call` \u2014 persisted per instance, used by `cache_stats`, org savings, and SSE analytics stream."," \u{1F4CA} 137 MCP tools"],"0.10.111":["\u{1F3E2} **Org-wide ROI + GitLab CI \u2014 the CFO number, aggregated**"," \u{1F4B0} `cache_org_stats` now shows **org-wide Tokenmaxxing ROI**: total savings summed across every instance in your org, monthly projection, top-5 instances by savings. One number for the whole team."," \u{1F50C} Backed by new API endpoint `GET /api/v1/orgs/:id/savings` (org members only)"," \u{1F98A} GitLab CI integration: `brain-from-ci-gitlab.yml` \u2014 two `.post` jobs (on_success/on_failure), same helper script, pipeline never fails because of Brain push"," \u{1F6E0}\uFE0F GitHub Action template fixed: `secrets.*` in job-level `if` is invalid \u2014 guard removed (helper script skips cleanly on missing secrets)"," \u{1F4CA} 136 MCP tools"],"0.10.110":["\u{1F3E2} **Zero friction: ROI-Projektor, Org-Shared-Cache, CI ready-to-paste**"," \u{1F4C8} `cache_stats` \u2014 zero-hit projection calculator: shows expected monthly savings for 5/10/20-dev teams before a single cache hit lands. ROI visible from day 1."," \u{1F3E2} New `cache_org_stats` \u2014 shared org-level cache stats across multiple instances. Convention-based (`org:{id}:sem`), zero API changes needed."," \u{1F517} `cache_set`/`cache_get` now accept optional `org_id` \u2014 write to personal + org namespace, fall back to org on miss. Fully backwards-compatible."," \u{1F4CB} Copy-paste CI integration: `brain-from-ci-action.yml` \u2192 Brain self-calibrates confidence on every GitHub Actions run. No config beyond two repo secrets."," \u{1F4CA} 136 MCP tools"],"0.10.109":["\u{1F4B0} **Tokenmaxxing ROI + Closed-loop CI learning**"," \u{1F4CA} `cache_stats` now shows a **Tokenmaxxing ROI** section \u2014 total tokens saved, monthly cost projection, and top 3 most-cached prompts. Finally a number you can show the CFO."," \u{1F504} New `brain_from_ci` \u2014 bulk-ingest CI history into the Brain (red\u2192green = learned fix, green\u2192red = CKG causal edge). The brain_from_git equivalent for CI. Bootstrap the Brain from your CI logs in one call."," \u{1F4CA} 134 MCP tools"],"0.10.108":["\u{1F9E0} **Focused briefing + trustworthy autopilot**"," \u{1F3AF} New `session_start_summary` \u2014 top-N focused briefing by topic. Scores lessons by relevance \xD7 confidence + recall_count + severity + recency. Use it for a quick pre-brief without loading the full Brain.",' \u{1F91D} Autopilot template now explains the data flow upfront ("your own Brain, EU-hosted, no third parties") \u2014 no more friction with cautious AI assistants during onboarding'," \u{1F4CA} 132 MCP tools"],"0.10.107":["\u{1F3AF} **Recall@3 gap closed \u2014 the moat proof got sharper (the head-to-head that matters)**"," \u{1F50D} External-corpus Recall@3 **82.1% \u2192 98.2%** \xB7 Precision@1 **80.4%** \xB7 MRR **89.0%** \u2014 now ahead of flat-file memory on every metric"," \u{1F9EC} Root-cause fix: documents are no longer indexed with cross-lingual synonyms (the symmetric-expansion bug inflated any doc containing a common word ~28\xD7, burying topic-specific lessons). Queries still expand \u2014 a \u65E5\u672C\u8A9E query still finds English lessons."," \u2696\uFE0F Reranker now compresses BM25 with score^0.3 before applying lesson quality, so a proven success can overtake a text-similar failed attempt even when the distractor scores 5\u20136\xD7 higher in raw BM25"," \u{1F6E1}\uFE0F CI bench gate widened to both home + external corpora with committed floors (`npm run bench:gate`) \u2014 recall quality can never silently regress again"," \u{1F4CA} 121 MCP tools"],"0.10.98":['\u{1F310} **Model-Neutrality as Feature (W9) \u2014 "Bring your own model, keep your brain."**'," \u{1F50C} New `brain_portability()` \u2014 shows your Brain ID, all compatible MCP clients, and ready-to-paste config snippets for each; proves the same Brain works in Claude, Cursor, Copilot, Windsurf, Cline, Zed and more"," \u{1F9E0} Your lessons, crystals, and predictions are model-agnostic \u2014 no vendor lock-in at the memory layer",' \u{1F4D6} New docs: cachly.dev/docs/model-neutral \u2014 "Same Brain, Any Model" guide with live config examples'," \u{1F4CA} 121 MCP tools (new: `brain_portability`)"],"0.10.97":['\u{1F4A0} **Team Crystallize (W8) \u2014 the team-wide, causal counter to per-user "Dreaming"**'," \u{1F9EC} New `team_crystallize()` \u2014 surfaces fixes that 2+ teammates *independently converged on*, the one thing a single-user memory structurally can't build",' \u{1F9E9} Cross-person clustering: groups structurally similar problems across people & namespaces (e.g. "pool exhaustion" solved the same way in payments, auth & db)',' \u{1F465} Each pattern names who converged \u2014 instant "ask @alice and @bob, they both hit this"'," \u{1F48E} Shows up in `crystal_view` alongside your per-brain crystal"," \u{1F4CA} 120 MCP tools (new: `team_crystallize`)"],"0.10.96":["\u{1F510} **Roles & Team-Scopes now ENFORCED (W6) \u2014 the enterprise governance layer**"," \u{1F6E1}\uFE0F `team_confirm` is now reviewer-gated, `team_learn` blocks viewers (recall-only) \u2014 roles stop being cosmetic the moment an admin is assigned"," \u{1F4DC} New `team_audit` tool \u2014 immutable, admin-only trail of every role change & lesson confirmation, for compliance & security reviews"," \u{1F513} Zero impact on open teams: governance activates only once you bootstrap an admin; before that everything works exactly as before"," \u{1F4CA} 119 MCP tools (new: `team_audit`)"],"0.10.95":["\u{1F9E0} **Domain Brain Marketplace (W10) \u2014 bootstrap your Brain with curated community knowledge**"," \u{1F6D2} `brain_marketplace()` \u2014 browse installable packs of high-trust lessons by domain: \u2638\uFE0F Kubernetes, \u{1F510} Auth, \u{1F5C4}\uFE0F Database, \u269B\uFE0F React, \u{1F4B3} Payments & more",' \u{1F4E6} `brain_install(slug="k8s")` \u2014 merges a domain\'s verified lessons into your Brain; live in `smart_recall` instantly, even offline',' \u{1F6E1}\uFE0F Idempotent + non-destructive: installed lessons are tagged `source:"marketplace:<slug>"` and NEVER override your own'," \u{1F50C} New gRPC `Subscribe` RPC \u2014 agents get new lessons streamed live the moment they're learned (completes the M2M loop with `Learn`)"," \u{1F4CA} 118 MCP tools (new: `brain_marketplace`, `brain_install`)"],"0.10.94":['\u{1F91D} **Person\u2194Person Collaboration Graph (W5) \u2014 "Frag X und Y, die haben das zusammen gel\xF6st"**'," \u{1F578}\uFE0F New `brain_collab_pairs()` tool: shows every contributor pair who've touched the same files or recalled each other's lessons \u2014 ideal for onboarding and bus-factor analysis"," \u{1F4E1} `smart_recall` now wires CKG `collaborates` edges live when cross-author reuse fires \u2014 collaboration graph grows with every team recall"," \u26A0\uFE0F Bus-factor section: flags solo contributors whose knowledge no teammate has yet recalled"," \u{1F4CA} 116 MCP tools (new: `brain_collab_pairs`)"],"0.10.93":['\u{1F5FA}\uFE0F **`brain_plan` \u2014 your Brain goes from "what did we learn?" to "what should I do?"**',` \u{1F9ED} Give it a task you're about to start ("upgrade Postgres 14\u219216") and it returns an ordered, grounded action plan: ranked failure modes to avoid, proven fix steps (dependency-aware, with commands), and a pre-flight checklist \u2014 all from your own lessons`,' \u{1F52C} Generative layer on the CKG: where `brain_predict` answers "what might fail?", `brain_plan` answers "what, in what order?"'," \u{1F4CA} 115 MCP tools (new: `brain_plan`)"],"0.10.92":["\u{1F44B} **Welcome-back digest \u2014 re-entry after a week away now leads with what changed**"," \u{1F331} `session_start` detects a gap \u2265 7 days and surfaces how many lessons your Brain gained while you were gone (git, CI & teammates kept it learning), with the freshest topics"," \u{1F9E0} Zero extra latency \u2014 the digest is computed from data session_start already loads"," \u{1F50C} New gRPC `Learn` RPC (M2M write path) \u2014 agents & CI can now teach the Brain directly, byte-compatible with the MCP lesson format"," \u{1F4CA} 115 MCP tools"],"0.10.91":["\u{1F550} **Lesson-anchored time attribution \u2014 every recall shows when it was learned and how much it saves**",` \u{1F4A1} "Brain saved you" banner now includes: when the lesson was learned (e.g. "12 May"), estimated original debug cost by severity (critical=2h, major=1h, minor=20min), and team attribution when a teammate's lesson fires`,' \u{1F4C5} Origin date formatted as "12 May" (same year) or "12 May 2024" (different year) \u2014 no clutter',' \u{1F465} Cross-author recalls now show "@author" on the same banner line \u2014 the "team knowledge reuse" value is front-and-center'," \u{1F4CA} 115 MCP tools"],"0.10.90":["\u{1F4CA} **External benchmark corpus v2 \u2014 the proof is now statistically solid**"," \u{1F9EA} Expanded from 8 queries \u2192 61 lessons / 56 queries across 10 real-world engineering domains (k8s, DB, auth, CI, frontend, API, payments, observability, network, Node.js, security, infra)"," \u2694\uFE0F 10 adversarial distractors added \u2014 failure lessons that share the exact same vocabulary as the query, forcing the quality reranker to earn its lift"," \u{1F4C8} Results on the larger corpus: cachly +2.9% Precision@1 vs flat-file, +12.5% vs raw BM25 baseline \u2014 on a harder dataset, not an easy one"," \u{1F4CA} 115 MCP tools"],"0.10.89":["\u26A1 **One-command onboarding \u2014 `autopilot` is now the single canonical setup**"," \u{1F680} Every surface (README, docs, emails, dashboard, blog) now leads with `npx @cachly-dev/mcp-server@latest autopilot` \u2014 one command signs you in, configures every editor, and bootstraps from git",' \u{1F9F9} Retired the split-brain instructions (legacy `@cachly-dev/init` wrapper + the old "three steps") so there\'s exactly one thing to copy-paste'," \u{1F527} Fixed a build-breaking duplicate `export` on buildClaudeMdBlock that failed CI on v0.10.88"," \u{1F522} Version hygiene \u2014 package.json, server.json, lockfile and MCP_VERSION resynced (server.json/lockfile were stuck at 0.10.85)"," \u{1F4CA} 115 MCP tools"],"0.10.88":["\u2705 **Honest setup + tested config writer (v0.10.88)**",' \u{1F512} setup/autopilot now report the truth: if no editor config could be written it exits non-zero with the exact failures instead of falsely claiming "Brain is ready"'," \u{1F4CA} New setup_config_write_failed telemetry \u2014 permission/path failures that block activation are now visible in reporting"," \u{1F9EA} 24 new unit tests for the config writer (buildServerEnv, buildMcpConfig, mergeMcpConfig, buildClaudeMdBlock) \u2014 the merge logic that must never clobber your other MCP servers is now regression-guarded"," \u{1F4CA} 115 MCP tools"],"0.10.87":["\u{1F310} **Multi-editor auth persistence + full activation telemetry (v0.10.87)**"," \u2705 JWT + instance_id now persisted to Cursor (`~/.cursor/mcp.json`) and Windsurf configs on auth \u2014 no more infinite re-auth for non-Claude-Code users",' \u{1F4E1} `device_flow_failed(reason="timeout")` now fires when sign-in window expires \u2014 blind spot in the funnel is gone'," \u{1F4E1} `auto_provision_failed` now fires on network errors too (was only HTTP non-2xx)"," \u{1F4E1} `brain_from_git_failed` fires with error reason when auto-bootstrap throws \u2014 no more empty brain with no explanation",' \u2728 Git bootstrap shows lesson count in session_start: "Brain bootstrapped \u2014 23 lessons loaded" \u2014 the WOW moment'," \u{1F527} API: McpEvent stores brain quality metrics (fixes/features/refactors/total) \u2014 git learning quality now measurable"," \u{1F4CA} 115 MCP tools"],"0.10.86":["\u{1F6E1}\uFE0F **Zero-friction activation \u2014 your Brain self-heals into existence**"," \u{1F527} Auto-provision now runs on *any* tool call, not just at sign-in \u2014 if instance creation ever fails once, the next call recovers it automatically"," \u{1F91D} Parallel tool calls on startup are coalesced into one resolve (no more duplicate-instance races)",' \u{1F4AC} Clearer "instance still starting" message \u2014 leads with "just retry", no manual config needed'," \u{1F4C8} New activation-funnel telemetry: install \u2192 sign-in \u2192 provision, so silent churn is visible"," \u{1F4CA} 115 MCP tools"],"0.10.85":["\u{1F680} **One-command onboarding \u2014 `npx @cachly-dev/mcp-server@latest autopilot`**"," \u26A1 Single command does it all: auth \u2192 instance \u2192 every editor config \u2192 CLAUDE.md \u2192 Brain bootstrap \u2192 health"," \u{1F331} Seeds the 16-lesson starter corpus when git history is empty \u2014 your first smart_recall hits instantly"," \u{1F916} Fully automatic (zero prompts); `setup` remains for interactive, pick-your-editors onboarding"],"0.10.84":["\u{1F916} **M2M & agent-ecosystem reach \u2014 cachly for every caller, human or machine**"," \u{1F511} OAuth2 `client_credentials` grant \u2014 set CACHLY_CLIENT_ID + CACHLY_CLIENT_SECRET for fully headless auth (CI, agents, AI-to-AI)"," \u{1F5C2}\uFE0F `npx ... tool-specs --format=openai|anthropic|langchain` \u2014 export all 121 tools in any framework's dialect"," \u{1F310} `npx ... openapi` \u2014 OpenAPI 3.1 doc (1 POST path per tool) for Assistants / codegen / Postman"," \u{1F9EA} 14 new tests: client_credentials helpers, all four spec dialects, OpenAPI required-body inference"],"0.10.83":["\u{1F9E0} **Brain Viz \u2014 3D graph export**"," \u{1F310} `brain_graph(instance_id)` \u2014 exports the Causal Knowledge Graph as a render-ready {nodes, links} payload"," \u{1F3A8} Schema `cachly.brain_graph/v1`: node kinds (concept/person/file/service) with color groups + size, edges with confidence"," \u{1F5A5}\uFE0F Consumed verbatim by the 3D frontend (react-force-graph-3d / three.js) \u2014 the visual brain map",' \u{1F50E} Filters: `domain`, `min_confidence`, `max_nodes` (with truncation flag), `format="summary"`'," \u{1F9EA} 10 tests: kind detection, dangling-edge pruning, confidence/domain filters, schema contract"," \u{1F4CA} 115 MCP tools"],"0.10.82":["\u{1F4E1} **Telemetry API contracts \u2014 dashboard ingest pipeline prepared**"," \u{1F5C2}\uFE0F `src/telemetry-types.ts` \u2014 canonical event types + resolved event format (TelemetryEventRaw / TelemetryEventResolved)"," \u{1F510} `user_fingerprint` \u2014 non-reversible JWT sub hash, PII-free user clustering for dashboard"," \u{1F4CA} `metrics` block on key events: smart_recall (hit/topic), brain_from_git (fixes/features/total), brain_seed_starter (seeded_count)"," \u{1F6A6} Internal API contract documented: GET /internal/telemetry/stream \u2192 TelemetryEventResolved[]"],"0.10.81":["\u23F1\uFE0F **Onboarding-Bench \u2014 time-to-first-recall, now measured**"," \u{1F4D0} `npm run bench:onboarding` \u2014 cold (empty) vs seeded (starter) first-query hit rate via the real search engine"," \u{1F4CA} Result: first-query hit@1 **0% \u2192 87.5%**, hit@3 **0% \u2192 100%**, MRR **0% \u2192 93.8%**"," \u{1F6E1}\uFE0F CI-defended in `onboarding-bench.test.ts` (cold must answer 0%, seeded thresholds enforced)"," \u{1F4CB} `PLAN-DASHBOARD.md` \u2014 full build spec for the external Team-Knowledge-Reuse dashboard (separate repo)"],"0.10.80":["\u{1F331} **Starter corpus \u2014 first recall in seconds, not sessions**"," \u{1F4DA} `brain_seed_starter(instance_id)` \u2014 seed 16 curated universal lessons (Docker cache, JWT skew, K8s OOM, N+1, cache stampede\u2026)"," \u{1F680} Auto-seeds on first session when git history is empty/shallow \u2014 your first `smart_recall` returns a real hit",' \u{1F6E1}\uFE0F Tagged `source:"starter"`, idempotent, never overrides your own lessons'," \u23F1\uFE0F Directly attacks time-to-first-recall (<2 min goal); stamps `born_at` on seed"," \u{1F9EA} 18 new tests: seeding, idempotency, topic_filter, force, user-lesson protection"," \u{1F4CA} 113 MCP tools"],"0.10.79":["\u{1F310} **Brain Marketplace \u2014 full shareable Brain lifecycle**"," \u{1F4CB} `brain_share_list(instance_id)` \u2014 see all your public shares with lesson counts + share IDs"," \u{1F5D1}\uFE0F `brain_unshare(share_id)` \u2014 revoke a share; link goes dead immediately"," \u{1F50D} `brain_discover(query, topic)` \u2014 search public Brains in the cachly marketplace"," \u{1F4E4} `publish` CLI \u2014 fancy card + share URL; `--public` for discoverable; `--title` custom name"," \u{1F9EA} stress tests: team_expertise_map with 50 contributors, chain import/unshare, discover fallback"," \u{1F4CA} 112 MCP tools"],"0.10.78":["\u{1F310} **Phase 3: Shareable / Public Brains**",' \u{1F4E4} `brain_share(instance_id="...")` \u2014 export a Brain snapshot as a shareable link (public or unlisted)',' \u{1F4E5} `brain_import(instance_id="...", share_id="...")` \u2014 import any public Brain into yours (1-line)'," \u{1F50D} Optional topic_filter, min_confidence, topic_prefix \u2014 precise control over what gets shared/imported"," \u{1F512} dry_run mode on both tools \u2014 preview before committing"," \u{1F9EA} Regression suite added: share handler, buildServerEnv, stdout cleanness"," \u{1F4CA} 109 MCP tools"],"0.10.77":["\u{1FA79} **Frictionless onboarding + first-class self-hosting/BYOK**"," \u{1F680} Editor-launched server now serves all tools even before sign-in \u2014 zero-credential device flow finally triggers on first tool call"," \u{1F512} No more stdout pollution: stdio JSON-RPC stream stays clean when no JWT is set"," \u{1F3E0} `setup --api-url` / `init --api-url` point at a self-hosted backend; URL baked into config only when non-default",' \u{1F9EC} `health` now shows your BYOK embedding provider + accepts cky_ long-lived keys (was a false "invalid token" failure)'],"0.10.76":["\u{1F4CA} **Agent-trace benchmark + editor matrix + setup timer**"," \u{1F4C2} New `agent-traces-corpus.json` \u2014 22 lessons, 15 queries, realistic AI-agent debug patterns"," \u{1F3C6} Benchmark result: **P@1 +66.7%, MRR +9.5% vs flat-file** (adversarial failure-distractors per category)"," \u{1F4CB} README: first-class editor support matrix (Claude Code, Cursor, Windsurf, VSCode, Cline, Continue.dev, Zed)",' \u23F1\uFE0F setup now shows elapsed time + "Brain is ready" banner at the end'],"0.10.75":["\u{1FA79} **Setup hardened \u2014 works from the VSCode plugin & any non-TTY terminal**"," \u{1F50C} `setup` no longer hangs on a non-interactive stdin (VSCode tasks, CI) \u2014 auto-detects + runs in automatic mode"," \u{1F510} Device-flow now uses the API proxy first (the old direct-Keycloak call returned 403) \u2014 sign-in actually completes"," \u{1F310} When auto sign-in is unavailable, the browser opens with a step-by-step API-key guide instead of a silent paste prompt"," \u{1F4CA} Funnel fix: `device_flow_completed` / `device_flow_failed(reason)` now emitted from setup too"],"0.10.73":["\u{1F510} **Team visibility scopes + one-command init + external bench**",' \u{1F195} `team_grant_scope` / `team_scopes` \u2014 scope lessons to a sub-team (group="security"); only members + admins recall them',' \u{1F3F7}\uFE0F `learn_from_attempts(group="...")` \u2014 orthogonal to private; smart_recall enforces it per requester'," \u26A1 `init` is now zero-arg + idempotent (reuses saved creds, only writes what changed, <60s)"," \u{1F4E6} `npm run bench:external` \u2014 prove recall-lift on a third-party-labeled corpus (portable JSON format)"," \u{1F4CA} 107 MCP tools"],"0.10.72":["\u{1F451} **Role model \u2014 admin \xB7 reviewer \xB7 contributor \xB7 viewer (Phase 3)**"," \u{1F195} `team_assign_role` \u2014 establish governance; first call bootstraps, admins manage the rest"," \u{1F195} `team_whoami` \u2014 see your own role and capabilities"," \u{1F195} `team_roster` \u2014 full team role table (\u{1F451}\u{1F6E1}\uFE0F\u270F\uFE0F\u{1F441}\uFE0F)"," \u{1F6E1}\uFE0F `team_confirm` is now role-aware \u2014 admin/reviewer auto-get senior weight; contributor gets peer; no self-promotion"," \u{1F451} Setup CLI now prompts for governance bootstrap (idempotent). 105 MCP tools"],"0.10.71":['\u{1F6E1}\uFE0F **Self-healing auth** \u2014 no more silent "0 recalls because the token quietly died"'," \u267B\uFE0F Near-expiry tokens auto-refresh into a long-lived key while still valid (zero interaction)"," \u{1F501} A rejected (401) call self-heals once and retries before surfacing an error"," \u{1F4E3} If the credential is truly dead, session_start + get_api_status say so plainly + how to fix"],"0.10.70":["\u{1F6F0}\uFE0F **Service/System nodes in the graph (Phase 3)** \u2014 the brain now models running systems",' \u{1F195} `brain_service_map(service="prometheus")` \u2014 who owns it + every failure/fix known',' \u{1F3F7}\uFE0F Tag lessons with `service="..."` (`service_kind="system"` for infra) \u2014 person\u2192operates, file\u2192runs_in edges',` \u{1F6A8} Incident triage: a restarting pod \u2192 instant "who knows this and what's broken before"`," \u{1F4CA} 102 MCP tools"],"0.10.69":["\u{1F4C1} **Personalized context-aware recall (Phase 3)** \u2014 pass `context_files` to `smart_recall`"," \u{1F3AF} Lessons learned on your current files bubble up \u2014 even when the query doesn't name the file"," \u{1F3F7}\uFE0F `\u{1F4C1} context match` badge on every file-boosted result so you know WHY it ranked up"," \u2705 Works alongside keyword, semantic, CKG, and governance signals \u2014 a 6th ranking dimension"],"0.10.68":["\u{1F91D} **Collaboration graph (Phase 3)** \u2014 person\u2194person edges from shared files"," \u{1F465} `brain_who_knows` now shows who the top expert frequently works with",' \u{1F68C} Bus-factor insight: "ask X and Y together \u2014 they solved this on shared files"'," \u{1F6E1}\uFE0F Stability: every network call in the agent hot path is now timeout-bounded"],"0.10.64":["\u{1F4C8} **The three decisive metrics, now measurable**"," \u23F1\uFE0F `brain_metrics()` \u2014 time-to-first-recall, recall-lift, team-knowledge-reuse in one view"," \u{1F465} Cross-author reuse tracked: recall a teammate's lesson \u2192 counted + surfaced inline"," \u{1F6E1}\uFE0F Stability: scans now capped + timed out (a huge keyspace can't hang the agent)"],"0.10.61":["\u{1F3AF} **Phase 3C: 100 MCP tools milestone \u2014 zero-setup knowledge graph**"," \u{1F504} `brain_from_git` now auto-builds Person+File nodes from git history (zero setup!)"," \u{1F50D} `skill_gaps()` \u2014 find domains with unresolved failures, missing attribution"," \u{1F4CA} `brain_coverage()` \u2014 scored 0-100 health report: lessons \xB7 attribution \xB7 file coverage"],"0.10.60":["\u{1F195} **Phase 3B: File map + team overview + visibility**"," \u{1F4C1} `brain_file_map(file_paths=[...])` \u2014 experts + lessons per file before you touch it"," \u{1F5FA}\uFE0F `team_expertise_map()` \u2014 full team skills matrix in one table",' \u{1F512} `visibility: "private"` on `learn_from_attempts` \u2014 private notes never leak to smart_recall'," \u{1F4CA} 98 MCP tools"],"0.10.59":["\u{1F195} **Phase 3A: Org-wide knowledge graph**",' \u{1F465} `brain_who_knows(topic="...")` \u2014 find your team\'s experts on any topic instantly',' \u{1F578}\uFE0F Person + File nodes auto-built from `learn_from_attempts(author="...", file_paths=[...])`'," \u{1F464} Author attribution now shown inline in `smart_recall` results"," \u{1F4CA} 96 MCP tools \xB7 386 tests green"],"0.10.58":["\u{1F9F9} Zero lint warnings \u2014 all unused imports cleaned across every handler","\u2705 Build: 0 errors, 0 warnings \xB7 379 tests green"],"0.10.57":[`\u{1F195} **What's new in v${Ve}:**`," \u{1F527} **Critical fix** \u2014 corrected the npm bin/main entry path (`dist/src/index.js`); 0.10.50\u20130.10.52 shipped a broken entry point"," \u{1F527} Version hygiene \u2014 synced package.json, server.json, lockfile, MCP_VERSION; `latest` now points forward again"," \u2705 Accurate tool count (95) across README, server.json, CLI banners"," \u2705 Includes all Phase 2/3 work: quality reranking, Cachly-Bench, hybrid + CKG recall, team_confirm governance"],"0.10.52":[" \u2705 `smart_recall` \u2014 CKG traversal as Layer 3: finds lessons that FIXED causal-graph-similar problems (\u{1F578}\uFE0F causal graph badge)"," \u2705 Bench corpus expanded: governance adversarial pair proves review-boost end-to-end (+22% P@1 headline)"," \u{1F527} Import cleanup: unused symbols removed from brain.ts (no functional change)"," \u{1F4A1} `smart_recall` now shows keyword / semantic / CKG / hybrid match type for every result"],"0.10.51":[" \u2705 `team_confirm` \u2014 human review raises lesson recall ranking (\u{1F6E1}\uFE0F senior / \u2714\uFE0F peer)"," \u2705 Governance-aware recall \u2014 confirmed lessons outrank unreviewed auto-learned entries"],"0.10.50":[" \u2705 `smart_recall` \u2014 unified keyword + semantic hybrid list (one ranked result, not two sections)",' \u2705 `smart_recall` \u2014 "Brain saved you here" banner surfaces time saved inline per proven lesson'," \u2705 Quality reranking \u2014 proven successes outrank symptom-dense failures (+11% Precision@1)"," \u2705 Cachly-Bench \u2014 IR benchmark with CI regression guard (see BENCH.md for the proof)"," \u{1F527} Contradiction resolution now persisted to Redis (90-day TTL)"," \u{1F4A1} Run `smart_recall` to see your Brain's recall quality in action"],"0.10.49":[" \u2705 `brain_from_git` \u2014 auto-seed your Brain from git history in seconds"," \u2705 `brain_predict_failures` \u2014 predict CI/build failures before they happen"," \u2705 VS Code extension \u2014 Brain status bar, ambient learning, CodeLens hints"," \u2705 Team Brain \u2014 share lessons across your whole engineering team"," \u{1F527} Stability \u2014 auto-expiring memory keys, longer provisioning wait, robust recall"]},Ta=new Set(["learn_from_attempts","recall_best_solution","smart_recall","recall_feedback","lesson_verified","session_start","session_start_summary","session_end","session_ping","session_handoff","auto_learn_session","brain_who_knows","brain_file_map","team_expertise_map","skill_gaps","brain_coverage","brain_metrics","brain_service_map","brain_collab_pairs","brain_portability","brain_changelog","brain_set_pref","brain_get_pref"]),$o=uo("/billing","upgrade"),ts=new Map,ss=new Set,ko=1680*60*60;function vo(D=new Date){return`cachly:quota:recalls:${D.toISOString().slice(0,7)}`}async function yt(D){try{const M=vo();await D.incr(M)===1&&await D.expire(M,ko)}catch{}}function ns(D){return`cachly:stats:drei-ausgaenge:${D}`}async function _t(D,M,j){try{await D.hincrby(ns(M),j,1)}catch{}}function os(D,M,j,ae){yt(D),mt(D,j,ae);const f={ts:new Date().toISOString(),frage:j.slice(0,200),geliefert:[]};(async()=>{const t=pt(M);await D.lpush(t,JSON.stringify(f)),await D.ltrim(t,0,Pt-1)})().catch(()=>{}),_t(D,M,"schweigen")}async function as(D){const M={tage:0,rekord:0};try{const j=await D.get("cachly:streak:current"),ae=O(j,null),f=new Date().toISOString().slice(0,10);if(ae){const t=ae.last_date,l=new Date(Date.now()-864e5).toISOString().slice(0,10);t===f?(M.tage=ae.days,M.rekord=ae.record):t===l?(M.tage=ae.days+1,M.rekord=Math.max(M.tage,ae.record),await D.set("cachly:streak:current",JSON.stringify({days:M.tage,last_date:f,record:M.rekord}))):(M.tage=1,M.rekord=ae.record,await D.set("cachly:streak:current",JSON.stringify({days:1,last_date:f,record:M.rekord})))}else M.tage=1,M.rekord=1,await D.set("cachly:streak:current",JSON.stringify({days:1,last_date:f,record:1}))}catch{}return M}async function is(D,M,j){await D.set("cachly:session:current",JSON.stringify({started:new Date().toISOString(),focus:M,provider:j}),"EX",86400)}async function rs(D,M){const j=ts.get(D);if(j&&j.expiresAt>Date.now())return j.gate;try{const ae=await M(`/api/v1/instances/${D}/memory`),f=ae?.recall_limit??-1,t=ae?.total_recall_count??0,l={reached:f>0&&t>=f,limit:f,used:t,goodwillMessage:ae?.goodwill_message,notice:ae?.notice};return ts.set(D,{gate:l,expiresAt:Date.now()+6e4}),l}catch{return{reached:!1,limit:-1,used:0}}}const So={smart_recall:"query",recall_best_solution:"topic",causal_trace:"problem"};async function Na(D,M,j,ae){const f=await Co(D,M,j,ae),t=So[D];if(t&&typeof f=="string")try{zn(String(M.instance_id??""),String(M[t]??""),f.length)}catch{}return f}async function Co(D,M,j,ae){switch(D){case"learn_from_attempts":{{const u=yn(M);if(u.length)return`\u26A0\uFE0F learn_from_attempts needs ${u.join(" and ")}. Nothing was stored.`}const f=po(M),t=go(f.felder),l=t.length?{...f.felder,tags:mo(f.felder.tags)}:f.felder,{instance_id:_,topic:o,outcome:n,what_worked:E,what_failed:$="",context:i="",severity:h="major",file_paths:a=[],commands:s=[],tags:C=[],depends_on:r=[],author:c="",visibility:v="team",service:m="",service_kind:y="service",group:A="",grund:I="",ersetzt:e="",gilt_ab:G="",gilt_fuer:ce}=l,{what_worked:oe,what_failed:he="",context:te=""}=Us({what_worked:E,what_failed:$,context:i}),p=await j(_),V=new Date,U=V.toISOString();let Q=Ot(ce,void 0);const Z=G&&!Number.isNaN(Date.parse(String(G)))?new Date(String(G)):V,K=st(oe,Z),ne=st(he,Z),d=st(te,Z),k=nt(oe,Z)+nt(he,Z)+nt(te,Z),T=ao({uebergeben:c,umgebung:process.env.CACHLY_AUTHOR,git:oo(process.env.CACHLY_WORKSPACE??process.cwd())});let N="";try{N=no(await p.get(`cachly:prefs:${to}:${_}`))}catch{}const b=io(T.autor,N),S=so(T);if(v!=="private"&&await Bs(p,_))return Os(p,o,{topic:o,outcome:n,what_worked:K,what_failed:ne,context:d,severity:h,file_paths:a,commands:s,tags:C,depends_on:r,...Q.angaben.length?{gilt_fuer:Q.angaben}:{},...b?{author:b}:{},source:Ht(),...m?{service:m}:{},...A?{group:String(A).toLowerCase().trim()}:{},visibility:v,recall_count:0,ts:U,confidence:1,version:3});const x=o.split(":")[0],B=ds[x],J=[];if(B)for(const u of B.required)u==="commands"&&s.length===0&&J.push(`\u{1F4CB} ${B.hint}`);let le=!1,ye=0,se,de=[];const ge=await p.get(`cachly:lesson:best:${o}`),ke=String(I||l.reason||"").trim();if(ge&&!ke)return[`\u270B **Update rejected:** \`${o}\` already exists \u2014 an update needs the field \`grund\` (the reason; \`reason\` works too).`,"","One line is enough: WHY was the previous version wrong or incomplete?",'Example: grund="The deploy target moved from port 3000 to 8080"',"","The diff says WHAT changes. The grund line says why the old version was wrong \u2014","and that is the line someone reads two months from now (audit_trail, recall_best_solution)."].filter(Boolean).join(`
2
+ `);if(ge)try{const u=JSON.parse(ge);Q=Ot(ce,u.gilt_fuer),ye=u.recall_count??0,se=u.last_recalled_at,de=u.audit_trail??[],de.push({ts:U,action:"updated",prev_outcome:u.outcome,grund:ke}),de.length>20&&(de=de.slice(-20)),le=!0;const z=[];if(u.outcome==="success"&&n==="failure"?z.push("\u26A0\uFE0F **Contradiction detected!** Existing lesson has outcome: `success`, but you're storing `failure`.",'The existing "success" lesson will be preserved. Only the audit trail is updated.',"If you meant to mark this as failed permanently, store a new lesson with a distinct topic slug."):u.outcome==="failure"&&n==="success"&&z.push("\u2705 **Conflict resolved!** Previous lesson was `failure` \u2014 now overwriting with `success`."),z.length>0){de[de.length-1].action="contradiction-resolved";try{const re=we(o),F=we(`resolution:${o}`);await $e(p,re,"contradicts",F,!1)}catch{}try{const re=`cachly:contradictions:${o}`,F=u.outcome==="success"&&n==="failure"?"kept-success":"overwrote-failure";await p.rpush(re,JSON.stringify({ts:U,prev_outcome:u.outcome,new_outcome:n,direction:F})),await p.ltrim(re,-50,-1),await p.expire(re,180*86400)}catch{}z.push(`\u{1F5F3}\uFE0F Run \`madc_deliberate(topic="${o}")\` to resolve via expert agent voting.`)}}catch{}else de=[{ts:U,action:"created"}];const Re=[];if(n==="failure"){const u=[],z=p.scanStream({match:"cachly:lesson:best:*",count:100});await new Promise((F,P)=>{z.on("data",H=>u.push(...H)),z.on("end",F),z.on("error",P)});const re=o.split(/[:\-_]/).filter(F=>F.length>2);for(const F of u.slice(0,50)){const P=await p.get(F);if(P)try{const H=JSON.parse(P);if(H.outcome!=="success")continue;const pe=(H.topic??"").split(/[:\-_]/).filter(W=>W.length>2);if(re.filter(W=>pe.includes(W)).length>=1&&H.topic!==o){Re.push(`\u26A0\uFE0F **"I Was Wrong"**: lesson \`${H.topic}\` (success, ${H.severity??"major"}) might have prevented this failure.`,` \u2192 Use \`recall_best_solution(topic="${H.topic}")\` before next attempt.`,` \u2192 To mark it critical: \`learn_from_attempts(topic="${H.topic}", ..., severity="critical")\``);break}}catch{}}}for(const u of r){const z=`cachly:dep:${u}`,re=await p.get(z),F=O(re,[]);F.includes(o)||F.push(o),await p.set(z,JSON.stringify(F),"EX",90*86400)}const Se={topic:o,outcome:n,what_worked:K,what_failed:ne,context:d,severity:h,file_paths:a,commands:s,tags:C,depends_on:r,...b?{author:b}:{},source:Ht(),...m?{service:m}:{},...A?{group:String(A).toLowerCase().trim()}:{},visibility:v,recall_count:ye,...se?{last_recalled_at:se}:{},...G&&!Number.isNaN(Date.parse(String(G)))?{gilt_ab:new Date(String(G)).toISOString()}:{},...Q.angaben.length?{gilt_fuer:Q.angaben}:{},ts:U,verified_at:n==="success"||n==="partial"?U:void 0,confidence:1,audit_trail:de,version:3},ve=String(e??"").trim();let Ee="",Y=null;ve===o&&ve?Ee="\u26A0\uFE0F `ersetzt` points to the same topic \u2014 for a correction within one topic, an update with `grund` is enough. The link was ignored.":ve&&(Y=await p.get(`cachly:lesson:best:${ve}`),Y?O(Y,{}).ersetzt===o?(Y=null,Ee=`\u26A0\uFE0F \`${ve}\` already replaces \`${o}\` \u2014 two lessons cannot replace each other. The link was NOT stored. If this version is the newer one: remove the link in \`${ve}\` first, then set it here.`):Se.ersetzt=ve:Ee=`\u26A0\uFE0F The lesson to replace, \`${ve}\`, does not exist \u2014 the link was NOT stored. \`smart_recall\` shows topic names.`);let Ae="";if(!ve)try{const u=H=>{const pe=O(H.content,{});return{topic:pe.topic??H.key.slice(19),what_worked:pe.what_worked}},[z,re]=await Promise.all([Xe(p,["cachly:lesson:best:*"],`${o} ${K}`.slice(0,300),5),Ds(p,["cachly:lesson:best:*"],a??[])]),F=new Set,P=[...z,...re].filter(H=>H.key.startsWith("cachly:lesson:best:")).filter(H=>!F.has(H.key)&&F.add(H.key)).map(u);Ae=nn(o,K,P)??""}catch{}if(n==="failure")try{const z=(await p.lrange(pt(_),0,199)).map(P=>O(P,{ts:"",frage:"",geliefert:[]})).filter(P=>P.ts!==""),re=(await Xe(p,["cachly:lesson:best:*"],`${o} ${ne||K}`.slice(0,300),5)).filter(P=>P.key.startsWith("cachly:lesson:best:")).map(P=>{const H=O(P.content,{});return{topic:H.topic??P.key.slice(19),what_worked:H.what_worked}}),F=un(o,String(ne||""),z,re);await p.hincrby(zt(_),F,1)}catch{}const Ie=dn([K,ne,i].filter(Boolean).join(" ").slice(0,2e3));Ie&&(Se.entscheidung=Ie);const Le=fo(o,U,K);Se.beleg=Le;const Me=JSON.stringify(Se);p.set(`cachly:stats:born_at:${_}`,U,"EX",365*86400,"NX").catch(()=>{});const g=`cachly:lessons:${o}`;await p.rpush(g,Me),await p.ltrim(g,-100,-1),await p.expire(g,cn*86400);let w=!1;if(n==="success"||n==="partial"?(await p.set(`cachly:lesson:best:${o}`,Me),w=!0):ge||(await p.set(`cachly:lesson:best:${o}`,Me),w=!0),Y&&w)try{const u=JSON.parse(Y);u.ersetzt_durch=o,u.gilt_bis=U,u.ersetzt_am=U,ke&&(u.ersetzt_grund=ke.slice(0,200)),await p.set(`cachly:lesson:best:${ve}`,JSON.stringify(u))}catch{}w&&At();let L=()=>{};const R=new Promise(u=>{L=u});w&&Oe()||L(null),w&&Oe()&&(await p.sadd(Ke,o).catch(()=>{}),await p.sadd(ft,o).catch(()=>{}),(Te==="cachly"||Te==="ollama")&&await p.sadd(Qe,o).catch(()=>{}),(async()=>{const u=O(Me,{});try{const z=await xe(Ge(u),{geduld:"lang"});if(z?.length?(await p.set(`${Lt}${o}`,De(z)),await p.srem(Ke,o).catch(()=>{}),L(await Dn(p,{...u,topic:o},z,gt(p).vektor))):L(null),Te==="cachly"||Te==="ollama")try{const re=await xe(Ge(u),{geduld:"lang",modell:qe});re?.length&&(await p.set(`${Fe}${o}`,De(re)),await p.srem(Qe,o).catch(()=>{}))}catch{}}catch(z){L(null),await p.sadd(Ke,o).catch(()=>{}),await Ze(p,it,`Volltext-Vektor nicht geschrieben (${Te}): ${at(z)} \u2014 die Lektion ist gespeichert, der Bedeutungsabgleich uebergeht sie.`)}try{const z=await xe(Bt(o),{geduld:"lang"});z?.length&&(await p.set(`${ut}${o}`,De(z)),await p.srem(ft,o).catch(()=>{}))}catch(z){await Ze(p,it,`Namens-Vektor nicht geschrieben (${Te}): ${at(z)} \u2014 siehe oben, die Lektion selbst ist gespeichert.`)}try{await Zs(p,o,u,z=>xe(z,{geduld:"lang"}))}catch(z){await Ze(p,it,`Eingaenge nicht geschrieben (${Te}): ${at(z)} \u2014 siehe oben, die Lektion selbst ist gespeichert.`)}await cs(p,o,3),L(null)})());try{const u="cachly:session:decision-log",z=JSON.stringify({ts:U,topic:o,outcome:n,what_worked:K.slice(0,120)});await p.rpush(u,z),await p.ltrim(u,-50,-1)}catch{}let ee=null;try{const u=we(o),z=o.split(":")[0]??"unknown";await je(p,u,z,z);for(const F of C){const P=we(`tag:${F}`);await je(p,P,"tag","tag"),await $e(p,u,"co-occurs",P,n==="success",n==="partial")}for(const F of r){const P=we(F);await $e(p,u,"requires",P,!0)}if((z==="fix"||z==="debug")&&(n==="success"||n==="partial")){const F=ne||d||"",P=F?tt(F):null;if(P){const H=we(`problem:${P}`);await je(p,H,"problem","problem"),await $e(p,u,"fixes",H,n==="success",n==="partial")}}if(n==="failure"&&(ne||K)){const P=tt(ne||K);if(P){const H=we(`cause:${P}`);await je(p,H,"cause","cause"),await $e(p,u,"causes",H,!1)}}if(n==="failure"){const F=await p.smembers(`cachly:ckg:idx:from:${u}`);for(const P of F){const H=await p.get(P);if(!H)continue;const pe=O(H,null);if(pe&&pe.edgeType==="fixes"&&pe.confidence>.7&&pe.trials>=3){ee=`\u26A0\uFE0F **belief_conflict** on \`${o}\`: previously confirmed fix (confidence ${pe.confidence.toFixed(2)}, n=${pe.trials}) now reports failure. Both beliefs retained as \`contested\`. Use \`brain_resolve_conflict(instance_id="...", topic="${o}", winner="success"|"failure")\` to arbitrate, or \`ckg_inspect(concept="${u}")\` to review.`;const _e=`cachly:ckg:conflict:${u}`;await p.set(_e,JSON.stringify({topic:o,concept_id:u,detected_at:new Date().toISOString(),fix_confidence:pe.confidence,fix_trials:pe.trials,failure_outcome:n,reported_by:b||"unknown",what_failed:ne||"",resolved:!1}),"EX",3600*24*90)}}}if(n==="success"&&(z==="fix"||z==="debug")){const F=await p.smembers(`cachly:ckg:idx:from:${u}`);for(const P of F.slice(0,10)){const H=await p.get(P);if(!H)continue;const pe=O(H,null);if(!pe||pe.edgeType!=="fixes")continue;const _e=await p.smembers(`cachly:ckg:idx:from:${pe.to}`);for(const W of _e.slice(0,5)){const me=await p.get(W);if(!me)continue;const fe=O(me,null);fe&&fe.edgeType==="co-occurs"&&(fe.successes=(fe.successes||0)+.1,fe.trials=(fe.trials||0)+.1,fe.confidence=(fe.successes+1)/(fe.trials+2),fe.last_updated=new Date().toISOString(),await p.set(W,JSON.stringify(fe)))}}}if(Math.random()<.01){const F=Date.now()-7776e6,P=await p.smembers(`cachly:ckg:idx:from:${u}`);for(const H of P){const pe=await p.get(H);if(!pe)continue;const _e=O(pe,null);_e&&_e.trials<3&&_e.last_updated&&new Date(_e.last_updated).getTime()<F&&(_e.confidence=_e.confidence*.9,_e.last_updated=new Date().toISOString(),await p.set(H,JSON.stringify(_e)))}}}catch{}if(b){p.set(`cachly:agents:active:${b}`,JSON.stringify({author:b,last_topic:o,last_outcome:n,ts:new Date().toISOString()}),"EX",3600).catch(()=>{});try{const u=o.split(":")[0]??"unknown",z=await ws(p,Ut(b),u),re=we(o);await $e(p,z,"authored",re,n==="success",n==="partial");for(const F of a.slice(0,8)){const P=await kt(p,F);await $e(p,z,"touched",P,!0),await ys(p,P,z)}}catch{}}if(m&&typeof m=="string"&&m.trim())try{const u=o.split(":")[0]??"unknown",z=y==="system"?"system":"service",re=await _s(p,m.trim(),u,z),F=we(o);if(await $e(p,F,"affects",re,n==="success",n==="partial"),b){const P=`person:${we(Ut(b))}`;await $e(p,P,"operates",re,!0)}for(const P of a.slice(0,8)){const H=`file:${we(P)}`;await $e(p,H,"runs_in",re,!0)}}catch{}if(b&&(n==="success"||n==="partial")){const u={topic:o,outcome:n,what_worked:K,what_failed:ne??"",severity:h,author:b,file_paths:a,commands:s,tags:[...new Set([...C,"team"])],timestamp:new Date().toISOString(),recall_count:0,version:2,auto_propagated:!0},z=`cachly:lessons:${o}`;p.rpush(z,JSON.stringify(u)).catch(()=>{})}const q=n==="success"?"\u2705":n==="partial"?"\u26A0\uFE0F":"\u274C",ie=h==="critical"?"\u{1F534}":h==="major"?"\u{1F7E1}":"\u{1F7E2}",X=le?"updated":"stored",ue=await On(R);return[`${q} **Lesson ${X}:** \`${o}\` (${n}) ${ie} ${h} \xB7 receipt: \`${Le}\``,f.funde.length?`\u{1F512} **${f.funde.length} secret(s) redacted** before saving (${f.funde.join("; ")}). The value is not in the Brain \u2014 refer to where it lives instead (e.g. an Infisical path or a variable name).`:"",wo(t),ee??"","",`**What worked:** ${K}`,ne?`**What failed:** ${ne}`:"",d?`**Context:** ${d}`:"",a.length>0?`**Files:** ${a.map(u=>`\`${u}\``).join(", ")}`:"",s.length>0?`**Commands:** ${s.map(u=>`\`${u}\``).join(", ")}`:"",C.length>0?`**Tags:** ${C.map(u=>`#${u}`).join(" ")}`:"","",le?`\u267B\uFE0F Updated (recall count: ${ye} \xB7 audit entries: ${de.length})`:`\u{1F4A1} Recall later with \`recall_best_solution(topic="${o}")\``,b&&(n==="success"||n==="partial")?`\u{1F465} Auto-shared with team (by _${b}_${T.quelle!=="uebergeben"?` \xB7 from ${T.quelle}`:""}) \u2014 visible in \`team_recall\``:"",S??"",r.length>0?`\u{1F517} Depends on: ${r.map(u=>`\`${u}\``).join(", ")} \u2192 trace with \`trace_dependency\``:"",Ae,Ln(ue,Ae),Q.angaben.length?`\u{1F4E6} Applies to ${Q.angaben.map(u=>`\`${u}\``).join(", ")}${Q.uebernommen?" (carried over from the previous version)":""} \u2014 if the project does not match, the lesson ranks last in recall and is marked.`:"",...Q.fehler.map(u=>`\u26A0\uFE0F gilt_fuer not stored, ${u}. Format: package@range, for example next@<15 or python:fastapi@>=0.100 <0.110.`),Y&&w?ke?`\u21B7 Replaces \`${ve}\` \u2014 recall hides the old version and names its successor with the reason.`:`\u21B7 Replaces \`${ve}\` \u2014 recall hides the old version and names its successor. Without \`grund\` it says 'No reason recorded' \u2014 one line (why the old one was wrong) makes the link checkable.`:Ee,...J,...Re,k>0?`\u{1F5D3}\uFE0F ${k} relative time reference${k===1?"":"s"} dated (e.g. "yesterday" \u2192 "yesterday (${new Date(V.getTime()-864e5).toISOString().slice(0,10)})") \u2014 the original text stays unchanged, the date stands next to it.`:"",co(K)??""].filter(u=>u!=="").join(`
3
+ `)}case"lesson_verified":{const{instance_id:f}=M,t=await j(f),l=new Date().toISOString(),_=M,o=ze({geprueft_mit:_.geprueft_mit,befund:_.befund}),n=ms({..._,...o.felder},l);if(!n.ok)return[`\u26A0\uFE0F ${n.grund}`,"","Beispiel:","```",'lesson_verified(topic="deploy:bau-hero-node1", haelt=true,',' geprueft_mit="curl -s http://127.0.0.1:3220/bereit")',"```","Bei haelt=false gehoert `befund` dazu: was hast du stattdessen gesehen?"].join(`
4
+ `);const E=n.eintrag,$=`cachly:lesson:best:${E.topic}`,i=await t.get($);if(!i)return`\u{1F4ED} Keine Lektion \`${E.topic}\` \u2014 nichts zu pruefen. Tippfehler im Themennamen?`;const h=O(i,{}),a=gs(h,E);return await t.set($,JSON.stringify(o.verdacht.length?{...a,tags:yo(a.tags,o.verdacht)}:a)),At(),E.haelt?[`\u2705 \`${E.topic}\` ist wieder frisch \u2014 geprueft am ${l.slice(0,10)}.`,E.geprueft_mit?` geprueft mit: \`${E.geprueft_mit}\``:"","","Das ist die einzige Handlung, die `verified_at` setzt. Ein Abruf tut es nicht.",...He(o)].filter(Boolean).join(`
5
+ `):[`\u26A0\uFE0F \`${E.topic}\` ist als fraglich markiert.`,` Befund: ${E.befund}`,"","Geloescht wurde nichts \u2014 eine Pruefung kann aus Gruenden fehlschlagen,","die mit der Lektion nichts zu tun haben. Wenn du weisst, was jetzt gilt:",`\`learn_from_attempts(topic="${E.topic}", \u2026, grund="\u2026")\`.`,...He(o)].join(`
6
+ `)}case"recall_feedback":{const{instance_id:f}=M,t=await j(f),l=bn($n(M),new Date().toISOString());if(!l.ok)return[`\u26A0\uFE0F ${l.grund}`,"","Example:","```",'recall_feedback(instance_id="\u2026", query="why does the deploy abort",',' topic="ci:timeout-reports-cancelled", helped=true, rank=0)',"```","rank=0 means: the lesson was NOT in the answer at all.","That is the most valuable feedback there is."].join(`
7
+ `);const _=l.eintrag,o=`cachly:wirkung:${f}`;await t.rpush(o,JSON.stringify(_));const E=(await t.lrange(o,0,-1).catch(()=>[])).map(s=>O(s,null)).filter(Boolean),$=kn(E),i=vn(_),h={"fehlender-treffer":"It helped but was not in the answer at all \u2014 the search never picked it up. That is the most valuable case.","zu-weit-hinten":`It helped but was only at rank ${_.platz} \u2014 the ranking was too weak.`,"falsch-und-sicher":`It was at rank ${_.platz} and did not help \u2014 the ranking was confident and wrong.`,bestaetigung:"It was near the top and helped. Confirmed.",unauffaellig:"It did not help and was far down. Says little."},a=$.trefferquote===null?"no helpful feedback yet":`${($.trefferquote*100).toFixed(0)} % of the helpful lessons were in the top three`;return[`\u2705 Noted: **${_.thema}** \u2014 ${_.geholfen?"helped":"did not help"}, rank ${_.platz===0?"\u2014 (not in the answer)":_.platz}`,"",h[i],"",`Feedback so far: ${$.gesamt}, ${a}.`,`Of these: ${$.nachLehrwert["fehlender-treffer"]} not found \xB7 ${$.nachLehrwert["zu-weit-hinten"]} too far down \xB7 ${$.nachLehrwert["falsch-und-sicher"]} confident and wrong.`].join(`
8
+ `)}case"recall_best_solution":{const f=Date.now(),{instance_id:t,topic:l,author:_}=M,o=await Ws(t,ae,`\u{1F4ED} No lessons found for \`${l}\`.`);if(o)return o;const n=await j(t);try{const s=await n.get(`cachly:letzter-recall:${t}`),C=s?O(s,null):null,r=Sn(C,l,new Date().toISOString());r&&(await n.rpush(`cachly:wirkung:${t}`,JSON.stringify(r)),await n.del(`cachly:letzter-recall:${t}`))}catch{}const E=await n.get(`cachly:lesson:best:${l}`);if(E){const s=O(E,null);if(!s)return`\u26A0\uFE0F Lesson data for \`${l}\` is corrupted. Re-store it with \`learn_from_attempts\`.`;const C=s.group;if(C){const se=_?await Nt(n,t,_).catch(()=>new Set):new Set,de=_?await ct(n,t,_).catch(()=>null)==="admin":!1;if(!Et(C,se,de))return`\u{1F4ED} No lessons found for \`${l}\`. Use \`learn_from_attempts\` after solving it.`}const r=$t(s),c=s.verified_at??s.ts,v=(Date.now()-new Date(c).getTime())/864e5,m=s.commands,y=us(r,v,Array.isArray(m)&&typeof m[0]=="string"?m[0]:void 0,s.pruefung_gefallen_am?{am:s.pruefung_gefallen_am,befund:s.pruefung_befund}:void 0),A=new Date().toISOString(),I={...s,recall_count:(s.recall_count??0)+1,last_recalled_at:A};await n.set(`cachly:lesson:best:${l}`,JSON.stringify(I)),yt(n),mt(n,String(l??""),Date.now()-f);const e=St(s)?vt(s):0;e>0&&n.incrbyfloat(`cachly:stats:time_saved_mins:${t}`,e).catch(()=>{});const G=s.severity==="critical"?"\u{1F534}":s.severity==="major"?"\u{1F7E1}":s.severity?"\u{1F7E2}":"",ce=s.audit_trail??[],oe=ce.filter(se=>se&&typeof se.grund=="string"&&se.grund.trim().length>0).slice(-3).map(se=>` \xB7 ${se.ts?new Date(se.ts).toLocaleDateString("de-DE"):"?"}: ${se.grund.trim()}`),he=ce.length>1?[`_Audit: ${ce.length} changes \xB7 stored ${new Date(s.ts).toLocaleDateString("de-DE")}_`,...oe.length>0?["**Warum zuletzt geaendert:**",...oe]:[]].join(`
9
+ `):"",te=(Date.now()-new Date(s.ts).getTime())/864e5,p=te>60?`\u{1F4AD} _Remember when you solved this ${Math.round(te/30)} months ago? Still works._`:"",V=I.recall_count===3&&!s.pinned?"\u{1F4CC} **This lesson came up in 3 answers so far.** If you need it often, pin it: add `pinned: true` via `learn_from_attempts` to always surface it first.":"",U=s.authors??(s.author?[s.author]:[]),Q=[...new Set(U.filter(Boolean))];let Z="";Q.length>=2&&(Z=`\u2705 **Team-verified** \u2014 confirmed by ${Q.length} developers.`);const K=s.ersetzt_durch,ne=s.ersetzt_am?` (on ${s.ersetzt_am.slice(0,10)})`:"",d=K?await n.get(`cachly:lesson:best:${K}`):null,k=String(s.ersetzt_grund??"").trim(),T=k?` Reason: ${k}`:" No reason recorded.",N=d?!We(Be(d)):!1,b=K?d?N?`\u{1F6AB} **THIS VERSION IS REPLACED**${ne} \u2014 the successor lesson is private, not visible to the team. The text below stays readable as history; its author knows the current version.`:`\u{1F6AB} **THIS VERSION IS REPLACED**${ne} \u2014 the current answer is in \`recall_best_solution(topic="${K}")\`.${T} The text below stays readable as history.`:`\u26A0\uFE0F **REPLACEMENT POINTS NOWHERE**${ne} \u2014 the successor \`${K}\` has been deleted. This version applies again as the best EXISTING answer; confirm it when you can (learn_from_attempts with grund).`:"",S=s.gilt_ab,x=s.gilt_bis,B=S?`\u{1F4C5} Valid since ${String(S).slice(0,10)}.`:"",J=s.entscheidung,le=J?.gegen?jt({gegen:J.gegen,grund:J.grund??null}):"",ye=x&&Date.parse(String(x))<Date.now()?`\u23F3 **THIS FACT WAS VALID UNTIL ${String(x).slice(0,10)}** \u2014 it is history, not current information.`:"";return[Zt(s),b,ye,rn(s.gilt_fuer),B,le,p,Z,`${y} **Best solution for \`${l}\`** ${G}${s.severity?` (${s.severity})`:""} \xB7 in ${I.recall_count} answer${I.recall_count===1?"":"s"} so far`,Rn(s.author,s.source),"",s.what_worked?`**What worked:** ${s.what_worked}`:"",s.what_failed?`**What failed (avoid this):** ${s.what_failed}`:"",s.context?`**Context:** ${s.context}`:"",(s.file_paths??[]).length>0?`**Files:** ${(s.file_paths??[]).map(se=>`\`${se}\``).join(", ")}`:"",(s.commands??[]).length>0?`**Commands:** ${(s.commands??[]).map(se=>`\`${se}\``).join(", ")}`:"",(s.tags??[]).length>0?`**Tags:** ${(s.tags??[]).map(se=>`#${se}`).join(" ")}`:"",he,V].filter(se=>se!=="").join(`
10
+ `)}const $=[],i=n.scanStream({match:"cachly:lesson:best:*",count:100});await new Promise((s,C)=>{i.on("data",r=>$.push(...r)),i.on("end",s),i.on("error",C)});const h=$.filter(s=>s.toLowerCase().includes(l.toLowerCase()));if(h.length===0){const s=`cachly:lessons:${l}`,C=await n.lrange(s,-3,-1);if(C.length>0){const c=C.map(v=>O(v,null)).filter(Boolean).map(v=>`- ${v.outcome==="success"?"\u2705":"\u274C"} ${v.what_worked.slice(0,120)} (${new Date(v.ts).toLocaleDateString("de-DE")})`);return`\u26A0\uFE0F No successful solution for \`${l}\` yet. Last attempts:
11
11
 
12
12
  ${c.join(`
13
- `)}`}return`\u{1F4ED} No lessons found for \`${h}\`. Use \`learn_from_attempts\` after solving it.`}const a=[];for(const s of l.slice(0,5)){const C=await n.get(s);if(!C)continue;const r=O(C,null);if(!r)continue;const c=Zt(r);a.push(`${c?`${c}
14
- `:""}**\`${r.topic}\`** \u2014 ${r.what_worked.slice(0,400)}${r.what_worked.length>400?" \u2026":""}`)}return`\u{1F50D} **Partial matches for \`${h}\`:**
13
+ `)}`}return`\u{1F4ED} No lessons found for \`${l}\`. Use \`learn_from_attempts\` after solving it.`}const a=[];for(const s of h.slice(0,5)){const C=await n.get(s);if(!C)continue;const r=O(C,null);if(!r)continue;const c=Zt(r);a.push(`${c?`${c}
14
+ `:""}**\`${r.topic}\`** \u2014 ${r.what_worked.slice(0,400)}${r.what_worked.length>400?" \u2026":""}`)}return`\u{1F50D} **Partial matches for \`${l}\`:**
15
15
 
16
16
  ${a.join(`
17
17
 
18
- `)}`}case"smart_recall":{const d=Date.now(),{instance_id:t,query:h,threshold:b=.78,author:o="",context_files:n=[],limit:E}=M,$=Fn(E),i=Array.isArray(n)?n.filter(g=>typeof g=="string"):[];if(ke("smart_recall: Anfang"),await Mt(t,ne)){const g=Date.now()-d;return j(t).then(w=>os(w,t,String(h??""),g)).catch(()=>{}),ke("smart_recall: neues Brain startet noch \u2014 sofort geantwortet"),Rt(String(h??""),{startet:!0,...await ht(t)})}const l=await j(t);ke("smart_recall: Verbindung steht");const{vektor:a,namen:s,zweit:C,eingang:r,seltenheit:c}=gt(l);cs(l,void 0,2);let v=new Set,m=!1;o&&(v=await Nt(l,t,o).catch(()=>new Set),m=await ct(l,t,o).catch(()=>null)==="admin");const y=["cachly:ctx:*","cachly:lesson:best:*","cachly:idx:*"],A=await js(l,y);if(!A)return os(l,t,String(h??""),Date.now()-d),ke("smart_recall: Brain leer \u2014 sofort geantwortet"),Rt(String(h??""),{startet:!1,...await ht(t)});const I=A.docs.reduce((g,w)=>g+(w.key.startsWith("cachly:lesson:best:")?1:0),0),e=await Xe(l,y,h,25);ke(`smart_recall: Wortsuche fertig (${e.length} Treffer)`);const H=Gs(e);let ae=!1,ce=null;const de=await(async()=>{if(!Oe())return ot(Ts,"kein Einbettungsdienst eingerichtet \u2014 die Suche laeuft nur ueber Woerter"),H;try{if(await a.aktualisiere(l),ke(`smart_recall: Vektorbestand geladen (${a.groesse})`),a.groesse===0)return I>0&&ot(xs,"keine einzige Lektion hat eine Einbettung \u2014 die Suche laeuft nur ueber Woerter. Nachruesten mit eingaenge-nachruesten, pruefen mit brain_doctor"),H;const g=await xe(h);if(ke("smart_recall: Frage eingebettet"),!g?.length)return ot(Ns,"die Frage liess sich nicht einbetten \u2014 diese Suche laeuft nur ueber Woerter"),H;await s.aktualisiere(l),await r.aktualisiere(l),await c.aktualisiere(l),await C.aktualisiere(l);const w=a.groesse>0?C.groesse/a.groesse:0;w<Gt&&a.groesse>0&&vo(l,3);const B=w>=Gt?xe(h,{modell:Ve}).catch(()=>null):null,R=H.filter(F=>F.key.startsWith("cachly:lesson:best:")).map(F=>F.key.slice(19)),ee=F=>Math.max(a.naehe(g,F),s.naehe(g,F)),U=[...c.themen()],oe=(U.length>0?U:a.aehnlichste(g,500).map(F=>F.topic)).map(F=>({t:F,n:ee(F)})).sort((F,me)=>me.n-F.n).slice(0,Vt).map(F=>F.t),X=[...new Set([...R,...oe])],he=oe.slice(0,3).map(F=>a.rohvektor(F)).filter(Boolean),f=he.length?pn(g,he):g,z=Ft(h),re=c.statistik,G=X.map(F=>{const me=new Set([...Ft(c.textVon(F))].map(mn));return{naeheText:a.naehe(g,F),naeheThema:s.naehe(g,F),naeheRueckkopplung:a.naehe(f,F),seltenheitsDeckung:re?re.deckung(z,me):0,besterZeuge:re?re.besterZeuge(z,me):0}});let P=fn(G,gn);{const F=[...P].sort((me,ue)=>ue-me);ce=F.length>1?F[0]-F[1]:null}if(r.groesse>0){const F=X.map(ue=>{const Ce=r.besteNaehe(g,ue);return Ce>=Pn?Ce:-2}),me=Wt(F);P=P.map((ue,Ce)=>ue+jn*me[Ce])}if(B){const F=await B;if(F?.length){const me=Wt(X.map(ue=>C.naehe(F,ue)));P=P.map((ue,Ce)=>ue+Nn*me[Ce])}}let V=X.map((F,me)=>({t:F,p:P[me]})).sort((F,me)=>me.p-F.p).map(F=>F.t);if(Js(t)&&V.length>1){const F=V.slice(0,Ht),me=V.slice(Ht),ue=new Map(X.map((Ue,Je)=>[Ue,P[Je]]));ke("smart_recall: Leser Anfang");const Ce=await Vs(h,F.map(Ue=>c.leserTextVon(Ue)),{instanceId:t});ke(`smart_recall: Leser ${Ce?"fertig":"ohne Ergebnis"}`),Ce&&(V=[...qs(F.map(Je=>ue.get(Je)??0),Ce).map(Je=>F[Je]),...me])}const pe=new Map(H.map(F=>[F.key,F])),be=[];for(const F of V){const me=`cachly:lesson:best:${F}`,ue=pe.get(me);if(ue){be.push(ue),pe.delete(me);continue}const Ce=await l.get(me).catch(()=>null);Ce&&be.push({key:me,content:Ce,score:0,matchedWords:[],wortBelege:0,finalScore:0,qualityBoost:1})}return be.push(...pe.values()),Sn&&vn({frage:h.slice(0,200),themen:X,ausWortsuche:R.length,ausSinnsuche:oe.length,obergrenze:Vt,gezeigt:V.slice(0,10)}),ae=!0,be}catch(g){return await Ze(l,Es,`der Bedeutungspfad brach ab (${g instanceof Error?g.message:String(g)}) \u2014 diese Suche lief nur ueber Woerter`),H}})();ke("smart_recall: Bedeutungspfad fertig"),yt(l),mt(l,String(h??""),Date.now()-d);{const g=de.filter(w=>w.key.startsWith("cachly:lesson:best:")).slice(0,3).map(w=>w.key.slice(19));if(g.length>0){const w={ts:new Date().toISOString(),themen:g};(async()=>{const B=Dt(t);await l.lpush(B,JSON.stringify(w)),await l.ltrim(B,0,hn-1)})().catch(()=>{})}{const w={ts:new Date().toISOString(),frage:h.slice(0,200),geliefert:g};(async()=>{const B=pt(t);await l.lpush(B,JSON.stringify(w)),await l.ltrim(B,0,Pt-1)})().catch(()=>{})}}if(Oe()&&!Tt()){const g=de.filter(w=>w.key.startsWith("cachly:lesson:best:")).slice(0,3).map(w=>w.key.slice(19));sn(l,h,g,xe).catch(()=>{})}const le=de.filter(g=>g.key.startsWith("cachly:lesson:best:"));let p=0,W=0;for(const g of le.slice(0,5)){const w=await l.get(g.key).catch(()=>null);if(w){const B=O(w,null);if(B?.state==="archived")continue;if(B){const R=new Date().toISOString(),ee={...B,recall_count:(B.recall_count??0)+1,last_recalled_at:R};Tt()||l.set(g.key,JSON.stringify(ee)).catch(()=>{});const U=St(B)?vt(B):0;if(U>0&&l.incrbyfloat(`cachly:stats:time_saved_mins:${t}`,U).catch(()=>{}),B.author&&o&&B.author!==o){p++,l.sadd(`cachly:stats:reuse_pairs:${t}`,`${o}<-${B.author}`).catch(()=>{});const oe=`person:${_e(o)}`,X=`person:${_e(B.author)}`;ve(l,oe,"collaborates",X,!0).catch(()=>{}),ve(l,X,"collaborates",oe,!0).catch(()=>{})}B.outcome==="success"&&W++}}}W>0&&l.set(`cachly:stats:first_recall_at:${t}`,new Date().toISOString(),"EX",365*86400,"NX").catch(()=>{}),le.length>0&&(l.incr(`cachly:stats:recalls_total:${t}`).catch(()=>{}),p>0&&l.incr(`cachly:stats:cross_author_recalls:${t}`).catch(()=>{})),ke("smart_recall: Treffer gezaehlt");const Z=await ne(`/api/v1/instances/${t}`).catch(()=>null);ke("smart_recall: Tarif abgefragt");const q=!Z?.tier||Z.tier.toLowerCase()==="free",Q=[];if(!q&&Z?.vector_token&&Oe())try{const g=await xe(h),w=`https://api.cachly.dev/v1/sem/${Z.vector_token}`,B=await fetch(`${w}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedding:g,namespace:"cachly:ctx",threshold:b,top_k:5}),signal:AbortSignal.timeout(8e3)});if(B.ok){const R=await B.json();for(const ee of R.filter(U=>U.found&&U.id)){const U=ee.id.replace("ctx:","").split(":"),oe=U[0],X=U.slice(1).join(":"),he=`cachly:ctx:${oe}:${X}`,f=await l.get(he).catch(()=>null);Q.push({key:he,similarity:ee.similarity??0,content:f??"(evicted)"})}}}catch{}const K=de.map(g=>g.score),te=K.length?Math.min(...K):0,u=K.length&&Math.max(...K)-te||1,k=g=>(g-te)/u,x=new Map,T=ae?en(de.map(g=>k(g.score))):null,_=new Map;for(const[g,w]of de.entries()){const B=k(w.score),R=T?T[g]:B;T&&x.set(w.key,g),_.set(w.key,{key:w.key,content:w.content,bm25Score:B,hausWert:R,hybridScore:R*(Q.length>0?.7:1),matchedWords:w.matchedWords,matchType:"keyword",subQuery:w.subQuery,wortBelege:w.wortBelege})}for(const g of Q){const w=_.get(g.key);w?(w.semScore=g.similarity,w.hybridScore=(w.hausWert??0)*.6+g.similarity*.4,w.matchType="hybrid"):_.set(g.key,{key:g.key,content:g.content,semScore:g.similarity,hybridScore:g.similarity*.5,matchType:"semantic"})}try{const g=lt(h).filter(R=>R.length>=4).slice(0,4),w=new Set;for(const R of g){const ee=l.scanStream({match:`cachly:ckg:node:*${R}*`,count:20});await new Promise((U,oe)=>{ee.on("data",X=>{for(const he of X.slice(0,5))w.add(he.replace("cachly:ckg:node:",""))}),ee.on("end",U),ee.on("error",oe)})}const B=new Set;for(const R of[...w].slice(0,12)){const ee=await l.smembers(`cachly:ckg:idx:to:${R}`);for(const U of ee.slice(0,6)){const oe=await l.get(U);if(!oe)continue;const X=O(oe,null);if(!X||X.edgeType!=="fixes"||X.confidence<.35||B.has(X.from))continue;B.add(X.from);const he=`cachly:lesson:best:${X.from}`,f=_.get(he);if(f)f.ckgScore=Math.max(f.ckgScore??0,X.confidence),f.hybridScore=f.hybridScore*.88+X.confidence*.12,f.matchType==="keyword"&&(f.matchType="hybrid");else{const z=await l.get(he).catch(()=>null);if(!z)continue;_.set(he,{key:he,content:z,ckgScore:X.confidence,hybridScore:X.confidence*.3,matchType:"ckg"})}}}}catch{}if(ke("smart_recall: Sinn-Dienst und CKG fertig"),i.length>0){const g=new Set(i.map(w=>w.replace(/\\/g,"/")));for(const w of _.values()){if(!w.key.startsWith("cachly:lesson:best:"))continue;const B=O(w.content,{});(Array.isArray(B.file_paths)?B.file_paths.filter(ee=>typeof ee=="string").map(ee=>ee.replace(/\\/g,"/")):[]).some(ee=>g.has(ee))&&(w.hybridScore*=1.15,w.contextBoost=!0)}}for(const g of _.values()){if(g.matchType!=="semantic"&&g.matchType!=="ckg")continue;const w=Ks({key:g.key,content:g.content});w&&(g.hybridScore*=Hs(w))}const S=[..._.values()].filter(g=>{if(!g.key.startsWith("cachly:lesson:best:"))return!0;const w=Le(g.content);return!(!We(w)||!Et(w?.group,v,m))}),N=T?Qs(S,x):S.sort((g,w)=>w.hybridScore-g.hybridScore),L=[];for(const g of N){if(!g.key.startsWith("cachly:lesson:best:"))continue;const w=O(g.content,{});w.gilt_bis&&Date.parse(String(w.gilt_bis))<Date.now()&&L.push(`\u23F3 \`${g.key.replace("cachly:lesson:best:","")}\` was valid until ${String(w.gilt_bis).slice(0,10)} \u2014 history, not a current fact.`);const B=O(g.content,{});if(B.pruefung_gefallen_am){const R=g.key.replace("cachly:lesson:best:",""),ee=String(B.pruefung_gefallen_am).slice(0,10),U=String(B.pruefung_befund??"").trim().slice(0,200);L.push(`\u274C \`${R}\` \u2014 Pruefung am ${ee} FEHLGESCHLAGEN`+(U?`: ${U}`:""))}}{const g=new Set(N.map(w=>w.key));for(let w=N.length-1;w>=0;w--){const B=N[w];if(!B.key.startsWith("cachly:lesson:best:"))continue;const R=O(B.content,{});if(!R.ersetzt_durch)continue;const ee=String(R.ersetzt_grund??"").trim(),U=B.key.replace("cachly:lesson:best:","");if(g.has(`cachly:lesson:best:${R.ersetzt_durch}`))N.splice(w,1),L.push(`\u21B7 \`${U}\` hidden \u2014 replaced by \`${R.ersetzt_durch}\`${R.ersetzt_am?` on ${R.ersetzt_am.slice(0,10)}`:""}${ee?` \u2014 reason: ${ee}`:" \u2014 no reason recorded"}`);else if(await l.get(`cachly:lesson:best:${R.ersetzt_durch}`)){const oe=Le(await l.get(`cachly:lesson:best:${R.ersetzt_durch}`));We(oe)?L.push(`\u26A0\uFE0F \`${U}\` is replaced by \`${R.ersetzt_durch}\` (not in this list) \u2014 the current answer: \`recall_best_solution(topic="${R.ersetzt_durch}")\``):L.push(`\u21B7 \`${U}\`: replaced by a version that is not visible to the team \u2014 this version stays the best visible answer.`)}else L.push(`\u21A9\uFE0F \`${U}\` was replaced by \`${R.ersetzt_durch}\` \u2014 the successor was deleted, so this version applies again (confirm it when you can).`)}}const J=on(N,i),ie=[];{await a.aktualisiere(l).catch(()=>{});const g=N.slice(0,8).filter(w=>w.key.startsWith("cachly:lesson:best:"));for(let w=0;w<g.length;w++)for(let B=w+1;B<g.length;B++){const R=g[w].key.replace("cachly:lesson:best:",""),ee=g[B].key.replace("cachly:lesson:best:",""),U=a.rohvektor(R),oe=a.rohvektor(ee);if(!U||!oe)continue;let X=0,he=0,f=0;for(let G=0;G<U.length;G++)X+=U[G]*oe[G],he+=U[G]*U[G],f+=oe[G]*oe[G];if((he>0&&f>0?X/(Math.sqrt(he)*Math.sqrt(f)):0)<En)continue;const re=G=>{const P=O(G??"",{});return String(P.verified_at??P.ts??"").slice(0,10)||"no date"};ie.push(`\u26A0\uFE0F \`${R}\` (${re(g[w].content)}) und \`${ee}\` (${re(g[B].content)}) say almost the same \u2014 check which version fits your case; the newer one is not automatically right.`)}ie.length>3&&(ie.length=3)}if(L.push(...ie),N.length>=2){const g=N[0].hybridScore,w=N[Math.min(4,N.length-1)].hybridScore,B=Math.max(0,g-w);(async()=>{try{await l.lpush("cachly:recall:spread",JSON.stringify({ts:new Date().toISOString(),spread:Number(B.toFixed(4)),n:N.length})),await l.ltrim("cachly:recall:spread",0,199)}catch{}})()}const ge=As();let se="",ye=null;if(ge!==null){se=Is();const g=N[0]?.hybridScore??0,w=Ms(N.map(B=>({key:B.key,content:B.content,punktzahl:B.hybridScore})),se,ge,g);w.weggelassenesThema!==void 0?N.splice(0,1):w.markierungGesetzt&&N[0]&&w.treffer[0]&&(N[0]={...N[0],content:w.treffer[0].content}),ye={frage:h,hoechstePunktzahl:g,themen:[],zulassungsfaehig:w.zulassungsfaehig,zulassungsGrund:w.zulassungsGrund,zuteilung:w.zuteilung,weggelassenesThema:w.weggelassenesThema,markierungGesetzt:w.markierungGesetzt}}ke("smart_recall: Notizen und Duplikate fertig");const $e=await rs(t,ne);ke("smart_recall: Abruf-Grenze abgefragt");const Ne=$e.reached,Ae=qt({ueberLimit:Ne,gesamt:N.length,volleTiefe:$??Gn}),we=process.env.CACHLY_RECALL_COMPACT==="1",fe=(()=>{const g=Number.parseInt(process.env.CACHLY_RECALL_COMPACT_HITS??"",10);return Number.isFinite(g)?Math.min(20,Math.max(1,g)):5})(),Se=we?Math.min(fe,Ae.sichtbar):Ae.sichtbar,Y=[`\u{1F9E0} **Smart Recall** for: _"${h}"_
19
- `];$e.goodwillMessage&&Y.push(`${$e.goodwillMessage}
18
+ `)}`}case"smart_recall":{const f=Date.now(),{instance_id:t,query:l,threshold:_=.78,author:o="",context_files:n=[],limit:E}=M,$=Hn(E),i=Array.isArray(n)?n.filter(g=>typeof g=="string"):[];if(be("smart_recall: Anfang"),!_n(l))return'\u26A0\uFE0F smart_recall needs `query`: one line about the task or the error, e.g. query="login redirect loop after OAuth callback".';if(await Mt(t,ae)){const g=Date.now()-f;return j(t).then(w=>os(w,t,String(l??""),g)).catch(()=>{}),be("smart_recall: neues Brain startet noch \u2014 sofort geantwortet"),Rt(String(l??""),{startet:!0,...await ht(t)})}const h=await j(t);be("smart_recall: Verbindung steht");const{vektor:a,namen:s,zweit:C,eingang:r,seltenheit:c}=gt(h);cs(h,void 0,2);let v=new Set,m=!1;o&&(v=await Nt(h,t,o).catch(()=>new Set),m=await ct(h,t,o).catch(()=>null)==="admin");const y=["cachly:ctx:*","cachly:lesson:best:*","cachly:idx:*"],A=await js(h,y);if(!A)return os(h,t,String(l??""),Date.now()-f),be("smart_recall: Brain leer \u2014 sofort geantwortet"),Rt(String(l??""),{startet:!1,...await ht(t)});const I=A.docs.reduce((g,w)=>g+(w.key.startsWith("cachly:lesson:best:")?1:0),0),e=await Xe(h,y,l,25);be(`smart_recall: Wortsuche fertig (${e.length} Treffer)`);const G=Gs(e);let ce=!1,oe=null;const he=await(async()=>{if(!Oe())return ot(Ts,"kein Einbettungsdienst eingerichtet \u2014 die Suche laeuft nur ueber Woerter"),G;try{if(await a.aktualisiere(h),be(`smart_recall: Vektorbestand geladen (${a.groesse})`),a.groesse===0)return I>0&&ot(xs,"keine einzige Lektion hat eine Einbettung \u2014 die Suche laeuft nur ueber Woerter. Nachruesten mit eingaenge-nachruesten, pruefen mit brain_doctor"),G;const g=await xe(l);if(be("smart_recall: Frage eingebettet"),!g?.length)return ot(Ns,"die Frage liess sich nicht einbetten \u2014 diese Suche laeuft nur ueber Woerter"),G;await s.aktualisiere(h),await r.aktualisiere(h),await c.aktualisiere(h),await C.aktualisiere(h);const w=a.groesse>0?C.groesse/a.groesse:0;w<Gt&&a.groesse>0&&xo(h,3);const L=w>=Gt?xe(l,{modell:qe}).catch(()=>null):null,R=G.filter(W=>W.key.startsWith("cachly:lesson:best:")).map(W=>W.key.slice(19)),ee=W=>Math.max(a.naehe(g,W),s.naehe(g,W)),q=[...c.themen()],ie=(q.length>0?q:a.aehnlichste(g,500).map(W=>W.topic)).map(W=>({t:W,n:ee(W)})).sort((W,me)=>me.n-W.n).slice(0,qt).map(W=>W.t),X=[...new Set([...R,...ie])],ue=ie.slice(0,3).map(W=>a.rohvektor(W)).filter(Boolean),u=ue.length?pn(g,ue):g,z=Ft(l),re=c.statistik,F=X.map(W=>{const me=new Set([...Ft(c.textVon(W))].map(mn));return{naeheText:a.naehe(g,W),naeheThema:s.naehe(g,W),naeheRueckkopplung:a.naehe(u,W),seltenheitsDeckung:re?re.deckung(z,me):0,besterZeuge:re?re.besterZeuge(z,me):0}});let P=fn(F,gn);{const W=[...P].sort((me,fe)=>fe-me);oe=W.length>1?W[0]-W[1]:null}if(r.groesse>0){const W=X.map(fe=>{const Ce=r.besteNaehe(g,fe);return Ce>=Fn?Ce:-2}),me=Wt(W);P=P.map((fe,Ce)=>fe+Gn*me[Ce])}if(L){const W=await L;if(W?.length){const me=Wt(X.map(fe=>C.naehe(W,fe)));P=P.map((fe,Ce)=>fe+In*me[Ce])}}let H=X.map((W,me)=>({t:W,p:P[me]})).sort((W,me)=>me.p-W.p).map(W=>W.t);if(Js(t)&&H.length>1){const W=H.slice(0,Kt),me=H.slice(Kt),fe=new Map(X.map((Ue,Je)=>[Ue,P[Je]]));be("smart_recall: Leser Anfang");const Ce=await qs(l,W.map(Ue=>c.leserTextVon(Ue)),{instanceId:t});be(`smart_recall: Leser ${Ce?"fertig":"ohne Ergebnis"}`),Ce&&(H=[...Vs(W.map(Je=>fe.get(Je)??0),Ce).map(Je=>W[Je]),...me])}const pe=new Map(G.map(W=>[W.key,W])),_e=[];for(const W of H){const me=`cachly:lesson:best:${W}`,fe=pe.get(me);if(fe){_e.push(fe),pe.delete(me);continue}const Ce=await h.get(me).catch(()=>null);Ce&&_e.push({key:me,content:Ce,score:0,matchedWords:[],wortBelege:0,finalScore:0,qualityBoost:1})}return _e.push(...pe.values()),Tn&&xn({frage:l.slice(0,200),themen:X,ausWortsuche:R.length,ausSinnsuche:ie.length,obergrenze:qt,gezeigt:H.slice(0,10)}),ce=!0,_e}catch(g){return await Ze(h,Es,`der Bedeutungspfad brach ab (${g instanceof Error?g.message:String(g)}) \u2014 diese Suche lief nur ueber Woerter`),G}})();be("smart_recall: Bedeutungspfad fertig"),yt(h),mt(h,String(l??""),Date.now()-f);{const g=he.filter(w=>w.key.startsWith("cachly:lesson:best:")).slice(0,3).map(w=>w.key.slice(19));if(g.length>0){const w={ts:new Date().toISOString(),themen:g};(async()=>{const L=Dt(t);await h.lpush(L,JSON.stringify(w)),await h.ltrim(L,0,hn-1)})().catch(()=>{})}{const w={ts:new Date().toISOString(),frage:l.slice(0,200),geliefert:g};(async()=>{const L=pt(t);await h.lpush(L,JSON.stringify(w)),await h.ltrim(L,0,Pt-1)})().catch(()=>{})}}if(Oe()&&!Tt()){const g=he.filter(w=>w.key.startsWith("cachly:lesson:best:")).slice(0,3).map(w=>w.key.slice(19));sn(h,l,g,xe).catch(()=>{})}const te=he.filter(g=>g.key.startsWith("cachly:lesson:best:"));let p=0,V=0;for(const g of te.slice(0,5)){const w=await h.get(g.key).catch(()=>null);if(w){const L=O(w,null);if(L?.state==="archived")continue;if(L){const R=new Date().toISOString(),ee={...L,recall_count:(L.recall_count??0)+1,last_recalled_at:R};Tt()||h.set(g.key,JSON.stringify(ee)).catch(()=>{});const q=St(L)?vt(L):0;if(q>0&&h.incrbyfloat(`cachly:stats:time_saved_mins:${t}`,q).catch(()=>{}),L.author&&o&&L.author!==o){p++,h.sadd(`cachly:stats:reuse_pairs:${t}`,`${o}<-${L.author}`).catch(()=>{});const ie=`person:${we(o)}`,X=`person:${we(L.author)}`;$e(h,ie,"collaborates",X,!0).catch(()=>{}),$e(h,X,"collaborates",ie,!0).catch(()=>{})}L.outcome==="success"&&V++}}}V>0&&h.set(`cachly:stats:first_recall_at:${t}`,new Date().toISOString(),"EX",365*86400,"NX").catch(()=>{}),te.length>0&&(h.incr(`cachly:stats:recalls_total:${t}`).catch(()=>{}),p>0&&h.incr(`cachly:stats:cross_author_recalls:${t}`).catch(()=>{})),be("smart_recall: Treffer gezaehlt");const U=await ae(`/api/v1/instances/${t}`).catch(()=>null);be("smart_recall: Tarif abgefragt");const Q=!U?.tier||U.tier.toLowerCase()==="free",Z=[];if(!Q&&U?.vector_token&&Oe())try{const g=await xe(l),w=`https://api.cachly.dev/v1/sem/${U.vector_token}`,L=await fetch(`${w}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedding:g,namespace:"cachly:ctx",threshold:_,top_k:5}),signal:AbortSignal.timeout(8e3)});if(L.ok){const R=await L.json();for(const ee of R.filter(q=>q.found&&q.id)){const q=ee.id.replace("ctx:","").split(":"),ie=q[0],X=q.slice(1).join(":"),ue=`cachly:ctx:${ie}:${X}`,u=await h.get(ue).catch(()=>null);Z.push({key:ue,similarity:ee.similarity??0,content:u??"(evicted)"})}}}catch{}const K=he.map(g=>g.score),ne=K.length?Math.min(...K):0,d=K.length&&Math.max(...K)-ne||1,k=g=>(g-ne)/d,T=new Map,N=ce?en(he.map(g=>k(g.score))):null,b=new Map;for(const[g,w]of he.entries()){const L=k(w.score),R=N?N[g]:L;N&&T.set(w.key,g),b.set(w.key,{key:w.key,content:w.content,bm25Score:L,hausWert:R,hybridScore:R*(Z.length>0?.7:1),matchedWords:w.matchedWords,matchType:"keyword",subQuery:w.subQuery,wortBelege:w.wortBelege})}for(const g of Z){const w=b.get(g.key);w?(w.semScore=g.similarity,w.hybridScore=(w.hausWert??0)*.6+g.similarity*.4,w.matchType="hybrid"):b.set(g.key,{key:g.key,content:g.content,semScore:g.similarity,hybridScore:g.similarity*.5,matchType:"semantic"})}try{const g=lt(l).filter(R=>R.length>=4).slice(0,4),w=new Set;for(const R of g){const ee=h.scanStream({match:`cachly:ckg:node:*${R}*`,count:20});await new Promise((q,ie)=>{ee.on("data",X=>{for(const ue of X.slice(0,5))w.add(ue.replace("cachly:ckg:node:",""))}),ee.on("end",q),ee.on("error",ie)})}const L=new Set;for(const R of[...w].slice(0,12)){const ee=await h.smembers(`cachly:ckg:idx:to:${R}`);for(const q of ee.slice(0,6)){const ie=await h.get(q);if(!ie)continue;const X=O(ie,null);if(!X||X.edgeType!=="fixes"||X.confidence<.35||L.has(X.from))continue;L.add(X.from);const ue=`cachly:lesson:best:${X.from}`,u=b.get(ue);if(u)u.ckgScore=Math.max(u.ckgScore??0,X.confidence),u.hybridScore=u.hybridScore*.88+X.confidence*.12,u.matchType==="keyword"&&(u.matchType="hybrid");else{const z=await h.get(ue).catch(()=>null);if(!z)continue;b.set(ue,{key:ue,content:z,ckgScore:X.confidence,hybridScore:X.confidence*.3,matchType:"ckg"})}}}}catch{}if(be("smart_recall: Sinn-Dienst und CKG fertig"),i.length>0){const g=new Set(i.map(w=>w.replace(/\\/g,"/")));for(const w of b.values()){if(!w.key.startsWith("cachly:lesson:best:"))continue;const L=O(w.content,{});(Array.isArray(L.file_paths)?L.file_paths.filter(ee=>typeof ee=="string").map(ee=>ee.replace(/\\/g,"/")):[]).some(ee=>g.has(ee))&&(w.hybridScore*=1.15,w.contextBoost=!0)}}for(const g of b.values()){if(g.matchType!=="semantic"&&g.matchType!=="ckg")continue;const w=Hs({key:g.key,content:g.content});w&&(g.hybridScore*=Ks(w))}const S=[...b.values()].filter(g=>{if(!g.key.startsWith("cachly:lesson:best:"))return!0;const w=Be(g.content);return!(!We(w)||!Et(w?.group,v,m))}),x=N?Qs(S,T):S.sort((g,w)=>w.hybridScore-g.hybridScore),B=[];for(const g of x){if(!g.key.startsWith("cachly:lesson:best:"))continue;const w=O(g.content,{});w.gilt_bis&&Date.parse(String(w.gilt_bis))<Date.now()&&B.push(`\u23F3 \`${g.key.replace("cachly:lesson:best:","")}\` was valid until ${String(w.gilt_bis).slice(0,10)} \u2014 history, not a current fact.`);const L=O(g.content,{});if(L.pruefung_gefallen_am){const R=g.key.replace("cachly:lesson:best:",""),ee=String(L.pruefung_gefallen_am).slice(0,10),q=String(L.pruefung_befund??"").trim().slice(0,200);B.push(`\u274C \`${R}\` \u2014 Pruefung am ${ee} FEHLGESCHLAGEN`+(q?`: ${q}`:""))}}{const g=new Set(x.map(w=>w.key));for(let w=x.length-1;w>=0;w--){const L=x[w];if(!L.key.startsWith("cachly:lesson:best:"))continue;const R=O(L.content,{});if(!R.ersetzt_durch)continue;const ee=String(R.ersetzt_grund??"").trim(),q=L.key.replace("cachly:lesson:best:","");if(g.has(`cachly:lesson:best:${R.ersetzt_durch}`))x.splice(w,1),B.push(`\u21B7 \`${q}\` hidden \u2014 replaced by \`${R.ersetzt_durch}\`${R.ersetzt_am?` on ${R.ersetzt_am.slice(0,10)}`:""}${ee?` \u2014 reason: ${ee}`:" \u2014 no reason recorded"}`);else if(await h.get(`cachly:lesson:best:${R.ersetzt_durch}`)){const ie=Be(await h.get(`cachly:lesson:best:${R.ersetzt_durch}`));We(ie)?B.push(`\u26A0\uFE0F \`${q}\` is replaced by \`${R.ersetzt_durch}\` (not in this list) \u2014 the current answer: \`recall_best_solution(topic="${R.ersetzt_durch}")\``):B.push(`\u21B7 \`${q}\`: replaced by a version that is not visible to the team \u2014 this version stays the best visible answer.`)}else B.push(`\u21A9\uFE0F \`${q}\` was replaced by \`${R.ersetzt_durch}\` \u2014 the successor was deleted, so this version applies again (confirm it when you can).`)}}const J=on(x,i),le=[];{await a.aktualisiere(h).catch(()=>{});const g=x.slice(0,8).filter(w=>w.key.startsWith("cachly:lesson:best:"));for(let w=0;w<g.length;w++)for(let L=w+1;L<g.length;L++){const R=g[w].key.replace("cachly:lesson:best:",""),ee=g[L].key.replace("cachly:lesson:best:",""),q=a.rohvektor(R),ie=a.rohvektor(ee);if(!q||!ie)continue;let X=0,ue=0,u=0;for(let F=0;F<q.length;F++)X+=q[F]*ie[F],ue+=q[F]*q[F],u+=ie[F]*ie[F];if((ue>0&&u>0?X/(Math.sqrt(ue)*Math.sqrt(u)):0)<Mn)continue;const re=F=>{const P=O(F??"",{});return String(P.verified_at??P.ts??"").slice(0,10)||"no date"};le.push(`\u26A0\uFE0F \`${R}\` (${re(g[w].content)}) und \`${ee}\` (${re(g[L].content)}) say almost the same \u2014 check which version fits your case; the newer one is not automatically right.`)}le.length>3&&(le.length=3)}if(B.push(...le),x.length>=2){const g=x[0].hybridScore,w=x[Math.min(4,x.length-1)].hybridScore,L=Math.max(0,g-w);(async()=>{try{await h.lpush("cachly:recall:spread",JSON.stringify({ts:new Date().toISOString(),spread:Number(L.toFixed(4)),n:x.length})),await h.ltrim("cachly:recall:spread",0,199)}catch{}})()}const ye=As();let se="",de=null;if(ye!==null){se=Is();const g=x[0]?.hybridScore??0,w=Ms(x.map(L=>({key:L.key,content:L.content,punktzahl:L.hybridScore})),se,ye,g);w.weggelassenesThema!==void 0?x.splice(0,1):w.markierungGesetzt&&x[0]&&w.treffer[0]&&(x[0]={...x[0],content:w.treffer[0].content}),de={frage:l,hoechstePunktzahl:g,themen:[],zulassungsfaehig:w.zulassungsfaehig,zulassungsGrund:w.zulassungsGrund,zuteilung:w.zuteilung,weggelassenesThema:w.weggelassenesThema,markierungGesetzt:w.markierungGesetzt}}be("smart_recall: Notizen und Duplikate fertig");const ge=await rs(t,ae);be("smart_recall: Abruf-Grenze abgefragt");const ke=ge.reached,Re=Vt({ueberLimit:ke,gesamt:x.length,volleTiefe:$??Jn}),Se=process.env.CACHLY_RECALL_COMPACT==="1",ve=(()=>{const g=Number.parseInt(process.env.CACHLY_RECALL_COMPACT_HITS??"",10);return Number.isFinite(g)?Math.min(20,Math.max(1,g)):5})(),Ee=Se?Math.min(ve,Re.sichtbar):Re.sichtbar,Y=[`\u{1F9E0} **Smart Recall** for: _"${l}"_
19
+ `];ge.goodwillMessage&&Y.push(`${ge.goodwillMessage}
20
20
  `),p>0&&Y.push(`> \u{1F465} **Team knowledge reuse** \u2014 ${p} of these lesson${p!==1?"s were":" was"} written by a teammate. This is the value only a shared brain delivers.
21
- `);const Ie=N.filter(g=>g.contextBoost).length;Ie>0&&Y.push(`> \u{1F4C1} **Personalized** \u2014 ${Ie} lesson${Ie!==1?"s":""} boosted because ${Ie!==1?"they match":"it matches"} your current file context (${i.slice(0,3).join(", ")}${i.length>3?", \u2026":""})
22
- `);const Me=zs(h);Me.length>1&&Y.push(`_Detected ${Me.length} sub-topics:_ ${Me.map((g,w)=>`${w+1}. "${g}"`).join(", ")}
23
- `);const Be=Ss(N.map(g=>({wortBelege:g.wortBelege,semScore:g.semScore,ckgScore:g.ckgScore})));if(Be.schweigen)return _t(l,t,"schweigen"),Y.push(ks(Be)),Y.join(`
24
- `);const Re=wn(ce);if(Re&&Y.push(Re,""),_t(l,t,Re?"knapp":"beantwortet"),N.length>0){const g=N.some(R=>R.ckgScore!==void 0),w=ae||Q.length>0,B=w&&g?"keyword + semantic + CKG hybrid":w?"keyword + semantic hybrid":g?"keyword + CKG hybrid":"keyword";if(Y.push(`### \u{1F50D} Results (${N.length} \u2014 ${B})
25
- `),Be.belegte<N.length){const R=[];for(const U of N){if(!U.key.startsWith("cachly:lesson:best:")||!Cs({wortBelege:U.wortBelege,semScore:U.semScore,ckgScore:U.ckgScore}))continue;const oe=O(U.content,{}),X=Date.parse(oe.verified_at??oe.ts??"");Number.isFinite(X)&&R.push(Math.floor((Date.now()-X)/864e5))}const ee=vs(R);Y.push(`> \u{1F4CA} ${Be.belegte} of ${N.length} backed by evidence (at least one matching word, close meaning or a graph link). The rest are only the nearest entries, not evidence.`+(ee?` \u23F3 ${ee}`:"")+`
26
- `)}if(L.push(...an(N.slice(0,Se),J)),L.length>0&&Y.push(...L.map(R=>`> ${R}`),""),Me.length>1){const R=new Map,ee=qt({ueberLimit:Ne,gesamt:N.length,volleTiefe:$??Hn});for(const X of N.slice(0,ee.sichtbar)){const he=X.subQuery??h;R.has(he)||R.set(he,[]),R.get(he).push(X)}for(const[X,he]of R){Y.push(`**Topic: "${X}"** (${he.length} results)
27
- `);for(const f of he.slice(0,we?2:$??4)){const z=f.key.replace("cachly:ctx:","\u{1F4DD} ").replace("cachly:lesson:best:","\u{1F4A1} ").replace("cachly:idx:","\u{1F4C2} "),re=f.matchType==="ckg"?`CKG: ${(f.ckgScore??0).toFixed(2)}, \u{1F578}\uFE0F causal graph`:f.ckgScore!==void 0&&f.bm25Score!==void 0?`BM25: ${f.bm25Score.toFixed(2)}, CKG: ${f.ckgScore.toFixed(2)}, \u{1F500} hybrid`:f.matchType==="hybrid"?`BM25: ${(f.bm25Score??0).toFixed(2)}, sem: ${((f.semScore??0)*100).toFixed(0)}%, \u{1F500} hybrid`:f.matchType==="semantic"?`sem: ${((f.semScore??0)*100).toFixed(0)}%, \u{1F3AF} semantic`:`BM25: ${(f.bm25Score??0).toFixed(2)}, matched: ${f.matchedWords?.join(", ")}`,G=we?rt(f.key,f.content,300):rt(f.key,f.content,Ct,300);if(Y.push(we?` **${z}**`:` **${z}** _(${re})_`),f.key.startsWith("cachly:lesson:best:")){const P=await l.get(f.key.replace("lesson:best:","lesson:human:")).catch(()=>null)??await l.get(f.key.replace("cachly:lesson:best:","lesson:human:")).catch(()=>null),V=P?O(P,{}):{};V.title&&Y.push(` \u{1F4D6} **${V.title}** \u2014 ${V.summary??""}`)}{const P=f.key.startsWith("cachly:lesson:best:")?Xt(f.content):"";P&&Y.push(` ${P}`)}for(const P of G)Y.push(` > ${P}`);Y.push("")}}const U=[...R.keys()],oe=Me.filter(X=>!U.includes(X));oe.length>0&&Y.push(`
28
- \u26A0\uFE0F **No results for:** ${oe.map(X=>`"${X}"`).join(", ")}`)}else for(const R of N.slice(0,Se)){const ee=R.key.replace("cachly:ctx:","\u{1F4DD} ").replace("cachly:lesson:best:","\u{1F4A1} ").replace("cachly:idx:","\u{1F4C2} ");let U="";if(R.key.startsWith("cachly:lesson:best:")){const f=O(R.content,{});f.author&&(U=` \xB7 \u{1F464} ${f.author}`)}const oe=R.contextBoost?" \xB7 \u{1F4C1} context match":"",X=R.matchType==="hybrid"?`BM25: ${(R.bm25Score??0).toFixed(2)}, sem: ${((R.semScore??0)*100).toFixed(0)}%, \u{1F500} hybrid`:R.matchType==="semantic"?`sem: ${((R.semScore??0)*100).toFixed(0)}%, \u{1F3AF} semantic`:`BM25: ${(R.bm25Score??0).toFixed(2)}, matched: ${R.matchedWords?.join(", ")}`,he=rt(R.key,R.content,we?xt:Ct,xt);Y.push(we?`**${ee}**`:`**${ee}**${U}${oe} _(${X})_`);{const f=O(R.content??"",{});f.entscheidung?.gegen&&Y.push(jt({gegen:f.entscheidung.gegen,grund:f.entscheidung.grund??null}))}if(R.key.startsWith("cachly:lesson:best:")){const f=await l.get(R.key.replace("lesson:best:","lesson:human:")).catch(()=>null)??await l.get(R.key.replace("cachly:lesson:best:","lesson:human:")).catch(()=>null),z=f?O(f,{}):{};z.title&&Y.push(`\u{1F4D6} **${z.title}** \u2014 ${z.summary??""}`)}{const f=R.key.startsWith("cachly:lesson:best:")?Xt(R.content):"";f&&Y.push(f)}for(const f of he)Y.push(`> ${f}`);Y.push("")}if(Ne&&Ae.zurueckgehalten>0){const R=Ae.zurueckgehalten;Y.push(`\u{1F512} **Free recall limit reached** (${$e.used}/${$e.limit} this month). Showing your top match; ${R} further relevant lesson${R!==1?"s are":" is"} withheld. Your limit resets next month, or unlock your full Brain now \u2192 ${yo}
29
- `)}}else{Y.push(`\u26A0\uFE0F No matches found for: "${h}"`),L.length>0&&Y.push("",...L.map(B=>`> ${B}`));const g=lt(h),w=[];if(It.size>0&&g.length>0)for(const B of g.slice(0,3)){if(B.length<4)continue;let R=3,ee="";for(const U of It){if(U.length<3||Math.abs(U.length-B.length)>4)continue;const oe=Ps(B,U);oe>0&&oe<R&&(R=oe,ee=U)}ee&&w.push(`"${ee}" (instead of "${B}")`)}w.length>0?Y.push(`\u{1F4A1} **Did you mean:** ${w.join(", ")}?`):(Y.push(`
30
- \u{1F4A1} Tips:`),Y.push(" \u2022 Try different keywords"),Y.push(" \u2022 Use `list_remembered` to see available context"),Y.push(' \u2022 Use `recall_best_solution("topic")` for exact topic lookup'))}if(!we&&N.length>0){Y.push(""),Y.push("\u21A9\uFE0E Did one of these help? `recall_feedback(query=\u2026, topic=\u2026, helped=true, rank=N)` \u2014 and if the right lesson was **not** in the list: the same call with `rank=0`. That is the only number that improves the ranking.");const g=N.filter(w=>w.key.startsWith("cachly:lesson:best:")).map(w=>w.key.slice(19));if(g.length>0)try{await l.setex(`cachly:letzter-recall:${t}`,7200,JSON.stringify({frage:h,themen:g}))}catch{}}if(we&&N.length>0){const g=ps();g&&(Y.push(""),Y.push(g))}if(ge!==null&&ye){const g=N.slice(0,Se).filter(w=>w.key.startsWith("cachly:lesson:best:")).map(w=>w.key.slice(19));return Rs(l,se,{...ye,themen:g},Y.join(`
21
+ `);const Ae=x.filter(g=>g.contextBoost).length;Ae>0&&Y.push(`> \u{1F4C1} **Personalized** \u2014 ${Ae} lesson${Ae!==1?"s":""} boosted because ${Ae!==1?"they match":"it matches"} your current file context (${i.slice(0,3).join(", ")}${i.length>3?", \u2026":""})
22
+ `);const Ie=zs(l);Ie.length>1&&Y.push(`_Detected ${Ie.length} sub-topics:_ ${Ie.map((g,w)=>`${w+1}. "${g}"`).join(", ")}
23
+ `);const Le=Ss(x.map(g=>({wortBelege:g.wortBelege,semScore:g.semScore,ckgScore:g.ckgScore})));if(Le.schweigen)return _t(h,t,"schweigen"),Y.push(ks(Le)),Y.join(`
24
+ `);const Me=wn(oe);if(Me&&Y.push(Me,""),_t(h,t,Me?"knapp":"beantwortet"),x.length>0){const g=x.some(R=>R.ckgScore!==void 0),w=ce||Z.length>0,L=w&&g?"keyword + semantic + CKG hybrid":w?"keyword + semantic hybrid":g?"keyword + CKG hybrid":"keyword";if(Y.push(`### \u{1F50D} Results (${x.length} \u2014 ${L})
25
+ `),Le.belegte<x.length){const R=[];for(const q of x){if(!q.key.startsWith("cachly:lesson:best:")||!Cs({wortBelege:q.wortBelege,semScore:q.semScore,ckgScore:q.ckgScore}))continue;const ie=O(q.content,{}),X=Date.parse(ie.verified_at??ie.ts??"");Number.isFinite(X)&&R.push(Math.floor((Date.now()-X)/864e5))}const ee=vs(R);Y.push(`> \u{1F4CA} ${Le.belegte} of ${x.length} backed by evidence (at least one matching word, close meaning or a graph link). The rest are only the nearest entries, not evidence.`+(ee?` \u23F3 ${ee}`:"")+`
26
+ `)}if(B.push(...an(x.slice(0,Ee),J)),B.length>0&&Y.push(...B.map(R=>`> ${R}`),""),Ie.length>1){const R=new Map,ee=Vt({ueberLimit:ke,gesamt:x.length,volleTiefe:$??qn});for(const X of x.slice(0,ee.sichtbar)){const ue=X.subQuery??l;R.has(ue)||R.set(ue,[]),R.get(ue).push(X)}for(const[X,ue]of R){Y.push(`**Topic: "${X}"** (${ue.length} results)
27
+ `);for(const u of ue.slice(0,Se?2:$??4)){const z=u.key.replace("cachly:ctx:","\u{1F4DD} ").replace("cachly:lesson:best:","\u{1F4A1} ").replace("cachly:idx:","\u{1F4C2} "),re=u.matchType==="ckg"?`CKG: ${(u.ckgScore??0).toFixed(2)}, \u{1F578}\uFE0F causal graph`:u.ckgScore!==void 0&&u.bm25Score!==void 0?`BM25: ${u.bm25Score.toFixed(2)}, CKG: ${u.ckgScore.toFixed(2)}, \u{1F500} hybrid`:u.matchType==="hybrid"?`BM25: ${(u.bm25Score??0).toFixed(2)}, sem: ${((u.semScore??0)*100).toFixed(0)}%, \u{1F500} hybrid`:u.matchType==="semantic"?`sem: ${((u.semScore??0)*100).toFixed(0)}%, \u{1F3AF} semantic`:`BM25: ${(u.bm25Score??0).toFixed(2)}, matched: ${u.matchedWords?.join(", ")}`,F=Se?rt(u.key,u.content,300):rt(u.key,u.content,Ct,300);if(Y.push(Se?` **${z}**`:` **${z}** _(${re})_`),u.key.startsWith("cachly:lesson:best:")){const P=await h.get(u.key.replace("lesson:best:","lesson:human:")).catch(()=>null)??await h.get(u.key.replace("cachly:lesson:best:","lesson:human:")).catch(()=>null),H=P?O(P,{}):{};H.title&&Y.push(` \u{1F4D6} **${H.title}** \u2014 ${H.summary??""}`)}{const P=u.key.startsWith("cachly:lesson:best:")?Xt(u.content):"";P&&Y.push(` ${P}`)}for(const P of F)Y.push(` > ${P}`);Y.push("")}}const q=[...R.keys()],ie=Ie.filter(X=>!q.includes(X));ie.length>0&&Y.push(`
28
+ \u26A0\uFE0F **No results for:** ${ie.map(X=>`"${X}"`).join(", ")}`)}else for(const R of x.slice(0,Ee)){const ee=R.key.replace("cachly:ctx:","\u{1F4DD} ").replace("cachly:lesson:best:","\u{1F4A1} ").replace("cachly:idx:","\u{1F4C2} ");let q="";if(R.key.startsWith("cachly:lesson:best:")){const u=O(R.content,{});u.author&&(q=` \xB7 \u{1F464} ${u.author}`)}const ie=R.contextBoost?" \xB7 \u{1F4C1} context match":"",X=R.matchType==="hybrid"?`BM25: ${(R.bm25Score??0).toFixed(2)}, sem: ${((R.semScore??0)*100).toFixed(0)}%, \u{1F500} hybrid`:R.matchType==="semantic"?`sem: ${((R.semScore??0)*100).toFixed(0)}%, \u{1F3AF} semantic`:`BM25: ${(R.bm25Score??0).toFixed(2)}, matched: ${R.matchedWords?.join(", ")}`,ue=rt(R.key,R.content,Se?xt:Ct,xt);Y.push(Se?`**${ee}**`:`**${ee}**${q}${ie} _(${X})_`);{const u=O(R.content??"",{});u.entscheidung?.gegen&&Y.push(jt({gegen:u.entscheidung.gegen,grund:u.entscheidung.grund??null}))}if(R.key.startsWith("cachly:lesson:best:")){const u=await h.get(R.key.replace("lesson:best:","lesson:human:")).catch(()=>null)??await h.get(R.key.replace("cachly:lesson:best:","lesson:human:")).catch(()=>null),z=u?O(u,{}):{};z.title&&Y.push(`\u{1F4D6} **${z.title}** \u2014 ${z.summary??""}`)}{const u=R.key.startsWith("cachly:lesson:best:")?Xt(R.content):"";u&&Y.push(u)}for(const u of ue)Y.push(`> ${u}`);Y.push("")}if(ke&&Re.zurueckgehalten>0){const R=Re.zurueckgehalten;Y.push(`\u{1F512} **Free recall limit reached** (${ge.used}/${ge.limit} this month). Showing your top match; ${R} further relevant lesson${R!==1?"s are":" is"} withheld. Your limit resets next month, or unlock your full Brain now \u2192 ${$o}
29
+ `)}}else{Y.push(`\u26A0\uFE0F No matches found for: "${l}"`),B.length>0&&Y.push("",...B.map(L=>`> ${L}`));const g=lt(l),w=[];if(It.size>0&&g.length>0)for(const L of g.slice(0,3)){if(L.length<4)continue;let R=3,ee="";for(const q of It){if(q.length<3||Math.abs(q.length-L.length)>4)continue;const ie=Ps(L,q);ie>0&&ie<R&&(R=ie,ee=q)}ee&&w.push(`"${ee}" (instead of "${L}")`)}w.length>0?Y.push(`\u{1F4A1} **Did you mean:** ${w.join(", ")}?`):(Y.push(`
30
+ \u{1F4A1} Tips:`),Y.push(" \u2022 Try different keywords"),Y.push(" \u2022 Use `list_remembered` to see available context"),Y.push(' \u2022 Use `recall_best_solution("topic")` for exact topic lookup'))}if(!Se&&x.length>0){Y.push(""),Y.push("\u21A9\uFE0E Did one of these help? `recall_feedback(query=\u2026, topic=\u2026, helped=true, rank=N)` \u2014 and if the right lesson was **not** in the list: the same call with `rank=0`. That is the only number that improves the ranking.");const g=x.filter(w=>w.key.startsWith("cachly:lesson:best:")).map(w=>w.key.slice(19));if(g.length>0)try{await h.setex(`cachly:letzter-recall:${t}`,7200,JSON.stringify({frage:l,themen:g}))}catch{}}if(Se&&x.length>0){const g=ps();g&&(Y.push(""),Y.push(g))}if(ye!==null&&de){const g=x.slice(0,Ee).filter(w=>w.key.startsWith("cachly:lesson:best:")).map(w=>w.key.slice(19));return Rs(h,se,{...de,themen:g},Y.join(`
31
31
  `))}return Y.join(`
32
- `)}case"session_start":{const{instance_id:d,focus:t="",author:h="",provider:b="",workspace_path:o=""}=M;if(await Mt(d,ne))return j(d).then(async u=>{await as(u),await is(u,t,b),await u.set("cachly:mcp:version:last_seen",qe,"EX",365*86400)}).catch(()=>{}),ke("session_start: neues Brain startet noch \u2014 sofort geantwortet"),Fs({instanzId:d,provider:b,...await ht(d)});const n=await j(d),E=[],$=n.scanStream({match:"cachly:lesson:best:*",count:1e3});await new Promise((u,k)=>{$.on("data",x=>E.push(...x)),$.on("end",u),$.on("error",k)});const i=[];let l=0;if(E.length>0){const u=await n.mget(...E);for(const k of u){const x=O(k??null,null);if(x){if(Yt(x)){l++;continue}es(x.topic)||i.push(x)}}}i.sort((u,k)=>new Date(k.ts).getTime()-new Date(u.ts).getTime());let a=0;const s=n.scanStream({match:"cachly:ctx:*",count:1e3});await new Promise((u,k)=>{s.on("data",x=>{a+=x.filter(T=>!T.endsWith(":meta")).length}),s.on("end",u),s.on("error",k)});const C=await n.get("cachly:session:last"),r=O(C,null),c=t.toLowerCase().split(/\s+/).filter(Boolean),v=c.length>0?i.filter(u=>c.some(k=>u.topic.toLowerCase().includes(k)||(u.tags??[]).some(x=>x.toLowerCase().includes(k)))):[],{tage:m,rekord:y}=await as(n);let A="";m>=7?A=`\u{1F525} **${m}-day streak!** ${m===y?" New record!":`Best: ${y}d`}`:m>1&&(A=`\u{1F525} ${m}-day streak`),await is(n,t,b);const e=[`\u{1F9E0} **Session Briefing**${b?` \xB7 ${b}`:""}`,""];A&&e.push(A,"");const H=await rs(d,ne),ae=Wn({used:H.used,limit:H.limit});if(ae&&e.push(ae,""),H.notice&&!ss.has(d)&&(ss.add(d),e.push(H.notice,"")),r?.ts){const u=new Date(r.ts).getTime(),k=Math.floor((Date.now()-u)/864e5);if(k>=7&&!Number.isNaN(u)){const x=i.filter(T=>{const _=new Date(T.ts).getTime();return!Number.isNaN(_)&&_>u});if(e.push(`\u{1F44B} **Welcome back** \u2014 it's been ${k} days since your last session.`),x.length>0){const T=x.slice(0,3).map(_=>`\`${_.topic}\``).join(", ");e.push(` \u{1F331} Your Brain grew by **${x.length} lesson${x.length!==1?"s":""}** while you were gone (git, CI & teammates kept it learning): ${T}${x.length>3?", \u2026":""}`)}else e.push(` \u{1F9E0} ${i.length} lesson${i.length!==1?"s":""} held steady and ready \u2014 recall anything with \`smart_recall\`.`);e.push(" \u{1F4A1} Full breakdown: `npx @cachly-dev/mcp-server@latest digest`","")}}try{if(await n.get("cachly:mcp:version:last_seen")!==qe){await n.set("cachly:mcp:version:last_seen",qe,"EX",365*86400);const k=wo[qe];if(k){e.push(...k,"");const x=go();x&&e.push(`\u{1F4CA} ${x} MCP tools`,"")}}}catch{}const ce=!r&&i.length===0&&l===0&&a===0;ce&&(e.push("\u{1F389} **Welcome! Your AI Brain is live.**",""),o?(e.push("\u{1F680} **Auto-bootstrapping from your git history...** (this takes a few seconds)"),e.push(" Your brain will learn from your recent commits \u2014 no setup needed.","")):(e.push("It learns from your work automatically. After your first session it will look like this:",""),e.push(" \u2705 `api:auth` \u2014 Bearer token in header, not cookie; 401 on missing scope"),e.push(" \u2705 `database:migrations` \u2014 always run migrations before deploy"),e.push(" \u26A0\uFE0F `docker:build` \u2014 ARG changes bust all subsequent cache layers"),e.push(""),e.push("**Tip:** Pass `workspace_path` to `session_start` to auto-learn from git history instantly."),e.push("")),e.push("**Your brain grows automatically:**"),e.push(' \u2022 End each session \u2192 `session_end(summary="What I did")` \u2014 auto-learns from git commits'),e.push(' \u2022 After fixing bugs \u2192 `learn_from_attempts(topic="...", outcome="success", what_worked="...")`'),e.push(""),e.push("\u{1F331} **Want answers right now?** Seed 16 universal engineering lessons so your first"),e.push(" `smart_recall` returns a hit instead of nothing:"),e.push(` \`brain_seed_starter(instance_id="${d}")\``),e.push(" _(Docker cache, JWT skew, K8s OOM, N+1 queries, cache stampede\u2026 \u2014 never overrides your own lessons.)_"),e.push(""),e.push("\u{1F4A1} Run `brain_doctor` for a health-check and personalised tips."),e.push(""));const de=v.slice(0,4),le=new Set(de.map(u=>u.topic));if(h&&!ce)try{const u=`cachly:team:first_briefing:${h}`;if(!await n.get(u)){const x=i.filter(T=>T.author&&T.author!==h&&!le.has(T.topic));if(x.length>0){await n.set(u,"1","EX",365*86400);const T=new Map;for(const _ of x){const S=_.author;T.has(S)||T.set(S,[]),T.get(S).push(_)}e.push(""),e.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),e.push("\u{1F91D} **Your team's AI brain has been briefing you.**"),e.push(""),e.push("Your teammates have already solved problems you're about to hit:"),e.push("");for(const[_,S]of T){e.push(` \u{1F464} **${_}** fixed ${S.length} thing${S.length>1?"s":""}:`);for(const N of S.slice(0,2)){const L=N.outcome==="success"?"\u2705":"\u26A0\uFE0F";e.push(` ${L} \`${N.topic}\` \u2014 ${N.what_worked.slice(0,90)}`),le.add(N.topic)}S.length>2&&e.push(` \u2026 and ${S.length-2} more lessons`)}e.push(""),e.push("\u{1F4A1} Use `team_learn` after your next fix to pay it forward."),e.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),e.push("")}}}catch{}const p=await n.get("cachly:session:handoff");if(p)try{const u=JSON.parse(p),k=Math.round((Date.now()-new Date(u.ts).getTime())/6e4),x=k<60?`${k}m ago`:k<1440?`${Math.round(k/60)}h ago`:`${Math.round(k/1440)}d ago`;if(e.push(`\u{1F91D} **Handoff from previous window** (${x}):`),u.context_summary&&e.push(` ${u.context_summary}`),u.remaining_tasks.length>0){e.push(" \u23F3 **Remaining tasks:**");for(const _ of u.remaining_tasks)e.push(` - ${_}`)}u.completed_tasks.length>0&&e.push(` \u2705 **Already done:** ${u.completed_tasks.join(", ")}`);const T=(u.files_changed??[]).filter(_=>_.status==="broken"||_.status==="partial");T.length>0&&e.push(` \u26A0\uFE0F **Needs fix:** ${T.map(_=>`\`${_.path}\` (${_.status}${_.description?": "+_.description:""})`).join(", ")}`),u.blocked_on&&e.push(` \u{1F6AB} **Blocked on:** ${u.blocked_on}`),u.instructions&&e.push(` \u{1F4DD} **Instructions:** ${u.instructions}`),e.push("")}catch{}const W=await n.get("cachly:session:checkpoint");if(W)try{const u=JSON.parse(W),k=new Date(u.ts).getTime(),x=r?new Date(r.ts).getTime():0;if(k>x){const T=Math.round((Date.now()-k)/6e4),_=T<60?`${T}m ago`:T<1440?`${Math.round(T/60)}h ago`:`${Math.round(T/1440)}d ago`,S=u.provider?` [${u.provider}]`:"";e.push(`\u{1F4CC} **Last checkpoint**${S} (${_}): ${u.task}`),u.files_touched.length>0&&e.push(` Files: ${u.files_touched.slice(0,5).map(N=>`\`${N}\``).join(", ")}`),u.next_step&&e.push(` \u{1F4CD} Next step was: ${u.next_step}`),(!r||k-x>3e5)&&e.push(" \u26A0\uFE0F No `session_end` found \u2014 reconstructed from last checkpoint"),e.push("")}}catch{}const Z=r?null:et(o);if(Z)try{const u=Pe(Z,["log","--oneline","--format=%h %s","-15"]).trim();let k;try{k=Pe(Z,["diff","--stat","HEAD~3"]).trim()}catch{k=Pe(Z,["diff","--stat"]).trim()}if(u){e.push("\u{1F50D} **Git reconstruction** (no session_end found \u2014 reconstructed from git):");for(const x of u.split(`
33
- `).slice(0,8))e.push(` ${x}`);if(k){const x=k.split(`
34
- `).filter(T=>T.includes("|")||T.includes("changed"));if(x.length>0){e.push(" **Recent changes:**");for(const T of x.slice(0,5))e.push(` ${T.trim()}`)}}e.push("")}}catch{}if(r){const u=Math.round((Date.now()-new Date(r.ts).getTime())/6e4),k=u<60?`${u}m ago`:u<1440?`${Math.round(u/60)}h ago`:`${Math.round(u/1440)}d ago`;e.push(`\u{1F4C5} **Last session** (${k}): ${r.summary}`),r.duration_min&&e.push(` Duration: ${r.duration_min} min`),(r.files_changed??[]).length>0&&e.push(` Files: ${(r.files_changed??[]).slice(0,5).map(x=>`\`${x}\``).join(", ")}`),e.push("")}e.push(`\u{1F4CA} **Brain:** ${i.length} lessons \xB7 ${a} context entries`,""),l>0&&e.push(`\u26A0 ${l} Lektion(en) wurden beim Speichern als verd\xE4chtig markiert und stehen nicht in diesem Briefing. Ausdr\xFCcklich abrufbar mit recall_best_solution (mit Warnzeile); pr\xFCfen oder neu speichern.`,"");{const k=await n.lrange(`cachly:wirkung:${d}`,0,-1).catch(()=>[]),x=kn(k.map(S=>O(S,null)).filter(Boolean)),T=S=>x.get(S)??{geholfen:0,geoeffnet:0},_=i.filter(S=>!bs(S)).filter(S=>{if(S.outcome!=="success")return!1;const N=T(S.topic);return S.pinned===!0||N.geholfen>=1||N.geoeffnet>=2}).filter(S=>!le.has(S.topic)).sort((S,N)=>{const L=T(S.topic),J=T(N.topic);return J.geholfen-L.geholfen||J.geoeffnet-L.geoeffnet||(N.recall_count??0)-(S.recall_count??0)}).slice(0,5);if(_.length>0){e.push("\u{1F4CC} **Confirmed lessons** (pinned, reported helpful, or opened after a search \u2014 being shown does not count):");for(const S of _){le.add(S.topic);const N=S.severity==="critical"?"\u{1F534}":S.severity==="major"?"\u{1F7E1}":"",L=T(S.topic),J=[S.pinned===!0?"pinned":"",L.geholfen>0?`reported helpful ${L.geholfen}\xD7`:"",L.geoeffnet>0?`opened ${L.geoeffnet}\xD7 after a search`:""].filter(Boolean).join(" \xB7 "),ie=Ye(S);e.push(` \u2705${N} \`${S.topic}\` _(${J})_ \u2014 ${ie[0]??""}`,...ie.slice(1))}e.push("")}}try{const u=[],k=n.scanStream({match:"cachly:ckg:conflict:*",count:1e3});if(await new Promise((x,T)=>{k.on("data",_=>u.push(..._)),k.on("end",x),k.on("error",T)}),u.length>0){e.push(`\u26A1 **Active belief conflicts (${u.length}):**`);for(const x of u.slice(0,3)){const T=await n.get(x);if(!T)continue;const _=O(T,null);_&&e.push(` \u26A0\uFE0F \`${_.topic}\` \u2014 previously confirmed fix (${(_.fix_confidence*100).toFixed(0)}%, n=${_.fix_trials}) now contradicted. Use \`ckg_inspect(concept="${_e(_.topic)}")\``)}e.push("")}}catch{}if(de.length>0){e.push(`\u{1F3AF} **Relevant for "${t}":**`);for(const u of de){const k=u.outcome==="success"?"\u2705":u.outcome==="partial"?"\u26A0\uFE0F":"\u274C",x=u.severity==="critical"?"\u{1F534}":u.severity==="major"?"\u{1F7E1}":"",T=Ye(u);e.push(` ${k}${x} \`${u.topic}\` \u2014 ${T[0]??""}`,...T.slice(1))}e.push("")}if(i.length>0){const u=(v.length>0?i.filter(k=>!v.includes(k)):i).filter(k=>!le.has(k.topic)).slice(0,v.length>0?3:4);if(u.length>0){e.push("\u{1F550} **Recent lessons:**");for(const k of u){le.add(k.topic);const x=k.outcome==="success"?"\u2705":k.outcome==="partial"?"\u26A0\uFE0F":"\u274C",T=k.severity==="critical"?"\u{1F534}":k.severity==="major"?"\u{1F7E1}":"",_=Ye(k);e.push(` ${x}${T} \`${k.topic}\` \u2014 ${_[0]??""}`,..._.slice(1))}e.push("")}}else e.push("\u{1F4ED} No lessons yet. Use `learn_from_attempts` after solving tasks.","");i.length>=10&&(i.some(k=>k.author)||(e.push(`\u{1F91D} **Your Brain has ${i.length} lessons \u2014 your team could benefit instantly.**`),e.push(" Share: **cachly.dev/team** \xB7 or run `team_learn` after the next fix.","")));const q=await Mn(n,i);if(q.length>0){e.push("\u267B\uFE0F **Possible duplicates** (same meaning, different topic \u2014 nothing is merged):");for(const u of q.slice(0,3))e.push(` \`${u.neu}\` \u2248 \`${u.alt}\` (${u.naehe.toFixed(2)})`);e.push(' _If the newer one replaces the older, pass `ersetzt="<older topic>"` on its next `learn_from_attempts`._'),e.push("")}const Q=r;if(Q?.decision_log?.length){const u=Q.decision_log,k=u.filter((_,S)=>!le.has(_.topic)&&u.findIndex(N=>N.topic===_.topic)===S),x=k.filter(_=>_.outcome==="success"),T=k.filter(_=>_.outcome==="failure");if(x.length>0||T.length>0){e.push(`\u{1F3AC} **Last session decisions** (${u.length} lessons stored):`),x.length>0&&e.push(` \u2705 Worked: ${x.slice(0,3).map(_=>`\`${_.topic}\``).join(", ")}`),T.length>0&&e.push(` \u274C Failed: ${T.slice(0,3).map(_=>`\`${_.topic}\``).join(", ")}`),e.push("");for(const _ of[...x.slice(0,3),...T.slice(0,3)])le.add(_.topic)}}{const u=new Set(["src","lib","test","tests","dist","index","main","app","internal","pkg","cmd","node_modules","components","utils","ts","tsx","js","jsx","go","py","rs","java","json","yaml","yml"]),k=S=>{const N=new Set;for(const L of S)for(const J of L.toLowerCase().split(/[/\\._-]/))J.length>3&&!u.has(J)&&N.add(J);return[...N]};let x=c,T=t,_=!1;if(x.length===0&&r){const S=k(r.files_changed??[]),N=(r.summary??"").toLowerCase().replace(/[^a-z0-9\s:_-]/g," ").split(/\s+/).filter(L=>L.length>3&&!u.has(L));x=[...new Set([...S,...N])],T="where you left off last session",_=!0}if(x.length>0){const S=i.filter(N=>{if(N.outcome==="success")return!1;const L=N.topic.split(":")[0];return x.some(J=>N.topic.toLowerCase().includes(J)||L===J||(N.tags??[]).some(ie=>ie.toLowerCase()===J))});if(S.length>=1){const L=["\u{1F6A8} **PRE-WARNING** \u2014 Read this BEFORE starting:",_?` You're likely to continue **${T}** \u2014 ${S.length} known pitfall${S.length>1?"s":""} there:`:` Known pitfalls for **"${T}"** (${S.length} past failure${S.length>1?"s":""}):`,...S.slice(0,3).map(J=>` \u274C \`${J.topic}\` \u2014 ${(J.what_failed??J.what_worked).slice(0,80)}`),""];e.splice(2,0,...L)}}}if(h){const u=Date.now()-6048e5,k=i.filter(x=>x.author&&x.author!==h&&new Date(x.ts).getTime()>u&&!le.has(x.topic));if(k.length>0){const x=new Map;for(const T of k){const _=T.author;x.has(_)||x.set(_,[]),x.get(_).push(T)}e.push(`\u{1F465} **Team this week** (${k.length} lesson${k.length>1?"s":""} from teammates):`);for(const[T,_]of x){e.push(` \u{1F464} **${T}**:`);for(const S of _.slice(0,3)){const N=S.outcome==="success"?"\u2705":S.outcome==="partial"?"\u26A0\uFE0F":"\u274C";e.push(` ${N} \`${S.topic}\` \u2014 ${S.what_worked.slice(0,80)}`),le.add(S.topic)}_.length>3&&e.push(` \u2026 and ${_.length-3} more`)}e.push("")}}try{const u=await n.get("cachly:crystal:latest");if(u){const k=O(u,null);if(k&&Math.round((Date.now()-new Date(k.ts).getTime())/864e5)<=90){e.push(`\u{1F48E} **Memory Crystal** (${k.label} \xB7 ${k.session_count} sessions compressed):`);for(const T of k.top_patterns.slice(0,3))e.push(` \u2022 **${T.category}** (${T.count}\xD7): ${T.insight.slice(0,90)}`);e.push("")}}}catch{}try{const u=await n.hgetall(`cachly:roadmap:${d}`);if(u&&Object.keys(u).length>0){const k={critical:0,high:1,medium:2,low:3},x={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F535}"},T=new Set(["planned","in-progress","blocked"]),_=Object.values(u).flatMap(L=>{const J=O(L,null);return J?[J]:[]}),S=_.filter(L=>T.has(L.status)).sort((L,J)=>L.status==="in-progress"&&J.status!=="in-progress"?-1:J.status==="in-progress"&&L.status!=="in-progress"?1:(k[L.priority]??99)-(k[J.priority]??99)),N=_.filter(L=>L.status==="done").length;if(S.length>0){e.push(`\u{1F5FA}\uFE0F **Roadmap** (${S.length} open \xB7 ${N} done):`);for(const L of S.slice(0,5)){const J=L.status==="in-progress"?"\u26A1":L.status==="blocked"?"\u{1F6AB}":"\u{1F4CB}";e.push(` ${J} ${x[L.priority]??"\u26AA"} \`${L.id}\` **${L.title}**`)}S.length>5&&e.push(` \u2026 and ${S.length-5} more`),e.push(" _Use `roadmap_next` for the top priority item \xB7 `roadmap_list` for full view_"),e.push("")}}}catch{}try{const u=await n.get("cachly:mcm:calibration:last"),k=O(u,null)?.ts??0;if((Date.now()-k)/864e5>=30&&i.length>=5){const T=i.filter(S=>S.outcome==="success"&&(S.recall_count??0)>0),_=i.filter(S=>(S.recall_count??0)>0);if(_.length>=3){const S=_.length>0?T.length/_.length:1,N=[],L=new Map;for(const J of _){const ie=J.topic.split(":")[0]??"other";L.has(ie)||L.set(ie,{success:0,total:0});const ge=L.get(ie);ge.total++,J.outcome==="success"&&ge.success++}for(const[J,ie]of L)ie.total>=2&&ie.success/ie.total<.6&&N.push(J);await n.set("cachly:mcm:calibration:last",JSON.stringify({ts:Date.now(),precision:S,recalled:_.length,low_precision_domains:N})),(N.length>0||S<.7)&&(e.push(`\u{1F4D0} **MCM Calibration** (30-day pass \u2014 ${_.length} recalled lessons):`),e.push(` Overall precision: **${(S*100).toFixed(0)}%** recalled lessons actually worked`),N.length>0&&e.push(` \u26A0\uFE0F Low-precision domains: ${N.map(J=>`\`${J}\``).join(", ")} \u2014 consider revisiting these lessons`),e.push(" \u{1F4A1} Use `learn_from_attempts` to update stale lessons and improve accuracy."),e.push(""))}await n.set("cachly:mcm:calibration:last",JSON.stringify({ts:Date.now(),precision:1,recalled:0}),"EX",35*86400)}}catch{}const K=i.filter(u=>u.outcome==="failure"||u.outcome==="partial").length,te=i.filter(u=>u.outcome==="success"&&$t(u)<fs).length;if(i.length===0||K>0||te>0){const u=[];i.length===0&&u.push("brain is empty"),K>0&&u.push(`${K} open failure${K>1?"s":""}`),te>0&&u.push(`${te} stale lesson${te>1?"s":""}`),e.push(`\u{1FA7A} _Run \`brain_doctor\` to fix: ${u.join(", ")}._`)}try{if(await n.get(`cachly:prefs:auto_changelog:${d}`).catch(()=>null)!=="false"&&r?.ts&&!ce){const x=new Date(r.ts).getTime();if(!Number.isNaN(x)){const T=i.filter(S=>{const N=new Date(S.ts).getTime();return!Number.isNaN(N)&&N>x}),_=T.filter(S=>!le.has(S.topic));if(_.length>0){const S=Math.round((Date.now()-x)/6e4),N=S<60?`${S}m`:S<1440?`${Math.round(S/60)}h`:`${Math.round(S/1440)}d`,L=T.length-_.length;e.push(""),e.push(`\u{1F4CB} **${T.length} new lesson${T.length!==1?"s":""} since your last session** (${N} ago${L>0?`, ${L} already listed above`:""}):`);const J=new Map;for(const ie of _.slice(0,20)){const ge=ie.topic.includes(":")?ie.topic.split(":")[0]:"general",se=J.get(ge)??[];se.push(ie),J.set(ge,se)}for(const[ie,ge]of J){e.push(` **${ie}**`);for(const se of ge){const ye=se.outcome==="failure"?"\u274C":se.severity==="critical"?"\u{1F6A8}":se.severity==="major"?"\u26A0\uFE0F":"\u2705",$e=(se.outcome==="failure"?se.what_failed:se.what_worked)??"";e.push(` ${ye} \`${se.topic}\`${$e?` \u2014 ${$e.slice(0,80)}`:""}`)}}_.length>20&&e.push(` \u2026 and ${_.length-20} more. Call \`brain_changelog\` for the full list.`),e.push(` _Disable auto-changelog: \`brain_set_pref(instance_id="${d}", key="auto_changelog", value="false")\`_`)}}}}catch{}return e.join(`
35
- `)}case"session_start_summary":{const{instance_id:d,focus:t,top_n:h=10,author:b=""}=M;if(!t||!t.trim())throw new Error("focus is required for session_start_summary");const o=Math.min(Math.max(1,Math.floor(Number(h)||10)),25),n=await j(d),E=[],$=n.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((e,H)=>{$.on("data",ae=>E.push(...ae)),$.on("end",e),$.on("error",H)});const i=[];if(E.length>0){const e=await n.mget(...E);for(const H of e){const ae=O(H??null,null);ae&&!Yt(ae)&&!es(ae.topic)&&i.push(ae)}}if(i.length===0)return["\u{1F9E0} Brain Summary \u2014 no lessons yet","","Your brain is empty. Use `learn_from_attempts` after solving tasks to build it up.",`\u{1F4A1} Tip: \`brain_seed_starter(instance_id="${d}")\` to bootstrap with 16 universal lessons.`].join(`
36
- `);const l=i.length>o,a=t.toLowerCase().split(/\s+/).filter(Boolean),s=Date.now(),C=i.map(e=>{const H=a.filter(K=>e.topic.toLowerCase().includes(K)).length,ae=a.filter(K=>(e.tags??[]).some(te=>te.toLowerCase().includes(K))).length,ce=H*2+ae,de=Math.max(0,e.recall_count??0),le=Math.min(1.5,Math.log1p(de)/10),p=e.severity==="critical"?1:e.severity==="major"?.5:0,W=(s-new Date(e.verified_at??e.ts).getTime())/864e5,Z=W<30?.5*(1-W/30):0,q=e.outcome==="success"?.5:e.outcome==="partial"?.2:0,Q=ce+le+p+Z+q;return{lesson:e,score:Q}});C.sort((e,H)=>H.score!==e.score?H.score-e.score:new Date(H.lesson.ts).getTime()-new Date(e.lesson.ts).getTime());const r=(l?C.slice(0,o):C).map(e=>e.lesson),c=i.length,v=r.length,A=[l?`\u{1F9E0} Brain Summary \u2014 top ${v} of ${c} lessons for "${t}"`:`\u{1F9E0} Brain Summary \u2014 all ${v} lesson${v!==1?"s":""} for "${t}"`,"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",""];for(const e of r){const H=e.severity==="critical"?"\u{1F534}":e.severity==="major"?"\u26A0\uFE0F":"\u2705",ae=e.severity?`[${e.severity}] `:"[minor] ",ce=e.recall_count??0,de=ce>0?` (in ${ce} answer${ce===1?"":"s"})`:"",le=e.what_worked.slice(0,110),p=io(e.author,b);A.push(`${H} ${ae}\`${e.topic}\` \u2014 ${le}${de}${p}`)}A.push("");const I=ro(r,b);return I>0&&A.push(`\u{1F465} ${I} of these ${I===1?"was":"were"} written by a teammate \u2014 the value only a shared brain delivers.`,""),l?A.push(`\u{1F4A1} Showing top ${v} of ${c} lessons \xB7 \`session_start\` for full briefing`):A.push(`\u{1F4A1} All ${v} lesson${v!==1?"s":""} shown \xB7 \`session_start\` for full briefing`),A.join(`
37
- `)}case"session_end":{const{instance_id:d,summary:t,files_changed:h=[],lessons_learned:b,workspace_path:o=""}=M,n=ze({summary:t,files_changed:h}),E=n.felder.summary,$=n.felder.files_changed,i=await j(d),l=new Date;let a;const s=await i.get("cachly:session:current");if(!s)return"\u26A0\uFE0F No active session found.\n\nRun `session_start` first to begin tracking a session, then call `session_end` when you're done.";const C=O(s,null);C?.started&&(a=Math.round((l.getTime()-new Date(C.started).getTime())/6e4));let r=[];try{r=(await i.lrange("cachly:session:decision-log",0,-1)).flatMap(Z=>{const q=O(Z,null);return q?[q]:[]}),await i.del("cachly:session:decision-log")}catch{}const c=On(d);Dn(d);const v={ts:l.toISOString(),summary:E,files_changed:$,...c.recalls>0?{memory_cost:c}:{},...n.verdacht.length?{verdacht:n.verdacht}:{},...b!==void 0?{lessons_learned:b}:{},...a!==void 0?{duration_min:a}:{},...r.length>0?{decision_log:r}:{}};await i.set("cachly:session:last",JSON.stringify(v)),await i.lpush("cachly:session:history",JSON.stringify(v)),await i.ltrim("cachly:session:history",0,49),await i.expire("cachly:session:history",90*86400),await i.del("cachly:session:current");const m=[];try{const W=/\b(fixed|deployed|added|removed|refactored|migrated|updated|resolved|implemented|improved|optimized|configured|created|deleted|disabled|enabled|discovered|found|learned|debugged|patched|upgraded|installed|tested|built|rewrote|moved|renamed|split|merged|extracted)\b/i,Z=E.split(/[.!\n]+/).map(q=>q.trim()).filter(q=>q.length>20&&W.test(q));for(const q of Z.slice(0,6)){const K=q.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(T=>T.length>3&&!["that","this","with","from","have","been","were","they","then","when","also","into","will","would","could","should"].includes(T)).slice(0,4).join("-");if(!K)continue;const te=`auto:${K}`,u=`cachly:lesson:best:${te}`,k=await i.get(u);if(k)try{if(JSON.parse(k).outcome==="success")continue}catch{}const x={topic:te,outcome:"success",what_worked:q,context:`Auto-learned from session summary. Full summary: ${E.slice(0,300)}`,severity:"minor",ts:l.toISOString(),recall_count:0,auto_learned:!0,session_ts:l.toISOString(),version:2,...n.verdacht.length?{tags:[wt]}:{}};await i.set(u,JSON.stringify(x)),await i.expire(u,90*86400),m.push(te)}}catch{}const y=[],A=et(o);let I=0,e=0;if(A)try{const Z=`--since=${(s?(()=>{try{return JSON.parse(s).started??""}catch{return""}})():"")||"1 hour ago"}`,q=Pe(A,["log",Z,"--oneline","--format=%H|||%s|||%b|||%ai|||ENDE"]).trim();if(q){const Q=/\b(fix|add|remove|refactor|migrate|update|resolve|implement|improve|optimize|configure|create|delete|disable|enable|debug|patch|upgrade|build|rewrite|deploy|feat|chore|docs|test|perf|ci)\b/i,K=q.split("|||ENDE").map(te=>te.trim()).filter(Boolean);for(const te of K.slice(0,10)){const[u,k,x,T]=te.split("|||"),_=ze({msg:k??"",body:x??""}),S=_.felder.msg,N=_.felder.body;if(hs(u))try{const $e=Pe(A,["diff-tree","--no-commit-id","--name-only","-r","--root",u]).trim(),Ae=$e?$e.split(`
38
- `).map(fe=>fe.trim()).filter(Boolean).slice(0,12):[],we=[];for(const fe of Ae)we.push(await kt(i,fe));for(let fe=0;fe<we.length;fe++)for(let Se=fe+1;Se<we.length;Se++)await ve(i,we[fe],"co-occurs",we[Se],!0),await ve(i,we[Se],"co-occurs",we[fe],!0)}catch{}if(!S||!Q.test(S))continue;const L=(N??"").trim();if(L.length<120)continue;const J=S.toLowerCase().replace(/^(fix|feat|chore|docs|test|ci|perf|refactor|build|revert)[:(\s]/i,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);if(!J)continue;const ie=`git:${J}`,ge=`cachly:lesson:best:${ie}`;if(await i.get(ge))continue;const ye={topic:ie,outcome:"success",what_worked:`${S}
32
+ `)}case"session_start":{const{instance_id:f,focus:t="",author:l="",provider:_="",workspace_path:o=""}=M;if(await Mt(f,ae))return j(f).then(async d=>{await as(d),await is(d,t,_),await d.set("cachly:mcp:version:last_seen",Ve,"EX",365*86400)}).catch(()=>{}),be("session_start: neues Brain startet noch \u2014 sofort geantwortet"),Fs({instanzId:f,provider:_,...await ht(f)});const n=await j(f),E=[],$=n.scanStream({match:"cachly:lesson:best:*",count:1e3});await new Promise((d,k)=>{$.on("data",T=>E.push(...T)),$.on("end",d),$.on("error",k)});const i=[];let h=0;if(E.length>0){const d=await n.mget(...E);for(const k of d){const T=O(k??null,null);if(T){if(Yt(T)){h++;continue}es(T.topic)||i.push(T)}}}i.sort((d,k)=>new Date(k.ts).getTime()-new Date(d.ts).getTime());let a=0;const s=n.scanStream({match:"cachly:ctx:*",count:1e3});await new Promise((d,k)=>{s.on("data",T=>{a+=T.filter(N=>!N.endsWith(":meta")).length}),s.on("end",d),s.on("error",k)});const C=await n.get("cachly:session:last"),r=O(C,null),c=t.toLowerCase().split(/\s+/).filter(Boolean),v=c.length>0?i.filter(d=>c.some(k=>d.topic.toLowerCase().includes(k)||(d.tags??[]).some(T=>T.toLowerCase().includes(k)))):[],{tage:m,rekord:y}=await as(n);let A="";m>=7?A=`\u{1F525} **${m}-day streak!** ${m===y?" New record!":`Best: ${y}d`}`:m>1&&(A=`\u{1F525} ${m}-day streak`),await is(n,t,_);const e=[`\u{1F9E0} **Session Briefing**${_?` \xB7 ${_}`:""}`,""];A&&e.push(A,"");const G=await rs(f,ae),ce=Kn({used:G.used,limit:G.limit});if(ce&&e.push(ce,""),G.notice&&!ss.has(f)&&(ss.add(f),e.push(G.notice,"")),r?.ts){const d=new Date(r.ts).getTime(),k=Math.floor((Date.now()-d)/864e5);if(k>=7&&!Number.isNaN(d)){const T=i.filter(N=>{const b=new Date(N.ts).getTime();return!Number.isNaN(b)&&b>d});if(e.push(`\u{1F44B} **Welcome back** \u2014 it's been ${k} days since your last session.`),T.length>0){const N=T.slice(0,3).map(b=>`\`${b.topic}\``).join(", ");e.push(` \u{1F331} Your Brain grew by **${T.length} lesson${T.length!==1?"s":""}** while you were gone (git, CI & teammates kept it learning): ${N}${T.length>3?", \u2026":""}`)}else e.push(` \u{1F9E0} ${i.length} lesson${i.length!==1?"s":""} held steady and ready \u2014 recall anything with \`smart_recall\`.`);e.push(" \u{1F4A1} Full breakdown: `npx @cachly-dev/mcp-server@latest digest`","")}}try{if(await n.get("cachly:mcp:version:last_seen")!==Ve){await n.set("cachly:mcp:version:last_seen",Ve,"EX",365*86400);const k=bo[Ve];if(k){e.push(...k,"");const T=_o();T&&e.push(`\u{1F4CA} ${T} MCP tools`,"")}}}catch{}const oe=!r&&i.length===0&&h===0&&a===0;oe&&(e.push("\u{1F389} **Welcome! Your AI Brain is live.**",""),o?(e.push("\u{1F680} **Auto-bootstrapping from your git history...** (this takes a few seconds)"),e.push(" Your brain will learn from your recent commits \u2014 no setup needed.","")):(e.push("It learns from your work automatically. After your first session it will look like this:",""),e.push(" \u2705 `api:auth` \u2014 Bearer token in header, not cookie; 401 on missing scope"),e.push(" \u2705 `database:migrations` \u2014 always run migrations before deploy"),e.push(" \u26A0\uFE0F `docker:build` \u2014 ARG changes bust all subsequent cache layers"),e.push(""),e.push("**Tip:** Pass `workspace_path` to `session_start` to auto-learn from git history instantly."),e.push("")),e.push("**Your brain grows automatically:**"),e.push(' \u2022 End each session \u2192 `session_end(summary="What I did")` \u2014 auto-learns from git commits'),e.push(' \u2022 After fixing bugs \u2192 `learn_from_attempts(topic="...", outcome="success", what_worked="...")`'),e.push(""),e.push("\u{1F331} **Want answers right now?** Seed 16 universal engineering lessons so your first"),e.push(" `smart_recall` returns a hit instead of nothing:"),e.push(` \`brain_seed_starter(instance_id="${f}")\``),e.push(" _(Docker cache, JWT skew, K8s OOM, N+1 queries, cache stampede\u2026 \u2014 never overrides your own lessons.)_"),e.push(""),e.push("\u{1F4A1} Run `brain_doctor` for a health-check and personalised tips."),e.push(""));const he=v.slice(0,4),te=new Set(he.map(d=>d.topic));if(l&&!oe)try{const d=`cachly:team:first_briefing:${l}`;if(!await n.get(d)){const T=i.filter(N=>N.author&&N.author!==l&&!te.has(N.topic));if(T.length>0){await n.set(d,"1","EX",365*86400);const N=new Map;for(const b of T){const S=b.author;N.has(S)||N.set(S,[]),N.get(S).push(b)}e.push(""),e.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),e.push("\u{1F91D} **Your team's AI brain has been briefing you.**"),e.push(""),e.push("Your teammates have already solved problems you're about to hit:"),e.push("");for(const[b,S]of N){e.push(` \u{1F464} **${b}** fixed ${S.length} thing${S.length>1?"s":""}:`);for(const x of S.slice(0,2)){const B=x.outcome==="success"?"\u2705":"\u26A0\uFE0F";e.push(` ${B} \`${x.topic}\` \u2014 ${x.what_worked.slice(0,90)}`),te.add(x.topic)}S.length>2&&e.push(` \u2026 and ${S.length-2} more lessons`)}e.push(""),e.push("\u{1F4A1} Use `team_learn` after your next fix to pay it forward."),e.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),e.push("")}}}catch{}const p=await n.get("cachly:session:handoff");if(p)try{const d=JSON.parse(p),k=Math.round((Date.now()-new Date(d.ts).getTime())/6e4),T=k<60?`${k}m ago`:k<1440?`${Math.round(k/60)}h ago`:`${Math.round(k/1440)}d ago`;if(e.push(`\u{1F91D} **Handoff from previous window** (${T}):`),d.context_summary&&e.push(` ${d.context_summary}`),d.remaining_tasks.length>0){e.push(" \u23F3 **Remaining tasks:**");for(const b of d.remaining_tasks)e.push(` - ${b}`)}d.completed_tasks.length>0&&e.push(` \u2705 **Already done:** ${d.completed_tasks.join(", ")}`);const N=(d.files_changed??[]).filter(b=>b.status==="broken"||b.status==="partial");N.length>0&&e.push(` \u26A0\uFE0F **Needs fix:** ${N.map(b=>`\`${b.path}\` (${b.status}${b.description?": "+b.description:""})`).join(", ")}`),d.blocked_on&&e.push(` \u{1F6AB} **Blocked on:** ${d.blocked_on}`),d.instructions&&e.push(` \u{1F4DD} **Instructions:** ${d.instructions}`),e.push("")}catch{}const V=await n.get("cachly:session:checkpoint");if(V)try{const d=JSON.parse(V),k=new Date(d.ts).getTime(),T=r?new Date(r.ts).getTime():0;if(k>T){const N=Math.round((Date.now()-k)/6e4),b=N<60?`${N}m ago`:N<1440?`${Math.round(N/60)}h ago`:`${Math.round(N/1440)}d ago`,S=d.provider?` [${d.provider}]`:"";e.push(`\u{1F4CC} **Last checkpoint**${S} (${b}): ${d.task}`),d.files_touched.length>0&&e.push(` Files: ${d.files_touched.slice(0,5).map(x=>`\`${x}\``).join(", ")}`),d.next_step&&e.push(` \u{1F4CD} Next step was: ${d.next_step}`),(!r||k-T>3e5)&&e.push(" \u26A0\uFE0F No `session_end` found \u2014 reconstructed from last checkpoint"),e.push("")}}catch{}const U=r?null:et(o);if(U)try{const d=Pe(U,["log","--oneline","--format=%h %s","-15"]).trim();let k;try{k=Pe(U,["diff","--stat","HEAD~3"]).trim()}catch{k=Pe(U,["diff","--stat"]).trim()}if(d){e.push("\u{1F50D} **Git reconstruction** (no session_end found \u2014 reconstructed from git):");for(const T of d.split(`
33
+ `).slice(0,8))e.push(` ${T}`);if(k){const T=k.split(`
34
+ `).filter(N=>N.includes("|")||N.includes("changed"));if(T.length>0){e.push(" **Recent changes:**");for(const N of T.slice(0,5))e.push(` ${N.trim()}`)}}e.push("")}}catch{}if(r){const d=Math.round((Date.now()-new Date(r.ts).getTime())/6e4),k=d<60?`${d}m ago`:d<1440?`${Math.round(d/60)}h ago`:`${Math.round(d/1440)}d ago`;e.push(`\u{1F4C5} **Last session** (${k}): ${r.summary}`),r.duration_min&&e.push(` Duration: ${r.duration_min} min`),(r.files_changed??[]).length>0&&e.push(` Files: ${(r.files_changed??[]).slice(0,5).map(T=>`\`${T}\``).join(", ")}`),e.push("")}e.push(`\u{1F4CA} **Brain:** ${i.length} lessons \xB7 ${a} context entries`,""),h>0&&e.push(`\u26A0 ${h} lesson(s) were flagged as suspicious when saved and are not in this briefing. You can still fetch them explicitly with recall_best_solution (with a warning line); check them or save them again.`,"");{const k=await n.lrange(`cachly:wirkung:${f}`,0,-1).catch(()=>[]),T=Cn(k.map(S=>O(S,null)).filter(Boolean)),N=S=>T.get(S)??{geholfen:0,geoeffnet:0},b=i.filter(S=>!bs(S)).filter(S=>{if(S.outcome!=="success")return!1;const x=N(S.topic);return S.pinned===!0||x.geholfen>=1||x.geoeffnet>=2}).filter(S=>!te.has(S.topic)).sort((S,x)=>{const B=N(S.topic),J=N(x.topic);return J.geholfen-B.geholfen||J.geoeffnet-B.geoeffnet||(x.recall_count??0)-(S.recall_count??0)}).slice(0,5);if(b.length>0){e.push("\u{1F4CC} **Confirmed lessons** (pinned, reported helpful, or opened after a search \u2014 being shown does not count):");for(const S of b){te.add(S.topic);const x=S.severity==="critical"?"\u{1F534}":S.severity==="major"?"\u{1F7E1}":"",B=N(S.topic),J=[S.pinned===!0?"pinned":"",B.geholfen>0?`reported helpful ${B.geholfen}\xD7`:"",B.geoeffnet>0?`opened ${B.geoeffnet}\xD7 after a search`:""].filter(Boolean).join(" \xB7 "),le=Ye(S);e.push(` \u2705${x} \`${S.topic}\` _(${J})_ \u2014 ${le[0]??""}`,...le.slice(1))}e.push("")}}try{const d=[],k=n.scanStream({match:"cachly:ckg:conflict:*",count:1e3});if(await new Promise((T,N)=>{k.on("data",b=>d.push(...b)),k.on("end",T),k.on("error",N)}),d.length>0){e.push(`\u26A1 **Active belief conflicts (${d.length}):**`);for(const T of d.slice(0,3)){const N=await n.get(T);if(!N)continue;const b=O(N,null);b&&e.push(` \u26A0\uFE0F \`${b.topic}\` \u2014 previously confirmed fix (${(b.fix_confidence*100).toFixed(0)}%, n=${b.fix_trials}) now contradicted. Use \`ckg_inspect(concept="${we(b.topic)}")\``)}e.push("")}}catch{}if(he.length>0){e.push(`\u{1F3AF} **Relevant for "${t}":**`);for(const d of he){const k=d.outcome==="success"?"\u2705":d.outcome==="partial"?"\u26A0\uFE0F":"\u274C",T=d.severity==="critical"?"\u{1F534}":d.severity==="major"?"\u{1F7E1}":"",N=Ye(d);e.push(` ${k}${T} \`${d.topic}\` \u2014 ${N[0]??""}`,...N.slice(1))}e.push("")}if(i.length>0){const d=(v.length>0?i.filter(k=>!v.includes(k)):i).filter(k=>!te.has(k.topic)).slice(0,v.length>0?3:4);if(d.length>0){e.push("\u{1F550} **Recent lessons:**");for(const k of d){te.add(k.topic);const T=k.outcome==="success"?"\u2705":k.outcome==="partial"?"\u26A0\uFE0F":"\u274C",N=k.severity==="critical"?"\u{1F534}":k.severity==="major"?"\u{1F7E1}":"",b=Ye(k);e.push(` ${T}${N} \`${k.topic}\` \u2014 ${b[0]??""}`,...b.slice(1))}e.push("")}}else e.push("\u{1F4ED} No lessons yet. Use `learn_from_attempts` after solving tasks.","");i.length>=10&&(i.some(k=>k.author)||(e.push(`\u{1F91D} **Your Brain has ${i.length} lessons \u2014 your team could benefit instantly.**`),e.push(" Share: **cachly.dev/team** \xB7 or run `team_learn` after the next fix.","")));const Q=await Bn(n,i);if(Q.length>0){e.push("\u267B\uFE0F **Possible duplicates** (same meaning, different topic \u2014 nothing is merged):");for(const d of Q.slice(0,3))e.push(` \`${d.neu}\` \u2248 \`${d.alt}\` (${d.naehe.toFixed(2)})`);e.push(' _If the newer one replaces the older, pass `ersetzt="<older topic>"` on its next `learn_from_attempts`._'),e.push("")}const Z=r;if(Z?.decision_log?.length){const d=Z.decision_log,k=d.filter((b,S)=>!te.has(b.topic)&&d.findIndex(x=>x.topic===b.topic)===S),T=k.filter(b=>b.outcome==="success"),N=k.filter(b=>b.outcome==="failure");if(T.length>0||N.length>0){e.push(`\u{1F3AC} **Last session decisions** (${d.length} lessons stored):`),T.length>0&&e.push(` \u2705 Worked: ${T.slice(0,3).map(b=>`\`${b.topic}\``).join(", ")}`),N.length>0&&e.push(` \u274C Failed: ${N.slice(0,3).map(b=>`\`${b.topic}\``).join(", ")}`),e.push("");for(const b of[...T.slice(0,3),...N.slice(0,3)])te.add(b.topic)}}{const d=new Set(["src","lib","test","tests","dist","index","main","app","internal","pkg","cmd","node_modules","components","utils","ts","tsx","js","jsx","go","py","rs","java","json","yaml","yml"]),k=S=>{const x=new Set;for(const B of S)for(const J of B.toLowerCase().split(/[/\\._-]/))J.length>3&&!d.has(J)&&x.add(J);return[...x]};let T=c,N=t,b=!1;if(T.length===0&&r){const S=k(r.files_changed??[]),x=(r.summary??"").toLowerCase().replace(/[^a-z0-9\s:_-]/g," ").split(/\s+/).filter(B=>B.length>3&&!d.has(B));T=[...new Set([...S,...x])],N="where you left off last session",b=!0}if(T.length>0){const S=i.filter(x=>{if(x.outcome==="success")return!1;const B=x.topic.split(":")[0];return T.some(J=>x.topic.toLowerCase().includes(J)||B===J||(x.tags??[]).some(le=>le.toLowerCase()===J))});if(S.length>=1){const B=["\u{1F6A8} **PRE-WARNING** \u2014 Read this BEFORE starting:",b?` You're likely to continue **${N}** \u2014 ${S.length} known pitfall${S.length>1?"s":""} there:`:` Known pitfalls for **"${N}"** (${S.length} past failure${S.length>1?"s":""}):`,...S.slice(0,3).map(J=>` \u274C \`${J.topic}\` \u2014 ${(J.what_failed??J.what_worked).slice(0,80)}`),""];e.splice(2,0,...B)}}}if(l){const d=Date.now()-6048e5,k=i.filter(T=>T.author&&T.author!==l&&new Date(T.ts).getTime()>d&&!te.has(T.topic));if(k.length>0){const T=new Map;for(const N of k){const b=N.author;T.has(b)||T.set(b,[]),T.get(b).push(N)}e.push(`\u{1F465} **Team this week** (${k.length} lesson${k.length>1?"s":""} from teammates):`);for(const[N,b]of T){e.push(` \u{1F464} **${N}**:`);for(const S of b.slice(0,3)){const x=S.outcome==="success"?"\u2705":S.outcome==="partial"?"\u26A0\uFE0F":"\u274C";e.push(` ${x} \`${S.topic}\` \u2014 ${S.what_worked.slice(0,80)}`),te.add(S.topic)}b.length>3&&e.push(` \u2026 and ${b.length-3} more`)}e.push("")}}try{const d=await n.get("cachly:crystal:latest");if(d){const k=O(d,null);if(k&&Math.round((Date.now()-new Date(k.ts).getTime())/864e5)<=90){e.push(`\u{1F48E} **Memory Crystal** (${k.label} \xB7 ${k.session_count} sessions compressed):`);for(const N of k.top_patterns.slice(0,3))e.push(` \u2022 **${N.category}** (${N.count}\xD7): ${N.insight.slice(0,90)}`);e.push("")}}}catch{}try{const d=await n.hgetall(`cachly:roadmap:${f}`);if(d&&Object.keys(d).length>0){const k={critical:0,high:1,medium:2,low:3},T={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F535}"},N=new Set(["planned","in-progress","blocked"]),b=Object.values(d).flatMap(B=>{const J=O(B,null);return J?[J]:[]}),S=b.filter(B=>N.has(B.status)).sort((B,J)=>B.status==="in-progress"&&J.status!=="in-progress"?-1:J.status==="in-progress"&&B.status!=="in-progress"?1:(k[B.priority]??99)-(k[J.priority]??99)),x=b.filter(B=>B.status==="done").length;if(S.length>0){e.push(`\u{1F5FA}\uFE0F **Roadmap** (${S.length} open \xB7 ${x} done):`);for(const B of S.slice(0,5)){const J=B.status==="in-progress"?"\u26A1":B.status==="blocked"?"\u{1F6AB}":"\u{1F4CB}";e.push(` ${J} ${T[B.priority]??"\u26AA"} \`${B.id}\` **${B.title}**`)}S.length>5&&e.push(` \u2026 and ${S.length-5} more`),e.push(" _Use `roadmap_next` for the top priority item \xB7 `roadmap_list` for full view_"),e.push("")}}}catch{}try{const d=await n.get("cachly:mcm:calibration:last"),k=O(d,null)?.ts??0;if((Date.now()-k)/864e5>=30&&i.length>=5){const N=i.filter(S=>S.outcome==="success"&&(S.recall_count??0)>0),b=i.filter(S=>(S.recall_count??0)>0);if(b.length>=3){const S=b.length>0?N.length/b.length:1,x=[],B=new Map;for(const J of b){const le=J.topic.split(":")[0]??"other";B.has(le)||B.set(le,{success:0,total:0});const ye=B.get(le);ye.total++,J.outcome==="success"&&ye.success++}for(const[J,le]of B)le.total>=2&&le.success/le.total<.6&&x.push(J);await n.set("cachly:mcm:calibration:last",JSON.stringify({ts:Date.now(),precision:S,recalled:b.length,low_precision_domains:x})),(x.length>0||S<.7)&&(e.push(`\u{1F4D0} **MCM Calibration** (30-day pass \u2014 ${b.length} recalled lessons):`),e.push(` Overall precision: **${(S*100).toFixed(0)}%** recalled lessons actually worked`),x.length>0&&e.push(` \u26A0\uFE0F Low-precision domains: ${x.map(J=>`\`${J}\``).join(", ")} \u2014 consider revisiting these lessons`),e.push(" \u{1F4A1} Use `learn_from_attempts` to update stale lessons and improve accuracy."),e.push(""))}await n.set("cachly:mcm:calibration:last",JSON.stringify({ts:Date.now(),precision:1,recalled:0}),"EX",35*86400)}}catch{}const K=i.filter(d=>d.outcome==="failure"||d.outcome==="partial").length,ne=i.filter(d=>d.outcome==="success"&&$t(d)<fs).length;if(i.length===0||K>0||ne>0){const d=[];i.length===0&&d.push("brain is empty"),K>0&&d.push(`${K} open failure${K>1?"s":""}`),ne>0&&d.push(`${ne} stale lesson${ne>1?"s":""}`),e.push(`\u{1FA7A} _Run \`brain_doctor\` to fix: ${d.join(", ")}._`)}try{if(await n.get(`cachly:prefs:auto_changelog:${f}`).catch(()=>null)!=="false"&&r?.ts&&!oe){const T=new Date(r.ts).getTime();if(!Number.isNaN(T)){const N=i.filter(S=>{const x=new Date(S.ts).getTime();return!Number.isNaN(x)&&x>T}),b=N.filter(S=>!te.has(S.topic));if(b.length>0){const S=Math.round((Date.now()-T)/6e4),x=S<60?`${S}m`:S<1440?`${Math.round(S/60)}h`:`${Math.round(S/1440)}d`,B=N.length-b.length;e.push(""),e.push(`\u{1F4CB} **${N.length} new lesson${N.length!==1?"s":""} since your last session** (${x} ago${B>0?`, ${B} already listed above`:""}):`);const J=new Map;for(const le of b.slice(0,20)){const ye=le.topic.includes(":")?le.topic.split(":")[0]:"general",se=J.get(ye)??[];se.push(le),J.set(ye,se)}for(const[le,ye]of J){e.push(` **${le}**`);for(const se of ye){const de=se.outcome==="failure"?"\u274C":se.severity==="critical"?"\u{1F6A8}":se.severity==="major"?"\u26A0\uFE0F":"\u2705",ge=(se.outcome==="failure"?se.what_failed:se.what_worked)??"";e.push(` ${de} \`${se.topic}\`${ge?` \u2014 ${ge.slice(0,80)}`:""}`)}}b.length>20&&e.push(` \u2026 and ${b.length-20} more. Call \`brain_changelog\` for the full list.`),e.push(` _Disable auto-changelog: \`brain_set_pref(instance_id="${f}", key="auto_changelog", value="false")\`_`)}}}}catch{}return e.join(`
35
+ `)}case"session_start_summary":{const{instance_id:f,focus:t,top_n:l=10,author:_=""}=M;if(!t||!t.trim())throw new Error("focus is required for session_start_summary");const o=Math.min(Math.max(1,Math.floor(Number(l)||10)),25),n=await j(f),E=[],$=n.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((e,G)=>{$.on("data",ce=>E.push(...ce)),$.on("end",e),$.on("error",G)});const i=[];if(E.length>0){const e=await n.mget(...E);for(const G of e){const ce=O(G??null,null);ce&&!Yt(ce)&&!es(ce.topic)&&i.push(ce)}}if(i.length===0)return["\u{1F9E0} Brain Summary \u2014 no lessons yet","","Your brain is empty. Use `learn_from_attempts` after solving tasks to build it up.",`\u{1F4A1} Tip: \`brain_seed_starter(instance_id="${f}")\` to bootstrap with 16 universal lessons.`].join(`
36
+ `);const h=i.length>o,a=t.toLowerCase().split(/\s+/).filter(Boolean),s=Date.now(),C=i.map(e=>{const G=a.filter(K=>e.topic.toLowerCase().includes(K)).length,ce=a.filter(K=>(e.tags??[]).some(ne=>ne.toLowerCase().includes(K))).length,oe=G*2+ce,he=Math.max(0,e.recall_count??0),te=Math.min(1.5,Math.log1p(he)/10),p=e.severity==="critical"?1:e.severity==="major"?.5:0,V=(s-new Date(e.verified_at??e.ts).getTime())/864e5,U=V<30?.5*(1-V/30):0,Q=e.outcome==="success"?.5:e.outcome==="partial"?.2:0,Z=oe+te+p+U+Q;return{lesson:e,score:Z}});C.sort((e,G)=>G.score!==e.score?G.score-e.score:new Date(G.lesson.ts).getTime()-new Date(e.lesson.ts).getTime());const r=(h?C.slice(0,o):C).map(e=>e.lesson),c=i.length,v=r.length,A=[h?`\u{1F9E0} Brain Summary \u2014 top ${v} of ${c} lessons for "${t}"`:`\u{1F9E0} Brain Summary \u2014 all ${v} lesson${v!==1?"s":""} for "${t}"`,"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",""];for(const e of r){const G=e.severity==="critical"?"\u{1F534}":e.severity==="major"?"\u26A0\uFE0F":"\u2705",ce=e.severity?`[${e.severity}] `:"[minor] ",oe=e.recall_count??0,he=oe>0?` (in ${oe} answer${oe===1?"":"s"})`:"",te=e.what_worked.slice(0,110),p=lo(e.author,_);A.push(`${G} ${ce}\`${e.topic}\` \u2014 ${te}${he}${p}`)}A.push("");const I=ho(r,_);return I>0&&A.push(`\u{1F465} ${I} of these ${I===1?"was":"were"} written by a teammate \u2014 the value only a shared brain delivers.`,""),h?A.push(`\u{1F4A1} Showing top ${v} of ${c} lessons \xB7 \`session_start\` for full briefing`):A.push(`\u{1F4A1} All ${v} lesson${v!==1?"s":""} shown \xB7 \`session_start\` for full briefing`),A.join(`
37
+ `)}case"session_end":{const{instance_id:f,summary:t,files_changed:l=[],lessons_learned:_,workspace_path:o=""}=M,n=ze({summary:t,files_changed:l}),E=n.felder.summary,$=n.felder.files_changed,i=await j(f),h=new Date;let a;const s=await i.get("cachly:session:current");if(!s)return"\u26A0\uFE0F No active session found.\n\nRun `session_start` first to begin tracking a session, then call `session_end` when you're done.";const C=O(s,null);C?.started&&(a=Math.round((h.getTime()-new Date(C.started).getTime())/6e4));let r=[];try{r=(await i.lrange("cachly:session:decision-log",0,-1)).flatMap(he=>{const te=O(he,null);return te?[te]:[]}),await i.del("cachly:session:decision-log")}catch{}const c=Pn(f);jn(f);const v={ts:h.toISOString(),summary:E,files_changed:$,...c.recalls>0?{memory_cost:c}:{},...n.verdacht.length?{verdacht:n.verdacht}:{},..._!==void 0?{lessons_learned:_}:{},...a!==void 0?{duration_min:a}:{},...r.length>0?{decision_log:r}:{}};await i.set("cachly:session:last",JSON.stringify(v)),await i.lpush("cachly:session:history",JSON.stringify(v)),await i.ltrim("cachly:session:history",0,49),await i.expire("cachly:session:history",90*86400),await i.del("cachly:session:current");const m=[];try{const oe=/\b(fixed|deployed|added|removed|refactored|migrated|updated|resolved|implemented|improved|optimized|configured|created|deleted|disabled|enabled|discovered|found|learned|debugged|patched|upgraded|installed|tested|built|rewrote|moved|renamed|split|merged|extracted)\b/i,he=E.split(/[.!\n]+/).map(te=>te.trim()).filter(te=>te.length>20&&oe.test(te));for(const te of he.slice(0,6)){const V=te.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(ne=>ne.length>3&&!["that","this","with","from","have","been","were","they","then","when","also","into","will","would","could","should"].includes(ne)).slice(0,4).join("-");if(!V)continue;const U=`auto:${V}`,Q=`cachly:lesson:best:${U}`,Z=await i.get(Q);if(Z)try{if(JSON.parse(Z).outcome==="success")continue}catch{}const K={topic:U,outcome:"success",what_worked:te,context:`Auto-learned from session summary. Full summary: ${E.slice(0,300)}`,severity:"minor",ts:h.toISOString(),recall_count:0,auto_learned:!0,session_ts:h.toISOString(),version:2,...n.verdacht.length?{tags:[wt]}:{}};await i.set(Q,JSON.stringify(K)),await i.expire(Q,90*86400),m.push(U)}}catch{}const y=[],A=et(o);let I=0,e=0;if(A)try{const he=`--since=${(s?(()=>{try{return JSON.parse(s).started??""}catch{return""}})():"")||"1 hour ago"}`,te=Pe(A,["log",he,"--oneline","--format=%H|||%s|||%b|||%ai|||ENDE"]).trim();if(te){const p=/\b(fix|add|remove|refactor|migrate|update|resolve|implement|improve|optimize|configure|create|delete|disable|enable|debug|patch|upgrade|build|rewrite|deploy|feat|chore|docs|test|perf|ci)\b/i,V=te.split("|||ENDE").map(U=>U.trim()).filter(Boolean);for(const U of V.slice(0,10)){const[Q,Z,K,ne]=U.split("|||"),d=ze({msg:Z??"",body:K??""}),k=d.felder.msg,T=d.felder.body;if(hs(Q))try{const le=Pe(A,["diff-tree","--no-commit-id","--name-only","-r","--root",Q]).trim(),se=le?le.split(`
38
+ `).map(ge=>ge.trim()).filter(Boolean).slice(0,12):[],de=[];for(const ge of se)de.push(await kt(i,ge));for(let ge=0;ge<de.length;ge++)for(let ke=ge+1;ke<de.length;ke++)await $e(i,de[ge],"co-occurs",de[ke],!0),await $e(i,de[ke],"co-occurs",de[ge],!0)}catch{}if(!k||!p.test(k))continue;const N=(T??"").trim();if(N.length<120)continue;const b=k.toLowerCase().replace(/^(fix|feat|chore|docs|test|ci|perf|refactor|build|revert)[:(\s]/i,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);if(!b)continue;const S=`git:${b}`,x=`cachly:lesson:best:${S}`;if(await i.get(x))continue;const J={topic:S,outcome:"success",what_worked:`${k}
39
39
 
40
- ${L.slice(0,1200)}`,context:`Auto-learned from git commit ${(u??"").slice(0,7)} at ${T??""} in ${A}`,severity:"minor",ts:l.toISOString(),recall_count:0,auto_learned:!0,source:"ambient-git",version:3,..._.verdacht.length?{tags:[wt]}:{}};await i.set(ge,JSON.stringify(ye)),await i.expire(ge,60*86400),y.push(ie),_.geschwaerzt.length&&I++,_.verdacht.length&&e++}}}catch{}const H=a!==void 0?` \xB7 ${a} min`:"",ae=m.length+y.length,ce=[];a!==void 0&&a>0&&ce.push(`\u23F1 ${a} min session`),ae>0&&ce.push(`\u{1F9E0} ${ae} lessons saved to Brain`),$.length>0&&ce.push(`\u{1F4C1} ${$.length} file${$.length>1?"s":""} changed`);const de=ce.length>0?`${ce.join(" \xB7 ")}
41
-
42
- My AI Brain remembers this so I never repeat it. @cachlydev
43
- cachly.dev`:`Session saved to my AI Brain. No more re-explaining this tomorrow. @cachlydev
44
- cachly.dev`,le=`https://twitter.com/intent/tweet?text=${encodeURIComponent(de)}`,p=["","\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510","\u2502 \u{1F9E0} Session Summary Card \u2502","\u2502 Share your progress \u2014 cached forever \u2502","\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524",a!==void 0?`\u2502 \u23F1 Duration : ${(a+" min").padEnd(26)}\u2502`:"",ae>0?`\u2502 \u{1F4DA} Learned : ${(ae+" lessons").padEnd(26)}\u2502`:"",$.length>0?`\u2502 \u{1F4C1} Changed : ${($.length+" file"+($.length>1?"s":"")).padEnd(26)}\u2502`:"","\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524",`\u2502 \u{1F4E3} Share: ${le.slice(0,34).padEnd(34)}\u2502`,"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"].filter(W=>W!=="").join(`
45
- `);return[`\u2705 **Session saved**${H}`,"",`\u{1F4CB} **Summary:** ${E}`,$.length>0?`\u{1F4C1} **Files changed:** ${$.map(W=>`\`${W}\``).join(", ")}`:"",b!==void 0?`\u{1F9E0} **Lessons stored:** ${b}`:"",...zn(c),m.length>0?`\u{1F916} **Auto-learned:** ${m.length} lessons extracted from summary (${m.slice(0,3).map(W=>`\`${W}\``).join(", ")}${m.length>3?"\u2026":""})`:"",y.length>0?`\u{1F33F} **Ambient git learning:** ${y.length} commit${y.length>1?"s":""} auto-learned (${y.slice(0,3).map(W=>`\`${W}\``).join(", ")}${y.length>3?"\u2026":""})`:"",...Ke(n,"notiz"),...Qt(I,e,`${y.length} Git-Lektionen`),p,"",'\u{1F4A1} Next session: `session_start(focus="...")` to see this summary.'].filter(W=>W!=="").join(`
46
- `)}case"session_ping":{const{instance_id:d,task:t,files_touched:h=[],next_step:b="",provider:o=""}=M,n=ze({task:t,files_touched:h,next_step:b,provider:o}),{task:E,files_touched:$,next_step:i,provider:l}=n.felder,a=await j(d),s={ts:new Date().toISOString(),task:E,files_touched:$,next_step:i,provider:l,...n.verdacht.length?{verdacht:n.verdacht}:{}};await a.set("cachly:session:checkpoint",JSON.stringify(s),"EX",86400*3),await a.lpush("cachly:session:checkpoint:log",JSON.stringify(s)),await a.ltrim("cachly:session:checkpoint:log",0,19);const C=l?` [${l}]`:"",r=$.length>0?` \xB7 ${$.length} file${$.length>1?"s":""} touched`:"",c=i?`
47
- \u{1F4CD} **Next step:** ${i}`:"";return[`\u{1F4CC} **Checkpoint saved**${C} \u2014 ${new Date().toLocaleTimeString()}`,`\u{1F528} **Working on:** ${E}${r}`,c,"","\u{1F4A1} If you switch providers, `session_start` will show this checkpoint automatically.",...Ke(n,"notiz")].filter(v=>v!=="").join(`
48
- `)}case"session_handoff":{const{instance_id:d,completed_tasks:t=[],remaining_tasks:h=[],files_changed:b=[],instructions:o="",context_summary:n="",blocked_on:E=""}=M,$=ze({completed_tasks:t,remaining_tasks:h,files_changed:b,instructions:o,context_summary:n,blocked_on:E}),{completed_tasks:i,remaining_tasks:l,files_changed:a,instructions:s,context_summary:C,blocked_on:r}=$.felder,c=await j(d),m={ts:new Date().toISOString(),completed_tasks:i,remaining_tasks:l,files_changed:a,instructions:s,context_summary:C,blocked_on:r,...$.verdacht.length?{verdacht:$.verdacht}:{}};await c.set("cachly:session:handoff",JSON.stringify(m)),await c.lpush("cachly:session:handoff:history",JSON.stringify(m)),await c.ltrim("cachly:session:handoff:history",0,19);const y=i.length+l.length,A=y>0?Math.round(i.length/y*100):0,I=a.filter(e=>e.status==="broken"||e.status==="partial");return[`\u{1F91D} **Handoff saved** \u2014 ${i.length}/${y} tasks done (${A}%)`,"",i.length>0?`\u2705 **Completed:**
40
+ ${N.slice(0,1200)}`,context:`Auto-learned from git commit ${(Q??"").slice(0,7)} at ${ne??""} in ${A}`,severity:"minor",ts:h.toISOString(),recall_count:0,auto_learned:!0,source:"ambient-git",version:3,...d.verdacht.length?{tags:[wt]}:{}};await i.set(x,JSON.stringify(J)),await i.expire(x,60*86400),y.push(S),d.geschwaerzt.length&&I++,d.verdacht.length&&e++}}}catch{}const G=a!==void 0?` \xB7 ${a} min`:"",ce=m.length+y.length;return[`\u2705 **Session saved**${G}`,"",`\u{1F4CB} **Summary:** ${E}`,$.length>0?`\u{1F4C1} **Files changed:** ${$.map(oe=>`\`${oe}\``).join(", ")}`:"",_!==void 0?`\u{1F9E0} **Lessons stored:** ${_}`:"",...Wn(c),m.length>0?`\u{1F916} **Auto-learned:** ${m.length} lessons extracted from summary (${m.slice(0,3).map(oe=>`\`${oe}\``).join(", ")}${m.length>3?"\u2026":""})`:"",y.length>0?`\u{1F33F} **Ambient git learning:** ${y.length} commit${y.length>1?"s":""} auto-learned (${y.slice(0,3).map(oe=>`\`${oe}\``).join(", ")}${y.length>3?"\u2026":""})`:"",...He(n,"notiz"),...Qt(I,e,`${y.length} git lessons`),ce>0?`\u{1F4DA} **Learned this session:** ${ce} lesson${ce>1?"s":""}`:"","",'\u{1F4A1} Next session: `session_start(focus="...")` to see this summary.'].filter(oe=>oe!=="").join(`
41
+ `)}case"session_ping":{const{instance_id:f,task:t,files_touched:l=[],next_step:_="",provider:o=""}=M,n=ze({task:t,files_touched:l,next_step:_,provider:o}),{task:E,files_touched:$,next_step:i,provider:h}=n.felder,a=await j(f),s={ts:new Date().toISOString(),task:E,files_touched:$,next_step:i,provider:h,...n.verdacht.length?{verdacht:n.verdacht}:{}};await a.set("cachly:session:checkpoint",JSON.stringify(s),"EX",86400*3),await a.lpush("cachly:session:checkpoint:log",JSON.stringify(s)),await a.ltrim("cachly:session:checkpoint:log",0,19);const C=h?` [${h}]`:"",r=$.length>0?` \xB7 ${$.length} file${$.length>1?"s":""} touched`:"",c=i?`
42
+ \u{1F4CD} **Next step:** ${i}`:"";return[`\u{1F4CC} **Checkpoint saved**${C} \u2014 ${new Date().toLocaleTimeString()}`,`\u{1F528} **Working on:** ${E}${r}`,c,"","\u{1F4A1} If you switch providers, `session_start` will show this checkpoint automatically.",...He(n,"notiz")].filter(v=>v!=="").join(`
43
+ `)}case"session_handoff":{const{instance_id:f,completed_tasks:t=[],remaining_tasks:l=[],files_changed:_=[],instructions:o="",context_summary:n="",blocked_on:E=""}=M,$=ze({completed_tasks:t,remaining_tasks:l,files_changed:_,instructions:o,context_summary:n,blocked_on:E}),{completed_tasks:i,remaining_tasks:h,files_changed:a,instructions:s,context_summary:C,blocked_on:r}=$.felder,c=await j(f),m={ts:new Date().toISOString(),completed_tasks:i,remaining_tasks:h,files_changed:a,instructions:s,context_summary:C,blocked_on:r,...$.verdacht.length?{verdacht:$.verdacht}:{}};await c.set("cachly:session:handoff",JSON.stringify(m)),await c.lpush("cachly:session:handoff:history",JSON.stringify(m)),await c.ltrim("cachly:session:handoff:history",0,19);const y=i.length+h.length,A=y>0?Math.round(i.length/y*100):0,I=a.filter(e=>e.status==="broken"||e.status==="partial");return[`\u{1F91D} **Handoff saved** \u2014 ${i.length}/${y} tasks done (${A}%)`,"",i.length>0?`\u2705 **Completed:**
49
44
  ${i.map(e=>` - ${e}`).join(`
50
- `)}`:"",l.length>0?`
45
+ `)}`:"",h.length>0?`
51
46
  \u23F3 **Remaining for next window:**
52
- ${l.map(e=>` - ${e}`).join(`
47
+ ${h.map(e=>` - ${e}`).join(`
53
48
  `)}`:"",I.length>0?`
54
49
  \u26A0\uFE0F **Needs attention:** ${I.map(e=>`\`${e.path}\` (${e.status})`).join(", ")}`:"",r?`
55
50
  \u{1F6AB} **Blocked on:** ${r}`:"",s?`
56
- \u{1F4DD} **Instructions:** ${s}`:"","","\u{1F4A1} The next `session_start` will include this handoff automatically.",...Ke($,"notiz")].filter(e=>e!=="").join(`
57
- `)}case"auto_learn_session":{const{instance_id:d,observations:t}=M,h=await j(d),b=[],o=[];let n=0,E=0;for(const i of t){const l=ze(i),a=l.felder,s=a.topic??a.action.toLowerCase().replace(/[^a-z0-9:\-_\s]/g,"").trim().split(/\s+/).slice(0,4).join("-"),C=s.includes(":")?s:`auto:${s}`,r=`cachly:lesson:best:${C}`,c=await h.get(r);if(c&&JSON.parse(c).outcome==="success"&&a.outcome!=="success"){o.push(C);continue}const v={topic:C,outcome:a.outcome,what_worked:a.outcome==="success"?a.action:a.details??a.action,what_failed:a.outcome==="failure"?a.action:void 0,context:a.details,severity:a.severity??"minor",ts:new Date().toISOString(),confidence:1,recall_count:0,auto_learned:!0,version:2,...l.verdacht.length?{tags:[wt]}:{}},m=JSON.stringify(v);l.geschwaerzt.length&&n++,l.verdacht.length&&E++,await h.set(r,m);const y=`cachly:lessons:${C}`;await h.rpush(y,m),await h.ltrim(y,-100,-1),await h.expire(y,90*86400);try{const A=C.split(":")[0]??"auto",I=_e(C);if(await je(h,I,A,A),a.outcome==="success"||a.outcome==="partial"){const e=a.details?tt(a.details):null;if(e){const H=_e(`problem:${e}`);await je(h,H,"problem","problem"),await ve(h,I,"fixes",H,a.outcome==="success",a.outcome==="partial")}}}catch{}b.push(`${a.outcome==="success"?"\u2705":a.outcome==="partial"?"\u26A0\uFE0F":"\u274C"} \`${C}\``)}const $=[`\u{1F916} **Auto-learn complete**: ${b.length} stored, ${o.length} skipped`,""];return b.length>0&&$.push("**Stored:**",...b.map(i=>" "+i),""),o.length>0&&$.push(`**Skipped** (better lesson already exists): ${o.map(i=>`\`${i}\``).join(", ")}`),$.push(...Qt(n,E,`${b.length} Beobachtungen`)),$.join(`
58
- `)}case"brain_who_knows":{const{instance_id:d,topic:t,limit:h=10}=M;if(!t||typeof t!="string"||!t.trim())return'\u26A0\uFE0F `brain_who_knows` requires a non-empty `topic`. Example: `brain_who_knows(topic="kubernetes deployment")`.';const b=Math.max(1,Math.min(Number(h)||10,50)),o=await j(d),n=_e(t),E=lt(t).filter(r=>r.length>=3).slice(0,5),$=new Set([n]);for(const r of E.slice(0,3)){const c=o.scanStream({match:`cachly:ckg:node:*${r}*`,count:30});await new Promise((v,m)=>{c.on("data",y=>{for(const A of y.slice(0,10)){const I=A.replace("cachly:ckg:node:","");!I.startsWith("person:")&&!I.startsWith("file:")&&$.add(I)}}),c.on("end",v),c.on("error",m)})}const i=new Map;for(const r of[...$].slice(0,25)){const c=await o.smembers(`cachly:ckg:idx:to:${r}`);for(const v of c){const m=await o.get(v);if(!m)continue;const y=O(m,null);if(!y||y.edgeType!=="authored")continue;const A=await o.get(`cachly:ckg:node:${y.from}`);if(!A)continue;const I=O(A,null);if(!I||I.type!=="person")continue;const e=i.get(y.from);e?(e.lessonCount++,e.totalConfidence+=y.confidence,e.domains.add(I.domain),y.last_updated>e.lastActive&&(e.lastActive=y.last_updated)):i.set(y.from,{handle:I.handle,lessonCount:1,totalConfidence:y.confidence,domains:new Set([I.domain]),lastActive:I.last_active??y.last_updated})}}if(i.size===0)return[`## \u{1F465} Who Knows About \`${t}\`?`,"","No attributed lessons found yet.","",'Knowledge attribution builds automatically \u2014 add `author="name"` to `learn_from_attempts` calls:',`\`learn_from_attempts(topic="${t}", author="your-handle", ...)\``].join(`
59
- `);const l=[...i.entries()].map(([r,c])=>({id:r,handle:c.handle,lessonCount:c.lessonCount,avgConfidence:c.totalConfidence/c.lessonCount,domains:[...c.domains].slice(0,3).join(", "),lastActive:c.lastActive})).sort((r,c)=>c.lessonCount*c.avgConfidence-r.lessonCount*r.avgConfidence).slice(0,b),a=[],s=l[0];if(s)try{const r=await o.smembers(`cachly:ckg:idx:from:${s.id}`),c=[];for(const v of r){const m=await o.get(v);if(!m)continue;const y=O(m,null);if(!y||y.edgeType!=="collaborates")continue;const A=await o.get(`cachly:ckg:node:${y.to}`),I=A?O(A,null):null;I?.handle&&c.push({handle:I.handle,trials:y.trials})}c.sort((v,m)=>m.trials-v.trials);for(const v of c.slice(0,3))a.push(v.handle)}catch{}const C=[`## \u{1F465} Who Knows About \`${t}\`?`,"",`**${l.length}** contributor${l.length!==1?"s":""} with relevant lessons:`,""];for(const[r,c]of l.entries()){const v=r===0?"\u{1F947}":r===1?"\u{1F948}":r===2?"\u{1F949}":`${r+1}.`,m=(c.avgConfidence*100).toFixed(0),y=Math.floor((Date.now()-new Date(c.lastActive).getTime())/864e5),A=y<1?"today":y<7?`${y}d ago`:y<30?`${Math.floor(y/7)}w ago`:`${Math.floor(y/30)}mo ago`,I=await ct(o,d,c.handle).catch(()=>null),e=I?` ${Bs[I]}`:"";C.push(`${v} **${c.handle}**${e} \u2014 ${c.lessonCount} lesson${c.lessonCount!==1?"s":""} \xB7 ${m}% confidence \xB7 last active ${A}`,` _domains: ${c.domains}_`,"")}return a.length>0&&s&&C.push(`\u{1F91D} **${s.handle}** frequently works with: ${a.map(r=>`**${r}**`).join(", ")}`," _(ask them together \u2014 they've solved structurally similar things on shared files)_",""),C.push("---",'_Attribution grows with every `learn_from_attempts(author="...", ...)` call._'),C.join(`
60
- `)}case"brain_file_map":{const{instance_id:d,file_paths:t=[]}=M,h=(Array.isArray(t)?t:[]).filter(n=>typeof n=="string"&&n.trim().length>0).map(n=>n.trim());if(!h.length)return'\u26A0\uFE0F Pass at least one file path. Example: `brain_file_map(file_paths=["src/auth/jwt.ts"])`.';const b=await j(d),o=["## \u{1F4C1} File Knowledge Map","","_What cachly knows about the files you're about to touch:_",""];for(const n of h.slice(0,10)){o.push(`### \`${n}\``);const E=`file:${_e(n)}`,$=await b.smembers(`cachly:ckg:idx:to:${E}`),i=[];for(const C of $){const r=await b.get(C);if(!r)continue;const c=O(r,null);if(!c||c.edgeType!=="touched")continue;const v=await b.get(`cachly:ckg:node:${c.from}`);if(!v)continue;const m=O(v,null);!m||m.type!=="person"||i.push({handle:m.handle,touches:c.trials,lastSeen:c.last_updated})}if(i.sort((C,r)=>r.touches-C.touches),i.length>0){const C=i.slice(0,5).map((r,c)=>{const v=c===0?"\u{1F947}":c===1?"\u{1F948}":c===2?"\u{1F949}":`${c+1}.`,m=Math.floor((Date.now()-new Date(r.lastSeen).getTime())/864e5),y=m<1?"today":m<7?`${m}d ago`:`${Math.floor(m/7)}w ago`;return`${v} **${r.handle}** (${r.touches}\xD7 \xB7 ${y})`}).join(" \xB7 ");o.push(`**Experts:** ${C}`)}else o.push("**Experts:** _None yet \u2014 add `author` to `learn_from_attempts` calls_");const a=n.replace(/[^a-z0-9]/gi," ").split(/\s+/).filter(C=>C.length>=4).slice(0,3).join(" "),s=[];if(a)try{const C=await Xe(b,["cachly:lesson:best:*"],a,4);for(const r of C){const c=Le(r.content);if(!We(c))continue;const v=c.outcome==="success"?"\u2705":c.outcome==="partial"?"\u26A0\uFE0F":"\u274C",m=c.severity==="critical"?" \u{1F534}":c.severity==="major"?" \u{1F7E1}":"",y=c.author?` \xB7 \u{1F464} ${c.author}`:"",A=r.key.replace("cachly:lesson:best:","");s.push(` - ${v} \`${A}\`${m}${y} \u2014 ${(c.what_worked??"").slice(0,100)}`)}}catch{}if(s.length<2){const C=await Ee(b,"cachly:lesson:best:*",{max:1500});for(const r of C.slice(0,300)){const c=await b.get(r);if(!c)continue;const v=Le(c);if(!We(v)||!(v.file_paths??[]).includes(n))continue;const m=r.replace("cachly:lesson:best:","");if(s.some(e=>e.includes(`\`${m}\``)))continue;const y=v.outcome==="success"?"\u2705":v.outcome==="partial"?"\u26A0\uFE0F":"\u274C",A=v.severity==="critical"?" \u{1F534}":v.severity==="major"?" \u{1F7E1}":"",I=v.author?` \xB7 \u{1F464} ${v.author}`:"";s.push(` - ${y} \`${m}\`${A}${I} \u2014 ${(v.what_worked??"").slice(0,100)}`)}}s.length>0?(o.push("**Related lessons:**"),o.push(...s.slice(0,5))):o.push("**Related lessons:** _None yet_"),o.push("")}return o.push("---",'_Attribution builds with `learn_from_attempts(file_paths=[...], author="...")`. Run this before committing to track what changed and why._'),o.join(`
61
- `)}case"team_expertise_map":{const{instance_id:d,top_n:t=20}=M,h=await j(d),b=await Ee(h,"cachly:ckg:node:person:*",{max:2e3});if(b.length===0)return["## \u{1F5FA}\uFE0F Team Expertise Map","","No contributors yet.","",'Add `author="handle"` to any `learn_from_attempts` call to start building the map.'].join(`
62
- `);const o=[];for(const $ of b){const i=await h.get($);if(!i)continue;const l=O(i,null);if(!l||l.type!=="person")continue;const a=await h.smembers(`cachly:ckg:idx:from:${l.id}`),s=new Map;let C=0;for(const r of a){const c=await h.get(r);if(!c)continue;const v=O(c,null);if(!v||v.edgeType!=="authored")continue;C++;const m=v.to.split(":")[0]??"unknown";s.set(m,(s.get(m)??0)+1)}o.push({handle:l.handle,lessonCount:C,domains:s,lastActive:l.last_active})}o.sort(($,i)=>i.lessonCount-$.lessonCount);const n=o.slice(0,t),E=["## \u{1F5FA}\uFE0F Team Expertise Map","",`**${n.length}** contributor${n.length!==1?"s":""} tracked:`,"","| # | Handle | Lessons | Top domains | Last active |","|---|--------|---------|-------------|-------------|"];for(const[$,i]of n.entries()){const l=$===0?"\u{1F947}":$===1?"\u{1F948}":$===2?"\u{1F949}":`${$+1}`,a=Math.floor((Date.now()-new Date(i.lastActive).getTime())/864e5),s=a<1?"today":a<7?`${a}d ago`:a<30?`${Math.floor(a/7)}w ago`:`${Math.floor(a/30)}mo ago`,C=[...i.domains.entries()].sort((r,c)=>c[1]-r[1]).slice(0,3).map(([r,c])=>`${r}(${c})`).join(", ")||"\u2014";E.push(`| ${l} | **${i.handle}** | ${i.lessonCount} | ${C} | ${s} |`)}return E.push("","---",'_Use `brain_who_knows(topic="...")` to find the expert for a specific problem._'),E.join(`
63
- `)}case"skill_gaps":{const{instance_id:d,min_failures:t=1}=M,h=await j(d),b=await Ee(h,"cachly:lesson:best:*",{max:3e3}),o=new Map;let n=0,E=0;for(const l of b){const a=await h.get(l);if(!a)continue;const s=Le(a);if(s===null)continue;const C=(s.topic??"").split(":")[0]??"unknown",r=o.get(C)??{successes:0,failures:0,attributed:0,topics:[]};s.outcome==="success"||s.outcome==="partial"?r.successes++:r.failures++,s.author&&(r.attributed++,n++),s.visibility==="private"&&E++,r.topics.push(s.topic??""),o.set(C,r)}const $=[];for(const[l,a]of o)a.failures>=t&&a.successes===0&&$.push({domain:l,type:"unresolved failures",severity:a.failures>=3?"\u{1F534} critical":"\u{1F7E1} warn",detail:`${a.failures} failure${a.failures!==1?"s":""}, 0 solutions \u2014 use \`learn_from_attempts\` to store what fixed it`}),a.successes+a.failures>=3&&a.attributed===0&&$.push({domain:l,type:"no attribution",severity:"\u{1F535} info",detail:`${a.successes+a.failures} lessons with no \`author\` field \u2014 attribution lost, \`brain_who_knows\` can't help here`});const i=["## \u{1F50D} Skill Gaps \u2014 Knowledge Blind Spots","",`**${b.length}** lessons scanned \xB7 **${n}** attributed \xB7 **${$.length}** gap${$.length!==1?"s":""} found`,E>0?`_(${E} private lessons excluded from gap analysis)_`:"",""].filter(Boolean);if($.length===0)i.push("\u2705 No significant knowledge gaps detected. All domains with failures have at least one success lesson.");else{const l=[...$.filter(a=>a.severity.includes("critical")),...$.filter(a=>a.severity.includes("warn")),...$.filter(a=>a.severity.includes("info"))];for(const a of l.slice(0,15))i.push(`${a.severity} **\`${a.domain}\`** \u2014 ${a.type}`),i.push(` ${a.detail}`),i.push("");$.length>15&&i.push(`_\u2026and ${$.length-15} more gaps. Fix critical ones first._`)}return i.push("---",`_Fix gaps with \`learn_from_attempts(topic="${$[0]?.domain??"your:topic"}", outcome="success", what_worked="...")\`_`,'_Add `author="name"` to every call to enable `brain_who_knows` + `team_expertise_map`._'),i.join(`
64
- `)}case"brain_coverage":{const{instance_id:d,repo_path:t="."}=M,h=await j(d),[b,o,n]=await Promise.all([Ee(h,"cachly:lesson:best:*",{max:5e3}),Ee(h,"cachly:ckg:node:person:*",{max:2e3}),Ee(h,"cachly:ckg:node:file:*",{max:5e3})]);let E=0,$="n/a";try{const m=et(t);if(!m)throw new Error("repo_path ist kein Ordner");E=Pe(m,["ls-files"],{timeoutMs:3e4}).trim().split(`
65
- `).filter(Boolean).length,$=String(E)}catch{}let i=0,l=0,a=0;const s=new Set;for(const m of b){const y=await h.get(m);if(!y)continue;const A=O(y,{});A.outcome==="success"||A.outcome==="partial"?i++:l++,A.author&&a++,s.add((A.topic??"").split(":")[0]??"unknown")}const C=E>0?`${Math.round(n.length/E*100)}% (${n.length}/${E} files)`:`${n.length} files tracked`,r=b.length>0?Math.round(a/b.length*100):0,c=Math.round(Math.min(1,b.length/50)*25+Math.min(1,i/Math.max(1,b.length))*25+Math.min(1,r/100)*25+Math.min(1,o.length/5)*25);return["## \u{1F4CA} Brain Coverage Report","",`${c>=80?"\u{1F7E2}":c>=50?"\u{1F7E1}":"\u{1F534}"} **Overall score: ${c}/100**`,"","| Metric | Value |","|--------|-------|",`| Total lessons | **${b.length}** (${i} success \xB7 ${l} failure) |`,`| Problem domains | **${s.size}** |`,`| Attribution | **${a}/${b.length}** (${r}%) |`,`| Contributors | **${o.length}** tracked in knowledge graph |`,`| File coverage | **${C}** |`,"","**Score breakdown (each 0\u201325):**",`- Lesson volume: **${Math.round(Math.min(1,b.length/50)*25)}/25** _(target: 50+ lessons)_`,`- Success ratio: **${Math.round(Math.min(1,i/Math.max(1,b.length))*25)}/25** _(target: all lessons resolved)_`,`- Attribution: **${Math.round(Math.min(1,r/100)*25)}/25** _(target: 100% attributed)_`,`- Team engagement: **${Math.round(Math.min(1,o.length/5)*25)}/25** _(target: 5+ contributors)_`,"","---","_Boost score: run `brain_from_git` \xB7 add `author` to `learn_from_attempts` \xB7 fix gaps with `skill_gaps`_"].join(`
66
- `)}case"brain_metrics":{const{instance_id:d}=M,t=await j(d),[h,b,o,n,E,$]=await Promise.all([t.get(`cachly:stats:born_at:${d}`).catch(()=>null),t.get(`cachly:stats:first_recall_at:${d}`).catch(()=>null),t.get(`cachly:stats:recalls_total:${d}`).catch(()=>null),t.get(`cachly:stats:cross_author_recalls:${d}`).catch(()=>null),t.get(`cachly:stats:time_saved_mins:${d}`).catch(()=>null),t.smembers(`cachly:stats:reuse_pairs:${d}`).catch(()=>[])]);let i={};try{i=await t.hgetall(ns(d))??{}}catch{i={}}let l={};try{l=await t.hgetall(zt(d))??{}}catch{l={}}const a={widersprochen:0,korrigiert:0,veraltet:0,ohneEreignis:0,lieferungen:0};try{const p=await t.lrange(Dt(d),0,499),W=new Map;for(const Z of p){const q=O(Z,{ts:"",themen:[]});if(!(!q.ts||!Array.isArray(q.themen)))for(const Q of q.themen){if(a.lieferungen++,!W.has(Q)){const te=await t.get(`cachly:lesson:best:${Q}`).catch(()=>null);W.set(Q,te?O(te,{}):null)}const K=ln(q.ts,W.get(Q)??null);if(K.length===0){a.ohneEreignis++;continue}for(const te of K)a[te]++}}}catch{}let s=0,C=0;try{const p=await t.lrange(`cachly:wirkung:${d}`,0,-1);for(const W of p){const Z=O(W,{});C++,Z.geholfen===!0&&s++}}catch{}let r={};try{r=await t.hgetall(Jn(d))??{}}catch{r={}}const c=Vn(r,10);let v={token:0,abrufe:0,treffer:0};try{const p=await t.get(Yn(d)),W=await t.hgetall(Zn(d))??{};v={token:p??0,abrufe:W.abrufe??0,treffer:W.treffer??0}}catch{}const m=Un(v);let y;if(h&&b){const p=new Date(b).getTime()-new Date(h).getTime(),W=Math.max(0,Math.round(p/6e4)),Z=W<1?"<1 min":W<60?`${W} min`:`${(W/60).toFixed(1)} h`,q=p<=2*6e4?"\u{1F7E2} under 2 min target":p<=60*6e4?"\u{1F7E1} over 2 min":"\u{1F534} over 1 h";y=`**${Z}** from first lesson \u2192 first proven recall \xB7 ${q}`}else h&&!b?y="\u{1F7E1} Brain has knowledge but no proven recall yet \u2014 try `smart_recall`":y="\u26AA Not enough data yet \u2014 store a lesson, then recall it";const A=["\u26AA **Nicht gemessen** \u2014 diese Zahl erhebt der Server nicht selbst."," Selbst messen: `npm run bench` in `sdk/mcp`."," Achtung, der Pruefstand hat 17 Lektionen und 13 Fragen \u2014 ein Beispielsatz, kein Bestand."," Auf 498 ECHTEN Lektionen gemessen am 19.08.2026: Precision@1 **30 %** (vorher 15 %)."].join(`
67
- `),I=Number(o??0),e=Number(n??0),H=I>0?Math.round(e/I*100):0,ae=H>=30?"\u{1F7E2} above 30 % target":I===0?"\u26AA no recalls yet":"\u{1F7E1} below 30 % target",ce=Math.round(Number(E??0)),de=ce<60?`${ce} min`:`${(ce/60).toFixed(1)} h`;return["## \u{1F4C8} Brain Metrics \u2014 the three that decide everything","","### 1. Time-to-first-recall _(onboarding friction)_",y,"","### 2. Recall-lift _(the moat proof)_",A,"_Reproduce: `npm run bench` \xB7 CI-defended in `rerank.test.ts` \xB7 details in BENCH.md_","","### 3. Team-knowledge-reuse _(the value only a shared brain delivers)_",`**${H}%** of proven recalls used a teammate's lesson (${e}/${I}) \xB7 ${ae}`,$.length>0?`**${$.length}** distinct reuse relationship${$.length!==1?"s":""} across the team`:"","","---","### \u{1F4E6} Aus dem Brain geliefert _(gemessen, nicht gerechnet)_",qn(m),"",m.abrufe>0?`_Daneben eine **Sch\xE4tzung**: ${de} nicht neu recherchiert \u2014 gerechnet mit 30\u2013240 Min. je Lektion nach Schwere, nicht gemessen. Die Zahl dar\xFCber braucht diese Annahme nicht._`:`_Eine **Sch\xE4tzung** von ${de} \u201Enicht neu recherchiert" w\xE4re hier gerechnet, nicht gemessen \u2014 sie erscheint erst, wenn daneben eine gemessene Zahl steht._`,I===0?'_Tip: pass `author="your-handle"` to `smart_recall` so cross-author reuse can be tracked._':"_These numbers compound: every learned lesson and every teammate raises all three._","","### \u2696\uFE0F Die drei Ausgaenge der Suche _(gezaehlt, nicht behauptet)_",(()=>{const p=Number(i.beantwortet??0),W=Number(i.knapp??0),Z=Number(i.schweigen??0),q=p+W+Z;if(q===0)return"\u26AA Noch keine Zaehlung \u2014 die Ausgaenge zaehlen ab dieser Fassung je smart_recall.";const Q=K=>`${Math.round(100*K/q)} %`;return`**${q}** Suchen: ${Q(p)} beantwortet \xB7 ${Q(W)} als knapper Sieg markiert \xB7 ${Q(Z)} ehrliches Nein. (Ob ein Nein stimmt, zeigt nur die Stichprobe \u2014 die Rate hier zaehlt, sie urteilt nicht.)`})(),"","### \u{1F53D} Recall-Trichter _(je Fehlschlag: WORAN scheiterte der Abruf)_",(()=>{const p=Q=>Number(l[Q]??0),W=[["nicht-gesucht","nicht gesucht"],["nominierung","Nominierung (gesucht, nichts geliefert)"],["ranking","Ranking (geliefert, das Richtige fehlte)"],["anwendung","Anwendung (das Richtige lag vor)"],["ohne-vorwissen","ohne Vorwissen (es gab nichts zu finden)"]],Z=W.reduce((Q,[K])=>Q+p(K),0);if(Z===0)return"\u26AA Noch keine Zaehlung \u2014 jeder learn_from_attempts-Fehlschlag wird ab dieser Fassung klassifiziert.";const q=W.filter(([Q])=>Q!=="ohne-vorwissen").sort((Q,K)=>p(K[0])-p(Q[0]))[0];return`**${Z}** klassifizierte Fehlschlaege: `+W.map(([Q,K])=>`${p(Q)} ${K}`).join(" \xB7 ")+`. Die groesste vermeidbare Klasse ist **${q[1]}** (${p(q[0])}) \u2014 dorthin gehoert das naechste Investment. (Heuristik je Einzelfall; erst die Verteilung ueber viele Sessions traegt.)`})(),"","### \u{1F3F7}\uFE0F Drei Etiketten ohne Selbstauskunft _(widersprochen \xB7 korrigiert \xB7 veraltet)_",a.lieferungen===0?"\u26AA Noch kein Liefer-Journal \u2014 die Etiketten entstehen ab dieser Fassung je smart_recall.":`**${a.lieferungen}** gelieferte Lektionen (letzte 500 Abrufe): ${a.widersprochen} widersprochen \xB7 ${a.korrigiert} korrigiert \xB7 ${a.veraltet} veraltet \xB7 ${a.ohneEreignis} ohne spaeteres Ereignis. (Kein Etikett heisst: nichts ist passiert \u2014 nicht "benutzt". Das vierte Etikett haette nur eine geschmeichelte Fassung.)`,"","### \u2696\uFE0F Netto-verhinderter-Schaden _(kann negativ werden \u2014 sonst misst es nichts)_",(()=>{const p=Number(a.widersprochen)+Number(a.veraltet);if(C===0&&p===0)return"\u26AA Noch keine Wirkungs- oder Schadens-Daten \u2014 die Zahl entsteht aus Wirkungsspur und Etiketten.";const W=s-p;return`**${W>=0?"+":""}${W}** = ${s} bestaetigt geholfen (Wirkungsspur, ${C} Eintraege) \u2212 ${p} Schadenslieferungen (${a.widersprochen} spaeter widerlegt + ${a.veraltet} schon ersetzt geliefert). 'korrigiert' zaehlt nicht als Schaden \u2014 eine Nachbesserung ist kein Vorfall.`})(),"","### \u{1F527} Werkzeug-Nutzung _(erst messen, dann zusammenlegen)_",Kn(c,oo.length),...c.spitze.length>0?c.spitze.map((p,W)=>` ${String(W+1).padStart(2)}. \`${p.name}\` \u2014 ${p.aufrufe}\xD7 (${Math.round(p.anteil*100)} %)`):[]].filter(Boolean).join(`
68
- `)}case"brain_service_map":{const{instance_id:d,service:t}=M;if(!t||typeof t!="string"||!t.trim())return'\u26A0\uFE0F `brain_service_map` requires a non-empty `service`. Example: `brain_service_map(service="prometheus")`.';const h=await j(d),b=`service:${_e(t.trim())}`,o=await h.get(`cachly:ckg:node:${b}`).catch(()=>null),n=o?O(o,null):null,E=await h.smembers(`cachly:ckg:idx:to:${b}`).catch(()=>[]),$=[],i=[],l=[];for(const y of E){const A=await h.get(y).catch(()=>null);if(!A)continue;const I=O(A,null);if(I)if(I.edgeType==="operates"){const e=await h.get(`cachly:ckg:node:${I.from}`).catch(()=>null),H=e?O(e,null):null;H?.handle&&$.push({handle:H.handle,trials:I.trials})}else if(I.edgeType==="runs_in"){const e=await h.get(`cachly:ckg:node:${I.from}`).catch(()=>null),H=e?O(e,null):null;H?.path&&i.push({path:H.path,trials:I.trials})}else I.edgeType==="affects"&&l.push({id:I.from,confidence:I.confidence})}$.sort((y,A)=>A.trials-y.trials),i.sort((y,A)=>A.trials-y.trials),l.sort((y,A)=>A.confidence-y.confidence);const a=[],s=await Ee(h,"cachly:lesson:best:*",{max:1500}),C=t.trim().toLowerCase();for(const y of s.slice(0,400)){const A=await h.get(y).catch(()=>null);if(!A)continue;const I=Le(A);We(I)&&(I.service??"").toLowerCase()===C&&a.push({...I,topic:y.replace("cachly:lesson:best:","")})}if(!n&&a.length===0&&$.length===0)return[`## \u{1F6F0}\uFE0F Service Map \u2014 \`${t}\``,"","Nothing known about this service yet.","","Tag lessons with the service they concern so the brain can build this map:",`\`learn_from_attempts(topic="...", service="${t}", author="you", file_paths=[...])\``].join(`
51
+ \u{1F4DD} **Instructions:** ${s}`:"","","\u{1F4A1} The next `session_start` will include this handoff automatically.",...He($,"notiz")].filter(e=>e!=="").join(`
52
+ `)}case"auto_learn_session":{const{instance_id:f,observations:t}=M,l=await j(f),_=[],o=[];let n=0,E=0;for(const i of t){const h=ze(i),a=h.felder,s=a.topic??a.action.toLowerCase().replace(/[^a-z0-9:\-_\s]/g,"").trim().split(/\s+/).slice(0,4).join("-"),C=s.includes(":")?s:`auto:${s}`,r=`cachly:lesson:best:${C}`,c=await l.get(r);if(c&&JSON.parse(c).outcome==="success"&&a.outcome!=="success"){o.push(C);continue}const v={topic:C,outcome:a.outcome,what_worked:a.outcome==="success"?a.action:a.details??a.action,what_failed:a.outcome==="failure"?a.action:void 0,context:a.details,severity:a.severity??"minor",ts:new Date().toISOString(),confidence:1,recall_count:0,auto_learned:!0,version:2,...h.verdacht.length?{tags:[wt]}:{}},m=JSON.stringify(v);h.geschwaerzt.length&&n++,h.verdacht.length&&E++,await l.set(r,m);const y=`cachly:lessons:${C}`;await l.rpush(y,m),await l.ltrim(y,-100,-1),await l.expire(y,90*86400);try{const A=C.split(":")[0]??"auto",I=we(C);if(await je(l,I,A,A),a.outcome==="success"||a.outcome==="partial"){const e=a.details?tt(a.details):null;if(e){const G=we(`problem:${e}`);await je(l,G,"problem","problem"),await $e(l,I,"fixes",G,a.outcome==="success",a.outcome==="partial")}}}catch{}_.push(`${a.outcome==="success"?"\u2705":a.outcome==="partial"?"\u26A0\uFE0F":"\u274C"} \`${C}\``)}const $=[`\u{1F916} **Auto-learn complete**: ${_.length} stored, ${o.length} skipped`,""];return _.length>0&&$.push("**Stored:**",..._.map(i=>" "+i),""),o.length>0&&$.push(`**Skipped** (better lesson already exists): ${o.map(i=>`\`${i}\``).join(", ")}`),$.push(...Qt(n,E,`${_.length} observations`)),$.join(`
53
+ `)}case"brain_who_knows":{const{instance_id:f,topic:t,limit:l=10}=M;if(!t||typeof t!="string"||!t.trim())return'\u26A0\uFE0F `brain_who_knows` requires a non-empty `topic`. Example: `brain_who_knows(topic="kubernetes deployment")`.';const _=Math.max(1,Math.min(Number(l)||10,50)),o=await j(f),n=we(t),E=lt(t).filter(r=>r.length>=3).slice(0,5),$=new Set([n]);for(const r of E.slice(0,3)){const c=o.scanStream({match:`cachly:ckg:node:*${r}*`,count:30});await new Promise((v,m)=>{c.on("data",y=>{for(const A of y.slice(0,10)){const I=A.replace("cachly:ckg:node:","");!I.startsWith("person:")&&!I.startsWith("file:")&&$.add(I)}}),c.on("end",v),c.on("error",m)})}const i=new Map;for(const r of[...$].slice(0,25)){const c=await o.smembers(`cachly:ckg:idx:to:${r}`);for(const v of c){const m=await o.get(v);if(!m)continue;const y=O(m,null);if(!y||y.edgeType!=="authored")continue;const A=await o.get(`cachly:ckg:node:${y.from}`);if(!A)continue;const I=O(A,null);if(!I||I.type!=="person")continue;const e=i.get(y.from);e?(e.lessonCount++,e.totalConfidence+=y.confidence,e.domains.add(I.domain),y.last_updated>e.lastActive&&(e.lastActive=y.last_updated)):i.set(y.from,{handle:I.handle,lessonCount:1,totalConfidence:y.confidence,domains:new Set([I.domain]),lastActive:I.last_active??y.last_updated})}}if(i.size===0)return[`## \u{1F465} Who Knows About \`${t}\`?`,"","No attributed lessons found yet.","",'Knowledge attribution builds automatically \u2014 add `author="name"` to `learn_from_attempts` calls:',`\`learn_from_attempts(topic="${t}", author="your-handle", ...)\``].join(`
54
+ `);const h=[...i.entries()].map(([r,c])=>({id:r,handle:c.handle,lessonCount:c.lessonCount,avgConfidence:c.totalConfidence/c.lessonCount,domains:[...c.domains].slice(0,3).join(", "),lastActive:c.lastActive})).sort((r,c)=>c.lessonCount*c.avgConfidence-r.lessonCount*r.avgConfidence).slice(0,_),a=[],s=h[0];if(s)try{const r=await o.smembers(`cachly:ckg:idx:from:${s.id}`),c=[];for(const v of r){const m=await o.get(v);if(!m)continue;const y=O(m,null);if(!y||y.edgeType!=="collaborates")continue;const A=await o.get(`cachly:ckg:node:${y.to}`),I=A?O(A,null):null;I?.handle&&c.push({handle:I.handle,trials:y.trials})}c.sort((v,m)=>m.trials-v.trials);for(const v of c.slice(0,3))a.push(v.handle)}catch{}const C=[`## \u{1F465} Who Knows About \`${t}\`?`,"",`**${h.length}** contributor${h.length!==1?"s":""} with relevant lessons:`,""];for(const[r,c]of h.entries()){const v=r===0?"\u{1F947}":r===1?"\u{1F948}":r===2?"\u{1F949}":`${r+1}.`,m=(c.avgConfidence*100).toFixed(0),y=Math.floor((Date.now()-new Date(c.lastActive).getTime())/864e5),A=y<1?"today":y<7?`${y}d ago`:y<30?`${Math.floor(y/7)}w ago`:`${Math.floor(y/30)}mo ago`,I=await ct(o,f,c.handle).catch(()=>null),e=I?` ${Ls[I]}`:"";C.push(`${v} **${c.handle}**${e} \u2014 ${c.lessonCount} lesson${c.lessonCount!==1?"s":""} \xB7 ${m}% confidence \xB7 last active ${A}`,` _domains: ${c.domains}_`,"")}return a.length>0&&s&&C.push(`\u{1F91D} **${s.handle}** frequently works with: ${a.map(r=>`**${r}**`).join(", ")}`," _(ask them together \u2014 they've solved structurally similar things on shared files)_",""),C.push("---",'_Attribution grows with every `learn_from_attempts(author="...", ...)` call._'),C.join(`
55
+ `)}case"brain_file_map":{const{instance_id:f,file_paths:t=[]}=M,l=(Array.isArray(t)?t:[]).filter(n=>typeof n=="string"&&n.trim().length>0).map(n=>n.trim());if(!l.length)return'\u26A0\uFE0F Pass at least one file path. Example: `brain_file_map(file_paths=["src/auth/jwt.ts"])`.';const _=await j(f),o=["## \u{1F4C1} File Knowledge Map","","_What cachly knows about the files you're about to touch:_",""];for(const n of l.slice(0,10)){o.push(`### \`${n}\``);const E=`file:${we(n)}`,$=await _.smembers(`cachly:ckg:idx:to:${E}`),i=[];for(const C of $){const r=await _.get(C);if(!r)continue;const c=O(r,null);if(!c||c.edgeType!=="touched")continue;const v=await _.get(`cachly:ckg:node:${c.from}`);if(!v)continue;const m=O(v,null);!m||m.type!=="person"||i.push({handle:m.handle,touches:c.trials,lastSeen:c.last_updated})}if(i.sort((C,r)=>r.touches-C.touches),i.length>0){const C=i.slice(0,5).map((r,c)=>{const v=c===0?"\u{1F947}":c===1?"\u{1F948}":c===2?"\u{1F949}":`${c+1}.`,m=Math.floor((Date.now()-new Date(r.lastSeen).getTime())/864e5),y=m<1?"today":m<7?`${m}d ago`:`${Math.floor(m/7)}w ago`;return`${v} **${r.handle}** (${r.touches}\xD7 \xB7 ${y})`}).join(" \xB7 ");o.push(`**Experts:** ${C}`)}else o.push("**Experts:** _None yet \u2014 add `author` to `learn_from_attempts` calls_");const a=n.replace(/[^a-z0-9]/gi," ").split(/\s+/).filter(C=>C.length>=4).slice(0,3).join(" "),s=[];if(a)try{const C=await Xe(_,["cachly:lesson:best:*"],a,4);for(const r of C){const c=Be(r.content);if(!We(c))continue;const v=c.outcome==="success"?"\u2705":c.outcome==="partial"?"\u26A0\uFE0F":"\u274C",m=c.severity==="critical"?" \u{1F534}":c.severity==="major"?" \u{1F7E1}":"",y=c.author?` \xB7 \u{1F464} ${c.author}`:"",A=r.key.replace("cachly:lesson:best:","");s.push(` - ${v} \`${A}\`${m}${y} \u2014 ${(c.what_worked??"").slice(0,100)}`)}}catch{}if(s.length<2){const C=await Ne(_,"cachly:lesson:best:*",{max:1500});for(const r of C.slice(0,300)){const c=await _.get(r);if(!c)continue;const v=Be(c);if(!We(v)||!(v.file_paths??[]).includes(n))continue;const m=r.replace("cachly:lesson:best:","");if(s.some(e=>e.includes(`\`${m}\``)))continue;const y=v.outcome==="success"?"\u2705":v.outcome==="partial"?"\u26A0\uFE0F":"\u274C",A=v.severity==="critical"?" \u{1F534}":v.severity==="major"?" \u{1F7E1}":"",I=v.author?` \xB7 \u{1F464} ${v.author}`:"";s.push(` - ${y} \`${m}\`${A}${I} \u2014 ${(v.what_worked??"").slice(0,100)}`)}}s.length>0?(o.push("**Related lessons:**"),o.push(...s.slice(0,5))):o.push("**Related lessons:** _None yet_"),o.push("")}return o.push("---",'_Attribution builds with `learn_from_attempts(file_paths=[...], author="...")`. Run this before committing to track what changed and why._'),o.join(`
56
+ `)}case"team_expertise_map":{const{instance_id:f,top_n:t=20}=M,l=await j(f),_=await Ne(l,"cachly:ckg:node:person:*",{max:2e3});if(_.length===0)return["## \u{1F5FA}\uFE0F Team Expertise Map","","No contributors yet.","",'Add `author="handle"` to any `learn_from_attempts` call to start building the map.'].join(`
57
+ `);const o=[];for(const $ of _){const i=await l.get($);if(!i)continue;const h=O(i,null);if(!h||h.type!=="person")continue;const a=await l.smembers(`cachly:ckg:idx:from:${h.id}`),s=new Map;let C=0;for(const r of a){const c=await l.get(r);if(!c)continue;const v=O(c,null);if(!v||v.edgeType!=="authored")continue;C++;const m=v.to.split(":")[0]??"unknown";s.set(m,(s.get(m)??0)+1)}o.push({handle:h.handle,lessonCount:C,domains:s,lastActive:h.last_active})}o.sort(($,i)=>i.lessonCount-$.lessonCount);const n=o.slice(0,t),E=["## \u{1F5FA}\uFE0F Team Expertise Map","",`**${n.length}** contributor${n.length!==1?"s":""} tracked:`,"","| # | Handle | Lessons | Top domains | Last active |","|---|--------|---------|-------------|-------------|"];for(const[$,i]of n.entries()){const h=$===0?"\u{1F947}":$===1?"\u{1F948}":$===2?"\u{1F949}":`${$+1}`,a=Math.floor((Date.now()-new Date(i.lastActive).getTime())/864e5),s=a<1?"today":a<7?`${a}d ago`:a<30?`${Math.floor(a/7)}w ago`:`${Math.floor(a/30)}mo ago`,C=[...i.domains.entries()].sort((r,c)=>c[1]-r[1]).slice(0,3).map(([r,c])=>`${r}(${c})`).join(", ")||"\u2014";E.push(`| ${h} | **${i.handle}** | ${i.lessonCount} | ${C} | ${s} |`)}return E.push("","---",'_Use `brain_who_knows(topic="...")` to find the expert for a specific problem._'),E.join(`
58
+ `)}case"skill_gaps":{const{instance_id:f,min_failures:t=1}=M,l=await j(f),_=await Ne(l,"cachly:lesson:best:*",{max:3e3}),o=new Map;let n=0,E=0;for(const h of _){const a=await l.get(h);if(!a)continue;const s=Be(a);if(s===null)continue;const C=(s.topic??"").split(":")[0]??"unknown",r=o.get(C)??{successes:0,failures:0,attributed:0,topics:[]};s.outcome==="success"||s.outcome==="partial"?r.successes++:r.failures++,s.author&&(r.attributed++,n++),s.visibility==="private"&&E++,r.topics.push(s.topic??""),o.set(C,r)}const $=[];for(const[h,a]of o)a.failures>=t&&a.successes===0&&$.push({domain:h,type:"unresolved failures",severity:a.failures>=3?"\u{1F534} critical":"\u{1F7E1} warn",detail:`${a.failures} failure${a.failures!==1?"s":""}, 0 solutions \u2014 use \`learn_from_attempts\` to store what fixed it`}),a.successes+a.failures>=3&&a.attributed===0&&$.push({domain:h,type:"no attribution",severity:"\u{1F535} info",detail:`${a.successes+a.failures} lessons with no \`author\` field \u2014 attribution lost, \`brain_who_knows\` can't help here`});const i=["## \u{1F50D} Skill Gaps \u2014 Knowledge Blind Spots","",`**${_.length}** lessons scanned \xB7 **${n}** attributed \xB7 **${$.length}** gap${$.length!==1?"s":""} found`,E>0?`_(${E} private lessons excluded from gap analysis)_`:"",""].filter(Boolean);if($.length===0)i.push("\u2705 No significant knowledge gaps detected. All domains with failures have at least one success lesson.");else{const h=[...$.filter(a=>a.severity.includes("critical")),...$.filter(a=>a.severity.includes("warn")),...$.filter(a=>a.severity.includes("info"))];for(const a of h.slice(0,15))i.push(`${a.severity} **\`${a.domain}\`** \u2014 ${a.type}`),i.push(` ${a.detail}`),i.push("");$.length>15&&i.push(`_\u2026and ${$.length-15} more gaps. Fix critical ones first._`)}return i.push("---",`_Fix gaps with \`learn_from_attempts(topic="${$[0]?.domain??"your:topic"}", outcome="success", what_worked="...")\`_`,'_Add `author="name"` to every call to enable `brain_who_knows` + `team_expertise_map`._'),i.join(`
59
+ `)}case"brain_coverage":{const{instance_id:f,repo_path:t="."}=M,l=await j(f),[_,o,n]=await Promise.all([Ne(l,"cachly:lesson:best:*",{max:5e3}),Ne(l,"cachly:ckg:node:person:*",{max:2e3}),Ne(l,"cachly:ckg:node:file:*",{max:5e3})]);let E=0,$="n/a";try{const m=et(t);if(!m)throw new Error("repo_path ist kein Ordner");E=Pe(m,["ls-files"],{timeoutMs:3e4}).trim().split(`
60
+ `).filter(Boolean).length,$=String(E)}catch{}let i=0,h=0,a=0;const s=new Set;for(const m of _){const y=await l.get(m);if(!y)continue;const A=O(y,{});A.outcome==="success"||A.outcome==="partial"?i++:h++,A.author&&a++,s.add((A.topic??"").split(":")[0]??"unknown")}const C=E>0?`${Math.round(n.length/E*100)}% (${n.length}/${E} files)`:`${n.length} files tracked`,r=_.length>0?Math.round(a/_.length*100):0,c=Math.round(Math.min(1,_.length/50)*25+Math.min(1,i/Math.max(1,_.length))*25+Math.min(1,r/100)*25+Math.min(1,o.length/5)*25);return["## \u{1F4CA} Brain Coverage Report","",`${c>=80?"\u{1F7E2}":c>=50?"\u{1F7E1}":"\u{1F534}"} **Overall score: ${c}/100**`,"","| Metric | Value |","|--------|-------|",`| Total lessons | **${_.length}** (${i} success \xB7 ${h} failure) |`,`| Problem domains | **${s.size}** |`,`| Attribution | **${a}/${_.length}** (${r}%) |`,`| Contributors | **${o.length}** tracked in knowledge graph |`,`| File coverage | **${C}** |`,"","**Score breakdown (each 0\u201325):**",`- Lesson volume: **${Math.round(Math.min(1,_.length/50)*25)}/25** _(target: 50+ lessons)_`,`- Success ratio: **${Math.round(Math.min(1,i/Math.max(1,_.length))*25)}/25** _(target: all lessons resolved)_`,`- Attribution: **${Math.round(Math.min(1,r/100)*25)}/25** _(target: 100% attributed)_`,`- Team engagement: **${Math.round(Math.min(1,o.length/5)*25)}/25** _(target: 5+ contributors)_`,"","---","_Boost score: run `brain_from_git` \xB7 add `author` to `learn_from_attempts` \xB7 fix gaps with `skill_gaps`_"].join(`
61
+ `)}case"brain_metrics":{const{instance_id:f}=M,t=await j(f),[l,_,o,n,E,$]=await Promise.all([t.get(`cachly:stats:born_at:${f}`).catch(()=>null),t.get(`cachly:stats:first_recall_at:${f}`).catch(()=>null),t.get(`cachly:stats:recalls_total:${f}`).catch(()=>null),t.get(`cachly:stats:cross_author_recalls:${f}`).catch(()=>null),t.get(`cachly:stats:time_saved_mins:${f}`).catch(()=>null),t.smembers(`cachly:stats:reuse_pairs:${f}`).catch(()=>[])]);let i={};try{i=await t.hgetall(ns(f))??{}}catch{i={}}let h={};try{h=await t.hgetall(zt(f))??{}}catch{h={}}const a={widersprochen:0,korrigiert:0,veraltet:0,ohneEreignis:0,lieferungen:0};try{const p=await t.lrange(Dt(f),0,499),V=new Map;for(const U of p){const Q=O(U,{ts:"",themen:[]});if(!(!Q.ts||!Array.isArray(Q.themen)))for(const Z of Q.themen){if(a.lieferungen++,!V.has(Z)){const ne=await t.get(`cachly:lesson:best:${Z}`).catch(()=>null);V.set(Z,ne?O(ne,{}):null)}const K=ln(Q.ts,V.get(Z)??null);if(K.length===0){a.ohneEreignis++;continue}for(const ne of K)a[ne]++}}}catch{}let s=0,C=0;try{const p=await t.lrange(`cachly:wirkung:${f}`,0,-1);for(const V of p){const U=O(V,{});C++,U.geholfen===!0&&s++}}catch{}let r={};try{r=await t.hgetall(Un(f))??{}}catch{r={}}const c=Yn(r,10);let v={token:0,abrufe:0,treffer:0};try{const p=await t.get(Qn(f)),V=await t.hgetall(eo(f))??{};v={token:p??0,abrufe:V.abrufe??0,treffer:V.treffer??0}}catch{}const m=Xn(v);let y;if(l&&_){const p=new Date(_).getTime()-new Date(l).getTime(),V=Math.max(0,Math.round(p/6e4)),U=V<1?"<1 min":V<60?`${V} min`:`${(V/60).toFixed(1)} h`,Q=p<=2*6e4?"\u{1F7E2} under 2 min target":p<=60*6e4?"\u{1F7E1} over 2 min":"\u{1F534} over 1 h";y=`**${U}** from first lesson \u2192 first proven recall \xB7 ${Q}`}else l&&!_?y="\u{1F7E1} Brain has knowledge but no proven recall yet \u2014 try `smart_recall`":y="\u26AA Not enough data yet \u2014 store a lesson, then recall it";const A=["\u26AA **Nicht gemessen** \u2014 diese Zahl erhebt der Server nicht selbst."," Selbst messen: `npm run bench` in `sdk/mcp`."," Achtung, der Pruefstand hat 17 Lektionen und 13 Fragen \u2014 ein Beispielsatz, kein Bestand."," Auf 498 ECHTEN Lektionen gemessen am 19.08.2026: Precision@1 **30 %** (vorher 15 %)."].join(`
62
+ `),I=Number(o??0),e=Number(n??0),G=I>0?Math.round(e/I*100):0,ce=G>=30?"\u{1F7E2} above 30 % target":I===0?"\u26AA no recalls yet":"\u{1F7E1} below 30 % target",oe=Math.round(Number(E??0)),he=oe<60?`${oe} min`:`${(oe/60).toFixed(1)} h`;return["## \u{1F4C8} Brain Metrics \u2014 the three that decide everything","","### 1. Time-to-first-recall _(onboarding friction)_",y,"","### 2. Recall-lift _(the moat proof)_",A,"_Reproduce: `npm run bench` \xB7 CI-defended in `rerank.test.ts` \xB7 details in BENCH.md_","","### 3. Team-knowledge-reuse _(the value only a shared brain delivers)_",`**${G}%** of proven recalls used a teammate's lesson (${e}/${I}) \xB7 ${ce}`,$.length>0?`**${$.length}** distinct reuse relationship${$.length!==1?"s":""} across the team`:"","","---","### \u{1F4E6} Aus dem Brain geliefert _(gemessen, nicht gerechnet)_",Zn(m),"",m.abrufe>0?`_Daneben eine **Sch\xE4tzung**: ${he} nicht neu recherchiert \u2014 gerechnet mit 30\u2013240 Min. je Lektion nach Schwere, nicht gemessen. Die Zahl dar\xFCber braucht diese Annahme nicht._`:`_Eine **Sch\xE4tzung** von ${he} \u201Enicht neu recherchiert" w\xE4re hier gerechnet, nicht gemessen \u2014 sie erscheint erst, wenn daneben eine gemessene Zahl steht._`,I===0?'_Tip: pass `author="your-handle"` to `smart_recall` so cross-author reuse can be tracked._':"_These numbers compound: every learned lesson and every teammate raises all three._","","### \u2696\uFE0F Die drei Ausgaenge der Suche _(gezaehlt, nicht behauptet)_",(()=>{const p=Number(i.beantwortet??0),V=Number(i.knapp??0),U=Number(i.schweigen??0),Q=p+V+U;if(Q===0)return"\u26AA Noch keine Zaehlung \u2014 die Ausgaenge zaehlen ab dieser Fassung je smart_recall.";const Z=K=>`${Math.round(100*K/Q)} %`;return`**${Q}** Suchen: ${Z(p)} beantwortet \xB7 ${Z(V)} als knapper Sieg markiert \xB7 ${Z(U)} ehrliches Nein. (Ob ein Nein stimmt, zeigt nur die Stichprobe \u2014 die Rate hier zaehlt, sie urteilt nicht.)`})(),"","### \u{1F53D} Recall-Trichter _(je Fehlschlag: WORAN scheiterte der Abruf)_",(()=>{const p=Z=>Number(h[Z]??0),V=[["nicht-gesucht","nicht gesucht"],["nominierung","Nominierung (gesucht, nichts geliefert)"],["ranking","Ranking (geliefert, das Richtige fehlte)"],["anwendung","Anwendung (das Richtige lag vor)"],["ohne-vorwissen","ohne Vorwissen (es gab nichts zu finden)"]],U=V.reduce((Z,[K])=>Z+p(K),0);if(U===0)return"\u26AA Noch keine Zaehlung \u2014 jeder learn_from_attempts-Fehlschlag wird ab dieser Fassung klassifiziert.";const Q=V.filter(([Z])=>Z!=="ohne-vorwissen").sort((Z,K)=>p(K[0])-p(Z[0]))[0];return`**${U}** klassifizierte Fehlschlaege: `+V.map(([Z,K])=>`${p(Z)} ${K}`).join(" \xB7 ")+`. Die groesste vermeidbare Klasse ist **${Q[1]}** (${p(Q[0])}) \u2014 dorthin gehoert das naechste Investment. (Heuristik je Einzelfall; erst die Verteilung ueber viele Sessions traegt.)`})(),"","### \u{1F3F7}\uFE0F Drei Etiketten ohne Selbstauskunft _(widersprochen \xB7 korrigiert \xB7 veraltet)_",a.lieferungen===0?"\u26AA Noch kein Liefer-Journal \u2014 die Etiketten entstehen ab dieser Fassung je smart_recall.":`**${a.lieferungen}** gelieferte Lektionen (letzte 500 Abrufe): ${a.widersprochen} widersprochen \xB7 ${a.korrigiert} korrigiert \xB7 ${a.veraltet} veraltet \xB7 ${a.ohneEreignis} ohne spaeteres Ereignis. (Kein Etikett heisst: nichts ist passiert \u2014 nicht "benutzt". Das vierte Etikett haette nur eine geschmeichelte Fassung.)`,"","### \u2696\uFE0F Netto-verhinderter-Schaden _(kann negativ werden \u2014 sonst misst es nichts)_",(()=>{const p=Number(a.widersprochen)+Number(a.veraltet);if(C===0&&p===0)return"\u26AA Noch keine Wirkungs- oder Schadens-Daten \u2014 die Zahl entsteht aus Wirkungsspur und Etiketten.";const V=s-p;return`**${V>=0?"+":""}${V}** = ${s} bestaetigt geholfen (Wirkungsspur, ${C} Eintraege) \u2212 ${p} Schadenslieferungen (${a.widersprochen} spaeter widerlegt + ${a.veraltet} schon ersetzt geliefert). 'korrigiert' zaehlt nicht als Schaden \u2014 eine Nachbesserung ist kein Vorfall.`})(),"","### \u{1F527} Werkzeug-Nutzung _(erst messen, dann zusammenlegen)_",Vn(c,ro.length),...c.spitze.length>0?c.spitze.map((p,V)=>` ${String(V+1).padStart(2)}. \`${p.name}\` \u2014 ${p.aufrufe}\xD7 (${Math.round(p.anteil*100)} %)`):[]].filter(Boolean).join(`
63
+ `)}case"brain_service_map":{const{instance_id:f,service:t}=M;if(!t||typeof t!="string"||!t.trim())return'\u26A0\uFE0F `brain_service_map` requires a non-empty `service`. Example: `brain_service_map(service="prometheus")`.';const l=await j(f),_=`service:${we(t.trim())}`,o=await l.get(`cachly:ckg:node:${_}`).catch(()=>null),n=o?O(o,null):null,E=await l.smembers(`cachly:ckg:idx:to:${_}`).catch(()=>[]),$=[],i=[],h=[];for(const y of E){const A=await l.get(y).catch(()=>null);if(!A)continue;const I=O(A,null);if(I)if(I.edgeType==="operates"){const e=await l.get(`cachly:ckg:node:${I.from}`).catch(()=>null),G=e?O(e,null):null;G?.handle&&$.push({handle:G.handle,trials:I.trials})}else if(I.edgeType==="runs_in"){const e=await l.get(`cachly:ckg:node:${I.from}`).catch(()=>null),G=e?O(e,null):null;G?.path&&i.push({path:G.path,trials:I.trials})}else I.edgeType==="affects"&&h.push({id:I.from,confidence:I.confidence})}$.sort((y,A)=>A.trials-y.trials),i.sort((y,A)=>A.trials-y.trials),h.sort((y,A)=>A.confidence-y.confidence);const a=[],s=await Ne(l,"cachly:lesson:best:*",{max:1500}),C=t.trim().toLowerCase();for(const y of s.slice(0,400)){const A=await l.get(y).catch(()=>null);if(!A)continue;const I=Be(A);We(I)&&(I.service??"").toLowerCase()===C&&a.push({...I,topic:y.replace("cachly:lesson:best:","")})}if(!n&&a.length===0&&$.length===0)return[`## \u{1F6F0}\uFE0F Service Map \u2014 \`${t}\``,"","Nothing known about this service yet.","","Tag lessons with the service they concern so the brain can build this map:",`\`learn_from_attempts(topic="...", service="${t}", author="you", file_paths=[...])\``].join(`
69
64
  `);const r=n?.kind==="system"?"\u{1F5A5}\uFE0F system":"\u{1F6F0}\uFE0F service",c=[`## ${n?.kind==="system"?"\u{1F5A5}\uFE0F":"\u{1F6F0}\uFE0F"} Service Map \u2014 \`${t}\``,"",n?`_${r} \xB7 ${n.count} lesson${n.count!==1?"s":""} referenced \xB7 domain: ${n.domain}_`:"_No graph node yet \u2014 built from tagged lessons below._",""];if($.length>0){const y=$.slice(0,5).map((A,I)=>`${I===0?"\u{1F947}":I===1?"\u{1F948}":I===2?"\u{1F949}":`${I+1}.`} **${A.handle}** (${A.trials}\xD7)`).join(" \xB7 ");c.push(`**Operators:** ${y}`,"")}else c.push("**Operators:** _None attributed \u2014 add `author` when learning about this service_","");const v=a.filter(y=>y.outcome==="failure"),m=a.filter(y=>y.outcome!=="failure");if(v.length>0){c.push(`**\u26A0\uFE0F Known failures (${v.length}):**`);for(const y of v.slice(0,6)){const A=y.severity==="critical"?" \u{1F534}":y.severity==="major"?" \u{1F7E1}":"",I=y.author?` \xB7 \u{1F464} ${y.author}`:"";c.push(` - \u274C \`${y.topic}\`${A}${I} \u2014 ${(y.what_worked??"").slice(0,100)}`)}c.push("")}if(m.length>0){c.push(`**\u2705 Proven fixes (${m.length}):**`);for(const y of m.slice(0,6)){const A=y.outcome==="partial"?"\u26A0\uFE0F":"\u2705",I=y.author?` \xB7 \u{1F464} ${y.author}`:"";c.push(` - ${A} \`${y.topic}\`${I} \u2014 ${(y.what_worked??"").slice(0,100)}`)}c.push("")}return i.length>0&&c.push(`**Files in this service:** ${i.slice(0,8).map(y=>`\`${y.path}\``).join(", ")}`,""),c.push("---",`_Tag lessons with \`service="${t}"\` to keep this map current. For infra, pass \`service_kind="system"\`._`),c.join(`
70
- `)}case"brain_collab_pairs":{const{instance_id:d,min_weight:t=1}=M,h=await j(d),b=typeof t=="number"&&t>=1?Math.floor(t):1,o=await Ee(h,"cachly:ckg:node:person:*",{max:500}),n=new Map;for(const s of o){const C=await h.get(s);if(!C)continue;const r=O(C,null);!r||r.type!=="person"||n.set(r.id,{id:r.id,handle:r.handle,domain:r.domain,count:r.count})}if(n.size===0)return["## \u{1F91D} Team Collaboration Graph","","No contributors found yet.","",'The graph builds automatically as teammates store lessons with `author="name"`:','`learn_from_attempts(topic="...", author="alice", file_paths=["src/auth.ts"], ...)`'].join(`
71
- `);const E=new Map;for(const[s,C]of n){const r=await h.smembers(`cachly:ckg:idx:from:${s}`);for(const c of r){const v=await h.get(c);if(!v)continue;const m=O(v,null);if(!m||m.edgeType!=="collaborates")continue;const y=n.get(m.to);if(!y)continue;const[A,I]=s<m.to?[s,m.to]:[m.to,s],e=`${A}||${I}`,H=E.get(e);if(H)H.trials+=m.trials;else{const[ae,ce]=s<m.to?[C,y]:[y,C];E.set(e,{a:ae,b:ce,trials:m.trials,confidence:m.confidence})}}}const $=[...E.values()].filter(s=>s.trials>=b).sort((s,C)=>C.trials-s.trials),i=new Set;for(const s of $)i.add(s.a.id),i.add(s.b.id);const l=[...n.values()].filter(s=>!i.has(s.id)),a=["## \u{1F91D} Team Collaboration Graph",""];if($.length===0)a.push(`No collaboration pairs yet (min weight: ${b}).`,"","Pairs appear when two contributors touch the same file in `learn_from_attempts`",'or when someone recalls a teammate\'s lesson via `smart_recall(requester="...")`.');else{a.push(`**${$.length} collaboration pair${$.length!==1?"s":""}** across ${i.size} contributor${i.size!==1?"s":""}:`,"");for(const s of $.slice(0,20)){const C=(s.confidence*100).toFixed(0),r=[s.a.domain,s.b.domain].filter((c,v,m)=>c&&m.indexOf(c)===v).map(c=>`\`${c}\``).join(", ");a.push(`### @${s.a.handle} \u2194 @${s.b.handle}`,`- **${s.trials} collaboration event${s.trials!==1?"s":""}** \xB7 ${C}% confidence`,`- Domains: ${r||"_unknown_"}`,`> \u{1F4AC} _"Frag **@${s.a.handle}** und **@${s.b.handle}** \u2014 they've solved problems together in these areas."_`,"")}$.length>20&&a.push(`_...and ${$.length-20} more pairs. Use \`min_weight\` to filter._`,"")}return l.length>0&&a.push("---","### \u26A0\uFE0F Bus Factor Alert \u2014 No Collaborators Yet","",`${l.length} contributor${l.length!==1?"s have":" has"} knowledge that no teammate has recalled or co-touched:`,"",...l.slice(0,10).map(s=>`- **@${s.handle}** \u2014 ${s.count} lesson${s.count!==1?"s":""} \xB7 domain: \`${s.domain}\``),"","_Tip: pair these contributors with teammates on shared files to reduce bus factor._"),a.push("","---","_Collaboration edges build from `learn_from_attempts(author=..., file_paths=[...])` and `smart_recall(requester=...)` cross-author reuse._"),a.join(`
72
- `)}case"brain_portability":{const{instance_id:d}=M,t=[{name:"Claude Code",slug:"claude-code",icon:"\u{1F916}",configPath:"~/.claude/settings.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}}},null,2)},{name:"Cursor",slug:"cursor",icon:"\u{1F5B1}\uFE0F",configPath:"~/.cursor/mcp.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}}},null,2)},{name:"Windsurf",slug:"windsurf",icon:"\u{1F3C4}",configPath:"~/.windsurf/mcp.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}}},null,2)},{name:"GitHub Copilot (VS Code)",slug:"copilot",icon:"\u{1F419}",configPath:".vscode/mcp.json (workspace)",configBlock:JSON.stringify({servers:{cachly:{type:"stdio",command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}}},null,2)},{name:"Cline",slug:"cline",icon:"\u26A1",configPath:"~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}}},null,2)},{name:"Zed",slug:"zed",icon:"\u26A1",configPath:"~/.config/zed/settings.json",configBlock:JSON.stringify({context_servers:{cachly:{command:{path:"npx",args:["@cachly-dev/mcp-server@latest"]},env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}}},null,2)},{name:"Continue",slug:"continue",icon:"\u25B6\uFE0F",configPath:"~/.continue/config.json",configBlock:JSON.stringify({mcpServers:[{name:"cachly",command:"npx @cachly-dev/mcp-server@latest",env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:d}}]},null,2)}],h=["## \u{1F310} Brain Portability \u2014 Same Brain, Any Model","",'> _"Bring your own model, keep your brain."_',"",`**Your Brain ID:** \`${d}\``,"","Replace `<cky_live_...>` below with your own API key (get one at https://cachly.dev/setup-ai) \u2014 the Brain ID alone does not authenticate you.","","This Brain ID is model-agnostic. Every lesson you've learned, every Memory Crystal you've distilled,","and every prediction from your Causal Knowledge Graph is accessible from **any MCP-compatible AI client**.","","---","",`### Compatible Clients (${t.length} supported)`,""];for(const b of t)h.push(`#### ${b.icon} ${b.name}`,`Config: \`${b.configPath}\``,"","```json",b.configBlock,"```","");return h.push("---","","### What travels with you","","| Data | Model-neutral? |","|------|---------------|","| Lessons (learn_from_attempts) | \u2705 Full access in all clients |","| Memory Crystals (memory_crystalize) | \u2705 Included in every session_start |","| Causal Knowledge Graph (brain_predict) | \u2705 Same graph, same predictions |","| Team lessons (team_learn, team_confirm) | \u2705 Team is per-Brain, not per-model |","| Domain Brain Marketplace installs | \u2705 Installed once, available everywhere |","| Session handoffs (session_handoff) | \u2705 Cross-client continuity |","","---","","### One-command setup for any new client","","```bash","npx @cachly-dev/mcp-server@latest autopilot","```","",`Autopilot detects all installed editors and writes the config for each \u2014 with your \`${d}\` Brain ID pre-filled.`,"","---","_Docs: cachly.dev/docs/model-neutral \u2022 Your Brain, Any Model_"),h.join(`
73
- `)}case"brain_changelog":{const{instance_id:d,days:t=7,max_lessons:h=30,include_failures:b=!0}=M,o=await j(d),n=Date.now()-Number(t)*864e5,E=await Ee(o,"cachly:lesson:best:*",{max:4e3,timeoutMs:4e3}),$=[];for(const c of E){const v=await o.get(c).catch(()=>null);if(!v)continue;const m=O(v,{});if((m.ts?new Date(m.ts).getTime():0)<n||!b&&m.outcome==="failure")continue;const A=c.replace("cachly:lesson:best:","");$.push({...m,topic:A})}$.sort((c,v)=>{const m=c.ts?new Date(c.ts).getTime():0;return(v.ts?new Date(v.ts).getTime():0)-m});const i=$.slice(0,Number(h));if(i.length===0)return[`## \u{1F4CB} Brain Changelog \u2014 last ${t} day${Number(t)!==1?"s":""}`,"","_No new lessons learned in this window. Your Brain already knows everything it knows \u2014 add new lessons with `learn_from_attempts`._"].join(`
74
- `);const l=new Map;for(const c of i){const v=c.topic.includes(":")?c.topic.split(":")[0]:"general",m=l.get(v)??[];m.push(c),l.set(v,m)}const a=c=>c==="critical"?"\u{1F6A8}":c==="major"?"\u26A0\uFE0F":c==="minor"?"\u{1F4A1}":"\u2728",s=c=>c==="failure"?"\u274C":c==="partial"?"\u{1F536}":"\u2705",r=[`## \u{1F4CB} Brain Changelog \u2014 ${new Date().toISOString().slice(0,10)} (last ${t} day${Number(t)!==1?"s":""})`,"",`_${i.length} lesson${i.length!==1?"s":""} learned across ${l.size} topic area${l.size!==1?"s":""}_`,""];for(const[c,v]of Array.from(l.entries()).sort((m,y)=>y[1].length-m[1].length)){r.push(`### ${c}`),r.push("");for(const m of v){const y=m.outcome==="failure"?s(m.outcome):a(m.severity),A=m.outcome==="failure"?m.what_failed:m.what_worked,I=[];m.author&&I.push(`by **${m.author}**`),m.recall_count&&I.push(`${m.recall_count} recall${m.recall_count!==1?"s":""}`),m.confidence!==void 0&&I.push(`${Math.round(m.confidence*100)}% confidence`),r.push(`- ${y} **${m.topic}**${A?` \u2014 ${A}`:""}`),I.length&&r.push(` _${I.join(" \xB7 ")}_`)}r.push("")}return r.push("---",`_Generated from Brain \`${d}\` \xB7 Share this in your standup or Slack_`),r.join(`
75
- `)}case"brain_set_pref":{const{instance_id:d,key:t,value:h}=M;if(!t||!t.trim())throw new Error("key is required");const b=ze({value:String(h)}),o=b.felder.value,n=await j(d),E=`cachly:prefs:${t.trim()}:${d}`;return await n.set(E,o),[`\u2705 Preference \`${t}\` set to \`${o}\` for this Brain instance.`,...Ke(b,"wert")].join(`
76
- `)}case"brain_get_pref":{const{instance_id:d,key:t}=M,h=await j(d);if(t&&t.trim()){const E=await h.get(`cachly:prefs:${t.trim()}:${d}`).catch(()=>null);return E===null?`\u2139\uFE0F Preference \`${t}\` is not set (using default).`:`\u{1F527} Preference \`${t}\` = \`${E}\``}const b=await Ee(h,`cachly:prefs:*:${d}`,{max:200,timeoutMs:2e3});if(b.length===0)return"\u2139\uFE0F No preferences set for this Brain instance.";const o=await h.mget(...b),n=["\u{1F527} **Brain preferences:**",""];for(let E=0;E<b.length;E++){const $=b[E].replace("cachly:prefs:","").replace(`:${d}`,"");n.push(`- \`${$}\` = \`${o[E]??"(null)"}\``)}return n.join(`
77
- `)}default:return null}}async function vo(D,M){try{if(!Oe()||Te!=="cachly"&&Te!=="ollama")return 0;const{vektor:j,zweit:ne}=gt(D);let d=0;for(const t of j.themen()){if(d>=M)break;if(ne.hat(t)||await D.exists(`${Fe}${t}`))continue;const h=await D.get(`cachly:lesson:best:${t}`);if(!h)continue;const b=O(h,{});try{const o=await xe(Ge(b),{geduld:"lang",modell:Ve});o?.length&&(await D.set(`${Fe}${t}`,De(o)),d++)}catch{}}return d}catch{return 0}}async function cs(D,M,j){try{if(!Oe())return;const ne=await D.srandmember(He,j),d=Array.isArray(ne)?ne:ne?[ne]:[];for(const t of d){if(t===M)continue;const h=await D.get(`cachly:lesson:best:${t}`);if(!h){await D.srem(He,t);continue}const b=O(h,{});try{const o=await xe(Ge(b),{geduld:"lang"});if(o?.length)await D.set(`${Bt}${t}`,De(o)),await D.srem(He,t);else continue;if(Te==="cachly"||Te==="ollama")try{const n=await xe(Ge(b),{geduld:"lang",modell:Ve});n?.length&&(await D.set(`${Fe}${t}`,De(n)),await D.srem(Qe,t))}catch{}}catch{}}await ls(D,ft,ut,M,j,t=>xe(Lt(t),{geduld:"lang"})),(Te==="cachly"||Te==="ollama")&&await ls(D,Qe,Fe,M,j,(t,h)=>xe(Ge(h),{geduld:"lang",modell:Ve}))}catch{}}async function ls(D,M,j,ne,d,t){const h=await D.srandmember(M,d),b=Array.isArray(h)?h:h?[h]:[];for(const o of b){if(o===ne)continue;const n=await D.get(`cachly:lesson:best:${o}`);if(!n){await D.srem(M,o);continue}if(await D.exists(`${j}${o}`)){await D.srem(M,o);continue}try{const E=await t(o,O(n,{}));E?.length&&(await D.set(`${j}${o}`,De(E)),await D.srem(M,o))}catch{}}}export{va as BRAIN_TOOL_NAMES,xt as RECALL_ROH_ZEICHEN,Ct as RECALL_VOLLTEXT_ZEICHEN,gt as bestaendeFuer,Sa as handleBrainTool,Tt as recallNurLesen,rt as recallVorschau};
65
+ `)}case"brain_collab_pairs":{const{instance_id:f,min_weight:t=1}=M,l=await j(f),_=typeof t=="number"&&t>=1?Math.floor(t):1,o=await Ne(l,"cachly:ckg:node:person:*",{max:500}),n=new Map;for(const s of o){const C=await l.get(s);if(!C)continue;const r=O(C,null);!r||r.type!=="person"||n.set(r.id,{id:r.id,handle:r.handle,domain:r.domain,count:r.count})}if(n.size===0)return["## \u{1F91D} Team Collaboration Graph","","No contributors found yet.","",'The graph builds automatically as teammates store lessons with `author="name"`:','`learn_from_attempts(topic="...", author="alice", file_paths=["src/auth.ts"], ...)`'].join(`
66
+ `);const E=new Map;for(const[s,C]of n){const r=await l.smembers(`cachly:ckg:idx:from:${s}`);for(const c of r){const v=await l.get(c);if(!v)continue;const m=O(v,null);if(!m||m.edgeType!=="collaborates")continue;const y=n.get(m.to);if(!y)continue;const[A,I]=s<m.to?[s,m.to]:[m.to,s],e=`${A}||${I}`,G=E.get(e);if(G)G.trials+=m.trials;else{const[ce,oe]=s<m.to?[C,y]:[y,C];E.set(e,{a:ce,b:oe,trials:m.trials,confidence:m.confidence})}}}const $=[...E.values()].filter(s=>s.trials>=_).sort((s,C)=>C.trials-s.trials),i=new Set;for(const s of $)i.add(s.a.id),i.add(s.b.id);const h=[...n.values()].filter(s=>!i.has(s.id)),a=["## \u{1F91D} Team Collaboration Graph",""];if($.length===0)a.push(`No collaboration pairs yet (min weight: ${_}).`,"","Pairs appear when two contributors touch the same file in `learn_from_attempts`",'or when someone recalls a teammate\'s lesson via `smart_recall(requester="...")`.');else{a.push(`**${$.length} collaboration pair${$.length!==1?"s":""}** across ${i.size} contributor${i.size!==1?"s":""}:`,"");for(const s of $.slice(0,20)){const C=(s.confidence*100).toFixed(0),r=[s.a.domain,s.b.domain].filter((c,v,m)=>c&&m.indexOf(c)===v).map(c=>`\`${c}\``).join(", ");a.push(`### @${s.a.handle} \u2194 @${s.b.handle}`,`- **${s.trials} collaboration event${s.trials!==1?"s":""}** \xB7 ${C}% confidence`,`- Domains: ${r||"_unknown_"}`,`> \u{1F4AC} _"Frag **@${s.a.handle}** und **@${s.b.handle}** \u2014 they've solved problems together in these areas."_`,"")}$.length>20&&a.push(`_...and ${$.length-20} more pairs. Use \`min_weight\` to filter._`,"")}return h.length>0&&a.push("---","### \u26A0\uFE0F Bus Factor Alert \u2014 No Collaborators Yet","",`${h.length} contributor${h.length!==1?"s have":" has"} knowledge that no teammate has recalled or co-touched:`,"",...h.slice(0,10).map(s=>`- **@${s.handle}** \u2014 ${s.count} lesson${s.count!==1?"s":""} \xB7 domain: \`${s.domain}\``),"","_Tip: pair these contributors with teammates on shared files to reduce bus factor._"),a.push("","---","_Collaboration edges build from `learn_from_attempts(author=..., file_paths=[...])` and `smart_recall(requester=...)` cross-author reuse._"),a.join(`
67
+ `)}case"brain_portability":{const{instance_id:f}=M,t=[{name:"Claude Code",slug:"claude-code",icon:"\u{1F916}",configPath:"~/.claude/settings.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}}},null,2)},{name:"Cursor",slug:"cursor",icon:"\u{1F5B1}\uFE0F",configPath:"~/.cursor/mcp.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}}},null,2)},{name:"Windsurf",slug:"windsurf",icon:"\u{1F3C4}",configPath:"~/.windsurf/mcp.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}}},null,2)},{name:"GitHub Copilot (VS Code)",slug:"copilot",icon:"\u{1F419}",configPath:".vscode/mcp.json (workspace)",configBlock:JSON.stringify({servers:{cachly:{type:"stdio",command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}}},null,2)},{name:"Cline",slug:"cline",icon:"\u26A1",configPath:"~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",configBlock:JSON.stringify({mcpServers:{cachly:{command:"npx",args:["@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}}},null,2)},{name:"Zed",slug:"zed",icon:"\u26A1",configPath:"~/.config/zed/settings.json",configBlock:JSON.stringify({context_servers:{cachly:{command:{path:"npx",args:["@cachly-dev/mcp-server@latest"]},env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}}},null,2)},{name:"Continue",slug:"continue",icon:"\u25B6\uFE0F",configPath:"~/.continue/config.json",configBlock:JSON.stringify({mcpServers:[{name:"cachly",command:"npx @cachly-dev/mcp-server@latest",env:{CACHLY_JWT:"<cky_live_...>",CACHLY_BRAIN_INSTANCE_ID:f}}]},null,2)}],l=["## \u{1F310} Brain Portability \u2014 Same Brain, Any Model","",'> _"Bring your own model, keep your brain."_',"",`**Your Brain ID:** \`${f}\``,"","Replace `<cky_live_...>` below with your own API key (get one at https://cachly.dev/setup-ai) \u2014 the Brain ID alone does not authenticate you.","","This Brain ID is model-agnostic. Every lesson you've learned, every Memory Crystal you've distilled,","and every prediction from your Causal Knowledge Graph is accessible from **any MCP-compatible AI client**.","","---","",`### Compatible Clients (${t.length} supported)`,""];for(const _ of t)l.push(`#### ${_.icon} ${_.name}`,`Config: \`${_.configPath}\``,"","```json",_.configBlock,"```","");return l.push("---","","### What travels with you","","| Data | Model-neutral? |","|------|---------------|","| Lessons (learn_from_attempts) | \u2705 Full access in all clients |","| Memory Crystals (memory_crystalize) | \u2705 Included in every session_start |","| Causal Knowledge Graph (brain_predict) | \u2705 Same graph, same predictions |","| Team lessons (team_learn, team_confirm) | \u2705 Team is per-Brain, not per-model |","| Domain Brain Marketplace installs | \u2705 Installed once, available everywhere |","| Session handoffs (session_handoff) | \u2705 Cross-client continuity |","","---","","### One-command setup for any new client","","```bash","npx @cachly-dev/mcp-server@latest autopilot","```","",`Autopilot detects all installed editors and writes the config for each \u2014 with your \`${f}\` Brain ID pre-filled.`,"","---","_Docs: cachly.dev/docs/model-neutral \u2022 Your Brain, Any Model_"),l.join(`
68
+ `)}case"brain_changelog":{const{instance_id:f,days:t=7,max_lessons:l=30,include_failures:_=!0}=M,o=await j(f),n=Date.now()-Number(t)*864e5,E=await Ne(o,"cachly:lesson:best:*",{max:4e3,timeoutMs:4e3}),$=[];for(const c of E){const v=await o.get(c).catch(()=>null);if(!v)continue;const m=O(v,{});if((m.ts?new Date(m.ts).getTime():0)<n||!_&&m.outcome==="failure")continue;const A=c.replace("cachly:lesson:best:","");$.push({...m,topic:A})}$.sort((c,v)=>{const m=c.ts?new Date(c.ts).getTime():0;return(v.ts?new Date(v.ts).getTime():0)-m});const i=$.slice(0,Number(l));if(i.length===0)return[`## \u{1F4CB} Brain Changelog \u2014 last ${t} day${Number(t)!==1?"s":""}`,"","_No new lessons learned in this window. Your Brain already knows everything it knows \u2014 add new lessons with `learn_from_attempts`._"].join(`
69
+ `);const h=new Map;for(const c of i){const v=c.topic.includes(":")?c.topic.split(":")[0]:"general",m=h.get(v)??[];m.push(c),h.set(v,m)}const a=c=>c==="critical"?"\u{1F6A8}":c==="major"?"\u26A0\uFE0F":c==="minor"?"\u{1F4A1}":"\u2728",s=c=>c==="failure"?"\u274C":c==="partial"?"\u{1F536}":"\u2705",r=[`## \u{1F4CB} Brain Changelog \u2014 ${new Date().toISOString().slice(0,10)} (last ${t} day${Number(t)!==1?"s":""})`,"",`_${i.length} lesson${i.length!==1?"s":""} learned across ${h.size} topic area${h.size!==1?"s":""}_`,""];for(const[c,v]of Array.from(h.entries()).sort((m,y)=>y[1].length-m[1].length)){r.push(`### ${c}`),r.push("");for(const m of v){const y=m.outcome==="failure"?s(m.outcome):a(m.severity),A=m.outcome==="failure"?m.what_failed:m.what_worked,I=[];m.author&&I.push(`by **${m.author}**`),m.recall_count&&I.push(`${m.recall_count} recall${m.recall_count!==1?"s":""}`),m.confidence!==void 0&&I.push(`${Math.round(m.confidence*100)}% confidence`),r.push(`- ${y} **${m.topic}**${A?` \u2014 ${A}`:""}`),I.length&&r.push(` _${I.join(" \xB7 ")}_`)}r.push("")}return r.push("---",`_Generated from Brain \`${f}\` \xB7 Share this in your standup or Slack_`),r.join(`
70
+ `)}case"brain_set_pref":{const{instance_id:f,key:t,value:l}=M;if(!t||!t.trim())throw new Error("key is required");const _=ze({value:String(l)}),o=_.felder.value,n=await j(f),E=`cachly:prefs:${t.trim()}:${f}`;return await n.set(E,o),[`\u2705 Preference \`${t}\` set to \`${o}\` for this Brain instance.`,...He(_,"wert")].join(`
71
+ `)}case"brain_get_pref":{const{instance_id:f,key:t}=M,l=await j(f);if(t&&t.trim()){const E=await l.get(`cachly:prefs:${t.trim()}:${f}`).catch(()=>null);return E===null?`\u2139\uFE0F Preference \`${t}\` is not set (using default).`:`\u{1F527} Preference \`${t}\` = \`${E}\``}const _=await Ne(l,`cachly:prefs:*:${f}`,{max:200,timeoutMs:2e3});if(_.length===0)return"\u2139\uFE0F No preferences set for this Brain instance.";const o=await l.mget(..._),n=["\u{1F527} **Brain preferences:**",""];for(let E=0;E<_.length;E++){const $=_[E].replace("cachly:prefs:","").replace(`:${f}`,"");n.push(`- \`${$}\` = \`${o[E]??"(null)"}\``)}return n.join(`
72
+ `)}default:return null}}async function xo(D,M){try{if(!Oe()||Te!=="cachly"&&Te!=="ollama")return 0;const{vektor:j,zweit:ae}=gt(D);let f=0;for(const t of j.themen()){if(f>=M)break;if(ae.hat(t)||await D.exists(`${Fe}${t}`))continue;const l=await D.get(`cachly:lesson:best:${t}`);if(!l)continue;const _=O(l,{});try{const o=await xe(Ge(_),{geduld:"lang",modell:qe});o?.length&&(await D.set(`${Fe}${t}`,De(o)),f++)}catch{}}return f}catch{return 0}}async function cs(D,M,j){try{if(!Oe())return;const ae=await D.srandmember(Ke,j),f=Array.isArray(ae)?ae:ae?[ae]:[];for(const t of f){if(t===M)continue;const l=await D.get(`cachly:lesson:best:${t}`);if(!l){await D.srem(Ke,t);continue}const _=O(l,{});try{const o=await xe(Ge(_),{geduld:"lang"});if(o?.length)await D.set(`${Lt}${t}`,De(o)),await D.srem(Ke,t);else continue;if(Te==="cachly"||Te==="ollama")try{const n=await xe(Ge(_),{geduld:"lang",modell:qe});n?.length&&(await D.set(`${Fe}${t}`,De(n)),await D.srem(Qe,t))}catch{}}catch{}}await ls(D,ft,ut,M,j,t=>xe(Bt(t),{geduld:"lang"})),(Te==="cachly"||Te==="ollama")&&await ls(D,Qe,Fe,M,j,(t,l)=>xe(Ge(l),{geduld:"lang",modell:qe}))}catch{}}async function ls(D,M,j,ae,f,t){const l=await D.srandmember(M,f),_=Array.isArray(l)?l:l?[l]:[];for(const o of _){if(o===ae)continue;const n=await D.get(`cachly:lesson:best:${o}`);if(!n){await D.srem(M,o);continue}if(await D.exists(`${j}${o}`)){await D.srem(M,o);continue}try{const E=await t(o,O(n,{}));E?.length&&(await D.set(`${j}${o}`,De(E)),await D.srem(M,o))}catch{}}}export{Ta as BRAIN_TOOL_NAMES,xt as RECALL_ROH_ZEICHEN,Ct as RECALL_VOLLTEXT_ZEICHEN,gt as bestaendeFuer,Na as handleBrainTool,Tt as recallNurLesen,rt as recallVorschau};