@cachly-dev/mcp-server 0.10.128 → 0.10.129

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.
@@ -28,6 +28,18 @@
28
28
  * Ueber stdout laeuft das MCP-Protokoll. Eine Zeile Text dort ist kein
29
29
  * Hinweis, sondern ein Verbindungsabbruch.
30
30
  */
31
+ /**
32
+ * Aus einem geworfenen Wert einen lesbaren Grund machen.
33
+ *
34
+ * JavaScript laesst zu, dass ALLES geworfen wird — ein Error, ein String, ein
35
+ * undefined. `String(e)` auf einem leeren Objekt ergibt "[object Object]", und
36
+ * das ist als Ursachenangabe wertlos.
37
+ *
38
+ * Gekuerzt auf 200 Zeichen: der Grund gehoert in EINE Protokollzeile. Manche
39
+ * Anbieter antworten mit einer ganzen HTML-Seite, und ein Protokoll, das
40
+ * niemand mehr liest, ist wieder Stille.
41
+ */
42
+ export declare function fehlerText(e: unknown): string;
31
43
  /**
32
44
  * Meldet einen Aussetzer genau einmal je Grund und Prozess.
33
45
  *
@@ -65,3 +77,80 @@ export declare const OHNE_DIENST = "bedeutung-ohne-dienst";
65
77
  * Namen fuehrt, sucht spaeter an der falschen Stelle.
66
78
  */
67
79
  export declare const OHNE_FRAGEVEKTOR = "bedeutung-ohne-fragevektor";
80
+ /**
81
+ * Der Auffang-catch am Ende des Bedeutungspfads.
82
+ *
83
+ * Eigener Grund, weil er etwas anderes bedeutet als die drei darueber: die
84
+ * sind BENANNTE Ausstiege ("kein Dienst", "keine Vektoren", "Frage nicht
85
+ * einbettbar") — hier ist etwas Unerwartetes passiert. Wer beides unter einem
86
+ * Namen fuehrt, sieht nicht, dass er einen Fehler hat statt einer Luecke.
87
+ */
88
+ export declare const SINNPFAD_ABBRUCH = "bedeutung-abbruch";
89
+ /**
90
+ * Die Einbettung BEIM SCHREIBEN einer Lektion ist fehlgeschlagen.
91
+ *
92
+ * ── Warum dieser Grund am 22.08.2026 dazukam ────────────────────────────────
93
+ *
94
+ * Alle Gruende darueber betreffen das SUCHEN. Das Schreiben hatte keinen: in
95
+ * handlers/brain.ts standen drei leere `catch { }` um die drei Einbettungen
96
+ * (Volltext, Themenname, Eingaenge). Sie sind mit Absicht fehlertolerant —
97
+ * eine Lektion muss auch ohne Netz gespeichert werden koennen — aber sie
98
+ * warfen den GRUND weg.
99
+ *
100
+ * Gemessen am 22.08.2026 ueber den Admin-Endpunkt: von 50 gezogenen Instanzen
101
+ * hatten drei Lektionen (10, 2 und 46) und NULL Vektoren. Warum, konnte
102
+ * niemand sagen — kein Protokoll, keine Zahl, nichts. Der Wachhund meldete
103
+ * seit zwei Tagen "Bedeutungsabgleich AUS", ohne dass eine Ursache
104
+ * feststellbar war.
105
+ *
106
+ * Die Fehlerklasse ist bekannt: "Stille wird als gruen gebucht". Der
107
+ * Unterschied hier ist, dass nicht einmal der Ausfall still war, sondern nur
108
+ * sein Grund. Ein Alarm ohne Ursache ist ein Alarm, den man abschaltet.
109
+ *
110
+ * Die Lektion wird weiterhin gespeichert. Es aendert sich NUR, dass der Grund
111
+ * einmal je Prozess im Protokoll steht.
112
+ */
113
+ export declare const OHNE_SCHREIBVEKTOR = "einbettung-beim-schreiben";
114
+ /** Der Versuch ist eingeschaltet, laeuft aber ohne Wirkung (siehe versuch.ts). */
115
+ export declare const VERSUCH_LEER = "versuch-laeuft-leer";
116
+ /** Schluessel-Vorsatz der Vermerke. */
117
+ export declare const AUSSETZER_VORSATZ = "cachly:aussetzer:";
118
+ /** 30 Tage. Wird bei jedem Vermerk erneuert. */
119
+ export declare const AUSSETZER_TTL_SEKUNDEN: number;
120
+ /** Was ein Vermerk enthaelt. */
121
+ export interface Aussetzervermerk {
122
+ grund: string;
123
+ anzahl: number;
124
+ zuletzt: string;
125
+ text: string;
126
+ }
127
+ /** So viel wie ein Vermerk braucht, ohne den Speicher zu belasten. */
128
+ interface VermerkSpeicher {
129
+ hincrby(key: string, field: string, increment: number): Promise<number>;
130
+ hset(key: string, values: Record<string, string>): Promise<unknown>;
131
+ expire(key: string, seconds: number): Promise<unknown>;
132
+ keys(pattern: string): Promise<string[]>;
133
+ hgetall(key: string): Promise<Record<string, string>>;
134
+ }
135
+ /**
136
+ * Meldet den Aussetzer (einmal je Prozess) UND vermerkt ihn dauerhaft.
137
+ *
138
+ * Der Vermerk darf die Anfrage NIE zum Scheitern bringen: wer eine Suche
139
+ * abbricht, weil er den Ausfall nicht aufschreiben konnte, hat aus einer
140
+ * Verschlechterung einen Ausfall gemacht. Scheitert das Schreiben, bleibt es
141
+ * bei der stderr-Zeile — gemeldet ueber meldeEinmal, damit auch dieser
142
+ * Rueckfall nicht still ist.
143
+ *
144
+ * @returns true, wenn die stderr-Meldung wirklich hinausging
145
+ */
146
+ export declare function meldeUndVermerke(redis: VermerkSpeicher | null | undefined, grund: string, text: string): Promise<boolean>;
147
+ /**
148
+ * Alle Vermerke der letzten 30 Tage, der haeufigste zuerst.
149
+ *
150
+ * Fuer `brain_doctor`. Ein Fehler beim Lesen liefert eine leere Liste — aber
151
+ * NICHT still: der Aufrufer bekommt `null` und kann "nicht gemessen" von
152
+ * "nichts gefunden" unterscheiden. Genau dieser Unterschied fehlte an allen
153
+ * Stellen, aus denen diese Datei entstanden ist.
154
+ */
155
+ export declare function leseVermerke(redis: VermerkSpeicher | null | undefined): Promise<Aussetzervermerk[] | null>;
156
+ export {};
@@ -30,6 +30,34 @@
30
30
  */
