@cachly-dev/mcp-server 0.10.125 → 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.
- package/dist/src/bauabdruck-daten.generiert.json +3 -3
- package/dist/src/handlers/brain.js +44 -0
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/versuch.d.ts +179 -0
- package/dist/src/versuch.js +250 -0
- package/dist/src/versuch.js.map +1 -0
- package/glama.json +1 -1
- package/package.json +1 -1
- 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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"}
|
package/glama.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cachly-dev/mcp-server",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.126",
|
|
4
4
|
"mcpName": "io.github.cachly-dev/mcp-server",
|
|
5
5
|
"description": "Your AI forgets everything between sessions. cachly fixes that permanently: 122 MCP tools for Claude Code, Cursor, Copilot & Windsurf. Learns from every bug fix, commit and CI run. Arrives pre-briefed. Free tier forever, EU servers, GDPR.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
"url": "https://github.com/cachly-dev/cachly-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.10.
|
|
9
|
+
"version": "0.10.126",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"name": "@cachly-dev/mcp-server",
|
|
14
14
|
"identifier": "@cachly-dev/mcp-server",
|
|
15
|
-
"version": "0.10.
|
|
15
|
+
"version": "0.10.126",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|