@cachly-dev/mcp-server 0.10.124 → 0.10.126

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 (88) 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 +536 -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/versuch.d.ts +179 -0
  75. package/dist/src/versuch.js +250 -0
  76. package/dist/src/versuch.js.map +1 -0
  77. package/dist/src/vorspann.d.ts +73 -0
  78. package/dist/src/vorspann.js +146 -0
  79. package/dist/src/vorspann.js.map +1 -0
  80. package/dist/src/werkzeug-nutzung.d.ts +62 -0
  81. package/dist/src/werkzeug-nutzung.js +72 -0
  82. package/dist/src/werkzeug-nutzung.js.map +1 -0
  83. package/dist/src/wertbeitrag.d.ts +57 -0
  84. package/dist/src/wertbeitrag.js +73 -0
  85. package/dist/src/wertbeitrag.js.map +1 -0
  86. package/glama.json +1 -1
  87. package/package.json +12 -4
  88. package/server.json +2 -2
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Messtechnik fuer den Rueckfall-Versuch (dev.to "rules-before-the-run").
3
+ *
4
+ * ── Worum es geht ────────────────────────────────────────────────────────────
5
+ *
6
+ * Der Artikel sagt zu: wir messen, ob eine abgerufene Lektion, die WIRKLICH bis
7
+ * ins Modell kommt, den Rueckfall senkt (dieselbe Lektion muss kein zweites Mal
8
+ * geschrieben werden). Diese Datei baut nur das Messgeraet. Sie startet nichts.
9
+ *
10
+ * Ohne den Schalter `CACHLY_VERSUCH=an` (siehe `versuchStart`) aendert sich am
11
+ * Verhalten des Recall-Pfads NICHTS — kein Zurueckhalten, kein Schreiben nach
12
+ * Redis, keine Markierung im Text. Das ist keine Nebenbedingung, das ist der
13
+ * ganze Zweck: der Versuch soll erst beginnen, wenn Heinrich ihn startet.
14
+ *
15
+ * ── Der Kanarienvogel ────────────────────────────────────────────────────────
16
+ *
17
+ * Zu pruefen ist nicht, ob eine Lektion ABGERUFEN wurde. Zu pruefen ist, ob ihr
18
+ * Text VOLLSTAENDIG in der Antwort landet, die das Modell sieht. Anlass: eine
19
+ * Lektion trug die gesuchte Adresse an Zeichen 323 von `what_worked`, die
20
+ * Anzeige schnitt bei 100 Zeichen ab (siehe lesson-preview.ts). Die Lektion war
21
+ * eingeblendet, der Fehler passierte trotzdem — genau der Zustand, den ein
22
+ * "wurde abgerufen"-Zaehler nie sieht.
23
+ *
24
+ * Die Markierung sitzt deshalb am Ende von `what_worked` — dem Feld, an dem der
25
+ * Anlass hing, und dem Feld, das `lessonPreviewLines` als Erstes und am
26
+ * grosszuegigsten kuerzt. Sie besteht aus acht Zero-Width-Space-Zeichen
27
+ * (U+200B): unsichtbar in jedem Renderer (Terminal, Markdown, HTML), und sie
28
+ * uebersteht sowohl `flat()`s Leerzeichen-Kollaps als auch `String#trim()` —
29
+ * beide fassen nur "richtige" Leerzeichen an, U+200B gehoert zu keiner der
30
+ * beiden Kategorien. Acht Wiederholungen, damit ein einzelnes zufaelliges
31
+ * Zero-Width-Zeichen in echtem Nutzertext die Markierung nicht vortaeuscht.
32
+ *
33
+ * WICHTIG: die Markierung wird NICHT roh ans Ende des JSON-Strings gehaengt.
34
+ * Das wuerde `JSON.parse` in `recallVorschau` brechen und den Fallback-Pfad
35
+ * (rohes `trimTo` ueber das GANZE JSON-Objekt) erzwingen — einen Pfad, den
36
+ * echte, unmarkierte Lektionen nie durchlaufen. Das Ergebnis waere ein
37
+ * Fehlalarm, kein Messwert. Die Markierung wird darum in die geparste
38
+ * `what_worked`-Zeichenkette eingefuegt, das Objekt bleibt gueltiges JSON, und
39
+ * der Kanarienvogel durchlaeuft exakt den Pfad, den eine echte Lektion auch
40
+ * durchlaeuft.
41
+ *
42
+ * Geprueft wird am Ende die WIRKLICH zusammengebaute Antwort (`lines.join`),
43
+ * nicht eine Nebenrechnung — siehe `schliesseVersuchAb`. Die Markierung wird
44
+ * danach aus dem Text entfernt, bevor er den Nutzer erreicht.
45
+ */
46
+ import type { Redis } from 'ioredis';
47
+ export declare const VERSUCH_ENV = "CACHLY_VERSUCH";
48
+ export declare const VERSUCH_SALZ_ENV = "CACHLY_VERSUCH_SALZ";
49
+ /** Grund fuer `meldeEinmal`: Versuch eingeschaltet, aber kein Salz gesetzt. */
50
+ export declare const OHNE_SALZ = "versuch-ohne-salz";
51
+ /**
52
+ * Der einzige Einstiegspunkt fuer die Frage "soll der Versuch jetzt laufen?".
53
+ *
54
+ * Gibt das Salz zurueck, wenn ja — sonst `null`. Zwei Faelle enden bei `null`:
55
+ * der Schalter steht nicht auf "an", oder er steht auf "an", aber das Salz
56
+ * fehlt. Der zweite Fall meldet sich einmal je Prozess nach stderr (ueber
57
+ * `meldeEinmal`) und verhaelt sich danach wie der erste — still weiterlaufen
58
+ * waere die Fehlerklasse, gegen die `aussetzer.ts` gebaut wurde.
59
+ */
60
+ export declare function versuchStart(): string | null;
61
+ /**
62
+ * Eine neue, stabile Kennung je Recall-Aufruf.
63
+ *
64
+ * Im Recall-Pfad gibt es keine vorhandene Anfrage- oder Turn-Kennung, die man
65
+ * verlaengern koennte — `requester` ist ein Autorenname, `cachly:session:current`
66
+ * haelt Zustand je INSTANZ, nicht je Aufruf. Darum: Zeitstempel (sortierbar,
67
+ * in einer Zeile lesbar) plus vier zufaellige Byte, damit zwei Aufrufe in
68
+ * derselben Millisekunde nicht kollidieren.
69
+ */
70
+ export declare function neueKennung(): string;
71
+ export type Zuteilung = 'HOLD' | 'DELIVER';
72
+ /**
73
+ * Reine Funktion, ohne Netz testbar: Kennung + Salz -> HOLD oder DELIVER.
74
+ *
75
+ * Berechnung exakt wie oeffentlich angekuendigt: Kennung und Salz aneinander-
76
+ * haengen, SHA-256, die ersten VIER Hex-Ziffern als Zahl lesen, modulo 100.
77
+ * Unter 50 heisst HOLD, sonst DELIVER.
78
+ */
79
+ export declare function zuteilung(kennung: string, salz: string): Zuteilung;
80
+ /** Ohne einen Treffer ueber dieser Punktzahl gibt es nichts zu messen. */
81
+ export declare const ZULASSUNGS_SCHWELLE = 0;
82
+ /** Gibt es ueberhaupt einen Treffer, der ueber der Schwelle liegt? */
83
+ export declare function istZulassungsfaehig(hoechstePunktzahl: number, schwelle?: number): boolean;
84
+ /** Der Grund, warum eine Anfrage NICHT zulassungsfaehig ist — oder `undefined`. */
85
+ export declare function zulassungsGrund(hoechstePunktzahl: number, schwelle?: number): string | undefined;
86
+ /**
87
+ * Acht Zero-Width-Spaces. Siehe Dateikopf fuer die Begruendung der Wahl.
88
+ *
89
+ * Ueber String.fromCharCode, nicht als roher Unicode-Buchstabe im
90
+ * Quelltext — ein unsichtbares Zeichen zwischen Anfuehrungszeichen ist beim
91
+ * Lesen und in `git diff` nicht von "kein Zeichen" zu unterscheiden.
92
+ */
93
+ export declare const KANARIEN_MARKIERUNG: string;
94
+ /**
95
+ * Setzt die Kanarien-Markierung ans Ende des `what_worked`-Feldes.
96
+ *
97
+ * `inhalt` ist der rohe Redis-Wert, wie ihn `recallVorschau` bekommt — meist
98
+ * das JSON einer Lektion, manchmal (Kontext-Eintraege, Index) einfacher Text.
99
+ * Ist es eine Lektion mit `what_worked`, wandert die Markierung dorthin und
100
+ * das Objekt bleibt gueltiges JSON (Begruendung siehe Dateikopf). Sonst wird
101
+ * roh ans Ende angehaengt — das ist fuer Nicht-Lektionen korrekt, weil
102
+ * `recallVorschau` sie ohnehin nur als Rohtext kuerzt.
103
+ */
104
+ export declare function markiereDatensatz(inhalt: string, markierung?: string): string;
105
+ /**
106
+ * Ueberlebt die Markierung in der fertig zusammengebauten Antwort?
107
+ *
108
+ * Ueberlebt sie: der ganze Datensatz kam durch. Fehlt sie: er wurde gekuerzt.
109
+ */
110
+ export declare function kanarienUeberlebt(fertigeAntwort: string, markierung?: string): boolean;
111
+ /** Entfernt die Markierung, bevor der Text den Nutzer erreicht. */
112
+ export declare function entferneMarkierung(text: string, markierung?: string): string;
113
+ /** Redis-Schluessel einer Lektion -> ihr Themenname (ohne Praefix). */
114
+ export declare function themaVon(key: string): string;
115
+ /** Hash der Frage — die Frage selbst wird nie gespeichert (Zusage des Artikels). */
116
+ export declare function hashFrage(frage: string): string;
117
+ /** Redis-Schluessel fuer den Protokoll-Eintrag einer Kennung. */
118
+ export declare function turnSchluessel(kennung: string): string;
119
+ export interface VersuchTurnProtokoll {
120
+ ts: string;
121
+ frageHash: string;
122
+ hoechstePunktzahl: number;
123
+ /** Themenschluessel der zurueckgegebenen (tatsaechlich gezeigten) Lektionen. */
124
+ themen: string[];
125
+ zulassungsfaehig: boolean;
126
+ zulassungsGrund?: string;
127
+ zuteilung: Zuteilung;
128
+ /** Nur bei HOLD gesetzt: das Thema, das weggelassen wurde. */
129
+ weggelassenesThema?: string;
130
+ kanarienBestanden: boolean;
131
+ }
132
+ /**
133
+ * Schreibt den Turn-Eintrag. Schlaegt das fehl, wird NICHTS geworfen — der
134
+ * Nutzer wartet auf eine Antwort, nicht auf ein Protokoll.
135
+ */
136
+ export declare function schreibeTurnProtokoll(redis: Redis, kennung: string, protokoll: VersuchTurnProtokoll): Promise<void>;
137
+ /** Ein Kandidat aus der schon sortierten Trefferliste von smart_recall. */
138
+ export interface VersuchTreffer {
139
+ key: string;
140
+ content: string;
141
+ punktzahl: number;
142
+ }
143
+ export interface ZuteilungsErgebnis {
144
+ /** Die neue Trefferliste — bei HOLD fehlt der bisher beste Treffer. */
145
+ treffer: VersuchTreffer[];
146
+ zuteilung: Zuteilung;
147
+ zulassungsfaehig: boolean;
148
+ zulassungsGrund?: string;
149
+ /** War DELIVER UND es gab einen Treffer, der die Markierung tragen konnte. */
150
+ markierungGesetzt: boolean;
151
+ weggelassenesThema?: string;
152
+ }
153
+ /**
154
+ * Wendet Zulassung + Zuteilung auf eine Trefferliste an. Reine Funktion,
155
+ * mutiert `treffer` nicht.
156
+ *
157
+ * Ohne zulassungsfaehigen Treffer passiert nichts — Zuteilung wird trotzdem
158
+ * berechnet und protokolliert, aber es gibt nichts zurueckzuhalten oder zu
159
+ * markieren. Bei HOLD verschwindet der beste Treffer aus der Liste, die
160
+ * uebrigen bleiben unveraendert. Bei DELIVER traegt der beste Treffer danach
161
+ * die Kanarien-Markierung.
162
+ */
163
+ export declare function wendeZuteilungAn(treffer: readonly VersuchTreffer[], kennung: string, salz: string, hoechstePunktzahl: number, schwelle?: number): ZuteilungsErgebnis;
164
+ export interface VersuchProtokollTeil {
165
+ frage: string;
166
+ hoechstePunktzahl: number;
167
+ themen: string[];
168
+ zulassungsfaehig: boolean;
169
+ zulassungsGrund?: string;
170
+ zuteilung: Zuteilung;
171
+ weggelassenesThema?: string;
172
+ markierungGesetzt: boolean;
173
+ }
174
+ /**
175
+ * Letzter Schritt: prueft den Kanarienvogel gegen die WIRKLICH zusammen-
176
+ * gebaute Antwort, entfernt die Markierung, schreibt das Protokoll (fire-and-
177
+ * forget) und gibt den bereinigten Text zurueck — den, den der Nutzer sieht.
178
+ */
179
+ export declare function schliesseVersuchAb(redis: Redis, kennung: string, teil: VersuchProtokollTeil, fertigeAntwort: string): string;
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Messtechnik fuer den Rueckfall-Versuch (dev.to "rules-before-the-run").
3
+ *
4
+ * ── Worum es geht ────────────────────────────────────────────────────────────
5
+ *
6
+ * Der Artikel sagt zu: wir messen, ob eine abgerufene Lektion, die WIRKLICH bis
7
+ * ins Modell kommt, den Rueckfall senkt (dieselbe Lektion muss kein zweites Mal
8
+ * geschrieben werden). Diese Datei baut nur das Messgeraet. Sie startet nichts.
9
+ *
10
+ * Ohne den Schalter `CACHLY_VERSUCH=an` (siehe `versuchStart`) aendert sich am
11
+ * Verhalten des Recall-Pfads NICHTS — kein Zurueckhalten, kein Schreiben nach
12
+ * Redis, keine Markierung im Text. Das ist keine Nebenbedingung, das ist der
13
+ * ganze Zweck: der Versuch soll erst beginnen, wenn Heinrich ihn startet.
14
+ *
15
+ * ── Der Kanarienvogel ────────────────────────────────────────────────────────
16
+ *
17
+ * Zu pruefen ist nicht, ob eine Lektion ABGERUFEN wurde. Zu pruefen ist, ob ihr
18
+ * Text VOLLSTAENDIG in der Antwort landet, die das Modell sieht. Anlass: eine
19
+ * Lektion trug die gesuchte Adresse an Zeichen 323 von `what_worked`, die
20
+ * Anzeige schnitt bei 100 Zeichen ab (siehe lesson-preview.ts). Die Lektion war
21
+ * eingeblendet, der Fehler passierte trotzdem — genau der Zustand, den ein
22
+ * "wurde abgerufen"-Zaehler nie sieht.
23
+ *
24
+ * Die Markierung sitzt deshalb am Ende von `what_worked` — dem Feld, an dem der
25
+ * Anlass hing, und dem Feld, das `lessonPreviewLines` als Erstes und am
26
+ * grosszuegigsten kuerzt. Sie besteht aus acht Zero-Width-Space-Zeichen
27
+ * (U+200B): unsichtbar in jedem Renderer (Terminal, Markdown, HTML), und sie
28
+ * uebersteht sowohl `flat()`s Leerzeichen-Kollaps als auch `String#trim()` —
29
+ * beide fassen nur "richtige" Leerzeichen an, U+200B gehoert zu keiner der
30
+ * beiden Kategorien. Acht Wiederholungen, damit ein einzelnes zufaelliges
31
+ * Zero-Width-Zeichen in echtem Nutzertext die Markierung nicht vortaeuscht.
32
+ *
33
+ * WICHTIG: die Markierung wird NICHT roh ans Ende des JSON-Strings gehaengt.
34
+ * Das wuerde `JSON.parse` in `recallVorschau` brechen und den Fallback-Pfad
35
+ * (rohes `trimTo` ueber das GANZE JSON-Objekt) erzwingen — einen Pfad, den
36
+ * echte, unmarkierte Lektionen nie durchlaufen. Das Ergebnis waere ein
37
+ * Fehlalarm, kein Messwert. Die Markierung wird darum in die geparste
38
+ * `what_worked`-Zeichenkette eingefuegt, das Objekt bleibt gueltiges JSON, und
39
+ * der Kanarienvogel durchlaeuft exakt den Pfad, den eine echte Lektion auch
40
+ * durchlaeuft.
41
+ *
42
+ * Geprueft wird am Ende die WIRKLICH zusammengebaute Antwort (`lines.join`),
43
+ * nicht eine Nebenrechnung — siehe `schliesseVersuchAb`. Die Markierung wird
44
+ * danach aus dem Text entfernt, bevor er den Nutzer erreicht.
45
+ */
46
+ import { randomBytes, createHash } from 'node:crypto';
47
+ import { meldeEinmal } from './aussetzer.js';
48
+ // ── Der Schalter ─────────────────────────────────────────────────────────────
49
+ export const VERSUCH_ENV = 'CACHLY_VERSUCH';
50
+ export const VERSUCH_SALZ_ENV = 'CACHLY_VERSUCH_SALZ';
51
+ /** Grund fuer `meldeEinmal`: Versuch eingeschaltet, aber kein Salz gesetzt. */
52
+ export const OHNE_SALZ = 'versuch-ohne-salz';
53
+ /**
54
+ * Der einzige Einstiegspunkt fuer die Frage "soll der Versuch jetzt laufen?".
55
+ *
56
+ * Gibt das Salz zurueck, wenn ja — sonst `null`. Zwei Faelle enden bei `null`:
57
+ * der Schalter steht nicht auf "an", oder er steht auf "an", aber das Salz
58
+ * fehlt. Der zweite Fall meldet sich einmal je Prozess nach stderr (ueber
59
+ * `meldeEinmal`) und verhaelt sich danach wie der erste — still weiterlaufen
60
+ * waere die Fehlerklasse, gegen die `aussetzer.ts` gebaut wurde.
61
+ */
62
+ export function versuchStart() {
63
+ if (process.env[VERSUCH_ENV] !== 'an')
64
+ return null;
65
+ const salz = process.env[VERSUCH_SALZ_ENV];
66
+ if (!salz) {
67
+ meldeEinmal(OHNE_SALZ, `${VERSUCH_ENV}=an, aber ${VERSUCH_SALZ_ENV} fehlt — der Versuch verhaelt sich wie "aus"`);
68
+ return null;
69
+ }
70
+ return salz;
71
+ }
72
+ // ── Turn-Kennung ─────────────────────────────────────────────────────────────
73
+ /**
74
+ * Eine neue, stabile Kennung je Recall-Aufruf.
75
+ *
76
+ * Im Recall-Pfad gibt es keine vorhandene Anfrage- oder Turn-Kennung, die man
77
+ * verlaengern koennte — `requester` ist ein Autorenname, `cachly:session:current`
78
+ * haelt Zustand je INSTANZ, nicht je Aufruf. Darum: Zeitstempel (sortierbar,
79
+ * in einer Zeile lesbar) plus vier zufaellige Byte, damit zwei Aufrufe in
80
+ * derselben Millisekunde nicht kollidieren.
81
+ */
82
+ export function neueKennung() {
83
+ const zeit = new Date().toISOString().replace(/[-:.]/g, '').slice(0, 15); // 20260820T171523
84
+ const zufall = randomBytes(4).toString('hex');
85
+ return `${zeit}-${zufall}`;
86
+ }
87
+ /**
88
+ * Reine Funktion, ohne Netz testbar: Kennung + Salz -> HOLD oder DELIVER.
89
+ *
90
+ * Berechnung exakt wie oeffentlich angekuendigt: Kennung und Salz aneinander-
91
+ * haengen, SHA-256, die ersten VIER Hex-Ziffern als Zahl lesen, modulo 100.
92
+ * Unter 50 heisst HOLD, sonst DELIVER.
93
+ */
94
+ export function zuteilung(kennung, salz) {
95
+ const hash = createHash('sha256').update(`${kennung}${salz}`).digest('hex');
96
+ const ersteVier = hash.slice(0, 4);
97
+ const zahl = parseInt(ersteVier, 16) % 100;
98
+ return zahl < 50 ? 'HOLD' : 'DELIVER';
99
+ }
100
+ // ── Zulassung ────────────────────────────────────────────────────────────────
101
+ /** Ohne einen Treffer ueber dieser Punktzahl gibt es nichts zu messen. */
102
+ export const ZULASSUNGS_SCHWELLE = 0;
103
+ /** Gibt es ueberhaupt einen Treffer, der ueber der Schwelle liegt? */
104
+ export function istZulassungsfaehig(hoechstePunktzahl, schwelle = ZULASSUNGS_SCHWELLE) {
105
+ return hoechstePunktzahl > schwelle;
106
+ }
107
+ /** Der Grund, warum eine Anfrage NICHT zulassungsfaehig ist — oder `undefined`. */
108
+ export function zulassungsGrund(hoechstePunktzahl, schwelle = ZULASSUNGS_SCHWELLE) {
109
+ if (istZulassungsfaehig(hoechstePunktzahl, schwelle))
110
+ return undefined;
111
+ return `hoechste Punktzahl ${hoechstePunktzahl.toFixed(3)} liegt nicht ueber der Schwelle ${schwelle}`;
112
+ }
113
+ // ── Kanarienvogel ────────────────────────────────────────────────────────────
114
+ /**
115
+ * Acht Zero-Width-Spaces. Siehe Dateikopf fuer die Begruendung der Wahl.
116
+ *
117
+ * Ueber String.fromCharCode, nicht als roher Unicode-Buchstabe im
118
+ * Quelltext — ein unsichtbares Zeichen zwischen Anfuehrungszeichen ist beim
119
+ * Lesen und in `git diff` nicht von "kein Zeichen" zu unterscheiden.
120
+ */
121
+ export const KANARIEN_MARKIERUNG = String.fromCharCode(0x200b).repeat(8);
122
+ /**
123
+ * Setzt die Kanarien-Markierung ans Ende des `what_worked`-Feldes.
124
+ *
125
+ * `inhalt` ist der rohe Redis-Wert, wie ihn `recallVorschau` bekommt — meist
126
+ * das JSON einer Lektion, manchmal (Kontext-Eintraege, Index) einfacher Text.
127
+ * Ist es eine Lektion mit `what_worked`, wandert die Markierung dorthin und
128
+ * das Objekt bleibt gueltiges JSON (Begruendung siehe Dateikopf). Sonst wird
129
+ * roh ans Ende angehaengt — das ist fuer Nicht-Lektionen korrekt, weil
130
+ * `recallVorschau` sie ohnehin nur als Rohtext kuerzt.
131
+ */
132
+ export function markiereDatensatz(inhalt, markierung = KANARIEN_MARKIERUNG) {
133
+ try {
134
+ const objekt = JSON.parse(inhalt);
135
+ if (typeof objekt.what_worked === 'string') {
136
+ objekt.what_worked = `${objekt.what_worked}${markierung}`;
137
+ return JSON.stringify(objekt);
138
+ }
139
+ }
140
+ catch {
141
+ // kein JSON — Markierung ans Ende des Rohtexts, siehe unten
142
+ }
143
+ return `${inhalt}${markierung}`;
144
+ }
145
+ /**
146
+ * Ueberlebt die Markierung in der fertig zusammengebauten Antwort?
147
+ *
148
+ * Ueberlebt sie: der ganze Datensatz kam durch. Fehlt sie: er wurde gekuerzt.
149
+ */
150
+ export function kanarienUeberlebt(fertigeAntwort, markierung = KANARIEN_MARKIERUNG) {
151
+ return fertigeAntwort.includes(markierung);
152
+ }
153
+ /** Entfernt die Markierung, bevor der Text den Nutzer erreicht. */
154
+ export function entferneMarkierung(text, markierung = KANARIEN_MARKIERUNG) {
155
+ return text.split(markierung).join('');
156
+ }
157
+ // ── Themenschluessel ─────────────────────────────────────────────────────────
158
+ const LEKTION_PRAEFIX = 'cachly:lesson:best:';
159
+ /** Redis-Schluessel einer Lektion -> ihr Themenname (ohne Praefix). */
160
+ export function themaVon(key) {
161
+ return key.startsWith(LEKTION_PRAEFIX) ? key.slice(LEKTION_PRAEFIX.length) : key;
162
+ }
163
+ // ── Protokoll je Turn ────────────────────────────────────────────────────────
164
+ /** Hash der Frage — die Frage selbst wird nie gespeichert (Zusage des Artikels). */
165
+ export function hashFrage(frage) {
166
+ return createHash('sha256').update(frage).digest('hex');
167
+ }
168
+ const TURN_TTL_SEKUNDEN = 90 * 24 * 60 * 60; // 90 Tage — der Versuch dauert 14
169
+ /** Redis-Schluessel fuer den Protokoll-Eintrag einer Kennung. */
170
+ export function turnSchluessel(kennung) {
171
+ return `cachly:versuch:turn:${kennung}`;
172
+ }
173
+ /**
174
+ * Schreibt den Turn-Eintrag. Schlaegt das fehl, wird NICHTS geworfen — der
175
+ * Nutzer wartet auf eine Antwort, nicht auf ein Protokoll.
176
+ */
177
+ export async function schreibeTurnProtokoll(redis, kennung, protokoll) {
178
+ try {
179
+ await redis.set(turnSchluessel(kennung), JSON.stringify(protokoll), 'EX', TURN_TTL_SEKUNDEN);
180
+ }
181
+ catch {
182
+ // Ein fehlendes Protokoll ist besser als eine gescheiterte Suche.
183
+ }
184
+ }
185
+ /**
186
+ * Wendet Zulassung + Zuteilung auf eine Trefferliste an. Reine Funktion,
187
+ * mutiert `treffer` nicht.
188
+ *
189
+ * Ohne zulassungsfaehigen Treffer passiert nichts — Zuteilung wird trotzdem
190
+ * berechnet und protokolliert, aber es gibt nichts zurueckzuhalten oder zu
191
+ * markieren. Bei HOLD verschwindet der beste Treffer aus der Liste, die
192
+ * uebrigen bleiben unveraendert. Bei DELIVER traegt der beste Treffer danach
193
+ * die Kanarien-Markierung.
194
+ */
195
+ export function wendeZuteilungAn(treffer, kennung, salz, hoechstePunktzahl, schwelle = ZULASSUNGS_SCHWELLE) {
196
+ const zulassungsfaehig = istZulassungsfaehig(hoechstePunktzahl, schwelle);
197
+ const grund = zulassungsGrund(hoechstePunktzahl, schwelle);
198
+ const entscheidung = zuteilung(kennung, salz);
199
+ if (!zulassungsfaehig || treffer.length === 0) {
200
+ return {
201
+ treffer: [...treffer],
202
+ zuteilung: entscheidung,
203
+ zulassungsfaehig,
204
+ zulassungsGrund: grund,
205
+ markierungGesetzt: false,
206
+ };
207
+ }
208
+ const [bester, ...rest] = treffer;
209
+ if (entscheidung === 'HOLD') {
210
+ return {
211
+ treffer: rest,
212
+ zuteilung: entscheidung,
213
+ zulassungsfaehig,
214
+ zulassungsGrund: grund,
215
+ markierungGesetzt: false,
216
+ weggelassenesThema: themaVon(bester.key),
217
+ };
218
+ }
219
+ const markiert = { ...bester, content: markiereDatensatz(bester.content) };
220
+ return {
221
+ treffer: [markiert, ...rest],
222
+ zuteilung: entscheidung,
223
+ zulassungsfaehig,
224
+ zulassungsGrund: grund,
225
+ markierungGesetzt: true,
226
+ };
227
+ }
228
+ /**
229
+ * Letzter Schritt: prueft den Kanarienvogel gegen die WIRKLICH zusammen-
230
+ * gebaute Antwort, entfernt die Markierung, schreibt das Protokoll (fire-and-
231
+ * forget) und gibt den bereinigten Text zurueck — den, den der Nutzer sieht.
232
+ */
233
+ export function schliesseVersuchAb(redis, kennung, teil, fertigeAntwort) {
234
+ const kanarienBestanden = teil.markierungGesetzt ? kanarienUeberlebt(fertigeAntwort) : false;
235
+ const bereinigt = entferneMarkierung(fertigeAntwort);
236
+ const protokoll = {
237
+ ts: new Date().toISOString(),
238
+ frageHash: hashFrage(teil.frage),
239
+ hoechstePunktzahl: teil.hoechstePunktzahl,
240
+ themen: teil.themen,
241
+ zulassungsfaehig: teil.zulassungsfaehig,
242
+ zulassungsGrund: teil.zulassungsGrund,
243
+ zuteilung: teil.zuteilung,
244
+ weggelassenesThema: teil.weggelassenesThema,
245
+ kanarienBestanden,
246
+ };
247
+ void schreibeTurnProtokoll(redis, kennung, protokoll);
248
+ return bereinigt;
249
+ }
250
+ //# sourceMappingURL=versuch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versuch.js","sourceRoot":"","sources":["../../src/versuch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Der Vorspann einer Lektion — die ersten 100 Zeichen, und nur die.
3
+ *
4
+ * GEMESSEN am 16.08.2026: Das Session-Briefing zeigt je Lektion rund 100
5
+ * Zeichen. In `node4:einrichtung-contabo-und-fallen` stand die gesuchte
6
+ * Adresse `10.8.0.7` an Zeichen 323, und die passende Warnung im Feld
7
+ * `what_failed`, das im Briefing gar nicht erscheint. Die Lektion war
8
+ * eingeblendet. Der Fehler passierte trotzdem.
9
+ *
10
+ * Die Abhilfe war eine SCHREIBREGEL, kein Codefix: die entscheidende Tatsache
11
+ * gehoert in die ersten 100 Zeichen — die Zahl, die Adresse, der Befehl. Nicht
12
+ * der Anlass, nicht die Ausstattung, nicht die Vorgeschichte.
13
+ *
14
+ * Eine Regel, die kein Skript prueft, ist aber kein Gesetz, sondern ein
15
+ * Wunsch. Dieses Modul prueft sie — im Moment des Schreibens, wo der Autor
16
+ * (Mensch oder Agent) die Lektion noch in der Hand hat.
17
+ *
18
+ * Bewusst ein HINWEIS und keine Ablehnung: es gibt Lektionen ohne harte Zahl
19
+ * (eine Entscheidung, eine Richtung), und eine abgelehnte Lektion ist immer
20
+ * schlechter als eine unscharf formulierte.
21
+ */
22
+ /** So viel sieht die naechste Sitzung im Briefing. Gemessen, nicht geschaetzt. */
23
+ export declare const VORSPANN_ZEICHEN = 100;
24
+ export interface VorspannBefund {
25
+ /** Die ersten VORSPANN_ZEICHEN Zeichen — das, was die naechste Sitzung sieht. */
26
+ vorspann: string;
27
+ /** Traegt der Vorspann mindestens eine harte Tatsache? */
28
+ traegtTatsache: boolean;
29
+ /** Welche Muster gegriffen haben (leer, wenn keines). */
30
+ gefunden: string[];
31
+ /**
32
+ * Steht die erste harte Tatsache erst HINTER dem Vorspann? Dann ist die
33
+ * Lektion nicht inhaltsleer — sie ist nur falsch herum geschrieben, und
34
+ * genau dieser Fall ist der teure.
35
+ */
36
+ tatsacheStehtSpaeter: boolean;
37
+ /** Position der ersten harten Tatsache im ganzen Text, oder -1. */
38
+ ersteTatsacheBei: number;
39
+ /**
40
+ * Endet der Vorspann MITTEN im Satz?
41
+ *
42
+ * GEMESSEN am 19.08.2026 an einer selbst geschriebenen Lektion. Ihre ersten
43
+ * 100 Zeichen lauteten:
44
+ *
45
+ * "109 Testdateien lesen Quelltext, 102 OHNE Kommentar-Filter, 13 davon
46
+ * GRUEN weil"
47
+ *
48
+ * Drei Zahlen, alle drei da — und genau bei "weil" ist Schluss. Das Briefing
49
+ * zeigt also die Zahlen, aber nicht, WOFUER sie stehen. Wer das liest, weiss
50
+ * nicht, ob 13 gut oder schlecht ist.
51
+ *
52
+ * Die Tatsachen-Pruefung allein war zufrieden, und zu Recht — die Zahlen
53
+ * stehen ja vorn. Was fehlte, war ihre Wirkung, und die passt fast immer in
54
+ * denselben Satz: "13 Waechter waren gruen, ohne etwas zu pruefen" sind 47
55
+ * Zeichen und vollstaendig.
56
+ */
57
+ abgeschnitten: boolean;
58
+ }
59
+ /**
60
+ * Prueft den Vorspann von `what_worked`.
61
+ *
62
+ * Leerer oder fehlender Text ergibt einen Befund ohne Tatsache und ohne
63
+ * "steht spaeter" — es gibt dann nichts umzustellen, sondern nichts zu lesen.
64
+ */
65
+ export declare function pruefeVorspann(whatWorked: string | null | undefined): VorspannBefund;
66
+ /**
67
+ * Ein Satz fuer den Autor — oder null, wenn alles in Ordnung ist.
68
+ *
69
+ * Der Hinweis nennt die POSITION, wenn die Tatsache nur zu weit hinten steht.
70
+ * Ohne Zahl waere er eine Ermahnung; mit Zahl ist er eine Anweisung, die man
71
+ * in zehn Sekunden befolgen kann.
72
+ */
73
+ export declare function vorspannHinweis(whatWorked: string | null | undefined): string | null;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Der Vorspann einer Lektion — die ersten 100 Zeichen, und nur die.
3
+ *
4
+ * GEMESSEN am 16.08.2026: Das Session-Briefing zeigt je Lektion rund 100
5
+ * Zeichen. In `node4:einrichtung-contabo-und-fallen` stand die gesuchte
6
+ * Adresse `10.8.0.7` an Zeichen 323, und die passende Warnung im Feld
7
+ * `what_failed`, das im Briefing gar nicht erscheint. Die Lektion war
8
+ * eingeblendet. Der Fehler passierte trotzdem.
9
+ *
10
+ * Die Abhilfe war eine SCHREIBREGEL, kein Codefix: die entscheidende Tatsache
11
+ * gehoert in die ersten 100 Zeichen — die Zahl, die Adresse, der Befehl. Nicht
12
+ * der Anlass, nicht die Ausstattung, nicht die Vorgeschichte.
13
+ *
14
+ * Eine Regel, die kein Skript prueft, ist aber kein Gesetz, sondern ein
15
+ * Wunsch. Dieses Modul prueft sie — im Moment des Schreibens, wo der Autor
16
+ * (Mensch oder Agent) die Lektion noch in der Hand hat.
17
+ *
18
+ * Bewusst ein HINWEIS und keine Ablehnung: es gibt Lektionen ohne harte Zahl
19
+ * (eine Entscheidung, eine Richtung), und eine abgelehnte Lektion ist immer
20
+ * schlechter als eine unscharf formulierte.
21
+ */
22
+ /** So viel sieht die naechste Sitzung im Briefing. Gemessen, nicht geschaetzt. */
23
+ export const VORSPANN_ZEICHEN = 100;
24
+ /**
25
+ * Was als "harte Tatsache" zaehlt.
26
+ *
27
+ * Jedes Muster steht fuer etwas, das man beim naechsten Mal ABSCHREIBEN will
28
+ * und nicht neu herausfinden: eine Adresse, ein Port, eine Zahl mit Einheit,
29
+ * ein Pfad, ein Befehl, eine Version, ein Schluessel-Wert-Paar.
30
+ */
31
+ const HARTE_MUSTER = [
32
+ { name: 'IP-Adresse', re: /\b\d{1,3}(?:\.\d{1,3}){3}\b/ },
33
+ { name: 'Port', re: /:\d{2,5}\b/ },
34
+ { name: 'Zahl', re: /\b\d+(?:[.,]\d+)?\s*(?:%|s|ms|m|h|d|MB|GB|TB|KB|k|x|×|EUR|€|\$)?\b/ },
35
+ { name: 'Pfad', re: /(?:^|\s)(?:\/[\w.-]+){2,}|[A-Za-z]:\\[\w\\.-]+/ },
36
+ { name: 'Befehl oder Datei', re: /`[^`]+`|\b[\w-]+\.(?:ts|tsx|go|js|mjs|py|sh|yml|yaml|json|sql|env)\b/ },
37
+ // Ein Name in Grossbuchstaben mit mindestens zwei Unterstrichen ist in der
38
+ // Praxis immer eine Umgebungsvariable oder ein Schalter — also etwas zum
39
+ // Abschreiben. Zwei Unterstriche, damit nicht jedes betonte Wort greift.
40
+ // Der Doppelpunkt ist bewusst NICHT dabei. Er war zuerst erlaubt, und die
41
+ // Gegenprobe fing sofort den Fehlalarm: "WICHTIG:" ist ein betontes Wort,
42
+ // keine Angabe. Bleibt das Gleichheitszeichen (PORT=3095) und der Name mit
43
+ // mindestens zwei Unterstrichen, der in der Praxis immer eine
44
+ // Umgebungsvariable ist.
45
+ { name: 'Schluessel', re: /\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+){2,}\b|\b[A-Z][A-Z0-9_]{3,}\s*=/ },
46
+ { name: 'Version', re: /\bv?\d+\.\d+(?:\.\d+)?\b/ },
47
+ ];
48
+ /**
49
+ * Steht im Vorspann ein abgeschlossener Gedanke?
50
+ *
51
+ * Mechanisch, nicht inhaltlich: gesucht wird ein Satzende (Punkt, Semikolon,
52
+ * Ausrufe- oder Fragezeichen) innerhalb der ersten 100 Zeichen. Findet sich
53
+ * keines, endet der Vorspann mitten im Satz.
54
+ *
55
+ * Absichtlich KEINE Bedeutungspruefung. Ein Waechter, der raet, ob ein Satz
56
+ * "vollstaendig genug" ist, schlaegt bei jeder zweiten Lektion falschen Alarm
57
+ * — und ein Waechter, der zu oft Nein sagt, wird abgeschaltet. Ein fehlendes
58
+ * Satzzeichen ist dagegen eine Tatsache.
59
+ *
60
+ * Ein Text, der insgesamt kuerzer ist als der Vorspann, gilt NICHT als
61
+ * abgeschnitten: dort steht alles, was es gibt.
62
+ */
63
+ function endetMittenImSatz(text) {
64
+ if (text.length <= VORSPANN_ZEICHEN)
65
+ return false;
66
+ const vorspann = text.slice(0, VORSPANN_ZEICHEN);
67
+ // Zwei Feinheiten, beide am echten Fall gelernt:
68
+ //
69
+ // 1. Ein Punkt in "10.8.0.7" oder "v1.2" beendet keinen Satz — deshalb muss
70
+ // auf das Satzzeichen ein Leerzeichen oder das Textende folgen.
71
+ // 2. Der DOPPELPUNKT zaehlt NICHT. Er leitet ein, er schliesst nicht ab.
72
+ // Genau daran scheiterte die erste Fassung: "GEMESSEN 19.08.2026: 109
73
+ // Testdateien ... 13 davon GRUEN weil" galt als abgeschlossen, weil der
74
+ // Doppelpunkt nach dem Datum mitzaehlte. Der Satz endet dort aber nicht,
75
+ // er faengt an.
76
+ return !/[.;!?](?:\s|$)/.test(vorspann);
77
+ }
78
+ /** Frueheste Fundstelle irgendeines harten Musters, oder -1. */
79
+ function ersteFundstelle(text) {
80
+ let best = -1;
81
+ for (const m of HARTE_MUSTER) {
82
+ const treffer = m.re.exec(text);
83
+ if (!treffer)
84
+ continue;
85
+ const i = treffer.index;
86
+ if (best === -1 || i < best)
87
+ best = i;
88
+ }
89
+ return best;
90
+ }
91
+ /**
92
+ * Prueft den Vorspann von `what_worked`.
93
+ *
94
+ * Leerer oder fehlender Text ergibt einen Befund ohne Tatsache und ohne
95
+ * "steht spaeter" — es gibt dann nichts umzustellen, sondern nichts zu lesen.
96
+ */
97
+ export function pruefeVorspann(whatWorked) {
98
+ const text = typeof whatWorked === 'string' ? whatWorked : '';
99
+ const vorspann = text.slice(0, VORSPANN_ZEICHEN);
100
+ const gefunden = HARTE_MUSTER.filter(m => m.re.test(vorspann)).map(m => m.name);
101
+ const ersteTatsacheBei = ersteFundstelle(text);
102
+ return {
103
+ vorspann,
104
+ traegtTatsache: gefunden.length > 0,
105
+ gefunden,
106
+ tatsacheStehtSpaeter: gefunden.length === 0 && ersteTatsacheBei >= VORSPANN_ZEICHEN,
107
+ ersteTatsacheBei,
108
+ abgeschnitten: endetMittenImSatz(text),
109
+ };
110
+ }
111
+ /**
112
+ * Ein Satz fuer den Autor — oder null, wenn alles in Ordnung ist.
113
+ *
114
+ * Der Hinweis nennt die POSITION, wenn die Tatsache nur zu weit hinten steht.
115
+ * Ohne Zahl waere er eine Ermahnung; mit Zahl ist er eine Anweisung, die man
116
+ * in zehn Sekunden befolgen kann.
117
+ */
118
+ export function vorspannHinweis(whatWorked) {
119
+ const b = pruefeVorspann(whatWorked);
120
+ if (!b.vorspann.trim())
121
+ return null;
122
+ /*
123
+ * Die Zahl ist da, aber der Satz ist es nicht.
124
+ *
125
+ * Dieser Fall kam am 19.08.2026 dazu und ist der feinere: die alte Pruefung
126
+ * war zufrieden, weil drei Zahlen in den ersten 100 Zeichen standen — und
127
+ * genau bei "weil" war Schluss. Zahlen ohne ihre Wirkung sind im Briefing
128
+ * kein Wissen, sondern ein Raetsel.
129
+ */
130
+ if (b.traegtTatsache && b.abgeschnitten) {
131
+ return (`📏 **Der Vorspann endet mitten im Satz** — die Zahl steht vorn, ihre Wirkung nicht. ` +
132
+ `Das Briefing der nächsten Sitzung bricht bei ${VORSPANN_ZEICHEN} Zeichen ab und zeigt dann ` +
133
+ `eine Zahl ohne Bedeutung. Ein abgeschlossener Satz in den ersten ${VORSPANN_ZEICHEN} Zeichen ` +
134
+ `genügt: nicht „13 davon grün weil", sondern „13 Wächter waren grün, ohne etwas zu prüfen."`);
135
+ }
136
+ if (b.traegtTatsache)
137
+ return null;
138
+ if (b.tatsacheStehtSpaeter) {
139
+ return (`📏 **Die entscheidende Tatsache steht bei Zeichen ${b.ersteTatsacheBei}** — das Briefing der nächsten ` +
140
+ `Sitzung endet bei ${VORSPANN_ZEICHEN}. Stell die Zahl, Adresse oder den Befehl an den Anfang von ` +
141
+ `\`what_worked\`; die Vorgeschichte gehört ans Ende oder in \`context\`.`);
142
+ }
143
+ return (`📏 **In den ersten ${VORSPANN_ZEICHEN} Zeichen steht keine Zahl, Adresse oder Befehl** — nur so viel sieht ` +
144
+ `die nächste Sitzung im Briefing. Trägt diese Lektion eine harte Tatsache, gehört sie nach vorn.`);
145
+ }
146
+ //# sourceMappingURL=vorspann.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vorspann.js","sourceRoot":"","sources":["../../src/vorspann.ts"],"names":[],"mappings":"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"}