31
31
  /** Welche Gruende schon gemeldet wurden. Je Prozess, nicht je Anfrage. */
32
32
  const gemeldet = new Set();
33
+ /**
34
+ * Aus einem geworfenen Wert einen lesbaren Grund machen.
35
+ *
36
+ * JavaScript laesst zu, dass ALLES geworfen wird — ein Error, ein String, ein
37
+ * undefined. `String(e)` auf einem leeren Objekt ergibt "[object Object]", und
38
+ * das ist als Ursachenangabe wertlos.
39
+ *
40
+ * Gekuerzt auf 200 Zeichen: der Grund gehoert in EINE Protokollzeile. Manche
41
+ * Anbieter antworten mit einer ganzen HTML-Seite, und ein Protokoll, das
42
+ * niemand mehr liest, ist wieder Stille.
43
+ */
44
+ export function fehlerText(e) {
45
+ if (e instanceof Error)
46
+ return (e.message || e.name || 'Error').slice(0, 200);
47
+ if (typeof e === 'string')
48
+ return e.slice(0, 200) || 'leerer Text geworfen';
49
+ if (e === undefined)
50
+ return 'undefined geworfen (kein Grund mitgegeben)';
51
+ if (e === null)
52
+ return 'null geworfen (kein Grund mitgegeben)';
53
+ try {
54
+ const j = JSON.stringify(e);
55
+ return j && j !== '{}' ? j.slice(0, 200) : `${typeof e} ohne Inhalt geworfen`;
56
+ }
57
+ catch {
58
+ return `${typeof e} geworfen, nicht darstellbar`;
59
+ }
60
+ }
33
61
  /**
34
62
  * Meldet einen Aussetzer genau einmal je Grund und Prozess.
35
63
  *
@@ -79,4 +107,119 @@ export const OHNE_DIENST = 'bedeutung-ohne-dienst';
79
107
  * Namen fuehrt, sucht spaeter an der falschen Stelle.
80
108
  */
81
109
  export const OHNE_FRAGEVEKTOR = 'bedeutung-ohne-fragevektor';
