@cachly-dev/mcp-server 0.10.128 → 0.10.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +1 -7
- package/dist/packages/telegram-notify/client.js +2 -75
- package/dist/src/ambient-cli.js +7 -253
- package/dist/src/ambient-deps.js +1 -55
- package/dist/src/ambient-hooks.js +5 -278
- package/dist/src/ambient-ledger.js +6 -95
- package/dist/src/ambient-memory.js +1 -67
- package/dist/src/ambient-recall.js +1 -221
- package/dist/src/aussetzer.js +1 -82
- package/dist/src/auth.js +9 -140
- package/dist/src/autor-abzeichen.js +1 -56
- package/dist/src/autor.js +1 -136
- package/dist/src/bauabdruck.js +1 -93
- package/dist/src/bedeutung.js +2 -323
- package/dist/src/bench/cachly-bench.js +4 -249
- package/dist/src/bench/external-corpus.js +4 -135
- package/dist/src/bench/fixtures.js +1 -225
- package/dist/src/brain-export.js +16 -119
- package/dist/src/brain-watch-hook.js +8 -86
- package/dist/src/cachly-url.js +1 -21
- package/dist/src/ckg.js +1 -108
- package/dist/src/cls-hook.js +8 -94
- package/dist/src/confidence.js +1 -50
- package/dist/src/credentials.js +1 -85
- package/dist/src/deutsch.js +1 -223
- package/dist/src/doctor.js +2 -248
- package/dist/src/editor.js +1 -63
- package/dist/src/eingaenge-nachruesten.js +1 -290
- package/dist/src/eingaenge.js +2 -228
- package/dist/src/embeddings.js +13 -223
- package/dist/src/feldreparatur.js +3 -0
- package/dist/src/first-contact.js +4 -108
- package/dist/src/first-recall-proof.js +4 -41
- package/dist/src/funnel-milestones.js +2 -48
- package/dist/src/geliefert.js +1 -114
- package/dist/src/handlers/advanced.js +38 -537
- package/dist/src/handlers/brain.js +69 -4181
- package/dist/src/handlers/cache.js +59 -638
- package/dist/src/handlers/context.js +17 -182
- package/dist/src/handlers/fedbrain.js +40 -1436
- package/dist/src/handlers/instances.js +16 -201
- package/dist/src/handlers/roadmap.js +13 -175
- package/dist/src/handlers/share.js +19 -530
- package/dist/src/handlers/syndicate.js +38 -904
- package/dist/src/handlers/team.js +89 -1850
- package/dist/src/handlers/viz.js +3 -148
- package/dist/src/index.js +321 -4281
- package/dist/src/interferenz.js +1 -0
- package/dist/src/kostprobe.js +7 -125
- package/dist/src/lesson-preview.js +2 -133
- package/dist/src/namespace.js +1 -128
- package/dist/src/notifier.js +1 -6
- package/dist/src/rangfolge-stellschrauben.js +1 -0
- package/dist/src/rangfolge.js +1 -214
- package/dist/src/raumkorrektur.js +1 -197
- package/dist/src/recall-tiefe.js +1 -42
- package/dist/src/rerank.js +1 -154
- package/dist/src/resonanz.js +1 -0
- package/dist/src/search.js +1 -2104
- package/dist/src/seltenheitsbestand.js +1 -92
- package/dist/src/speicherlage.js +1 -84
- package/dist/src/spuren.js +1 -150
- package/dist/src/starter-corpus.js +1 -165
- package/dist/src/telemetry-types.js +0 -13
- package/dist/src/tools.js +10 -2465
- package/dist/src/toolspecs.js +1 -121
- package/dist/src/upgrade-nudge.js +1 -29
- package/dist/src/utils.js +1 -89
- package/dist/src/versuch.js +1 -250
- package/dist/src/vorspann.js +1 -146
- package/dist/src/wartung/vektoren-verdichten.js +1 -200
- package/dist/src/werkzeug-nutzung.js +1 -72
- package/dist/src/wertbeitrag.js +1 -73
- package/dist/src/wirkungsspur.js +1 -0
- package/glama.json +1 -1
- package/package.json +8 -7
- package/server.json +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js.map +0 -1
- package/dist/packages/telegram-notify/client.d.ts +0 -19
- package/dist/packages/telegram-notify/client.js.map +0 -1
- package/dist/src/ambient-cli.d.ts +0 -101
- package/dist/src/ambient-cli.js.map +0 -1
- package/dist/src/ambient-deps.d.ts +0 -26
- package/dist/src/ambient-deps.js.map +0 -1
- package/dist/src/ambient-hooks.d.ts +0 -137
- package/dist/src/ambient-hooks.js.map +0 -1
- package/dist/src/ambient-ledger.d.ts +0 -19
- package/dist/src/ambient-ledger.js.map +0 -1
- package/dist/src/ambient-memory.d.ts +0 -12
- package/dist/src/ambient-memory.js.map +0 -1
- package/dist/src/ambient-recall.d.ts +0 -127
- package/dist/src/ambient-recall.js.map +0 -1
- package/dist/src/aussetzer.d.ts +0 -67
- package/dist/src/aussetzer.js.map +0 -1
- package/dist/src/auth.d.ts +0 -43
- package/dist/src/auth.js.map +0 -1
- package/dist/src/autor-abzeichen.d.ts +0 -41
- package/dist/src/autor-abzeichen.js.map +0 -1
- package/dist/src/autor.d.ts +0 -101
- package/dist/src/autor.js.map +0 -1
- package/dist/src/bauabdruck-daten.generiert.json +0 -5
- package/dist/src/bauabdruck.d.ts +0 -38
- package/dist/src/bauabdruck.js.map +0 -1
- package/dist/src/bedeutung.d.ts +0 -168
- package/dist/src/bedeutung.js.map +0 -1
- package/dist/src/bench/cachly-bench.d.ts +0 -61
- package/dist/src/bench/cachly-bench.js.map +0 -1
- package/dist/src/bench/external-corpus.d.ts +0 -44
- package/dist/src/bench/external-corpus.js.map +0 -1
- package/dist/src/bench/fixtures.d.ts +0 -37
- package/dist/src/bench/fixtures.js.map +0 -1
- package/dist/src/brain-export.d.ts +0 -64
- package/dist/src/brain-export.js.map +0 -1
- package/dist/src/brain-watch-hook.d.ts +0 -21
- package/dist/src/brain-watch-hook.js.map +0 -1
- package/dist/src/cachly-url.d.ts +0 -68
- package/dist/src/cachly-url.js.map +0 -1
- package/dist/src/ckg.d.ts +0 -54
- package/dist/src/ckg.js.map +0 -1
- package/dist/src/cls-hook.d.ts +0 -15
- package/dist/src/cls-hook.js.map +0 -1
- package/dist/src/confidence.d.ts +0 -19
- package/dist/src/confidence.js.map +0 -1
- package/dist/src/credentials.d.ts +0 -37
- package/dist/src/credentials.js.map +0 -1
- package/dist/src/deutsch.d.ts +0 -98
- package/dist/src/deutsch.js.map +0 -1
- package/dist/src/doctor.d.ts +0 -47
- package/dist/src/doctor.js.map +0 -1
- package/dist/src/editor.d.ts +0 -9
- package/dist/src/editor.js.map +0 -1
- package/dist/src/eingaenge-nachruesten.d.ts +0 -41
- package/dist/src/eingaenge-nachruesten.js.map +0 -1
- package/dist/src/eingaenge.d.ts +0 -135
- package/dist/src/eingaenge.js.map +0 -1
- package/dist/src/embeddings.d.ts +0 -48
- package/dist/src/embeddings.js.map +0 -1
- package/dist/src/first-contact.d.ts +0 -56
- package/dist/src/first-contact.js.map +0 -1
- package/dist/src/first-recall-proof.d.ts +0 -19
- package/dist/src/first-recall-proof.js.map +0 -1
- package/dist/src/funnel-milestones.d.ts +0 -5
- package/dist/src/funnel-milestones.js.map +0 -1
- package/dist/src/geliefert.d.ts +0 -63
- package/dist/src/geliefert.js.map +0 -1
- package/dist/src/handlers/advanced.d.ts +0 -6
- package/dist/src/handlers/advanced.js.map +0 -1
- package/dist/src/handlers/brain.d.ts +0 -41
- package/dist/src/handlers/brain.js.map +0 -1
- package/dist/src/handlers/cache.d.ts +0 -6
- package/dist/src/handlers/cache.js.map +0 -1
- package/dist/src/handlers/context.d.ts +0 -6
- package/dist/src/handlers/context.js.map +0 -1
- package/dist/src/handlers/fedbrain.d.ts +0 -18
- package/dist/src/handlers/fedbrain.js.map +0 -1
- package/dist/src/handlers/instances.d.ts +0 -33
- package/dist/src/handlers/instances.js.map +0 -1
- package/dist/src/handlers/roadmap.d.ts +0 -6
- package/dist/src/handlers/roadmap.js.map +0 -1
- package/dist/src/handlers/share.d.ts +0 -8
- package/dist/src/handlers/share.js.map +0 -1
- package/dist/src/handlers/syndicate.d.ts +0 -6
- package/dist/src/handlers/syndicate.js.map +0 -1
- package/dist/src/handlers/team.d.ts +0 -45
- package/dist/src/handlers/team.js.map +0 -1
- package/dist/src/handlers/viz.d.ts +0 -6
- package/dist/src/handlers/viz.js.map +0 -1
- package/dist/src/index.d.ts +0 -93
- package/dist/src/index.js.map +0 -1
- package/dist/src/kostprobe.d.ts +0 -25
- package/dist/src/kostprobe.js.map +0 -1
- package/dist/src/lesson-preview.d.ts +0 -81
- package/dist/src/lesson-preview.js.map +0 -1
- package/dist/src/namespace.d.ts +0 -20
- package/dist/src/namespace.js.map +0 -1
- package/dist/src/notifier.d.ts +0 -5
- package/dist/src/notifier.js.map +0 -1
- package/dist/src/rangfolge.d.ts +0 -169
- package/dist/src/rangfolge.js.map +0 -1
- package/dist/src/raumkorrektur.d.ts +0 -100
- package/dist/src/raumkorrektur.js.map +0 -1
- package/dist/src/recall-tiefe.d.ts +0 -48
- package/dist/src/recall-tiefe.js.map +0 -1
- package/dist/src/rerank.d.ts +0 -48
- package/dist/src/rerank.js.map +0 -1
- package/dist/src/search.d.ts +0 -153
- package/dist/src/search.js.map +0 -1
- package/dist/src/seltenheitsbestand.d.ts +0 -53
- package/dist/src/seltenheitsbestand.js.map +0 -1
- package/dist/src/speicherlage.d.ts +0 -51
- package/dist/src/speicherlage.js.map +0 -1
- package/dist/src/spuren.d.ts +0 -97
- package/dist/src/spuren.js.map +0 -1
- package/dist/src/starter-corpus.d.ts +0 -28
- package/dist/src/starter-corpus.js.map +0 -1
- package/dist/src/telemetry-types.d.ts +0 -87
- package/dist/src/telemetry-types.js.map +0 -1
- package/dist/src/tools.d.ts +0 -2933
- package/dist/src/tools.js.map +0 -1
- package/dist/src/toolspecs.d.ts +0 -43
- package/dist/src/toolspecs.js.map +0 -1
- package/dist/src/upgrade-nudge.d.ts +0 -15
- package/dist/src/upgrade-nudge.js.map +0 -1
- package/dist/src/utils.d.ts +0 -34
- package/dist/src/utils.js.map +0 -1
- package/dist/src/versuch.d.ts +0 -179
- package/dist/src/versuch.js.map +0 -1
- package/dist/src/vorspann.d.ts +0 -73
- package/dist/src/vorspann.js.map +0 -1
- package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
- package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
- package/dist/src/werkzeug-nutzung.d.ts +0 -62
- package/dist/src/werkzeug-nutzung.js.map +0 -1
- package/dist/src/wertbeitrag.d.ts +0 -57
- package/dist/src/wertbeitrag.js.map +0 -1
package/dist/src/bauabdruck.js
CHANGED
|
@@ -1,93 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Zwei Bauten koennen dieselbe Versionsnummer tragen und trotzdem
|
|
4
|
-
// verschiedenen Code ausfuehren. Gemessen am 20.08.2026: package.json sagte
|
|
5
|
-
// beim installierten UND beim aktuellen Server 0.10.124 — der installierte
|
|
6
|
-
// dist/ hatte den Code aus instances.ts (get_connection_string) trotzdem
|
|
7
|
-
// nicht. Von aussen war das nicht zu unterscheiden. Der Bauabdruck macht den
|
|
8
|
-
// Commit sichtbar, nicht nur die Versionsnummer.
|
|
9
|
-
//
|
|
10
|
-
// Die echten Werte kommen aus scripts/bauabdruck.mjs, das beim Bauen
|
|
11
|
-
// (`npm run build`) eine JSON-Datei daneben schreibt. Diese Datei ist
|
|
12
|
-
// generiert und steht darum in .gitignore. DIESE Datei (bauabdruck.ts) ist
|
|
13
|
-
// eingecheckt und bleibt es — sie ist die Rueckfall-Fassung, die einspringt,
|
|
14
|
-
// wenn noch nie gebaut wurde. Genau das prueft CI im Schritt "Type check":
|
|
15
|
-
// `tsc --noEmit` laeuft dort OHNE vorherigen `npm run build`.
|
|
16
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
-
import { dirname, join } from 'node:path';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
-
/** Rueckfall, solange kein Bauabdruck generiert wurde (kein Absturz, keine Erfindung). */
|
|
20
|
-
export const RUECKFALL_BAUABDRUCK = {
|
|
21
|
-
version: 'unbekannt',
|
|
22
|
-
commit: 'unbekannt',
|
|
23
|
-
gebautAm: 'unbekannt',
|
|
24
|
-
};
|
|
25
|
-
function istBauabdruck(wert) {
|
|
26
|
-
if (typeof wert !== 'object' || wert === null)
|
|
27
|
-
return false;
|
|
28
|
-
const w = wert;
|
|
29
|
-
return typeof w.version === 'string' && typeof w.commit === 'string' && typeof w.gebautAm === 'string';
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Laedt den generierten Bauabdruck aus dem eigenen Verzeichnis.
|
|
33
|
-
*
|
|
34
|
-
* Faellt auf RUECKFALL_BAUABDRUCK zurueck, wenn die Datei fehlt, kein gueltiges
|
|
35
|
-
* JSON enthaelt oder die falsche Form hat — ein fehlender Bauabdruck ist kein
|
|
36
|
-
* Grund, den Server gar nicht erst zu starten.
|
|
37
|
-
*/
|
|
38
|
-
export function ladeBauabdruck(verzeichnis = dirname(fileURLToPath(import.meta.url))) {
|
|
39
|
-
const pfad = join(verzeichnis, 'bauabdruck-daten.generiert.json');
|
|
40
|
-
if (!existsSync(pfad))
|
|
41
|
-
return RUECKFALL_BAUABDRUCK;
|
|
42
|
-
try {
|
|
43
|
-
const wert = JSON.parse(readFileSync(pfad, 'utf-8'));
|
|
44
|
-
return istBauabdruck(wert) ? wert : RUECKFALL_BAUABDRUCK;
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return RUECKFALL_BAUABDRUCK;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/** Der Bauabdruck dieses laufenden Prozesses — einmal beim Start geladen. */
|
|
51
|
-
export const bauabdruck = ladeBauabdruck();
|
|
52
|
-
/** Ab wie vielen Tagen der Bauabdruck in brain_doctor als alt gilt (🟡). */
|
|
53
|
-
export const ALTER_SCHWELLE_TAGE = 14;
|
|
54
|
-
/**
|
|
55
|
-
* Der Waechter: ist der Bauabdruck aelter als die Schwelle?
|
|
56
|
-
*
|
|
57
|
-
* Eigene Funktion, damit sie einzeln aufrufbar und pruefbar ist — aus
|
|
58
|
-
* demselben Grund wie beurteileDeckung in eingaenge.ts: ein Wächter, den man
|
|
59
|
-
* nicht einzeln aufrufen kann, ist ein Wächter, dessen Nein niemand je sieht.
|
|
60
|
-
* "unbekannt" gilt NICHT als alt — ein Server, der nie gebaut wurde, ist ein
|
|
61
|
-
* anderer Zustand als einer, der seit Wochen laeuft.
|
|
62
|
-
*/
|
|
63
|
-
export function istAlt(gebautAm, jetzt = new Date()) {
|
|
64
|
-
const datum = new Date(gebautAm);
|
|
65
|
-
if (Number.isNaN(datum.getTime()))
|
|
66
|
-
return false;
|
|
67
|
-
const tage = (jetzt.getTime() - datum.getTime()) / 86_400_000;
|
|
68
|
-
return tage > ALTER_SCHWELLE_TAGE;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Baut die Zeile fuer brain_doctor — Form und Ton wie die Zeile
|
|
72
|
-
* "Semantic coverage" dort: Emoji, Fettdruck-Label, Zahlen statt Adjektive.
|
|
73
|
-
*
|
|
74
|
-
* Ein Server, der seit Wochen laeuft, ist kein Fehler — aber man muss es
|
|
75
|
-
* sehen koennen. Darum 🟡 statt ❌ fuer den Altersfall.
|
|
76
|
-
*/
|
|
77
|
-
export function formatiereBauabdruck(a, jetzt = new Date()) {
|
|
78
|
-
if (a.gebautAm === 'unbekannt') {
|
|
79
|
-
return `❔ **Bauabdruck: unbekannt** — noch kein \`npm run build\` gelaufen, Version/Commit nicht feststellbar`;
|
|
80
|
-
}
|
|
81
|
-
const datum = new Date(a.gebautAm);
|
|
82
|
-
const gueltigesDatum = !Number.isNaN(datum.getTime());
|
|
83
|
-
const alt = gueltigesDatum && istAlt(a.gebautAm, jetzt);
|
|
84
|
-
const tage = gueltigesDatum ? Math.floor((jetzt.getTime() - datum.getTime()) / 86_400_000) : null;
|
|
85
|
-
const alterText = tage === null ? '' : ` (vor ${tage} Tag${tage === 1 ? '' : 'en'})`;
|
|
86
|
-
const emoji = alt ? '🟡' : '✅';
|
|
87
|
-
const hinweis = alt
|
|
88
|
-
? ` — laeuft laenger als ${ALTER_SCHWELLE_TAGE} Tage. Kein Fehler, aber sichtbar.`
|
|
89
|
-
: '';
|
|
90
|
-
const datumsText = gueltigesDatum ? a.gebautAm.slice(0, 10) : a.gebautAm;
|
|
91
|
-
return `${emoji} **Bauabdruck:** v${a.version} · commit \`${a.commit}\` · gebaut am ${datumsText}${alterText}${hinweis}`;
|
|
92
|
-
}
|
|
93
|
-
//# sourceMappingURL=bauabdruck.js.map
|
|
1
|
+
import{existsSync as g,readFileSync as l}from"node:fs";import{dirname as f,join as p}from"node:path";import{fileURLToPath as d}from"node:url";const o={version:"unbekannt",commit:"unbekannt",gebautAm:"unbekannt"};function k(t){if(typeof t!="object"||t===null)return!1;const e=t;return typeof e.version=="string"&&typeof e.commit=="string"&&typeof e.gebautAm=="string"}function A(t=f(d(import.meta.url))){const e=p(t,"bauabdruck-daten.generiert.json");if(!g(e))return o;try{const n=JSON.parse(l(e,"utf-8"));return k(n)?n:o}catch{return o}}const y=A(),i=14;function T(t,e=new Date){const n=new Date(t);return Number.isNaN(n.getTime())?!1:(e.getTime()-n.getTime())/864e5>i}function B(t,e=new Date){if(t.gebautAm==="unbekannt")return"\u2754 **Bauabdruck: unbekannt** \u2014 noch kein `npm run build` gelaufen, Version/Commit nicht feststellbar";const n=new Date(t.gebautAm),r=!Number.isNaN(n.getTime()),u=r&&T(t.gebautAm,e),a=r?Math.floor((e.getTime()-n.getTime())/864e5):null,m=a===null?"":` (vor ${a} Tag${a===1?"":"en"})`,s=u?"\u{1F7E1}":"\u2705",c=u?` \u2014 laeuft laenger als ${i} Tage. Kein Fehler, aber sichtbar.`:"",b=r?t.gebautAm.slice(0,10):t.gebautAm;return`${s} **Bauabdruck:** v${t.version} \xB7 commit \`${t.commit}\` \xB7 gebaut am ${b}${m}${c}`}export{i as ALTER_SCHWELLE_TAGE,o as RUECKFALL_BAUABDRUCK,y as bauabdruck,B as formatiereBauabdruck,T as istAlt,A as ladeBauabdruck};
|
package/dist/src/bedeutung.js
CHANGED
|
@@ -1,323 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* ── Warum es das gibt ────────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* Gemessen am 19.08.2026 an 499 echten Lektionen mit 100 Fragen in Alltags-
|
|
7
|
-
* sprache:
|
|
8
|
-
*
|
|
9
|
-
* Verfahren Platz 1 Top 3 MRR
|
|
10
|
-
* Wörter (bisher) 21 % 33 % 29,3 %
|
|
11
|
-
* Bedeutung (bge-m3) 40 % 60 % 51,4 %
|
|
12
|
-
*
|
|
13
|
-
* Der Wortabgleich war ausgereizt: nach drei Reparaturen brachten weitere
|
|
14
|
-
* Eingriffe je eine Frage. Der Grund liegt in der Aufgabe, nicht in der
|
|
15
|
-
* Umsetzung. Eine Frage beschreibt ein SYMPTOM, die Lektion eine URSACHE.
|
|
16
|
-
* "Der Deploy hängt beim Bauen" und "No space left on device" teilen kein Wort
|
|
17
|
-
* und meinen dasselbe. Kein Wörterbuch schließt diese Lücke.
|
|
18
|
-
*
|
|
19
|
-
* ── Und es ist SCHNELLER, nicht langsamer ───────────────────────────────────
|
|
20
|
-
*
|
|
21
|
-
* Das war die Überraschung. Am selben Tag gemessen:
|
|
22
|
-
*
|
|
23
|
-
* Wortabgleich (heute, 499 Lektionen) 788 ms (Mitte)
|
|
24
|
-
* Einbettung der Frage (Netzaufruf) 282 ms
|
|
25
|
-
* Vergleich gegen 494 Vektoren 0 ms (14 ms im schlechtesten Fall)
|
|
26
|
-
*
|
|
27
|
-
* Der Wortabgleich baut seinen Index bei JEDER Frage neu: alle Datensätze
|
|
28
|
-
* holen, zerlegen, Worthäufigkeiten und Bigramme rechnen. Das wächst linear
|
|
29
|
-
* mit dem Bestand — gemessen 1,6 ms je Lektion und Frage:
|
|
30
|
-
*
|
|
31
|
-
* Lektionen 50 100 250 499 998 1996
|
|
32
|
-
* Wartezeit 75ms 156ms 408ms 792ms 1601ms 3185ms
|
|
33
|
-
*
|
|
34
|
-
* Bei 2000 Lektionen sind das 3,2 Sekunden. Die automatische Einblendung hat
|
|
35
|
-
* ein 3-Sekunden-Budget — das erklärt, warum sie nichts liefert
|
|
36
|
-
* (cachly:ambient-recall-blendet-nichts-ein).
|
|
37
|
-
*
|
|
38
|
-
* Der Bedeutungsabgleich kostet dagegen konstant einen Netzaufruf. Er wird
|
|
39
|
-
* nicht langsamer, wenn der Bestand wächst.
|
|
40
|
-
*
|
|
41
|
-
* ── Was das Modul NICHT tut ─────────────────────────────────────────────────
|
|
42
|
-
*
|
|
43
|
-
* Es ersetzt den Wortabgleich nicht. Zwei Gründe:
|
|
44
|
-
*
|
|
45
|
-
* 1. Ohne Netz gibt es keine Einbettung. Der Wortabgleich läuft immer.
|
|
46
|
-
* 2. Gemischt sind die ersten zehn Treffer besser als bei beiden einzeln
|
|
47
|
-
* (71 % gegen 68 und 47). Wörter finden das wörtlich Genannte, Bedeutung
|
|
48
|
-
* das sinngemäß Gemeinte.
|
|
49
|
-
*/
|
|
50
|
-
/** Wo ein Lektionsvektor liegt. Ein Schlüssel je Lektion, neben der Lektion. */
|
|
51
|
-
export const VEKTOR_PRAEFIX = 'cachly:lesson:vec:';
|
|
52
|
-
/**
|
|
53
|
-
* Wo der Vektor des THEMENNAMENS liegt.
|
|
54
|
-
*
|
|
55
|
-
* Warum getrennt vom Volltextvektor: der Name ist kurz und frageähnlich, der
|
|
56
|
-
* Volltext ist 1376 Zeichen lang. Eine 60-Zeichen-Frage gegen 60 Zeichen zu
|
|
57
|
-
* halten ist symmetrisch, gegen 1376 nicht — der Sortierer bewertet beide
|
|
58
|
-
* deshalb als eigene Merkmale (`naeheText` 1,0 und `naeheThema` 0,6).
|
|
59
|
-
*
|
|
60
|
-
* Gemessen am 19.08.2026: der Name allein findet nur 26 %, im Verbund trägt er
|
|
61
|
-
* verlässlich. Bis zum 20.08. gab es diese Vektoren nur im Messstand
|
|
62
|
-
* (korpus-gross.sicht-c.json), im Produkt nicht — der Sortierer aus
|
|
63
|
-
* rangfolge.ts war deshalb auch gar nicht verdrahtet.
|
|
64
|
-
*/
|
|
65
|
-
export const NAME_VEKTOR_PRAEFIX = 'cachly:lesson:vecname:';
|
|
66
|
-
/** Der Text, aus dem der Namensvektor gebildet wird: Trennzeichen zu Wörtern. */
|
|
67
|
-
export function textFuerNamensVektor(topic) {
|
|
68
|
-
return topic.replace(/[:_-]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Vektoren werden als base64 gespeichert, nicht als JSON-Zahlenliste.
|
|
72
|
-
*
|
|
73
|
-
* 1024 Zahlen als JSON sind rund 20 KB je Lektion, als Float32 sind es 4 KB.
|
|
74
|
-
* Bei 500 Lektionen ist das der Unterschied zwischen 10 MB und 2 MB, die bei
|
|
75
|
-
* jedem kalten Start über die Leitung gehen.
|
|
76
|
-
*
|
|
77
|
-
* ── Seit 20.08.2026: int8 statt float32 ────────────────────────────────────
|
|
78
|
-
*
|
|
79
|
-
* Gemessen an diesem Tag: von 23,6 MB im Speicher waren 11,2 MB (47 %)
|
|
80
|
-
* Vektoren — 6,0 MB Lektionsvektoren und 5,2 MB Eingänge. Der Freitarif hat
|
|
81
|
-
* 25 MB. Der Bestand stand bei 94 % und die nächste Lektion wäre an einem
|
|
82
|
-
* Schreibfehler gescheitert.
|
|
83
|
-
*
|
|
84
|
-
* Eine Zahl braucht hier keine 4 Bytes. Die Vektoren sind normiert, alle Werte
|
|
85
|
-
* liegen dicht beieinander, und für den Kosinus zählt die RICHTUNG. Also wird
|
|
86
|
-
* je Vektor eine Skala mitgeschrieben und jede Zahl als ein Byte abgelegt.
|
|
87
|
-
* Aus 4096 Bytes werden 1033 — Faktor 3,96.
|
|
88
|
-
*
|
|
89
|
-
* Warum weiter base64 und nicht roh: rohe Bytes müssten überall als Buffer
|
|
90
|
-
* gelesen werden. Eine einzige Stelle, die weiter Text liest, macht Vektoren
|
|
91
|
-
* still kaputt statt laut. Der base64-Aufschlag von einem Drittel ist billiger
|
|
92
|
-
* als diese Fehlerklasse.
|
|
93
|
-
*
|
|
94
|
-
* ── Aufbau des neuen Formats ───────────────────────────────────────────────
|
|
95
|
-
*
|
|
96
|
-
* Byte 0 Kennung 0x01
|
|
97
|
-
* Byte 1–2 uint16 LE — wie viele Zahlen
|
|
98
|
-
* Byte 3–6 float32 LE — die Skala
|
|
99
|
-
* ab Byte 7 je Zahl ein int8
|
|
100
|
-
* danach Füllbytes, bis die Länge durch 4 geteilt den Rest 1 lässt
|
|
101
|
-
*
|
|
102
|
-
* Die Füllbytes sind kein Schönheitsfehler, sie sind die Unterscheidung: das
|
|
103
|
-
* alte Format ist IMMER durch 4 teilbar (float32), das neue lässt immer den
|
|
104
|
-
* Rest 1. Die Kennung allein würde nicht reichen — ein float32 darf zufällig
|
|
105
|
-
* mit 0x01 anfangen. Zwei unabhängige Merkmale, nicht eines.
|
|
106
|
-
*
|
|
107
|
-
* `entpacke` liest beide Formate. Alte Vektoren bleiben gültig und werden
|
|
108
|
-
* ersetzt, wenn ihre Lektion das nächste Mal geschrieben wird.
|
|
109
|
-
*/
|
|
110
|
-
const KENNUNG_INT8 = 0x01;
|
|
111
|
-
const KOPF_BYTES = 7; // Kennung(1) + Anzahl(2) + Skala(4)
|
|
112
|
-
export function packe(vektor) {
|
|
113
|
-
let groesstes = 0;
|
|
114
|
-
for (const x of vektor) {
|
|
115
|
-
const a = Math.abs(x);
|
|
116
|
-
if (a > groesstes)
|
|
117
|
-
groesstes = a;
|
|
118
|
-
}
|
|
119
|
-
// Skala 1 bei einem Nullvektor: dann ist jede Zahl 0 und die Skala egal.
|
|
120
|
-
const skala = groesstes > 0 ? groesstes / 127 : 1;
|
|
121
|
-
let laenge = KOPF_BYTES + vektor.length;
|
|
122
|
-
while (laenge % 4 !== 1)
|
|
123
|
-
laenge++;
|
|
124
|
-
const b = Buffer.alloc(laenge); // alloc, nicht allocUnsafe: die Füllbytes sollen 0 sein
|
|
125
|
-
b.writeUInt8(KENNUNG_INT8, 0);
|
|
126
|
-
b.writeUInt16LE(vektor.length, 1);
|
|
127
|
-
b.writeFloatLE(skala, 3);
|
|
128
|
-
for (let i = 0; i < vektor.length; i++) {
|
|
129
|
-
const q = Math.round(vektor[i] / skala);
|
|
130
|
-
b.writeInt8(q > 127 ? 127 : q < -127 ? -127 : q, KOPF_BYTES + i);
|
|
131
|
-
}
|
|
132
|
-
return b.toString('base64');
|
|
133
|
-
}
|
|
134
|
-
export function entpacke(s) {
|
|
135
|
-
try {
|
|
136
|
-
const b = Buffer.from(s, 'base64');
|
|
137
|
-
if (b.byteLength === 0)
|
|
138
|
-
return null;
|
|
139
|
-
// Neues Format: Kennung UND Restklasse 1 müssen beide stimmen.
|
|
140
|
-
if (b[0] === KENNUNG_INT8 && b.byteLength % 4 === 1 && b.byteLength > KOPF_BYTES) {
|
|
141
|
-
const anzahl = b.readUInt16LE(1);
|
|
142
|
-
if (anzahl === 0 || KOPF_BYTES + anzahl > b.byteLength)
|
|
143
|
-
return null;
|
|
144
|
-
const skala = b.readFloatLE(3);
|
|
145
|
-
if (!Number.isFinite(skala))
|
|
146
|
-
return null;
|
|
147
|
-
const aus = new Array(anzahl);
|
|
148
|
-
for (let i = 0; i < anzahl; i++)
|
|
149
|
-
aus[i] = b.readInt8(KOPF_BYTES + i) * skala;
|
|
150
|
-
return aus;
|
|
151
|
-
}
|
|
152
|
-
// Altes Format: reine float32-Folge.
|
|
153
|
-
if (b.byteLength % 4 !== 0)
|
|
154
|
-
return null;
|
|
155
|
-
const f = new Float32Array(b.buffer, b.byteOffset, b.byteLength / 4);
|
|
156
|
-
return Array.from(f);
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Kosinus-Ähnlichkeit zweier Vektoren.
|
|
164
|
-
*
|
|
165
|
-
* Die Länge wird bei JEDEM Aufruf neu gerechnet statt einmal vorher. Das ist
|
|
166
|
-
* absichtlich: gemessen liegt der gesamte Vergleich gegen 494 Vektoren bei
|
|
167
|
-
* 0 ms. Eine Vorberechnung wäre eine zweite Wahrheit über denselben Vektor —
|
|
168
|
-
* und der Gewinn wäre nicht messbar.
|
|
169
|
-
*/
|
|
170
|
-
export function kosinus(a, b) {
|
|
171
|
-
if (a.length !== b.length)
|
|
172
|
-
return -1;
|
|
173
|
-
let p = 0;
|
|
174
|
-
let qa = 0;
|
|
175
|
-
let qb = 0;
|
|
176
|
-
for (let i = 0; i < a.length; i++) {
|
|
177
|
-
p += a[i] * b[i];
|
|
178
|
-
qa += a[i] * a[i];
|
|
179
|
-
qb += b[i] * b[i];
|
|
180
|
-
}
|
|
181
|
-
return qa && qb ? p / Math.sqrt(qa * qb) : 0;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Der Text, aus dem der Vektor einer Lektion gebildet wird.
|
|
185
|
-
*
|
|
186
|
-
* Bewusst NICHT das rohe JSON: Feldnamen, Zeitstempel und Prüfspuren tragen
|
|
187
|
-
* keine Bedeutung, verbrauchen aber vom Fenster des Einbettungsmodells. Der
|
|
188
|
-
* Dienst kürzt bei rund 2000 Zeichen — gemessen liefern 2000, 4000 und 6000
|
|
189
|
-
* Zeichen denselben Vektor. Was hinten steht, existiert für ihn nicht.
|
|
190
|
-
*/
|
|
191
|
-
export function textFuerVektor(lektion) {
|
|
192
|
-
const s = (v) => (typeof v === 'string' ? v : '');
|
|
193
|
-
return [s(lektion.topic), s(lektion.what_worked), s(lektion.what_failed)]
|
|
194
|
-
.filter(Boolean).join('\n').slice(0, 2000);
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Ein Vektorbestand, der im Arbeitsspeicher bleibt.
|
|
198
|
-
*
|
|
199
|
-
* Warum: 494 Vektoren sind rund 2 MB. Die bei jeder Frage aus dem Speicher zu
|
|
200
|
-
* holen wäre langsamer als der Wortabgleich, den wir gerade ersetzen — der
|
|
201
|
-
* Gewinn wäre weg, bevor er anfängt.
|
|
202
|
-
*
|
|
203
|
-
* Der MCP-Server läuft lange, also wird einmal geladen und danach nur
|
|
204
|
-
* nachgetragen. Die Prüfung "hat sich etwas geändert" läuft über die ANZAHL
|
|
205
|
-
* der Schlüssel, nicht über ihren Inhalt: eine geänderte Lektion bekommt beim
|
|
206
|
-
* Lernen sofort ihren neuen Vektor geschrieben, und dieser Bestand holt ihn
|
|
207
|
-
* beim nächsten Nachladen.
|
|
208
|
-
*/
|
|
209
|
-
export class Vektorbestand {
|
|
210
|
-
frischeMs;
|
|
211
|
-
praefix;
|
|
212
|
-
vektoren = new Map();
|
|
213
|
-
geladen = 0;
|
|
214
|
-
/**
|
|
215
|
-
* @param frischeMs wie lange ein geladener Bestand als frisch gilt
|
|
216
|
-
* @param praefix welche Sicht — Volltext (Standard) oder Themenname
|
|
217
|
-
*
|
|
218
|
-
* Der Präfix ist ein Parameter und keine zweite Klasse: die Mechanik ist
|
|
219
|
-
* identisch (scannen, in Blöcken holen, entpacken, Gelöschtes vergessen).
|
|
220
|
-
* Zwei Klassen dafür wären zwei Orte, an denen dieselbe Änderung nachgezogen
|
|
221
|
-
* werden müsste.
|
|
222
|
-
*/
|
|
223
|
-
constructor(frischeMs = 60_000, praefix = VEKTOR_PRAEFIX) {
|
|
224
|
-
this.frischeMs = frischeMs;
|
|
225
|
-
this.praefix = praefix;
|
|
226
|
-
}
|
|
227
|
-
/** Die Nähe einer Frage zu EINER Lektion. -2, wenn sie keinen Vektor hat. */
|
|
228
|
-
naehe(frage, topic) {
|
|
229
|
-
const v = this.vektoren.get(`${this.praefix}${topic}`);
|
|
230
|
-
return v ? kosinus(frage, v) : -2;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Der Vektor selbst — für die Rückkopplung, die aus den besten Treffern
|
|
234
|
-
* einen angereicherten Fragevektor baut (`reichereAn`). `null`, wenn die
|
|
235
|
-
* Lektion keinen hat; der Aufrufer filtert das weg.
|
|
236
|
-
*/
|
|
237
|
-
rohvektor(topic) {
|
|
238
|
-
return this.vektoren.get(`${this.praefix}${topic}`) ?? null;
|
|
239
|
-
}
|
|
240
|
-
get groesse() { return this.vektoren.size; }
|
|
241
|
-
/** Lädt fehlende Vektoren nach. Tut nichts, wenn kürzlich geladen wurde. */
|
|
242
|
-
async aktualisiere(redis, jetzt = Date.now()) {
|
|
243
|
-
if (this.vektoren.size > 0 && jetzt - this.geladen < this.frischeMs)
|
|
244
|
-
return;
|
|
245
|
-
const schluessel = [];
|
|
246
|
-
let cursor = '0';
|
|
247
|
-
do {
|
|
248
|
-
const [next, gefunden] = await redis.scan(cursor, 'MATCH', `${this.praefix}*`, 'COUNT', 500);
|
|
249
|
-
cursor = next;
|
|
250
|
-
schluessel.push(...gefunden);
|
|
251
|
-
} while (cursor !== '0');
|
|
252
|
-
const fehlend = schluessel.filter((k) => !this.vektoren.has(k));
|
|
253
|
-
for (let i = 0; i < fehlend.length; i += 100) {
|
|
254
|
-
const block = fehlend.slice(i, i + 100);
|
|
255
|
-
const werte = await redis.mget(...block);
|
|
256
|
-
for (const [j, roh] of werte.entries()) {
|
|
257
|
-
if (!roh)
|
|
258
|
-
continue;
|
|
259
|
-
const v = entpacke(roh);
|
|
260
|
-
if (v)
|
|
261
|
-
this.vektoren.set(block[j], v);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
// Gelöschte Lektionen wieder loswerden.
|
|
265
|
-
if (schluessel.length < this.vektoren.size) {
|
|
266
|
-
const da = new Set(schluessel);
|
|
267
|
-
for (const k of [...this.vektoren.keys()])
|
|
268
|
-
if (!da.has(k))
|
|
269
|
-
this.vektoren.delete(k);
|
|
270
|
-
}
|
|
271
|
-
this.geladen = jetzt;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Die ähnlichsten Lektionen zu einem Fragevektor.
|
|
275
|
-
*
|
|
276
|
-
* Zurück kommen die THEMEN, nicht die Schlüssel — der Aufrufer arbeitet mit
|
|
277
|
-
* Themen und soll das Schlüsselformat nicht kennen müssen.
|
|
278
|
-
*/
|
|
279
|
-
aehnlichste(frage, anzahl) {
|
|
280
|
-
const aus = [];
|
|
281
|
-
for (const [k, v] of this.vektoren) {
|
|
282
|
-
aus.push({ topic: k.slice(this.praefix.length), naehe: kosinus(frage, v) });
|
|
283
|
-
}
|
|
284
|
-
aus.sort((a, b) => b.naehe - a.naehe);
|
|
285
|
-
return aus.slice(0, anzahl);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Mischt zwei Rangfolgen über die PLATZIERUNG, nicht über die Punktzahl.
|
|
290
|
-
*
|
|
291
|
-
* Punktzahlen aus BM25 und aus einem Kosinus sind nicht vergleichbar: die eine
|
|
292
|
-
* ist nach oben offen, die andere liegt zwischen -1 und 1. Wer sie addiert oder
|
|
293
|
-
* skaliert, wählt unbeabsichtigt einen Gewinner und merkt es nie.
|
|
294
|
-
*
|
|
295
|
-
* Reciprocal Rank Fusion braucht keine gemeinsame Einheit.
|
|
296
|
-
*
|
|
297
|
-
* ── Wie viel Wortabgleich? ──────────────────────────────────────────────────
|
|
298
|
-
*
|
|
299
|
-
* Gemessen am 19.08.2026 an 499 Lektionen mit 100 Fragen:
|
|
300
|
-
*
|
|
301
|
-
* Wortgewicht Platz 1 Top 3 Top 10 MRR
|
|
302
|
-
* 0,0 (nur Sinn) 40 % 60 % 68 % 51,4 %
|
|
303
|
-
* 0,1 41 % 63 % 71 % 53,5 %
|
|
304
|
-
* 0,2 42 % 57 % 70 % 52,0 %
|
|
305
|
-
* 0,3 39 % 55 % 71 % 49,0 %
|
|
306
|
-
* 0,4 38 % 51 % 69 % 47,7 %
|
|
307
|
-
* 1,0 (nur Wörter) 21 % 33 % 47 % 29,3 %
|
|
308
|
-
*
|
|
309
|
-
* 0,1 ist auf drei von vier Maßen vorn und überall besser als reine Bedeutung.
|
|
310
|
-
* Wenig Wortabgleich hilft, viel schadet: die Wörter fangen den Fall, in dem
|
|
311
|
-
* jemand eine Fehlermeldung wörtlich einfügt, und stören sonst.
|
|
312
|
-
*
|
|
313
|
-
* Zu 0,2 ist der Abstand bei Platz 1 EINE Frage. Das ist kein Beleg, sondern
|
|
314
|
-
* eine Richtung — wer hier dreht, misst nach.
|
|
315
|
-
*/
|
|
316
|
-
export const WORT_GEWICHT = 0.1;
|
|
317
|
-
export function mischeRangfolgen(woerter, bedeutung, wortGewicht = WORT_GEWICHT, k = 60) {
|
|
318
|
-
const punkte = new Map();
|
|
319
|
-
woerter.forEach((t, i) => punkte.set(t, (punkte.get(t) ?? 0) + wortGewicht / (k + i + 1)));
|
|
320
|
-
bedeutung.forEach((t, i) => punkte.set(t, (punkte.get(t) ?? 0) + (1 - wortGewicht) / (k + i + 1)));
|
|
321
|
-
return [...punkte.entries()].sort((a, b) => b[1] - a[1]).map(([t]) => t);
|
|
322
|
-
}
|
|
323
|
-
//# sourceMappingURL=bedeutung.js.map
|
|
1
|
+
const x="cachly:lesson:vec:",y="cachly:lesson:vecname:";function b(o){return o.replace(/[:_-]+/g," ").replace(/\s+/g," ").trim()}const f=1,c=7;function d(o){let e=0;for(const t of o){const n=Math.abs(t);n>e&&(e=n)}const i=e>0?e/127:1;let r=c+o.length;for(;r%4!==1;)r++;const s=Buffer.alloc(r);s.writeUInt8(f,0),s.writeUInt16LE(o.length,1),s.writeFloatLE(i,3);for(let t=0;t<o.length;t++){const n=Math.round(o[t]/i);s.writeInt8(n>127?127:n<-127?-127:n,c+t)}return s.toString("base64")}function k(o){try{const e=Buffer.from(o,"base64");if(e.byteLength===0)return null;if(e[0]===f&&e.byteLength%4===1&&e.byteLength>c){const r=e.readUInt16LE(1);if(r===0||c+r>e.byteLength)return null;const s=e.readFloatLE(3);if(!Number.isFinite(s))return null;const t=new Array(r);for(let n=0;n<r;n++)t[n]=e.readInt8(c+n)*s;return t}if(e.byteLength%4!==0)return null;const i=new Float32Array(e.buffer,e.byteOffset,e.byteLength/4);return Array.from(i)}catch{return null}}function u(o,e){if(o.length!==e.length)return-1;let i=0,r=0,s=0;for(let t=0;t<o.length;t++)i+=o[t]*e[t],r+=o[t]*o[t],s+=e[t]*e[t];return r&&s?i/Math.sqrt(r*s):0}function E(o){const e=i=>typeof i=="string"?i:"";return[e(o.topic),e(o.what_worked),e(o.what_failed)].filter(Boolean).join(`
|
|
2
|
+
`).slice(0,2e3)}class v{frischeMs;praefix;vektoren=new Map;geladen=0;constructor(e=6e4,i=x){this.frischeMs=e,this.praefix=i}naehe(e,i){const r=this.vektoren.get(`${this.praefix}${i}`);return r?u(e,r):-2}rohvektor(e){return this.vektoren.get(`${this.praefix}${e}`)??null}get groesse(){return this.vektoren.size}async aktualisiere(e,i=Date.now()){if(this.vektoren.size>0&&i-this.geladen<this.frischeMs)return;const r=[];let s="0";do{const[n,a]=await e.scan(s,"MATCH",`${this.praefix}*`,"COUNT",500);s=n,r.push(...a)}while(s!=="0");const t=r.filter(n=>!this.vektoren.has(n));for(let n=0;n<t.length;n+=100){const a=t.slice(n,n+100),g=await e.mget(...a);for(const[p,l]of g.entries()){if(!l)continue;const h=k(l);h&&this.vektoren.set(a[p],h)}}if(r.length<this.vektoren.size){const n=new Set(r);for(const a of[...this.vektoren.keys()])n.has(a)||this.vektoren.delete(a)}this.geladen=i}aehnlichste(e,i){const r=[];for(const[s,t]of this.vektoren)r.push({topic:s.slice(this.praefix.length),naehe:u(e,t)});return r.sort((s,t)=>t.naehe-s.naehe),r.slice(0,i)}}const w=.1;function L(o,e,i=w,r=60){const s=new Map;return o.forEach((t,n)=>s.set(t,(s.get(t)??0)+i/(r+n+1))),e.forEach((t,n)=>s.set(t,(s.get(t)??0)+(1-i)/(r+n+1))),[...s.entries()].sort((t,n)=>n[1]-t[1]).map(([t])=>t)}export{y as NAME_VEKTOR_PRAEFIX,x as VEKTOR_PRAEFIX,v as Vektorbestand,w as WORT_GEWICHT,k as entpacke,u as kosinus,L as mischeRangfolgen,d as packe,b as textFuerNamensVektor,E as textFuerVektor};
|