@cachly-dev/mcp-server 0.10.124 → 0.10.125

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.
Files changed (85) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +7 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/src/aussetzer.d.ts +67 -0
  5. package/dist/src/aussetzer.js +82 -0
  6. package/dist/src/aussetzer.js.map +1 -0
  7. package/dist/src/autor-abzeichen.d.ts +41 -0
  8. package/dist/src/autor-abzeichen.js +56 -0
  9. package/dist/src/autor-abzeichen.js.map +1 -0
  10. package/dist/src/autor.d.ts +101 -0
  11. package/dist/src/autor.js +136 -0
  12. package/dist/src/autor.js.map +1 -0
  13. package/dist/src/bauabdruck-daten.generiert.json +5 -0
  14. package/dist/src/bauabdruck.d.ts +38 -0
  15. package/dist/src/bauabdruck.js +93 -0
  16. package/dist/src/bauabdruck.js.map +1 -0
  17. package/dist/src/bedeutung.d.ts +175 -0
  18. package/dist/src/bedeutung.js +254 -0
  19. package/dist/src/bedeutung.js.map +1 -0
  20. package/dist/src/brain-export.d.ts +24 -0
  21. package/dist/src/brain-export.js +31 -1
  22. package/dist/src/brain-export.js.map +1 -1
  23. package/dist/src/deutsch.d.ts +98 -0
  24. package/dist/src/deutsch.js +223 -0
  25. package/dist/src/deutsch.js.map +1 -0
  26. package/dist/src/eingaenge-nachruesten.d.ts +41 -0
  27. package/dist/src/eingaenge-nachruesten.js +290 -0
  28. package/dist/src/eingaenge-nachruesten.js.map +1 -0
  29. package/dist/src/eingaenge.d.ts +135 -0
  30. package/dist/src/eingaenge.js +228 -0
  31. package/dist/src/eingaenge.js.map +1 -0
  32. package/dist/src/geliefert.d.ts +63 -0
  33. package/dist/src/geliefert.js +114 -0
  34. package/dist/src/geliefert.js.map +1 -0
  35. package/dist/src/handlers/advanced.js +7 -3
  36. package/dist/src/handlers/advanced.js.map +1 -1
  37. package/dist/src/handlers/brain.d.ts +19 -0
  38. package/dist/src/handlers/brain.js +492 -48
  39. package/dist/src/handlers/brain.js.map +1 -1
  40. package/dist/src/handlers/instances.d.ts +24 -0
  41. package/dist/src/handlers/instances.js +49 -1
  42. package/dist/src/handlers/instances.js.map +1 -1
  43. package/dist/src/handlers/team.js +105 -0
  44. package/dist/src/handlers/team.js.map +1 -1
  45. package/dist/src/index.d.ts +20 -0
  46. package/dist/src/index.js +272 -26
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/lesson-preview.d.ts +81 -0
  49. package/dist/src/lesson-preview.js +133 -0
  50. package/dist/src/lesson-preview.js.map +1 -0
  51. package/dist/src/rangfolge.d.ts +169 -0
  52. package/dist/src/rangfolge.js +214 -0
  53. package/dist/src/rangfolge.js.map +1 -0
  54. package/dist/src/raumkorrektur.d.ts +100 -0
  55. package/dist/src/raumkorrektur.js +197 -0
  56. package/dist/src/raumkorrektur.js.map +1 -0
  57. package/dist/src/recall-tiefe.d.ts +48 -0
  58. package/dist/src/recall-tiefe.js +42 -0
  59. package/dist/src/recall-tiefe.js.map +1 -0
  60. package/dist/src/rerank.d.ts +0 -14
  61. package/dist/src/rerank.js +39 -2
  62. package/dist/src/rerank.js.map +1 -1
  63. package/dist/src/search.d.ts +56 -17
  64. package/dist/src/search.js +335 -73
  65. package/dist/src/search.js.map +1 -1
  66. package/dist/src/seltenheitsbestand.d.ts +53 -0
  67. package/dist/src/seltenheitsbestand.js +92 -0
  68. package/dist/src/seltenheitsbestand.js.map +1 -0
  69. package/dist/src/spuren.d.ts +97 -0
  70. package/dist/src/spuren.js +150 -0
  71. package/dist/src/spuren.js.map +1 -0
  72. package/dist/src/tools.js +1 -1
  73. package/dist/src/tools.js.map +1 -1
  74. package/dist/src/vorspann.d.ts +73 -0
  75. package/dist/src/vorspann.js +146 -0
  76. package/dist/src/vorspann.js.map +1 -0
  77. package/dist/src/werkzeug-nutzung.d.ts +62 -0
  78. package/dist/src/werkzeug-nutzung.js +72 -0
  79. package/dist/src/werkzeug-nutzung.js.map +1 -0
  80. package/dist/src/wertbeitrag.d.ts +57 -0
  81. package/dist/src/wertbeitrag.js +73 -0
  82. package/dist/src/wertbeitrag.js.map +1 -0
  83. package/glama.json +1 -1
  84. package/package.json +12 -4
  85. package/server.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bauabdruck.js","sourceRoot":"","sources":["../../src/bauabdruck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Bedeutungsabgleich: die Frage findet die Lektion auch ohne gemeinsame Wörter.
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
+ import type { Redis } from 'ioredis';
51
+ /** Wo ein Lektionsvektor liegt. Ein Schlüssel je Lektion, neben der Lektion. */
52
+ export declare const VEKTOR_PRAEFIX = "cachly:lesson:vec:";
53
+ /**
54
+ * Wo der Vektor des THEMENNAMENS liegt.
55
+ *
56
+ * Warum getrennt vom Volltextvektor: der Name ist kurz und frageähnlich, der
57
+ * Volltext ist 1376 Zeichen lang. Eine 60-Zeichen-Frage gegen 60 Zeichen zu
58
+ * halten ist symmetrisch, gegen 1376 nicht — der Sortierer bewertet beide
59
+ * deshalb als eigene Merkmale (`naeheText` 1,0 und `naeheThema` 0,6).
60
+ *
61
+ * Gemessen am 19.08.2026: der Name allein findet nur 26 %, im Verbund trägt er
62
+ * verlässlich. Bis zum 20.08. gab es diese Vektoren nur im Messstand
63
+ * (korpus-gross.sicht-c.json), im Produkt nicht — der Sortierer aus
64
+ * rangfolge.ts war deshalb auch gar nicht verdrahtet.
65
+ */
66
+ export declare const NAME_VEKTOR_PRAEFIX = "cachly:lesson:vecname:";
67
+ /** Der Text, aus dem der Namensvektor gebildet wird: Trennzeichen zu Wörtern. */
68
+ export declare function textFuerNamensVektor(topic: string): string;
69
+ /**
70
+ * Vektoren werden als base64 gespeichert, nicht als JSON-Zahlenliste.
71
+ *
72
+ * 1024 Zahlen als JSON sind rund 20 KB je Lektion, als Float32 sind es 4 KB.
73
+ * Bei 500 Lektionen ist das der Unterschied zwischen 10 MB und 2 MB, die bei
74
+ * jedem kalten Start über die Leitung gehen.
75
+ */
76
+ export declare function packe(vektor: number[]): string;
77
+ export declare function entpacke(s: string): number[] | null;
78
+ /**
79
+ * Kosinus-Ähnlichkeit zweier Vektoren.
80
+ *
81
+ * Die Länge wird bei JEDEM Aufruf neu gerechnet statt einmal vorher. Das ist
82
+ * absichtlich: gemessen liegt der gesamte Vergleich gegen 494 Vektoren bei
83
+ * 0 ms. Eine Vorberechnung wäre eine zweite Wahrheit über denselben Vektor —
84
+ * und der Gewinn wäre nicht messbar.
85
+ */
86
+ export declare function kosinus(a: number[], b: number[]): number;
87
+ /**
88
+ * Der Text, aus dem der Vektor einer Lektion gebildet wird.
89
+ *
90
+ * Bewusst NICHT das rohe JSON: Feldnamen, Zeitstempel und Prüfspuren tragen
91
+ * keine Bedeutung, verbrauchen aber vom Fenster des Einbettungsmodells. Der
92
+ * Dienst kürzt bei rund 2000 Zeichen — gemessen liefern 2000, 4000 und 6000
93
+ * Zeichen denselben Vektor. Was hinten steht, existiert für ihn nicht.
94
+ */
95
+ export declare function textFuerVektor(lektion: Record<string, unknown>): string;
96
+ /**
97
+ * Ein Vektorbestand, der im Arbeitsspeicher bleibt.
98
+ *
99
+ * Warum: 494 Vektoren sind rund 2 MB. Die bei jeder Frage aus dem Speicher zu
100
+ * holen wäre langsamer als der Wortabgleich, den wir gerade ersetzen — der
101
+ * Gewinn wäre weg, bevor er anfängt.
102
+ *
103
+ * Der MCP-Server läuft lange, also wird einmal geladen und danach nur
104
+ * nachgetragen. Die Prüfung "hat sich etwas geändert" läuft über die ANZAHL
105
+ * der Schlüssel, nicht über ihren Inhalt: eine geänderte Lektion bekommt beim
106
+ * Lernen sofort ihren neuen Vektor geschrieben, und dieser Bestand holt ihn
107
+ * beim nächsten Nachladen.
108
+ */
109
+ export declare class Vektorbestand {
110
+ private readonly frischeMs;
111
+ private readonly praefix;
112
+ private vektoren;
113
+ private geladen;
114
+ /**
115
+ * @param frischeMs wie lange ein geladener Bestand als frisch gilt
116
+ * @param praefix welche Sicht — Volltext (Standard) oder Themenname
117
+ *
118
+ * Der Präfix ist ein Parameter und keine zweite Klasse: die Mechanik ist
119
+ * identisch (scannen, in Blöcken holen, entpacken, Gelöschtes vergessen).
120
+ * Zwei Klassen dafür wären zwei Orte, an denen dieselbe Änderung nachgezogen
121
+ * werden müsste.
122
+ */
123
+ constructor(frischeMs?: number, praefix?: string);
124
+ /** Die Nähe einer Frage zu EINER Lektion. -2, wenn sie keinen Vektor hat. */
125
+ naehe(frage: number[], topic: string): number;
126
+ /**
127
+ * Der Vektor selbst — für die Rückkopplung, die aus den besten Treffern
128
+ * einen angereicherten Fragevektor baut (`reichereAn`). `null`, wenn die
129
+ * Lektion keinen hat; der Aufrufer filtert das weg.
130
+ */
131
+ rohvektor(topic: string): number[] | null;
132
+ get groesse(): number;
133
+ /** Lädt fehlende Vektoren nach. Tut nichts, wenn kürzlich geladen wurde. */
134
+ aktualisiere(redis: Redis, jetzt?: number): Promise<void>;
135
+ /**
136
+ * Die ähnlichsten Lektionen zu einem Fragevektor.
137
+ *
138
+ * Zurück kommen die THEMEN, nicht die Schlüssel — der Aufrufer arbeitet mit
139
+ * Themen und soll das Schlüsselformat nicht kennen müssen.
140
+ */
141
+ aehnlichste(frage: number[], anzahl: number): Array<{
142
+ topic: string;
143
+ naehe: number;
144
+ }>;
145
+ }
146
+ /**
147
+ * Mischt zwei Rangfolgen über die PLATZIERUNG, nicht über die Punktzahl.
148
+ *
149
+ * Punktzahlen aus BM25 und aus einem Kosinus sind nicht vergleichbar: die eine
150
+ * ist nach oben offen, die andere liegt zwischen -1 und 1. Wer sie addiert oder
151
+ * skaliert, wählt unbeabsichtigt einen Gewinner und merkt es nie.
152
+ *
153
+ * Reciprocal Rank Fusion braucht keine gemeinsame Einheit.
154
+ *
155
+ * ── Wie viel Wortabgleich? ──────────────────────────────────────────────────
156
+ *
157
+ * Gemessen am 19.08.2026 an 499 Lektionen mit 100 Fragen:
158
+ *
159
+ * Wortgewicht Platz 1 Top 3 Top 10 MRR
160
+ * 0,0 (nur Sinn) 40 % 60 % 68 % 51,4 %
161
+ * 0,1 41 % 63 % 71 % 53,5 %
162
+ * 0,2 42 % 57 % 70 % 52,0 %
163
+ * 0,3 39 % 55 % 71 % 49,0 %
164
+ * 0,4 38 % 51 % 69 % 47,7 %
165
+ * 1,0 (nur Wörter) 21 % 33 % 47 % 29,3 %
166
+ *
167
+ * 0,1 ist auf drei von vier Maßen vorn und überall besser als reine Bedeutung.
168
+ * Wenig Wortabgleich hilft, viel schadet: die Wörter fangen den Fall, in dem
169
+ * jemand eine Fehlermeldung wörtlich einfügt, und stören sonst.
170
+ *
171
+ * Zu 0,2 ist der Abstand bei Platz 1 EINE Frage. Das ist kein Beleg, sondern
172
+ * eine Richtung — wer hier dreht, misst nach.
173
+ */
174
+ export declare const WORT_GEWICHT = 0.1;
175
+ export declare function mischeRangfolgen(woerter: string[], bedeutung: string[], wortGewicht?: number, k?: number): string[];
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Bedeutungsabgleich: die Frage findet die Lektion auch ohne gemeinsame Wörter.
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
+ export function packe(vektor) {
78
+ const f = new Float32Array(vektor);
79
+ return Buffer.from(f.buffer, f.byteOffset, f.byteLength).toString('base64');
80
+ }
81
+ export function entpacke(s) {
82
+ try {
83
+ const b = Buffer.from(s, 'base64');
84
+ if (b.byteLength === 0 || b.byteLength % 4 !== 0)
85
+ return null;
86
+ const f = new Float32Array(b.buffer, b.byteOffset, b.byteLength / 4);
87
+ return Array.from(f);
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Kosinus-Ähnlichkeit zweier Vektoren.
95
+ *
96
+ * Die Länge wird bei JEDEM Aufruf neu gerechnet statt einmal vorher. Das ist
97
+ * absichtlich: gemessen liegt der gesamte Vergleich gegen 494 Vektoren bei
98
+ * 0 ms. Eine Vorberechnung wäre eine zweite Wahrheit über denselben Vektor —
99
+ * und der Gewinn wäre nicht messbar.
100
+ */
101
+ export function kosinus(a, b) {
102
+ if (a.length !== b.length)
103
+ return -1;
104
+ let p = 0;
105
+ let qa = 0;
106
+ let qb = 0;
107
+ for (let i = 0; i < a.length; i++) {
108
+ p += a[i] * b[i];
109
+ qa += a[i] * a[i];
110
+ qb += b[i] * b[i];
111
+ }
112
+ return qa && qb ? p / Math.sqrt(qa * qb) : 0;
113
+ }
114
+ /**
115
+ * Der Text, aus dem der Vektor einer Lektion gebildet wird.
116
+ *
117
+ * Bewusst NICHT das rohe JSON: Feldnamen, Zeitstempel und Prüfspuren tragen
118
+ * keine Bedeutung, verbrauchen aber vom Fenster des Einbettungsmodells. Der
119
+ * Dienst kürzt bei rund 2000 Zeichen — gemessen liefern 2000, 4000 und 6000
120
+ * Zeichen denselben Vektor. Was hinten steht, existiert für ihn nicht.
121
+ */
122
+ export function textFuerVektor(lektion) {
123
+ const s = (v) => (typeof v === 'string' ? v : '');
124
+ return [s(lektion.topic), s(lektion.what_worked), s(lektion.what_failed)]
125
+ .filter(Boolean).join('\n').slice(0, 2000);
126
+ }
127
+ /**
128
+ * Ein Vektorbestand, der im Arbeitsspeicher bleibt.
129
+ *
130
+ * Warum: 494 Vektoren sind rund 2 MB. Die bei jeder Frage aus dem Speicher zu
131
+ * holen wäre langsamer als der Wortabgleich, den wir gerade ersetzen — der
132
+ * Gewinn wäre weg, bevor er anfängt.
133
+ *
134
+ * Der MCP-Server läuft lange, also wird einmal geladen und danach nur
135
+ * nachgetragen. Die Prüfung "hat sich etwas geändert" läuft über die ANZAHL
136
+ * der Schlüssel, nicht über ihren Inhalt: eine geänderte Lektion bekommt beim
137
+ * Lernen sofort ihren neuen Vektor geschrieben, und dieser Bestand holt ihn
138
+ * beim nächsten Nachladen.
139
+ */
140
+ export class Vektorbestand {
141
+ frischeMs;
142
+ praefix;
143
+ vektoren = new Map();
144
+ geladen = 0;
145
+ /**
146
+ * @param frischeMs wie lange ein geladener Bestand als frisch gilt
147
+ * @param praefix welche Sicht — Volltext (Standard) oder Themenname
148
+ *
149
+ * Der Präfix ist ein Parameter und keine zweite Klasse: die Mechanik ist
150
+ * identisch (scannen, in Blöcken holen, entpacken, Gelöschtes vergessen).
151
+ * Zwei Klassen dafür wären zwei Orte, an denen dieselbe Änderung nachgezogen
152
+ * werden müsste.
153
+ */
154
+ constructor(frischeMs = 60_000, praefix = VEKTOR_PRAEFIX) {
155
+ this.frischeMs = frischeMs;
156
+ this.praefix = praefix;
157
+ }
158
+ /** Die Nähe einer Frage zu EINER Lektion. -2, wenn sie keinen Vektor hat. */
159
+ naehe(frage, topic) {
160
+ const v = this.vektoren.get(`${this.praefix}${topic}`);
161
+ return v ? kosinus(frage, v) : -2;
162
+ }
163
+ /**
164
+ * Der Vektor selbst — für die Rückkopplung, die aus den besten Treffern
165
+ * einen angereicherten Fragevektor baut (`reichereAn`). `null`, wenn die
166
+ * Lektion keinen hat; der Aufrufer filtert das weg.
167
+ */
168
+ rohvektor(topic) {
169
+ return this.vektoren.get(`${this.praefix}${topic}`) ?? null;
170
+ }
171
+ get groesse() { return this.vektoren.size; }
172
+ /** Lädt fehlende Vektoren nach. Tut nichts, wenn kürzlich geladen wurde. */
173
+ async aktualisiere(redis, jetzt = Date.now()) {
174
+ if (this.vektoren.size > 0 && jetzt - this.geladen < this.frischeMs)
175
+ return;
176
+ const schluessel = [];
177
+ let cursor = '0';
178
+ do {
179
+ const [next, gefunden] = await redis.scan(cursor, 'MATCH', `${this.praefix}*`, 'COUNT', 500);
180
+ cursor = next;
181
+ schluessel.push(...gefunden);
182
+ } while (cursor !== '0');
183
+ const fehlend = schluessel.filter((k) => !this.vektoren.has(k));
184
+ for (let i = 0; i < fehlend.length; i += 100) {
185
+ const block = fehlend.slice(i, i + 100);
186
+ const werte = await redis.mget(...block);
187
+ for (const [j, roh] of werte.entries()) {
188
+ if (!roh)
189
+ continue;
190
+ const v = entpacke(roh);
191
+ if (v)
192
+ this.vektoren.set(block[j], v);
193
+ }
194
+ }
195
+ // Gelöschte Lektionen wieder loswerden.
196
+ if (schluessel.length < this.vektoren.size) {
197
+ const da = new Set(schluessel);
198
+ for (const k of [...this.vektoren.keys()])
199
+ if (!da.has(k))
200
+ this.vektoren.delete(k);
201
+ }
202
+ this.geladen = jetzt;
203
+ }
204
+ /**
205
+ * Die ähnlichsten Lektionen zu einem Fragevektor.
206
+ *
207
+ * Zurück kommen die THEMEN, nicht die Schlüssel — der Aufrufer arbeitet mit
208
+ * Themen und soll das Schlüsselformat nicht kennen müssen.
209
+ */
210
+ aehnlichste(frage, anzahl) {
211
+ const aus = [];
212
+ for (const [k, v] of this.vektoren) {
213
+ aus.push({ topic: k.slice(this.praefix.length), naehe: kosinus(frage, v) });
214
+ }
215
+ aus.sort((a, b) => b.naehe - a.naehe);
216
+ return aus.slice(0, anzahl);
217
+ }
218
+ }
219
+ /**
220
+ * Mischt zwei Rangfolgen über die PLATZIERUNG, nicht über die Punktzahl.
221
+ *
222
+ * Punktzahlen aus BM25 und aus einem Kosinus sind nicht vergleichbar: die eine
223
+ * ist nach oben offen, die andere liegt zwischen -1 und 1. Wer sie addiert oder
224
+ * skaliert, wählt unbeabsichtigt einen Gewinner und merkt es nie.
225
+ *
226
+ * Reciprocal Rank Fusion braucht keine gemeinsame Einheit.
227
+ *
228
+ * ── Wie viel Wortabgleich? ──────────────────────────────────────────────────
229
+ *
230
+ * Gemessen am 19.08.2026 an 499 Lektionen mit 100 Fragen:
231
+ *
232
+ * Wortgewicht Platz 1 Top 3 Top 10 MRR
233
+ * 0,0 (nur Sinn) 40 % 60 % 68 % 51,4 %
234
+ * 0,1 41 % 63 % 71 % 53,5 %
235
+ * 0,2 42 % 57 % 70 % 52,0 %
236
+ * 0,3 39 % 55 % 71 % 49,0 %
237
+ * 0,4 38 % 51 % 69 % 47,7 %
238
+ * 1,0 (nur Wörter) 21 % 33 % 47 % 29,3 %
239
+ *
240
+ * 0,1 ist auf drei von vier Maßen vorn und überall besser als reine Bedeutung.
241
+ * Wenig Wortabgleich hilft, viel schadet: die Wörter fangen den Fall, in dem
242
+ * jemand eine Fehlermeldung wörtlich einfügt, und stören sonst.
243
+ *
244
+ * Zu 0,2 ist der Abstand bei Platz 1 EINE Frage. Das ist kein Beleg, sondern
245
+ * eine Richtung — wer hier dreht, misst nach.
246
+ */
247
+ export const WORT_GEWICHT = 0.1;
248
+ export function mischeRangfolgen(woerter, bedeutung, wortGewicht = WORT_GEWICHT, k = 60) {
249
+ const punkte = new Map();
250
+ woerter.forEach((t, i) => punkte.set(t, (punkte.get(t) ?? 0) + wortGewicht / (k + i + 1)));
251
+ bedeutung.forEach((t, i) => punkte.set(t, (punkte.get(t) ?? 0) + (1 - wortGewicht) / (k + i + 1)));
252
+ return [...punkte.entries()].sort((a, b) => b[1] - a[1]).map(([t]) => t);
253
+ }
254
+ //# sourceMappingURL=bedeutung.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bedeutung.js","sourceRoot":"","sources":["../../src/bedeutung.ts"],"names":[],"mappings":"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"}
@@ -20,7 +20,31 @@ export interface ExportLesson {
20
20
  ts?: string;
21
21
  author?: string;
22
22
  recallCount?: number;
23
+ /**
24
+ * Der GESPEICHERTE Datensatz, unveraendert.
25
+ *
26
+ * Warum es das Feld gibt: bis zum 19.08.2026 zog `cachly export` seine Daten
27
+ * aus /memory, einer Dashboard-Zusammenfassung. Ergebnis: 50 Lektionen von
28
+ * 493, what_worked auf 120 Zeichen abgeschnitten. Das Format war nie das
29
+ * Problem — dieses Modul kann what_failed und file_paths seit jeher — die
30
+ * QUELLE war es.
31
+ *
32
+ * Damit sich das nicht wiederholt, traegt die JSONL-Zeile jetzt den rohen
33
+ * Datensatz, wenn er da ist. Wer morgen ein Feld an die Lektion haengt, muss
34
+ * dieses Modul nicht anfassen: es steht automatisch im Export. Ein Export,
35
+ * der wissen muss, welche Felder es gibt, verliert genau die, an die niemand
36
+ * gedacht hat.
37
+ */
38
+ raw?: Record<string, unknown>;
23
39
  }
40
+ /**
41
+ * Macht aus einem gespeicherten Lektions-Datensatz eine ExportLesson.
42
+ *
43
+ * Die bekannten Felder werden benannt, damit das Markdown sie rendern kann.
44
+ * Der ganze Datensatz bleibt zusaetzlich unter `raw` erhalten, damit die
45
+ * JSONL-Zeile nichts verliert.
46
+ */
47
+ export declare function vonRohLektion(roh: unknown): ExportLesson | null;
24
48
  /**
25
49
  * Builds the human-readable Markdown dump: one `##` section per topic
26
50
  * category (the part of `topic` before the first `:`, or "Ungrouped" when
@@ -4,6 +4,36 @@
4
4
  * design — no network, no filesystem. The `export` CLI command in index.ts
5
5
  * fetches the lessons and writes the files these functions build.
6
6
  */
7
+ /**
8
+ * Macht aus einem gespeicherten Lektions-Datensatz eine ExportLesson.
9
+ *
10
+ * Die bekannten Felder werden benannt, damit das Markdown sie rendern kann.
11
+ * Der ganze Datensatz bleibt zusaetzlich unter `raw` erhalten, damit die
12
+ * JSONL-Zeile nichts verliert.
13
+ */
14
+ export function vonRohLektion(roh) {
15
+ if (typeof roh !== 'object' || roh === null)
16
+ return null;
17
+ const r = roh;
18
+ const topic = typeof r.topic === 'string' ? r.topic : '';
19
+ if (!topic)
20
+ return null;
21
+ const text = (v) => (typeof v === 'string' && v ? v : undefined);
22
+ const liste = (v) => Array.isArray(v) && v.length > 0 ? v.filter((x) => typeof x === 'string') : undefined;
23
+ return {
24
+ topic,
25
+ outcome: text(r.outcome),
26
+ severity: text(r.severity),
27
+ whatWorked: text(r.what_worked),
28
+ whatFailed: text(r.what_failed),
29
+ filePaths: liste(r.file_paths),
30
+ tags: liste(r.tags),
31
+ ts: text(r.ts),
32
+ author: text(r.author),
33
+ recallCount: typeof r.recall_count === 'number' ? r.recall_count : undefined,
34
+ raw: r,
35
+ };
36
+ }
7
37
  const UNGROUPED_HEADING = 'Ungrouped';
8
38
  function groupByCategory(lessons) {
9
39
  const groups = new Map();
@@ -73,7 +103,7 @@ export function buildLessonsMarkdown(lessons) {
73
103
  export function buildLessonsJsonl(lessons) {
74
104
  if (lessons.length === 0)
75
105
  return '';
76
- return lessons.map((lesson) => JSON.stringify({
106
+ return lessons.map((lesson) => JSON.stringify(lesson.raw ?? {
77
107
  topic: lesson.topic,
78
108
  outcome: lesson.outcome,
79
109
  severity: lesson.severity,
@@ -1 +1 @@
1
- {"version":3,"file":"brain-export.js","sourceRoot":"","sources":["../../src/brain-export.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"brain-export.js","sourceRoot":"","sources":["../../src/brain-export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Deutsch für die Suche: Umlaute, Wortformen, zusammengesetzte Wörter.
3
+ *
4
+ * ── Warum es dieses Modul gibt ───────────────────────────────────────────────
5
+ *
6
+ * Gemessen am 19.08.2026 an 498 echten Lektionen mit 20 Fragen in Alltags-
7
+ * deutsch: 2 Treffer. Sechs Fragen teilten mit der richtigen Antwort NICHT EIN
8
+ * EINZIGES Wort. Die Aufteilung nach Ursachen (src/bench/wortschatz-oder-
9
+ * menge.ts) zeigte: schon bei 20 Lektionen und null Ablenkern scheitern 14 von
10
+ * 20. Es ist also der Wortschatz, nicht die Menge.
11
+ *
12
+ * Die Suche hatte deutsche Stoppwörter und 35 deutsche Wortpaare. Was fehlte,
13
+ * waren drei Dinge — und alle drei sind an echten Fragen belegt:
14
+ *
15
+ * 1. UMLAUTE. Die Wortliste stand mit echten Umlauten (`lösung`,
16
+ * `überwachung`), Lektionen und Fragen schreiben oft `ae/oe/ue`. Damit
17
+ * fand `ueberwachung` den Eintrag `überwachung` nie. Zwei Schreibweisen
18
+ * desselben Wortes, und die Suche hielt sie für verschieden.
19
+ *
20
+ * 2. WORTFORM. `abhaengigkeiten` gegen `abhaengigkeit`, `prozesse` gegen
21
+ * `prozess`. Für sieben Sprachen gab es einen Stemmer, für Deutsch nicht.
22
+ *
23
+ * 3. ZUSAMMENGESETZTE WÖRTER. Das ist die deutsche Besonderheit und war die
24
+ * größte Lücke. `Arbeitsspeicher` enthält `speicher`, und `speicher` steht
25
+ * längst in der Wortliste — nur kam niemand dort an. Genauso
26
+ * `Zugriffsprotokoll` → `protokoll` → `log`, `Browsertests` → `test`.
27
+ *
28
+ * ── Die Entscheidung, die dieses Modul klein hält ────────────────────────────
29
+ *
30
+ * Für die Zerlegung braucht es ein Wörterbuch der Grundwörter. Es wäre nahe-
31
+ * liegend, dafür eine zweite Liste zu pflegen. Genau das ist die Fehlerklasse,
32
+ * die uns in diesem Haus am häufigsten trifft: eine Wahrheit an zwei Orten.
33
+ *
34
+ * Deshalb: die Wortliste IST das Wörterbuch. Wer ein Wortpaar einträgt, macht
35
+ * es damit automatisch als Grundwort für zusammengesetzte Wörter verfügbar.
36
+ * Eine Liste, zwei Wirkungen.
37
+ */
38
+ /**
39
+ * Schreibt Umlaute aus. Aus `ä` wird `ae`, aus `ß` wird `ss`.
40
+ *
41
+ * Beide Schreibweisen werden auf DIESELBE Form gebracht — nicht die eine in die
42
+ * andere übersetzt. Nur so ist `Lösung` dasselbe Wort wie `Loesung`, egal in
43
+ * welcher Richtung jemand tippt.
44
+ */
45
+ export declare function ohneUmlaute(wort: string): string;
46
+ /**
47
+ * Deutsche Wortform auf einen gemeinsamen Stamm bringen.
48
+ *
49
+ * Absichtlich zurückhaltend: eine Endung, nicht mehrere hintereinander. Bei
50
+ * `Abhaengigkeiten` reicht `-en`, und `Abhaengigkeit` ist genau die Form, die
51
+ * in den Lektionen steht.
52
+ */
53
+ export declare function deutscherStamm(wort: string): string;
54
+ /**
55
+ * Die Rückumlautung: `eintraeg` → `eintrag`, `laeuft` → `lauft`.
56
+ *
57
+ * Das Deutsche verändert im Plural und in gebeugten Formen den Stammvokal
58
+ * (Eintrag → Einträge). Ohne diesen Schritt bleiben Einzahl und Mehrzahl zwei
59
+ * verschiedene Wörter.
60
+ *
61
+ * Nur die letzte Umlautstelle wird zurückgenommen. `Ueberpruefung` soll nicht
62
+ * zu `Uberprufung` werden — das Wort beginnt mit einem echten `ü`, das kein
63
+ * Umlaut einer Mehrzahl ist.
64
+ */
65
+ export declare function ohneRueckumlaut(stamm: string): string | null;
66
+ /**
67
+ * Zerlegt ein zusammengesetztes Wort in seine Bestandteile.
68
+ *
69
+ * `Arbeitsspeicher` → `speicher` (und `arbeit`)
70
+ * `Zugriffsprotokoll` → `protokoll` (und `zugriff`)
71
+ * `Browsertests` → `test` (und `browser`)
72
+ *
73
+ * Wie es arbeitet: Das Grundwort steht im Deutschen HINTEN. Also wird von
74
+ * hinten nach einem bekannten Wort gesucht. Der Rest davor wird ebenfalls
75
+ * zurückgegeben, wenn er lang genug ist — er trägt oft die eigentliche
76
+ * Bedeutung (`Arbeit` in `Arbeitsspeicher`).
77
+ *
78
+ * Das `s` in `Arbeits|speicher` ist eine Fuge und gehört zu keinem der beiden
79
+ * Teile. Es wird abgeschnitten.
80
+ *
81
+ * @param bekannt Grundwörter, gegen die geprüft wird. Das ist die Wortliste der
82
+ * Suche selbst — kein zweites Wörterbuch, das veralten kann.
83
+ */
84
+ export declare function zerlegeKompositum(wort: string, bekannt: Set<string>): string[];
85
+ /**
86
+ * Deutsche Fachwörter, die in unseren Lektionen und Fragen vorkommen.
87
+ *
88
+ * ── Woher diese Liste kommt ──────────────────────────────────────────────────
89
+ *
90
+ * Nicht aus dem Kopf. Jeder Eintrag stammt aus der Messung vom 19.08.2026: es
91
+ * sind die Wörter, die in echten Fragen standen und in der richtigen Antwort
92
+ * keinen Anschluss fanden (src/bench/wo-bricht-die-frage.ts nennt sie).
93
+ *
94
+ * Deshalb ist die Liste kurz und wird es bleiben. Sie wächst aus Messungen,
95
+ * nicht aus Vermutungen — wer etwas einträgt, soll die Frage nennen können, die
96
+ * ohne den Eintrag danebengeht.
97
+ */
98
+ export declare const DEUTSCHE_FACHWOERTER: ReadonlyArray<readonly [string, string[]]>;