110
+ /**
111
+ * Der Auffang-catch am Ende des Bedeutungspfads.
112
+ *
113
+ * Eigener Grund, weil er etwas anderes bedeutet als die drei darueber: die
114
+ * sind BENANNTE Ausstiege ("kein Dienst", "keine Vektoren", "Frage nicht
115
+ * einbettbar") — hier ist etwas Unerwartetes passiert. Wer beides unter einem
116
+ * Namen fuehrt, sieht nicht, dass er einen Fehler hat statt einer Luecke.
117
+ */
118
+ export const SINNPFAD_ABBRUCH = 'bedeutung-abbruch';
119
+ /**
120
+ * Die Einbettung BEIM SCHREIBEN einer Lektion ist fehlgeschlagen.
121
+ *
122
+ * ── Warum dieser Grund am 22.08.2026 dazukam ────────────────────────────────
123
+ *
124
+ * Alle Gruende darueber betreffen das SUCHEN. Das Schreiben hatte keinen: in
125
+ * handlers/brain.ts standen drei leere `catch { }` um die drei Einbettungen
126
+ * (Volltext, Themenname, Eingaenge). Sie sind mit Absicht fehlertolerant —
127
+ * eine Lektion muss auch ohne Netz gespeichert werden koennen — aber sie
128
+ * warfen den GRUND weg.
129
+ *
130
+ * Gemessen am 22.08.2026 ueber den Admin-Endpunkt: von 50 gezogenen Instanzen
131
+ * hatten drei Lektionen (10, 2 und 46) und NULL Vektoren. Warum, konnte
132
+ * niemand sagen — kein Protokoll, keine Zahl, nichts. Der Wachhund meldete
133
+ * seit zwei Tagen "Bedeutungsabgleich AUS", ohne dass eine Ursache
134
+ * feststellbar war.
135
+ *
136
+ * Die Fehlerklasse ist bekannt: "Stille wird als gruen gebucht". Der
137
+ * Unterschied hier ist, dass nicht einmal der Ausfall still war, sondern nur
138
+ * sein Grund. Ein Alarm ohne Ursache ist ein Alarm, den man abschaltet.
139
+ *
140
+ * Die Lektion wird weiterhin gespeichert. Es aendert sich NUR, dass der Grund
141
+ * einmal je Prozess im Protokoll steht.
142
+ */
143
+ export const OHNE_SCHREIBVEKTOR = 'einbettung-beim-schreiben';
144
+ /** Der Versuch ist eingeschaltet, laeuft aber ohne Wirkung (siehe versuch.ts). */
145
+ export const VERSUCH_LEER = 'versuch-laeuft-leer';
146
+ // ── Der Vermerk, der einen Neustart ueberlebt ───────────────────────────────
147
+ //
148
+ // meldeEinmal schreibt nach stderr und merkt sich den Grund im
149
+ // Arbeitsspeicher. Beides ist nach einem Neustart weg — bei jedem Deploy,
150
+ // jedem Absturz, jeder Skalierung. Gemessen am 20.08.2026 (Karte
151
+ // opupbt3l9wcq): eine Fehlkonfiguration konnte so WOCHENLANG bestehen, ohne
152
+ // dass jemand sie sah, weil "keine Daten" und "noch nicht genug Daten" von
153
+ // aussen gleich aussehen.
154
+ //
155
+ // Deshalb zusaetzlich ein Vermerk in Valkey: Zaehler, letzter Zeitpunkt,
156
+ // letzter Text. `brain_doctor` liest ihn, also braucht niemand ein Protokoll
157
+ // zu durchsuchen.
158
+ //
159
+ // Die Lebensdauer wird bei JEDEM Vermerk erneuert. Damit heisst "vorhanden"
160
+ // immer "in den letzten 30 Tagen passiert" — ein Aussetzer von vor einem
161
+ // halben Jahr steht nicht mehr im Weg, und ein laufender verschwindet nie.
162
+ /** Schluessel-Vorsatz der Vermerke. */
163
+ export const AUSSETZER_VORSATZ = 'cachly:aussetzer:';
164
+ /** 30 Tage. Wird bei jedem Vermerk erneuert. */
165
+ export const AUSSETZER_TTL_SEKUNDEN = 30 * 24 * 60 * 60;
166
+ /**
167
+ * Meldet den Aussetzer (einmal je Prozess) UND vermerkt ihn dauerhaft.
168
+ *
169
+ * Der Vermerk darf die Anfrage NIE zum Scheitern bringen: wer eine Suche
170
+ * abbricht, weil er den Ausfall nicht aufschreiben konnte, hat aus einer
171
+ * Verschlechterung einen Ausfall gemacht. Scheitert das Schreiben, bleibt es
172
+ * bei der stderr-Zeile — gemeldet ueber meldeEinmal, damit auch dieser
173
+ * Rueckfall nicht still ist.
174
+ *
175
+ * @returns true, wenn die stderr-Meldung wirklich hinausging
176
+ */
177
+ export async function meldeUndVermerke(redis, grund, text) {
178
+ const gemeldetJetzt = meldeEinmal(grund, text);
179
+ if (!redis)
180
+ return gemeldetJetzt;
181
+ try {
182
+ const key = `${AUSSETZER_VORSATZ}${grund}`;
183
+ await redis.hincrby(key, 'anzahl', 1);
184
+ await redis.hset(key, { zuletzt: new Date().toISOString(), text: text.slice(0, 500) });
185
+ await redis.expire(key, AUSSETZER_TTL_SEKUNDEN);
186
+ }
187
+ catch (e) {
188
+ meldeEinmal(`${grund}-vermerk`, `der Aussetzer liess sich nicht vermerken (${e instanceof Error ? e.message : String(e)}) `
189
+ + '— er steht nur in diesem Protokoll und ist nach einem Neustart weg');
190
+ }
191
+ return gemeldetJetzt;
192
+ }
193
+ /**
194
+ * Alle Vermerke der letzten 30 Tage, der haeufigste zuerst.
195
+ *
196
+ * Fuer `brain_doctor`. Ein Fehler beim Lesen liefert eine leere Liste — aber
197
+ * NICHT still: der Aufrufer bekommt `null` und kann "nicht gemessen" von
198
+ * "nichts gefunden" unterscheiden. Genau dieser Unterschied fehlte an allen
199
+ * Stellen, aus denen diese Datei entstanden ist.
200
+ */
201
+ export async function leseVermerke(redis) {
202
+ if (!redis)
203
+ return null;
204
+ try {
205
+ const keys = await redis.keys(`${AUSSETZER_VORSATZ}*`);
206
+ const vermerke = [];
207
+ for (const key of keys) {
208
+ const h = await redis.hgetall(key);
209
+ if (!h || !h.anzahl)
210
+ continue;
211
+ vermerke.push({
212
+ grund: key.slice(AUSSETZER_VORSATZ.length),
213
+ anzahl: Number(h.anzahl) || 0,
214
+ zuletzt: h.zuletzt ?? '',
215
+ text: h.text ?? '',
216
+ });
217
+ }
218
+ vermerke.sort((a, b) => b.anzahl - a.anzahl || a.grund.localeCompare(b.grund));
219
+ return vermerke;
220
+ }
221
+ catch {
222
+ return null;
223
+ }
224
+ }
82
225
  //# sourceMappingURL=aussetzer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"aussetzer.js","sourceRoot":"","sources":["../../src/aussetzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,0EAA0E;AAC1E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,IAAY;IACrD,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpB,OAAO,CAAC,KAAK,CAAC,eAAe,KAAK,KAAK,IAAI,EAAE,CAAC,CAAC;IAC/C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;IACnC,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAEvD,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,4BAA4B,CAAC"}
1
+ {"version":3,"file":"aussetzer.js","sourceRoot":"","sources":["../../src/aussetzer.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.128",
3
- "commit": "0646aac",
4
- "gebautAm": "2026-08-20T21:20:33.284Z"
2
+ "version": "0.10.129",
3
+ "commit": "2df4e88",
4
+ "gebautAm": "2026-08-22T10:34:37.954Z"
5
5
  }
@@ -105,6 +105,30 @@ export declare class Eingangsbestand {
105
105
  * Lektionen mit vielen Türen bestrafen — gemessen und belegt am 20.08.
106
106
  */
107
107
  besteNaehe(frage: number[], topic: string): number;
108
+ /**
109
+ * Die MITTLERE Nähe über alle Eingänge einer Lektion. -2, wenn sie keine hat.
110
+ *
111
+ * Sie ersetzt `besteNaehe` nicht, sie steht daneben. Am 21.08.2026 wurde
112
+ * "Mittelwert STATT Maximum" gemessen und verworfen — er verlor auf der
113
+ * Findequote@3 (55 und 62 Prozent gegen 58 und 63).
114
+ *
115
+ * Dieselbe Messung zeigte aber, dass die beiden ENTGEGENGESETZT ziehen: das
116
+ * Maximum gewinnt auf @3, der Mittelwert auf Platz 1, gleichgerichtet auf
117
+ * zwei unabhängigen Fragensätzen. Sie messen Verschiedenes.
118
+ *
119
+ * Maximum "gibt es EINE Tür, die genau auf diese Frage passt?"
120
+ * Mittelwert "passt die Lektion als GANZE zu dieser Frage?"
121
+ *
122
+ * Das Maximum allein belohnt eine Lektion, die zufällig eine gut passende
123
+ * Tür trägt und sonst am Thema vorbeigeht. Der Mittelwert allein bestraft
124
+ * eine breit aufgestellte Lektion, die nur mit einer Tür trifft. Nebeneinander
125
+ * heben sich die beiden Fehler teilweise auf.
126
+ *
127
+ * Ohne Schwelle, anders als das Maximum: ein Mittelwert liegt naturgemäß
128
+ * niedriger, und die für das Maximum abgetastete 0,5 würde ihn fast überall
129
+ * auf "kein Wert" drücken — das Merkmal wäre still abgeschaltet.
130
+ */
131
+ mittelNaehe(frage: number[], topic: string): number;
108
132
  /** Die ähnlichsten Lektionen, je Lektion ihr bester Eingang. */
109
133
  aehnlichste(frage: number[], anzahl: number): Array<{
110
134
  topic: string;
@@ -200,6 +200,38 @@ export class Eingangsbestand {
200
200
  }
201
201
  return best;
202
202
  }
203
+ /**
204
+ * Die MITTLERE Nähe über alle Eingänge einer Lektion. -2, wenn sie keine hat.
205
+ *
206
+ * Sie ersetzt `besteNaehe` nicht, sie steht daneben. Am 21.08.2026 wurde
207
+ * "Mittelwert STATT Maximum" gemessen und verworfen — er verlor auf der
208
+ * Findequote@3 (55 und 62 Prozent gegen 58 und 63).
209
+ *
210
+ * Dieselbe Messung zeigte aber, dass die beiden ENTGEGENGESETZT ziehen: das
211
+ * Maximum gewinnt auf @3, der Mittelwert auf Platz 1, gleichgerichtet auf
212
+ * zwei unabhängigen Fragensätzen. Sie messen Verschiedenes.
213
+ *
214
+ * Maximum "gibt es EINE Tür, die genau auf diese Frage passt?"
215
+ * Mittelwert "passt die Lektion als GANZE zu dieser Frage?"
216
+ *
217
+ * Das Maximum allein belohnt eine Lektion, die zufällig eine gut passende
218
+ * Tür trägt und sonst am Thema vorbeigeht. Der Mittelwert allein bestraft
219
+ * eine breit aufgestellte Lektion, die nur mit einer Tür trifft. Nebeneinander
220
+ * heben sich die beiden Fehler teilweise auf.
221
+ *
222
+ * Ohne Schwelle, anders als das Maximum: ein Mittelwert liegt naturgemäß
223
+ * niedriger, und die für das Maximum abgetastete 0,5 würde ihn fast überall
224
+ * auf "kein Wert" drücken — das Merkmal wäre still abgeschaltet.
225
+ */
226
+ mittelNaehe(frage, topic) {
227
+ const vs = this.eingaenge.get(topic);
228
+ if (!vs?.length)
229
+ return -2;
230
+ let summe = 0;
231
+ for (const v of vs)
232
+ summe += kosinus(frage, v);
233
+ return summe / vs.length;
234
+ }
203
235
  /** Die ähnlichsten Lektionen, je Lektion ihr bester Eingang. */
204
236
  aehnlichste(frage, anzahl) {
205
237
  const aus = [];
@@ -1 +1 @@
1
- {"version":3,"file":"eingaenge.js","sourceRoot":"","sources":["../../src/eingaenge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAErD,uEAAuE;AACvE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,+CAA+C,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACxG,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,UAAU,GAAG,oBAAoB;IAC5E,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,CAAC,CAAqB,EAAQ,EAAE;QAC3C,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACjF,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,GAAG;YAAE,OAAO;QAC3C,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;YAAE,OAAO;QACjE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,0CAA0C,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,IAAI,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3D,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1E,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CACxB,oJAAoJ,CACrJ;QAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEd,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1E,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CACxB,iJAAiJ,CAClJ;QAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEd,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAClC,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,OAAgC;IAC3D,MAAM,CAAC,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;AAChE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAY,EACZ,KAAa,EACb,OAAgC,EAChC,SAAqD;IAErD,MAAM,KAAK,GAAG,cAAc,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,GAAG,eAAe,GAAG,KAAK,EAAE,CAAC;IAEhD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAElC,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,eAAe;IAIG;IAHrB,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC1C,OAAO,GAAG,CAAC,CAAC;IAEpB,YAA6B,YAAY,MAAM;QAAlB,cAAS,GAAT,SAAS,CAAS;IAAG,CAAC;IAEnD,IAAI,OAAO,KAAa,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAErD,IAAI,eAAe;QACjB,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE;YAAE,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;QACzD,OAAO,CAAC,CAAC;IACX,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,YAAY,CAChB,KAAY,EACZ,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,EAClB,WAA8B,CAAC,eAAe,EAAE,qBAAqB,CAAC;QAEtE,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS;YAAE,OAAO;QAE7E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC7C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,UAAU,GAAa,EAAE,CAAC;YAChC,IAAI,MAAM,GAAG,GAAG,CAAC;YACjB,GAAG,CAAC;gBACF,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;gBACxF,MAAM,GAAG,IAAI,CAAC;gBACd,UAAU,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;YAC/B,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;YAEzB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACpC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBACtC,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBACnC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;oBACxB,IAAI,CAAC;wBAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACpB,CAAC;gBACD,IAAI,EAAE,CAAC,MAAM;oBAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,KAAe,EAAE,KAAa;QACvC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,EAAE,MAAM;YAAE,OAAO,CAAC,CAAC,CAAC;QAC3B,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;QACd,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,GAAG,IAAI;gBAAE,IAAI,GAAG,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gEAAgE;IAChE,WAAW,CAAC,KAAe,EAAE,MAAc;QACzC,MAAM,GAAG,GAA4C,EAAE,CAAC;QACxD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;YAC1C,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACtC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAgBnC,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,QAAgB;IAClE,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAO,oCAAoC;IAC7E,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACjC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AAC9D,CAAC"}
1
+ {"version":3,"file":"eingaenge.js","sourceRoot":"","sources":["../../src/eingaenge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Verklebte Felder trennen, BEVOR eine Lektion geschrieben wird.
3
+ *
4
+ * ── Der Anlass, gemessen am 22.08.2026 ──────────────────────────────────────
5
+ *
6
+ * `ohneMarkierungen()` in eingaenge.ts filtert beim Einbetten die Zeichenkette
7
+ * `</what_worked>\n<what_failed>` aus dem Text. Der Kommentar dort sagt, viele
8
+ * Lektionen enthielten das "woertlich". Was er NICHT sagt: wo es herkommt.
9
+ *
10
+ * Es kommt beim SCHREIBEN herein. Wer `learn_from_attempts` aufruft und ein
11
+ * Feld mit dem falschen Tag schliesst, uebergibt den gesamten Rest — inklusive
12
+ * der Marke des naechsten Feldes — als Inhalt von `what_worked`. Das Ergebnis
13
+ * sieht aus wie eine gefuellte Lektion, hat aber ein leeres `what_failed` und
14
+ * einen Fliesstext mit Steuerzeichen darin.
15
+ *
16
+ * Belegt an derselben Lektion zweimal hintereinander, beide Male unbemerkt:
17
+ * die Rueckmeldung des Werkzeugs zeigte den verklebten Text an, ohne dass
18
+ * irgendetwas rot wurde.
19
+ *
20
+ * ── Warum hier und nicht beim Lesen ─────────────────────────────────────────
21
+ *
22
+ * Beim Lesen wird gefiltert, damit der Vektor nicht auf Feldnamen zeigt. Das
23
+ * ist richtig und bleibt. Es heilt aber nur die Einbettung — im Bestand steht
24
+ * der Text weiter falsch, `what_failed` bleibt leer, und das Briefing zeigt
25
+ * Steuerzeichen. Zwei Stellen fuer dasselbe Problem waeren die uebliche
26
+ * Fehlerklasse dieses Hauses: eine Information an zwei Orten gepflegt.
27
+ *
28
+ * Deshalb wird HIER repariert, an der einzigen Stelle, an der der Fehler
29
+ * entsteht.
30
+ *
31
+ * ── Was es ausdruecklich NICHT tut ──────────────────────────────────────────
32
+ *
33
+ * Es fasst Text NICHT an, der die Feldnamen bloss ERWAEHNT. "der what_failed-
34
+ * Text stand in what_worked" ist ein voellig normaler Satz in einer Lektion
35
+ * ueber genau diesen Fehler. Getrennt wird nur an einer echten Marke, also an
36
+ * spitzen Klammern.
37
+ *
38
+ * Und es wirft nie Text weg. Findet es keinen Platz fuer ein Stueck, bleibt
39
+ * es stehen, wo es war — nur ohne die Marke.
40
+ */
41
+ /** Die Felder einer Lektion, die verkleben koennen. */
42
+ export interface Lektionsfelder {
43
+ what_worked: string;
44
+ what_failed?: string;
45
+ context?: string;
46
+ }
47
+ /** Ist in diesem Text ueberhaupt eine Marke? Billig, fuer den Normalfall. */
48
+ export declare function hatFeldmarke(text: string | undefined): boolean;
49
+ /**
50
+ * Trennt verklebte Felder auf und gibt sie einzeln zurueck.
51
+ *
52
+ * Der Text VOR der ersten Marke bleibt in dem Feld, in dem er stand. Jedes
53
+ * Stueck dahinter geht in das Feld, dessen Marke davorstand — aber nur, wenn
54
+ * dieses Feld noch leer ist. Ein bereits gefuelltes Feld wird nie
55
+ * ueberschrieben; das Stueck haengt sich dann hinten an, damit nichts
56
+ * verlorengeht.
57
+ */
58
+ export declare function repariereFelder(f: Lektionsfelder): Lektionsfelder;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Verklebte Felder trennen, BEVOR eine Lektion geschrieben wird.
3
+ *
4
+ * ── Der Anlass, gemessen am 22.08.2026 ──────────────────────────────────────
5
+ *
6
+ * `ohneMarkierungen()` in eingaenge.ts filtert beim Einbetten die Zeichenkette
7
+ * `</what_worked>\n<what_failed>` aus dem Text. Der Kommentar dort sagt, viele
8
+ * Lektionen enthielten das "woertlich". Was er NICHT sagt: wo es herkommt.
9
+ *
10
+ * Es kommt beim SCHREIBEN herein. Wer `learn_from_attempts` aufruft und ein
11
+ * Feld mit dem falschen Tag schliesst, uebergibt den gesamten Rest — inklusive
12
+ * der Marke des naechsten Feldes — als Inhalt von `what_worked`. Das Ergebnis
13
+ * sieht aus wie eine gefuellte Lektion, hat aber ein leeres `what_failed` und
14
+ * einen Fliesstext mit Steuerzeichen darin.
15
+ *
16
+ * Belegt an derselben Lektion zweimal hintereinander, beide Male unbemerkt:
17
+ * die Rueckmeldung des Werkzeugs zeigte den verklebten Text an, ohne dass
18
+ * irgendetwas rot wurde.
19
+ *
20
+ * ── Warum hier und nicht beim Lesen ─────────────────────────────────────────
21
+ *
22
+ * Beim Lesen wird gefiltert, damit der Vektor nicht auf Feldnamen zeigt. Das
23
+ * ist richtig und bleibt. Es heilt aber nur die Einbettung — im Bestand steht
24
+ * der Text weiter falsch, `what_failed` bleibt leer, und das Briefing zeigt
25
+ * Steuerzeichen. Zwei Stellen fuer dasselbe Problem waeren die uebliche
26
+ * Fehlerklasse dieses Hauses: eine Information an zwei Orten gepflegt.
27
+ *
28
+ * Deshalb wird HIER repariert, an der einzigen Stelle, an der der Fehler
29
+ * entsteht.
30
+ *
31
+ * ── Was es ausdruecklich NICHT tut ──────────────────────────────────────────
32
+ *
33
+ * Es fasst Text NICHT an, der die Feldnamen bloss ERWAEHNT. "der what_failed-
34
+ * Text stand in what_worked" ist ein voellig normaler Satz in einer Lektion
35
+ * ueber genau diesen Fehler. Getrennt wird nur an einer echten Marke, also an
36
+ * spitzen Klammern.
37
+ *
38
+ * Und es wirft nie Text weg. Findet es keinen Platz fuer ein Stueck, bleibt
39
+ * es stehen, wo es war — nur ohne die Marke.
40
+ */
41
+ /** Nur diese drei. `topic` und `outcome` sind kurz und kleben nicht. */
42
+ const FELDNAMEN = ['what_worked', 'what_failed', 'context'];
43
+ /**
44
+ * Eine Marke ist ein Feldname in spitzen Klammern — in allen Formen, die
45
+ * tatsaechlich vorkommen:
46
+ *
47
+ * </what_worked> schliessend
48
+ * <what_failed> oeffnend
49
+ * <parameter name="what_failed"> die Form, die den Fehler ausloest
50
+ *
51
+ * Ohne spitze Klammern ist es Fliesstext und bleibt unberuehrt.
52
+ */
53
+ const MARKE = new RegExp(String.raw `<\/?(?:parameter\s+name\s*=\s*["']?)?(${FELDNAMEN.join('|')})["']?\s*\/?>`, 'gi');
54
+ /** Ist in diesem Text ueberhaupt eine Marke? Billig, fuer den Normalfall. */
55
+ export function hatFeldmarke(text) {
56
+ if (!text)
57
+ return false;
58
+ MARKE.lastIndex = 0;
59
+ return MARKE.test(text);
60
+ }
61
+ /**
62
+ * Trennt verklebte Felder auf und gibt sie einzeln zurueck.
63
+ *
64
+ * Der Text VOR der ersten Marke bleibt in dem Feld, in dem er stand. Jedes
65
+ * Stueck dahinter geht in das Feld, dessen Marke davorstand — aber nur, wenn
66
+ * dieses Feld noch leer ist. Ein bereits gefuelltes Feld wird nie
67
+ * ueberschrieben; das Stueck haengt sich dann hinten an, damit nichts
68
+ * verlorengeht.
69
+ */
70
+ export function repariereFelder(f) {
71
+ if (!hatFeldmarke(f.what_worked) && !hatFeldmarke(f.what_failed) && !hatFeldmarke(f.context)) {
72
+ return f;
73
+ }
74
+ const aus = {
75
+ what_worked: '',
76
+ what_failed: f.what_failed ?? '',
77
+ context: f.context ?? '',
78
+ };
79
+ // Nur what_worked wird zerlegt — dort landet der Rest, weil es das erste
80
+ // lange Feld ist. Die anderen beiden werden nur von Marken befreit.
81
+ const text = f.what_worked ?? '';
82
+ const stuecke = [];
83
+ let letzterIndex = 0;
84
+ let aktuellesZiel = 'what_worked';
85
+ MARKE.lastIndex = 0;
86
+ for (let m = MARKE.exec(text); m !== null; m = MARKE.exec(text)) {
87
+ stuecke.push({ ziel: aktuellesZiel, text: text.slice(letzterIndex, m.index) });
88
+ const genannt = m[1].toLowerCase();
89
+ // Eine SCHLIESSENDE Marke sagt nur "hier endet etwas" und nennt kein
90
+ // neues Ziel. Der Text danach gehoert dem naechsten genannten Feld —
91
+ // bis dahin bleibt er beim bisherigen.
92
+ aktuellesZiel = m[0].startsWith('</') ? aktuellesZiel : genannt;
93
+ letzterIndex = m.index + m[0].length;
94
+ }
95
+ stuecke.push({ ziel: aktuellesZiel, text: text.slice(letzterIndex) });
96
+ for (const s of stuecke) {
97
+ const t = s.text.trim();
98
+ if (!t)
99
+ continue;
100
+ if (aus[s.ziel])
101
+ aus[s.ziel] = `${aus[s.ziel]}\n\n${t}`;
102
+ else
103
+ aus[s.ziel] = t;
104
+ }
105
+ // Reste in den anderen beiden Feldern: Marke raus, Text behalten.
106
+ const entmarken = (s) => s.replace(MARKE, ' ').replace(/[ \t]{2,}/g, ' ').trim();
107
+ return {
108
+ what_worked: entmarken(aus.what_worked),
109
+ what_failed: aus.what_failed ? entmarken(aus.what_failed) : f.what_failed,
110
+ context: aus.context ? entmarken(aus.context) : f.context,
111
+ };
112
+ }
113
+ //# sourceMappingURL=feldreparatur.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feldreparatur.js","sourceRoot":"","sources":["../../src/feldreparatur.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { ckgSlug } from '../ckg.js';
2
2
  import { safeJsonParse } from '../utils.js';
3
- import { ersparteMinuten } from '../wertbeitrag.js';
3
+ import { ersparteMinuten, zaehltAlsErsparnis } from '../wertbeitrag.js';
4
4
  export const ADVANCED_TOOL_NAMES = new Set([
5
5
  'memory_consolidate', 'brain_diff', 'causal_trace', 'knowledge_decay', 'autopilot',
6
6
  ]);
@@ -379,8 +379,8 @@ export async function handleAdvancedTool(name, args, getConnection, _apiFetch) {
379
379
  lines.push(`\`\`\``);
380
380
  lines.push('');
381
381
  // Track estimated time saved — causal_trace finding a known fix is worth 1-4h.
382
- // Starter-Lektionen zaehlen 0 — siehe wertbeitrag.ts.
383
- const savedMins = ersparteMinuten(topSolution, 'trace');
382
+ // Nur beim ERSTEN Mal, und nie fuer den Startvorrat — wertbeitrag.ts.
383
+ const savedMins = zaehltAlsErsparnis(topSolution) ? ersparteMinuten(topSolution, 'trace') : 0;
384
384
  if (savedMins > 0) {
385
385
  redis.incrbyfloat(`cachly:stats:time_saved_mins:${instance_id}`, savedMins).catch(() => { });
386
386
  }